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: