Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  aboutGlean.js

  Sprache: JAVA
 

/* 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";

// Docs: https://devdocs.io/d3~3/
Services.scriptloader.loadSubScript(
  "chrome://global/content/third_party/d3/d3.js"
);
const d3 = this.d3;

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

const METRIC_DATA = {};
let MAPPED_METRIC_DATA = [];
let FILTERED_METRIC_DATA = [];
let LIMITED_METRIC_DATA = [];
let LIMIT_OFFSET = 0;
let LIMIT_COUNT = 200;
let METRIC_DATA_INITIALIZED = false;
const INVALID_VALUE_REASONS = {
  DUAL_LABELED_METRIC: 0,
  UNKNOWN_METRIC: 1,
};
const SIMPLE_TYPES = {
  Boolean"Boolean",
  String: "String",
  StringList: "StringList",
  Text: "Text",
  Counter: "Counter",
};
const SELECTED_METRICS = [];
let DOCUMENT_BODY_SEL = undefined;

function updatePrefsAndDefines() {
  let upload = Services.prefs.getBoolPref(
    "datareporting.healthreport.uploadEnabled"
  );
  document.l10n.setAttributes(
    document.querySelector("[data-l10n-id='about-glean-data-upload']"),
    "about-glean-data-upload",
    {
      "data-upload-pref-value": upload,
    }
  );
  let port = Services.prefs.getIntPref("telemetry.fog.test.localhost_port");
  document.l10n.setAttributes(
    document.querySelector("[data-l10n-id='about-glean-local-port']"),
    "about-glean-local-port",
    {
      "local-port-pref-value": port,
    }
  );
  document.l10n.setAttributes(
    document.querySelector("[data-l10n-id='about-glean-glean-android']"),
    "about-glean-glean-android",
    { "glean-android-define-value": AppConstants.MOZ_GLEAN_ANDROID }
  );
  document.l10n.setAttributes(
    document.querySelector("[data-l10n-id='about-glean-moz-official']"),
    "about-glean-moz-official",
    { "moz-official-define-value": AppConstants.MOZILLA_OFFICIAL }
  );

  // Knowing what we know, and copying logic from viaduct_uploader.rs,
  // (which is documented in Preferences and Defines),
  // tell the fine user whether and why upload is disabled.
  let uploadMessageEl = document.getElementById("upload-status");
  let uploadL10nId = "about-glean-upload-enabled";
  if (!upload) {
    uploadL10nId = "about-glean-upload-disabled";
  } else if (port < 0 || (port == 0 && !AppConstants.MOZILLA_OFFICIAL)) {
    uploadL10nId = "about-glean-upload-fake-enabled";
    // This message has a link to the Glean Debug Ping Viewer in it.
    // We must add the anchor element now so that Fluent can match it.
    let a = document.createElement("a");
    a.href = "https://debug-ping-preview.firebaseapp.com/";
    a.setAttribute("data-l10n-name""glean-debug-ping-viewer");
    uploadMessageEl.appendChild(a);
  } else if (port > 0) {
    uploadL10nId = "about-glean-upload-enabled-local";
  }
  document.l10n.setAttributes(uploadMessageEl, uploadL10nId);
}

function camelToKebab(str) {
  let out = "";
  for (let i = 0; i < str.length; i++) {
    let c = str.charAt(i);
    if (c == c.toUpperCase()) {
      out += "-";
      c = c.toLowerCase();
    }
    out += c;
  }
  return out;
}

// I'm consciously omitting "deletion-request" until someone can come up with
// a use-case for sending it via about:glean.
const GLEAN_BUILTIN_PINGS = ["metrics""events""baseline"];
const NO_PING = "(don't submit any ping)";

function refillPingNames() {
  const builtInGroup = document.getElementById("builtin-pings");
  const customGroup = document.getElementById("custom-pings");

  // Add built-in ping options to the dropdown.
  GLEAN_BUILTIN_PINGS.forEach(id => {
    const option = document.createElement("option");
    option.value = id;
    option.textContent = camelToKebab(id);
    builtInGroup.appendChild(option);
  });

  // Add "(don't submit any ping)" as last built-in option in the dropdown.
  const noPingOption = document.createElement("option");
  noPingOption.value = NO_PING;
  document.l10n.setAttributes(noPingOption, "about-glean-no-ping-label");
  builtInGroup.appendChild(noPingOption);

  // Add alpha sorted custom ping options to the dropdown.
  Object.keys(GleanPings)
    .map(id => ({ id, label: camelToKebab(id) }))
    .sort((a, b) => a.label.localeCompare(b.label))
    .forEach(({ label }) => {
      const option = document.createElement("option");
      option.textContent = label;
      customGroup.appendChild(option);
    });
}

// If there's been a previous tag, use it.
// If not, be _slightly_ clever and derive a default one from the profile dir.
function fillDebugTag() {
  const DEBUG_TAG_PREF = "telemetry.fog.aboutGlean.debugTag";
  let debugTag;
  if (Services.prefs.prefHasUserValue(DEBUG_TAG_PREF)) {
    debugTag = Services.prefs.getStringPref(DEBUG_TAG_PREF);
  } else {
    const debugTagPrefix = "about-glean-";
    const profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
    let charSum = Array.from(profileDir).reduce(
      (prev, cur) => prev + cur.charCodeAt(0),
      0
    );

    debugTag = debugTagPrefix + (charSum % 1000);
  }

  let tagInput = document.getElementById("tag-pings");
  tagInput.value = debugTag;
  const updateDebugTagValues = () => {
    document.l10n.setAttributes(
      document.querySelector(
        "[data-l10n-id='about-glean-label-for-controls-submit']"
      ),
      "about-glean-label-for-controls-submit",
      { "debug-tag": tagInput.value }
    );
    const GDPV_ROOT = "https://debug-ping-preview.firebaseapp.com/pings/";
    let gdpvLink = document.querySelector(
      "[data-l10n-name='gdpv-tagged-pings-link']"
    );
    gdpvLink.href = GDPV_ROOT + tagInput.value;
  };
  tagInput.addEventListener("change", () => {
    Services.prefs.setStringPref(DEBUG_TAG_PREF, tagInput.value);
    updateDebugTagValues();
  });
  updateDebugTagValues();
}

function showTab(button) {
  let current_tab = document.querySelector(".active");
  let category = button.getAttribute("id").substring("category-".length);
  let content = document.getElementById(category);
  if (current_tab == content) {
    return;
  }
  current_tab.classList.remove("active");
  current_tab.hidden = true;
  content.classList.add("active");
  content.hidden = false;
  let current_button = document.querySelector("[selected=true]");
  current_button.removeAttribute("selected");
  button.setAttribute("selected""true");

  if (category == "metrics-table") {
    // Init base level metric data
    initializeMetricData();

    const table = document.getElementById("metrics-table-instance");
    table.removeAttribute("hidden");

    // Map the metric data into a better defined type structure
    MAPPED_METRIC_DATA = Object.entries(METRIC_DATA).flatMap(
      ([category, metrics]) =>
        Object.entries(metrics).map(([name, metric]) => ({
          category,
          name,
          fullName: `${category}.${name}`,
          ...metric,
        }))
    );
    updateFilteredMetricData(
      document.getElementById("filter-metrics").value.toLowerCase()
    );
    updateTable();
  }
}

function handleRedesign() {
  const categories = document.getElementById("categories");
  const div = document.createElement("div");
  div.id = "category-metrics-table";
  div.className = "category";
  div.setAttribute("role""menuitem");
  div.setAttribute("tabindex"0);
  const span = document.createElement("span");
  span.className = "category-name";
  span.setAttribute("data-l10n-id""about-glean-category-metrics-table");
  div.appendChild(span);
  categories.appendChild(div);

  /**
   * Handle metric filter input.
   *
   * This uses a timeout to debounce the events down to 200ms.
   * Instead of updating the DOM every time the input changes, it'll only update when the input hasn't changed in the last 200ms since it last changed.
   */

  let inputTimeout = undefined;
  document.getElementById("filter-metrics").addEventListener("input", e => {
    clearTimeout(inputTimeout);
    inputTimeout = setTimeout(() => {
      updateFilteredMetricData(e.target.value.toLowerCase() ?? "");
    }, 200);
  });

  // Handle loading all metric data
  document.getElementById("load-all").addEventListener("click", () => {
    MAPPED_METRIC_DATA.forEach(datum => {
      updateDatum(datum);
    });
    updateTable();
  });
}

