Files
yakpanel-core/class/process_task.py

742 lines
27 KiB
Python
Raw Normal View History

2026-04-07 02:04:22 +05:30
#coding: utf-8
# +-------------------------------------------------------------------
# | YakPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@yakpanel.com>
# +-------------------------------------------------------------------
#--------------------------------
# 进程监控模块
#--------------------------------
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<bytes> 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<bytes> 目标地址
@param src<bytes> 源地址
@param pack_size<int> 数据包长度
@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<string> 16进制的IP地址:16进程端口
@return tuple(ip<str>,port<int>) 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<bool> 是否强制刷新
@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<int> 端口
@return bytes
'''
return struct.pack('H',int(port))[::-1]
def get_ip_pack(self,ip):
'''
@name 将IP地址转换为字节流
@author hwliang<2021-09-13>
@param ip<str> 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<string> inode
@param force<bool> 是否强制刷新
@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<bool> 是否强制刷新
@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<str> 进程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<int> 进程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<?',self.get_expire_time()).delete()
_sql.close()
def get_top_list(sekf,process_info_list):
'''
@name 排序
@param process_info_list list
@return list
'''
process_info_list = sorted(process_info_list,key=lambda x:[x['cpu_percent'],x['disk_total'],x['net_total'],x['memory']],reverse=True)
top_num = 5
all_top = []
for p in process_info_list[:top_num]:
_line = [p['cpu_percent'],p['disk_read'],p['disk_write'],p['memory'],p['up'],p['down'],p['pid'],public.xssencode2(p['name']),public.xssencode2(p['cmdline']),public.xssencode2(p['username']),p['create_time']]
all_top.append(_line)
process_info_list = sorted(process_info_list,key=lambda x:x['cpu_percent'],reverse=True)
cpu_top = []
for p in process_info_list[:top_num]:
if not p['cpu_percent']: continue
_line = [p['cpu_percent'],p['pid'],public.xssencode2(p['name']),public.xssencode2(p['cmdline']),public.xssencode2(p['username']),p['create_time']]
cpu_top.append(_line)
process_info_list = sorted(process_info_list,key=lambda x:x['disk_total'],reverse=True)
disk_top = []
for p in process_info_list[:top_num]:
if not p['disk_total']: continue
_line = [p['disk_total'],p['disk_read'],p['disk_write'],p['pid'],public.xssencode2(p['name']),public.xssencode2(p['cmdline']),public.xssencode2(p['username']),p['create_time']]
disk_top.append(_line)
process_info_list = sorted(process_info_list,key=lambda x:x['net_total'],reverse=True)
net_top = []
for p in process_info_list[:top_num]:
if not p['net_total']: continue
_line = [p['net_total'],p['up'],p['down'],p['connect_count'],p['package_total'],p['pid'],public.xssencode2(p['name']),public.xssencode2(p['cmdline']),public.xssencode2(p['username']),p['create_time']]
net_top.append(_line)
process_info_list = sorted(process_info_list,key=lambda x:x['memory'],reverse=True)
memory_top = []
for p in process_info_list[:top_num]:
if not p['memory']: continue
_line = [p['memory'],p['pid'],public.xssencode2(p['name']),public.xssencode2(p['cmdline']),public.xssencode2(p['username']),p['create_time']]
memory_top.append(_line)
process_info_list = sorted(process_info_list,key=lambda x:x['swap'],reverse=True)
swap_top = []
for p in process_info_list[:top_num]:
if not p['swap']: continue
_line = [p['swap'],p['pid'],public.xssencode2(p['name']),public.xssencode2(p['cmdline']),public.xssencode2(p['username']),p['create_time']]
swap_top.append(_line)
return all_top,cpu_top,disk_top,net_top,memory_top,swap_top
if __name__ == '__main__':
# net = process_network_total()
# threading.Thread(target = net.start,args=()).start()
p = process_task()
while True:
p.get_monitor_list()
time.sleep(1)
print("-"*50)