MemoryStatistics.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  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. * @param {WebInspector.TimelinePanel} timelinePanel
  32. * @param {WebInspector.TimelineModel} model
  33. * @param {number} sidebarWidth
  34. * @constructor
  35. */
  36. WebInspector.MemoryStatistics = function(timelinePanel, model, sidebarWidth)
  37. {
  38. this._timelinePanel = timelinePanel;
  39. this._counters = [];
  40. model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this);
  41. model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this);
  42. this._containerAnchor = timelinePanel.element.lastChild;
  43. this._memorySidebarView = new WebInspector.SidebarView(WebInspector.SidebarView.SidebarPosition.Start, undefined, sidebarWidth);
  44. this._memorySidebarView.sidebarElement.addStyleClass("sidebar");
  45. this._memorySidebarView.element.id = "memory-graphs-container";
  46. this._memorySidebarView.addEventListener(WebInspector.SidebarView.EventTypes.Resized, this._sidebarResized.bind(this));
  47. this._canvasContainer = this._memorySidebarView.mainElement;
  48. this._canvasContainer.id = "memory-graphs-canvas-container";
  49. this._createCurrentValuesBar();
  50. this._canvas = this._canvasContainer.createChild("canvas");
  51. this._canvas.id = "memory-counters-graph";
  52. this._lastMarkerXPosition = 0;
  53. this._canvas.addEventListener("mouseover", this._onMouseOver.bind(this), true);
  54. this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this), true);
  55. this._canvas.addEventListener("mouseout", this._onMouseOut.bind(this), true);
  56. this._canvas.addEventListener("click", this._onClick.bind(this), true);
  57. // We create extra timeline grid here to reuse its event dividers.
  58. this._timelineGrid = new WebInspector.TimelineGrid();
  59. this._canvasContainer.appendChild(this._timelineGrid.dividersElement);
  60. // Populate sidebar
  61. this._memorySidebarView.sidebarElement.createChild("div", "sidebar-tree sidebar-tree-section").textContent = WebInspector.UIString("COUNTERS");
  62. this._counterUI = this._createCounterUIList();
  63. }
  64. /**
  65. * @constructor
  66. * @param {number} time
  67. */
  68. WebInspector.MemoryStatistics.Counter = function(time)
  69. {
  70. this.time = time;
  71. }
  72. /**
  73. * @constructor
  74. * @extends {WebInspector.Object}
  75. */
  76. WebInspector.SwatchCheckbox = function(title, color)
  77. {
  78. this.element = document.createElement("div");
  79. this._swatch = this.element.createChild("div", "swatch");
  80. this.element.createChild("span", "title").textContent = title;
  81. this._color = color;
  82. this.checked = true;
  83. this.element.addEventListener("click", this._toggleCheckbox.bind(this), true);
  84. }
  85. WebInspector.SwatchCheckbox.Events = {
  86. Changed: "Changed"
  87. }
  88. WebInspector.SwatchCheckbox.prototype = {
  89. get checked()
  90. {
  91. return this._checked;
  92. },
  93. set checked(v)
  94. {
  95. this._checked = v;
  96. if (this._checked)
  97. this._swatch.style.backgroundColor = this._color;
  98. else
  99. this._swatch.style.backgroundColor = "";
  100. },
  101. _toggleCheckbox: function(event)
  102. {
  103. this.checked = !this.checked;
  104. this.dispatchEventToListeners(WebInspector.SwatchCheckbox.Events.Changed);
  105. },
  106. __proto__: WebInspector.Object.prototype
  107. }
  108. /**
  109. * @constructor
  110. */
  111. WebInspector.CounterUIBase = function(memoryCountersPane, title, graphColor, valueGetter)
  112. {
  113. this._memoryCountersPane = memoryCountersPane;
  114. this.valueGetter = valueGetter;
  115. var container = memoryCountersPane._memorySidebarView.sidebarElement.createChild("div", "memory-counter-sidebar-info");
  116. var swatchColor = graphColor;
  117. this._swatch = new WebInspector.SwatchCheckbox(WebInspector.UIString(title), swatchColor);
  118. this._swatch.addEventListener(WebInspector.SwatchCheckbox.Events.Changed, this._toggleCounterGraph.bind(this));
  119. container.appendChild(this._swatch.element);
  120. this._value = null;
  121. this.graphColor =graphColor;
  122. this.strokeColor = graphColor;
  123. this.graphYValues = [];
  124. }
  125. WebInspector.CounterUIBase.prototype = {
  126. _toggleCounterGraph: function(event)
  127. {
  128. if (this._swatch.checked)
  129. this._value.removeStyleClass("hidden");
  130. else
  131. this._value.addStyleClass("hidden");
  132. this._memoryCountersPane.refresh();
  133. },
  134. updateCurrentValue: function(countersEntry)
  135. {
  136. this._value.textContent = Number.bytesToString(this.valueGetter(countersEntry));
  137. },
  138. clearCurrentValueAndMarker: function(ctx)
  139. {
  140. this._value.textContent = "";
  141. },
  142. get visible()
  143. {
  144. return this._swatch.checked;
  145. },
  146. }
  147. WebInspector.MemoryStatistics.prototype = {
  148. _createCurrentValuesBar: function()
  149. {
  150. throw new Error("Not implemented");
  151. },
  152. _createCounterUIList: function()
  153. {
  154. throw new Error("Not implemented");
  155. },
  156. _onRecordsCleared: function()
  157. {
  158. this._counters = [];
  159. },
  160. /**
  161. * @param {WebInspector.TimelineGrid} timelineGrid
  162. */
  163. setMainTimelineGrid: function(timelineGrid)
  164. {
  165. this._mainTimelineGrid = timelineGrid;
  166. },
  167. /**
  168. * @param {number} top
  169. */
  170. setTopPosition: function(top)
  171. {
  172. this._memorySidebarView.element.style.top = top + "px";
  173. this._updateSize();
  174. },
  175. /**
  176. * @param {number} width
  177. */
  178. setSidebarWidth: function(width)
  179. {
  180. if (this._ignoreSidebarResize)
  181. return;
  182. this._ignoreSidebarResize = true;
  183. this._memorySidebarView.setSidebarWidth(width);
  184. this._ignoreSidebarResize = false;
  185. },
  186. /**
  187. * @param {WebInspector.Event} event
  188. */
  189. _sidebarResized: function(event)
  190. {
  191. if (this._ignoreSidebarResize)
  192. return;
  193. this._ignoreSidebarResize = true;
  194. this._timelinePanel.splitView.setSidebarWidth(event.data);
  195. this._ignoreSidebarResize = false;
  196. },
  197. _canvasHeight: function()
  198. {
  199. throw new Error("Not implemented");
  200. },
  201. _updateSize: function()
  202. {
  203. var width = this._mainTimelineGrid.dividersElement.offsetWidth + 1;
  204. this._canvasContainer.style.width = width + "px";
  205. var height = this._canvasHeight();
  206. this._canvas.width = width;
  207. this._canvas.height = height;
  208. },
  209. /**
  210. * @param {WebInspector.Event} event
  211. */
  212. _onRecordAdded: function(event)
  213. {
  214. throw new Error("Not implemented");
  215. },
  216. _draw: function()
  217. {
  218. this._calculateVisibleIndexes();
  219. this._calculateXValues();
  220. this._clear();
  221. this._setVerticalClip(10, this._canvas.height - 20);
  222. },
  223. _calculateVisibleIndexes: function()
  224. {
  225. var calculator = this._timelinePanel.calculator;
  226. var start = calculator.minimumBoundary() * 1000;
  227. var end = calculator.maximumBoundary() * 1000;
  228. var firstIndex = 0;
  229. var lastIndex = this._counters.length - 1;
  230. for (var i = 0; i < this._counters.length; i++) {
  231. var time = this._counters[i].time;
  232. if (time <= start) {
  233. firstIndex = i;
  234. } else {
  235. if (end < time)
  236. break;
  237. lastIndex = i;
  238. }
  239. }
  240. // Maximum index of element whose time <= start.
  241. this._minimumIndex = firstIndex;
  242. // Maximum index of element whose time <= end.
  243. this._maximumIndex = lastIndex;
  244. // Current window bounds.
  245. this._minTime = start;
  246. this._maxTime = end;
  247. },
  248. /**
  249. * @param {MouseEvent} event
  250. */
  251. _onClick: function(event)
  252. {
  253. var x = event.x - event.target.offsetParent.offsetLeft;
  254. var i = this._recordIndexAt(x);
  255. var counter = this._counters[i];
  256. if (counter)
  257. this._timelinePanel.revealRecordAt(counter.time / 1000);
  258. },
  259. /**
  260. * @param {MouseEvent} event
  261. */
  262. _onMouseOut: function(event)
  263. {
  264. delete this._markerXPosition;
  265. var ctx = this._canvas.getContext("2d");
  266. this._clearCurrentValueAndMarker(ctx);
  267. },
  268. /**
  269. * @param {CanvasRenderingContext2D} ctx
  270. */
  271. _clearCurrentValueAndMarker: function(ctx)
  272. {
  273. for (var i = 0; i < this._counterUI.length; i++)
  274. this._counterUI[i].clearCurrentValueAndMarker(ctx);
  275. },
  276. /**
  277. * @param {MouseEvent} event
  278. */
  279. _onMouseOver: function(event)
  280. {
  281. this._onMouseMove(event);
  282. },
  283. /**
  284. * @param {MouseEvent} event
  285. */
  286. _onMouseMove: function(event)
  287. {
  288. var x = event.x - event.target.offsetParent.offsetLeft
  289. this._markerXPosition = x;
  290. this._refreshCurrentValues();
  291. },
  292. _refreshCurrentValues: function()
  293. {
  294. if (!this._counters.length)
  295. return;
  296. if (this._markerXPosition === undefined)
  297. return;
  298. if (this._maximumIndex === -1)
  299. return;
  300. var i = this._recordIndexAt(this._markerXPosition);
  301. this._updateCurrentValue(this._counters[i]);
  302. this._highlightCurrentPositionOnGraphs(this._markerXPosition, i);
  303. },
  304. _updateCurrentValue: function(counterEntry)
  305. {
  306. for (var j = 0; j < this._counterUI.length; j++)
  307. this._counterUI[j].updateCurrentValue(counterEntry);
  308. },
  309. _recordIndexAt: function(x)
  310. {
  311. var i;
  312. for (i = this._minimumIndex + 1; i <= this._maximumIndex; i++) {
  313. var statX = this._counters[i].x;
  314. if (x < statX)
  315. break;
  316. }
  317. i--;
  318. return i;
  319. },
  320. _highlightCurrentPositionOnGraphs: function(x, index)
  321. {
  322. var ctx = this._canvas.getContext("2d");
  323. this._restoreImageUnderMarker(ctx);
  324. this._drawMarker(ctx, x, index);
  325. },
  326. _restoreImageUnderMarker: function(ctx)
  327. {
  328. throw new Error("Not implemented");
  329. },
  330. _drawMarker: function(ctx, x, index)
  331. {
  332. throw new Error("Not implemented");
  333. },
  334. visible: function()
  335. {
  336. return this._memorySidebarView.isShowing();
  337. },
  338. show: function()
  339. {
  340. var anchor = /** @type {Element|null} */ (this._containerAnchor.nextSibling);
  341. this._memorySidebarView.show(this._timelinePanel.element, anchor);
  342. this._updateSize();
  343. this._refreshDividers();
  344. setTimeout(this._draw.bind(this), 0);
  345. },
  346. refresh: function()
  347. {
  348. this._updateSize();
  349. this._refreshDividers();
  350. this._draw();
  351. this._refreshCurrentValues();
  352. },
  353. hide: function()
  354. {
  355. this._memorySidebarView.detach();
  356. },
  357. _refreshDividers: function()
  358. {
  359. this._timelineGrid.updateDividers(this._timelinePanel.calculator);
  360. },
  361. _setVerticalClip: function(originY, height)
  362. {
  363. this._originY = originY;
  364. this._clippedHeight = height;
  365. },
  366. _calculateXValues: function()
  367. {
  368. if (!this._counters.length)
  369. return;
  370. var width = this._canvas.width;
  371. var xFactor = width / (this._maxTime - this._minTime);
  372. this._counters[this._minimumIndex].x = 0;
  373. for (var i = this._minimumIndex + 1; i < this._maximumIndex; i++)
  374. this._counters[i].x = xFactor * (this._counters[i].time - this._minTime);
  375. this._counters[this._maximumIndex].x = width;
  376. },
  377. _clear: function() {
  378. var ctx = this._canvas.getContext("2d");
  379. ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  380. this._discardImageUnderMarker();
  381. },
  382. _discardImageUnderMarker: function()
  383. {
  384. throw new Error("Not implemented");
  385. }
  386. }