From 50cdee1b70d7bcd3d52ca769fef5333ce436d4ea Mon Sep 17 00:00:00 2001 From: Krzysztof Wasielewski Date: Thu, 26 Oct 2023 12:22:06 +0200 Subject: [PATCH] Added cache and tmpfs to graph --- common.py | 2 +- graph.py | 27 +++++++++++++++++++++++---- sargraph.py | 20 +++++++++++--------- watch.py | 23 +++++++++++++++-------- 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/common.py b/common.py index 1bfa45e..141fc34 100644 --- a/common.py +++ b/common.py @@ -14,7 +14,7 @@ import re # Increase major number for general changes, middle number for smaller changes # that can cause incompatibilities and minor number for regular fixes -SARGRAPH_VERSION = "2.3.1" +SARGRAPH_VERSION = "2.4.1" # Define units for use with unit_str TIME_UNITS = ['seconds', 'minutes', 'hours'] diff --git a/graph.py b/graph.py index 4a88c04..608212c 100644 --- a/graph.py +++ b/graph.py @@ -107,7 +107,26 @@ def plot(ylabel, title, session, column, space=3, autoscale=None): g(f"set title \"{{/:Bold {title}}}" + ("\\n" * space) + "\"") g(f"plot '{session}.txt' using 1:{column}:{column} title 'cpu' with boxes palette") - +def plot_stacked(ylabel, title, session, column, tmpfs_color, other_cache_color, space=3, autoscale=None): + if autoscale is None: + g("set yrange [0:100]") + g("set cbrange [0:100]") + else: + g("unset xdata") + g("set yrange [0:*]") + g(f"stats '{session}.txt' using {column}") + g(f"set yrange [0:STATS_max*{autoscale}]") + g(f"set cbrange [0:STATS_max*{autoscale}]") + g("set xdata time") + g(f"set ylabel '{ylabel}'") + g(f"set title \"{{/:Bold {title}}}" + ("\\n" * space) + "\"") + g('set style data histograms') + g('set style histogram rowstacked') + g('set key reverse below Left width -25') + g(f"plot '{session}.txt' using 1:($8 + ${column}):{column} title 'RAM' with boxes palette, \ + '' using 1:8 with boxes title 'tmpfs' lc rgb '{tmpfs_color}', \ + '' using 1:($8 - $7) with boxes title 'Other cache (freed automatically)' lc rgb '{other_cache_color}'") + g('unset key') # Read additional information from 'data.txt' comments def read_comments(session): global START_DATE @@ -267,7 +286,7 @@ def read_comments(session): DURATION = unit_str(DURATION, TIME_UNITS, 60) -def graph(session, fname='plot'): +def graph(session, tmpfs_color, other_cache_color, fname='plot'): global OUTPUT_TYPE global OUTPUT_EXT @@ -404,8 +423,8 @@ def graph(session, fname='plot'): # Set scale for plots displayed in relative units (%) plot("CPU load (%)", f"CPU load (average = {AVERAGE_LOAD:.2f} %)", session, 2, space=space) - plot(f"RAM usage (100% = {TOTAL_RAM})", - f"RAM usage (max = {MAX_USED_RAM})", session, 3, space=space) + plot_stacked(f"RAM usage (100% = {TOTAL_RAM})", + f"RAM usage (max = {MAX_USED_RAM})", session, 3, tmpfs_color, other_cache_color, space=space) plot(f"FS usage (100% = {TOTAL_FS})", f"{NAME_FS} usage (max = {MAX_USED_FS})", session, 4, space=space) diff --git a/sargraph.py b/sargraph.py index 575b8ba..490038d 100755 --- a/sargraph.py +++ b/sargraph.py @@ -16,12 +16,14 @@ from common import * # Declare and parse command line flags parser = argparse.ArgumentParser() -parser.add_argument('session', metavar='SESSION-NAME', type=str, nargs='?', default=None, help='sargraph session name') -parser.add_argument('command', metavar='COMMAND', type=str, nargs='*', help='send command') -parser.add_argument('-f', metavar='DEVICE-NAME', type=str, nargs='?', default=None, dest='fsdev', help='observe a chosen filesystem') -parser.add_argument('-m', metavar='MOUNT-DIR', type=str, nargs='?', default=None, dest='fspath', help='observe a chosen filesystem') -parser.add_argument('-n', metavar='IFACE-NAME', type=str, nargs='?', default=None, dest='iface', help='observe chosen network iface') -parser.add_argument('-o', metavar='OUTPUT-NAME', type=str, nargs='?', default='data', dest='name', help='set output base names') +parser.add_argument('session', metavar='SESSION-NAME', type=str, nargs='?', default=None, help='sargraph session name') +parser.add_argument('command', metavar='COMMAND', type=str, nargs='*', help='send command') +parser.add_argument('-f', metavar='DEVICE-NAME', type=str, nargs='?', default=None, dest='fsdev', help='observe a chosen filesystem') +parser.add_argument('-m', metavar='MOUNT-DIR', type=str, nargs='?', default=None, dest='fspath', help='observe a chosen filesystem') +parser.add_argument('-n', metavar='IFACE-NAME', type=str, nargs='?', default=None, dest='iface', help='observe chosen network iface') +parser.add_argument('-o', metavar='OUTPUT-NAME', type=str, nargs='?', default='data', dest='name', help='set output base names') +parser.add_argument('-t', metavar='TMPFS-COLOR', type=str, nargs='?', default='#f2c71b', dest='tmpfs', help='set tmpfs plot color' ) +parser.add_argument('-c', metavar='CACHE-COLOR', type=str, nargs='?', default='#ee7af0', dest='cache', help='set cache plot color' ) args = parser.parse_args() def send(sid, msg): @@ -49,7 +51,7 @@ if args.session is None: if not args.fsdev: fail(f"no device is mounted on {args.fspath}") - watch.watch(args.name, args.fsdev, args.iface) + watch.watch(args.name, args.fsdev, args.iface, args.tmpfs, args.cache) sys.exit(0) # Now handle the commands @@ -108,8 +110,8 @@ elif cmd[0] == 'save': send(sid, f"command:s:{cmd[1]}") elif cmd[0] == 'plot': if len(cmd) < 2: - graph.graph(sid) + graph.graph(sid, args.tmpfs, args.cache) else: - graph.graph(sid, cmd[1]) + graph.graph(sid, args.tmpfs, args.cache, cmd[1]) else: fail(f"unknown command '{cmd[0]}'") diff --git a/watch.py b/watch.py index 4816ca5..861003d 100644 --- a/watch.py +++ b/watch.py @@ -184,7 +184,7 @@ def summarize(session): # Run sar and gather data from it -def watch(session, fsdev, iface): +def watch(session, fsdev, iface, tmpfs_color, other_cache_color): global SAMPLE_NUMBER global START_DATE global END_DATE @@ -215,6 +215,7 @@ def watch(session, fsdev, iface): my_env = os.environ my_env["S_TIME_FORMAT"] = "ISO" p = run_or_fail("sar", "-F", "-u", "-r", "-n", "DEV", "1", stdout=subprocess.PIPE, env=my_env) + p2 = run_or_fail("while true; do df -t tmpfs --output=used | awk '{sum+=$1} END {print sum}'; sleep 1; done", shell=True, stdout=subprocess.PIPE, env=my_env) # subprocess for GPU data fetching in the background try: pgpu = subprocess.Popen( @@ -257,9 +258,9 @@ def watch(session, fsdev, iface): if label_line == "none": pass elif label_line: - graph.graph(session, label_line) + graph.graph(session, tmpfs_color, other_cache_color, label_line) elif not dont_plot: - graph.graph(session) + graph.graph(session, tmpfs_color, other_cache_color) dont_plot = True die = 1 break @@ -271,9 +272,9 @@ def watch(session, fsdev, iface): if label_line != "none": summarize(session) if not label_line: - graph.graph(session) + graph.graph(session, tmpfs_color, other_cache_color) else: - graph.graph(session, label_line) + graph.graph(session, tmpfs_color, other_cache_color, label_line) elif label_line.startswith('label:'): label_line = label_line[len('label:'):] with open(f"{session}.txt", "a") as f: @@ -294,10 +295,14 @@ def watch(session, fsdev, iface): # Read and process RAM data ram_data = read_table(p.stdout) + tmpfs_data = p2.stdout.readline().decode('utf-8') + while p2.poll(): + tmpfs_data = p2.stdout.readline().decode('utf-8') + if TOTAL_RAM == 0: TOTAL_RAM = (int(ram_data['kbmemused'][0]) + int(ram_data['kbmemfree'][0])) - if MAX_USED_RAM < int(ram_data['kbmemused'][0]): - MAX_USED_RAM = int(ram_data['kbmemused'][0]) + if MAX_USED_RAM < int(ram_data['kbmemused'][0]) + int(ram_data['kbcached'][0]): + MAX_USED_RAM = int(ram_data['kbmemused'][0]) + int(ram_data['kbcached'][0]) # Read and process network data net_data = read_table(p.stdout) @@ -358,7 +363,9 @@ def watch(session, fsdev, iface): ram_data['%memused'][0], fs_data['%fsused'][FS_SAR_INDEX], stof(net_data['rxkB/s'][IFACE_SAR_INDEX])/128, # kB/s to Mb/s - stof(net_data['txkB/s'][IFACE_SAR_INDEX])/128 # kB/s to Mb/s + stof(net_data['txkB/s'][IFACE_SAR_INDEX])/128, # kB/s to Mb/s + f'{100*int(tmpfs_data)/TOTAL_RAM:.2f}', + f'{100*int(ram_data["kbcached"][0])/TOTAL_RAM:.2f}' ] if pgpu and TOTAL_GPU_RAM != 0: line.extend([