[uniquename] - 2013dec21 On 2013dec05, I posted code at [A Tachometer-style Meter --- for CPU Usage] to make and use a 'tachometer style' meter based on a meter style for which Marco Maggi provided a demo script [http://wiki.tcl.tk/9107] back in 2003. That meter was built using 'create arc', 'create line', and 'create text' commands on a Tk canvas. At the bottom of that page, I mentioned that I may try using a 'create image' technique to make HD (high-definition) meters. That is the subject of this page (code below). Like the CPU-meter implementation posted on the wiki page [A Tachometer-style Meter --- for CPU Usage], this implementation provides a Tk script that is a 'wrapper' for a shell script that provides 3 different kinds of output from the 'cat /proc/stat' command. See that wiki page for an example and discussion of the output from 'cat /proc/stat'. As was noted on that wiki page, this utility is probably usable on BSD and Mac (and Unix) systems with some changes to the shell script. And for almost any Linux system with an 8.x Tcl-Tk 'wish' interpreter installed, this utility should be usable with essentially no changes. ------ '''Designing the Tk GUI''' I explained on the wiki page [A Tachometer-style Meter --- for CPU Usage] why I decided to go with a single meter on the GUI --- rather than 2 meters, like in the 'memory-and-swap' and the 'network-activity' Tk GUI's that I had devised before --- as seen on the wiki pages [A Pair of Tachometer-style Meters --- for Memory and Swap] and [A Pair of Tachometer-style Meters --- for Network Activity]. I patterned the CPU usage GUI on the single-meter GUI that I used for the 'file-system-usage' Tk GUI that I devised before --- as seen on the wiki page [A Tachometer-style Meter --- for File System Usage]. Besides showing a 'percent-usage' meter (on a Tk canvas on the GUI), I wanted to provide a means for the user to query the CPU ID's available on the computer --- and a means for the user to choose one of the CPU-ID's to monitor. I implemented a periodic-sampling ability by use of the Tcl 'after' command, in the form after $WAITmillisecs update_needles as explained on the [A Tachometer-style Meter --- for CPU Usage] page. I also wanted to supply a 'Refresh' button on the GUI, so that the user can request at ANY TIME, a new query of the usage data for the CPU-ID being monitored. This is especially helpful if you have the sampling rate set at 10 seconds or more, and you want to intervene immediately to change the sampling rate. And, in case there is more than one CPU that is quite active, I wanted to supply a 'Report' button on the GUI, so that the user can see, at any time, the usage of ALL the CPU's on the computer --- even a monster computer with 24 or more CPU's. In putting together the code for '''this''' GUI, instead of using the 'shadow-circle' technique of Marco Maggi to make nice looking meters, I collected some nice background images and used 'create image' to make a background for the meters. I have still used 'create line' to make optional tic-marks (if tic marks are not included on the background image). And I have used 'create arc', to (optionally) provide a drawing of the arc along which the tic marks are located. Furthermore, I have used 'create text' to (optionally) provide numeric labels for the tic marks --- and to place a couple of text strings on the face of the meter, above and below the center point of the arc. --- '''Themes''' Since I am using 'create image' to create the meter backgrounds, that opens up the possibility of giving the user a choice of background images --- and 'pointer' images (corresponding to the 'needle' on the Maggi-style meter) --- and 'corner-feature' images (corresponding to the 'rivets' on the Maggi-style meter). In fact, I decided to allow an unlimited set of themes --- by using Tcl arrays to hold a set of parameters for each theme. Here is an example of the 'set' statements and array names that are used to define a theme indexed by the integer 7: set aRimgBkgd(7) "meter_background_blackSolidDisk_grayTOgreenBkgd_BULLEThole" set aRticsYorN(7) "N" set aRcenterX(7) 0.5 set aRcenterY(7) 0.5 set aRradiusArc(7) 0.19 set aRinORout(7) "in" set aRhexcolorText(7) "#ffffff" set aRhexcolorTics(7) "#ffffff" set aRimgPointer(7) "bullet_green_32x32_transp" set aRimgCornerFeature(7) "rivet_1_greenish_35x34_transp" The array 'aRimgBkgd' holds the 'prefix name' of the background image file for the theme. According to the setting of the S-M-L radiobuttons on the GUI, the suffix '_SMALL' or '_MEDIUM' or '_LARGE' is added to this name. And a file suffix of '.gif' or '.png' is added to get the complete name of the file to be used for the meter background. The use of '.gif' or '.png' is determined by an 'imgSUFFIX' variable, described further below, when describing the naming of the 'pointer' and 'corner feature' image files. The array 'aRticsYorN' is used to indicate whether the background image already has tic marks on it. If it is set to 'Y', then no tick marks (nor circular arc) need to be drawn by the 'make_one_meter' proc. The factors 'aRcenterX' and 'aRcenterY' (between 0 and 1) are used to help locate the center of the arc-circle, especially in those cases when the background image is not symmetric, in other words, off-center. The array 'aRradiusArc' is used to adjust the radius at which the tick marks and tick labels are drawn. The array 'aRinORout' is used to determine whether this theme should draw the tick marks and tick labels to the outside of the arc-circle or the inside of the arc-circle. The array 'aRhexcolorText' is used to set the color of the tick mark labels and other text labels on the meter. The array 'aRhexcolorTics' is used to set the color of the tick marks. The array 'aRimgPointer' holds the name of the 'pointer' image file, minus its suffix --- '.gif' or '.png'. The array 'aRimgCornerFeature' holds the name of the 'corner feature' image file, minus its suffix. Generally, the person setting up theme files will want to use transparent GIF's or transparent PNG's for these image files. There is an 'imgSUFFIX' variable that can be set to 'gif' or 'png', to determine which type of image file the user is using for the 'aRimgBkgd' and 'aRimgPointer' and 'aRimgCornerFeature' files. --- '''Resizing the meter''' In my implementation of the Marco-Maggi-like meter, I spent quite a bit of effort in converting Maggi's procs * FROM using hard-coded numbers for making the meters and their needles * TO using variables that are set based on a query on the current size of a frame or canvas widget. So the user is able to resize the window and click on the 'Refresh' button to get a bigger version of the meter and the needle position. One advantage of building the entire meter with 'create line,arc,text' is that the meter can be resized to ANY size using some simple arithmetic. For reasons outlined at the bottom of the [A Tachometer-style Meter --- for File System Usage] page, I provide small/medium/large radiobuttons on the GUI --- by which the user can choose 3 different image sizes, from among the images for a user-selected theme. ------ '''SOME GUI IMAGES''' On the basis of these goals, I ended up with a GUI with Tk 'label', 'button', 'entry', 'radiobutton', 'checkbutton', and 'scale' widgets --- and a multitude of theme possibilities --- as seen in the following images. [cpu_meter_redShinyBall_img_screenshot_385x589.jpg] Not only can one use high-quality meter backgrounds like the one above, but one can choose from abstract images like the one below. [cpu_meter_abstractAlienLike_img_screenshot_431x590.jpg] And one can take a more organic approach, like in the following image. [cpu_meter_greenLeaf_img_screenshot_415x587.jpg] Almost any image with a circular solid colored background in the middle is a good candidate for the meter background in this utility --- as the following two images demonstrate. [cpu_meter_bulletHole-GreenBkgd_img_screenshot_385x588.jpg] [cpu_meter_wristWatch_img_screenshot_386x589.jpg] --- '''user and system percents''' As I mentioned on the 'tachometer-style-meter' page, typically, there are not many 'ticks' accumulating in 'nice mode'. So most of the 'activity' of a CPU is accumlating in the 'user' and 'system' modes. I show the '%user' and '%system' numbers stacked above each other in a couple of 'label' widget lines on the GUI. Their sum will typically be the same as the %CPU value pointed to by the needle. --- '''Sample count''' Also as mentioned on the 'tachometer-style-meter' page, the integer shown just to the right of the 'scale' widget is a sample-count. I found that it is helpful to have this count displayed in cases when the needle and numbers on the GUI are not changing perceptibly --- so that the user is assured that the sampling is taking place, and at the rate that is currently requested. --- '''CPU ID's''' The 'ShowCPUs' button on the GUI can be used to show the available CPU-ID's. A small window pops up showing a list of CPU-ID's. An image on the 'tachometer-style-meter' page demonstrates that. Also, there is an image on the 'tachometer-style-meter' page that shows that one can change the 'MonitorCPU' entry field so that a different CPU-ID is monitored. In that image the CPU-ID 'cpu1' was entered --- the second of four CPU's on my desktop computer. --- '''The initial display''' In making the Small, Medium, and Large images for each 'theme', I made the 'Small' image about 200x200 pixels, the 'Medium' about 350x350, and the 'Large' about 500x500 pixels. When the GUI first comes up, the meter is defaulted to showing theme number 1, the 'Small' sized image. The CPU data shown is based on an initial couple of executions of the 'cat /proc/stat' command --- from which data for the 'cpu' CPU-ID is extracted. --- '''The "Pointer"''' In the Maggi-style meter, a 'needle' was drawn using 'create line'. I had hoped to come up with a technique to avoid the 'aliasing' effect on the needle --- but I did not devise a successful technique for any of the 4 Maggi-tachometer-style-meter scripts that I have done so far. I considered trying to collect or create high-definition needle images and try to place them on the canvas by rotating the image at suitable angles. However, I decided to avoid the processing in doing that. I could try building on the rotate-image code that I used in [Rotate TkPhoto Image - via GUI with File and Bkgd-Color Select - 2 scrollable canvases] --- and add the code necessary to deal with transparency data (which Tk supports) in a GIF (or PNG) image. But I have some doubts about how successful I may be in doing that. Besides that will take a lot more development time. I also decided to avoid the alternative of making about 320 needles, at various angles --- for each style of needle image for the themes. --- Another approach to making 'pointers': In searching for ideas for meter background images, I stumbled upon some images of 'knobs' where the position of the rotary knob was indicated by lighted 'spots' around the knob. [meter_IDEA_coloredARCsegsFORneedle_242x250.jpg] [meter_IDEA_dotsForNeedle_370x255.jpg] I decided that I could use images like 'bullet' icons to indicate a position around the arc of the meter. The Tk canvas supports transparency, so using small transparent GIF's (or PNG's) is feasible. So besides a background image for the meter, I have allowed for a 'pointer' image and a 'corner feature' image (like a rivet or screw head) to be supplied in each 'theme'. And if a 'pointer' image is not supplied for a theme, I fall back to using code like that used in the 'tachometer-style-meter' Tk script --- to draw a red needle with 'create line'. ------ CAPTURING THE GENERATED IMAGE: When you get an image that you want to save, 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 with the editor --- say, to make a smaller image suitable for presentation in an email or on a web page.) This is the technique that I used to prepare the images that I posted above, to show some GUI features of this utility. However, I replaced the relatively large PNG files that came from the 'mtpaint' editing step with smaller JPEG files that were created by the ImageMagick 'convert' command. ------ '''The code structure''' Below, I provide the Tk script code for this 'high-definition CPU-usage' display utility. I follow my usual 'canonical' structure for Tk code for this Tk script: 0) Set general window & widget parms (win-name, win-position, win-color-scheme, fonts, widget-geometry-parms, win-size-control, text-array-for-labels-etc). 1a) Define ALL frames (and sub-frames, if any). 1b) Pack ALL frames and sub-frames. 2) Define & pack all widgets in the frames, frame by frame. Within each frame, define ALL the widgets. Then pack the widgets. 3) Define keyboard and mouse/touchpad/touch-sensitive-screen action BINDINGS, if needed. 4) Define PROCS, if needed. 5) Additional GUI initialization (typically with one or more of the procs), if needed. This Tk coding structure is discussed in more detail on the page [A Canonical Structure for Tk Code --- and variations]. This structure makes it easy for me to find code sections --- while generating and testing a Tk script, and when looking for code snippets to include in other scripts (code re-use). I call your attention to step-zero. One thing that I have started doing in 2013 is using a text-array for text in labels, buttons, and other widgets in the GUI. This can make it easier for people to internationalize my scripts. I will be using a text-array like this in most of my scripts in the future. --- I put the arrays that define the themes just under the code that sets the widget-text strings. So the themes can be changed or augmented by going to the same section of the code where one can go to alter most of the text strings appearing on the GUI. ------ '''Experimenting with the GUI''' As in all my scripts that use the 'pack' geometry manager (which is all of my 100-plus scripts, so far), I provide the four main pack parameters --- '-side', '-anchor', '-fill', '-expand' --- on all of the 'pack' commands for the frames and widgets. That helps me when I am initially testing the behavior of a GUI (the various widgets within it) as I resize the main window. I think that I have used a pretty nice choice of the 'pack' parameters. The label and button widgets stay fixed in size and relative-location if the window is re-sized --- while the canvas area (without scroll bars) expands/contracts whenever a Small, Medium, or Large image is chosen via the radiobuttons. You can experiment with the '-side', '-anchor', '-fill', and '-expand' parameters on the 'pack' commands for the various frames and widgets --- to get the widget behavior that you want. ___ Additional experimentation: You might want to change the fonts used for the various GUI widgets. For example, you could change '-weight' from 'bold' to 'normal' --- or '-slant' from 'roman' to 'italic'. Or change font families. In fact, you may NEED to change the font families, because the families I used may not be available on your computer --- and the default font that the 'wish' interpreter chooses may not be very pleasing. I use variables to set geometry parameters of widgets --- parameters such as border-widths and padding. And I have included the '-relief' parameter on the definitions of frames and widgets. Feel free to experiment with those 'appearance' parameters as well. If you find the gray 'palette' of the GUI is not to your liking, you can change the value of the RGB parameter supplied to the 'tk_setPalette' command near the top of the code. ------ '''Some features in the code''' That said, here's the code --- with plenty of comments to describe what most of the code-sections are doing. You can look at the top of the PROCS section of the code to see a list of the procs used in this script, along with brief descriptions of how they are called and what they do. The procs are structured to accomodate putting more than one meter on the GUI --- in case someone wants to try that in the future. The main procs are 'make_meters' - to draw meter(s) within Tk canvas(es). The size of the canvas will be determined by the size of the background image, which in turn is determined by the theme-index and the setting of the Small-Medium-Large radiobuttons. 'make_one_meter' - to draw one meter. Called by 'make_meters' to make the meter(s). 'update_pointers' - to update the 'pointer(s)' on the meter(s). Called initially at the bottom of this script. And called in the 'Refresh' proc. Calls on the 'get_cpu_usage_info.sh' script with the CPU-ID for its single argument. 'update_one_pointer' - to place one 'pointer' on a specified meter/canvas. Called by 'update_pointers' to update each of the pointers on the one or more meters. 'Refresh' - called by the 'Refresh' button. Runs the procs 'make_meters' and 'update_pointers'. In particular, this proc can be used to force the meters to be resized if the user click-releases on one of the S-M-L radiobuttons. And this proc is used whenever the user wants a new start of the sampling, typically on changing the sample rate. '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. Thanks to Marco Maggi whose tachometer-style-meter influenced the making of this meter utility --- features such as using an arc of tic-marks, and allowing for corner-decorations (rivets in the case of the original Maggi demo code). ----- It is my hope that the copious comments in the code will help Tcl-Tk coding 'newbies' get started in making GUI's like this. Without the comments, potential young Tcler's might be tempted to return to their iPhones and iPads and iPods --- to watch videos of zombies and vampires and Sasquatch-hunts and ghost-hunts --- and other such brain-eating, life-sapping fare. ------ <>Code for Tk script 'meters_cpu_usage_hidef-image.tk' : (the 'wrapper') ====== #!/usr/bin/wish -f ##+######################################################################## ## ## SCRIPT: meters_cpus_usage_hidef-image.tk ## ## PURPOSE: This script is meant to show a GUI with a nice-looking, ## high-definition-image METER which shows the PERCENT-ACTIVITY ## of a CPU. ## ## The CPU could be one of several on a computer, or it could ## be the 'total' CPU (which could also be considered to be 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. a total or ## average for all the CPU's). ## ## Showing a meter simultaneously for each CPU was considered, ## BUT ... ## On systems with multiple CPU's, %-activity meters could be ## shown for all the CPU's, including the total/average CPU --- ## UP TO A LIMIT. The display would require quite a bit of ## screen area for a computer with 8 or more CPU's. Some ## consideration was given to showing a limited subset of ## the CPU's in the case of many CPU's --- but, in the end, ## it seemed better to display info for one CPU at a time --- ## and offer a 'Report' option to show a text-report on ALL ## the CPU's. ## ## We allow the user to specify a particular CPU whose 'activity' ## is to be shown on the meter. ## ## We could have allowed MULTIPLE METERS to be shown --- say 4 --- ## showing %-user, %-nice, %-system, and %-idle --- for ## a single user-specified CPU. But ... ## ## To simplify, we show just ONE METER (for a user-specified ## CPU) --- with the %-user+nice+system (the 'total activity') ## being shown. ## ## When the GUI first appears, the meter will show the %-activity ## for the 'total 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 ## to show individual user,nice,system,idle percents for the ## user-selectable CPU. Then, 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 user-selectable ## set of CPU's. ## ################################# ## METHOD OF DRAWING THE METER(S): ## ## A previously published version of this utility, by this author, ## used Tk canvas 'create arc' and 'create line' commands to draw a ## tachometer-style meter --- using a 'shadow-circle' technique of ## Marco Maggi to make nice-looking meter components. ## ## 'create line' was used to draw tic marks around the meter arc. ## Furthermore, 'create line' was used to draw a needle on the meter, ## at various angles. ## ## THIS script uses the Tk canvas 'create image' command to put a ## nice-looking, HD (high-definition) meter background on the canvas. ## Then 'create line' may (optionally) be used to put tic marks, ## in an arc, on the meter background --- if there are not tic marks ## on the background image already. ## ## To avoid problems that may come in trying to draw an HD (hi-def) needle ## at various angles on the meter, we use a 'bullet' image (a small 'pointer' ## image), placed along the tic-marks arc, to indicate the current %-activity ## for the user-selected CPU. We generally use a color for this 'bullet' ## which contrasts well with the background image for the meter. ## ## By means of Tcl arrays, we allow the user to choose from multiple ## 'themes' for the meter. (The user can choose a theme via a ## 'scale' widget rather than 'radiobuttons' --- to allow for ## supporting an almost unlimited number of themes, rather than being ## limited by the amount of space that radiobuttons consume on the GUI.) ## ## Each theme (indexed via an integer) includes the following items, ## stored in Tcl arrays: ## - a BACKGROUND IMAGE (a GIF or PNG file) - and we allow for a ## small, medium, and large background images (about 200, 350, ## and 500 pixels high) ## - a BULLET/POINTER IMAGE (a small transparent GIF or PNG file) ## - a RIVET IMAGE (a small transparent GIF or PNG file), which ## is shown optionally, according to a checkbutton on the GUI ## - some parameters, such as ## - the CENTER LOCATION on the background image (in case it is ## not symmetric) --- typically about 0.5,0.5 ## - a RADIUS for an arc, for positioning tic marks and tic mark ## labels --- as well as for positioning the 'bullet' image. ## (Typically this radius value will be about 0.25.) ## - an indicator of whether the tic marks & labels should be ## PLACED ON THE INSIDE OR THE OUTSIDE OF THE ARC. (This ## indicator is set to 'in' or 'out'.) ## - an indicator of WHETHER TIC MARKS ARE TO BE DRAWN/PLACED. ## Tic marks are not needed if they are already on the ## background image. ## ## NOTE1: ## The center location and radius parameters are to be given as ## ratios (not pixels) --- so that they work for any size of the ## background image --- small, medium, or large. ## NOTE2: ## We do not require the meter background images to be square. ## They can be rectangular. ## ################### ## THE GUI LAYOUT: ## The options available to the user are compactly indicated ## by the following 'sketch' of the GUI: ## ## --------------------------------------------------------------- ## CPU Activity [window title] ## --------------------------------------------------------------- ## {Exit} {Help} {Report} {Refresh} O Small O Medium O Large ## ## Theme for ## the meter : <--------------------> X DrawTicLabels ## ## X DrawTicsArc X ShowCornerFeatures ## ## {ShowCPUs} Monitor CPU: ________ X Rivets ## ## SampleRate NN ## (seconds) : <----------------------------> ## ## $user = nn.nn %nice = n.nn ## $system = nn.nn %idle = nn.nn ## ## ## The meter will be placed in this area on a Tk canvas. ## ## A background image will be placed here, according to ## the integer setting on the 'theme' scale widget. ## ## Tic marks can be drawn in an arc on the meter background, ## with 'create line', if they are not already on the ## background image. ## ## The check-buttons above can be used by the user ## to specify whether the labels (0 to 100) should be ## drawn/shown (with 'create text') --- and whether an ## arc should be drawn/shown (with 'create arc') along the ## path of the tic marks. ## ## Optionally, as part of the 'theme' selected, 4 images ## ('rivets' or screw-heads or whatever) can be placed ## in the 4 corners of the background image. ## ---------------------------------------------------------------- ## ## In the GUI diagram above: ## - Braces indicate a Tk 'button' widget. ## - Underscores indicate a Tk 'entry' widget. ## - A colon indicates that the text before the colon is on a 'label' widget. ## - Capital-O indicates a Tk 'radiobutton' widget. ## - Capital-X indicates a Tk 'checkbutton' widget. ## - A line of hyphens with arrow-heads on each end (<-------->) ## is used to indicate a 'scale' widget. ## - Square brackets indicate a comment (not to be placed on the GUI). ## ## NOTE: We may need to abbreviate or move the Small-Medium-Large ## radiobuttons to fit them into the GUI without making ## it too wide. If necessary, we may need to add a line ## below the themes scale widget, for more theme controls ## (checkbuttons and radiobuttons). ## ##+############## ## GUI components: ## ## From the GUI 'sketch' above, it is seen that the GUI consists of ## about ## ## - 5 button widgets ## - 5 label widgets ## - 1 entry widget ## - 3 radiobutton widgets in 1 group ## - 2 checkbutton widgets ## - 2 scale widgets ## - 0 listbox widgets ## ########################################################## ## GETTING & USING CPU-USAGE-DATA for 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 see the ID's of the selectable ## CPU's, whose %-activity can be shown on the meter. ## ## For a user-selected CPU-ID, whenever the 'cat /proc/stat' ## is run, a 'grep' command is used to get the line of data ## for the user-selected CPU-ID. The data is summed and ## divided to get the %-value to be pointed to by the ## 'pointer' of the meter. ## ## The %-user, %-system, %-nice, and %-idle values are also ## shown as text, on label widgets --- just above the meter ## on the canvas. ## ############################################################### ## AUTO-UPDATE of the METER-'POINTER' and TEXT-DATA for the CPU: ## ## The needle (or other style of 'pointer') 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. ## ## A 'Refresh' button on the GUI can be used to reset ## the start time for sampling --- at the current rate ## (a sampling interval, set via the scale widget). ## ## The 'Refresh' button is useful if the sampling rate is set high ## (say 10 seconds or more) and the user wants to immediately ## reset the sampling rate. (The 'Refresh' button is also ## used to resize the canvas drawing of the meter, after ## the user resizes the GUI window.) ## #################### ## 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. ## ## This utility uses 'awk' to subtract the data from two ## issues of the 'cat /proc/stat' command, spaced a couple ## of seconds apart. In addition, 'awk' is used to ## format the data into a report with column headings, etc. ## ## 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' mode), ## 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 (and 5?). 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 'pointer' on the meter is updated periodically to show the ## %-activity of the user-specified CPU, 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. ## ##+########### ## METHOD USED to respond to 'Report' and 'ShowCPUs' buttons ## and 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 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 - a 'report' ## 2) a list of the CPU ID's ## 3) activity data for a user-specified CPU-ID. ## ##+####################### ## 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. (PNG is better for scaling up or down.) ## ## 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 ## '.fRtheme1' - to contain a scale widget and a checkbutton widget ## '.fRtheme2' - to contain checkbutton widgets ## '.fRcontrol1' - to contain a 'ShowCPUs' button --- and an entry widget ## '.fRcontrol2' - to contain a scale widget, with a label widget. ## '.fRmeters' - to contain 2 label widgets over a square canvas ## widget, which will display the meter. ## ## Sub-frames: ## '.fRmeters.fRmeter1' - for 2 (or more) label widgets & 1 canvas widget, ## corresponding to one CPU-ID ## ## (The '.fRmeters' frame could contain more sub-frames ## someday, to hold more meters shown simultaneously.) ## ## 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 ## (See the BINDINGS section of the code for details.) ## ## 4) Define PROCS: ## ## 'make_meters' - to draw up to one (or more) meter(s) within ## Tk canvas(es). ## ## The size of the canvas will be determined ## by the size of the background image, which in ## turn is determined by the theme-index and ## the setting of the Small-Medium-Large radiobuttons. ## ## 'make_one_meter' - called by 'make_meters', to make each meter. ## ## 'update_pointers' - to update the pointers on the one or more meters. ## ## 'update_one_pointer' - called by 'update_pointers', to draw the ## pointer (bullet, needle, whatever) on a meter. ## ## 'Refresh' - called by 'Refresh' button. Runs ## 'make_meters' and 'update_pointers'. ## ## '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_meters' to put the meter(s) in the canvas(es). ## - call 'update_pointers' --- to initialize the pointer 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 2013dec18 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 2013 ##+######################################################################## ##+###################################################### ## 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 entryBKGD "#ffffff" # set listboxBKGD "#ffffff" set radbuttBKGD "#ffffff" set chkbuttBKGD "#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 ## RADIOBUTTON widget geom settings: set PADXpx_radbutton 0 set PADYpx_radbutton 0 set BDwidthPx_radbutt 2 ## CHECKBUTTON widget geom settings: set PADXpx_chkbutton 0 set PADYpx_chkbutton 0 set BDwidthPx_chkbutt 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, ## 2 chars high for the '.fRtheme1' frame, ## 1 char high for the '.fRtheme2' 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' ## 2 chars high for 'fRtheme1' ## 1 char high for 'fRtheme2' ## 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 {7 * $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 ## (~6 widgets x 4 pixels/widget = 24 pixels). set minWinHeightPx [expr {94 + $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 the user ## choosing Small/Medium/Large meter backgrounds. ## 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" set aRtext(radbuttSMALL) "S" set aRtext(radbuttMEDIUM) "M" set aRtext(radbuttLARGE) "L" ## For '.fRtheme' frame: set aRtext(labelTHEMEscale) "Theme for the meter :" set aRtext(chkbuttLABELS) "DrawTicLabels" ## For '.fRtheme2' frame: set aRtext(chkbuttARC) "DrawTicsArc" set aRtext(chkbuttCORNERS) "ShowCornerFeatures (if in theme)" ## For '.fRcontrol1' frame: set aRtext(buttonCPUS) "ShowCPUs" set aRtext(labelCPU) " MonitorCPU:" ## For '.fRcontrol2' frame: set aRtext(labelSAMPLEscale) "SampleRate (seconds) :" ## END OF if { "$VARlocale" == "en"} ##+############################################################## ## DEFINE THE AVAILABLE THEMES, VIA SEVERAL ARRAYS. ## Recall, from above: ## Each theme (indexed via an integer) includes the following items, ## stored in Tcl arrays: ## - a BACKGROUND IMAGE (a GIF or PNG file) - and we allow for a ## small, medium, and large background images (about 200, 350, ## and 500 pixels high) ## - a BULLET/POINTER IMAGE (a small transparent GIF or PNG file) ## - a RIVET IMAGE (a small transparent GIF or PNG file), which ## is shown optionally, according to a checkbutton on the GUI ## (If null, of course it is not available to be shown.) ## - some parameters, such as ## - the CENTER LOCATION on the background image (in case it is ## not symmetric) --- typically about 0.5,0.5 ## - a RADIUS for an arc, for positioning tic marks and tic mark ## labels --- as well as for positioning the 'bullet' image. ## (Typically this radius value will be about 0.35.) ## - an indicator of whether the tic marks & labels should be ## PLACED ON THE INSIDE OR THE OUTSIDE OF THE ARC. (This ## indicator is set to 'in' or 'out'.) ## - an indicator of WHETHER TIC MARKS ARE TO BE DRAWN/PLACED. ## Tic marks are not needed if they are already on the ## background image. ##+############################################################## ## Theme 1: set aRimgBkgd(1) "meter_background_3DsphericalPuzzle-whiteBkgd" set aRticsYorN(1) "N" set aRcenterX(1) 0.5 set aRcenterY(1) 0.5 set aRradiusArc(1) 0.35 set aRinORout(1) "in" set aRhexcolorText(1) "#000000" set aRhexcolorTics(1) "#000000" set aRimgPointer(1) "bullet_earth_whiteOnBlue_32x32_transp" set aRimgCornerFeature(1) "screwHeadSlotted_30deg_gray_37x40_transp" ## Theme 2: set aRimgBkgd(2) "meter_background_blackShadedDisk_metallicEdge_blueTOredARC_whiteBkgd" set aRticsYorN(2) "N" set aRcenterX(2) 0.5 set aRcenterY(2) 0.5 set aRradiusArc(2) 0.30 set aRinORout(2) "in" set aRhexcolorText(2) "#ffffff" set aRhexcolorTics(2) "#ffffff" set aRimgPointer(2) "bullet_glassyYellowOrange_32x32_transp" set aRimgCornerFeature(2) "" ## Theme 3: set aRimgBkgd(3) "meter_background_blackSolidDisk_grayShadedEdge_whiteBkgd" set aRticsYorN(3) "N" set aRcenterX(3) 0.5 set aRcenterY(3) 0.5 set aRradiusArc(3) 0.30 set aRinORout(3) "in" set aRhexcolorText(3) "#ffffff" set aRhexcolorTics(3) "#ffffff" set aRimgPointer(3) "bullet_8ball_32x32_transp" set aRimgCornerFeature(3) "screwHeadSlotted_real_36x35_transp" ## Theme 4: set aRimgBkgd(4) "meter_background_greenLeaf_closeup" set aRticsYorN(4) "N" set aRcenterX(4) 0.5 set aRcenterY(4) 0.5 set aRradiusArc(4) 0.30 set aRinORout(4) "out" set aRhexcolorText(4) "#ffffff" set aRhexcolorTics(4) "#ffffff" set aRimgPointer(4) "bullet_greenSquare_32x32_transp" set aRimgCornerFeature(4) "rivet_1_greenish_35x34_transp" ## Theme 5: set aRimgBkgd(5) "meter_background_blackSolidOval_metallicEdge_ABSTRACT1" set aRticsYorN(5) "N" set aRcenterX(5) 0.5 set aRcenterY(5) 0.5 set aRradiusArc(5) 0.25 set aRinORout(5) "in" set aRhexcolorText(5) "#ffffff" set aRhexcolorTics(5) "#ffffff" set aRimgPointer(5) "bullet_8ball_32x32_transp" set aRimgCornerFeature(5) "screwHeadPhillipsA_gray_41x39_transp" ## Theme 6: set aRimgBkgd(6) "meter_background_blackSolidDisk_metallicEdge_grayBkgd_WATCH8screws" set aRticsYorN(6) "N" set aRcenterX(6) 0.5 set aRcenterY(6) 0.5 set aRradiusArc(6) 0.30 set aRinORout(6) "in" set aRhexcolorText(6) "#ffffff" set aRhexcolorTics(6) "#ffffff" set aRimgPointer(6) "bullet_greenWithBlackOutline_32x32_transp" set aRimgCornerFeature(6) "" ## Theme 7: set aRimgBkgd(7) "meter_background_blackSolidDisk_grayTOgreenBkgd_BULLEThole" set aRticsYorN(7) "N" set aRcenterX(7) 0.5 set aRcenterY(7) 0.5 set aRradiusArc(7) 0.19 set aRinORout(7) "in" set aRhexcolorText(7) "#ffffff" set aRhexcolorTics(7) "#ffffff" set aRimgPointer(7) "bullet_green_32x32_transp" set aRimgCornerFeature(7) "rivet_1_greenish_35x34_transp" ## Theme 8: set aRimgBkgd(8) "meter_background_blackSolidDisk_greenBkgd_BULLEThole" set aRticsYorN(8) "N" set aRcenterX(8) 0.5 set aRcenterY(8) 0.48 set aRradiusArc(8) 0.20 set aRinORout(8) "in" set aRhexcolorText(8) "#ffffff" set aRhexcolorTics(8) "#ffffff" set aRimgPointer(8) "bullet_green_32x32_transp" set aRimgCornerFeature(8) "screwHeadPhillipsWorn_greenish_49x48_transp" ## Theme 9: set aRimgBkgd(9) "meter_background_glossyRedBall_blackHilitedHood_noBkgd" set aRticsYorN(9) "N" set aRcenterX(9) 0.5 set aRcenterY(9) 0.5 set aRradiusArc(9) 0.30 set aRinORout(9) "in" set aRhexcolorText(9) "#ffffff" set aRhexcolorTics(9) "#ffffff" set aRimgPointer(9) "bullet_reddish_32x32_transp" set aRimgCornerFeature(9) "screwHeadPhillipsB_gray_42x41_transp" ## Theme 10: set aRimgBkgd(10) "meter_background_grinningPumpkin_noEdge_blackBkgd" set aRticsYorN(10) "N" set aRcenterX(10) 0.47 set aRcenterY(10) 0.48 set aRradiusArc(10) 0.30 set aRinORout(10) "in" set aRhexcolorText(10) "#000000" set aRhexcolorTics(10) "#000000" set aRimgPointer(10) "bullet_lightbulb_32x32_transp" set aRimgCornerFeature(10) "rivetPop_2_gray-white_30x30_transp" set Nthemes 10 ##+################################################################ ## DEFINE-AND-PACK *ALL* THE FRAMES: ## ## Top-level : '.fRbuttons' , '.fRtheme', ## '.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 .fRtheme1 -relief $RELIEF_frame -bd $BDwidth_frame frame .fRtheme2 -relief $RELIEF_frame -bd $BDwidth_frame # frame .fRcontrol1 -relief $RELIEF_frame -bd $BDwidth_frame frame .fRcontrol1 -relief raised -bd 2 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 .fRtheme1 \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRtheme2 \ -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} ## DEFINE Radiobuttons for Small/Medium/Large meters (background images) : radiobutton .fRbuttons.radbuttSMALL \ -text "$aRtext(radbuttSMALL)" \ -font fontTEMP_varwidth \ -anchor w \ -variable RADVARsizeSML \ -value "SMALL" \ -selectcolor "$radbuttBKGD" \ -relief flat \ -bd $BDwidthPx_radbutt radiobutton .fRbuttons.radbuttMEDIUM \ -text "$aRtext(radbuttMEDIUM)" \ -font fontTEMP_varwidth \ -anchor w \ -variable RADVARsizeSML \ -value "MEDIUM" \ -selectcolor "$radbuttBKGD" \ -relief flat \ -bd $BDwidthPx_radbutt radiobutton .fRbuttons.radbuttLARGE \ -text "$aRtext(radbuttLARGE)" \ -font fontTEMP_varwidth \ -anchor w \ -variable RADVARsizeSML \ -value "LARGE" \ -selectcolor "$radbuttBKGD" \ -relief flat \ -bd $BDwidthPx_radbutt ## Set the default value for this set of radiobuttons. set RADVARsizeSML "SMALL" ## Pack the widgets in frame '.fRbuttons'. pack .fRbuttons.buttEXIT \ .fRbuttons.buttHELP \ .fRbuttons.buttREPORT \ .fRbuttons.buttREFRESH \ .fRbuttons.radbuttSMALL \ .fRbuttons.radbuttMEDIUM \ .fRbuttons.radbuttLARGE \ -side left \ -anchor w \ -fill none \ -expand 0 ##+########################################################## ## In FRAME '.fRtheme1' - ## DEFINE-and-PACK a LABEL, a SCALE, and 1 CHECKBUTTON. ##+########################################################## label .fRtheme1.labelTHEME \ -text "$aRtext(labelTHEMEscale)" \ -font fontTEMP_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 THEMEindex 1 scale .fRtheme1.scaleTHEME \ -from 1 -to $Nthemes \ -resolution 1 \ -font fontTEMP_SMALL_varwidth \ -variable THEMEindex \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -length 100 \ -width $scaleThicknessPx set LABELS0or1 1 checkbutton .fRtheme1.chkbuttLABELS \ -text "$aRtext(chkbuttLABELS)" \ -font fontTEMP_varwidth \ -padx $PADXpx_chkbutton \ -pady $PADYpx_chkbutton \ -bd $BDwidthPx_chkbutt \ -variable LABELS0or1 \ -selectcolor "$chkbuttBKGD" \ -relief flat ## Pack the widgets in the '.fRtheme1' frame: pack .fRtheme1.labelTHEME \ .fRtheme1.scaleTHEME \ .fRtheme1.chkbuttLABELS \ -side left \ -anchor w \ -fill none \ -expand 0 ##+########################################################## ## In FRAME '.fRtheme2' - ## DEFINE-and-PACK 2 CHECKBUTTONS. ##+########################################################## set ARC0or1 1 checkbutton .fRtheme2.chkbuttARC \ -text "$aRtext(chkbuttARC)" \ -font fontTEMP_varwidth \ -padx $PADXpx_chkbutton \ -pady $PADYpx_chkbutton \ -bd $BDwidthPx_chkbutt \ -variable ARC0or1 \ -selectcolor "$chkbuttBKGD" \ -relief flat set CORNERS0or1 1 checkbutton .fRtheme2.chkbuttCORNERS \ -text "$aRtext(chkbuttCORNERS)" \ -font fontTEMP_varwidth \ -padx $PADXpx_chkbutton \ -pady $PADYpx_chkbutton \ -bd $BDwidthPx_chkbutt \ -variable CORNERS0or1 \ -selectcolor "$chkbuttBKGD" \ -relief flat ## Pack the widgets in the '.fRtheme2' frame: pack .fRtheme2.chkbuttARC \ .fRtheme2.chkbuttCORNERS \ -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 pointer(s). ##+########################################################## ## Here is the LABEL-AND-SCALE pair for the wait-seconds (sample rate). label .fRcontrol2.labelSAMPLEscale \ -text "$aRtext(labelSAMPLEscale)" \ -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.labelSAMPLEscale \ .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 .fRtheme1.scaleTHEME {Refresh} bind .fRbuttons.radbuttSMALL {Refresh} bind .fRbuttons.radbuttMEDIUM {Refresh} bind .fRbuttons.radbuttLARGE {Refresh} bind .fRcontrol1.entryCPU {Refresh} bind .fRtheme1.chkbuttLABELS {Refresh} bind .fRtheme2.chkbuttARC {Refresh} bind .fRtheme2.chkbuttCORNERS {Refresh} ## The Refresh' proc runs both the 'make_meters' proc and the ## 'update_pointers' proc. ## ## In the CHECKBUTTON 'events' above, the theme (in particular, the ## meter background, and size) has not changed, so we do not ## need to rebuild the entire meter. For example, reloading the ## meter background image is not really necessary. (The same ## can be said of the 'entryCPU' event.) ## ## But the use of these checkbuttons is very seldom, if at all, ## in a session. So running 'Refresh' for those events is not ## really incurring an inordinate amount of processing. We use ## 'Refresh' for these events, rather than breaking up the ## 'make_meters' proc into a smaller sequence of procs --- or ## adding arguments to the 'make_meters' proc to turn various ## sections of the make-meter processing off and on. ##+################################################################## ##+################################################################## ## DEFINE PROCS SECTION: ## We assume we are 'drawing' one meter per CPU. ## Although we draw only one meter on the GUI in this implementation, ## we structure the procs so that they could, with relatively little ## change, support drawing more than one meter on the GUI per CPU --- ## OR meters on the GUI for more than one CPU. ## ## 'make_meters' - to draw meter(s) within Tk canvas(es). ## ## The size of the canvas will be determined ## by the size of the background image, which in ## turn is determined by the theme-index and ## the setting of the Small-Medium-Large radiobuttons. ## ## 'make_one_meter' - to draw one meter. Called by 'make_meters' ## to make the meter(s). ## ## Calls on the 'get_cpu_usage_info.sh' script ## with the CPU-ID for its single argument. ## ## 'update_pointers' - to update the 'pointer(s)' on the meter(s). ## Called initially at the bottom of this script. ## And called in the 'Refresh' proc. ## ## 'update_one_pointer' - to place one 'pointer' on a specified meter/canvas. ## Called by 'update_pointers' to update each of the ## pointers on the one or more meters. ## ## 'Refresh' - called by the 'Refresh' button. Runs the procs ## 'make_meters' and 'update_pointers' --- in particular, ## for the user to force the meters to be resized if ## the user resizes the meter with the S-M-L radiobuttons ## --- and whenever the user wants a new start of the ## sampling, typically on changing the sample rate. ## ## '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_meters' ##+######################################################################## ## PURPOSE: Draws all features (except the 'pointers') of one or more ## high-definition meters. ## ## The size of the meter is determined by the theme-index and ## the setting of the Small-Medium-Large radiobuttons --- ## which determines which background image is loaded. ## ## CALLED BY: once, at the 'Additional GUI Initialization' section, ## at the bottom of this script --- AND in the 'Refresh' proc ## which is called, for example, after the meter is resized ## via the S-M-L radiobuttons. ##+######################################################################## # set Nmeters 1 proc make_meters {} { # global 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)}] ######################################################### ## Draw meter1 (without pointer). ######################################################### make_one_meter .fRmeters.fRmeter1.can ######################################################### ## If it is ever decided to draw more meters (such ## as additional meters for 'user' and 'system' and 'idle' ## percents, as well as a meter for 'total' percent ## activity), we could call on 'make_one_meter' additional ## times here. ######################################################### # make_one_meter .fRmeters.fRmeter2.can # make_one_meter .fRmeters.fRmeter3.can # make_one_meter .fRmeters.fRmeter4.can ## OR, use a loop such as: # for {set i 1} {$i < $Nmeters} {incr i} { # make_one_meter $i # } ####################################################### ## NEEDED(?) to force the canvases and frames to update ## according to the new meter (image-on-canvas) sizes. ####################################################### update } ## END OF proc 'make_meters' ##+######################################################################## ## PROC 'make_one_meter' ##+######################################################################## ## PURPOSE: Places/draws all features of a high-definition meter (except ## the 'pointer'). ## ## The features include: ## - the meter background image - with canvas 'create image' ## - tic-marks following an arc path - with canvas 'create line' ## - optionally (according to a checkbutton setting), an arc ## along the tic-marks - with canvas 'create arc' ## - optionally (according to a checkbutton setting), labels ## for the tic-marks - with canvas 'create text' ## - optionally (according to a checkbutton setting), decorative ## 'features' (rivets or whatever) at the 4 corners of the ## meter background image. ## ## The theme arrays (in particular, the dimensions of the meter ## background image and the 'center point' and 'radius' ratio-parameters) ## are used to set top-right and bottom-left coordinates to specify ## the location of the arc --- along which tic-marks and labels ## are drawn. ## ## A CPU-ID is passed into this proc to .... ## ## CALLED BY: proc 'make_meters' ##+####################################################################### set pi [expr {4.0 * atan(1.0)}] set radsPERdeg [expr {$pi/180.0}] set INDENTfactor 0.10 ## The above variables are set ONCE, for use in drawing tic-marks ## in an arc on the meter. ## 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_meter {canvas} { global pi radsPERdeg Nsegs ticLabels THEMEindex \ aRimgBkgd aRimgPointer aRimgCornerFeature \ aRcenterX aRcenterY aRradiusArc aRinORout aRticsYorN \ aRhexcolorText aRhexcolorTics \ DIRscripts imgSUFFIX RADVARsizeSML \ ARC0or1 LABELS0or1 CORNERS0or1 \ centerXpx centerYpx radiusPx ## NOTE: centerXpx centerYpx radiusPx are calculated in this proc, ## and they are made global variables for use in the ## 'update_one_needle' proc, so that proc does not have ## to do the calculations again. We prefer that those ## calculations only be done when the meter image is changed. ## FOR TESTING: (to dummy out this proc) # return ################################################################ ## REMOVE any previously placed ELEMENTS IN THIS CANVAS, if any. ################################################################ catch {$canvas delete all} ################################################################## ## CREATE THE TK IN-MEMORY IMAGE STRUCTURE FROM THE ## METER-BACKGROUND-IMAGE FILE --- determined by the current ## theme index, $THEMEindex. ################################################################## image create photo imgID_meter -file \ "$DIRscripts/meter_backgrounds/$aRimgBkgd($THEMEindex)_${RADVARsizeSML}.$imgSUFFIX" ################################################## ## PUT THE BACKGROUND IMAGE ON THE CANVAS. ################################################## $canvas create image 0 0 -anchor nw -image imgID_meter ## FOR TESTING: (exit this proc before adding more to the meter) # return ################################################################## ## CREATE THE TK IN-MEMORY IMAGE STRUCTURE FROM THE ## POINTER-IMAGE FILE --- determined by the current ## theme index, $THEMEindex --- IF the file is specified in ## the theme. ################################################################## if {"$aRimgPointer($THEMEindex)" != ""} { image create photo imgID_pointer -file \ "$DIRscripts/pointers/$aRimgPointer($THEMEindex).$imgSUFFIX" } ################################################################## ## Start setting parameters that may be used to place/draw other ## items on the meter --- tic-marks, arc, tic-labels, ## a-feature-in-4-corners, meter-title. ################################################################## ## GET the WIDTH AND HEIGHT OF THE BACKGROUND IMAGE for the meter. ################################################################## set meterWidthPx [image width imgID_meter] set meterHeightPx [image height imgID_meter] set meterMinPx $meterWidthPx if {$meterHeightPx < $meterMinPx} {set meterMinPx $meterHeightPx} ################################################################## ## FORCE THE CANVAS TO CHANGE SIZE ACCORDINT TO THE NEW METER IMAGE. ################################################################## $canvas configure -width $meterWidthPx $canvas configure -height $meterHeightPx ## Wait and do the update for all the meters in the ## 'make_meters' proc? # update #################################################################### ## SET THE CORNER COORDS FOR DETERMINING THE ARC PATH (for tic-marks ## and optional labels and optional arc). ## We use the 'center point' and 'radius' ratio-parameters of ## the chosen theme --- from arrays aRcenterX aRcenterY aRradiusArc, ## at theme index, $THEMEindex. #################################################################### set topleftXpx \ [expr {($aRcenterX($THEMEindex) * $meterWidthPx) - \ ($aRradiusArc($THEMEindex) * $meterMinPx)}] set topleftYpx \ [expr {($aRcenterY($THEMEindex) * $meterHeightPx) - \ ($aRradiusArc($THEMEindex) * $meterMinPx)}] set botrightXpx \ [expr {($aRcenterX($THEMEindex) * $meterWidthPx) - \ ($aRradiusArc($THEMEindex) * $meterMinPx)}] set botrightYpx \ [expr {($aRcenterY($THEMEindex) * $meterHeightPx) - \ ($aRradiusArc($THEMEindex) * $meterMinPx)}] ########################################################### ## SET parameters for drawing TIC-MARKS and/or LABELS ## around the ARC. ########################################################### set centerXpx [expr {$meterWidthPx * $aRcenterX($THEMEindex)}] set centerYpx [expr {$meterHeightPx * $aRcenterY($THEMEindex)}] set radiusPx [expr {$meterMinPx * $aRradiusArc($THEMEindex)}] set DEGperTIC [expr {320.0/$Nsegs}] ########################################################### ## DRAW TIC-MARKS around the meter. ## (The ticmarks are drawn according to the value of the ## array setting $aRticsYorN($THEMEindex).) ########################################################### if { "$aRticsYorN($THEMEindex)" == "N" } { ## Set the outer or inner location (radius) of the TICMARKS, ## according to the value of $aRinORout($THEMEindex). if { "$aRinORout($THEMEindex)" == "in" } { set radius2Px [expr {0.90 * $radiusPx}] } if { "$aRinORout($THEMEindex)" == "out" } { set radius2Px [expr {1.10 * $radiusPx}] } set angle0 250.0 for {set i 0} {$i <= $Nsegs} {incr i} { set rads [expr {($angle0 - ($DEGperTIC * $i)) * $radsPERdeg}] set x1 [expr {$centerXpx + $radiusPx * cos($rads)}] set y1 [expr {$centerYpx - $radiusPx * sin($rads)}] set x2 [expr {$centerXpx + $radius2Px * cos($rads)}] set y2 [expr {$centerYpx - $radius2Px * sin($rads)}] $canvas create line \ $x1 $y1 $x2 $y2 \ -fill $aRhexcolorTics($THEMEindex) -width 2 } ## END OF for {set i 0} ... (for drawing TIC MARKS) } ## END OF if { "$aRticsYorN($THEMEindex)" == "N" } ## FOR TESTING: (exit this proc before adding more to the meter) # return ########################################################### ## DRAW LABELS around the meter. ## (The labels are drawn if the LABELS check button is ON.) ########################################################### if {$LABELS0or1 == 1} { ## Set the outer or inner location (radius) of the LABELS, ## according to the value of $aRinORout($THEMEindex). if { "$aRinORout($THEMEindex)" == "in" } { set radius2Px [expr {0.80 * $radiusPx}] } if { "$aRinORout($THEMEindex)" == "out" } { set radius2Px [expr {1.20 * $radiusPx}] } set angle0 250.0 for {set i 0} {$i <= $Nsegs} {incr i} { set rads [expr {($angle0 - ($DEGperTIC * $i)) * $radsPERdeg}] ## Calculate a center point for the i-th label. set x1 [expr {$centerXpx + $radius2Px * cos($rads)}] set y1 [expr {$centerYpx - $radius2Px * sin($rads)}] ## Get the i-th label from the list of labels. set ticlabel [lindex $ticLabels $i] ## Draw the tic-label on the canvas. $canvas create text \ $x1 $y1 \ -anchor center -justify center \ -fill $aRhexcolorText($THEMEindex) \ -text "$ticlabel" -font { Helvetica 10 } } ## END OF for {set i 0} ... (for drawing TIC LABELS) } ## END OF if {$LABELS0or1 == 1} ## FOR TESTING: (exit this proc before adding more to the meter) # return ################################################# ## IF the ARC checkbutton is ON. ## DRAW ARC-LINE along 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 ################################################# if {$ARC0or1 == 1} { set x1 [expr {$centerXpx - $radiusPx}] set y1 [expr {$centerYpx - $radiusPx}] set x2 [expr {$centerXpx + $radiusPx}] set y2 [expr {$centerYpx + $radiusPx}] $canvas create arc $x1 $y1 $x2 $y2 \ -start -70 -extent 320 -style arc \ -outline $aRhexcolorTics($THEMEindex) -width 2 } ## END OF if {$ARC0or1 == 1} ## FOR TESTING: (exit this proc before adding more to the meter) # return ############################################################# ## IF the CORNERS checkbutton is ON, ## PLACE 4 'FEATURES' AT THE CORNERS of the meter background. ############################################################# if {$CORNERS0or1 == 1 && "$aRimgCornerFeature($THEMEindex)" != ""} { ################################################################## ## CREATE THE TK IN-MEMORY IMAGE STRUCTURE FROM THE ## CORNER-FEATURE-IMAGE FILE --- determined by the current ## theme index, $THEMEindex. ################################################################## image create photo imgID_feature -file \ "$DIRscripts/corner_features/$aRimgCornerFeature($THEMEindex).$imgSUFFIX" ############################################################### ## We set the corner coordinates of the 'outside-corner' of ## the 4 features at a fixed offset. ## ## (We could change this to position the 4 features based on ## its image dimensions and the width and height of the ## meter background.) ############################################################### ## Get the current imgID_feature width and height. set featureWidthPx [image width imgID_feature] set featureHeightPx [image height imgID_feature] # set featureMaxPx $featureWidthPx # if {$featureHeightPx > $featureMaxPx} {set featureMaxPx $featureHeightPx} # set FEATUREoffsetPx 8 set FEATUREoffsetPx [expr {0.02 * $meterMinPx}] ## Place upper-left feature. set x1 $FEATUREoffsetPx set y1 $FEATUREoffsetPx .fRmeters.fRmeter1.can create image $x1 $y1 \ -anchor nw -image imgID_feature ## Place upper-right feature. set x1 [expr {$meterWidthPx - $FEATUREoffsetPx}] set y1 $FEATUREoffsetPx .fRmeters.fRmeter1.can create image $x1 $y1 \ -anchor ne -image imgID_feature ## Place lower-left feature. set x1 $FEATUREoffsetPx set y1 [expr {$meterHeightPx - $FEATUREoffsetPx}] .fRmeters.fRmeter1.can create image $x1 $y1 \ -anchor sw -image imgID_feature ## Place lower-right feature. set x1 [expr {$meterWidthPx - $FEATUREoffsetPx}] set y1 [expr {$meterHeightPx - $FEATUREoffsetPx}] .fRmeters.fRmeter1.can create image $x1 $y1 \ -anchor se -image imgID_feature } ## END OF if {$CORNERS0or1 == 1} ## FOR TESTING: (exit this proc before adding more to the meter) # return #################################################################### ## DRAW A '%USED' TEXT-LABEL, ## near the top of the meter arc. #################################################################### $canvas create text \ $centerXpx [expr {$centerYpx - (0.50 * $radiusPx)}] \ -anchor center -justify center \ -fill $aRhexcolorText($THEMEindex) \ -text "%used" -font { Helvetica 10 normal roman} } ## END OF proc 'make_one_meter' ##+######################################################################## ## PROC 'update_pointers' ##+######################################################################## ## PURPOSE: Updates pointer(s) on the canvas(es) --- using the ## Linux '/proc/stat' file to get 'activity' data ## for the current user-selected CPU. ## ## This proc uses the 'update_one_pointer' proc to update ## the pointer for each of the Nmeters canvases. ## ## CALLED BY: the 'Additional GUI Initialization' section at the ## bottom of this script, and within this proc itself. ## Also within the 'Refresh' proc. ##+######################################################################## proc update_pointers {} { 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_pointers:" 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 pointer 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_pointer .fRmeters.fRmeter1 $CPUname $PERCENTtot \ $PERCENTuser $PERCENTnice $PERCENTsystem $PERCENTidle ############################################################ ## Force the pointers to show up on the GUI. (needed???) ############################################################ # update ################################################################ ## 'Pseudo-Recursively' 'fork off' another (delayed) instance of the ## 'update_pointers' 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_pointers ################################################################ ## Reset the PREV vars. ################################################################ set PREVuser $CURuser set PREVnice $CURnice set PREVsystem $CURsystem set PREVidle $CURidle } ## END OF proc 'update_pointers' ##+######################################################################## ## PROC 'update_one_pointer' ##+######################################################################## ## PURPOSE: Updates a pointer 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 pointer as a proportion of the (square) canvas size. ## ## CALLED BY: the 'update_pointers' proc ##+######################################################################## proc update_one_pointer \ {frame CPUNAME PERCENT PERCENTuser PERCENTnice PERCENTsystem PERCENTidle} { ## For the meter background image, we calculated the ## 'center point' and the radius of the arc (for the current theme ## and SML size) in the 'make_one_meter' proc. Those results were ## put in global vars: centerXpx centerYpx radiusPx ## ## (This works OK because we are using only one meter for this version. ## This is one way to avoid recalculating those 3 variables.) global pi radsPERdeg Nsegs \ THEMEindex aRhexcolorText \ centerXpx centerYpx radiusPx \ THEMEindex aRimgPointer aRinORout ## 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]" #################################################################### ## Before drawing the pointer, ## draw a text-label, CPUNAME, near the bottom of the meter arc. #################################################################### catch {$frame.can delete -tags TAG_cputext} $frame.can create text \ $centerXpx [expr {$centerYpx + (0.40 * $radiusPx)}] \ -anchor center -justify center \ -fill $aRhexcolorText($THEMEindex) \ -text "CPU: $CPUNAME" -font { Helvetica 10 normal roman} \ -tag TAG_cputext ############################################################ ## Prepare to draw the pointer on the meter. ############################################################ ## SET THE ANGLE FOR THE ZERO-POINT on the arc-of-tic-marks. ############################################################ set angle0 250.0 ########################################################## ## Convert $PERCENT 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_pointer' :" # puts "PERCENT: $PERCENT degs: $degs rads: $rads" ##################################### ## Remove a previous pointer, if any. ##################################### catch {$frame.can delete -tags TAGpointer} ################################################## ## If an image was supplied for a pointer, ## PUT THE POINTER-IMAGE ON THE CANVAS --- ## otherwise draw a red pointer 'needle' with ## canvas 'create line'. ################################################## if {"$aRimgPointer($THEMEindex)" != ""} { ################################################################ ## Calculate the coordinates for the tip and base of the ## IMAGE pointer --- according to whether aRinORout is ## "in" or "out". ################################################################ if {"$aRinORout($THEMEindex)" == "in"} { set x1 [expr {$centerXpx + 0.60 * $radiusPx*cos($rads)}] set y1 [expr {$centerYpx - 0.60 * $radiusPx*sin($rads)}] } else { set x1 [expr {$centerXpx + 1.35 * $radiusPx*cos($rads)}] set y1 [expr {$centerYpx - 1.35 * $radiusPx*sin($rads)}] } $frame.can create image $x1 $y1 -anchor center \ -image imgID_pointer -tag TAGpointer } else { ################################################################ ## Calculate the coordinates for the tip and base of the ## NEEDLE pointer. ################################################################ set xtip [expr {$centerXpx + 0.60 * $radiusPx*cos($rads)}] set ytip [expr {$centerYpx - 0.60 * $radiusPx*sin($rads)}] set xbase $centerXpx set ybase $centerYpx #################################################################### ## Draw a red-line pointer 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 TAGpointer $frame.can create line \ [expr {$xbase + 1}] [expr {$ybase + 1}] \ [expr {$xtip + 1}] [expr {$ytip + 1}] \ -fill #ff8888 -width 2 -tag TAGpointer $frame.can create line \ [expr {$xbase - 1}] [expr {$ybase - 1}] \ [expr {$xtip - 1}] [expr {$ytip - 1}] \ -fill #ff8888 -width 2 -tag TAGpointer } ## END OF if {"$aRimgPointer" != ""} } ## END OF proc 'update_one_pointer' ##+############################################################# ## proc Refresh ## ## PURPOSE: 'Refresh' the meter(s) and their pointers --- ## 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 pointer update(s), before redrawing ## the meters and restarting the update-pointers 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 pointer(s) on the meter(s). after 500 # after 1000 make_meters update_pointers } ## 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_meters update_pointers 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 high-definition METER, with a range of 0 to 100 percent. The pointer 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 'pointer' on the meter (which is typically supplied as a small glossy ball image) 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. *************************************** THEMES (for the meter background, etc.): A 'scale' widget on the GUI can be used to select from a set of themes. The themes are referenced by an integer index and include - an image for the meter background - an image for the 'pointer' that moves around an arc on the meter background - an optional image for a feature to be placed at the four corners of the meter background - X and Y locations for the center of the arc to be used to determine the path of the 'pointer' on the meter - a radius for that arc - an indicator whether the tic-marks and labels should be drawn to the outside OR the inside of the arc - an indicator whether tic marks should be drawn (the tic marks could be supplied as part of the meter background image) - a color for the ticmarks - a color for the ticmark text labels There are several themes supplied with this GUI. But the user can add their own meter-background images and 'pointer' images and related theme-parameters, by updating a set of theme-parameter arrays ---in a section of the code near the arrays for setting the text that appears in widgets such as labels, buttons, radiobuttons, and checkbuttons. *************************************** METER RESIZE (Small, Medium, or Large): The user may resize the meter rather than using a fixed meter size. The user can choose among Small, Medium, and Large meter sizes, via 3 radiobuttons on the GUI. If the user resizes the meter, by clicking on one of the S-M-L radiobuttons, the 'Refresh' function is automatically performed for the user, to force the meter to be drawn with a different sized background image --- in accordance with the theme (number) that is currently selected via the theme 'scale' widget. ************************************************* THE SHELL SCRIPT USED to update the meter pointer: 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 technique or command 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_meters'. ## - Start an execution loop for the 'update_pointers' 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 number of tic-mark segments, and put the ## labels for the meter in a string variable. ##+#################################################### set Nsegs 10 set ticLabels "0 10 20 30 40 50 60 70 80 90 100" ####################################################### ## Set a suffix to use for the image files. ## We assume we use all GIF's or all PNG's. ####################################################### set imgSUFFIX "gif" # set imgSUFFIX "png" ##+############################################################# ## 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_pointers', '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]" ##+################################################# ## Draw the meter(s) (without pointer). ##+################################################# ## Need 'update' here to set the size of the canvases, ## because 'make_meters' uses 'winfo' to get ## the width and height of some frames and canvases. ##+################################################# update make_meters ##+######################################################### ## 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 pointer(s), after WAITseconds ## --- to allow some time from the 'initial sample' done ## just above. ## ## We call on the proc 'update_pointers' to draw the ## pointer(s). ## ## NOTE0: ## 'update_pointers' starts a loop to keep updating the pointer(s). ## ## The proc 'update_pointers' 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'. ##+###################################################### set WAITmillisecs [expr {int(1000*$WAITseconds)}] after $WAITmillisecs update_pointers ====== <> ------ '''The shell script (the 'wrapee') :''' And here is the code for the shell script called by this Tk script. This is the same script that I supplied on the page [A Tachometer-style Meter --- for CPU Usage] --- except that I added a 'percent-idle' column to the 'Report' ('all') option. This is a wrapper script for the 'cat /proc /stat' command. This script takes a single string-parameter which can be either 'all' or 'cpuslist' or a CPU-ID ('cpu', 'cpu0', 'cpu1', ...). You can put this script in the same directory with the Tk script. The Tk script includes some code (involving the 'argv0' variable) to determine the location of the shell script by extracting the name of the directory in which the Tk script lies. --- By the way, the Tk script is written so that it expects the images for the themes to be put in 'meter_backgrounds', 'pointers', and 'corner_features' subdirectories of the directory where the Tk script and the shell script are placed. Futhermore, the Tk script is written with an 'imgSUFFIX' variable that is set to 'gif'. In my testing, I used GIF files for the meter backgrounds, and transparent GIF's for the 'pointer' and 'corner feature' images --- because I was using Tcl-Tk 8.5 which supports GIF's but not PNG's. Anyone using Tcl-Tk 8.6 can put '.png' files in those 3 image directories, and change the 'imgSUFFIX' variable to be 'png'. <>Code for the shell script 'get_cpu_usage_info.sh' : ====== #!/bin/sh ## ## SCRIPT NAME: get_cpu_usage_info.sh ## ## PURPOSE: ## This script gets 'user', 'nice', 'system', and 'idle' CPU-data ## (in 'jiffies' --- typically hundredths of a second)) via output ## from the command 'cat /proc/stat'. ## ## This shell script is meant to be capable of returning info of 3 types: ## 1) a report of the usage of ALL the CPU's known to '/proc/stat' ## 2) a list of the CPU's --- for example, cpu, cpu0, cpu1, ... ## 3) 'user', 'nice', 'system', and 'idle' data for a user-specified CPU. ## ## This script takes a single parameter --- a text string --- to indicate ## which of these 3 types of info to return: ## 1) 'all' ## 2) 'cpuslist' ## 3) the name of a cpu, such as 'cpu0' or 'cpu1' --- or simply 'cpu' ## which represents all the CPU's. ## ## Example output from the 'cat /proc/stat' command: ## ## cpu 39842 1406 12184 1523220 6634 95 1824 0 0 ## cpu0 18990 914 4855 758670 3358 40 54 0 0 ## cpu1 20852 492 7329 764549 3275 54 1770 0 0 ## intr 872783 276605 3275 0 0 0 0 0 0 1 25293 0 0 ... about 300 more integers ## ctxt 1582873 ## btime 1385094848 ## processes 2593 ## procs_running 2 ## procs_blocked 0 ## softirq 567280 0 343218 2282 831 26582 7400 120750 119 66098 ## ######################################### ## EXPLANATION OF 'cat /proc/stat' OUTPUT: ## ## According to http://www.linuxhowtos.org/System/procstat.htm, ## the lines above can be described as follows. ## ## The very first "cpu" line aggregates the numbers in all of ## the other "cpuN" lines. ## ## These numbers identify the amount of time the CPU has spent ## performing different kinds of work. Time units are in USER_HZ ## or Jiffies (typically hundredths of a second). ## ## (The jiffie numbers reported in file /proc/stat are aggregates ## since the system first booted. Hence to get the values over ## a time interval, one should get the difference of values ## at a pair of start and end times.) ## ## The meanings of the columns are explained in 'man proc' --- ## specifically, the section on '/proc/stat'. The integers in the ## columns are 'jiffies' for columns, from left to right: ## ## 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 ## After Linux 2.6.x kernels: ## 5 * iowait: waiting for I/O to complete ## 6 * irq: servicing interrupts ## 7 * softirq: servicing softirqs ## 8 * steal_time: the ticks spent in other operating systems when ## running in a virtualized environment like Xen - since the ## 2.6.11 Linux kernel ## 9 * guest: for time spent running a virtual CPU for guest operating ## systems under the control of the Linux kernel - since the ## 2.6.24 Linux kernel ## ## The "intr" line gives counts of interrupts serviced since boot time, ## for each of the possible system interrupts. The first column is ## the total of all interrupts serviced; each subsequent column is ## the total for that particular interrupt. ## ## The "ctxt" line gives the total number of context switches ## across all CPUs. ## ## The "btime" line gives the time at which the system booted, ## in seconds since the Unix epoch. ## ## The "processes" line gives the number of processes and threads ## created, which includes (but is not limited to) those created by ## calls to the fork() and clone() system calls. ## ## The "procs_running" line gives the number of processes currently ## running on CPUs. ## ## The "procs_blocked" line gives the number of processes currently ## blocked, waiting for I/O to complete. ## ########################################## ## THE 3 FUNCTIONS OF THIS SCRIPT: ## ## 1) ## If 'all' is passed, all the cpu-lines data from the 'cat /proc/stat' ## command is used. It is reformatted from 'jiffies' into percents in ## 'user', 'nice', 'system', and 'idle' columns. ## The reformatted columnar data is returned by this script --- ## to stdout (standard-out). ## ## 2) ## If 'cpuslist' is passed, a list of the CPU's is passed. Here is an ## how the list is created, using 'grep' and 'awk': ## ## $ cat /proc/stat | grep '^cpu' | awk '{print $1}' ## cpu ## cpu0 ## cpu1 ## ## ## The list is returned to stdout. ## ## 3) ## If a cpu-name is passed (like 'cpu0' or 'cpu1' or 'cpu'), ## the user-nice-system-idle-etc 'jiffies' data is returned. Example: ## A line like ## ## cpu0 18990 914 4855 758670 3358 40 54 0 0 ## ## may be returned to stdout. ## ##+######### ## CALLED BY: a Tk GUI script that shows CPU usage data as PERCENT-used ## needle readings on one or more meters (dials) drawn on a ## Tk canvas --- Tk script name: ## meters_cpu_usage.tk ## ## The 'all' output is intended to be used to show usage for ALL the CPU's ## in a text-window of the Tk script GUI. ## ## The 'cpuslist' output is intended to be used to show a list of ALL the ## CPU names. ## ##+################### ## MAINTENANCE HISTORY: ## Updated by: Blaise Montandon 2013nov21 Started this script on Linux, ## using Ubuntu 9.10 (2009 October, ## 'Karmic Koala'). ## Updated by: Blaise Montandon 2013dec04 Prep the 'awk' program for the ## 'all' input parameter. ## Updated by: Blaise Montandon 2013dec21 Added a '%idle' column, for each ## CPU in the report. ##+######################################################################### ## FOR TESTING: (show statements as they execute) # set -x ## Simply exit if there is no argument passed to this script. if test "$1" = "" then exit fi ##+############################################################# ## THE FOLLOWING IS EXECUTED IF $1 CONTAINS 'cpuslist'. ## ## The output goes to standard-out. ## ##+############################################################ if test "$1" = "cpuslist" then cat /proc/stat | grep '^cpu' | awk '{print $1}' exit fi ## END OF if test "$1" = "cpuslist" SECTION. if test "$1" = "all" then ########################################################################## ## SET REPORT HEADING. ########################################################################## HOST_ID="`hostname`" echo "\ ......................... `date '+%Y %b %d %a %T%p %Z'` ........................ CPU USAGE for CPU(s) ON HOST *** $HOST_ID *** If there is more than one CPU, the data for individual CPU's is labelled 'cpu0', 'cpu1', ... The totals (summed data) for ALL the CPU's is labelled 'cpu'. CPU USAGE is presented in clock 'ticks' --- sometimes called 'jiffies' which are on the order of 1/100th of a second. 5 6 0 1 2 3 4 Activity Total *PERCENT* *PERCENT* CPU User Nice System Idle Total ticks CPU-USAGE CPU-IDLE ID ticks ticks ticks ticks (1+2+3) (1+2+3+4) (100 * 5 / 6) (100 * 4 / 6) ------ ------ ----- ------- ------- --------- --------- ------------- ------------- " ########################################################################## ## Get the CPU data lines from file '/proc/stat' --- all lines that start ## with 'cpu'. Get the data twice, separated by about 0.5 or 1 second. ########################################################################## CPUdata1="`cat /proc/stat | grep '^cpu'`" SECONDS=3 # SECONDS=1 sleep $SECONDS CPUdata2="`cat /proc/stat | grep '^cpu'`" ########################################################################## ## Concatenate the two sets of data together and pipe the data into ## 'sort', and then into an 'awk' program to get the difference ## data --- the difference of user-nice-system-idle-etc 'jiffies' ## for each CPU --- and format the report lines. ########################################################################## ## SAMPLE CALCULATION: ## ## If the information for a cpu is displayed like this: ## ## cpu 607557 143290 385125 136873807 1279793 18764 14856 0 0 ## ## To get the average system load over any given amount of time, ## from the FIRST data sample, ## we read only the first four values into variables (for example, ## let us call them u1, n1, s1, and i1). ## ## Then we read the values from the SECOND data sample into ## new variables, u2, n2, s2, and i2. ## ## The total usage time is equal to ## ## (u2-u1) + (n2 - n1) + (s2 - s1). ## ## The total time overall is the usage time + (i2-i1). ## ## Take (100*usage)/total to get the percent-CPU-usage. ## ## If there are multiple CPU's, also get the percent-CPU-usage for ## 'cpu0', 'cpu1', ... ## ## ----------------------------- ## Similar /proc/stat info is at: ## http://juliano.info/en/Blog:Memory_Leak/Understanding_the_Linux_load_average ## http://www.linuxhowtos.org/System/procstat.htm ## http://stackoverflow.com/questions/5514119/accurately-calculating-cpu-utilization-in-linux-using-proc-stat ## http://askubuntu.com/questions/120953/exact-field-meaning-of-proc-stat ## http://man7.org/linux/man-pages/man5/proc.5.html ## http://stackoverflow.com/questions/1420426/calculating-cpu-usage-of-a-process-in-linux ############################################################################ CPUdata="$CPUdata1 $CPUdata2" echo "$CPUdata" | sort | awk '{ cpuid1=$1 u1=$2 n1=$3 s1=$4 i1=$5 getline cpuid2=$1 u2=$2 n2=$3 s2=$4 i2=$5 du=u2 - u1 dn=n2 - n1 ds=s2 - s1 di=i2 - i1 act=du + dn + ds tot=act + di pcentu=100 * act / tot pcenti=100 * di / tot printf("%-6s %7d %6d %7d %8d %10d %10d %11.2f %11.2f\n",cpuid2,du,dn,ds,di,act,tot,pcentu,pcenti) }' ## END of awk program ########################################################################## ## ADD REPORT 'TRAILER'. ########################################################################## SCRIPT_BASENAME=`basename $0` SCRIPT_DIRNAME=`dirname $0` echo " ......................... `date '+%Y %b %d %a %T%p %Z'` ........................ The output above is from script $SCRIPT_BASENAME in directory $SCRIPT_DIRNAME This script ran the command 'cat /proc/stat' twice ($SECONDS SECONDS APART) and used the 'user', 'nice', 'system', and 'idle' data for each CPU on host $HOST_ID to formulate the report. The script concatenates the lines of CPU data from the two data-samples, sorts the lines by CPU using the 'sort' command, and pipes the sorted data (2 lines for each CPU) into an 'awk' program. For the 2 lines of data for each CPU, the 'awk' program gets the difference of the 'user', 'nice', 'system', and 'idle' data and calculates the PERCENT-CPU-USAGE as indicated in the column headings above. ......................... `date '+%Y %b %d %a %T%p %Z'` ........................ " exit fi ## END OF if test "$1" = "all" SECTION. ##+############################################################# ## THE FOLLOWING IS EXECUTED IF $1 CONTAINS ANYTHING other than, ## 'all' or 'cpuslist'. It is assumed that $1 contains a CPU ## name such as 'cpu0' or 'cpu1' or 'cpu'. ## ## The output goes to standard-out. ## ## EXAMPLE OUTPUT FROM THE 'PIPE' BELOW, for 'cpu0': ## ## cpu0 18990 914 4855 758670 3358 40 54 0 0 ## ##+############################################################# cat /proc/stat | grep "$1" | head -1 ====== <> ------ '''A FEW MORE "THEMES"''' A black disk and gray-rim theme follows. (I have images for quite a few variations on this theme.) The 'pointer' in this case is an 8-ball transparent GIF. [cpu_meter_blackDiskGrayRim-WhiteBkgd_screenshot_385x589.jpg] Here is an example with the arc-drawing turned off. The 'pointer' in this case is a blue-and-white earth transparent GIF. And corner features are turned off. [cpu_meter_puzzleEarth-WhiteBkgd_img_screenshot_385x587.jpg] Here is an example suggesting that one could use colorful background images to suggest 'cool' and 'hot' sides of the arc. [cpu_meter_rainbowDial_img_screenshot_385x589.jpg] Here is an example demonstrating a seasonal theme. Furthermore, the pumpkin is not centered in this background image. I used 'centerX' and 'centerY' factors to locate the center of the arc --- on all 3 versions of this image (small, medium, and large). The 'pointer' is a light-bulb transparent GIF, and the drawing of the arc is turned off. [cpu_meter_grinningPumpkin-BlackBkgd_img_screenshot_383x591.jpg] ------ '''INSTALLING THE FILES:''' The Tk script and the shell script can be put in a subdirectory of the user's home directory --- for example, $HOME/apps/tkCPUusage. Make 3 subdirectories of that directory --- 'corner_features', 'meter_backgrounds', and 'pointers' --- in which to put the GIF or PNG files for those 3 types of images. I used more than 30 '.gif' files for making these test themes --- in the 'meter_backgrounds', 'pointers', and 'corner_features' subdirectories of the scripts directory. I am probably stretching my wiki-images-quota by uploading the screenshot images to the wiki, so I do not put anywhere near 30-plus images, for this utility, on the wiki. But here are a few to at least be able to run the Tk script above. These could also be used when similar 'create image' scripts are made for the 'memory-and-swap' and the 'network activity' monitoring utilities. Meter backgrounds: (one S-M-L set) (non-transparent GIF's) [meter_background_glossyBlueDisk_blackHilitedEdge_blackBkgd_SMALL.gif] [meter_background_glossyBlueDisk_blackHilitedEdge_blackBkgd_MEDIUM.gif] [meter_background_glossyBlueDisk_blackHilitedEdge_blackBkgd_LARGE.gif] Pointers: (transparent GIF's) [bullet_grayGlossy_15x14_transp.gif] [bullet_yellowOrangeGlassy_22x22_transp.gif] [bullet_redGlassy_25x25_transp.gif] [bullet_8ball_32x32_transp.gif] [bullet_earth_whiteOnBlue_32x32_transp.gif] [bullet_lightbulb_32x32_transp.gif] In uploading these, I saw that some need some definite improvement. I will try to replace some of these with better quality ones in the future. Corner features: (transparent GIF's) (rather crude ones - will make better quality ones) [rivetPop_2_gray-white_25x25_transp.gif] [screwHeadPhillipsB_gray_23x23_transp.gif] [screwHeadSlotted_30deg_gray_27x27_transp.gif] You can right-click in your web browser to download these. In the future, I plan to put this hi-def CPU-usage utility (along with about 50 to 100 other mini-utilities --- 'apps') in a 'tkGooies' menu system --- using Tk 'toolchests' to provide the menus. When I post that 'tkGooies' system at my www.freedomenv.com software site, that system will contain the GIF files that were used for the 'themes' shown above --- as well as GIF files for many more 'themes'. ------ SOME TIPS ON MAKING NICE S-M-L BACKGROUND IMAGES: In taking a good quality image and scaling it up or down to make the three Small-Medium-Large images, I found that starting with a GIF file was not a good idea. If the file is already using its limit of about 256 colors, when you scale it up or down, it does not have anymore colors it can turn to in order to make more color shades during the process of averaging the colors of adjacent pixels. So if you find a nice GIF file that you want to use, convert it to a PNG file first. I did something like this with the 'mtpaint' image editor. It has an 'Image > Convert to RGB' toolbar menu option that can be used to convert a loaded GIF file to allow for up to 16 million colors, instead of just 256. Then I used the 'Image > Scale Canvas...' option to scale the image up or down. (I was quite surprised to see that even when scaling the RGB image up a factor of about 2 that the quality of the image was usually preserved.) After scaling the image down (or up), I could then save the file as a GIF. OR ... I could save as PNG, and then use the ImageMagick 'convert' command to convert to the PNG as a GIF. The conversion usually kept most of the quality that was in the image after the scaling operation. With this technique, you can end up with both PNG and GIF files --- and use GIF's with a Tcl-Tk 8.5 'wish' interpreter, and PNG's with a Tcl-Tk 8.6 'wish' interpreter. ------ '''MORE METER UTILITIES:''' I could return to the tachometer-style meters that I made for - memory-and-swap monitoring - network activity monitoring - file-system space-used monitoring and make 'high-definition' versions of those system monitoring apps. HOWEVER, I have so many other Tk projects on my 'to-do' list (projects that I believe would be more instructive to implement) that I will leave making high-definition versions of those apps to others --- or to 2015 (or thereabout). ------ '''ENHANCEMENTS TO CONSIDER''' '''A 'hi-def needle making' technique:''' I may try, someday, an image technique to make a high-quality needle that does not suffer from 'the jaggies'. I do not have a good idea on how to approach that at this time (other than the 2 approaches mentioned near the top of this page). If I stumble across a promising looking technique, I may try to implement it. Or, if I ever feel I have the time to experiment with this, I may try the rotate-code-enhancement that I mentioned above --- to see if it works fast enough to be used when changing the needle direction on the order of every second or every tenth of a second. --- '''Flexible placement of tick marks:''' In the Tk script code, I hard-coded the angular start and end points of the arc (of tick marks and tick labels). The arc-drawing essentially starts at -70 degrees and goes counter-clockwise 320 degrees, ending at +250 degrees. In other words, I used the same hard-coded values that Marco Maggi used. In the future, I may add a couple of more parameters to the themes --- to allow for setting the start and end angles for each theme. That would have come in handy for one of the examples above --- the one with the rainbow-colored arc on the meter background image. --- '''Hiding theme parameter widgets''' We could 'hide' the theme parameter widgets --- that is, the S-M-L radiobuttons, the theme-number scale widget, and several check buttons --- by putting a 'SetTheme' button on the GUI. Clicking on that button would cause a separate window to pop up, containing those widgets. If we did that, we would have more room in that window to replace the theme-number scale widget by a listbox in which 'theme names' could be shown. By adding a new array variable, say 'aRthemeName($THEMEindex)', we could show a theme-name. When the user selects a theme-name, they would effectively be choosing the theme-index number. --- '''S-M-L pointers and S-M-L corner-features''' For this Tk script, I avoided a proliferation of 'pointer' images and 'corner-feature' images by using the same sized 'pointer' and 'corner-feature' images no matter whether a Small, Medium, or Large background image was being shown. However, on setting the pointer and corner-feature filenames, we could mimic the code that set the meter background image by adding '_SMALL' or '_MEDIUM' or '_LARGE' to the array-filename-prefix of the selected background image file. That is, we could make '_SMALL', '_MEDIUM', and '_LARGE' files in the 'pointers' and 'corner-features' subdirectories --- and change the code that sets those pointer and corner-feature filenames to add '_SMALL' or '_MEDIUM' or '_LARGE' to their array-filename-prefixes --- depending on the current setting of the S-M-L radiobuttons. I did not do that because I was trying to avoid making 3 times more pointer and corner-feature files. But, by doing that, for LARGE background images, some of the pointers and corner-features may look rather tiny on the large background. --- '''A 'moving skyscraper silhouette' technique:''' As I mentioned at the bottom of the [A Tachometer-style Meter --- for CPU Usage] page, another type of display that I may try on the Tk canvas is a 'moving mountain-range silhouette' --- which one sees in some 'system monitor' apps, such as 'gnome-system-monitor' (on Linux) -- under the 'Resources' tab, titled 'CPU history'. This kind of display has the advantage of preserving the recent history of the CPU percentage of one or more CPU's --- about 6 minutes of history across a 180-pixel-wide canvas, if we are sampling about once every 2 seconds. This type of animated plot could be created with Tk by using a 'create line' technique on the Tk canvas, to plot vertical lines (to create a 'silhouette-type' plot) --- or simply single pixels (to create a 'thin-line-type' plot). This 'history display' technique may be especially suitable for an alternative version of 3 of the 4 apps that I have implemented: the 'memory-and-swap', the 'network activity', and the 'CPU activity' apps. Those are the 3 types of activity shown in the 'Resources' panel of the 'gnome-system-monitor' utility. ------ '''Responsiveness of the GUI to theme changes''' I should point out that I was pleased by how fast the Tk GUI responded when I changed themes via the 'theme-scale' widget. I put a 'bind' command on the 'scale' widget so that when the user releases MB1 (mouse button 1) after sliding the slider button to a new integer value --- the 'Refresh' proc is run to re-build the meter background and start updating the 'pointer' again, according to the current setting of the 'sample-interval-scale'. Within a fraction of a second after releasing MB1 (after making a change to the theme integer), the entire meter is redrawn --- including - meter background image - tic marks - tic mark arc - tic labels - the 4 corner features, - various text labels, and - the 'pointer'. Similarly, a 'bind' command was put on the SML radiobuttons --- and within a fraction of a second after releasing MB1 on one of the radiobuttons, the meter is redrawn using the new size of background image. (Thank you, wonderful 'wish' interpreter --- and Gnu/Linux scripting.) --- Oh, the possibilities we have with Tk. The possibilities are endless ... I say to the 'Tcl is dead' nay-sayers ... those 'nattering nabobs of negativism' [http://en.wikipedia.org/wiki/Spiro_Agnew]. ------ '''IN CONCLUSION''' As I have said on several other code-donation pages on this wiki ... There's a lot to like about a utility that is 'free freedom' --- that is, no-cost and open-source so that you can modify/enhance/fix it without having to wait for someone else to do it for you (which may be never). A BIG THANK YOU to Ousterhout for starting Tcl-Tk, and a BIG THANK YOU to the Tcl-Tk developers and maintainers who have kept the simply MAH-velous 'wish' interpreter going.