function onLoad() {
  let menu = document.getElementById("categories");
  menu.addEventListener("click"function click(e) {
    if (e.target && e.target.parentNode == menu) {
      showTab(e.target);
    }
  });
  menu.addEventListener("keydown"function keypress(e) {
    if (
      e.target &&
      e.target.parentNode == menu &&
      (e.key == " " || e.key == "Enter")
    ) {
      showTab(e.target);
    }
  });
  showTab(document.getElementById("category-about-glean"));

  updatePrefsAndDefines();
  refillPingNames();
  fillDebugTag();
  document.getElementById("controls-submit").addEventListener("click", () => {
    let tag = document.getElementById("tag-pings").value;
    let log = document.getElementById("log-pings").checked;
    let ping = document.getElementById("ping-names").value;
    let feedbackToast = document.getElementById("feedback");

    Services.fog.setLogPings(log);
    Services.fog.setTagPings(tag);

    if (ping != NO_PING) {
      Services.fog.sendPing(ping);
      feedbackToast.setAttribute(
        "data-l10n-id",
        "about-glean-feedback-settings-and-ping"
      );
    } else {
      feedbackToast.setAttribute(
        "data-l10n-id",
        "about-glean-feedback-settings-only"
      );
    }

    feedbackToast.style.visibility = "visible";
    setTimeout(() => {
      feedbackToast.style.visibility = "hidden";
    }, 3000);
  });

  handleRedesign();

  DOCUMENT_BODY_SEL = d3.select(document.body);

  document
    .getElementById("metrics-table-settings-button")
    .addEventListener("click", () => {
      const settingsDiv = document.getElementById("metrics-table-settings");
      if (settingsDiv.getAttribute("hidden")) {
        settingsDiv.removeAttribute("hidden");
      } else {
        settingsDiv.setAttribute("hidden"true);
      }
      settingsChanged();
    });
  initMetricsSettings();

  document.getElementById("export-data").addEventListener("click", () => {
    exportData();
  });
}

