Andreas Raab uploaded a new version of System to project The Trunk:
http://source.squeak.org/trunk/System-ar.140.mcz ==================== Summary ==================== Name: System-ar.140 Author: ar Time: 4 September 2009, 12:10:59 pm UUID: f149b02b-e23d-fa48-b523-dfc2539b8878 Ancestors: System-ar.139 http://bugs.squeak.org/view.php?id=7301 Change Set: MessageTallyEnh-ar-jmv Date: 4 March 2009 Author: Andreas Raab Improve MessageTally to provide cross-process profiling. =============== Diff against System-ar.139 =============== Item was changed: ----- Method: MessageTally>>fullPrintOn:tallyExact:orThreshold: (in category 'printing') ----- fullPrintOn: aStream tallyExact: isExact orThreshold: perCent | threshold | + isExact ifFalse: [threshold _ (perCent asFloat / 100 * tally) rounded]. - isExact ifFalse: [threshold := (perCent asFloat / 100 * tally) rounded]. aStream nextPutAll: '**Tree**'; cr. + self rootPrintOn: aStream - self treePrintOn: aStream - tabs: OrderedCollection new - thisTab: '' total: tally totalTime: time tallyExact: isExact orThreshold: threshold. aStream nextPut: Character newPage; cr. aStream nextPutAll: '**Leaves**'; cr. self leavesPrintOn: aStream tallyExact: isExact orThreshold: threshold! Item was changed: ----- Method: MessageTally>>sonsOver: (in category 'comparing') ----- sonsOver: threshold | hereTally last sons | (receivers == nil or: [receivers size = 0]) ifTrue: [^#()]. + hereTally _ tally. + sons _ receivers select: "subtract subNode tallies for primitive hits here" - hereTally := tally. - sons := receivers select: "subtract subNode tallies for primitive hits here" [:son | + hereTally _ hereTally - son tally. - hereTally := hereTally - son tally. son tally > threshold]. hereTally > threshold ifTrue: + [last _ MessageTally new class: class method: method. + last process: process. - [last := MessageTally new class: class method: method. ^sons copyWith: (last primitives: hereTally)]. ^sons! Item was changed: ----- Method: MessageTally>>close (in category 'initialize-release') ----- close (Timer isMemberOf: Process) ifTrue: [Timer terminate]. + Timer := nil. - Timer := ObservedProcess := nil. class := method := tally := receivers := nil! Item was added: + ----- Method: MessageTally>>spyAllEvery:on: (in category 'initialize-release') ----- + 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 isBlock) + 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]! Item was changed: ----- Method: MessageTally>>tallyPath:by: (in category 'tallying') ----- 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. - aMethod := context method. - receivers do: - [:aMessageTally | - aMessageTally method == aMethod ifTrue: [path := aMessageTally]]. - path == nil ifTrue: - [path := MessageTally new class: context receiver class method: aMethod; - maxClassNameSize: maxClassNameSize; - maxClassPlusSelectorSize: maxClassPlusSelectorSize; - maxTabs: maxTabs. receivers := receivers copyWith: path]. + ^ path bumpBy: count! Item was changed: ----- Method: MessageTally>>spyEvery:on: (in category 'initialize-release') ----- 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. - | myDelay startTime time0 | - aBlock isBlock - ifTrue: [self class: aBlock receiver class method: aBlock method] - ifFalse: [self class: aBlock class method: aBlock method]. "set up the probe" + observedProcess _ Processor activeProcess. - ObservedProcess := Processor activeProcess. myDelay := Delay forMilliseconds: millisecs. time0 := Time millisecondClockValue. gcStats := SmalltalkImage current getVMParameters. + Timer := [ + [true] whileTrue: [ + startTime := Time millisecondClockValue. - Timer := - [[true] whileTrue: - [startTime := Time millisecondClockValue. myDelay wait. + self tally: Processor preemptedProcess suspendedContext + in: (ShowProcesses ifTrue: [ + observedProcess == Processor preemptedProcess ifTrue: [observedProcess] ifFalse: [nil]]) - self tally: ObservedProcess suspendedContext "tally can be > 1 if ran a long primitive" by: (Time millisecondClockValue - startTime) // millisecs]. nil] newProcess. + Timer priority: Processor timingPriority-1. - Timer priority: Processor userInterruptPriority. "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))]. - ^ 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]! Item was added: + ----- Method: MessageTally>>rootPrintOn:total:totalTime:tallyExact:orThreshold: (in category 'printing') ----- + rootPrintOn: aStream total: total totalTime: totalTime tallyExact: isExact orThreshold: threshold + | sons groups p | + 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. + p := g anyOne process. + "Do not show 'other processes' " + "Please keep consistency with #leavesInto:fromSender: + on showing them or not!!" + p ifNotNil: [ + aStream nextPutAll: '--------------------------------'; cr. + aStream nextPutAll: 'Process: ', (p ifNil: [ 'other processes'] ifNotNil: [ p 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]]. + ].! Item was changed: ----- Method: MessageTally>>report: (in category 'reporting') ----- report: strm "Print a report, with cutoff percentage of each element of the tree + (leaves, roots, tree), on the stream, strm." - (leaves, roots, tree)=2, on the stream, strm." + self report: strm cutoff: 1! - self report: strm cutoff: 2! Item was changed: Magnitude subclass: #MessageTally + instanceVariableNames: 'class method process tally receivers senders time gcStats maxClassNameSize maxClassPlusSelectorSize maxTabs' + classVariableNames: 'DefaultPollPeriod ShowProcesses Timer' - instanceVariableNames: 'class method tally receivers senders time gcStats maxClassNameSize maxClassPlusSelectorSize maxTabs' - classVariableNames: 'DefaultPollPeriod ObservedProcess Timer' poolDictionaries: '' category: 'System-Tools'! !MessageTally commentStamp: 'nk 3/8/2004 12:43' prior: 0! My instances observe and report the amount of time spent in methods. NOTE: a higher-level user interface (combining the MessageTally result tree with a method browser) is available from TimeProfileBrowser. MessageTally provides two different strategies available for profiling: * spyOn: and friends use a high-priority Process to interrupt the block or process being spied on at periodic intervals. The interrupted call stack is then examined for caller information. * tallySends: and friends use the interpreter simulator to run the block, recording every method call. The two give you different results: * spyOn: gives you a view of where the time is being spent in your program, at least on a rough statistical level (assuming you've run the block for long enough and have a high enough poll rate). If you're trying to optimize your code, start here and optimize the methods where most of the time is being spent first. * tallySends: gives you accurate counts of how many times methods get called, and by exactly which route. If you're debugging, or trying to figure out if a given method is getting called too many times, this is your tool. You can change the printing format (that is, the whitespace and string compression) by using these instance methods: maxClassNameSize: maxClassPlusSelectorSize: maxTabs: You can change the default polling period (initially set to 1) by calling MessageTally defaultPollPeriod: numberOfMilliseconds Q: How do you interpret MessageTally>>tallySends A: The methods #tallySends and #spyOn: measure two very different quantities, but broken down in the same who-called-who format. #spyOn: is approximate, but more indicative of real time spent, whereas #tallySends is exact and a precise record of how many times each method got executed.! Item was changed: ----- Method: MessageTally>>= (in category 'comparing') ----- = aMessageTally self species == aMessageTally species ifFalse: [^ false]. + ^ aMessageTally method == method and:[aMessageTally process == process]! - ^ aMessageTally method == method! Item was changed: ----- Method: MessageTally class>>defaultMaxTabs (in category 'defaults') ----- defaultMaxTabs "Return the default number of tabs after which leading white space is compressed" + ^120! - ^40! Item was changed: ----- Method: MessageTally class>>spyOn: (in category 'spying') ----- + spyOn: aBlock + " + [1000 timesRepeat: [ + 100 timesRepeat: [120 factorial]. + (Delay forMilliseconds: 10) wait + ]] forkAt: 45 named: '45'. + MessageTally spyOn: [10000 timesRepeat: [1.23 printString]] + " - spyOn: aBlock "MessageTally spyOn: [100 timesRepeat: [3.14159 printString]]" | node result | + node _ self new. + result _ node spyEvery: self defaultPollPeriod on: aBlock. - node := self new. - result := node spyEvery: self defaultPollPeriod on: aBlock. (StringHolder new contents: (String streamContents: [:s | node report: s; close])) openLabel: 'Spy Results'. ^ result! Item was changed: ----- Method: MessageTally>>tally:by: (in category 'tallying') ----- tally: context by: count "Explicitly tally the specified context and its stack." + | sender | + + "Add to this node if appropriate" - | root | 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! - (root := context home sender) == nil - ifTrue: [^ (self bumpBy: count) tallyPath: context by: count]. - ^ (self tally: root by: count) tallyPath: context by: count! Item was changed: ----- Method: MessageTally>>leavesInto:fromSender: (in category 'collecting leaves') ----- leavesInto: leafDict fromSender: senderTally | rcvrs | + rcvrs _ self sonsOver: 0. - rcvrs := self sonsOver: 0. rcvrs size = 0 ifTrue: [self into: leafDict fromSender: senderTally] + ifFalse: [ + + "Do not show 'other processes' " + "Please keep consistency with #rootPrintOn:total:totalTime:tallyExact:orThreshold: + on showing them or not!!" + rcvrs anyOne process ifNil: [^self]. + + rcvrs do: - ifFalse: [rcvrs do: [:node | node isPrimitives ifTrue: [node leavesInto: leafDict fromSender: senderTally] ifFalse: [node leavesInto: leafDict fromSender: self]]]! Item was added: + ----- Method: MessageTally class>>spyAllOn: (in category 'spying') ----- + 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! Item was changed: ----- Method: MessageTally class>>spyOnProcess:forMilliseconds: (in category 'spying') ----- spyOnProcess: aProcess forMilliseconds: msecDuration + " + | p1 p2 | + p1 _ [100000 timesRepeat: [3.14159 printString. Processor yield]] fork. + p2 _ [100000 timesRepeat: [3.14159 printString. Processor yield]] fork. - "| p | - p := [100000 timesRepeat: [3.14159 printString]] fork. (Delay forMilliseconds: 100) wait. + MessageTally spyOnProcess: p1 forMilliseconds: 1000 + " - MessageTally spyOnProcess: p forMilliseconds: 1000" | node | node := self new. node spyEvery: self defaultPollPeriod onProcess: aProcess forMilliseconds: msecDuration. (StringHolder new contents: (String streamContents: [:s | node report: s; close])) openLabel: 'Spy Results'! Item was changed: ----- Method: MessageTally>>into:fromSender: (in category 'collecting leaves') ----- into: leafDict fromSender: senderTally | leafNode | + leafNode _ leafDict at: method - leafNode := leafDict at: method ifAbsent: [leafDict at: method + put: ((MessageTally new class: class method: method) + process: process)]. - put: (MessageTally new class: class method: method)]. leafNode bump: tally fromSender: senderTally! Item was added: + ----- Method: MessageTally>>tallyPath:in:by: (in category 'tallying') ----- + 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! Item was added: + ----- Method: MessageTally>>process: (in category 'private') ----- + process: aProcess + process := aProcess! Item was changed: ----- Method: MessageTally>>copyWithTally: (in category 'private') ----- copyWithTally: hitCount + ^ (MessageTally new class: class method: method) + process: process; + bump: hitCount! - ^ (MessageTally new class: class method: method) bump: hitCount! Item was added: + ----- Method: MessageTally class>>showProcesses (in category 'defaults') ----- + 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] + + " + <preference: 'Show Tally Processes' + category: 'debug' + description: 'When true, show individual processes in MessageTally' + type: #Boolean> + ^ShowProcesses! Item was added: + ----- Method: MessageTally class>>initialize (in category 'class initialization') ----- + initialize + "MessageTally initialize" + "By default, show each process separately" + ShowProcesses := true! Item was added: + ----- Method: MessageTally>>process (in category 'private') ----- + process + ^process! Item was added: + ----- Method: MessageTally class>>showProcesses: (in category 'defaults') ----- + 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.! Item was added: + ----- Method: MessageTally>>tally:in:by: (in category 'tallying') ----- + 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! Item was changed: ----- Method: MessageTally>>spyEvery:onProcess:forMilliseconds: (in category 'initialize-release') ----- 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 | - | myDelay time0 endTime sem | (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. - ObservedProcess := aProcess. myDelay := Delay forMilliseconds: millisecs. time0 := Time millisecondClockValue. endTime := time0 + msecDuration. sem := Semaphore new. + gcStats := SmalltalkImage current getVMParameters. + Timer := [ + [ + startTime := Time millisecondClockValue. + myDelay wait. + self tally: Processor preemptedProcess suspendedContext + in: (ShowProcesses ifTrue: [ + observedProcess == Processor preemptedProcess ifTrue: [observedProcess] ifFalse: [nil]]) + "tally can be > 1 if ran a long primitive" + by: (Time millisecondClockValue - startTime) // millisecs. + startTime < endTime + ] whileTrue. + sem signal. + ] newProcess. + Timer priority: Processor timingPriority-1. + "activate the probe and evaluate the block" + Timer resume. - gcStats := SmalltalkImage current getVMParameters. - Timer := [[| startTime | - startTime := Time millisecondClockValue. - myDelay wait. - self tally: ObservedProcess suspendedContext 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)]. - SmalltalkImage current getVMParameters keysAndValuesDo: - [:idx :gcVal| gcStats at: idx put: (gcVal - gcStats at: idx)]. time := Time millisecondClockValue - time0! |
Free forum by Nabble | Edit this page |