Version 1 of CPU Monitor

Updated 2013-03-08 22:00:43 by kpv

Keith Vetter 2013-03-08 : Here's a cpu activity widget that shows the amount of activity for each cpu on your machine.

It's Linux specific--it uses mpstat and /proc/cpuinfo.

It works by running mpstat which writes cpu usage information which a fileevent reads mpstats and updates the display. mpstat has a 1 second granularity. I wrote a version that read from /proc/stat directly but faster granularity wasn't worth the extra complexity.

##+##########################################################################
#
# mpstatPlot -- visual display of mpstat idle value for all processors
# by Keith Vetter 2013-02-22
#

package require Tk

set S(bar,width) 20
set S(bar,height) 87
set S(padding) 3
set S(border) 5

set CLR(bg) lightyellow
set CLR(rainbow) [list magenta yellow green cyan3 red blue purple]

proc DoDisplay {} {
    global S CLR

    wm overrideredirect . 1
    wm geometry . +200+200
    
    set width [expr {$S(cpus) * ($S(bar,width)+$S(padding)) + $S(padding)}]
    set height [expr {$S(bar,height)}]

    frame .f -bd $S(border) -relief flat -bg navyblue
    canvas .c -highlightthickness 0 -bd 0 -width $width -height $height -bg $CLR(bg)
    pack .f
    pack .c -in .f
    
    bind . <Button-3> exit
    bind . <Control-Button-1> Hide
    WindowMove setup .
    
    # Draw our bar graph
    for {set cpu 0} {$cpu < $S(cpus)} {incr cpu} {
        set x0 [expr {$cpu * ($S(bar,width) + $S(padding)) + $S(padding)}]
        set x1 [expr {$x0 + $S(bar,width)}]
        set y0 $S(bar,height)
        set y1 0

        set clr [lindex $CLR(rainbow) [expr {$cpu % [llength $CLR(rainbow)]}]]
        .c create rect $x0 $y0 $x1 $y1 -fill white
        .c create rect $x0 $y0 $x1 $y1 -tag bar$cpu -fill $clr
    }
}
##+##########################################################################
# 
# StatsOneLine -- Processes one line from mpstat and updates display
# 
proc StatsOneLine {line} {
    lassign $line when ampm cpu usr nice sys iowait irq soft steal guest idle
    if {[string is integer -strict $cpu]} {
        AdjustBarHeight $cpu $idle
    }
}
##+##########################################################################
# 
# Parse /proc/cpuinfo for the number of cpus on this machine
# 
proc HowManyProcessors {} {
    set fin [open /proc/cpuinfo r]
    set data [read $fin]; list
    close $fin
    set ::S(cpus) [llength [regexp -all -inline processor $data]]
    set ::S(cpus,list) {}
    for {set i 0} {$i < $::S(cpus)} {incr i} {
        lappend ::S(cpus,list) $i
    }
}
##+##########################################################################
# 
# AdjustBarHeight -- changes the bar graph height for a particular cpu
# 
proc AdjustBarHeight {cpu percent} {
    set tag bar$cpu
    lassign [.c coords $tag] x0 y0 x1 y1
    set newY [expr {$::S(bar,height) - $::S(bar,height)*$percent/100}]
    .c coords $tag $x0 $newY $x1 $y1
}
##+##########################################################################
# 
# WindowMove -- Allows mouse to move window (window manager can't with overrideredirect)
# 
proc WindowMove {what w} {
    global _WM_

    set w [winfo toplevel $w]
    if {$what eq "setup"} {
        set _WM_($w,moving) {}
        bind $w <1> [list WindowMove down %W]
        bind $w <Motion> [list WindowMove move %W]
        bind $w <ButtonRelease-1> [list WindowMove up %W]
        return
    }
    
    if {$what eq "down"} {
        set dx [expr {[winfo pointerx $w] - [winfo x $w]}]
        set dy [expr {[winfo pointery $w] - [winfo y $w]}]
        set _WM_($w,moving) [list $dx $dy]
    } elseif {$what eq "up"} {
        set _WM_($w,moving) {}
    } else {
        if {! [info exists _WM_($w,moving)]} return
        if {$_WM_($w,moving) eq {}} return
        lassign $_WM_($w,moving) dx dy
        set gx [expr {[winfo pointerx $w] - $dx}]
        set gy [expr {[winfo pointery $w] - $dy}]
        wm geom $w +$gx+$gy
    }
}
##+##########################################################################
# 
# Go -- Open pipe to mpstat and set up a fileevent handler
# 
proc Go {{cnt 30}} {
    set fin [open "|mpstat -P [join $::S(cpus,list) ,] 1 $cnt" r]

    fconfigure $fin -blocking false
    fileevent $fin readable [list isReadable $fin]
    vwait ::DONE

    close $fin
}
##+##########################################################################
# 
# isReadable -- Called when another line from mpstat is available
# 
proc isReadable { fin } {
    set status [catch { gets $fin line } result]
    
    if { $status != 0 } {
        # Error on the channel
        puts "error reading $fin: $result"
        set ::DONE 2
    } elseif { $result >= 0 } {
        # Successfully read the channel
        if {![string match {[0-9]*} $line]} return
        set stats [StatsOneLine $line]
    } elseif { [eof $fin] } {
        set ::DONE 1
    } elseif { [fblocked $fin] } {
        # Read blocked.  Just return
    } else {
        # Something else
        puts "can't happen"
        set ::DONE 3
    }
}
##+##########################################################################
# 
# Withdraw ourselves temporarily
# 
proc Hide {} {
    wm withdraw .
    after 5000 wm deiconify .
}

################################################################

if {! [file exists /proc/cpuinfo] || [auto_execok mpstat] eq ""} {
    tk_messageBox -icon error -message "Only works on Linux" -title "mpstat Plot : ERROR"
    return
}
HowManyProcessors
DoDisplay
update

after idle {Go ""}
return