1+ # -*- coding: utf-8 -*-
2+
13"""Main module."""
24
35import contextlib
1012import sys
1113import time
1214import tempfile
15+ import textwrap
1316
1417from 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}\n avg: {1} {3}\n max: {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