From e0d4a27d09448180a37c7645f8b85683918c3e10 Mon Sep 17 00:00:00 2001 From: zongyu Date: Thu, 18 Aug 2022 14:22:23 +0800 Subject: [PATCH 1/6] Use another thread to combine the output of powertrics making asitop not need to write file on the filesystem Signed-off-by: Zhang Zongyu --- asitop/asitop.py | 8 +++++--- asitop/utils.py | 40 ++++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/asitop/asitop.py b/asitop/asitop.py index e3f1dd4..aced40a 100644 --- a/asitop/asitop.py +++ b/asitop/asitop.py @@ -144,14 +144,15 @@ def main(): powermetrics_process = run_powermetrics_process(timecode, interval=args.interval * 1000) + queue, _thread = build_enqueue_thread(powermetrics_process.stdout) print("\n[3/3] Waiting for first reading...\n") def get_reading(wait=0.1): - ready = parse_powermetrics(timecode=timecode) + ready = parse_powermetrics(queue) while not ready: time.sleep(wait) - ready = parse_powermetrics(timecode=timecode) + ready = parse_powermetrics(queue) return ready ready = get_reading() @@ -177,8 +178,9 @@ def get_avg(inlist): timecode = str(int(time.time())) powermetrics_process = run_powermetrics_process( timecode, interval=args.interval * 1000) + queue, _thread = build_enqueue_thread(powermetrics_process.stdout) count += 1 - ready = parse_powermetrics(timecode=timecode) + ready = parse_powermetrics(queue) if ready: cpu_metrics_dict, gpu_metrics_dict, thermal_pressure, bandwidth_metrics, timestamp = ready diff --git a/asitop/utils.py b/asitop/utils.py index 02b2d34..285bbc6 100644 --- a/asitop/utils.py +++ b/asitop/utils.py @@ -1,19 +1,19 @@ import os import glob import subprocess +from queue import LifoQueue from subprocess import PIPE +from threading import Thread import psutil from .parsers import * import plistlib -def parse_powermetrics(path='/tmp/asitop_powermetrics', timecode="0"): - data = None +def parse_powermetrics(queue, timecode="0"): try: - with open(path+timecode, 'rb') as fp: - data = fp.read() - data = data.split(b'\x00') - powermetrics_parse = plistlib.loads(data[-1]) + # a Last in First out queue + data = queue.get() + powermetrics_parse = plistlib.loads(data) thermal_pressure = parse_thermal_pressure(powermetrics_parse) cpu_metrics_dict = parse_cpu_metrics(powermetrics_parse) gpu_metrics_dict = parse_gpu_metrics(powermetrics_parse) @@ -21,15 +21,6 @@ def parse_powermetrics(path='/tmp/asitop_powermetrics', timecode="0"): timestamp = powermetrics_parse["timestamp"] return cpu_metrics_dict, gpu_metrics_dict, thermal_pressure, bandwidth_metrics, timestamp except Exception as e: - if data: - if len(data) > 1: - powermetrics_parse = plistlib.loads(data[-2]) - thermal_pressure = parse_thermal_pressure(powermetrics_parse) - cpu_metrics_dict = parse_cpu_metrics(powermetrics_parse) - gpu_metrics_dict = parse_gpu_metrics(powermetrics_parse) - bandwidth_metrics = parse_bandwidth_metrics(powermetrics_parse) - timestamp = powermetrics_parse["timestamp"] - return cpu_metrics_dict, gpu_metrics_dict, thermal_pressure, bandwidth_metrics, timestamp return False @@ -41,20 +32,33 @@ def clear_console(): def convert_to_GB(value): return round(value/1024/1024/1024, 1) +def enqueue_powermetrics(buffered_reader, queue_in): + buffer = b'' + for line in buffered_reader: + # magic string + if line.startswith(b"\x00"): + queue_in.put(buffer) + buffer = line[1:] + else: + buffer += line + +def build_enqueue_thread(powermetrics_stdout): + queue = LifoQueue() + enqueue_thread = Thread(target=enqueue_powermetrics, + args=(powermetrics_stdout, queue)) + enqueue_thread.start() + return queue, enqueue_thread def run_powermetrics_process(timecode, nice=10, interval=1000): #ver, *_ = platform.mac_ver() #major_ver = int(ver.split(".")[0]) for tmpf in glob.glob("/tmp/asitop_powermetrics*"): os.remove(tmpf) - output_file_flag = "-o" command = " ".join([ "sudo nice -n", str(nice), "powermetrics", "--samplers cpu_power,gpu_power,thermal,bandwidth", - output_file_flag, - "/tmp/asitop_powermetrics"+timecode, "-f plist", "-i", str(interval) From 9977cf5f5880d3ce00c41e89b67f37bfa6881265 Mon Sep 17 00:00:00 2001 From: zongyu Date: Thu, 18 Aug 2022 17:30:10 +0800 Subject: [PATCH 2/6] cleanup codes about creating temp files Signed-off-by: zongyu --- asitop/asitop.py | 8 ++------ asitop/utils.py | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/asitop/asitop.py b/asitop/asitop.py index aced40a..80b6131 100644 --- a/asitop/asitop.py +++ b/asitop/asitop.py @@ -140,10 +140,7 @@ def main(): print("\n[2/3] Starting powermetrics process\n") - timecode = str(int(time.time())) - - powermetrics_process = run_powermetrics_process(timecode, - interval=args.interval * 1000) + powermetrics_process = run_powermetrics_process(interval=args.interval * 1000) queue, _thread = build_enqueue_thread(powermetrics_process.stdout) print("\n[3/3] Waiting for first reading...\n") @@ -175,9 +172,8 @@ def get_avg(inlist): if count >= args.max_count: count = 0 powermetrics_process.terminate() - timecode = str(int(time.time())) powermetrics_process = run_powermetrics_process( - timecode, interval=args.interval * 1000) + interval=args.interval * 1000) queue, _thread = build_enqueue_thread(powermetrics_process.stdout) count += 1 ready = parse_powermetrics(queue) diff --git a/asitop/utils.py b/asitop/utils.py index 285bbc6..b448e8a 100644 --- a/asitop/utils.py +++ b/asitop/utils.py @@ -9,7 +9,7 @@ import plistlib -def parse_powermetrics(queue, timecode="0"): +def parse_powermetrics(queue): try: # a Last in First out queue data = queue.get() @@ -33,6 +33,13 @@ def convert_to_GB(value): return round(value/1024/1024/1024, 1) def enqueue_powermetrics(buffered_reader, queue_in): + """ + a helper to convert the output of `powermetrics` + into list of plist strings. + + buffered_reader: stdout of the `powermetrics` process + queue_in: a LIFO queue, will also be provided to the parser + """ buffer = b'' for line in buffered_reader: # magic string @@ -43,17 +50,21 @@ def enqueue_powermetrics(buffered_reader, queue_in): buffer += line def build_enqueue_thread(powermetrics_stdout): + """ + build a thread to run enqueue_powermetrics() + returns: + queue: the LIFO queue, containing plist strings + equeue_thread: the identifier of the thread + """ queue = LifoQueue() enqueue_thread = Thread(target=enqueue_powermetrics, args=(powermetrics_stdout, queue)) enqueue_thread.start() return queue, enqueue_thread -def run_powermetrics_process(timecode, nice=10, interval=1000): +def run_powermetrics_process(nice=10, interval=1000): #ver, *_ = platform.mac_ver() #major_ver = int(ver.split(".")[0]) - for tmpf in glob.glob("/tmp/asitop_powermetrics*"): - os.remove(tmpf) command = " ".join([ "sudo nice -n", str(nice), From 27c0379b2bfb827fae940d1fa2d4fa257a6a7f85 Mon Sep 17 00:00:00 2001 From: zongyu Date: Thu, 18 Aug 2022 17:32:27 +0800 Subject: [PATCH 3/6] remove code about restarting `powermetric` process since temp files do not exists anymore, the memory consumption problem no longer exists. Signed-off-by: zongyu --- asitop/asitop.py | 11 ----------- asitop/utils.py | 1 - 2 files changed, 12 deletions(-) diff --git a/asitop/asitop.py b/asitop/asitop.py index 80b6131..0c954ed 100644 --- a/asitop/asitop.py +++ b/asitop/asitop.py @@ -14,8 +14,6 @@ help='Interval for averaged values (seconds)') parser.add_argument('--show_cores', type=bool, default=False, help='Choose show cores mode') -parser.add_argument('--max_count', type=int, default=0, - help='Max show count to restart powermetrics') args = parser.parse_args() @@ -165,17 +163,8 @@ def get_avg(inlist): clear_console() - count=0 try: while True: - if args.max_count > 0: - if count >= args.max_count: - count = 0 - powermetrics_process.terminate() - powermetrics_process = run_powermetrics_process( - interval=args.interval * 1000) - queue, _thread = build_enqueue_thread(powermetrics_process.stdout) - count += 1 ready = parse_powermetrics(queue) if ready: cpu_metrics_dict, gpu_metrics_dict, thermal_pressure, bandwidth_metrics, timestamp = ready diff --git a/asitop/utils.py b/asitop/utils.py index b448e8a..08c2cda 100644 --- a/asitop/utils.py +++ b/asitop/utils.py @@ -1,5 +1,4 @@ import os -import glob import subprocess from queue import LifoQueue from subprocess import PIPE From 1acead9e3d0216324f5c62b82a04fb5c3a03a31f Mon Sep 17 00:00:00 2001 From: Zhang Zongyu Date: Thu, 1 Sep 2022 14:52:37 +0800 Subject: [PATCH 4/6] suggested by "Codacy Static Code Analysis" Signed-off-by: Zhang Zongyu --- .DS_Store | Bin 6148 -> 6148 bytes asitop/asitop.py | 7 ++++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.DS_Store b/.DS_Store index c8b0e235afe667497b7ab5513d1c8c819f61e4a0..31565a11ef4f0cd5dca38b861bad396aaccdafab 100644 GIT binary patch delta 51 zcmZoMXfc@J&nU1lU^g?Pz+@hl>dow|eTyW?#+T6Vl120cUvfNJ%&t%6oyoW#GG`);N<+=0tPTJGz3yevbp&# zE=f80NkCDK<0sZ;+x$7~h^``qfQo_)WE&O$wQp`?>1V9xXGmly20EjJA)lcD=%7@F zbRg6PisUh*G328 Date: Thu, 1 Sep 2022 14:54:49 +0800 Subject: [PATCH 5/6] asitop/asitop.py: no need to sleep during the main loop Waiting for the queue to be filled by utils.enqueue_powermetrics() implies that we need to sleep for args.interval seconds, so we do not need extra lines to sleep between `powermetrcis` outputs. Signed-off-by: Zhang Zongyu --- asitop/asitop.py | 1 - 1 file changed, 1 deletion(-) diff --git a/asitop/asitop.py b/asitop/asitop.py index f3fd3b4..81ccc7f 100644 --- a/asitop/asitop.py +++ b/asitop/asitop.py @@ -388,7 +388,6 @@ def get_avg(inlist): ui.display() - time.sleep(args.interval) except KeyboardInterrupt: print("Stopping...") From b5fe4049dbdc5897f7c10c6c8a5a40ef8d695899 Mon Sep 17 00:00:00 2001 From: Zhang Zongyu Date: Mon, 28 Aug 2023 12:12:00 +0800 Subject: [PATCH 6/6] Merge branch 'main' into pipe --- asitop/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/asitop/utils.py b/asitop/utils.py index 968a69c..5558fee 100644 --- a/asitop/utils.py +++ b/asitop/utils.py @@ -80,8 +80,6 @@ def run_powermetrics_process(nice=10, interval=1000): str(nice), "powermetrics", "--samplers cpu_power,gpu_power,thermal", - output_file_flag, - "/tmp/asitop_powermetrics"+timecode, "-f plist", "-i", str(interval)