[squeak-dev] Re: How to profile a server image?

Juan Vuletich juan at jvuletich.org
Wed Mar 4 13:07:52 UTC 2009


Hi Andreas,

Andreas Raab wrote:
> Juan Vuletich wrote:
>> I understand that for a server having an variable (and big) number of 
>> processes it is not useful. But for our case, an application with a 
>> small and fixed set of processes, the first question we want to 
>> answer is "which is the process using all the cpu?". Please see the 
>> message I just sent on this thread to see an example of what I want.
>
> Okay, I cracked ;-) Attached you'll find patches for a message tally 
> version which does per-process reporting if so desired. I've moved 
> this out of the arguments and into class variables so you get/set 
> either priority or preference via, e.g.:
>
>   MessageTally spyPriority: <new priority>
>   MessageTally showProcesses: <true/false>
>
> The defaults are such that the priority is TimerPriority-1 and 
> showProcesses is on. With it, your example gives the result shown below.
>
> Let me know if this works for you (or anyone else). Comments welcome.
>
> Cheers,
>   - Andreas

Thank you! After wasting many hours on the idea of attaching the 
firstContext to each process, I also realized it is not necessary at 
all. But before I could get the tallies properly built, I went to sleep, 
and you did it! Thanks! I found a small bug, though. You forgot to 
actually use the class var SpyPriority.

The attached change is based on yours, but with a few differences (some 
of them you might like, some others you might not like :)

- If you tally a single process via #spyEvery:onProcess:forMilliseconds: 
(for example using the option in the ProcessBrowser) you'll get bogus 
results, as all tallies are captured as if they belonged to the observed 
process. I fixed it. This one is important.

- I left the old #spyOn: for spying on a single process, with the fix I 
just mentioned to report realistic results. I added #spyAllOn: to spy on 
all the running processes. I believe many times one might want to spy on 
just one process.

- I removed the SpyPriority classVar. If spying on all processes, I use 
a really high priority. If spying on single process, I use a slightly 
higher priority than his (using a a really high priority would not hurt, 
though). This is just a matter of taste.

- I removed the old ObservedProcess class var, as it is no longer necessary.

- Allowed for more visible tabs (just cosmetic).

- Show nodes using more than 1% (instead of 2%). Not very important.

- Modified the menu options in World menu / Debug, to allow for both 
tallyin the UI or all the system, and to make this clear.

That's all. I hope you and others find it useful.

Cheers,
Juan Vuletich
-------------- next part --------------
'From Squeak3.10.2 of ''5 June 2008'' [latest update: #7179] on 4 March 2009 at 10:44:49 am'!
Magnitude subclass: #MessageTally
	instanceVariableNames: 'class method tally receivers senders time gcStats maxClassNameSize maxClassPlusSelectorSize maxTabs process '
	classVariableNames: 'DefaultPollPeriod ObservedProcess Timer ShowProcesses '
	poolDictionaries: ''
	category: 'System-Tools'!

!MessageTally methodsFor: 'comparing' stamp: 'ar 3/3/2009 19:36'!
= aMessageTally

	self species == aMessageTally species ifFalse: [^ false].
	^ aMessageTally method == method and:[aMessageTally process == process]! !

!MessageTally methodsFor: 'initialize-release' stamp: 'jmv 3/4/2009 10:35'!
close

	(Timer isMemberOf: Process) ifTrue: [Timer terminate].
	Timer := nil.
	class := method := tally := receivers := nil! !

