A Pair of Tachometer-style Meters --- for Network Activity

uniquename - 2013sep06

A couple of months ago, I put it on my 'to do' list to implement some practical applications for the nice 'tachometer style' meter for which Marco Maggi provided a demo script [1 ] back in 2003.

I have implemented one application of this meter --- to show memory and swap used on my computer at any time --- by using a Tk script as a 'wrapper' for the 'free' command, which is available on my operating system (Ubuntu 9.10, 2009 October, 'Karmic Koala').

The code for that pair-of-meters-application is on the wiki page titled A Pair of Tachometer-style Meters --- for Memory and Swap. (I plan to revisit that code to apply some techniques I learned from the code on this page.)

I had several other applications of the 'tachometer style' meter on my 'to-do' list --- including a network activity monitoring application. That is the subject of this page.

Since the 'free' command (or a command returning similar data) is probably available on most Linux, Unix, and BSD systems --- and since the Apple Mac operating system is based on a BSD system, the 'memory-and-swap' utility is probably usable (with very little change) on Linux-Unix-BSD-Mac systems.

For this 'network activity' application, I decided to make a Tk wrapper script for the 'netstat' command, which returns data like

  $ netstat -i
  Kernel Interface table
  Iface   MTU Met   RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
  eth0       1500 0     23449      0      0 0         19670      0      0      0 BMRU
  lo        16436 0         4      0      0 0             4      0      0      0 LRU
  wlan0      1500 0         0      0      0 0             0      0      0      0 BMU
  wmaster0      0 0         0      0      0 0             0      0      0      0 RU

In particular, I want the Tk GUI to show two meters --- to show the rate of increase of the 'RX-OK' and the 'TX-OK' counts.

On Linux systems, there are many commands that show this network activity data. For example, 'ifconfig' could be used as an alternative to 'netstat':

  $ ifconfig -a -s
  Iface   MTU Met   RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
  eth0       1500 0     23449      0      0 0         19670      0      0      0 BMRU
  lo        16436 0         4      0      0 0             4      0      0      0 LRU
  wlan0      1500 0         0      0      0 0             0      0      0      0 BMU
  wmaster0      0 0         0      0      0 0             0      0      0      0 RU

And there are newer commands, like 'ip', that are said to be a replacement, eventually, for older commands like 'netstat'.

For now, I decided to go with 'netstat'.

Like with the 'free' command --- since the 'netstat' command (or a command returning similar data) is probably available on most Linux, Unix, and BSD systems --- and since the Apple Mac operating system is based on a BSD system, this 'network activity' utility is probably usable (with very little change) on Linux-Unix-BSD-Mac systems.

Besides showing two 'tachometer style' meters, side by side, on two Tk 'canvas' widgets --- I wanted to provide a 'scale' widget on the GUI, by which the user could specify, at any time, a new 'sampling rate' (actually, a 'wait-time' = 'wave-length', rather than a 'frequency').

I was able to implement that ability by use of the Tcl 'after' command, in the form

   after $WAITmillisecs update_needles

This command is issued once to initialize the GUI --- and it is issued within the 'update_needles' proc itself, to continue sampling a network interface with the 'netstat' command.

I have also supplied a 'Refresh' button on the GUI, so that the user can request at ANY TIME, a new set of interface RX and TX values with which to update the GUI.

In putting together the code for this GUI, I drew heavily on the 'shadow-circle' technique of Marco Maggi to make nice looking meters. And I included a 'red-line' (danger) area on the meters, like he did.

One rather unique thing about this implementation of the meters (something not done by Maggi in his demo) is that the window and the canvases and the meters are resizable. In other words, 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 work off of queries on the current size of frame and canvas widgets.

So the user is able to resize the window and click on the 'Refresh' button to get a bigger version of the meters and the needle position.

