toolkit/content/aboutTelemetry.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 'use strict';
michael@0 6
michael@0 7 const Ci = Components.interfaces;
michael@0 8 const Cc = Components.classes;
michael@0 9 const Cu = Components.utils;
michael@0 10
michael@0 11 Cu.import("resource://gre/modules/Services.jsm");
michael@0 12 Cu.import("resource://gre/modules/TelemetryTimestamps.jsm");
michael@0 13 Cu.import("resource://gre/modules/TelemetryPing.jsm");
michael@0 14
michael@0 15 const Telemetry = Services.telemetry;
michael@0 16 const bundle = Services.strings.createBundle(
michael@0 17 "chrome://global/locale/aboutTelemetry.properties");
michael@0 18 const brandBundle = Services.strings.createBundle(
michael@0 19 "chrome://branding/locale/brand.properties");
michael@0 20
michael@0 21 // Maximum height of a histogram bar (in em for html, in chars for text)
michael@0 22 const MAX_BAR_HEIGHT = 18;
michael@0 23 const MAX_BAR_CHARS = 25;
michael@0 24 const PREF_TELEMETRY_SERVER_OWNER = "toolkit.telemetry.server_owner";
michael@0 25 const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
michael@0 26 const PREF_DEBUG_SLOW_SQL = "toolkit.telemetry.debugSlowSql";
michael@0 27 const PREF_SYMBOL_SERVER_URI = "profiler.symbolicationUrl";
michael@0 28 const DEFAULT_SYMBOL_SERVER_URI = "http://symbolapi.mozilla.org";
michael@0 29
michael@0 30 // ms idle before applying the filter (allow uninterrupted typing)
michael@0 31 const FILTER_IDLE_TIMEOUT = 500;
michael@0 32
michael@0 33 #ifdef XP_WIN
michael@0 34 const EOL = "\r\n";
michael@0 35 #else
michael@0 36 const EOL = "\n";
michael@0 37 #endif
michael@0 38
michael@0 39 // Cached value of document's RTL mode
michael@0 40 let documentRTLMode = "";
michael@0 41
michael@0 42 /**
michael@0 43 * Helper function for fetching a config pref
michael@0 44 *
michael@0 45 * @param aPrefName Name of config pref to fetch.
michael@0 46 * @param aDefault Default value to return if pref isn't set.
michael@0 47 * @return Value of pref
michael@0 48 */
michael@0 49 function getPref(aPrefName, aDefault) {
michael@0 50 let result = aDefault;
michael@0 51
michael@0 52 try {
michael@0 53 let prefType = Services.prefs.getPrefType(aPrefName);
michael@0 54 if (prefType == Ci.nsIPrefBranch.PREF_BOOL) {
michael@0 55 result = Services.prefs.getBoolPref(aPrefName);
michael@0 56 } else if (prefType == Ci.nsIPrefBranch.PREF_STRING) {
michael@0 57 result = Services.prefs.getCharPref(aPrefName);
michael@0 58 }
michael@0 59 } catch (e) {
michael@0 60 // Return default if Prefs service throws exception
michael@0 61 }
michael@0 62
michael@0 63 return result;
michael@0 64 }
michael@0 65
michael@0 66 /**
michael@0 67 * Helper function for determining whether the document direction is RTL.
michael@0 68 * Caches result of check on first invocation.
michael@0 69 */
michael@0 70 function isRTL() {
michael@0 71 if (!documentRTLMode)
michael@0 72 documentRTLMode = window.getComputedStyle(document.body).direction;
michael@0 73 return (documentRTLMode == "rtl");
michael@0 74 }
michael@0 75
michael@0 76 let observer = {
michael@0 77
michael@0 78 enableTelemetry: bundle.GetStringFromName("enableTelemetry"),
michael@0 79
michael@0 80 disableTelemetry: bundle.GetStringFromName("disableTelemetry"),
michael@0 81
michael@0 82 /**
michael@0 83 * Observer is called whenever Telemetry is enabled or disabled
michael@0 84 */
michael@0 85 observe: function observe(aSubject, aTopic, aData) {
michael@0 86 if (aData == PREF_TELEMETRY_ENABLED) {
michael@0 87 this.updatePrefStatus();
michael@0 88 }
michael@0 89 },
michael@0 90
michael@0 91 /**
michael@0 92 * Updates the button & text at the top of the page to reflect Telemetry state.
michael@0 93 */
michael@0 94 updatePrefStatus: function updatePrefStatus() {
michael@0 95 // Notify user whether Telemetry is enabled
michael@0 96 let enabledElement = document.getElementById("description-enabled");
michael@0 97 let disabledElement = document.getElementById("description-disabled");
michael@0 98 let toggleElement = document.getElementById("toggle-telemetry");
michael@0 99 if (getPref(PREF_TELEMETRY_ENABLED, false)) {
michael@0 100 enabledElement.classList.remove("hidden");
michael@0 101 disabledElement.classList.add("hidden");
michael@0 102 toggleElement.innerHTML = this.disableTelemetry;
michael@0 103 } else {
michael@0 104 enabledElement.classList.add("hidden");
michael@0 105 disabledElement.classList.remove("hidden");
michael@0 106 toggleElement.innerHTML = this.enableTelemetry;
michael@0 107 }
michael@0 108 }
michael@0 109 };
michael@0 110
michael@0 111 let SlowSQL = {
michael@0 112
michael@0 113 slowSqlHits: bundle.GetStringFromName("slowSqlHits"),
michael@0 114
michael@0 115 slowSqlAverage: bundle.GetStringFromName("slowSqlAverage"),
michael@0 116
michael@0 117 slowSqlStatement: bundle.GetStringFromName("slowSqlStatement"),
michael@0 118
michael@0 119 mainThreadTitle: bundle.GetStringFromName("slowSqlMain"),
michael@0 120
michael@0 121 otherThreadTitle: bundle.GetStringFromName("slowSqlOther"),
michael@0 122
michael@0 123 /**
michael@0 124 * Render slow SQL statistics
michael@0 125 */
michael@0 126 render: function SlowSQL_render() {
michael@0 127 let debugSlowSql = getPref(PREF_DEBUG_SLOW_SQL, false);
michael@0 128 let {mainThread, otherThreads} =
michael@0 129 Telemetry[debugSlowSql ? "debugSlowSQL" : "slowSQL"];
michael@0 130
michael@0 131 let mainThreadCount = Object.keys(mainThread).length;
michael@0 132 let otherThreadCount = Object.keys(otherThreads).length;
michael@0 133 if (mainThreadCount == 0 && otherThreadCount == 0) {
michael@0 134 return;
michael@0 135 }
michael@0 136
michael@0 137 setHasData("slow-sql-section", true);
michael@0 138
michael@0 139 if (debugSlowSql) {
michael@0 140 document.getElementById("sql-warning").classList.remove("hidden");
michael@0 141 }
michael@0 142
michael@0 143 let slowSqlDiv = document.getElementById("slow-sql-tables");
michael@0 144
michael@0 145 // Main thread
michael@0 146 if (mainThreadCount > 0) {
michael@0 147 let table = document.createElement("table");
michael@0 148 this.renderTableHeader(table, this.mainThreadTitle);
michael@0 149 this.renderTable(table, mainThread);
michael@0 150
michael@0 151 slowSqlDiv.appendChild(table);
michael@0 152 slowSqlDiv.appendChild(document.createElement("hr"));
michael@0 153 }
michael@0 154
michael@0 155 // Other threads
michael@0 156 if (otherThreadCount > 0) {
michael@0 157 let table = document.createElement("table");
michael@0 158 this.renderTableHeader(table, this.otherThreadTitle);
michael@0 159 this.renderTable(table, otherThreads);
michael@0 160
michael@0 161 slowSqlDiv.appendChild(table);
michael@0 162 slowSqlDiv.appendChild(document.createElement("hr"));
michael@0 163 }
michael@0 164 },
michael@0 165
michael@0 166 /**
michael@0 167 * Creates a header row for a Slow SQL table
michael@0 168 * Tabs & newlines added to cells to make it easier to copy-paste.
michael@0 169 *
michael@0 170 * @param aTable Parent table element
michael@0 171 * @param aTitle Table's title
michael@0 172 */
michael@0 173 renderTableHeader: function SlowSQL_renderTableHeader(aTable, aTitle) {
michael@0 174 let caption = document.createElement("caption");
michael@0 175 caption.appendChild(document.createTextNode(aTitle + "\n"));
michael@0 176 aTable.appendChild(caption);
michael@0 177
michael@0 178 let headings = document.createElement("tr");
michael@0 179 this.appendColumn(headings, "th", this.slowSqlHits + "\t");
michael@0 180 this.appendColumn(headings, "th", this.slowSqlAverage + "\t");
michael@0 181 this.appendColumn(headings, "th", this.slowSqlStatement + "\n");
michael@0 182 aTable.appendChild(headings);
michael@0 183 },
michael@0 184
michael@0 185 /**
michael@0 186 * Fills out the table body
michael@0 187 * Tabs & newlines added to cells to make it easier to copy-paste.
michael@0 188 *
michael@0 189 * @param aTable Parent table element
michael@0 190 * @param aSql SQL stats object
michael@0 191 */
michael@0 192 renderTable: function SlowSQL_renderTable(aTable, aSql) {
michael@0 193 for (let [sql, [hitCount, totalTime]] of Iterator(aSql)) {
michael@0 194 let averageTime = totalTime / hitCount;
michael@0 195
michael@0 196 let sqlRow = document.createElement("tr");
michael@0 197
michael@0 198 this.appendColumn(sqlRow, "td", hitCount + "\t");
michael@0 199 this.appendColumn(sqlRow, "td", averageTime.toFixed(0) + "\t");
michael@0 200 this.appendColumn(sqlRow, "td", sql + "\n");
michael@0 201
michael@0 202 aTable.appendChild(sqlRow);
michael@0 203 }
michael@0 204 },
michael@0 205
michael@0 206 /**
michael@0 207 * Helper function for appending a column to a Slow SQL table.
michael@0 208 *
michael@0 209 * @param aRowElement Parent row element
michael@0 210 * @param aColType Column's tag name
michael@0 211 * @param aColText Column contents
michael@0 212 */
michael@0 213 appendColumn: function SlowSQL_appendColumn(aRowElement, aColType, aColText) {
michael@0 214 let colElement = document.createElement(aColType);
michael@0 215 let colTextElement = document.createTextNode(aColText);
michael@0 216 colElement.appendChild(colTextElement);
michael@0 217 aRowElement.appendChild(colElement);
michael@0 218 }
michael@0 219 };
michael@0 220
michael@0 221 /**
michael@0 222 * Removes child elements from the supplied div
michael@0 223 *
michael@0 224 * @param aDiv Element to be cleared
michael@0 225 */
michael@0 226 function clearDivData(aDiv) {
michael@0 227 while (aDiv.hasChildNodes()) {
michael@0 228 aDiv.removeChild(aDiv.lastChild);
michael@0 229 }
michael@0 230 };
michael@0 231
michael@0 232 let StackRenderer = {
michael@0 233
michael@0 234 stackTitle: bundle.GetStringFromName("stackTitle"),
michael@0 235
michael@0 236 memoryMapTitle: bundle.GetStringFromName("memoryMapTitle"),
michael@0 237
michael@0 238 /**
michael@0 239 * Outputs the memory map associated with this hang report
michael@0 240 *
michael@0 241 * @param aDiv Output div
michael@0 242 */
michael@0 243 renderMemoryMap: function StackRenderer_renderMemoryMap(aDiv, memoryMap) {
michael@0 244 aDiv.appendChild(document.createTextNode(this.memoryMapTitle));
michael@0 245 aDiv.appendChild(document.createElement("br"));
michael@0 246
michael@0 247 for (let currentModule of memoryMap) {
michael@0 248 aDiv.appendChild(document.createTextNode(currentModule.join(" ")));
michael@0 249 aDiv.appendChild(document.createElement("br"));
michael@0 250 }
michael@0 251
michael@0 252 aDiv.appendChild(document.createElement("br"));
michael@0 253 },
michael@0 254
michael@0 255 /**
michael@0 256 * Outputs the raw PCs from the hang's stack
michael@0 257 *
michael@0 258 * @param aDiv Output div
michael@0 259 * @param aStack Array of PCs from the hang stack
michael@0 260 */
michael@0 261 renderStack: function StackRenderer_renderStack(aDiv, aStack) {
michael@0 262 aDiv.appendChild(document.createTextNode(this.stackTitle));
michael@0 263 let stackText = " " + aStack.join(" ");
michael@0 264 aDiv.appendChild(document.createTextNode(stackText));
michael@0 265
michael@0 266 aDiv.appendChild(document.createElement("br"));
michael@0 267 aDiv.appendChild(document.createElement("br"));
michael@0 268 },
michael@0 269 renderStacks: function StackRenderer_renderStacks(aPrefix, aStacks,
michael@0 270 aMemoryMap, aRenderHeader) {
michael@0 271 let div = document.getElementById(aPrefix + '-data');
michael@0 272 clearDivData(div);
michael@0 273
michael@0 274 let fetchE = document.getElementById(aPrefix + '-fetch-symbols');
michael@0 275 if (fetchE) {
michael@0 276 fetchE.classList.remove("hidden");
michael@0 277 }
michael@0 278 let hideE = document.getElementById(aPrefix + '-hide-symbols');
michael@0 279 if (hideE) {
michael@0 280 hideE.classList.add("hidden");
michael@0 281 }
michael@0 282
michael@0 283 if (aStacks.length == 0) {
michael@0 284 return;
michael@0 285 }
michael@0 286
michael@0 287 setHasData(aPrefix + '-section', true);
michael@0 288
michael@0 289 this.renderMemoryMap(div, aMemoryMap);
michael@0 290
michael@0 291 for (let i = 0; i < aStacks.length; ++i) {
michael@0 292 let stack = aStacks[i];
michael@0 293 aRenderHeader(i);
michael@0 294 this.renderStack(div, stack)
michael@0 295 }
michael@0 296 },
michael@0 297
michael@0 298 /**
michael@0 299 * Renders the title of the stack: e.g. "Late Write #1" or
michael@0 300 * "Hang Report #1 (6 seconds)".
michael@0 301 *
michael@0 302 * @param aFormatArgs formating args to be passed to formatStringFromName.
michael@0 303 */
michael@0 304 renderHeader: function StackRenderer_renderHeader(aPrefix, aFormatArgs) {
michael@0 305 let div = document.getElementById(aPrefix + "-data");
michael@0 306
michael@0 307 let titleElement = document.createElement("span");
michael@0 308 titleElement.className = "stack-title";
michael@0 309
michael@0 310 let titleText = bundle.formatStringFromName(
michael@0 311 aPrefix + "-title", aFormatArgs, aFormatArgs.length);
michael@0 312 titleElement.appendChild(document.createTextNode(titleText));
michael@0 313
michael@0 314 div.appendChild(titleElement);
michael@0 315 div.appendChild(document.createElement("br"));
michael@0 316 }
michael@0 317 };
michael@0 318
michael@0 319 function SymbolicationRequest(aPrefix, aRenderHeader, aMemoryMap, aStacks) {
michael@0 320 this.prefix = aPrefix;
michael@0 321 this.renderHeader = aRenderHeader;
michael@0 322 this.memoryMap = aMemoryMap;
michael@0 323 this.stacks = aStacks;
michael@0 324 }
michael@0 325 /**
michael@0 326 * A callback for onreadystatechange. It replaces the numeric stack with
michael@0 327 * the symbolicated one returned by the symbolication server.
michael@0 328 */
michael@0 329 SymbolicationRequest.prototype.handleSymbolResponse =
michael@0 330 function SymbolicationRequest_handleSymbolResponse() {
michael@0 331 if (this.symbolRequest.readyState != 4)
michael@0 332 return;
michael@0 333
michael@0 334 let fetchElement = document.getElementById(this.prefix + "-fetch-symbols");
michael@0 335 fetchElement.classList.add("hidden");
michael@0 336 let hideElement = document.getElementById(this.prefix + "-hide-symbols");
michael@0 337 hideElement.classList.remove("hidden");
michael@0 338 let div = document.getElementById(this.prefix + "-data");
michael@0 339 clearDivData(div);
michael@0 340 let errorMessage = bundle.GetStringFromName("errorFetchingSymbols");
michael@0 341
michael@0 342 if (this.symbolRequest.status != 200) {
michael@0 343 div.appendChild(document.createTextNode(errorMessage));
michael@0 344 return;
michael@0 345 }
michael@0 346
michael@0 347 let jsonResponse = {};
michael@0 348 try {
michael@0 349 jsonResponse = JSON.parse(this.symbolRequest.responseText);
michael@0 350 } catch (e) {
michael@0 351 div.appendChild(document.createTextNode(errorMessage));
michael@0 352 return;
michael@0 353 }
michael@0 354
michael@0 355 for (let i = 0; i < jsonResponse.length; ++i) {
michael@0 356 let stack = jsonResponse[i];
michael@0 357 this.renderHeader(i);
michael@0 358
michael@0 359 for (let symbol of stack) {
michael@0 360 div.appendChild(document.createTextNode(symbol));
michael@0 361 div.appendChild(document.createElement("br"));
michael@0 362 }
michael@0 363 div.appendChild(document.createElement("br"));
michael@0 364 }
michael@0 365 };
michael@0 366 /**
michael@0 367 * Send a request to the symbolication server to symbolicate this stack.
michael@0 368 */
michael@0 369 SymbolicationRequest.prototype.fetchSymbols =
michael@0 370 function SymbolicationRequest_fetchSymbols() {
michael@0 371 let symbolServerURI =
michael@0 372 getPref(PREF_SYMBOL_SERVER_URI, DEFAULT_SYMBOL_SERVER_URI);
michael@0 373 let request = {"memoryMap" : this.memoryMap, "stacks" : this.stacks,
michael@0 374 "version" : 3};
michael@0 375 let requestJSON = JSON.stringify(request);
michael@0 376
michael@0 377 this.symbolRequest = new XMLHttpRequest();
michael@0 378 this.symbolRequest.open("POST", symbolServerURI, true);
michael@0 379 this.symbolRequest.setRequestHeader("Content-type", "application/json");
michael@0 380 this.symbolRequest.setRequestHeader("Content-length",
michael@0 381 requestJSON.length);
michael@0 382 this.symbolRequest.setRequestHeader("Connection", "close");
michael@0 383 this.symbolRequest.onreadystatechange = this.handleSymbolResponse.bind(this);
michael@0 384 this.symbolRequest.send(requestJSON);
michael@0 385 }
michael@0 386
michael@0 387 let ChromeHangs = {
michael@0 388
michael@0 389 symbolRequest: null,
michael@0 390
michael@0 391 /**
michael@0 392 * Renders raw chrome hang data
michael@0 393 */
michael@0 394 render: function ChromeHangs_render() {
michael@0 395 let hangs = Telemetry.chromeHangs;
michael@0 396 let stacks = hangs.stacks;
michael@0 397 let memoryMap = hangs.memoryMap;
michael@0 398
michael@0 399 StackRenderer.renderStacks("chrome-hangs", stacks, memoryMap,
michael@0 400 this.renderHangHeader);
michael@0 401 },
michael@0 402
michael@0 403 renderHangHeader: function ChromeHangs_renderHangHeader(aIndex) {
michael@0 404 let durations = Telemetry.chromeHangs.durations;
michael@0 405 StackRenderer.renderHeader("chrome-hangs", [aIndex + 1, durations[aIndex]]);
michael@0 406 }
michael@0 407 };
michael@0 408
michael@0 409 let ThreadHangStats = {
michael@0 410
michael@0 411 /**
michael@0 412 * Renders raw thread hang stats data
michael@0 413 */
michael@0 414 render: function() {
michael@0 415 let div = document.getElementById("thread-hang-stats");
michael@0 416 clearDivData(div);
michael@0 417
michael@0 418 let stats = Telemetry.threadHangStats;
michael@0 419 stats.forEach((thread) => {
michael@0 420 div.appendChild(this.renderThread(thread));
michael@0 421 });
michael@0 422 if (stats.length) {
michael@0 423 setHasData("thread-hang-stats-section", true);
michael@0 424 }
michael@0 425 },
michael@0 426
michael@0 427 /**
michael@0 428 * Creates and fills data corresponding to a thread
michael@0 429 */
michael@0 430 renderThread: function(aThread) {
michael@0 431 let div = document.createElement("div");
michael@0 432
michael@0 433 let title = document.createElement("h2");
michael@0 434 title.textContent = aThread.name;
michael@0 435 div.appendChild(title);
michael@0 436
michael@0 437 // Don't localize the histogram name, because the
michael@0 438 // name is also used as the div element's ID
michael@0 439 Histogram.render(div, aThread.name + "-Activity",
michael@0 440 aThread.activity, {exponential: true});
michael@0 441 aThread.hangs.forEach((hang, index) => {
michael@0 442 let hangName = aThread.name + "-Hang-" + (index + 1);
michael@0 443 let hangDiv = Histogram.render(
michael@0 444 div, hangName, hang.histogram, {exponential: true});
michael@0 445 let stackDiv = document.createElement("div");
michael@0 446 hang.stack.forEach((frame) => {
michael@0 447 stackDiv.appendChild(document.createTextNode(frame));
michael@0 448 // Leave an extra <br> at the end of the stack listing
michael@0 449 stackDiv.appendChild(document.createElement("br"));
michael@0 450 });
michael@0 451 // Insert stack after the histogram title
michael@0 452 hangDiv.insertBefore(stackDiv, hangDiv.childNodes[1]);
michael@0 453 });
michael@0 454 return div;
michael@0 455 },
michael@0 456 };
michael@0 457
michael@0 458 let Histogram = {
michael@0 459
michael@0 460 hgramSamplesCaption: bundle.GetStringFromName("histogramSamples"),
michael@0 461
michael@0 462 hgramAverageCaption: bundle.GetStringFromName("histogramAverage"),
michael@0 463
michael@0 464 hgramSumCaption: bundle.GetStringFromName("histogramSum"),
michael@0 465
michael@0 466 hgramCopyCaption: bundle.GetStringFromName("histogramCopy"),
michael@0 467
michael@0 468 /**
michael@0 469 * Renders a single Telemetry histogram
michael@0 470 *
michael@0 471 * @param aParent Parent element
michael@0 472 * @param aName Histogram name
michael@0 473 * @param aHgram Histogram information
michael@0 474 * @param aOptions Object with render options
michael@0 475 * * exponential: bars follow logarithmic scale
michael@0 476 */
michael@0 477 render: function Histogram_render(aParent, aName, aHgram, aOptions) {
michael@0 478 let hgram = this.unpack(aHgram);
michael@0 479 let options = aOptions || {};
michael@0 480
michael@0 481 let outerDiv = document.createElement("div");
michael@0 482 outerDiv.className = "histogram";
michael@0 483 outerDiv.id = aName;
michael@0 484
michael@0 485 let divTitle = document.createElement("div");
michael@0 486 divTitle.className = "histogram-title";
michael@0 487 divTitle.appendChild(document.createTextNode(aName));
michael@0 488 outerDiv.appendChild(divTitle);
michael@0 489
michael@0 490 let stats = hgram.sample_count + " " + this.hgramSamplesCaption + ", " +
michael@0 491 this.hgramAverageCaption + " = " + hgram.pretty_average + ", " +
michael@0 492 this.hgramSumCaption + " = " + hgram.sum;
michael@0 493
michael@0 494 let divStats = document.createElement("div");
michael@0 495 divStats.appendChild(document.createTextNode(stats));
michael@0 496 outerDiv.appendChild(divStats);
michael@0 497
michael@0 498 if (isRTL())
michael@0 499 hgram.values.reverse();
michael@0 500
michael@0 501 let textData = this.renderValues(outerDiv, hgram.values, hgram.max,
michael@0 502 hgram.sample_count, options);
michael@0 503
michael@0 504 // The 'Copy' button contains the textual data, copied to clipboard on click
michael@0 505 let copyButton = document.createElement("button");
michael@0 506 copyButton.className = "copy-node";
michael@0 507 copyButton.appendChild(document.createTextNode(this.hgramCopyCaption));
michael@0 508 copyButton.histogramText = aName + EOL + stats + EOL + EOL + textData;
michael@0 509 copyButton.addEventListener("click", function(){
michael@0 510 Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper)
michael@0 511 .copyString(this.histogramText);
michael@0 512 });
michael@0 513 outerDiv.appendChild(copyButton);
michael@0 514
michael@0 515 aParent.appendChild(outerDiv);
michael@0 516 return outerDiv;
michael@0 517 },
michael@0 518
michael@0 519 /**
michael@0 520 * Unpacks histogram values
michael@0 521 *
michael@0 522 * @param aHgram Packed histogram
michael@0 523 *
michael@0 524 * @return Unpacked histogram representation
michael@0 525 */
michael@0 526 unpack: function Histogram_unpack(aHgram) {
michael@0 527 let sample_count = aHgram.counts.reduceRight(function (a, b) a + b);
michael@0 528 let buckets = [0, 1];
michael@0 529 if (aHgram.histogram_type != Telemetry.HISTOGRAM_BOOLEAN) {
michael@0 530 buckets = aHgram.ranges;
michael@0 531 }
michael@0 532
michael@0 533 let average = Math.round(aHgram.sum * 10 / sample_count) / 10;
michael@0 534 let max_value = Math.max.apply(Math, aHgram.counts);
michael@0 535
michael@0 536 let first = true;
michael@0 537 let last = 0;
michael@0 538 let values = [];
michael@0 539 for (let i = 0; i < buckets.length; i++) {
michael@0 540 let count = aHgram.counts[i];
michael@0 541 if (!count)
michael@0 542 continue;
michael@0 543 if (first) {
michael@0 544 first = false;
michael@0 545 if (i) {
michael@0 546 values.push([buckets[i - 1], 0]);
michael@0 547 }
michael@0 548 }
michael@0 549 last = i + 1;
michael@0 550 values.push([buckets[i], count]);
michael@0 551 }
michael@0 552 if (last && last < buckets.length) {
michael@0 553 values.push([buckets[last], 0]);
michael@0 554 }
michael@0 555
michael@0 556 let result = {
michael@0 557 values: values,
michael@0 558 pretty_average: average,
michael@0 559 max: max_value,
michael@0 560 sample_count: sample_count,
michael@0 561 sum: aHgram.sum
michael@0 562 };
michael@0 563
michael@0 564 return result;
michael@0 565 },
michael@0 566
michael@0 567 /**
michael@0 568 * Return a non-negative, logarithmic representation of a non-negative number.
michael@0 569 * e.g. 0 => 0, 1 => 1, 10 => 2, 100 => 3
michael@0 570 *
michael@0 571 * @param aNumber Non-negative number
michael@0 572 */
michael@0 573 getLogValue: function(aNumber) {
michael@0 574 return Math.max(0, Math.log10(aNumber) + 1);
michael@0 575 },
michael@0 576
michael@0 577 /**
michael@0 578 * Create histogram HTML bars, also returns a textual representation
michael@0 579 * Both aMaxValue and aSumValues must be positive.
michael@0 580 * Values are assumed to use 0 as baseline.
michael@0 581 *
michael@0 582 * @param aDiv Outer parent div
michael@0 583 * @param aValues Histogram values
michael@0 584 * @param aMaxValue Value of the longest bar (length, not label)
michael@0 585 * @param aSumValues Sum of all bar values
michael@0 586 * @param aOptions Object with render options (@see #render)
michael@0 587 */
michael@0 588 renderValues: function Histogram_renderValues(aDiv, aValues, aMaxValue, aSumValues, aOptions) {
michael@0 589 let text = "";
michael@0 590 // If the last label is not the longest string, alignment will break a little
michael@0 591 let labelPadTo = String(aValues[aValues.length -1][0]).length;
michael@0 592 let maxBarValue = aOptions.exponential ? this.getLogValue(aMaxValue) : aMaxValue;
michael@0 593
michael@0 594 for (let [label, value] of aValues) {
michael@0 595 let barValue = aOptions.exponential ? this.getLogValue(value) : value;
michael@0 596
michael@0 597 // Create a text representation: <right-aligned-label> |<bar-of-#><value> <percentage>
michael@0 598 text += EOL
michael@0 599 + " ".repeat(Math.max(0, labelPadTo - String(label).length)) + label // Right-aligned label
michael@0 600 + " |" + "#".repeat(Math.round(MAX_BAR_CHARS * barValue / maxBarValue)) // Bar
michael@0 601 + " " + value // Value
michael@0 602 + " " + Math.round(100 * value / aSumValues) + "%"; // Percentage
michael@0 603
michael@0 604 // Construct the HTML labels + bars
michael@0 605 let belowEm = Math.round(MAX_BAR_HEIGHT * (barValue / maxBarValue) * 10) / 10;
michael@0 606 let aboveEm = MAX_BAR_HEIGHT - belowEm;
michael@0 607
michael@0 608 let barDiv = document.createElement("div");
michael@0 609 barDiv.className = "bar";
michael@0 610 barDiv.style.paddingTop = aboveEm + "em";
michael@0 611
michael@0 612 // Add value label or an nbsp if no value
michael@0 613 barDiv.appendChild(document.createTextNode(value ? value : '\u00A0'));
michael@0 614
michael@0 615 // Create the blue bar
michael@0 616 let bar = document.createElement("div");
michael@0 617 bar.className = "bar-inner";
michael@0 618 bar.style.height = belowEm + "em";
michael@0 619 barDiv.appendChild(bar);
michael@0 620
michael@0 621 // Add bucket label
michael@0 622 barDiv.appendChild(document.createTextNode(label));
michael@0 623
michael@0 624 aDiv.appendChild(barDiv);
michael@0 625 }
michael@0 626
michael@0 627 return text.substr(EOL.length); // Trim the EOL before the first line
michael@0 628 },
michael@0 629
michael@0 630 /**
michael@0 631 * Helper function for filtering histogram elements by their id
michael@0 632 * Adds the "filter-blocked" class to histogram nodes whose IDs don't match the filter.
michael@0 633 *
michael@0 634 * @param aContainerNode Container node containing the histogram class nodes to filter
michael@0 635 * @param aFilterText either text or /RegEx/. If text, case-insensitive and AND words
michael@0 636 */
michael@0 637 filterHistograms: function _filterHistograms(aContainerNode, aFilterText) {
michael@0 638 let filter = aFilterText.toString();
michael@0 639
michael@0 640 // Pass if: all non-empty array items match (case-sensitive)
michael@0 641 function isPassText(subject, filter) {
michael@0 642 for (let item of filter) {
michael@0 643 if (item.length && subject.indexOf(item) < 0) {
michael@0 644 return false; // mismatch and not a spurious space
michael@0 645 }
michael@0 646 }
michael@0 647 return true;
michael@0 648 }
michael@0 649
michael@0 650 function isPassRegex(subject, filter) {
michael@0 651 return filter.test(subject);
michael@0 652 }
michael@0 653
michael@0 654 // Setup normalized filter string (trimmed, lower cased and split on spaces if not RegEx)
michael@0 655 let isPassFunc; // filter function, set once, then applied to all elements
michael@0 656 filter = filter.trim();
michael@0 657 if (filter[0] != "/") { // Plain text: case insensitive, AND if multi-string
michael@0 658 isPassFunc = isPassText;
michael@0 659 filter = filter.toLowerCase().split(" ");
michael@0 660 } else {
michael@0 661 isPassFunc = isPassRegex;
michael@0 662 var r = filter.match(/^\/(.*)\/(i?)$/);
michael@0 663 try {
michael@0 664 filter = RegExp(r[1], r[2]);
michael@0 665 }
michael@0 666 catch (e) { // Incomplete or bad RegExp - always no match
michael@0 667 isPassFunc = function() {
michael@0 668 return false;
michael@0 669 };
michael@0 670 }
michael@0 671 }
michael@0 672
michael@0 673 let needLower = (isPassFunc === isPassText);
michael@0 674
michael@0 675 let histograms = aContainerNode.getElementsByClassName("histogram");
michael@0 676 for (let hist of histograms) {
michael@0 677 hist.classList[isPassFunc((needLower ? hist.id.toLowerCase() : hist.id), filter) ? "remove" : "add"]("filter-blocked");
michael@0 678 }
michael@0 679 },
michael@0 680
michael@0 681 /**
michael@0 682 * Event handler for change at histograms filter input
michael@0 683 *
michael@0 684 * When invoked, 'this' is expected to be the filter HTML node.
michael@0 685 */
michael@0 686 histogramFilterChanged: function _histogramFilterChanged() {
michael@0 687 if (this.idleTimeout) {
michael@0 688 clearTimeout(this.idleTimeout);
michael@0 689 }
michael@0 690
michael@0 691 this.idleTimeout = setTimeout( () => {
michael@0 692 Histogram.filterHistograms(document.getElementById(this.getAttribute("target_id")), this.value);
michael@0 693 }, FILTER_IDLE_TIMEOUT);
michael@0 694 }
michael@0 695 };
michael@0 696
michael@0 697 /*
michael@0 698 * Helper function to render JS objects with white space between top level elements
michael@0 699 * so that they look better in the browser
michael@0 700 * @param aObject JavaScript object or array to render
michael@0 701 * @return String
michael@0 702 */
michael@0 703 function RenderObject(aObject) {
michael@0 704 let output = "";
michael@0 705 if (Array.isArray(aObject)) {
michael@0 706 if (aObject.length == 0) {
michael@0 707 return "[]";
michael@0 708 }
michael@0 709 output = "[" + JSON.stringify(aObject[0]);
michael@0 710 for (let i = 1; i < aObject.length; i++) {
michael@0 711 output += ", " + JSON.stringify(aObject[i]);
michael@0 712 }
michael@0 713 return output + "]";
michael@0 714 }
michael@0 715 let keys = Object.keys(aObject);
michael@0 716 if (keys.length == 0) {
michael@0 717 return "{}";
michael@0 718 }
michael@0 719 output = "{\"" + keys[0] + "\":\u00A0" + JSON.stringify(aObject[keys[0]]);
michael@0 720 for (let i = 1; i < keys.length; i++) {
michael@0 721 output += ", \"" + keys[i] + "\":\u00A0" + JSON.stringify(aObject[keys[i]]);
michael@0 722 }
michael@0 723 return output + "}";
michael@0 724 };
michael@0 725
michael@0 726 let KeyValueTable = {
michael@0 727 /**
michael@0 728 * Returns a 2-column table with keys and values
michael@0 729 * @param aMeasurements Each key in this JS object is rendered as a row in
michael@0 730 * the table with its corresponding value
michael@0 731 * @param aKeysLabel Column header for the keys column
michael@0 732 * @param aValuesLabel Column header for the values column
michael@0 733 */
michael@0 734 render: function KeyValueTable_render(aMeasurements, aKeysLabel, aValuesLabel) {
michael@0 735 let table = document.createElement("table");
michael@0 736 this.renderHeader(table, aKeysLabel, aValuesLabel);
michael@0 737 this.renderBody(table, aMeasurements);
michael@0 738 return table;
michael@0 739 },
michael@0 740
michael@0 741 /**
michael@0 742 * Create the table header
michael@0 743 * Tabs & newlines added to cells to make it easier to copy-paste.
michael@0 744 *
michael@0 745 * @param aTable Table element
michael@0 746 * @param aKeysLabel Column header for the keys column
michael@0 747 * @param aValuesLabel Column header for the values column
michael@0 748 */
michael@0 749 renderHeader: function KeyValueTable_renderHeader(aTable, aKeysLabel, aValuesLabel) {
michael@0 750 let headerRow = document.createElement("tr");
michael@0 751 aTable.appendChild(headerRow);
michael@0 752
michael@0 753 let keysColumn = document.createElement("th");
michael@0 754 keysColumn.appendChild(document.createTextNode(aKeysLabel + "\t"));
michael@0 755 let valuesColumn = document.createElement("th");
michael@0 756 valuesColumn.appendChild(document.createTextNode(aValuesLabel + "\n"));
michael@0 757
michael@0 758 headerRow.appendChild(keysColumn);
michael@0 759 headerRow.appendChild(valuesColumn);
michael@0 760 },
michael@0 761
michael@0 762 /**
michael@0 763 * Create the table body
michael@0 764 * Tabs & newlines added to cells to make it easier to copy-paste.
michael@0 765 *
michael@0 766 * @param aTable Table element
michael@0 767 * @param aMeasurements Key/value map
michael@0 768 */
michael@0 769 renderBody: function KeyValueTable_renderBody(aTable, aMeasurements) {
michael@0 770 for (let [key, value] of Iterator(aMeasurements)) {
michael@0 771 // use .valueOf() to unbox Number, String, etc. objects
michael@0 772 if ((typeof value == "object") && (typeof value.valueOf() == "object")) {
michael@0 773 value = RenderObject(value);
michael@0 774 }
michael@0 775
michael@0 776 let newRow = document.createElement("tr");
michael@0 777 aTable.appendChild(newRow);
michael@0 778
michael@0 779 let keyField = document.createElement("td");
michael@0 780 keyField.appendChild(document.createTextNode(key + "\t"));
michael@0 781 newRow.appendChild(keyField);
michael@0 782
michael@0 783 let valueField = document.createElement("td");
michael@0 784 valueField.appendChild(document.createTextNode(value + "\n"));
michael@0 785 newRow.appendChild(valueField);
michael@0 786 }
michael@0 787 }
michael@0 788 };
michael@0 789
michael@0 790 let AddonDetails = {
michael@0 791 tableIDTitle: bundle.GetStringFromName("addonTableID"),
michael@0 792 tableDetailsTitle: bundle.GetStringFromName("addonTableDetails"),
michael@0 793
michael@0 794 /**
michael@0 795 * Render the addon details section as a series of headers followed by key/value tables
michael@0 796 * @param aSections Object containing the details sections to render
michael@0 797 */
michael@0 798 render: function AddonDetails_render(aSections) {
michael@0 799 let addonSection = document.getElementById("addon-details");
michael@0 800 for (let provider in aSections) {
michael@0 801 let providerSection = document.createElement("h2");
michael@0 802 let titleText = bundle.formatStringFromName("addonProvider", [provider], 1);
michael@0 803 providerSection.appendChild(document.createTextNode(titleText));
michael@0 804 addonSection.appendChild(providerSection);
michael@0 805 addonSection.appendChild(
michael@0 806 KeyValueTable.render(aSections[provider],
michael@0 807 this.tableIDTitle, this.tableDetailsTitle));
michael@0 808 }
michael@0 809 }
michael@0 810 };
michael@0 811
michael@0 812 /**
michael@0 813 * Helper function for showing either the toggle element or "No data collected" message for a section
michael@0 814 *
michael@0 815 * @param aSectionID ID of the section element that needs to be changed
michael@0 816 * @param aHasData true (default) indicates that toggle should be displayed
michael@0 817 */
michael@0 818 function setHasData(aSectionID, aHasData) {
michael@0 819 let sectionElement = document.getElementById(aSectionID);
michael@0 820 sectionElement.classList[aHasData ? "add" : "remove"]("has-data");
michael@0 821 }
michael@0 822
michael@0 823 /**
michael@0 824 * Helper function that expands and collapses sections +
michael@0 825 * changes caption on the toggle text
michael@0 826 */
michael@0 827 function toggleSection(aEvent) {
michael@0 828 let parentElement = aEvent.target.parentElement;
michael@0 829 if (!parentElement.classList.contains("has-data")) {
michael@0 830 return; // nothing to toggle
michael@0 831 }
michael@0 832
michael@0 833 parentElement.classList.toggle("expanded");
michael@0 834
michael@0 835 // Store section opened/closed state in a hidden checkbox (which is then used on reload)
michael@0 836 let statebox = parentElement.getElementsByClassName("statebox")[0];
michael@0 837 statebox.checked = parentElement.classList.contains("expanded");
michael@0 838 }
michael@0 839
michael@0 840 /**
michael@0 841 * Sets the text of the page header based on a config pref + bundle strings
michael@0 842 */
michael@0 843 function setupPageHeader()
michael@0 844 {
michael@0 845 let serverOwner = getPref(PREF_TELEMETRY_SERVER_OWNER, "Mozilla");
michael@0 846 let brandName = brandBundle.GetStringFromName("brandFullName");
michael@0 847 let subtitleText = bundle.formatStringFromName(
michael@0 848 "pageSubtitle", [serverOwner, brandName], 2);
michael@0 849
michael@0 850 let subtitleElement = document.getElementById("page-subtitle");
michael@0 851 subtitleElement.appendChild(document.createTextNode(subtitleText));
michael@0 852 }
michael@0 853
michael@0 854 /**
michael@0 855 * Initializes load/unload, pref change and mouse-click listeners
michael@0 856 */
michael@0 857 function setupListeners() {
michael@0 858 Services.prefs.addObserver(PREF_TELEMETRY_ENABLED, observer, false);
michael@0 859 observer.updatePrefStatus();
michael@0 860
michael@0 861 // Clean up observers when page is closed
michael@0 862 window.addEventListener("unload",
michael@0 863 function unloadHandler(aEvent) {
michael@0 864 window.removeEventListener("unload", unloadHandler);
michael@0 865 Services.prefs.removeObserver(PREF_TELEMETRY_ENABLED, observer);
michael@0 866 }, false);
michael@0 867
michael@0 868 document.getElementById("toggle-telemetry").addEventListener("click",
michael@0 869 function () {
michael@0 870 let value = getPref(PREF_TELEMETRY_ENABLED, false);
michael@0 871 Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, !value);
michael@0 872 }, false);
michael@0 873
michael@0 874 document.getElementById("chrome-hangs-fetch-symbols").addEventListener("click",
michael@0 875 function () {
michael@0 876 let hangs = Telemetry.chromeHangs;
michael@0 877 let req = new SymbolicationRequest("chrome-hangs",
michael@0 878 ChromeHangs.renderHangHeader,
michael@0 879 hangs.memoryMap, hangs.stacks);
michael@0 880 req.fetchSymbols();
michael@0 881 }, false);
michael@0 882
michael@0 883 document.getElementById("chrome-hangs-hide-symbols").addEventListener("click",
michael@0 884 function () {
michael@0 885 ChromeHangs.render();
michael@0 886 }, false);
michael@0 887
michael@0 888 document.getElementById("late-writes-fetch-symbols").addEventListener("click",
michael@0 889 function () {
michael@0 890 let lateWrites = TelemetryPing.getPayload().lateWrites;
michael@0 891 let req = new SymbolicationRequest("late-writes",
michael@0 892 LateWritesSingleton.renderHeader,
michael@0 893 lateWrites.memoryMap,
michael@0 894 lateWrites.stacks);
michael@0 895 req.fetchSymbols();
michael@0 896 }, false);
michael@0 897
michael@0 898 document.getElementById("late-writes-hide-symbols").addEventListener("click",
michael@0 899 function () {
michael@0 900 let ping = TelemetryPing.getPayload();
michael@0 901 LateWritesSingleton.renderLateWrites(ping.lateWrites);
michael@0 902 }, false);
michael@0 903
michael@0 904
michael@0 905 // Clicking on the section name will toggle its state
michael@0 906 let sectionHeaders = document.getElementsByClassName("section-name");
michael@0 907 for (let sectionHeader of sectionHeaders) {
michael@0 908 sectionHeader.addEventListener("click", toggleSection, false);
michael@0 909 }
michael@0 910
michael@0 911 // Clicking on the "toggle" text will also toggle section's state
michael@0 912 let toggleLinks = document.getElementsByClassName("toggle-caption");
michael@0 913 for (let toggleLink of toggleLinks) {
michael@0 914 toggleLink.addEventListener("click", toggleSection, false);
michael@0 915 }
michael@0 916 }
michael@0 917
michael@0 918
michael@0 919 function onLoad() {
michael@0 920 window.removeEventListener("load", onLoad);
michael@0 921
michael@0 922 // Set the text in the page header
michael@0 923 setupPageHeader();
michael@0 924
michael@0 925 // Set up event listeners
michael@0 926 setupListeners();
michael@0 927
michael@0 928 // Show slow SQL stats
michael@0 929 SlowSQL.render();
michael@0 930
michael@0 931 // Show chrome hang stacks
michael@0 932 ChromeHangs.render();
michael@0 933
michael@0 934 // Show thread hang stats
michael@0 935 ThreadHangStats.render();
michael@0 936
michael@0 937 // Show histogram data
michael@0 938 let histograms = Telemetry.histogramSnapshots;
michael@0 939 if (Object.keys(histograms).length) {
michael@0 940 let hgramDiv = document.getElementById("histograms");
michael@0 941 for (let [name, hgram] of Iterator(histograms)) {
michael@0 942 Histogram.render(hgramDiv, name, hgram);
michael@0 943 }
michael@0 944
michael@0 945 let filterBox = document.getElementById("histograms-filter");
michael@0 946 filterBox.addEventListener("input", Histogram.histogramFilterChanged, false);
michael@0 947 if (filterBox.value.trim() != "") { // on load, no need to filter if empty
michael@0 948 Histogram.filterHistograms(hgramDiv, filterBox.value);
michael@0 949 }
michael@0 950
michael@0 951 setHasData("histograms-section", true);
michael@0 952 }
michael@0 953
michael@0 954 // Show addon histogram data
michael@0 955 let addonDiv = document.getElementById("addon-histograms");
michael@0 956 let addonHistogramsRendered = false;
michael@0 957 let addonData = Telemetry.addonHistogramSnapshots;
michael@0 958 for (let [addon, histograms] of Iterator(addonData)) {
michael@0 959 for (let [name, hgram] of Iterator(histograms)) {
michael@0 960 addonHistogramsRendered = true;
michael@0 961 Histogram.render(addonDiv, addon + ": " + name, hgram);
michael@0 962 }
michael@0 963 }
michael@0 964
michael@0 965 if (addonHistogramsRendered) {
michael@0 966 setHasData("addon-histograms-section", true);
michael@0 967 }
michael@0 968
michael@0 969 // Get the Telemetry Ping payload
michael@0 970 Telemetry.asyncFetchTelemetryData(displayPingData);
michael@0 971
michael@0 972 // Restore sections states
michael@0 973 let stateboxes = document.getElementsByClassName("statebox");
michael@0 974 for (let box of stateboxes) {
michael@0 975 if (box.checked) { // Was open. Will still display as empty if not has-data
michael@0 976 box.parentElement.classList.add("expanded");
michael@0 977 }
michael@0 978 }
michael@0 979 }
michael@0 980
michael@0 981 let LateWritesSingleton = {
michael@0 982 renderHeader: function LateWritesSingleton_renderHeader(aIndex) {
michael@0 983 StackRenderer.renderHeader("late-writes", [aIndex + 1]);
michael@0 984 },
michael@0 985
michael@0 986 renderLateWrites: function LateWritesSingleton_renderLateWrites(lateWrites) {
michael@0 987 let stacks = lateWrites.stacks;
michael@0 988 let memoryMap = lateWrites.memoryMap;
michael@0 989 StackRenderer.renderStacks('late-writes', stacks, memoryMap,
michael@0 990 LateWritesSingleton.renderHeader);
michael@0 991 }
michael@0 992 };
michael@0 993
michael@0 994 /**
michael@0 995 * Helper function for sorting the startup milestones in the Simple Measurements
michael@0 996 * section into temporal order.
michael@0 997 *
michael@0 998 * @param aSimpleMeasurements Telemetry ping's "Simple Measurements" data
michael@0 999 * @return Sorted measurements
michael@0 1000 */
michael@0 1001 function sortStartupMilestones(aSimpleMeasurements) {
michael@0 1002 const telemetryTimestamps = TelemetryTimestamps.get();
michael@0 1003 let startupEvents = Services.startup.getStartupInfo();
michael@0 1004 delete startupEvents['process'];
michael@0 1005
michael@0 1006 function keyIsMilestone(k) {
michael@0 1007 return (k in startupEvents) || (k in telemetryTimestamps);
michael@0 1008 }
michael@0 1009
michael@0 1010 let sortedKeys = Object.keys(aSimpleMeasurements);
michael@0 1011
michael@0 1012 // Sort the measurements, with startup milestones at the front + ordered by time
michael@0 1013 sortedKeys.sort(function keyCompare(keyA, keyB) {
michael@0 1014 let isKeyAMilestone = keyIsMilestone(keyA);
michael@0 1015 let isKeyBMilestone = keyIsMilestone(keyB);
michael@0 1016
michael@0 1017 // First order by startup vs non-startup measurement
michael@0 1018 if (isKeyAMilestone && !isKeyBMilestone)
michael@0 1019 return -1;
michael@0 1020 if (!isKeyAMilestone && isKeyBMilestone)
michael@0 1021 return 1;
michael@0 1022 // Don't change order of non-startup measurements
michael@0 1023 if (!isKeyAMilestone && !isKeyBMilestone)
michael@0 1024 return 0;
michael@0 1025
michael@0 1026 // If both keys are startup measurements, order them by value
michael@0 1027 return aSimpleMeasurements[keyA] - aSimpleMeasurements[keyB];
michael@0 1028 });
michael@0 1029
michael@0 1030 // Insert measurements into a result object in sort-order
michael@0 1031 let result = {};
michael@0 1032 for (let key of sortedKeys) {
michael@0 1033 result[key] = aSimpleMeasurements[key];
michael@0 1034 }
michael@0 1035
michael@0 1036 return result;
michael@0 1037 }
michael@0 1038
michael@0 1039 function displayPingData() {
michael@0 1040 let ping = TelemetryPing.getPayload();
michael@0 1041
michael@0 1042 let keysHeader = bundle.GetStringFromName("keysHeader");
michael@0 1043 let valuesHeader = bundle.GetStringFromName("valuesHeader");
michael@0 1044
michael@0 1045 // Show simple measurements
michael@0 1046 let simpleMeasurements = sortStartupMilestones(ping.simpleMeasurements);
michael@0 1047 if (Object.keys(simpleMeasurements).length) {
michael@0 1048 let simpleSection = document.getElementById("simple-measurements");
michael@0 1049 simpleSection.appendChild(KeyValueTable.render(simpleMeasurements,
michael@0 1050 keysHeader, valuesHeader));
michael@0 1051 setHasData("simple-measurements-section", true);
michael@0 1052 }
michael@0 1053
michael@0 1054 LateWritesSingleton.renderLateWrites(ping.lateWrites);
michael@0 1055
michael@0 1056 // Show basic system info gathered
michael@0 1057 if (Object.keys(ping.info).length) {
michael@0 1058 let infoSection = document.getElementById("system-info");
michael@0 1059 infoSection.appendChild(KeyValueTable.render(ping.info,
michael@0 1060 keysHeader, valuesHeader));
michael@0 1061 setHasData("system-info-section", true);
michael@0 1062 }
michael@0 1063
michael@0 1064 let addonDetails = ping.addonDetails;
michael@0 1065 if (Object.keys(addonDetails).length) {
michael@0 1066 AddonDetails.render(addonDetails);
michael@0 1067 setHasData("addon-details-section", true);
michael@0 1068 }
michael@0 1069 }
michael@0 1070
michael@0 1071 window.addEventListener("load", onLoad, false);

mercurial