зеркало из https://github.com/mozilla/usecounters.git
Add current dashboard code from georgf.github repo
This commit is contained in:
Родитель
bbe3a3b33c
Коммит
e84eeb898c
|
@ -0,0 +1,467 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
#content {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
width: 960px;
|
||||
height: 500px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#details {
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.data {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 3px;
|
||||
}
|
||||
|
||||
tr:hover > td {
|
||||
background-color: #F0F0F0;
|
||||
}
|
||||
|
||||
td {
|
||||
background-color: #F5F5F5;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.percentCell {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.barCell {
|
||||
vertical-align: center;
|
||||
padding: 0px;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.barCell > svg {
|
||||
width: 400px;
|
||||
height: 18px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#loading-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: #000;
|
||||
opacity: 0.7;
|
||||
filter: alpha(opacity=70);
|
||||
z-index: 14;
|
||||
}
|
||||
|
||||
#loading-anim {
|
||||
width: 220px;
|
||||
height: 19px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -9px 0 0 -110px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.infolink
|
||||
{
|
||||
display: inline-block;
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
width: 1.5ex;
|
||||
height: 1.5ex;
|
||||
font-size: 1.0ex;
|
||||
border-radius: 1.4ex;
|
||||
margin-right: 0px;
|
||||
padding: 2px;
|
||||
color: black;
|
||||
background: white;
|
||||
border: 1px solid black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.infolink:hover
|
||||
{
|
||||
color: white;
|
||||
background: black;
|
||||
border-color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="loading-overlay">
|
||||
<img id="loading-anim" src="loading.gif">
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<p id="selection">
|
||||
Show <a href="https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/use-counters.html">use counters</a>
|
||||
for group
|
||||
<select class="groups"></select>
|
||||
relative to
|
||||
<select class="kind">
|
||||
<option value="page">page (toplevel)</option>
|
||||
<option value="document">document</option>
|
||||
</select>
|
||||
counts.
|
||||
</p>
|
||||
|
||||
<p id="details">
|
||||
<form>
|
||||
The data comes from Firefox
|
||||
<select id="select_version">...</select>
|
||||
<select id="select_channel">
|
||||
<option value="beta">beta</option>
|
||||
<option value="nightly">nightly</option>
|
||||
</select>.
|
||||
It was collected from <span id="page_count">...</span> top level page loads and <span id="document_count">...</span> individual document loads.
|
||||
</form>
|
||||
</p>
|
||||
|
||||
<div class="data"></div>
|
||||
</div>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
||||
<script src="https://telemetry.mozilla.org/v2/telemetry.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
// Use counters are only collected from prerelease channels.
|
||||
const gValidChannels = ["beta", "nightly"];
|
||||
|
||||
// Map() of (channel -> version).
|
||||
var gAvailableVersions = null;
|
||||
|
||||
// Caching already loaded data in-memory to subsequently avoid outgoing requests.
|
||||
var metricsCache = new Map();
|
||||
var histogramSumCache = new Map();
|
||||
|
||||
function getChannel() {
|
||||
return $("#select_channel").val();
|
||||
}
|
||||
|
||||
function getVersion() {
|
||||
if ($("#select_version option").length == 0) {
|
||||
return null;
|
||||
}
|
||||
return $("#select_version").val();
|
||||
}
|
||||
|
||||
async function getMetrics() {
|
||||
let cacheId = `${getChannel()}/${getVersion()}`;
|
||||
if (!metricsCache.get(cacheId)) {
|
||||
let filterOptions = await new Promise(r => {
|
||||
return Telemetry.getFilterOptions(getChannel(), getVersion(), r);
|
||||
});
|
||||
metricsCache.set(cacheId, filterOptions.metric);
|
||||
}
|
||||
|
||||
return metricsCache.get(cacheId).filter(m => m.startsWith("USE_COUNTER2_"));
|
||||
}
|
||||
|
||||
async function getHistogramSum(metric) {
|
||||
let cacheId = `${getChannel()}/${getVersion()}/${metric}`;
|
||||
|
||||
if (histogramSumCache.has(cacheId)) {
|
||||
return histogramSumCache.get(cacheId);
|
||||
}
|
||||
|
||||
let filters = {};
|
||||
let evo = await new Promise(r => Telemetry.getEvolution(getChannel(), getVersion(), metric, filters, false, r));
|
||||
let sum = 0;
|
||||
if ("" in evo) {
|
||||
sum = evo[""].histogram().sum;
|
||||
}
|
||||
|
||||
histogramSumCache.set(cacheId, sum);
|
||||
return sum;
|
||||
}
|
||||
|
||||
function strippedUseCounterName(uc) {
|
||||
let kind = $('.kind').val();
|
||||
let group = $('.groups').val();
|
||||
let regexPrefix = new RegExp('^USE_COUNTER2_' + group + '_');
|
||||
let regexSuffix = new RegExp('_' + kind.toUpperCase());
|
||||
return uc.replace(regexPrefix, '')
|
||||
.replace(regexSuffix, '');
|
||||
}
|
||||
|
||||
function linkForProbeDetails(id) {
|
||||
const base = `https://georgf.github.io/fx-data-explorer/`;
|
||||
const params = {
|
||||
searchtype: "in_any",
|
||||
optout: "false",
|
||||
constraint: "is_in",
|
||||
version: "any",
|
||||
detailView: `histogram/${id}`,
|
||||
channel: getChannel(),
|
||||
search: id,
|
||||
};
|
||||
|
||||
return "https://georgf.github.io/fx-data-explorer/?" +
|
||||
Object.entries(params)
|
||||
.map(p => p[0] + "=" + encodeURIComponent(p[1]))
|
||||
.join("&");
|
||||
}
|
||||
|
||||
function createTable(data) {
|
||||
let table = document.createElement('table');
|
||||
|
||||
for (let d of data) {
|
||||
let tr = document.createElement('tr');
|
||||
|
||||
// Add name.
|
||||
let td = document.createElement('td');
|
||||
td.appendChild(document.createTextNode(d.shortName + " "));
|
||||
let link = $('<a>', {
|
||||
text: "?",
|
||||
title: "More about this probe",
|
||||
href: linkForProbeDetails(d.metric),
|
||||
"class": "infolink",
|
||||
});
|
||||
link.appendTo(td);
|
||||
td.setAttribute('title', d.metric);
|
||||
tr.appendChild(td);
|
||||
|
||||
// Add percentage.
|
||||
td = document.createElement('td');
|
||||
td.appendChild(document.createTextNode(`${d.percentage.toFixed(3)}%`));
|
||||
td.className += 'percentCell';
|
||||
td.setAttribute('title', `${d.count.toLocaleString()} of total loads`);
|
||||
tr.appendChild(td);
|
||||
|
||||
// Create bar cell.
|
||||
|
||||
td = document.createElement('td');
|
||||
td.className += 'barCell';
|
||||
td.setAttribute('title', `${d.percentage.toFixed(3)}%`);
|
||||
|
||||
const svgNs = "http://www.w3.org/2000/svg";
|
||||
let svg = document.createElementNS(svgNs, "svg");
|
||||
|
||||
let usedRectWidth = Math.min(400, Math.round(4 * d.percentage));
|
||||
|
||||
let rect = document.createElementNS(svgNs, 'rect');
|
||||
rect.setAttribute('x', 0);
|
||||
rect.setAttribute('y', 0);
|
||||
rect.setAttribute('width', usedRectWidth);
|
||||
rect.setAttribute('height', 100);
|
||||
rect.setAttribute('fill', 'steelblue');
|
||||
svg.appendChild(rect);
|
||||
|
||||
if (usedRectWidth < 400) {
|
||||
let rect = document.createElementNS(svgNs, 'rect');
|
||||
rect.setAttribute('x', usedRectWidth);
|
||||
rect.setAttribute('y', 0);
|
||||
rect.setAttribute('width', 400 - usedRectWidth);
|
||||
rect.setAttribute('height', 100);
|
||||
rect.setAttribute('fill', '#F0F0F0');
|
||||
svg.appendChild(rect);
|
||||
}
|
||||
|
||||
td.appendChild(svg);
|
||||
tr.appendChild(td);
|
||||
|
||||
table.appendChild(tr);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
function pct(fraction, total) {
|
||||
return 100 * (fraction / total);
|
||||
}
|
||||
|
||||
async function getTotalCount() {
|
||||
let totalMetric = ($('.kind').val() == "page") ?
|
||||
"TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED" :
|
||||
"CONTENT_DOCUMENTS_DESTROYED";
|
||||
return await getHistogramSum(totalMetric);
|
||||
}
|
||||
|
||||
async function addTable(metrics) {
|
||||
let totalCount = await getTotalCount();
|
||||
|
||||
let promises = [];
|
||||
let sums = [];
|
||||
for (let m of metrics) {
|
||||
let p = getHistogramSum(m).then(sum => sums.push({
|
||||
shortName: strippedUseCounterName(m),
|
||||
metric: m,
|
||||
percentage: pct(sum, totalCount),
|
||||
count: sum,
|
||||
}));
|
||||
promises.push(p);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
sums.sort((a, b) => a.shortName.localeCompare(b.shortName));
|
||||
|
||||
let table = createTable(sums);
|
||||
$('.data').append(table);
|
||||
}
|
||||
|
||||
function makeHash() {
|
||||
window.location.hash = `#kind=${encodeURIComponent($('.kind').val())}` +
|
||||
`&group=${encodeURIComponent($('.groups').val())}` +
|
||||
`&channel=${getChannel()}` +
|
||||
`&version=${getVersion()}`;
|
||||
}
|
||||
|
||||
async function updateMetrics() {
|
||||
$('.data').empty();
|
||||
let kind = $('.kind').val();
|
||||
let group = $('.groups').val();
|
||||
|
||||
let metrics = (await getMetrics())
|
||||
.filter(m => m.endsWith("_" + kind.toUpperCase()))
|
||||
.filter(m => m.startsWith("USE_COUNTER2_" + group + "_"));
|
||||
console.log("metrics:", metrics.join(', '));
|
||||
|
||||
await addTable(metrics);
|
||||
}
|
||||
|
||||
function parseQueryString() {
|
||||
let params = new Map;
|
||||
for (let pair of window.location.hash.substring(1).split('&')) {
|
||||
let [key, value] = pair.split('=');
|
||||
params.set(key, decodeURIComponent(value));
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
async function updateUI(params = null) {
|
||||
var last = array => array[array.length - 1];
|
||||
|
||||
if (params && params.has('channel')) {
|
||||
let channel = params.get('channel');
|
||||
if (gValidChannels.includes(channel)) {
|
||||
$('#select_channel').val(channel);
|
||||
}
|
||||
}
|
||||
|
||||
// Show only versions available for this channel.
|
||||
const validVersions = gAvailableVersions.get(getChannel());
|
||||
console.log("validVersions", validVersions);
|
||||
$("#select_version > option").each(function() {
|
||||
$(this).toggle(validVersions.includes(parseInt(this.value)));
|
||||
});
|
||||
|
||||
// Use the closest valid version if an unavailable one was selected.
|
||||
let version = (getVersion() != null) ? parseInt(getVersion()) : validVersions[0];
|
||||
if (params && params.has('version')) {
|
||||
version = parseInt(params.get('version'));
|
||||
}
|
||||
if (!validVersions.includes(version)) {
|
||||
version = Math.max(last(validVersions), Math.min(validVersions[0], version));
|
||||
}
|
||||
|
||||
$("#select_version").val(version);
|
||||
|
||||
// Update top level metrics.
|
||||
let topLevelCount = await getHistogramSum("TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED");
|
||||
let docCount = await getHistogramSum("CONTENT_DOCUMENTS_DESTROYED");
|
||||
$('#page_count').text(topLevelCount.toLocaleString());
|
||||
$('#document_count').text(docCount.toLocaleString());
|
||||
|
||||
// Add individual metrics.
|
||||
let metrics = await getMetrics();
|
||||
let groups = [...new Set(metrics.map(m => m.split('_')[2]))];
|
||||
groups = groups.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
for (let g of groups) {
|
||||
$('.groups').append($('<option>', {
|
||||
value: g,
|
||||
text: g,
|
||||
}));
|
||||
}
|
||||
|
||||
if (params && params.has('kind')) {
|
||||
$('.kind').val(params.get('kind'));
|
||||
}
|
||||
|
||||
if (params && params.has('group')) {
|
||||
$('.groups').val(params.get('group'));
|
||||
}
|
||||
}
|
||||
|
||||
async function update(params = null) {
|
||||
document.getElementById("loading-overlay").classList.remove("hidden");
|
||||
|
||||
await updateUI(params);
|
||||
makeHash();
|
||||
await updateMetrics();
|
||||
|
||||
document.getElementById("loading-overlay").classList.add("hidden");
|
||||
}
|
||||
|
||||
function initUI() {
|
||||
// Build map of sorted valid versions per channel.
|
||||
let versions = Telemetry.getVersions()
|
||||
.map(v => v.split("/"))
|
||||
.filter(v => gValidChannels.includes(v[0]));
|
||||
gAvailableVersions = new Map();
|
||||
for (let channel of gValidChannels) {
|
||||
let vs = versions.filter(v => v[0] == channel)
|
||||
.map(v => parseInt(v[1]))
|
||||
.sort()
|
||||
.reverse();
|
||||
gAvailableVersions.set(channel, vs);
|
||||
}
|
||||
|
||||
// Sanitize beta version range.
|
||||
if (gAvailableVersions.get("beta")[0] == gAvailableVersions.get("nightly")[0]) {
|
||||
gAvailableVersions.get("beta").shift();
|
||||
}
|
||||
|
||||
// Fill version selector.
|
||||
let select = $("#select_version");
|
||||
let allVersions = [... (new Set(versions.map(v => parseInt(v[1])))).values()]
|
||||
.sort()
|
||||
.reverse();
|
||||
for (var version of allVersions) {
|
||||
select.append("<option value=\""+version+"\" >"+version+"</option>");
|
||||
}
|
||||
|
||||
// Set up event handlers.
|
||||
let handler = () => update();
|
||||
let selectors = [
|
||||
"#select_version",
|
||||
"#select_channel",
|
||||
".groups",
|
||||
".kind",
|
||||
];
|
||||
|
||||
for (let selector of selectors) {
|
||||
$(selector).change(handler);
|
||||
$(selector).keyup(handler);
|
||||
}
|
||||
}
|
||||
|
||||
async function start() {
|
||||
await new Promise(r => Telemetry.init(r));
|
||||
initUI();
|
||||
await update(parseQueryString())
|
||||
}
|
||||
|
||||
start();
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 11 KiB |
Загрузка…
Ссылка в новой задаче