msiltop 0.2.2__py3-none-any.whl → 0.2.3__py3-none-any.whl
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.
- msiltop/msiltop.py +77 -8
- msiltop/utils.py +1 -1
- {msiltop-0.2.2.dist-info → msiltop-0.2.3.dist-info}/METADATA +1 -1
- msiltop-0.2.3.dist-info/RECORD +10 -0
- msiltop-0.2.2.dist-info/RECORD +0 -10
- {msiltop-0.2.2.dist-info → msiltop-0.2.3.dist-info}/WHEEL +0 -0
- {msiltop-0.2.2.dist-info → msiltop-0.2.3.dist-info}/entry_points.txt +0 -0
- {msiltop-0.2.2.dist-info → msiltop-0.2.3.dist-info}/licenses/LICENSE +0 -0
msiltop/msiltop.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import time
|
|
2
2
|
import click
|
|
3
3
|
import asyncio
|
|
4
|
+
import json
|
|
4
5
|
from collections import deque
|
|
5
6
|
from typing import Optional
|
|
6
7
|
|
|
@@ -410,7 +411,7 @@ class FluidTopApp(App):
|
|
|
410
411
|
|
|
411
412
|
# CSS is set dynamically in _apply_theme method
|
|
412
413
|
|
|
413
|
-
def __init__(self, interval: int, theme: str, avg: int, max_count: int, show_cores: bool = False):
|
|
414
|
+
def __init__(self, interval: int, theme: str, avg: int, max_count: int, show_cores: bool = False, thermal_bell: bool = False):
|
|
414
415
|
self.interval = interval
|
|
415
416
|
# Store theme temporarily, don't assign to self.theme yet
|
|
416
417
|
theme_value = theme
|
|
@@ -425,6 +426,7 @@ class FluidTopApp(App):
|
|
|
425
426
|
self.avg = avg
|
|
426
427
|
self.max_count = max_count
|
|
427
428
|
self.show_cores = show_cores
|
|
429
|
+
self.thermal_bell = thermal_bell
|
|
428
430
|
|
|
429
431
|
# Initialize metrics storage
|
|
430
432
|
# No longer tracking averages or peaks
|
|
@@ -440,6 +442,19 @@ class FluidTopApp(App):
|
|
|
440
442
|
self.timecode = None
|
|
441
443
|
self.last_timestamp = 0
|
|
442
444
|
self.count = 0
|
|
445
|
+
self.last_thermal_pressure = None
|
|
446
|
+
self.session_start = time.time()
|
|
447
|
+
self.summary_samples = 0
|
|
448
|
+
self.cpu_usage_sum = 0.0
|
|
449
|
+
self.gpu_usage_sum = 0.0
|
|
450
|
+
self.ram_usage_sum = 0.0
|
|
451
|
+
self.cpu_usage_peak = 0.0
|
|
452
|
+
self.gpu_usage_peak = 0.0
|
|
453
|
+
self.ram_usage_peak = 0.0
|
|
454
|
+
self.cpu_power_peak = 0.0
|
|
455
|
+
self.gpu_power_peak = 0.0
|
|
456
|
+
self.ane_power_peak = 0.0
|
|
457
|
+
self.thermal_pressure_max = "Nominal"
|
|
443
458
|
|
|
444
459
|
# SoC info
|
|
445
460
|
self.soc_info_dict = get_soc_info()
|
|
@@ -587,6 +602,13 @@ class FluidTopApp(App):
|
|
|
587
602
|
height: 100%;
|
|
588
603
|
}}
|
|
589
604
|
|
|
605
|
+
#thermal-label {{
|
|
606
|
+
width: auto;
|
|
607
|
+
text-align: right;
|
|
608
|
+
color: $text;
|
|
609
|
+
padding: 0 1;
|
|
610
|
+
}}
|
|
611
|
+
|
|
590
612
|
#timestamp-label {{
|
|
591
613
|
width: auto;
|
|
592
614
|
text-align: right;
|
|
@@ -617,6 +639,7 @@ class FluidTopApp(App):
|
|
|
617
639
|
yield Label("Initializing...", id="system-info-label")
|
|
618
640
|
# Timestamp on the right
|
|
619
641
|
with Horizontal(id="controls-buttons"):
|
|
642
|
+
yield Label("Thermal Pressure: --", id="thermal-label")
|
|
620
643
|
yield Label("", id="timestamp-label")
|
|
621
644
|
|
|
622
645
|
# Usage Charts section
|
|
@@ -752,6 +775,7 @@ class FluidTopApp(App):
|
|
|
752
775
|
# Update title to show both CPU types (smoothed values)
|
|
753
776
|
combined_title = f"E-CPU: {e_cpu_usage}% | P-CPU: {p_cpu_usage}%"
|
|
754
777
|
cpu_combined_chart.update_title(combined_title)
|
|
778
|
+
combined_cpu_usage = (e_cpu_usage + p_cpu_usage) / 2
|
|
755
779
|
|
|
756
780
|
# Update GPU usage chart
|
|
757
781
|
gpu_chart = self.query_one("#gpu-usage-chart", UsageChart)
|
|
@@ -763,7 +787,7 @@ class FluidTopApp(App):
|
|
|
763
787
|
# Update RAM usage chart with swap information
|
|
764
788
|
ram_metrics_dict = get_ram_metrics_dict()
|
|
765
789
|
ram_chart = self.query_one("#ram-usage-chart", UsageChart)
|
|
766
|
-
ram_usage_percent =
|
|
790
|
+
ram_usage_percent = ram_metrics_dict["used_percent"]
|
|
767
791
|
|
|
768
792
|
# Include swap information in the title
|
|
769
793
|
if ram_metrics_dict["swap_total_GB"] < 0.1:
|
|
@@ -774,6 +798,14 @@ class FluidTopApp(App):
|
|
|
774
798
|
ram_chart.update_title(ram_title)
|
|
775
799
|
ram_chart.add_data(ram_usage_percent, render=should_render)
|
|
776
800
|
|
|
801
|
+
self.summary_samples += 1
|
|
802
|
+
self.cpu_usage_sum += combined_cpu_usage
|
|
803
|
+
self.gpu_usage_sum += gpu_usage
|
|
804
|
+
self.ram_usage_sum += ram_usage_percent
|
|
805
|
+
self.cpu_usage_peak = max(self.cpu_usage_peak, combined_cpu_usage)
|
|
806
|
+
self.gpu_usage_peak = max(self.gpu_usage_peak, gpu_usage)
|
|
807
|
+
self.ram_usage_peak = max(self.ram_usage_peak, ram_usage_percent)
|
|
808
|
+
|
|
777
809
|
if self.show_cores:
|
|
778
810
|
await self.update_core_usage_charts(cpu_metrics_dict, should_render)
|
|
779
811
|
|
|
@@ -817,6 +849,10 @@ class FluidTopApp(App):
|
|
|
817
849
|
cpu_power_W = cpu_metrics_dict["cpu_W"]
|
|
818
850
|
gpu_power_W = cpu_metrics_dict["gpu_W"]
|
|
819
851
|
ane_power_W = cpu_metrics_dict["ane_W"]
|
|
852
|
+
|
|
853
|
+
self.cpu_power_peak = max(self.cpu_power_peak, cpu_power_W)
|
|
854
|
+
self.gpu_power_peak = max(self.gpu_power_peak, gpu_power_W)
|
|
855
|
+
self.ane_power_peak = max(self.ane_power_peak, ane_power_W)
|
|
820
856
|
|
|
821
857
|
# Update energy consumption for each component (watts * seconds = watt-seconds)
|
|
822
858
|
self.total_energy_consumed += package_power_W * self.interval
|
|
@@ -866,10 +902,19 @@ class FluidTopApp(App):
|
|
|
866
902
|
ane_power_chart.add_data(ane_power_percent, render=should_render)
|
|
867
903
|
|
|
868
904
|
# Update system info label with total power and thermal info
|
|
869
|
-
thermal_throttle = "no" if thermal_pressure == "Nominal" else "yes"
|
|
870
905
|
total_energy_display = format_energy(self.total_energy_consumed)
|
|
871
|
-
system_info = f"{self.soc_info_dict['name']} ({self.soc_info_dict['e_core_count']}E+{self.soc_info_dict['p_core_count']}P+{self.soc_info_dict['gpu_core_count']}GPU) | Total: {package_power_W:.1f}W ({total_energy_display})
|
|
906
|
+
system_info = f"{self.soc_info_dict['name']} ({self.soc_info_dict['e_core_count']}E+{self.soc_info_dict['p_core_count']}P+{self.soc_info_dict['gpu_core_count']}GPU) | Total: {package_power_W:.1f}W ({total_energy_display})"
|
|
872
907
|
self.query_one("#system-info-label", Label).update(system_info)
|
|
908
|
+
self.query_one("#thermal-label", Label).update(f"Thermal Pressure: {thermal_pressure}")
|
|
909
|
+
thermal_levels = ["Nominal", "Moderate", "Heavy", "Critical"]
|
|
910
|
+
if thermal_pressure in thermal_levels and thermal_levels.index(thermal_pressure) > thermal_levels.index(self.thermal_pressure_max):
|
|
911
|
+
self.thermal_pressure_max = thermal_pressure
|
|
912
|
+
if self.thermal_bell and thermal_pressure != "Nominal" and thermal_pressure != self.last_thermal_pressure:
|
|
913
|
+
try:
|
|
914
|
+
print("\a", end="", flush=True)
|
|
915
|
+
except Exception:
|
|
916
|
+
pass
|
|
917
|
+
self.last_thermal_pressure = thermal_pressure
|
|
873
918
|
|
|
874
919
|
async def update_timestamp(self):
|
|
875
920
|
"""Update the timestamp display"""
|
|
@@ -885,6 +930,28 @@ class FluidTopApp(App):
|
|
|
885
930
|
self.powermetrics_process.terminate()
|
|
886
931
|
except:
|
|
887
932
|
pass
|
|
933
|
+
duration_sec = max(0, time.time() - self.session_start)
|
|
934
|
+
if self.summary_samples > 0:
|
|
935
|
+
avg_cpu = self.cpu_usage_sum / self.summary_samples
|
|
936
|
+
avg_gpu = self.gpu_usage_sum / self.summary_samples
|
|
937
|
+
avg_ram = self.ram_usage_sum / self.summary_samples
|
|
938
|
+
else:
|
|
939
|
+
avg_cpu = avg_gpu = avg_ram = 0.0
|
|
940
|
+
summary = {
|
|
941
|
+
"duration_sec": round(duration_sec, 1),
|
|
942
|
+
"avg_cpu_percent": round(avg_cpu, 2),
|
|
943
|
+
"avg_gpu_percent": round(avg_gpu, 2),
|
|
944
|
+
"avg_ram_percent": round(avg_ram, 2),
|
|
945
|
+
"peak_cpu_percent": round(self.cpu_usage_peak, 2),
|
|
946
|
+
"peak_gpu_percent": round(self.gpu_usage_peak, 2),
|
|
947
|
+
"peak_ram_percent": round(self.ram_usage_peak, 2),
|
|
948
|
+
"peak_cpu_w": round(self.cpu_power_peak, 2),
|
|
949
|
+
"peak_gpu_w": round(self.gpu_power_peak, 2),
|
|
950
|
+
"peak_ane_w": round(self.ane_power_peak, 2),
|
|
951
|
+
"total_energy_wh": round(self.total_energy_consumed / 3600, 3),
|
|
952
|
+
"thermal_pressure_max": self.thermal_pressure_max,
|
|
953
|
+
}
|
|
954
|
+
print("\nSession summary:\n" + json.dumps(summary, indent=2))
|
|
888
955
|
|
|
889
956
|
@click.command()
|
|
890
957
|
@click.option('--interval', type=float, default=1.0,
|
|
@@ -897,19 +964,21 @@ class FluidTopApp(App):
|
|
|
897
964
|
help='Max show count to restart powermetrics')
|
|
898
965
|
@click.option('--show_cores', is_flag=True, default=False,
|
|
899
966
|
help='Show per-core CPU usage charts')
|
|
900
|
-
|
|
967
|
+
@click.option('--thermal_bell', is_flag=True, default=False,
|
|
968
|
+
help='Ring terminal bell when thermal throttling is detected')
|
|
969
|
+
def main(interval, theme, avg, max_count, show_cores, thermal_bell):
|
|
901
970
|
"""msiltop: Performance monitoring CLI tool for Apple Silicon"""
|
|
902
|
-
return _main_logic(interval, theme, avg, max_count, show_cores=show_cores)
|
|
971
|
+
return _main_logic(interval, theme, avg, max_count, show_cores=show_cores, thermal_bell=thermal_bell)
|
|
903
972
|
|
|
904
973
|
|
|
905
|
-
def _main_logic(interval, theme, avg, max_count, show_cores=False):
|
|
974
|
+
def _main_logic(interval, theme, avg, max_count, show_cores=False, thermal_bell=False):
|
|
906
975
|
"""Main logic using Textual app"""
|
|
907
976
|
print("\nMSILTOP - Performance monitoring CLI tool for Apple Silicon")
|
|
908
977
|
print("Get help at `https://github.com/pratikdevnani/msiltop`")
|
|
909
978
|
print("P.S. You are recommended to run MSILTOP with `sudo msiltop`\n")
|
|
910
979
|
|
|
911
980
|
# Create and run the Textual app
|
|
912
|
-
app = FluidTopApp(interval, theme, avg, max_count, show_cores=show_cores)
|
|
981
|
+
app = FluidTopApp(interval, theme, avg, max_count, show_cores=show_cores, thermal_bell=thermal_bell)
|
|
913
982
|
try:
|
|
914
983
|
app.run()
|
|
915
984
|
except KeyboardInterrupt:
|
msiltop/utils.py
CHANGED
|
@@ -81,7 +81,7 @@ def get_ram_metrics_dict():
|
|
|
81
81
|
"total_GB": round(total_GB, 1),
|
|
82
82
|
"free_GB": round(free_GB, 1),
|
|
83
83
|
"used_GB": round(used_GB, 1),
|
|
84
|
-
"
|
|
84
|
+
"used_percent": int(ram_metrics.percent),
|
|
85
85
|
"swap_total_GB": swap_total_GB,
|
|
86
86
|
"swap_used_GB": swap_used_GB,
|
|
87
87
|
"swap_free_GB": swap_free_GB,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: msiltop
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Real-time macOS hardware performance monitoring for Apple Silicon (M1/M2/M3/M4) with AI workload focus - enhanced asitop alternative
|
|
5
5
|
Project-URL: Homepage, https://github.com/pratikdevnani/msiltop
|
|
6
6
|
Project-URL: Repository, https://github.com/pratikdevnani/msiltop
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
msiltop/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
msiltop/msiltop.py,sha256=xokVTnlTRkdciw-DGSPUi6hkkcsJqiHi9Eg2eJBJorc,41574
|
|
3
|
+
msiltop/parsers.py,sha256=WTrI09rjKVKP7wxv3dWckn-EgXG3Qr053BTb61iYqV4,2701
|
|
4
|
+
msiltop/soc_info.json,sha256=EPSMp7pGZyhE3Z8Pqc1VOqxTLM3k4tZq3ek_ly2hcwg,990
|
|
5
|
+
msiltop/utils.py,sha256=HLskmiEQoja8gT59_2VX07poQmCvBvSsL7oLMz5Bg9g,5236
|
|
6
|
+
msiltop-0.2.3.dist-info/METADATA,sha256=iXayAvkLDsm6Le8eByGvCE5ZSZY-gydr_6GeidRmwBw,9485
|
|
7
|
+
msiltop-0.2.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
+
msiltop-0.2.3.dist-info/entry_points.txt,sha256=7q0YR2ZtNUtndkgfCHP8rvO3Elg6nEqGxUXIk-A7zCk,49
|
|
9
|
+
msiltop-0.2.3.dist-info/licenses/LICENSE,sha256=Ks65kWYKLsrlfXMglBPLfcXvRDpNo6miAedt3h-lICs,1103
|
|
10
|
+
msiltop-0.2.3.dist-info/RECORD,,
|
msiltop-0.2.2.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
msiltop/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
msiltop/msiltop.py,sha256=ESm4BlF1QwQ3BmOzvuPNsxPhNTcp9rOYp8joBte35wg,38298
|
|
3
|
-
msiltop/parsers.py,sha256=WTrI09rjKVKP7wxv3dWckn-EgXG3Qr053BTb61iYqV4,2701
|
|
4
|
-
msiltop/soc_info.json,sha256=EPSMp7pGZyhE3Z8Pqc1VOqxTLM3k4tZq3ek_ly2hcwg,990
|
|
5
|
-
msiltop/utils.py,sha256=szm69SZNiu_yH_RaTB1B6CSpdlpo-3UFulqOiHD_K64,5266
|
|
6
|
-
msiltop-0.2.2.dist-info/METADATA,sha256=uqELdaHWVoib2te0lBwTtVnvN57yYDzXsxXaLMDuybI,9485
|
|
7
|
-
msiltop-0.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
-
msiltop-0.2.2.dist-info/entry_points.txt,sha256=7q0YR2ZtNUtndkgfCHP8rvO3Elg6nEqGxUXIk-A7zCk,49
|
|
9
|
-
msiltop-0.2.2.dist-info/licenses/LICENSE,sha256=Ks65kWYKLsrlfXMglBPLfcXvRDpNo6miAedt3h-lICs,1103
|
|
10
|
-
msiltop-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|