Skip to content

Commit 13a5419

Browse files
Correct real time CPU Utilization calculation (#173)
**What I did** Correct real time CPU Utilization calculation **Why I did it** Now in procdockerstatsd script we iterate all processes every 2 minutes and ingest into state_db, https://psutil.readthedocs.io/en/latest/#psutil.cpu_percent, psutil supports both blocking and non-blocking way, and we prefer to use non-blocking way, thus now psutil.cpu_percent() used is to calculate CPU utilization since last time being triggered, current implementation will create new process object in below lines every time: for process in psutil.process_iter(['pid', 'ppid', 'memory_percent', 'cpu_percent', 'create_time', 'cmdline']): In order to get correct CPU utilization from last 2 minutes, we need store process object into some variable, so that every iteration will first lookup all_process_obj member and use existing process object, otherwise create new object for further use. And clean up the dead process object in case some process crashes. **How I verified it** Verified by UT and will check by ksoftirqd reproduced case.
1 parent f95b7cd commit 13a5419

File tree

2 files changed

+36
-4
lines changed

2 files changed

+36
-4
lines changed

scripts/procdockerstatsd

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ REDIS_HOSTIP = "127.0.0.1"
2424

2525

2626
class ProcDockerStats(daemon_base.DaemonBase):
27+
all_process_obj = {}
2728

2829
def __init__(self, log_identifier):
2930
super(ProcDockerStats, self).__init__(log_identifier)
@@ -137,10 +138,25 @@ class ProcDockerStats(daemon_base.DaemonBase):
137138

138139
def update_processstats_command(self):
139140
processdata = []
140-
for process in psutil.process_iter(['pid', 'ppid', 'memory_percent', 'cpu_percent', 'create_time', 'cmdline']):
141+
pid_set = set()
142+
143+
processes_all = []
144+
for process_obj in psutil.process_iter(['pid', 'ppid', 'memory_percent', 'cpu_percent', 'create_time', 'cmdline']):
145+
processes_all.append(process_obj)
146+
sorted_processes = sorted(processes_all, key=lambda x: x.cpu_percent(), reverse=True)
147+
top_processes = sorted_processes[:1024]
148+
149+
for process_obj in top_processes:
141150
try:
151+
pid = process_obj.pid
152+
pid_set.add(pid)
153+
# store process object and reuse for CPU utilization
154+
if pid in self.all_process_obj:
155+
process = self.all_process_obj[pid]
156+
else:
157+
self.all_process_obj[pid] = process_obj
158+
process = process_obj
142159
uid = process.uids()[0]
143-
pid = process.pid
144160
ppid = process.ppid()
145161
mem = process.memory_percent()
146162
cpu = process.cpu_percent()
@@ -156,6 +172,14 @@ class ProcDockerStats(daemon_base.DaemonBase):
156172
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
157173
pass
158174

175+
# erase dead process
176+
remove_keys = []
177+
for id in self.all_process_obj:
178+
if id not in pid_set:
179+
remove_keys.append(id)
180+
for id in remove_keys:
181+
del self.all_process_obj[id]
182+
159183
# wipe out all data before updating with new values
160184
self.state_db.delete_all_by_pattern('STATE_DB', 'PROCESS_STATS|*')
161185

tests/procdockerstatsd_test.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,21 @@ def test_update_processstats_command(self):
115115
valid_create_time2 = int((current_time - timedelta(days=2)).timestamp())
116116
# Create a list of mocked processes
117117
mocked_processes = [
118-
MockProcess(uids=[1000], pid=1234, ppid=5678, memory_percent=10.5, cpu_percent=20.5, create_time=valid_create_time1, cmdline=['python', 'script.py'], user_time=1.5, system_time=2.0),
119-
MockProcess(uids=[1000], pid=5678, ppid=0, memory_percent=5.5, cpu_percent=15.5, create_time=valid_create_time2, cmdline=['bash', 'script.sh'], user_time=3.5, system_time=4.0)
118+
MockProcess(uids=[1000], pid=1234, ppid=0, memory_percent=10.5, cpu_percent=99.0, create_time=valid_create_time1, cmdline=['python', 'script.py'], user_time=1.5, system_time=2.0),
119+
MockProcess(uids=[1000], pid=5678, ppid=0, memory_percent=5.5, cpu_percent=15.5, create_time=valid_create_time2, cmdline=['bash', 'script.sh'], user_time=3.5, system_time=4.0),
120+
MockProcess(uids=[1000], pid=3333, ppid=0, memory_percent=5.5, cpu_percent=15.5, create_time=valid_create_time2, cmdline=['bash', 'script.sh'], user_time=3.5, system_time=4.0)
121+
]
122+
mocked_processes2 = [
123+
MockProcess(uids=[1000], pid=1234, ppid=0, memory_percent=10.5, cpu_percent=20.5, create_time=valid_create_time1, cmdline=['python', 'script.py'], user_time=1.5, system_time=2.0),
124+
MockProcess(uids=[1000], pid=6666, ppid=0, memory_percent=5.5, cpu_percent=15.5, create_time=valid_create_time2, cmdline=['bash', 'script.sh'], user_time=3.5, system_time=4.0)
120125
]
121126

122127
with patch("procdockerstatsd.psutil.process_iter", return_value=mocked_processes) as mock_process_iter:
128+
pdstatsd.all_process_obj = {1234: mocked_processes2[0],
129+
6666: mocked_processes2[1]}
123130
pdstatsd.update_processstats_command()
124131
mock_process_iter.assert_called_once()
132+
assert(len(pdstatsd.all_process_obj)== 3)
125133

126134
@patch('procdockerstatsd.getstatusoutput_noshell_pipe', return_value=([0, 0], ''))
127135
def test_update_fipsstats_command(self, mock_cmd):

0 commit comments

Comments
 (0)