123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724 |
- // Copyright 2006 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview Definition of the Tracer class and associated classes.
- *
- * @see ../demos/tracer.html
- */
- goog.provide('goog.debug.Trace');
- goog.require('goog.array');
- goog.require('goog.debug.Logger');
- goog.require('goog.iter');
- goog.require('goog.log');
- goog.require('goog.structs.Map');
- goog.require('goog.structs.SimplePool');
- /**
- * Class used for singleton goog.debug.Trace. Used for timing slow points in
- * the code. Based on the java Tracer class but optimized for javascript.
- * See com.google.common.tracing.Tracer.
- * @constructor
- * @private
- */
- goog.debug.Trace_ = function() {
- /**
- * Events in order.
- * @type {Array<goog.debug.Trace_.Event_>}
- * @private
- */
- this.events_ = [];
- /**
- * Outstanding events that have started but haven't yet ended. The keys are
- * numeric ids and the values are goog.debug.Trace_.Event_ objects.
- * @type {goog.structs.Map}
- * @private
- */
- this.outstandingEvents_ = new goog.structs.Map();
- /**
- * Start time of the event trace
- * @type {number}
- * @private
- */
- this.startTime_ = 0;
- /**
- * Cummulative overhead of calls to startTracer
- * @type {number}
- * @private
- */
- this.tracerOverheadStart_ = 0;
- /**
- * Cummulative overhead of calls to endTracer
- * @type {number}
- * @private
- */
- this.tracerOverheadEnd_ = 0;
- /**
- * Cummulative overhead of calls to addComment
- * @type {number}
- * @private
- */
- this.tracerOverheadComment_ = 0;
- /**
- * Keeps stats on different types of tracers. The keys are strings and the
- * values are goog.debug.Stat
- * @type {goog.structs.Map}
- * @private
- */
- this.stats_ = new goog.structs.Map();
- /**
- * Total number of traces created in the trace.
- * @type {number}
- * @private
- */
- this.tracerCount_ = 0;
- /**
- * Total number of comments created in the trace.
- * @type {number}
- * @private
- */
- this.commentCount_ = 0;
- /**
- * Next id to use for the trace.
- * @type {number}
- * @private
- */
- this.nextId_ = 1;
- /**
- * A pool for goog.debug.Trace_.Event_ objects so we don't keep creating and
- * garbage collecting these (which is very expensive in IE6).
- * @private {!goog.structs.SimplePool}
- */
- this.eventPool_ = new goog.structs.SimplePool(0, 4000);
- this.eventPool_.createObject = function() {
- return new goog.debug.Trace_.Event_();
- };
- /**
- * A pool for goog.debug.Trace_.Stat_ objects so we don't keep creating and
- * garbage collecting these (which is very expensive in IE6).
- * @private {!goog.structs.SimplePool}
- */
- this.statPool_ = new goog.structs.SimplePool(0, 50);
- this.statPool_.createObject = function() {
- return new goog.debug.Trace_.Stat_();
- };
- var self = this;
- /** @private {!goog.structs.SimplePool} */
- this.idPool_ = new goog.structs.SimplePool(0, 2000);
- // TODO(nicksantos): SimplePool is supposed to only return objects.
- // Reconcile this so that we don't have to cast to number below.
- this.idPool_.createObject = function() { return String(self.nextId_++); };
- this.idPool_.disposeObject = function(obj) {};
- /**
- * Default threshold below which a tracer shouldn't be reported
- * @type {number}
- * @private
- */
- this.defaultThreshold_ = 3;
- };
- /**
- * Logger for the tracer
- * @type {goog.log.Logger}
- * @private
- */
- goog.debug.Trace_.prototype.logger_ = goog.log.getLogger('goog.debug.Trace');
- /**
- * Maximum size of the trace before we discard events
- * @type {number}
- */
- goog.debug.Trace_.prototype.MAX_TRACE_SIZE = 1000;
- /**
- * Event type supported by tracer
- * @enum {number}
- */
- goog.debug.Trace_.EventType = {
- /**
- * Start event type
- */
- START: 0,
- /**
- * Stop event type
- */
- STOP: 1,
- /**
- * Comment event type
- */
- COMMENT: 2
- };
- /**
- * Class to keep track of a stat of a single tracer type. Stores the count
- * and cumulative time.
- * @constructor
- * @private
- */
- goog.debug.Trace_.Stat_ = function() {
- /**
- * Number of tracers
- * @type {number}
- */
- this.count = 0;
- /**
- * Cumulative time of traces
- * @type {number}
- */
- this.time = 0;
- /**
- * Total number of allocations for this tracer type
- * @type {number}
- */
- this.varAlloc = 0;
- };
- /**
- * @type {string|null|undefined}
- */
- goog.debug.Trace_.Stat_.prototype.type;
- /**
- * @return {string} A string describing the tracer stat.
- * @override
- */
- goog.debug.Trace_.Stat_.prototype.toString = function() {
- var sb = [];
- sb.push(
- this.type, ' ', this.count, ' (', Math.round(this.time * 10) / 10,
- ' ms)');
- if (this.varAlloc) {
- sb.push(' [VarAlloc = ', this.varAlloc, ']');
- }
- return sb.join('');
- };
- /**
- * Private class used to encapsulate a single event, either the start or stop
- * of a tracer.
- * @constructor
- * @private
- */
- goog.debug.Trace_.Event_ = function() {
- // the fields are different for different events - see usage in code
- };
- /**
- * @type {string|null|undefined}
- */
- goog.debug.Trace_.Event_.prototype.type;
- /**
- * Returns a formatted string for the event.
- * @param {number} startTime The start time of the trace to generate relative
- * times.
- * @param {number} prevTime The completion time of the previous event or -1.
- * @param {string} indent Extra indent for the message
- * if there was no previous event.
- * @return {string} The formatted tracer string.
- */
- goog.debug.Trace_.Event_.prototype.toTraceString = function(
- startTime, prevTime, indent) {
- var sb = [];
- if (prevTime == -1) {
- sb.push(' ');
- } else {
- sb.push(goog.debug.Trace_.longToPaddedString_(this.eventTime - prevTime));
- }
- sb.push(' ', goog.debug.Trace_.formatTime_(this.eventTime - startTime));
- if (this.eventType == goog.debug.Trace_.EventType.START) {
- sb.push(' Start ');
- } else if (this.eventType == goog.debug.Trace_.EventType.STOP) {
- sb.push(' Done ');
- var delta = this.stopTime - this.startTime;
- sb.push(goog.debug.Trace_.longToPaddedString_(delta), ' ms ');
- } else {
- sb.push(' Comment ');
- }
- sb.push(indent, this);
- if (this.totalVarAlloc > 0) {
- sb.push('[VarAlloc ', this.totalVarAlloc, '] ');
- }
- return sb.join('');
- };
- /**
- * @return {string} A string describing the tracer event.
- * @override
- */
- goog.debug.Trace_.Event_.prototype.toString = function() {
- if (this.type == null) {
- return this.comment;
- } else {
- return '[' + this.type + '] ' + this.comment;
- }
- };
- /**
- * Add the ability to explicitly set the start time. This is useful for example
- * for measuring initial load time where you can set a variable as soon as the
- * main page of the app is loaded and then later call this function when the
- * Tracer code has been loaded.
- * @param {number} startTime The start time to set.
- */
- goog.debug.Trace_.prototype.setStartTime = function(startTime) {
- this.startTime_ = startTime;
- };
- /**
- * Initializes and resets the current trace
- * @param {number} defaultThreshold The default threshold below which the
- * tracer output will be suppressed. Can be overridden on a per-Tracer basis.
- */
- goog.debug.Trace_.prototype.initCurrentTrace = function(defaultThreshold) {
- this.reset(defaultThreshold);
- };
- /**
- * Clears the current trace
- */
- goog.debug.Trace_.prototype.clearCurrentTrace = function() {
- this.reset(0);
- };
- /**
- * Resets the trace.
- * @param {number} defaultThreshold The default threshold below which the
- * tracer output will be suppressed. Can be overridden on a per-Tracer basis.
- */
- goog.debug.Trace_.prototype.reset = function(defaultThreshold) {
- this.defaultThreshold_ = defaultThreshold;
- this.releaseEvents_();
- this.outstandingEvents_.clear();
- this.startTime_ = goog.debug.Trace_.now();
- this.tracerOverheadStart_ = 0;
- this.tracerOverheadEnd_ = 0;
- this.tracerOverheadComment_ = 0;
- this.tracerCount_ = 0;
- this.commentCount_ = 0;
- var keys = this.stats_.getKeys();
- for (var i = 0; i < keys.length; i++) {
- var key = keys[i];
- var stat = this.stats_.get(key);
- stat.count = 0;
- stat.time = 0;
- stat.varAlloc = 0;
- this.statPool_.releaseObject(/** @type {Object} */ (stat));
- }
- this.stats_.clear();
- };
- /**
- * @private
- */
- goog.debug.Trace_.prototype.releaseEvents_ = function() {
- for (var i = 0; i < this.events_.length; i++) {
- var event = this.events_[i];
- if (event.id) {
- this.idPool_.releaseObject(event.id);
- }
- this.eventPool_.releaseObject(event);
- }
- this.events_.length = 0;
- };
- /**
- * Starts a tracer
- * @param {string} comment A comment used to identify the tracer. Does not
- * need to be unique.
- * @param {string=} opt_type Type used to identify the tracer. If a Trace is
- * given a type (the first argument to the constructor) and multiple Traces
- * are done on that type then a "TOTAL line will be produced showing the
- * total number of traces and the sum of the time
- * ("TOTAL Database 2 (37 ms)" in our example). These traces should be
- * mutually exclusive or else the sum won't make sense (the time will
- * be double counted if the second starts before the first ends).
- * @return {number} The identifier for the tracer that should be passed to the
- * the stopTracer method.
- */
- goog.debug.Trace_.prototype.startTracer = function(comment, opt_type) {
- var tracerStartTime = goog.debug.Trace_.now();
- var varAlloc = this.getTotalVarAlloc();
- var outstandingEventCount = this.outstandingEvents_.getCount();
- if (this.events_.length + outstandingEventCount > this.MAX_TRACE_SIZE) {
- goog.log.warning(
- this.logger_, 'Giant thread trace. Clearing to avoid memory leak.');
- // This is the more likely case. This usually means that we
- // either forgot to clear the trace or else we are performing a
- // very large number of events
- if (this.events_.length > this.MAX_TRACE_SIZE / 2) {
- this.releaseEvents_();
- }
- // This is less likely and probably indicates that a lot of traces
- // aren't being closed. We want to avoid unnecessarily clearing
- // this though in case the events do eventually finish.
- if (outstandingEventCount > this.MAX_TRACE_SIZE / 2) {
- this.outstandingEvents_.clear();
- }
- }
- goog.debug.Logger.logToProfilers('Start : ' + comment);
- /** @const */
- var event =
- /** @type {!goog.debug.Trace_.Event_} */ (this.eventPool_.getObject());
- event.totalVarAlloc = varAlloc;
- event.eventType = goog.debug.Trace_.EventType.START;
- event.id = Number(this.idPool_.getObject());
- event.comment = comment;
- event.type = opt_type;
- this.events_.push(event);
- this.outstandingEvents_.set(String(event.id), event);
- this.tracerCount_++;
- var now = goog.debug.Trace_.now();
- event.startTime = event.eventTime = now;
- this.tracerOverheadStart_ += now - tracerStartTime;
- return event.id;
- };
- /**
- * Stops a tracer
- * @param {number|undefined|null} id The id of the tracer that is ending.
- * @param {number=} opt_silenceThreshold Threshold below which the tracer is
- * silenced.
- * @return {?number} The elapsed time for the tracer or null if the tracer
- * identitifer was not recognized.
- */
- goog.debug.Trace_.prototype.stopTracer = function(id, opt_silenceThreshold) {
- // this used to call goog.isDef(opt_silenceThreshold) but that causes an
- // object allocation in IE for some reason (doh!). The following code doesn't
- // cause an allocation
- var now = goog.debug.Trace_.now();
- var silenceThreshold;
- if (opt_silenceThreshold === 0) {
- silenceThreshold = 0;
- } else if (opt_silenceThreshold) {
- silenceThreshold = opt_silenceThreshold;
- } else {
- silenceThreshold = this.defaultThreshold_;
- }
- var startEvent = this.outstandingEvents_.get(String(id));
- if (startEvent == null) {
- return null;
- }
- this.outstandingEvents_.remove(String(id));
- var stopEvent;
- var elapsed = now - startEvent.startTime;
- if (elapsed < silenceThreshold) {
- var count = this.events_.length;
- for (var i = count - 1; i >= 0; i--) {
- var nextEvent = this.events_[i];
- if (nextEvent == startEvent) {
- this.events_.splice(i, 1);
- this.idPool_.releaseObject(startEvent.id);
- this.eventPool_.releaseObject(/** @type {Object} */ (startEvent));
- break;
- }
- }
- } else {
- stopEvent =
- /** @type {goog.debug.Trace_.Event_} */ (this.eventPool_.getObject());
- stopEvent.eventType = goog.debug.Trace_.EventType.STOP;
- stopEvent.startTime = startEvent.startTime;
- stopEvent.comment = startEvent.comment;
- stopEvent.type = startEvent.type;
- stopEvent.stopTime = stopEvent.eventTime = now;
- this.events_.push(stopEvent);
- }
- var type = startEvent.type;
- var stat = null;
- if (type) {
- stat = this.getStat_(type);
- stat.count++;
- stat.time += elapsed;
- }
- if (stopEvent) {
- goog.debug.Logger.logToProfilers('Stop : ' + stopEvent.comment);
- stopEvent.totalVarAlloc = this.getTotalVarAlloc();
- if (stat) {
- stat.varAlloc += (stopEvent.totalVarAlloc - startEvent.totalVarAlloc);
- }
- }
- var tracerFinishTime = goog.debug.Trace_.now();
- this.tracerOverheadEnd_ += tracerFinishTime - now;
- return elapsed;
- };
- /**
- * Sets the ActiveX object that can be used to get GC tracing in IE6.
- * @param {Object} gcTracer GCTracer ActiveX object.
- */
- goog.debug.Trace_.prototype.setGcTracer = function(gcTracer) {
- this.gcTracer_ = gcTracer;
- };
- /**
- * Returns the total number of allocations since the GC stats were reset. Only
- * works in IE.
- * @return {number} The number of allocaitons or -1 if not supported.
- */
- goog.debug.Trace_.prototype.getTotalVarAlloc = function() {
- var gcTracer = this.gcTracer_;
- // isTracing is defined on the ActiveX object.
- if (gcTracer && gcTracer['isTracing']()) {
- return gcTracer['totalVarAlloc'];
- }
- return -1;
- };
- /**
- * Adds a comment to the trace. Makes it possible to see when a specific event
- * happened in relation to the traces.
- * @param {string} comment A comment that is inserted into the trace.
- * @param {?string=} opt_type Type used to identify the tracer. If a comment is
- * given a type and multiple comments are done on that type then a "TOTAL
- * line will be produced showing the total number of comments of that type.
- * @param {?number=} opt_timeStamp The timestamp to insert the comment. If not
- * specified, the current time wil be used.
- */
- goog.debug.Trace_.prototype.addComment = function(
- comment, opt_type, opt_timeStamp) {
- var now = goog.debug.Trace_.now();
- var timeStamp = opt_timeStamp ? opt_timeStamp : now;
- var eventComment =
- /** @type {goog.debug.Trace_.Event_} */ (this.eventPool_.getObject());
- eventComment.eventType = goog.debug.Trace_.EventType.COMMENT;
- eventComment.eventTime = timeStamp;
- eventComment.type = opt_type;
- eventComment.comment = comment;
- eventComment.totalVarAlloc = this.getTotalVarAlloc();
- this.commentCount_++;
- if (opt_timeStamp) {
- var numEvents = this.events_.length;
- for (var i = 0; i < numEvents; i++) {
- var event = this.events_[i];
- var eventTime = event.eventTime;
- if (eventTime > timeStamp) {
- goog.array.insertAt(this.events_, eventComment, i);
- break;
- }
- }
- if (i == numEvents) {
- this.events_.push(eventComment);
- }
- } else {
- this.events_.push(eventComment);
- }
- var type = eventComment.type;
- if (type) {
- var stat = this.getStat_(type);
- stat.count++;
- }
- this.tracerOverheadComment_ += goog.debug.Trace_.now() - now;
- };
- /**
- * Gets a stat object for a particular type. The stat object is created if it
- * hasn't yet been.
- * @param {string} type The type of stat.
- * @return {goog.debug.Trace_.Stat_} The stat object.
- * @private
- */
- goog.debug.Trace_.prototype.getStat_ = function(type) {
- var stat = this.stats_.get(type);
- if (!stat) {
- stat = /** @type {goog.debug.Trace_.Event_} */ (this.statPool_.getObject());
- stat.type = type;
- this.stats_.set(type, stat);
- }
- return /** @type {goog.debug.Trace_.Stat_} */ (stat);
- };
- /**
- * Returns a formatted string for the current trace
- * @return {string} A formatted string that shows the timings of the current
- * trace.
- */
- goog.debug.Trace_.prototype.getFormattedTrace = function() {
- return this.toString();
- };
- /**
- * Returns a formatted string that describes the thread trace.
- * @return {string} A formatted string.
- * @override
- */
- goog.debug.Trace_.prototype.toString = function() {
- var sb = [];
- var etime = -1;
- var indent = [];
- for (var i = 0; i < this.events_.length; i++) {
- var e = this.events_[i];
- if (e.eventType == goog.debug.Trace_.EventType.STOP) {
- indent.pop();
- }
- sb.push(' ', e.toTraceString(this.startTime_, etime, indent.join('')));
- etime = e.eventTime;
- sb.push('\n');
- if (e.eventType == goog.debug.Trace_.EventType.START) {
- indent.push('| ');
- }
- }
- if (this.outstandingEvents_.getCount() != 0) {
- var now = goog.debug.Trace_.now();
- sb.push(' Unstopped timers:\n');
- goog.iter.forEach(this.outstandingEvents_, function(startEvent) {
- sb.push(
- ' ', startEvent, ' (', now - startEvent.startTime,
- ' ms, started at ',
- goog.debug.Trace_.formatTime_(startEvent.startTime), ')\n');
- });
- }
- var statKeys = this.stats_.getKeys();
- for (var i = 0; i < statKeys.length; i++) {
- var stat = this.stats_.get(statKeys[i]);
- if (stat.count > 1) {
- sb.push(' TOTAL ', stat, '\n');
- }
- }
- sb.push(
- 'Total tracers created ', this.tracerCount_, '\n',
- 'Total comments created ', this.commentCount_, '\n', 'Overhead start: ',
- this.tracerOverheadStart_, ' ms\n', 'Overhead end: ',
- this.tracerOverheadEnd_, ' ms\n', 'Overhead comment: ',
- this.tracerOverheadComment_, ' ms\n');
- return sb.join('');
- };
- /**
- * Converts 'v' to a string and pads it with up to 3 spaces for
- * improved alignment. TODO there must be a better way
- * @param {number} v A number.
- * @return {string} A padded string.
- * @private
- */
- goog.debug.Trace_.longToPaddedString_ = function(v) {
- v = Math.round(v);
- // todo (pupius) - there should be a generic string in goog.string for this
- var space = '';
- if (v < 1000) space = ' ';
- if (v < 100) space = ' ';
- if (v < 10) space = ' ';
- return space + v;
- };
- /**
- * Return the sec.ms part of time (if time = "20:06:11.566", "11.566
- * @param {number} time The time in MS.
- * @return {string} A formatted string as sec.ms'.
- * @private
- */
- goog.debug.Trace_.formatTime_ = function(time) {
- time = Math.round(time);
- var sec = (time / 1000) % 60;
- var ms = time % 1000;
- // TODO their must be a nicer way to get zero padded integers
- return String(100 + sec).substring(1, 3) + '.' +
- String(1000 + ms).substring(1, 4);
- };
- /**
- * Returns the current time. Done through a wrapper function so it can be
- * overridden by application code. Gmail has an ActiveX extension that provides
- * higher precision timing info.
- * @return {number} The current time in milliseconds.
- */
- goog.debug.Trace_.now = function() {
- return goog.now();
- };
- /**
- * Singleton trace object
- * @type {goog.debug.Trace_}
- */
- goog.debug.Trace = new goog.debug.Trace_();
|