Path: blob/trunk/third_party/closure/goog/debug/tracer.js
2868 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Definition of the Tracer class and associated classes.16*17* @see ../demos/tracer.html18*/1920goog.provide('goog.debug.Trace');2122goog.require('goog.array');23goog.require('goog.debug.Logger');24goog.require('goog.iter');25goog.require('goog.log');26goog.require('goog.structs.Map');27goog.require('goog.structs.SimplePool');28293031/**32* Class used for singleton goog.debug.Trace. Used for timing slow points in33* the code. Based on the java Tracer class but optimized for javascript.34* See com.google.common.tracing.Tracer.35* @constructor36* @private37*/38goog.debug.Trace_ = function() {3940/**41* Events in order.42* @type {Array<goog.debug.Trace_.Event_>}43* @private44*/45this.events_ = [];4647/**48* Outstanding events that have started but haven't yet ended. The keys are49* numeric ids and the values are goog.debug.Trace_.Event_ objects.50* @type {goog.structs.Map}51* @private52*/53this.outstandingEvents_ = new goog.structs.Map();5455/**56* Start time of the event trace57* @type {number}58* @private59*/60this.startTime_ = 0;6162/**63* Cummulative overhead of calls to startTracer64* @type {number}65* @private66*/67this.tracerOverheadStart_ = 0;6869/**70* Cummulative overhead of calls to endTracer71* @type {number}72* @private73*/74this.tracerOverheadEnd_ = 0;7576/**77* Cummulative overhead of calls to addComment78* @type {number}79* @private80*/81this.tracerOverheadComment_ = 0;8283/**84* Keeps stats on different types of tracers. The keys are strings and the85* values are goog.debug.Stat86* @type {goog.structs.Map}87* @private88*/89this.stats_ = new goog.structs.Map();9091/**92* Total number of traces created in the trace.93* @type {number}94* @private95*/96this.tracerCount_ = 0;9798/**99* Total number of comments created in the trace.100* @type {number}101* @private102*/103this.commentCount_ = 0;104105/**106* Next id to use for the trace.107* @type {number}108* @private109*/110this.nextId_ = 1;111112/**113* A pool for goog.debug.Trace_.Event_ objects so we don't keep creating and114* garbage collecting these (which is very expensive in IE6).115* @private {!goog.structs.SimplePool}116*/117this.eventPool_ = new goog.structs.SimplePool(0, 4000);118this.eventPool_.createObject = function() {119return new goog.debug.Trace_.Event_();120};121122123/**124* A pool for goog.debug.Trace_.Stat_ objects so we don't keep creating and125* garbage collecting these (which is very expensive in IE6).126* @private {!goog.structs.SimplePool}127*/128this.statPool_ = new goog.structs.SimplePool(0, 50);129this.statPool_.createObject = function() {130return new goog.debug.Trace_.Stat_();131};132133var self = this;134135/** @private {!goog.structs.SimplePool} */136this.idPool_ = new goog.structs.SimplePool(0, 2000);137138// TODO(nicksantos): SimplePool is supposed to only return objects.139// Reconcile this so that we don't have to cast to number below.140this.idPool_.createObject = function() { return String(self.nextId_++); };141this.idPool_.disposeObject = function(obj) {};142143/**144* Default threshold below which a tracer shouldn't be reported145* @type {number}146* @private147*/148this.defaultThreshold_ = 3;149};150151152/**153* Logger for the tracer154* @type {goog.log.Logger}155* @private156*/157goog.debug.Trace_.prototype.logger_ = goog.log.getLogger('goog.debug.Trace');158159160/**161* Maximum size of the trace before we discard events162* @type {number}163*/164goog.debug.Trace_.prototype.MAX_TRACE_SIZE = 1000;165166167/**168* Event type supported by tracer169* @enum {number}170*/171goog.debug.Trace_.EventType = {172/**173* Start event type174*/175START: 0,176177/**178* Stop event type179*/180STOP: 1,181182/**183* Comment event type184*/185COMMENT: 2186};187188189190/**191* Class to keep track of a stat of a single tracer type. Stores the count192* and cumulative time.193* @constructor194* @private195*/196goog.debug.Trace_.Stat_ = function() {197/**198* Number of tracers199* @type {number}200*/201this.count = 0;202203/**204* Cumulative time of traces205* @type {number}206*/207this.time = 0;208209/**210* Total number of allocations for this tracer type211* @type {number}212*/213this.varAlloc = 0;214};215216217/**218* @type {string|null|undefined}219*/220goog.debug.Trace_.Stat_.prototype.type;221222223/**224* @return {string} A string describing the tracer stat.225* @override226*/227goog.debug.Trace_.Stat_.prototype.toString = function() {228var sb = [];229sb.push(230this.type, ' ', this.count, ' (', Math.round(this.time * 10) / 10,231' ms)');232if (this.varAlloc) {233sb.push(' [VarAlloc = ', this.varAlloc, ']');234}235return sb.join('');236};237238239240/**241* Private class used to encapsulate a single event, either the start or stop242* of a tracer.243* @constructor244* @private245*/246goog.debug.Trace_.Event_ = function() {247// the fields are different for different events - see usage in code248};249250251/**252* @type {string|null|undefined}253*/254goog.debug.Trace_.Event_.prototype.type;255256257/**258* Returns a formatted string for the event.259* @param {number} startTime The start time of the trace to generate relative260* times.261* @param {number} prevTime The completion time of the previous event or -1.262* @param {string} indent Extra indent for the message263* if there was no previous event.264* @return {string} The formatted tracer string.265*/266goog.debug.Trace_.Event_.prototype.toTraceString = function(267startTime, prevTime, indent) {268var sb = [];269270if (prevTime == -1) {271sb.push(' ');272} else {273sb.push(goog.debug.Trace_.longToPaddedString_(this.eventTime - prevTime));274}275276sb.push(' ', goog.debug.Trace_.formatTime_(this.eventTime - startTime));277if (this.eventType == goog.debug.Trace_.EventType.START) {278sb.push(' Start ');279} else if (this.eventType == goog.debug.Trace_.EventType.STOP) {280sb.push(' Done ');281var delta = this.stopTime - this.startTime;282sb.push(goog.debug.Trace_.longToPaddedString_(delta), ' ms ');283} else {284sb.push(' Comment ');285}286287sb.push(indent, this);288if (this.totalVarAlloc > 0) {289sb.push('[VarAlloc ', this.totalVarAlloc, '] ');290}291return sb.join('');292};293294295/**296* @return {string} A string describing the tracer event.297* @override298*/299goog.debug.Trace_.Event_.prototype.toString = function() {300if (this.type == null) {301return this.comment;302} else {303return '[' + this.type + '] ' + this.comment;304}305};306307308/**309* Add the ability to explicitly set the start time. This is useful for example310* for measuring initial load time where you can set a variable as soon as the311* main page of the app is loaded and then later call this function when the312* Tracer code has been loaded.313* @param {number} startTime The start time to set.314*/315goog.debug.Trace_.prototype.setStartTime = function(startTime) {316this.startTime_ = startTime;317};318319320/**321* Initializes and resets the current trace322* @param {number} defaultThreshold The default threshold below which the323* tracer output will be suppressed. Can be overridden on a per-Tracer basis.324*/325goog.debug.Trace_.prototype.initCurrentTrace = function(defaultThreshold) {326this.reset(defaultThreshold);327};328329330/**331* Clears the current trace332*/333goog.debug.Trace_.prototype.clearCurrentTrace = function() {334this.reset(0);335};336337338/**339* Resets the trace.340* @param {number} defaultThreshold The default threshold below which the341* tracer output will be suppressed. Can be overridden on a per-Tracer basis.342*/343goog.debug.Trace_.prototype.reset = function(defaultThreshold) {344this.defaultThreshold_ = defaultThreshold;345346this.releaseEvents_();347this.outstandingEvents_.clear();348this.startTime_ = goog.debug.Trace_.now();349this.tracerOverheadStart_ = 0;350this.tracerOverheadEnd_ = 0;351this.tracerOverheadComment_ = 0;352this.tracerCount_ = 0;353this.commentCount_ = 0;354355var keys = this.stats_.getKeys();356for (var i = 0; i < keys.length; i++) {357var key = keys[i];358var stat = this.stats_.get(key);359stat.count = 0;360stat.time = 0;361stat.varAlloc = 0;362this.statPool_.releaseObject(/** @type {Object} */ (stat));363}364this.stats_.clear();365};366367368/**369* @private370*/371goog.debug.Trace_.prototype.releaseEvents_ = function() {372for (var i = 0; i < this.events_.length; i++) {373var event = this.events_[i];374if (event.id) {375this.idPool_.releaseObject(event.id);376}377this.eventPool_.releaseObject(event);378}379this.events_.length = 0;380};381382383/**384* Starts a tracer385* @param {string} comment A comment used to identify the tracer. Does not386* need to be unique.387* @param {string=} opt_type Type used to identify the tracer. If a Trace is388* given a type (the first argument to the constructor) and multiple Traces389* are done on that type then a "TOTAL line will be produced showing the390* total number of traces and the sum of the time391* ("TOTAL Database 2 (37 ms)" in our example). These traces should be392* mutually exclusive or else the sum won't make sense (the time will393* be double counted if the second starts before the first ends).394* @return {number} The identifier for the tracer that should be passed to the395* the stopTracer method.396*/397goog.debug.Trace_.prototype.startTracer = function(comment, opt_type) {398var tracerStartTime = goog.debug.Trace_.now();399var varAlloc = this.getTotalVarAlloc();400var outstandingEventCount = this.outstandingEvents_.getCount();401if (this.events_.length + outstandingEventCount > this.MAX_TRACE_SIZE) {402goog.log.warning(403this.logger_, 'Giant thread trace. Clearing to avoid memory leak.');404// This is the more likely case. This usually means that we405// either forgot to clear the trace or else we are performing a406// very large number of events407if (this.events_.length > this.MAX_TRACE_SIZE / 2) {408this.releaseEvents_();409}410411// This is less likely and probably indicates that a lot of traces412// aren't being closed. We want to avoid unnecessarily clearing413// this though in case the events do eventually finish.414if (outstandingEventCount > this.MAX_TRACE_SIZE / 2) {415this.outstandingEvents_.clear();416}417}418419goog.debug.Logger.logToProfilers('Start : ' + comment);420421/** @const */422var event =423/** @type {!goog.debug.Trace_.Event_} */ (this.eventPool_.getObject());424event.totalVarAlloc = varAlloc;425event.eventType = goog.debug.Trace_.EventType.START;426event.id = Number(this.idPool_.getObject());427event.comment = comment;428event.type = opt_type;429this.events_.push(event);430this.outstandingEvents_.set(String(event.id), event);431this.tracerCount_++;432var now = goog.debug.Trace_.now();433event.startTime = event.eventTime = now;434this.tracerOverheadStart_ += now - tracerStartTime;435return event.id;436};437438439/**440* Stops a tracer441* @param {number|undefined|null} id The id of the tracer that is ending.442* @param {number=} opt_silenceThreshold Threshold below which the tracer is443* silenced.444* @return {?number} The elapsed time for the tracer or null if the tracer445* identitifer was not recognized.446*/447goog.debug.Trace_.prototype.stopTracer = function(id, opt_silenceThreshold) {448// this used to call goog.isDef(opt_silenceThreshold) but that causes an449// object allocation in IE for some reason (doh!). The following code doesn't450// cause an allocation451var now = goog.debug.Trace_.now();452var silenceThreshold;453if (opt_silenceThreshold === 0) {454silenceThreshold = 0;455} else if (opt_silenceThreshold) {456silenceThreshold = opt_silenceThreshold;457} else {458silenceThreshold = this.defaultThreshold_;459}460461var startEvent = this.outstandingEvents_.get(String(id));462if (startEvent == null) {463return null;464}465466this.outstandingEvents_.remove(String(id));467468var stopEvent;469var elapsed = now - startEvent.startTime;470if (elapsed < silenceThreshold) {471var count = this.events_.length;472for (var i = count - 1; i >= 0; i--) {473var nextEvent = this.events_[i];474if (nextEvent == startEvent) {475this.events_.splice(i, 1);476this.idPool_.releaseObject(startEvent.id);477this.eventPool_.releaseObject(/** @type {Object} */ (startEvent));478break;479}480}481482} else {483stopEvent =484/** @type {goog.debug.Trace_.Event_} */ (this.eventPool_.getObject());485stopEvent.eventType = goog.debug.Trace_.EventType.STOP;486stopEvent.startTime = startEvent.startTime;487stopEvent.comment = startEvent.comment;488stopEvent.type = startEvent.type;489stopEvent.stopTime = stopEvent.eventTime = now;490491this.events_.push(stopEvent);492}493494var type = startEvent.type;495var stat = null;496if (type) {497stat = this.getStat_(type);498stat.count++;499stat.time += elapsed;500}501if (stopEvent) {502goog.debug.Logger.logToProfilers('Stop : ' + stopEvent.comment);503504stopEvent.totalVarAlloc = this.getTotalVarAlloc();505506if (stat) {507stat.varAlloc += (stopEvent.totalVarAlloc - startEvent.totalVarAlloc);508}509}510var tracerFinishTime = goog.debug.Trace_.now();511this.tracerOverheadEnd_ += tracerFinishTime - now;512return elapsed;513};514515516/**517* Sets the ActiveX object that can be used to get GC tracing in IE6.518* @param {Object} gcTracer GCTracer ActiveX object.519*/520goog.debug.Trace_.prototype.setGcTracer = function(gcTracer) {521this.gcTracer_ = gcTracer;522};523524525/**526* Returns the total number of allocations since the GC stats were reset. Only527* works in IE.528* @return {number} The number of allocaitons or -1 if not supported.529*/530goog.debug.Trace_.prototype.getTotalVarAlloc = function() {531var gcTracer = this.gcTracer_;532// isTracing is defined on the ActiveX object.533if (gcTracer && gcTracer['isTracing']()) {534return gcTracer['totalVarAlloc'];535}536return -1;537};538539540/**541* Adds a comment to the trace. Makes it possible to see when a specific event542* happened in relation to the traces.543* @param {string} comment A comment that is inserted into the trace.544* @param {?string=} opt_type Type used to identify the tracer. If a comment is545* given a type and multiple comments are done on that type then a "TOTAL546* line will be produced showing the total number of comments of that type.547* @param {?number=} opt_timeStamp The timestamp to insert the comment. If not548* specified, the current time wil be used.549*/550goog.debug.Trace_.prototype.addComment = function(551comment, opt_type, opt_timeStamp) {552var now = goog.debug.Trace_.now();553var timeStamp = opt_timeStamp ? opt_timeStamp : now;554555var eventComment =556/** @type {goog.debug.Trace_.Event_} */ (this.eventPool_.getObject());557eventComment.eventType = goog.debug.Trace_.EventType.COMMENT;558eventComment.eventTime = timeStamp;559eventComment.type = opt_type;560eventComment.comment = comment;561eventComment.totalVarAlloc = this.getTotalVarAlloc();562this.commentCount_++;563564if (opt_timeStamp) {565var numEvents = this.events_.length;566for (var i = 0; i < numEvents; i++) {567var event = this.events_[i];568var eventTime = event.eventTime;569570if (eventTime > timeStamp) {571goog.array.insertAt(this.events_, eventComment, i);572break;573}574}575if (i == numEvents) {576this.events_.push(eventComment);577}578} else {579this.events_.push(eventComment);580}581582var type = eventComment.type;583if (type) {584var stat = this.getStat_(type);585stat.count++;586}587588this.tracerOverheadComment_ += goog.debug.Trace_.now() - now;589};590591592/**593* Gets a stat object for a particular type. The stat object is created if it594* hasn't yet been.595* @param {string} type The type of stat.596* @return {goog.debug.Trace_.Stat_} The stat object.597* @private598*/599goog.debug.Trace_.prototype.getStat_ = function(type) {600var stat = this.stats_.get(type);601if (!stat) {602stat = /** @type {goog.debug.Trace_.Event_} */ (this.statPool_.getObject());603stat.type = type;604this.stats_.set(type, stat);605}606return /** @type {goog.debug.Trace_.Stat_} */ (stat);607};608609610/**611* Returns a formatted string for the current trace612* @return {string} A formatted string that shows the timings of the current613* trace.614*/615goog.debug.Trace_.prototype.getFormattedTrace = function() {616return this.toString();617};618619620/**621* Returns a formatted string that describes the thread trace.622* @return {string} A formatted string.623* @override624*/625goog.debug.Trace_.prototype.toString = function() {626var sb = [];627var etime = -1;628var indent = [];629for (var i = 0; i < this.events_.length; i++) {630var e = this.events_[i];631if (e.eventType == goog.debug.Trace_.EventType.STOP) {632indent.pop();633}634sb.push(' ', e.toTraceString(this.startTime_, etime, indent.join('')));635etime = e.eventTime;636sb.push('\n');637if (e.eventType == goog.debug.Trace_.EventType.START) {638indent.push('| ');639}640}641642if (this.outstandingEvents_.getCount() != 0) {643var now = goog.debug.Trace_.now();644645sb.push(' Unstopped timers:\n');646goog.iter.forEach(this.outstandingEvents_, function(startEvent) {647sb.push(648' ', startEvent, ' (', now - startEvent.startTime,649' ms, started at ',650goog.debug.Trace_.formatTime_(startEvent.startTime), ')\n');651});652}653654var statKeys = this.stats_.getKeys();655for (var i = 0; i < statKeys.length; i++) {656var stat = this.stats_.get(statKeys[i]);657if (stat.count > 1) {658sb.push(' TOTAL ', stat, '\n');659}660}661662sb.push(663'Total tracers created ', this.tracerCount_, '\n',664'Total comments created ', this.commentCount_, '\n', 'Overhead start: ',665this.tracerOverheadStart_, ' ms\n', 'Overhead end: ',666this.tracerOverheadEnd_, ' ms\n', 'Overhead comment: ',667this.tracerOverheadComment_, ' ms\n');668669return sb.join('');670};671672673/**674* Converts 'v' to a string and pads it with up to 3 spaces for675* improved alignment. TODO there must be a better way676* @param {number} v A number.677* @return {string} A padded string.678* @private679*/680goog.debug.Trace_.longToPaddedString_ = function(v) {681v = Math.round(v);682// todo (pupius) - there should be a generic string in goog.string for this683var space = '';684if (v < 1000) space = ' ';685if (v < 100) space = ' ';686if (v < 10) space = ' ';687return space + v;688};689690691/**692* Return the sec.ms part of time (if time = "20:06:11.566", "11.566693* @param {number} time The time in MS.694* @return {string} A formatted string as sec.ms'.695* @private696*/697goog.debug.Trace_.formatTime_ = function(time) {698time = Math.round(time);699var sec = (time / 1000) % 60;700var ms = time % 1000;701702// TODO their must be a nicer way to get zero padded integers703return String(100 + sec).substring(1, 3) + '.' +704String(1000 + ms).substring(1, 4);705};706707708/**709* Returns the current time. Done through a wrapper function so it can be710* overridden by application code. Gmail has an ActiveX extension that provides711* higher precision timing info.712* @return {number} The current time in milliseconds.713*/714goog.debug.Trace_.now = function() {715return goog.now();716};717718719/**720* Singleton trace object721* @type {goog.debug.Trace_}722*/723goog.debug.Trace = new goog.debug.Trace_();724725726