You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

302 lines
6.9 KiB

  1. unit uutlEmbeddedProfiler;
  2. {$mode objfpc}{$H+}
  3. {$OPTIMIZATION ON}
  4. {$OPTIMIZATION REGVAR}
  5. {$OPTIMIZATION PEEPHOLE}
  6. {$OPTIMIZATION CSE}
  7. {$OPTIMIZATION ASMCSE}
  8. interface
  9. uses
  10. SysUtils;
  11. var
  12. ProfilerEnabled: boolean;
  13. procedure ProfilerEnterProc(const Addr: Pointer);
  14. procedure ProfilerEnterProc(const Addr: Pointer; const aName: String);
  15. procedure ProfilerLeaveProc;
  16. implementation
  17. {$I uutlEmbeddedProfiler.inc}
  18. {$IFDEF PROFILER_ENABLE}
  19. uses
  20. Windows, lineinfo{%H-}, Classes, fgl, unFastFileStream;
  21. type
  22. TWriterThread = class(TThread)
  23. private type
  24. TCacheEntry = record
  25. Name, Src: ShortString; Line: integer;
  26. end;
  27. TCacheList = specialize TFPGMap<PtrUInt, TCacheEntry>;
  28. private
  29. fAddressCache: TCacheList;
  30. fPF: Int64;
  31. procedure SaveCurrentWrite;
  32. public
  33. constructor Create;
  34. destructor Destroy; override;
  35. procedure Execute; override;
  36. end;
  37. PEventRecord = ^TEventRecord;
  38. TEventRecord = packed record
  39. Name: String;
  40. Func: PtrUInt;
  41. Thread: TThreadID;
  42. When: Int64;
  43. end;
  44. TProfileDataFile = class
  45. public
  46. constructor Create(const {%H-}aFileName: string);
  47. procedure WriteEnter(Thread: TThreadID; When: Int64; Func, Src: String; Line: Integer); virtual; abstract;
  48. procedure WriteLeave(Thread: TThreadID; When: Int64); virtual; abstract;
  49. end;
  50. {$DEFINE __HEAD}
  51. //{$I uutlProfilerPlainText.inc}
  52. {$I uutlProfilerPlainTextMMap.inc}
  53. {$UnDef __HEAD}
  54. //{$I uutlProfilerPlainText.inc}
  55. {$I uutlProfilerPlainTextMMap.inc}
  56. const
  57. MAX_EVENT_COUNT = 1000;
  58. RETURN_FUNCTION : PtrUInt = PtrUInt(-1);
  59. var
  60. ProfilerDataFile: TProfileDataFile;
  61. LineNumberComp: PtrUInt;
  62. Events: array[0..MAX_EVENT_COUNT-1] of TEventRecord;
  63. InsertPtr, WritePtr: Integer;
  64. WriterThread: TWriterThread;
  65. SLInsert: Cardinal;
  66. procedure InstallWriterThread;
  67. begin
  68. if not Assigned(WriterThread) then
  69. WriterThread:= TWriterThread.Create;
  70. end;
  71. procedure UninstallWriterThread;
  72. begin
  73. if Assigned(WriterThread) then begin
  74. WriterThread.Terminate;
  75. WriterThread.WaitFor;
  76. FreeAndNil(WriterThread);
  77. end;
  78. end;
  79. procedure NextInsert;
  80. begin
  81. inc(InsertPtr);
  82. if InsertPtr >= MAX_EVENT_COUNT then
  83. InsertPtr:= 0;
  84. // wait until writer cleared this element
  85. While Events[InsertPtr].Func <> 0 do
  86. ThreadSwitch;
  87. end;
  88. procedure CalibrateLineNumberCompensation1(const Addr: PtrUInt);
  89. begin
  90. LineNumberComp:= Addr;
  91. end;
  92. procedure CalibrateLineNumberCompensation;
  93. label
  94. mark;
  95. begin
  96. mark:
  97. CalibrateLineNumberCompensation1({%H-}PtrUInt(Get_pc_addr));
  98. //measure out one CALL
  99. LineNumberComp:= LineNumberComp - {%H-}PtrUInt(@mark);
  100. //go somewhere into the stack prep before the call
  101. inc(LineNumberComp);
  102. end;
  103. procedure TestDebugInfoPresent;
  104. var
  105. f,s: ShortString;
  106. l: LongInt;
  107. begin
  108. f:= '';
  109. s:= '';
  110. l:= 0;
  111. if not GetLineInfo({%H-}PtrUInt(@TestDebugInfoPresent),f,s,l) then begin
  112. raise Exception.Create('Profiler is enabled, but no suitable debug info could be found.');
  113. Halt();
  114. end;
  115. end;
  116. procedure ProfilerEnterProc(const Addr: Pointer);
  117. begin
  118. ProfilerEnterProc(Addr, '');
  119. end;
  120. procedure ProfilerEnterProc(const Addr: Pointer; const aName: String);
  121. var
  122. f: PtrUInt;
  123. tid: TThreadID;
  124. er: PEventRecord;
  125. begin
  126. if not ProfilerEnabled then
  127. exit;
  128. tid:= GetCurrentThreadId;
  129. InstallWriterThread;
  130. repeat
  131. System.InterlockedCompareExchange(SLInsert, tid, 0);
  132. until SLInsert = tid;
  133. try
  134. // measure as late (close to measured code) as possible, but still write .Func last, because that's our lockvar
  135. f:= {%H-}PtrUInt(addr) - LineNumberComp;
  136. er:= @Events[InsertPtr];
  137. er^.Thread := tid;
  138. er^.Name := aName;
  139. QueryPerformanceCounter(er^.When);
  140. er^.Func := f;
  141. NextInsert;
  142. finally
  143. System.InterLockedExchange(SLInsert, 0);
  144. end;
  145. end;
  146. procedure ProfilerLeaveProc;
  147. var
  148. t: Int64;
  149. tid: TThreadID;
  150. er: PEventRecord;
  151. begin
  152. if not ProfilerEnabled then
  153. exit;
  154. QueryPerformanceCounter(t{%H-});
  155. tid:= GetCurrentThreadId;
  156. repeat
  157. System.InterlockedCompareExchange(SLInsert, tid, 0);
  158. until SLInsert = tid;
  159. try
  160. // measure as early (close to measured code) as possible, but still write .Func last, because that's our lockvar
  161. er := @Events[InsertPtr];
  162. er^.Thread := tid;
  163. er^.When := t;
  164. er^.Name := '';
  165. er^.Func := RETURN_FUNCTION;
  166. NextInsert;
  167. finally
  168. System.InterLockedExchange(SLInsert, 0);
  169. end;
  170. end;
  171. { TProfileDataFile }
  172. constructor TProfileDataFile.Create(const aFileName: string);
  173. begin
  174. inherited Create;
  175. end;
  176. { TWriterThread }
  177. constructor TWriterThread.Create;
  178. begin
  179. inherited Create(false);
  180. fAddressCache:= TCacheList.Create;
  181. fAddressCache.Sorted:= true;
  182. QueryPerformanceFrequency(fPF);
  183. end;
  184. destructor TWriterThread.Destroy;
  185. begin
  186. FreeAndNil(fAddressCache);
  187. inherited Destroy;
  188. end;
  189. procedure TWriterThread.Execute;
  190. begin
  191. while not Terminated do begin
  192. while Events[WritePtr].Func<>0 do begin
  193. SaveCurrentWrite;
  194. inc(WritePtr);
  195. if WritePtr >= MAX_EVENT_COUNT then
  196. WritePtr:= 0;
  197. end;
  198. Sleep(1);
  199. end;
  200. //finish up remaining data, by now, writing is disabled
  201. while Events[WritePtr].Func<>0 do begin
  202. SaveCurrentWrite;
  203. inc(WritePtr);
  204. if WritePtr >= MAX_EVENT_COUNT then
  205. WritePtr:= 0;
  206. end;
  207. end;
  208. procedure TWriterThread.SaveCurrentWrite;
  209. var
  210. ce: TCacheEntry;
  211. i: integer;
  212. begin
  213. if Events[WritePtr].Func = 0 then
  214. exit;
  215. if Events[WritePtr].Func = RETURN_FUNCTION then
  216. ProfilerDataFile.WriteLeave(Events[WritePtr].Thread, (Events[WritePtr].When * 1000 * 1000) div fPF)
  217. else begin
  218. i:= fAddressCache.IndexOf(Events[WritePtr].Func);
  219. if i < 0 then begin
  220. ce.Line:= 0;
  221. ce.Src:= '';
  222. GetLineInfo(Events[WritePtr].Func,ce.Name,ce.Src,ce.Line);
  223. if (ce.Name = '') then
  224. ce.Name := Format('0x%.16x', [Events[WritePtr].Func]);
  225. fAddressCache.Add(Events[WritePtr].Func, ce);
  226. end else
  227. ce:= fAddressCache.Data[i];
  228. if (Events[WritePtr].Name <> '') then
  229. ce.Name := '[' + Events[WritePtr].Name + '] ' + ce.Name;
  230. ProfilerDataFile.WriteEnter(Events[WritePtr].Thread, (Events[WritePtr].When * 1000 * 1000) div fPF, ce.Name, ce.Src, ce.Line);
  231. end;
  232. Events[WritePtr].Func:= 0;
  233. end;
  234. {$ELSE}
  235. procedure ProfilerEnterProc(const Addr: Pointer); inline;
  236. begin end;
  237. procedure ProfilerEnterProc(const Addr: Pointer; const aName: String); inline;
  238. begin end;
  239. procedure ProfilerLeaveProc; inline;
  240. begin end;
  241. {$ENDIF}
  242. initialization
  243. {$IFDEF PROFILER_ENABLE}
  244. ProfilerEnabled:= false;
  245. InsertPtr:= 0;
  246. WritePtr:= 0;
  247. WriterThread:= nil;
  248. CalibrateLineNumberCompensation;
  249. TestDebugInfoPresent;
  250. //ProfilerDataFile:= TProfilePlainText.Create(ChangeFileExt(ParamStr(0), '.profraw'));
  251. //ProfilerDataFile:= TProfileBinary.Create(ChangeFileExt(ParamStr(0), '.profbin'));
  252. ProfilerDataFile:= TProfilePlainTextMMap.Create(ChangeFileExt(ParamStr(0), '.profraw'));
  253. ProfilerEnabled:= true;
  254. {$ENDIF}
  255. finalization
  256. {$IFDEF PROFILER_ENABLE}
  257. ProfilerEnabled:= false;
  258. UninstallWriterThread;
  259. FreeAndNil(ProfilerDataFile);
  260. {$ENDIF}
  261. end.