TimelineModel.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. /*
  2. * Copyright (C) 2012 Google Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are
  6. * met:
  7. *
  8. * * Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above
  11. * copyright notice, this list of conditions and the following disclaimer
  12. * in the documentation and/or other materials provided with the
  13. * distribution.
  14. * * Neither the name of Google Inc. nor the names of its
  15. * contributors may be used to endorse or promote products derived from
  16. * this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. */
  30. /**
  31. * @constructor
  32. * @extends {WebInspector.Object}
  33. */
  34. WebInspector.TimelineModel = function()
  35. {
  36. this._records = [];
  37. this._stringPool = new StringPool();
  38. this._minimumRecordTime = -1;
  39. this._maximumRecordTime = -1;
  40. this._collectionEnabled = false;
  41. WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onRecordAdded, this);
  42. }
  43. WebInspector.TimelineModel.TransferChunkLengthBytes = 5000000;
  44. WebInspector.TimelineModel.RecordType = {
  45. Root: "Root",
  46. Program: "Program",
  47. EventDispatch: "EventDispatch",
  48. BeginFrame: "BeginFrame",
  49. ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
  50. RecalculateStyles: "RecalculateStyles",
  51. InvalidateLayout: "InvalidateLayout",
  52. Layout: "Layout",
  53. PaintSetup: "PaintSetup",
  54. Paint: "Paint",
  55. Rasterize: "Rasterize",
  56. ScrollLayer: "ScrollLayer",
  57. DecodeImage: "DecodeImage",
  58. ResizeImage: "ResizeImage",
  59. CompositeLayers: "CompositeLayers",
  60. ParseHTML: "ParseHTML",
  61. TimerInstall: "TimerInstall",
  62. TimerRemove: "TimerRemove",
  63. TimerFire: "TimerFire",
  64. XHRReadyStateChange: "XHRReadyStateChange",
  65. XHRLoad: "XHRLoad",
  66. EvaluateScript: "EvaluateScript",
  67. MarkLoad: "MarkLoad",
  68. MarkDOMContent: "MarkDOMContent",
  69. TimeStamp: "TimeStamp",
  70. Time: "Time",
  71. TimeEnd: "TimeEnd",
  72. ScheduleResourceRequest: "ScheduleResourceRequest",
  73. ResourceSendRequest: "ResourceSendRequest",
  74. ResourceReceiveResponse: "ResourceReceiveResponse",
  75. ResourceReceivedData: "ResourceReceivedData",
  76. ResourceFinish: "ResourceFinish",
  77. FunctionCall: "FunctionCall",
  78. GCEvent: "GCEvent",
  79. RequestAnimationFrame: "RequestAnimationFrame",
  80. CancelAnimationFrame: "CancelAnimationFrame",
  81. FireAnimationFrame: "FireAnimationFrame",
  82. WebSocketCreate : "WebSocketCreate",
  83. WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest",
  84. WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse",
  85. WebSocketDestroy : "WebSocketDestroy",
  86. }
  87. WebInspector.TimelineModel.Events = {
  88. RecordAdded: "RecordAdded",
  89. RecordsCleared: "RecordsCleared"
  90. }
  91. WebInspector.TimelineModel.startTimeInSeconds = function(record)
  92. {
  93. return record.startTime / 1000;
  94. }
  95. WebInspector.TimelineModel.endTimeInSeconds = function(record)
  96. {
  97. return (typeof record.endTime === "undefined" ? record.startTime : record.endTime) / 1000;
  98. }
  99. WebInspector.TimelineModel.durationInSeconds = function(record)
  100. {
  101. return WebInspector.TimelineModel.endTimeInSeconds(record) - WebInspector.TimelineModel.startTimeInSeconds(record);
  102. }
  103. /**
  104. * @param {Object} total
  105. * @param {Object} rawRecord
  106. */
  107. WebInspector.TimelineModel.aggregateTimeForRecord = function(total, rawRecord)
  108. {
  109. var childrenTime = 0;
  110. var children = rawRecord["children"] || [];
  111. for (var i = 0; i < children.length; ++i) {
  112. WebInspector.TimelineModel.aggregateTimeForRecord(total, children[i]);
  113. childrenTime += WebInspector.TimelineModel.durationInSeconds(children[i]);
  114. }
  115. var categoryName = WebInspector.TimelinePresentationModel.recordStyle(rawRecord).category.name;
  116. var ownTime = WebInspector.TimelineModel.durationInSeconds(rawRecord) - childrenTime;
  117. total[categoryName] = (total[categoryName] || 0) + ownTime;
  118. }
  119. /**
  120. * @param {Object} total
  121. * @param {Object} addend
  122. */
  123. WebInspector.TimelineModel.aggregateTimeByCategory = function(total, addend)
  124. {
  125. for (var category in addend)
  126. total[category] = (total[category] || 0) + addend[category];
  127. }
  128. WebInspector.TimelineModel.prototype = {
  129. /**
  130. * @param {boolean=} includeDomCounters
  131. */
  132. startRecord: function(includeDomCounters)
  133. {
  134. if (this._collectionEnabled)
  135. return;
  136. this.reset();
  137. var maxStackFrames = WebInspector.settings.timelineLimitStackFramesFlag.get() ? WebInspector.settings.timelineStackFramesToCapture.get() : 30;
  138. WebInspector.timelineManager.start(maxStackFrames, includeDomCounters);
  139. this._collectionEnabled = true;
  140. },
  141. stopRecord: function()
  142. {
  143. if (!this._collectionEnabled)
  144. return;
  145. WebInspector.timelineManager.stop();
  146. this._collectionEnabled = false;
  147. },
  148. get records()
  149. {
  150. return this._records;
  151. },
  152. _onRecordAdded: function(event)
  153. {
  154. if (this._collectionEnabled)
  155. this._addRecord(event.data);
  156. },
  157. _addRecord: function(record)
  158. {
  159. this._stringPool.internObjectStrings(record);
  160. this._records.push(record);
  161. this._updateBoundaries(record);
  162. this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
  163. },
  164. /**
  165. * @param {!Blob} file
  166. * @param {!WebInspector.Progress} progress
  167. */
  168. loadFromFile: function(file, progress)
  169. {
  170. var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress);
  171. var fileReader = this._createFileReader(file, delegate);
  172. var loader = new WebInspector.TimelineModelLoader(this, fileReader, progress);
  173. fileReader.start(loader);
  174. },
  175. /**
  176. * @param {string} url
  177. */
  178. loadFromURL: function(url, progress)
  179. {
  180. var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress);
  181. var urlReader = new WebInspector.ChunkedXHRReader(url, delegate);
  182. var loader = new WebInspector.TimelineModelLoader(this, urlReader, progress);
  183. urlReader.start(loader);
  184. },
  185. _createFileReader: function(file, delegate)
  186. {
  187. return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModel.TransferChunkLengthBytes, delegate);
  188. },
  189. _createFileWriter: function(fileName, callback)
  190. {
  191. var stream = new WebInspector.FileOutputStream();
  192. stream.open(fileName, callback);
  193. },
  194. saveToFile: function()
  195. {
  196. var now = new Date();
  197. var fileName = "TimelineRawData-" + now.toISO8601Compact() + ".json";
  198. function callback(stream)
  199. {
  200. var saver = new WebInspector.TimelineSaver(stream);
  201. saver.save(this._records, window.navigator.appVersion);
  202. }
  203. this._createFileWriter(fileName, callback.bind(this));
  204. },
  205. reset: function()
  206. {
  207. this._records = [];
  208. this._stringPool.reset();
  209. this._minimumRecordTime = -1;
  210. this._maximumRecordTime = -1;
  211. this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared);
  212. },
  213. minimumRecordTime: function()
  214. {
  215. return this._minimumRecordTime;
  216. },
  217. maximumRecordTime: function()
  218. {
  219. return this._maximumRecordTime;
  220. },
  221. _updateBoundaries: function(record)
  222. {
  223. var startTime = WebInspector.TimelineModel.startTimeInSeconds(record);
  224. var endTime = WebInspector.TimelineModel.endTimeInSeconds(record);
  225. if (this._minimumRecordTime === -1 || startTime < this._minimumRecordTime)
  226. this._minimumRecordTime = startTime;
  227. if (this._maximumRecordTime === -1 || endTime > this._maximumRecordTime)
  228. this._maximumRecordTime = endTime;
  229. },
  230. /**
  231. * @param {Object} rawRecord
  232. */
  233. recordOffsetInSeconds: function(rawRecord)
  234. {
  235. return WebInspector.TimelineModel.startTimeInSeconds(rawRecord) - this._minimumRecordTime;
  236. },
  237. __proto__: WebInspector.Object.prototype
  238. }
  239. /**
  240. * @constructor
  241. * @implements {WebInspector.OutputStream}
  242. * @param {!WebInspector.TimelineModel} model
  243. * @param {!{cancel: function()}} reader
  244. * @param {!WebInspector.Progress} progress
  245. */
  246. WebInspector.TimelineModelLoader = function(model, reader, progress)
  247. {
  248. this._model = model;
  249. this._reader = reader;
  250. this._progress = progress;
  251. this._buffer = "";
  252. this._firstChunk = true;
  253. }
  254. WebInspector.TimelineModelLoader.prototype = {
  255. /**
  256. * @param {string} chunk
  257. */
  258. write: function(chunk)
  259. {
  260. var data = this._buffer + chunk;
  261. var lastIndex = 0;
  262. var index;
  263. do {
  264. index = lastIndex;
  265. lastIndex = WebInspector.findBalancedCurlyBrackets(data, index);
  266. } while (lastIndex !== -1)
  267. var json = data.slice(0, index) + "]";
  268. this._buffer = data.slice(index);
  269. if (!index)
  270. return;
  271. // Prepending "0" to turn string into valid JSON.
  272. if (!this._firstChunk)
  273. json = "[0" + json;
  274. var items;
  275. try {
  276. items = /** @type {Array} */ (JSON.parse(json));
  277. } catch (e) {
  278. WebInspector.showErrorMessage("Malformed timeline data.");
  279. this._model.reset();
  280. this._reader.cancel();
  281. this._progress.done();
  282. return;
  283. }
  284. if (this._firstChunk) {
  285. this._version = items[0];
  286. this._firstChunk = false;
  287. this._model.reset();
  288. }
  289. // Skip 0-th element - it is either version or 0.
  290. for (var i = 1, size = items.length; i < size; ++i)
  291. this._model._addRecord(items[i]);
  292. },
  293. close: function() { }
  294. }
  295. /**
  296. * @constructor
  297. * @implements {WebInspector.OutputStreamDelegate}
  298. * @param {!WebInspector.TimelineModel} model
  299. * @param {!WebInspector.Progress} progress
  300. */
  301. WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress)
  302. {
  303. this._model = model;
  304. this._progress = progress;
  305. }
  306. WebInspector.TimelineModelLoadFromFileDelegate.prototype = {
  307. onTransferStarted: function()
  308. {
  309. this._progress.setTitle(WebInspector.UIString("Loading\u2026"));
  310. },
  311. /**
  312. * @param {WebInspector.ChunkedReader} reader
  313. */
  314. onChunkTransferred: function(reader)
  315. {
  316. if (this._progress.isCanceled()) {
  317. reader.cancel();
  318. this._progress.done();
  319. this._model.reset();
  320. return;
  321. }
  322. var totalSize = reader.fileSize();
  323. if (totalSize) {
  324. this._progress.setTotalWork(totalSize);
  325. this._progress.setWorked(reader.loadedSize());
  326. }
  327. },
  328. onTransferFinished: function()
  329. {
  330. this._progress.done();
  331. },
  332. /**
  333. * @param {WebInspector.ChunkedReader} reader
  334. */
  335. onError: function(reader, event)
  336. {
  337. this._progress.done();
  338. this._model.reset();
  339. switch (event.target.error.code) {
  340. case FileError.NOT_FOUND_ERR:
  341. WebInspector.showErrorMessage(WebInspector.UIString("File \"%s\" not found.", reader.fileName()));
  342. break;
  343. case FileError.NOT_READABLE_ERR:
  344. WebInspector.showErrorMessage(WebInspector.UIString("File \"%s\" is not readable", reader.fileName()));
  345. break;
  346. case FileError.ABORT_ERR:
  347. break;
  348. default:
  349. WebInspector.showErrorMessage(WebInspector.UIString("An error occurred while reading the file \"%s\"", reader.fileName()));
  350. }
  351. }
  352. }
  353. /**
  354. * @constructor
  355. */
  356. WebInspector.TimelineSaver = function(stream)
  357. {
  358. this._stream = stream;
  359. }
  360. WebInspector.TimelineSaver.prototype = {
  361. /**
  362. * @param {Array} records
  363. * @param {string} version
  364. */
  365. save: function(records, version)
  366. {
  367. this._records = records;
  368. this._recordIndex = 0;
  369. this._prologue = "[" + JSON.stringify(version);
  370. this._writeNextChunk(this._stream);
  371. },
  372. _writeNextChunk: function(stream)
  373. {
  374. const separator = ",\n";
  375. var data = [];
  376. var length = 0;
  377. if (this._prologue) {
  378. data.push(this._prologue);
  379. length += this._prologue.length;
  380. delete this._prologue;
  381. } else {
  382. if (this._recordIndex === this._records.length) {
  383. stream.close();
  384. return;
  385. }
  386. data.push("");
  387. }
  388. while (this._recordIndex < this._records.length) {
  389. var item = JSON.stringify(this._records[this._recordIndex]);
  390. var itemLength = item.length + separator.length;
  391. if (length + itemLength > WebInspector.TimelineModel.TransferChunkLengthBytes)
  392. break;
  393. length += itemLength;
  394. data.push(item);
  395. ++this._recordIndex;
  396. }
  397. if (this._recordIndex === this._records.length)
  398. data.push(data.pop() + "]");
  399. stream.write(data.join(separator), this._writeNextChunk.bind(this));
  400. }
  401. }