MODULE WMPerfMonPlugins;
IMPORT
KernelLog, Machine, Objects, Kernel, Modules, Heaps, Commands, Plugins,
Configuration, Strings, WMDiagramComponents, WMGraphics, Events,
XML, XMLObjects;
CONST
EventPluginsChanged* = 0;
EventPerfUpdate* = 1;
EventParametersChanged* = 2;
EventSampleLoopDone* = 3;
DefaultSampleBufferSize = 10;
DefaultSampleInterval = 50;
DefaultScreenRefresh = 500;
Hidden* = WMDiagramComponents.Hidden;
Sum* = WMDiagramComponents.Sum;
Maximum* = WMDiagramComponents.Maximum;
Standalone* =WMDiagramComponents.Standalone;
Verbose = FALSE;
TYPE
Name* = ARRAY 32 OF CHAR;
Description* = ARRAY 128 OF CHAR;
DeviceName* = ARRAY 128 OF CHAR;
Dataset* = WMDiagramComponents.Dataset;
DatasetDescriptor* = WMDiagramComponents.DatasetDescriptor;
PluginLoader = PROCEDURE;
TYPE
Parameter* = POINTER TO RECORD;
name* : Name; description* : Description;
devicename* : DeviceName;
modulename* : ARRAY 128 OF CHAR;
datasetDescriptor* : WMDiagramComponents.DatasetDescriptor;
noSuperSampling* : BOOLEAN;
min*, max* : LONGINT;
autoMin*, autoMax* : BOOLEAN;
unit* : ARRAY 16 OF CHAR;
perSecond* : BOOLEAN;
scale* : REAL;
minDigits*, fraction* : LONGINT;
statsUnit* : ARRAY 16 OF CHAR;
showPercent* : BOOLEAN;
showSum* : BOOLEAN;
hide* : BOOLEAN;
helper* : Helper;
END;
TYPE
Plugin* = OBJECT
VAR
dataset- : Dataset;
p- : Parameter;
datamodel- : WMDiagramComponents.MultiPointModel;
active : BOOLEAN;
nbrOfClients : LONGINT;
currentDataset : Dataset;
sample, nbrOfSamples, nbrOfValidSamples : LONGINT;
samples : POINTER TO ARRAY OF Dataset;
milliTimer : Kernel.MilliTimer;
lastDataset, temp : Dataset;
isFirstUpdate : BOOLEAN;
dimensions : LONGINT;
link : Plugin;
PROCEDURE UpdateDataset*;
BEGIN
HALT(301);
END UpdateDataset;
PROCEDURE Init*(p : Parameter);
BEGIN
HALT(301);
END Init;
PROCEDURE IncNbrOfClients*;
BEGIN {EXCLUSIVE}
IF (nbrOfClients = 0) THEN
IF ~IsActive() THEN
SetActive(TRUE);
END;
END;
INC(nbrOfClients);
END IncNbrOfClients;
PROCEDURE DecNbrOfClients*;
BEGIN {EXCLUSIVE}
DEC(nbrOfClients);
IF nbrOfClients < 0 THEN
KernelLog.String("WMPerfMonPlugins: Warning: NbrOfClients < 0"); KernelLog.Ln;
nbrOfClients := 0;
END;
IF (nbrOfClients = 0) THEN
IF IsActive() THEN
SetActive(FALSE);
END;
END;
END DecNbrOfClients;
PROCEDURE SetActive*(active : BOOLEAN);
BEGIN
IF active THEN
UpdateDataset;
CopyDataset(dataset, lastDataset);
SELF.active := TRUE;
ELSE
SELF.active := FALSE;
END;
updater.NotifyListeners({EventPluginsChanged}, 0);
END SetActive;
PROCEDURE IsActive*() : BOOLEAN;
BEGIN
RETURN active;
END IsActive;
PROCEDURE Reset*;
BEGIN
datamodel.Acquire; datamodel.Reset; datamodel.Release;
END Reset;
PROCEDURE SetSampleBufferSize*(size : LONGINT);
VAR i : LONGINT;
BEGIN
IF p.noSuperSampling THEN size := 1; END;
sample := 0; nbrOfValidSamples := 0;
nbrOfSamples := size;
NEW(samples, nbrOfSamples);
FOR i := 0 TO nbrOfSamples-1 DO NEW(samples[i], dimensions); END;
END SetSampleBufferSize;
PROCEDURE Finalize*;
BEGIN
updater.RemovePlugin(SELF);
END Finalize;
PROCEDURE Update;
VAR i, dim, dTime : LONGINT; sum: REAL;
BEGIN
dTime := Kernel.Elapsed(milliTimer);
Kernel.SetTimer(milliTimer, 0);
UpdateDataset;
IF ~isFirstUpdate THEN
IF p.perSecond & (dTime # 0) THEN
FOR dim := 0 TO dimensions-1 DO
temp[dim] := (dataset[dim] - lastDataset[dim]) * (Kernel.Second / dTime);
lastDataset[dim] := dataset[dim]; dataset[dim] := temp[dim];
END;
END;
IF nbrOfValidSamples < nbrOfSamples THEN INC(nbrOfValidSamples); END;
FOR dim := 0 TO dimensions-1 DO
samples[sample][dim] := dataset[dim];
sum := 0;
FOR i := 0 TO nbrOfValidSamples-1 DO sum := sum + samples[i][dim]; END;
currentDataset[dim] := sum / nbrOfValidSamples;
END;
sample := (sample + 1) MOD nbrOfSamples;
ELSE
isFirstUpdate := FALSE;
END;
END Update;
PROCEDURE UpdateScreen;
BEGIN
datamodel.Acquire; datamodel.PutValues(currentDataset); datamodel.Release;
END UpdateScreen;
PROCEDURE CopyDataset(source : Dataset; VAR target : Dataset);
VAR dim : LONGINT;
BEGIN
FOR dim := 0 TO dimensions-1 DO target[dim] := source[dim]; END;
END CopyDataset;
PROCEDURE Show;
BEGIN
KernelLog.String(p.name); KernelLog.String(" ("); KernelLog.String(p.description); KernelLog.String(")");
IF p.devicename # "" THEN KernelLog.String(" on "); KernelLog.String(p.devicename); END;
IF p.modulename # "" THEN KernelLog.String(" defined in "); KernelLog.String(p.modulename); END; KernelLog.Char(" ");
IF active THEN KernelLog.String("[active]"); END;
IF p.hide THEN KernelLog.String("[hidden]"); END;
END Show;
PROCEDURE EvaluateParameter(p : Parameter);
CONST Decrement = 35;
VAR r, g, b, a, i, round : LONGINT;
BEGIN
IF (p.scale = 0) & (p.statsUnit = "") THEN COPY(p.unit, p.statsUnit); END;
IF p.scale = 0 THEN p.scale := 1.0; END;
IF p.datasetDescriptor = NIL THEN
NEW(p.datasetDescriptor, 1);
p.datasetDescriptor[0].name := "Default";
p.datasetDescriptor[0].color := WMGraphics.Red;
dimensions := 1;
ELSE
IF (LEN(p.datasetDescriptor) > 0) & (p.datasetDescriptor[0].color = 0) THEN p.datasetDescriptor[0].color := WMGraphics.Yellow; END;
IF (LEN(p.datasetDescriptor) > 1) & (p.datasetDescriptor[1].color = 0) THEN p.datasetDescriptor[1].color := WMGraphics.Green; END;
IF (LEN(p.datasetDescriptor) > 2) & (p.datasetDescriptor[2].color = 0) THEN p.datasetDescriptor[2].color := WMGraphics.Red; END;
IF (LEN(p.datasetDescriptor) > 3) THEN
round := 0;
r := 255; g := 255; b := 255; a := 200;
FOR i := 3 TO LEN(p.datasetDescriptor)-1 DO
IF round = 0 THEN
p.datasetDescriptor[i].color := WMGraphics.RGBAToColor(r, g, b, a);
ELSIF round = 1 THEN
p.datasetDescriptor[i].color := WMGraphics.RGBAToColor(g, r, b, a);
ELSE
p.datasetDescriptor[i].color := WMGraphics.RGBAToColor(b, g, r, a);
END;
IF (r - Decrement > 0) THEN DEC(r, Decrement);
ELSIF (g - 2*Decrement > 0) THEN DEC(g, Decrement);
ELSIF (b - 3*Decrement > 0) THEN DEC(b, Decrement);
ELSE
INC(round);
r := 255; g := 255; b := 255;
END;
END;
END;
dimensions := LEN(p.datasetDescriptor);
END;
NEW(datamodel, 1024, dimensions);
datamodel.SetDescriptor(p.datasetDescriptor);
END EvaluateParameter;
PROCEDURE &New*(p : Parameter);
BEGIN
ASSERT(p # NIL); SELF.p := p; active := FALSE;
isFirstUpdate := TRUE;
Kernel.SetTimer(milliTimer, 0);
Init(p);
EvaluateParameter(p);
NEW(temp, dimensions);
NEW(dataset, dimensions); NEW(lastDataset, dimensions); NEW(currentDataset, dimensions);
updater.AddPlugin(SELF);
END New;
END Plugin;
TYPE
Helper* = OBJECT
VAR
next : Helper;
updated : BOOLEAN;
PROCEDURE Update*;
BEGIN
HALT(301);
END Update;
END Helper;
TYPE
Notifier = PROCEDURE {DELEGATE} (events : SET; perf : REAL);
Notifiers = POINTER TO RECORD
events : SET;
proc : Notifier;
next : Notifiers;
END;
PluginArray* = POINTER TO ARRAY OF Plugin;
Updater = OBJECT
VAR
sampleInterval- : LONGINT;
sampleBufferSize- : LONGINT;
screenInterval- : LONGINT;
plugins : Plugin;
notifiers : Notifiers;
lastCycles, lastTimestamp : HUGEINT;
sample : LONGINT; sampleBuffer : POINTER TO ARRAY OF REAL;
me : Objects.Process;
milliTimer : Kernel.MilliTimer;
left, samplingLeft : LONGINT;
screenTimer : Kernel.MilliTimer;
alive, dead : BOOLEAN;
timer : Kernel.Timer;
PROCEDURE AddListener*(events : SET; proc : Notifier);
VAR nr : Notifiers;
BEGIN {EXCLUSIVE}
ASSERT(proc # NIL);
NEW(nr); nr.proc := proc; nr.events := events;
nr.next := notifiers.next; notifiers.next := nr;
END AddListener;
PROCEDURE RemoveListener*(proc : Notifier);
VAR n : Notifiers;
BEGIN {EXCLUSIVE}
n := notifiers;
WHILE n.next # NIL DO
IF (n.next.proc = proc) THEN
n.next := n.next.next;
ELSE
n := n.next;
END;
END;
END RemoveListener;
PROCEDURE NotifyListeners*(events : SET; perf : REAL);
VAR n : Notifiers;
BEGIN
n := notifiers.next;
WHILE n # NIL DO
IF n.events * events # {} THEN
n.proc(events, perf);
END;
n := n.next;
END;
END NotifyListeners;
PROCEDURE GetByFullname*(CONST fullname : ARRAY OF CHAR; VAR index : LONGINT; VAR msg : ARRAY OF CHAR) : Plugin;
VAR plugin : Plugin; sa : Strings.StringArray; name : Name; i : LONGINT;
BEGIN {EXCLUSIVE}
msg := "";
sa := Strings.Split(fullname, ".");
IF LEN(sa) = 2 THEN
COPY(sa[0]^, name);
plugin := GetByNameX(name, "");
IF plugin # NIL THEN
i := 0; WHILE (i < LEN(plugin.p.datasetDescriptor)) & (plugin.p.datasetDescriptor[i].name # sa[1]^) DO INC(i); END;
IF (i < LEN(plugin.p.datasetDescriptor)) THEN
index := i;
ELSE
plugin := NIL;
msg := "Data not found";
END;
ELSE
msg := "Plugin not found";
END;
ELSE
msg := "Incorrect fullname";
END;
RETURN plugin;
END GetByFullname;
PROCEDURE GetByName*(CONST name : Name; CONST devicename : DeviceName) : Plugin;
BEGIN {EXCLUSIVE}
RETURN GetByNameX(name, devicename);
END GetByName;
PROCEDURE GetByNameX(CONST name : Name; CONST devicename : DeviceName) : Plugin;
VAR p : Plugin;
BEGIN
p := plugins;
WHILE p # NIL DO
IF (p.p.name = name) & (p.p.devicename = devicename) THEN
RETURN p;
END;
p := p.link;
END;
RETURN NIL;
END GetByNameX;
PROCEDURE RemoveByName*(CONST name : Name; CONST devicename : DeviceName);
VAR p : Plugin;
BEGIN {EXCLUSIVE}
p := plugins; WHILE (p # NIL) & ~((p.p.name = name) & (p.p.devicename = devicename)) DO p := p.link; END;
IF p # NIL THEN RemovePluginIntern(p);
ELSE KernelLog.String("WMCounters: Could not remove plugin "); KernelLog.String(name); KernelLog.Ln;
END;
END RemoveByName;
PROCEDURE RemoveByModuleName*(CONST modulename : ARRAY OF CHAR);
VAR p : Plugin; removed : BOOLEAN;
BEGIN {EXCLUSIVE}
LOOP
removed := FALSE;
p := plugins; WHILE (p # NIL) & ~(p.p.modulename = modulename) DO p := p.link; END;
IF p # NIL THEN
removed := TRUE;
RemovePluginIntern(p);
END;
IF removed = FALSE THEN EXIT; END;
END;
END RemoveByModuleName;
PROCEDURE RemovePlugin*(p : Plugin);
BEGIN {EXCLUSIVE}
RemovePluginIntern(p);
END RemovePlugin;
PROCEDURE RemovePluginIntern(p : Plugin);
VAR temp : Plugin; removed : BOOLEAN;
BEGIN
IF Verbose THEN KernelLog.String("WMPerfMon: Removing counter "); p.Show;KernelLog.Ln; END;
IF plugins = p THEN
plugins := plugins.link; removed := TRUE;
ELSE
temp := plugins; WHILE(temp # NIL) & (temp.link # p) DO temp := temp.link; END;
IF temp # NIL THEN
temp.link := temp.link.link;
removed := TRUE;
END;
END;
IF removed THEN
NotifyListeners({EventPluginsChanged}, 0);
DEC(NnofPlugins);
IF p.p.datasetDescriptor # NIL THEN
DEC(NnofValues, LEN(p.p.datasetDescriptor));
ELSE
DEC(NnofValues);
END;
END;
END RemovePluginIntern;
PROCEDURE GetPlugins*() : PluginArray;
VAR p : Plugin; nbrOfPlugins, i : LONGINT; ca : PluginArray;
BEGIN {EXCLUSIVE}
IF plugins # NIL THEN
p := plugins; nbrOfPlugins := 0;
WHILE p # NIL DO
IF ~p.p.hide THEN INC(nbrOfPlugins); END;
p := p.link;
END;
NEW(ca, nbrOfPlugins);
p := plugins; i := 0;
WHILE p # NIL DO
IF ~p.p.hide THEN ca[i] := p; INC(i); END;
p := p.link;
END;
END;
RETURN ca;
END GetPlugins;
PROCEDURE ClearAll*;
VAR p : Plugin;
BEGIN {EXCLUSIVE}
p := plugins; WHILE p # NIL DO p.Reset; p := p.link; END;
END ClearAll;
PROCEDURE Show;
VAR p : Plugin;
BEGIN {EXCLUSIVE}
KernelLog.String("WMPerfMon: ");
IF plugins = NIL THEN
KernelLog.String("No counters installed."); KernelLog.Ln;
ELSE
KernelLog.Ln;
p := plugins; WHILE p # NIL DO p.Show; KernelLog.Ln; p := p.link; END;
END;
END Show;
PROCEDURE SetIntervals*(VAR sampleInterval, sampleBufferSize, screenInterval : LONGINT);
VAR p : Plugin;
BEGIN {EXCLUSIVE}
IF sampleInterval < 1 THEN sampleInterval := 1; END;
IF screenInterval < sampleInterval THEN screenInterval := sampleInterval; END;
IF sampleBufferSize < screenInterval DIV sampleInterval THEN
sampleBufferSize := screenInterval DIV sampleInterval;
END;
ASSERT(sampleBufferSize > 0);
SELF.sampleInterval := sampleInterval;
SELF.screenInterval := screenInterval;
IF sampleBufferSize # SELF.sampleBufferSize THEN
SELF.sampleBufferSize := sampleBufferSize;
p := plugins; WHILE p # NIL DO p.SetSampleBufferSize(sampleBufferSize); p := p.link; END;
NEW(sampleBuffer, sampleBufferSize);
END;
NotifyListeners({EventParametersChanged}, 0);
Kernel.SetTimer(screenTimer, 1);
timer.Wakeup;
END SetIntervals;
PROCEDURE AddPlugin(plugin : Plugin);
VAR p : Plugin;
BEGIN {EXCLUSIVE}
IF Verbose THEN KernelLog.String("WMPerfMon: Adding counter "); plugin.Show; KernelLog.Ln; END;
plugin.link := NIL;
IF (plugins = NIL) THEN
plugins := plugin;
ELSE
p := plugins; WHILE (p.link # NIL) DO p := p.link; END;
p.link := plugin;
END;
plugin.SetSampleBufferSize(sampleBufferSize);
INC(NnofPlugins);
IF plugin.p.datasetDescriptor # NIL THEN
INC(NnofValues, LEN(plugin.p.datasetDescriptor));
ELSE
INC(NnofValues);
END;
NotifyListeners({EventPluginsChanged}, 0);
END AddPlugin;
PROCEDURE UpdatePlugin(p : Plugin);
BEGIN
IF p.p.helper # NIL THEN UpdateHelpers(p.p.helper); END;
p.Update;
END UpdatePlugin;
PROCEDURE UpdatePlugins(screen : BOOLEAN);
VAR p : Plugin;
BEGIN {EXCLUSIVE}
p := plugins;
WHILE alive & (p # NIL) DO
IF p.active THEN
IF screen THEN
IF p.p.noSuperSampling THEN
UpdatePlugin(p);
END;
p.UpdateScreen;
ELSE
IF ~p.p.noSuperSampling THEN
UpdatePlugin(p);
END;
END;
END;
p := p.link;
END;
END UpdatePlugins;
PROCEDURE UpdateHelpers(h : Helper);
BEGIN
WHILE alive & (h # NIL) DO
IF ~h.updated THEN h.Update; h.updated := TRUE; END;
h := h.next;
END;
END UpdateHelpers;
PROCEDURE ResetHelpers;
VAR p : Plugin; h : Helper;
BEGIN
p := plugins;
WHILE p # NIL DO
h := p.p.helper;
WHILE h # NIL DO
h.updated := FALSE;
h := h.next;
END;
p := p.link;
END;
END ResetHelpers;
PROCEDURE UpdatePerf;
VAR timestamp, cycles : HUGEINT; cpuCycles : Objects.CpuCyclesArray; i : LONGINT; value, sum : REAL;
BEGIN {EXCLUSIVE}
timestamp := Machine.GetTimer();
IF lastTimestamp # 0 THEN
Objects.GetCpuCycles(me, cpuCycles, TRUE);
FOR i := 0 TO LEN(cpuCycles)-1 DO INC (cycles , cpuCycles[i]); END;
value := SHORT(100.0 * Machine.HIntToLReal(cycles - lastCycles) / Machine.HIntToLReal(timestamp - lastTimestamp));
sampleBuffer[sample MOD sampleBufferSize] := value; INC(sample);
lastCycles := cycles;
FOR i := 0 TO sampleBufferSize-1 DO sum := sum + sampleBuffer[i]; END;
value := sum / sampleBufferSize;
NotifyListeners({EventPerfUpdate}, value);
END;
lastTimestamp := timestamp;
END UpdatePerf;
PROCEDURE Terminate;
BEGIN
alive := FALSE; timer.Wakeup;
BEGIN {EXCLUSIVE} AWAIT(dead); END;
END Terminate;
PROCEDURE &New*;
BEGIN
NEW(timer); alive := TRUE; dead := FALSE;
sampleInterval := DefaultSampleInterval;
sampleBufferSize := DefaultSampleBufferSize;
screenInterval := DefaultScreenRefresh;
NEW(sampleBuffer, sampleBufferSize);
NEW(notifiers);
END New;
BEGIN {ACTIVE, PRIORITY(Objects.High)}
me := Objects.CurrentProcess();
Kernel.SetTimer(screenTimer, screenInterval);
WHILE alive DO
Kernel.SetTimer(milliTimer, sampleInterval);
(* Sampling: sample values of all plugins *)
UpdatePlugins(FALSE);
ResetHelpers;
samplingLeft := Kernel.Left(milliTimer); IF samplingLeft < 0 THEN samplingLeft := 0; END;
left := Kernel.Left(screenTimer); IF (left < 0) THEN left := 0; END;
IF left <= samplingLeft THEN (* screen refresh before next sample update (or now) *)
IF left > 0 THEN timer.Sleep(left); END;
UpdatePlugins(TRUE);
Kernel.SetTimer(screenTimer, screenInterval);
END;
NotifyListeners({EventSampleLoopDone}, 0);
(* Determine how much cpu cycles have been used by this process *)
UpdatePerf;
samplingLeft := Kernel.Left(milliTimer);
IF alive & (samplingLeft > 0) THEN timer.Sleep(samplingLeft); END;
END;
BEGIN {EXCLUSIVE} dead := TRUE; END;
END Updater;
VAR
updater- : Updater;
NnofPlugins-, NnofValues- : LONGINT;
PROCEDURE EstimateCpuClockrate*(VAR clockrate : LONGINT) : BOOLEAN;
VAR
timer : Kernel.Timer; milliTimer : Kernel.MilliTimer;
startTime, endTime, timeDiff : HUGEINT;
nbrOfGcRuns : LONGINT;
BEGIN
NEW(timer); nbrOfGcRuns := Heaps.Ngc;
Kernel.SetTimer(milliTimer, 1000);
startTime := Machine.GetTimer();
WHILE ~Kernel.Expired(milliTimer) DO
timer.Sleep(1);
IF nbrOfGcRuns # Heaps.Ngc THEN RETURN FALSE; END;
END;
endTime := Machine.GetTimer();
IF nbrOfGcRuns # Heaps.Ngc THEN RETURN FALSE; END;
timeDiff := endTime - startTime;
clockrate := SHORT (Machine.DivH(timeDiff, 1000*1000));
RETURN TRUE;
END EstimateCpuClockrate;
PROCEDURE CyclesToMs*(cycles : HUGEINT; mhz : LONGINT) : LONGINT;
BEGIN
RETURN SHORT (Machine.DivH(cycles, 1000*mhz));
END CyclesToMs;
PROCEDURE MsToString*(ms : LONGINT; VAR string : ARRAY OF CHAR);
CONST Day=24*60*60*1000; Hour = 60*60*1000; Minute = 60*1000; Second = 1000; Millisecond = 1;
PROCEDURE Append(divisor : LONGINT; CONST unit : ARRAY OF CHAR);
VAR nbr : ARRAY 16 OF CHAR; val : LONGINT;
BEGIN
val := ms DIV divisor; ms := ms MOD divisor;
Strings.IntToStr(val, nbr); Strings.Append(string, nbr); Strings.Append(string, unit);
END Append;
BEGIN
string := "";
IF ms >= Minute THEN
IF ms >= Day THEN Append(Day, "d "); END;
IF ms >= Hour THEN Append(Hour, "h "); END;
IF ms >= Minute THEN Append(Minute, "m "); END;
IF ms >= Second THEN Append(Second, "s"); END;
ELSE
Append(Second, "."); Append(100, ""); Append(10, ""); Append(Millisecond, "s");
END;
END MsToString;
PROCEDURE GetNameDesc*(plugin : Plugins.Plugin; VAR devicename : DeviceName);
BEGIN
COPY(plugin.name, devicename);
Strings.Append(devicename, " ("); Strings.Append(devicename, plugin.desc); Strings.Append(devicename, ")");
END GetNameDesc;
PROCEDURE Show*(context : Commands.Context);
BEGIN
updater.Show;
END Show;
PROCEDURE LoadPlugin(CONST name : ARRAY OF CHAR);
VAR loaderProc : PluginLoader; msg : Events.Message;
BEGIN
GETPROCEDURE(name, "Install", loaderProc);
IF (loaderProc # NIL) THEN
loaderProc;
ELSE
msg := "Could not load plugin "; Strings.Append(msg, name); Strings.Append(msg, " - Install command not found");
Events.AddEvent("WMPerfMonPlugins", Events.Error, 2, 1, 0, msg, TRUE);
END;
END LoadPlugin;
PROCEDURE LoadConfiguration;
VAR elem : XML.Element; enum : XMLObjects.Enumerator; ptr : ANY; string : XML.String;
BEGIN
elem := Configuration.GetSection("Applications.Performance Monitor.Plugins");
IF elem # NIL THEN
enum := elem.GetContents(); enum.Reset;
WHILE enum.HasMoreElements() DO
ptr := enum.GetNext();
IF ptr IS XML.Element THEN
string := ptr(XML.Element).GetAttributeValue("value");
IF (string # NIL) THEN
IF Strings.Match("TRUE", string^) THEN
string := ptr(XML.Element).GetAttributeValue("name");
IF (string # NIL) THEN
LoadPlugin(string^);
END;
END;
END;
END;
END;
ELSE KernelLog.String("WMPerfMon: Warning: Section 'Applications.Performance Monitor.Plugins' not found in config file."); KernelLog.Ln;
END;
END LoadConfiguration;
PROCEDURE Cleanup;
BEGIN
updater.Terminate;
END Cleanup;
BEGIN
Modules.InstallTermHandler(Cleanup);
NEW(updater);
LoadConfiguration;
END WMPerfMonPlugins.