Analyzer - Documentation
Analyzer is a PureBasic Profiler originally written by Remi Meier. It can be used as an PureBasic IDE tool
to measure how often lines of your PureBasic code are called and more importantly how much time they consume
at runtime. It consists of two components, the analyzer tool and the analyzer runtime. The tool takes some
arguments to merge a source file and its include files into a single file (using a third merge tool). The single
file is then complemented with code for measurement as well as with some GUI code to display the results at the
end of the runtime of your program.
Installation
Copy the Linux binary
analyzer or the Windows executable
analyzer.exe to your PureBasic IDE
tools directory, for example
/home/user/purebasic_tools/. The program will create a preferences file
analyzer.ini in its program directory. If you don't have a binary/executable you can create one from
the source file
analyzerTool.pb. The Threadsafe compiler option must be enabled and if you're on
Windows you might have to remove the subsystem
gtk2 in the compiler options in case it is set. With your
tool binary in place, in the PureBasic IDE go to
Tools > Configure Tools > New
and set the binary/executable as well as the other settings as shown. The double quotes allow for filenames
containing spaces to work correctly as well as identifying empty arguments passed by the IDE. Keep the order of arguments as the first two files might be modified by the tool.
---------------------------------------------------------------
Commandline : /home/user/purebasic_tools/analyzer
Arguments : "%COMPILEFILE" "%TEMPFILE" "%FILE"
Name : analyzer
Event to trigger the tool : Before Compile/Run
also check : [x] Wait until tool quits
---------------------------------------------------------------
Using the tool
By running the tool from the tools menu you can disable/enable it. If disabled, it will not interfere with the compilation process.
Make sure analyzer is enabled and open a new tab/file in the IDE and paste the following code.
x = 10
If 0 = Delay(x)
Delay(2* x)
EndIf
If you press F5 to Compile/Run the code then analyzer will ask for confirmation.
You can cancel with
Exit (or with the ESC key or by closing the window) to compile/run your code
without analyzer. After clicking
Continue (or pressing the Return key) the merge tool will merge
include files if there are any. It would also ask for confirmation to overwrite the
*.merged.pb
file in case it already exists. After the merge, analyzer will ask you for confirmation to
overwrite the input file.
If you would choose
No then you would get a second filename option in case don't want to overwrite the
input file but want to create a separate file instead, for example if you use analyzer as a commandline
program, passing an original file that you don't want to modify. It will then also ask you if you want
to open that new file in the IDE (if run as a tool), but the compilation process started by the IDE will
still compile/run the input file. If you open and run the separately created file manually in the IDE then
keep in mind that the analyzer tool will be triggered again during the compilation process (if you didn't
disable the tool beforehand via the tools menu) in which case you want to cancel it by
Exit/ESC
because the file already contains analyzer code and you would want to compile/run it as is.
Choose
Continue to modify the temporary file given by the IDE and close the tool with
Exit/ESC when
it has finished.
Using the runtime GUI
When the IDE runs your code the analyzer runtime will give you a notice if the debugger is enabled and
you could stop the program, then disable the debugger for your source code and compile/run again.
After choosing
Yes the original code will finish quickly and the analyzer runtime will display the results
while the program is still running.
The first tab
Measurements contains details about lines that have been measured:
Line Nb | The line number in the merged file. |
Total Time | The sum of milliseconds spent by that line. |
%-Time | The percentage of Total Time relative to all measurements. |
Time/Call | The average time per call as calculated by Total Time / Calls. |
Calls | The number of times the line has been called. |
Orig. Line Nb,
Orig. File,
Orig. Filename |
The original code location. In this case a temporary file because the file has not been saved
before compilation. |
The second tab
Other Lines contains lines that have been ignored, like
EndIf or other
keywords, or lines for which analyzer has been disabled.
You can double-click a list entry to open the location in the IDE. For this to work the tool program
must have been run as a tool like in this case at least once to know the PureBasic IDE program path.
With the top left input field you can filter the shown results. The top right input field limits the
number of shown list entries. The numbers in the panel tab headers show the number of shown / matching /
total list entries. For example filtering for
dela and setting
show max. to only 1 will
display
(1/2/3) for the tab
Measurements and only 1 of the 2 matching lines containing
Delay() will be shown from the 3 total lines.
If the preference option
showZeroTimeLines is set to 0 then lines with
a
Total Time of 0 will not show up in the tab
Measurements and in the entry count in its
header, i.e. in this example the line containing
x = 10 would not be displayed at all.
Control Comments
There are 4 control comments that can be used in the code and they start with
; analyzer:
(exactly, including one space). They can be indented but there must not be any code before them on the
line, otherwise the tool will throw a notice in the tool log window.
With
analyzer:disable and
analyzer:enable you can deactivate the measurement for some lines:
Delay(5) ; measured (enabled by default)
; analyzer:disable
Delay(10) ; not measured
Delay(20) ; not measured
; analyzer:enable
Delay(30) ; measured
Delay(40) ; measured
You might want to start your mainfile with
analyzer:disable and only enable it for specific code.
With
analyzer:startblock and
analyzer:endblock you can measure multiple lines of code as one
block.
; analyzer:startblock
Delay(10)
Delay(20)
; analyzer:startblock block name
Delay(30)
Delay(40)
; analyzer:endblock
; analyzer:endblock
analyzer:startblock can have an optional text for displaying in the result list ('block name' in
the example). The unnamed first block above will measure 100ms and the second block will measure 70ms.
The last
endblock finishes the measurement for the first
startblock. The lines containing
Delay() will still also be measured individually unless
analyzer:disable is used, which in turn
doesn't affect the block measurement.
What is [not] being measured
Analyzer will be disabled automatically for blocks of
EnableASM /
DisableASM
Structure /
EndStructure
Structureunion /
EndStructureUnion
Enumeration /
EndEnumeration
Interface /
EndInterface
DataSection /
EndDataSection
Macro /
EndMacro
Import /
EndImport
It will also not measure individually (but still as part of a block or call) lines of
- inline
! asm
-
ProcedureReturn ..
- conditions/heads/tails of loops
-
Select /
Case /
Default statements (code within the
Case /
Default
section itself will be measured as usual)
-
Debug statements
But it will measure conditions of
If and
ElseIf (I guess the same could work for
While and
Until conditions but that's not implemented currently).
It supports line concatenation - each part will be measured individually if applicable:
x + 1 : If x : x + 1 : EndIf
It supports split lines / line continuation. such lines will be measured and displayed as one.
Procedure p(p1,
p2)
EndProcedure
If x And
x +
1 Or
p(11,
22)
EndIf
Macro definitions/bodies will not be measured, but macro invocations will be measured as usual. If your
macros contain code like
[End]Procedure or
[End]Structure or otherwise affect the natural nesting
of code then you will most likely get a compilation error with the code generated by analyzer.
The Merge Tool
The merge tool has been improved a bit but currently still only supports bare string literals for include
statements, no string concatenation or evaluation of constants.
CompilerIf /
CompilerSelect
statements are also not being evaluated. That might not be a problem if the merger just includes files for
example within dead
CompilerIf 0 branches because the included code will still be ignored by the
PureBasic compiler. But in the following example the merger will resolve the first
IncludeFile
statement but not the second
XIncludeFile statement (because the file has been included already),
whereas the PureBasic compiler would handle it the other way round:
CompilerIf 0
IncludeFile "file"
CompilerEndIf
XIncludeFile "file"
Another issue with this is that the merging of the following code will fail no matter which OS you're
currently working with, because the merge tool will try to resolve both include statements and it will
result in a file error for one of them:
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
XIncludeFile "C:\path\file"
CompilerElse
XIncludeFile "/path/file"
CompilerEndIf
The merge tool will replace occurrences of
#PB_Compiler_IsMainFile and
#PB_Compiler_IsIncludeFile
as follows depending on where they occur:
#PB_Compiler_IsMainFile |
(1 + 0 * #PB_Compiler_IsMainFile) |
In the mainfile |
#PB_Compiler_IsMainFile |
(0 + 0 * #PB_Compiler_IsMainFile) |
In an include file |
#PB_Compiler_IsIncludeFile |
(0 + 0 * #PB_Compiler_IsIncludeFile) |
In the mainfile |
#PB_Compiler_IsIncludeFile |
(1 + 0 * #PB_Compiler_IsIncludeFile) |
In an include file |
Performance / Accuracy
The analyzer code could impact the performance of you code quite a bit depending on how much of your code
is being measured. When in doubt, measure only relevant parts of your code and/or use block measurement to
decrease the per-line impact. You might also want to
analyzer:disable before blocks to disable the individual
measurement of lines within the block.
As for accuracy. If you are testing something with Delay() keep in mind that a Delay(4) could also take 10
milliseconds. But in general, lines that take very little time are measured less accurate. That could be
compensated for if the line gets called more often. The addition within the following loop will on its own
never take a millisecond but it's called often enough so that ElapsedMilliseconds() returns a different value
before and after the line during some iterations, which will add up finally.
x+1
x+1
x+1
For i=0 To 100000
x+1
Next
Preferences
The analyzer tool binary/executable will maintain a preference file
<program name>.ini (usually
analyzer.ini) in its program directory. The settings will only be written back to the file if the tool
exits normally after finishing successfully. The following settings exist:
analyzerActive |
Bool, 1 (Default) or 0 |
Defines if the tool will interfere with the compilation process when triggered as a tool. |
inlineProc |
Bool, 1 (Default) or 0 |
Defines if the measurement code will be inlined for normal lines and blocks or if a function
call is being used instead. If/ElseIf conditions can't be inlined and will always use
function calls. |
showZeroTimeLines |
Bool, 1 (Default) or 0 |
Defines if measurements with a total time of 0 will appear in the results list / GUI. |
lastPbToolIde |
|
Full path to the PureBasic IDE program that will be used to open pb source files. Will be updated
with the path of the IDE that started the tool. Will be passed on to the runtime (which doesn't
have access to the tooling environment) so it can let you open result lines in the IDE. Will also
be used by analyzer if not started from the IDE, i.e. if started as a commandline program. |
lastPbToolCompiler |
|
Full path to the PureBasic compiler program that will be used to resolve occurrences of
#PB_Compiler_Home in include statements during the run of the merge tool. Will be updated with
the path to the currently used compiler passed to the tool by the IDE. The merge tool will receive a trimmed version (without the directory and binary name compilers/pbcompiler, or Compilers/pbcompiler.exe on Windows) that matches
the format of #PB_Compiler_Home. Will also be used by analyzer if not started from the IDE, i.e. if started as a commandline program. |
toolWindowWidth / toolWindowHeight |
Integer, Pixels |
Stores the tool window size. (The size of the runtime results window is not stored because currently
it doesn't know the tool's preferences file since it basically runs as part of your own program) |
writeAnalysisTxt |
Bool, 1 or 0 (Default) |
Defines if the results should be written to a file analysis.txt in the runtime execution
directory (depending on your compiler options that would be the directory for temporary files used by the IDE or the directory of your source code). |
stickyToolWindow |
Bool, 1 (Default) or 0 |
Defines if the tool window should be sticky / stay on top. |
stickyRuntimeWindow |
Bool, 1 (Default) or 0 |
Defines if the runtime results window should be sticky / stay on top. |
Example preferences file
/home/user/purebasic_tools/analyzer.ini:
analyzerActive = 1
inlineProc = 1
showZeroTimeLines = 1
lastPbToolIde = /home/user/purebasic_570_lts/compilers/purebasic
lastPbToolCompiler = /home/user/purebasic_570_lts/compilers/pbcompiler
toolWindowWidth = 627
toolWindowHeight = 470
writeAnalysisTxt = 0
stickyToolWindow = 1
stickyRuntimeWindow = 1
The default values of
lastPbToolIde and
lastPbToolCompiler will be empty, so if you
want to open code lines from within the results window or if you are using the constant
#PB_Compiler_Home in include statements then you have to start analyzer as a tool from the IDE at
least once (It is sufficient to start it from menu without any arguments) or you have to create/edit
the file
analyzer.ini manually before using analyzer as a command-line program if you need to
access these settings.
If you start analyzer as a command-line program (or by drag'n'dropping a source file onto it) then make
sure that analyzer is enabled (via the PureBasic IDE tools menu entry or by editing the preferences file and setting
analyzerActive to
1). Otherwise the program will silently ignore the task.
Some Other Infos
Control comments take effect in compilation order, i.e from top to bottom in the source code, not
in runtime order.
; analyzer:disable ; disabled
Procedure pE()
; analyzer:enable ; enabled
Delay(10) ; measured
EndProcedure
Delay(20) ; measured
Procedure pD()
; analyzer:disable ; disabled
Delay(30) ; not measured
EndProcedure
Delay(40) ; not measured
pE() ; not measured
pD() ; not measured
Delay(50) ; not measured
; analyzer:enable ; enabled
Delay(60) ; measured
pE() ; measured
pD() ; measured
Delay(70) ; measured
Analyzer shows percentages as relative to the total of measurements, even if some
measurements are overlapping. To show percentages as relative to the actual time the
program has spent would require analysis of call hierarchy or the additional overhead of
watching caller/parent measurements.
Procedure p() ; shown as actual ;
Delay(100) ; 25% 50% ;
Delay(100) ; 25% 50% ;
EndProcedure
p() ; 50% 100% ;
analyzer:enable /
analyzer:disable don't maintain a stack so you shouldn't
nest them. An
analyzer:disable will disable the analyzer even if there were
multiple
analyzer:enables beforehand and vice versa. Blocks on the other hand can
be nested.
Threading is not supported by analyzer. If measured code is called by multiple threads the
analyzer data and processes are likely to get screwed up. Adding mutexes would come with a
performance overhead and also would probably change the execution conditions. And even then
a time measured in one thread might include time spent by another thread or spent waiting,
so it seems to make little sense to support it.
That's it for now.
Thanks to Remi Meier and the Forums / the Community!