Skip to content

Commit 0729e0b

Browse files
committed
- Restructure code a bit
1 parent 6e2eb2d commit 0729e0b

File tree

2 files changed

+102
-45
lines changed

2 files changed

+102
-45
lines changed

tmon/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# -*- coding: utf-8 -*-
2+
13
"""Top-level package for Temp Monitor."""
24

35
__author__ = """Goncalo Magno"""

tmon/tmon.py

Lines changed: 100 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# -*- coding: utf-8 -*-
2+
13
"""Main module."""
24

35
import contextlib
@@ -10,22 +12,25 @@
1012
import sys
1113
import time
1214
import tempfile
15+
import textwrap
1316

1417
from tmon.asciichart import plot
18+
from tmon.utils import eprint
1519

1620

17-
class TMon:
21+
class Monitor:
1822

19-
def __init__(self, config):
20-
self.config = config
23+
def __init__(self):
2124
self.keep_running = True
2225
self.tf = None
2326
self.proc = None
2427

25-
def eprint(self, *args, **kwargs):
26-
print(*args, file=sys.stderr, **kwargs)
28+
def _setup_signal_handlers(self):
29+
catchable_sigs = set(signal.Signals) - {signal.SIGKILL, signal.SIGSTOP}
30+
for sig in catchable_sigs:
31+
signal.signal(sig, self._signal_handler)
2732

28-
def signal_handler(self, sig, frame):
33+
def _signal_handler(self, sig, frame):
2934
if sig == signal.Signals.SIGCHLD:
3035
# tmon returns only when child process exits, i.e sends SIGCHLD
3136
# back to parent
@@ -36,27 +41,29 @@ def signal_handler(self, sig, frame):
3641
self.proc.send_signal(sig)
3742
except AttributeError:
3843
# Just in case a signal is sent before the process is spawned
39-
# or no child process was executed at all
44+
# or no child process was executed at all, i.e when tmon is run
45+
# with no arguments
4046
if sig == signal.Signals.SIGINT:
4147
self.keep_running = False
4248

43-
def setup_signal_handlers(self):
44-
catchable_sigs = set(signal.Signals) - {signal.SIGKILL, signal.SIGSTOP}
45-
for sig in catchable_sigs:
46-
signal.signal(sig, self.signal_handler)
49+
def _start_timer(self):
50+
self.start = datetime.datetime.now()
51+
self.cdt = self.start.strftime("%Y%m%d@%Hh%Mm%S")
4752

48-
def run(self, args):
49-
self.setup_signal_handlers()
50-
start = datetime.datetime.now()
51-
cdt = start.strftime("%Y%m%d@%Hh%Mm%S")
53+
def _stop_timer(self):
54+
self.period = str(datetime.datetime.now() - self.start).split('.')[0]
55+
56+
def start(self, args):
57+
self._setup_signal_handlers()
58+
self._start_timer()
5259

5360
with contextlib.ExitStack() as stack:
5461
if args:
5562
self.proc = stack.enter_context(
5663
Popen(args, stdout=sys.stdout, stderr=sys.stderr)
5764
)
5865
self.tf = stack.enter_context(tempfile.NamedTemporaryFile(
59-
mode='w+', prefix="tmon-{}-".format(cdt),
66+
mode='w+', prefix="tmon-{}-".format(self.cdt),
6067
suffix=".txt", delete=False, buffering=1
6168
))
6269

@@ -70,52 +77,100 @@ def run(self, args):
7077
ret = 0
7178
if args:
7279
try:
73-
outs, errs = self.proc.communicate(timeout=5)
80+
self.proc.communicate(timeout=5)
7481
except TimeoutExpired:
7582
self.proc.kill()
76-
outs, errs = self.proc.communicate()
83+
self.proc.communicate() # we don't care about stdout/err
7784
ret = self.proc.returncode
85+
self._stop_timer()
7886

79-
self.period = str(datetime.datetime.now() - start).split('.')[0]
80-
81-
self.report()
87+
lines = pathlib.Path(self.tf.name).read_text().splitlines()
88+
self.ds = [max([float(t)
89+
for t in l.split(' ')])*0.001 for l in lines]
8290
return ret
8391