/**
 * Initializes the base level metric data.
 *
 * Should only be able to be called once.
 */

function initializeMetricData() {
  if (METRIC_DATA_INITIALIZED) {
    return;
  }
  for (let [category, metrics] of Object.entries(Glean)) {
    for (let [metricName, metric] of Object.entries(metrics)) {
      // Trim "Glean" from the constructor names (e.g. "GleanBoolean" -> "Boolean").
      let constructorName = metric.constructor.name.replace("Glean""");
      // For labeled metrics, get their submetrics' constructor names and append it
      if (constructorName == "Labeled") {
        constructorName += metric.__other__.constructor.name.replace(
          "Glean",
          ""
        );
      }
      if (!METRIC_DATA[category]) {
        METRIC_DATA[category] = {};
      }
      METRIC_DATA[category][metricName] = {
        type: constructorName,
        value: undefined,
        metric,
      };
    }
  }
  METRIC_DATA_INITIALIZED = true;
}

function updateButtonsSelection(selection) {
  selection.attr("data-l10n-id", d =>
    d.watching ? "about-glean-button-unwatch" : "about-glean-button-watch"
  );
}

function createOrUpdateLabeledHistogram(selection, datum) {
  const keysAndValues = Object.entries(datum.value || {}).map(
    ([key, value]) => [
      key,
      {
        fullName: datum.fullName + `.${key}`,
        value,
      },
    ]
  );

  selection.selectAll("*").remove();
  if (keysAndValues.length === 0) {
    selection
      .append("p")
      .attr("data-l10n-id""about-glean-no-data-to-display");
    return;
  }
  for (let entry of keysAndValues.toSorted((a, b) =>
    d3.ascending(a[1].fullName, b[1].fullName)
  )) {
    const div = selection
      .append("div")
      .classed({ "keyed-chart"true })
      .attr("id", `${entry[0]}-container`)
      .attr("data-d3-key", entry[0]);
    div.append("h2").text(entry[0]);
    const svgContainer = div.append("div");
    createOrUpdateHistogram(svgContainer, entry[1]);
  }
}

