tracer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Definition of the Tracer class and associated classes.
  16. *
  17. * @see ../demos/tracer.html
  18. */
  19. goog.provide('goog.debug.Trace');
  20. goog.require('goog.array');
  21. goog.require('goog.debug.Logger');
  22. goog.require('goog.iter');
  23. goog.require('goog.log');
  24. goog.require('goog.structs.Map');
  25. goog.require('goog.structs.SimplePool');
  26. /**
  27. * Class used for singleton goog.debug.Trace. Used for timing slow points in
  28. * the code. Based on the java Tracer class but optimized for javascript.
  29. * See com.google.common.tracing.Tracer.
  30. * @constructor
  31. * @private
  32. */
  33. goog.debug.Trace_ = function() {
  34. /**
  35. * Events in order.
  36. * @type {Array<goog.debug.Trace_.Event_>}
  37. * @private
  38. */
  39. this.events_ = [];
  40. /**
  41. * Outstanding events that have started but haven't yet ended. The keys are
  42. * numeric ids and the values are goog.debug.Trace_.Event_ objects.
  43. * @type {goog.structs.Map}
  44. * @private
  45. */
  46. this.outstandingEvents_ = new goog.structs.Map();
  47. /**
  48. * Start time of the event trace
  49. * @type {number}
  50. * @private
  51. */
  52. this.startTime_ = 0;
  53. /**
  54. * Cummulative overhead of calls to startTracer
  55. * @type {number}
  56. * @private
  57. */
  58. this.tracerOverheadStart_ = 0;
  59. /**
  60. * Cummulative overhead of calls to endTracer
  61. * @type {number}
  62. * @private
  63. */
  64. this.tracerOverheadEnd_ = 0;
  65. /**
  66. * Cummulative overhead of calls to addComment
  67. * @type {number}
  68. * @private
  69. */
  70. this.tracerOverheadComment_ = 0;
  71. /**
  72. * Keeps stats on different types of tracers. The keys are strings and the
  73. * values are goog.debug.Stat
  74. * @type {goog.structs.Map}
  75. * @private
  76. */
  77. this.stats_ = new goog.structs.Map();
  78. /**
  79. * Total number of traces created in the trace.
  80. * @type {number}
  81. * @private
  82. */
  83. this.tracerCount_ = 0;
  84. /**
  85. * Total number of comments created in the trace.
  86. * @type {number}
  87. * @private
  88. */
  89. this.commentCount_ = 0;
  90. /**
  91. * Next id to use for the trace.
  92. * @type {number}
  93. * @private
  94. */
  95. this.nextId_ = 1;
  96. /**
  97. * A pool for goog.debug.Trace_.Event_ objects so we don't keep creating and
  98. * garbage collecting these (which is very expensive in IE6).
  99. * @private {!goog.structs.SimplePool}
  100. */
  101. this.eventPool_ = new goog.structs.SimplePool(0, 4000);
  102. this.eventPool_.createObject = function() {
  103. return new goog.debug.Trace_.Event_();
  104. };
  105. /**
  106. * A pool for goog.debug.Trace_.Stat_ objects so we don't keep creating and
  107. * garbage collecting these (which is very expensive in IE6).
  108. * @private {!goog.structs.SimplePool}
  109. */
  110. this.statPool_ = new goog.structs.SimplePool(0, 50);
  111. this.statPool_.createObject = function() {
  112. return new goog.debug.Trace_.Stat_();
  113. };
  114. var self = this;
  115. /** @private {!goog.structs.SimplePool} */
  116. this.idPool_ = new goog.structs.SimplePool(0, 2000);
  117. // TODO(nicksantos): SimplePool is supposed to only return objects.
  118. // Reconcile this so that we don't have to cast to number below.
  119. this.idPool_.createObject = function() { return String(self.nextId_++); };
  120. this.idPool_.disposeObject = function(obj) {};
  121. /**
  122. * Default threshold below which a tracer shouldn't be reported
  123. * @type {number}
  124. * @private
  125. */
  126. this.defaultThreshold_ = 3;
  127. };
  128. /**
  129. * Logger for the tracer
  130. * @type {goog.log.Logger}
  131. * @private
  132. */
  133. goog.debug.Trace_.prototype.logger_ = goog.log.getLogger('goog.debug.Trace');
  134. /**
  135. * Maximum size of the trace before we discard events
  136. * @type {number}
  137. */
  138. goog.debug.Trace_.prototype.MAX_TRACE_SIZE = 1000;
  139. /**
  140. * Event type supported by tracer
  141. * @enum {number}
  142. */
  143. goog.debug.Trace_.EventType = {
  144. /**
  145. * Start event type
  146. */
  147. START: 0,
  148. /**
  149. * Stop event type
  150. */
  151. STOP: 1,
  152. /**
  153. * Comment event type
  154. */
  155. COMMENT: 2
  156. };
  157. /**
  158. * Class to keep track of a stat of a single tracer type. Stores the count
  159. * and cumulative time.
  160. * @constructor
  161. * @private
  162. */
  163. goog.debug.Trace_.Stat_ = function() {
  164. /**
  165. * Number of tracers
  166. * @type {number}
  167. */
  168. this.count = 0;
  169. /**
  170. * Cumulative time of traces
  171. * @type {number}
  172. */
  173. this.time = 0;
  174. /**
  175. * Total number of allocations for this tracer type
  176. * @type {number}
  177. */
  178. this.varAlloc = 0;
  179. };
  180. /**
  181. * @type {string|null|undefined}
  182. */
  183. goog.debug.Trace_.Stat_.prototype.type;
  184. /**
  185. * @return {string} A string describing the tracer stat.
  186. * @override
  187. */
  188. goog.debug.Trace_.Stat_.prototype.toString = function() {
  189. var sb = [];
  190. sb.push(
  191. this.type, ' ', this.count, ' (', Math.round(this.time * 10) / 10,
  192. ' ms)');
  193. if (this.varAlloc) {
  194. sb.push(' [VarAlloc = ', this.varAlloc, ']');
  195. }
  196. return sb.join('');
  197. };
  198. /**
  199. * Private class used to encapsulate a single event, either the start or stop
  200. * of a tracer.
  201. * @constructor
  202. * @private
  203. */
  204. goog.debug.Trace_.Event_ = function() {
  205. // the fields are different for different events - see usage in code
  206. };
  207. /**
  208. * @type {string|null|undefined}
  209. */
  210. goog.debug.Trace_.Event_.prototype.type;
  211. /**
  212. * Returns a formatted string for the event.
  213. * @param {number} startTime The start time of the trace to generate relative
  214. * times.
  215. * @param {number} prevTime The completion time of the previous event or -1.
  216. * @param {string} indent Extra indent for the message
  217. * if there was no previous event.
  218. * @return {string} The formatted tracer string.
  219. */
  220. goog.debug.Trace_.Event_.prototype.toTraceString = function(
  221. startTime, prevTime, indent) {
  222. var sb = [];
  223. if (prevTime == -1) {
  224. sb.push(' ');
  225. } else {
  226. sb.push(goog.debug.Trace_.longToPaddedString_(this.eventTime - prevTime));
  227. }
  228. sb.push(' ', goog.debug.Trace_.formatTime_(this.eventTime - startTime));
  229. if (this.eventType == goog.debug.Trace_.EventType.START) {
  230. sb.push(' Start ');
  231. } else if (this.eventType == goog.debug.Trace_.EventType.STOP) {
  232. sb.push(' Done ');
  233. var delta = this.stopTime - this.startTime;
  234. sb.push(goog.debug.Trace_.longToPaddedString_(delta), ' ms ');
  235. } else {
  236. sb.push(' Comment ');
  237. }
  238. sb.push(indent, this);
  239. if (this.totalVarAlloc > 0) {
  240. sb.push('[VarAlloc ', this.totalVarAlloc, '] ');
  241. }
  242. return sb.join('');
  243. };
  244. /**
  245. * @return {string} A string describing the tracer event.
  246. * @override
  247. */
  248. goog.debug.Trace_.Event_.prototype.toString = function() {
  249. if (this.type == null) {
  250. return this.comment;
  251. } else {
  252. return '[' + this.type + '] ' + this.comment;
  253. }
  254. };
  255. /**
  256. * Add the ability to explicitly set the start time. This is useful for example
  257. * for measuring initial load time where you can set a variable as soon as the
  258. * main page of the app is loaded and then later call this function when the
  259. * Tracer code has been loaded.
  260. * @param {number} startTime The start time to set.
  261. */
  262. goog.debug.Trace_.prototype.setStartTime = function(startTime) {
  263. this.startTime_ = startTime;
  264. };
  265. /**
  266. * Initializes and resets the current trace
  267. * @param {number} defaultThreshold The default threshold below which the
  268. * tracer output will be suppressed. Can be overridden on a per-Tracer basis.
  269. */
  270. goog.debug.Trace_.prototype.initCurrentTrace = function(defaultThreshold) {
  271. this.reset(defaultThreshold);
  272. };
  273. /**
  274. * Clears the current trace
  275. */
  276. goog.debug.Trace_.prototype.clearCurrentTrace = function() {
  277. this.reset(0);
  278. };
  279. /**
  280. * Resets the trace.
  281. * @param {number} defaultThreshold The default threshold below which the
  282. * tracer output will be suppressed. Can be overridden on a per-Tracer basis.
  283. */
  284. goog.debug.Trace_.prototype.reset = function(defaultThreshold) {
  285. this.defaultThreshold_ = defaultThreshold;
  286. this.releaseEvents_();
  287. this.outstandingEvents_.clear();
  288. this.startTime_ = goog.debug.Trace_.now();
  289. this.tracerOverheadStart_ = 0;
  290. this.tracerOverheadEnd_ = 0;
  291. this.tracerOverheadComment_ = 0;
  292. this.tracerCount_ = 0;
  293. this.commentCount_ = 0;
  294. var keys = this.stats_.getKeys();
  295. for (var i = 0; i < keys.length; i++) {
  296. var key = keys[i];
  297. var stat = this.stats_.get(key);
  298. stat.count = 0;
  299. stat.time = 0;
  300. stat.varAlloc = 0;
  301. this.statPool_.releaseObject(/** @type {Object} */ (stat));
  302. }
  303. this.stats_.clear();
  304. };
  305. /**
  306. * @private
  307. */
  308. goog.debug.Trace_.prototype.releaseEvents_ = function() {
  309. for (var i = 0; i < this.events_.length; i++) {
  310. var event = this.events_[i];
  311. if (event.id) {
  312. this.idPool_.releaseObject(event.id);
  313. }
  314. this.eventPool_.releaseObject(event);
  315. }
  316. this.events_.length = 0;
  317. };
  318. /**
  319. * Starts a tracer
  320. * @param {string} comment A comment used to identify the tracer. Does not
  321. * need to be unique.
  322. * @param {string=} opt_type Type used to identify the tracer. If a Trace is
  323. * given a type (the first argument to the constructor) and multiple Traces
  324. * are done on that type then a "TOTAL line will be produced showing the
  325. * total number of traces and the sum of the time
  326. * ("TOTAL Database 2 (37 ms)" in our example). These traces should be
  327. * mutually exclusive or else the sum won't make sense (the time will
  328. * be double counted if the second starts before the first ends).
  329. * @return {number} The identifier for the tracer that should be passed to the
  330. * the stopTracer method.
  331. */
  332. goog.debug.Trace_.prototype.startTracer = function(comment, opt_type) {
  333. var tracerStartTime = goog.debug.Trace_.now();
  334. var varAlloc = this.getTotalVarAlloc();
  335. var outstandingEventCount = this.outstandingEvents_.getCount();
  336. if (this.events_.length + outstandingEventCount > this.MAX_TRACE_SIZE) {
  337. goog.log.warning(
  338. this.logger_, 'Giant thread trace. Clearing to avoid memory leak.');
  339. // This is the more likely case. This usually means that we
  340. // either forgot to clear the trace or else we are performing a
  341. // very large number of events
  342. if (this.events_.length > this.MAX_TRACE_SIZE / 2) {
  343. this.releaseEvents_();
  344. }
  345. // This is less likely and probably indicates that a lot of traces
  346. // aren't being closed. We want to avoid unnecessarily clearing
  347. // this though in case the events do eventually finish.
  348. if (outstandingEventCount > this.MAX_TRACE_SIZE / 2) {
  349. this.outstandingEvents_.clear();
  350. }
  351. }
  352. goog.debug.Logger.logToProfilers('Start : ' + comment);
  353. /** @const */
  354. var event =
  355. /** @type {!goog.debug.Trace_.Event_} */ (this.eventPool_.getObject());
  356. event.totalVarAlloc = varAlloc;
  357. event.eventType = goog.debug.Trace_.EventType.START;
  358. event.id = Number(this.idPool_.getObject());
  359. event.comment = comment;
  360. event.type = opt_type;
  361. this.events_.push(event);
  362. this.outstandingEvents_.set(String(event.id), event);
  363. this.tracerCount_++;
  364. var now = goog.debug.Trace_.now();
  365. event.startTime = event.eventTime = now;
  366. this.tracerOverheadStart_ += now - tracerStartTime;
  367. return event.id;
  368. };
  369. /**
  370. * Stops a tracer
  371. * @param {number|undefined|null} id The id of the tracer that is ending.
  372. * @param {number=} opt_silenceThreshold Threshold below which the tracer is
  373. * silenced.
  374. * @return {?number} The elapsed time for the tracer or null if the tracer
  375. * identitifer was not recognized.
  376. */
  377. goog.debug.Trace_.prototype.stopTracer = function(id, opt_silenceThreshold) {
  378. // this used to call goog.isDef(opt_silenceThreshold) but that causes an
  379. // object allocation in IE for some reason (doh!). The following code doesn't
  380. // cause an allocation
  381. var now = goog.debug.Trace_.now();
  382. var silenceThreshold;
  383. if (opt_silenceThreshold === 0) {
  384. silenceThreshold = 0;
  385. } else if (opt_silenceThreshold) {
  386. silenceThreshold = opt_silenceThreshold;
  387. } else {
  388. silenceThreshold = this.defaultThreshold_;
  389. }
  390. var startEvent = this.outstandingEvents_.get(String(id));
  391. if (startEvent == null) {
  392. return null;
  393. }
  394. this.outstandingEvents_.remove(String(id));
  395. var stopEvent;
  396. var elapsed = now - startEvent.startTime;
  397. if (elapsed < silenceThreshold) {
  398. var count = this.events_.length;
  399. for (var i = count - 1; i >= 0; i--) {
  400. var nextEvent = this.events_[i];
  401. if (nextEvent == startEvent) {
  402. this.events_.splice(i, 1);
  403. this.idPool_.releaseObject(startEvent.id);
  404. this.eventPool_.releaseObject(/** @type {Object} */ (startEvent));
  405. break;
  406. }
  407. }
  408. } else {
  409. stopEvent =
  410. /** @type {goog.debug.Trace_.Event_} */ (this.eventPool_.getObject());
  411. stopEvent.eventType = goog.debug.Trace_.EventType.STOP;
  412. stopEvent.startTime = startEvent.startTime;
  413. stopEvent.comment = startEvent.comment;
  414. stopEvent.type = startEvent.type;
  415. stopEvent.stopTime = stopEvent.eventTime = now;
  416. this.events_.push(stopEvent);
  417. }
  418. var type = startEvent.type;
  419. var stat = null;
  420. if (type) {
  421. stat = this.getStat_(type);
  422. stat.count++;
  423. stat.time += elapsed;
  424. }
  425. if (stopEvent) {
  426. goog.debug.Logger.logToProfilers('Stop : ' + stopEvent.comment);
  427. stopEvent.totalVarAlloc = this.getTotalVarAlloc();
  428. if (stat) {
  429. stat.varAlloc += (stopEvent.totalVarAlloc - startEvent.totalVarAlloc);
  430. }
  431. }
  432. var tracerFinishTime = goog.debug.Trace_.now();
  433. this.tracerOverheadEnd_ += tracerFinishTime - now;
  434. return elapsed;
  435. };
  436. /**
  437. * Sets the ActiveX object that can be used to get GC tracing in IE6.
  438. * @param {Object} gcTracer GCTracer ActiveX object.
  439. */
  440. goog.debug.Trace_.prototype.setGcTracer = function(gcTracer) {
  441. this.gcTracer_ = gcTracer;
  442. };
  443. /**
  444. * Returns the total number of allocations since the GC stats were reset. Only
  445. * works in IE.
  446. * @return {number} The number of allocaitons or -1 if not supported.
  447. */
  448. goog.debug.Trace_.prototype.getTotalVarAlloc = function() {
  449. var gcTracer = this.gcTracer_;
  450. // isTracing is defined on the ActiveX object.
  451. if (gcTracer && gcTracer['isTracing']()) {
  452. return gcTracer['totalVarAlloc'];
  453. }
  454. return -1;
  455. };
  456. /**
  457. * Adds a comment to the trace. Makes it possible to see when a specific event
  458. * happened in relation to the traces.
  459. * @param {string} comment A comment that is inserted into the trace.
  460. * @param {?string=} opt_type Type used to identify the tracer. If a comment is
  461. * given a type and multiple comments are done on that type then a "TOTAL
  462. * line will be produced showing the total number of comments of that type.
  463. * @param {?number=} opt_timeStamp The timestamp to insert the comment. If not
  464. * specified, the current time wil be used.
  465. */
  466. goog.debug.Trace_.prototype.addComment = function(
  467. comment, opt_type, opt_timeStamp) {
  468. var now = goog.debug.Trace_.now();
  469. var timeStamp = opt_timeStamp ? opt_timeStamp : now;
  470. var eventComment =
  471. /** @type {goog.debug.Trace_.Event_} */ (this.eventPool_.getObject());
  472. eventComment.eventType = goog.debug.Trace_.EventType.COMMENT;
  473. eventComment.eventTime = timeStamp;
  474. eventComment.type = opt_type;
  475. eventComment.comment = comment;
  476. eventComment.totalVarAlloc = this.getTotalVarAlloc();
  477. this.commentCount_++;
  478. if (opt_timeStamp) {
  479. var numEvents = this.events_.length;
  480. for (var i = 0; i < numEvents; i++) {
  481. var event = this.events_[i];
  482. var eventTime = event.eventTime;
  483. if (eventTime > timeStamp) {
  484. goog.array.insertAt(this.events_, eventComment, i);
  485. break;
  486. }
  487. }
  488. if (i == numEvents) {
  489. this.events_.push(eventComment);
  490. }
  491. } else {
  492. this.events_.push(eventComment);
  493. }
  494. var type = eventComment.type;
  495. if (type) {
  496. var stat = this.getStat_(type);
  497. stat.count++;
  498. }
  499. this.tracerOverheadComment_ += goog.debug.Trace_.now() - now;
  500. };
  501. /**
  502. * Gets a stat object for a particular type. The stat object is created if it
  503. * hasn't yet been.
  504. * @param {string} type The type of stat.
  505. * @return {goog.debug.Trace_.Stat_} The stat object.
  506. * @private
  507. */
  508. goog.debug.Trace_.prototype.getStat_ = function(type) {
  509. var stat = this.stats_.get(type);
  510. if (!stat) {
  511. stat = /** @type {goog.debug.Trace_.Event_} */ (this.statPool_.getObject());
  512. stat.type = type;
  513. this.stats_.set(type, stat);
  514. }
  515. return /** @type {goog.debug.Trace_.Stat_} */ (stat);
  516. };
  517. /**
  518. * Returns a formatted string for the current trace
  519. * @return {string} A formatted string that shows the timings of the current
  520. * trace.
  521. */
  522. goog.debug.Trace_.prototype.getFormattedTrace = function() {
  523. return this.toString();
  524. };
  525. /**
  526. * Returns a formatted string that describes the thread trace.
  527. * @return {string} A formatted string.
  528. * @override
  529. */
  530. goog.debug.Trace_.prototype.toString = function() {
  531. var sb = [];
  532. var etime = -1;
  533. var indent = [];
  534. for (var i = 0; i < this.events_.length; i++) {
  535. var e = this.events_[i];
  536. if (e.eventType == goog.debug.Trace_.EventType.STOP) {
  537. indent.pop();
  538. }
  539. sb.push(' ', e.toTraceString(this.startTime_, etime, indent.join('')));
  540. etime = e.eventTime;
  541. sb.push('\n');
  542. if (e.eventType == goog.debug.Trace_.EventType.START) {
  543. indent.push('| ');
  544. }
  545. }
  546. if (this.outstandingEvents_.getCount() != 0) {
  547. var now = goog.debug.Trace_.now();
  548. sb.push(' Unstopped timers:\n');
  549. goog.iter.forEach(this.outstandingEvents_, function(startEvent) {
  550. sb.push(
  551. ' ', startEvent, ' (', now - startEvent.startTime,
  552. ' ms, started at ',
  553. goog.debug.Trace_.formatTime_(startEvent.startTime), ')\n');
  554. });
  555. }
  556. var statKeys = this.stats_.getKeys();
  557. for (var i = 0; i < statKeys.length; i++) {
  558. var stat = this.stats_.get(statKeys[i]);
  559. if (stat.count > 1) {
  560. sb.push(' TOTAL ', stat, '\n');
  561. }
  562. }
  563. sb.push(
  564. 'Total tracers created ', this.tracerCount_, '\n',
  565. 'Total comments created ', this.commentCount_, '\n', 'Overhead start: ',
  566. this.tracerOverheadStart_, ' ms\n', 'Overhead end: ',
  567. this.tracerOverheadEnd_, ' ms\n', 'Overhead comment: ',
  568. this.tracerOverheadComment_, ' ms\n');
  569. return sb.join('');
  570. };
  571. /**
  572. * Converts 'v' to a string and pads it with up to 3 spaces for
  573. * improved alignment. TODO there must be a better way
  574. * @param {number} v A number.
  575. * @return {string} A padded string.
  576. * @private
  577. */
  578. goog.debug.Trace_.longToPaddedString_ = function(v) {
  579. v = Math.round(v);
  580. // todo (pupius) - there should be a generic string in goog.string for this
  581. var space = '';
  582. if (v < 1000) space = ' ';
  583. if (v < 100) space = ' ';
  584. if (v < 10) space = ' ';
  585. return space + v;
  586. };
  587. /**
  588. * Return the sec.ms part of time (if time = "20:06:11.566", "11.566
  589. * @param {number} time The time in MS.
  590. * @return {string} A formatted string as sec.ms'.
  591. * @private
  592. */
  593. goog.debug.Trace_.formatTime_ = function(time) {
  594. time = Math.round(time);
  595. var sec = (time / 1000) % 60;
  596. var ms = time % 1000;
  597. // TODO their must be a nicer way to get zero padded integers
  598. return String(100 + sec).substring(1, 3) + '.' +
  599. String(1000 + ms).substring(1, 4);
  600. };
  601. /**
  602. * Returns the current time. Done through a wrapper function so it can be
  603. * overridden by application code. Gmail has an ActiveX extension that provides
  604. * higher precision timing info.
  605. * @return {number} The current time in milliseconds.
  606. */
  607. goog.debug.Trace_.now = function() {
  608. return goog.now();
  609. };
  610. /**
  611. * Singleton trace object
  612. * @type {goog.debug.Trace_}
  613. */
  614. goog.debug.Trace = new goog.debug.Trace_();