WMPerfMonPlugins.Mod 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. MODULE WMPerfMonPlugins; (** AUTHOR "staubesv"; PURPOSE "Performance Monitor plugin framework"; *)
  2. (**
  3. * The idea behind the Peformance Monitor plugin framework is to facilitate simple performance monitoring / measurement tasks by
  4. * providing some commonly used services as:
  5. * - Sampling (incl. averaging, simple statistics, configurable sampling intervals and sample buffers)
  6. * - Visualization of the sampled values / statistics
  7. * - Managing self-describing Performance Monitor plugins and providing access to them
  8. *
  9. * This module provides:
  10. * - Plugin interface implemented by actual Performance Monitor plugins
  11. * - Helper objects that provide access to sampled value to multiple plugins
  12. * - Updater object that performs the actual sampling in a single loop for all active Plugins
  13. *
  14. * Usage: WMPerfMonPlugins.Show ~ displays a list of all registered plugins System.Free WMPerfMonPlugins ~
  15. *
  16. * Possible improvements:
  17. * - Use low-level timer (Machine.GetTimer) everywhere since Objects.ticks is inaccurate/not reliable
  18. *
  19. * History:
  20. *
  21. * 16.02.2006 First Release (staubesv)
  22. * 23.06.2006 Adapted to WMDiagramComponents, support for multidimensional data visualization (staubesv)
  23. * 19.07.2006 Added EstimateCpuClockrate procedure, load MessagesPlugin per default, plugins can optionally bypass supersampling (staubesv)
  24. * 24.07.2006 Added CyclesToMs, MsToString procedures (staubesv)
  25. * 28.07.2006 Moved GUI-related code to WMPerfMonComponents (staubesv)
  26. * 16.01.2006 Updater.SetIntervals also clears screenTimer to force appliation of new value, no upper bounds for sample interval,
  27. * only consider valid samples when averaging (staubesv)
  28. * 26.02.2007 Added Updater.RemoveByModuleName, generate colors for datasets with more than 3 datas, configuration in config file (staubesv)
  29. *)
  30. IMPORT
  31. KernelLog, Machine, Objects, Kernel, Modules, Heaps, Commands, Plugins,
  32. Configuration, Strings, WMDiagramComponents, WMGraphics, Events,
  33. XML, XMLObjects;
  34. CONST
  35. EventPluginsChanged* = 0; (** Plugins have been added, removed, activated or deactivated *)
  36. EventPerfUpdate* = 1; (** Updater performance has been updated *)
  37. EventParametersChanged* = 2; (** Sampling parameters have changed *)
  38. EventSampleLoopDone* = 3; (** All plugin values have been updated *)
  39. (* How many samples should be used for averaging? *)
  40. DefaultSampleBufferSize = 10; (* samples *)
  41. DefaultSampleInterval = 50; (* ms *)
  42. DefaultScreenRefresh = 500; (* ms *)
  43. (* DataDescriptor.flags *)
  44. Hidden* = WMDiagramComponents.Hidden; (* Don't visualize! *)
  45. Sum* = WMDiagramComponents.Sum; (* This value is the sum of all other values in the same dataset *)
  46. Maximum* = WMDiagramComponents.Maximum; (* This value is the maximum that other values in the same dataset can reach *)
  47. Standalone* =WMDiagramComponents.Standalone; (* Indicate that this value is not affected by Sum/Maximum of dataset *)
  48. (* Display registering/unregistering of plugins *)
  49. Verbose = FALSE;
  50. TYPE
  51. Name* = ARRAY 32 OF CHAR;
  52. Description* = ARRAY 128 OF CHAR;
  53. DeviceName* = ARRAY 128 OF CHAR;
  54. Dataset* = WMDiagramComponents.Dataset;
  55. DatasetDescriptor* = WMDiagramComponents.DatasetDescriptor;
  56. PluginLoader = PROCEDURE;
  57. TYPE
  58. (** Plugin parameters. *)
  59. (** A parameter object must be passed as argument for the plugin object's constructor. The fields *)
  60. (** may be set later in the Init procedure but shall not be modified after the Init procedure. *)
  61. Parameter* = POINTER TO RECORD;
  62. (** Name and description of the plugin *)
  63. name* : Name; description* : Description;
  64. (** Name of the monitored device if applicable. Default = "" (not applicable) *)
  65. devicename* : DeviceName;
  66. (** Module where counter is implemented *)
  67. modulename* : ARRAY 128 OF CHAR;
  68. (** Describes names and colors of sampled values *)
  69. datasetDescriptor* : WMDiagramComponents.DatasetDescriptor;
  70. (** If TRUE, the plugin will only be sampled when a screen update occurs. If FALSE, the plugin *)
  71. (* will be sampled at the sample rate specified by the Updater object. The average of all *)
  72. (* samples in the sample buffer will then be displayed *)
  73. (* Default: FALSE *)
  74. noSuperSampling* : BOOLEAN;
  75. (** Minimum and maximum value the UpdateCounter procedure will return *)
  76. (** Defaults: min = 0; max = 0 (no max) *)
  77. min*, max* : LONGINT;
  78. (** Should the diagram panel automatically detect a min/max value? *)
  79. (** Defaults: autoMin = FALSE; autoMax = FALSE *)
  80. autoMin*, autoMax* : BOOLEAN;
  81. (** Unit of the counter ("/s" will be appended automatically if the field 'perSecond' is TRUE) *)
  82. (** Default: "" (None) *)
  83. unit* : ARRAY 16 OF CHAR;
  84. (** Shall the counter be interpreted as change per second? Default: FALSE *)
  85. perSecond* : BOOLEAN;
  86. (** Statistic gathering related parameters *)
  87. (** The stats value will be computed as UpdateCounter() * scale *)
  88. (** Default: 0.0 (is interpreted as 1.0 -> don't scale) *)
  89. scale* : REAL;
  90. (** Minimum number of digits, number of fraction digits *)
  91. (** Defaults: minDigits = 0; fraction = 0; *)
  92. minDigits*, fraction* : LONGINT;
  93. (** Unit of the statistics values ("/s" will be appended automatically if 'perSecond' is TRUE) *)
  94. (** Default: "" (None) *)
  95. statsUnit* : ARRAY 16 OF CHAR;
  96. (** If TRUE and 'max' # 0, the statistics values will also be shown as percent of 'max' *)
  97. (** Default: FALSE *)
  98. showPercent* : BOOLEAN;
  99. (** If TRUE and ~perSecond, the sum of all values will be displayed. Default: FALSE *)
  100. showSum* : BOOLEAN;
  101. (** Shall the counter be hidden/excluded from updater.GetPlugins & SelectionWindow? *)
  102. (** Default: FALSE *)
  103. hide* : BOOLEAN;
  104. (** Optional list of helper objects *)
  105. helper* : Helper;
  106. END;
  107. TYPE
  108. Plugin* = OBJECT
  109. VAR
  110. (** Plugins update this data set in the UpdateDataset procedure *)
  111. dataset- : Dataset;
  112. (** Parameters mustn't be changed after a plugin instance is created *)
  113. p- : Parameter;
  114. datamodel- : WMDiagramComponents.MultiPointModel;
  115. active : BOOLEAN; (* Update counter / display? *)
  116. nbrOfClients : LONGINT;
  117. currentDataset : Dataset; (* Most recent averaged values *)
  118. sample, nbrOfSamples, nbrOfValidSamples : LONGINT;
  119. samples : POINTER TO ARRAY OF Dataset;
  120. milliTimer : Kernel.MilliTimer;
  121. lastDataset, temp : Dataset;
  122. isFirstUpdate : BOOLEAN;
  123. dimensions : LONGINT;
  124. link : Plugin;
  125. (** Plugin interface to be implemented *)
  126. (** Update Plugin.dataset value(s) *)
  127. PROCEDURE UpdateDataset*;
  128. BEGIN
  129. HALT(301); (* abstract *)
  130. END UpdateDataset;
  131. (* Called by constructor after panel has been created for plugin specific initialization *)
  132. PROCEDURE Init*(p : Parameter);
  133. BEGIN
  134. HALT(301); (* abstract *)
  135. END Init;
  136. PROCEDURE IncNbrOfClients*;
  137. BEGIN {EXCLUSIVE}
  138. IF (nbrOfClients = 0) THEN
  139. IF ~IsActive() THEN
  140. SetActive(TRUE);
  141. END;
  142. END;
  143. INC(nbrOfClients);
  144. END IncNbrOfClients;
  145. PROCEDURE DecNbrOfClients*;
  146. BEGIN {EXCLUSIVE}
  147. DEC(nbrOfClients);
  148. IF nbrOfClients < 0 THEN
  149. KernelLog.String("WMPerfMonPlugins: Warning: NbrOfClients < 0"); KernelLog.Ln;
  150. nbrOfClients := 0;
  151. END;
  152. IF (nbrOfClients = 0) THEN
  153. IF IsActive() THEN
  154. SetActive(FALSE);
  155. END;
  156. END;
  157. END DecNbrOfClients;
  158. (** Active sampling for this plugin *)
  159. PROCEDURE SetActive*(active : BOOLEAN);
  160. BEGIN
  161. IF active THEN
  162. UpdateDataset;
  163. CopyDataset(dataset, lastDataset);
  164. SELF.active := TRUE;
  165. ELSE
  166. SELF.active := FALSE;
  167. END;
  168. updater.NotifyListeners({EventPluginsChanged}, 0);
  169. END SetActive;
  170. (** Is sampling activated for this plugin? *)
  171. PROCEDURE IsActive*() : BOOLEAN;
  172. BEGIN
  173. RETURN active;
  174. END IsActive;
  175. (** Reset data model *)
  176. PROCEDURE Reset*;
  177. BEGIN
  178. datamodel.Acquire; datamodel.Reset; datamodel.Release;
  179. END Reset;
  180. PROCEDURE SetSampleBufferSize*(size : LONGINT);
  181. VAR i : LONGINT;
  182. BEGIN (* no concurrency allowed, Plugin.Update may not called at the same time *)
  183. IF p.noSuperSampling THEN size := 1; END;
  184. sample := 0; nbrOfValidSamples := 0;
  185. nbrOfSamples := size;
  186. NEW(samples, nbrOfSamples);
  187. FOR i := 0 TO nbrOfSamples-1 DO NEW(samples[i], dimensions); END;
  188. END SetSampleBufferSize;
  189. PROCEDURE Finalize*;
  190. BEGIN
  191. updater.RemovePlugin(SELF);
  192. END Finalize;
  193. PROCEDURE Update;
  194. VAR i, dim, dTime : LONGINT; sum: REAL;
  195. BEGIN
  196. dTime := Kernel.Elapsed(milliTimer);
  197. Kernel.SetTimer(milliTimer, 0);
  198. UpdateDataset;
  199. IF ~isFirstUpdate THEN
  200. IF p.perSecond & (dTime # 0) THEN
  201. FOR dim := 0 TO dimensions-1 DO
  202. temp[dim] := (dataset[dim] - lastDataset[dim]) * (Kernel.Second / dTime);
  203. lastDataset[dim] := dataset[dim]; dataset[dim] := temp[dim];
  204. END;
  205. END;
  206. IF nbrOfValidSamples < nbrOfSamples THEN INC(nbrOfValidSamples); END;
  207. FOR dim := 0 TO dimensions-1 DO
  208. samples[sample][dim] := dataset[dim];
  209. sum := 0;
  210. FOR i := 0 TO nbrOfValidSamples-1 DO sum := sum + samples[i][dim]; END;
  211. currentDataset[dim] := sum / nbrOfValidSamples;
  212. END;
  213. sample := (sample + 1) MOD nbrOfSamples;
  214. ELSE
  215. isFirstUpdate := FALSE;
  216. END;
  217. END Update;
  218. PROCEDURE UpdateScreen;
  219. BEGIN
  220. datamodel.Acquire; datamodel.PutValues(currentDataset); datamodel.Release;
  221. END UpdateScreen;
  222. PROCEDURE CopyDataset(source : Dataset; VAR target : Dataset);
  223. VAR dim : LONGINT;
  224. BEGIN
  225. FOR dim := 0 TO dimensions-1 DO target[dim] := source[dim]; END;
  226. END CopyDataset;
  227. PROCEDURE Show;
  228. BEGIN
  229. KernelLog.String(p.name); KernelLog.String(" ("); KernelLog.String(p.description); KernelLog.String(")");
  230. IF p.devicename # "" THEN KernelLog.String(" on "); KernelLog.String(p.devicename); END;
  231. IF p.modulename # "" THEN KernelLog.String(" defined in "); KernelLog.String(p.modulename); END; KernelLog.Char(" ");
  232. IF active THEN KernelLog.String("[active]"); END;
  233. IF p.hide THEN KernelLog.String("[hidden]"); END;
  234. END Show;
  235. PROCEDURE EvaluateParameter(p : Parameter);
  236. CONST Decrement = 35;
  237. VAR r, g, b, a, i, round : LONGINT;
  238. BEGIN
  239. IF (p.scale = 0) & (p.statsUnit = "") THEN COPY(p.unit, p.statsUnit); END;
  240. IF p.scale = 0 THEN p.scale := 1.0; END;
  241. IF p.datasetDescriptor = NIL THEN
  242. NEW(p.datasetDescriptor, 1);
  243. p.datasetDescriptor[0].name := "Default";
  244. p.datasetDescriptor[0].color := WMGraphics.Red;
  245. dimensions := 1;
  246. ELSE
  247. IF (LEN(p.datasetDescriptor) > 0) & (p.datasetDescriptor[0].color = 0) THEN p.datasetDescriptor[0].color := WMGraphics.Yellow; END;
  248. IF (LEN(p.datasetDescriptor) > 1) & (p.datasetDescriptor[1].color = 0) THEN p.datasetDescriptor[1].color := WMGraphics.Green; END;
  249. IF (LEN(p.datasetDescriptor) > 2) & (p.datasetDescriptor[2].color = 0) THEN p.datasetDescriptor[2].color := WMGraphics.Red; END;
  250. IF (LEN(p.datasetDescriptor) > 3) THEN
  251. round := 0;
  252. r := 255; g := 255; b := 255; a := 200;
  253. FOR i := 3 TO LEN(p.datasetDescriptor)-1 DO
  254. IF round = 0 THEN
  255. p.datasetDescriptor[i].color := WMGraphics.RGBAToColor(r, g, b, a);
  256. ELSIF round = 1 THEN
  257. p.datasetDescriptor[i].color := WMGraphics.RGBAToColor(g, r, b, a);
  258. ELSE
  259. p.datasetDescriptor[i].color := WMGraphics.RGBAToColor(b, g, r, a);
  260. END;
  261. IF (r - Decrement > 0) THEN DEC(r, Decrement);
  262. ELSIF (g - 2*Decrement > 0) THEN DEC(g, Decrement);
  263. ELSIF (b - 3*Decrement > 0) THEN DEC(b, Decrement);
  264. ELSE
  265. INC(round);
  266. r := 255; g := 255; b := 255;
  267. END;
  268. END;
  269. END;
  270. dimensions := LEN(p.datasetDescriptor);
  271. END;
  272. NEW(datamodel, 1024, dimensions);
  273. datamodel.SetDescriptor(p.datasetDescriptor);
  274. END EvaluateParameter;
  275. PROCEDURE &New*(p : Parameter);
  276. BEGIN
  277. ASSERT(p # NIL); SELF.p := p; active := FALSE;
  278. isFirstUpdate := TRUE;
  279. Kernel.SetTimer(milliTimer, 0);
  280. Init(p);
  281. EvaluateParameter(p);
  282. NEW(temp, dimensions);
  283. NEW(dataset, dimensions); NEW(lastDataset, dimensions); NEW(currentDataset, dimensions);
  284. updater.AddPlugin(SELF);
  285. END New;
  286. END Plugin;
  287. TYPE
  288. (**
  289. * Plugins may optionally use helper objects. Idea: If the sampling of a value is expensive in terms of
  290. * required computational power/memory, and this value is used by multiple plugins, the plugins may share
  291. * a single implementation/instance of the updater for this value.
  292. * The value will be only sampled once in an entire update loop.
  293. *)
  294. Helper* = OBJECT
  295. VAR
  296. next : Helper;
  297. updated : BOOLEAN;
  298. PROCEDURE Update*;
  299. BEGIN
  300. HALT(301); (* abstract *)
  301. END Update;
  302. END Helper;
  303. TYPE
  304. (* Notifies listener about current CPU time usage of updater thread and changes of the plugin list *)
  305. Notifier = PROCEDURE {DELEGATE} (events : SET; perf : REAL);
  306. Notifiers = POINTER TO RECORD
  307. events : SET;
  308. proc : Notifier;
  309. next : Notifiers;
  310. END;
  311. PluginArray* = POINTER TO ARRAY OF Plugin;
  312. (* This object...
  313. * - maintains a list of all plugin instances
  314. * - updates all plugins periodically (value/screen at separate update intervals) & manages size of sampling buffers
  315. * - is a singleton
  316. *)
  317. Updater = OBJECT
  318. VAR
  319. sampleInterval- : LONGINT; (* ms *)
  320. sampleBufferSize- : LONGINT; (* samples *)
  321. screenInterval- : LONGINT; (* ms *)
  322. plugins : Plugin;
  323. notifiers : Notifiers;
  324. (* Fields related to CPU time consumption calculation of the Updater obj *)
  325. lastCycles, lastTimestamp : HUGEINT;
  326. sample : LONGINT; sampleBuffer : POINTER TO ARRAY OF REAL;
  327. me : Objects.Process; (* Process associated to this active object *)
  328. milliTimer : Kernel.MilliTimer;
  329. left, samplingLeft : LONGINT;
  330. screenTimer : Kernel.MilliTimer;
  331. alive, dead : BOOLEAN;
  332. timer : Kernel.Timer;
  333. PROCEDURE AddListener*(events : SET; proc : Notifier);
  334. VAR nr : Notifiers;
  335. BEGIN {EXCLUSIVE}
  336. ASSERT(proc # NIL);
  337. NEW(nr); nr.proc := proc; nr.events := events;
  338. nr.next := notifiers.next; notifiers.next := nr;
  339. END AddListener;
  340. PROCEDURE RemoveListener*(proc : Notifier);
  341. VAR n : Notifiers;
  342. BEGIN {EXCLUSIVE}
  343. n := notifiers;
  344. WHILE n.next # NIL DO
  345. IF (n.next.proc = proc) THEN
  346. n.next := n.next.next;
  347. ELSE
  348. n := n.next;
  349. END;
  350. END;
  351. END RemoveListener;
  352. PROCEDURE NotifyListeners*(events : SET; perf : REAL);
  353. VAR n : Notifiers;
  354. BEGIN
  355. n := notifiers.next;
  356. WHILE n # NIL DO
  357. IF n.events * events # {} THEN
  358. n.proc(events, perf);
  359. END;
  360. n := n.next;
  361. END;
  362. END NotifyListeners;
  363. PROCEDURE GetByFullname*(CONST fullname : ARRAY OF CHAR; VAR index : LONGINT; VAR msg : ARRAY OF CHAR) : Plugin;
  364. VAR plugin : Plugin; sa : Strings.StringArray; name : Name; i : LONGINT;
  365. BEGIN {EXCLUSIVE}
  366. msg := "";
  367. sa := Strings.Split(fullname, ".");
  368. IF LEN(sa) = 2 THEN
  369. COPY(sa[0]^, name);
  370. plugin := GetByNameX(name, "");
  371. IF plugin # NIL THEN
  372. i := 0; WHILE (i < LEN(plugin.p.datasetDescriptor)) & (plugin.p.datasetDescriptor[i].name # sa[1]^) DO INC(i); END;
  373. IF (i < LEN(plugin.p.datasetDescriptor)) THEN
  374. index := i;
  375. ELSE
  376. plugin := NIL;
  377. msg := "Data not found";
  378. END;
  379. ELSE
  380. msg := "Plugin not found";
  381. END;
  382. ELSE
  383. msg := "Incorrect fullname";
  384. END;
  385. RETURN plugin;
  386. END GetByFullname;
  387. PROCEDURE GetByName*(CONST name : Name; CONST devicename : DeviceName) : Plugin;
  388. BEGIN {EXCLUSIVE}
  389. RETURN GetByNameX(name, devicename);
  390. END GetByName;
  391. PROCEDURE GetByNameX(CONST name : Name; CONST devicename : DeviceName) : Plugin;
  392. VAR p : Plugin;
  393. BEGIN
  394. p := plugins;
  395. WHILE p # NIL DO
  396. IF (p.p.name = name) & (p.p.devicename = devicename) THEN
  397. RETURN p;
  398. END;
  399. p := p.link;
  400. END;
  401. RETURN NIL;
  402. END GetByNameX;
  403. PROCEDURE RemoveByName*(CONST name : Name; CONST devicename : DeviceName);
  404. VAR p : Plugin;
  405. BEGIN {EXCLUSIVE}
  406. p := plugins; WHILE (p # NIL) & ~((p.p.name = name) & (p.p.devicename = devicename)) DO p := p.link; END;
  407. IF p # NIL THEN RemovePluginIntern(p);
  408. ELSE KernelLog.String("WMCounters: Could not remove plugin "); KernelLog.String(name); KernelLog.Ln;
  409. END;
  410. END RemoveByName;
  411. (** Removes all plugins whose modulename parameter matches the specified modulename *)
  412. PROCEDURE RemoveByModuleName*(CONST modulename : ARRAY OF CHAR);
  413. VAR p : Plugin; removed : BOOLEAN;
  414. BEGIN {EXCLUSIVE}
  415. LOOP
  416. removed := FALSE;
  417. p := plugins; WHILE (p # NIL) & ~(p.p.modulename = modulename) DO p := p.link; END;
  418. IF p # NIL THEN
  419. removed := TRUE;
  420. RemovePluginIntern(p);
  421. END;
  422. IF removed = FALSE THEN EXIT; END;
  423. END;
  424. END RemoveByModuleName;
  425. PROCEDURE RemovePlugin*(p : Plugin);
  426. BEGIN {EXCLUSIVE}
  427. RemovePluginIntern(p);
  428. END RemovePlugin;
  429. PROCEDURE RemovePluginIntern(p : Plugin);
  430. VAR temp : Plugin; removed : BOOLEAN;
  431. BEGIN
  432. IF Verbose THEN KernelLog.String("WMPerfMon: Removing counter "); p.Show;KernelLog.Ln; END;
  433. IF plugins = p THEN
  434. plugins := plugins.link; removed := TRUE;
  435. ELSE
  436. temp := plugins; WHILE(temp # NIL) & (temp.link # p) DO temp := temp.link; END;
  437. IF temp # NIL THEN
  438. temp.link := temp.link.link;
  439. removed := TRUE;
  440. END;
  441. END;
  442. IF removed THEN
  443. NotifyListeners({EventPluginsChanged}, 0);
  444. DEC(NnofPlugins);
  445. IF p.p.datasetDescriptor # NIL THEN
  446. DEC(NnofValues, LEN(p.p.datasetDescriptor));
  447. ELSE
  448. DEC(NnofValues);
  449. END;
  450. END;
  451. END RemovePluginIntern;
  452. (** Will not return hidden plugins *)
  453. PROCEDURE GetPlugins*() : PluginArray;
  454. VAR p : Plugin; nbrOfPlugins, i : LONGINT; ca : PluginArray;
  455. BEGIN {EXCLUSIVE}
  456. IF plugins # NIL THEN
  457. (* determine size of array *)
  458. p := plugins; nbrOfPlugins := 0;
  459. WHILE p # NIL DO
  460. IF ~p.p.hide THEN INC(nbrOfPlugins); END;
  461. p := p.link;
  462. END;
  463. (* fill array *)
  464. NEW(ca, nbrOfPlugins);
  465. p := plugins; i := 0;
  466. WHILE p # NIL DO
  467. IF ~p.p.hide THEN ca[i] := p; INC(i); END;
  468. p := p.link;
  469. END;
  470. END;
  471. RETURN ca;
  472. END GetPlugins;
  473. (** Clear statistics of all plugins *)
  474. PROCEDURE ClearAll*;
  475. VAR p : Plugin;
  476. BEGIN {EXCLUSIVE}
  477. p := plugins; WHILE p # NIL DO p.Reset; p := p.link; END;
  478. END ClearAll;
  479. PROCEDURE Show;
  480. VAR p : Plugin;
  481. BEGIN {EXCLUSIVE}
  482. KernelLog.String("WMPerfMon: ");
  483. IF plugins = NIL THEN
  484. KernelLog.String("No counters installed."); KernelLog.Ln;
  485. ELSE
  486. KernelLog.Ln;
  487. p := plugins; WHILE p # NIL DO p.Show; KernelLog.Ln; p := p.link; END;
  488. END;
  489. END Show;
  490. PROCEDURE SetIntervals*(VAR sampleInterval, sampleBufferSize, screenInterval : LONGINT);
  491. VAR p : Plugin;
  492. BEGIN {EXCLUSIVE}
  493. IF sampleInterval < 1 THEN sampleInterval := 1; END;
  494. IF screenInterval < sampleInterval THEN screenInterval := sampleInterval; END;
  495. IF sampleBufferSize < screenInterval DIV sampleInterval THEN
  496. sampleBufferSize := screenInterval DIV sampleInterval;
  497. END;
  498. ASSERT(sampleBufferSize > 0);
  499. SELF.sampleInterval := sampleInterval;
  500. SELF.screenInterval := screenInterval;
  501. IF sampleBufferSize # SELF.sampleBufferSize THEN
  502. SELF.sampleBufferSize := sampleBufferSize;
  503. p := plugins; WHILE p # NIL DO p.SetSampleBufferSize(sampleBufferSize); p := p.link; END;
  504. NEW(sampleBuffer, sampleBufferSize);
  505. END;
  506. NotifyListeners({EventParametersChanged}, 0);
  507. Kernel.SetTimer(screenTimer, 1);
  508. timer.Wakeup;
  509. END SetIntervals;
  510. PROCEDURE AddPlugin(plugin : Plugin);
  511. VAR p : Plugin;
  512. BEGIN {EXCLUSIVE}
  513. IF Verbose THEN KernelLog.String("WMPerfMon: Adding counter "); plugin.Show; KernelLog.Ln; END;
  514. plugin.link := NIL;
  515. IF (plugins = NIL) THEN
  516. plugins := plugin;
  517. ELSE
  518. p := plugins; WHILE (p.link # NIL) DO p := p.link; END;
  519. p.link := plugin;
  520. END;
  521. plugin.SetSampleBufferSize(sampleBufferSize);
  522. INC(NnofPlugins);
  523. IF plugin.p.datasetDescriptor # NIL THEN
  524. INC(NnofValues, LEN(plugin.p.datasetDescriptor));
  525. ELSE
  526. INC(NnofValues);
  527. END;
  528. NotifyListeners({EventPluginsChanged}, 0);
  529. END AddPlugin;
  530. PROCEDURE UpdatePlugin(p : Plugin);
  531. BEGIN
  532. IF p.p.helper # NIL THEN UpdateHelpers(p.p.helper); END;
  533. p.Update;
  534. END UpdatePlugin;
  535. (* Updates plugin counter values / screen representation *)
  536. PROCEDURE UpdatePlugins(screen : BOOLEAN);
  537. VAR p : Plugin;
  538. BEGIN {EXCLUSIVE}
  539. p := plugins;
  540. WHILE alive & (p # NIL) DO
  541. IF p.active THEN
  542. IF screen THEN
  543. IF p.p.noSuperSampling THEN
  544. UpdatePlugin(p);
  545. END;
  546. p.UpdateScreen;
  547. ELSE
  548. IF ~p.p.noSuperSampling THEN
  549. UpdatePlugin(p);
  550. END;
  551. END;
  552. END;
  553. p := p.link;
  554. END;
  555. END UpdatePlugins;
  556. PROCEDURE UpdateHelpers(h : Helper);
  557. BEGIN (* Caller holds obj lock *)
  558. WHILE alive & (h # NIL) DO
  559. IF ~h.updated THEN h.Update; h.updated := TRUE; END;
  560. h := h.next;
  561. END;
  562. END UpdateHelpers;
  563. (* Sets the update field of all helpers to FALSE *)
  564. PROCEDURE ResetHelpers;
  565. VAR p : Plugin; h : Helper;
  566. BEGIN
  567. p := plugins;
  568. WHILE p # NIL DO
  569. h := p.p.helper;
  570. WHILE h # NIL DO
  571. h.updated := FALSE;
  572. h := h.next;
  573. END;
  574. p := p.link;
  575. END;
  576. END ResetHelpers;
  577. (* Calculates % CPU time consumed by this process. Updates field perf *)
  578. PROCEDURE UpdatePerf;
  579. VAR timestamp, cycles : HUGEINT; cpuCycles : Objects.CpuCyclesArray; i : LONGINT; value, sum : REAL;
  580. BEGIN {EXCLUSIVE}
  581. timestamp := Machine.GetTimer();
  582. IF lastTimestamp # 0 THEN
  583. Objects.GetCpuCycles(me, cpuCycles, TRUE);
  584. FOR i := 0 TO LEN(cpuCycles)-1 DO INC (cycles , cpuCycles[i]); END;
  585. value := SHORT(100.0 * LONGREAL(cycles - lastCycles) / LONGREAL(timestamp - lastTimestamp));
  586. sampleBuffer[sample MOD sampleBufferSize] := value; INC(sample);
  587. lastCycles := cycles;
  588. FOR i := 0 TO sampleBufferSize-1 DO sum := sum + sampleBuffer[i]; END;
  589. value := sum / sampleBufferSize;
  590. NotifyListeners({EventPerfUpdate}, value);
  591. END;
  592. lastTimestamp := timestamp;
  593. END UpdatePerf;
  594. PROCEDURE Terminate;
  595. BEGIN
  596. alive := FALSE; timer.Wakeup;
  597. BEGIN {EXCLUSIVE} AWAIT(dead); END;
  598. END Terminate;
  599. PROCEDURE &New*;
  600. BEGIN
  601. NEW(timer); alive := TRUE; dead := FALSE;
  602. sampleInterval := DefaultSampleInterval;
  603. sampleBufferSize := DefaultSampleBufferSize;
  604. screenInterval := DefaultScreenRefresh;
  605. NEW(sampleBuffer, sampleBufferSize);
  606. NEW(notifiers); (* head of list *)
  607. END New;
  608. BEGIN {ACTIVE, PRIORITY(Objects.High)}
  609. me := Objects.CurrentProcess();
  610. Kernel.SetTimer(screenTimer, screenInterval);
  611. WHILE alive DO
  612. Kernel.SetTimer(milliTimer, sampleInterval);
  613. (* Sampling: sample values of all plugins *)
  614. UpdatePlugins(FALSE);
  615. ResetHelpers;
  616. samplingLeft := Kernel.Left(milliTimer); IF samplingLeft < 0 THEN samplingLeft := 0; END;
  617. left := Kernel.Left(screenTimer); IF (left < 0) THEN left := 0; END;
  618. IF left <= samplingLeft THEN (* screen refresh before next sample update (or now) *)
  619. IF left > 0 THEN timer.Sleep(left); END;
  620. UpdatePlugins(TRUE);
  621. Kernel.SetTimer(screenTimer, screenInterval);
  622. END;
  623. NotifyListeners({EventSampleLoopDone}, 0);
  624. (* Determine how much cpu cycles have been used by this process *)
  625. UpdatePerf;
  626. samplingLeft := Kernel.Left(milliTimer);
  627. IF alive & (samplingLeft > 0) THEN timer.Sleep(samplingLeft); END;
  628. END;
  629. BEGIN {EXCLUSIVE} dead := TRUE; END;
  630. END Updater;
  631. VAR
  632. updater- : Updater;
  633. (* statistics *)
  634. NnofPlugins-, NnofValues- : LONGINT;
  635. (** Estimate the processors clock rate.
  636. This is done by measuring the number of clock cycles within a time interval of 1 second.
  637. Note: While the GC is running, all interrupts are masked. Therefore, also Objects.ticks is
  638. not updated which makes the measurement unaccurate.
  639. @param clockrate: Estimated clockrate in MHz
  640. @return: TRUE, if the estimation can be considered accurate, FALSE otherwise
  641. *)
  642. PROCEDURE EstimateCpuClockrate*(VAR clockrate : LONGINT) : BOOLEAN;
  643. VAR
  644. timer : Kernel.Timer; milliTimer : Kernel.MilliTimer;
  645. startTime, endTime, timeDiff : HUGEINT;
  646. nbrOfGcRuns : LONGINT;
  647. BEGIN
  648. NEW(timer); nbrOfGcRuns := Heaps.Ngc;
  649. Kernel.SetTimer(milliTimer, 1000);
  650. startTime := Machine.GetTimer();
  651. WHILE ~Kernel.Expired(milliTimer) DO
  652. timer.Sleep(1);
  653. IF nbrOfGcRuns # Heaps.Ngc THEN RETURN FALSE; END;
  654. END;
  655. endTime := Machine.GetTimer();
  656. IF nbrOfGcRuns # Heaps.Ngc THEN RETURN FALSE; END;
  657. timeDiff := ABS( endTime - startTime );
  658. clockrate := SHORT (timeDiff DIV (1000*1000));
  659. RETURN TRUE;
  660. END EstimateCpuClockrate;
  661. (** Convert number of cycles (high-res timer) to milliseconds
  662. @param cycles: Number of cycles
  663. @param mhz: CPU clockrate in MHz
  664. @return: Number of milliseconds
  665. *)
  666. PROCEDURE CyclesToMs*(cycles : HUGEINT; mhz : LONGINT) : LONGINT;
  667. BEGIN
  668. RETURN SHORT (ABS(cycles) DIV (1000*ABS(mhz)));
  669. END CyclesToMs;
  670. (** Convert number of milliseconds into string of the form d:h:m:s if m >= 1 or x.xxxs if m < 1*)
  671. PROCEDURE MsToString*(ms : LONGINT; VAR string : ARRAY OF CHAR);
  672. CONST Day=24*60*60*1000; Hour = 60*60*1000; Minute = 60*1000; Second = 1000; Millisecond = 1;
  673. PROCEDURE Append(divisor : LONGINT; CONST unit : ARRAY OF CHAR);
  674. VAR nbr : ARRAY 16 OF CHAR; val : LONGINT;
  675. BEGIN
  676. val := ms DIV divisor; ms := ms MOD divisor;
  677. Strings.IntToStr(val, nbr); Strings.Append(string, nbr); Strings.Append(string, unit);
  678. END Append;
  679. BEGIN
  680. string := "";
  681. IF ms >= Minute THEN (* d:h:m:s *)
  682. IF ms >= Day THEN Append(Day, "d "); END;
  683. IF ms >= Hour THEN Append(Hour, "h "); END;
  684. IF ms >= Minute THEN Append(Minute, "m "); END;
  685. IF ms >= Second THEN Append(Second, "s"); END;
  686. ELSE (* x.xxxs *)
  687. Append(Second, "."); Append(100, ""); Append(10, ""); Append(Millisecond, "s");
  688. END;
  689. END MsToString;
  690. PROCEDURE GetNameDesc*(plugin : Plugins.Plugin; VAR devicename : DeviceName);
  691. BEGIN
  692. COPY(plugin.name, devicename);
  693. Strings.Append(devicename, " ("); Strings.Append(devicename, plugin.desc); Strings.Append(devicename, ")");
  694. END GetNameDesc;
  695. (** Show all currently installed performance counters *)
  696. PROCEDURE Show*(context : Commands.Context);
  697. BEGIN
  698. updater.Show;
  699. END Show;
  700. PROCEDURE LoadPlugin(CONST name : ARRAY OF CHAR);
  701. VAR loaderProc : PluginLoader; msg : Events.Message;
  702. BEGIN
  703. GETPROCEDURE(name, "Install", loaderProc);
  704. IF (loaderProc # NIL) THEN
  705. loaderProc;
  706. ELSE
  707. msg := "Could not load plugin "; Strings.Append(msg, name); Strings.Append(msg, " - Install command not found");
  708. Events.AddEvent("WMPerfMonPlugins", Events.Error, 2, 1, 0, msg, TRUE);
  709. END;
  710. END LoadPlugin;
  711. PROCEDURE LoadConfiguration;
  712. VAR elem : XML.Element; enum : XMLObjects.Enumerator; ptr : ANY; string : XML.String;
  713. BEGIN
  714. elem := Configuration.GetSection("Applications.Performance Monitor.Plugins");
  715. IF elem # NIL THEN
  716. enum := elem.GetContents(); enum.Reset;
  717. WHILE enum.HasMoreElements() DO
  718. ptr := enum.GetNext();
  719. IF ptr IS XML.Element THEN
  720. string := ptr(XML.Element).GetAttributeValue("value");
  721. IF (string # NIL) THEN
  722. IF Strings.Match("TRUE", string^) THEN
  723. string := ptr(XML.Element).GetAttributeValue("name");
  724. IF (string # NIL) THEN
  725. LoadPlugin(string^);
  726. END;
  727. END;
  728. END;
  729. END;
  730. END;
  731. ELSE KernelLog.String("WMPerfMon: Warning: Section 'Applications.Performance Monitor.Plugins' not found in config file."); KernelLog.Ln;
  732. END;
  733. END LoadConfiguration;
  734. PROCEDURE Cleanup;
  735. BEGIN
  736. updater.Terminate;
  737. END Cleanup;
  738. BEGIN
  739. Modules.InstallTermHandler(Cleanup);
  740. NEW(updater);
  741. LoadConfiguration;
  742. END WMPerfMonPlugins.