function createOrUpdateHistogram(selection, datum) {
  const values = Object.entries(datum.value?.values || {}).map((d, i) => [
    ...d,
    i,
  ]);

  if (!values || values.length === 0) {
    selection.select("p")?.remove();
    selection
      .append("p")
      .attr("data-l10n-id""about-glean-no-data-to-display");
    selection.select("svg")?.remove();
    return;
  }
  selection.select("p")?.remove();

  const chartSettings = {
    boxPadding: 5,
    chartMax: 150,
    leftPadding: 20,
    chartPadding: 50,
    scaledMax: 110,
    ...metricsTableSettings.histograms,
  };
  const max = values.map(d => d[1]).sort((a, b) => b - a)[0],
    keyMax = values.map(d => d[0]).sort((a, b) => b - a)[0],
    boxWidth = Math.max(`${Math.max(max, keyMax)}`.length * 1030),
    denom = max / chartSettings.scaledMax;

  let hist = selection.select(`svg[data-d3-datum='${datum.fullName}']`);
  if (hist.empty()) {
    hist = selection
      .append("svg")
      .classed({ histogram: true })
      .attr("data-d3-datum", datum.fullName)
      .attr(
        "width",
        values.length * (boxWidth + chartSettings.boxPadding) +
          chartSettings.chartPadding +
          chartSettings.leftPadding
      )
      .attr("height", chartSettings.chartMax + chartSettings.chartPadding);
  }

  let boxesContainer = hist.select("g.boxes");
  if (boxesContainer.empty()) {
    boxesContainer = hist.append("g").classed({ boxes: true });
  }

  const boxes = boxesContainer.selectAll("g").data(values, d => d);

  const newBoxes = boxes.enter().append("g").attr("tabindex"0);
  newBoxes.append("rect").attr("width", boxWidth);
  newBoxes.append("text").attr("data-d3-role""y");
  newBoxes.append("text").attr("data-d3-role""x");

  const xFn = index =>
    boxWidth * index +
    chartSettings.boxPadding * index +
    chartSettings.leftPadding;
  const yFn = yv =>
    Math.abs(Math.max(yv / denom, 1) - chartSettings.scaledMax) +
    (chartSettings.chartMax - chartSettings.scaledMax);

  boxes
    .selectAll("rect")
    .attr("height", d => Math.max(d[1] / denom, 1))
    .attr("x", (_, __, i) => xFn(i))
    .attr("y", d => yFn(d[1]));
  boxes
    .selectAll("text[data-d3-role=y]")
    .attr("x", (_, __, i) => xFn(i))
    .attr("y", d => yFn(d[1]) - 5)
    .text(d => d[1]);
  boxes
    .selectAll("text[data-d3-role=x]")
    .attr("x", d => xFn(d[2]))
    .attr("y", chartSettings.chartMax + 20)
    .text(d => d[0]);

  function focusStart(_) {
    this.classList.add("hovered");
  }

  function focusEnd(_) {
    this.classList.remove("hovered");
  }

  boxes
    .attr("data-d3-box", d => d[0])
    .on("focusin", focusStart)
    .on("mouseover", focusStart)
    .on("focusout", focusEnd)
    .on("mouseout", focusEnd);

  boxes.exit().remove();
}

