HierarchicalProfiler.Mod 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. MODULE HierarchicalProfiler; (** AUTHOR "staubesv"; PURPOSE "Simple statistical hierarchical profiler"; *)
  2. IMPORT
  3. SYSTEM, Machine, Streams, Modules, Objects, Kernel, Reflection, Commands, Options, Strings, Errors, HierarchicalProfiler0;
  4. CONST
  5. Ok* = 0;
  6. AlreadyRunning* = 5101;
  7. NotRunning* = 5102;
  8. NoProfileDataAvailable* = 5103;
  9. SampleBufferFull* = 5104;
  10. SampleBufferNotInitialized* = 5105;
  11. (* profile creation parameters
  12. Note: The profiler always gathers all data necessary for any profile *)
  13. (* type *)
  14. Hierarchical* = 0;
  15. Flat* = 1;
  16. (* thread / processor information *)
  17. None* = 0;
  18. Threads* = 1;
  19. Processors* = 2;
  20. ThreadsProcessors* = 3;
  21. ProcessorsThreads* = 4;
  22. (* Profiler states *)
  23. NotRunningNoDataAvailable* = 0;
  24. NotRunningDataAvailable* = 1;
  25. Running* = 2;
  26. DefaultMaxTime = 30;
  27. MaxUnwindingDepth = 64;
  28. Invalid = 0;
  29. TYPE
  30. Name = ARRAY 256 OF CHAR;
  31. Sample = ARRAY MaxUnwindingDepth OF ADDRESS;
  32. Samples = POINTER TO ARRAY OF Sample; (* HUGE!! *)
  33. SampleInfo = RECORD
  34. processorID : LONGINT;
  35. process : Objects.Process;
  36. END;
  37. SampleInfos = POINTER TO ARRAY OF SampleInfo;
  38. TYPE
  39. (** 'Node's are use to represent the hierarchical profile *)
  40. Node* = OBJECT
  41. VAR
  42. parent- : Node;
  43. child- : Node;
  44. sibling- : Node;
  45. count- : LONGINT;
  46. percent- : REAL;
  47. nofChildren- : LONGINT; (* number of direct descendants *)
  48. name- : Name;
  49. (* for external profile processing *)
  50. extern* : BOOLEAN;
  51. marked* : BOOLEAN;
  52. next : Node; (* for internal purposes, e.g. sorting *)
  53. PROCEDURE GetCaption*() : Strings.String;
  54. VAR string : ARRAY 256 OF CHAR; number : ARRAY 16 OF CHAR;
  55. BEGIN
  56. string := "[";
  57. Strings.IntToStr(ENTIER(percent), number); Strings.AppendX(string, number); Strings.AppendX(string, ".");
  58. Strings.IntToStr(ENTIER(10 * (percent - ENTIER(percent))), number); Strings.AppendX(string, number);
  59. Strings.AppendX(string, "%, ");
  60. Strings.IntToStr(count, number); Strings.AppendX(string, number); Strings.AppendX(string, "]: ");
  61. Strings.AppendX(string, name);
  62. RETURN Strings.NewString(string);
  63. END GetCaption;
  64. PROCEDURE Show(out : Streams.Writer; indent : LONGINT);
  65. VAR i : LONGINT;
  66. BEGIN
  67. ASSERT(out # NIL);
  68. FOR i := 0 TO indent-1 DO out.Char(" "); END;
  69. out.String("["); out.Int(ENTIER(percent), 0); out.Char("."); out.Int(ENTIER(10*(percent - ENTIER(percent))), 0); out.String("%, "); out.Int(count, 0); out.String("]: ");
  70. out.String(name); out.Ln;
  71. out.Update;
  72. END Show;
  73. PROCEDURE &Init*; (* private *)
  74. BEGIN
  75. parent := NIL; child := NIL; sibling := NIL;
  76. count := 0; percent := 0; nofChildren := 0;
  77. name := "";
  78. extern := FALSE; marked := TRUE;
  79. next := NIL;
  80. END Init;
  81. END Node;
  82. TYPE
  83. VisitorProcedure* = PROCEDURE {DELEGATE} (node : Node);
  84. Profile* = OBJECT
  85. VAR
  86. nodes- : Node;
  87. nofSamples- : LONGINT;
  88. nofProcessors- : LONGINT;
  89. nofRunsTooDeep- : LONGINT;
  90. nofUnwindingFaults- : LONGINT;
  91. nofSamplesNotStored- : LONGINT;
  92. pattern : ARRAY 64 OF CHAR;
  93. minPercent : LONGINT;
  94. PROCEDURE FindNode(CONST name : Name; list : Node) : Node;
  95. BEGIN
  96. WHILE (list # NIL) & (list.name # name) DO list := list.next; END;
  97. RETURN list;
  98. END FindNode;
  99. (** Insert node 'newNode' into 'parent.next' list. If a node with the same name is already present, merge it with the newNode *)
  100. PROCEDURE MergeNode(newNode, parent: Node);
  101. VAR node : Node;
  102. BEGIN
  103. ASSERT((newNode # NIL) & (parent # NIL));
  104. node := FindNode(newNode.name, parent.next);
  105. IF (node = NIL) THEN
  106. newNode.next := parent.next;
  107. parent.next := newNode;
  108. ELSE
  109. node.count := node.count + newNode.count;
  110. newNode.next := NIL;
  111. END;
  112. END MergeNode;
  113. PROCEDURE Flatten*(parent: Node);
  114. VAR
  115. child : Node;
  116. PROCEDURE MergeChildren(child : Node);
  117. BEGIN
  118. WHILE (child # NIL) DO
  119. MergeNode(child, parent);
  120. MergeChildren(child.child);
  121. child := child.sibling;
  122. END;
  123. END MergeChildren;
  124. BEGIN {EXCLUSIVE}
  125. ASSERT(parent # NIL);
  126. (* Merge all children of 'parent' into the parent.next list *)
  127. parent.next := NIL;
  128. MergeChildren(parent.child);
  129. (* adjust sibling references *)
  130. parent.child := parent.next;
  131. parent.next := NIL;
  132. child := parent.child;
  133. WHILE (child # NIL) DO
  134. child.sibling := child.next;
  135. child := child.next;
  136. END;
  137. (* clear 'next' references *)
  138. parent.nofChildren := 0;
  139. child := parent.child;
  140. WHILE (child # NIL) DO
  141. child.parent := parent;
  142. INC(parent.nofChildren);
  143. child.child := NIL;
  144. child.nofChildren := 0;
  145. child.next := NIL;
  146. child := child.sibling;
  147. END;
  148. (* sort children *)
  149. PostProcessProfile(SELF);
  150. END Flatten;
  151. PROCEDURE VisitorClearMark(node : Node);
  152. BEGIN
  153. ASSERT(node # NIL);
  154. node.marked := FALSE;
  155. END VisitorClearMark;
  156. PROCEDURE Mark*(CONST pattern : ARRAY OF CHAR; minPercent : LONGINT);
  157. BEGIN {EXCLUSIVE}
  158. COPY(pattern, SELF.pattern);
  159. SELF.minPercent := minPercent;
  160. VisitNodes(nodes, VisitorClearMark);
  161. VisitNodes(nodes, VisitorSetMark);
  162. END Mark;
  163. PROCEDURE VisitorSetMark(node : Node);
  164. VAR parent : Node;
  165. BEGIN
  166. ASSERT(node # NIL);
  167. IF Strings.Match(pattern, node.name) & (node.percent >= minPercent) THEN (* mark leaf node and all its parents *)
  168. node.marked := TRUE;
  169. parent := node.parent;
  170. WHILE (parent # NIL) & (parent.marked = FALSE) DO
  171. parent.marked := TRUE;
  172. parent := parent.parent;
  173. END;
  174. END;
  175. END VisitorSetMark;
  176. PROCEDURE VisitNodes(node : Node; visitorProc : VisitorProcedure);
  177. BEGIN
  178. ASSERT(visitorProc # NIL);
  179. WHILE (node # NIL) DO
  180. VisitNodes(node.child, visitorProc);
  181. visitorProc(node);
  182. node := node.sibling;
  183. END;
  184. END VisitNodes;
  185. PROCEDURE Visit*(visitorProc : VisitorProcedure);
  186. BEGIN {EXCLUSIVE}
  187. VisitNodes(nodes, visitorProc);
  188. END Visit;
  189. PROCEDURE &Init*;
  190. BEGIN
  191. nodes := NIL;
  192. nofSamples := 0;
  193. nofProcessors := 0;
  194. nofRunsTooDeep := 0;
  195. nofUnwindingFaults := 0;
  196. nofSamplesNotStored := 0;
  197. pattern := "*";
  198. minPercent := 0;
  199. END Init;
  200. END Profile;
  201. VAR
  202. (* sample data *)
  203. samples : Samples;
  204. sampleInfos : SampleInfos;
  205. maxNofSamples : LONGINT;
  206. (* statistics *)
  207. nofRunsTooDeep : LONGINT;
  208. nofUnwindingFaults : LONGINT;
  209. nofSamplesNotStored : LONGINT;
  210. nofSamples : LONGINT;
  211. (* current index into 'samples' array *)
  212. currentIndex : LONGINT;
  213. locked : BOOLEAN; (* protect 'currentIndex' *)
  214. (* Profiler state *)
  215. state : LONGINT;
  216. (* Find a node with name 'name' within the children of 'parent'. Returns NIL if no such node found *)
  217. PROCEDURE FindChildNode(CONST name : Name; parent : Node) : Node;
  218. VAR child : Node;
  219. BEGIN
  220. ASSERT(parent # NIL);
  221. child := parent.child;
  222. WHILE (child # NIL) & (child.name # name) DO child := child.sibling; END;
  223. RETURN child;
  224. END FindChildNode;
  225. (* Add node for procedure 'procedurename' to the children of 'parent'. If there is already a node
  226. for the procedure, just increment the 'Node.count' field *)
  227. PROCEDURE MergeChildNode(CONST procedureName : ARRAY OF CHAR; parent : Node) : Node;
  228. VAR child, temp : Node; name : Name;
  229. BEGIN
  230. ASSERT((procedureName # "") & (parent # NIL));
  231. COPY(procedureName, name);
  232. child := FindChildNode(name, parent);
  233. IF (child # NIL) THEN (* merge *)
  234. INC(child.count);
  235. ELSE (* create and insert new child *)
  236. NEW(child);
  237. child.name := name;
  238. child.count := 1;
  239. child.parent := parent;
  240. INC(parent.nofChildren);
  241. IF (parent.child = NIL) THEN
  242. parent.child := child;
  243. ELSE
  244. temp := parent.child;
  245. WHILE (temp.sibling # NIL) DO temp := temp.sibling; END;
  246. temp.sibling := child;
  247. END;
  248. END;
  249. ASSERT(child # NIL);
  250. RETURN child;
  251. END MergeChildNode;
  252. (* Add 'sample' to 'profile' *)
  253. PROCEDURE AddSample(profile : Node; type, info : LONGINT; CONST sampleInfo : SampleInfo; CONST sample : Sample);
  254. VAR node : Node; module : Modules.Module; pc,startpc : ADDRESS; nodeName, name : Name; i : LONGINT;
  255. PROCEDURE GenerateNodeName(module : Modules.Module; CONST procedureName : ARRAY OF CHAR) : Name;
  256. VAR name : Name;
  257. BEGIN
  258. IF (module # NIL) THEN COPY(module.name, name); ELSE name := "Unknown"; END;
  259. Strings.AppendX(name, ".");
  260. Strings.AppendX(name, procedureName);
  261. RETURN name;
  262. END GenerateNodeName;
  263. PROCEDURE GenerateProcessorName(processorID : LONGINT) : Name;
  264. VAR name : Name; nbr : ARRAY 16 OF CHAR;
  265. BEGIN
  266. name := "Processor P";
  267. Strings.IntToStr(processorID, nbr); Strings.AppendX(name, nbr);
  268. RETURN name;
  269. END GenerateProcessorName;
  270. PROCEDURE GenerateProcessName(process : Objects.Process) : Name;
  271. VAR
  272. name : Name; nbr : ARRAY 16 OF CHAR;
  273. module : Modules.Module; typeDescriptor : Modules.TypeDesc;
  274. adr : ADDRESS;
  275. BEGIN
  276. IF (process # NIL) THEN
  277. name := "Thread ID=";
  278. Strings.IntToStr(process.id, nbr); Strings.AppendX(name, nbr);
  279. Strings.AppendX(name, " [");
  280. IF (process.obj # NIL) THEN
  281. SYSTEM.GET(SYSTEM.VAL(ADDRESS, process.obj)-SIZEOF(ADDRESS), adr);
  282. Modules.ThisTypeByAdr(adr, module, typeDescriptor);
  283. IF (module # NIL) THEN
  284. Strings.AppendX(name, module.name); Strings.AppendX(name, ".");
  285. IF (typeDescriptor # NIL) & (typeDescriptor.name # "") THEN
  286. Strings.AppendX(name, typeDescriptor.name);
  287. ELSE
  288. Strings.AppendX(name, "UnknownType");
  289. END;
  290. ELSE
  291. Strings.AppendX(name, "UnknownModule");
  292. END;
  293. ELSE
  294. Strings.AppendX(name, "Unknown");
  295. END;
  296. Strings.AppendX(name, "]");
  297. ELSE
  298. name := "Thread=NIL";
  299. END;
  300. RETURN name;
  301. END GenerateProcessName;
  302. BEGIN
  303. node := profile;
  304. CASE info OF
  305. |None: (* skip *)
  306. |Threads:
  307. node := MergeChildNode(GenerateProcessName(sampleInfo.process) , node);
  308. |Processors:
  309. node := MergeChildNode(GenerateProcessorName(sampleInfo.processorID) , node);
  310. |ThreadsProcessors:
  311. node := MergeChildNode(GenerateProcessName(sampleInfo.process) , node);
  312. node := MergeChildNode(GenerateProcessorName(sampleInfo.processorID) , node);
  313. |ProcessorsThreads:
  314. node := MergeChildNode(GenerateProcessorName(sampleInfo.processorID) , node);
  315. node := MergeChildNode(GenerateProcessName(sampleInfo.process) , node);
  316. ELSE
  317. (* ignore *)
  318. END;
  319. IF (type = Hierarchical) THEN
  320. i := MaxUnwindingDepth-1;
  321. WHILE (i >= 1) & (sample[i] = Invalid) DO DEC(i); END;
  322. WHILE (i >= 0) DO
  323. (* get procedure name *)
  324. pc := sample[i];
  325. module := Modules.ThisModuleByAdr(pc);
  326. Reflection.GetProcedureName(pc, name, startpc);
  327. nodeName := GenerateNodeName(module, name);
  328. node := MergeChildNode(nodeName, node);
  329. DEC(i);
  330. END;
  331. ELSE
  332. IF (sample[0] # Invalid) THEN
  333. pc := sample[0];
  334. module := Modules.ThisModuleByAdr(pc);
  335. Reflection.GetProcedureName(pc, name,startpc);
  336. nodeName := GenerateNodeName(module, name);
  337. node := MergeChildNode(nodeName, node);
  338. END;
  339. END;
  340. END AddSample;
  341. PROCEDURE HandleTimer(id: LONGINT; process : Objects.Process; pc, bp, lowAdr, highAdr : ADDRESS);
  342. VAR index, depth : LONGINT;
  343. BEGIN
  344. (* acquire lock that protects currentIndex *)
  345. WHILE Machine.AtomicTestSet(locked) DO Machine.SpinHint; (* busy wait *) END;
  346. index := currentIndex;
  347. INC(currentIndex);
  348. locked := FALSE; (* release lock *)
  349. IF (index < maxNofSamples) THEN
  350. Machine.AtomicInc(nofSamples);
  351. ELSE
  352. Machine.AtomicInc(nofSamplesNotStored);
  353. RETURN;
  354. END;
  355. sampleInfos[index].processorID := id;
  356. sampleInfos[index].process := process;
  357. (* unwind stack *)
  358. samples[index][0] := pc;
  359. depth := 1;
  360. WHILE (bp # 0) & (lowAdr <= bp) & (bp < highAdr) (* Machine.Less(bp, highAdr)*) & (depth < MaxUnwindingDepth) DO
  361. SYSTEM.GET(bp + SIZEOF(ADDRESS), pc);
  362. SYSTEM.GET(bp, bp);
  363. samples[index][depth] := pc;
  364. INC(depth);
  365. END;
  366. IF (bp # 0) & ((bp < lowAdr) OR (bp > highAdr)) THEN
  367. InvalidateSample(samples[index]);
  368. Machine.AtomicInc(nofUnwindingFaults);
  369. END;
  370. IF (depth >= MaxUnwindingDepth) THEN (* run not valid *)
  371. InvalidateSample(samples[index]);
  372. Machine.AtomicInc(nofRunsTooDeep);
  373. END;
  374. END HandleTimer;
  375. PROCEDURE InvalidateSample(VAR sample : Sample);
  376. VAR i : LONGINT;
  377. BEGIN
  378. FOR i := 0 TO MaxUnwindingDepth-1 DO
  379. sample[i] := Invalid;
  380. END;
  381. END InvalidateSample;
  382. (* Sort children of node 'parent' using insertion sort *)
  383. PROCEDURE SortChildren(parent : Node);
  384. VAR temp, sortedNodes : Node;
  385. PROCEDURE InsertSorted(node : Node; VAR list : Node);
  386. VAR temp : Node;
  387. BEGIN
  388. ASSERT(node # NIL);
  389. IF (list = NIL) OR (node.count >= list.count) THEN
  390. node.next := list;
  391. list := node;
  392. ELSE
  393. temp := list;
  394. WHILE (temp.next # NIL) & (temp.next.count >= node.count) DO temp := temp.next; END;
  395. node.next := temp.next;
  396. temp.next := node;
  397. END;
  398. END InsertSorted;
  399. BEGIN
  400. IF (parent # NIL) & (parent.child # NIL) & (parent.child.sibling # NIL) THEN
  401. temp := parent.child;
  402. WHILE (temp # NIL) DO
  403. InsertSorted(temp, sortedNodes);
  404. temp := temp.sibling;
  405. END;
  406. parent.child := sortedNodes;
  407. temp := sortedNodes;
  408. WHILE (temp # NIL) DO
  409. temp.sibling := temp.next;
  410. temp := temp.next;
  411. END;
  412. END;
  413. END SortChildren;
  414. PROCEDURE PostProcessNode(profile : Profile; node : Node);
  415. BEGIN
  416. WHILE (node # NIL) DO
  417. (* calculate percentages *)
  418. node.extern := FALSE;
  419. node.percent := 100 * (node.count / profile.nofSamples);
  420. SortChildren(node);
  421. PostProcessNode(profile, node.child);
  422. node := node.sibling;
  423. END;
  424. END PostProcessNode;
  425. PROCEDURE PostProcessProfile(profile : Profile);
  426. BEGIN
  427. ASSERT(profile # NIL);
  428. PostProcessNode(profile, profile.nodes);
  429. END PostProcessProfile;
  430. PROCEDURE CreateProfile(type : LONGINT; info : LONGINT) : Profile;
  431. VAR profile : Profile; index : LONGINT;
  432. BEGIN (* {Caller holds module lock} *)
  433. ASSERT(samples # NIL);
  434. NEW(profile);
  435. profile.nofSamples := nofSamples;
  436. profile.nofProcessors := Machine.NumberOfProcessors();
  437. profile.nofRunsTooDeep := nofRunsTooDeep;
  438. profile.nofUnwindingFaults := nofUnwindingFaults;
  439. profile.nofSamplesNotStored := nofSamplesNotStored;
  440. NEW(profile.nodes); profile.nodes.count := nofSamples;
  441. profile.nodes.name := "Profile";
  442. FOR index := 0 TO nofSamples-1 DO
  443. AddSample(profile.nodes, type, info, sampleInfos[index], samples[index]);
  444. END;
  445. PostProcessProfile(profile);
  446. RETURN profile;
  447. END CreateProfile;
  448. (** Returns the size of the sampling buffer in bytes for a given maximum sampling time in seconds *)
  449. PROCEDURE GetBufferSize*(time : LONGINT) : LONGINT;
  450. BEGIN
  451. RETURN time * Kernel.Second * Machine.NumberOfProcessors() * MaxUnwindingDepth * SIZEOF(ADDRESS);
  452. END GetBufferSize;
  453. (** Generate hierarchical profile of the last profiler run's data. Returns NIL if no data available *)
  454. PROCEDURE GetProfile*(type, info : LONGINT; VAR profile : Profile; VAR res : LONGINT);
  455. BEGIN {EXCLUSIVE}
  456. profile := NIL;
  457. IF (state # Running) THEN
  458. IF (samples # NIL) THEN
  459. profile := CreateProfile(type, info);
  460. res := Ok;
  461. ELSE
  462. res := NoProfileDataAvailable;
  463. END;
  464. ELSE
  465. res := AlreadyRunning;
  466. END;
  467. END GetProfile;
  468. (** Start profiling. If the profiler is already running, it is stopped and the sample data is discarded before re-starting it *)
  469. PROCEDURE Start*(context : Commands.Context); (** [options] ~ *)
  470. VAR options : Options.Options; unit : ARRAY 4 OF CHAR; maxTime, bufferSize, res : LONGINT;
  471. BEGIN
  472. NEW(options);
  473. options.Add("t", "time", Options.Integer); (* in seconds *)
  474. IF options.Parse(context.arg, context.error) THEN
  475. IF ~options.GetInteger("time", maxTime) THEN maxTime := DefaultMaxTime; END;
  476. IF (maxTime > 0) THEN
  477. StartProfiling(maxTime, res);
  478. IF (res = Ok) THEN
  479. context.out.String("Profiler started. MaxTime: "); context.out.Int(maxTime, 0);
  480. context.out.String(" seconds, MaxDepth: "); context.out.Int(MaxUnwindingDepth, 0);
  481. context.out.String(" frames [");
  482. unit := "B";
  483. bufferSize := GetBufferSize(maxTime);
  484. IF (bufferSize DIV 1024 > 10) THEN bufferSize := bufferSize DIV 1024; unit := "KB"; END;
  485. IF (bufferSize DIV 1024 > 10000) THEN bufferSize := bufferSize DIV 1024; unit := "MB"; END;
  486. context.out.Int(bufferSize, 0); context.out.String(" "); context.out.String(unit);
  487. context.out.String(" buffer]");
  488. ELSE
  489. Errors.ToStream(res, context.out)
  490. END;
  491. ELSE
  492. context.out.String("Parameter error: time must be >= 1");
  493. END;
  494. context.out.Ln;
  495. END;
  496. END Start;
  497. (** Start profiling. If the profiler is already running, it is stopped and the sample data is discarded before re-starting it *)
  498. PROCEDURE StartProfiling*(maxTime : LONGINT; VAR res : LONGINT);
  499. BEGIN {EXCLUSIVE}
  500. ASSERT(maxTime > 0);
  501. IF (state # Running) THEN
  502. currentIndex := 0;
  503. nofSamples := 0;
  504. nofRunsTooDeep := 0;
  505. nofUnwindingFaults := 0;
  506. nofSamplesNotStored := 0;
  507. maxNofSamples := maxTime * Kernel.Second * Machine.NumberOfProcessors();
  508. NEW(samples, maxNofSamples);
  509. NEW(sampleInfos, maxNofSamples);
  510. HierarchicalProfiler0.Enable(HandleTimer);
  511. state := Running;
  512. res := Ok;
  513. ELSE
  514. res := AlreadyRunning;
  515. END;
  516. END StartProfiling;
  517. (** Stop profiling. The profile data is not discarded. It can be retrieved using the procedure 'GetProfile' *)
  518. PROCEDURE Stop*(context : Commands.Context);
  519. VAR res : LONGINT;
  520. BEGIN
  521. StopProfiling(res);
  522. IF (res = Ok) THEN
  523. context.out.String("Profiler stopped, "); context.out.Int(nofSamples, 0);
  524. context.out.String(" samples");
  525. ELSE
  526. Errors.ToStream(res, context.out);
  527. END;
  528. context.out.Ln;
  529. END Stop;
  530. (** Stop profiling. The profile data is not discarded. It can be retrieved using the procedure 'GetProfile' *)
  531. PROCEDURE StopProfiling*(VAR res : LONGINT);
  532. BEGIN {EXCLUSIVE}
  533. IF (state = Running) THEN
  534. HierarchicalProfiler0.Disable;
  535. state := NotRunningDataAvailable;
  536. res := Ok;
  537. ELSE
  538. res := NotRunning;
  539. END;
  540. END StopProfiling;
  541. PROCEDURE Continue*(context : Commands.Context); (** ~ *)
  542. VAR res : LONGINT;
  543. BEGIN
  544. ContinueProfiling(res);
  545. IF (res = Ok) THEN
  546. context.out.String("Continue profiling...");
  547. ELSE
  548. Errors.ToStream(res, context.out);
  549. END;
  550. context.out.Ln;
  551. END Continue;
  552. PROCEDURE ContinueProfiling*(VAR res : LONGINT);
  553. BEGIN {EXCLUSIVE}
  554. IF (state # Running) THEN
  555. IF (samples # NIL) THEN
  556. IF (nofSamples < maxNofSamples) THEN
  557. HierarchicalProfiler0.Enable(HandleTimer);
  558. state := Running;
  559. res := Ok;
  560. ELSE res := SampleBufferFull;
  561. END;
  562. ELSE res := SampleBufferNotInitialized;
  563. END;
  564. ELSE res := AlreadyRunning;
  565. END;
  566. END ContinueProfiling;
  567. (** Returns TRUE if the profiler is currently running, FALSE otherwise *)
  568. PROCEDURE GetState*(VAR currentSamples, maxSamples : LONGINT) : LONGINT;
  569. BEGIN {EXCLUSIVE}
  570. IF (state = Running) THEN
  571. currentSamples := currentIndex;
  572. maxSamples := maxNofSamples;
  573. END;
  574. RETURN state;
  575. END GetState;
  576. (** Show the profile *)
  577. PROCEDURE Show*(context : Commands.Context);
  578. VAR profile : Profile; indent : LONGINT;
  579. PROCEDURE ShowNodes(parent : Node; indent : LONGINT; out : Streams.Writer);
  580. BEGIN
  581. WHILE (parent # NIL) DO
  582. parent.Show(out, indent);
  583. ShowNodes(parent.child, indent +4, out);
  584. parent := parent.sibling;
  585. END;
  586. END ShowNodes;
  587. BEGIN {EXCLUSIVE}
  588. IF (state # Running) THEN
  589. IF (samples # NIL) THEN
  590. profile := CreateProfile(Hierarchical, None);
  591. indent := 0;
  592. ShowNodes(profile.nodes, 0, context.out);
  593. ELSE
  594. context.out.String("No profile data available!");
  595. END;
  596. ELSE
  597. context.out.String("Profiler is running!");
  598. END;
  599. context.out.Ln;
  600. END Show;
  601. PROCEDURE Cleanup;
  602. VAR ignore : LONGINT;
  603. BEGIN
  604. StopProfiling(ignore);
  605. END Cleanup;
  606. BEGIN
  607. locked := FALSE;
  608. state := NotRunningNoDataAvailable;
  609. Modules.InstallTermHandler(Cleanup);
  610. END HierarchicalProfiler.
  611. HierarchicalProfiler.Start ~
  612. HierarchicalProfiler.Stop ~
  613. HierarchicalProfiler.Show ~
  614. SystemTools.Free HierarchicalProfiler ~