Files
lazarus-ccr/components/jvcllaz/run/JvCmp/jvprofilerform.pas

535 lines
12 KiB
ObjectPascal

{-----------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/MPL-1.1.html
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.
The Original Code is: JvProfilerForm.PAS, released on 2002-05-26.
The Initial Developer of the Original Code is Certified Software Corp. [certsoft att quest-net dott com]
Portions created by Peter Thörnqvist are Copyright (C) 1996 Certified Software Corp.
All Rights Reserved.
Contributor(s): Peter Thörnqvist [peter3 at sourceforge dot net]
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.delphi-jedi.org
Known Issues:
Use QueryPerformanceCounter / Frequency instead of GetTickCount (the high resolution timer)
-----------------------------------------------------------------------------}
// $Id$
unit JvProfilerForm;
{$mode objfpc}{$H+}
interface
uses
Classes, Dialogs, ComCtrls, StdCtrls, Controls, ExtCtrls, Forms;
const
MaxProfEntries = 1024; { maximum number of "blocks" to profile }
MaxStackSize = 1024; { maximum nesting of blocks at any one time }
var
OddClick: Boolean = True;
type
TJvProfileInfo = array [0..MaxProfEntries - 1] of record
InOutTime: Longint;
TimeSpent: Longint;
Calls: Longint;
StringID: string;
end;
TProcStack = array [1..MaxStackSize] of record
CallerID: Integer;
EntryTime: Longint;
end;
TProfReport = class(TForm)
Panel1: TPanel;
SaveBtn: TButton;
lvReport: TListView;
OKBtn: TButton;
TrimBtn: TButton;
procedure FormShow(Sender: TObject);
procedure lvReportColumnClick(Sender: TObject; Column: TListColumn);
procedure SaveBtnClick(Sender: TObject);
procedure OKBtnClick(Sender: TObject);
procedure TrimBtnClick(Sender: TObject);
public
StartTime: Integer;
EndTime: Integer;
LastProc: Integer;
ProfileInfo: TJvProfileInfo;
end;
TJvProfiler = class(TComponent)
private
FProfileInfo: TJvProfileInfo;
FNames: TStringList;
FStack: TProcStack;
FStartTime: Longint;
FEndTime: Longint;
FLastProc: Integer;
FStackSize: Integer;
FEnabled: Boolean;
FStarted: Boolean;
FSorted: Boolean;
FOnStart: TNotifyEvent;
FOnStop: TNotifyEvent;
function GetNames: TStrings;
procedure SetNames(Value: TStrings);
procedure SetEnabled(Value: Boolean);
procedure SetSorted(Value: Boolean);
protected
procedure DoStart; virtual;
procedure DoStop; virtual;
procedure Initialize; virtual;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Start;
procedure EnterID(ID: Integer);
procedure EnterName(const AName: string);
procedure ExitName(const AName: string);
procedure ExitID(ID: Integer);
procedure Stop;
procedure ShowReport;
published
property Enabled: Boolean read FEnabled write SetEnabled default False;
property Names: TStrings read GetNames write SetNames;
property Sorted: Boolean read FSorted write SetSorted default False;
property OnStart: TNotifyEvent read FOnStart write FOnStart;
property OnStop: TNotifyEvent read FOnStop write FOnStop;
end;
implementation
uses
LCLVersion,
{$IFDEF UNIX} // RPH 16Apr2020 - Fix for Darwin
baseunix, unix,
{$IFDEF LINUX}
linux, users,
{$ENDIF}
{$ELSE}
Windows,
{$ENDIF}
SysUtils, LazUTF8,
JvConsts, JvTypes, JvResources;
{$R *.lfm}
const
EmptyLine = '0.00';
DefHeader2 =
'Profiler 32 - (C) 1996 Certified Software Corp, portions Copyright (C) 1997 by Peter Thörnqvist; all rights reserved.';
{
type
PProfType = ^TProfType;
TProfType = record
InOutTime: Integer;
TimeSpent: Integer;
Calls: Integer;
StringID: string;
end;
PStackType = ^TStackType;
TStackType = record
CallerID: Integer;
EntryTime: Integer;
end;
}
function GetUserName: String;
// http://forum.lazarus.freepascal.org/index.php/topic,23171.msg138057.html#msg138057
{$IFDEF WINDOWS}
const
MaxLen = 256;
var
Len: DWORD;
WS: WideString = '';
Res: windows.BOOL;
{$ENDIF}
begin
Result := '';
{$IFDEF UNIX}
{$IF (DEFINED(LINUX)) OR (DEFINED(FREEBSD))}
Result := SysToUtf8(users.GetUserName(fpgetuid)); //GetUsername in unit Users, fpgetuid in unit BaseUnix
{$ELSE Linux/BSD}
Result := GetEnvironmentVariableUtf8('USER');
{$ENDIF UNIX}
{$ELSE}
{$IFDEF WINDOWS}
Len := MaxLen;
{$IFnDEF WINCE}
if Win32MajorVersion <= 4 then begin
SetLength(Result,MaxLen);
Res := Windows.GetUserName(@Result[1], Len);
//writeln('GetUserNameA = ',Res);
if Res then begin
SetLength(Result,Len-1);
Result := WinCPToUtf8(Result);
end else
SetLength(Result,0);
end
else
{$ENDIF NOT WINCE}
begin
SetLength(WS, MaxLen-1);
Res := Windows.GetUserNameW(@WS[1], Len);
//writeln('GetUserNameW = ',Res);
if Res then begin
SetLength(WS, Len - 1);
Result := Utf16ToUtf8(WS);
end else
SetLength(Result,0);
end;
{$ENDIF WINDOWS}
{$ENDIF UNIX}
end;
function GetComputerName : string;
{$IFDEF WINDOWS}
var
len : cardinal;
begin
Result := '';
len := MAX_COMPUTERNAME_LENGTH + 1;
SetLength(result, len);
if Windows.GetComputerName(PChar(result), len) then begin
SetLength(Result, len);
Result := WinCPToUTF8(Result);
end
else
RaiseLastWin32Error;
end;
{$ELSE}
begin
Result := GetHostName;
end;
{$ENDIF}
//=== { TJvProfiler } ========================================================
constructor TJvProfiler.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FNames := TStringList.Create;
end;
destructor TJvProfiler.Destroy;
begin
Stop;
FNames.Free;
inherited Destroy;
end;
procedure TJvProfiler.Initialize;
var
I: Integer;
begin
FEnabled := False;
FStarted := False;
FStartTime := 0;
FEndTime := 0;
FStackSize := 0;
FLastProc := -1;
{ build ID list }
for I := 0 to FNames.Count - 1 do
begin
if Length(Trim(FNames[I])) < 1 then
Continue; { skip empty ID's }
if FLastProc > MaxProfEntries then
raise EJVCLException.CreateResFmt(@RsEMaxNumberOfIDsExceededd, [MaxProfEntries - 1]);
Inc(FLastProc);
with FProfileInfo[FLastProc] do
begin
TimeSpent := 0;
Calls := 0;
StringID := FNames[I];
end;
end;
end;
procedure TJvProfiler.EnterID(ID: Integer);
var
Snap: Integer;
begin
if FEnabled then
begin
Snap := GetTickCount64;
if FStackSize > MaxStackSize then
raise EJVCLException.CreateResFmt(@RsEMaxStackSizeExceededd, [MaxStackSize]);
Inc(FStackSize);
with FStack[FStackSize] do
begin
EntryTime := Snap;
CallerID := ID
end;
with FProfileInfo[ID] do
begin
Inc(Calls);
InOutTime := Snap;
end;
end;
end;
procedure TJvProfiler.EnterName(const AName: string);
begin
EnterID(FNames.IndexOf(AName));
end;
procedure TJvProfiler.ExitName(const AName: string);
begin
ExitID(FNames.IndexOf(AName));
end;
procedure TJvProfiler.ExitID(ID: Integer);
var
Snap, Elapsed: Integer;
begin
if Enabled then
begin
Snap := GetTickCount64;
with FProfileInfo[ID] do
begin
Elapsed := Snap - InOutTime;
TimeSpent := TimeSpent + Elapsed;
end;
if FStackSize > 0 then
Dec(FStackSize);
if FStackSize > 0 then
with FProfileInfo[FStack[FStackSize].CallerID] do
TimeSpent := TimeSpent - Elapsed;
end;
end;
procedure TJvProfiler.DoStart;
begin
if Assigned(FOnStart) then
FOnStart(Self);
end;
procedure TJvProfiler.DoStop;
begin
if Assigned(FOnStop) then
FOnStop(Self);
end;
procedure TJvProfiler.Start;
begin
if FEnabled and not FStarted then
begin
// Initialize;
DoStart;
FStartTime := GetTickCount64;
FStarted := True;
end;
end;
procedure TJvProfiler.Stop;
begin
if FEnabled and FStarted then
begin
FEndTime := GetTickCount64;
DoStop;
FStarted := False;
end;
end;
function TJvProfiler.GetNames: TStrings;
begin
Result := FNames;
end;
procedure TJvProfiler.SetNames(Value: TStrings);
begin
FNames.Assign(Value);
Initialize;
end;
procedure TJvProfiler.SetEnabled(Value: Boolean);
begin
if FEnabled <> Value then
FEnabled := Value;
end;
procedure TJvProfiler.SetSorted(Value: Boolean);
begin
if FSorted <> Value then
begin
FSorted := Value;
FNames.Sorted := FSorted;
Initialize;
end;
end;
procedure TJvProfiler.ShowReport;
begin
if FEnabled then
begin
if FStarted then
Stop;
with TProfReport.Create(nil) do
begin
EndTime := FEndTime;
StartTime := FStartTime;
LastProc := FLastProc;
ProfileInfo := FProfileInfo;
ShowModal;
Free;
end;
end;
end;
//=== { TProfReport } ========================================================
procedure TProfReport.FormShow(Sender: TObject);
const
NumberFormat = '%4.2f';
var
ThisProc: Integer;
TotalSum: Integer;
LItem: TListItem;
begin
OddClick := True;
TotalSum := (EndTime - StartTime);
if TotalSum = 0 then
Exit;
lvReport.Items.BeginUpdate;
lvReport.Items.Clear;
for ThisProc := 0 to LastProc do
with ProfileInfo[ThisProc] do
begin
LItem := lvReport.Items.Add;
LItem.Caption := StringID; { function ID }
if Calls <> 0 then
begin
LItem.SubItems.Add(Format(NumberFormat, [TimeSpent * 1.0])); { Total time spent here }
LItem.SubItems.Add(IntToStr(Calls)); { Total number of calls }
LItem.SubItems.Add(Format(NumberFormat, [TimeSpent / Calls])); { average time }
LItem.SubItems.Add(Format(NumberFormat, [TimeSpent / TotalSum * 100.0])); { percentage }
end
else
begin
LItem.SubItems.Add(EmptyLine);
LItem.SubItems.Add('0');
LItem.SubItems.Add(EmptyLine);
LItem.SubItems.Add(EmptyLine);
end;
end;
Caption := Format(RsTotalElapsedTimedms, [RsDefCaption, TotalSum]);
lvReport.Items.EndUpdate;
end;
function IsFloat(S: string): Boolean;
var
x: Double;
begin
Result := TryStrToFloat(S, x);
end;
function DefSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer; stdcall;
var
l1, l2: Extended;
begin
if lParamSort = 0 then
Result := AnsiCompareText(lParam1.Caption, lParam2.Caption)
else
begin
if not IsFloat(lParam1.SubItems[lParamSort - 1]) then
l1 := -1.0
else
l1 := StrToFloat(lParam1.SubItems[lParamSort - 1]);
if not IsFloat(lParam2.SubItems[lParamSort - 1]) then
l2 := -1.0
else
l2 := StrToFloat(lParam2.SubItems[lParamSort - 1]);
Result := Round((l1 * 1000) - (l2 * 1000));
end;
if OddClick then
Result := -Result;
end;
procedure TProfReport.lvReportColumnClick(Sender: TObject; Column: TListColumn);
begin
{$IF LCL_FullVersion >= 2000000}
// lvReport.Items.BeginUpdate;
lvReport.CustomSort(TLVCompare(@DefSort), Column.Index);
OddClick := not OddClick;
// lvReport.Items.EndUpdate;
{$IFEND}
end;
procedure TProfReport.SaveBtnClick(Sender: TObject);
var
OutList: TStringList;
S: string;
I, J: Integer;
begin
with TSaveDialog.Create(nil) do
begin
Filter := RsTextFormatsasctxtinfdocAllFiles;
if Execute then
begin
OutList := TStringList.Create;
OutList.Add(Format(RsDefHeader, [DateToStr(Now), GetUserName,
GetComputerName]));
OutList.Add(DefHeader2);
S := '';
for I := 0 to lvReport.Columns.Count - 1 do
S := S + lvReport.Columns[I].Caption + Tab;
OutList.Add(S);
S := '';
with lvReport do
for I := 0 to Items.Count - 1 do
begin
with Items[I] do
begin
S := S + Caption + Tab;
for J := 0 to SubItems.Count - 1 do
S := S + SubItems[J] + Tab;
OutList.Add(S);
end;
S := '';
end;
OutList.SaveToFile(Filename);
OutList.Free;
end;
Free;
end;
end;
procedure TProfReport.OKBtnClick(Sender: TObject);
begin
Close;
end;
procedure TProfReport.TrimBtnClick(Sender: TObject);
var
I: Integer;
begin
lvReport.Items.BeginUpdate;
for I := lvReport.Items.Count - 1 downto 0 do
{ no calls = not used }
if lvReport.Items[I].SubItems[1] = '0' then
lvReport.Items.Delete(I);
lvReport.Items.EndUpdate;
end;
end.