George Peter Staplin: I had a recent need to track the memory usage of an application using my code. I found it daunting to stare at top and other tools, so I wrote a graphing tool with Tcl/Tk. It uses ps to monitor the resident set size, and the virtual size of an application. Peak values for RSS or VSZ are displayed in a larger font, in orange color. It's easy to jump to the peaks too. Thus far it has been very useful in my quest to optimize memory usage patterns, and has aided in the elimination of leaks.
Example usage:
$ monitor_memory_graph-3.tcl 9332 50
(50 is the scaling factor for the graph. Different applications may need a different scaling factor to make a graph that pleases you.)
Change History
2006.07.20 Revision 3:
I changed vsz= to vsize= after Will Dye pointed out that it fails in Linux with vsz. I didn't realize that vsz was an alias for vsize in NetBSD until now.
2007.08.27
I fixed a bug with the exec of ps in Linux. It seems that the Linux or GNU ps doesn't accept -o vsize=,rss=. So I've fixed that. The code is also available here: http://megapkg.googlecode.com/svn/trunk/tools/monitor_memory_graph-3.tcl
#!/usr/bin/env tclkit8.5 package require Tk set ::cursor(x) 1 set ::rsspeak 0 set ::rsspeaktext "" set ::vszpeak 0 set ::vszpeaktext "" set ::follow_graph 1 proc draw.value {win value height id peak_ptr peaktext_ptr} { global cursor scale upvar $peak_ptr peak upvar $peaktext_ptr peaktext set lasty [lindex [$win coords $id] end] # 800 is a good height for the graph offset set y [expr {800 - ($value / double($scale))}] $win coords $id [concat [$win coords $id] [list $cursor(x) $y]] # # Test if this value is greater than the previous peak. # if {$value > $peak} { set peak $value # # Change the previous peak's color # if {"" ne $peaktext} { $win itemconfigure $peaktext -fill white -font {fixed 10} } set peaktext [$win create text $cursor(x) $y -text $value \ -fill orange -font {fixed 14}] } else { $win create text $cursor(x) $y -text $value -fill white -font {fixed 10} } } proc update.stats {win pid} { global cursor if {[catch {exec ps -p $pid -o vsize= -o rss=} res]} { return -code error $res } lassign [string trim $res] vsz rss global rsspeak rsspeaktext set height [winfo height $win] draw.value $win $rss $height $::rssid rsspeak rsspeaktext incr cursor(x) 50 global vszpeak vszpeaktext draw.value $win $vsz $height $::vszid vszpeak vszpeaktext incr cursor(x) 50 $win config -scrollregion [$win bbox all] if {$::follow_graph} { $win xview moveto 1.0 } after 1000 [list update.stats $win $pid] } proc view.tag {win tag} { lassign [$win bbox all] x1 y1 x2 y2 foreach [$win bbox $tag] tx1 ty1 _ _ $win xview moveto [expr {$tx1 / double($x2)}] $win yview moveto [expr {$ty1 / double($y2)}] } proc main {argc argv} { if {2 != $argc} { puts stderr "syntax is: [info script] pid scale" return 1 } frame .controls pack [checkbutton .controls.follow -text "Follow Graph" -variable ::follow_graph] -side left pack [button .controls.rssPeak -text "Jump to RSS Peak" -command { set ::follow_graph 0 view.tag .graph.c $::rsspeaktext }] -side left pack [button .controls.vsz -text "Jump to VSZ Peak" -command { set ::follow_graph 0 view.tag .graph.c $::vszpeaktext }] -side left pack .controls -fill x frame .graph canvas .graph.c \ -xscrollcommand {.graph.xscroll set} \ -yscrollcommand {.graph.yscroll set} \ -bg black scrollbar .graph.xscroll -command {.graph.c xview} -orient horizontal scrollbar .graph.yscroll -command {.graph.c yview} pack .graph -fill both -expand 1 grid .graph.c -row 0 -column 0 -sticky nesw grid .graph.xscroll -row 1 -column 0 -sticky we grid .graph.yscroll -row 0 -column 1 -sticky ns grid columnconfigure .graph 0 -weight 100 grid rowconfigure .graph 0 -weight 100 set ::vszid [.graph.c create line 0 0 1 1 -fill red] set ::rssid [.graph.c create line 0 0 1 1 -fill green] set ::scale [lindex $argv 1] update idletasks after idle [list update.stats .graph.c [lindex $argv 0]] } main $::argc $::argv