{"id":15107724,"url":"https://github.com/objectprofile/spy2","last_synced_at":"2025-10-23T02:31:34.933Z","repository":{"id":78478184,"uuid":"88306968","full_name":"ObjectProfile/Spy2","owner":"ObjectProfile","description":"Profiling framework for Pharo. Also contains the Hapao test coverage tool.","archived":false,"fork":false,"pushed_at":"2021-05-17T10:02:01.000Z","size":2247,"stargazers_count":7,"open_issues_count":1,"forks_count":2,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-10-09T13:22:15.332Z","etag":null,"topics":["pharo","pharo-smalltalk","roassal"],"latest_commit_sha":null,"homepage":null,"language":"Smalltalk","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ObjectProfile.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-04-14T22:11:26.000Z","updated_at":"2023-09-07T13:07:25.000Z","dependencies_parsed_at":null,"dependency_job_id":"78f438cc-99c7-499d-8c1e-3cb95aa77835","html_url":"https://github.com/ObjectProfile/Spy2","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ObjectProfile%2FSpy2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ObjectProfile%2FSpy2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ObjectProfile%2FSpy2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ObjectProfile%2FSpy2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ObjectProfile","download_url":"https://codeload.github.com/ObjectProfile/Spy2/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219868432,"owners_count":16555761,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["pharo","pharo-smalltalk","roassal"],"created_at":"2024-09-25T21:41:13.704Z","updated_at":"2025-10-23T02:31:32.246Z","avatar_url":"https://github.com/ObjectProfile.png","language":"Smalltalk","funding_links":[],"categories":[],"sub_categories":[],"readme":"# The Spy2 Profiling Framework for Pharo\n\n[![Build Status](https://travis-ci.org/ObjectProfile/Spy2.svg?branch=master)](https://travis-ci.org/ObjectProfile/Spy2)\n\nComprehending 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.\n\nThe latest stable version of Spy2 may be installed on Pharo 8 and Pharo 9 using the expression:\n\n```Smalltalk\nMetacello new \n\tbaseline: 'Spy2'; \n\trepository: 'github://ObjectProfile/Spy2:1.0'; \n\tload\n```  \n\nThe latest unstable version may be loaded using:\n\n```Smalltalk\nMetacello new \n\tbaseline: 'Spy2'; \n\trepository: 'github://ObjectProfile/Spy2'; \n\tload\n```  \n\nSpy2 is also available on the Pharo Catalog Browser.\n\nSpy2 depends on [Roassal3](https://github.com/ObjectProfile/Roassal3/) since some of the accompagning profilers use visualization. The core of Spy2, without any external dependencies, may be loaded using:\n\n```Smalltalk\nMetacello new \n\tbaseline: 'Spy2'; \n\trepository: 'github://ObjectProfile/Spy2'; \n\tload: 'Core'\n```\n\n----\n# Hapao\n\nSpy2 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:\n\n![alt](screenshots/hapao-worldmenu.png)\n\nA description of Hapao may be seen in this [.pdf](http://bergel.eu/download/papers/Berg12c-HapaoSCP.pdf).\n\nHere is a screenshot using the dark theme:\n![alt](screenshots/hapao-dark.png)\n\nThe same output using the light theme:\n![alt](screenshots/hapao-light.png)\n\n----\n# Short Tutorial about Spy\n\nThis 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.\n\n## Step 1 - Building a skeleton for the profiler\nWe 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:\n\n```Smalltalk\nSpy2 generate: 'DynAnalyzer' category: 'Spy2DemoProfiler'\n```\n\nThe skeleton is made of four classes: `DynAnalyzer`, `DynAnalyzerPackage`, `DynAnalyzerClass`, and `DynAnalyzerMethod`.\n\n## Step 2 - Counting method execution\n\nWe 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.\n\n```Smalltalk\nS2Method subclass: #DynAnalyzerMethod\n\tinstanceVariableNames: 'numberOfExecutions'\n\tclassVariableNames: ''\n\tpackage: 'Spy2DemoProfiler'\n```\n\nThe variable is initialized using: \n\n```Smalltalk\nDynAnalyzerMethod\u003e\u003einitialize\n\tsuper initialize.\n\tnumberOfExecutions := 0\n```\n\nWe need a way to access it:\n\n```Smalltalk\nDynAnalyzerMethod\u003e\u003enumberOfExecutions\n\t^ numberOfExecutions\n```\n\nThe variable has to be incremented at each method execution:\n```Smalltalk\nDynAnalyzerMethod\u003e\u003ebeforeRun: methodName with: listOfArguments in: receiver\n\t\"This method is executed before each method of the profiled application.\n\t Insert here the instrumentation you would like to perform during the profiling.\"\n\tnumberOfExecutions := numberOfExecutions + 1\n```\n\n## Step 3 - Counting objects\n\nWe keep the number of objects in the class `DynAnalyzerClass`:\n\n```Smalltalk\nS2Class subclass: #DynAnalyzerClass\n\tinstanceVariableNames: 'numberOfObjects'\n\tclassVariableNames: ''\n\tpackage: 'Spy2DemoProfiler'\n```\n\nWhen created, we simply initialize this variable to 0:\n```Smalltalk\nDynAnalyzerClass\u003e\u003einitialize\n\tsuper initialize.\n\tnumberOfObjects := 0.\n```\n\nA small utility method to increase the number of created objects:\n```Smalltalk\nDynAnalyzerClass\u003e\u003eincreaseNumberOfObjects \n\tnumberOfObjects := numberOfObjects + 1\n```\n\nThe number of objects has to be accessible:\n```Smalltalk\nDynAnalyzerClass\u003e\u003enumberOfObjects \n\t^ numberOfObjects\n```\n\nWe now need to make our profiler call `increaseNumberOfObjects` whenever a new object is created. Spy2 offers a dedicated pluggin for this:\n\n```Smalltalk\nDynAnalyzer\u003e\u003ebasicNewPlugin\n\t\u003cS2ClassPlugin\u003e\n\t^ S2SpecialBehaviorPlugin basicNewPluginOn: self\n```\n\nThe method `onBasicNew:` is called on the profiler when a new object is created. This is where we should increase the number of objects:\n\n```Smalltalk\nDynAnalyzer\u003e\u003eonBasicNew: obj\n\t\"obj is a newly created object\"\n\t(obj class getSpy: self) increaseNumberOfObjects\n```\n\n\n## Step 4 - Visualization\n\nWe will redefine the method `printOn:` to indicate the number of objects created:\n\n```Smalltalk\nDynAnalyzerClass\u003e\u003eprintOn: str\n\tsuper printOn: str.\n\tstr nextPutAll: self className.\n\tstr nextPutAll: ' - '.\n\tstr nextPutAll: numberOfObjects asString. \n\tstr nextPutAll: ' created objects'.\n```\n\nWe build a dedicated visualization using Roassal3. Consider the method: \n\n```Smalltalk\nDynAnalyzer\u003e\u003egtInspectorViewIn: composite\n\t\u003cgtInspectorPresentationOrder: -10\u003e\n\tcomposite roassal3\n\t\ttitle: 'View';\n\t\tinitializeCanvas: [ self buildCanvas ]\n```\n\nThe visualization is implemented using `buildCanvas`:\n\n```Smalltalk\nDynAnalyzer\u003e\u003ebuildCanvas\n\t| c executedClasses label methods composite |\n\tc := RSCanvas new.\n\texecutedClasses := self allClasses\n\t\tselect:\n\t\t\t[ :clsSpy | clsSpy allMethods anySatisfy: [ :m | m numberOfExecutions \u003e 0 ] ].\n\texecutedClasses\n\t\tdo: [ :clsSpy | \n\t\t\t\"Name of the class\"\n\t\t\tlabel := RSLabel new text: clsSpy className.\n\n\t\t\t\"Build the method visual elements\"\n\t\t\tmethods := clsSpy methods\n\t\t\t\tcollect: [ :m | \n\t\t\t\t\tRSBox new\n\t\t\t\t\t\tmodel: m;\n\t\t\t\t\t\tsize: (m numberOfExecutions sqrt max: 4);\n\t\t\t\t\t\tcolor: Color blue ]\n\t\t\t\tas: RSGroup.\n\n\t\t\t\"Make all the method located as a grid\"\n\t\t\tRSGridLayout on: methods.\n\t\t\tmethods @ RSPopup.\n\n\t\t\t\"Locate the label above the methods\"\n\t\t\tRSLocation new\n\t\t\t\tabove;\n\t\t\t\tmove: label on: methods.\n\t\t\tcomposite := RSComposite new.\n\t\t\tcomposite model: clsSpy.\n\t\t\tcomposite color: Color veryVeryLightGray.\n\t\t\tcomposite shapes: {label} , methods.\n\t\t\tcomposite @ RSDraggable.\n\t\t\tcomposite padding: 10.\n\t\t\tc add: composite.\n\t\t\tcomposite @ RSPopup ].\n\tRSFlowLayout on: c shapes.\n\tRSNormalizer color\n\t\tshapes: c shapes;\n\t\tfrom: Color gray;\n\t\tto: Color lightRed;\n\t\tnormalize: #numberOfObjects.\n\tc @ RSCanvasController.\n\t^ c\n```\n\n```Smalltalk\nDynAnalyzer new\n\tprofile: [ RSShapeExamples new example10Donut open ] \n\tonPackagesMatchingExpresions: #('Roassal3*')\n```\n\nResult of the execution is:\n\n![alt text](screenshots/tutorial01-01.png]\n\t\nThe 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.\n\nAnother example could be:\n\n```Smalltalk\nDynAnalyzer new\n\tprofile: [ RSShapeExamples new example07NormalizeColor open ] \n\tonPackagesMatchingExpresions: #('Roassal3*')\n```\n![alt text](screenshots/tutorial01-02.png]\n\n----\n\n\n\n# Hapao \nHapao is a test coverage tool for Pharo and VisualWorks. After having run your test, it gives an intuitive visualization of the test coverage.\nMore information can be found on *http://bergel.eu/download/papers/Berg12c-HapaoSCP.pdf*\n\n\n# Contributing to Spy and Hapao\nIf you wish to contribute to Spy2 or Hapao (e.g., fixing bug, proposing an improvement), please, submit pull requests.\n\n# Pharo 6 and 7\n\nThe git repository contains a tag `Pharo7`. You can also use:\n\n```Smalltalk\nMetacello new \n\tbaseline: 'Spy2'; \n\trepository: 'github://ObjectProfile/Spy2:v1.0/src'; \n\tload\n```\n\n# Developer corner\n\nIf you have a local copy of Spy, then you can load it via:\n```Smalltalk\nMetacello new \n\tbaseline: 'Spy2'; \n\trepository: 'gitlocal:///Users/alexandrebergel/Dropbox/GitRepos/Spy2' ;\n\tlock;\n\tload\n``` \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobjectprofile%2Fspy2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fobjectprofile%2Fspy2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobjectprofile%2Fspy2/lists"}