function createOrUpdateEventChart(selection, datum) {
  const values = (datum.value || []).map((d, i) => ({
    ...d,
    index: i,
    fullName: datum.fullName,
  }));

  if (!values || values.length === 0) {
    selection.select("p")?.remove();
    selection
      .append("p")
      .attr("data-l10n-id""about-glean-no-data-to-display");
    selection.select("svg")?.remove();
    return;
  }
  selection.select("p")?.remove();

  const chartSettings = {
    height: 75,
    width: 500,
    chartPadding: 50,
    circleRadius: 6,
    verticalLineXOffset: 10,
    verticalLineYOffset: 10,
    ...metricsTableSettings.timelines,
  };
  const max = values.map(d => d.timestamp).sort((a, b) => b - a)[0],
    min = values.map(d => d.timestamp).sort((a, b) => a - b)[0];

  let diagram = selection.select(`svg[data-d3-datum='${datum.fullName}']`);
  if (diagram.empty()) {
    diagram = selection
      .append("svg")
      .attr("data-d3-datum", datum.fullName)
      .classed({ timeline: true });
  }
  diagram
    .attr("width", chartSettings.width)
    .attr("height", chartSettings.height);

  let lineAcross = diagram.select("line[data-d3-role='across']");
  if (lineAcross.empty()) {
    lineAcross = diagram.append("line").attr("data-d3-role""across");
  }
  lineAcross
    .attr("x1", chartSettings.chartPadding)
    .attr("y1", chartSettings.height / 2)
    .attr("x2", chartSettings.width - chartSettings.chartPadding)
    .attr("y2", chartSettings.height / 2);

  let leftLineThrough = diagram.select("line[data-d3-role='left-through']");
  if (leftLineThrough.empty()) {
    leftLineThrough = diagram
      .append("line")
      .attr("data-d3-role""left-through");
  }
  leftLineThrough
    .attr("x1", chartSettings.chartPadding + chartSettings.verticalLineXOffset)
    .attr("y1", chartSettings.height / 2 - chartSettings.verticalLineYOffset)
    .attr("x2", chartSettings.chartPadding + chartSettings.verticalLineXOffset)
    .attr("y2", chartSettings.height / 2 + chartSettings.verticalLineYOffset);

  let rightLineThrough = diagram.select("line[data-d3-role='right-through']");
  if (rightLineThrough.empty()) {
    rightLineThrough = diagram
      .append("line")
      .attr("data-d3-role""right-through");
  }
  rightLineThrough
    .attr(
      "x1",
      chartSettings.width -
        chartSettings.chartPadding -
        chartSettings.verticalLineXOffset
    )
    .attr("y1", chartSettings.height / 2 - chartSettings.verticalLineYOffset)
    .attr(
      "x2",
      chartSettings.width -
        chartSettings.chartPadding -
        chartSettings.verticalLineXOffset
    )
    .attr("y2", chartSettings.height / 2 + chartSettings.verticalLineYOffset);

  let code = selection.select("pre");
  if (code.empty()) {
    code = selection.append("pre").classed({ withChart: true }).append("code");
  } else {
    code = code.select("code");
  }

  const xFn = d3.scale
    .linear()
    .domain([min, max])
    .range([
      chartSettings.verticalLineXOffset + chartSettings.chartPadding,
      chartSettings.width -
        chartSettings.chartPadding -
        chartSettings.verticalLineXOffset,
    ]);

  let eventsContainer = diagram.select("g.events");
  if (eventsContainer.empty()) {
    eventsContainer = diagram.append("g").classed({ events: true });
  }

  const events = eventsContainer
    .selectAll("g.event")
    .data(values, d => `${d.fullName}-${d.index}-${d.timestamp}`);

  const newEvents = events
    .enter()
    .append("g")
    .classed({ event: true })
    .attr("tabindex"0);

  newEvents.append("circle");

  function focusStart(_) {
    this.classList.add("hovered");
  }

  function focusEnd(_) {
    this.classList.remove("hovered");
  }

  function select(_) {
    const dataPoint = this.__data__;
    code.text(prettyPrint(dataPoint.extra));

    document.querySelectorAll("g.event.selected").forEach(el => {
      el.classList.remove("selected");
      el.querySelector("text")?.remove();
    });
    this.classList.add("selected");

    const text = this.appendChild(
      document.createElementNS("http://www.w3.org/2000/svg", "text")
    );
    text.setAttribute("y", chartSettings.height / 2 + 25);
    text.setAttribute(
      "x",
      xFn(dataPoint.timestamp) - `${dataPoint.timestamp}`.length * 4.5
    );
    text.textContent = dataPoint.timestamp;
  }

  events.attr("data-d3-datum", d => `${d.fullName}-${d.index}-${d.timestamp}`);

  events
    .selectAll("circle")
    .attr("cy", chartSettings.height / 2)
    .attr("cx", d => xFn(d.timestamp))
    .attr("r", chartSettings.circleRadius);

  events
    .on("focusin", select)
    .on("mouseover", focusStart)
    .on("focusout", focusEnd)
    .on("mouseout", focusEnd);

  events.exit().remove();
}

const METRICS_TABLE_SETTINGS_KEY = "about-glean-metrics-table-settings";
/**
 * When adding new fields to the metrics table settings,
 * a corresponding element matching the query selector
 * `[data-form-control=<key>]` must be present in the DOM.
 */

const metricsTableSettings = {
  hideEmptyValueRows: false,
  histograms: {
    boxPadding: 5,
    chartMax: 150,
    leftPadding: 20,
    chartPadding: 50,
    scaledMax: 110,
  },
  timelines: {
    height: 75,
    width: 500,
    chartPadding: 50,
    circleRadius: 6,
    verticalLineXOffset: 10,
    verticalLineYOffset: 10,
  },
};