!MessageTally methodsFor: 'initialize-release' stamp: 'jmv 3/4/2009 10:40'!
spyAllEvery: millisecs on: aBlock
	"Create a spy and spy on the given block at the specified rate."
	"Spy all the system processes"

	| myDelay startTime time0 observedProcess |
	(aBlock isMemberOf: BlockContext)
		ifFalse: [self error: 'spy needs a block here'].
	self class: aBlock receiver class method: aBlock method.
		"set up the probe"
	myDelay := Delay forMilliseconds: millisecs.
	time0 := Time millisecondClockValue.
	gcStats := SmalltalkImage current getVMParameters.
	Timer := [
		[true] whileTrue: [
			startTime := Time millisecondClockValue.
			myDelay wait.
			observedProcess := Processor preemptedProcess.
			self tally: observedProcess suspendedContext
				in: (ShowProcesses ifTrue: [observedProcess])
				"tally can be > 1 if ran a long primitive"
				by: (Time millisecondClockValue - startTime) // millisecs].
		nil] newProcess.
	Timer priority: Processor timingPriority-1.
		"activate the probe and evaluate the block"
	Timer resume.
	^ aBlock ensure: [
		"Collect gc statistics"
		SmalltalkImage current getVMParameters keysAndValuesDo: [ :idx :gcVal | 
			gcStats at: idx put: (gcVal - (gcStats at: idx))].
		"cancel the probe and return the value"
		Timer terminate.
		time := Time millisecondClockValue - time0]! !

!MessageTally methodsFor: 'initialize-release' stamp: 'jmv 3/4/2009 10:41'!
spyEvery: millisecs on: aBlock 
	"Create a spy and spy on the given block at the specified rate."
	"Spy only on the active process (in which aBlock is run)"

	| myDelay startTime time0 observedProcess |
	(aBlock isMemberOf: BlockContext)
		ifFalse: [self error: 'spy needs a block here'].
	self class: aBlock receiver class method: aBlock method.
		"set up the probe"
	observedProcess _ Processor activeProcess.
	myDelay := Delay forMilliseconds: millisecs.
	time0 := Time millisecondClockValue.
	gcStats := SmalltalkImage current getVMParameters.
	Timer := [
		[true] whileTrue: [
			Processor preemptedProcess == observedProcess ifTrue: [
				startTime _ Time millisecondClockValue.
				myDelay wait.
				self tally: observedProcess suspendedContext
					in: observedProcess
					"tally can be > 1 if ran a long primitive"
					by: (Time millisecondClockValue - startTime) // millisecs]].
		nil] newProcess.
	Timer priority: (observedProcess priority + 1 min: Processor highestPriority).
		"activate the probe and evaluate the block"
	Timer resume.
	^ aBlock ensure: [
		"Collect gc statistics"
		SmalltalkImage current getVMParameters keysAndValuesDo: [ :idx :gcVal | 
			gcStats at: idx put: (gcVal - (gcStats at: idx))].
		"cancel the probe and return the value"
		Timer terminate.
		time := Time millisecondClockValue - time0]! !

!MessageTally methodsFor: 'initialize-release' stamp: 'jmv 3/4/2009 10:42'!
spyEvery: millisecs onProcess: aProcess forMilliseconds: msecDuration 
	"Create a spy and spy on the given process at the specified rate."
	| myDelay startTime time0 endTime sem observedProcess |
	(aProcess isKindOf: Process)
		ifFalse: [self error: 'spy needs a Process here'].
	self class: aProcess suspendedContext receiver class method: aProcess suspendedContext method.
	"set up the probe"
	observedProcess := aProcess.
	myDelay := Delay forMilliseconds: millisecs.
	time0 := Time millisecondClockValue.
	endTime := time0 + msecDuration.
	sem := Semaphore new.
	gcStats := SmalltalkImage current getVMParameters.
	Timer := [
			[
				startTime _ Time millisecondClockValue.
					Processor preemptedProcess == observedProcess ifTrue: [
					myDelay wait.
					self tally: observedProcess suspendedContext 
						in: observedProcess
						by: Time millisecondClockValue - startTime // millisecs].
				startTime < endTime
			] whileTrue.
			sem signal
		] forkAt: (observedProcess priority + 1 min: Processor highestPriority).
	"activate the probe and wait for it to finish"
	sem wait.
	"Collect gc statistics"
	SmalltalkImage current getVMParameters keysAndValuesDo: [ :idx :gcVal | 
		gcStats at: idx put: (gcVal - gcStats at: idx)].
	time := Time millisecondClockValue - time0! !

!MessageTally methodsFor: 'printing' stamp: 'ar 3/3/2009 19:43'!
fullPrintOn: aStream tallyExact: isExact orThreshold: perCent
	| threshold |  
	isExact ifFalse: [threshold _ (perCent asFloat / 100 * tally) rounded].
	aStream nextPutAll: '**Tree**'; cr.
	self rootPrintOn: aStream
		total: tally
		totalTime: time
		tallyExact: isExact
		orThreshold: threshold.
	aStream nextPut: Character newPage; cr.
	aStream nextPutAll: '**Leaves**'; cr.
	self leavesPrintOn: aStream
		tallyExact: isExact
		orThreshold: threshold! !

!MessageTally methodsFor: 'printing' stamp: 'ar 3/3/2009 19:47'!
rootPrintOn: aStream total: total totalTime: totalTime tallyExact: isExact orThreshold: threshold 
	| sons groups |
	ShowProcesses ifFalse:[
		^self treePrintOn: aStream
			tabs: OrderedCollection new
			thisTab: ''
			total: total
			totalTime: totalTime
			tallyExact: isExact
			orThreshold: threshold.
	].
	sons := isExact ifTrue: [receivers] ifFalse: [self sonsOver: threshold].
	groups := sons groupBy:[:aTally| aTally process] having:[:g| true].
	groups do:[:g|
		sons := g asSortedCollection.
		aStream nextPutAll: '--------------------------------'; cr.
		aStream nextPutAll: 'Process: ', g anyOne process browserPrintString; cr.
		aStream nextPutAll: '--------------------------------'; cr.
		(1 to: sons size) do:[:i | 
			(sons at: i) 
				treePrintOn: aStream
				tabs: OrderedCollection new
				thisTab: ''
				total: total
				totalTime: totalTime
				tallyExact: isExact
				orThreshold: threshold].
	].! !

!MessageTally methodsFor: 'reporting' stamp: 'jmv 3/4/2009 09:27'!
report: strm 
	"Print a report, with cutoff percentage of each element of the tree 
	(leaves, roots, tree), on the stream, strm."

	self report: strm cutoff: 1! !

!MessageTally methodsFor: 'tallying' stamp: 'jmv 3/4/2009 10:36'!
tally: context by: count
	"Explicitly tally the specified context and its stack."
	| sender |
	
	"Add to this node if appropriate"
	context method == method ifTrue: [^self bumpBy: count].
	
	"No sender? Add new branch to the tree."
	(sender := context home sender)ifNil: [
		^ (self bumpBy: count) tallyPath: context by: count].
	
	"Find the node for the sending context (or add it if necessary)"
	^ (self tally: sender by: count) tallyPath: context by: count! !

!MessageTally methodsFor: 'tallying' stamp: 'jmv 3/4/2009 09:42'!
tally: context in: aProcess by: count
	"Explicitly tally the specified context and its stack."
	| sender |

	"Add to this node if appropriate"
	context method == method ifTrue: [^self bumpBy: count].
	
	"No sender? Add new branch to the tree."
	(sender := context home sender) ifNil: [
		^ (self bumpBy: count) tallyPath: context in: aProcess by: count].
	
	"Find the node for the sending context (or add it if necessary)"
	^ (self tally: sender in: aProcess by: count) tallyPath: context in: aProcess by: count! !

!MessageTally methodsFor: 'tallying' stamp: 'jmv 3/4/2009 10:36'!
tallyPath: context by: count
	| aMethod path |
	aMethod :=context method.
	
	"Find the correct child (if there)"
	receivers do: [ :oldTally | 
		oldTally method == aMethod ifTrue: [path := oldTally]].
	
	"Add new child if needed"
	path ifNil: [
		path := MessageTally new class: context receiver class method: aMethod.
		receivers := receivers copyWith: path].
	
	^ path bumpBy: count! !

!MessageTally methodsFor: 'tallying' stamp: 'jmv 3/4/2009 10:38'!
tallyPath: context in: aProcess by: count
	| aMethod path |
	aMethod := context method.
	
	"Find the correct child (if there)"
	receivers do: [ :oldTally | 
		(oldTally method == aMethod and: [oldTally process == aProcess])
			ifTrue: [path := oldTally]].
		
	"Add new child if needed"
	path ifNil:[
		path := MessageTally new class: context receiver class method: aMethod;
			process: aProcess;
			maxClassNameSize: maxClassNameSize;
			maxClassPlusSelectorSize: maxClassPlusSelectorSize;
			maxTabs: maxTabs.
		receivers := receivers copyWith: path].

	^ path bumpBy: count! !

!MessageTally methodsFor: 'private' stamp: 'ar 3/3/2009 19:29'!
process
	^process! !

!MessageTally methodsFor: 'private' stamp: 'ar 3/3/2009 19:29'!
process: aProcess
	process := aProcess! !


!MessageTally class methodsFor: 'defaults' stamp: 'jmv 3/2/2009 12:32'!
defaultMaxTabs
	"Return the default number of tabs after which leading white space is compressed"
	^120! !

!MessageTally class methodsFor: 'defaults' stamp: 'jmv 3/4/2009 10:29'!
showProcesses
	"Indicates whether to show each process separately or cumulatively.
	For example, compare the spy results of the following with both values:
	
		[1000 timesRepeat: [3.14159 printString. Processor yield]] fork.
		[1000 timesRepeat: [30 factorial. Processor yield]] fork.
		[1000 timesRepeat: [30 factorial. Processor yield]] fork.
		MessageTally spyAllOn: [ (Delay forMilliseconds: 100) wait] 

	"
	^ShowProcesses! !

!MessageTally class methodsFor: 'defaults' stamp: 'jmv 3/4/2009 10:29'!
showProcesses: aBool
	"Indicates whether to show each process separately or cumulatively.
	For example, compare the spy results of the following with both values:
	
		[1000 timesRepeat: [3.14159 printString. Processor yield]] fork.
		[1000 timesRepeat: [30 factorial. Processor yield]] fork.
		[1000 timesRepeat: [30 factorial. Processor yield]] fork.
		MessageTally spyAllOn: [ (Delay forMilliseconds: 100) wait]

	"
	ShowProcesses := aBool.! !

!MessageTally class methodsFor: 'spying' stamp: 'jmv 3/4/2009 10:42'!
spyAllOn: aBlock
	"Spy on all the processes in the system
	
	[1000 timesRepeat: [3.14159 printString. Processor yield]] fork.
	[1000 timesRepeat: [20 factorial. Processor yield]] fork.
	[1000 timesRepeat: [20 factorial. Processor yield]] fork.
	MessageTally spyAllOn: [ (Delay forMilliseconds: 100) wait]
	
	"
	| node result |
	node := self new.
	result := node spyAllEvery: self defaultPollPeriod on: aBlock.
	(StringHolder new contents: (String streamContents: [:s | node report: s; close]))
		openLabel: 'Spy Results'.
	^ result! !

!MessageTally class methodsFor: 'class initialization' stamp: 'jmv 3/4/2009 09:24'!
initialize
	"MessageTally initialize"
	"By default, show each process separately"
	ShowProcesses := true! !


!TheWorldMenu methodsFor: 'commands' stamp: 'jmv 3/4/2009 10:43'!
startMessageTally
	"Tally on all the processes in the system, and not only the UI"
	
	(self confirm: 'MessageTally all the processes in
the system, until the mouse pointer
goes to the top of the screen') ifTrue: [
		MessageTally spyAllOn: [
			[Sensor peekMousePt y > 0] whileTrue: [World doOneCycle]]]! !

!TheWorldMenu methodsFor: 'construction' stamp: 'jmv 3/4/2009 10:44'!
debugMenu

        | menu |

        menu _ self menu: 'debug...'.
        self fillIn: menu from: { 
                { 'inspect world' . { #myWorld . #inspect } }.
                { 'explore world' . { #myWorld . #explore } }.
                { 'inspect model' . { self . #inspectWorldModel } }.
                        " { 'talk to world...' . { self . #typeInMessageToWorld } }."
                { 'MessageTally all Processes' . { self . #startMessageTally } }.
                { 'MessageTally UI and browse' . { self . #startThenBrowseMessageTally } }.
                { 'open process browser' . { self . #openProcessBrowser } }.
                nil.
                        "(self hasProperty: #errorOnDraw) ifTrue:  Later make this come up only when needed."
                { 'start drawing again' . { #myWorld . #resumeAfterDrawError } }.
                { 'start stepping again' . { #myWorld . #resumeAfterStepError } }.
                nil.
                { 'call #tempCommand' . { #myWorld . #tempCommand } }.
                { 'define #tempCommand' . { #myWorld . #defineTempCommand } }.
        }.
	self haltOnceEnabled
		ifTrue: [menu
				add: 'disable halt/inspect once' translated
				target: menu
				action: #clearHaltOnce]
		ifFalse: [menu
				add: 'enable halt/inspect once' translated
				target: menu
				action: #setHaltOnce].
	^menu
	! !

!TheWorldMenu methodsFor: '*Tools' stamp: 'jmv 3/4/2009 10:43'!
startThenBrowseMessageTally
	"Tally only the UI process"
	
	(self confirm: 'MessageTally the UI process until the
mouse pointer goes to the top of the screen')
		ifTrue: [TimeProfileBrowser
				onBlock: [[Sensor peekMousePt y > 10]
						whileTrue: [World doOneCycle]]]! !

MessageTally initialize!
Magnitude subclass: #MessageTally
	instanceVariableNames: 'class method process tally receivers senders time gcStats maxClassNameSize maxClassPlusSelectorSize maxTabs'
	classVariableNames: 'DefaultPollPeriod ShowProcesses Timer'
	poolDictionaries: ''
	category: 'System-Tools'!


More information about the Squeak-dev mailing list