-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmetrics_error_finder.py
executable file
·148 lines (112 loc) · 4.4 KB
/
metrics_error_finder.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/usr/bin/env python3
# pylint: disable=W0603,W0602
import json
import subprocess
import time
import plistlib
import argparse
import zlib
import base64
import xml
import signal
import sys
import uuid
import os
import os.path
import stat
import urllib.request
import configparser
import sqlite3
import http
from datetime import timezone
from pathlib import Path
from libs import caribou
# Shared variable to signal the thread to stop
stop_signal = False
def sigint_handler(_, __):
global stop_signal
if stop_signal:
# If you press CTR-C the second time we bail
sys.exit()
stop_signal = True
print('Received stop signal. Terminating all processes.')
def siginfo_handler(_, __):
print(SETTINGS)
print(stats)
signal.signal(signal.SIGINT, sigint_handler)
signal.signal(signal.SIGTERM, sigint_handler)
signal.signal(signal.SIGINFO, siginfo_handler)
SETTINGS = {
'powermetrics': 5000,
}
def run_powermetrics():
cmd = ['powermetrics',
'--show-all',
'-i', str(SETTINGS['powermetrics']),
'-f', 'plist']
with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) as process:
buffer = []
for line in process.stdout:
line = line.strip().replace('&', '&')
buffer.append(line)
if line == '</plist>':
parse_powermetrics_output(''.join(buffer))
buffer = []
if stop_signal:
break
if stop_signal:
process.terminate()
def find_top_processes(data: list):
# As iterm2 will probably show up as it spawns the processes called from the shell we look at the tasks
new_data = []
for coalition in data:
if coalition['name'] == 'com.googlecode.iterm2' or coalition['name'].strip() == '':
new_data.extend(coalition['tasks'])
else:
new_data.append(coalition)
return new_data
def is_difference_more_than_5_percent(x, y):
if x == 0 and y == 0:
return False # If both values are 0, the percentage difference is undefined.
if x == 0 or y == 0:
return True # If one of the values is 0 and the other is not, they differ by more than 5%.
percent_difference = abs(x - y) / ((x + y) / 2) * 100
return percent_difference > 5
def parse_powermetrics_output(output: str):
global stats
for data in output.encode('utf-8').split(b'\x00'):
if data:
if data == b'powermetrics must be invoked as the superuser\n':
raise PermissionError('You need to run this script as root!')
try:
data=plistlib.loads(data)
data['timezone'] = time.tzname
data['timestamp'] = int(data['timestamp'].replace(tzinfo=timezone.utc).timestamp() * 1e3)
except xml.parsers.expat.ExpatError as exc:
print(data)
raise exc
for process in find_top_processes(data['coalitions']):
cpu_ns_dirty = process['cputime_ns']
cpu_ns_clean = ((process['cputime_ms_per_s'] * 1_000_000) / 1_000_000_000) * data['elapsed_ns']
ei_dirty = process['energy_impact']
ei_clean = process['energy_impact_per_s'] * data['elapsed_ns'] / 1_000_000_000
if is_difference_more_than_5_percent(cpu_ns_dirty, cpu_ns_clean) or \
is_difference_more_than_5_percent(ei_dirty, ei_clean):
print(f"Name : {process['name']}")
print(f"Elapsed ns : {data['elapsed_ns']}")
print('')
print(f"CPU Time ns : {process['cputime_ns']}")
print(f"CPU Time ns / con : {cpu_ns_clean}")
print(f"cputime_ms_per_s : {process['cputime_ms_per_s']}")
print('')
print(f"energy_impact : {process['energy_impact']}")
print(f"energy_impact con : {ei_clean}")
print(f"energy_impact_per_s : {process['energy_impact_per_s']}")
print('')
print(f"diskio_bytesread : {process['diskio_bytesread']}")
print(f"diskio_bytesread_per_s: {process['diskio_bytesread_per_s']}")
print('')
print(process)
print('------------')
if __name__ == '__main__':
run_powermetrics()