diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp index 454770c7d74c..512a87f8723f 100644 --- a/docshell/base/nsAboutRedirector.cpp +++ b/docshell/base/nsAboutRedirector.cpp @@ -64,6 +64,8 @@ static RedirEntry kRedirMap[] = { nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT }, { "support", "chrome://global/content/aboutSupport.xhtml", + nsIAboutModule::ALLOW_SCRIPT }, + { "telemetry", "chrome://global/content/aboutTelemetry.xhtml", nsIAboutModule::ALLOW_SCRIPT } }; static const int kRedirTotal = NS_ARRAY_LENGTH(kRedirMap); diff --git a/docshell/build/nsDocShellModule.cpp b/docshell/build/nsDocShellModule.cpp index e1008a57e3cf..9dc5abbc4c2b 100644 --- a/docshell/build/nsDocShellModule.cpp +++ b/docshell/build/nsDocShellModule.cpp @@ -182,6 +182,7 @@ const mozilla::Module::ContractIDEntry kDocShellContracts[] = { { NS_ABOUT_MODULE_CONTRACTID_PREFIX "addons", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newaddon", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "support", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "telemetry", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_URI_LOADER_CONTRACTID, &kNS_URI_LOADER_CID }, { NS_DOCUMENTLOADER_SERVICE_CONTRACTID, &kNS_DOCUMENTLOADER_SERVICE_CID }, { NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &kNS_EXTERNALHELPERAPPSERVICE_CID }, diff --git a/toolkit/content/aboutTelemetry.css b/toolkit/content/aboutTelemetry.css new file mode 100644 index 000000000000..5f8e2ce82518 --- /dev/null +++ b/toolkit/content/aboutTelemetry.css @@ -0,0 +1,130 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +.hidden { + display: none; +} + +html { + background-color: -moz-Field; + color: -moz-FieldText; + font: message-box; +} + +body { + padding: 0px; + margin: 0px; +} + +h2 { + font-size: medium; +} + +#page-description { + background-color: LightGray; + border: 1px solid threedshadow; + margin: 0px; + padding: 10px; +} + +#description-enabled > span { + color: green; +} + +#description-disabled > span { + color: red; +} + +.data-section { + background-color: WhiteSmoke; + border-top: 1px solid threedshadow; + border-bottom: 1px solid threedshadow; + margin: 0px; + padding: 10px; +} + +.section-name { + font-size: x-large; + display: inline; + cursor: pointer; +} + +.data { + margin: 15px; +} + +.toggle-caption { + font-style: italic; + cursor: pointer; +} + +.empty-caption { + font-style: italic; +} + +.hang-title { + font-size: medium; + font-weight: bold; + text-decoration: underline; +} + +#histograms, #addon-histograms { + overflow: hidden; +} + +.histogram { + float: left; + border: 1px solid gray; + white-space: nowrap; + padding: 10px; +} + +body[dir="rtl"] .histogram { + float: right; +} + +.histogram-title { + text-overflow: ellipsis; + width: 100%; + white-space: nowrap; + overflow: hidden; +} + +.bar { + width: 2em; + margin: 2px; + text-align: center; + float: left; + font-family: monospace; +} + +body[dir="rtl"] .bar { + float: right; +} + +.bar-inner { + background-color: DeepSkyBlue; + border: 1px solid #0000b0; +} + +th { + font-weight: bold; + white-space: nowrap; + text-align: left; +} + +body[dir="rtl"] th { + text-align: right; +} + +caption { + font-weight: bold; + white-space: nowrap; + text-align: left; + font-size: large; +} + +body[dir="rtl"] caption { + text-align: right; +} diff --git a/toolkit/content/aboutTelemetry.js b/toolkit/content/aboutTelemetry.js new file mode 100644 index 000000000000..67ffab63a626 --- /dev/null +++ b/toolkit/content/aboutTelemetry.js @@ -0,0 +1,694 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); + +const Telemetry = Services.telemetry; +const bundle = Services.strings.createBundle( + "chrome://global/locale/aboutTelemetry.properties"); +const TelemetryPing = Cc["@mozilla.org/base/telemetry-ping;1"]. + getService(Ci.nsIObserver); + +// Maximum height of a histogram bar (in em) +const MAX_BAR_HEIGHT = 18; +const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled"; +const PREF_DEBUG_SLOW_SQL = "toolkit.telemetry.debugSlowSql"; +const PREF_SYMBOL_SERVER_URI = "profiler.symbolicationUrl"; +const DEFAULT_SYMBOL_SERVER_URI = "http://symbolapi.mozilla.org"; + +// Cached value of document's RTL mode +let documentRTLMode = ""; + +/** + * Helper function for fetching a config pref + * + * @param aPrefName Name of config pref to fetch. + * @param aDefault Default value to return if pref isn't set. + * @return Value of pref + */ +function getPref(aPrefName, aDefault) { + let result = aDefault; + + try { + let prefType = Services.prefs.getPrefType(aPrefName); + if (prefType == Ci.nsIPrefBranch.PREF_BOOL) { + result = Services.prefs.getBoolPref(aPrefName); + } else if (prefType == Ci.nsIPrefBranch.PREF_STRING) { + result = Services.prefs.getCharPref(aPrefName); + } + } catch (e) { + // Return default if Prefs service throws exception + } + + return result; +} + +/** + * Helper function for determining whether the document direction is RTL. + * Caches result of check on first invocation. + */ +function isRTL() { + if (!documentRTLMode) + documentRTLMode = window.getComputedStyle(document.body).direction; + return (documentRTLMode == "rtl"); +} + +let observer = { + + enableTelemetry: bundle.GetStringFromName("enableTelemetry"), + + disableTelemetry: bundle.GetStringFromName("disableTelemetry"), + + /** + * Observer is called whenever Telemetry is enabled or disabled + */ + observe: function observe(aSubject, aTopic, aData) { + if (aData == PREF_TELEMETRY_ENABLED) { + this.updatePrefStatus(); + } + }, + + /** + * Updates the button & text at the top of the page to reflect Telemetry state. + */ + updatePrefStatus: function updatePrefStatus() { + // Notify user whether Telemetry is enabled + let enabledElement = document.getElementById("description-enabled"); + let disabledElement = document.getElementById("description-disabled"); + let toggleElement = document.getElementById("toggle-telemetry"); + if (getPref(PREF_TELEMETRY_ENABLED, false)) { + enabledElement.classList.remove("hidden"); + disabledElement.classList.add("hidden"); + toggleElement.innerHTML = this.disableTelemetry; + } else { + enabledElement.classList.add("hidden"); + disabledElement.classList.remove("hidden"); + toggleElement.innerHTML = this.enableTelemetry; + } + } +}; + +let SlowSQL = { + + slowSqlHits: bundle.GetStringFromName("slowSqlHits"), + + slowSqlAverage: bundle.GetStringFromName("slowSqlAverage"), + + slowSqlStatement: bundle.GetStringFromName("slowSqlStatement"), + + mainThreadTitle: bundle.GetStringFromName("slowSqlMain"), + + otherThreadTitle: bundle.GetStringFromName("slowSqlOther"), + + /** + * Render slow SQL statistics + */ + render: function SlowSQL_render() { + let debugSlowSql = getPref(PREF_DEBUG_SLOW_SQL, false); + let {mainThread, otherThreads} = + Telemetry[debugSlowSql ? "debugSlowSQL" : "slowSQL"]; + + let mainThreadCount = Object.keys(mainThread).length; + let otherThreadCount = Object.keys(otherThreads).length; + if (mainThreadCount == 0 && otherThreadCount == 0) { + showEmptySectionMessage("slow-sql-section"); + return; + } + + if (debugSlowSql) { + document.getElementById("sql-warning").classList.remove("hidden"); + } + + let slowSqlDiv = document.getElementById("slow-sql-tables"); + + // Main thread + if (mainThreadCount > 0) { + let table = document.createElement("table"); + this.renderTableHeader(table, this.mainThreadTitle); + this.renderTable(table, mainThread); + + slowSqlDiv.appendChild(table); + slowSqlDiv.appendChild(document.createElement("hr")); + } + + // Other threads + if (otherThreadCount > 0) { + let table = document.createElement("table"); + this.renderTableHeader(table, this.otherThreadTitle); + this.renderTable(table, otherThreads); + + slowSqlDiv.appendChild(table); + slowSqlDiv.appendChild(document.createElement("hr")); + } + }, + + /** + * Creates a header row for a Slow SQL table + * + * @param aTable Parent table element + * @param aTitle Table's title + */ + renderTableHeader: function SlowSQL_renderTableHeader(aTable, aTitle) { + let caption = document.createElement("caption"); + caption.appendChild(document.createTextNode(aTitle)); + aTable.appendChild(caption); + + let headings = document.createElement("tr"); + this.appendColumn(headings, "th", this.slowSqlHits); + this.appendColumn(headings, "th", this.slowSqlAverage); + this.appendColumn(headings, "th", this.slowSqlStatement); + aTable.appendChild(headings); + }, + + /** + * Fills out the table body + * + * @param aTable Parent table element + * @param aSql SQL stats object + */ + renderTable: function SlowSQL_renderTable(aTable, aSql) { + for (let [sql, [hitCount, totalTime]] of Iterator(aSql)) { + let averageTime = totalTime / hitCount; + + let sqlRow = document.createElement("tr"); + + this.appendColumn(sqlRow, "td", hitCount); + this.appendColumn(sqlRow, "td", averageTime.toFixed(0)); + this.appendColumn(sqlRow, "td", sql); + + aTable.appendChild(sqlRow); + } + }, + + /** + * Helper function for appending a column to a Slow SQL table. + * + * @param aRowElement Parent row element + * @param aColType Column's tag name + * @param aColText Column contents + */ + appendColumn: function SlowSQL_appendColumn(aRowElement, aColType, aColText) { + let colElement = document.createElement(aColType); + let aColTextElement = document.createTextNode(aColText); + colElement.appendChild(aColTextElement); + aRowElement.appendChild(colElement); + } +}; + +let ChromeHangs = { + + symbolRequest: null, + + stackTitle: bundle.GetStringFromName("stackTitle"), + + memoryMapTitle: bundle.GetStringFromName("memoryMapTitle"), + + errorMessage: bundle.GetStringFromName("errorFetchingSymbols"), + + /** + * Renders raw chrome hang data + */ + render: function ChromeHangs_render() { + let hangsDiv = document.getElementById("chrome-hangs-data"); + this.clearHangData(hangsDiv); + document.getElementById("fetch-symbols").classList.remove("hidden"); + document.getElementById("hide-symbols").classList.add("hidden"); + + let hangs = Telemetry.chromeHangs; + if (hangs.length == 0) { + showEmptySectionMessage("chrome-hangs-section"); + return; + } + + this.renderMemoryMap(hangsDiv); + + for (let i = 0; i < hangs.length; ++i) { + let currentHang = hangs[i]; + this.renderHangHeader(hangsDiv, i + 1, currentHang.duration); + this.renderStack(hangsDiv, currentHang.stack) + } + }, + + /** + * Renders the title of the hang: e.g. "Hang Report #1 (6 seconds)" + * + * @param aDiv Output div + * @param aIndex The number of the hang + * @param aDuration The duration of the hang + */ + renderHangHeader: function ChromeHangs_renderHangHeader(aDiv, aIndex, aDuration) { + let titleElement = document.createElement("span"); + titleElement.className = "hang-title"; + + let titleText = bundle.formatStringFromName( + "hangTitle", [aIndex, aDuration], 2); + titleElement.appendChild(document.createTextNode(titleText)); + + aDiv.appendChild(titleElement); + aDiv.appendChild(document.createElement("br")); + }, + + /** + * Outputs the raw PCs from the hang's stack + * + * @param aDiv Output div + * @param aStack Array of PCs from the hang stack + */ + renderStack: function ChromeHangs_renderStack(aDiv, aStack) { + aDiv.appendChild(document.createTextNode(this.stackTitle)); + let stackText = " " + aStack.join(" "); + aDiv.appendChild(document.createTextNode(stackText)); + + aDiv.appendChild(document.createElement("br")); + aDiv.appendChild(document.createElement("br")); + }, + + /** + * Outputs the memory map associated with this hang report + * + * @param aDiv Output div + */ + renderMemoryMap: function ChromeHangs_renderMemoryMap(aDiv) { + aDiv.appendChild(document.createTextNode(this.memoryMapTitle)); + aDiv.appendChild(document.createElement("br")); + + let singleMemoryMap = Telemetry.chromeHangs[0].memoryMap; + for (let currentModule of singleMemoryMap) { + aDiv.appendChild(document.createTextNode(currentModule.join(" "))); + aDiv.appendChild(document.createElement("br")); + } + + aDiv.appendChild(document.createElement("br")); + }, + + /** + * Sends a symbolication request for the recorded hangs + */ + fetchSymbols: function ChromeHangs_fetchSymbols() { + let symbolServerURI = + getPref(PREF_SYMBOL_SERVER_URI, DEFAULT_SYMBOL_SERVER_URI); + + let chromeHangsJSON = JSON.stringify(Telemetry.chromeHangs); + + this.symbolRequest = XMLHttpRequest(); + this.symbolRequest.open("POST", symbolServerURI, true); + this.symbolRequest.setRequestHeader("Content-type", "application/json"); + this.symbolRequest.setRequestHeader("Content-length", chromeHangsJSON.length); + this.symbolRequest.setRequestHeader("Connection", "close"); + + this.symbolRequest.onreadystatechange = this.handleSymbolResponse.bind(this); + this.symbolRequest.send(chromeHangsJSON); + }, + + /** + * Called when the 'readyState' of the XMLHttpRequest changes. We only care + * about state 4 ("completed") - handling the response data. + */ + handleSymbolResponse: function ChromeHangs_handleSymbolResponse() { + if (this.symbolRequest.readyState != 4) + return; + + document.getElementById("fetch-symbols").classList.add("hidden"); + document.getElementById("hide-symbols").classList.remove("hidden"); + + let hangsDiv = document.getElementById("chrome-hangs-data"); + this.clearHangData(hangsDiv); + + if (this.symbolRequest.status != 200) { + hangsDiv.appendChild(document.createTextNode(this.errorMessage)); + return; + } + + let jsonResponse = {}; + try { + jsonResponse = JSON.parse(this.symbolRequest.responseText); + } catch (e) { + hangsDiv.appendChild(document.createTextNode(this.errorMessage)); + return; + } + + let hangs = Telemetry.chromeHangs; + for (let i = 0; i < jsonResponse.length; ++i) { + let stack = jsonResponse[i]; + let hangDuration = hangs[i].duration; + this.renderHangHeader(hangsDiv, i + 1, hangDuration); + + for (let symbol of stack) { + hangsDiv.appendChild(document.createTextNode(symbol)); + hangsDiv.appendChild(document.createElement("br")); + } + hangsDiv.appendChild(document.createElement("br")); + } + }, + + /** + * Removes child elements from the supplied div + * + * @param aDiv Element to be cleared + */ + clearHangData: function ChromeHangs_clearHangData(aDiv) { + while (aDiv.hasChildNodes()) { + aDiv.removeChild(aDiv.lastChild); + } + } +}; + +let Histogram = { + + hgramSamplesCaption: bundle.GetStringFromName("histogramSamples"), + + hgramAverageCaption: bundle.GetStringFromName("histogramAverage"), + + hgramSumCaption: bundle.GetStringFromName("histogramSum"), + + /** + * Renders a single Telemetry histogram + * + * @param aParent Parent element + * @param aName Histogram name + * @param aHgram Histogram information + */ + render: function Histogram_render(aParent, aName, aHgram) { + let hgram = this.unpack(aHgram); + + let outerDiv = document.createElement("div"); + outerDiv.className = "histogram"; + outerDiv.id = aName; + + let divTitle = document.createElement("div"); + divTitle.className = "histogram-title"; + divTitle.appendChild(document.createTextNode(aName)); + outerDiv.appendChild(divTitle); + + let stats = hgram.sample_count + " " + this.hgramSamplesCaption + ", " + + this.hgramAverageCaption + " = " + hgram.pretty_average + ", " + + this.hgramSumCaption + " = " + hgram.sum; + + let divStats = document.createElement("div"); + divStats.appendChild(document.createTextNode(stats)); + outerDiv.appendChild(divStats); + + if (isRTL()) + hgram.values.reverse(); + + this.renderValues(outerDiv, hgram.values, hgram.max); + + aParent.appendChild(outerDiv); + }, + + /** + * Unpacks histogram values + * + * @param aHgram Packed histogram + * + * @return Unpacked histogram representation + */ + unpack: function Histogram_unpack(aHgram) { + let sample_count = aHgram.counts.reduceRight(function (a, b) a + b); + let buckets = [0, 1]; + if (aHgram.histogram_type != Telemetry.HISTOGRAM_BOOLEAN) { + buckets = aHgram.ranges; + } + + let average = Math.round(aHgram.sum * 10 / sample_count) / 10; + let max_value = Math.max.apply(Math, aHgram.counts); + + let first = true; + let last = 0; + let values = []; + for (let i = 0; i < buckets.length; i++) { + let count = aHgram.counts[i]; + if (!count) + continue; + if (first) { + first = false; + if (i) { + values.push([buckets[i - 1], 0]); + } + } + last = i + 1; + values.push([buckets[i], count]); + } + if (last && last < buckets.length) { + values.push([buckets[last], 0]); + } + + let result = { + values: values, + pretty_average: average, + max: max_value, + sample_count: sample_count, + sum: aHgram.sum + }; + + return result; + }, + + /** + * Create histogram bars + * + * @param aDiv Outer parent div + * @param aValues Histogram values + * @param aMaxValue Largest histogram value in set + */ + renderValues: function Histogram_renderValues(aDiv, aValues, aMaxValue) { + for (let [label, value] of aValues) { + let belowEm = Math.round(MAX_BAR_HEIGHT * (value / aMaxValue) * 10) / 10; + let aboveEm = MAX_BAR_HEIGHT - belowEm; + + let barDiv = document.createElement("div"); + barDiv.className = "bar"; + barDiv.style.paddingTop = aboveEm + "em"; + + // Add value label or an nbsp if no value + barDiv.appendChild(document.createTextNode(value ? value : '\u00A0')); + + // Create the blue bar + let bar = document.createElement("div"); + bar.className = "bar-inner"; + bar.style.height = belowEm + "em"; + barDiv.appendChild(bar); + + // Add bucket label + barDiv.appendChild(document.createTextNode(label)); + + aDiv.appendChild(barDiv); + } + } +}; + +let KeyValueTable = { + + keysHeader: bundle.GetStringFromName("keysHeader"), + + valuesHeader: bundle.GetStringFromName("valuesHeader"), + + /** + * Fill out a 2-column table with keys and values + */ + render: function KeyValueTable_render(aTableID, aMeasurements) { + let table = document.getElementById(aTableID); + this.renderHeader(table); + this.renderBody(table, aMeasurements); + }, + + /** + * Create the table header + * + * @param aTable Table element + */ + renderHeader: function KeyValueTable_renderHeader(aTable) { + let headerRow = document.createElement("tr"); + aTable.appendChild(headerRow); + + let keysColumn = document.createElement("th"); + keysColumn.appendChild(document.createTextNode(this.keysHeader)); + let valuesColumn = document.createElement("th"); + valuesColumn.appendChild(document.createTextNode(this.valuesHeader)); + + headerRow.appendChild(keysColumn); + headerRow.appendChild(valuesColumn); + }, + + /** + * Create the table body + * + * @param aTable Table element + * @param aMeasurements Key/value map + */ + renderBody: function KeyValueTable_renderBody(aTable, aMeasurements) { + for (let [key, value] of Iterator(aMeasurements)) { + if (typeof value == "object") { + value = JSON.stringify(value); + } + + let newRow = document.createElement("tr"); + aTable.appendChild(newRow); + + let keyField = document.createElement("td"); + keyField.appendChild(document.createTextNode(key)); + newRow.appendChild(keyField); + + let valueField = document.createElement("td"); + valueField.appendChild(document.createTextNode(value)); + newRow.appendChild(valueField); + } + } +}; + +/** + * Helper function for showing "No data collected" message for a section + * + * @param aSectionID ID of the section element that needs to be changed + */ +function showEmptySectionMessage(aSectionID) { + let sectionElement = document.getElementById(aSectionID); + + // Hide toggle captions + let toggleElements = sectionElement.getElementsByClassName("toggle-caption"); + toggleElements[0].classList.add("hidden"); + toggleElements[1].classList.add("hidden"); + + // Show "No data collected" message + let messageElement = sectionElement.getElementsByClassName("empty-caption")[0]; + messageElement.classList.remove("hidden"); + + // Don't allow section to be expanded by clicking on the header text + let sectionHeaders = sectionElement.getElementsByClassName("section-name"); + for (let sectionHeader of sectionHeaders) { + sectionHeader.removeEventListener("click", toggleSection); + sectionHeader.style.cursor = "auto"; + } + + // Don't allow section to be expanded by clicking on the toggle text + let toggleLinks = sectionElement.getElementsByClassName("toggle-caption"); + for (let toggleLink of toggleLinks) { + toggleLink.removeEventListener("click", toggleSection); + } +} + +/** + * Helper function that expands and collapses sections + + * changes caption on the toggle text + */ +function toggleSection(aEvent) { + let parentElement = aEvent.target.parentElement; + let sectionDiv = parentElement.getElementsByTagName("div")[0]; + sectionDiv.classList.toggle("hidden"); + + let toggleLinks = parentElement.getElementsByClassName("toggle-caption"); + toggleLinks[0].classList.toggle("hidden"); + toggleLinks[1].classList.toggle("hidden"); +} + + +/** + * Initializes load/unload, pref change and mouse-click listeners + */ +function setupListeners() { + Services.prefs.addObserver(PREF_TELEMETRY_ENABLED, observer, false); + observer.updatePrefStatus(); + + // Clean up observers when page is closed + window.addEventListener("unload", + function unloadHandler(aEvent) { + window.removeEventListener("unload", unloadHandler); + Services.prefs.removeObserver(PREF_TELEMETRY_ENABLED, observer); + }, false); + + document.getElementById("toggle-telemetry").addEventListener("click", + function () { + let value = getPref(PREF_TELEMETRY_ENABLED, false); + Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, !value); + }, false); + + document.getElementById("fetch-symbols").addEventListener("click", + function () { + ChromeHangs.fetchSymbols(); + }, false); + + document.getElementById("hide-symbols").addEventListener("click", + function () { + ChromeHangs.render(); + }, false); + + // Clicking on the section name will toggle its state + let sectionHeaders = document.getElementsByClassName("section-name"); + for (let sectionHeader of sectionHeaders) { + sectionHeader.addEventListener("click", toggleSection, false); + } + + // Clicking on the "collapse"/"expand" text will also toggle section's state + let toggleLinks = document.getElementsByClassName("toggle-caption"); + for (let toggleLink of toggleLinks) { + toggleLink.addEventListener("click", toggleSection, false); + } +} + +function onLoad() { + window.removeEventListener("load", onLoad); + + // Set up event listeners + setupListeners(); + + // Show slow SQL stats + SlowSQL.render(); + + // Show chrome hang stacks + ChromeHangs.render(); + + // Show histogram data + let histograms = Telemetry.histogramSnapshots; + if (Object.keys(histograms).length) { + let hgramDiv = document.getElementById("histograms"); + for (let [name, hgram] of Iterator(histograms)) { + Histogram.render(hgramDiv, name, hgram); + } + } else { + showEmptySectionMessage("histograms-section"); + } + + // Get the Telemetry Ping payload + let pingData = Cc['@mozilla.org/supports-string;1']. + createInstance(Ci.nsISupportsString); + TelemetryPing.observe(pingData, "get-payload", ""); + let ping = {}; + try { + ping = JSON.parse(pingData.data); + } catch (e) { + } + + // Show simple measurements + if (Object.keys(ping.simpleMeasurements).length) { + KeyValueTable.render("simple-measurements-table", ping.simpleMeasurements); + } else { + showEmptySectionMessage("simple-measurements-section"); + } + + // Show basic system info gathered + if (Object.keys(ping.info).length) { + KeyValueTable.render("system-info-table", ping.info); + } else { + showEmptySectionMessage("system-info-section"); + } + + // Show addon histogram data + histograms = Telemetry.addonHistogramSnapshots; + if (Object.keys(histograms).length) { + let addonDiv = document.getElementById("addon-histograms"); + for (let [name, hgram] of Iterator(histograms)) { + Histogram.render(addonDiv, "ADDON_" + name, hgram); + } + } else { + showEmptySectionMessage("addon-histograms-section"); + } +} + +window.addEventListener("load", onLoad, false); diff --git a/toolkit/content/aboutTelemetry.xhtml b/toolkit/content/aboutTelemetry.xhtml new file mode 100644 index 000000000000..d3969ef292d7 --- /dev/null +++ b/toolkit/content/aboutTelemetry.xhtml @@ -0,0 +1,106 @@ + + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + + %htmlDTD; + %globalDTD; + %brandDTD; + %aboutTelemetryDTD; +]> + + + + &aboutTelemetry.pageTitle; + + + +