function initMetricsSettings() {
  const handleSetting = (obj, key, parent) => {
    let element = parent.querySelector(`[data-form-control='${key}']`);
    let valueFn = e => e.target.value;
    if (!element && typeof obj[key] !== "object") {
      console.error(
        new Error(
          `Unable to find form control with key '${key}' in the parent element`
        ),
        parent
      );
      return;
    }
    switch (typeof obj[key]) {
      case "boolean":
        valueFn = e => e.target.checked;
        obj[key] = valueFn({ target: element });
        break;
      case "object":
        element = parent.querySelector(`[data-form-group='${key}']`);
        if (!element) {
          console.error(
            new Error(
              `Unable to find form control with key '${key}' in the parent element`
            ),
            parent
          );
          return;
        }
        for (const subKey of Object.keys(obj[key])) {
          handleSetting(obj[key], subKey, element);
        }
        break;
      case "number":
        valueFn = e => parseInt(e.target.value);
      // eslint-disable-next-line no-fallthrough
      default:
        if (element.type !== typeof obj[key]) {
          console.warn(
            new Error(
              `Form control input type does not match JavaScript value type ${typeof obj[key]}`
            )
          );
        }
        if (valueFn({ target: element })) {
          obj[key] = valueFn({ target: element });
        } else {
          element.value = obj[key];
        }
    }
    element.addEventListener("input", handleSettingChange(obj, valueFn));
  };

  for (const key of Object.keys(metricsTableSettings)) {
    handleSetting(
      metricsTableSettings,
      key,
      document.getElementById("metrics-table-settings")
    );
  }
}

function handleSettingChange(obj, valueFn) {
  return e => {
    obj[e.target.getAttribute("data-form-control")] = valueFn(e);
    settingsChanged();
  };
}

function settingsChanged() {
  createOrUpdateHistogram(
    d3.select("#metrics-table-settings-histogram-example"),
    {
      fullName: "histogram-example",
      value: {
        values: {
          01,
          15,
          24,
          30,
        },
      },
    }
  );

  createOrUpdateEventChart(
    d3.select("#metrics-table-settings-timeline-example"),
    {
      fullName: "timeline-example",
      value: [
        {
          timestamp: 0,
          extra: {
            value: 1,
          },
        },
        {
          timestamp: 1,
          extra: {
            value: 2,
          },
        },
        {
          timestamp: 4,
          extra: {
            value: 3,
          },
        },
        {
          timestamp: 8,
          extra: {
            value: 4,
          },
        },
      ],
    }
  );

  updateTable();
}

function updateValueSelection(selection) {
  // Set the `data-l10n-id` attribute to the appropriate warning if the value is invalid, otherwise
  // unset it by returning `null`.
  selection
    ?.attr("data-l10n-id", d => {
      switch (d.invalidValue) {
        case INVALID_VALUE_REASONS.DUAL_LABELED_METRIC:
          return "about-glean-dual-labeled-metric-warning";
        case INVALID_VALUE_REASONS.UNKNOWN_METRIC:
          return "about-glean-unknown-metric-type-warning";
        default:
          return null;
      }
    })
    ?.each(function (datum) {
      if (datum.loaded) {
        let codeSelection = d3.select(this).select("pre>code");
        switch (datum.type) {
          case "Event":
            createOrUpdateEventChart(d3.select(this), datum);
            break;
          case "CustomDistribution":
          case "MemoryDistribution":
          case "TimingDistribution":
            createOrUpdateHistogram(d3.select(this), datum);
            break;
          case "LabeledCustomDistribution":
          case "LabeledMemoryDistribution":
          case "LabeledTimingDistribution":
            createOrUpdateLabeledHistogram(d3.select(this), datum);
            break;
          default:
            if (codeSelection.empty()) {
              codeSelection = d3.select(this).append("pre").append("code");
            }
            codeSelection.text(prettyPrint(datum.value));
        }
      }
    });
}

/**
 * Updates a datum object with its value from `testGetValue`.
 *
 * @param {*} datum the datum object to update
 * @param {*} update update the table after updating the datum (defaults to `true`)
 */

function updateDatum(datum) {
  if (typeof datum.metric.testGetValue == "function") {
    try {
      datum.value = datum.metric.testGetValue();
      datum.error = undefined;
    } catch (e) {
      datum.error = e;
    }
    datum.loaded = true;
    datum.invalidValue = undefined;
  } else if (datum.type.includes("DualLabeled")) {
    datum.invalidValue = INVALID_VALUE_REASONS.DUAL_LABELED_METRIC;
  } else {
    datum.invalidValue = INVALID_VALUE_REASONS.UNKNOWN_METRIC;
  }

  updateValueSelection(
    DOCUMENT_BODY_SEL.select(
      `tr[data-d3-row="${datum.fullName}"]>td[data-d3-cell=value]`
    )
  );
}

