macpm 0.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- macpm-0.12/LICENSE +21 -0
- macpm-0.12/PKG-INFO +19 -0
- macpm-0.12/README.md +93 -0
- macpm-0.12/macpm/__init__.py +0 -0
- macpm-0.12/macpm/macpm.py +529 -0
- macpm-0.12/macpm/parsers.py +171 -0
- macpm-0.12/macpm/utils.py +192 -0
- macpm-0.12/macpm.egg-info/PKG-INFO +19 -0
- macpm-0.12/macpm.egg-info/SOURCES.txt +14 -0
- macpm-0.12/macpm.egg-info/dependency_links.txt +1 -0
- macpm-0.12/macpm.egg-info/entry_points.txt +2 -0
- macpm-0.12/macpm.egg-info/not-zip-safe +1 -0
- macpm-0.12/macpm.egg-info/requires.txt +3 -0
- macpm-0.12/macpm.egg-info/top_level.txt +1 -0
- macpm-0.12/setup.cfg +4 -0
- macpm-0.12/setup.py +34 -0
macpm-0.12/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Timothy Liu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
macpm-0.12/PKG-INFO
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: macpm
|
|
3
|
+
Version: 0.12
|
|
4
|
+
Summary: Performance monitoring CLI tool for Apple Silicon
|
|
5
|
+
Home-page: https://github.com/visualcjy/macpm
|
|
6
|
+
Author: Jinyu Chen
|
|
7
|
+
Author-email: visualcjy@mail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: macpm
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: dashing
|
|
16
|
+
Requires-Dist: psutil
|
|
17
|
+
Requires-Dist: humanize
|
|
18
|
+
|
|
19
|
+
Performance monitoring CLI tool for Apple Silicon
|
macpm-0.12/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# macpm
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Performance monitoring CLI tool for Apple Silicon
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
pip install macpm
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What is `macpm`
|
|
14
|
+
|
|
15
|
+
forked form https://github.com/tlkh/asitop
|
|
16
|
+
1. fixed the problem that "E-CPU Usage" and "P-CPU Usage" always 100%
|
|
17
|
+
2. add "Disk IO" and "Network IO" info
|
|
18
|
+
|
|
19
|
+
A Python-based `nvtop`-inspired command line tool for Apple Silicon (aka M1) Macs.
|
|
20
|
+
|
|
21
|
+
* Utilization info:
|
|
22
|
+
* CPU (E-cluster and P-cluster), GPU
|
|
23
|
+
* Frequency and utilization
|
|
24
|
+
* ANE utilization (measured by power)
|
|
25
|
+
* Memory info:
|
|
26
|
+
* RAM and swap, size and usage
|
|
27
|
+
* (Apple removed memory bandwidth from `powermetrics`)
|
|
28
|
+
* Power info:
|
|
29
|
+
* CPU power, GPU power (Apple removed package power from `powermetrics`)
|
|
30
|
+
* Chart for CPU/GPU power
|
|
31
|
+
* Peak power, rolling average display
|
|
32
|
+
|
|
33
|
+
`macpm` uses the built-in [`powermetrics`](https://www.unix.com/man-page/osx/1/powermetrics/) utility on macOS, which allows access to a variety of hardware performance counters. Note that it requires `sudo` to run due to `powermetrics` needing root access to run. `macpm` is lightweight and has minimal performance impact.
|
|
34
|
+
|
|
35
|
+
**`macpm` only works on Apple Silicon Macs on macOS Monterey!**
|
|
36
|
+
|
|
37
|
+
## Installation and Usage
|
|
38
|
+
|
|
39
|
+
`macpm` is a Python-based command line tool. You need `pip` to download and install `macpm`. macOS already comes with Python, to install `pip`, you can follow an [online guide](https://phoenixnap.com/kb/install-pip-mac). After you install `macpm` via `pip`, you can use it via the Terminal.
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
# to enter password before start
|
|
43
|
+
# this mode is recommended!
|
|
44
|
+
sudo macpm
|
|
45
|
+
|
|
46
|
+
# it will prompt password on start
|
|
47
|
+
macpm
|
|
48
|
+
|
|
49
|
+
# advanced options
|
|
50
|
+
macpm [-h] [--interval INTERVAL] [--color COLOR] [--avg AVG]
|
|
51
|
+
optional arguments:
|
|
52
|
+
-h, --help show this help message and exit
|
|
53
|
+
--interval INTERVAL Display interval and sampling interval for powermetrics (seconds)
|
|
54
|
+
--color COLOR Choose display color (0~8)
|
|
55
|
+
--avg AVG Interval for averaged values (seconds)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## How it works
|
|
59
|
+
|
|
60
|
+
`powermetrics` is used to measure the following:
|
|
61
|
+
|
|
62
|
+
* CPU/GPU utilization via active residency
|
|
63
|
+
* CPU/GPU frequency
|
|
64
|
+
* Package/CPU/GPU/ANE energy consumption
|
|
65
|
+
* CPU/GPU/Media Total memory bandwidth via the DCS (DRAM Command Scheduler)
|
|
66
|
+
|
|
67
|
+
[`psutil`](https://github.com/giampaolo/psutil) is used to measure the following:
|
|
68
|
+
|
|
69
|
+
* memory and swap usage
|
|
70
|
+
|
|
71
|
+
[`sysctl`](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/sysctl.3.html) is used to measure the following:
|
|
72
|
+
|
|
73
|
+
* CPU name
|
|
74
|
+
* CPU core counts
|
|
75
|
+
|
|
76
|
+
[`system_profiler`](https://ss64.com/osx/system_profiler.html) is used to measure the following:
|
|
77
|
+
|
|
78
|
+
* GPU core count
|
|
79
|
+
|
|
80
|
+
Some information is guesstimate and hardcoded as there doesn't seem to be a official source for it on the system:
|
|
81
|
+
|
|
82
|
+
* CPU/GPU TDP
|
|
83
|
+
* CPU/GPU maximum memory bandwidth
|
|
84
|
+
* ANE max power
|
|
85
|
+
* Media engine max bandwidth
|
|
86
|
+
|
|
87
|
+
## Why
|
|
88
|
+
|
|
89
|
+
Because I didn't find something like this online. Also, just curious about stuff.
|
|
90
|
+
|
|
91
|
+
## Disclaimers
|
|
92
|
+
|
|
93
|
+
I did this randomly don't blame me if it fried your new MacBook or something.
|
|
File without changes
|
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import argparse
|
|
3
|
+
import humanize
|
|
4
|
+
from collections import deque
|
|
5
|
+
from dashing import VSplit, HSplit, HGauge, HChart, VGauge, HBrailleChart, HBrailleFilledChart
|
|
6
|
+
from .utils import *
|
|
7
|
+
|
|
8
|
+
parser = argparse.ArgumentParser(
|
|
9
|
+
description='macpm: Performance monitoring CLI tool for Apple Silicon')
|
|
10
|
+
parser.add_argument('--interval', type=int, default=1,
|
|
11
|
+
help='Display interval and sampling interval for powermetrics (seconds)')
|
|
12
|
+
parser.add_argument('--color', type=int, default=2,
|
|
13
|
+
help='Choose display color (0~8)')
|
|
14
|
+
parser.add_argument('--avg', type=int, default=30,
|
|
15
|
+
help='Interval for averaged values (seconds)')
|
|
16
|
+
parser.add_argument('--show_cores', type=bool, default=False,
|
|
17
|
+
help='Choose show cores mode')
|
|
18
|
+
parser.add_argument('--max_count', type=int, default=0,
|
|
19
|
+
help='Max show count to restart powermetrics')
|
|
20
|
+
args = parser.parse_args()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main():
|
|
24
|
+
print("\nmacpm - Performance monitoring CLI tool for Apple Silicon")
|
|
25
|
+
print("You can update macpm by running `pip install macpm --upgrade`")
|
|
26
|
+
print("Get help at `https://github.com/visualcjy/macpm`")
|
|
27
|
+
print("P.S. You are recommended to run macpm with `sudo macpm`\n")
|
|
28
|
+
print("\n[1/3] Loading macpm\n")
|
|
29
|
+
print("\033[?25l")
|
|
30
|
+
|
|
31
|
+
cpu1_gauge = HGauge(title="E-CPU Usage", val=0, color=args.color)
|
|
32
|
+
cpu2_gauge = HGauge(title="P-CPU Usage", val=0, color=args.color)
|
|
33
|
+
gpu_gauge = HGauge(title="GPU Usage", val=0, color=args.color)
|
|
34
|
+
ane_gauge = HGauge(title="ANE", val=0, color=args.color)
|
|
35
|
+
gpu_ane_gauges = [gpu_gauge, ane_gauge]
|
|
36
|
+
|
|
37
|
+
soc_info_dict = get_soc_info()
|
|
38
|
+
e_core_count = soc_info_dict["e_core_count"]
|
|
39
|
+
e_core_gauges = [VGauge(val=0, color=args.color, border_color=args.color) for _ in range(e_core_count)]
|
|
40
|
+
p_core_count = soc_info_dict["p_core_count"]
|
|
41
|
+
p_core_gauges = [VGauge(val=0, color=args.color, border_color=args.color) for _ in range(min(p_core_count, 8))]
|
|
42
|
+
p_core_split = [HSplit(
|
|
43
|
+
*p_core_gauges,
|
|
44
|
+
)]
|
|
45
|
+
if p_core_count > 8:
|
|
46
|
+
p_core_gauges_ext = [VGauge(val=0, color=args.color, border_color=args.color) for _ in range(p_core_count - 8)]
|
|
47
|
+
p_core_split.append(HSplit(
|
|
48
|
+
*p_core_gauges_ext,
|
|
49
|
+
))
|
|
50
|
+
processor_gauges = [cpu1_gauge,
|
|
51
|
+
HSplit(*e_core_gauges),
|
|
52
|
+
cpu2_gauge,
|
|
53
|
+
*p_core_split,
|
|
54
|
+
*gpu_ane_gauges
|
|
55
|
+
] if args.show_cores else [
|
|
56
|
+
HSplit(cpu1_gauge, cpu2_gauge),
|
|
57
|
+
HSplit(*gpu_ane_gauges)
|
|
58
|
+
]
|
|
59
|
+
processor_split = VSplit(
|
|
60
|
+
*processor_gauges,
|
|
61
|
+
title="Processor Utilization",
|
|
62
|
+
border_color=args.color,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
ram_gauge = HGauge(title="RAM Usage", val=0, color=args.color)
|
|
66
|
+
"""
|
|
67
|
+
ecpu_bw_gauge = HGauge(title="E-CPU B/W", val=50, color=args.color)
|
|
68
|
+
pcpu_bw_gauge = HGauge(title="P-CPU B/W", val=50, color=args.color)
|
|
69
|
+
gpu_bw_gauge = HGauge(title="GPU B/W", val=50, color=args.color)
|
|
70
|
+
media_bw_gauge = HGauge(title="Media B/W", val=50, color=args.color)
|
|
71
|
+
bw_gauges = [HSplit(
|
|
72
|
+
ecpu_bw_gauge,
|
|
73
|
+
pcpu_bw_gauge,
|
|
74
|
+
),
|
|
75
|
+
HSplit(
|
|
76
|
+
gpu_bw_gauge,
|
|
77
|
+
media_bw_gauge,
|
|
78
|
+
)] if args.show_cores else [
|
|
79
|
+
HSplit(
|
|
80
|
+
ecpu_bw_gauge,
|
|
81
|
+
pcpu_bw_gauge,
|
|
82
|
+
gpu_bw_gauge,
|
|
83
|
+
media_bw_gauge,
|
|
84
|
+
)]
|
|
85
|
+
"""
|
|
86
|
+
memory_gauges = VSplit(
|
|
87
|
+
ram_gauge,
|
|
88
|
+
#*bw_gauges,
|
|
89
|
+
border_color=args.color,
|
|
90
|
+
title="Memory"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
cpu_power_chart = HChart(title="CPU Power", color=args.color)
|
|
94
|
+
gpu_power_chart = HChart(title="GPU Power", color=args.color)
|
|
95
|
+
power_charts = VSplit(
|
|
96
|
+
cpu_power_chart,
|
|
97
|
+
gpu_power_chart,
|
|
98
|
+
title="Power Chart",
|
|
99
|
+
border_color=args.color,
|
|
100
|
+
) if args.show_cores else HSplit(
|
|
101
|
+
cpu_power_chart,
|
|
102
|
+
gpu_power_chart,
|
|
103
|
+
title="Power Chart",
|
|
104
|
+
border_color=args.color,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
disk_read_iops_charts = HChart(title="read iops", color=args.color)
|
|
108
|
+
disk_write_iops_charts = HBrailleFilledChart(title="write iops", color=args.color)
|
|
109
|
+
disk_read_bps_charts = HChart(title="read Bps", color=args.color)
|
|
110
|
+
disk_write_bps_charts = HBrailleFilledChart(title="write Bps", color=args.color)
|
|
111
|
+
network_in_bps_charts = HChart(title="in Bps", color=args.color)
|
|
112
|
+
network_out_bps_charts = HBrailleFilledChart(title="out Bps", color=args.color)
|
|
113
|
+
disk_io_charts = HSplit(
|
|
114
|
+
disk_read_iops_charts,
|
|
115
|
+
disk_write_iops_charts,
|
|
116
|
+
disk_read_bps_charts,
|
|
117
|
+
disk_write_bps_charts,
|
|
118
|
+
title="Disk IO",
|
|
119
|
+
color=args.color,
|
|
120
|
+
border_color=args.color)
|
|
121
|
+
|
|
122
|
+
network_io_charts = HSplit(
|
|
123
|
+
network_in_bps_charts,
|
|
124
|
+
network_out_bps_charts,
|
|
125
|
+
title="Network IO",
|
|
126
|
+
color=args.color,
|
|
127
|
+
border_color=args.color)
|
|
128
|
+
|
|
129
|
+
ui = HSplit(
|
|
130
|
+
processor_split,
|
|
131
|
+
VSplit(
|
|
132
|
+
memory_gauges,
|
|
133
|
+
power_charts,
|
|
134
|
+
disk_io_charts,
|
|
135
|
+
network_io_charts,
|
|
136
|
+
)
|
|
137
|
+
) if args.show_cores else VSplit(
|
|
138
|
+
processor_split,
|
|
139
|
+
memory_gauges,
|
|
140
|
+
power_charts,
|
|
141
|
+
disk_io_charts,
|
|
142
|
+
network_io_charts,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
usage_gauges = ui.items[0]
|
|
146
|
+
#bw_gauges = memory_gauges.items[1]
|
|
147
|
+
|
|
148
|
+
cpu_title = "".join([
|
|
149
|
+
soc_info_dict["name"],
|
|
150
|
+
" (cores: ",
|
|
151
|
+
str(soc_info_dict["e_core_count"]),
|
|
152
|
+
"E+",
|
|
153
|
+
str(soc_info_dict["p_core_count"]),
|
|
154
|
+
"P+",
|
|
155
|
+
str(soc_info_dict["gpu_core_count"]),
|
|
156
|
+
"GPU)"
|
|
157
|
+
])
|
|
158
|
+
usage_gauges.title = cpu_title
|
|
159
|
+
cpu_max_power = soc_info_dict["cpu_max_power"]
|
|
160
|
+
gpu_max_power = soc_info_dict["gpu_max_power"]
|
|
161
|
+
ane_max_power = 8.0
|
|
162
|
+
"""max_cpu_bw = soc_info_dict["cpu_max_bw"]
|
|
163
|
+
max_gpu_bw = soc_info_dict["gpu_max_bw"]
|
|
164
|
+
max_media_bw = 7.0"""
|
|
165
|
+
|
|
166
|
+
cpu_peak_power = 0
|
|
167
|
+
gpu_peak_power = 0
|
|
168
|
+
package_peak_power = 0
|
|
169
|
+
disk_read_iops_peak = 0
|
|
170
|
+
disk_write_iops_peak = 0
|
|
171
|
+
disk_read_bps_peak = 0
|
|
172
|
+
disk_write_bps_peak = 0
|
|
173
|
+
network_in_bps_peak = 0
|
|
174
|
+
network_out_bps_peak = 0
|
|
175
|
+
|
|
176
|
+
print("\n[2/3] Starting powermetrics process\n")
|
|
177
|
+
|
|
178
|
+
timecode = str(int(time.time()))
|
|
179
|
+
|
|
180
|
+
powermetrics_process = run_powermetrics_process(timecode,
|
|
181
|
+
interval=args.interval * 1000)
|
|
182
|
+
|
|
183
|
+
print("\n[3/3] Waiting for first reading...\n")
|
|
184
|
+
|
|
185
|
+
def get_reading(wait=0.1):
|
|
186
|
+
ready = parse_powermetrics(timecode=timecode)
|
|
187
|
+
while not ready:
|
|
188
|
+
time.sleep(wait)
|
|
189
|
+
ready = parse_powermetrics(timecode=timecode)
|
|
190
|
+
return ready
|
|
191
|
+
|
|
192
|
+
ready = get_reading()
|
|
193
|
+
last_timestamp = ready[-1]
|
|
194
|
+
|
|
195
|
+
def get_avg(inlist):
|
|
196
|
+
avg = sum(inlist) / len(inlist)
|
|
197
|
+
return avg
|
|
198
|
+
|
|
199
|
+
avg_package_power_list = deque([], maxlen=int(args.avg / args.interval))
|
|
200
|
+
avg_cpu_power_list = deque([], maxlen=int(args.avg / args.interval))
|
|
201
|
+
avg_gpu_power_list = deque([], maxlen=int(args.avg / args.interval))
|
|
202
|
+
|
|
203
|
+
clear_console()
|
|
204
|
+
|
|
205
|
+
count=0
|
|
206
|
+
try:
|
|
207
|
+
while True:
|
|
208
|
+
if args.max_count > 0:
|
|
209
|
+
if count >= args.max_count:
|
|
210
|
+
count = 0
|
|
211
|
+
powermetrics_process.terminate()
|
|
212
|
+
timecode = str(int(time.time()))
|
|
213
|
+
powermetrics_process = run_powermetrics_process(
|
|
214
|
+
timecode, interval=args.interval * 1000)
|
|
215
|
+
count += 1
|
|
216
|
+
ready = parse_powermetrics(timecode=timecode)
|
|
217
|
+
if ready:
|
|
218
|
+
cpu_metrics_dict, gpu_metrics_dict, thermal_pressure, bandwidth_metrics, disk_metrics_dict, network_metrics_dict, timestamp = ready
|
|
219
|
+
|
|
220
|
+
if timestamp > last_timestamp:
|
|
221
|
+
last_timestamp = timestamp
|
|
222
|
+
|
|
223
|
+
if thermal_pressure == "Nominal":
|
|
224
|
+
thermal_throttle = "no"
|
|
225
|
+
else:
|
|
226
|
+
thermal_throttle = "yes"
|
|
227
|
+
|
|
228
|
+
"""e_cpu_usage = 0
|
|
229
|
+
core_count = 0
|
|
230
|
+
for i in cpu_metrics_dict["e_core"]:
|
|
231
|
+
e_cpu_usage += cpu_metrics_dict["E-Cluster" + str(i) + "_active"]
|
|
232
|
+
core_count += 1
|
|
233
|
+
e_cpu_usage = (e_cpu_usage / core_count) if core_count > 0 else 0"""
|
|
234
|
+
cpu1_gauge.title = "".join([
|
|
235
|
+
"E-CPU Usage: ",
|
|
236
|
+
str(cpu_metrics_dict["E-Cluster_active"]),
|
|
237
|
+
"% @ ",
|
|
238
|
+
str(cpu_metrics_dict["E-Cluster_freq_Mhz"]),
|
|
239
|
+
" MHz"
|
|
240
|
+
])
|
|
241
|
+
cpu1_gauge.value = cpu_metrics_dict["E-Cluster_active"]
|
|
242
|
+
|
|
243
|
+
"""p_cpu_usage = 0
|
|
244
|
+
core_count = 0
|
|
245
|
+
for i in cpu_metrics_dict["p_core"]:
|
|
246
|
+
p_cpu_usage += cpu_metrics_dict["P-Cluster" + str(i) + "_active"]
|
|
247
|
+
core_count += 1
|
|
248
|
+
p_cpu_usage = (p_cpu_usage / core_count) if core_count > 0 else 0"""
|
|
249
|
+
cpu2_gauge.title = "".join([
|
|
250
|
+
"P-CPU Usage: ",
|
|
251
|
+
str(cpu_metrics_dict["P-Cluster_active"]),
|
|
252
|
+
"% @ ",
|
|
253
|
+
str(cpu_metrics_dict["P-Cluster_freq_Mhz"]),
|
|
254
|
+
" MHz"
|
|
255
|
+
])
|
|
256
|
+
cpu2_gauge.value = cpu_metrics_dict["P-Cluster_active"]
|
|
257
|
+
|
|
258
|
+
if args.show_cores:
|
|
259
|
+
core_count = 0
|
|
260
|
+
for i in cpu_metrics_dict["e_core"]:
|
|
261
|
+
e_core_gauges[core_count % 4].title = "".join([
|
|
262
|
+
"Core-" + str(i + 1) + " ",
|
|
263
|
+
str(cpu_metrics_dict["E-Cluster" + str(i) + "_active"]),
|
|
264
|
+
"%",
|
|
265
|
+
])
|
|
266
|
+
e_core_gauges[core_count % 4].value = cpu_metrics_dict["E-Cluster" + str(i) + "_active"]
|
|
267
|
+
core_count += 1
|
|
268
|
+
core_count = 0
|
|
269
|
+
for i in cpu_metrics_dict["p_core"]:
|
|
270
|
+
core_gauges = p_core_gauges if core_count < 8 else p_core_gauges_ext
|
|
271
|
+
core_gauges[core_count % 8].title = "".join([
|
|
272
|
+
("Core-" if p_core_count < 6 else 'C-') + str(i + 1) + " ",
|
|
273
|
+
str(cpu_metrics_dict["P-Cluster" + str(i) + "_active"]),
|
|
274
|
+
"%",
|
|
275
|
+
])
|
|
276
|
+
core_gauges[core_count % 8].value = cpu_metrics_dict["P-Cluster" + str(i) + "_active"]
|
|
277
|
+
core_count += 1
|
|
278
|
+
|
|
279
|
+
gpu_gauge.title = "".join([
|
|
280
|
+
"GPU Usage: ",
|
|
281
|
+
str(gpu_metrics_dict["active"]),
|
|
282
|
+
"% @ ",
|
|
283
|
+
str(gpu_metrics_dict["freq_MHz"]),
|
|
284
|
+
" MHz"
|
|
285
|
+
])
|
|
286
|
+
gpu_gauge.value = gpu_metrics_dict["active"]
|
|
287
|
+
|
|
288
|
+
ane_util_percent = int(
|
|
289
|
+
cpu_metrics_dict["ane_W"] / args.interval / ane_max_power * 100)
|
|
290
|
+
ane_gauge.title = "".join([
|
|
291
|
+
"ANE Usage: ",
|
|
292
|
+
str(ane_util_percent),
|
|
293
|
+
"% @ ",
|
|
294
|
+
'{0:.1f}'.format(
|
|
295
|
+
cpu_metrics_dict["ane_W"] / args.interval),
|
|
296
|
+
" W"
|
|
297
|
+
])
|
|
298
|
+
ane_gauge.value = ane_util_percent
|
|
299
|
+
|
|
300
|
+
ram_metrics_dict = get_ram_metrics_dict()
|
|
301
|
+
|
|
302
|
+
if ram_metrics_dict["swap_total_GB"] < 0.1:
|
|
303
|
+
ram_gauge.title = "".join([
|
|
304
|
+
"RAM Usage: ",
|
|
305
|
+
str(ram_metrics_dict["used_GB"]),
|
|
306
|
+
"/",
|
|
307
|
+
str(ram_metrics_dict["total_GB"]),
|
|
308
|
+
"GB - swap inactive"
|
|
309
|
+
])
|
|
310
|
+
else:
|
|
311
|
+
ram_gauge.title = "".join([
|
|
312
|
+
"RAM Usage: ",
|
|
313
|
+
str(ram_metrics_dict["used_GB"]),
|
|
314
|
+
"/",
|
|
315
|
+
str(ram_metrics_dict["total_GB"]),
|
|
316
|
+
"GB",
|
|
317
|
+
" - swap:",
|
|
318
|
+
str(ram_metrics_dict["swap_used_GB"]),
|
|
319
|
+
"/",
|
|
320
|
+
str(ram_metrics_dict["swap_total_GB"]),
|
|
321
|
+
"GB"
|
|
322
|
+
])
|
|
323
|
+
ram_gauge.value = ram_metrics_dict["free_percent"]
|
|
324
|
+
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
ecpu_bw_percent = int(
|
|
328
|
+
(bandwidth_metrics["ECPU DCS RD"] + bandwidth_metrics[
|
|
329
|
+
"ECPU DCS WR"]) / args.interval / max_cpu_bw * 100)
|
|
330
|
+
ecpu_read_GB = bandwidth_metrics["ECPU DCS RD"] / \
|
|
331
|
+
args.interval
|
|
332
|
+
ecpu_write_GB = bandwidth_metrics["ECPU DCS WR"] / \
|
|
333
|
+
args.interval
|
|
334
|
+
ecpu_bw_gauge.title = "".join([
|
|
335
|
+
"E-CPU: ",
|
|
336
|
+
'{0:.1f}'.format(ecpu_read_GB + ecpu_write_GB),
|
|
337
|
+
"GB/s"
|
|
338
|
+
])
|
|
339
|
+
ecpu_bw_gauge.value = ecpu_bw_percent
|
|
340
|
+
|
|
341
|
+
pcpu_bw_percent = int(
|
|
342
|
+
(bandwidth_metrics["PCPU DCS RD"] + bandwidth_metrics[
|
|
343
|
+
"PCPU DCS WR"]) / args.interval / max_cpu_bw * 100)
|
|
344
|
+
pcpu_read_GB = bandwidth_metrics["PCPU DCS RD"] / \
|
|
345
|
+
args.interval
|
|
346
|
+
pcpu_write_GB = bandwidth_metrics["PCPU DCS WR"] / \
|
|
347
|
+
args.interval
|
|
348
|
+
pcpu_bw_gauge.title = "".join([
|
|
349
|
+
"P-CPU: ",
|
|
350
|
+
'{0:.1f}'.format(pcpu_read_GB + pcpu_write_GB),
|
|
351
|
+
"GB/s"
|
|
352
|
+
])
|
|
353
|
+
pcpu_bw_gauge.value = pcpu_bw_percent
|
|
354
|
+
|
|
355
|
+
gpu_bw_percent = int(
|
|
356
|
+
(bandwidth_metrics["GFX DCS RD"] + bandwidth_metrics["GFX DCS WR"]) / max_gpu_bw * 100)
|
|
357
|
+
gpu_read_GB = bandwidth_metrics["GFX DCS RD"]
|
|
358
|
+
gpu_write_GB = bandwidth_metrics["GFX DCS WR"]
|
|
359
|
+
gpu_bw_gauge.title = "".join([
|
|
360
|
+
"GPU: ",
|
|
361
|
+
'{0:.1f}'.format(gpu_read_GB + gpu_write_GB),
|
|
362
|
+
"GB/s"
|
|
363
|
+
])
|
|
364
|
+
gpu_bw_gauge.value = gpu_bw_percent
|
|
365
|
+
|
|
366
|
+
media_bw_percent = int(
|
|
367
|
+
bandwidth_metrics["MEDIA DCS"] / args.interval / max_media_bw * 100)
|
|
368
|
+
media_bw_gauge.title = "".join([
|
|
369
|
+
"Media: ",
|
|
370
|
+
'{0:.1f}'.format(
|
|
371
|
+
bandwidth_metrics["MEDIA DCS"] / args.interval),
|
|
372
|
+
"GB/s"
|
|
373
|
+
])
|
|
374
|
+
media_bw_gauge.value = media_bw_percent
|
|
375
|
+
|
|
376
|
+
total_bw_GB = (
|
|
377
|
+
bandwidth_metrics["DCS RD"] + bandwidth_metrics["DCS WR"]) / args.interval
|
|
378
|
+
bw_gauges.title = "".join([
|
|
379
|
+
"Memory Bandwidth: ",
|
|
380
|
+
'{0:.2f}'.format(total_bw_GB),
|
|
381
|
+
" GB/s (R:",
|
|
382
|
+
'{0:.2f}'.format(
|
|
383
|
+
bandwidth_metrics["DCS RD"] / args.interval),
|
|
384
|
+
"/W:",
|
|
385
|
+
'{0:.2f}'.format(
|
|
386
|
+
bandwidth_metrics["DCS WR"] / args.interval),
|
|
387
|
+
" GB/s)"
|
|
388
|
+
])
|
|
389
|
+
if args.show_cores:
|
|
390
|
+
bw_gauges_ext = memory_gauges.items[2]
|
|
391
|
+
bw_gauges_ext.title = "Memory Bandwidth:"
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
package_power_W = cpu_metrics_dict["package_W"] / \
|
|
395
|
+
args.interval
|
|
396
|
+
if package_power_W > package_peak_power:
|
|
397
|
+
package_peak_power = package_power_W
|
|
398
|
+
avg_package_power_list.append(package_power_W)
|
|
399
|
+
avg_package_power = get_avg(avg_package_power_list)
|
|
400
|
+
power_charts.title = "".join([
|
|
401
|
+
"CPU+GPU+ANE Power: ",
|
|
402
|
+
'{0:.2f}'.format(package_power_W),
|
|
403
|
+
"W (avg: ",
|
|
404
|
+
'{0:.2f}'.format(avg_package_power),
|
|
405
|
+
"W peak: ",
|
|
406
|
+
'{0:.2f}'.format(package_peak_power),
|
|
407
|
+
"W) throttle: ",
|
|
408
|
+
thermal_throttle,
|
|
409
|
+
])
|
|
410
|
+
|
|
411
|
+
cpu_power_percent = int(
|
|
412
|
+
cpu_metrics_dict["cpu_W"] / args.interval / cpu_max_power * 100)
|
|
413
|
+
cpu_power_W = cpu_metrics_dict["cpu_W"] / args.interval
|
|
414
|
+
if cpu_power_W > cpu_peak_power:
|
|
415
|
+
cpu_peak_power = cpu_power_W
|
|
416
|
+
avg_cpu_power_list.append(cpu_power_W)
|
|
417
|
+
avg_cpu_power = get_avg(avg_cpu_power_list)
|
|
418
|
+
cpu_power_chart.title = "".join([
|
|
419
|
+
"CPU: ",
|
|
420
|
+
'{0:.2f}'.format(cpu_power_W),
|
|
421
|
+
"W (avg: ",
|
|
422
|
+
'{0:.2f}'.format(avg_cpu_power),
|
|
423
|
+
"W peak: ",
|
|
424
|
+
'{0:.2f}'.format(cpu_peak_power),
|
|
425
|
+
"W)"
|
|
426
|
+
])
|
|
427
|
+
cpu_power_chart.append(cpu_power_percent)
|
|
428
|
+
|
|
429
|
+
gpu_power_percent = int(
|
|
430
|
+
cpu_metrics_dict["gpu_W"] / args.interval / gpu_max_power * 100)
|
|
431
|
+
gpu_power_W = cpu_metrics_dict["gpu_W"] / args.interval
|
|
432
|
+
if gpu_power_W > gpu_peak_power:
|
|
433
|
+
gpu_peak_power = gpu_power_W
|
|
434
|
+
avg_gpu_power_list.append(gpu_power_W)
|
|
435
|
+
avg_gpu_power = get_avg(avg_gpu_power_list)
|
|
436
|
+
gpu_power_chart.title = "".join([
|
|
437
|
+
"GPU: ",
|
|
438
|
+
'{0:.2f}'.format(gpu_power_W),
|
|
439
|
+
"W (avg: ",
|
|
440
|
+
'{0:.2f}'.format(avg_gpu_power),
|
|
441
|
+
"W peak: ",
|
|
442
|
+
'{0:.2f}'.format(gpu_peak_power),
|
|
443
|
+
"W)"
|
|
444
|
+
])
|
|
445
|
+
gpu_power_chart.append(gpu_power_percent)
|
|
446
|
+
|
|
447
|
+
def format_number(number):
|
|
448
|
+
return humanize.naturalsize(number)
|
|
449
|
+
|
|
450
|
+
disk_read_iops = disk_metrics_dict["read_iops"]
|
|
451
|
+
if disk_read_iops > disk_read_iops_peak:
|
|
452
|
+
disk_read_iops_peak = disk_read_iops
|
|
453
|
+
disk_read_iops_charts.title = "Read iops: "+ f'{disk_read_iops} (peak: {disk_read_iops_peak})'
|
|
454
|
+
if disk_read_iops_charts.datapoints:
|
|
455
|
+
disk_read_iops_rate = int(disk_read_iops / disk_read_iops_peak * 100)
|
|
456
|
+
else:
|
|
457
|
+
disk_read_iops_rate = 100
|
|
458
|
+
disk_read_iops_charts.append(disk_read_iops_rate)
|
|
459
|
+
|
|
460
|
+
disk_write_iops = disk_metrics_dict["write_iops"]
|
|
461
|
+
if disk_write_iops > disk_write_iops_peak:
|
|
462
|
+
disk_write_iops_peak = disk_write_iops
|
|
463
|
+
disk_write_iops_charts.title = "Write iops: "+ f'{disk_write_iops} (peak: {disk_write_iops_peak})'
|
|
464
|
+
if disk_write_iops_charts.datapoints:
|
|
465
|
+
disk_write_iops_rate = int(disk_read_iops / disk_write_iops_peak * 100)
|
|
466
|
+
else:
|
|
467
|
+
disk_write_iops_rate = 100
|
|
468
|
+
disk_write_iops_charts.append(disk_write_iops_rate)
|
|
469
|
+
|
|
470
|
+
disk_read_bps = disk_metrics_dict["read_Bps"]
|
|
471
|
+
if disk_read_bps > disk_read_bps_peak:
|
|
472
|
+
disk_read_bps_peak = disk_read_bps
|
|
473
|
+
disk_read_bps_charts.title = "Read : "+ f'{format_number(disk_read_bps)}/s (peak: {format_number(disk_read_bps_peak)}/s)'
|
|
474
|
+
if disk_read_bps_charts.datapoints:
|
|
475
|
+
disk_read_bps_rate = int(disk_read_bps / disk_read_bps_peak * 100)
|
|
476
|
+
else:
|
|
477
|
+
disk_read_bps_rate = 100
|
|
478
|
+
disk_read_bps_charts.append(disk_read_bps_rate)
|
|
479
|
+
|
|
480
|
+
disk_write_bps = disk_metrics_dict["write_Bps"]
|
|
481
|
+
if disk_write_bps > disk_write_bps_peak:
|
|
482
|
+
disk_write_bps_peak = disk_write_bps
|
|
483
|
+
disk_write_bps_charts.title = "Write : "+ f'{format_number(disk_write_bps)}/s (peak: {format_number(disk_write_bps_peak)}/s)'
|
|
484
|
+
if disk_write_bps_charts.datapoints:
|
|
485
|
+
disk_write_bps_rate = int(disk_write_bps / disk_write_bps_peak * 100)
|
|
486
|
+
else:
|
|
487
|
+
disk_write_bps_rate = 100
|
|
488
|
+
disk_write_bps_charts.append(disk_write_bps_rate)
|
|
489
|
+
|
|
490
|
+
network_in_bps = network_metrics_dict["in_Bps"]
|
|
491
|
+
if network_in_bps > network_in_bps_peak:
|
|
492
|
+
network_in_bps_peak = network_in_bps
|
|
493
|
+
network_in_bps_charts.title = "in : "+ f'{format_number(network_in_bps)}/s (peak: {format_number(network_in_bps_peak)}/s)'
|
|
494
|
+
if network_in_bps_charts.datapoints:
|
|
495
|
+
network_in_bps_rate = int(network_in_bps / network_in_bps_peak * 100)
|
|
496
|
+
else:
|
|
497
|
+
network_in_bps_rate = 100
|
|
498
|
+
network_in_bps_charts.append(network_in_bps_rate)
|
|
499
|
+
|
|
500
|
+
network_out_bps = network_metrics_dict["out_Bps"]
|
|
501
|
+
if network_out_bps > network_out_bps_peak:
|
|
502
|
+
network_out_bps_peak = network_out_bps
|
|
503
|
+
network_out_bps_charts.title = "out : "+ f'{format_number(network_out_bps)}/s (peak: {format_number(network_out_bps_peak)}/s)'
|
|
504
|
+
if network_out_bps_charts.datapoints:
|
|
505
|
+
network_out_bps_rate = int(network_out_bps / network_out_bps_peak * 100)
|
|
506
|
+
else:
|
|
507
|
+
network_out_bps_rate = 100
|
|
508
|
+
network_out_bps_charts.append(network_out_bps_rate)
|
|
509
|
+
|
|
510
|
+
ui.display()
|
|
511
|
+
|
|
512
|
+
time.sleep(args.interval)
|
|
513
|
+
|
|
514
|
+
except KeyboardInterrupt:
|
|
515
|
+
print("Stopping...")
|
|
516
|
+
print("\033[?25h")
|
|
517
|
+
|
|
518
|
+
return powermetrics_process
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
if __name__ == "__main__":
|
|
522
|
+
powermetrics_process = main()
|
|
523
|
+
try:
|
|
524
|
+
powermetrics_process.terminate()
|
|
525
|
+
print("Successfully terminated powermetrics process")
|
|
526
|
+
except Exception as e:
|
|
527
|
+
print(e)
|
|
528
|
+
powermetrics_process.terminate()
|
|
529
|
+
print("Successfully terminated powermetrics process")
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
def parse_thermal_pressure(powermetrics_parse):
|
|
2
|
+
return powermetrics_parse["thermal_pressure"]
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def parse_bandwidth_metrics(powermetrics_parse):
|
|
6
|
+
bandwidth_metrics = powermetrics_parse["bandwidth_counters"]
|
|
7
|
+
bandwidth_metrics_dict = {}
|
|
8
|
+
data_fields = ["PCPU0 DCS RD", "PCPU0 DCS WR",
|
|
9
|
+
"PCPU1 DCS RD", "PCPU1 DCS WR",
|
|
10
|
+
"PCPU2 DCS RD", "PCPU2 DCS WR",
|
|
11
|
+
"PCPU3 DCS RD", "PCPU3 DCS WR",
|
|
12
|
+
"PCPU DCS RD", "PCPU DCS WR",
|
|
13
|
+
"ECPU0 DCS RD", "ECPU0 DCS WR",
|
|
14
|
+
"ECPU1 DCS RD", "ECPU1 DCS WR",
|
|
15
|
+
"ECPU DCS RD", "ECPU DCS WR",
|
|
16
|
+
"GFX DCS RD", "GFX DCS WR",
|
|
17
|
+
"ISP DCS RD", "ISP DCS WR",
|
|
18
|
+
"STRM CODEC DCS RD", "STRM CODEC DCS WR",
|
|
19
|
+
"PRORES DCS RD", "PRORES DCS WR",
|
|
20
|
+
"VDEC DCS RD", "VDEC DCS WR",
|
|
21
|
+
"VENC0 DCS RD", "VENC0 DCS WR",
|
|
22
|
+
"VENC1 DCS RD", "VENC1 DCS WR",
|
|
23
|
+
"VENC2 DCS RD", "VENC2 DCS WR",
|
|
24
|
+
"VENC3 DCS RD", "VENC3 DCS WR",
|
|
25
|
+
"VENC DCS RD", "VENC DCS WR",
|
|
26
|
+
"JPG0 DCS RD", "JPG0 DCS WR",
|
|
27
|
+
"JPG1 DCS RD", "JPG1 DCS WR",
|
|
28
|
+
"JPG2 DCS RD", "JPG2 DCS WR",
|
|
29
|
+
"JPG3 DCS RD", "JPG3 DCS WR",
|
|
30
|
+
"JPG DCS RD", "JPG DCS WR",
|
|
31
|
+
"DCS RD", "DCS WR"]
|
|
32
|
+
for h in data_fields:
|
|
33
|
+
bandwidth_metrics_dict[h] = 0
|
|
34
|
+
for l in bandwidth_metrics:
|
|
35
|
+
if l["name"] in data_fields:
|
|
36
|
+
bandwidth_metrics_dict[l["name"]] = l["value"]/(1e9)
|
|
37
|
+
bandwidth_metrics_dict["PCPU DCS RD"] = bandwidth_metrics_dict["PCPU DCS RD"] + \
|
|
38
|
+
bandwidth_metrics_dict["PCPU0 DCS RD"] + \
|
|
39
|
+
bandwidth_metrics_dict["PCPU1 DCS RD"] + \
|
|
40
|
+
bandwidth_metrics_dict["PCPU2 DCS RD"] + \
|
|
41
|
+
bandwidth_metrics_dict["PCPU3 DCS RD"]
|
|
42
|
+
bandwidth_metrics_dict["PCPU DCS WR"] = bandwidth_metrics_dict["PCPU DCS WR"] + \
|
|
43
|
+
bandwidth_metrics_dict["PCPU0 DCS WR"] + \
|
|
44
|
+
bandwidth_metrics_dict["PCPU1 DCS WR"] + \
|
|
45
|
+
bandwidth_metrics_dict["PCPU2 DCS WR"] + \
|
|
46
|
+
bandwidth_metrics_dict["PCPU3 DCS WR"]
|
|
47
|
+
bandwidth_metrics_dict["JPG DCS RD"] = bandwidth_metrics_dict["JPG DCS RD"] + \
|
|
48
|
+
bandwidth_metrics_dict["JPG0 DCS RD"] + \
|
|
49
|
+
bandwidth_metrics_dict["JPG1 DCS RD"] + \
|
|
50
|
+
bandwidth_metrics_dict["JPG2 DCS RD"] + \
|
|
51
|
+
bandwidth_metrics_dict["JPG3 DCS RD"]
|
|
52
|
+
bandwidth_metrics_dict["JPG DCS WR"] = bandwidth_metrics_dict["JPG DCS WR"] + \
|
|
53
|
+
bandwidth_metrics_dict["JPG0 DCS WR"] + \
|
|
54
|
+
bandwidth_metrics_dict["JPG1 DCS WR"] + \
|
|
55
|
+
bandwidth_metrics_dict["JPG2 DCS WR"] + \
|
|
56
|
+
bandwidth_metrics_dict["JPG3 DCS WR"]
|
|
57
|
+
bandwidth_metrics_dict["VENC DCS RD"] = bandwidth_metrics_dict["VENC DCS RD"] + \
|
|
58
|
+
bandwidth_metrics_dict["VENC0 DCS RD"] + \
|
|
59
|
+
bandwidth_metrics_dict["VENC1 DCS RD"] + \
|
|
60
|
+
bandwidth_metrics_dict["VENC2 DCS RD"] + \
|
|
61
|
+
bandwidth_metrics_dict["VENC3 DCS RD"]
|
|
62
|
+
bandwidth_metrics_dict["VENC DCS WR"] = bandwidth_metrics_dict["VENC DCS WR"] + \
|
|
63
|
+
bandwidth_metrics_dict["VENC0 DCS WR"] + \
|
|
64
|
+
bandwidth_metrics_dict["VENC1 DCS WR"] + \
|
|
65
|
+
bandwidth_metrics_dict["VENC2 DCS WR"] + \
|
|
66
|
+
bandwidth_metrics_dict["VENC3 DCS WR"]
|
|
67
|
+
bandwidth_metrics_dict["MEDIA DCS"] = sum([
|
|
68
|
+
bandwidth_metrics_dict["ISP DCS RD"], bandwidth_metrics_dict["ISP DCS WR"],
|
|
69
|
+
bandwidth_metrics_dict["STRM CODEC DCS RD"], bandwidth_metrics_dict["STRM CODEC DCS WR"],
|
|
70
|
+
bandwidth_metrics_dict["PRORES DCS RD"], bandwidth_metrics_dict["PRORES DCS WR"],
|
|
71
|
+
bandwidth_metrics_dict["VDEC DCS RD"], bandwidth_metrics_dict["VDEC DCS WR"],
|
|
72
|
+
bandwidth_metrics_dict["VENC DCS RD"], bandwidth_metrics_dict["VENC DCS WR"],
|
|
73
|
+
bandwidth_metrics_dict["JPG DCS RD"], bandwidth_metrics_dict["JPG DCS WR"],
|
|
74
|
+
])
|
|
75
|
+
return bandwidth_metrics_dict
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def parse_cpu_metrics(powermetrics_parse):
|
|
79
|
+
e_core = []
|
|
80
|
+
p_core = []
|
|
81
|
+
cpu_metrics = powermetrics_parse["processor"]
|
|
82
|
+
cpu_metric_dict = {}
|
|
83
|
+
# cpu_clusters
|
|
84
|
+
cpu_clusters = cpu_metrics["clusters"]
|
|
85
|
+
e_total_idle_ratio = 0
|
|
86
|
+
e_core_count = 0
|
|
87
|
+
p_total_idle_ratio = 0
|
|
88
|
+
p_core_count = 0
|
|
89
|
+
for cluster in cpu_clusters:
|
|
90
|
+
name = cluster["name"]
|
|
91
|
+
cpu_metric_dict[name+"_freq_Mhz"] = int(cluster["freq_hz"]/(1e6))
|
|
92
|
+
cpu_metric_dict[name+"_active"] = int((1 - cluster["idle_ratio"])*100)
|
|
93
|
+
for cpu in cluster["cpus"]:
|
|
94
|
+
name = 'E-Cluster' if name[0] == 'E' else 'P-Cluster'
|
|
95
|
+
core = e_core if name[0] == 'E' else p_core
|
|
96
|
+
core.append(cpu["cpu"])
|
|
97
|
+
cpu_metric_dict[name + str(cpu["cpu"]) + "_freq_Mhz"] = int(cpu["freq_hz"] / (1e6))
|
|
98
|
+
cpu_metric_dict[name + str(cpu["cpu"]) + "_active"] = int((1 - cpu["idle_ratio"]) * 100)
|
|
99
|
+
if name[0] == 'E':
|
|
100
|
+
e_total_idle_ratio += cluster["down_ratio"] + (1 - cluster["down_ratio"]) * (cpu["idle_ratio"] + cpu["down_ratio"])
|
|
101
|
+
e_core_count += 1
|
|
102
|
+
else:
|
|
103
|
+
p_total_idle_ratio += cluster["down_ratio"] + (1 - cluster["down_ratio"]) * (cpu["idle_ratio"] + cpu["down_ratio"])
|
|
104
|
+
p_core_count += 1
|
|
105
|
+
|
|
106
|
+
cpu_metric_dict["E-Cluster_active"] = int((1 - e_total_idle_ratio/e_core_count)*100)
|
|
107
|
+
cpu_metric_dict["P-Cluster_active"] = int((1 - p_total_idle_ratio/p_core_count)*100)
|
|
108
|
+
cpu_metric_dict["e_core"] = e_core
|
|
109
|
+
cpu_metric_dict["p_core"] = p_core
|
|
110
|
+
if "E-Cluster_active" not in cpu_metric_dict:
|
|
111
|
+
# M1 Ultra
|
|
112
|
+
cpu_metric_dict["E-Cluster_active"] = int(
|
|
113
|
+
(cpu_metric_dict["E0-Cluster_active"] + cpu_metric_dict["E1-Cluster_active"])/2)
|
|
114
|
+
if "E-Cluster_freq_Mhz" not in cpu_metric_dict:
|
|
115
|
+
# M1 Ultra
|
|
116
|
+
cpu_metric_dict["E-Cluster_freq_Mhz"] = max(
|
|
117
|
+
cpu_metric_dict["E0-Cluster_freq_Mhz"], cpu_metric_dict["E1-Cluster_freq_Mhz"])
|
|
118
|
+
if "P-Cluster_active" not in cpu_metric_dict:
|
|
119
|
+
if "P2-Cluster_active" in cpu_metric_dict:
|
|
120
|
+
# M1 Ultra
|
|
121
|
+
cpu_metric_dict["P-Cluster_active"] = int((cpu_metric_dict["P0-Cluster_active"] + cpu_metric_dict["P1-Cluster_active"] +
|
|
122
|
+
cpu_metric_dict["P2-Cluster_active"] + cpu_metric_dict["P3-Cluster_active"]) / 4)
|
|
123
|
+
else:
|
|
124
|
+
cpu_metric_dict["P-Cluster_active"] = int(
|
|
125
|
+
(cpu_metric_dict["P0-Cluster_active"] + cpu_metric_dict["P1-Cluster_active"])/2)
|
|
126
|
+
if "P-Cluster_freq_Mhz" not in cpu_metric_dict:
|
|
127
|
+
if "P2-Cluster_freq_Mhz" in cpu_metric_dict:
|
|
128
|
+
# M1 Ultra
|
|
129
|
+
freqs = [
|
|
130
|
+
cpu_metric_dict["P0-Cluster_freq_Mhz"],
|
|
131
|
+
cpu_metric_dict["P1-Cluster_freq_Mhz"],
|
|
132
|
+
cpu_metric_dict["P2-Cluster_freq_Mhz"],
|
|
133
|
+
cpu_metric_dict["P3-Cluster_freq_Mhz"]]
|
|
134
|
+
cpu_metric_dict["P-Cluster_freq_Mhz"] = max(freqs)
|
|
135
|
+
else:
|
|
136
|
+
cpu_metric_dict["P-Cluster_freq_Mhz"] = max(
|
|
137
|
+
cpu_metric_dict["P0-Cluster_freq_Mhz"], cpu_metric_dict["P1-Cluster_freq_Mhz"])
|
|
138
|
+
# power
|
|
139
|
+
cpu_metric_dict["ane_W"] = cpu_metrics["ane_energy"]/1000
|
|
140
|
+
#cpu_metric_dict["dram_W"] = cpu_metrics["dram_energy"]/1000
|
|
141
|
+
cpu_metric_dict["cpu_W"] = cpu_metrics["cpu_energy"]/1000
|
|
142
|
+
cpu_metric_dict["gpu_W"] = cpu_metrics["gpu_energy"]/1000
|
|
143
|
+
cpu_metric_dict["package_W"] = cpu_metrics["combined_power"]/1000
|
|
144
|
+
return cpu_metric_dict
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def parse_gpu_metrics(powermetrics_parse):
|
|
148
|
+
gpu_metrics = powermetrics_parse["gpu"]
|
|
149
|
+
gpu_metrics_dict = {
|
|
150
|
+
"freq_MHz": int(gpu_metrics["freq_hz"]),
|
|
151
|
+
"active": int((1 - gpu_metrics["idle_ratio"])*100),
|
|
152
|
+
}
|
|
153
|
+
return gpu_metrics_dict
|
|
154
|
+
|
|
155
|
+
def parse_disk_metrics(powermetrics_parse):
|
|
156
|
+
disk_metrics = powermetrics_parse["disk"]
|
|
157
|
+
disk_metrics_dict = {
|
|
158
|
+
"read_iops": int(disk_metrics["rops_per_s"]),
|
|
159
|
+
"write_iops": int(disk_metrics["wops_per_s"]),
|
|
160
|
+
"read_Bps": int(disk_metrics["rbytes_per_s"]),
|
|
161
|
+
"write_Bps": int(disk_metrics["wbytes_per_s"]),
|
|
162
|
+
}
|
|
163
|
+
return disk_metrics_dict
|
|
164
|
+
|
|
165
|
+
def parse_network_metrics(powermetrics_parse):
|
|
166
|
+
network_metrics = powermetrics_parse["network"]
|
|
167
|
+
network_metrics_dict = {
|
|
168
|
+
"out_Bps": int(network_metrics["obyte_rate"]),
|
|
169
|
+
"in_Bps": int(network_metrics["ibyte_rate"]),
|
|
170
|
+
}
|
|
171
|
+
return network_metrics_dict
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import glob
|
|
3
|
+
import subprocess
|
|
4
|
+
from subprocess import PIPE
|
|
5
|
+
import psutil
|
|
6
|
+
from .parsers import *
|
|
7
|
+
import plistlib
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def parse_powermetrics(path='/tmp/asitop_powermetrics', timecode="0"):
|
|
11
|
+
data = None
|
|
12
|
+
try:
|
|
13
|
+
with open(path+timecode, 'rb') as fp:
|
|
14
|
+
data = fp.read()
|
|
15
|
+
data = data.split(b'\x00')
|
|
16
|
+
powermetrics_parse = plistlib.loads(data[-1])
|
|
17
|
+
thermal_pressure = parse_thermal_pressure(powermetrics_parse)
|
|
18
|
+
cpu_metrics_dict = parse_cpu_metrics(powermetrics_parse)
|
|
19
|
+
gpu_metrics_dict = parse_gpu_metrics(powermetrics_parse)
|
|
20
|
+
disk_metrics_dict = parse_disk_metrics(powermetrics_parse)
|
|
21
|
+
network_metrics_dict = parse_network_metrics(powermetrics_parse)
|
|
22
|
+
#bandwidth_metrics = parse_bandwidth_metrics(powermetrics_parse)
|
|
23
|
+
bandwidth_metrics = None
|
|
24
|
+
timestamp = powermetrics_parse["timestamp"]
|
|
25
|
+
return cpu_metrics_dict, gpu_metrics_dict, thermal_pressure, bandwidth_metrics, disk_metrics_dict, network_metrics_dict, timestamp
|
|
26
|
+
except Exception as e:
|
|
27
|
+
if data:
|
|
28
|
+
if len(data) > 1:
|
|
29
|
+
powermetrics_parse = plistlib.loads(data[-2])
|
|
30
|
+
thermal_pressure = parse_thermal_pressure(powermetrics_parse)
|
|
31
|
+
cpu_metrics_dict = parse_cpu_metrics(powermetrics_parse)
|
|
32
|
+
gpu_metrics_dict = parse_gpu_metrics(powermetrics_parse)
|
|
33
|
+
disk_metrics_dict = parse_disk_metrics(powermetrics_parse)
|
|
34
|
+
network_metrics_dict = parse_network_metrics(powermetrics_parse)
|
|
35
|
+
#bandwidth_metrics = parse_bandwidth_metrics(powermetrics_parse)
|
|
36
|
+
bandwidth_metrics = None
|
|
37
|
+
timestamp = powermetrics_parse["timestamp"]
|
|
38
|
+
return cpu_metrics_dict, gpu_metrics_dict, thermal_pressure, bandwidth_metrics, disk_metrics_dict, network_metrics_dict, timestamp
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def clear_console():
|
|
43
|
+
command = 'clear'
|
|
44
|
+
os.system(command)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def convert_to_GB(value):
|
|
48
|
+
return round(value/1024/1024/1024, 1)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def run_powermetrics_process(timecode, nice=10, interval=1000):
|
|
52
|
+
#ver, *_ = platform.mac_ver()
|
|
53
|
+
#major_ver = int(ver.split(".")[0])
|
|
54
|
+
#for tmpf in glob.glob("/tmp/asitop_powermetrics*"):
|
|
55
|
+
# os.remove(tmpf)
|
|
56
|
+
output_file_flag = "-o"
|
|
57
|
+
command = " ".join([
|
|
58
|
+
"sudo nice -n",
|
|
59
|
+
str(nice),
|
|
60
|
+
"powermetrics",
|
|
61
|
+
"--samplers cpu_power,gpu_power,thermal,network,disk",
|
|
62
|
+
output_file_flag,
|
|
63
|
+
"/tmp/asitop_powermetrics"+timecode,
|
|
64
|
+
"-f plist",
|
|
65
|
+
"-i",
|
|
66
|
+
str(interval)
|
|
67
|
+
])
|
|
68
|
+
process = subprocess.Popen(command.split(" "), stdin=PIPE, stdout=PIPE)
|
|
69
|
+
return process
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_ram_metrics_dict():
|
|
73
|
+
ram_metrics = psutil.virtual_memory()
|
|
74
|
+
swap_metrics = psutil.swap_memory()
|
|
75
|
+
total_GB = convert_to_GB(ram_metrics.total)
|
|
76
|
+
free_GB = convert_to_GB(ram_metrics.available)
|
|
77
|
+
used_GB = convert_to_GB(ram_metrics.total-ram_metrics.available)
|
|
78
|
+
swap_total_GB = convert_to_GB(swap_metrics.total)
|
|
79
|
+
swap_used_GB = convert_to_GB(swap_metrics.used)
|
|
80
|
+
swap_free_GB = convert_to_GB(swap_metrics.total-swap_metrics.used)
|
|
81
|
+
if swap_total_GB > 0:
|
|
82
|
+
swap_free_percent = int(100-(swap_free_GB/swap_total_GB*100))
|
|
83
|
+
else:
|
|
84
|
+
swap_free_percent = None
|
|
85
|
+
ram_metrics_dict = {
|
|
86
|
+
"total_GB": round(total_GB, 1),
|
|
87
|
+
"free_GB": round(free_GB, 1),
|
|
88
|
+
"used_GB": round(used_GB, 1),
|
|
89
|
+
"free_percent": int(100-(ram_metrics.available/ram_metrics.total*100)),
|
|
90
|
+
"swap_total_GB": swap_total_GB,
|
|
91
|
+
"swap_used_GB": swap_used_GB,
|
|
92
|
+
"swap_free_GB": swap_free_GB,
|
|
93
|
+
"swap_free_percent": swap_free_percent,
|
|
94
|
+
}
|
|
95
|
+
return ram_metrics_dict
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_cpu_info():
|
|
99
|
+
cpu_info = os.popen('sysctl -a | grep machdep.cpu').read()
|
|
100
|
+
cpu_info_lines = cpu_info.split("\n")
|
|
101
|
+
data_fields = ["machdep.cpu.brand_string", "machdep.cpu.core_count"]
|
|
102
|
+
cpu_info_dict = {}
|
|
103
|
+
for l in cpu_info_lines:
|
|
104
|
+
for h in data_fields:
|
|
105
|
+
if h in l:
|
|
106
|
+
value = l.split(":")[1].strip()
|
|
107
|
+
cpu_info_dict[h] = value
|
|
108
|
+
return cpu_info_dict
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_core_counts():
|
|
112
|
+
cores_info = os.popen('sysctl -a | grep hw.perflevel').read()
|
|
113
|
+
cores_info_lines = cores_info.split("\n")
|
|
114
|
+
data_fields = ["hw.perflevel0.logicalcpu", "hw.perflevel1.logicalcpu"]
|
|
115
|
+
cores_info_dict = {}
|
|
116
|
+
for l in cores_info_lines:
|
|
117
|
+
for h in data_fields:
|
|
118
|
+
if h in l:
|
|
119
|
+
value = int(l.split(":")[1].strip())
|
|
120
|
+
cores_info_dict[h] = value
|
|
121
|
+
return cores_info_dict
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_gpu_cores():
|
|
125
|
+
try:
|
|
126
|
+
cores = os.popen(
|
|
127
|
+
"system_profiler -detailLevel basic SPDisplaysDataType | grep 'Total Number of Cores'").read()
|
|
128
|
+
cores = int(cores.split(": ")[-1])
|
|
129
|
+
except:
|
|
130
|
+
cores = "?"
|
|
131
|
+
return cores
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_soc_info():
|
|
135
|
+
cpu_info_dict = get_cpu_info()
|
|
136
|
+
core_counts_dict = get_core_counts()
|
|
137
|
+
try:
|
|
138
|
+
e_core_count = core_counts_dict["hw.perflevel1.logicalcpu"]
|
|
139
|
+
p_core_count = core_counts_dict["hw.perflevel0.logicalcpu"]
|
|
140
|
+
except:
|
|
141
|
+
e_core_count = "?"
|
|
142
|
+
p_core_count = "?"
|
|
143
|
+
soc_info = {
|
|
144
|
+
"name": cpu_info_dict["machdep.cpu.brand_string"],
|
|
145
|
+
"core_count": int(cpu_info_dict["machdep.cpu.core_count"]),
|
|
146
|
+
"cpu_max_power": None,
|
|
147
|
+
"gpu_max_power": None,
|
|
148
|
+
"cpu_max_bw": None,
|
|
149
|
+
"gpu_max_bw": None,
|
|
150
|
+
"e_core_count": e_core_count,
|
|
151
|
+
"p_core_count": p_core_count,
|
|
152
|
+
"gpu_core_count": get_gpu_cores()
|
|
153
|
+
}
|
|
154
|
+
# TDP (power)
|
|
155
|
+
if soc_info["name"] == "Apple M1 Max":
|
|
156
|
+
soc_info["cpu_max_power"] = 30
|
|
157
|
+
soc_info["gpu_max_power"] = 60
|
|
158
|
+
elif soc_info["name"] == "Apple M1 Pro":
|
|
159
|
+
soc_info["cpu_max_power"] = 30
|
|
160
|
+
soc_info["gpu_max_power"] = 30
|
|
161
|
+
elif soc_info["name"] == "Apple M1":
|
|
162
|
+
soc_info["cpu_max_power"] = 20
|
|
163
|
+
soc_info["gpu_max_power"] = 20
|
|
164
|
+
elif soc_info["name"] == "Apple M1 Ultra":
|
|
165
|
+
soc_info["cpu_max_power"] = 60
|
|
166
|
+
soc_info["gpu_max_power"] = 120
|
|
167
|
+
elif soc_info["name"] == "Apple M2":
|
|
168
|
+
soc_info["cpu_max_power"] = 25
|
|
169
|
+
soc_info["gpu_max_power"] = 15
|
|
170
|
+
else:
|
|
171
|
+
soc_info["cpu_max_power"] = 20
|
|
172
|
+
soc_info["gpu_max_power"] = 20
|
|
173
|
+
# bandwidth
|
|
174
|
+
if soc_info["name"] == "Apple M1 Max":
|
|
175
|
+
soc_info["cpu_max_bw"] = 250
|
|
176
|
+
soc_info["gpu_max_bw"] = 400
|
|
177
|
+
elif soc_info["name"] == "Apple M1 Pro":
|
|
178
|
+
soc_info["cpu_max_bw"] = 200
|
|
179
|
+
soc_info["gpu_max_bw"] = 200
|
|
180
|
+
elif soc_info["name"] == "Apple M1":
|
|
181
|
+
soc_info["cpu_max_bw"] = 70
|
|
182
|
+
soc_info["gpu_max_bw"] = 70
|
|
183
|
+
elif soc_info["name"] == "Apple M1 Ultra":
|
|
184
|
+
soc_info["cpu_max_bw"] = 500
|
|
185
|
+
soc_info["gpu_max_bw"] = 800
|
|
186
|
+
elif soc_info["name"] == "Apple M2":
|
|
187
|
+
soc_info["cpu_max_bw"] = 100
|
|
188
|
+
soc_info["gpu_max_bw"] = 100
|
|
189
|
+
else:
|
|
190
|
+
soc_info["cpu_max_bw"] = 70
|
|
191
|
+
soc_info["gpu_max_bw"] = 70
|
|
192
|
+
return soc_info
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: macpm
|
|
3
|
+
Version: 0.12
|
|
4
|
+
Summary: Performance monitoring CLI tool for Apple Silicon
|
|
5
|
+
Home-page: https://github.com/visualcjy/macpm
|
|
6
|
+
Author: Jinyu Chen
|
|
7
|
+
Author-email: visualcjy@mail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: macpm
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: dashing
|
|
16
|
+
Requires-Dist: psutil
|
|
17
|
+
Requires-Dist: humanize
|
|
18
|
+
|
|
19
|
+
Performance monitoring CLI tool for Apple Silicon
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
macpm/__init__.py
|
|
5
|
+
macpm/macpm.py
|
|
6
|
+
macpm/parsers.py
|
|
7
|
+
macpm/utils.py
|
|
8
|
+
macpm.egg-info/PKG-INFO
|
|
9
|
+
macpm.egg-info/SOURCES.txt
|
|
10
|
+
macpm.egg-info/dependency_links.txt
|
|
11
|
+
macpm.egg-info/entry_points.txt
|
|
12
|
+
macpm.egg-info/not-zip-safe
|
|
13
|
+
macpm.egg-info/requires.txt
|
|
14
|
+
macpm.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
macpm
|
macpm-0.12/setup.cfg
ADDED
macpm-0.12/setup.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
long_description = 'Performance monitoring CLI tool for Apple Silicon'
|
|
4
|
+
|
|
5
|
+
setup(
|
|
6
|
+
name='macpm',
|
|
7
|
+
version='0.12',
|
|
8
|
+
author='Jinyu Chen',
|
|
9
|
+
author_email='visualcjy@mail.com',
|
|
10
|
+
url='https://github.com/visualcjy/macpm',
|
|
11
|
+
description='Performance monitoring CLI tool for Apple Silicon',
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
license='MIT',
|
|
15
|
+
packages=find_packages(),
|
|
16
|
+
package_dir={'macpm':'macpm'},
|
|
17
|
+
entry_points={
|
|
18
|
+
'console_scripts': [
|
|
19
|
+
'macpm = macpm.macpm:main'
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
classifiers=(
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Operating System :: MacOS",
|
|
26
|
+
),
|
|
27
|
+
keywords='macpm',
|
|
28
|
+
install_requires=[
|
|
29
|
+
"dashing",
|
|
30
|
+
"psutil",
|
|
31
|
+
"humanize",
|
|
32
|
+
],
|
|
33
|
+
zip_safe=False
|
|
34
|
+
)
|