After several iterations (including finding that I really needed to add an 'Ifaces' button on the GUI, to return to the user the network interfaces, on the user's computer, that are known to the 'netstat' command), I ended up with the GUI seen in the following image.

metersNetActivity_screenshot_463x339.jpg

When the GUI first comes up, the two meters (their canvases) are sized at about 200x200 pixels --- and the data shown is based on an initial couple of executions of the 'netstat' command.

You can see the page A Pair of Tachometer-style Meters --- for Memory and Swap to see an example image that demonstrates that the user is able to resize the window and click on the 'Refresh' button to get a bigger version of the meters and the needle position.

I had hoped to come up with a technique to avoid the 'aliasing' effect on the needles --- but I did not do it for the 'memory-and-swap' script --- and I have still not done that for this 'RX-and-TX-network-activity' script.

However, as 'retina display' monitors come into use more and more (with resolutions above about 2000x1500 pixels), even without changing the needle-drawing code in this script, you may find that the 'jaggies' are hard to see. (I do not have such a monitor yet, so I cannot say for sure.)


CAPTURING THE GENERATED IMAGE:

When you get an image that you want to save --- say, to send an 'abuse report' to a web master at a site that has web pages that are blasting your interface card with unjustified traffic --- 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 presentation in an email or on a web page.


A LITTLE NETWORK ACITIVITY EXPERIMENT:

Most of the time, my network interface card is experiencing no change to the cumulative counts of RX (received) and TX (transmitted) packets. So the needles are usually resting at zero.

To perk up the meters, I opened my Seamonkey web browser and went to the YouTube site and started up a movie. By doing that, I was able to capture the image above, that shows the needles off of their zero positions.

It was interesting to note, however, that the movie packets seem to be downloaded in bursts (buffers) --- because the needles would return to zero for periods of many seconds while a movie was running.


The code

Below, I provide the Tk script code for this 'network activity in tachometers' 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 new thing that I have started doing recently 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.


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 two canvas areas (without scroll bars) expand/contract whenever the window is re-sized, and the 'Refresh' button is clicked.

The meters expand/contract when the window is re-sized --- but probably not always in a way you would expect. Occasionally, you may need to tug the borders of the window to center the meters in a way that suits you.

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.

And you can look into the code that is drawing the meters to see if you can devise meter-resizing behavior that pleases you more.

___

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.


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 main procs are

   'make_tachometers' - to draw 2 meters within their 2 Tk (square) canvases.

                       (We allow the 2 canvases to resize according to
                        a resizing of the window. This proc will set the
                        SQUARE size of the 2 canvases according to the current
                        size of the frame containing the 2 canvases.)

   'make_one_tachometer' - called by 'make_tachometers', to make each meter.

   'draw_rivet'      - called by 'make_one_tachometer', 4 times, to put
                       rivets in 4 corners around a meter.

   'draw_circle_shadow'  - called by 'make_one_tachometer' to put a shadowed
                           edge around the circle that makes the meter.
                           Also called to help make a 'pin' in the center of
                           the meter to hold the needle. Also called to make
                           the 4 rivets.

   'update_needles'      - to update the needles on the 2 meters.

   'update_one_needle'   - called by 'update_needles', to draw each needle.

   'Refresh'             - called by 'Refresh' button. Runs 'make_tachometers'
                           and 'update_needles'.  

   'popup_msgVarWithScroll' - called by the 'Help' button,
                             to show text in variable $HELPtext.

Thanks to Marco Maggi whose 'shadow-circle' drawing technique and code made this script much easier to write.


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 'plump', shirtless guys talking to you while seated in front of their computers.


 Code for Tk script 'meters_net_stats.tk' :
#!/usr/bin/wish -f
##+########################################################################
##
## SCRIPT: meters_net_stats.tk
##
## PURPOSE: This script is meant to show a GUI that holds a pair of
##          tachometer-style meters. The needles on the meters can be updated
##          periodically to show the rate of network activity --- rate of
##          change of RX (packets received) and TX (packets transmitted) data.
##
##          This script was developed on Linux and uses the 'netstat'
##          command (or an alternative command) to periodically get the
##          RX and TX data to determine where to relocate the position of
##          the needles.
##
##+################
## GUI DESCRIPTION:
##
##         This script provides a Tk GUI with the following widgets.
##
##         1) There is an 'fRbuttons' frame to hold BUTTONS such as
##            'Exit' and 'Help' buttons --- as well as a 'Refresh'
##            button.
##
##            There is an ENTRY widget to hold a network interface-ID
##            such as eth0 or eth1 or wlan0.
##
##            There is a SCALE widget that allows the user to set a
##            'wait-seconds' parameter for auto-refresh of the meters ---
##            in seconds --- down to tenths of seconds, and up to multiple
##            minutes.
##
##         2) There is an 'fRcanvases' frame to contain 2 CANVAS widgets that
##            hold the two meter images, in 2 SQUARE canvases, side by side.
##            Also the 'fRcanvases' frame holds some LABEL widgets, to show
##            RX-RATE and TX-RATE, as text items.
##
##+################################
## METHOD USED to update the meters:
##
##    A Tcl 'exec' command calls on a separate shell script that uses
##    the 'netstat' (or other) command to get the RX,TX data and
##    extract-and-format the data for return to this Tk script.
##
##+#######################
## CAPTURING THE GUI IMAGE:
##
##   A screen/window capture utility (like 'gnome-screenshot'
##   on Linux) can be used to capture the GUI image in a PNG
##   or GIF file, say.
##
##   If necessary, an image editor (like 'mtpaint' on Linux)
##   can be used to crop the window capture image.  The image
##   could also be down-sized --- say to make a smaller image
##   suitable for use in a web page or an email.
##
##+#######################################################################
## 'CANONICAL' STRUCTURE OF THIS CODE:
##
##  0) Set general window parms (win-name, win-position, win-color-scheme,
##     fonts, widget-geom-parms, win-size-control, text-array-for-labels-etc).
##
##  1a) Define ALL frames (and sub-frames, if any).
##  1b) Pack the frames.
##
##  2) Define & pack all widgets in the frames, frame by frame.
##     After all the widgets for a frame are defined, pack them in the frame.
##
##  3) Define keyboard or mouse/touchpad/touch-sensitive-screen 'event'
##     BINDINGS, if needed.
##
##  4) Define PROCS, if needed.
##
##  5) Additional GUI INITIALIZATION (typically with one or more of
##     the procs), if needed.
##
##+#################################
## Some detail of the code structure of this particular script:
##
##  1a) Define ALL frames:
## 
##   Top-level :
##      '.fRbuttons'  - to contain 'Exit', 'Help', 'Refresh' buttons
##                      as well as an entry widget for interface-ID
##                      and a label-and-scale pair.
##
##      '.fRcanvases' - to contain 2 canvas widgets, which will display
##                      the two meters, side-by-side.
##
##   Sub-frames:
##       '.fRcanvases.fRcanvas1' - for 1 label widget & 1 canvas widget
##       '.fRcanvases.fRcanvas2' - for 1 label widget & 1 canvas widget
##
##  1b) Pack ALL frames.
##
##  2) Define & pack all widgets in the frames -- basically going through
##     frames & their interiors in  left-to-right, or top-to-bottom order.
##
##  3) Define BINDINGS:  none
##
##  4) Define PROCS:
##
##    'make_tachometers' - to draw 2 meters within their 2 Tk (square) canvases.
##
##                        (We may allow the 2 canvases to resize according to
##                         a resizing of the window. This proc will set the
##                         SQUARE size of the 2 canvases according to the current
##                         size of the frame containing the 2 canvases.)
##
##    'make_one_tachometer' - called by 'make_tachometers', to make each meter.
##
##    'draw_rivet'      - called by 'make_one_tachometer', 4 times, to put
##                        rivets in 4 corners around a meter.
##
##    'draw_circle_shadow'  - called by 'make_one_tachometer' to put a shadowed
##                            edge around the circle that makes the meter.
##                            Also called to help make a 'pin' in the center of
##                            the meter to hold the needle. Also called to make
##                            the 4 rivets.
##
##    'update_needles'      - to update the needles on the 2 meters.
##
##    'update_one_needle'   - called by 'update_needles', to draw each needle.
##
##    'Refresh'             - called by 'Refresh' button. Runs 'make_tachometers'
##                            and 'update_needles'.                     
##
##   'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var.
##         
##
##  5) Additional GUI Initialization:
##        - call 'make_tachometers' to put the 2 meters on the canvas
##        - call 'update_needles' --- to initialize the needle locations.
##
##+#######################################################################
## 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 2013sep05 Started the basic code of the
##                                        script based on my previous
##                                        meters script
##                                          meters_mem_and_swap.tk
##                                        which was based on a
##                                        demo script by Marco Maggi
##                                        at https://wiki.tcl-lang.org/9107
## Changed by: Blaise Montandon 2013sep06 Added 'Ifaces' button in place
##                                        of an 'Ifaces:' label. 
##                                        Initialized the IFACEname var with
##                                        a call to 'netstat' instead of 
##                                        using a hardcoded value.
##+########################################################################

##+######################################################
## Set WINDOW TITLE and POSITION.
##+######################################################

wm title    . "Network Activity - RX and TX Rates"
wm iconname . "RX-TX"

wm geometry . +15+30


##+######################################################
## Set the COLOR SCHEME for the window and its widgets ---
## such as listbox and entry field background color.
##+######################################################

tk_setPalette "#e0e0e0"

# set listboxBKGD "#ffffff"
  set entryBKGD "#ffffff"
  set scaleBKGD "#f0f0f0"


##+########################################################
## 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 Help-text in a TEXT widget, and for
## the text in ENTRY fields, if any.
##+########################################################

font create fontTEMP_varwidth \
   -family {comic sans ms} \
   -size -14 \
   -weight bold \
   -slant roman

font create fontTEMP_SMALL_varwidth \
   -family {comic sans ms} \
   -size -12 \
   -weight bold \
   -slant roman

## Some other possible (similar) variable width fonts:
##  Arial
##  Bitstream Vera Sans
##  DejaVu Sans
##  Droid Sans
##  FreeSans
##  Liberation Sans
##  Nimbus Sans L
##  Trebuchet MS
##  Verdana


font create fontTEMP_fixedwidth  \
   -family {liberation mono} \
   -size -14 \
   -weight bold \
   -slant roman

font create fontTEMP_SMALL_fixedwidth  \
   -family {liberation mono} \
   -size -12 \
   -weight bold \
   -slant roman

## Some other possible fixed width fonts (esp. on Linux):
##  Andale Mono
##  Bitstream Vera Sans Mono
##  Courier 10 Pitch
##  DejaVu Sans Mono
##  Droid Sans Mono
##  FreeMono
##  Nimbus Mono L
##  TlwgMono


##+###########################################################
## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS.
## (e.g. width and height of canvas, and padding for Buttons)
##+###########################################################

## CANVAS widget geom settings:

set initCanWidthPx  200
set initCanHeightPx 200

# set BDwidthPx_canvas 2
  set BDwidthPx_canvas 0


## LABEL widget geom settings:

set PADXpx_label 0
set PADYpx_label 0
set BDwidthPx_label 2


## BUTTON widget geom settings:

set PADXpx_button 0
set PADYpx_button 0
set BDwidthPx_button 2


## ENTRY widget geom settings:

set BDwidthPx_entry 2


## SCALE widget geom parameters:

set BDwidthPx_scale 2
set scaleThicknessPx 10


##+######################################################################
## Set a MIN-SIZE of the window (roughly).
##
## For WIDTH, allow for the min-width of the '.fRbuttons' and '.fRcanvas'
## frames --- at least, the widgets in the 'fRbuttons' frame.
##
## For HEIGHT, allow for the stacked frames:
##            2 chars  high for the '.fRbuttons' frame,
##  at least 50 pixels high for the '.fRcanvas'  frame.
##+#####################################################################

## FOR WIDTH:

set minWidthPx [font measure fontTEMP_varwidth \
   " Exit  Help  Refresh Ifaces  eth0  Sample rate "]

## We accomodate a label-and-scale pair ---
## add pixels for length of the scale widget, at least 100.
##
## We add some pixels to account for right-left-size of
## window-manager decoration (~8 pixels) and some pixels for
## frame/widget borders (~3 widgets x 4 pixels/widget = 12 pixels).

set minWinWidthPx [expr {120 + $minWidthPx}]


## For HEIGHT --- for
##    2 char   high for 'fRbuttons'
##   50 pixels high for 'fRcanvas'

set charHeightPx [font metrics fontTEMP_varwidth -linespace]

set minWinHeightPx [expr {2 * $charHeightPx}]

## Add about 50 pixels for height of the canvas
## AND add about 20 pixels for top-bottom window decoration --
## and some pixels for top-and-bottom of frame/widget borders
## (~4 widgets x 4 pixels/widget = 16 pixels).

set minWinHeightPx [expr {86 + $minWinHeightPx}]


## FOR TESTING:
#   puts "minWinWidthPx = $minWinWidthPx"
#   puts "minWinHeightPx = $minWinHeightPx"

wm minsize . $minWinWidthPx $minWinHeightPx


## We may allow the window to be resizable.  We pack the canvases
## (and the frames that contain them) with '-fill both -expand 1'
## so that the canvases can be enlarged by enlarging the window.

## If you want to make the window un-resizable, 
## you can use the following statement.
#   wm resizable . 0 0


##+##############################################################
## Set a TEXT-ARRAY to hold text for buttons & labels on the GUI.
##     NOTE: This can aid INTERNATIONALIZATION. This array can
##           be set according to a nation/region parameter.
##+##############################################################

## if { "$VARlocale" == "en"}

## For '.fRbuttons' frame:

set aRtext(buttonEXIT)  "Exit"
set aRtext(buttonHELP)  "Help"

set aRtext(buttonREFRESH)  "Refresh"

set aRtext(buttonIFACES)  "Ifaces"

set aRtext(labelSCALE)  "SampleRate
(seconds) :"

## END OF  if { "$VARlocale" == "en"}


##+################################################################
## DEFINE *ALL* THE FRAMES:
##
##   Top-level : '.fRbuttons' , '.fRcanvases'
##
##   Sub-frames: '.fRcanvases.fRcanvas1'  '.fRcanvases.fRcanvas2' 
##+################################################################

## 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 .fRcanvases  -relief $RELIEF_frame  -bd $BDwidth_frame

frame .fRcanvases.fRcanvas1  -relief raised  -bd 2
frame .fRcanvases.fRcanvas2  -relief raised  -bd 2


##+##############################
## PACK the FRAMES. 
##+##############################

pack .fRbuttons \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

pack .fRcanvases \
   -side top \
   -anchor nw \
   -fill both \
   -expand 1

pack .fRcanvases.fRcanvas1 \
   -side left \
   -anchor nw \
   -fill both \
   -expand 1

pack .fRcanvases.fRcanvas2 \
   -side right \
   -anchor ne \
   -fill both \
   -expand 1



##+##########################################################
## The FRAMES ARE PACKED. START PACKING WIDGETS IN THE FRAMES.
##+##########################################################

##+##########################################################
## In FRAME '.fRbuttons' -
## DEFINE-and-PACK 'BUTTON' WIDGETS
## --- Exit, Help, ... --- and a LABEL-AND-SCALE widget pair
## (for changing the 'refresh rate' for the meter needles.
##+##########################################################

button .fRbuttons.buttEXIT \
   -text "$aRtext(buttonEXIT)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {set loop0or1 0 ; 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.buttREFRESH \
   -text "$aRtext(buttonREFRESH)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {Refresh}


## Here is the BUTTON for showing installed network interfaces.

button .fRbuttons.buttIFACES \
   -text "$aRtext(buttonIFACES)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {sho_ifaces}

## Here is the ENTRY field for the network interface-ID.

## Set this widget var in the GUI initialization section
## at the bottom of this script.
# set IFACEname "eth0"

entry .fRbuttons.entryIFACE \
   -textvariable IFACEname \
   -bg $entryBKGD \
   -font fontTEMP_fixedwidth \
   -width 6 \
   -relief sunken \
   -bd $BDwidthPx_entry


## Here is the LABEL-AND-SCALE pair for the wait-seconds (sample rate).

label .fRbuttons.labelSCALE \
   -text "$aRtext(labelSCALE)" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label


## Set this widget var in the GUI initialization section
## at the bottom of this script.
# set WAITseconds 5.0

scale .fRbuttons.scaleSECONDS \
   -from 0.1 -to 20.0 \
   -resolution 0.1 \
   -font fontTEMP_SMALL_varwidth \
   -variable WAITseconds \
   -showvalue true \
   -orient horizontal \
   -bd $BDwidthPx_scale \
   -length 100 \
   -width $scaleThicknessPx

## Here is a label to show the current sample count.

label .fRbuttons.labelCOUNT \
   -textvariable VARsampcnt \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd 0

## Pack the widgets in frame '.fRbutton'.

pack .fRbuttons.buttEXIT \
     .fRbuttons.buttHELP \
     .fRbuttons.buttREFRESH \
     .fRbuttons.buttIFACES \
     .fRbuttons.entryIFACE \
     .fRbuttons.labelSCALE \
     .fRbuttons.scaleSECONDS \
     .fRbuttons.labelCOUNT \
   -side left \
   -anchor w \
   -fill none \
   -expand 0




##+########################################################
## In FRAME '.fRcanvases.fRcanvas1' -
## 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 .fRcanvases.fRcanvas1.labelINFO1 \
   -text "" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

label .fRcanvases.fRcanvas1.labelINFO2 \
   -text "" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

canvas .fRcanvases.fRcanvas1.can \
   -width  $initCanWidthPx \
   -height $initCanHeightPx \
   -relief flat \
   -highlightthickness 0 \
   -borderwidth 0

## Pack the widgets in frame '.fRcanvases.fRcanvas1'.

pack .fRcanvases.fRcanvas1.labelINFO1 \
     .fRcanvases.fRcanvas1.labelINFO2 \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

pack .fRcanvases.fRcanvas1.can \
   -side top \
   -anchor nw \
   -fill none \
   -expand 0


##+########################################################
## In FRAME '.fRcanvases.fRcanvas2' -
## 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 .fRcanvases.fRcanvas2.labelINFO1 \
   -text "" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

label .fRcanvases.fRcanvas2.labelINFO2 \
   -text "" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

canvas .fRcanvases.fRcanvas2.can \
   -width  $initCanWidthPx \
   -height $initCanHeightPx \
   -relief flat \
   -highlightthickness 0 \
   -borderwidth 0

## Pack the widgets in frame '.fRcanvases.fRcanvas2'.

pack .fRcanvases.fRcanvas2.labelINFO1 \
     .fRcanvases.fRcanvas2.labelINFO2 \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

pack .fRcanvases.fRcanvas2.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:  none
##+##################################################################


##+##################################################################
##+##################################################################
## DEFINE PROCS SECTION:
##
##    'make_tachometers' - to draw 2 meters within 2 Tk canvases
##
##                        (We may allow the Tk canvases to resize according to
##                         a resizing of the window. This proc will draw the
##                         2 meters in proportion to the size of their canvases.)
##
##    'make_one_tachometer' - to draw one tachometer. Called by 'make_tachometers'
##                            to make the 2 meters.
##
##    'draw_rivet'          - called by 'make_tachometers' to put rivets in 4
##                            corners around each meter.
##
##    'draw_circle_shadow'  - called by 'make_tachometers' to put a shadowed
##                            edge around the circle that makes each meter.
##                            Also called by 'make_tachometers' to put
##                            a shadowed edge on the 'pin' that holds a needle.
##                            Also called by 'draw_rivet' to put a shadowed
##                            edge on each rivet.
##
##    'update_needles'     - to update the 2 needles on the 2 meters. Called in a
##                           recursive loop, initiated at the bottom of this script.
##                           And called in a 'Refresh' proc.
##
##   'update_one_needle'   - to draw one needle in a specified canvas. Called by
##                           'update_needles' to update the 2 needles on the 2 meters.
##
##   'Refresh'             - called by the 'Refresh' button. Runs the procs
##                           'make_tachometers' and 'update_needles' --- in particular,
##                           for the user to force the meters to be resized if
##                           the user resizes the window --- and whenever the user
##                           wants a new 'reading'.
##
## 'popup_msgVarWithScroll' - to show the HELPtext var. Called by the 'Help' button.
##
##+#################################################################


##+########################################################################
## PROC 'make_tachometers'
##+########################################################################
## PURPOSE: Draws all features of 2 tachometer-style meters (except the
##          needles) --- in a 'nice filling-size' according to the
##          current canvas dimensions.
##
##            (We will allow the canvas to resize according to
##             a resizing of the window. This proc will redraw the
##             meters in proportion to the new size of the canvas.)
##
## CALLED BY: once, at the 'Additional GUI Initialization' section,
##            at the bottom of this script --- and
##            in the 'ReDraw...' proc.
##+########################################################################

proc make_tachometers {} {

   global marginPx

   ## FOR TESTING: (to dummy out this proc)
   #  return

   ############################################################
   ## Get current '.fRcanvases' dimensions --- in case the user
   ## has resized the window, and thus the '.fRcanvases' frame,
   ## which was packed with '-fill both -expand 1'.
   ############################################################
 
   # set curCanvasesWidthPx  [.fRcanvases cget -width]
   # set curCanvasesHeightPx [.fRcanvases cget -height]

   set curCanvasesWidthPx  [winfo width  .fRcanvases]
   set curCanvasesHeightPx [winfo height .fRcanvases]


   ############################################################
   ## Set a width-and-height to use for a canvas to contain
   ## each of the 2 meters. (Half the 'fRcanvases' width. Also
   ## take the height of 'fRcanvases' into account.)
   ## (We take 8 pixels off the width to account for some
   ##  borderwidths of frames within the 'fRcanvases' frame.) 
   ############################################################

   set canvasSizePx [expr {int(($curCanvasesWidthPx - 8) / 2.0)}]

   if {$curCanvasesHeightPx < $canvasSizePx} {set canvasSizePx $curCanvasesHeightPx}

   ####################################################################
   ## Resize the canvases 'fRcanvas1.can' and 'fRcanvas2.can' that
   ## hold the 2 (square) Tk canvases for the 2 meters. Note that those
   ## 2 canvases and their parent frames were all packed with
   ## '-fill both -expand 1' --- so if the canvas widgets
   ## expand/contract, then the parent frames should do the same.
   ####################################################################

   if {0} {
   .fRcanvases.fRcanvas1 configure -width  $canvasSizePx
   .fRcanvases.fRcanvas1 configure -height $canvasSizePx
   .fRcanvases.fRcanvas2 configure -width  $canvasSizePx
   .fRcanvases.fRcanvas2 configure -height $canvasSizePx
   }

   .fRcanvases.fRcanvas1.can configure -width  $canvasSizePx
   .fRcanvases.fRcanvas1.can configure -height $canvasSizePx

   ## FOR TESTING:
   # update

   .fRcanvases.fRcanvas2.can configure -width  $canvasSizePx
   .fRcanvases.fRcanvas2.can configure -height $canvasSizePx

   ## FOR TESTING:
   # update

   ## Following not needed? The resizing of the '.can' canvas
   ## widgets should cause the parent frames to resize.

   # set doubleWidthPx [expr {(2 * $canvasSizePx) + 8}]
   # .fRcanvases configure -width  $doubleWidthPx
   # .fRcanvases configure -height $canvasSizePx


   #####################################################
   ## NEEDED to force the canvases and frames to update
   ## according to the new canvas sizes.
   #####################################################

   update


   #########################################################
   ## Draw meter1 (without needle).
   #########################################################

   make_one_tachometer .fRcanvases.fRcanvas1.can


   #########################################################
   ## Draw meter2 (without needle).
   #########################################################

   make_one_tachometer .fRcanvases.fRcanvas2.can



}
## END OF proc 'make_tachometers'


##+########################################################################
## PROC 'make_one_tachometer'
##+########################################################################
## PURPOSE: Draws all features of a tachometer-style meter (except the
##          needle) --- according to the 'marginPx' parameter to set
##          top-right and bottom-left coordinates to specify the location
##          of the square exactly containing the circular meter on
##          the canvas whose ID is passed into this proc.
##
##  The features include:
##      - white-filled circle for the meter background
##      - a gray-shaded (shadowed) edge around the circle
##      - a 'pin' in the center of the circle, for the needle
##      - 4 decorative rivets at the corners of the canvas
##      - an arc with tic-marks
##      - a red danger-zone in the last segment of the arc
##        (between the last pair of tic-marks)
##      - labels for the tic-marks
##
## CALLED BY: proc 'make_tachometers'
##+#######################################################################

## Set an 'indentation' to use for placing the outer-circle of the 2 meters
## from the 4 edges of their respective canvases.

set marginPx 12

set pi [expr {4.0 * atan(1.0)}]
set radsPERdeg [expr {$pi/180.0}]


## The above variables are set ONCE, for use in the following proc.

proc make_one_tachometer {canvas} {

   global marginPx pi radsPERdeg Nsegs pcentLabels

   ## FOR TESTING: (to dummy out this proc)
   #   return
 
   ################################################################
   ## Remove any previously drawn elements in this canvas, if any.
   ################################################################

   catch {$canvas delete all}


   ##################################################################
   ## Get the width (= height) of the specified (square) canvas.
   ##################################################################

   set curCanvasSizePx  [winfo width  $canvas]

   ##################################################################
   ## Set the corner coords for drawing the meter circle (background).
   ##################################################################

   set topleftXpx $marginPx
   set topleftYpx $marginPx
   set botrightXpx [expr {$curCanvasSizePx - $marginPx}]
   set botrightYpx [expr {$curCanvasSizePx - $marginPx}]


   ################################################
   ## Draw basic white-filled circle for the meter.
   ################################################

   $canvas create oval \
      $topleftXpx $topleftYpx $botrightXpx $botrightYpx \
      -fill white -outline {}

   # -width 1 -outline lightgray

   ## FOR TESTING: (exit this proc before adding more to the meter)
   #   return


   #######################################################################
   ## Draw shadow-circle at the outer circle of the meter.
   #######################################################################
   ## INPUTS:
   ## - the 4 corner coordinates of the oval/circle box (in pixels)
   ## - number of segments for the arc (segments of differing color shade)
   ## - width (in pixels) to draw the arc segments
   ## - start angle for drawing the (darker) arc segments
   ##   (measured counter-clockwise from the 3 o'clock position)
   ##   (An angle of +135=90+45 means the dark side of the 'shadow-circle'
   ##    is on the north-west side of the circle.)
   ######################################################################

   draw_circle_shadow $canvas \
      $topleftXpx $topleftYpx $botrightXpx $botrightYpx \
      40 6 135.0
 
   ## FOR TESTING: (exit this proc before adding more to the meter)
   #   return


   ###################################################################
   ## Draw a shadow-circle for the 'pin' of the meter needle.
   ###################################################################
   ## INPUTS:
   ## - the 4 corner coordinates of the oval/circle box (in pixels)
   ## - number of segments for the arc (segments of differing color shade)
   ## - width (in pixels) to draw the arc segments
   ## - start angle for drawing the (darker) arc segments
   ##    (measured counter-clockwise from the 3 o'clock position)
   ##    (An angle of -45 means the dark side of the 'shadow-circle'
   ##     is on the south-east side of the circle.)
   ###################################################################

   set centerXpx [expr {int($curCanvasSizePx/2.0)}]
   set centerYpx $centerXpx

   set pinOuterRadiusPx 14

   set x1 [expr {$centerXpx - $pinOuterRadiusPx}]
   set y1 [expr {$centerYpx - $pinOuterRadiusPx}]
   set x2 [expr {$centerXpx + $pinOuterRadiusPx}]
   set y2 [expr {$centerYpx + $pinOuterRadiusPx}]

   draw_circle_shadow $canvas $x1 $y1 $x2 $y2 40 6 -45.0

   ## FOR TESTING: (exit this proc before adding more to the meter)
   #   return


   ############################################################
   ## Draw a red-filled circle on the 'pin' of the meter needle.
   ############################################################

   set pinRadiusPx 12

   set x1 [expr {$centerXpx - $pinRadiusPx}]
   set y1 [expr {$centerYpx - $pinRadiusPx}]
   set x2 [expr {$centerXpx + $pinRadiusPx}]
   set y2 [expr {$centerYpx + $pinRadiusPx}]

   $canvas create oval \
      $x1 $y1 $x2 $y2 -fill red -outline {}

   #   -width 1 -outline lightgray

   ## FOR TESTING: (exit this proc before adding more to the meter)
   #   return


   ###########################################
   ## Draw arc-line on which to put tic marks.
   #################################################
   ## 320 degrees counter-clockwise from -70 degrees
   ## (based at 3 oclock) is 70 degrees beyond 180.
   ## I.e. -70 + 320 = 250 = 180 + 70
   #################################################

   set arcLineIndentPx 10

   set x1 [expr {$topleftXpx  + $arcLineIndentPx}]
   set y1 [expr {$topleftYpx  + $arcLineIndentPx}]
   set x2 [expr {$botrightXpx - $arcLineIndentPx}]
   set y2 [expr {$botrightYpx - $arcLineIndentPx}]

   $canvas create arc $x1 $y1 $x2 $y2 \
      -start -70 -extent 320 -style arc \
      -outline black -width 2

   ## FOR TESTING: (exit this proc before adding more to the meter)
   #   return


   ##################################################
   ## Draw tic-marks and labels around the meter.
   ##################################################

   set DEGperTIC [expr {320.0/$Nsegs}]
   set half  $centerXpx

   ##  outer location (radius) of tic marks
   set l1    [expr {$half - ($arcLineIndentPx + $marginPx)}]

   ##  inner location (radius) of tic marks
   set l2    [expr {$l1 - $arcLineIndentPx}]

   ## inner location of tic labels
   set l3    [expr {$l2 - $arcLineIndentPx}]
 
   set angle0  250.0

   for {set i 0} {$i <= $Nsegs} {incr i} {

      set rads [expr {($angle0 - ($DEGperTIC * $i)) * $radsPERdeg}]
 
      set x1 [expr {$half + $l1 * cos($rads)}]
      set y1 [expr {$half - $l1 * sin($rads)}]
      set x2 [expr {$half + $l2 * cos($rads)}]
      set y2 [expr {$half - $l2 * sin($rads)}]

      $canvas  create line \
         $x1 $y1 $x2 $y2 \
         -fill black -width 2
 
      set x1 [expr {$half + $l3 * cos($rads)}]
      set y1 [expr {$half - $l3 * sin($rads)}]
 
      set label [lindex $pcentLabels $i]

      if { [string length $label] } {
         $canvas create text \
            $x1 $y1 \
            -anchor center -justify center -fill black \
            -text $label -font { Helvetica 10 }
      }
      ## END OF labels loop.

   }
   ## END OF i-loop for tic-marks

   ## FOR TESTING: (exit this proc before adding more to the meter)
   #   return


   #######################################################
   ## Draw red-line arc-segment (danger zone) of the meter.
   #######################################################

   set redLineIndentPx 15

   set x1 [expr {$topleftXpx  + $redLineIndentPx}]
   set y1 [expr {$topleftYpx  + $redLineIndentPx}]
   set x2 [expr {$botrightXpx - $redLineIndentPx}]
   set y2 [expr {$botrightYpx - $redLineIndentPx}]

   $canvas create arc $x1 $y1 $x2 $y2 \
      -start -70 -extent $DEGperTIC -style arc \
      -outline red -fill red -width 8

   ## FOR TESTING: (exit this proc before adding more to the meter)
   #   return


   ##################################
   ## Draw 4 rivets around the meter.
   ##################################

   set RIVETindentPx 10
   set RIVEToutdentPx [expr {$curCanvasSizePx - $RIVETindentPx}]

   ## upper-left rivet
   draw_rivet $canvas $RIVETindentPx $RIVETindentPx
   ## upper-right rivet
   draw_rivet $canvas $RIVEToutdentPx $RIVETindentPx
   ## lower-left rivet
   draw_rivet $canvas $RIVETindentPx $RIVEToutdentPx
   ## lower-right rivet
   draw_rivet $canvas $RIVEToutdentPx $RIVEToutdentPx

 
}
## END OF proc 'make_tachometers'


##+########################################################################
## PROC 'draw_rivet'
##+########################################################################
## PURPOSE: Put a rivet at a specified center point.
##          The center point is specified in pixels, as a location on
##          the canvas of the GUI, relative to the upper left corner.
##
##          (We pass the radius of the rivets in a global variable.)
##
## CALLED BY: the 'make_tachometer' proc
##+########################################################################

set rivetRadiusPx 4

proc draw_rivet { canvas centerXpx centerYpx } {

   global rivetRadiusPx

   ## FOR TESTING:
   #   return

   ########################################################
   ## Draw a color shaded arc using
   ## - 5 arc segments around each half of the circle/oval
   ## - 3 pixels for width of the arc segments
   ## - -45 degrees for the start angle (darkest shade)
   ########################################################

   draw_circle_shadow $canvas \
      [expr {$centerXpx - $rivetRadiusPx}] \
      [expr {$centerYpx - $rivetRadiusPx}] \
      [expr {$centerXpx + $rivetRadiusPx}] \
      [expr {$centerYpx + $rivetRadiusPx}] \
      5 3 -45.0

}
## END OF proc 'draw_rivet'


##+########################################################################
## PROC 'draw_circle_shadow'
##+########################################################################
## PURPOSE: Puts a shadowed edge around an oval/circle in a specified 'box'.
##          
## INPUTS: - the corner coordinates of the oval/circle box (in pixels)
##         - number of segments for the arc (segments of differing color shade)
##         - width (in pixels) to draw the arc segments
##         - start angle for drawing the arc segments
##
## CALLED BY: the 'make_tachometers' and 'draw_rivets' procs
##+########################################################################

proc draw_circle_shadow {canvas x1 y1 x2 y2 Nsegs ARCwidthPx startDEGREES } {

   ## FOR TESTING: (dummy out this proc)
   #   return

   set DEGperSHADE [expr {180.0/$Nsegs}]

   for {set i 0} {$i <= $Nsegs} {incr i} {

      set a [expr {($startDEGREES + $i * $DEGperSHADE)}]
      set b [expr {($startDEGREES - $i * $DEGperSHADE)}]
 
      ## Make darker grays for greater angles.
      set color255 [expr {40 + $i*(200/$Nsegs)}]
      set hexcolor [format "#%x%x%x" $color255 $color255 $color255]
 
      $canvas create arc \
         $x1 $y1 $x2 $y2 \
         -start $a -extent $DEGperSHADE \
         -style arc -outline $hexcolor -width $ARCwidthPx

      $canvas create arc \
         $x1 $y1 $x2 $y2 \
         -start $b -extent $DEGperSHADE \
         -style arc -outline $hexcolor -width $ARCwidthPx

      ## FOR TESTING:  (show each pair of segments before
      ##                drawing the next pair)
      # update

   }
   ## END OF loop over the arc segments

}
## END OF proc 'draw_circle_shadow'



##+########################################################################
## PROC 'update_needles'
##+########################################################################
## PURPOSE: Updates the needles on 2 square canvases --- using the
##          Linux/Unix/BSD/Mac 'netstat' (or other) command to get 
##          RX and TX data.
##
##          Input is the canvas ID. This proc queries the canvas to
##          get its center and to determine an appropriate length for
##          the needle.
##
## CALLED BY: the 'Additional GUI Initialization' section at the
##            bottom of this script, and
##            within this proc itself.
##+########################################################################
 
proc update_needles {} {

   global argv0 DIRscripts WAITseconds IFACEname \
      PREVrx PREVtx PREVclock_millisecs VARsampcnt

   # global env


   ##########################################################
   ## Get RX and TX data --- and the current clock time,
   ## in millisecs.
   ##########################################################

   foreach {CURrx CURtx} \
      [exec $DIRscripts/get_net_stats.sh $IFACEname]  {break}

   incr VARsampcnt

   set CURclock_millisecs [clock milliseconds]

   ## FOR TESTING:
   if {0} {
      puts "proc update_needles:"
      puts "PREVrx: $PREVrx      PREVtx: $PREVtx"
      puts "PREVclock_millisecs: $PREVclock_millisecs"
      puts "CURrx: $CURrx      CURtx: $CURtx"
      puts "CURclock_millisecs: $CURclock_millisecs"
   }

   #########################################################
   ## Calculate the RX and TX rates.
   #########################################################

   set deltaRX [expr {$CURrx - $PREVrx}]
   set deltaTX [expr {$CURtx - $PREVtx}]
   set deltaSECS \
      [expr {double($CURclock_millisecs - $PREVclock_millisecs)/1000.0}]

   set RATErx [expr {$deltaRX/$deltaSECS}]
   set RATEtx [expr {$deltaTX/$deltaSECS}]


   #########################################################
   ## Update the 2 needles.
   #########################################################

   ## FOR TESTING: (hardcoded meter values)
   # update_one_needle .fRcanvases.fRcanvas1 123456789 1234 RX
   # update_one_needle .fRcanvases.fRcanvas2 654321 321 TX

   update_one_needle .fRcanvases.fRcanvas1 $CURrx $RATErx RX

   update_one_needle .fRcanvases.fRcanvas2 $CURtx $RATEtx TX


   #########################################################
   ## Load the current counts to the PREV vars.
   #########################################################

   set PREVrx $CURrx
   set PREVtx $CURtx
   set PREVclock_millisecs $CURclock_millisecs


   ############################################################
   ## Force the needles to show up on the GUI. (needed???)
   ############################################################

   # update

   ################################################################
   ## 'Pseudo-Recursively' 'fork off' another (delayed) instance of the
   ## 'update_needles' here to support the wait-seconds scale widget
   ## --- using the 'after ms cmd arg arg ...' form of the 'after'
   ## command.
   ##
   ## We may need an 'after idle update_needles' somewhere
   ## in this script to 'register'/'queue' the 'update_needles'
   ## proc for execution at idle times --- to assure responsiveness
   ## of the GUI.
   ################################################################

   set WAITmillisecs [expr {int($WAITseconds * 1000)}]
   after $WAITmillisecs update_needles

}
## END OF proc 'update_needles'


##+########################################################################
## PROC 'update_one_needle'
##+########################################################################
## PURPOSE: Updates the a needle on a square canvas --- using the
##          canvas ID and the rate number and label-name passed as arguments.
##
##          Input is the canvas ID. This proc queries the canvas to
##          get its center and to determine an appropriate length for
##          the needle as a proportion of the (square) canvas size.
##
## CALLED BY: the 'update_needles' proc
##+########################################################################
 
proc update_one_needle {frame TOT RATE TYPE} {

   global pi radsPERdeg Nsegs MAXrate

   ## FOR TESTING: (dummy out this routine)
   #  return

   set RATEtext "$TYPE Rate = [format "%0.2f" $RATE] packets/second"
   set TOTtext  "$TOT $TYPE pkts so far this session"

   $frame.labelINFO1 configure -text "$RATEtext"
   $frame.labelINFO2 configure -text "$TOTtext"

   ## Set the angle for the zero-point on the arc-of-tic-marks.

   set angle0  250.0

   ## Convert RATE/MAXrate to an angle in radians on the arc.

   if {$RATE > $MAXrate} {set RATE $MAXrate}

   set degs [expr {$angle0 - (320.0 * $RATE / $MAXrate)}]
   set rads [expr {$degs * $radsPERdeg}]

   ## FOR TESTING:
   #   puts "proc 'update_one_needle' :"
   #   puts "RATE: $RATE  MAXrate: $MAXrate   degs: $degs  rads: $rads"

   ## Get the coord(s) of the center of the (square) canvas
   ## and calculate a length of the needle.

   # set width  [$frame.can cget -width]
   set width  [winfo width $frame.can]
   set half   [expr {int($width / 2.0)}]
   set length [expr {int($half * 0.5)}]

   ## Calculate the coordinates for the tip and base of the needle.
 
   set xtip [expr {$half + $length*cos($rads)}]
   set ytip [expr {$half - $length*sin($rads)}]
 
   # set xbase [expr {$half + 0.2*$length*cos($rads)}]
   # set ybase [expr {$half - 0.2*$length*sin($rads)}]

   set xbase $half
   set ybase $half
 
   ## Remove a previous needle, if any.

   catch {$frame.can delete -tags TAGneedle}

   ####################################################################
   ## Draw a red-line needle and a reddish-white line on either side
   ## --- for an (attempted) anti-aliasing effect.
   ##
   ## NOTE: This attempt at anti-aliasing did not work out well.
   ##       This code needs improvement --- or simply one 'create line'.
   ####################################################################

   $frame.can create line \
      $xbase $ybase $xtip $ytip \
      -fill #ff0000 -width 4 -tag TAGneedle

   $frame.can create line \
      [expr {$xbase + 1}] [expr {$ybase + 1}] \
      [expr {$xtip + 1}]  [expr {$ytip + 1}] \
      -fill #ff8888 -width 2 -tag TAGneedle

   $frame.can create line \
      [expr {$xbase - 1}] [expr {$ybase - 1}] \
      [expr {$xtip - 1}]  [expr {$ytip - 1}] \
      -fill #ff8888 -width 2 -tag TAGneedle

}
## END OF proc 'update_one_needle'



##+#############################################################
## proc Refresh
##
## PURPOSE: 'Refresh' the two meters and their needles ---
##           for when the user wants a new set of values
##           and/or when the user resizes the window.
##
## CALLED BY: 'Refresh' button
##+#############################################################

proc Refresh {} {

   ## Cancel pending needle update(s), before redrawing
   ## the meters and restarting the update-needles cycle.

   set LISTids [after info]
   foreach ID $LISTids {
      after cancel $ID
   }

   make_tachometers
   update_needles

}
## END OF proc 'Refresh'


##+#############################################################
## proc sho_ifaces
##
## PURPOSE: Shows available network interfaces, in a
##          popup message window.
##
## CALLED BY: 'Ifaces' button
##+#############################################################

proc sho_ifaces {} {

   set IFACES [exec /bin/sh -c {netstat -i | tail -n +3 |  awk '{print $1}'}]

   popup_msgVarWithScroll .topIFACES $IFACES
}
## END OF proc 'sho_ifaces'


##+#############################################################
## proc ReDraw_if_canvases_resized
##
## PURPOSE: To handle resizing the meters when the window is
##          resized --- IF the <Configure> 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 .fRcanvas.can <Configure> 
##            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  .fRcanvases]
   set CURcanvasesHeightPx [winfo height .fRcanvases]

   if { $CURcanvasesWidthPx  != $PREVcanvasesWidthPx || \
        $CURcanvasesHeightPx != $PREVcanvasesHeightPx} {

      ## The 'after' could be used to prevent too many
      ## redraws (and flickering and unnecessary processing)
      ## as the window is being moved.
      # after 200

      make_tachometers
      update_needles

      set PREVcanvasesWidthPx  $CURcanvasesWidthPx
      set PREVcanvasesHeightPx $CURcanvasesHeightPx
      set draw_wait0or1 0
   }

}
## END OF proc 'ReDraw_if_canvases_resized'