/**
 * Prettifies a JSON value to make it render more nicely in the table.
 *
 * @param {*} jsonValue the JSON value to prettify
 * @returns a string containing the prettified JSON value in a pre+code
 */

function prettyPrint(jsonValue) {
  if (typeof jsonValue == "object") {
    jsonValue = Object.keys(jsonValue ?? {})
      .sort()
      .reduce((obj, key) => {
        obj[key] = jsonValue[key];
        return obj;
      }, {});
  }
  // from devtools/client/jsonview/json-viewer.mjs
  const pretty = JSON.stringify(
    jsonValue,
    (key, value) => {
      if (value?.type === Symbol("JSON_NUMBER")) {
        return JSON.rawJSON(value.source);
      }

      // By default, -0 will be stringified as `0`, so we need to handle it
      if (Object.is(value, -0)) {
        return JSON.rawJSON("-0");
      }

      return value;
    },
    "  "
  );
  return pretty;
}

function getDocsURL(datum) {
  const upperRegExp = /[A-Z]/;
  const app = "firefox_desktop";
  let category = datum.category;
  let index = category.search(upperRegExp);
  while (index != -1) {
    category = category.replace(
      upperRegExp,
      "_" + category[index].toLowerCase()
    );
    index = category.search(upperRegExp);
  }

  let name = datum.name;
  index = name.search(upperRegExp);
  while (index != -1) {
    name = name.replace(upperRegExp, "_" + name[index].toLowerCase());
    index = name.search(upperRegExp);
  }
  return `https://dictionary.telemetry.mozilla.org/apps/${app}/metrics/${category}_${name}`;
}

/**
 * Updates the `about:glean` metrics table body based on the data points in FILTERED_METRIC_DATA.
 */

function updateTable() {
  LIMITED_METRIC_DATA = FILTERED_METRIC_DATA.toSorted((a, b) =>
    d3.ascending(a.fullName, b.fullName)
  )
    // Filter out rows whose datum elements have either a) been loaded and have a value, or b) have not yet been loaded.
    .filter(d =>
      metricsTableSettings.hideEmptyValueRows
        ? (d.value !== undefined && d.value !== null) || !d.loaded
        : true
    )
    // Filter down to only the datum elements whose indexes fall in the limit offset+count.
    .filter((_, i) => i >= LIMIT_OFFSET && i < LIMIT_COUNT + LIMIT_OFFSET);

  // Let's talk about d3.js
  // `d3.select` is a rough equivalent to `document.querySelector`, but the resulting object(s) are things d3 knows how to manipulate.
  const tbody = DOCUMENT_BODY_SEL.select("#metrics-table-body");
  // Select all the `tr` elements within the previously selected `tbody` element.
  const rows = tbody
    .selectAll("tr")
    // Set the data for the `tr` elements to be the FILTERED_METRIC_DATA, keyed off the data element's full name
    .data(LIMITED_METRIC_DATA, d => d.fullName);

  // `.enter()` means this section determines how we handle new data elements in the array.
  // We class them and insert the appropriate data cells
  let newRows = rows
    .enter()
    .append("tr")
    .attr("data-d3-row", d => d.fullName)
    .classed({ "metric-row"true });

  const actions = newRows
    .append("td")
    .attr("data-d3-cell""actions")
    .append("div");
  // Set the HTML content for the `category` and `name` cells, and store the name cells in-scope so we can
  // append our buttons to them.
  newRows
    .append("td")
    .attr("data-d3-cell""category")
    .append("pre")
    .text(d => d.category);
  newRows
    .append("td")
    .attr("data-d3-cell""name")
    .append("pre")
    .text(d => d.name);
  // Handle displaying the metric type.
  newRows
    .append("td")
    .attr("data-d3-cell""type")
    .text(d => d.type);
  newRows.append("td").attr("data-d3-cell""value");

  actions
    .append("button")
    .attr("data-l10n-id""about-glean-button-load-value")
    .on("click", datum => updateDatum(datum));
  actions
    .append("button")
    .attr("data-l10n-id""about-glean-button-dictionary-link")
    .classed({ primary: true })
    // On click, rewrite the metric category+name to snake-case, so we can link to the Glean dictionary.
    // TODO: add canonical_name field to metrics https://bugzilla.mozilla.org/show_bug.cgi?id=1983630
    .on("click", datum => {
      const url = getDocsURL(datum);
      window.open(url, "_blank").focus();
    });

  // Since `.enter` has been called on `rows` and we've handled new data points, everything
  // that touches `rows` from here on out will affect ALL elements, old and new.

  updateButtonsSelection(
    rows.selectAll("td[data-d3-cell=actions] button[data-d3-button=watch]")
  );
  // Handle the metric's value.
  updateValueSelection(rows.selectAll("td[data-d3-cell=value]"));

  // Sort the `tr` elements by full metric category+name.
  rows.sort((a, b) => d3.ascending(a.fullName, b.fullName));

  // Handle exiting data points by removing their elements.
  rows.exit().remove();

  // Manually trigger translation on the table, as DOM updates after the first application of the `data-l10n-id` will not translate.
  document.l10n.translateFragment(
    document.querySelector("#metrics-table-body")
  );
}

