mirror of https://gitlab.com/basile.b/dexed.git
559 lines
13 KiB
Plaintext
559 lines
13 KiB
Plaintext
unit u_profileviewer;
|
|
|
|
{$I u_defines.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, FileUtil, TASources, TAGraph, TATransformations, TASeries,
|
|
TATools, Forms, Controls, Graphics, Dialogs, ExtCtrls, Menus, ComCtrls,
|
|
StdCtrls, TALegend, math,
|
|
u_widget, u_common, u_stringrange, u_dsgncontrols, u_ddemangle,
|
|
u_interfaces, u_observer, u_writableComponent, u_controls;
|
|
|
|
type
|
|
|
|
TProfileViewerOptionsBase = class(TWritableLfmTextComponent)
|
|
private
|
|
fHideAttributes: boolean;
|
|
fHideStandardLibraryCalls: boolean;
|
|
fHideRunTimeCalls: boolean;
|
|
fOtherExclusions: TStringList;
|
|
procedure setOtherExclusions(value: TStringList);
|
|
public
|
|
constructor create(aOwner: TComponent); override;
|
|
destructor destroy; override;
|
|
procedure assign(value: TPersistent); override;
|
|
published
|
|
property hideAttributes: boolean read fHideAttributes write fHideAttributes;
|
|
property hideStandardLibraryCalls: boolean read fHideStandardLibraryCalls write fHideStandardLibraryCalls;
|
|
property hideRuntimeCalls: boolean read fHideRunTimeCalls write fHideRunTimeCalls;
|
|
property otherExclusions: TStringList read fOtherExclusions write setOtherExclusions;
|
|
end;
|
|
|
|
TProfileViewerOptions = class(TProfileViewerOptionsBase, IEditableOptions)
|
|
private
|
|
fBackup: TProfileViewerOptionsBase;
|
|
function optionedWantCategory(): string;
|
|
function optionedWantEditorKind: TOptionEditorKind;
|
|
function optionedWantContainer: TPersistent;
|
|
procedure optionedEvent(event: TOptionEditorEvent);
|
|
function optionedOptionsModified: boolean;
|
|
public
|
|
constructor create(aOwner: TComponent); override;
|
|
destructor destroy; override;
|
|
end;
|
|
|
|
TProfileViewerWidget = class(TDexedWidget, IProjectObserver)
|
|
btnOpen: TDexedToolButton;
|
|
btnOpts: TDexedToolButton;
|
|
btnProj: TDexedToolButton;
|
|
btnRefresh: TDexedToolButton;
|
|
button0: TDexedToolButton;
|
|
ChartToolset1: TChartToolset;
|
|
ChartToolset1DataPointHintTool1: TDataPointHintTool;
|
|
ImageList1: TImageList;
|
|
selPieSource: TComboBox;
|
|
datNumCalls: TListChartSource;
|
|
datTreeTime: TListChartSource;
|
|
datFuncTime: TListChartSource;
|
|
datPerCall: TListChartSource;
|
|
Panel1: TPanel;
|
|
pie: TChart;
|
|
list: TListView;
|
|
pieSeries: TPieSeries;
|
|
Splitter1: TSplitter;
|
|
procedure btnOpenClick(Sender: TObject);
|
|
procedure btnProjClick(Sender: TObject);
|
|
procedure btnRefreshClick(Sender: TObject);
|
|
procedure btnOptsClick(Sender: TObject);
|
|
procedure selPieSourceSelect(Sender: TObject);
|
|
procedure selPieSourceSelectionChange(Sender: TObject; User: boolean);
|
|
procedure Splitter1CanResize(Sender: TObject; var NewSize: Integer; var Accept: Boolean);
|
|
procedure Splitter1Moved(Sender: TObject);
|
|
private
|
|
fOptions: TProfileViewerOptions;
|
|
logFname: string;
|
|
fProj: ICommonProject;
|
|
procedure updateIcons;
|
|
procedure clearViewer;
|
|
procedure updateFromFile(const fname: string);
|
|
procedure updatePie;
|
|
procedure listCompare(Sender: TObject; item1, item2: TListItem; Data: Integer; var Compare: Integer);
|
|
procedure projNew(project: ICommonProject);
|
|
procedure projChanged(project: ICommonProject);
|
|
procedure projClosing(project: ICommonProject);
|
|
procedure projFocused(project: ICommonProject);
|
|
procedure projCompiling(project: ICommonProject);
|
|
procedure projCompiled(project: ICommonProject; success: boolean);
|
|
public
|
|
constructor create(aOwner: TComponent); override;
|
|
destructor destroy; override;
|
|
procedure reloadCurrent;
|
|
end;
|
|
|
|
const optFname = 'profileviewer.txt';
|
|
|
|
implementation
|
|
{$R *.lfm}
|
|
|
|
constructor TProfileViewerOptionsBase.create(aOwner: TComponent);
|
|
begin
|
|
inherited create(aOwner);
|
|
fOtherExclusions := TStringList.Create;
|
|
end;
|
|
|
|
destructor TProfileViewerOptionsBase.destroy;
|
|
begin
|
|
fOtherExclusions.free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TProfileViewerOptionsBase.assign(value: TPersistent);
|
|
var
|
|
s: TProfileViewerOptionsBase;
|
|
begin
|
|
if value is TProfileViewerOptionsBase then
|
|
begin
|
|
s := TProfileViewerOptionsBase(value);
|
|
fOtherExclusions.Assign(s.fOtherExclusions);
|
|
fHideRunTimeCalls:=s.fHideRunTimeCalls;
|
|
fHideStandardLibraryCalls:=fHideStandardLibraryCalls;
|
|
end
|
|
else inherited;
|
|
end;
|
|
|
|
procedure TProfileViewerOptionsBase.setOtherExclusions(value: TStringList);
|
|
begin
|
|
fOtherExclusions.assign(value);
|
|
end;
|
|
|
|
constructor TProfileViewerOptions.create(aOwner: TComponent);
|
|
var
|
|
s: string;
|
|
begin
|
|
inherited create(aOwner);
|
|
fBackup := TProfileViewerOptionsBase.create(nil);
|
|
EntitiesConnector.addObserver(self);
|
|
s := getDocPath + optFname;
|
|
if s.fileExists then
|
|
loadFromFile(s);
|
|
end;
|
|
|
|
destructor TProfileViewerOptions.destroy;
|
|
begin
|
|
saveTofile(getDocPath + optFname);
|
|
EntitiesConnector.removeObserver(self);
|
|
fBackup.free;
|
|
inherited;
|
|
end;
|
|
|
|
function TProfileViewerOptions.optionedWantCategory(): string;
|
|
begin
|
|
result := 'Profile viewer';
|
|
end;
|
|
|
|
function TProfileViewerOptions.optionedWantEditorKind: TOptionEditorKind;
|
|
begin
|
|
result := oekGeneric;
|
|
end;
|
|
|
|
function TProfileViewerOptions.optionedWantContainer: TPersistent;
|
|
begin
|
|
result := self;
|
|
end;
|
|
|
|
procedure TProfileViewerOptions.optionedEvent(event: TOptionEditorEvent);
|
|
begin
|
|
case event of
|
|
oeeAccept:
|
|
begin
|
|
fBackup.assign(self);
|
|
TProfileViewerWidget(owner).reloadCurrent;
|
|
end;
|
|
oeeCancel:
|
|
begin
|
|
self.assign(fBackup);
|
|
TProfileViewerWidget(owner).reloadCurrent;
|
|
end;
|
|
oeeSelectCat:
|
|
fBackup.assign(self);
|
|
oeeChange:
|
|
TProfileViewerWidget(owner).reloadCurrent;
|
|
end;
|
|
end;
|
|
|
|
function TProfileViewerOptions.optionedOptionsModified: boolean;
|
|
begin
|
|
result := false;
|
|
end;
|
|
|
|
constructor TProfileViewerWidget.create(aOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
EntitiesConnector.addObserver(self);
|
|
fOptions:= TProfileViewerOptions.create(self);
|
|
clearViewer;
|
|
updatePie;
|
|
list.OnCompare:=@listCompare;
|
|
selPieSourceSelect(nil);
|
|
list.PopupMenu := TListViewCopyMenu.create(list);
|
|
end;
|
|
|
|
destructor TProfileViewerWidget.destroy;
|
|
begin
|
|
EntitiesConnector.removeObserver(self);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.projNew(project: ICommonProject);
|
|
begin
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.projChanged(project: ICommonProject);
|
|
begin
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.projClosing(project: ICommonProject);
|
|
begin
|
|
if project = fProj then
|
|
fProj := nil;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.projFocused(project: ICommonProject);
|
|
begin
|
|
fProj := project;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.projCompiling(project: ICommonProject);
|
|
begin
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.projCompiled(project: ICommonProject; success: boolean);
|
|
begin
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.reloadCurrent;
|
|
var
|
|
fname: string;
|
|
begin
|
|
if logFname.isNotEmpty and logFname.fileExists then
|
|
updateFromFile(logFname)
|
|
else
|
|
begin
|
|
fname := GetCurrentDir + DirectorySeparator + 'trace.log';
|
|
if fileExists(fname) then
|
|
begin
|
|
updateFromFile(fname);
|
|
logFname:=fname;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.btnRefreshClick(Sender: TObject);
|
|
begin
|
|
reloadCurrent;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.btnOptsClick(Sender: TObject);
|
|
begin
|
|
getOptionsEditor.showOptionEditor(fOptions as IEditableOptions);
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.selPieSourceSelect(Sender: TObject);
|
|
begin
|
|
case selPieSource.ItemIndex of
|
|
1: pieSeries.Source := datTreeTime;
|
|
2: pieSeries.Source := datFuncTime;
|
|
3: pieSeries.Source := datPerCall;
|
|
else pieSeries.Source := datNumCalls;
|
|
end;
|
|
updateIcons;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.btnOpenClick(Sender: TObject);
|
|
begin
|
|
with TOpenDialog.Create(nil) do
|
|
try
|
|
if logFname.fileExists and logFname.isNotEmpty then
|
|
FileName := logFname;
|
|
if execute then
|
|
begin
|
|
updateFromFile(filename);
|
|
logFname := FileName;
|
|
end;
|
|
finally
|
|
free;
|
|
end;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.btnProjClick(Sender: TObject);
|
|
var
|
|
fname: string;
|
|
begin
|
|
if assigned(fProj) then
|
|
begin
|
|
fname := fProj.outputFilename.extractFileDir + DirectorySeparator + 'trace.log';
|
|
if fileExists(fname) then
|
|
begin
|
|
updateFromFile(fname);
|
|
logFname:=fname;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.updatePie;
|
|
begin
|
|
pieSeries.FixedRadius:= max(1, pie.Height div 2 - 10);
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.selPieSourceSelectionChange(Sender: TObject;User: boolean);
|
|
begin
|
|
updatePie;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.Splitter1CanResize(Sender: TObject;
|
|
var NewSize: Integer; var Accept: Boolean);
|
|
begin
|
|
if accept then
|
|
updatePie;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.Splitter1Moved(Sender: TObject);
|
|
begin
|
|
updatePie;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.clearViewer;
|
|
begin
|
|
list.Clear;
|
|
pieSeries.Clear;
|
|
datFuncTime.Clear;
|
|
datNumCalls.Clear;
|
|
datTreeTime.Clear;
|
|
datPerCall.Clear;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.updateIcons;
|
|
var
|
|
b: TBitmap;
|
|
i: integer;
|
|
begin
|
|
list.SmallImages := nil;
|
|
ImageList1.BeginUpdate;
|
|
ImageList1.Clear;
|
|
b := TBitmap.Create;
|
|
try
|
|
for i:= 0 to pieSeries.Count-1 do
|
|
begin
|
|
b.SetSize(12,12);
|
|
b.Canvas.Brush.Color := pieSeries.GetColor(i);
|
|
b.Canvas.Pen.Color:= clblack;
|
|
b.Canvas.Brush.Style := bsSolid;
|
|
b.Canvas.Pen.Style := psSolid;
|
|
b.Canvas.Pen.Width := 1;
|
|
b.Transparent := false;
|
|
b.Canvas.Rectangle(Rect(0,0,12,12));
|
|
ImageList1.Add(b, nil);
|
|
list.Items.Item[i].ImageIndex:=i;
|
|
b.Clear;
|
|
end;
|
|
finally
|
|
b.Free;
|
|
ImageList1.EndUpdate;
|
|
list.SmallImages := ImageList1;
|
|
end;
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.updateFromFile(const fname: string);
|
|
var
|
|
log: string;
|
|
rng: TStringRange = (ptr:nil; pos:0; len: 0);
|
|
idt: string;
|
|
fnc: qword;
|
|
fft: qword;
|
|
ftt: qword;
|
|
fpc: qword;
|
|
exc: string;
|
|
|
|
procedure fillRow();
|
|
var
|
|
itm: TListItem;
|
|
c: TColor;
|
|
begin
|
|
list.AddItem('', nil);
|
|
itm := list.Items[list.Items.Count-1];
|
|
itm.SubItems.Add(fnc.ToString);
|
|
itm.SubItems.Add(fft.ToString);
|
|
itm.SubItems.Add(ftt.ToString);
|
|
itm.SubItems.Add(fpc.ToString);
|
|
itm.SubItems.Add(idt);
|
|
c := Random($70F0F0F0) + $F0F0F0F;
|
|
datNumCalls.Add(0, fnc, idt, c);
|
|
datFuncTime.Add(0, fft, idt, c);
|
|
datTreeTime.Add(0, ftt, idt, c);
|
|
datPerCall.Add(0, fpc, idt, c);
|
|
end;
|
|
|
|
function canShow: boolean;
|
|
begin
|
|
result := true;
|
|
if fOptions.hideRuntimeCalls and (Pos(' core.', idt) > 0) then
|
|
exit(false);
|
|
if fOptions.hideStandardLibraryCalls and (Pos(' std.', idt) > 0) then
|
|
exit(false);
|
|
if fOptions.otherExclusions.Count > 0 then
|
|
for exc in fOptions.otherExclusions do
|
|
if Pos(exc, idt) > 0 then
|
|
exit(false);
|
|
end;
|
|
|
|
procedure filterAttributes;
|
|
const
|
|
a: array[0..12] of string = ('const','pure','nothrow','@safe','@nogc',
|
|
'@trusted', '@system', 'immutable', 'inout', 'return', '@property',
|
|
'shared', 'scope');
|
|
var
|
|
i: integer = 0;
|
|
j: integer;
|
|
p: integer;
|
|
s: string;
|
|
begin
|
|
if not fOptions.hideAttributes then
|
|
exit;
|
|
p := pos('(', idt);
|
|
if p = 0 then
|
|
p := integer.MaxValue;
|
|
for s in a do
|
|
begin
|
|
j := pos(s, idt);
|
|
if (j > 0) then
|
|
j += s.length + 1;
|
|
if (j < p) and (j >= i) then
|
|
i := j;
|
|
end;
|
|
if i > 0 then
|
|
idt := idt[i..idt.length];
|
|
end;
|
|
|
|
begin
|
|
clearViewer;
|
|
|
|
if not fname.fileExists or (fname.extractFileName <> 'trace.log') then
|
|
exit;
|
|
|
|
with TStringList.Create do
|
|
try
|
|
loadFromFile(fname);
|
|
log := strictText;
|
|
finally
|
|
free;
|
|
end;
|
|
|
|
if log.length = 0 then
|
|
exit;
|
|
|
|
// ======== Timer Is 35.... ============
|
|
rng.init(log);
|
|
rng.popUntil('=')^.popUntil(['0','1','2','3','4','5','6','7','8','9']);
|
|
if rng.empty then
|
|
exit;
|
|
idt := rng.nextWord;
|
|
|
|
// columns headers
|
|
if rng.popLine^.empty then
|
|
exit;
|
|
if rng.popLine^.empty then
|
|
exit;
|
|
if rng.popLine^.empty then
|
|
exit;
|
|
if rng.popLine^.empty then
|
|
exit;
|
|
if rng.popLine^.empty then
|
|
exit;
|
|
|
|
list.BeginUpdate;
|
|
|
|
// each function
|
|
while true do
|
|
begin
|
|
|
|
idt:= 'unknown function';
|
|
fnc:= 0;
|
|
fft:= 0;
|
|
ftt:= 0;
|
|
fpc:= 0;
|
|
// num calls
|
|
fnc := StrToQWordDef(rng.nextWord, 0);
|
|
if not rng.empty then
|
|
rng.popFront
|
|
else
|
|
break;
|
|
// function time
|
|
fft := StrToQWordDef(rng.nextWord, 0);
|
|
if not rng.empty then
|
|
rng.popFront
|
|
else
|
|
break;
|
|
// tree time
|
|
ftt := StrToQWordDef(rng.nextWord, 0);
|
|
if not rng.empty then
|
|
rng.popFront
|
|
else
|
|
break;
|
|
// per call
|
|
fpc := StrToQWordDef(rng.nextWord, 0);
|
|
if not rng.empty then
|
|
rng.popFront
|
|
else
|
|
break;
|
|
// function name
|
|
rng.popWhile(' ')^.empty;
|
|
idt := demangle(rng.takeUntil(#10).yield);
|
|
|
|
// apply options and add item
|
|
if canShow then
|
|
begin
|
|
filterAttributes;
|
|
fillRow;
|
|
end;
|
|
|
|
if not rng.empty then
|
|
rng.popFront
|
|
else
|
|
break;
|
|
end;
|
|
|
|
list.EndUpdate;
|
|
selPieSourceSelect(nil);
|
|
|
|
end;
|
|
|
|
procedure TProfileViewerWidget.listCompare(Sender: TObject; item1, item2: TListItem; Data: Integer; var Compare: Integer);
|
|
var
|
|
i1, i2: qword;
|
|
col: Integer;
|
|
begin
|
|
col := list.SortColumn;
|
|
if col = 5 then
|
|
begin
|
|
Compare := AnsiCompareStr(item1.SubItems[3], item2.SubItems[3]);
|
|
end
|
|
else if col <> 0 then
|
|
begin
|
|
i1 := item1.SubItems[col-1].ToInt64;
|
|
i2 := item2.SubItems[col-1].ToInt64;
|
|
if (i1 = i2) then
|
|
Compare := 0
|
|
else if (i1 < i2) then
|
|
Compare := -1
|
|
else
|
|
Compare := 1;
|
|
end;
|
|
if list.SortDirection = sdDescending then
|
|
Compare := -Compare;
|
|
end;
|
|
|
|
end.
|
|
|