##+########################################################################
## PROC 'popup_msgVarWithScroll'
##+########################################################################
## PURPOSE: Report help or error conditions to the user.
##
##       We do not use focus,grab,tkwait in this proc,
##       because we use it to show help when the GUI is idle,
##       and we may want the user to be able to keep the Help
##       window open while doing some other things with the GUI
##       such as putting a filename in the filename entry field
##       or clicking on a radiobutton.
##
##       For a similar proc with focus-grab-tkwait added,
##       see the proc 'popup_msgVarWithScroll_wait' in a
##       3DterrainGeneratorExaminer Tk script.
##
## REFERENCE: page 602 of 'Practical Programming in Tcl and Tk',
##            4th edition, by Welch, Jones, Hobbs.
##
## ARGUMENTS: A toplevel frame name (such as .fRhelp or .fRerrmsg)
##            and a variable holding text (many lines, if needed).
##
## CALLED BY: 'help' button
##+########################################################################
## To have more control over the formatting of the message (esp.
## words per line), we use this 'toplevel-text' method, 
## rather than the 'tk_dialog' method -- like on page 574 of the book 
## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications
## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor".
##+########################################################################

proc popup_msgVarWithScroll { toplevName VARtext } {

   ## global fontTEMP_varwidth #; Not needed. 'wish' makes this global.
   ## global env

   # bell
   # bell
  
   #################################################
   ## Set VARwidth & VARheight from $VARtext.
   #################################################
   ## To get VARheight,
   ##    split at '\n' (newlines) and count 'lines'.
   #################################################
 
   set VARlist [ split $VARtext "\n" ]

   ## For testing:
   #  puts "VARlist: $VARlist"

   set VARheight [ llength $VARlist ]

   ## For testing:
   #  puts "VARheight: $VARheight"


   #################################################
   ## To get VARwidth,
   ##    loop through the 'lines' getting length
   ##     of each; save max.
   #################################################

   set VARwidth 0

   #############################################
   ## LOOK AT EACH LINE IN THE LIST.
   #############################################
   foreach line $VARlist {

      #############################################
      ## Get the length of the line.
      #############################################
      set LINEwidth [ string length $line ]

      if { $LINEwidth > $VARwidth } {
         set VARwidth $LINEwidth 
      }

   }
   ## END OF foreach line $VARlist

   ## For testing:
   #   puts "VARwidth: $VARwidth"


   ###############################################################
   ## NOTE: VARwidth works for a fixed-width font used for the
   ##       text widget ... BUT the programmer may need to be
   ##       careful that the contents of VARtext are all
   ##       countable characters by the 'string length' command.
   ###############################################################


   #####################################
   ## SETUP 'TOP LEVEL' HELP WINDOW.
   #####################################

   catch {destroy $toplevName}
   toplevel  $toplevName

   # wm geometry $toplevName 600x400+100+50

   wm geometry $toplevName +100+50

   wm title     $toplevName "Note"
   # wm title   $toplevName "Note to $env(USER)"

   wm iconname  $toplevName "Note"


   #####################################
   ## In the frame '$toplevName' -
   ## DEFINE THE TEXT WIDGET and
   ## its two scrollbars --- and
   ## DEFINE an OK BUTTON widget.
   #####################################

   if {$VARheight > 10} {
      text $toplevName.text \
         -wrap none \
         -font fontTEMP_varwidth \
         -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_varwidth \
         -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 'Network Activity' Monitoring Utility **

This utility is meant to show a GUI that holds a pair of
tachometer-style meters. The needles on the meters are updated
periodically --- according to a 'wait-seconds' setting on a
'scale' widget of the GUI --- OR whenever the user chooses to
click on the 'Refresh' button --- to show the RATE of packet
receipts and transmissions (RX and TX rates) on this computer.

The RX and TX rates (packets per second) are shown on the GUI
as numeric-text as well as by the positions of the needles.

This Tcl-Tk script was developed on Linux and uses the 'netstat'
command to get the RX and TX data to determine where to
relocate the position of the needles on the meters.

An 'Ifaces' button on the GUI can be clicked to get a list of
the names of network interfaces known to the 'netstat' command.
Typically, the names are 'eth0', 'eth1', or 'wlan0'.

Change the interface name in the entry field if you want to
monitor a different interface.

An integer on the top right of the GUI is incremented each
time a sample is taken with the 'netstat' command.


***************************************
WINDOW RESIZE (an experimental feature):

We can allow the user to resize the window rather than using a fixed
window (and fixed meters) size. If the user resizes the window, the
'Refresh' button can be used to force the meters to be resized
according to the new window size. (The meters may be resized such that
they are 'too tall' for the new window size. Just pull the bottom
border of the window down, to see the entire meters.)


************************************
THE SCRIPT USED to update the meters:

A Tcl 'exec' command calls on a separate shell script ---
'get_net_stats.sh' --- that uses the 'netstat' (or other)
command to get the RX and TX data --- and
extract-and-format the data for return to this Tk script.

If the 'netstat' command is not available on your computer,
you may have to edit the shell script to use a different
command (such as 'ifconfig') to get the RX and TX 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 2 meters on their 2 canvases, with 'make_tachometers'.
##  - Start an execution loop for the 'update_needles' proc.
##+################################################################

##+########################################################
## Get a default network interface-ID for the entry widget.
##+########################################################

set IFACEname [exec /bin/sh -c {netstat -i | tail -n +3 |  awk '{print $1}' | head -1}]

# set IFACEname "eth0"
# set IFACEname "eth1"
# set IFACEname "wlan0"


##+###################################################
## 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 2.0

#  set WAITseconds 5.0
#  set WAITseconds 1.0
#  set WAITseconds 0.1


##+#####################################################
## Set a max-rate (packets/sec) to use for the limit
## on the 2 meters.
##+#####################################################

set MAXrate 1000

##+####################################################
## Set a number of segments and labels for the 2 meters,
## according to the MAXrate setting.
##+####################################################

set Nsegs 10
set pcentLabels "0 100 200 300 400 500 600 700 800 900 1000"


##+#################################################
## Draw the 2 tachometers (without needles).
##+#################################################
## Need 'update' here to set the size of the canvases,
## because 'make_tachometers' uses 'winfo' to get
## the width and height of some frames and canvases
## --- to support resizing the meters if the window
## is resized.
##+#################################################

update
make_tachometers


##+#######################################################
## Get the directory that this Tk script is in. That should
## be the directory that the utility shell script is in
## --- to get the RX,TX data values.
##+#######################################################

## FOR TESTING:
#  puts "argv0: $argv0"

# set DIRscripts "."
# set DIRscripts "[pwd]"
# set DIRscripts "$env(HOME)/apps/tkUtils"
  set DIRscripts "[file dirname $argv0]"


##+####################################################
## Get an initial sample of the RX,TX data, along with
## the time at which the sample was taken --- in
## 'PREV' variables.
##+####################################################

foreach {PREVrx PREVtx} \
   [exec $DIRscripts/get_net_stats.sh $IFACEname] {break}

set PREVclock_millisecs [clock milliseconds]

## FOR TESTING:
#   puts "PREVrx: $PREVrx      PREVtx: $PREVtx"
#   puts "PREVclock_millisecs: $PREVclock_millisecs"


##+####################################################
## Initialize the variable we use to keep track of
## the sample count.
##+####################################################

set VARsampcnt 0

##+######################################################
## Do an initial draw of the needles, after WAITseconds.
##
## NOTE:
## 'update_needles' starts a loop to keep updating the needles.
##
## The proc 'update_needles' called itself ---
## with 'after <millisecs>'.
##
## NOTE:
## I am leery of that kind of 'forked-recursion' --- even though
## it is a 'recursive-like-call-with-FORKED-AND-DELAYED-execution'
## --- which immediately returns to processing after doing the
## 'queue-command-with-delay'.
##
## This could possibly cause ever-increasing memory consumption
## or the generation of an ever-increasing 'stack' of
## processes --- or 'stack' of 'whatever'.  I would
## like to take the time to test to make sure that a
## high-percent of CPU and memory are NOT being used.
##
## (Besides the loop structure itself,
##  there is the possibility of excessive CPU usage or
##  memory leaks due to implementation within the 'wish'
##  interpreter. This has happened in the past --- for
##  example, with 'tags' on canvas items. So I prefer
##  to take the time to test CPU and memory usage ---
##  especially for Tk techniques I have not used before.)
##
## I plan to investigate usage of the 'after' command in its
## several forms (including 'after idle'), and try to
## make sure that the use of 'after ms cmd' and/or
## 'after idle cmd' does not lead to 'issues'.
##
## In any case, I provide the user with the 'Refresh'
## button to do the needle updates --- for example,
## if the user resizes the window and wants to force
## a refresh (which runs 'make_tachometers') to resize
## the meters and the variables that the 'update_needles'
## proc uses to draw the needles on the meters.
##+######################################################

set WAITmillisecs [expr {int(1000*$WAITseconds)}]
after $WAITmillisecs update_needles


##+#################################################
## Set a resize binding on the canvas ---
## to redraw the tachometers and needles
## if the window is resized.
##
## DE-ACTIVATED, for now.
## (Code is here for future experimentation.
##  It is not easy to avoid extraneous redraws of
##  the GUI as the window is being dragged/resized.)
##+#################################################

if {0} {
set draw_wait0or1 0
set PREVcanvasesWidthPx  [winfo width  .fRcanvases]
set PREVcanvasesHeightPx [winfo height .fRcanvases]
bind .fRcanvases <Configure> "ReDraw_if_canvases_resized"
}

And here is the code for the shell script called by this Tk script.

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.

 Code for the shell script 'get_net_stats.sh' :
#!/bin/sh
##
## SCRIPT NAME: get_net_stats.sh
##
## PURPOSE:
##    Gets 'RX' (received) and 'TX' (transmitted) data from output of
##    the 'netstat' command (or some other command supplying the same data).
##
##+############################################
## Example output from the 'netstat -i' command:
##
## Kernel Interface table
## Iface   MTU Met   RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
## eth0       1500 0     23449      0      0 0         19670      0      0      0 BMRU
## lo        16436 0         4      0      0 0             4      0      0      0 LRU
## wlan0      1500 0         0      0      0 0             0      0      0      0 BMU
## wmaster0      0 0         0      0      0 0             0      0      0      0 RU
##
##+#########################################################
## ALTERNATIVELY, we could use 'ifconfig -a -s', which gives:
## 
## Iface   MTU Met   RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
## eth0       1500 0     23449      0      0 0         19670      0      0      0 BMRU
## lo        16436 0         4      0      0 0             4      0      0      0 LRU
## wlan0      1500 0         0      0      0 0             0      0      0      0 BMU
## wmaster0      0 0         0      0      0 0             0      0      0      0 RU
##
## where '-a' stands for 'all' --- and '-s' stand for 'summary'.
##+########################################################################
##
## This script gets the data from the 'RX-OK' and 'TX-OK' columns.
##
## INPUT: a single argument supplies an interface ID, such as
##                                      'eth0' or 'eth1' or 'wlan0'.
##
## CALLED BY: a Tk GUI script that shows 'RX' and 'TX' DIFFERENCE data
##            --- i.e. RATE data ---
##            as needle readings on a couple of meters (dials) drawn
##            on a Tk canvas --- Tk script name:
##                    meters_net_stats.tk
##
## MAINTENANCE HISTORY:
## Updated by: Blaise Montandon 2013sep05 Started this script on Linux,
##                                        using Ubuntu 9.10 (2009 October,
##                                        'Karmic Koala').
## Updated by: Blaise Montandon 20.......
##+########################################################################

## FOR TESTING:
# set -x

if test "$1" = ""
then
   echo "ERROR: Missing argument. Need a network interface ID."
   exit
fi

## ALTERNATIVE:
# ifconfig -a -s | grep "^$1" | awk '{print $4 " " $8}'

netstat -i | grep "^$1" | awk '{print $4 " " $8}'


SIMILAR UTILITIES:

There are a couple of more 'meter utilities' on my 'to-do' list at the bottom of my 'bio' page at uniquename --- in the 'CME' (Code for MEters) group.

In particular, I may make a meter utility that shows CPU activity (preferably for all the CPU's on multiple CPU computers, which are everywhere nowadays).


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.


uniquename UPDATE 2013nov22 :

Added a small 'foreach' loop to the 'Refresh' proc of the Tk script. The 'after info' and 'after cancel' commands are used to cancel any pending commands that were issued by a previous 'after' command.