/**
 * Updates the FILTERED_METRIC_DATA value based on the provided `searchString`.
 *
 * @param {*} searchString the string by which the metric data will be filtered
 */

function updateFilteredMetricData(searchString) {
  if (!searchString) {
    FILTERED_METRIC_DATA = MAPPED_METRIC_DATA;
  } else {
    const simpleTypeValueSearch = datum => {
      if (!Object.values(SIMPLE_TYPES).includes(datum.type)) {
        return false;
      }
      switch (datum.type) {
        case SIMPLE_TYPES.Boolean:
          if (searchString == "true") {
            return datum.value === true;
          } else if (searchString == "false") {
            return datum.value === false;
          }
          return false;
        default:
          return false;
      }
    };
    FILTERED_METRIC_DATA = MAPPED_METRIC_DATA.filter(
      datum =>
        datum.category.toLowerCase().includes(searchString) ||
        datum.name.toLowerCase().includes(searchString) ||
        datum.type.toLowerCase().includes(searchString) ||
        simpleTypeValueSearch(datum)
    );
  }

  if (FILTERED_METRIC_DATA.length > LIMIT_COUNT + LIMIT_OFFSET) {
    const table = document.getElementById("metrics-table-instance");
    let scrollTimeout,
      scrollTimeoutIsCleared = true;
    table.addEventListener("scroll", el => {
      if (scrollTimeoutIsCleared) {
        scrollTimeout = setTimeout(
          ({ target }) => {
            clearTimeout(scrollTimeout);
            scrollTimeoutIsCleared = true;
            let changes = false;
            if (target.scrollTop < 1500) {
              if (LIMIT_COUNT >= 500 && LIMIT_OFFSET > 0) {
                LIMIT_OFFSET = LIMIT_OFFSET - 100 < 0 ? 0 : LIMIT_OFFSET - 100;
                changes = true;
              }
            } else if (target.scrollHeight - target.scrollTop < 1500) {
              if (LIMIT_COUNT >= 500) {
                if (
                  LIMIT_OFFSET + LIMIT_COUNT + 100 >
                  FILTERED_METRIC_DATA.length
                ) {
                  LIMIT_OFFSET = FILTERED_METRIC_DATA.length - LIMIT_COUNT;
                } else if (
                  LIMIT_OFFSET + LIMIT_COUNT <
                  FILTERED_METRIC_DATA.length - 100
                ) {
                  LIMIT_OFFSET += 100;
                }
              } else {
                LIMIT_COUNT += 100;
              }
              changes = true;
            }
            if (changes) {
              updateTable();
            }
          },
          10,
          el
        );
        scrollTimeoutIsCleared = false;
      }
    });
  } else {
    LIMIT_COUNT = 200;
    LIMIT_OFFSET = 0;
  }

  updateTable();
}

function exportData() {
  const data = MAPPED_METRIC_DATA.toSorted((a, b) =>
    d3.ascending(a.fullName, b.fullName)
  ).reduce(
    (accumulator, datum) => [
      ...accumulator,
      {
        name: `${datum.fullName}`,
        docs: getDocsURL(datum),
        value: datum.value,
      },
    ],
    []
  );
  const href = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(data))}`;
  const anchor = document.createElement("a");
  anchor.setAttribute("href", href);
  anchor.setAttribute(
    "download",
    `about-glean-export-${new Date().toJSON()}.json`
  );
  document.body.appendChild(anchor);
  anchor.click();
  anchor.remove();
}

window.addEventListener("load", onLoad);

Messung V0.5 in Prozent
C=96 H=91 G=93

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet am  2026-08-22) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=141584
#Domains=752002