зеркало из https://github.com/mozilla/gecko-dev.git
Bug 661881: Add an about:telemetry page to Firefox. r=ttaubert
This commit is contained in:
Родитель
9aac74be0b
Коммит
e71ad7f6b1
|
@ -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);
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -0,0 +1,106 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
# 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/.
|
||||
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> %htmlDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> %brandDTD;
|
||||
<!ENTITY % aboutTelemetryDTD SYSTEM "chrome://global/locale/aboutTelemetry.dtd"> %aboutTelemetryDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&aboutTelemetry.pageTitle;</title>
|
||||
|
||||
<link rel="stylesheet" href="chrome://global/content/aboutTelemetry.css"
|
||||
type="text/css"/>
|
||||
|
||||
<script type="application/javascript;version=1.7"
|
||||
src="chrome://global/content/aboutTelemetry.js"/>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
|
||||
<header id="page-description">
|
||||
<h1>&aboutTelemetry.pageTitle;</h1>
|
||||
|
||||
<h2>&aboutTelemetry.pageSubtitle;</h2>
|
||||
|
||||
<p id="description-enabled">&aboutTelemetry.telemetryEnabled;</p>
|
||||
<p id="description-disabled">&aboutTelemetry.telemetryDisabled;</p>
|
||||
|
||||
<button id="toggle-telemetry" type="button"/>
|
||||
</header>
|
||||
|
||||
<section id="slow-sql-section" class="data-section">
|
||||
<h1 class="section-name">&aboutTelemetry.slowSqlSection;</h1>
|
||||
<span class="toggle-caption">&aboutTelemetry.toggleOn;</span>
|
||||
<span class="toggle-caption hidden">&aboutTelemetry.toggleOff;</span>
|
||||
<span class="empty-caption hidden">&aboutTelemetry.emptySection;</span>
|
||||
<div id="slow-sql-tables" class="data hidden">
|
||||
<p id="sql-warning" class="hidden">&aboutTelemetry.fullSqlWarning;</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="chrome-hangs-section" class="data-section">
|
||||
<h1 class="section-name">&aboutTelemetry.chromeHangsSection;</h1>
|
||||
<span class="toggle-caption">&aboutTelemetry.toggleOn;</span>
|
||||
<span class="toggle-caption hidden">&aboutTelemetry.toggleOff;</span>
|
||||
<span class="empty-caption hidden">&aboutTelemetry.emptySection;</span>
|
||||
<div id="chrome-hangs" class="data hidden">
|
||||
<a id="fetch-symbols" href="javascript:">&aboutTelemetry.fetchSymbols;</a>
|
||||
<a id="hide-symbols" class="hidden" href="javascript:">&aboutTelemetry.hideSymbols;</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<div id="chrome-hangs-data">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="histograms-section" class="data-section">
|
||||
<h1 class="section-name">&aboutTelemetry.histogramsSection;</h1>
|
||||
<span class="toggle-caption">&aboutTelemetry.toggleOn;</span>
|
||||
<span class="toggle-caption hidden">&aboutTelemetry.toggleOff;</span>
|
||||
<span class="empty-caption hidden">&aboutTelemetry.emptySection;</span>
|
||||
<div id="histograms" class="data hidden">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="simple-measurements-section" class="data-section">
|
||||
<h1 class="section-name">&aboutTelemetry.simpleMeasurementsSection;</h1>
|
||||
<span class="toggle-caption">&aboutTelemetry.toggleOn;</span>
|
||||
<span class="toggle-caption hidden">&aboutTelemetry.toggleOff;</span>
|
||||
<span class="empty-caption hidden">&aboutTelemetry.emptySection;</span>
|
||||
<div id="simple-measurements" class="data hidden">
|
||||
<table id="simple-measurements-table">
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="system-info-section" class="data-section">
|
||||
<h1 class="section-name">&aboutTelemetry.systemInfoSection;</h1>
|
||||
<span class="toggle-caption">&aboutTelemetry.toggleOn;</span>
|
||||
<span class="toggle-caption hidden">&aboutTelemetry.toggleOff;</span>
|
||||
<span class="empty-caption hidden">&aboutTelemetry.emptySection;</span>
|
||||
<div id="system-info" class="data hidden">
|
||||
<table id="system-info-table">
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="addon-histograms-section" class="data-section">
|
||||
<h1 class="section-name">&aboutTelemetry.addonHistogramsSection;</h1>
|
||||
<span class="toggle-caption">&aboutTelemetry.toggleOn;</span>
|
||||
<span class="toggle-caption hidden">&aboutTelemetry.toggleOff;</span>
|
||||
<span class="empty-caption hidden">&aboutTelemetry.emptySection;</span>
|
||||
<div id="addon-histograms" class="data hidden">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -16,6 +16,9 @@ toolkit.jar:
|
|||
content/global/aboutRights-unbranded.xhtml (aboutRights-unbranded.xhtml)
|
||||
* content/global/aboutSupport.js
|
||||
* content/global/aboutSupport.xhtml
|
||||
content/global/aboutTelemetry.js
|
||||
* content/global/aboutTelemetry.xhtml
|
||||
content/global/aboutTelemetry.css (aboutTelemetry.css)
|
||||
content/global/directionDetector.html
|
||||
content/global/plugins.html
|
||||
content/global/plugins.css
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<!ENTITY aboutTelemetry.pageTitle "Telemetry Data">
|
||||
|
||||
<!ENTITY aboutTelemetry.pageSubtitle "
|
||||
This page shows the performance and feature-use data collected by Telemetry.
|
||||
This information is submitted anonymously to Mozilla to help improve &brandShortName;.
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.telemetryEnabled "
|
||||
Telemetry is <span>enabled</span>.
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.telemetryDisabled "
|
||||
Telemetry is <span>disabled</span>.
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.slowSqlSection "
|
||||
Slow SQL Statements
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.chromeHangsSection "
|
||||
Browser Hangs
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.histogramsSection "
|
||||
Histograms
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.simpleMeasurementsSection "
|
||||
Simple Measurements
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.systemInfoSection "
|
||||
System Information
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.addonHistogramsSection "
|
||||
Histograms Collected by Add-ons
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.toggleOn "
|
||||
Click to expand section
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.toggleOff "
|
||||
Click to collapse section
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.emptySection "
|
||||
(No data collected)
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.fullSqlWarning "
|
||||
NOTE: Slow SQL debugging is enabled. Full SQL strings may be displayed below but they will not be submitted to Telemetry.
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.fetchSymbols "
|
||||
Fetch function names for hang stacks
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.hideSymbols "
|
||||
Show raw data from hangs
|
||||
">
|
|
@ -0,0 +1,39 @@
|
|||
# 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/.
|
||||
|
||||
slowSqlMain = Slow SQL Statements on Main Thread
|
||||
|
||||
slowSqlOther = Slow SQL Statements on Helper Threads
|
||||
|
||||
slowSqlHits = Hits
|
||||
|
||||
slowSqlAverage = Avg. Time (ms)
|
||||
|
||||
slowSqlStatement = Statement
|
||||
|
||||
# Note to translators: Please leave the %S specifiers in the translated string.
|
||||
# They will be replaced with values programmaticaly, e.g. "Hang Report #3 (5 seconds)"
|
||||
# - The first %S will be replaced with the number of the hang
|
||||
# - The second %S will be replaced with the duration of the hang
|
||||
hangTitle = Hang Report #%S (%S seconds)
|
||||
|
||||
stackTitle = Stack:
|
||||
|
||||
memoryMapTitle = Memory map:
|
||||
|
||||
errorFetchingSymbols = An error occurred while fetching symbols. Check that you are connected to the Internet and try again.
|
||||
|
||||
histogramSamples = samples
|
||||
|
||||
histogramAverage = average
|
||||
|
||||
histogramSum = sum
|
||||
|
||||
disableTelemetry = Disable Telemetry
|
||||
|
||||
enableTelemetry = Enable Telemetry
|
||||
|
||||
keysHeader = Property
|
||||
|
||||
valuesHeader = Value
|
|
@ -12,6 +12,8 @@
|
|||
locale/@AB_CD@/global/aboutRights.properties (%chrome/global/aboutRights.properties)
|
||||
locale/@AB_CD@/global/aboutSupport.dtd (%chrome/global/aboutSupport.dtd)
|
||||
locale/@AB_CD@/global/aboutSupport.properties (%chrome/global/aboutSupport.properties)
|
||||
locale/@AB_CD@/global/aboutTelemetry.dtd (%chrome/global/aboutTelemetry.dtd)
|
||||
locale/@AB_CD@/global/aboutTelemetry.properties (%chrome/global/aboutTelemetry.properties)
|
||||
locale/@AB_CD@/global/actions.dtd (%chrome/global/actions.dtd)
|
||||
locale/@AB_CD@/global/appPicker.dtd (%chrome/global/appPicker.dtd)
|
||||
locale/@AB_CD@/global/brand.dtd (generic/chrome/global/brand.dtd)
|
||||
|
|
Загрузка…
Ссылка в новой задаче