Version 0 of CPU Monitor

Updated 2013-03-08 22:00:17 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

=====