#!/usr/bin/wish -f ##+######################################################################## ## ## SCRIPT: meters_cpus_usage.tk ## ## PURPOSE: This script is meant to show a GUI with a ## tachometer-style METER which shows the PERCENT-ACTIVITY ## of a CPU. The CPU could be one of several on a computer, ## or an average for all the CPU's of the computer. ## ## For a user-specified time interval, the sum of ## 'user', 'nice', 'system', and 'idle' times is found for ## a user-specified CPU. The sum of the first 3 times over the ## sum of the four is used to give the PERCENT-ACTIVITY of the CPU. ## ## This utility is designed for computer operating systems of the ## Linux/Unix/BSD type --- computers that have a '/proc/stat' (or ## similar) file that gives the user-nice-system-idle data for each ## cpu. This utility is also designed for computer operating ## systems that have commands like 'grep' and 'awk'. ## ## On computers with a single CPU, the meter simply shows an ## 'activity measure' for that one CPU. ## ## On computers with multiple CPU's, we default to showing ## an 'activity measure' for ALL the CPU's (i.e. an average ## for all the CPU's). And we allow the user to specify a ## particular CPU whose 'activity' is shown on the meter. ## ## We could allow MULTIPLE METERS to be shown --- say 4 --- ## showing %-user, %-nice, %-system, and %-idle --- for ## a user-specified CPU. But ... ## ## To simplify, we show just ONE METER (for a user-specified ## CPU) --- with the %-user+nice+system (the 'activity') ## being shown. ## ## When the GUI first appears, the meter will show the %-activity ## for the 'average CPU' (the sum of the CPU's). ## ## Of course, a Tcl-Tk programmer could change this script to ## initially show a single meter for the 'average' CPU --- AND the ## GUI could be implemented to allow the user to show 4 meters, say ## (user,nice,system,idle) for the initial 'average' CPU. ## Further, the user could change from the 'average' CPU to any of ## the individual CPU's. But ... ## ## For relative simplicity of coding, we stay with a single ## meter showing %-user+nice+system --- not multiple meters ## showing %-user, %-nice, %-system, %-idle for a given CPU. ## ## USAGE of a USER-SPECIFIED CPU: ## ## This script was developed on Linux and uses 'cat /proc/stat' ## to get 'activity' data for individual CPU's and for the ## 'total'/'average' CPU. ## ## A pop-out menu of the available CPU's is provided via a button ## on the GUI. Thus the user can select the CPU whose %-activity ## is shown on the meter. ## ## A red-line area of the meter indicates when the ## %-activity gets above about 85% or 90%. ## ## The %-user and %-system is also shown as text just above ## the meter. ## ## AUTO-UPDATE of the METER-NEEDLE and TEXT-DATA for the CPU: ## ## The needle on the meter is updated periodically to ## show the percent-activity for the user-selected CPU. ## ## The amount of time between updates is controlled by a ## scale widget on the GUI --- defaulted to about 5 seconds, ## say. And a 'Refresh' button on the GUI can be used to ## reset the start time for sampling --- at the current ## rate (sampling interval, set via the scale widget). ## ## A 'REPORT' OPTION: ## ## The 'cat /proc/stat' command can be used to show all the ## CPU 'ticks' (a.k.a. 'jiffies' - about 1/100th of a second) data, ## for ALL the CPU's of the computer. ## ## The user can click on a 'Report' button on the ## GUI to show the report in a pop-up scrollable Tk text widget. ## The user can copy-paste the text from that window to another ## window such as a text editor window or a word processor window. ## ## EXAMPLE 'cat /proc/stat' OUTPUT ... from a computer with 2 CPU's : ## ## $ cat /proc/stat ## cpu 183112 1474 25701 1008114 5609 357 1301 0 0 ## cpu0 96100 982 12954 469961 2710 77 88 0 0 ## cpu1 87011 491 12746 538152 2898 280 1213 0 0 ## intr 3118276 1241284 1838 0 0 0 0 0 0 1 17228 0 0 2573 0 0 0 869 62033 0 0 0 0 0 0 0 0 27446 59166 16612 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ## ctxt 4455035 ## btime 1378614163 ## processes 2381 ## procs_running 2 ## procs_blocked 0 ## softirq 1569874 0 1120419 10859 10176 27466 4934 307011 551 88458 ## ## This output is explained via the 'man proc' command. See the section ## that starts '/proc/stat'. ## ## The first 4 numbers on each 'cpu' line are the amount of time, measured ## in units of USER_HZ (approx. 1/100ths of a second on most architectures) ## that the system spent in ## 1) user mode, ## 2) user mode with low priority (nice), ## 3) system mode, ## 4) the idle task, ## respectively. The other numbers on each 'cpu' line are described below. ## The time is measured from boot time. The output from two consecutive ## queries can be used to get the cpu-activity (user,nice,system,idle) ## over a sample time interval. ## ## In the above example of 2 CPU's (plus the 'total/average CPU'), we use ## the 1st 4 of the 9 columns of data in the 3 lines starting with 'cpu', ## 'cpu0', and 'cpu1'. ## ## The data for 'cpu' is the combined data for 'cpu0' and 'cpu1'. ## The 4th colum is 'idle time' in 'jiffies' (approx. hundredths of ## seconds). Column 4 usually contains most of the 'jiffies'. ## ## The data in the 9 columns is 'jiffies' due to ## ## 1 * user: normal processes executing in user mode ## 2 * nice: niced processes executing in user mode ## 3 * system: processes executing in kernel mode ## 4 * idle: twiddling thumbs ## 5 * iowait: waiting for I/O to complete ## 6 * irq: servicing interrupts ## 7 * softirq: servicing softirqs ## 8 * 'steal_time' - the ticks spent executing other virtual hosts ## (in virtualised environments like Xen) ## 9 * ticks spent on (virtual) guest systems. ## ## We calculate the %-usage of a CPU by calculating a numerator and ## a denominator. For the numerator, we could sum all of the 9 columns ## except column 4. For the denominator we could use the sum of all 9 ## columns. ## ## Rather than using all 9 columns, we follow what others have done ## before and use the 1st 4 columns: user, nice, system, and idle. ## ## The needle on the meter is updated periodically to show the ## %-activity of the user-specified C, where %-activity means ## ## col1+col2+col3 / col1+col2+col3+col4. ## ## Of course, a Tcl-Tk coder could change calculations in this ## utility to use 9, rather than 4, columns of data. ## ##+################ ## GUI DESCRIPTION: ## ## This script provides a Tk GUI with the following widgets. ## ## 1) There is an 'fRbuttons' frame to hold BUTTONS such as ## 'Exit' 'Help' 'Report' buttons. In addition, there is a ## 'Refresh' button --- to issue the 'cat /proc/stat' command ## and update the needle display on the meter according ## to the latest data sample. ## ## 2) In an 'fRcontrol1' frame, below the 'fRbuttons' frame, ## there is a 'ShowCPUs' button to show the user the list ## of available CPU's. Next to that button is an ## entry widget in which to enter a CPU ID. That CPU ## is the one whose %-activity is shown on the meter. ## ## 3) In an 'fRcontrol2' frame, below the 'fRcontrol1' frame, ## there is a SCALE widget --- ## for the user to set a 'wait-seconds' for auto-refresh of ## the meter data. The 'wait' may be in seconds --- down to ## tenths of seconds, and up to multiple minutes. The scale ## could be set to an initial value for the auto-refresh ## --- say, an initial value of 5 seconds. ## ## 4) There is an 'fRmeters' frame to contain a CANVAS widget ## that holds the meter image, in a SQUARE canvas. ## (Someday, we may wish to put more meters in the ## 'fRmeters' frame --- to show percent-activity for 'user' ## 'system' and other processing.) ## ## The 'fRmeters' frame also holds some LABEL widgets, to show ## %-user and %-system (and perhaps %-nice and %idle), as ## text items --- for the user-selected CPU. ## ##+################################ ## METHOD USED to update the meter(s): ## ## A Tcl 'exec' command calls on a separate shell script that uses ## the 'cat /proc/stat' command to get the CPU usage data ## and reformat the data somehat for return to this Tk script. ## ## The shell script will be capable of returning info of 3 types: ## 1) activity data for ALL the CPU's (since boot time) ## 2) a list of the CPU's ## 3) activity data for a user-specified CPU (since boot time). ## ##+####################### ## CAPTURING THE GUI IMAGE: ## ## A screen/window capture utility (like 'gnome-screenshot' ## on Linux) can be used to capture the GUI image in a PNG ## or GIF file, say. ## ## If necessary, an image editor (like 'mtpaint' on Linux) ## can be used to crop the window capture image. The image ## could also be down-sized --- say to make a smaller image ## suitable for use in a web page or an email. ## ##+####################################################################### ## 'CANONICAL' STRUCTURE OF THIS CODE: ## ## 0) Set general window parms (win-name, win-position, win-color-scheme, ## fonts, widget-geom-parms, win-size-control, text-array-for-labels-etc). ## ## 1a) Define ALL frames (and sub-frames, if any). ## 1b) Pack the frames. ## ## 2) Define & pack all widgets in the frames, frame by frame. ## After all the widgets for a frame are defined, pack them in the frame. ## ## 3) Define keyboard or mouse/touchpad/touch-sensitive-screen 'event' ## BINDINGS, if needed. ## ## 4) Define PROCS, if needed. ## ## 5) Additional GUI INITIALIZATION (typically with one or more of ## the procs), if needed. ## ##+################################# ## Some detail of the code structure of this particular script: ## ## 1a) Define ALL frames: ## ## Top-level : ## '.fRbuttons' - to contain 'Exit', 'Help', 'Report', 'Refresh' buttons ## '.fRcontrol1' - to contain a 'ShowCPUs' button --- and an entry widget ## '.fRcontrol2' - to contain a scale widget, with a label widget. ## '.fRmeters' - to contain square canvas widget(s), which will display ## the the meter --- and perhaps more meters someday. ## ## Sub-frames: ## '.fRmeters.fRmeter1' - for 2 (or more) label widgets & 1 canvas widget ## ## 1b) Pack ALL frames. ## ## 2) Define & pack all widgets in the frames -- basically going through ## frames & their interiors in left-to-right, or top-to-bottom order. ## ## 3) Define BINDINGS: perhaps one or two ## ## 4) Define PROCS: ## ## 'make_tachometers' - to draw up to one (or more) meter(s) within ## Tk (square) canvas(es). ## ## We may allow the canvas(es) to resize according to ## a resizing of the window. This proc will set the ## SQUARE size of the canvases according to the current ## size of the frame containing the canvases. That ## frame will be allowed to expand if the user ## expands the entire GUI window. ## ## 'make_one_tachometer' - called by 'make_tachometers', to make each meter. ## ## 'draw_rivet' - called by 'make_one_tachometer', 4 times, to put ## rivets in 4 corners around a meter. ## ## 'draw_circle_shadow' - called by 'make_one_tachometer' to put a shadowed ## edge around the circle that makes the meter. ## Also called to help make a 'pin' in the center of ## the meter to hold the needle. Also called, ## in 'draw_rivet', to make each of the rivets. ## ## 'update_needles' - to update the needles on the one or more meters. ## ## 'update_one_needle' - called by 'update_needles', to draw the ## needle on each meter. ## ## 'Refresh' - called by 'Refresh' button. Runs ## 'make_tachometers' and 'update_needles'. ## ## 'Report' - called by 'Report' button. ## ## 'show_cpus' - called by 'ShowCPUs' button. ## ## 'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var. ## Also used by the 'Report' button and the ## 'ShowCPUs' button. ## ## 5) Additional GUI Initialization: ## - call 'make_tachometers' to put the meter(s) in the canvas(es). ## - call 'update_needles' --- to initialize the needle location(s). ## ##+####################################################################### ## DEVELOPED WITH: Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october, 'Karmic Koala') ## ## $ wish ## % puts "$tcl_version $tk_version" ## ## showed ## 8.5 8.5 ## but this script should work in most previous 8.x versions, and probably ## even in some 7.x versions (if font handling is made 'old-style'). ##+####################################################################### ## MAINTENANCE HISTORY: ## Started by: Blaise Montandon 2013sep08 Started the basic code of the ## script based on my previous ## meters scripts, which use the ## 'shadow-circle' technique of ## a demo script by Marco Maggi ## at http://wiki.tcl.tk/9107 ## Changed by: Blaise Montandon 2013nov21 Continued the coding on Linux, ## Ubuntu 9.10 after deciding to ## use one meter and allow the ## user to choose which CPU ## will be monitored with the meter. ## Changed by: Blaise Montandon 2013nov27 Develop and test all code except ## the 'Report' proc. ## Changed by: Blaise Montandon 2013dec04 Develop the 'Report' proc and ## the external shell script that ## is called by the 'Report' proc. ##+######################################################################## ##+###################################################### ## Set WINDOW TITLE and POSITION. ##+###################################################### wm title . "CPU Activity" wm iconname . "CPU use" wm geometry . +15+30 ##+###################################################### ## Set the COLOR SCHEME for the window and its widgets --- ## such as listbox and entry field background color. ##+###################################################### tk_setPalette "#e0e0e0" # set listboxBKGD "#ffffff" set entryBKGD "#ffffff" set scaleBKGD "#f0f0f0" set textBKGD "#f0f0f0" ##+######################################################## ## DEFINE (temporary) FONT NAMES. ## ## We use a VARIABLE-WIDTH font for text on LABEL and ## BUTTON widgets. ## ## We use a FIXED-WIDTH font for LISTBOX lists, ## for text in ENTRY fields --- and often for text in ## TEXT widgets. ##+######################################################## font create fontTEMP_varwidth \ -family {comic sans ms} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_varwidth \ -family {comic sans ms} \ -size -12 \ -weight bold \ -slant roman ## Some other possible (similar) variable width fonts: ## Arial ## Bitstream Vera Sans ## DejaVu Sans ## Droid Sans ## FreeSans ## Liberation Sans ## Nimbus Sans L ## Trebuchet MS ## Verdana font create fontTEMP_fixedwidth \ -family {liberation mono} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_fixedwidth \ -family {liberation mono} \ -size -12 \ -weight bold \ -slant roman ## Some other possible fixed width fonts (esp. on Linux): ## Andale Mono ## Bitstream Vera Sans Mono ## Courier 10 Pitch ## DejaVu Sans Mono ## Droid Sans Mono ## FreeMono ## Nimbus Mono L ## TlwgMono ##+########################################################### ## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS. ## (e.g. width and height of canvas, and padding for Buttons) ##+########################################################### ## CANVAS widget geom settings: set initCanWidthPx 200 set initCanHeightPx 200 # set BDwidthPx_canvas 2 set BDwidthPx_canvas 0 ## LABEL widget geom settings: set PADXpx_label 0 set PADYpx_label 0 set BDwidthPx_label 2 ## BUTTON widget geom settings: set PADXpx_button 0 set PADYpx_button 0 set BDwidthPx_button 2 ## ENTRY widget geom settings: set BDwidthPx_entry 2 ## SCALE widget geom parameters: set BDwidthPx_scale 2 set scaleThicknessPx 10 ##+###################################################################### ## Set a MIN-SIZE of the window (roughly). ## ## For WIDTH, allow for the min-width of the '.fRbuttons' frame ## --- the several button widgets in the 'fRbuttons' frame. ## ## For HEIGHT, allow for the stacked frames: ## 1 char high for the '.fRbuttons' frame, ## 1 char high for the '.fRcontrol1' frame, ## 2 chars high for the '.fRcontrol2' frame, ## at least 50 pixels high for the '.fRmeters' frame. ##+##################################################################### ## FOR WIDTH: set minWidthPx [font measure fontTEMP_varwidth \ " Exit Help Report Refresh "] ## We add some pixels to account for right-left-size of ## window-manager decoration (~8 pixels) and some pixels for ## frame/widget borders (~4 widgets x 4 pixels/widget = 16 pixels). set minWinWidthPx [expr {24 + $minWidthPx}] ## For HEIGHT --- for ## 1 char high for 'fRbuttons' ## 1 char high for 'fRcontrol1' ## 2 chars high for 'fRcontrol2' ## ~50 pixels high for 'fRmeters' set charHeightPx [font metrics fontTEMP_varwidth -linespace] set minWinHeightPx [expr {4 * $charHeightPx}] ## Add about 50 pixels for height of the canvas ## AND add about 20 pixels for top-bottom window decoration -- ## and some pixels for top-and-bottom of frame/widget borders ## (~4 widgets x 4 pixels/widget = 16 pixels). set minWinHeightPx [expr {86 + $minWinHeightPx}] ## FOR TESTING: # puts "minWinWidthPx = $minWinWidthPx" # puts "minWinHeightPx = $minWinHeightPx" wm minsize . $minWinWidthPx $minWinHeightPx ## We may allow the window to be resizable. We pack the canvases ## (and the frames that contain them) with '-fill both -expand 1' ## so that the canvases can be enlarged by enlarging the window. ## If you want to make the window un-resizable, ## you can use the following statement. # wm resizable . 0 0 ##+############################################################## ## Set a TEXT-ARRAY to hold text for buttons & labels on the GUI. ## NOTE: This can aid INTERNATIONALIZATION. This array can ## be set according to a nation/region parameter. ##+############################################################## ## if { "$VARlocale" == "en"} ## For '.fRbuttons' frame: set aRtext(buttonEXIT) "Exit" set aRtext(buttonHELP) "Help" set aRtext(buttonREPORT) "Report" set aRtext(buttonREFRESH) "Refresh" ## For '.fRcontrol1' frame: set aRtext(buttonCPUS) "ShowCPUs" set aRtext(labelCPU) " MonitorCPU:" ## For '.fRcontrol2' frame: set aRtext(labelSCALE) "SampleRate (seconds) :" ## END OF if { "$VARlocale" == "en"} ##+################################################################ ## DEFINE-AND-PACK *ALL* THE FRAMES: ## ## Top-level : '.fRbuttons' , '.fRcontrol1', '.fRcontrol2', '.fRmeters' ## ## Sub-frames: '.fRmeters.fRmeter1' one meter, for now. ##+################################################################ ## FOR TESTING: (to see size of frames as window is resized) # set BDwidth_frame 2 # set RELIEF_frame raised set BDwidth_frame 0 set RELIEF_frame flat frame .fRbuttons -relief $RELIEF_frame -bd $BDwidth_frame frame .fRcontrol1 -relief $RELIEF_frame -bd $BDwidth_frame frame .fRcontrol2 -relief $RELIEF_frame -bd $BDwidth_frame frame .fRmeters -relief $RELIEF_frame -bd $BDwidth_frame ##+########################################################## ## SUB-FRAME DEFINITIONS: ##+########################################################## set MAXmeters 1 # set MAXmeters 4 frame .fRmeters.fRmeter1 -relief raised -bd 2 # frame .fRmeters.fRmeter2 -relief raised -bd 2 # frame .fRmeters.fRmeter3 -relief raised -bd 2 # frame .fRmeters.fRmeter4 -relief raised -bd 2 ##+############################## ## PACK the top-level FRAMES. ##+############################## pack .fRbuttons \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRcontrol1 \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRcontrol2 \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRmeters \ -side top \ -anchor nw \ -fill both \ -expand 1 ##+################################################### ## We pack a single canvas sub-frame (meter), for now. ## ## Someday we may allow for at least two meters. ##+################################################### pack .fRmeters.fRmeter1 \ -side left \ -anchor nw \ -fill both \ -expand 1 ##+########################################################## ## The FRAMES ARE PACKED. START PACKING WIDGETS IN THE FRAMES. ##+########################################################## ##+########################################################## ## In FRAME '.fRbuttons' - ## DEFINE-and-PACK 'BUTTON' WIDGETS ## --- Exit, Help, Refresh, Report BUTTONS. ##+########################################################## button .fRbuttons.buttEXIT \ -text "$aRtext(buttonEXIT)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {exit} button .fRbuttons.buttHELP \ -text "$aRtext(buttonHELP)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {popup_msgVarWithScroll .topHelp "$HELPtext"} button .fRbuttons.buttREPORT \ -text "$aRtext(buttonREPORT)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {Report} button .fRbuttons.buttREFRESH \ -text "$aRtext(buttonREFRESH)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {Refresh} ## Pack the widgets in frame '.fRbuttons'. pack .fRbuttons.buttEXIT \ .fRbuttons.buttHELP \ .fRbuttons.buttREPORT \ .fRbuttons.buttREFRESH \ -side left \ -anchor w \ -fill none \ -expand 0 ##+########################################################## ## In FRAME '.fRcontrol1' - ## DEFINE-and-PACK a 'ShowCPUs' BUTTON, and ## a CPU-ID ENTRY widget. ##+########################################################## ## Here is the BUTTON for showing a list of CPU IDs. button .fRcontrol1.buttCPUS \ -text "$aRtext(buttonCPUS)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {show_cpus} label .fRcontrol1.labelCPU \ -text "$aRtext(labelCPU)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label ## Here is the ENTRY field for a CPU name. ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set CPUname "cpu" entry .fRcontrol1.entryCPU \ -textvariable CPUname \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width 9 \ -relief sunken \ -bd $BDwidthPx_entry ## Pack the widgets in frame '.fRcontrol1'. pack .fRcontrol1.buttCPUS \ .fRcontrol1.labelCPU \ .fRcontrol1.entryCPU \ -side left \ -anchor w \ -fill none \ -expand 0 ##+########################################################## ## In FRAME '.fRcontrol2' - ## DEFINE-and-PACK a LABEL-AND-SCALE widget pair --- ## for changing the 'refresh rate' for the meter needle(s). ##+########################################################## ## Here is the LABEL-AND-SCALE pair for the wait-seconds (sample rate). label .fRcontrol2.labelSCALE \ -text "$aRtext(labelSCALE)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set WAITseconds 60 scale .fRcontrol2.scaleSECONDS \ -from 0.1 -to 120.0 \ -resolution 0.1 \ -font fontTEMP_SMALL_varwidth \ -variable WAITseconds \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -length 180 \ -width $scaleThicknessPx ## Here is a label to show the current sample count. label .fRcontrol2.labelCOUNT \ -textvariable VARsampcnt \ -font fontTEMP_varwidth \ -justify right \ -anchor w \ -relief flat \ -bd 0 ## Pack the widgets in frame '.fRcontrol2'. pack .fRcontrol2.labelSCALE \ .fRcontrol2.scaleSECONDS \ .fRcontrol2.labelCOUNT \ -side left \ -anchor w \ -fill none \ -expand 0 ##+######################################################## ## In FRAME '.fRmeters.fRmeter1' - ## DEFINE-and-PACK TWO LABELs and ## ONE CANVAS WIDGET (no scrollbars). ## ## We highlightthickness & borderwidth of the canvas to ## zero, as suggested on page 558, Chapter 37, 'The Canvas ## Widget', in the 4th edition of the book 'Practical ## Programming in Tcl and Tk'. ##+####################################################### label .fRmeters.fRmeter1.labelINFO1 \ -text "" \ -font fontTEMP_SMALL_fixedwidth \ -justify left \ -anchor w \ -relief raised \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label label .fRmeters.fRmeter1.labelINFO2 \ -text "" \ -font fontTEMP_SMALL_fixedwidth \ -justify left \ -anchor w \ -relief raised \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label canvas .fRmeters.fRmeter1.can \ -width $initCanWidthPx \ -height $initCanHeightPx \ -relief flat \ -highlightthickness 0 \ -borderwidth 0 ## Pack the widgets in frame '.fRmeters.fRmeter1'. pack .fRmeters.fRmeter1.labelINFO1 \ .fRmeters.fRmeter1.labelINFO2 \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRmeters.fRmeter1.can \ -side top \ -anchor nw \ -fill none \ -expand 0 ##+################################################## ## END OF DEFINITION of the GUI widgets. ##+################################################## ## Start of BINDINGS, PROCS, Added-GUI-INIT sections. ##+################################################## ##+################################################################## ##+################################################################## ## BINDINGS SECTION: ##+################################################################## bind .fRcontrol1.entryCPU {Refresh} ##+################################################################## ##+################################################################## ## DEFINE PROCS SECTION: ## ## 'make_tachometers' - to draw meter(s) within Tk (square) canvas(es). ## ## We may allow the Tk canvas(es) to resize according to ## a resizing of the window. This proc will draw the ## meter(s) with size based on the current size of the ## 'fRmeters' frame. ## ## 'make_one_tachometer' - to draw one tachometer. Called by 'make_tachometers' ## to make the meter(s). ## ## 'draw_rivet' - called by 'make_tachometers' to put rivets in 4 ## corners around each meter. ## ## 'draw_circle_shadow' - called by 'make_tachometers' to put a shadowed ## edge around the circle that makes each meter. ## Also called by 'make_tachometers' to put ## a shadowed edge on the 'pin' that holds a needle. ## Also called by 'draw_rivet' to put a shadowed ## edge on each rivet. ## ## 'update_needles' - to update the needle(s) on the meter(s). ## Called initially at the bottom of this script. ## And called in the 'Refresh' proc. ## ## 'update_one_needle' - to draw one needle in a specified meter-canvas. ## Called by 'update_needles' to update each of the ## needles on the one or more meters. ## ## 'Refresh' - called by the 'Refresh' button. Runs the procs ## 'make_tachometers' and 'update_needles' --- in particular, ## for the user to force the meters to be resized if ## the user resizes the window --- and whenever the user ## wants a new 'reading'. ## ## 'Report' - called by the 'Report' button. This Report proc ## calls on the 'get_cpu_usage_info.sh' script ## with the 'all' parameter. ## ## 'show_cpus' - called by the 'ShowCPUs' button. This proc ## calls on the 'get_cpu_usage_info.sh' script ## with the 'cpuslist' parameter. ## ## 'popup_msgVarWithScroll' - to show the HELPtext var. Called by the 'Help' button. ## Also used by the 'Report' and 'ShowCPUs' procs. ## ##+################################################################# ##+######################################################################## ## PROC 'make_tachometers' ##+######################################################################## ## PURPOSE: Draws all features of one or more tachometer-style meters (except ## the needles) --- in a 'nice filling-size' according to the ## current canvas dimensions. ## ## (We will allow the canvas to resize according to ## a resizing of the window. This proc will redraw the ## meters in proportion to the new size of the canvas.) ## ## CALLED BY: once, at the 'Additional GUI Initialization' section, ## at the bottom of this script --- and ## in the 'Refresh' proc (which is called by the 'Refresh' ## button, for example, after the window is resized). ##+######################################################################## set Nmeters 1 proc make_tachometers {} { global marginPx Nmeters ## FOR TESTING: (to dummy out this proc) # return ############################################################ ## Get current '.fRmeters' dimensions --- in case the user ## has resized the window, and thus the '.fRmeters' frame, ## which was packed with '-fill both -expand 1'. ############################################################ set curMetersFrameWidthPx [winfo width .fRmeters] set curMetersFrameHeightPx [winfo height .fRmeters] ## Adjust height for 2 text lines in the '.fRmeters' frame ## for %-user and %-system processing activity for the ## user-specified CPU. set charHeightPx [font metrics fontTEMP_SMALL_fixedwidth -linespace] set curMetersFrameHeightPx \ [expr {$curMetersFrameHeightPx - (2 * $charHeightPx)}] ############################################################ ## Set a width-and-height to use for each (square) canvas ## to contain the one (or more) meters. ## (We take ~10 pixels off the height to account for some ## borderwidths of frames within the 'fRmeters' frame.) ############################################################ # set meterSizePx [expr {int(($curMetersFrameWidthPx - 6) / $Nmeters)}] # if {$curMetersFrameHeightPx < $meterSizePx} {set meterSizePx $curMetersFrameHeightPx} set meterSizePx [expr {$curMetersFrameHeightPx - 12}] #################################################################### ## Resize the canvas(es) --- like '.fRmeters.fRmeter1.can' --- that ## hold the one (or more) square meters. ## Note that this/these canvases and their parent frames were all packed ## with '-fill both -expand 1' --- so if the canvas widgets ## expand/contract, then the parent frames should do the same. #################################################################### # for {set i 1} {$i < $Nmeters} {incr i} { # eval .fRmeters.fRmeter$i.can configure -width $meterSizePx # eval .fRmeters.fRmeter$i.can configure -height $meterSizePx # } .fRmeters.fRmeter1.can configure -width $meterSizePx .fRmeters.fRmeter1.can configure -height $meterSizePx if {0} { ## Not needed?? Resizing the Tk canvases will determine the ## size of the 'parent' frames of the canvases?? for {set i 1} {$i < $Nmeters} {incr i} { eval .fRmeters.fRmeter$i configure -width $meterSizePx eval .fRmeters.fRmeter$i configure -height $meterSizePx } } if {0} { ## Not needed?? Resizing the Tk canvases will determine the ## size of the 'parent' frames of the canvases?? set fullWidthPx [expr {($Nmeters * ($meterSizePx + 4)}] .fRmeters configure -width $fullWidthPx .fRmeters configure -height $meterSizePx } ##################################################### ## NEEDED to force the canvases and frames to update ## according to the new canvas sizes. ##################################################### update ######################################################### ## Draw the Nmeters meters (without their needles). ######################################################### # for {set i 1} {$i < $Nmeters} {incr i} { # eval make_one_tachometer .fRmeters.fRmeter$i.can # } ######################################################### ## Draw meter1 (without needle). ######################################################### make_one_tachometer .fRmeters.fRmeter1.can } ## END OF proc 'make_tachometers' ##+######################################################################## ## PROC 'make_one_tachometer' ##+######################################################################## ## PURPOSE: Draws all features of a tachometer-style meter (except the ## needle) --- according to the 'marginPx' parameter to set ## top-right and bottom-left coordinates to specify the location ## of the square exactly containing the circular meter on ## the canvas whose ID is passed into this proc. ## ## The features include: ## - white-filled circle for the meter background ## - a gray-shaded (shadowed) edge around the circle ## - a 'pin' in the center of the circle, for the needle ## - 4 decorative rivets at the corners of the canvas ## - an arc with tic-marks ## - a red danger-zone in the last segment of the arc ## (between the last pair of tic-marks) ## - labels for the tic-marks ## ## CALLED BY: proc 'make_tachometers' ##+####################################################################### ## Set an 'indentation' to use for placing the outer-circle of meter(s) ## from the 4 edges of their respective canvases. set marginPx 12 set pi [expr {4.0 * atan(1.0)}] set radsPERdeg [expr {$pi/180.0}] ## The above variables are set ONCE, for use in the following proc. ## We may set the following tic-marks parms at the bottom ## of this script, in the 'Additional GUI Initialization' section. # set Nsegs 10 # set ticLabels "0 10 20 30 40 50 60 70 80 90 100" proc make_one_tachometer {canvas} { global marginPx pi radsPERdeg Nsegs ticLabels ## FOR TESTING: (to dummy out this proc) # return ################################################################ ## Remove any previously drawn elements in this canvas, if any. ################################################################ catch {$canvas delete all} ################################################################## ## Get the width (= height) of the specified (square) canvas. ################################################################## set curCanvasSizePx [winfo width $canvas] ################################################################## ## Set the corner coords for drawing the meter circle (background). ################################################################## set topleftXpx $marginPx set topleftYpx $marginPx set botrightXpx [expr {$curCanvasSizePx - $marginPx}] set botrightYpx [expr {$curCanvasSizePx - $marginPx}] ################################################ ## Draw basic white-filled circle for the meter. ################################################ $canvas create oval \ $topleftXpx $topleftYpx $botrightXpx $botrightYpx \ -fill white -outline {} # -width 1 -outline lightgray ## FOR TESTING: (exit this proc before adding more to the meter) # return ####################################################################### ## Draw shadow-circle at the outer circle of the meter. ####################################################################### ## INPUTS: ## - the 4 corner coordinates of the oval/circle box (in pixels) ## - number of segments for the arc (segments of differing color shade) ## - width (in pixels) to draw the arc segments ## - start angle for drawing the (darker) arc segments ## (measured counter-clockwise from the 3 o'clock position) ## (An angle of +135=90+45 means the dark side of the 'shadow-circle' ## is on the north-west side of the circle.) ###################################################################### draw_circle_shadow $canvas \ $topleftXpx $topleftYpx $botrightXpx $botrightYpx \ 40 6 135.0 ## FOR TESTING: (exit this proc before adding more to the meter) # return ################################################################### ## Draw a shadow-circle for the 'pin' of the meter needle. ################################################################### ## INPUTS: ## - the 4 corner coordinates of the oval/circle box (in pixels) ## - number of segments for the arc (segments of differing color shade) ## - width (in pixels) to draw the arc segments ## - start angle for drawing the (darker) arc segments ## (measured counter-clockwise from the 3 o'clock position) ## (An angle of -45 means the dark side of the 'shadow-circle' ## is on the south-east side of the circle.) ################################################################### set centerXpx [expr {int($curCanvasSizePx/2.0)}] set centerYpx $centerXpx set pinOuterRadiusPx 14 set x1 [expr {$centerXpx - $pinOuterRadiusPx}] set y1 [expr {$centerYpx - $pinOuterRadiusPx}] set x2 [expr {$centerXpx + $pinOuterRadiusPx}] set y2 [expr {$centerYpx + $pinOuterRadiusPx}] draw_circle_shadow $canvas $x1 $y1 $x2 $y2 40 6 -45.0 ## FOR TESTING: (exit this proc before adding more to the meter) # return ## FOR TESTING: # puts "proc 'make_one_tachometer'." # puts "curCanvasSizePx: $curCanvasSizePx centerYpx: $centerYpx" ############################################################ ## Draw a red-filled circle on the 'pin' of the meter needle. ############################################################ set pinRadiusPx 12 set x1 [expr {$centerXpx - $pinRadiusPx}] set y1 [expr {$centerYpx - $pinRadiusPx}] set x2 [expr {$centerXpx + $pinRadiusPx}] set y2 [expr {$centerYpx + $pinRadiusPx}] $canvas create oval \ $x1 $y1 $x2 $y2 -fill red -outline {} # -width 1 -outline lightgray ## FOR TESTING: (exit this proc before adding more to the meter) # return ########################################### ## Draw arc-line on which to put tic marks. ################################################# ## 320 degrees counter-clockwise from -70 degrees ## (based at 3 oclock) is 70 degrees beyond 180. ## I.e. -70 + 320 = 250 = 180 + 70 ################################################# set arcLineIndentPx 10 set x1 [expr {$topleftXpx + $arcLineIndentPx}] set y1 [expr {$topleftYpx + $arcLineIndentPx}] set x2 [expr {$botrightXpx - $arcLineIndentPx}] set y2 [expr {$botrightYpx - $arcLineIndentPx}] $canvas create arc $x1 $y1 $x2 $y2 \ -start -70 -extent 320 -style arc \ -outline black -width 2 ## FOR TESTING: (exit this proc before adding more to the meter) # return ################################################## ## Draw tic-marks and labels around the meter. ################################################## set DEGperTIC [expr {320.0/$Nsegs}] set half $centerXpx ## outer location (radius) of tic marks set l1 [expr {$half - ($arcLineIndentPx + $marginPx)}] ## inner location (radius) of tic marks set l2 [expr {$l1 - $arcLineIndentPx}] ## inner location of tic labels set l3 [expr {$l2 - $arcLineIndentPx}] set angle0 250.0 for {set i 0} {$i <= $Nsegs} {incr i} { set rads [expr {($angle0 - ($DEGperTIC * $i)) * $radsPERdeg}] set x1 [expr {$half + $l1 * cos($rads)}] set y1 [expr {$half - $l1 * sin($rads)}] set x2 [expr {$half + $l2 * cos($rads)}] set y2 [expr {$half - $l2 * sin($rads)}] $canvas create line \ $x1 $y1 $x2 $y2 \ -fill black -width 2 set x1 [expr {$half + $l3 * cos($rads)}] set y1 [expr {$half - $l3 * sin($rads)}] set label [lindex $ticLabels $i] if { [string length $label] } { $canvas create text \ $x1 $y1 \ -anchor center -justify center -fill black \ -text $label -font { Helvetica 10 } } ## END OF labels loop. } ## END OF i-loop for tic-marks ## FOR TESTING: (exit this proc before adding more to the meter) # return ####################################################### ## Draw red-line arc-segment (danger zone) of the meter. ####################################################### set redLineIndentPx 15 set x1 [expr {$topleftXpx + $redLineIndentPx}] set y1 [expr {$topleftYpx + $redLineIndentPx}] set x2 [expr {$botrightXpx - $redLineIndentPx}] set y2 [expr {$botrightYpx - $redLineIndentPx}] set extentDegrees $DEGperTIC # set extentDegrees [expr {1.5 * $DEGperTIC}] $canvas create arc $x1 $y1 $x2 $y2 \ -start -70 -extent $extentDegrees -style arc \ -outline red -fill red -width 8 ## FOR TESTING: (exit this proc before adding more to the meter) # return ################################## ## Draw 4 rivets around the meter. ################################## set RIVETindentPx 10 set RIVEToutdentPx [expr {$curCanvasSizePx - $RIVETindentPx}] ## upper-left rivet draw_rivet $canvas $RIVETindentPx $RIVETindentPx ## upper-right rivet draw_rivet $canvas $RIVEToutdentPx $RIVETindentPx ## lower-left rivet draw_rivet $canvas $RIVETindentPx $RIVEToutdentPx ## lower-right rivet draw_rivet $canvas $RIVEToutdentPx $RIVEToutdentPx #################################################################### ## Draw a text-label near the bottom of the meter. #################################################################### $canvas create text \ $centerXpx [expr {0.7 * $centerXpx}] \ -anchor center -justify center -fill black \ -text "%used" -font { Helvetica 10 normal roman} } ## END OF proc 'make_one_tachometer' ##+######################################################################## ## PROC 'draw_rivet' ##+######################################################################## ## PURPOSE: Put a rivet at a specified center point. ## The center point is specified in pixels, as a location on ## the canvas of the GUI, relative to the upper left corner. ## ## (We pass the radius of the rivets in a global variable.) ## ## CALLED BY: the 'make_tachometer' proc ##+######################################################################## set rivetRadiusPx 4 proc draw_rivet { canvas centerXpx centerYpx } { global rivetRadiusPx ## FOR TESTING: # return ######################################################## ## Draw a color shaded arc using ## - 5 arc segments around each half of the circle/oval ## - 3 pixels for width of the arc segments ## - -45 degrees for the start angle (darkest shade) ######################################################## draw_circle_shadow $canvas \ [expr {$centerXpx - $rivetRadiusPx}] \ [expr {$centerYpx - $rivetRadiusPx}] \ [expr {$centerXpx + $rivetRadiusPx}] \ [expr {$centerYpx + $rivetRadiusPx}] \ 5 3 -45.0 } ## END OF proc 'draw_rivet' ##+######################################################################## ## PROC 'draw_circle_shadow' ##+######################################################################## ## PURPOSE: Puts a shadowed edge around an oval/circle in a specified 'box'. ## ## INPUTS: - the corner coordinates of the oval/circle box (in pixels) ## - number of segments for the arc (segments of differing color shade) ## - width (in pixels) to draw the arc segments ## - start angle for drawing the arc segments ## ## CALLED BY: the 'make_tachometers' and 'draw_rivets' procs ##+######################################################################## proc draw_circle_shadow {canvas x1 y1 x2 y2 Nsegs ARCwidthPx startDEGREES } { ## FOR TESTING: (dummy out this proc) # return set DEGperSHADE [expr {180.0/$Nsegs}] for {set i 0} {$i <= $Nsegs} {incr i} { set a [expr {($startDEGREES + $i * $DEGperSHADE)}] set b [expr {($startDEGREES - $i * $DEGperSHADE)}] ## Make darker grays for greater angles. set color255 [expr {40 + $i*(200/$Nsegs)}] set hexcolor [format "#%x%x%x" $color255 $color255 $color255] $canvas create arc \ $x1 $y1 $x2 $y2 \ -start $a -extent $DEGperSHADE \ -style arc -outline $hexcolor -width $ARCwidthPx $canvas create arc \ $x1 $y1 $x2 $y2 \ -start $b -extent $DEGperSHADE \ -style arc -outline $hexcolor -width $ARCwidthPx ## FOR TESTING: (show each pair of segments before ## drawing the next pair) # update } ## END OF loop over the arc segments } ## END OF proc 'draw_circle_shadow' ##+######################################################################## ## PROC 'update_needles' ##+######################################################################## ## PURPOSE: Updates needle(s) on square canvas(es) --- using the ## Linux '/proc/stat' file to get 'activity' data ## for the current user-selected CPU. ## ## This proc uses the 'update_one_needle' proc to update ## the needle for each of the Nmeters canvases. ## ## CALLED BY: the 'Additional GUI Initialization' section at the ## bottom of this script, and within this proc itself. ##+######################################################################## proc update_needles {} { global argv0 DIRscripts WAITseconds VARsampcnt CPUname \ PREVuser PREVnice PREVsystem PREVidle # global env ########################################################## ## Get user-nice-system-idle-etc data --- for$CPUname. ########################################################## foreach {CPUcheck CURuser CURnice CURsystem CURidle} \ [exec $DIRscripts/get_cpu_usage_info.sh $CPUname] {break} set PREVtot [expr {$PREVuser + $PREVnice + $PREVsystem + $PREVidle}] set CURtot [expr {$CURuser + $CURnice + $CURsystem + $CURidle}] incr VARsampcnt ## FOR TESTING: if {0} { puts "proc update_needles:" puts "CPUname: $CPUname" puts "PREVuser: $PREVuser CURuser: $CURuser" puts "PREVnice: $PREVnice CURnice: $CURnice" puts "PREVsystem: $PREVsystem CURsystem: $CURsystem " puts "PREVidle: $PREVidle CURidle: $CURidle" } ############################################################## ## Update the needle for $CPUname with %-user+nice+system data. ############################################################## set denom [expr {$CURtot - $PREVtot}] if {$denom <= 0} { set PERCENTtot 0.0 set PERCENTuser 0.0 set PERCENTnice 0.0 set PERCENTsystem 0.0 set PERCENTidle 0.0 } else { set numer8or [expr {($CURuser + $CURnice + $CURsystem) - \ ($PREVuser + $PREVnice + $PREVsystem)}] set PERCENTtot [expr {100.0 * $numer8or / $denom}] set PERCENTuser [expr {100.0 * ($CURuser - $PREVuser) / $denom}] set PERCENTnice [expr {100.0 * ($CURnice - $PREVnice) / $denom}] set PERCENTsystem [expr {100.0 * ($CURsystem - $PREVsystem) / $denom}] set PERCENTidle [expr {100.0 * ($CURidle - $PREVidle) / $denom}] } update_one_needle .fRmeters.fRmeter1 $CPUname $PERCENTtot \ $PERCENTuser $PERCENTnice $PERCENTsystem $PERCENTidle ############################################################ ## Force the needles to show up on the GUI. (needed???) ############################################################ # update ################################################################ ## 'Pseudo-Recursively' 'fork off' another (delayed) instance of the ## 'update_needles' here to support the wait-seconds scale widget ## --- using the 'after ms cmd arg arg ...' form of the 'after' ## command. ################################################################ set WAITmillisecs [expr {int($WAITseconds * 1000)}] after $WAITmillisecs update_needles ################################################################ ## Reset the PREV vars. ################################################################ set PREVuser $CURuser set PREVnice $CURnice set PREVsystem $CURsystem set PREVidle $CURidle } ## END OF proc 'update_needles' ##+######################################################################## ## PROC 'update_one_needle' ##+######################################################################## ## PURPOSE: Updates a needle on a square canvas --- using the ## canvas ID and the %-activity data passed as arguments. ## ## Input is the canvas ID. This proc queries the canvas to ## get its center and to determine an appropriate length for ## the needle as a proportion of the (square) canvas size. ## ## CALLED BY: the 'update_needles' proc ##+######################################################################## proc update_one_needle \ {frame CPUNAME PERCENT PERCENTuser PERCENTnice PERCENTsystem PERCENTidle} { global pi radsPERdeg Nsegs MAXrate ## FOR TESTING: (dummy out this routine) # return ################################################# ## Put data in 2 label lines above the meter. ################################################# $frame.labelINFO1 configure -text \ "%user = [format "%6.2f" $PERCENTuser] %nice = [format "%6.2f" $PERCENTnice]" $frame.labelINFO2 configure -text \ "%system = [format "%6.2f" $PERCENTsystem] %idle = [format "%6.2f" $PERCENTidle]" ############################################################ ## Prepare to draw the needle on the meter. ## Set the angle for the zero-point on the arc-of-tic-marks. ############################################################ set angle0 250.0 ## Convert RATE/MAXrate to an angle in radians on the arc. if {$PERCENT > 100} {set PERCENT 100} set degs [expr {$angle0 - (320.0 * $PERCENT / 100.0)}] set rads [expr {$degs * $radsPERdeg}] ## FOR TESTING: # puts "proc 'update_one_needle' :" # puts "PERCENT: $PERCENT degs: $degs rads: $rads" ## Get the coord(s) of the center of the (square) canvas ## and calculate a length of the needle. # set width [$frame.can cget -width] set widthPx [winfo width $frame.can] set halfPx [expr {int($widthPx / 2.0)}] set lengthPx [expr {int($halfPx * 0.5)}] #################################################################### ## Before drawing the needle, ## draw a text-label, CPUNAME, near the bottom of the meter. #################################################################### catch {$frame.can delete -tags TAG_cputext} $frame.can create text \ $halfPx [expr {1.3 * $halfPx}] \ -anchor center -justify center -fill black \ -text "CPU: $CPUNAME" -font { Helvetica 10 normal roman} \ -tag TAG_cputext ################################################################ ## Calculate the coordinates for the tip and base of the needle. ################################################################ set xtip [expr {$halfPx + $lengthPx*cos($rads)}] set ytip [expr {$halfPx - $lengthPx*sin($rads)}] # set xbase [expr {$halfPx + 0.2*$lengthPx*cos($rads)}] # set ybase [expr {$halfPx - 0.2*$lengthPx*sin($rads)}] set xbase $halfPx set ybase $halfPx ## Remove a previous needle, if any. catch {$frame.can delete -tags TAGneedle} #################################################################### ## Draw a red-line needle and a reddish-white line on either side ## --- for an (attempted) anti-aliasing effect. ## ## NOTE: This attempt at anti-aliasing did not work out well. ## This code needs improvement --- or simply one 'create line'. #################################################################### $frame.can create line \ $xbase $ybase $xtip $ytip \ -fill #ff0000 -width 4 -tag TAGneedle $frame.can create line \ [expr {$xbase + 1}] [expr {$ybase + 1}] \ [expr {$xtip + 1}] [expr {$ytip + 1}] \ -fill #ff8888 -width 2 -tag TAGneedle $frame.can create line \ [expr {$xbase - 1}] [expr {$ybase - 1}] \ [expr {$xtip - 1}] [expr {$ytip - 1}] \ -fill #ff8888 -width 2 -tag TAGneedle } ## END OF proc 'update_one_needle' ##+############################################################# ## proc Refresh ## ## PURPOSE: 'Refresh' the meter(s) and their needles --- ## for when the user wants a new 'reading' for ## the user-specified CPU ## and/or after the user resizes the window. ## ## CALLED BY: 'Refresh' button ##+############################################################# proc Refresh {} { global DIRscripts CPUname PREVuser PREVnice PREVsystem PREVidle ## Cancel pending needle update(s), before redrawing ## the meters and restarting the update-needles cycle. set LISTids [after info] foreach ID $LISTids { after cancel $ID } foreach {CPUcheck PREVuser PREVnice PREVsystem PREVidle} \ [exec $DIRscripts/get_cpu_usage_info.sh $CPUname] {break} ## FOR TESTING: if {0} { puts "proc Refresh:" puts "CPUname: $CPUname" puts "PREVuser: $PREVuser" puts "PREVnice: $PREVnice" puts "PREVsystem: $PREVsystem" puts "PREVidle: $PREVidle" } ## Wait about 0.5 or 1.0 secs to get the next sample ## to update the needle(s) on the meter(s). after 500 # after 1000 make_tachometers update_needles } ## END OF proc 'Refresh' ##+############################################################# ## proc show_cpus ## ## PURPOSE: Shows available CPU IDs --- in a ## popup message window. ## ## CALLED BY: 'ShowCPUs' button ##+############################################################# proc show_cpus {} { global DIRscripts set CPUSlist [exec $DIRscripts/get_cpu_usage_info.sh cpuslist] popup_msgVarWithScroll .topIFACES $CPUSlist } ## END OF proc 'show_cpus' ##+############################################################# ## proc Report ## ## PURPOSE: Shows a report on CPU data for ALL the CPUs, ## in a popup message window. ## ## CALLED BY: 'Report' button ##+############################################################# proc Report {} { global DIRscripts ############################################################ ## Get the report from an external shell script, into a ## Tcl variable. Then show the report in a popup window. ## ## The shell script gets data for ALL the CPUs, then, ## after about 0.5 or 1 second, it gets the data for ## ALL the CPUs, again. The script combines and sorts ## the two lists of data for the CPU-IDs and feeds the ## sorted list into an 'awk' program to build the report. ########################################################### set REPORTtext [exec $DIRscripts/get_cpu_usage_info.sh all] popup_msgVarWithScroll .topREPORT "$REPORTtext" } ## END OF proc 'Report' ##+############################################################# ## proc ReDraw_if_canvases_resized ## ## PURPOSE: To handle resizing the meter(s) when the window is ## resized --- IF the binding is implemented. ## ## The intent is to avoid too many redraws --- for ## almost every little resize of the window as its ## border(s) are dragged. ## ## CALLED BY: bind .fRmeters.can ## at bottom of this script. ##+############################################################# ## NOT IMPLEMENTED. ## Code is included for possible future development. ##+############################################################# proc ReDraw_if_canvases_resized {} { global PREVcanvasesWidthPx PREVcanvasesHeightPx draw_wait0or1 ## FOR TESTING: (to dummy out this proc) # return if {$draw_wait0or1 == 1} {return} ## Set the wait indicator and delay doing the canvas resize ## check for about 300 milliseconds --- to allow time for the ## user to stop moving the window. After about 300 milliseconds, ## it is unlikely that the window is moving and thus causing ## multiple redraws. set draw_wait0or1 1 after 900 set CURcanvasesWidthPx [winfo width .fRmeters] set CURcanvasesHeightPx [winfo height .fRmeters] if { $CURcanvasesWidthPx != $PREVcanvasesWidthPx || \ $CURcanvasesHeightPx != $PREVcanvasesHeightPx} { ## The 'after' could be used to prevent too many ## redraws (and flickering and unnecessary processing) ## as the window is being moved. # after 200 make_tachometers update_needles set PREVcanvasesWidthPx $CURcanvasesWidthPx set PREVcanvasesHeightPx $CURcanvasesHeightPx set draw_wait0or1 0 } } ## END OF proc 'ReDraw_if_canvases_resized' ##+######################################################################## ## PROC 'popup_msgVarWithScroll' ##+######################################################################## ## PURPOSE: Report help or error conditions to the user. ## ## We do not use focus,grab,tkwait in this proc, ## because we use it to show help when the GUI is idle, ## and we may want the user to be able to keep the Help ## window open while doing some other things with the GUI ## such as putting a filename in the filename entry field ## or clicking on a radiobutton. ## ## For a similar proc with focus-grab-tkwait added, ## see the proc 'popup_msgVarWithScroll_wait' in a ## 3DterrainGeneratorExaminer Tk script. ## ## REFERENCE: page 602 of 'Practical Programming in Tcl and Tk', ## 4th edition, by Welch, Jones, Hobbs. ## ## ARGUMENTS: A toplevel frame name (such as .fRhelp or .fRerrmsg) ## and a variable holding text (many lines, if needed). ## ## CALLED BY: 'help' button ##+######################################################################## ## To have more control over the formatting of the message (esp. ## words per line), we use this 'toplevel-text' method, ## rather than the 'tk_dialog' method -- like on page 574 of the book ## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications ## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor". ##+######################################################################## proc popup_msgVarWithScroll { toplevName VARtext } { ## global fontTEMP_varwidth #; Not needed. 'wish' makes this global. ## global env # bell # bell ################################################# ## Set VARwidth & VARheight from $VARtext. ################################################# ## To get VARheight, ## split at '\n' (newlines) and count 'lines'. ################################################# set VARlist [ split $VARtext "\n" ] ## For testing: # puts "VARlist: $VARlist" set VARheight [ llength $VARlist ] ## For testing: # puts "VARheight: $VARheight" ################################################# ## To get VARwidth, ## loop through the 'lines' getting length ## of each; save max. ################################################# set VARwidth 0 ############################################# ## LOOK AT EACH LINE IN THE LIST. ############################################# foreach line $VARlist { ############################################# ## Get the length of the line. ############################################# set LINEwidth [ string length $line ] if { $LINEwidth > $VARwidth } { set VARwidth $LINEwidth } } ## END OF foreach line $VARlist ## For testing: # puts "VARwidth: $VARwidth" ############################################################### ## NOTE: VARwidth works for a fixed-width font used for the ## text widget ... BUT the programmer may need to be ## careful that the contents of VARtext are all ## countable characters by the 'string length' command. ############################################################### ##################################### ## SETUP 'TOP LEVEL' HELP WINDOW. ##################################### catch {destroy $toplevName} toplevel $toplevName # wm geometry $toplevName 600x400+100+50 wm geometry $toplevName +100+50 wm title $toplevName "Note" # wm title $toplevName "Note to $env(USER)" wm iconname $toplevName "Note" ##################################### ## In the frame '$toplevName' - ## DEFINE THE TEXT WIDGET and ## its two scrollbars --- and ## DEFINE an OK BUTTON widget. ##################################### if {$VARheight > 10} { text $toplevName.text \ -wrap none \ -font fontTEMP_fixedwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 \ -yscrollcommand "$toplevName.scrolly set" \ -xscrollcommand "$toplevName.scrollx set" scrollbar $toplevName.scrolly \ -orient vertical \ -command "$toplevName.text yview" scrollbar $toplevName.scrollx \ -orient horizontal \ -command "$toplevName.text xview" } else { text $toplevName.text \ -wrap none \ -font fontTEMP_fixedwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 } button $toplevName.butt \ -text "OK" \ -font fontTEMP_varwidth \ -command "destroy $toplevName" ############################################### ## PACK *ALL* the widgets in frame '$toplevName'. ############################################### ## Pack the bottom button BEFORE the ## bottom x-scrollbar widget, pack $toplevName.butt \ -side bottom \ -anchor center \ -fill none \ -expand 0 if {$VARheight > 10} { ## Pack the scrollbars BEFORE the text widget, ## so that the text does not monopolize the space. pack $toplevName.scrolly \ -side right \ -anchor center \ -fill y \ -expand 0 ## DO NOT USE '-expand 1' HERE on the Y-scrollbar. ## THAT ALLOWS Y-SCROLLBAR TO EXPAND AND PUTS ## BLANK SPACE BETWEEN Y-SCROLLBAR & THE TEXT AREA. pack $toplevName.scrollx \ -side bottom \ -anchor center \ -fill x \ -expand 0 ## DO NOT USE '-expand 1' HERE on the X-scrollbar. ## THAT KEEPS THE TEXT AREA FROM EXPANDING. pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } else { pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } ##################################### ## LOAD MSG INTO TEXT WIDGET. ##################################### ## $toplevName.text delete 1.0 end $toplevName.text insert end $VARtext $toplevName.text configure -state disabled } ## END OF PROC 'popup_msgVarWithScroll' ##+######################## ## END of PROC definitions. ##+######################## ## Set HELPtext var. ##+######################## set HELPtext "\ \ \ ** HELP for this 'CPU Activity' Monitoring Utility ** This utility is meant to show a GUI that holds a tachometer-style METER, with a range of 0 to 100 percent. The needle on the meter shows the PERCENT of usage of a CPU --- for a user-specifiable CPU name/IDentifier, where the computer running this utility may have multiple CPU's. Initially, the 'CPU name' is set to 'cpu' which represents the total (or average) activity of ALL the CPU's for a computer with multiple CPU's. If the computer has a single CPU, then the CPU name 'cpu' refers to that single CPU. The 'ShowCPUs' button can be used to show all the CPU ID's available on the computer. Example ID's for a 2-CPU computer: 'cpu', 'cpu0', and 'cpu1'. For example, if 'cpu0' is a valid CPU ID, that CPU name can be entered in the 'MonitorCPU' entry field to reset the CPU for which the meter displays the PERCENT-USAGE. The needle on the meter is updated periodically --- according to a 'SampleRate' (seconds) setting on the GUI. The seconds-setting can be reset by a 'scale' widget. Alternatively, instead of depending on auto-updates, the user may choose to click on a 'Refresh' button --- to immediately update the meter with a new PERCENT-USAGE value for the user-selected CPU. Then auto-updates continue according to the current 'SampleRate' setting. Besides PERCENT-USAGE, the actual '%-user' and '%-system' values may be shown for the user-selected CPU. The user can click on a 'Report' button on the GUI to show the activity values, in detail, for ALL the CPU's of the computer. The report is shown in a popup text window. The user can copy-and-paste the text into another window, such as a text editor or word processor window. This Tcl-Tk script was developed on Linux and uses the '/proc/stat' file to get the CPU acitivity data --- for the meter and for the 'Report' option and the 'ShowCPUs' option. *************************************** WINDOW RESIZE (an experimental feature): The user may resize the window rather than using a fixed window (and fixed meter) size. If the user resizes the window, the 'Refresh' button can be used to force the meter to be resized according to the new window size. (The meter may be resized such that it is 'too big' for the new window size. Just pull an edge or corner of the window, to see the entire meter.) ************************************ THE SCRIPT USED to update the meters: A Tcl 'exec' command calls on a separate shell script --- 'get_cpu_usage_info.sh' --- that uses the file '/proc/stat' (or an equivalent file/command) to get CPU usage data --- and extract-and-format the data for return to this Tk script. This shell script is used for 3 functions of this GUI: 1) To show CPU-activity-level via the meter, for a user-specified CPU. 2) To show the CPU ID names known to this computer. 3) To present a CPU activity report for all the CPU's known to this computer. If the '/proc/stat' file is not available on your computer, you may have to edit the shell script to use a different command/technique to get the CPU data. For a computer operating system other than a Linux, Unix, BSD, or Apple-Mac system, you may need to write a C program to get appropriate CPU activity data. *********************** CAPTURING THE GUI IMAGE: A screen/window capture utility (like 'gnome-screenshot' on Linux) can be used to capture the GUI image in a PNG or GIF file, say. If necessary, an image editor (like 'mtpaint' on Linux) can be used to crop the window capture image. The image could also be down-sized --- say to make a smaller image suitable for use in a web page or an email. " ##+################################################################ ##+################################################################ ## Additional GUI INITIALIZATION: Mainly to ## - Put the meter(s) on their canvas(es), with 'make_tachometers'. ## - Start an execution loop for the 'update_needles' proc. ##+################################################################ ##+######################################################## ## Set a default CPU-ID for the entry widget. ##+######################################################## set CPUname "cpu" ##+################################################### ## Set the scale widget var for initial 'refresh rate' ## (actually wait-time = 'wave-length', not 'frequency') ## --- in seconds. ## ## We set this before the following 'update' command, ## so that the slider-button on the scale widget is ## positioned accordingly when the GUI first appears. ##+################################################### # set WAITseconds 10.0 # set WAITseconds 5.0 set WAITseconds 2.0 # set WAITseconds 1.0 ##+##################################################### ## Set a max-value to use for the limit on the meter. ##+##################################################### set MAXvalue 100 ##+#################################################### ## Set a number of segments and labels for the meter, ## according to the MAXvalue setting. ##+#################################################### set Nsegs 10 set ticLabels "0 10 20 30 40 50 60 70 80 90 100" ##+################################################# ## Draw the tachometer(s) (without needle). ##+################################################# ## Need 'update' here to set the size of the canvases, ## because 'make_tachometers' uses 'winfo' to get ## the width and height of some frames and canvases. ##+################################################# update make_tachometers ##+############################################################# ## Get the directory that this Tk script is in. That will be the ## directory that the 'external' utility shell script should be ## in. This directory is used to call the shell script that ## is used in the 'update_needles', 'show_cpus', and 'Report' ## procs. ##+############################################################# ## FOR TESTING: # puts "argv0: $argv0" # set DIRscripts "." # set DIRscripts "[pwd]" # set DIRscripts "$env(HOME)/apps/tkUtils" set DIRscripts "[file dirname $argv0]" ##+######################################################### ## Get an initial sample of the 'jiffies' data for $CPUname. ##+######################################################### foreach {CPUcheck PREVuser PREVnice PREVsystem PREVidle} \ [exec $DIRscripts/get_cpu_usage_info.sh $CPUname] {break} ## FOR TESTING: # puts "PREVuser: $PREVuser PREVnice: $PREVnice" # puts "PREVsystem: $PREVsystem PREVidle: $PREVidle" ##+#################################################### ## Initialize the variable we use to keep track of ## the sample count. ##+#################################################### set VARsampcnt 0 ##+###################################################### ## Do an initial draw of the needles, after WAITseconds. ## ## NOTE: ## 'update_needles' starts a loop to keep updating the needles. ## ## The proc 'update_needles' calls itself --- ## with 'after '. ## ## NOTE1: ## The 'after' call is a ## 'recursive-like-call-with-FORKED-AND-DELAYED-execution'. ## The proc immediately returns to processing after doing the ## 'queue-command-with-delay'. ## ## NOTE2: ## This GUI provides the user with the 'Refresh' ## button to force a needle update --- for example, ## if the user resizes the window and wants to force ## a refresh (which runs 'make_tachometers') to resize ## the meters and get the data that the 'update_needles' ## proc uses to draw the needles on the meters. ##+###################################################### set WAITmillisecs [expr {int(1000*$WAITseconds)}] after $WAITmillisecs update_needles ##+################################################# ## Set a resize binding on the canvas --- ## to redraw the tachometers and needles ## if the window is resized. ## ## DE-ACTIVATED, for now. ## (Code is here for future experimentation. ## It is not easy to avoid extraneous redraws of ## the GUI as the window is being dragged/resized.) ##+################################################# if {0} { set draw_wait0or1 0 set PREVcanvasesWidthPx [winfo width .fRmeters] set PREVcanvasesHeightPx [winfo height .fRmeters] bind .fRmeters "ReDraw_if_canvases_resized" }