Javascript Profiling: Difference between revisions
m (→Methods Provided: typos) |
No edit summary |
||
| Line 3: | Line 3: | ||
The goal of the profiling API is to allow javascript access to profiling information and enable building tools on top of it to display the information in a readable and useful fashion. It should also be flexible and generalized to support different forms of profiling and even custom profiling techniques. | The goal of the profiling API is to allow javascript access to profiling information and enable building tools on top of it to display the information in a readable and useful fashion. It should also be flexible and generalized to support different forms of profiling and even custom profiling techniques. | ||
As an idea of what should be possible to build on top of this API, see [http://unity3d.com/support/documentation/Manual/Profiler.html Unity's profiler], | |||
== The Profiler == | |||
* <tt> | There will be a global <tt>Profiler</tt> object available to instantiate to begin profiling other objects. The <tt>Profiler</tt> object has the following methods: | ||
* <tt> | |||
* <tt> | * <tt>addProfilee : context → unit</tt> - Adds a new context (is this the right abstraction?) as an object which is being profiled. All further code executions of the script will be candidates for profiling and information can be learned of from the information below. Throws an error if the context is already being profiled by this instance. | ||
* <tt> | * <tt>removeProfilee : context → bool</tt> - Removes a context from being profiled, returning whether it was actually removed or not (possibly throw an error instead?). Throws an error if the context is currently being profiled. | ||
* <tt>info : context → stack list</tt> - Fetches information about an object which is being profiled. The list returned is detailed below. Throws an error if the context provided isn't being profiled by this <tt>Profiler</tt> instance. | |||
* <tt>startProfiling : context → unit</tt> - Forces the start of profiling on the specified context. Throws an error if the context is already being profiled or if the context hasn't been added yet. This function also clears all existing profiling information. | |||
* <tt>stopProfiling : context → stack list</tt> - Forces stopping profiling on the specified context. Returns all profiling information known about the specified context. Throws an error if the context is not being profiled or if the context hasn't been added yet. | |||
== The <tt>stack</tt> object == | == The <tt>stack</tt> object == | ||
| Line 17: | Line 20: | ||
{ | { | ||
// Filtered out based on permission of script | |||
// reading this information | |||
stack: [ | stack: [ | ||
['http://example.com/foo.js', 10], | ['http://example.com/foo.js', 10], | ||
| Line 22: | Line 27: | ||
... | ... | ||
['http://example.com/bar.js', 4] | ['http://example.com/bar.js', 4] | ||
], | ], | ||
events: [ | events: [ | ||
{type: 'runtime', total: 124, pct: 0.1582}, | {type: 'runtime', total: 124, pct: 0.1582}, | ||
| Line 33: | Line 38: | ||
The <tt>stack</tt> field is the actual stack trace this represents, and the first element is the function which triggered the events, and the subsequent elements are the callers of the previous element. The <tt>events</tt> field is a list of all events related to this stack trace. Events are added internally, but custom events can also be added via <tt>profiler.event</tt>. | The <tt>stack</tt> field is the actual stack trace this represents, and the first element is the function which triggered the events, and the subsequent elements are the callers of the previous element. The <tt>events</tt> field is a list of all events related to this stack trace. Events are added internally, but custom events can also be added via <tt>profiler.event</tt>. | ||
Possibly events would also be filtered out? This is more relevant to instrumented C++ functions, I would imagine that all JS events would be public to all <tt>Profiler</tt>s | |||
== The Profilee == | |||
A script being profiled also has control over how it's being profiled. These are the methods by which the script can control profiling: | |||
* <tt>profilee.start() : unit → unit</tt> - This begins profiling the current page, setting up all necessary internal information. Throws an error if profiling has already been started. | |||
* <tt>profilee.running() : unit → bool</tt> - Tests whether the profiler is currently running, returning <tt>true</tt> if it is or <tt>false</tt> otherwise. | |||
* <tt>profilee.event(a) : object → unit</tt> - Fires a custom profiling event from Javascript. The provided object is used to identify the event. Throws an error if profiling is not currently running. | |||
* <tt>profilee.end() : unit → stack list</tt> - Finishes the profiling process, cleaning up all internal state. The returned array is a list of <tt>stack</tt> objects (described below). Throws an error if profiling is not currently running. | |||
== Use Cases == | |||
Here's some example use cases for when profiling is wanted and how they would use the API provided above. It's assumed that there's not both a <tt>Profiler</tt> interacting with a page using <tt>profilee</tt> methods because some weird results and possibly errors could be thrown. (Am I correct in assuming that this is a case that shouldn't be worried about in great detail?) | |||
=== Test Cases === | |||
Each test case desiring profiling would call <tt>profilee.start()</tt>, run for awhile, and then <tt>profilee.end()</tt>. The information would be gathered fromt he call to <tt>end</tt>. No one else is interested in the information, there's no interaction with a <tt>Profiler</tt> object. | |||
=== Extension === | |||
Opened on a page and creates its own <tt>Profiler</tt> object. It then attaches itself to the current page via <tt>addProfilee</tt> and then forces the page to start profiling with <tt>startProfiling</tt> (probably a button to be hit). Some time later <tt>stopProfiling</tt> is used to get information about the run of profiling (again probably a button being hit) and then all of the information is sorted through and displayed in some fancy fashion. | |||
The page being profiled is unaware that it's being profiled, and there's nothing that it needs to do to help it along. If it so desires, it could conditionally call <tt>profilee.event()</tt> to learn some interesting information. | |||
= Implementation = | = Implementation = | ||
| Line 45: | Line 76: | ||
== Time Profiling == | == Time Profiling == | ||
Timing information is crucial to gather, and | Timing information is crucial to gather, and also the most difficult. All other events can most likely be lumped into the category below. The two methods of getting timing information are sampling and instrumentation: | ||
=== Instrumentation === | |||
Using instrumentation it is very easy to walk the stack because corruption is not a problem, and there is existing methods to do this. There is also no problems with locks, allocations, or whatnot. Some decisions which would need to be made: | |||
=== Pros === | * Only sample every Nth function call? (technique similar to https://bugzilla.mozilla.org/show_bug.cgi?id=652535) | ||
* Worry about the stack trace? Is it useful to know that you spent X% of time in in function foo, or Y% of time in foo called from bar and Z% of time in foo called from baz? | |||
* Where is the timing information actually stored? (probably part of question above) | |||
==== Pros ==== | |||
* Easy idea, simple (no signal handling/validation business) | * Easy idea, simple (no signal handling/validation business) | ||
* Can achieve fine-tuned results | * Can achieve fine-tuned results of both timing and lots of other information if necessary. | ||
=== Cons === | ==== Cons ==== | ||
* Can bias timing data due to overhead | |||
* Starting/stopping profiling affects code generation, which can be expensive. Might not be so great for the test profiling use case described above. | |||
* More overhead than sampling (instrumentation is per-function) | * More overhead than sampling (instrumentation is per-function) | ||
* Where to store timing | * Where to store timing information could get tricky and having a very quick recorder could get fun depending on what degree of context is desired. | ||
=== | === Sampling === | ||
Sampling is possible in two flavors. One is via just a raw signal handler, and the other is as an "operation callback." The major drawback of using a raw signal handler is that the state of the interrupted world could be corrupt, and everything inspected must be heavily validated. Additionally, the current context is unknown and difficult to get, so stack traversal also gets fairly hairy. On the other hand, an operation callback looks as if it will solve these problems because it is passed the current context and is only called at a "good time." This way it won't require extra validation. | |||
* | |||
==== Pros ==== | |||
* Statistically accurate (not biased against frequently run functions) | |||
* starting and stopping profiling is as easy as turning on/off the callback. | |||
* Stack trace context is free because it has to be inspected anyway and a relatively expensive operation isn't run <i>that</i> often | |||
==== Cons ==== | |||
* Might still require some wonky code maybe? Not quite sure about this... | |||
=== Choice === | |||
Ideally a hybrid solution of both sampling and instrumentation would be used, but for a first iteration only one should be implemented. So long as an operation callback is cheap and easy to implement, then sampling looks like the way to go. | |||
== Generalized Events == | == Generalized Events == | ||
Revision as of 21:55, 31 May 2012
Profiling API
The goal of the profiling API is to allow javascript access to profiling information and enable building tools on top of it to display the information in a readable and useful fashion. It should also be flexible and generalized to support different forms of profiling and even custom profiling techniques.
As an idea of what should be possible to build on top of this API, see Unity's profiler,
The Profiler
There will be a global Profiler object available to instantiate to begin profiling other objects. The Profiler object has the following methods:
- addProfilee : context → unit - Adds a new context (is this the right abstraction?) as an object which is being profiled. All further code executions of the script will be candidates for profiling and information can be learned of from the information below. Throws an error if the context is already being profiled by this instance.
- removeProfilee : context → bool - Removes a context from being profiled, returning whether it was actually removed or not (possibly throw an error instead?). Throws an error if the context is currently being profiled.
- info : context → stack list - Fetches information about an object which is being profiled. The list returned is detailed below. Throws an error if the context provided isn't being profiled by this Profiler instance.
- startProfiling : context → unit - Forces the start of profiling on the specified context. Throws an error if the context is already being profiled or if the context hasn't been added yet. This function also clears all existing profiling information.
- stopProfiling : context → stack list - Forces stopping profiling on the specified context. Returns all profiling information known about the specified context. Throws an error if the context is not being profiled or if the context hasn't been added yet.
The stack object
A stack object represents an event at a particular stack trace. A stack trace is defined by an ordered list of <url, line> pairs where the url is the source file of the function and line is the line number within the source file. The object would look similarly as follows:
{
// Filtered out based on permission of script
// reading this information
stack: [
['http://example.com/foo.js', 10],
['http://example.com/foo.js', 23],
...
['http://example.com/bar.js', 4]
],
events: [
{type: 'runtime', total: 124, pct: 0.1582},
{type: 'malloc', times: 4, average: 9, total: 35},
{type: 'recompile', times: 4},
{type: 'custom-foo', ...},
...
]
}
The stack field is the actual stack trace this represents, and the first element is the function which triggered the events, and the subsequent elements are the callers of the previous element. The events field is a list of all events related to this stack trace. Events are added internally, but custom events can also be added via profiler.event.
Possibly events would also be filtered out? This is more relevant to instrumented C++ functions, I would imagine that all JS events would be public to all Profilers
The Profilee
A script being profiled also has control over how it's being profiled. These are the methods by which the script can control profiling:
- profilee.start() : unit → unit - This begins profiling the current page, setting up all necessary internal information. Throws an error if profiling has already been started.
- profilee.running() : unit → bool - Tests whether the profiler is currently running, returning true if it is or false otherwise.
- profilee.event(a) : object → unit - Fires a custom profiling event from Javascript. The provided object is used to identify the event. Throws an error if profiling is not currently running.
- profilee.end() : unit → stack list - Finishes the profiling process, cleaning up all internal state. The returned array is a list of stack objects (described below). Throws an error if profiling is not currently running.
Use Cases
Here's some example use cases for when profiling is wanted and how they would use the API provided above. It's assumed that there's not both a Profiler interacting with a page using profilee methods because some weird results and possibly errors could be thrown. (Am I correct in assuming that this is a case that shouldn't be worried about in great detail?)
Test Cases
Each test case desiring profiling would call profilee.start(), run for awhile, and then profilee.end(). The information would be gathered fromt he call to end. No one else is interested in the information, there's no interaction with a Profiler object.
Extension
Opened on a page and creates its own Profiler object. It then attaches itself to the current page via addProfilee and then forces the page to start profiling with startProfiling (probably a button to be hit). Some time later stopProfiling is used to get information about the run of profiling (again probably a button being hit) and then all of the information is sorted through and displayed in some fancy fashion.
The page being profiled is unaware that it's being profiled, and there's nothing that it needs to do to help it along. If it so desires, it could conditionally call profilee.event() to learn some interesting information.
Implementation
The goals of the implementation of this profiling API are:
- Works on all platforms
- Works with IonMonkey, JaegerMonkey, and the interpreter
- Virtually no overhead when profiling is turned off
- Very little overhead when profiling is turned on
Time Profiling
Timing information is crucial to gather, and also the most difficult. All other events can most likely be lumped into the category below. The two methods of getting timing information are sampling and instrumentation:
Instrumentation
Using instrumentation it is very easy to walk the stack because corruption is not a problem, and there is existing methods to do this. There is also no problems with locks, allocations, or whatnot. Some decisions which would need to be made:
- Only sample every Nth function call? (technique similar to https://bugzilla.mozilla.org/show_bug.cgi?id=652535)
- Worry about the stack trace? Is it useful to know that you spent X% of time in in function foo, or Y% of time in foo called from bar and Z% of time in foo called from baz?
- Where is the timing information actually stored? (probably part of question above)
Pros
- Easy idea, simple (no signal handling/validation business)
- Can achieve fine-tuned results of both timing and lots of other information if necessary.
Cons
- Can bias timing data due to overhead
- Starting/stopping profiling affects code generation, which can be expensive. Might not be so great for the test profiling use case described above.
- More overhead than sampling (instrumentation is per-function)
- Where to store timing information could get tricky and having a very quick recorder could get fun depending on what degree of context is desired.
Sampling
Sampling is possible in two flavors. One is via just a raw signal handler, and the other is as an "operation callback." The major drawback of using a raw signal handler is that the state of the interrupted world could be corrupt, and everything inspected must be heavily validated. Additionally, the current context is unknown and difficult to get, so stack traversal also gets fairly hairy. On the other hand, an operation callback looks as if it will solve these problems because it is passed the current context and is only called at a "good time." This way it won't require extra validation.
Pros
- Statistically accurate (not biased against frequently run functions)
- starting and stopping profiling is as easy as turning on/off the callback.
- Stack trace context is free because it has to be inspected anyway and a relatively expensive operation isn't run that often
Cons
- Might still require some wonky code maybe? Not quite sure about this...
Choice
Ideally a hybrid solution of both sampling and instrumentation would be used, but for a first iteration only one should be implemented. So long as an operation callback is cheap and easy to implement, then sampling looks like the way to go.
Generalized Events
There will be a global "stack list" which keeps track of all stack traces which have had events on them. When an event fires, the current stack is looked up in this list, and if found, the event is added to the stack. If not found, the stack trace is serialized and added to the list.
There will be a way to instrument events from C++ as well to track things like GC triggers, GC allocations, type inference failures, etc.