Awesome
The Spy2 Profiling Framework for Pharo
Comprehending software execution is known to be a difficult, albeit essential task when building software. Spy2 is a profiling framework for Pharo, useful to gather some information about dynamic execution for a given piece of code. Spy2 allows one to easily build specific code profilers.
The latest stable version of Spy2 may be installed on Pharo 8 and Pharo 9 using the expression:
Metacello new
baseline: 'Spy2';
repository: 'github://ObjectProfile/Spy2:1.0';
load
The latest unstable version may be loaded using:
Metacello new
baseline: 'Spy2';
repository: 'github://ObjectProfile/Spy2';
load
Spy2 is also available on the Pharo Catalog Browser.
Spy2 depends on Roassal3 since some of the accompagning profilers use visualization. The core of Spy2, without any external dependencies, may be loaded using:
Metacello new
baseline: 'Spy2';
repository: 'github://ObjectProfile/Spy2';
load: 'Core'
Hapao
Spy2 embeds the Hapao test coverage tool. Hapao provides a visualization of the test coverage of your application. Hapao may be run from the World menu, as illustrated below:
A description of Hapao may be seen in this .pdf.
Here is a screenshot using the dark theme:
The same output using the light theme:
Short Tutorial about Spy
This short-tutorial assumes you have the complete installation of Spy, which includes Roassal3. The tutorial is about building a profiler that monitors the number of object creations for each class, and the number of times a method is executed. Afterward, a visualization is built to convey information gathered during an execution.
Step 1 - Building a skeleton for the profiler
We give the name DynAnalyzer
to our profiler and the package Spy2DemoProfiler
will contain all the related code. A skeleton is created by executing the following code in a playground:
Spy2 generate: 'DynAnalyzer' category: 'Spy2DemoProfiler'
The skeleton is made of four classes: DynAnalyzer
, DynAnalyzerPackage
, DynAnalyzerClass
, and DynAnalyzerMethod
.
Step 2 - Counting method execution
We need to add an instance variable to the class DynAnalyzerMethod
. The variable numberOfExecutions
keeps the number of execution for each method of the profiled application.
S2Method subclass: #DynAnalyzerMethod
instanceVariableNames: 'numberOfExecutions'
classVariableNames: ''
package: 'Spy2DemoProfiler'
The variable is initialized using:
DynAnalyzerMethod>>initialize
super initialize.
numberOfExecutions := 0
We need a way to access it:
DynAnalyzerMethod>>numberOfExecutions
^ numberOfExecutions
The variable has to be incremented at each method execution:
DynAnalyzerMethod>>beforeRun: methodName with: listOfArguments in: receiver
"This method is executed before each method of the profiled application.
Insert here the instrumentation you would like to perform during the profiling."
numberOfExecutions := numberOfExecutions + 1
Step 3 - Counting objects
We keep the number of objects in the class DynAnalyzerClass
:
S2Class subclass: #DynAnalyzerClass
instanceVariableNames: 'numberOfObjects'
classVariableNames: ''
package: 'Spy2DemoProfiler'
When created, we simply initialize this variable to 0:
DynAnalyzerClass>>initialize
super initialize.
numberOfObjects := 0.
A small utility method to increase the number of created objects:
DynAnalyzerClass>>increaseNumberOfObjects
numberOfObjects := numberOfObjects + 1
The number of objects has to be accessible:
DynAnalyzerClass>>numberOfObjects
^ numberOfObjects
We now need to make our profiler call increaseNumberOfObjects
whenever a new object is created. Spy2 offers a dedicated pluggin for this:
DynAnalyzer>>basicNewPlugin
<S2ClassPlugin>
^ S2SpecialBehaviorPlugin basicNewPluginOn: self
The method onBasicNew:
is called on the profiler when a new object is created. This is where we should increase the number of objects:
DynAnalyzer>>onBasicNew: obj
"obj is a newly created object"
(obj class getSpy: self) increaseNumberOfObjects
Step 4 - Visualization
We will redefine the method printOn:
to indicate the number of objects created:
DynAnalyzerClass>>printOn: str
super printOn: str.
str nextPutAll: self className.
str nextPutAll: ' - '.
str nextPutAll: numberOfObjects asString.
str nextPutAll: ' created objects'.
We build a dedicated visualization using Roassal3. Consider the method:
DynAnalyzer>>gtInspectorViewIn: composite
<gtInspectorPresentationOrder: -10>
composite roassal3
title: 'View';
initializeCanvas: [ self buildCanvas ]
The visualization is implemented using buildCanvas
:
DynAnalyzer>>buildCanvas
| c executedClasses label methods composite |
c := RSCanvas new.
executedClasses := self allClasses
select:
[ :clsSpy | clsSpy allMethods anySatisfy: [ :m | m numberOfExecutions > 0 ] ].
executedClasses
do: [ :clsSpy |
"Name of the class"
label := RSLabel new text: clsSpy className.
"Build the method visual elements"
methods := clsSpy methods
collect: [ :m |
RSBox new
model: m;
size: (m numberOfExecutions sqrt max: 4);
color: Color blue ]
as: RSGroup.
"Make all the method located as a grid"
RSGridLayout on: methods.
methods @ RSPopup.
"Locate the label above the methods"
RSLocation new
above;
move: label on: methods.
composite := RSComposite new.
composite model: clsSpy.
composite color: Color veryVeryLightGray.
composite shapes: {label} , methods.
composite @ RSDraggable.
composite padding: 10.
c add: composite.
composite @ RSPopup ].
RSFlowLayout on: c shapes.
RSNormalizer color
shapes: c shapes;
from: Color gray;
to: Color lightRed;
normalize: #numberOfObjects.
c @ RSCanvasController.
^ c
DynAnalyzer new
profile: [ RSShapeExamples new example10Donut open ]
onPackagesMatchingExpresions: #('Roassal3*')
Result of the execution is:
![alt text](screenshots/tutorial01-01.png]
The visualization represents classes as a box with methods in it. The size of the method represents the number of executions the method was executed during the execution. Class boxes faces from gray to red. A red class is the class that has the most objects created from it. On the example, hovering the mouse above the RSEllipse
indicates that the expression RSShapeExamples new example10Donut open
creates 1800 objects from that class.
Another example could be:
DynAnalyzer new
profile: [ RSShapeExamples new example07NormalizeColor open ]
onPackagesMatchingExpresions: #('Roassal3*')
![alt text](screenshots/tutorial01-02.png]
Hapao
Hapao is a test coverage tool for Pharo and VisualWorks. After having run your test, it gives an intuitive visualization of the test coverage. More information can be found on http://bergel.eu/download/papers/Berg12c-HapaoSCP.pdf
Contributing to Spy and Hapao
If you wish to contribute to Spy2 or Hapao (e.g., fixing bug, proposing an improvement), please, submit pull requests.
Pharo 6 and 7
The git repository contains a tag Pharo7
. You can also use:
Metacello new
baseline: 'Spy2';
repository: 'github://ObjectProfile/Spy2:v1.0/src';
load
Developer corner
If you have a local copy of Spy, then you can load it via:
Metacello new
baseline: 'Spy2';
repository: 'gitlocal:///Users/alexandrebergel/Dropbox/GitRepos/Spy2' ;
lock;
load