84-
def report(self):
85-
lines = pathlib.Path(self.tf.name).read_text().splitlines()
86-
self.ds = [max([float(t) for t in l.split(' ')]) / 1000 for l in lines]
87-
self.eprint("\n\n===================")
88-
self.eprint("Temp Monitor Report:\n")
89-
self.plot(self.ds)
90-
self.eprint("\n {}".format(self.tf.name))
91-
self.eprint("===================")
92-
93-
def plot(self, ds):
94-
ratio = int(len(ds) / self.config['xsize'])
92+
93+
class Report:
94+
"""Temperature Reporter
95+
"""
96+
97+
def __init__(self, ds, tfname, period, fahrenheit=False):
98+
self.ds = ds if not fahrenheit else [t*1.8 + 32 for t in ds]
99+
self.tfname = tfname
100+
self.period = period
101+
self.unit = "°F" if fahrenheit else "°C"
102+
103+
def header(self):
104+
return textwrap.dedent("""
105+
===================
106+
Temp Monitor Report
107+
""")
108+
109+
def footer(self):
110+
return textwrap.dedent("""
111+
===================
112+
""")
113+
114+
def chart(self, xsize, ysize, ylim):
115+
ds = self.ds
116+
ratio = int(len(ds) / xsize)
95117
if ratio > 1:
96118
ds = ds[::ratio]
97-
self.eprint(" Temp (°C) for a period of {}".format(self.period))
119+
ret = "temp ({}) for a period of {}\n".format(self.unit, self.period)
98120
if len(ds) > 1:
99121
try:
100-
minimum, maximum = self.config['ylim']
122+
minimum, maximum = ylim
101123
except TypeError:
102124
minimum = math.floor(min(ds))
103125
maximum = math.ceil(max(ds) + 0.1)
104126
else:
105127
minimum = min(minimum, math.floor(min(ds)))
106128
maximum = max(maximum, math.ceil(max(ds) + 0.1))
107129

108-
self.eprint(plot(ds, {
109-
'height': self.config['ysize'] - 1,
130+
ret += plot(ds, {
131+
'height': ysize - 1,
110132
'minimum': minimum,
111133
'maximum': maximum,
112-
}))
113-
self.eprint('')
114-
min_val = round(min(self.ds), 1)
115-
avg_val = round(sum(self.ds) / len(self.ds), 1)
116-
max_val = round(max(self.ds), 1)
117-
self.eprint(" >> min: {} °C <<".format(min_val))
118-
self.eprint(" >> avg: {} °C <<".format(avg_val))
119-
self.eprint(" >> max: {} °C <<".format(max_val))
134+
})
120135
else:
121-
self.eprint(" >> {} °C <<".format(round(ds[0], 1)))
136+
ret = ""
137+
return ret
138+
139+
def stats(self):
140+
mi = round(min(self.ds), 1)
141+
av = round(sum(self.ds) / len(self.ds), 1)
142+
ma = round(max(self.ds), 1)
143+
ret = "min: {0} {3}\navg: {1} {3}\nmax: {2} {3}".format(
144+
mi, av, ma, self.unit
145+
)
146+
return ret
147+
148+
def report(self, xsize, ysize, ylim):
149+
ret = self.header() + "\n"
150+
chart = self.chart(xsize, ysize, ylim)
151+
if chart != "":
152+
ret += textwrap.indent(self.chart(xsize, ysize, ylim), ' ')
153+
ret += "\n\n"
154+
ret += textwrap.indent(self.stats(), ' ') + "\n\n"
155+
ret += textwrap.indent("raw: " + self.tfname, ' ')
156+
ret += self.footer()
157+
return ret
158+
159+
160+
def run(
161+
cmd=None, xsize=70, ysize=15, ylim=None, fahrenheit=False,
162+
stats_only=False, chart_only=False, path_only=False
163+
):
164+
monitor = Monitor()
165+
ret = monitor.start(cmd)
166+
ds, tfname, period = monitor.ds, monitor.tf.name, monitor.period
167+
report = Report(ds, tfname, period, fahrenheit=fahrenheit)
168+
if stats_only:
169+
eprint('\n' + report.stats())
170+
elif chart_only:
171+
eprint('\n' + report.chart(xsize, ysize, ylim))
172+
elif path_only:
173+
eprint('\n' + tfname)
174+
else:
175+
eprint(report.report(xsize, ysize, ylim))
176+
return ret

0 commit comments

Comments
 (0)