/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const { ProfilerTestUtils } = ChromeUtils.importESModule( "resource://testing-common/ProfilerTestUtils.sys.mjs" ); /** * Start the profiler, run `func`, stop the profiler, and get a collection of * markers that were recorded while running `func`. * * @param {string} type The marker payload type, e.g. "Counter" * @param {object} func The function that runs glean code to generate markers * @returns {object} The markers generated during `func`, with the id field * expanded using the string table */ async function runWithProfilerAndGetMarkers(type, func) { await ProfilerTestUtils.startProfiler({ entries: 10000, interval: 10, features: ["nostacksampling"], threads: ["GeckoMain"], }); Assert.ok(Services.profiler.IsActive()); let startTime = Services.profiler.getElapsedTime(); await func(); let endTime = Services.profiler.getElapsedTime(); let profile = await ProfilerTestUtils.stopNowAndGetProfile(); // We assume that we only have one thread being profiled here. Assert.equal( profile.threads.length, 1, "We should only be profiling one thread" ); let thread = profile.threads[0]; // Exclude profiler setup and teardown, which can record unrelated IPC metrics. let timeIndex = thread.markers.schema.startTime; thread.markers.data = thread.markers.data.filter( marker => marker[timeIndex] >= startTime && marker[timeIndex] <= endTime ); let markers = ProfilerTestUtils.getPayloadsOfType(thread, type); let stringTable = thread.stringTable; // We expect that the id, or name, of a marker should be a unique string. // Go through them and look up the values so that we can just write a string // in the test, and not use a numerical id (which may not be stable!) for (let marker of markers) { marker.id = stringTable[marker.id]; if (marker.cat != undefined) { marker.cat = stringTable[marker.cat]; } if (marker.label != undefined) { marker.label = stringTable[marker.label]; } } // Next, filter the markers so that we only return those generated by test // metrics. We need to do this, as some metric types are used by other // parts of Fx, which may be running during the test. As we cannot predict // when they may occur, or what values they may hold, we can't write down // a "gold" value in our tests to compare them to - so we need to remove // them. Some notable existing examples include: // - Memory distribution metrics, "performanceCloneDeserialize." // - Custom distribution metrics (same as memory distribution) // - Timing distribution metrics (for timing of performance // clone/deserialise) // - Counter metrics ("javascriptGc"), which caused intermittents in Bug // 1943425 markers = markers.filter(marker => { // Metric markers with names coming from JS all start with `testOnly`, // `test_only` or `jog_` let name = marker.id.startsWith("testOnly") || marker.id.startsWith("test_only") || marker.id.startsWith("jog_"); // Marker categories all start with test or jog let cat = false; if (marker.cat != undefined) { cat = marker.cat.startsWith("test") || marker.cat.startsWith("jog_"); } // Ping markers are a little more varied, so we enumerate them let ping = ["test-ping", "one-ping-only", "ride-along-ping"].includes( marker.id ); // Include child lookup failures, but not background timing accumulations // (e.g. GC), which can also fail to resolve their metric names. let error = marker.id.startsWith("JOG") || (marker.type == "EventMetric" && marker.id == "id not found") || marker.id.endsWith(": LabeledBaseMetricIsNotDynamic") || (marker.id == "FOGMetricMapLookupFailed" && marker.timer_id != undefined); return name || cat || ping || error; }); // Return selected markers return markers; }