From 9614266548b2d19dadef2736b4d8d83cd33140dc Mon Sep 17 00:00:00 2001 From: Marcelina Oset Date: Wed, 7 Sep 2022 16:00:46 +0200 Subject: [PATCH] [#37192] graph: Added txt rendering --- graph.py | 245 +++++++++++++++++++++++++++++++++++++++++++++++----- sargraph.py | 2 + watch.py | 4 + 3 files changed, 228 insertions(+), 23 deletions(-) diff --git a/graph.py b/graph.py index 2ffb5fe..cf3a8ce 100644 --- a/graph.py +++ b/graph.py @@ -12,7 +12,10 @@ import socket import subprocess import sys import time - +import plotext as plt +import numpy as np +from pathlib import Path +from typing import List, Tuple, Optional from common import * global gnuplot @@ -28,9 +31,9 @@ TOTAL_RAM = 0 TOTAL_FS = 0 NAME_FS = "unknown" -UNAME="unknown" -CPUS=0 -CPU_NAME="unknown" +UNAME = "unknown" +CPUS = 0 +CPU_NAME = "unknown" DURATION = 0.0 HOST = socket.gethostname() @@ -39,8 +42,8 @@ HOST = socket.gethostname() NUMBER_OF_PLOTS = 3 # The default format -OUTPUT_TYPE="pngcairo" -OUTPUT_EXT="png" +OUTPUT_TYPE = "pngcairo" +OUTPUT_EXT = "png" labels = [] @@ -49,7 +52,8 @@ labels = [] p = run_or_fail("gnuplot", "--version", stdout=subprocess.PIPE) version = scan(r"gnuplot (\S+)", str, p.stdout.readline().decode()) if not is_version_ge(version, GNUPLOT_VERSION_EXPECTED): - fail(f"gnuplot version too low. Need at least {GNUPLOT_VERSION_EXPECTED} found {version}") + fail( + f"gnuplot version too low. Need at least {GNUPLOT_VERSION_EXPECTED} found {version}") # Run a command in a running gnuplot process @@ -191,27 +195,29 @@ def graph(session, fname='plot.png'): labels = [] # The default format - OUTPUT_TYPE="pngcairo" - OUTPUT_EXT="png" + OUTPUT_TYPE = "pngcairo" + OUTPUT_EXT = "png" if "SARGRAPH_OUTPUT_TYPE" in os.environ: if os.environ["SARGRAPH_OUTPUT_TYPE"] == "svg": - OUTPUT_TYPE="svg" - OUTPUT_EXT="svg" + OUTPUT_TYPE = "svg" + OUTPUT_EXT = "svg" elif fname.lower().endswith('.svg'): - OUTPUT_TYPE="svg" - OUTPUT_EXT="svg" + OUTPUT_TYPE = "svg" + OUTPUT_EXT = "svg" elif fname.lower().endswith('.png'): # Otherwise leave the default png pass else: - fail("unknown graph extension") + pass + # fail("unknown graph extension") # Leave just the base name fname = fname[:-4] read_comments(session) - gnuplot = run_or_fail("gnuplot", stdin=subprocess.PIPE, stdout=subprocess.PIPE) + gnuplot = run_or_fail("gnuplot", stdin=subprocess.PIPE, + stdout=subprocess.PIPE) sdt = datetime.datetime.strptime(START_DATE, '%Y-%m-%d-%H:%M:%S') edt = datetime.datetime.strptime(END_DATE, '%Y-%m-%d-%H:%M:%S') @@ -220,8 +226,8 @@ def graph(session, fname='plot.png'): if seconds_between < 100: seconds_between = 100 - nsdt = sdt - datetime.timedelta(seconds = (seconds_between * 0.01)) - nedt = edt + datetime.timedelta(seconds = (seconds_between * 0.01)) + nsdt = sdt - datetime.timedelta(seconds=(seconds_between * 0.01)) + nedt = edt + datetime.timedelta(seconds=(seconds_between * 0.01)) g(f"set terminal {OUTPUT_TYPE} size 1200,1200 background '#332d37' font 'monospace,{fix_size(8)}'") @@ -245,7 +251,6 @@ def graph(session, fname='plot.png'): g("unset key") g("set rmargin 6") - g(f"set output '{fname}.{OUTPUT_EXT}'") title_machine = f"Running on {{/:Bold {HOST}}} \@ {{/:Bold {UNAME}}}, {{/:Bold {CPUS}}} threads x {{/:Bold {CPU_NAME}}}" @@ -260,7 +265,7 @@ def graph(session, fname='plot.png'): i = 0 for label in labels: - if i%2 == 0: + if i % 2 == 0: offset = 1.08 else: offset = 1.20 @@ -270,7 +275,7 @@ def graph(session, fname='plot.png'): content = f"{{[{i}] {label[1][0:30]}" length = len(label[1][0:30]) + len(str(i)) + 5 if OUTPUT_EXT == "svg": - length *= 0.75 + length *= 0.75 # Draw the dotted line g(f"set arrow nohead from '{label[0]}', graph 0.01 to '{label[0]}', graph {offset-0.04} front lc rgb '#e74a3c' dt 2") @@ -295,10 +300,204 @@ def graph(session, fname='plot.png'): g("set object rectangle from graph 0, graph 0 to graph 2, graph 2 behind fillcolor rgb '#111111' fillstyle solid noborder") g(f"set object rectangle from '{START_DATE.replace(' ', '-')}', 0 to '{END_DATE.replace(' ', '-')}', 100 behind fillcolor rgb '#000000' fillstyle solid noborder") - plot("cpu % load (user)", f"cpu load (average = {AVERAGE_LOAD:.2f} %)", session, 2, space=space) - plot("ram % usage", f"ram usage (max = {MAX_USED_RAM})", session, 3, space=space) - plot(f"{NAME_FS}", f"{NAME_FS} usage (max = {MAX_USED_FS})", session, 4, space=space) + plot("cpu % load (user)", + f"cpu load (average = {AVERAGE_LOAD:.2f} %)", session, 2, space=space) + plot("ram % usage", + f"ram usage (max = {MAX_USED_RAM})", session, 3, space=space) + plot(f"{NAME_FS}", f"{NAME_FS} usage (max = {MAX_USED_FS})", + session, 4, space=space) g("unset multiplot") g("unset output") g("quit") + + +def read_data(session): + xdata = list() + ydata = [[], [], []] + with open(f"{session}.txt", "r") as f: + for line in f: + if(line[0] != '#'): + line = line.split(" ") + date = datetime.datetime.strptime(line[0], '%Y-%m-%d-%H:%M:%S') + xdata.append(date) + for i in range(3): + ydata[i].append(float(line[i+1])) + return (xdata, ydata) + + +def create_ascii_plot( + title: str, + xtitle: str, + xunit: str, + ytitle: str, + yunit: str, + xdata: List, + ydata: List, + xrange: Optional[Tuple] = (0, 10), + yrange: Optional[Tuple] = (0, 100), + trimxvalues: bool = True, + skipfirst: bool = False, + figsize: Tuple = (90, 30), + switchtobarchart: bool = False): + + plt.clear_figure() + start = 1 if skipfirst else 0 + xdata = np.array(xdata[start:], copy=True) + ydata = np.array(ydata[start:], copy=True) + + if trimxvalues: + minx = min(xdata) + xdata = [x - minx for x in xdata] + + xlabel = xtitle + if xunit is not None: + xlabel += f' [{xunit}]' + ylabel = ytitle + if yunit is not None: + ylabel += f' [{yunit}]' + + if switchtobarchart == True: + plt.bar(xdata, ydata, width=0.1) + else: + plt.scatter(xdata, ydata) + plt.plot_size(figsize[0], figsize[1]) + + if xrange is not None: + plt.xlim(xrange[0], xrange[1]) + if yrange is not None: + plt.ylim(yrange[0], yrange[1]) + plt.title(title) + plt.xlabel(xtitle) + plt.ylabel(ytitle) + plt.show() + +def render_ascii_plot( + outpath: Optional[Path], + title: str, + subtitles: List, + xtitles: List, + xunits: List, + ytitles: List, + yunits: List, + xdata: List, + ydata: List, + xrange: Optional[Tuple] = None, + yrange: Optional[Tuple] = None, + trimxvalues: bool = True, + skipfirst: bool = False, + figsize: Tuple = (1500, 1080), + bins: int = 20, + switchtobarchart: bool = True, + tags: List = [], + tagstype: str = "single", + outputext: str = "html"): + """ + Draws triple time series plot. + + Used i.e. for timeline of resource usage. + + It also draws the histograms of values that appeared throughout the + experiment. + + Parameters + ---------- + outpath : Optional[Path] + Output path for the plot image. If None, the plot will be displayed. + title : str + Title of the plot + xtitle : str + Name of the X axis + xuint : str + Unit for the X axis + ytitle : str + Name of the Y axis + yunit : str + Unit for the Y axis + xdata : List + The values for X dimension + ydata : List + The values for Y dimension + xrange : Optional[Tuple] + The range of zoom on X axis + yrange : Optional[Tuple] + The range of zoom on Y axis + trimxvalues : bool + True if all values for the X dimension should be subtracted by + the minimal value on this dimension + skipfirst: bool + True if the first entry should be removed from plotting. + figsize: Tuple + The size of the figure + bins: int + Number of bins for value histograms + tags: list + List of tags and their timestamps + tagstype: String + "single" if given list contain tags with only one timestamp + "double" if given list contain tags with two (start and end) + timestamps. + outputext: String + Extension of generated file. + "html" for HTML file, + "png" for PNG file, + "svg" for SVG file, + "txt" for TXT file + """ + if outputext == "txt": + for plot_id in range(3): + create_ascii_plot( + title, + xtitles[plot_id], + xunits[plot_id], + ytitles[plot_id], + yunits[plot_id], + xdata, + ydata[plot_id], + xrange=xrange, + yrange=yrange, + trimxvalues=trimxvalues, + skipfirst=skipfirst, + switchtobarchart=switchtobarchart + ) + return + + +def ascii_graph(session, fname='plot.png'): + plot_title = f"""Running on {UNAME}, + {CPUS} threads x {CPU_NAME}
+ Total ram: {TOTAL_RAM}, + Total disk space: {TOTAL_FS}
+ Duration: {START_DATE} .. {END_DATE} ({DURATION})""" + + data = read_data(session) + subtitles = [f"""cpu load (average = {AVERAGE_LOAD} %)""", + f"""ram usage (max = {MAX_USED_RAM})""", + f"""{NAME_FS} usage (max = {MAX_USED_FS})"""] + + y_titles = [f"cpu % load (user)", + f"ram % usage", + f"{NAME_FS}"] + + xdata, ydata = data + xdata_to_int = [int(timestamp.replace( + tzinfo=datetime.timezone.utc).timestamp()*1000)/1000 + for timestamp in xdata] + + render_ascii_plot( + fname, + plot_title, + subtitles, + ["time"]*3, + ["s"]*3, + y_titles, + [None, None, None], + xdata_to_int, + ydata, + xrange=(0, 160), + yrange=(0, 100), + trimxvalues=True, + skipfirst=True, + switchtobarchart=True, + outputext="txt" + ) diff --git a/sargraph.py b/sargraph.py index 755e5b1..1cd5050 100755 --- a/sargraph.py +++ b/sargraph.py @@ -106,7 +106,9 @@ elif cmd[0] == 'save': elif cmd[0] == 'plot': if len(cmd) < 2: graph.graph(sid) + graph.ascii_graph(sid) else: graph.graph(sid, cmd[1]) + graph.ascii_graph(sid, cmd[1]) else: fail(f"unknown command '{cmd[0]}'") diff --git a/watch.py b/watch.py index 2d702ab..5ccc0eb 100644 --- a/watch.py +++ b/watch.py @@ -174,8 +174,10 @@ def watch(session, fsdev): pass elif label_line: graph.graph(session, label_line) + graph.ascii_graph(session, label_line) elif not dont_plot: graph.graph(session) + graph.ascii_graph(session) dont_plot = True die = 1 break @@ -188,8 +190,10 @@ def watch(session, fsdev): summarize(session) if not label_line: graph.graph(session) + graph.ascii_graph(session) else: graph.graph(session, label_line) + graph.ascii_graph(session, label_line) elif label_line.startswith('label:'): label_line = label_line[len('label:'):] with open(f"{session}.txt", "a") as f: