moodss

(Modular Object Oriented Dynamic SpreadSheet)

Contents:

1. About this document

This document contains general information, reference information and examples designed to help the user understand the moodss application and the programmer write modules for it.

2. Introduction

Quite often, one needs to monitor changing data, whether it might come from a system, such as the different processes running on a Unix server, or from a network, such as the volume and distribution of traffic that runs through it.

Most often, such data can be organized in a table with rows of information, each column representing a different kind of data. For example, in the case of processes running on a computer system, rows might be sorted according to their unique process identifier, with columns containing values such as CPU usage, memory usage, owner's name, time of creation, ...

The software used to view this type of information comes in different forms and shapes. Unix users might be familiar with the top application which presents rows of process data as lines of text, whereas RMON (Remote MONitoring) SNMP software usually uses multiple windows with graphical displays, curves, pie charts, multiple configuration dialog boxes, even 3D visualization modules to visualize network traffic, connection matrices, ...

In most cases, data comes from one or more tables. A common interface, graphical with menus, drag'n'drop capability, table widgets, textual and graphical data viewers such as multiple line graphs, bar and pie charts, could be used. The user could then sort table rows, select one or more cells, rows, columns, create views such as other tables, charts, ... best suited to the way data should be presented. Once optimized, the data viewers layout and configuration could be saved for later reuse as a dashboard. In effect, what is needed is a spreadsheet tailored to dynamic data processing.

Moodss (Modular Object Oriented Dynamic SpreadSheet) is an attempt at answering these needs. It is composed of a main part (the core) and an unlimited number of modules, loaded as the application is launched, each module interfacing to a specific type of data. The core is written in the great Tcl language (at http://www.scriptics.com/) using object oriented techniques thanks to the stooop package (at http://www.multimania.com/jfontain/). The module function is to describe the data that it is also in charge of retrieving and formatting. Modules can be written in plain Tcl or use dynamically linked libraries written in the C language (modules are packages in the Tcl sense, so any language that can interface with Tcl is supported).

Modules are loaded when moodss is started. Several modules can be handled concurrently (starting with moodss version 3.0). This way, you may monitor data coming from any number of heterogeneous sources. Modules are specified in the command line and cannot be dynamically unloaded.

Versions 4.0 and up add a dashboard functionality: the current configuration (modules, viewers, poll time, windows sizes and placement, ...) can be saved in a file at any time, for later reuse (see the -f (--file) command line switch documentation).

Versions 4.3 and up support asynchronous modules (for which no polling is needed as module data may change on its own). Note that any number of asynchronous and regular (synchronous) modules can be simultaneously loaded.

Versions 5.0 and up add a free text viewer, which can be used for comments, and which can also embed live data cells in text form.

Versions 5.3 and up support viewer type mutation through viewer icon drag'n'drop, and viewer quick destruction by a drop into the trash icon. A new menu for empty viewer creation was also added.

Versions 6.0 and up support command line arguments per module, data table column anchoring in module configuration and automatic module discovery.

Versions 6.1 and up support HTML formatted help data for modules.

Versions 6.6 and up support automatic cross hairs with coordinates in message area for graph data viewers.

Versions 6.7 and up support contextual help (through the main window message area) on all menu items.

Versions 7.0 and up support per user (on UNIX systems) application wide preferences setting.

Versions 7.99 and up support concurrent instances of the same module.

Versions 8.1 and up support configuration settings per dashboard, with a user interface similar to the preferences interface.

Versions 9.0 and up are only compatible with Tcl/Tk versions 8.2 and above.

Since module data access is entirely customizable (through C code, Tcl, HTTP, ...) and since several modules can be loaded at once, applications for moodss become limitless. For example, comparing a remote database server CPU activity and traffic load from a network probe on the same graph becomes possible.

As features are added to moodss, different ways of viewing data will be made available while the module structure will stay the same. The goal of moodss is to become a nice feature packed generic way of viewing data. Moodss can be used to monitor any type of data, since the simplest cases can fit in one table with a single row, with the most complicated requiring loading several multiple table modules.

As moodss is written in Tcl and uses well supported extensions (Tktable and BLT), it will run on Tcl/Tk supported platforms: UNIX and Windows (I do not know if Tktable and BLT are available for the MacIntosh). Obviously, some modules may be specific to a platform, but the core is guaranteed to run on them all.

After reading and understanding this document, you should be able to write your own modules in order to monitor the data that you are interested in.

Moodss is free software. You can redistribute it and/or modify it under the terms described in the COPYRIGHT file or use the main window Help Copyright menu for more information.

3. Required software

If you are using a Linux Redhat system (6.0 or above), then use the moodss rpm file (available at http://www.multimania.com/jfontain/) for installation. It requires the tcl, tk, blt and tktable rpms also available at the same location (see INSTALL file for more information). You are then all set for using the included Linux modules or develop your own modules.

Unless you want to work on the moodss source code (not the modules), you can skip the rest of this section.

For the current version (9.0), the following packages must be installed before attempting to install moodss (make sure to check the INSTALL file for the latest information):

* many thanks to the authors for these great packages
The pie widgets, stooop and scwoop libraries are included in the self contained moodss application file. Therefore, it is not required to install the stooop, scwoop and tkpiechart packages, unless you want to work on the moodss source code itself. However, should you want to get more information on those extensions, you will find the latest versions: at http://www.multimania.com/jfontain/.

4. Architecture

The moodss application is composed of the core software and one or several modules. Modules are implemented as Tcl packages and thus usually comprise a Tcl source file and a pkgIndex.tcl file as required by the Tcl package implementation.

The core loads one or more modules, whose names are passed as command line parameters or come from a save file, and starts displaying module data in one or more tables. The tables are then updated at the frequency defined by the poll time, which the user may change, or asynchronously for the relevant modules. For example, to launch moodss with the random module, just type (on a UNIX machine):

$ moodss random
All the module code and data are kept in a separate namespace. The module data is stored is a single array including some configuration data used when the module is loaded by the core, and variable data (displayed in the application table and eventual graphical viewers). If a module is synchronous, it must start updating its data when requested by the core. If a module is asynchronous, its data may be updated at any time. The synchronous or asynchronous nature is specified in the configuration data for the module.

The initial data tables represent the first data views, from which any number of cells can be selected. Data viewers can be created by dragging and dropping cells into a graph, bar chart, pie chart, summary table or free text iconic site. In turn, these viewers can display more table cells, which when dropped into the viewer, result in the creation of corresponding data graph lines, chart bars, pie slices, table rows or text cells. Cells or rows can be removed from existing viewers, by simply selecting them and dropping them in the trash iconic site (a bullet hole).

Any viewer can be mutated (its type changed) by by dragging from a viewer icon and dropping into it. For example, create a stacked data bar viewer from several cells, then drag the 3D pie icon into it. Any viewer can also be destroyed in one shot by dropping the trash icon into it.

Any draggable data can be dropped in any valid drop site at any time. It is thus possible to drag several data cells from any table or any viewer into other ones, the trash, ... even if the data comes from different modules.

All data viewers can be moved and resized at will with the help of a simple internal window manager.

The current configuration (loaded modules, tables and viewers coordinates, sizes, poll time, main window size, ...) can be saved in a file at any time, so that an identical dashboard can be relaunched at will.

5. Core

5.1. User interface

Immediately after launch, module(s) is(are) loaded and initialized, with corresponding messages displayed in the message area, as follows:
moodss window view in load mode
Soon after, tabular data is displayed in one or more tkTable widgets with the module identifier as title, automatic scroll bars, between the menu bar, the drop sites with graphical viewers, summary table, free text and trash icons and a message area, as one can see below:
moodss initial main window view
The message area is used to display status information, such as when the data is being updated, and help information, as the user moves the mouse pointer over sensitive areas, such as table column headers. Contextual help on all menu items is also activated as traversal occurs, either using the mouse or the keyboard, resulting in a short explicative string appearing in the message area and related to the active (highlighted) menu item. Further help is provided through widget tips (also known as balloons) when appropriate, and of course the Help menu.

The window title shows the name of the loaded module(s) along with the poll time.

When several modules of the same type are loaded (for example, CPU statistics on a group of servers), the initial data tables feature the module name followed by an instance number (module<N>), or the module identifier generated from the module code (cpustats(host.domain.org) for example). A lone module keeps his unmodified name as table titles.
Any data displayed in a table can be sorted at any time by simply clicking on a column title. Clicking on the same column title again sorts the data in opposite order, thus toggling between increasing and decreasing orders.
When sorting, the selected column is used as a reference, meaning that all rows will be rearranged so that the selected column appears sorted, with values either increasing or decreasing.
A little triangular indicator is placed to the right of the reference column title label, pointing up or down depending on whether the sorting order is decreasing or increasing.
Table columns can be interactively resized by holding the first mouse button down on a column border. The mouse cursor is changed to an horizontal double arrow on column borders to show this capability.

Aside from the main tables, graphical and textual viewers can be created for monitoring table cell data over time. Viewers can also be deleted, data views (such as pie slices, curves, ...) can be added or removed from existing viewers, ... These functions are all implemented using the drag and drop functionality.

For all viewers, if a module identifier string is required (provided by the module, several instances of the same module, ...) for proper cell identification, that string will be placed first in the label. For example, data cells originating from the third instance of the random module would be labeled: "random<3>: data cell label".

Graphical viewers available at this time are BLT graph viewers (see images below), side-by-side bars charts, overlapped bars charts, stacked bars charts, 2D pie charts and 3D pie charts*. Graph viewers feature an automatic cross hair which follows the mouse pointer movements inside the plotting area. Corresponding coordinates are updated in real time in the main window message area.

*note: if you know of any other nice viewers (like 3D graphs) that work with Tcl, please let me know so I can integrate them. Many thanks in advance...

graph viewer sample
overlap bar chart viewer sample
side bar chart viewer sample
stacked bar chart viewer sample
2D pie chart viewer sample
3D pie chart viewer sample
There are 2 textual viewers.

The summary table displays for each row the cell label, the current, average, minimum and maximum values since the row was created. Data cells can be inserted one or several at a time through a simple drop. Rows can be deleted by selecting any cell in the row then dropping any number of them into the trash drop site. Data cells with missing data (could be no longer available if coming from a vanished process, for example) display the 000000 pattern.

The free text viewer is an editable Tk text widget with any number (including zero) of embedded data cell windows. Data cells can be inserted one or several at a time through a simple drop, as with the other viewers. New data cell windows are inserted at the current insertion cursor position. Data cells can be deleted by selecting then dropping any number of them into the trash drop site. They can also be deleted using the keyboard Delete and Backspace keys, which also work on the regular text, as well as the expected other key bindings. When dropping data cells, each data cell window is preceded by a relevant label text for the cell, which can later be edited at any time.

summary table viewer sample
free text viewer sample
Here is a screen shot of loaded ps and cpustats modules with several graphical viewers:
moodss window with graph data viewers
All data viewers can be moved and resized thanks to handling areas in the data viewer borders. When moving the mouse pointer over these areas, the mouse cursor changes to indicate the possible action. Corner handles allow resizing in both X and Y axis. Handles in the middle of the sides allow resizing in either the X or Y axis direction. All other areas can be used for moving the data viewer as shown by the quadruple arrow cursor. Clicking on any part of the border changes the stacking order: the viewer being clicked on either goes below (eventually becomes hidden) the other viewers, or becomes fully visible (on top, eventually hiding other viewers). When moving or resizing, the message area displays the current coordinates or size in real time as the mouse is being moved. Further description of this small window manager functionality is useless, as it behaves like a basic window manager (let me know if it does not).

Here is another shot featuring a free text viewer with loaded cpustats and memstats modules:

moodss window with free text data viewer
5.1.1. Menus
5.1.1.1. File
5.1.1.1.1. Save
The current application configuration (including existing data viewers) can be saved in a file, which achieves a dashboard functionality.

Once moodss has been launched with one or several modules and tables have been moved, resized, viewers created, moved and resized, the current configuration can be saved in a .moo file, and later reused by passing the corresponding file name with the -f (--file) command line switch.

For moodss version 4.0 and above, the following information is saved in the file (which is human readable):

For moodss version 5.1 and above, the following information is also saved: For moodss version 5.2 and above, the following information is also saved: For moodss version 8.1 and above, the following information is also saved: When using this menu for the first time, and if a file name was not specified in the command line, the file selector dialog box appears so that the user may choose a file name (with a .moo extension).

Once a file name has been specified (either through the command line or the file selector dialog box), that file name is reused whenever the File Save menu is used.

5.1.1.1.2. Save As
This menu behaves as expected, with the user always having to choose a file name using the file selector dialog box (see Save menu above).
This menu may be used at any time to change the current save file name.
5.1.1.1.3. Exit
Use this menu to gracefully quit the moodss application.
5.1.1.2. Edit
5.1.1.2.1. Configuration
When selected, this menu launches the following dialog box:
The Configuration dialog box allows the user to change global settings for the current view (also called a dashboard). This data is stored along when saving the configuration to a file (see Save menu).

Changing configuration choices do not affect the Preferences choices.

Further and up-to-date help is always provided in the different Configuration dialog box screens.

5.1.1.2.2. New
Allows the creation of empty viewers of any type (graph chart, overlap bar chart, side bar chart, stacked bar chart, 2D pie chart, 3D pie chart, summary table or free text).
This menu is only visible when not running in read-only mode (see Command line).
5.1.1.2.3. Preferences
The Preferences dialog box is very similar to the configuration dialog box.
It allows the user to change application wide settings, saved in a global file (known as an rc file to UNIX people), used for initialization when the application is started. Since this file is stored in the user home directory (on a UNIX system), it provides a way for a user to customize the look, behavior, ... of the moodss application in a permanent manner.

Preferences choices, when applied (either by choosing Apply or OK) also affect configuration settings.

Further and up-to-date help is always provided in the different Preferences dialog box screens.

5.1.1.3. View
5.1.1.3.1. Poll Time
The View menu (may not exist, see below) contains the Poll Time entry which when selected launches the corresponding dialog box, as shown below:
poll time dialog box
The user can select a new poll time among the module choices from a spin entry widget, or directly type in a new value, as long as it is not smaller than the module minimum poll time, in which case a warning message is displayed.

When several modules are used, the minimum poll time is the greater of the minimum poll times of all modules. The default poll time (used when moodss is started) is the greater of the default poll times of all modules. The available choices in the poll time dialog box is the combination of all modules poll times.

The Poll time menu entry is available only when needed, which is not the case if all the loaded modules are asynchronous. If this case, the Options menu itself is not displayed.
This menu is only visible when not running in read-only mode (see Command line).

5.1.1.4.  Help
5.1.1.4.1. Global
This menu launches an embedded HTML viewer with this very document minus the sections related to module development.
5.1.1.4.2. Modules
This menu features one sub menu per module,  which when selected displays the module's help data.
5.1.1.4.3. Copyright
Displays the GPL (Gnu General Public License) document, a truly great idea from the Free Software Foundation.
5.1.1.4.4. Source Versions
Displays all the source code file names with their versions, in a table with sort-able columns (as in table viewers).
5.1.1.4.5. About
Displays version, author, extension authors, license and basic information about moodss.
5.1.1 Drag and drop
Drag and drop in moodss tries to behave as the now familiar Windows functionality (no, it doesn't mean I am a Bill Gates fan, as they probably stole the idea from somebody else anyway :^). For example, to create a graphical plot, one must first select one or more data cells in a data table, hold down the first mouse button (the left one for a right handed user) while dragging over to the left-most icon below the menu bar (when dragging an object, as the mouse pointer passes over possible drop sites, they are highlighted with a thin black border for user feedback). Releasing the mouse button at this time results in the creation of a BLT graph viewer.

Only valid drop sites for the data being dragged are highlighted when the mouse cursor passes over them, thus guaranteeing error free operations (if there are no bugs, that is :).

In summary, data cells can be dragged from any table or any viewer into any viewer drop site icon, any viewer or the trash can.

5.1.1.1. Drop sites
All icons right below the menu bar are valid drop sites for data cells (several may be dropped at the same time). From left to right: For example, a graph viewer with 1 curve is created by dropping 1 data cell into the graph viewer icon.

Once a viewer exists, it also acts as a drop site for data cells, which may be dragged from any table or other viewers. Dropping one or more cells directly in the viewer results in corresponding lines, bars, slices or rows being created and automatically updated. Each new graphical element is assigned a new and different color.

You may delete one or more viewer elements (graph lines, bar chart bars, pie charts slices, summary table rows or free text cell window) from a viewer by selecting them (using the first mouse button) through their labels. Several elements can be selected by depressing the control key as the first mouse button is pressed. The selection can also be extended by depressing the shift key along with the first mouse button. The pie slices can also be directly selected by clicking on the slices themselves.
Then dragging from the viewer to the trash drop site (the bullet hole) on the upper right side of the main window and releasing the first mouse button result in the corresponding viewer elements to be destroyed. When there are no remaining elements, the viewer itself (graph, bar chart, pie or summary table) can be destroyed by dropping it into the trash. The free text viewer can only be deleted this way when completely emptied of any text and data cell window.

Any viewer can be deleted in one shot by dropping from the trash icon into it.

Any viewer also acts as a drop site for viewer type data, which allows viewer mutation by just dropping from the new viewer type icon into the existing viewer. It is much quicker than destroying the existing viewer and create a new one of the new type, while remembering which data cells were monitored by the viewer of the old type.
When mutating, if some cells in the current viewer no longer exist (they may belong to a disappeared summary table), they are not made a part of the new viewer, and a warning message is flashed to the user in the message area.

5.1.1.2. Drag sites
A table is obviously a drag site. One or more cells can be dragged at once after selection, using the traditional single/shift/control mouse click technique.

Any viewer is also a drag site. It requires selecting one or more viewer elements before initiating the drag operation from any selected element in the viewer. If there are no selected elements, dragging is impossible: the mouse cursor is not changed into the drag circular cursor.

If a viewer contains no elements, then the viewer itself can be dragged and dropped into the trash.

All viewer icons (below the menu bar) are drag sites for viewer type data, which allows quick viewer mutation (see mechanism description in Drop sites).

The trash icon is also a drag site of the killing action type, which allows viewer destruction in one shot.

5.2. Command line

5.2.1. Main arguments
Launching moodss is very simple: just pass one or more data module names as parameters, as in:
    $ wish moodss random
or, for 2 modules at once:
    $ wish moodss ps cpustats
You can specify the same module more than once and with different arguments in the command line:
    $ wish moodss ps -r host.domain ps -r otherhost.domain
When several modules of the same type are passed as argument, the initial data tables feature the module name followed by a number as title. For example "ps<2>". If the module provides an identifier string, that text will be used instead, as in "ps(host.domain)". Data cell labels include the module identifier or numbered module name as well, for proper identification.

You may eventually specify a poll time in seconds using:

    $ wish moodss -p 25 random
Note that when all the specified modules are asynchronous, the poll time option specifies the preferred interval for graph viewers.

Once saved through the File Save menus (for example in save.moo) , the configuration can be retrieved using:

    $ wish moodss -f save.moo
which would result in the same modules being loaded, the same viewers displayed at the same positions and sizes, the same poll time being used, as well at the same application window size. New modules data displays can be added at any latter time to existing dashboards by specifying modules on the command line after the -f (--file) switch / value pair.

Command line options include:

Moodss command line options must appear before any module name appears on the command line, so as not to interfere with the module options.
5.2.2. Module arguments
Module themselves can take options (if programmed to do so, see module initialization), through command line arguments placed right after the module name and before the next module name, if any.

For example, the following command:

    $ moodss -p 15 random --asynchronous arp --remote jdoe@foo.bar --numeric route --numeric
causes the random module to update asynchronously, the arp module to collect data from the foo.bar host under the jdoe login name and not attempt to lookup symbolic names for hosts, with the last module route doing the same.

Note the setting the application poll time to 15 seconds does not interfere with the module options.

The moodss core checks the validity of module options according to the information provided by the module programmer. Any invalid option / value combination for the module is detected, reported on the standard error channel before the application exits. 

6. Modules

6.1. apache

Documentation.

6.2. apachex

Documentation.

6.3. arp

Documentation.

6.4. cpustats

Documentation.

6.5. diskstats

Documentation.

6.6. memstats

Documentation.

6.7. minimal

The minimal module is as bare-bone as possible and can be used as a starting point for developing new modules.

6.8. mounts

Documentation.

6.9. ps

Documentation.

6.10. random

The random module code contains a lot of comments, providing some extra documentation about module coding.
Documentation.

6.11. route

Documentation.

7. Module development

All examples are drawn from the random sample module.

7.1. source

Since a module is a package, it must have a name in the Tcl sense. For example, at the beginning of the random.tcl file, one can find the following statement:
package provide random 1.1
This line simply states that this is the 1.1 version of the random package. Please note that the package name must also be used as the module namespace name (see below).

Module names can be any combination of the following characters:

Be careful with the non-alphanumeric characters: I could not test all the possible combinations...
7.1.1. namespace
All module procedures and data are kept in a specific namespace bearing the module name. For example (from the random.tcl source file):
namespace eval random {
    array set data {
        ...
    }
    proc update {} {
        ...
    }
}
The update procedure is not needed when the module is asynchronous.
7.1.2. configuration
The module configuration defines the data table column headers, help information, ... This data never changes during the lifetime of the application.

All the module configuration data is stored as array members of the array named data within the module namespace. For example:

namespace eval random {
    array set data {
        updates 0
        0,label name 0,type ascii 0,message {user name}
        1,label cpu 1,type real 1,message {cpu usage in percent}
        2,label disk 2,type integer 2,message {disk usage in megabytes}
        3,label command 3,type dictionary 3,message {most time consuming command} 3,anchor left
        pollTimes {10 5 20 30 60 120 300}
        sort {1 decreasing}
        indexColumns {0 3}
        helpText {
This is a simple demonstration module ...
        }
    }
    ...
}
The updates member is a counter used to keep track of the number of times that the module data was updated, and is also used by the core to detect when module data display should be updated (see variable data for more information).

The columns member is obsolete and no longer used after and including moodss version 3.5.

The n,label members define the text to be displayed as column titles. There must be as many n,label members as they are columns.
The n,type members define the type of the corresponding column data. Valid types are simply those that the Tcl lsort command can handle: ascii, dictionary, integer and real. There must be as many n,type members as they are columns.
The n,message members define the text of the help message to be displayed in the message area (see User Interface) as the user moves the mouse pointer over column titles. It should be composed of only a few words, just enough to actually help the user understand what the column data means. There must be as many n,message members as they are columns.
The n,anchor member is optional. Column data is either centered by default, tucked to the left or right side of the column. Valid values are center, left or right.
Note that column numbers start at 0. There must not be any hole in the column numbers sequence.

The pollTimes member is a list of valid poll times (in seconds) for the module. The list is not ordered, as its first element represents the default poll time value to be used when the moodss application starts. This value may be overridden by a command line argument. The smallest value in the list is used by the core as the lowest possible poll time and checked against when the user enters a new value through the poll time dialog box. The list must not be empty.
Note that the list is also used by moodss as a set of possible choices in the dialog box used to set the new poll time. The user may still directly input any value as long as it is greater than or equal to the minimum value.

If the module is asynchronous (data can be updated at any time and not in response to update procedure invocations (no polling required)), the pollTimes member must be a single negative integer value representing the preferred time interval for viewers that require one (only graphs at this point). For example, if you wish graph viewers to have a display interval of 10 seconds, use:

namespace eval random {
    array set data {
        ...
        pollTimes -10
        ...
    }
    ...
}
In this case, the graph viewers time range (knowing that they feature 100 time points) would be 1000 seconds. I guess that the value that you should specify as the pollTimes member should be the expected average update interval for your asynchronous data. Note that the graphical viewers x axis always display properly labeled absolute time ticks in any case.

When several asynchronous modules are loaded with no synchronous modules, the interval used for all relevant viewers is the average (in absolute value) of all module intervals. For example, if you load 2 asynchronous modules, one with a pollTimes member of -10 and the other of -20, then a 15 seconds interval value is retained. Note that the interval can be forced through the --poll-time command line argument.

If at least one synchronous module is loaded concurrently with any number of asynchronous modules, the actual application poll time (the one that can be set with the then available poll time dialog box) is used.

The visibleColumns member is an optional list that specifies the table columns that should be displayed. If not specified, all the table columns are visible.

The sort list defines the index of the column which should be initially used as a reference for sorting the data table rows, and in which order (increasing or decreasing) the rows should be sorted. The column index for sorting works like the -index Tcl lsort command option, that is rows are sorted so that that specific column appears sorted in the specified order. The specified column must be visible (see visibleColumns member documentation above).

The indexColumns list specifies the columns required to uniquely identify a row in the table. In database talk, it represents the table key. To maintain backward compatibility, it is optional and defaults to 0, the leftmost column. The index columns are used when creating data viewer elements: their label is built by concatenating the key value for the cell row with the cell column title. The key value is the concatenation of the index column values for the cell. When specified, all the columns in the list must be visible (see visibleColumns member documentation above).

The helpText member specifies a text of any length, to be displayed when the user requests help information on the current module from within the help menu. The text can be HTML formatted or plain. HTML formatted help requires the <HTML> and <BODY> tags to be present, while tables and frames are not supported (and many other tags: stick to formatted text at the moment). The core will render HTML formatted or plain text in the module help window according to the help text contents.

The views member is optional. If specified, it defines one or more views to be used in place of the default view. One table will be displayed per view. Each view is a list of array members, suitable as an argument to an "array set" command. For each view, 2 members must be defined: visibleColumns and sort (syntax and usage are identical to the default table members).

The switches member is optional. If specified, it defines a list of switch / boolean pairs. A switch is a single letter or a string prepended with the - or + sign. The boolean value (0 or 1) specifies whether the switch takes an argument. If the switches member exists, an appropriate initialize procedure must be provided by the module (see Initialization). The core will take care of parsing the command line and reject any invalid switch / value combination for the module. The switches value may not be changed in the initialize procedure.

For example, a recent random module configuration is as follows:

array set data {
    updates 0
    0,label name 0,type ascii 0,message {user name}
    1,label cpu 1,type real 1,message {cpu usage in percent}
    2,label disk 2,type integer 2,message {disk usage in megabytes}
    3,label memory 3,type integer 3,message {memory usage in kilobytes}
    4,label command 4,type dictionary 4,message {command name}
    pollTimes {10 5 20 30 60 120 300}
    sort {1 decreasing}
    indexColumns {0 4}
    helpText {...}
    views {
        {visibleColumns {0 1 3 4} sort {1 decreasing}}
        {visibleColumns {0 2 4} sort {2 decreasing}}
    }
    switches {-a 0 --asynchronous 0}
}
In this case, data is presented in 2 tables: one for the CPU and memory usage, the other for disk usage.

The identifier member is optional. It is string that uniquely identifies this module. If set, it is displayed by the core in the initial data tables title area and data cell labels in viewers. This feature can be used for example in modules that gather data from a remote host: in such a case, the identifier could be set to dataType(hostName) (the ps module uses ps(host) string). The allowed character set for the identifier is identical to the allowed set for a module name.
Note that the identifier member is usually set in the module initialize procedure, as it usually depends on the module options.

7.1.3. Initialization
The initialize procedure, if it exists, is invoked by the core before any update occurs (when the update procedure is invoked if the module is synchronous).

In order for a module to accept command line arguments, the initialize procedure must exist (more information below). Otherwise, if the module can take no arguments, it stays optional. In that case, it is rather redundant with inline module namespace code (namespace code outside of any module namespace procedure). When the module takes no arguments, so does the initialize procedure if it exists.

The initialize procedure is mandatory when the module supports command line arguments, and in such a case takes an array name as sole argument. The array contains the switched options values, indexed by switch. For example, if the command line was:

    $ moodss random --asynchronous --other-option value -x 1234
the options array will contain:
    options(--asynchronous) =
    options(--other-option) = value
    options(-x)             = 1234
Note that the --asynchronous member value is empty as that switch takes no argument. For the above example, switches would have been defined as:
    switches {-a 0 --asynchronous 0 --other-option 1 -x 1}
In all cases, data members other than updates and switches can be set or updated in the initialize procedure, and successfully taken into account by the core.

Example for a module that take arguments:

proc initialize {optionsName} {
    upvar $optionsName options
    variable asynchronous
    variable data

    set asynchronous [info exists options(--asynchronous)]
    if {$asynchronous} {
        array set data {pollTimes -10}
    }
}
The options array is accessed using the upvar Tcl command technique (used when passing arguments (such as arrays) by name). The module namespace variable asynchronous is set according to the --asynchronous command line switch existence. Then, if operating asynchronously, a preferred poll time is set in the module data.

The core waits for the initialize procedure to be completed before initializing the next module. For modules likely to initialize slowly and/or susceptible to initialization failure, it is advised to allow the user interface to be updated using the Tcl vwait command (technique used in all the remote capable modules shipped with moodss). For example:

proc initialize {} {
    ...
    set file [open "| /usr/bin/rsh -nl $remote(user) $remote(host) cat /proc/net/arp"]
    fileevent $file readable {set ::arp::remote(busy) 0}
    vwait ::arp::remote(busy)
    ...
}
Since the file to be read sits in a remote machine on an eventual slow link, the initialize procedure is blocked until the file becomes readable, but without hanging the user interface, using the Tcl fileevent and vwait commands combination.

Similar techniques must be used in such cases within the module update procedure, so that the user interface and eventual modules running concurrently are not prevented to update. The remote capable moodss modules contain various coding techniques achieving that functionality, which I am sure you can improve on.

7.1.4. variable data
The tabular data (variable data) that the module code must update is stored in the same data array as the module configuration data.

In case of a synchronous module, the core invokes the module update procedure (which obviously must exist) when it is time to refresh the data display (tables and eventually graphical viewers). At this time, the update procedure may update the tabular data straight away (synchronous operation) or launch a request for later data update (asynchronous operation).

In case of an asynchronous module, variable data may be updated at any time. The update procedure may not exist.

For all module types, it actually does not matter when the data is updated. The core will know that fresh data is available when the updates array member is set (actually incremented as it also serves as a counter for the number of updates so far).
It is the module programmer's responsibility to increment this counter right after all tabular data has been updated.

For example, retrieving information for the processes running on a machine is a local operation that can be achieved in a reasonably small amount of time. In such a case, data would be updated immediately and the updates variable incremented at the same time.
But if the data has to be retrieved from across a network, waiting for it to come back would cause a delay that the user would certainly notice, as the application would not respond to mouse or keyboard input during the whole time that it would take to fetch the whole data. In such cases, it is easier to let the update procedure return immediately without setting the updates variable, which would be incremented at a later time, only when the data would become available. For example, when waiting for data to come across a network connection, the Tcl fileevent command could be used on a non blocking channel, where the script to be evaluated when the channel becomes readable would increment the updates array member.

For example:

namespace eval random {
    ...
    proc update {} {
        variable data
        array set data "
            0,0 john    0,1 1234 0,2 4567 0,3 cc
            1,0 william 1,1 8901 1,2 2345 1,3 xedit
            2,0 anny    2,1 6789 2,2 0123 2,3 ps
            4,0 peter   4,1 4567 4,2 8901 4,3 ls
            6,0 laura   6,1 2345 6,2 6789 6,3 emacs
            3,0 robert  3,1 1234 3,2 5678 3,3 top
        "
        incr data(updates)
    }
}
The tabular data array index is the row number followed by the column number separated by a comma. The column number must start from 0 up to the total number of columns minus 1 (no holes are allowed in the column sequence).
The row number can take any positive integer value (between 0 and 2147483647) and be defined in any order, as long as it is unique during the lifetime of the module data. If a new row is created, it must take a value that was never used: the index of a row that has disappeared cannot be reused. Row numbers need not be consecutive.

When all rows (or only those table cells that have changed) have been updated, the updates member array must be incremented so that the core knows that it can update the table data display.

The random module source code can be made to function asynchronously: please look into the random.tcl file.

7.2. installation

A module is a package in the Tcl sense. When writing a module, you must then provide a pkgIndex.tcl file along with the module code file. The pkgIndex.tcl file is very simple, as the following example shows:
package ifneeded random 1.1 "source [file join $dir random.tcl]"
The line above says that if the random package is needed, the Tcl core should source the random.tcl module source code from the directory where it was installed. 1.1 is the version number for the package.

Modules can be installed at any valid place that the Tcl core allows (look at the pkg_mkIndex manual page for more information).

When you unpack moodss, you will find the sample modules in sub directories. The current directory (.) is appended to the auto_load global list variable so that sample modules can be found when moodss is run from the unpacking directory.

For example, if you unpacked moodss in /home/joe/moodss-X.x/, you will find the random module package in /home/joe/moodss-X.x/random/ so that the following will work:

$ cd /home/joe/moodss-X.x/
$ wish moodss random
You can install your new modules in the default location: /usr/local/lib/ on Unix. For example, if you move the files in /home/joe/moodss-X.x/random/ to /usr/local/lib/random/, moodss (actually Tcl :^) will still be able to find the random module (again, look at the pkg_mkIndex manual page for more information).

Please take a look at the INSTALL file for the latest information on how to install the moodss application itself.

7.3 Displaying messages

You may want to inform the user of the module activity. You may use the message area (called the messenger, across the bottom of the application main window) through the following API:
pushMessage "your message..."
popMessage
flashMessage "your message..." numberOfSeconds
Your message can be any kind of string (1 line only: it should fit in a reasonably wide main window), and numberOfSeconds being optional and defaulting to 1. One should use the message facilities with discretion, as it is already used by the application core for informing the user of modules loading, initialization, updates, context sensitive help, ... My advice is to use it for important messages or errors pertinent to the module itself. Also, the module name should appear at the beginning of the messages for identification, since multiple modules can be loaded concurrently.

Displaying separate (toplevel) message windows using Tk is of course always possible (if you load Tk in the module). Keep in mind that the core, not being aware of the module event that has taken place, will continue to invoke the module update procedure (unless the module is asynchronous, of course). In such a case, you may want to use an internal (to the module) busy flag so that the update procedure immediately returns until the user acknowledges the informational message. Other strategies are of course possible (let me know if you have one that you think should appear here as an example).

7.4. Tips and tricks

7.4.1. Single row tables
When data to be displayed is an array of unrelated values, the only solution is to organize the data in a table with a single row and 1 column per value (which does not prevent the use of several views for displaying the data).

In such a case, no index column is required and as a matter of fact rather gets in the way. The trick is to use an empty column 0, use as index by the core by default, and to prevent it from being displayed by using 1 or more views.

Please look at the cpustats module code for a working example.

7.4.2. Void values as numbers
In the module data array, you cannot leave or set a numeric cell empty when no data is available. Use the 000000 (6 zeros) void pattern (also used in summary tables) instead.

8. Future developments

The following features will eventually be added to the core (also look at the TODO file): I welcome any suggestion for new features that you may need in your specific use of moodss.

9. Miscellaneous information

For downloading Tcl software (such as stooop, scwoop, tkpiechart, ...), visit my web page.

Send your comments, complaints, ... to Jean-LucFontaine.