HierarchicalProfiler.Mod 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  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; n: ADDRESS;
  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, n);
  362. IF ODD(n) THEN
  363. INC(bp, SIZEOF(ADDRESS));
  364. END;
  365. SYSTEM.GET(bp + SIZEOF(ADDRESS), pc);
  366. SYSTEM.GET(bp, bp);
  367. samples[index][depth] := pc;
  368. INC(depth);
  369. END;
  370. IF (bp # 0) & ((bp < lowAdr) OR (bp > highAdr)) THEN
  371. InvalidateSample(samples[index]);
  372. Machine.AtomicInc(nofUnwindingFaults);
  373. END;
  374. IF (depth >= MaxUnwindingDepth) THEN (* run not valid *)
  375. InvalidateSample(samples[index]);
  376. Machine.AtomicInc(nofRunsTooDeep);
  377. END;
  378. END HandleTimer;
  379. PROCEDURE InvalidateSample(VAR sample : Sample);
  380. VAR i : LONGINT;
  381. BEGIN
  382. FOR i := 0 TO MaxUnwindingDepth-1 DO
  383. sample[i] := Invalid;
  384. END;
  385. END InvalidateSample;
  386. (* Sort children of node 'parent' using insertion sort *)
  387. PROCEDURE SortChildren(parent : Node);
  388. VAR temp, sortedNodes : Node;
  389. PROCEDURE InsertSorted(node : Node; VAR list : Node);
  390. VAR temp : Node;
  391. BEGIN
  392. ASSERT(node # NIL);
  393. IF (list = NIL) OR (node.count >= list.count) THEN
  394. node.next := list;
  395. list := node;
  396. ELSE
  397. temp := list;
  398. WHILE (temp.next # NIL) & (temp.next.count >= node.count) DO temp := temp.next; END;
  399. node.next := temp.next;
  400. temp.next := node;
  401. END;
  402. END InsertSorted;
  403. BEGIN
  404. IF (parent # NIL) & (parent.child # NIL) & (parent.child.sibling # NIL) THEN
  405. temp := parent.child;
  406. WHILE (temp # NIL) DO
  407. InsertSorted(temp, sortedNodes);
  408. temp := temp.sibling;
  409. END;
  410. parent.child := sortedNodes;
  411. temp := sortedNodes;
  412. WHILE (temp # NIL) DO
  413. temp.sibling := temp.next;
  414. temp := temp.next;
  415. END;
  416. END;
  417. END SortChildren;
  418. PROCEDURE PostProcessNode(profile : Profile; node : Node);
  419. BEGIN
  420. WHILE (node # NIL) DO
  421. (* calculate percentages *)
  422. node.extern := FALSE;
  423. node.percent := 100 * (node.count / profile.nofSamples);
  424. SortChildren(node);
  425. PostProcessNode(profile, node.child);
  426. node := node.sibling;
  427. END;
  428. END PostProcessNode;
  429. PROCEDURE PostProcessProfile(profile : Profile);
  430. BEGIN
  431. ASSERT(profile # NIL);
  432. PostProcessNode(profile, profile.nodes);
  433. END PostProcessProfile;
  434. PROCEDURE CreateProfile(type : LONGINT; info : LONGINT) : Profile;
  435. VAR profile : Profile; index : LONGINT;
  436. BEGIN (* {Caller holds module lock} *)
  437. ASSERT(samples # NIL);
  438. NEW(profile);
  439. profile.nofSamples := nofSamples;
  440. profile.nofProcessors := Machine.NumberOfProcessors();
  441. profile.nofRunsTooDeep := nofRunsTooDeep;
  442. profile.nofUnwindingFaults := nofUnwindingFaults;
  443. profile.nofSamplesNotStored := nofSamplesNotStored;
  444. NEW(profile.nodes); profile.nodes.count := nofSamples;
  445. profile.nodes.name := "Profile";
  446. FOR index := 0 TO nofSamples-1 DO
  447. AddSample(profile.nodes, type, info, sampleInfos[index], samples[index]);
  448. END;
  449. PostProcessProfile(profile);
  450. RETURN profile;
  451. END CreateProfile;
  452. (** Returns the size of the sampling buffer in bytes for a given maximum sampling time in seconds *)
  453. PROCEDURE GetBufferSize*(time : LONGINT) : LONGINT;
  454. BEGIN
  455. RETURN time * Kernel.Second * Machine.NumberOfProcessors() * MaxUnwindingDepth * SIZEOF(ADDRESS);
  456. END GetBufferSize;
  457. (** Generate hierarchical profile of the last profiler run's data. Returns NIL if no data available *)
  458. PROCEDURE GetProfile*(type, info : LONGINT; VAR profile : Profile; VAR res : WORD);
  459. BEGIN {EXCLUSIVE}
  460. profile := NIL;
  461. IF (state # Running) THEN
  462. IF (samples # NIL) THEN
  463. profile := CreateProfile(type, info);
  464. res := Ok;
  465. ELSE
  466. res := NoProfileDataAvailable;
  467. END;
  468. ELSE
  469. res := AlreadyRunning;
  470. END;
  471. END GetProfile;
  472. (** Start profiling. If the profiler is already running, it is stopped and the sample data is discarded before re-starting it *)
  473. PROCEDURE Start*(context : Commands.Context); (** [options] ~ *)
  474. VAR options : Options.Options; unit : ARRAY 4 OF CHAR; maxTime, bufferSize: LONGINT; res: WORD;
  475. BEGIN
  476. NEW(options);
  477. options.Add("t", "time", Options.Integer); (* in seconds *)
  478. IF options.Parse(context.arg, context.error) THEN
  479. IF ~options.GetInteger("time", maxTime) THEN maxTime := DefaultMaxTime; END;
  480. IF (maxTime > 0) THEN
  481. StartProfiling(maxTime, res);
  482. IF (res = Ok) THEN
  483. context.out.String("Profiler started. MaxTime: "); context.out.Int(maxTime, 0);
  484. context.out.String(" seconds, MaxDepth: "); context.out.Int(MaxUnwindingDepth, 0);
  485. context.out.String(" frames [");
  486. unit := "B";
  487. bufferSize := GetBufferSize(maxTime);
  488. IF (bufferSize DIV 1024 > 10) THEN bufferSize := bufferSize DIV 1024; unit := "KB"; END;
  489. IF (bufferSize DIV 1024 > 10000) THEN bufferSize := bufferSize DIV 1024; unit := "MB"; END;
  490. context.out.Int(bufferSize, 0); context.out.String(" "); context.out.String(unit);
  491. context.out.String(" buffer]");
  492. ELSE
  493. Errors.ToStream(res, context.out)
  494. END;
  495. ELSE
  496. context.out.String("Parameter error: time must be >= 1");
  497. END;
  498. context.out.Ln;
  499. END;
  500. END Start;
  501. (** Start profiling. If the profiler is already running, it is stopped and the sample data is discarded before re-starting it *)
  502. PROCEDURE StartProfiling*(maxTime : LONGINT; VAR res : WORD);
  503. BEGIN {EXCLUSIVE}
  504. ASSERT(maxTime > 0);
  505. IF (state # Running) THEN
  506. currentIndex := 0;
  507. nofSamples := 0;
  508. nofRunsTooDeep := 0;
  509. nofUnwindingFaults := 0;
  510. nofSamplesNotStored := 0;
  511. maxNofSamples := maxTime * Kernel.Second * Machine.NumberOfProcessors();
  512. NEW(samples, maxNofSamples);
  513. NEW(sampleInfos, maxNofSamples);
  514. TRACE('enabling hprop0');
  515. HierarchicalProfiler0.Enable(HandleTimer);
  516. state := Running;
  517. res := Ok;
  518. ELSE
  519. res := AlreadyRunning;
  520. END;
  521. END StartProfiling;
  522. (** Stop profiling. The profile data is not discarded. It can be retrieved using the procedure 'GetProfile' *)
  523. PROCEDURE Stop*(context : Commands.Context);
  524. VAR res : WORD;
  525. BEGIN
  526. StopProfiling(res);
  527. IF (res = Ok) THEN
  528. context.out.String("Profiler stopped, "); context.out.Int(nofSamples, 0);
  529. context.out.String(" samples");
  530. ELSE
  531. Errors.ToStream(res, context.out);
  532. END;
  533. context.out.Ln;
  534. END Stop;
  535. (** Stop profiling. The profile data is not discarded. It can be retrieved using the procedure 'GetProfile' *)
  536. PROCEDURE StopProfiling*(VAR res : WORD);
  537. BEGIN {EXCLUSIVE}
  538. IF (state = Running) THEN
  539. HierarchicalProfiler0.Disable;
  540. state := NotRunningDataAvailable;
  541. res := Ok;
  542. ELSE
  543. res := NotRunning;
  544. END;
  545. END StopProfiling;
  546. PROCEDURE Continue*(context : Commands.Context); (** ~ *)
  547. VAR res : WORD;
  548. BEGIN
  549. ContinueProfiling(res);
  550. IF (res = Ok) THEN
  551. context.out.String("Continue profiling...");
  552. ELSE
  553. Errors.ToStream(res, context.out);
  554. END;
  555. context.out.Ln;
  556. END Continue;
  557. PROCEDURE ContinueProfiling*(VAR res : WORD);
  558. BEGIN {EXCLUSIVE}
  559. IF (state # Running) THEN
  560. IF (samples # NIL) THEN
  561. IF (nofSamples < maxNofSamples) THEN
  562. HierarchicalProfiler0.Enable(HandleTimer);
  563. state := Running;
  564. res := Ok;
  565. ELSE res := SampleBufferFull;
  566. END;
  567. ELSE res := SampleBufferNotInitialized;
  568. END;
  569. ELSE res := AlreadyRunning;
  570. END;
  571. END ContinueProfiling;
  572. (** Returns TRUE if the profiler is currently running, FALSE otherwise *)
  573. PROCEDURE GetState*(VAR currentSamples, maxSamples : LONGINT) : LONGINT;
  574. BEGIN {EXCLUSIVE}
  575. IF (state = Running) THEN
  576. currentSamples := currentIndex;
  577. maxSamples := maxNofSamples;
  578. END;
  579. RETURN state;
  580. END GetState;
  581. (** Show the profile *)
  582. PROCEDURE Show*(context : Commands.Context);
  583. VAR profile : Profile; indent : LONGINT;
  584. PROCEDURE ShowNodes(parent : Node; indent : LONGINT; out : Streams.Writer);
  585. BEGIN
  586. WHILE (parent # NIL) DO
  587. parent.Show(out, indent);
  588. ShowNodes(parent.child, indent +4, out);
  589. parent := parent.sibling;
  590. END;
  591. END ShowNodes;
  592. BEGIN {EXCLUSIVE}
  593. IF (state # Running) THEN
  594. IF (samples # NIL) THEN
  595. profile := CreateProfile(Hierarchical, None);
  596. indent := 0;
  597. ShowNodes(profile.nodes, 0, context.out);
  598. ELSE
  599. context.out.String("No profile data available!");
  600. END;
  601. ELSE
  602. context.out.String("Profiler is running!");
  603. END;
  604. context.out.Ln;
  605. END Show;
  606. PROCEDURE Cleanup;
  607. VAR ignore : WORD;
  608. BEGIN
  609. StopProfiling(ignore);
  610. END Cleanup;
  611. BEGIN
  612. locked := FALSE;
  613. state := NotRunningNoDataAvailable;
  614. Modules.InstallTermHandler(Cleanup);
  615. END HierarchicalProfiler.
  616. HierarchicalProfiler.Start ~
  617. HierarchicalProfiler.Stop ~
  618. HierarchicalProfiler.Show ~
  619. System.Free HierarchicalProfiler ~