#coding: utf-8 # +------------------------------------------------------------------- # | YakPanel # +------------------------------------------------------------------- # | Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved. # +------------------------------------------------------------------- # | Author: hwliang # +------------------------------------------------------------------- #-------------------------------- # 进程监控模块 #-------------------------------- from psutil import cpu_count,pids,Process,cpu_times from json import dumps import os import sys os.chdir("/www/server/panel") sys.path.insert(0,"class/") import db import time import struct import copy import threading import public from cachelib import SimpleCache class process_network_total: __pid_file = 'logs/process_network_total.pid' __inode_list = {} __net_process_list = {} __net_process_size = {} __last_stat = 0 __last_write_time = 0 __last_check_time = 0 __tip_file = 'data/is_net_task.pl' __all_tip = 'data/control.conf' def install_pcap(self): ''' @name 安装pcap模块依赖包 @author hwliang @return void ''' # 标记只安装一次 tip_file= '{}/data/install_pcap.pl'.format(public.get_panel_path()) if os.path.exists(tip_file): return if os.path.exists('/usr/bin/apt'): os.system("apt install libpcap-dev -y") elif os.path.exists('/usr/bin/dnf'): red_file = '/etc/redhat-release' if os.path.exists(red_file): f = open(red_file,'r') red_body = f.read() f.close() if red_body.find('CentOS Linux release 8.') != -1: rpm_file = '/root/libpcap-1.9.1.rpm' down_url = "wget -O {} https://node.yakpanel.com/src/libpcap-devel-1.9.1-5.el8.x86_64.rpm --no-check-certificate -T 10".format( rpm_file) if os.path.exists(rpm_file): os.system(down_url) os.system("rpm -ivh {}".format(rpm_file)) if os.path.exists(rpm_file): os.remove(rpm_file) else: os.system("dnf install libpcap-devel -y") else: os.system("dnf install libpcap-devel -y") elif os.path.exists('/usr/bin/yum'): os.system("yum install libpcap-devel -y") os.system("btpip install pypcap") # 写入标记文件 public.writeFile(tip_file, 'True') def start(self): ''' @name 启动进程网络监控 @author hwliang<2021-09-13> @return void ''' try: import pcap except ImportError: try: self.install_pcap() import pcap except ImportError: print("pypcap module install failed.") return try: p = pcap.pcap() # 监听所有网卡 p.setfilter('tcp') # 只监听TCP数据包 for p_time,p_data in p: # 检查是否停止 if p_time - self.__last_check_time > 10: if not os.path.exists(self.__tip_file) or not os.path.exists(self.__all_tip): break # 处理数据包 self.handle_packet(p_data) except: pass def handle_packet(self, pcap_data): ''' @name 处理pcap数据包 @author hwliang<2021-09-12> @param pcap_data pcap数据包 @return void ''' # 获取IP协议头 ip_header = pcap_data[14:34] # 解析src/dst地址 src_ip = ip_header[12:16] dst_ip = ip_header[16:20] # 解析sport/dport端口 src_port = pcap_data[34:36] dst_port = pcap_data[36:38] src = src_ip + b':' + src_port dst = dst_ip + b':' + dst_port # 计算数据包长度 pack_size = len(pcap_data) # 统计进程流量 self.total_net_process(dst,src,pack_size) def total_net_process(self,dst,src,pack_size): ''' @name 统计进程流量 @author hwliang<2021-09-13> @param dst 目标地址 @param src 源地址 @param pack_size 数据包长度 @return void ''' self.get_tcp_stat() direction = None mtime = time.time() if dst in self.__net_process_list: pid = self.__net_process_list[dst] direction = 'down' elif src in self.__net_process_list: pid = self.__net_process_list[src] direction = 'up' else: if mtime - self.__last_stat > 3: self.__last_stat = mtime self.get_tcp_stat(True) if dst in self.__net_process_list: pid = self.__net_process_list[dst] direction = 'down' elif src in self.__net_process_list: pid = self.__net_process_list[src] direction = 'up' if not direction: return False if not pid: return False if not pid in self.__net_process_size: self.__net_process_size[pid] = {} self.__net_process_size[pid]['down'] = 0 self.__net_process_size[pid]['up'] = 0 self.__net_process_size[pid]['up_package'] = 0 self.__net_process_size[pid]['down_package'] = 0 self.__net_process_size[pid][direction] += pack_size self.__net_process_size[pid][direction + '_package'] += 1 # 写入到文件 if mtime - self.__last_write_time > 1: self.__last_write_time = mtime self.write_net_process() def write_net_process(self): ''' @name 写入进程流量 @author hwliang<2021-09-13> @return void ''' w_file = '/dev/shm/bt_net_process' process_size = copy.deepcopy(self.__net_process_size) net_process = [] for pid in process_size.keys(): net_process.append(str(pid) + " " + str(process_size[pid]['down']) + " " + str(process_size[pid]['up']) + " " + str(process_size[pid]['down_package']) + " " + str(process_size[pid]['up_package'])) f = open(w_file,'w+',encoding='utf-8') f.write('\n'.join(net_process)) f.close() def hex_to_ip(self, hex_ip): ''' @name 将16进制的IP地址转换为字符串IP地址 @author hwliang<2021-09-13> @param hex_ip 16进制的IP地址:16进程端口 @return tuple(ip,port) IP地址,端口 ''' hex_ip,hex_port = hex_ip.split(':') ip = '.'.join([str(int(hex_ip[i:i+2], 16)) for i in range(0, len(hex_ip), 2)][::-1]) port = int(hex_port, 16) return ip,port def get_tcp_stat(self,force = False): ''' @name 获取当前TCP连接状态表 @author hwliang<2021-09-13> @param force 是否强制刷新 @return dict ''' if not force and self.__net_process_list: return self.__net_process_list self.__net_process_list = {} tcp_stat_file = '/proc/net/tcp' tcp_stat = open(tcp_stat_file, 'rb') tcp_stat_list = tcp_stat.read().decode('utf-8').split('\n') tcp_stat.close() tcp_stat_list = tcp_stat_list[1:] if force: self.get_process_inodes(force) for i in tcp_stat_list: tcp_tmp = i.split() if len(tcp_tmp) < 10: continue inode = tcp_tmp[9] if inode == '0': continue local_ip,local_port = self.hex_to_ip(tcp_tmp[1]) if local_ip == '127.0.0.1': continue remote_ip,remote_port = self.hex_to_ip(tcp_tmp[2]) if local_ip == remote_ip: continue if remote_ip == '0.0.0.0': continue pid = self.inode_to_pid(inode,force) if not pid: continue key = self.get_ip_pack(local_ip) + b':' + self.get_port_pack(local_port) self.__net_process_list[key] = pid return self.__net_process_list def get_port_pack(self,port): ''' @name 将端口转换为字节流 @author hwliang<2021-09-13> @param port 端口 @return bytes ''' return struct.pack('H',int(port))[::-1] def get_ip_pack(self,ip): ''' @name 将IP地址转换为字节流 @author hwliang<2021-09-13> @param ip IP地址 @return bytes ''' ip_arr = ip.split('.') ip_pack = b'' for i in ip_arr: ip_pack += struct.pack('B',int(i)) return ip_pack def inode_to_pid(self,inode,force = False): ''' @name 将inode转换为进程ID @author hwliang<2021-09-13> @param inode inode @param force 是否强制刷新 @return int ''' inode_list = self.get_process_inodes() if inode in inode_list: return inode_list[inode] return None def get_process_inodes(self,force = False): ''' @name 获取进程inode列表 @author hwliang<2021-09-13> @param force 是否强制刷新 @return dict ''' if not force and self.__inode_list: return self.__inode_list proc_path = '/proc' inode_list = {} for pid in os.listdir(proc_path): try: if not pid.isdigit(): continue inode_path = proc_path + '/' + pid + '/fd' for fd in os.listdir(inode_path): try: fd_file = inode_path + '/' + fd fd_link = os.readlink(fd_file) if fd_link.startswith('socket:['): inode = fd_link[8:-1] inode_list[inode] = pid except: continue except: continue self.__inode_list = inode_list return inode_list def get_process_name(self,pid): ''' @name 获取进程名称 @author hwliang<2021-09-13> @param pid 进程ID @return str ''' pid_path = '/proc/' + pid + '/comm' if not os.path.exists(pid_path): return '' pid_file = open(pid_path, 'rb') pid_name = pid_file.read().decode('utf-8').strip() pid_file.close() return pid_name def write_pid(self): ''' @name 写入进程ID到PID文件 @author hwliang<2021-09-13> @return void ''' self_pid = os.getpid() pid_file = open(self.__pid_file,'w') pid_file.write(str(self_pid)) pid_file.close() def rm_pid_file(self): ''' @name 删除进程pid文件 @author hwliang<2021-09-13> @return void ''' if os.path.exists(self.__pid_file): os.remove(self.__pid_file) class process_task: __pids = [] __last_times = {} __last_dates = {} __write_last = {} __write_dates = {} __read_last = {} __read_dates = {} __cpu_count = cpu_count() __cache = SimpleCache(5000) last_net_process = {} last_net_process_time = 0 __process_net_list = {} __process_object = {} __insert_time = 0 __last_cpu_time = 0 old_key = 'old_cpu_times' new_key = 'new_cpu_times' new_info = {} old_info = {} def __init__(self): tip_file = '{}/data/process_index.pl'.format(public.get_panel_path()) if not public.M('sqlite_master').dbfile('system').where( 'type=? AND name=?', ('table', 'process_top_list')).count(): public.ExecShell('rm -f {}'.format(tip_file)) if not os.path.isfile(tip_file): _sql = db.Sql().dbfile('system') csql = '''CREATE TABLE IF NOT EXISTS `process_top_list` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, `cpu_top` REAL, `memory_top` REAL, `swap_top` REAL, `disk_top` REAL, `net_top` REAL, `all_top` REAL, `addtime` INTEGER )''' _sql.execute(csql, ()) _sql.execute('CREATE INDEX `addtime` ON `process_top_list` (`addtime`)', ()) _sql.close() public.writeFile(tip_file,'True') def get_pids(self): ''' @name 获取pid列表 @author hwliang<2021-09-04> @return None ''' self.__pids = pids() def get_cpu_time(self): s = cpu_times() return s.user + s.system + s.nice + s.idle def get_old(self): if self.old_info: return True data = self.__cache.get(self.old_key) if not data: return False if not data: return False self.old_info = data del(data) return True # def get_cpu_percent(self,pid,cpu_times,cpu_time): # self.get_old() # percent = 0.00 # process_cpu_time = self.get_process_cpu_time(cpu_times) # if not self.old_info: self.old_info = {} # if not pid in self.old_info: # self.new_info[pid] = {} # self.new_info[pid]['cpu_time'] = process_cpu_time # return percent # percent = round(100.00 * (process_cpu_time - self.old_info[pid]['cpu_time']) / (cpu_time - self.old_info['cpu_time']),2) # self.new_info[pid] = {} # self.new_info[pid]['cpu_time'] = process_cpu_time # if percent > 0: return percent # return 0.00 def get_process_cpu_time(self,cpu_times): cpu_time = 0.00 for s in cpu_times: cpu_time += s return cpu_time def get_cpu_percent(self,pid,cpu_time_total,s_cpu_times): ''' @name 获取pid的cpu占用率 @author hwliang<2021-09-04> @param pid 进程id @param cpu_time_total 进程总cpu时间 @return 占用cpu百分比 ''' stime = time.time() if pid in self.__last_times: old_time = self.__last_times[pid] else: self.__last_times[pid] = cpu_time_total self.__last_dates[pid] = stime return 0 cpu_percent = round(100.00 * float(cpu_time_total - old_time) / (s_cpu_times - self.__last_cpu_time),2) self.__last_times[pid] = cpu_time_total self.__last_dates[pid] = stime if cpu_percent > 100: cpu_percent = 99 if cpu_percent < 0: cpu_percent = 0 return cpu_percent def get_io_write(self,pid,io_write): disk_io_write = 0 stime = time.time() if pid in self.__write_last: old_write = self.__write_last[pid] else: self.__write_last[pid] = io_write self.__write_dates[pid] = stime return disk_io_write io_end = (io_write - old_write) if io_end > 0: disk_io_write = int(io_end / (stime - self.__write_dates[pid])) self.__write_last[pid] = io_write self.__write_dates[pid] = stime if disk_io_write < 0: disk_io_write = 0 return disk_io_write def get_io_read(self,pid,io_read): disk_io_read = 0 stime = time.time() if pid in self.__read_last: old_read = self.__read_last[pid] else: self.__read_last[pid] = io_read self.__read_dates[pid] = stime return disk_io_read io_end = (io_read - old_read) if io_end > 0: disk_io_read = int(io_end / (stime - self.__read_dates[pid])) self.__read_last[pid] = io_read self.__read_dates[pid] = stime if disk_io_read < 0: disk_io_read = 0 return disk_io_read def read_file(self,filename): f = open(filename,'rb') result = f.read() f.close() return result.decode().replace("\u0000"," ").strip() def get_process_net_list(self): w_file = '/dev/shm/bt_net_process' if not os.path.exists(w_file): return self.last_net_process = self.__cache.get('net_process') self.last_net_process_time = self.__cache.get('last_net_process') net_process_body = self.read_file(w_file) if not net_process_body: return net_process = net_process_body.split('\n') for np in net_process: if not np: continue tmp = {} np_list = np.split() if len(np_list) < 5: continue tmp['pid'] = int(np_list[0]) tmp['down'] = int(np_list[1]) tmp['up'] = int(np_list[2]) tmp['down_package'] = int(np_list[3]) tmp['up_package'] = int(np_list[4]) self.__process_net_list[str(tmp['pid'])] = tmp self.__cache.set('net_process',self.__process_net_list,600) self.__cache.set('last_net_process',time.time(),600) def get_process_network(self,pid): ''' @name 获取进程网络流量 @author hwliang<2021-09-13> @param pid 进程ID @return tuple ''' if not self.__process_net_list: self.get_process_net_list() if not self.last_net_process_time: return 0,0,0,0 if not pid in self.__process_net_list.keys(): return 0,0,0,0 if not pid in self.last_net_process: return self.__process_net_list[pid]['up'],self.__process_net_list[pid]['up_package'],self.__process_net_list[pid]['down'],self.__process_net_list[pid]['down_package'] up = int((self.__process_net_list[pid]['up'] - self.last_net_process[pid]['up']) / (time.time() - self.last_net_process_time)) down = int((self.__process_net_list[pid]['down'] - self.last_net_process[pid]['down']) / (time.time() - self.last_net_process_time)) up_package = int((self.__process_net_list[pid]['up_package'] - self.last_net_process[pid]['up_package']) / (time.time() - self.last_net_process_time)) down_package = int((self.__process_net_list[pid]['down_package'] - self.last_net_process[pid]['down_package']) / (time.time() - self.last_net_process_time)) if up < 0: up = 0 if down < 0: down = 0 if up_package < 0: up_package = 0 if down_package < 0: down_package = 0 return up,up_package,down,down_package def get_process_username(self,pid): ''' @name 获取进程用户名 @param pid 进程id @return 用户名 ''' try: import pwd return pwd.getpwuid(os.stat('/proc/' + str(pid)).st_uid).pw_name except: return 'root' def get_monitor_list(self,stime = None): ''' @name 获取监控列表 @author hwliang<2021-09-04> @return list ''' self.get_pids() process_info_list = [] total_cpu_precent = 0.0 my_pid = os.getpid() if type(self.new_info) != dict: self.new_info = {} all_cpu_time = self.get_cpu_time() self.new_info['time'] = time.time() for pid in self.__pids: try: # if pid < 100: continue if pid == my_pid: continue if not pid in self.__process_object.keys(): self.__process_object[pid] = Process(pid) try: if self.__process_object[pid].status() == 'terminated': self.__process_object[pid] = Process(pid).create_time except: self.__process_object[pid] = Process(pid) p = self.__process_object[pid] process_info = {} process_info['cpu_percent'] = self.get_cpu_percent(str(pid),sum(p.cpu_times()),all_cpu_time) #self.get_cpu_percent(pid,int(sum(p.cpu_times())),self.new_info['cpu_time']) # CPU使用率 total_cpu_precent += process_info['cpu_percent'] process_info['memory'] = p.memory_info().rss # 内存占用 if not process_info['memory']: continue # ========== 新增:获取Swap占用 ========== try: # swap_used_cmd = f"awk '/VmSwap/{{print $2}}' /proc/{pid}/status" # process_info['swap']=int(public.ExecShell(swap_used_cmd)[0].strip()) process_info['swap']=p.memory_full_info().swap except: process_info['swap']=0 io_counters = p.io_counters() process_info['disk_read'] = self.get_io_read(pid,io_counters.read_bytes) # 读取磁盘字节数 process_info['disk_write'] = self.get_io_write(pid,io_counters.write_bytes) # 写入磁盘字节数 process_info['disk_total'] = process_info['disk_read'] + process_info['disk_write'] # 磁盘总读写 process_info['up'],process_info['up_package'],process_info['down'],process_info['down_package'] = self.get_process_network(str(pid)) process_info['net_total'] = process_info['up'] + process_info['down'] # 网络总流量 process_info['package_total'] = process_info['up_package'] + process_info['down_package'] # 网络总包数 if not process_info['cpu_percent'] and not process_info['disk_total'] and not process_info['net_total']: continue process_proc_comm = '/proc/{}/comm'.format(pid) process_proc_cmdline = '/proc/{}/cmdline'.format(pid) process_info['pid'] = pid process_info['name'] = self.read_file(process_proc_comm) process_info['cmdline'] = self.read_file(process_proc_cmdline) # 启动命令 process_info['create_time'] = int(p.create_time()) # 创建时间 process_info['connect_count'] = len(p.connections()) # 连接数 process_info['username'] = self.get_process_username(pid) # 进程用户名 process_info_list.append(process_info) except: continue self.__last_cpu_time = all_cpu_time self.__process_net_list.clear() self.insert_db(process_info_list,stime) # import public # for pp in sorted(process_info_list,key=lambda x:x['cpu_percent'],reverse=True): # public.print_log("name: {}, cpu_percent: {}".format(pp['name'], pp['cpu_percent'])) if total_cpu_precent > 100: total_cpu_precent = 100 return total_cpu_precent def get_expire_time(self): ''' @name 获取过期时间 @return int ''' filename = 'data/control.conf' _day = 30 if os.path.exists(filename): try: conf = self.read_file(filename) if conf: _day = int(conf) except: pass return _day * 86400 def insert_db(self,process_info_list,_time): ''' @name 插入数据库 @param process_info_list list @return bool ''' if not process_info_list: return all_top,cpu_top,disk_top,net_top,memory_top,swap_top = self.get_top_list(process_info_list) with db.Sql().dbfile('system') as _sql: #检测process_top_list表是否存在swap_top字段 field = 'cpu_top,swap_top' check_result=_sql.table('process_top_list').order("id desc").field(field).select() if type(check_result) == str: _sql.table('process_top_list').execute("ALTER TABLE 'process_top_list' ADD 'swap_top' REAL DEFAULT []",()) if not _time: _time = int(time.time()) _sql.table('process_top_list').insert({ 'all_top':dumps(all_top), 'cpu_top':dumps(cpu_top), 'disk_top':dumps(disk_top), 'net_top':dumps(net_top), 'memory_top':dumps(memory_top), 'swap_top':dumps(swap_top), 'addtime':_time }) # 删除过期数据 if self.__insert_time and _time - self.__insert_time > 3600: self.__insert_time = _time _sql.table('process_top_list').where('addtime