1#!/bin/sh
2# -*-Mode: TCL;-*-
3#
4# Copyright (C) 2007 Roaring Penguin Software Inc.  This file may be
5# distributed under the terms of the GNU General Public License, Version 2,
6# or (at your option) any later version.
7
8# Next line restarts using wish \
9exec wish "$0" -- "$@" ; clear; echo "*****"; echo "Cannot find 'wish' -- you need Tcl/Tk installed to run this program"; exit 1
10
11# Main update interval (ms)
12set MainUpdateInterval 1500
13
14# Busy worker update interval (ms)
15set BusyWorkerUpdateInterval 3000
16
17# Trace command - pid is appended
18set TraceCommand "strace -s 100 -t -p"
19
20# Command to run SSH
21set SSHCommand "ssh"
22
23# Command to run md-mx-ctrl
24set MD_MX_CTRL "md-mx-ctrl -i"
25
26# Archive readings?
27set DoArchive 0
28
29# Have we done a redraw since the last update?
30# Start out set to yes to kick things off!
31set DoneARedrawSinceLastUpdate 1
32
33# Use new-style "rawload1" command?
34set NewStyle 0
35set NewStyleShowScans 0
36set NewStyleShowRelayoks 0
37set NewStyleShowSenderoks 0
38set NewStyleShowRecipoks 0
39
40# Time scale for graph y-axes (seconds)
41set NewStyleTimeInterval 1
42
43set MachinesAwaitingReply {}
44
45if {[info exists env(MD_MX_CTRL)]} {
46    set x $env(MD_MX_CTRL)
47    if {"$x" != ""} {
48	set MD_MX_CTRL $x
49    }
50}
51
52proc strip_zeros { num } {
53    return [regsub {\.0+$} $num ""]
54}
55
56# Don't edit anything below here!
57set Machines {}
58set ctr 0
59set after_token {}
60proc find_machine { mach } {
61    global Machines
62    set index 0
63    foreach m $Machines {
64	if {"[lindex $m 0]" == "$mach"} {
65	    return $index
66	}
67	incr index
68    }
69    return -1
70}
71
72proc add_machine { mach } {
73    if {[find_machine $mach] >= 0} {
74	return
75    }
76
77    global Machines
78    if {[catch {
79	set fp [open_connection $mach]
80	lappend Machines [list $mach $fp]
81    } err]} {
82	puts stderr $err
83    }
84}
85
86proc host_plus_user { mach } {
87    if { [string match "*@*" $mach] } {
88	return $mach
89    }
90    return "root@$mach"
91}
92
93proc del_machine { mach } {
94    global Machines
95    global Data
96    set mnew {}
97    set index 0
98    set did_something 0
99    foreach m $Machines {
100	if { "[lindex $m 0]" == "$mach"} {
101	    catch {
102		close [lindex $m 1]
103	    }
104	    catch { unset Data($mach,busy) }
105	    catch { unset Data($mach,time) }
106	    catch { unset Data($mach,persec) }
107	    catch { unset Data($mach,busy_snap) }
108	    catch { unset Data($mach,persec_snap) }
109	    catch { unset Data($mach,time_snap) }
110	    catch { unset Data($mach,qsize) }
111	    catch { unset Data($mach,busyworkerwin) }
112	    catch { unset Data($mach,busyworkerafter) }
113	    catch { unset Data($mach,error) }
114	    set did_something 1
115	    continue
116	}
117	lappend mnew $m
118    }
119    set Machines $mnew
120    if {$did_something} {
121	reconfigure
122    }
123}
124
125proc open_connection { mach } {
126    global SSHCommand
127    global MD_MX_CTRL
128    set hmach [host_plus_user $mach]
129    set fp [open "| $SSHCommand $hmach $MD_MX_CTRL" "r+"]
130    fconfigure $fp -blocking 0
131    #fconfigure $fp -translation binary
132    fileevent $fp readable [list connection_readable $mach $fp]
133    return $fp
134}
135
136proc connection_readable { mach fp } {
137    global DoArchive
138    global MachinesAwaitingReply
139    global after_token
140
141    # Delete from MachinesAwaitingReply
142    set index [lsearch -exact $MachinesAwaitingReply $mach]
143    if {$index >= 0} {
144	set MachinesAwaitingReply [lreplace $MachinesAwaitingReply $index $index]
145    }
146    gets $fp line
147
148    if {"$line" == ""} {
149	if {[eof $fp]} {
150	    catch { close $fp }
151	    del_machine $mach
152	}
153    } else {
154	set index [find_machine $mach]
155	if {$index >= 0} {
156	    if {[catch { update_machine $line $index $mach } err]} {
157		mach_set_status_error $mach $index $err
158	    }
159
160	    if {$DoArchive} {
161		if {[catch { log_stats $mach $line } err]} {
162		    puts stderr $err
163		}
164	    }
165	}
166    }
167
168    # If all machines have replied, redraw
169    if {[llength $MachinesAwaitingReply] == 0} {
170	if {"$after_token" != ""} {
171	    after cancel $after_token
172	    set after_token {}
173	}
174	redraw
175    }
176}
177
178proc log_stats { mach line } {
179    set dir "~/.watch-multiple-mimedefangs/$mach"
180    if {![file isdirectory $dir]} {
181	file mkdir $dir
182    }
183    set fp [open "$dir/data" "a"]
184    puts $fp "[clock seconds] $line"
185    close $fp
186}
187
188proc get_machine_windows { } {
189    set kids [winfo children .top]
190    set result {}
191    set hi 0
192    foreach k $kids {
193	if {[regexp {^\.top\.name([0-9]+)$} $k dummy index]} {
194	    if {$index > $hi} {
195		set hi $index
196	    }
197	}
198    }
199    foreach k $kids {
200	if {[regexp {^\.top\.[^0-9]+([0-9]+)$} $k dummy index]} {
201	    if {$index <= $hi} {
202		lappend result $k
203	    }
204	}
205    }
206    return $result
207}
208
209proc mach_set_status_error { mach index err } {
210    global Data
211    set Data($mach,error) 1
212    .top.name$index configure -foreground red
213    .top.c$index itemconfigure statusText -text $err
214    .top.c$index delete withtag data1
215    .top.c$index delete withtag data2
216    .top.c$index delete withtag data3
217}
218
219proc mach_set_status_normal { mach index status } {
220    global Data
221    set Data($mach,error) 0
222    .top.name$index configure -foreground black
223    .top.c$index itemconfigure statusText -text $status
224}
225
226proc mach_populate_data { mach index line } {
227    global Data
228    global NewStyle
229
230    if {$NewStyle} {
231	mach_populate_data_new_style $mach $index $line
232	return
233    }
234
235    foreach { msg0 msg1 msg5 msg10 busy0 busy1 busy5 busy10 ms0 ms1 ms5 ms10 a0 a1 a5 a10 r0 r1 r5 r10 busy idle stopped killed msgs activations qsize qnum uptime} $line { break }
236    set total_workers [expr $busy + $idle + $stopped + $killed]
237    set ms0 [format "%.0f" $ms0]
238    set msg0 [format "%.2f" [expr $msg0 / 10.0]]
239
240    lappend Data($mach,busy) $busy0
241    lappend Data($mach,time) $ms0
242    lappend Data($mach,persec) $msg0
243    set Data($mach,total_workers) $total_workers
244    set Data($mach,busy_snap) $busy
245    set Data($mach,persec_snap) $msg0
246    set Data($mach,time_snap) $ms0
247    set Data($mach,qsize) $qsize
248    schedule_redraw
249}
250
251proc mach_populate_data_new_style { mach index line } {
252    global Data
253    foreach { scans avgbusyscans scantime relayoks avgbusyrelayoks relayoktime senderoks avgbusysenderoks senderoktime recipoks avgbusyrecipoks recipoktime busyworkers idleworkers stoppedworkers killedworkers msgs activations qsize numqueued uptime back } $line { break }
254
255    set scantime [format "%.0f" $scantime]
256    set relayoktime [format "%.0f" $relayoktime]
257    set senderoktime [format "%.0f" $senderoktime]
258    set recipoktime [format "%.0f" $recipoktime]
259
260    set total_workers [expr $busyworkers + $idleworkers + $stoppedworkers + $killedworkers]
261
262    set back [expr 1.0 * $back]
263    set scanspersec [expr (1.0 * $scans) / $back ]
264    set relayokspersec [expr (1.0 * $relayoks) / $back ]
265    set senderokspersec [expr (1.0 * $senderoks) / $back ]
266    set recipokspersec [expr (1.0 * $recipoks) / $back ]
267
268    set scanspersec [format "%.2f" $scanspersec]
269    set relayokspersec [format "%.2f" $relayokspersec]
270    set senderokspersec [format "%.2f" $senderokspersec]
271    set recipokspersec [format "%.2f" $recipokspersec]
272
273    lappend Data($mach,busy) $busyworkers
274    lappend Data($mach,scantime) $scantime
275    lappend Data($mach,scanspersec) $scanspersec
276    lappend Data($mach,relayoktime) $relayoktime
277    lappend Data($mach,relayokspersec) $relayokspersec
278    lappend Data($mach,senderoktime) $senderoktime
279    lappend Data($mach,senderokspersec) $senderokspersec
280    lappend Data($mach,recipoktime) $recipoktime
281    lappend Data($mach,recipokspersec) $recipokspersec
282
283    set Data($mach,total_workers) $total_workers
284    set Data($mach,busy_snap) $busyworkers
285    set Data($mach,scanspersec_snap) $scanspersec
286    set Data($mach,relayokspersec_snap) $relayokspersec
287    set Data($mach,senderokspersec_snap) $senderokspersec
288    set Data($mach,recipokspersec_snap) $recipokspersec
289    set Data($mach,scantime_snap) $scantime
290    set Data($mach,relayoktime_snap) $relayoktime
291    set Data($mach,senderoktime_snap) $senderoktime
292    set Data($mach,recipoktime_snap) $recipoktime
293    set Data($mach,qsize) $qsize
294    set Data($mach,numqueued) $numqueued
295
296    schedule_redraw
297}
298
299proc schedule_redraw {} {
300    global MainUpdateInterval
301    global after_token
302    if {"$after_token" == ""} {
303	set after_token [after $MainUpdateInterval redraw]
304    }
305}
306
307proc redraw_new_style {} {
308    global Machines
309    global after_token
310    global Data
311    global TotalData
312    global NewStyleShowScans NewStyleShowRelayoks NewStyleShowSenderoks NewStyleShowRecipoks
313
314    set after_token ""
315    set index 0
316
317    set scanspersec_total 0
318    set relayokspersec_total 0
319    set senderokspersec_total 0
320    set recipokspersec_total 0
321    set scantime_total 0
322    set relayoktime_total 0
323    set senderoktime_total 0
324    set recipoktime_total 0
325    set busyworkers_total 0
326    set totalworkers_total 0
327    set busyworkers_active 0
328    set totalworkers_active 0
329    set num_machines 0
330
331    set num_graphs 1
332    if {$NewStyleShowScans} {
333	incr num_graphs 2
334    }
335    if {$NewStyleShowRelayoks} {
336	incr num_graphs 2
337    }
338    if {$NewStyleShowSenderoks} {
339	incr num_graphs 2
340    }
341    if {$NewStyleShowRecipoks} {
342	incr num_graphs 2
343    }
344
345    set wid [winfo width .top.clabels]
346    .top.clabels delete all
347    set spacing [expr $wid / (1.0 * $num_graphs)]
348    set x [expr $spacing / 2.0]
349    .top.clabels create text $x 2 -anchor n -fill "#A00000" -text "Busy Workers"
350    if {$NewStyleShowScans} {
351	set x [expr $x + $spacing]
352	.top.clabels create text $x 2 -anchor n -fill "#00A000" -text "Scans/d"
353	set x [expr $x + $spacing]
354	.top.clabels create text $x 2 -anchor n -fill "#0000A0" -text "ms/scan"
355    }
356    if {$NewStyleShowRelayoks} {
357	set x [expr $x + $spacing]
358	.top.clabels create text $x 2 -anchor n -fill "#808000" -text "Relays/d"
359	set x [expr $x + $spacing]
360	.top.clabels create text $x 2 -anchor n -fill "#008080" -text "ms/relay"
361    }
362    if {$NewStyleShowSenderoks} {
363	set x [expr $x + $spacing]
364	.top.clabels create text $x 2 -anchor n -fill "#808080" -text "Senders/d"
365	set x [expr $x + $spacing]
366	.top.clabels create text $x 2 -anchor n -fill "#800080" -text "ms/sender"
367    }
368    if {$NewStyleShowRecipoks} {
369	set x [expr $x + $spacing]
370	.top.clabels create text $x 2 -anchor n -fill "#008000" -text "Recips/d"
371	set x [expr $x + $spacing]
372	.top.clabels create text $x 2 -anchor n -fill "#000000" -text "ms/recip"
373    }
374
375    foreach m $Machines {
376	set mach [lindex $m 0]
377	if {![info exists Data($mach,busy)]} {
378	    incr index
379	    continue
380	}
381	if {$Data($mach,error)} {
382	    incr index
383	    continue
384	}
385
386	# Update totals
387	set busy $Data($mach,busy_snap)
388	set totalworkers $Data($mach,total_workers)
389
390	# Only update worker counts for machines that are actually doing something
391	if {$Data($mach,scanspersec_snap) > 0 || $Data($mach,relayokspersec_snap) > 0 || $Data($mach,senderokspersec_snap) > 0 || $Data($mach,recipokspersec_snap) > 0} {
392	    set totalworkers_active [expr $totalworkers_active + $totalworkers]
393	    set busyworkers_active [expr $busyworkers_active + 1.0*$busy]
394	}
395
396	set totalworkers_total [expr $totalworkers_total + $totalworkers]
397	set busyworkers_total [expr $busyworkers_total + 1.0*$busy]
398
399	set scanspersec_total [expr $scanspersec_total + 1.0*$Data($mach,scanspersec_snap)]
400	set scantime_total [expr $scantime_total + (1.0*$Data($mach,scanspersec_snap) * $Data($mach,scantime_snap))]
401	set relayokspersec_total [expr $relayokspersec_total + 1.0*$Data($mach,relayokspersec_snap)]
402	set relayoktime_total [expr $relayoktime_total + (1.0*$Data($mach,relayokspersec_snap) * $Data($mach,relayoktime_snap))]
403	set senderokspersec_total [expr $senderokspersec_total + 1.0*$Data($mach,senderokspersec_snap)]
404	set senderoktime_total [expr $senderoktime_total + (1.0*$Data($mach,senderokspersec_snap) * $Data($mach,senderoktime_snap))]
405	set recipokspersec_total [expr $recipokspersec_total + 1.0*$Data($mach,recipokspersec_snap)]
406	set recipoktime_total [expr $recipoktime_total + (1.0*$Data($mach,recipokspersec_snap) * $Data($mach,recipoktime_snap))]
407
408	set graph 0
409	set Data($mach,busy) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 $Data($mach,total_workers) $Data($mach,busy) $index "Busy" $graph "#A00000" 1]
410	if {$totalworkers > 0} {
411	    set pctbusy [expr int((1.0*$busy) / (1.0*$totalworkers) * 100)]
412	} else {
413	    set pctbusy 100
414	}
415	if {$pctbusy < 80} {
416	    .top.busy$index configure -background #D9D9D9 -foreground "#A00000"
417	} elseif {$pctbusy < 90} {
418	    .top.busy$index configure -background #C0C000 -foreground "#A00000"
419	} else {
420	    .top.busy$index configure -background #C00000 -foreground "#000000"
421	}
422
423	.top.busy$index configure -text "$busy/$totalworkers\n$pctbusy%"
424
425	incr graph
426
427	if {$NewStyleShowScans} {
428	    set Data($mach,scanspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,scanspersec) $index "Scans/s" $graph "#00A000" 1]
429	    incr graph
430	    set Data($mach,scantime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,scantime) $index "ms/scan" $graph "#0000A0" 1]
431	    incr graph
432	    set s $Data($mach,scanspersec_snap)
433	    set h [human_number [expr $s * 3600]]
434	    set d [human_number [expr $s * 86400]]
435	    if {$s == 0} {
436		.top.scanspersec$index configure -text "-"
437		.top.scantime$index configure -text "-"
438	    } else {
439		.top.scanspersec$index configure -text [format "%.2f\n%s/h\n%s/d" $s $h $d]
440		.top.scantime$index configure -text $Data($mach,scantime_snap)
441	    }
442	} else {
443	    set Data($mach,scanspersec) {}
444	    set Data($mach,scantime) {}
445	}
446
447	if {$NewStyleShowRelayoks} {
448	    set Data($mach,relayokspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,relayokspersec) $index "Relayoks/s" $graph "#808000" 1]
449	    incr graph
450	    set Data($mach,relayoktime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,relayoktime) $index "ms/relayok" $graph "#008080" 1]
451	    incr graph
452	    set s $Data($mach,relayokspersec_snap)
453	    set h [human_number [expr $s * 3600]]
454	    set d [human_number [expr $s * 86400]]
455	    if {$s == 0} {
456		.top.relayspersec$index configure -text "-"
457		.top.relaytime$index configure -text "-"
458	    } else {
459		.top.relayspersec$index configure -text [format "%.2f\n%s/h\n%s/d" $s $h $d]
460		.top.relaytime$index configure -text $Data($mach,relayoktime_snap)
461	    }
462	} else {
463	    set Data($mach,relayokspersec) {}
464	    set Data($mach,relayoktime) {}
465	}
466	if {$NewStyleShowSenderoks} {
467	    set Data($mach,senderokspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,senderokspersec) $index "Senderoks/s" $graph "#808080" 1]
468	    incr graph
469	    set Data($mach,senderoktime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,senderoktime) $index "ms/senderok" $graph "#800080" 1]
470	    incr graph
471	    set s $Data($mach,senderokspersec_snap)
472	    set h [human_number [expr $s * 3600]]
473	    set d [human_number [expr $s * 86400]]
474	    if {$s == 0} {
475		.top.senderspersec$index configure -text "-"
476		.top.sendertime$index configure -text "-"
477	    } else {
478		.top.senderspersec$index configure -text [format "%.2f\n%s/h\n%s/d" $s $h $d]
479		.top.sendertime$index configure -text $Data($mach,senderoktime_snap)
480	    }
481	} else {
482	    set Data($mach,senderokspersec) {}
483	    set Data($mach,senderoktime) {}
484	}
485	if {$NewStyleShowRecipoks} {
486	    set Data($mach,recipokspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,recipokspersec) $index "Recipoks/s" $graph "#008000" 1]
487	    incr graph
488	    set Data($mach,recipoktime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,recipoktime) $index "ms/recipok" $graph "#000000" 1]
489	    incr graph
490	    set s $Data($mach,recipokspersec_snap)
491	    set h [human_number [expr $s * 3600]]
492	    set d [human_number [expr $s * 86400]]
493	    if {$s == 0} {
494		.top.recipspersec$index configure -text "-"
495		.top.reciptime$index configure -text "-"
496	    } else {
497		.top.recipspersec$index configure -text [format "%.2f\n%s/h\n%s/d" $s $h $d]
498		.top.reciptime$index configure -text $Data($mach,recipoktime_snap)
499	    }
500	} else {
501	    set Data($mach,recipokspersec) {}
502	    set Data($mach,recipoktime) {}
503	}
504	incr index
505    }
506    lappend TotalData(busy) $busyworkers_total
507    lappend TotalData(total) $totalworkers_total
508    lappend TotalData(scanspersec) $scanspersec_total
509    set scantime_avg [expr $scantime_total / ($scanspersec_total + 0.000000001)]
510    set relayoktime_avg [expr $relayoktime_total / ($relayokspersec_total + 0.000000001)]
511    set senderoktime_avg [expr $senderoktime_total / ($senderokspersec_total + 0.000000001)]
512    set recipoktime_avg [expr $recipoktime_total / ($recipokspersec_total + 0.000000001)]
513    lappend TotalData(scantime) $scantime_avg
514    lappend TotalData(relayokspersec) $relayokspersec_total
515    lappend TotalData(relayoktime) $relayoktime_avg
516    lappend TotalData(senderokspersec) $senderokspersec_total
517    lappend TotalData(senderoktime) $senderoktime_avg
518    lappend TotalData(recipokspersec) $recipokspersec_total
519    lappend TotalData(recipoktime) $recipoktime_avg
520
521    set graph 0
522    set TotalData(busy) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 $totalworkers_total $TotalData(busy) $index "Busy" $graph "#A00000" 1]
523    if {$totalworkers_total > 0} {
524	set busyworkers_total [expr int($busyworkers_total)]
525	set pctbusy [expr int((1.0 * $busyworkers_total) / (1.0 * $totalworkers_total) * 100)]
526    } else {
527	set pctbusy 100
528    }
529
530    if {$totalworkers_active > 0} {
531	set busyworkers_active [expr int($busyworkers_active)]
532	set apct [expr int((1.0 * $busyworkers_active) / (1.0 * $totalworkers_active) * 100)]
533    } else {
534	set apct 100
535    }
536    .top.busytotal configure -text "$busyworkers_total/$totalworkers_total\n$pctbusy%\n$busyworkers_active/$totalworkers_active\n$apct%"
537    incr graph
538
539    if {$NewStyleShowScans} {
540	set TotalData(scanspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(scanspersec) $index "Scans/s" $graph "#00A000" 1]
541	incr graph
542	set TotalData(scantime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(scantime) $index "ms/scan" $graph "#0000A0" 1]
543	incr graph
544	set h [human_number [expr $scanspersec_total * 3600]]
545	set d [human_number [expr $scanspersec_total * 86400]]
546	.top.scanspersectotal configure -text [format "%.2f\n%s/h\n%s/d" $scanspersec_total $h $d]
547	.top.avgscantime configure -text [format "%.0f" $scantime_avg]
548    } else {
549	set TotalData(scanspersec) {}
550	set TotalData(scantime) {}
551    }
552
553    if {$NewStyleShowRelayoks} {
554	set TotalData(relayokspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(relayokspersec) $index "Relayoks/s" $graph "#808000" 1]
555	incr graph
556	set TotalData(relayoktime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(relayoktime) $index "ms/relayok" $graph "#008080" 1]
557	incr graph
558	set h [human_number [expr $relayokspersec_total * 3600]]
559	set d [human_number [expr $relayokspersec_total * 86400]]
560	.top.relayspersectotal configure -text [format "%.2f\n%s/h\n%s/d" $relayokspersec_total $h $d]
561	.top.avgrelaytime configure -text [format "%.0f" $relayoktime_avg]
562    } else {
563	set TotalData(relayokspersec) {}
564	set TotalData(relayoktime) {}
565    }
566    if {$NewStyleShowSenderoks} {
567	set TotalData(senderokspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(senderokspersec) $index "Senderoks/s" $graph "#808080" 1]
568	incr graph
569	set TotalData(senderoktime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(senderoktime) $index "ms/senderok" $graph "#800080" 1]
570	incr graph
571	set h [human_number [expr $senderokspersec_total * 3600]]
572	set d [human_number [expr $senderokspersec_total * 86400]]
573	.top.senderspersectotal configure -text [format "%.2f\n%s/h\n%s/d" $senderokspersec_total $h $d]
574	.top.avgsendertime configure -text [format "%.0f" $senderoktime_avg]
575	set s [human_number $senderokspersec_total]
576    } else {
577	set TotalData(senderokspersec) {}
578	set TotalData(senderoktime) {}
579    }
580    if {$NewStyleShowRecipoks} {
581	set TotalData(recipokspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(recipokspersec) $index "Recipoks/s" $graph "#008000" 1]
582	incr graph
583	set TotalData(recipoktime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(recipoktime) $index "ms/recipok" $graph "#000000" 1]
584	incr graph
585	set h [human_number [expr $recipokspersec_total * 3600]]
586	set d [human_number [expr $recipokspersec_total * 86400]]
587	.top.recipspersectotal configure -text [format "%.2f\n%s/h\n%s/d" $recipokspersec_total $h $d]
588	.top.avgreciptime configure -text [format "%.0f" $recipoktime_avg]
589    } else {
590	set TotalData(recipokspersec) {}
591	set TotalData(recipoktime) {}
592    }
593    update
594}
595
596proc redraw {} {
597    global Machines
598    global NewStyle
599    global after_token
600    global Data
601    global TotalData
602    global DoneARedrawSinceLastUpdate
603
604    set DoneARedrawSinceLastUpdate 1
605    if {$NewStyle} {
606	redraw_new_style
607	return
608    }
609
610    set after_token ""
611    set index 0
612    set persec_total 0
613    set busy_workers_total 0
614    set avail_workers_total 0
615    set msgs_per_sec_total 0
616    set ms_per_scan_total 0
617    set num_machines 0
618
619    foreach m $Machines {
620	set mach [lindex $m 0]
621	if {![info exists Data($mach,busy)]} {
622	    incr index
623	    continue
624	}
625	if {$Data($mach,error)} {
626	    incr index
627	    continue
628	}
629
630	set busy $Data($mach,busy_snap)
631	set total_workers $Data($mach,total_workers)
632	set msg0 $Data($mach,persec_snap)
633	set ms0 $Data($mach,time_snap)
634	set persec_total [expr $persec_total + $msg0]
635	# Format $busy to have as many characters as $total_workers
636	set l [string length $total_workers]
637	set busy [format "%${l}d" $busy]
638	.top.busy$index configure -text "$busy/$total_workers"
639	.top.persec$index configure -text $msg0
640	.top.time$index configure -text $ms0
641	if {$busy == $total_workers} {
642	    .top.name$index configure -background "#CCCC00"
643	} else {
644	    .top.name$index configure -background "#D9D9D9"
645	}
646
647	set Data($mach,busy) [graph 0 [expr 1.0/3] 0 $Data($mach,total_workers) $Data($mach,busy) $index "Busy" 1 red 1]
648	set Data($mach,persec) [graph [expr 1.0/3] [expr 2.0/3] 0 auto $Data($mach,persec) $index "Msgs/Sec" 2 green 1]
649	set Data($mach,time) [graph [expr 2.0/3] 1 0 auto $Data($mach,time) $index "ms/scan" 3 blue 1]
650	incr index
651
652	if {$ms0 > 0 || $Data($mach,busy_snap) > 0 || $msg0 > 0} {
653	    incr num_machines
654	    incr busy_workers_total $Data($mach,busy_snap)
655	    incr avail_workers_total $Data($mach,total_workers);
656	    incr ms_per_scan_total $ms0
657	}
658    }
659    lappend TotalData(busy) $busy_workers_total
660    if {$num_machines > 0} {
661	lappend TotalData(time) [expr 1.0 * $ms_per_scan_total / (1.0 * $num_machines)]
662    } else {
663	lappend TotalData(time) 0
664    }
665    lappend TotalData(persec) $persec_total
666
667    incr index
668    set TotalData(busy) [graph 0 [expr 1.0/3] 0 $avail_workers_total $TotalData(busy) $index "Busy" 1 red 1]
669    set TotalData(persec) [graph [expr 1.0/3] [expr 2.0/3] 0 auto $TotalData(persec) $index "Msgs/Sec" 2 green 1]
670    set TotalData(time) [graph [expr 2.0/3] 1 0 auto $TotalData(time) $index "ms/scan" 3 blue 1]
671
672    set msgs_per_sec_total $persec_total
673    set hour [human_number [expr $persec_total * 3600.0]]
674    set day  [human_number [expr $persec_total * 86400.0]]
675    set persec_total [strip_zeros [format "%.1f" $persec_total]]
676    .top.c configure -text "Total throughput $persec_total/s = $hour/hour = $day/day"
677    set l [string length $avail_workers_total]
678    set busy_workers_total [format "%${l}d" $busy_workers_total]
679    .top.busytotal configure -text "$busy_workers_total/$avail_workers_total"
680    .top.persectotal configure -text [strip_zeros [format "%.1f" $msgs_per_sec_total]]
681    if {$num_machines > 0} {
682	.top.avgtime configure -text [strip_zeros [format "%.0f" [expr 1.0 * $ms_per_scan_total / (1.0 * $num_machines)]]]
683    } else {
684	.top.avgtime configure -text "--"
685    }
686    update
687}
688
689proc graph { start_frac end_frac min max data index label tag fill_color line_width} {
690    global NewStyleTimeInterval
691
692    set tag "data$tag"
693    set c .top.c$index
694    set h [winfo height $c]
695    set w [winfo width $c]
696    set x0 [expr int($start_frac * $w)]
697    set x1 [expr int($end_frac * $w)]
698    set x0 [expr $x0 + 40]
699    set x1 [expr $x1 - 5]
700    set diff [expr $x1 - $x0]
701    set gridline_spacing 15
702    if {[llength $data] > $diff} {
703	set toChop [expr [llength $data] - $diff]
704	set data [lrange $data $toChop end]
705    }
706
707    set multiplier 1
708
709    if {"$label" == "Scans/s"} {
710	if {$NewStyleTimeInterval == 3600} {
711	    set label "Scans/h"
712	    set multiplier 3600
713	} elseif {$NewStyleTimeInterval == 86400} {
714	    set label "Scans/d"
715	    set multiplier 86400
716	}
717    }
718    if {"$label" == "Senderoks/s"} {
719	if {$NewStyleTimeInterval == 3600} {
720	    set label "Senderoks/h"
721	    set multiplier 3600
722	} elseif {$NewStyleTimeInterval == 86400} {
723	    set label "Senderoks/d"
724	    set multiplier 86400
725	}
726    }
727    if {"$label" == "Relayoks/s"} {
728	if {$NewStyleTimeInterval == 3600} {
729	    set label "Relayoks/h"
730	    set multiplier 3600
731	} elseif {$NewStyleTimeInterval == 86400} {
732	    set label "Relayoks/d"
733	    set multiplier 86400
734	}
735    }
736    if {"$label" == "Recipoks/s"} {
737	if {$NewStyleTimeInterval == 3600} {
738	    set label "Recipoks/h"
739	    set multiplier 3600
740	} elseif {$NewStyleTimeInterval == 86400} {
741	    set label "Recipoks/d"
742	    set multiplier 86400
743	}
744    }
745
746    if {"$min" == "auto"} {
747	set min [lindex $data 0]
748	foreach thing $data {
749	    if {$thing < $min} {set min $thing}
750	}
751	set min [expr $multiplier * $min]
752	set min [nicenum $min 1]
753    }
754    if {"$max" == "auto"} {
755	set max [lindex $data 0]
756	foreach thing $data {
757	    if {$thing > $max} {set max $thing}
758	}
759	set max [expr $multiplier * $max]
760	set max [nicenum $max 0]
761    }
762
763    set x $x0
764    $c delete withtag $tag
765    set coords {}
766    if {$max == $min} {
767	set max [expr $max + 1.0]
768    }
769    set diff [expr 1.0 * ($max - $min)]
770    set num_gridlines [expr int((1.0 * $h) / (1.0 * $gridline_spacing))]
771    if {$num_gridlines > 10} {
772	set num_gridlines 10
773    }
774    if {$num_gridlines < 1} {
775	set num_gridlines 1
776    }
777
778    set delta [nicenum [expr $diff / $num_gridlines] 1]
779    foreach point $data {
780	set y [expr $point * $multiplier - $min]
781	set y [expr (1.0 * $y * $h) / (1.0 * $diff)]
782	set y [expr $h - $y]
783	if {$y < 1} {
784	    set y 1
785	}
786	if {$y >= $h} {
787	    set y [expr $h - 1]
788	}
789	lappend coords $x $y
790	incr x
791    }
792    if {$delta > 0.0} {
793	set last_phys_y 99999
794	for {set y $min} {$y <= $max} {set y [expr $y + $delta]} {
795	    set cy [expr (1.0 * ($y-$min) * $h) / (1.0 * $diff)]
796	    set cy [expr $h - $cy]
797	    if {$cy <= 0} {
798		continue
799	    }
800	    if {$cy > [expr $h-1]} {
801		set cy [expr $h-1]
802	    }
803	    if {($last_phys_y - $cy) >= (2 * $gridline_spacing)} {
804		set last_phys_y $cy
805		set anc w
806		if {$cy < $gridline_spacing} {
807		    set anc nw
808		}
809		if {$cy >= ($h - $gridline_spacing)} {
810		    set anc sw
811		}
812		$c create line [expr $x0 - 10] $cy $x1 $cy -fill "#A0A0A0" -tags $tag
813		$c create text [expr $x0 - 37] $cy -text [human_number $y] -tag $tag -anchor $anc
814	    } else {
815		$c create line $x0 $cy $x1 $cy -fill "#DDDDDD" -tags $tag
816	    }
817	}
818    } else {
819	$c create text [expr $x0 - 37] 0 -anchor nw -text [human_number $max] -tag $tag
820	$c create text [expr $x0 - 37] $h -anchor sw -text [human_number $min] -tag $tag
821    }
822    if {[llength $coords] >= 4} {
823	$c create line $coords -fill $fill_color -width $line_width -tags $tag
824    }
825    return $data
826}
827proc update_machine { line index mach } {
828    if {[string match "ERROR *" $line]} {
829	mach_set_status_error $mach $index $line
830	return
831    }
832    mach_set_status_normal $mach $index ""
833
834    mach_populate_data $mach $index $line
835}
836
837proc interactive_add_machine {} {
838    set mach [.top.new get]
839    if {"$mach" != ""} {
840	add_machine $mach
841	reconfigure
842    }
843}
844
845proc reconfigure_new_style {} {
846    global Machines
847    global NewStyleShowScans NewStyleShowRelayoks NewStyleShowSenderoks NewStyleShowRecipoks
848    catch { destroy .top.name }
849    catch { destroy .top.busy }
850    catch { destroy .top.scanspersec }
851    catch { destroy .top.scantime }
852    catch { destroy .top.relayspersec }
853    catch { destroy .top.relaytime }
854    catch { destroy .top.senderspersec }
855    catch { destroy .top.sendertime }
856    catch { destroy .top.recipspersec }
857    catch { destroy .top.reciptime }
858    catch { destroy .top.c }
859
860    set col 2
861    set canv_width 200
862    label .top.name -text "Machine Name"
863    label .top.busy -text "Busy Workers   " -foreground "#A00000"
864    grid .top.name -row 0 -column 0 -sticky new
865    grid .top.busy -row 0 -column 1 -sticky new
866    catch { destroy .top.clabels}
867    canvas .top.clabels -width $canv_width -height 10 -takefocus 0 -borderwidth 0 -background #ffffff -highlightthickness 0
868    if {$NewStyleShowScans} {
869	incr canv_width 200
870	label .top.scanspersec -text "Scans/s   " -foreground "#00A000"
871	grid .top.scanspersec -row 0 -column $col -sticky new
872	incr col
873	label .top.scantime -text "ms/scan   " -foreground "#0000A0"
874	grid .top.scantime -row 0 -column $col -sticky new
875	incr col
876    }
877    if {$NewStyleShowRelayoks} {
878	incr canv_width 200
879	label .top.relayspersec -text "Relays/s   " -foreground "#808000"
880	grid .top.relayspersec -row 0 -column $col -sticky new
881	incr col
882	label .top.relaytime -text "ms/relay   " -foreground "#008080"
883	grid .top.relaytime -row 0 -column $col -sticky new
884	incr col
885    }
886    if {$NewStyleShowSenderoks} {
887	incr canv_width 200
888	label .top.senderspersec -text "Senders/s   " -foreground "#808080"
889	grid .top.senderspersec -row 0 -column $col -sticky new
890	incr col
891	label .top.sendertime -text "ms/sender   " -foreground "#800080"
892	grid .top.sendertime -row 0 -column $col -sticky new
893	incr col
894    }
895    if {$NewStyleShowRecipoks} {
896	incr canv_width 200
897	label .top.recipspersec -text "Recips/s   " -foreground "#008000"
898	grid .top.recipspersec -row 0 -column $col -sticky new
899	incr col
900	label .top.reciptime -text "ms/recip   " -foreground "#000000"
901	grid .top.reciptime -row 0 -column $col -sticky new
902	incr col
903    }
904
905    grid .top.clabels -row 0 -column $col -sticky nsew
906    grid rowconfigure .top 0 -weight 0
907    set index 0
908    foreach m $Machines {
909	grid_machine_new_style $m $index
910	incr index
911    }
912
913    # If a machine has been deleted, destroy its windows
914    catch { destroy .top.name$index }
915    catch { destroy .top.busy$index }
916    catch { destroy .top.scanspersec$index }
917    catch { destroy .top.scantime$index }
918    catch { destroy .top.relayspersec$index }
919    catch { destroy .top.relaytime$index }
920    catch { destroy .top.senderspersec$index }
921    catch { destroy .top.sendertime$index }
922    catch { destroy .top.recipspersec$index }
923    catch { destroy .top.reciptime$index }
924    catch { destroy .top.c$index }
925
926    # Bottom row of labels
927    catch { destroy .top.totalrow }
928    catch { destroy .top.busytotal }
929    catch { destroy .top.scanspersectotal }
930    catch { destroy .top.avgscantime }
931    catch { destroy .top.relayspersectotal }
932    catch { destroy .top.avgrelaytime }
933    catch { destroy .top.senderspersectotal }
934    catch { destroy .top.avgsendertime }
935    catch { destroy .top.recipspersectotal }
936    catch { destroy .top.avgreciptime }
937
938    # Mop up total window if a machine has been deleted
939    set row [expr $index + 1]
940    catch { destroy .top.c$row }
941
942    set col 2
943    label .top.totalrow -text "Totals:"
944    label .top.busytotal -foreground "#A00000"
945    grid .top.totalrow -row $row -column 0 -sticky new
946    grid .top.busytotal -row $row -column 1 -sticky new
947    if {$NewStyleShowScans} {
948	label .top.scanspersectotal -foreground "#00A000"
949	grid .top.scanspersectotal -row $row -column $col -sticky new
950	incr col
951	label .top.avgscantime -foreground "#0000A0"
952	grid .top.avgscantime -row $row -column $col -sticky new
953	incr col
954    }
955    if {$NewStyleShowRelayoks} {
956	label .top.relayspersectotal -foreground "#808000"
957	grid .top.relayspersectotal -row $row -column $col -sticky new
958	incr col
959	label .top.avgrelaytime -foreground "#008080"
960	grid .top.avgrelaytime -row $row -column $col -sticky new
961	incr col
962    }
963    if {$NewStyleShowSenderoks} {
964	label .top.senderspersectotal -foreground "#808080"
965	grid .top.senderspersectotal -row $row -column $col -sticky new
966	incr col
967	label .top.avgsendertime -foreground "#800080"
968	grid .top.avgsendertime -row $row -column $col -sticky new
969	incr col
970    }
971    if {$NewStyleShowRecipoks} {
972	label .top.recipspersectotal -foreground "#008000"
973	grid .top.recipspersectotal -row $row -column $col -sticky new
974	incr col
975	label .top.avgreciptime -foreground "#000000"
976	grid .top.avgreciptime -row $row -column $col -sticky new
977	incr col
978    }
979    set num_items [expr $col-1]
980
981    canvas .top.c$index -width $canv_width -height 60 -takefocus 0 -borderwidth 0 -background #FFFFEE -highlightthickness 0
982    grid .top.c$index -row $row -column $col -sticky nsew -pady 1
983    grid rowconfigure .top $row -weight 3
984
985    for {set i 0} {$i < $col} {incr i} {
986	grid columnconfigure .top $i -weight 0
987    }
988    grid columnconfigure .top $col -weight 1
989
990    incr index
991    incr row
992    # Now a spot for adding a new machine...
993    catch { destroy .top.newlab }
994    catch { destroy .top.new }
995    catch { destroy .top.all }
996
997    label .top.newlab -text "Add Machine: "
998    entry .top.new -width 20
999    grid .top.newlab -row $row -column 0
1000    grid .top.new -row $row -column 1 -columnspan [expr $col - 1] -sticky ew
1001    bind .top.new <Return> interactive_add_machine
1002    button .top.all -text "Summary" -command all_or_summary
1003    grid .top.all -row $row -column [expr $col] -sticky w
1004    grid rowconfigure .top $row -weight 0
1005
1006    wm deiconify .top
1007}
1008
1009proc all_or_summary {} {
1010    set text [.top.all cget -text]
1011    set win [get_machine_windows]
1012    set rowcol [grid size .top]
1013    set rows [lindex $rowcol 1]
1014
1015    if {"$text" == "Summary"} {
1016	.top.all configure -text "All"
1017	foreach w $win {
1018	    grid remove $w
1019	}
1020	for {set i 1} {$i < [expr $rows - 2]} {incr i} {
1021	    grid rowconfigure .top $i -weight 0
1022	}
1023    } else {
1024	.top.all configure -text "Summary"
1025	foreach w $win {
1026	    grid $w
1027	}
1028	for {set i 1} {$i < [expr $rows - 2]} {incr i} {
1029	    grid rowconfigure .top $i -weight 1
1030	}
1031    }
1032
1033    # Cancel any user-specified geometry
1034    wm geometry .top ""
1035}
1036
1037proc reconfigure {} {
1038    global Machines
1039    global NewStyle
1040    if {$NewStyle} {
1041	reconfigure_new_style
1042	return
1043    }
1044
1045    set index 0
1046    foreach m $Machines {
1047	grid_machine $m $index
1048	incr index
1049    }
1050
1051    # Top row of labels
1052    catch { destroy .top.busy }
1053    catch { destroy .top.persec }
1054    catch { destroy .top.time }
1055    catch { destroy .top.c }
1056    catch { destroy .top.name }
1057
1058    label .top.name -text "Machine Name"
1059    label .top.busy -text "Busy Workers" -foreground "#A00000"
1060    label .top.persec -text "Msgs/s" -foreground "#00A000"
1061    label .top.time -text " ms/scan " -foreground "#0000A0"
1062    label .top.c -text ""
1063    grid .top.name -row 0 -column 0 -sticky new
1064    grid .top.busy -row 0 -column 1 -sticky new
1065    grid .top.persec -row 0 -column 2 -sticky new
1066    grid .top.time -row 0 -column 3 -sticky new
1067    grid .top.c -row 0 -column 4 -sticky new
1068
1069    grid rowconfigure .top 0 -weight 0
1070    # If a machine has been deleted, destroy its windows
1071    catch { destroy .top.name$index}
1072    catch { destroy .top.busy$index}
1073    catch { destroy .top.persec$index}
1074    catch { destroy .top.time$index}
1075    catch { destroy .top.c$index}
1076
1077    incr index
1078    # Bottom row of labels
1079    catch { destroy .top.busytotal }
1080    catch { destroy .top.persectotal }
1081    catch { destroy .top.avgtime }
1082    catch { destroy .top.totalrow }
1083    catch { destroy .top.c$index }
1084
1085    # Mop up total window if a machine has been deleted
1086    set i [expr $index + 1]
1087    catch { destroy .top.c$i }
1088
1089    label .top.totalrow -text "Totals:"
1090    label .top.busytotal
1091    label .top.persectotal
1092    label .top.avgtime
1093    canvas .top.c$index -width 400 -height 60 -takefocus 0 -borderwidth 0 -background #FFFFF0 -highlightthickness 0
1094
1095    grid .top.totalrow -row $index -column 0 -sticky new
1096    grid .top.busytotal -row $index -column 1 -sticky new
1097    grid .top.persectotal -row $index -column 2 -sticky new
1098    grid .top.avgtime -row $index -column 3 -sticky new
1099    grid .top.c$index -row $index -column 4 -sticky nsew -pady 1
1100    grid rowconfigure .top $index -weight 3
1101    incr index
1102    # Now a spot for adding a new machine...
1103    catch { destroy .top.newlab }
1104    catch { destroy .top.new }
1105
1106    label .top.newlab -text "Add Machine: "
1107    entry .top.new -width 20
1108    grid .top.newlab -row $index -column 0
1109    grid .top.new -row $index -column 1 -columnspan 3 -sticky ew
1110    bind .top.new <Return> interactive_add_machine
1111    button .top.all -text "Summary" -command all_or_summary
1112    grid .top.all -row $index -column 4 -sticky w
1113    grid rowconfigure .top $index -weight 0
1114
1115    grid columnconfigure .top 0 -weight 0
1116    grid columnconfigure .top 1 -weight 0
1117    grid columnconfigure .top 2 -weight 0
1118    grid columnconfigure .top 3 -weight 0
1119    grid columnconfigure .top 4 -weight 1
1120    wm deiconify .top
1121}
1122
1123proc busyworkers { mach } {
1124    global ctr
1125    global Data
1126    incr ctr
1127    set w .workers$ctr
1128    catch { destroy $w }
1129    toplevel $w
1130    wm title $w "Busy workers: $mach"
1131    wm iconname $w "$mach workers"
1132    set Data($mach,busyworkerwin) $w
1133
1134    # Open a new SSH connection for the busyworkers info
1135    global SSHCommand
1136    global MD_MX_CTRL
1137    set hmach [host_plus_user $mach]
1138    set fp [open "| $SSHCommand $hmach $MD_MX_CTRL" "r+"]
1139    fconfigure $fp -blocking 0
1140    fileevent $fp readable [list busyworkers_readable $mach $fp]
1141
1142    tickle_busyworkers $mach $fp
1143
1144    text $w.t -width 80 -height 35
1145    pack $w.t -side left -expand 1 -fill both
1146    $w.t tag bind pid <Enter> [list enter_pid $w.t]
1147    $w.t tag bind pid <Leave> [list leave_pid $w.t]
1148    $w.t tag bind pid <ButtonPress-1> [list trace_worker $w.t $mach]
1149}
1150
1151proc tickle_busyworkers { mach fp } {
1152    global Data
1153
1154    catch {
1155	set Data($mach,busydata) ""
1156	# We have to use the old command for backware-compatibility.
1157	puts $fp "busyslaves\nfoo_no_such_command"
1158	flush $fp
1159    }
1160}
1161
1162proc busyworkers_readable { mach fp } {
1163    global Data
1164
1165    gets $fp line
1166    if {"$line" == ""} {
1167	if {[eof $fp]} {
1168	    close $fp
1169	    catch { destroy $Data($mach,busyworkerwin) }
1170	}
1171	return
1172    }
1173    if {"$line" != "error: Unknown command"} {
1174	lappend Data($mach,busydata) $line
1175	return
1176    }
1177    update_busyworkers $mach $fp
1178    global BusyWorkerUpdateInterval
1179    after $BusyWorkerUpdateInterval [list tickle_busyworkers $mach $fp]
1180}
1181proc trace_worker { w mach } {
1182    global TraceCommand
1183    set tags [$w tag names current]
1184    set index [lsearch -glob $tags "Z*"]
1185    if {$index >= 0} {
1186	set tag [lindex $tags $index]
1187	set pid [string range $tag 1 end]
1188	ssh $mach "$TraceCommand $pid" "Process $pid on $mach"
1189    }
1190}
1191proc enter_pid { w } {
1192    set tags [$w tag names current]
1193    set index [lsearch -glob $tags "Z*"]
1194    if {$index >= 0} {
1195	set tag [lindex $tags $index]
1196	$w tag configure $tag -foreground "#A00000"
1197    }
1198}
1199
1200proc leave_pid { w } {
1201    set tags [$w tag names current]
1202    set index [lsearch -glob $tags "Z*"]
1203    if {$index >= 0} {
1204	set tag [lindex $tags $index]
1205	$w tag configure $tag -foreground "#000000"
1206    }
1207}
1208
1209proc compare_workers { a b } {
1210    set acmd [lindex $a 3]
1211    set bcmd [lindex $b 3]
1212    set x [string compare $bcmd $acmd]
1213    if {$x != 0} {
1214	return $x
1215    }
1216
1217    set an [lindex $a 0]
1218    set bn [lindex $b 0]
1219
1220    set aago [lindex $a 5]
1221    set bago [lindex $b 5]
1222    if {[string match "ago=*" $aago] && [string match "ago=*" $bago]} {
1223	set aago [string range $aago 4 end]
1224	set bago [string range $bago 4 end]
1225	set x [expr $bago - $aago]
1226	if {$x != 0} {
1227	    return $x
1228	}
1229    }
1230
1231    return [expr $an - $bn]
1232}
1233
1234proc update_busyworkers { mach fp} {
1235    global Data
1236    set w $Data($mach,busyworkerwin)
1237    if {![winfo exists $w]} {
1238	catch { close $fp }
1239	return
1240    }
1241    $w.t configure -state normal
1242    $w.t delete 1.0 end
1243
1244    # Clear out tags
1245    foreach tag [$w.t tag names] {
1246	if {"$tag" != "pid"} {
1247	    $w.t tag delete $tag
1248	}
1249    }
1250
1251    set busyguys [lsort -command compare_workers $Data($mach,busydata)]
1252
1253    set count(scan) 0
1254    set count(relayok) 0
1255    set count(senderok) 0
1256    set count(recipok) 0
1257
1258    foreach line $busyguys {
1259	set lst [split $line]
1260	set workerno [lindex $lst 0]
1261	set pid [lindex $lst 2]
1262	set cmd [lindex $lst 3]
1263	incr count($cmd)
1264	set len [string length "$workerno B $pid "]
1265	set line [string range $line $len end]
1266	$w.t insert end [format "%4d" $workerno] workerno
1267	$w.t insert end " "
1268	$w.t tag delete "Z$pid"
1269	$w.t insert end [format "%6d" $pid] [list pid "Z$pid"]
1270	$w.t insert end " $line\n"
1271
1272    }
1273
1274    set title "Busy workers: $mach"
1275    foreach cmd {scan relayok senderok recipok} {
1276	if {$count($cmd) > 0} {
1277	    set c $count($cmd)
1278	    append title " $cmd=$c"
1279	}
1280    }
1281    wm title $w $title
1282}
1283
1284proc popup_machine_menu { m index x y} {
1285    catch { destroy .m }
1286    menu .m -tearoff 0
1287    .m add command -label "SSH" -command [list ssh $m]
1288    .m add command -label "Busy Workers" -command [list busyworkers $m]
1289    .m add separator
1290    .m add command -label "Delete" -command [list del_machine $m]
1291    tk_popup .m $x $y
1292}
1293
1294proc grid_machine_new_style { m index } {
1295    global NewStyleShowScans NewStyleShowRelayoks NewStyleShowSenderoks NewStyleShowRecipoks
1296    set m [lindex $m 0]
1297
1298    set disp_m $m
1299    if {[regexp {@(.*)$} $m foo host]} {
1300	set disp_m $host
1301    }
1302
1303    # Chop off domain name from host
1304    if {[regexp {^([^.]+)\.} $disp_m foo new_m]} {
1305	set disp_m $new_m
1306    }
1307    set row [expr $index + 1]
1308    catch { destroy .top.name$index }
1309    catch { destroy .top.busy$index }
1310    catch { destroy .top.scanspersec$index }
1311    catch { destroy .top.scantime$index }
1312    catch { destroy .top.relayspersec$index }
1313    catch { destroy .top.relaytime$index }
1314    catch { destroy .top.senderspersec$index }
1315    catch { destroy .top.sendertime$index }
1316    catch { destroy .top.recipspersec$index }
1317    catch { destroy .top.reciptime$index }
1318    catch { destroy .top.c$index }
1319
1320    set column 2
1321    set canv_width 200
1322    label .top.name$index -text $disp_m -relief raised
1323    bind .top.name$index <ButtonPress-1> [list popup_machine_menu $m $index %X %Y]
1324    bind .top.name$index <ButtonPress-2> [list popup_machine_menu $m $index %X %Y]
1325    bind .top.name$index <ButtonPress-3> [list popup_machine_menu $m $index %X %Y]
1326    label .top.busy$index -text "" -foreground "#A00000"
1327    grid .top.name$index -row $row -column 0 -sticky new
1328    grid .top.busy$index -row $row -column 1 -sticky new
1329
1330    if {$NewStyleShowScans} {
1331	label .top.scanspersec$index -foreground "#00A000"
1332	grid .top.scanspersec$index -row $row -column $column -sticky new
1333	incr column
1334	label .top.scantime$index -foreground "#0000A0"
1335	grid .top.scantime$index -row $row -column $column -sticky new
1336	incr column
1337	incr canv_width 200
1338    }
1339    if {$NewStyleShowRelayoks} {
1340	label .top.relayspersec$index -foreground "#808000"
1341	grid .top.relayspersec$index -row $row -column $column -sticky new
1342	incr column
1343	label .top.relaytime$index -foreground "#008080"
1344	grid .top.relaytime$index -row $row -column $column -sticky new
1345	incr column
1346	incr canv_width 200
1347    }
1348    if {$NewStyleShowSenderoks} {
1349	label .top.senderspersec$index -foreground "#808080"
1350	grid .top.senderspersec$index -row $row -column $column -sticky new
1351	incr column
1352	label .top.sendertime$index -foreground "#800080"
1353	grid .top.sendertime$index -row $row -column $column -sticky new
1354	incr column
1355	incr canv_width 200
1356    }
1357    if {$NewStyleShowRecipoks} {
1358	label .top.recipspersec$index -foreground "#008000"
1359	grid .top.recipspersec$index -row $row -column $column -sticky new
1360	incr column
1361	label .top.reciptime$index -foreground "#000000"
1362	grid .top.reciptime$index -row $row -column $column -sticky new
1363	incr column
1364	incr canv_width 200
1365    }
1366    canvas .top.c$index -width $canv_width -height 60 -takefocus 0 -borderwidth 0 -background #FFFFFF -highlightthickness 0
1367    grid .top.c$index -row $row -column $column -sticky nsew -pady 1
1368    grid rowconfigure .top $row -weight 1
1369}
1370proc grid_machine { m index } {
1371    set m [lindex $m 0]
1372    set row [expr $index + 1]
1373
1374    catch { destroy .top.name$index}
1375    catch { destroy .top.busy$index}
1376    catch { destroy .top.persec$index}
1377    catch { destroy .top.time$index}
1378    catch { destroy .top.c$index}
1379
1380    set disp_m $m
1381    if {[regexp {@(.*)$} $m foo host]} {
1382	set disp_m $host
1383    }
1384
1385    label .top.name$index -text $disp_m -relief raised
1386    bind .top.name$index <ButtonPress-1> [list popup_machine_menu $m $index %X %Y]
1387    bind .top.name$index <ButtonPress-2> [list popup_machine_menu $m $index %X %Y]
1388    bind .top.name$index <ButtonPress-3> [list popup_machine_menu $m $index %X %Y]
1389    label .top.busy$index -text ""
1390    label .top.persec$index -text ""
1391    label .top.time$index -text ""
1392    canvas .top.c$index -width 600 -height 60 -takefocus 0 -borderwidth 0 -background white -highlightthickness 0
1393    .top.c$index create text 2 2 -anchor nw -text "" -tags statusText
1394    grid .top.name$index -row $row -column 0 -sticky new
1395    grid .top.busy$index -row $row -column 1 -sticky new
1396    grid .top.persec$index -row $row -column 2 -sticky new
1397    grid .top.time$index -row $row -column 3 -sticky new
1398    grid .top.c$index -row $row -column 4 -sticky nsew -pady 1
1399    grid rowconfigure .top $row -weight 1
1400
1401}
1402
1403proc kick_off_update {} {
1404    global Machines
1405    global NewStyle
1406    global DoneARedrawSinceLastUpdate
1407    global MachinesAwaitingReply
1408    global MainUpdateInterval
1409
1410    if {$DoneARedrawSinceLastUpdate} {
1411	set DoneARedrawSinceLastUpdate 0
1412	if {$NewStyle} {
1413	    set cmd "rawload1 60"
1414	} else {
1415	    set cmd "rawload"
1416	}
1417
1418	set MachinesAwaitingReply {}
1419	foreach m $Machines {
1420	    catch {
1421		set fp [lindex $m 1]
1422		puts $fp $cmd
1423		flush $fp
1424		lappend MachinesAwaitingReply [lindex $m 0]
1425	    }
1426	}
1427    }
1428    after $MainUpdateInterval kick_off_update
1429}
1430
1431## translated from C-code in Blt, who got it from:
1432##      Taken from Paul Heckbert's "Nice Numbers for Graph Labels" in
1433##      Graphics Gems (pp 61-63).  Finds a "nice" number approximately
1434##      equal to x.
1435proc nicenum {x floor} {
1436
1437    if {$x == 0} {
1438	return 0
1439    }
1440
1441    set negative 0
1442
1443    if {$x < 0} {
1444        set x [expr -$x]
1445        set negative 1
1446    }
1447
1448    set exponX [expr floor(log10($x))]
1449    set fractX [expr $x/pow(10,$exponX)]; # between 1 and 10
1450    if {$floor} {
1451        if {$fractX < 2.0} {
1452            set nf 1.0
1453	} elseif {$fractX < 3.0} {
1454	    set nf 2.0
1455	} elseif {$fractX < 4.0} {
1456	    set nf 3.0
1457	} elseif {$fractX < 5.0} {
1458            set nf 4.0
1459        } elseif {$fractX < 10.0} {
1460            set nf 5.0
1461        } else {
1462	    set nf 10.0
1463        }
1464    } elseif {$fractX <= 1.0} {
1465        set nf 1.0
1466    } elseif {$fractX <= 1.5} {
1467	set nf 1.5
1468    } elseif {$fractX <= 2.0} {
1469        set nf 2.0
1470    } elseif {$fractX <= 2.5} {
1471        set nf 2.5
1472    } elseif {$fractX <= 3.0} {
1473	set nf 3.0
1474    } elseif {$fractX <= 4.0} {
1475	set nf 4.0
1476    } elseif {$fractX <= 5.0} {
1477        set nf 5.0
1478    } elseif {$fractX <= 6.0} {
1479        set nf 6.0
1480    } elseif {$fractX <= 8.0} {
1481        set nf 8.0
1482    } else {
1483        set nf 10.0
1484    }
1485    if { $negative } {
1486        return [expr -$nf * pow(10,$exponX)]
1487    } else {
1488	set value [expr $nf * pow(10,$exponX)]
1489	return $value
1490    }
1491}
1492
1493proc human_number { num } {
1494    if {$num <= 1000} {
1495	return [strip_zeros [format "%.1f" $num]]
1496    }
1497    set num [expr $num / 1000.0]
1498    if {$num <= 1000} {
1499	set num [strip_zeros [format "%.1f" $num]]
1500	return "${num}K"
1501    }
1502    set num [expr $num / 1000.0]
1503    if {$num <= 1000} {
1504	set num [strip_zeros [format "%.1f" $num]]
1505	return "${num}M"
1506    }
1507    set num [expr $num / 1000.0]
1508    set num [strip_zeros [format "%.1f" $num]]
1509    return "${num}G"
1510}
1511
1512proc pick_color { host } {
1513    set color 0
1514    set components {AA BB CC EE}
1515
1516    catch { set host [lindex $host end] }
1517    set host [split $host ""]
1518    foreach char $host {
1519	set color [expr $color + 1]
1520	binary scan $char "c" x
1521	incr color $x
1522	if { $color <= 0 } {
1523	    set color [expr $x + 1]
1524	}
1525    }
1526    set ans "#"
1527    expr srand($color)
1528    for {set i 0} {$i < 3} {incr i} {
1529	set off [expr int(4.0 * rand())]
1530	append ans [lindex $components $off]
1531    }
1532    return $ans
1533}
1534
1535proc ssh { host {cmd ""} {title ""}} {
1536    set color [pick_color $host]
1537    if {"$title" == ""} {
1538	set title "SSH $host"
1539    }
1540    global SSHCommand
1541    set hmach [host_plus_user $host]
1542    exec xterm -hold -title $title -bg #000000 -fg $color -e $SSHCommand $hmach $cmd &
1543}
1544
1545wm withdraw .
1546foreach mach $argv {
1547    if {"$mach" == "-archive"} {
1548	set DoArchive 1
1549	continue
1550    }
1551    if {"$mach" == "-d"} {
1552	set NewStyleTimeInterval 86400
1553	continue
1554    }
1555    if {"$mach" == "-h"} {
1556	set NewStyleTimeInterval 3600
1557	continue
1558    }
1559
1560    if {"$mach" == "-n"} {
1561	set NewStyle 1
1562	set NewStyleShowScans 1
1563	continue
1564    }
1565    if {"$mach" == "-r"} {
1566	set NewStyle 1
1567	set NewStyleShowRelayoks 1
1568	continue
1569    }
1570    if {"$mach" == "-s"} {
1571	set NewStyle 1
1572	set NewStyleShowSenderoks 1
1573	continue
1574    }
1575    if {"$mach" == "-t"} {
1576	set NewStyle 1
1577	set NewStyleShowRecipoks 1
1578	continue
1579    }
1580    add_machine $mach
1581}
1582
1583catch { destroy .top}
1584toplevel .top
1585wm title .top "Watch Multiple MIMEDefangs"
1586wm iconname .top "MIMEDefangs"
1587wm withdraw .top
1588reconfigure
1589wm deiconify .top
1590update
1591kick_off_update
1592tkwait window .top
1593exit
1594