Hi,
say I have a web server with several users logged into different sites.
What do i have to start a MessageTally on to see where the image spends its time?
Thanks,
Herbert
[Apologies for cross-posting but I figured some of the more interested audience may be on the Seaside list. What follows below is a summary of my experiences with server profiling over the last couple of years which I've done a goodly bit of]
Herbert König wrote:
say I have a web server with several users logged into different sites.
What do i have to start a MessageTally on to see where the image spends its time?
This is tricky with MessageTally. There are several issues to keep in mind, some of which you can fix, some of which you can't. Here we go:
1) Observing all Processes -------------------------- First, MessageTally only observes the block you are running, not the entire system. For example, if you were to profile this:
done := Semaphore new. worker:= [ 10 timesRepeat:[World changed; displayWorld]. done signal. ] fork. MessageTally spyOn:[done wait].
This is what you'd get:
**Tree** 100.0% {2367ms} primitives
**Leaves** 100.0% {2367ms} UndefinedObject>>DoIt
Obviously it's not actually measuring what is going on during the period of time which is a real problem if you are trying to measure server load in general. But it's fixable.
2) Priority issues ------------------ MessageTally uses userInterruptPriority and won't see anything running (for example) at lowIOPriority or higher. This is quite common for server images for handling incoming requests etc.
The good news is, both of the above issues can be fixed easily. Just file in the attached patch for MessageTally (I will also post this on Mantis) and it will report the work being done accurately when you use it via:
MessageTally spyAt: Processor timingPriority-1 on:[done wait].
The priority argument allows you to specify at which priority to run your spy so that you can measure what you need to measure (a word of warning though: You mustn't try to run at #timingPriority as this will interfere with the timing mechanism itself).
With these changes you can run a tally on, e.g.,
MessageTally spyOn:[(Delay forSeconds: 10) wait].
and see what happens over the next 10 seconds. This is effectively what we do for profiling on our server images.
3) VM timing problems --------------------- Unfortunately, recent VMs have a problem with spy accuracy since an important timer check was removed from the VM (I didn't notice that at the time it happened). This *heavily* affects the profiler, for example, try this:
MessageTally spyOn:[ 1 to: 100 do:[:i| Display fillWhite. 'Hello', ' World'. ]. ].
If you have a recent VM (I haven't tracked how far back this goes) the result is this:
**Tree** 100.0% {534ms} ByteString(SequenceableCollection)>>,
**Leaves** 100.0% {534ms} ByteString(SequenceableCollection)>>,
The problem is caused by the VM no longer doing a timer check after a primitive has been run. Consequently, the time spent in any long-running primitive (like BitBlt) is allocated to the *next real send* when an interrupt check occurs (as you can see in the above). This can be extremely confusing. If you want even remotely accurate profiles I recommend going back a few generations in VMs and pick one that behaves properly in this regard. For example, running the above in 3.6 I get:
**Tree** 100.0% {218ms} DisplayScreen(DisplayMedium)>>fillWhite 100.0% {218ms} DisplayScreen(DisplayMedium)>>fill:fillColor: 100.0% {218ms} DisplayScreen(Form)>>fill:rule:fillColor: 100.0% {218ms} BitBlt>>copy:from:in:fillColor:rule:
**Leaves** 100.0% {218ms} BitBlt>>copy:from:in:fillColor:rule:
Obviously, this is the correct tally. [Hm ... but this points out another issue. For some reason 3.6 runs this in half the total time??? What the heck... I'll have to look at this]
You *must* use a VM that doesn't have the above problem or the results will be completely confusing.
4) Measuring load with (sub-)msecs responses -------------------------------------------- Lastly, server images can have a particular issue if they are set up to do relatively fast request handling. For example, our routers generally act in sub-msecs responses. When you start putting load on servers that react that quickly, your whole msec-based sampling approach goes out of the window.
What's worse than that is that if (some of) your server activities are driven by the clock (using Delay etc) running under load can produce *utter* garbage result. Another example:
blamedProcess := [ [true] whileTrue:[(Delay forMilliseconds: 1) wait]. ] forkAt: Processor userInterruptPriority.
[MessageTally spyAt: Processor timingPriority -1 on:[ (Delay forSeconds: 5) wait. ]] ensure:[blamedProcess terminate]
When you run this, MessageTally will tell you that 100% CPU was spent in Delay wait for the blamedProcess. If you look at CPU utilization you should see practically no CPU activity. How come? When the timer event fires, it puts both message tally's profiling process as well as the blamed process in the runnable queue. MessageTally (being at higher priority) will go first and see that the process that is currently active is the blamed process. It will assume that the entire last msec was spent in it since it has no other timing base to use.
Be wary that the above can happen in many, many variations and depends heavily on your system's timer accuracy (if you want to play with this, increase the blamedProcess' wait time step by step and compare the reported CPU utilization various platforms). In general, when you see Delay wait show up in your tallies you can be sure there is something wrong with the measure.
Unfortunately, there is nothing much you can do about this other than be aware of it and wait. Because we ran into the same issues I have written a better profiler at Qwaq (which is both sub-msecs as well as primitive-aware) and I think we'll be releasing the necessary support for this in Cog but this will still take a while. For comparison, when you run our profiler on the above code you get:
Reporting - 45,975 tallies, 5,000 msec.
**Tree** 98.9 (4,945) ProcessorScheduler class startUp 98.9 (4,945) ProcessorScheduler class idleProcess 98.9 (4,945) ProcessorScheduler class relinquishProcessorForMicroseconds:
**Leaves** 98.9 (4,945) ProcessorScheduler class relinquishProcessorForMicroseconds:
So it correctly reports that the code was doing nothing (executing the idle process).
5) Summary ---------- In order to profile your server you definitely need to take of 1) through 3) or else you won't be able to find out anything. With these issues taken care of, just add a remotely executable action that runs message tally for the given number of seconds (we have this set up on our servers so that when you go to /profile?secs=xxx it runs the profiler for that amount of time and spits out the result).
Unless you do work that has very fast responses and is designed to work with high load this should be good enough to get you a pretty good understanding of what is going on.
Hope this helps!
Cheers, - Andreas
Hello Andreas,
AR> [Apologies for cross-posting but I figured some of the more interested AR> audience may be on the Seaside list. What follows below is a summary of AR> my experiences with server profiling over the last couple of years which AR> I've done a goodly bit of]
big thanks! Having spent an hour or so with trying and finding my first clues in the process browser in Socket>>waitforDataIfClosed: I decided not to ask on the Aida list but here.
But I'll post a link to your reply on the Aida list too.
With your fix and
MessageTally spyAt: Processor timingPriority-1 on:[(Delay forSeconds: 30) wait]
I'm still confused by the result.
I'm using Aida and I open two web apps in two tabs of the web browser. Then I just switch tabs alternating and refresh the browser.
So I'd expect both WebApps to show up in the tally but I only see the app of which ever tab in the web browser I refresh first. The remainder of the tally seems plausible to me except where noted in the tally.
The server is a slow machine the client with the browser is fast and connects via Lan. A refresh takes 5 seconds. Server uses lots of CPU during this.
Do you think it's time to go to the Aida list, or is this still a tally problem I have?
Squeak 3.8.2 with Win 3.10.10 VM.
Cheers,
Herbert mailto:herbertkoenig@gmx.net
Herbert König wrote:
With your fix and
MessageTally spyAt: Processor timingPriority-1 on:[(Delay forSeconds: 30) wait]
I'm still confused by the result.
Yes, if you are new to this game a bit of learning is required.
I'm using Aida and I open two web apps in two tabs of the web browser. Then I just switch tabs alternating and refresh the browser.
So I'd expect both WebApps to show up in the tally but I only see the app of which ever tab in the web browser I refresh first. The remainder of the tally seems plausible to me except where noted in the tally.
I can't tell you why you are seeing one app but keep in mind that if the call structure of both apps are very similar (which it usually is since it is being called from the same framework) the tree gets coalesced and you would only see one call tree until the apps actually do different things.
The server is a slow machine the client with the browser is fast and connects via Lan. A refresh takes 5 seconds. Server uses lots of CPU during this.
Do you think it's time to go to the Aida list, or is this still a tally problem I have?
Some of it is definitely a problem you have.
Squeak 3.8.2 with Win 3.10.10 VM.
That is your first problem 3.10.10 VM has the tallying problem. You need to go back in time find a VM which doesn't have the problem. Run the example that I sent in the first message and make sure it generates the right results.
???????????????????????????? Why does this eat so much? 61.2% {18360ms} ProcessorScheduler class>>startUp |61.2% {18360ms} ProcessorScheduler class>>idleProcess
This simply means is that the process was to 60% idle. This is accumulated over time so if you've waited for five seconds then did something for another five it would show up at roughly 50% idle.
Cheers, - Andreas
On 1-Mar-09, at 10:16 AM, Andreas Raab wrote:
That is your first problem 3.10.10 VM has the tallying problem. You need to go back in time find a VM which doesn't have the problem. Run the example that I sent in the first message and make sure it generates the right results.
So what's the VM change?
-- = = = ======================================================================== John M. McIntosh johnmci@smalltalkconsulting.com Corporate Smalltalk Consulting Ltd. http://www.smalltalkconsulting.com = = = ========================================================================
John M McIntosh wrote:
On 1-Mar-09, at 10:16 AM, Andreas Raab wrote:
That is your first problem 3.10.10 VM has the tallying problem. You need to go back in time find a VM which doesn't have the problem. Run the example that I sent in the first message and make sure it generates the right results.
So what's the VM change?
primitiveResponse "Details: Since primitives can run for a long time, we must check to see if it is time to process a timer interrupt. However, on the Mac, the high-resolution millisecond clock is expensive to poll. Thus, we use a fast, low-resolution (1/60th second) clock to determine if the primitive took enough time to justify polling the high-resolution clock. Seems Byzantine, but Bob Arning showed that the performance of primitive-intensive code decreased substantially if there was another process waiting on a Delay. One other detail: If the primitive fails, we want to postpone the timer interrupt until just after the primitive failure code has been entered. This is accomplished by setting the interrupt check counter to zero, thus triggering a check for interrupts when activating the method containing the primitive."
| timerPending startTime delta primIdx nArgs | timerPending _ nextWakeupTick ~= 0. timerPending ifTrue: [startTime _ self ioLowResMSecs]. DoBalanceChecks ifTrue:["check stack balance" nArgs _ argumentCount. primIdx _ primitiveIndex. delta _ stackPointer - activeContext. ]. successFlag _ true. self dispatchOn: primitiveIndex in: PrimitiveTable. DoBalanceChecks ifTrue:[ (self balancedStack: delta afterPrimitive: primIdx withArgs: nArgs) ifFalse:[self printUnbalancedStack: primIdx]. ]. timerPending ifTrue: [ (self ioLowResMSecs ~= startTime) ifTrue: [ "primitive ran for more than a tick; check for possible timer interrupts" ((self ioMSecs bitAnd: MillisecondClockMask) >= nextWakeupTick) ifTrue: [ successFlag ifTrue: ["process the interrupt now" self checkForInterrupts] ifFalse: ["process the interrupt in primtive failure code" interruptCheckCounter _ 0]]]]. ^ successFlag
Hello Andreas,
3.7.1 VM does not have the timing problem, 3.9.1 has it.
3.7.1 VM does not start my 3.8.2 image with a running web server. Actually it does not run any 3.8.2 image but it runs a standard 3.8-6665 full image.
Subsequently updating 3.8-6665 to 3.8.2 makes 3.7.1 VM start the image.
3.9.1 does start 3.8.2 with running Aida web server.
AR> Yes, if you are new to this game a bit of learning is required. AR> Some of it is definitely a problem you have. thanks a lot, I'll continue learning!
Cheers
Herbert mailto:herbertkoenig@gmx.net
Hi,
HK> 3.7.1 VM does not start my 3.8.2 image with a running web server. HK> Actually it does not run any 3.8.2 image but it runs a standard HK> 3.8-6665 full image.
that was wrong, doesn't start several 3.8.2 images but I found one with several packages loaded that starts.
Have to investigate more.
Cheers
Herbert mailto:herbertkoenig@gmx.net
The Logging package is able to set up per process logs. I have been planning to write a per-logged-in-user logging facility as an add on for for seaside.
Logging has facilities for timeing things, and the time stamps in 3.10.2 have millisecond resolution. I also have some ruby code somewhere which can trawl through logs, and generate data for plotting graphs.
Its not as clever as proper profiling, but its a potential option.
Keith
Let me share one more irritation fix.
If you have one of those nice 35 inch or so LCD screens, then having a much wider message tally report is rather nice.
MessageTally>>defaultMaxTabs "Return the default number of tabs after which leading white space is compressed" ^40
On 28-Feb-09, at 10:45 AM, Andreas Raab wrote:
-- = = = ======================================================================== John M. McIntosh johnmci@smalltalkconsulting.com Corporate Smalltalk Consulting Ltd. http://www.smalltalkconsulting.com = = = ========================================================================
On 28-Feb-09, at 10:45 AM, Andreas Raab wrote:
Unfortunately, recent VMs have a problem with spy accuracy since an important timer check was removed from the VM (I didn't notice that at the time it happened). This *heavily* affects the profiler, for example, try this:
MessageTally spyOn:[ 1 to: 100 do:[:i| Display fillWhite. 'Hello', ' World'. ]. ].
Ah you mean perhaps the removal of the checkForInterrupts() which *could/might* occur after the call in
dispatchFunctionPointer
I'm just guessing of course, mind adding in a clock check on every primitive call will result in a small amount of overhead.
-- = = = ======================================================================== John M. McIntosh johnmci@smalltalkconsulting.com Corporate Smalltalk Consulting Ltd. http://www.smalltalkconsulting.com = = = ========================================================================
Oops you CANN'T just call checkForInterrupts from dispatchFunctionPointer that makes squirrel food of object memory later in time (no idea why).
The safe way is to get the before and after time, then if they are different then call forceInterruptCheck
On 28-Feb-09, at 3:34 PM, John M McIntosh wrote:
On 28-Feb-09, at 10:45 AM, Andreas Raab wrote:
Unfortunately, recent VMs have a problem with spy accuracy since an important timer check was removed from the VM (I didn't notice that at the time it happened). This *heavily* affects the profiler, for example, try this:
MessageTally spyOn:[ 1 to: 100 do:[:i| Display fillWhite. 'Hello', ' World'. ]. ].
Ah you mean perhaps the removal of the checkForInterrupts() which *could/might* occur after the call in
dispatchFunctionPointer
I'm just guessing of course, mind adding in a clock check on every primitive call will result in a small amount of overhead.
-- = = = = = ====================================================================== John M. McIntosh johnmci@smalltalkconsulting.com Corporate Smalltalk Consulting Ltd. http:// www.smalltalkconsulting.com = = = = = ======================================================================
-- = = = ======================================================================== John M. McIntosh johnmci@smalltalkconsulting.com Corporate Smalltalk Consulting Ltd. http://www.smalltalkconsulting.com = = = ========================================================================
Mmmm guessing shows that ensuring forceInterruptCheck is called after dispatchFunctionPointer DOESN'T make the profile correct. So I'll wait for the true answer...
On 28-Feb-09, at 4:35 PM, John M McIntosh wrote:
Oops you CANN'T just call checkForInterrupts from dispatchFunctionPointer that makes squirrel food of object memory later in time (no idea why).
The safe way is to get the before and after time, then if they are different then call forceInterruptCheck
-- = = = ======================================================================== John M. McIntosh johnmci@smalltalkconsulting.com Corporate Smalltalk Consulting Ltd. http://www.smalltalkconsulting.com = = = ========================================================================
Hi Andreas,
Andreas Raab wrote:
... This is tricky with MessageTally. There are several issues to keep in mind, some of which you can fix, some of which you can't. Here we go:
- Observing all Processes
First, MessageTally only observes the block you are running, not the entire system. For example, if you were to profile this:
done := Semaphore new. worker:= [ 10 timesRepeat:[World changed; displayWorld]. done signal. ] fork. MessageTally spyOn:[done wait].
This is what you'd get:
**Tree** 100.0% {2367ms} primitives
**Leaves** 100.0% {2367ms} UndefinedObject>>DoIt
Obviously it's not actually measuring what is going on during the period of time which is a real problem if you are trying to measure server load in general. But it's fixable. ...
I found all this stuff very interesting and useful. Thanks!
WRT issue 1), what I'd really like is the tally tree to have several roots, one for each forked process. Otherwise, the process that forks other processes would also have their tallies added to him (which is wrong, as it would appear to be using more time than it really did). Fixing this, MessageTally could give a better insight on cpu usage than #tallyCPUUsageFor:.
To do this, I'd need to find out for a certain context in the sender chain, on which process it was running. (Then, when building the tally tree, I could know that I need to add a new root.) Do you know how to find out? I spent a couple of hours on this, and it seems it is not possible...
Thanks, Juan Vuletich
Ps. I found that the problem with long primitives first appears in your 3.9.1 VM, and that BitBlt seems to slow down a bit in 3.7.1 and even more in 3.9.1. Are you planning to address this issues in the Windows VM?
Hi Juan,
On Tue, Mar 3, 2009 at 5:28 AM, Juan Vuletich juan@jvuletich.org wrote:
Hi Andreas,
Andreas Raab wrote:
... This is tricky with MessageTally. There are several issues to keep in mind, some of which you can fix, some of which you can't. Here we go:
- Observing all Processes
First, MessageTally only observes the block you are running, not the entire system. For example, if you were to profile this:
done := Semaphore new. worker:= [ 10 timesRepeat:[World changed; displayWorld]. done signal. ] fork. MessageTally spyOn:[done wait].
This is what you'd get:
**Tree** 100.0% {2367ms} primitives
**Leaves** 100.0% {2367ms} UndefinedObject>>DoIt
Obviously it's not actually measuring what is going on during the period of time which is a real problem if you are trying to measure server load in general. But it's fixable. ...
I found all this stuff very interesting and useful. Thanks!
WRT issue 1), what I'd really like is the tally tree to have several roots, one for each forked process. Otherwise, the process that forks other processes would also have their tallies added to him (which is wrong, as it would appear to be using more time than it really did). Fixing this, MessageTally could give a better insight on cpu usage than #tallyCPUUsageFor:.
To do this, I'd need to find out for a certain context in the sender chain, on which process it was running. (Then, when building the tally tree, I could know that I need to add a new root.) Do you know how to find out? I spent a couple of hours on this, and it seems it is not possible...
When MessageTally runs to collect each tally the process that has been interrupted is the highest priority runnable process in the runnable process lists in ProcessorScheduler. You could implement it like this:
!ProcessorScheduler methodsFor: 'accessing' stamp: 'eem 3/3/2009 10:41'! highestPriorityRunnableProcess [quiescentProcessLists reverseDo: [:each| each isEmpty ifFalse: [^each first]]] valueUnpreemptively. ^nil
"| thisProcess interruptedProcess done | thisProcess := Processor activeProcess. done := false. [(Delay forSeconds: 1) wait. interruptedProcess := Processor highestPriorityRunnableProcess. done := true] forkAt: Processor userInterruptPriority. [done] whileFalse. self assert: thisProcess == interruptedProcess"! !
So modify MessageTally (or better still create a subclass called MultiProcessMessageTally) that uses the above to manage a set of tallies for each process found while spying.
HTH
Thanks, Juan Vuletich
Ps. I found that the problem with long primitives first appears in your 3.9.1 VM, and that BitBlt seems to slow down a bit in 3.7.1 and even more in 3.9.1. Are you planning to address this issues in the Windows VM?
Hi Eliot,
Eliot Miranda wrote:
Hi Juan,
On Tue, Mar 3, 2009 at 5:28 AM, Juan Vuletich <juan@jvuletich.org mailto:juan@jvuletich.org> wrote:
Hi Andreas, Andreas Raab wrote: ... This is tricky with MessageTally. There are several issues to keep in mind, some of which you can fix, some of which you can't. Here we go: 1) Observing all Processes -------------------------- First, MessageTally only observes the block you are running, not the entire system. For example, if you were to profile this: done := Semaphore new. worker:= [ 10 timesRepeat:[World changed; displayWorld]. done signal. ] fork. MessageTally spyOn:[done wait]. This is what you'd get: **Tree** 100.0% {2367ms} primitives **Leaves** 100.0% {2367ms} UndefinedObject>>DoIt Obviously it's not actually measuring what is going on during the period of time which is a real problem if you are trying to measure server load in general. But it's fixable. ... I found all this stuff very interesting and useful. Thanks! WRT issue 1), what I'd really like is the tally tree to have several roots, one for each forked process. Otherwise, the process that forks other processes would also have their tallies added to him (which is wrong, as it would appear to be using more time than it really did). Fixing this, MessageTally could give a better insight on cpu usage than #tallyCPUUsageFor:. To do this, I'd need to find out for a certain context in the sender chain, on which process it was running. (Then, when building the tally tree, I could know that I need to add a new root.) Do you know how to find out? I spent a couple of hours on this, and it seems it is not possible...
When MessageTally runs to collect each tally the process that has been interrupted is the highest priority runnable process in the runnable process lists in ProcessorScheduler. You could implement it like this:
!ProcessorScheduler methodsFor: 'accessing' stamp: 'eem 3/3/2009 10:41'! highestPriorityRunnableProcess [quiescentProcessLists reverseDo: [:each| each isEmpty ifFalse: [^each first]]] valueUnpreemptively. ^nil
"| thisProcess interruptedProcess done | thisProcess := Processor activeProcess. done := false. [(Delay forSeconds: 1) wait. interruptedProcess := Processor highestPriorityRunnableProcess. done := true] forkAt: Processor userInterruptPriority. [done] whileFalse. self assert: thisProcess == interruptedProcess"! !
So modify MessageTally (or better still create a subclass called MultiProcessMessageTally) that uses the above to manage a set of tallies for each process found while spying.
HTH
What you say sounds similar to what Andreas suggests to make MessageTally spy over all processes. The issue I point out is that when MessageTally builds the tree, the sender of a context might be in another process. This happens when a context forks a new process, it is still its sender. So when building the tally tree, I need to query each context for the process running it, and when it is different from the parent's one, I'll start a new tally tree.
I made it work, by adding an ivar to Process to hold the firstContext (the one that is sent to #forContext:priority:). Then, when building the tally tree, for each context, I check if it is the firstContext of some process. The problem with this approach is that it adds too much overhead to the tally.
I'd really appreciate a better way to do this. I'm sure everybody will like the multi-process tally that would result!
Cheers, Juan Vuletich
On Tue, Mar 3, 2009 at 11:56 AM, Juan Vuletich juan@jvuletich.org wrote:
Hi Eliot,
Eliot Miranda wrote:
Hi Juan,
On Tue, Mar 3, 2009 at 5:28 AM, Juan Vuletich <juan@jvuletich.orgmailto: juan@jvuletich.org> wrote:
Hi Andreas,
Andreas Raab wrote:
... This is tricky with MessageTally. There are several issues to keep in mind, some of which you can fix, some of which you can't. Here we go: 1) Observing all Processes -------------------------- First, MessageTally only observes the block you are running, not the entire system. For example, if you were to profile this: done := Semaphore new. worker:= [ 10 timesRepeat:[World changed; displayWorld]. done signal. ] fork. MessageTally spyOn:[done wait]. This is what you'd get: **Tree** 100.0% {2367ms} primitives **Leaves** 100.0% {2367ms} UndefinedObject>>DoIt Obviously it's not actually measuring what is going on during the period of time which is a real problem if you are trying to measure server load in general. But it's fixable. ...
I found all this stuff very interesting and useful. Thanks!
WRT issue 1), what I'd really like is the tally tree to have several roots, one for each forked process. Otherwise, the process that forks other processes would also have their tallies added to him (which is wrong, as it would appear to be using more time than it really did). Fixing this, MessageTally could give a better insight on cpu usage than #tallyCPUUsageFor:.
To do this, I'd need to find out for a certain context in the sender chain, on which process it was running. (Then, when building the tally tree, I could know that I need to add a new root.) Do you know how to find out? I spent a couple of hours on this, and it seems it is not possible...
When MessageTally runs to collect each tally the process that has been interrupted is the highest priority runnable process in the runnable process lists in ProcessorScheduler. You could implement it like this:
!ProcessorScheduler methodsFor: 'accessing' stamp: 'eem 3/3/2009 10:41'! highestPriorityRunnableProcess [quiescentProcessLists reverseDo: [:each| each isEmpty ifFalse: [^each first]]] valueUnpreemptively. ^nil
"| thisProcess interruptedProcess done | thisProcess := Processor activeProcess. done := false. [(Delay forSeconds: 1) wait. interruptedProcess := Processor highestPriorityRunnableProcess. done := true] forkAt: Processor userInterruptPriority. [done] whileFalse. self assert: thisProcess == interruptedProcess"! !
So modify MessageTally (or better still create a subclass called MultiProcessMessageTally) that uses the above to manage a set of tallies for each process found while spying.
HTH
What you say sounds similar to what Andreas suggests to make MessageTally spy over all processes. The issue I point out is that when MessageTally builds the tree, the sender of a context might be in another process. This happens when a context forks a new process, it is still its sender. So when building the tally tree, I need to query each context for the process running it, and when it is different from the parent's one, I'll start a new tally tree.
I think I see but I wouldn't put it like that. Yes, new processes get resumed in the context of other proesses, but no, the sender is not in another process. The bottom context of a process has no sender. So why bother at all trying to track down in which process a process was created and leave that to the user when interpreting the profile?
I made it work, by adding an ivar to Process to hold the firstContext (the
one that is sent to #forContext:priority:). Then, when building the tally tree, for each context, I check if it is the firstContext of some process. The problem with this approach is that it adds too much overhead to the tally.
IMO just throw this away. You don't absolutely need to know on behalf of which process a process is running.
I'd really appreciate a better way to do this. I'm sure everybody will like
the multi-process tally that would result!
I believe the VW multi-process profiler doesn't bother identifying the parent process. But if it does it can do so by e.g. adding an inst var to process that refers to the parent process, rather than the context in which the process was created. But both recording the context or the parent process are bad ideas for garbage collection.
I would simply use the process name facility (Process>>name[:] as displayed by the process browser) to label the various tallies you collect. If a user can't work out which process is which by just looking at the the profile ten they can change their code to use Process>>name: to add names to various processes and then be able to wrk it out.
just my 2¢
best E.
Cheers, Juan Vuletich
Hi Eliot,
Eliot Miranda wrote:
...
I think I see but I wouldn't put it like that. Yes, new processes get resumed in the context of other proesses, but no, the sender is not in another process. The bottom context of a process has no sender. So why bother at all trying to track down in which process a process was created and leave that to the user when interpreting the profile?
I made it work, by adding an ivar to Process to hold the firstContext (the one that is sent to #forContext:priority:). Then, when building the tally tree, for each context, I check if it is the firstContext of some process. The problem with this approach is that it adds too much overhead to the tally.
IMO just throw this away. You don't absolutely need to know on behalf of which process a process is running.
I'd really appreciate a better way to do this. I'm sure everybody will like the multi-process tally that would result!
I believe the VW multi-process profiler doesn't bother identifying the parent process. But if it does it can do so by e.g. adding an inst var to process that refers to the parent process, rather than the context in which the process was created. But both recording the context or the parent process are bad ideas for garbage collection.
I would simply use the process name facility (Process>>name[:] as displayed by the process browser) to label the various tallies you collect. If a user can't work out which process is which by just looking at the the profile ten they can change their code to use Process>>name: to add names to various processes and then be able to wrk it out.
just my 2¢
best E.
I'm not interested at all on identifying the parent process. To make my aim clearer, let's see an example:
[1000 timesRepeat: [3.14159 printString. Processor yield]] fork. [1000 timesRepeat: [30 factorial. Processor yield]] fork. [1000 timesRepeat: [30 factorial. Processor yield]] fork. MessageTally spyOn: [ (Delay forMilliseconds: 100) wait]
Without Andreas fixes, it gives a completely useless answer, as if all the time was spent in the Delay.
With Andreas' fix, it gives:
**Tree** 55.0% {55ms} SmallInteger(Integer)>>factorial |50.0% {50ms} SmallInteger(Integer)>>factorial | 47.0% {47ms} SmallInteger(Integer)>>factorial | 42.0% {42ms} SmallInteger(Integer)>>factorial | 39.0% {39ms} SmallInteger(Integer)>>factorial | 33.0% {33ms} SmallInteger(Integer)>>factorial | 30.0% {30ms} SmallInteger(Integer)>>factorial | 23.0% {23ms} SmallInteger(Integer)>>factorial | 22.0% {22ms} SmallInteger(Integer)>>factorial | 18.0% {18ms} SmallInteger(Integer)>>factorial | 18.0% {18ms} SmallInteger(Integer)>>factorial | 16.0% {16ms} SmallInteger(Integer)>>factorial | 14.0% {14ms} SmallInteger(Integer)>>factorial | 11.0% {11ms} SmallInteger(Integer)>>factorial 23.0% {23ms} Float(Object)>>printString 23.0% {23ms} Float(Object)>>printStringLimitedTo: 20.0% {20ms} Float(Number)>>printOn: 18.0% {18ms} Float>>printOn:base: 18.0% {18ms} Float>>absPrintOn:base:
It could separate the first forked process, but only because it runs different code (#printString). Both processes running #factorial are shown together. It is true that a context with no sender (the bottom of a stack) is a root at the tree. But we can not know to which process they belong. Therefore I can not decorate each tree with a process name!
What I can get with my code is:
**Tree** ----------------------------------- unknown process ----------------------------------- 29.2% {31ms} BlockContext>>newProcess |29.2% {31ms} ProcessorScheduler class>>startUp | 29.2% {31ms} ProcessorScheduler class>>idleProcess ----------------------------------- (40) process named: 1992 ----------------------------------- 28.3% {30ms} MessageTally class>>DoIt |28.3% {30ms} SmallInteger(Integer)>>timesRepeat: | 26.4% {28ms} MessageTally class>>DoIt | 26.4% {28ms} Float(Object)>>printString | 25.5% {27ms} Float(Object)>>printStringLimitedTo: | 25.5% {27ms} String class(SequenceableCollection class)>>streamContents:limitedTo: | 22.6% {24ms} Float(Object)>>printStringLimitedTo: | 22.6% {24ms} Float(Number)>>printOn: | 21.7% {23ms} Float>>printOn:base: | 21.7% {23ms} Float>>absPrintOn:base: ----------------------------------- (40) process named: 2910 ----------------------------------- 17.9% {19ms} MessageTally class>>DoIt |17.9% {19ms} SmallInteger(Integer)>>timesRepeat: | 17.0% {18ms} MessageTally class>>DoIt | 17.0% {18ms} SmallInteger(Integer)>>factorial | 15.1% {16ms} SmallInteger(Integer)>>factorial | 12.3% {13ms} SmallInteger(Integer)>>factorial ----------------------------------- (40) process named: 893 ----------------------------------- 15.1% {16ms} MessageTally class>>DoIt 15.1% {16ms} SmallInteger(Integer)>>timesRepeat: 15.1% {16ms} MessageTally class>>DoIt 15.1% {16ms} SmallInteger(Integer)>>factorial 14.2% {15ms} SmallInteger(Integer)>>factorial 14.2% {15ms} SmallInteger(Integer)>>factorial 13.2% {14ms} SmallInteger(Integer)>>factorial 13.2% {14ms} SmallInteger(Integer)>>factorial
I can get the tally for each process. Many times this will be much easier to optimize the application, especially if each process can have a meaningful name, and a well known purpose.
If you say that the process holding a reference to the first context it ran is bad for GC, I take your word. But I'd really like to solve this problem.
Thanks, Juan Vuletich
Juan Vuletich wrote:
I'm not interested at all on identifying the parent process. To make my aim clearer, let's see an example:
[1000 timesRepeat: [3.14159 printString. Processor yield]] fork. [1000 timesRepeat: [30 factorial. Processor yield]] fork. [1000 timesRepeat: [30 factorial. Processor yield]] fork. MessageTally spyOn: [ (Delay forMilliseconds: 100) wait]
Without Andreas fixes, it gives a completely useless answer, as if all the time was spent in the Delay.
Ah, very good! I've been looking for an example that illustrates just that. This is extremely helpful. For our purposes (lots of processes, you want to profile overall server behavior) the cumulative tree is the only useful response. There would be no point whatsoever even trying to separate hundreds of processes (it is statistical profiling after all).
However, I can see that in some situations your view might be useful for some people. I would guess that the easiest way to distinguish processes would be look at the identity of the context that is at the root of the call tree. If you send me the code I'll see if I can come up with a clever way to have our cake and eat it, too.
Cheers, - Andreas
Juan Vuletich wrote:
What you say sounds similar to what Andreas suggests to make MessageTally spy over all processes. The issue I point out is that when MessageTally builds the tree, the sender of a context might be in another process. This happens when a context forks a new process, it is still its sender. So when building the tally tree, I need to query each context for the process running it, and when it is different from the parent's one, I'll start a new tally tree.
I'm not sure how useful this is. When it comes to measuring where the time goes, the cumulative tree of multiple processes shows you if you have deficiencies in the core framework handling. This is extremely useful. And it breaks down quite nicely into separate pieces where actual different work is involved. I don't think you would get much more useful information if you were to break things apart. And I can say for sure that for the purposes of our server profiling it would be pretty much useless - under load we run literally hundreds of processes.
Cheers, - Andreas
Guys, can i ask you to put these recommendations on wiki page, so this VERY USEFUL guide will not be buried in mailing list and can serve as a basic guide for profiling in Squeak?
Igor Stasenko wrote:
Guys, can i ask you to put these recommendations on wiki page, so this VERY USEFUL guide will not be buried in mailing list and can serve as a basic guide for profiling in Squeak?
Where would you put it? I have never found my way through the Wiki. If it's helpful for others I'd be happy to put it up, just tell me where you'd like it to go.
Cheers, - Andreas
2009/3/4 Andreas Raab andreas.raab@gmx.de:
Igor Stasenko wrote:
Guys, can i ask you to put these recommendations on wiki page, so this VERY USEFUL guide will not be buried in mailing list and can serve as a basic guide for profiling in Squeak?
Where would you put it? I have never found my way through the Wiki. If it's helpful for others I'd be happy to put it up, just tell me where you'd like it to go.
There is a wiki page, already dedicated to it http://wiki.squeak.org/squeak/1799
Or , maybe its worth create a new page 'Profiling in Squeak' .. or give it more appropriate name
Cheers, - Andreas
Igor Stasenko wrote:
There is a wiki page, already dedicated to it http://wiki.squeak.org/squeak/1799
Or , maybe its worth create a new page 'Profiling in Squeak' .. or give it more appropriate name
Now up at http://wiki.squeak.org/squeak/6122
Which reminds me: I had forgotten how much I hate the Swiki. It is ugly, horribly conflated, and effectively unusable. Should we replace the damn thing with a community blog instead? I think this might be a better way of dealing with the information overload - you get dated, tagged entry and if we keep this open for pretty much every member of the community (using the "standard" username/password from the Swiki) we could use it much the same.
I'd be more than willing to write up my post as a blog article instead. I just don't have a blog and even if I had I doubt many people would use it. A community blog could be a nice, lively place to post your thoughts in a bit more organized manner and would make an attractive target for people who just want to know what's going on in the world of Squeak.
Cheers, - Andreas
2009/3/4 Andreas Raab andreas.raab@gmx.de:
Igor Stasenko wrote:
There is a wiki page, already dedicated to it http://wiki.squeak.org/squeak/1799
Or , maybe its worth create a new page 'Profiling in Squeak' .. or give it more appropriate name
Now up at http://wiki.squeak.org/squeak/6122
Which reminds me: I had forgotten how much I hate the Swiki. It is ugly, horribly conflated, and effectively unusable. Should we replace the damn thing with a community blog instead? I think this might be a better way of dealing with the information overload - you get dated, tagged entry and if we keep this open for pretty much every member of the community (using the "standard" username/password from the Swiki) we could use it much the same.
I'd be more than willing to write up my post as a blog article instead. I just don't have a blog and even if I had I doubt many people would use it. A community blog could be a nice, lively place to post your thoughts in a bit more organized manner and would make an attractive target for people who just want to know what's going on in the world of Squeak.
we had a http://people.squeak.org/ which has a kind of blogging facilities, but now it looks like dead. The page is different.
P.S. +1 for having a community blog.
Cheers, - Andreas
+1
Steve
On 3 Mar 2009, at 18:25, Andreas Raab andreas.raab@gmx.de wrote:
Igor Stasenko wrote:
There is a wiki page, already dedicated to it http://wiki.squeak.org/squeak/1799 Or , maybe its worth create a new page 'Profiling in Squeak' .. or give it more appropriate name
Now up at http://wiki.squeak.org/squeak/6122
Which reminds me: I had forgotten how much I hate the Swiki. It is ugly, horribly conflated, and effectively unusable. Should we replace the damn thing with a community blog instead? I think this might be a better way of dealing with the information overload - you get dated, tagged entry and if we keep this open for pretty much every member of the community (using the "standard" username/ password from the Swiki) we could use it much the same.
I'd be more than willing to write up my post as a blog article instead. I just don't have a blog and even if I had I doubt many people would use it. A community blog could be a nice, lively place to post your thoughts in a bit more organized manner and would make an attractive target for people who just want to know what's going on in the world of Squeak.
Cheers,
- Andreas
+1
Larry Trutter
Date: Tue, 3 Mar 2009 18:25:35 -0800 From: andreas.raab@gmx.de To: squeak-dev@lists.squeakfoundation.org Subject: [squeak-dev] Community blog? (was: Re: How to profile a server image?)
Igor Stasenko wrote:
There is a wiki page, already dedicated to it http://wiki.squeak.org/squeak/1799
Or , maybe its worth create a new page 'Profiling in Squeak' .. or give it more appropriate name
Now up at http://wiki.squeak.org/squeak/6122
Which reminds me: I had forgotten how much I hate the Swiki. It is ugly, horribly conflated, and effectively unusable. Should we replace the damn thing with a community blog instead? I think this might be a better way of dealing with the information overload - you get dated, tagged entry and if we keep this open for pretty much every member of the community (using the "standard" username/password from the Swiki) we could use it much the same.
I'd be more than willing to write up my post as a blog article instead. I just don't have a blog and even if I had I doubt many people would use it. A community blog could be a nice, lively place to post your thoughts in a bit more organized manner and would make an attractive target for people who just want to know what's going on in the world of Squeak.
Cheers,
- Andreas
_________________________________________________________________ Windows Live™ Contacts: Organize your contact list. http://windowslive.com/connect/post/marcusatmicrosoft.spaces.live.com-Blog-c...
A blog and a wiki is not for the same purpposes IMHO.
Maybe, could be interesting, migrate the actual wiki, to some new over Seaside-Pier / Aida-Scribo.
Cheers.
Andreas Raab escribió:
Igor Stasenko wrote:
There is a wiki page, already dedicated to it http://wiki.squeak.org/squeak/1799
Or , maybe its worth create a new page 'Profiling in Squeak' .. or give it more appropriate name
Now up at http://wiki.squeak.org/squeak/6122
Which reminds me: I had forgotten how much I hate the Swiki. It is ugly, horribly conflated, and effectively unusable. Should we replace the damn thing with a community blog instead? I think this might be a better way of dealing with the information overload - you get dated, tagged entry and if we keep this open for pretty much every member of the community (using the "standard" username/password from the Swiki) we could use it much the same.
I'd be more than willing to write up my post as a blog article instead. I just don't have a blog and even if I had I doubt many people would use it. A community blog could be a nice, lively place to post your thoughts in a bit more organized manner and would make an attractive target for people who just want to know what's going on in the world of Squeak.
Cheers,
- Andreas
"Andreas Raab" andreas.raab@gmx.de wrote:
Igor Stasenko wrote:
There is a wiki page, already dedicated to it http://wiki.squeak.org/squeak/1799
Or , maybe its worth create a new page 'Profiling in Squeak' .. or give it more appropriate name
Now up at http://wiki.squeak.org/squeak/6122
Which reminds me: I had forgotten how much I hate the Swiki. It is ugly, horribly conflated, and effectively unusable. Should we replace the damn thing with a community blog instead? I think this might be a better way of dealing with the information overload - you get dated, tagged entry and if we keep this open for pretty much every member of the community (using the "standard" username/password from the Swiki) we could use it much the same.
I'd be more than willing to write up my post as a blog article instead. I just don't have a blog and even if I had I doubt many people would use it. A community blog could be a nice, lively place to post your thoughts in a bit more organized manner and would make an attractive target for people who just want to know what's going on in the world of Squeak.
Well, I'd follow your blog, for one. But http://planet.squeak.org/ is pretty darn cool: start your own blog, and have the planet aggregate it.
Or there's Yahoo! Pipes, described by Bill Clementson here: http://bc.tech.coop/blog/081206.html
The concept's the same, at any rate: instead of a single community blog, there's a single blog aggregator, fed by multiple personal blogs.
frank
Well, I'd follow your blog, for one. But http://planet.squeak.org/ is pretty darn cool: start your own blog, and have the planet aggregate it.
I'd better use our own tool, a kind of mix between blog and wiki... using rights to allow some other people to modify/complete some posts... I think we have it already minus some particularities, it's more a question of organization.
One thing I'll find important is to define a common set of tags (topics, level of completion, importance, documentation type, deprecation (st version concerned) etc...).
Voting on relevance cound be nice too...
Having proper developper/person profile would be of high interest to me too... so that a beginner can find post written by beginner, tutorial for beginners, etc... If you "like" someone, you can get his posts/modifs/comments plus the ones of people that are "similar" to him...
...
Just my 2 cents ;)
Or there's Yahoo! Pipes, described by Bill Clementson here: http://bc.tech.coop/blog/081206.html
The concept's the same, at any rate: instead of a single community blog, there's a single blog aggregator, fed by multiple personal blogs.
frank
On 04.03.2009, at 10:01, Frank Shearar wrote:
http://planet.squeak.org/ is pretty darn cool: start your own blog, and have the planet aggregate it.
The concept's the same, at any rate: instead of a single community blog, there's a single blog aggregator, fed by multiple personal blogs.
It would be more useful if the individual feeds were only for those blog posts tagged "squeak" ... for my taste too many non-squeak related posts make it to the planet. Most blog engines allow to tag posts, and to get feeds for specific tags only. Those filtered feeds should get aggregated, not every "guitar hero" blurb ;)
- Bert -
Hi!
Bert Freudenberg wrote:
It would be more useful if the individual feeds were only for those blog posts tagged "squeak" ... for my taste too many non-squeak related posts make it to the planet. Most blog engines allow to tag posts, and to get feeds for specific tags only. Those filtered feeds should get aggregated, not every "guitar hero" blurb ;)
We just set up a Venus aggregator here at MSC - Venus is the enhanced version of Planet that has filtering capabilities. Then you can filter on categories/tags etc.
Furthermore - a blog aggregator (or community blog - but a blog aggregator is IMHO smarter) serves a different purpose than a wiki.
I would though consider setting up a new "purged" wiki that "starts over" from scratch and perhaps tries to limits itself in what it documents - like perhaps from a given version of Squeak and forward.
regards, Göran
On planet.squeak.org, I use squeak/smalltalk-specific topic feeds when I can find them, and I omit blogs which seem less than 50% squeak related. I usually find the result on-topic enough, with occasional enjoyable diversions to euro board games etc. My model is the Debian, Gnome and Ubuntu planets which are about building a strong community, not just propagating technical discussion.
But, just send a patch or suggestion implementing the changes you want. If a particular blog is too off-topic and there's no topic feed, you could try contacting the author here and talking them into line.
+1. blog is the way to go.
It *could* be st based but it *hasn't* necessarily to be st based.
Design matters so it *hasn't* to hurt the eye when you see it. Today that could be more important than bloating it with features.
cheers, sebastian
-----Mensaje original----- De: squeak-dev-bounces@lists.squeakfoundation.org [mailto:squeak-dev-bounces@lists.squeakfoundation.org] En nombre de Andreas Raab Enviado el: Tuesday, March 03, 2009 23:26 Para: The general-purpose Squeak developers list Asunto: [squeak-dev] Community blog? (was: Re: How to profile a serverimage?)
Igor Stasenko wrote:
There is a wiki page, already dedicated to it http://wiki.squeak.org/squeak/1799
Or , maybe its worth create a new page 'Profiling in Squeak' .. or give it more appropriate name
Now up at http://wiki.squeak.org/squeak/6122
Which reminds me: I had forgotten how much I hate the Swiki. It is ugly, horribly conflated, and effectively unusable. Should we replace the damn thing with a community blog instead? I think this might be a better way of dealing with the information overload - you get dated, tagged entry and if we keep this open for pretty much every member of the community (using the "standard" username/password from the Swiki) we could use it much the same.
I'd be more than willing to write up my post as a blog article instead. I just don't have a blog and even if I had I doubt many people would use it. A community blog could be a nice, lively place to post your thoughts in a bit more organized manner and would make an attractive target for people who just want to know what's going on in the world of Squeak.
Cheers,
- Andreas
On Tue, Mar 3, 2009 at 6:25 PM, Andreas Raab andreas.raab@gmx.de wrote:
Igor Stasenko wrote:
There is a wiki page, already dedicated to it http://wiki.squeak.org/squeak/1799
Or , maybe its worth create a new page 'Profiling in Squeak' .. or give it more appropriate name
Now up at http://wiki.squeak.org/squeak/6122
Which reminds me: I had forgotten how much I hate the Swiki. It is ugly, horribly conflated, and effectively unusable.
Wikis are like gardens. Untended they grow like weeds. Unless one has a writership with an active minority of editors, who spend time trying to reorganize the wiki as it sprawls one may well end up with something effectively unusable. But as a form for organizing growing reference material IMO they can't be beat; look at Wikipedia. That is a superb wiki. The VisualWorks wiki was pretty good until its lack of spam protection and lack of long-term hosting killed it dead. Couldn't we have a community project to reorganize the wiki? Its the right form, its just a little overgrown right now.
Should we replace the damn thing with a community blog instead?
Different form for different uses. The Squeak daily news works as a blog. I can't see how a blog would scale to hold the reference material for Galaxy Squeak.
I think this might be a better way of dealing with the information overload
- you get dated, tagged entry and if we keep this open for pretty much every
member of the community (using the "standard" username/password from the Swiki) we could use it much the same.
I'd be more than willing to write up my post as a blog article instead. I just don't have a blog and even if I had I doubt many people would use it. A community blog could be a nice, lively place to post your thoughts in a bit more organized manner and would make an attractive target for people who just want to know what's going on in the world of Squeak.
Cheers,
- Andreas
That's completely valid but it comes back to Andreas' original question - where to put stuff?
Wikipedia works because there is a dedicated group of people working on the daunting task of classifying all human knowledge.
Maybe the package universe can be the backbone of a Squeak ontology. If current and relevant materials are easy to find not only will newcomers flock to Squeak, people who have been around for a while will be far more productive.
Modern IDEs have come a long way in integrating documentation and providing clever and useful code-completion features. I would love to see this in Squeak.
Steve
On 4 Mar 2009, at 08:55, Eliot Miranda eliot.miranda@gmail.com wrote:
On Tue, Mar 3, 2009 at 6:25 PM, Andreas Raab andreas.raab@gmx.de wrote: Igor Stasenko wrote: There is a wiki page, already dedicated to it http://wiki.squeak.org/squeak/1799
Or , maybe its worth create a new page 'Profiling in Squeak' .. or give it more appropriate name
Now up at http://wiki.squeak.org/squeak/6122
Which reminds me: I had forgotten how much I hate the Swiki. It is ugly, horribly conflated, and effectively unusable.
Wikis are like gardens. Untended they grow like weeds. Unless one has a writership with an active minority of editors, who spend time trying to reorganize the wiki as it sprawls one may well end up with something effectively unusable. But as a form for organizing growing reference material IMO they can't be beat; look at Wikipedia. That is a superb wiki. The VisualWorks wiki was pretty good until its lack of spam protection and lack of long-term hosting killed it dead. Couldn't we have a community project to reorganize the wiki? Its the right form, its just a little overgrown right now.
Should we replace the damn thing with a community blog instead?
Different form for different uses. The Squeak daily news works as a blog. I can't see how a blog would scale to hold the reference material for Galaxy Squeak.
I think this might be a better way of dealing with the information overload - you get dated, tagged entry and if we keep this open for pretty much every member of the community (using the "standard" username/password from the Swiki) we could use it much the same.
I'd be more than willing to write up my post as a blog article instead. I just don't have a blog and even if I had I doubt many people would use it. A community blog could be a nice, lively place to post your thoughts in a bit more organized manner and would make an attractive target for people who just want to know what's going on in the world of Squeak.
Cheers,
- Andreas
Eliot Miranda wrote:
Wikis are like gardens. Untended they grow like weeds. Unless one has a writership with an active minority of editors, who spend time trying to reorganize the wiki as it sprawls one may well end up with something effectively unusable. But as a form for organizing growing reference material IMO they can't be beat; look at Wikipedia. That is a superb wiki. The VisualWorks wiki was pretty good until its lack of spam protection and lack of long-term hosting killed it dead. Couldn't we have a community project to reorganize the wiki? Its the right form, its just a little overgrown right now.
A wiki can be great for reference material, but it's terrible for something that has a limited lifetime. My post about the current state of profiling makes sense today, but it won't make sense even weeks from now because what we were talking about was in the context of changing things. So you need to be able to look at a date and say "oh, that was five years ago, nevermind" or "oh, this is how they ended up doing it that way". Much of the information in particular on the Swiki is dated material that needs to be seen within a particular time frame. And that is after all, what news and blogs are for.
As for out-reach, I have never looked at either the VisualWorks wiki or the VisualWorks news groups / mailing lists. Practically all my (limited) information about VisualWorks is straight from here:
http://www.cincomsmalltalk.com/userblogs/
And I don't think I'm the only one. As a matter of fact, I think that the Weekly Squeak has probably more outreach than any other part of the community. Except that it's a bit of a high-profile operation (which is great since it makes it attractive) and what I am looking for is a place where we can put a bit more technical discussions and insights.
Look at it that way: Once we had a discussion on Squeak-dev and come to a conclusion, I'm looking for a place where can put that conclusion so that other people know that we've had the discussion and what its conclusion was. And I don't think the Swiki is the place for it.
Cheers, - Andreas
PS. Did I mention the Swiki is ugly? I mean, come on ... this is the 21st century.
I agree with what you wrote - but would an aggregator be enough? Or do you want to be able to discuss the articles also? I wonder if there are aggregator software that can deal with discussions too...
regards, Göran
Hi Andreas,
Andreas Raab wrote:
Juan Vuletich wrote:
What you say sounds similar to what Andreas suggests to make MessageTally spy over all processes. The issue I point out is that when MessageTally builds the tree, the sender of a context might be in another process. This happens when a context forks a new process, it is still its sender. So when building the tally tree, I need to query each context for the process running it, and when it is different from the parent's one, I'll start a new tally tree.
I'm not sure how useful this is. When it comes to measuring where the time goes, the cumulative tree of multiple processes shows you if you have deficiencies in the core framework handling. This is extremely useful. And it breaks down quite nicely into separate pieces where actual different work is involved. I don't think you would get much more useful information if you were to break things apart. And I can say for sure that for the purposes of our server profiling it would be pretty much useless - under load we run literally hundreds of processes.
Cheers,
- Andreas
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.
Thanks, Juan Vuletich
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
- 101 tallies, 101 msec.
**Tree** -------------------------------- Process: (40) 3420: [] in UndefinedObject>>DoIt {[3.14159 printString. Processor yield]} -------------------------------- 23.8% {24ms} Float(Number)>>printStringBase: 21.8% {22ms} Float>>printOn:base: 20.8% {21ms} Float>>absPrintOn:base: 6.9% {7ms} False>>| 5.9% {6ms} primitives 3.0% {3ms} WriteStream>>nextPut: -------------------------------- Process: (40) 1426: [] in UndefinedObject>>DoIt {[30 factorial. Processor yield]} -------------------------------- 29.7% {30ms} SmallInteger(Integer)>>factorial 26.7% {27ms} SmallInteger(Integer)>>factorial |24.8% {25ms} SmallInteger(Integer)>>factorial | 22.8% {23ms} SmallInteger(Integer)>>factorial | 21.8% {22ms} SmallInteger(Integer)>>factorial | 20.8% {21ms} SmallInteger(Integer)>>factorial | 16.8% {17ms} SmallInteger(Integer)>>factorial | |14.9% {15ms} SmallInteger(Integer)>>factorial | | 13.9% {14ms} SmallInteger(Integer)>>factorial | | 10.9% {11ms} SmallInteger(Integer)>>factorial | | |8.9% {9ms} SmallInteger(Integer)>>factorial | | | 7.9% {8ms} SmallInteger(Integer)>>factorial | | | 6.9% {7ms} SmallInteger(Integer)>>factorial | | | 5.0% {5ms} SmallInteger(Integer)>>factorial | | | 5.0% {5ms} SmallInteger(Integer)>>factorial | | | 3.0% {3ms} SmallInteger(Integer)>>factorial | | | 3.0% {3ms} SmallInteger(Integer)>>factorial | | 3.0% {3ms} SmallInteger>>* | 4.0% {4ms} SmallInteger>>* 3.0% {3ms} SmallInteger>>* -------------------------------- Process: (40) 3777: [] in UndefinedObject>>DoIt {[30 factorial. Processor yield]} -------------------------------- 40.6% {41ms} SmallInteger(Integer)>>factorial 38.6% {39ms} SmallInteger(Integer)>>factorial 37.6% {38ms} SmallInteger(Integer)>>factorial 35.6% {36ms} SmallInteger(Integer)>>factorial 30.7% {31ms} SmallInteger(Integer)>>factorial |26.7% {27ms} SmallInteger(Integer)>>factorial | |23.8% {24ms} SmallInteger(Integer)>>factorial | | |22.8% {23ms} SmallInteger(Integer)>>factorial | | | 21.8% {22ms} SmallInteger(Integer)>>factorial | | | 21.8% {22ms} SmallInteger(Integer)>>factorial | | | 19.8% {20ms} SmallInteger(Integer)>>factorial | | | 17.8% {18ms} SmallInteger(Integer)>>factorial | | | 14.9% {15ms} SmallInteger(Integer)>>factorial | | | |9.9% {10ms} SmallInteger(Integer)>>factorial | | | | |7.9% {8ms} SmallInteger(Integer)>>factorial | | | | | 5.9% {6ms} SmallInteger(Integer)>>factorial | | | | | 3.0% {3ms} SmallInteger>>* | | | | | 3.0% {3ms} SmallInteger(Integer)>>factorial | | | |5.0% {5ms} SmallInteger>>* | | | | 3.0% {3ms} primitives | | | 3.0% {3ms} SmallInteger>>* | |3.0% {3ms} SmallInteger>>* |4.0% {4ms} SmallInteger>>* 5.0% {5ms} SmallInteger>>* 3.0% {3ms} primitives **Leaves** 51.5% {52ms} SmallInteger>>* 7.9% {8ms} SmallInteger(Number)>>negative 6.9% {7ms} False>>| 5.9% {6ms} Float>>absPrintOn:base: 5.0% {5ms} SmallInteger(Integer)>>* 5.0% {5ms} False(ProtoObject)>>~~ 4.0% {4ms} SmallInteger(Integer)>>factorial
**Memory** old +0 bytes young -70,492 bytes used -70,492 bytes free +70,492 bytes
**GCs** full 0 totalling 0ms (0.0% uptime) incr 17 totalling 13ms (13.0% uptime), avg 1.0ms tenures 0 root table 0 overflows
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
'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'!
Juan Vuletich wrote:
...
- 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. ...
Thinking better about it, now I believe using any priority but the maximum possible is wrong. The new code uses Processor preemptedProcess to know which process we are tallying. But #preemptedProcess will never answer a process with higher priority than the current. This makes sense, otherwise it would be called #previousRunningProcess or something like that.
Now, let's suppose that the tally process wakes up after a higher priority process ends or yields, after using some cpu. #preemptedProcess will answer another process, one with lower priority, and its time slice will be added to it. So it will seem as if the higher priority process never ran and the lower priority one used all the time. This is an incorrect answer.
So this is a real bug. The attached version fixes this, and a couple of other bugs, especially when tallying a single process. So, please disregard the one I sent earlier. I also added acouple of examples in the comment in class methods.
Andreas, please take a look.
Thanks, Juan Vuletich
'From Squeak3.10.2 of ''5 June 2008'' [latest update: #7179] on 4 March 2009 at 3:19:33 pm'! 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: 'collecting leaves' stamp: 'jmv 3/4/2009 13:48'! into: leafDict fromSender: senderTally | leafNode | leafNode _ leafDict at: method ifAbsent: [leafDict at: method put: ((MessageTally new class: class method: method) process: process)]. leafNode bump: tally fromSender: senderTally! !
!MessageTally methodsFor: 'collecting leaves' stamp: 'jmv 3/4/2009 14:33'! leavesInto: leafDict fromSender: senderTally | rcvrs | 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: [:node | node isPrimitives ifTrue: [node leavesInto: leafDict fromSender: senderTally] ifFalse: [node leavesInto: leafDict fromSender: self]]]! !
!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: 'comparing' stamp: 'jmv 3/4/2009 13:48'! sonsOver: threshold
| hereTally last sons | (receivers == nil or: [receivers size = 0]) ifTrue: [^#()]. hereTally _ tally. sons _ receivers select: "subtract subNode tallies for primitive hits here" [:son | hereTally _ hereTally - son tally. son tally > threshold]. hereTally > threshold ifTrue: [last _ MessageTally new class: class method: method. last process: process. ^sons copyWith: (last primitives: hereTally)]. ^sons! !
!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 15:16'! 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: [ 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]. 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 15:18'! 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. 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. "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: 'jmv 3/4/2009 15:19'! 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]]. ].! !
!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: 'jmv 3/4/2009 13:47'! copyWithTally: hitCount ^ (MessageTally new class: class method: method) process: process; bump: hitCount! !
!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: 'spying' stamp: 'jmv 3/4/2009 15:12'! spyOn: aBlock " [1000 timesRepeat: [ 100 timesRepeat: [120 factorial]. (Delay forMilliseconds: 10) wait ]] forkAt: 45 named: '45'. MessageTally spyOn: [10000 timesRepeat: [1.23 printString]] " | node result | node _ self new. result _ node spyEvery: self defaultPollPeriod on: aBlock. (StringHolder new contents: (String streamContents: [:s | node report: s; close])) openLabel: 'Spy Results'. ^ result! !
!MessageTally class methodsFor: 'spying' stamp: 'jmv 3/4/2009 15:13'! spyOnProcess: aProcess forMilliseconds: msecDuration " | p1 p2 | p1 _ [100000 timesRepeat: [3.14159 printString. Processor yield]] fork. p2 _ [100000 timesRepeat: [3.14159 printString. Processor yield]] fork. (Delay forMilliseconds: 100) wait. MessageTally spyOnProcess: p1 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'! !
!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'!
Thanks! I've updated
http://bugs.squeak.org/view.php?id=7301
with your version.
Cheers, - Andreas
Juan Vuletich wrote:
Juan Vuletich wrote:
...
- 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. ...
Thinking better about it, now I believe using any priority but the maximum possible is wrong. The new code uses Processor preemptedProcess to know which process we are tallying. But #preemptedProcess will never answer a process with higher priority than the current. This makes sense, otherwise it would be called #previousRunningProcess or something like that.
Now, let's suppose that the tally process wakes up after a higher priority process ends or yields, after using some cpu. #preemptedProcess will answer another process, one with lower priority, and its time slice will be added to it. So it will seem as if the higher priority process never ran and the lower priority one used all the time. This is an incorrect answer.
So this is a real bug. The attached version fixes this, and a couple of other bugs, especially when tallying a single process. So, please disregard the one I sent earlier. I also added acouple of examples in the comment in class methods.
Andreas, please take a look.
Thanks, Juan Vuletich
A long time ago I suggested that you could track CPU usage, or dispatch clock time in Interpreter>>transferTo: aProc since that still is the only place a transfer switch occurs between processes. You could collect more things too, like network traffic etc. I've always thought the benefit is that you don't have some ugly high priority watcher task thundering about creating garbage and leaving foot prints in the cake...
http://lists.squeakfoundation.org/pipermail/squeak-dev/2001-March/015945.htm...
On 3-Mar-09, at 5:31 PM, Juan Vuletich wrote:
Hi Andreas,
Andreas Raab wrote:
Juan Vuletich wrote:
What you say sounds similar to what Andreas suggests to make MessageTally spy over all processes. The issue I point out is that when MessageTally builds the tree, the sender of a context might be in another process. This happens when a context forks a new process, it is still its sender. So when building the tally tree, I need to query each context for the process running it, and when it is different from the parent's one, I'll start a new tally tree.
I'm not sure how useful this is. When it comes to measuring where the time goes, the cumulative tree of multiple processes shows you if you have deficiencies in the core framework handling. This is extremely useful. And it breaks down quite nicely into separate pieces where actual different work is involved. I don't think you would get much more useful information if you were to break things apart. And I can say for sure that for the purposes of our server profiling it would be pretty much useless - under load we run literally hundreds of processes.
Cheers,
- Andreas
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.
Thanks, Juan Vuletich
-- = = = ======================================================================== John M. McIntosh johnmci@smalltalkconsulting.com Corporate Smalltalk Consulting Ltd. http://www.smalltalkconsulting.com = = = ========================================================================
On Tue, Mar 03, 2009 at 09:06:16PM -0800, John M McIntosh wrote:
A long time ago I suggested that you could track CPU usage, or dispatch clock time in Interpreter>>transferTo: aProc since that still is the only place a transfer switch occurs between processes. You could collect more things too, like network traffic etc. I've always thought the benefit is that you don't have some ugly high priority watcher task thundering about creating garbage and leaving foot prints in the cake...
http://lists.squeakfoundation.org/pipermail/squeak-dev/2001-March/015945.htm...
Wow, back then the biggest impediment to progress was your 14400 baud modem ("just try doing uploads etc at 14400 baud") !
;)
John McIntosh writes:
A long time ago I suggested that you could track CPU usage, or dispatch clock time in Interpreter>>transferTo: aProc since that still is the only place a transfer switch occurs between processes. You could collect more things too, like network traffic etc. I've always thought the benefit is that you don't have some ugly high priority watcher task thundering about creating garbage and leaving foot prints in the cake...
Yup. My imprinting stuff hooks in there, too, to decide whether to start, continue, or stop imprinting as the active process changes. (Imprinting is the transfer of a method from one memory to another, as a side-effect of running it.)
-C
-- Craig Latta www.netjam.org next show: 2009-03-13 (www.thishere.org)
squeak-dev@lists.squeakfoundation.org