Javascript Profiling

From MozillaWiki
Revision as of 20:44, 31 May 2012 by Acrichton (talk | contribs)
Jump to navigation Jump to search

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.

Methods Provided

Here's a rough idea of what the API would be like from the javascript side of things:

  • proflier.start() : unit → unit - This beings profiling the current page, setting up all necessary internal information. Throws an error if profiling has already been started.
  • profiler.running() : unit → bool - Tests whether the profiler is currently running, returning true if it is or false otherwise.
  • proflier.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.
  • proflier.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.

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:

{                                                      
  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.

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 currently it seems best to do instrumentation-based profiling as opposed to sampling-based profiling. Using instrumentation makes it far easier to walk the stack because corruption is not a problem, and there is existing methods to do this. Sampling, on the other hand, has to worry about racing with looking at in-flux data structures on the stack and do a fair amount of validation. Sampling, as it's called from a signal handler, also must be careful to touch data structures or grab locks.

Currently, this is a draft version, so the idea is to get everything working and an infrastructure in place for this API, and it instrumentation looks like it's incurring far too much overhead, then sampling could be investigated in much more depth.

The exact details of where the timing information is stored needs to be worked out, and it must be a very small overhead.

Pros

  • Easy idea, simple (no signal handling/validation business)
  • Can achieve fine-tuned results
  • Don't have to worry about cross-platform issues

Cons

  • More overhead than sampling (instrumentation is per-function)
  • Where to store timing informatino could get tricky

TODO

  • 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)
  • Is this general enough for use with error stack traces? (as suggested by jimb)

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.