summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html')
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html791
1 files changed, 791 insertions, 0 deletions
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html
index 75cae51c2e1..5390adb2eb2 100644
--- a/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html
@@ -22,6 +22,795 @@ tr.exportTo('tr.metrics.v8', function() {
const TARGET_FPS = 60;
const MS_PER_SECOND = 1000;
const WINDOW_SIZE_MS = MS_PER_SECOND / TARGET_FPS;
+ const EPSILON = 1e-6;
+
+ /**
+ * A list of metrics that are measured and tracked.
+ *
+ * See https://bit.ly/v8-gc-stats-collection for the metric naming convention.
+ * You can add a variant of an existing metric by simply adding its name to
+ * this list. E.g. v8:gc:cycle:background_threads:full:atomic:mark:cpp.
+ *
+ * If you want to add a completely new metric with its own TRACE_EVENT then
+ * you also need to add the corresponding rule to the RULES list below.
+ *
+ * @const {!Array<string>}
+ */
+ const METRICS = [
+ 'v8:gc:cycle:full',
+ 'v8:gc:cycle:full:cpp',
+ 'v8:gc:cycle:full:mark',
+ 'v8:gc:cycle:full:mark:cpp',
+ 'v8:gc:cycle:full:weak',
+ 'v8:gc:cycle:full:weak:cpp',
+ 'v8:gc:cycle:full:sweep',
+ 'v8:gc:cycle:full:sweep:cpp',
+ 'v8:gc:cycle:full:compact',
+ 'v8:gc:cycle:full:compact:cpp',
+ 'v8:gc:cycle:main_thread:full',
+ 'v8:gc:cycle:main_thread:full:cpp',
+ 'v8:gc:cycle:main_thread:full:mark',
+ 'v8:gc:cycle:main_thread:full:mark:cpp',
+ 'v8:gc:cycle:main_thread:full:weak',
+ 'v8:gc:cycle:main_thread:full:weak:cpp',
+ 'v8:gc:cycle:main_thread:full:sweep',
+ 'v8:gc:cycle:main_thread:full:sweep:cpp',
+ 'v8:gc:cycle:main_thread:full:compact',
+ 'v8:gc:cycle:main_thread:full:compact:cpp',
+ 'v8:gc:cycle:main_thread:full:atomic',
+ 'v8:gc:cycle:main_thread:full:atomic:cpp',
+ 'v8:gc:cycle:main_thread:full:atomic:mark',
+ 'v8:gc:cycle:main_thread:full:atomic:mark:cpp',
+ 'v8:gc:cycle:main_thread:full:atomic:weak',
+ 'v8:gc:cycle:main_thread:full:atomic:weak:cpp',
+ 'v8:gc:cycle:main_thread:full:atomic:sweep',
+ 'v8:gc:cycle:main_thread:full:atomic:sweep:cpp',
+ 'v8:gc:cycle:main_thread:full:atomic:compact',
+ 'v8:gc:cycle:main_thread:full:atomic:compact:cpp',
+ 'v8:gc:cycle:main_thread:full:incremental',
+ 'v8:gc:cycle:main_thread:full:incremental:cpp',
+ 'v8:gc:cycle:main_thread:full:incremental:mark',
+ 'v8:gc:cycle:main_thread:full:incremental:mark:cpp',
+ 'v8:gc:cycle:main_thread:full:incremental:sweep',
+ 'v8:gc:cycle:main_thread:full:incremental:sweep:cpp',
+ 'v8:gc:event:main_thread:full:atomic',
+ 'v8:gc:event:main_thread:full:atomic:cpp',
+ 'v8:gc:event:main_thread:full:atomic:mark',
+ 'v8:gc:event:main_thread:full:atomic:mark:cpp',
+ 'v8:gc:event:main_thread:full:atomic:weak',
+ 'v8:gc:event:main_thread:full:atomic:weak:cpp',
+ 'v8:gc:event:main_thread:full:atomic:sweep',
+ 'v8:gc:event:main_thread:full:atomic:sweep:cpp',
+ 'v8:gc:event:main_thread:full:atomic:compact',
+ 'v8:gc:event:main_thread:full:atomic:compact:cpp',
+ 'v8:gc:event:main_thread:full:incremental',
+ 'v8:gc:event:main_thread:full:incremental:cpp',
+ 'v8:gc:event:main_thread:full:incremental:mark',
+ 'v8:gc:event:main_thread:full:incremental:mark:cpp',
+ 'v8:gc:event:main_thread:full:incremental:sweep',
+ 'v8:gc:event:main_thread:full:incremental:sweep:cpp',
+ 'v8:gc:cycle:young',
+ 'v8:gc:cycle:young:mark',
+ 'v8:gc:cycle:young:weak',
+ 'v8:gc:cycle:young:sweep',
+ 'v8:gc:cycle:young:compact',
+ 'v8:gc:cycle:main_thread:young',
+ 'v8:gc:cycle:main_thread:young:mark',
+ 'v8:gc:cycle:main_thread:young:weak',
+ 'v8:gc:cycle:main_thread:young:sweep',
+ 'v8:gc:cycle:main_thread:young:compact',
+ 'v8:gc:cycle:main_thread:young:atomic',
+ 'v8:gc:cycle:main_thread:young:atomic:mark',
+ 'v8:gc:cycle:main_thread:young:atomic:weak',
+ 'v8:gc:cycle:main_thread:young:atomic:sweep',
+ 'v8:gc:cycle:main_thread:young:atomic:compact',
+ 'v8:gc:cycle:main_thread:young:incremental',
+ 'v8:gc:cycle:main_thread:young:incremental:mark',
+ 'v8:gc:cycle:main_thread:young:incremental:sweep',
+ 'v8:gc:event:main_thread:young:atomic',
+ 'v8:gc:event:main_thread:young:atomic:mark',
+ 'v8:gc:event:main_thread:young:atomic:weak',
+ 'v8:gc:event:main_thread:young:atomic:sweep',
+ 'v8:gc:event:main_thread:young:atomic:compact',
+ 'v8:gc:event:main_thread:young:incremental',
+ 'v8:gc:event:main_thread:young:incremental:mark',
+ 'v8:gc:event:main_thread:young:incremental:sweep',
+ ];
+
+ /**
+ * Shorthands for various event groups to be used in RULES below.
+ */
+ const V8_FULL_ATOMIC_EVENTS = [
+ 'V8.GC_MARK_COMPACTOR'
+ ];
+
+ const V8_FULL_MARK_EVENTS = [
+ 'V8.GC_MC_BACKGROUND_MARKING',
+ 'V8.GC_MC_MARK',
+ 'V8.GC_MC_INCREMENTAL',
+ 'V8.GC_MC_INCREMENTAL_START',
+ ];
+
+ const V8_FULL_COMPACT_EVENTS = [
+ 'V8.GC_MC_BACKGROUND_EVACUATE_COPY',
+ 'V8.GC_MC_BACKGROUND_EVACUATE_UPDATE_POINTERS',
+ 'V8.GC_MC_EVACUATE',
+ ];
+
+ const V8_FULL_SWEEP_EVENTS = [
+ 'V8.GC_MC_BACKGROUND_SWEEPING',
+ 'V8.GC_MC_SWEEP',
+ 'V8.GC_MC_COMPLETE_SWEEPING',
+ ];
+
+ const V8_FULL_WEAK_EVENTS = [
+ 'V8.GC_MC_CLEAR',
+ ];
+
+ const V8_YOUNG_ATOMIC_EVENTS = [
+ 'V8.GC_SCAVENGER_BACKGROUND_SCAVENGE_PARALLEL',
+ 'V8.GC_SCAVENGER',
+ 'V8.GC_MINOR_MARK_COMPACTOR',
+ ];
+
+ const V8_YOUNG_MARK_EVENTS = [
+ 'V8.GC_MINOR_MC_BACKGROUND_MARKING',
+ 'V8.GC_MINOR_MC_MARK',
+ 'V8.GC_MINOR_MC_INCREMENTAL',
+ 'V8.GC_MINOR_MC_INCREMENTAL_START',
+ ];
+
+ const V8_YOUNG_COMPACT_EVENTS = [
+ 'V8.GC_MINOR_MC_BACKGROUND_EVACUATE_COPY',
+ 'V8.GC_MINOR_MC_BACKGROUND_EVACUATE_UPDATE_POINTERS',
+ 'V8.GC_MINOR_MC_EVACUATE',
+ ];
+
+ const V8_YOUNG_SWEEP_EVENTS = [
+ 'V8.GC_MINOR_MC_BACKGROUND_SWEEPING',
+ 'V8.GC_MINOR_MC_SWEEP',
+ ];
+
+ const V8_YOUNG_WEAK_EVENTS = [
+ 'V8.GC_MINOR_MC_CLEAR',
+ ];
+
+ const CPP_GC_FULL_MARK_EVENTS = [
+ 'BlinkGC.AtomicPauseMarkEpilogue',
+ 'BlinkGC.AtomicPauseMarkPrologue',
+ 'BlinkGC.AtomicPauseMarkRoots',
+ 'BlinkGC.AtomicPauseMarkTransitiveClosure',
+ 'BlinkGC.ConcurrentMarkingStep',
+ 'BlinkGC.IncrementalMarkingStartMarking',
+ 'BlinkGC.IncrementalMarkingStep',
+ 'BlinkGC.MarkBailOutObjects',
+ 'BlinkGC.MarkFlushEphemeronPairs',
+ 'BlinkGC.MarkFlushV8References',
+ 'BlinkGC.UnifiedMarkingStep',
+ 'CppGC.AtomicMark',
+ 'CppGC.IncrementalMark',
+ 'CppGC.ConcurrentMark',
+ ];
+
+ const CPP_GC_FULL_COMPACT_EVENTS = [
+ 'BlinkGC.AtomicPauseSweepAndCompact',
+ 'CppGC.AtomicCompact',
+ ];
+
+ const CPP_GC_FULL_SWEEP_EVENTS = [
+ 'BlinkGC.CompleteSweep',
+ 'BlinkGC.ConcurrentSweepingStep',
+ 'BlinkGC.LazySweepInIdle',
+ 'BlinkGC.LazySweepOnAllocation',
+ 'CppGC.AtomicSweep',
+ 'CppGC.IncrementalSweep',
+ 'CppGC.ConcurrentSweep',
+ ];
+
+ const CPP_GC_FULL_WEAK_EVENTS = [
+ 'BlinkGC.MarkWeakProcessing',
+ 'CppGC.AtomicWeak',
+ ];
+
+ /**
+ * A Rule object describes a mapping from events to metrics.
+ *
+ * For each event in a single GC cycle there can be at most one rule that
+ * that applies to that event. An event E applies to a rule R if all the
+ * following conditions hold:
+ *
+ * 1. R.events contains E.title.
+ *
+ * 2. if R.inside is not empty, then at least one event X specified by
+ * R.inside exists on some thread and fully encloses E:
+ * X.start <= E.start && E.end <= X.end.
+ * Note that X and E don't have to be on the same thread, which allows
+ * us to find background thread events that happen during atomic pause.
+ *
+ * 3. if R.outside is not empty, then there is no such event X specified
+ * by R.outside that fully encloses E. This is useful for background
+ * events that happen during incremental phases.
+ *
+ * TODO(chromium:1056170): Currently event names of V8 and CppGC do not
+ * collide because V8 events are prefixed with 'V8' and CppGC events are
+ * prefixed with 'CppGC'. Revisit this it before switching to the library.
+ * We may need to either include the category to rules or keep the prefixes.
+ *
+ * @typedef {Object} Rule
+ * @property {!Array<string>} events - Event names selected by this rule.
+ * @property {?Array<string>} inside - Allowlist of enclosing event names.
+ * @property {?Array<string>} outside - Blocklist of enclosing event names.
+ * @property {string} contribute_to - The most specific target metric name.
+ * The rule automatically applies to all more general metrics that can
+ * be derived by dropping parts of the target metric name.
+ * Note that the metric name does not include granularity and threads.
+ */
+
+ /**
+ * @const {!Array<!Rule>}
+ */
+ const RULES = [
+ {
+ events: V8_FULL_ATOMIC_EVENTS,
+ contribute_to: 'full:atomic',
+ },
+ {
+ events: V8_FULL_MARK_EVENTS,
+ inside: V8_FULL_ATOMIC_EVENTS,
+ contribute_to: 'full:atomic:mark',
+ },
+ {
+ events: CPP_GC_FULL_MARK_EVENTS,
+ inside: V8_FULL_ATOMIC_EVENTS,
+ contribute_to: 'full:atomic:mark:cpp',
+ },
+ {
+ events: V8_FULL_MARK_EVENTS,
+ outside: V8_FULL_ATOMIC_EVENTS,
+ contribute_to: 'full:incremental:mark',
+ },
+ {
+ events: CPP_GC_FULL_MARK_EVENTS,
+ outside: V8_FULL_ATOMIC_EVENTS,
+ contribute_to: 'full:incremental:mark:cpp',
+ },
+ {
+ events: V8_FULL_COMPACT_EVENTS,
+ inside: V8_FULL_ATOMIC_EVENTS,
+ contribute_to: 'full:atomic:compact',
+ },
+ {
+ events: CPP_GC_FULL_COMPACT_EVENTS,
+ inside: V8_FULL_ATOMIC_EVENTS,
+ contribute_to: 'full:atomic:compact:cpp',
+ },
+ {
+ events: V8_FULL_SWEEP_EVENTS,
+ inside: V8_FULL_ATOMIC_EVENTS,
+ outside: V8_FULL_COMPACT_EVENTS,
+ contribute_to: 'full:atomic:sweep',
+ },
+ {
+ events: CPP_GC_FULL_SWEEP_EVENTS,
+ inside: V8_FULL_ATOMIC_EVENTS,
+ contribute_to: 'full:atomic:sweep:cpp',
+ },
+ {
+ events: V8_FULL_WEAK_EVENTS,
+ inside: V8_FULL_ATOMIC_EVENTS,
+ contribute_to: 'full:atomic:weak',
+ },
+ {
+ events: CPP_GC_FULL_WEAK_EVENTS,
+ inside: V8_FULL_ATOMIC_EVENTS,
+ contribute_to: 'full:atomic:weak:cpp',
+ },
+ {
+ events: V8_FULL_SWEEP_EVENTS,
+ outside: V8_FULL_ATOMIC_EVENTS.concat(V8_FULL_COMPACT_EVENTS),
+ contribute_to: 'full:incremental:sweep',
+ },
+ {
+ events: CPP_GC_FULL_SWEEP_EVENTS,
+ outside: V8_FULL_ATOMIC_EVENTS,
+ contribute_to: 'full:incremental:sweep:cpp',
+ },
+ {
+ events: V8_YOUNG_ATOMIC_EVENTS,
+ contribute_to: 'young:atomic',
+ },
+ {
+ events: V8_YOUNG_MARK_EVENTS,
+ inside: V8_YOUNG_ATOMIC_EVENTS,
+ contribute_to: 'young:atomic:mark',
+ },
+ {
+ events: V8_YOUNG_MARK_EVENTS,
+ outside: V8_YOUNG_ATOMIC_EVENTS,
+ contribute_to: 'young:incremental:mark',
+ },
+ {
+ events: V8_YOUNG_COMPACT_EVENTS,
+ inside: V8_YOUNG_ATOMIC_EVENTS,
+ contribute_to: 'young:atomic:compact',
+ },
+ {
+ events: V8_YOUNG_SWEEP_EVENTS,
+ inside: V8_YOUNG_ATOMIC_EVENTS,
+ outside: V8_YOUNG_COMPACT_EVENTS,
+ contribute_to: 'young:atomic:sweep',
+ },
+ {
+ events: V8_YOUNG_WEAK_EVENTS,
+ inside: V8_YOUNG_ATOMIC_EVENTS,
+ contribute_to: 'young:atomic:weak',
+ },
+ {
+ events: V8_YOUNG_SWEEP_EVENTS,
+ outside: V8_YOUNG_ATOMIC_EVENTS.concat(V8_YOUNG_COMPACT_EVENTS),
+ contribute_to: 'young:incremental:sweep',
+ },
+ ];
+
+ /**
+ * A part of the metric name that defines how the events contributing to
+ * the metric are aggregated. See Metric.apply() below.
+ * @enum {string}
+ */
+ const Granularity = {
+ CYCLE: 'cycle',
+ EVENT: 'event',
+ };
+
+ /**
+ * A thread of a V8 isolate is considered a main thread including:
+ *
+ * - the main thread of the renderer.
+ * - the main thread of a dedicated worker.
+ * - the main thread of a service worker.
+ *
+ * A thread that runs background tasks is considered a background
+ * thread. See jsExecutionThreadsWithTypes() below.
+ *
+ * @enum {string}
+ */
+ const ThreadType = {
+ MAIN: 'main',
+ BACKGROUND: 'background',
+ ALL_THREADS: 'all_threads',
+ };
+
+ /**
+ * Represents a single metric together with its histogram of measurements.
+ */
+ class Metric {
+ /**
+ * @param{string} name The name of the metric.
+ * See https://bit.ly/v8-gc-stats-collection for the metric naming
+ * convention and the meanining of name parts.
+ */
+ constructor(name) {
+ const parts = name.split(':');
+
+ /** @private @const {Granularity} */
+ this.granularity_ = parts[2];
+ assert(this.granularity_ === Granularity.CYCLE ||
+ this.granularity_ === Granularity.EVENT);
+
+ /** @private @const {?ThreadType} */
+ this.thread_ = ThreadType.ALL_THREADS;
+ let phasesIndex = 3;
+ if (parts[3] === 'main_thread') {
+ this.thread_ = ThreadType.MAIN;
+ phasesIndex = 4;
+ }
+ if (parts[3] === 'background_threads') {
+ this.thread_ = ThreadType.BACKGROUND;
+ phasesIndex = 4;
+ }
+
+ /** @private @const {!Array<string>} */
+ this.phases_ = parts.slice(phasesIndex);
+
+ const maxValue = this.isPerCycleMetric() ? 10000 : 1000;
+ const boundaries =
+ tr.v.HistogramBinBoundaries.createExponential(0.1, maxValue, 100);
+
+ /** @package @const {!tr.v.Histogram} */
+ this.histogram = new tr.v.Histogram(name,
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, boundaries);
+ this.histogram.customizeSummaryOptions({
+ avg: true,
+ count: true,
+ max: true,
+ min: false,
+ std: false,
+ sum: this.isPerCycleMetric(),
+ });
+ }
+
+ /**
+ * @return {boolean} Whether the granularity of this metric is per cycle.
+ */
+ isPerCycleMetric() {
+ return this.granularity_ === Granularity.CYCLE;
+ }
+
+ /**
+ * @param {!Array<string>} phases - A list of metric phases.
+ * @return {boolean} Whether the phases of this metric are more general
+ * than (or equal to) the given phases.
+ */
+ isMoreGeneralThanOrEqualTo(phases) {
+ // Check that this.phases_ is a subset of phases.
+ const phasesSet = new Set(phases.split(':'));
+ return this.phases_.every(phase => phasesSet.has(phase));
+ }
+
+ /**
+ * @param {!Array<!Rule>} rules - Rules that map events to metrics.
+ * @param {!Array<!tr.model.Slice>} events - All events of a single GC cycle
+ * including the events of the main and background threads.
+ * @return {!Array<!tr.model.Slice>} A subset of the given events that
+ * contribute to this metric.
+ */
+ contributingEvents(rules, events) {
+ // A map from an event name to the events with that name.
+ // It is used to speed up enclosing checks below.
+ const eventsByName = groupBy(events, e => e.title);
+
+ // Checks if the given rule matches (or applies) to the given event.
+ function matches(rule, event) {
+ // Checks if there is an event with the given name that encloses
+ // |event|.
+ function isEnclosing(name) {
+ if (!eventsByName.has(name)) return false;
+ return eventsByName.get(name).some(e => encloses(e, event));
+ }
+ if (!rule.events.includes(event.title)) {
+ return false;
+ }
+ if (rule.inside && !rule.inside.some(isEnclosing)) {
+ return false;
+ }
+ if (rule.outside && rule.outside.some(isEnclosing)) {
+ return false;
+ }
+ return true;
+ }
+
+ // For each event find the applying rule and check if the rule also
+ // applies to this metric.
+ const result = [];
+ for (const event of events) {
+ const matching = rules.filter(r => matches(r, event));
+ if (matching.length === 0) {
+ continue;
+ }
+ assert(matching.length === 1,
+ `${event.userFriendlyName} matches more than one rule: ` +
+ JSON.stringify(matching));
+ if (this.isMoreGeneralThanOrEqualTo(matching[0].contribute_to)) {
+ result.push(event);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Finds all events that contribute to this metric and aggregates them
+ * in the metric's histogram.
+ *
+ * @param {!Array<!Rule>} rules - Rules that map events to metrics.
+ * @param {!Array<!tr.model.Slice>} events - All events of a single GC cycle
+ * including the events of the main and background threads.
+ * @param {!Map<number, ThreadType>} threadTypes - A map from a thread-id
+ * to a thread type.
+ */
+ apply(rules, events, threadTypes) {
+ // Find all events that are relevant for this metric.
+ const filtered = this.contributingEvents(rules, events);
+
+ // Group the events by thread.
+ const eventsByThread = groupBy(filtered, e => e.parentContainer.tid);
+
+ // Drop events nested in other events and also drop events from
+ // the irrelevant threads.
+ let flattened = [];
+ for (const [tid, threadEvents] of eventsByThread) {
+ if (this.thread_ === ThreadType.ALL_THREADS ||
+ this.thread_ === threadTypes.get(tid)) {
+ flattened = flattened.concat(flatten(threadEvents));
+ }
+ }
+
+ // Aggregate events in the histogram.
+ if (this.isPerCycleMetric()) {
+ let sum = 0;
+ for (const event of flattened) {
+ sum += event.cpuDuration;
+ }
+ if (flattened.length > 0) {
+ this.histogram.addSample(sum);
+ }
+ } else {
+ for (const event of flattened) {
+ this.histogram.addSample(event.cpuDuration);
+ }
+ }
+ }
+ }
+
+ /**
+ * A helper for checking the condition.
+ * @param {boolean} condition
+ * @param {string} message
+ */
+ function assert(condition, message) {
+ if (!condition) {
+ throw new Error(message);
+ }
+ }
+
+ /**
+ * A helper for grouping objects by the custom key.
+ * @param {!Array<!Object>} objects
+ * @param {!function(!Object): !Object} keyCallback
+ * @param {!Map<!Object, !Array<!Object>>} Objects grouped by the key.
+ */
+ function groupBy(objects, keyCallback) {
+ const result = new Map();
+ for (const object of objects) {
+ const group = keyCallback(object);
+ if (result.has(group)) {
+ result.get(group).push(object);
+ } else {
+ result.set(group, [object]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * A helper for getting all events relevant for the rules.
+ * @param {!Array<Rule>} rules
+ * @return {!Array<string>} Event names.
+ */
+ function eventsMentionedIn(rules) {
+ let result = [];
+ for (const rule of rules) {
+ result = result.concat(rule.events);
+ if (rule.inside) {
+ result = result.concat(rule.inside);
+ }
+ if (rule.outside) {
+ result = result.concat(rule.outside);
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * Performs thread-independent check for event nesting.
+ *
+ * @param {!Array<!tr.model.Slice>} event1
+ * @param {!Array<!tr.model.Slice>} event2
+ * @return {boolean} Whether the first event encloses the second event.
+ */
+ function encloses(event1, event2) {
+ return (event1.start - EPSILON <= event2.start &&
+ event2.end <= event1.end + EPSILON);
+ }
+
+ /**
+ * Finds all threads that may execute JS code in the given renderer
+ * and return them together with the thread-id to thread type mapping.
+ *
+ * @param {!tr.model.helpers.ChromeRendererHelper} rendererHelper
+ * @return {[!Array<tr.model.Thread>, !Map<number, ThreadType>] A pair
+ * of a thread list and a thread type mapping.
+ */
+ function jsExecutionThreadsWithTypes(rendererHelper) {
+ const mainThreads = ([rendererHelper.mainThread]
+ .concat(rendererHelper.dedicatedWorkerThreads)
+ .concat(rendererHelper.serviceWorkerThreads));
+ const backgroundThreads = rendererHelper.foregroundWorkerThreads;
+ const threadTypes = new Map();
+ for (const thread of mainThreads) {
+ threadTypes.set(thread.tid, ThreadType.MAIN);
+ }
+ for (const thread of backgroundThreads) {
+ threadTypes.set(thread.tid, ThreadType.BACKGROUND);
+ }
+ return [mainThreads.concat(backgroundThreads), threadTypes];
+ }
+
+ /**
+ * Drops all events that are nested in other events.
+ *
+ * @param {!Array<!tr.model.Slice>} events - Events on the same thread.
+ * @return {!Array<!tr.model.Slice>} Top-level events.
+ */
+ function flatten(events) {
+ function compareWithEpsilon(a, b) {
+ if (a.start < b.start - EPSILON) return -1;
+ if (a.start > b.start + EPSILON) return 1;
+ return b.end - a.end;
+ }
+ events.sort(compareWithEpsilon);
+ let last = events[0];
+ const result = [last];
+ for (const e of events) {
+ if (e.end > last.end + EPSILON) {
+ assert(e.start >= last.end - EPSILON,
+ 'Overlapping events: ' +
+ e.userFriendlyName + ' ' +
+ last.userFriendlyName);
+ result.push(e);
+ last = e;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Groups the events by GC cycle using the epoch argument of events.
+ *
+ * The function is more complex than expected for two reasons:
+ *
+ * 1. V8 and CppGC do not syncronize their epoch counters (yet).
+ * 2. V8 adds the epoch argument only to the top-level events. Nested
+ * events are not requred to have the epoch.
+ *
+ * The function first finds the mapping from CppGC epoch to V8 epoch assuming
+ * that there will always be a CppGC event nested in a V8 event.
+ * Then it finds the V8 epoch for each nested V8 event and CppGC event.
+ * Finally, it groups the events by their V8 epoch.
+ *
+ * @param {!Array<!tr.model.Slice>} events - Events from multiple GC cycles
+ * and multiple threads.
+ * @return {!Map<string, !Array<!tr.model.Slice>>} Grouped events.
+ */
+ function groupByEpoch(events) {
+ function isV8Event(event) {
+ // TODO(1056170): Adjust this if the CppGC library uses a v8 category
+ // for trace events.
+ return event.category && event.category.includes('v8');
+ }
+
+ // Finds the V8 and CppGC epoch arguments in the given event and its
+ // ancestors.
+ function getEpoch(event) {
+ function checkEpochConsistency(epoch, event) {
+ if (epoch === null) return;
+ assert(epoch === event.args.epoch,
+ `${event.userFriendlyName} has epoch ${event.args.epoch} ` +
+ `which contradicts the epoch of nested events ${epoch}`);
+ }
+ const result = {v8: null, cpp: null};
+ while (event) {
+ if ('epoch' in event.args) {
+ if (isV8Event(event)) {
+ checkEpochConsistency(result.v8, event);
+ result.v8 = event.args.epoch;
+ } else {
+ checkEpochConsistency(result.cpp, event);
+ result.cpp = event.args.epoch;
+ }
+ }
+ event = event.parentSlice;
+ }
+ return result;
+ }
+
+ // The following two functions combine V8 and CppGC epoch into a single
+ // global epoch. We give V8 even global epochs and CppGC odd global epochs.
+ function GlobalEpochFromV8(v8Epoch) {
+ return 2 * v8Epoch;
+ }
+
+ function GlobalEpochFromCpp(cppEpoch) {
+ return 2 * cppEpoch + 1;
+ }
+
+ // Find the mapping from a CppGC epoch to a V8 epoch.
+ const cppToV8 = new Map();
+ for (const event of events) {
+ const epoch = getEpoch(event);
+ if (epoch.cpp !== null && epoch.v8 !== null) {
+ // The start of V8 incremental marking may finalize GppGC sweeping
+ // of the previous garbage collection cycle. Thus it may happen that
+ // the same CppGC epoch maps to two V8 epoch. In such a conflict
+ // the smaller V8 epoch wins, essentially mapping the event back to
+ // the previous V8 cycle.
+ if (!cppToV8.has(epoch.cpp) || cppToV8.get(epoch.cpp) > epoch.v8) {
+ cppToV8.set(epoch.cpp, epoch.v8);
+ }
+ }
+ }
+
+ // For each event infer its V8 epoch and group by that.
+ const result = new Map();
+ for (const event of events) {
+ const epoch = getEpoch(event);
+ if (epoch.cpp === null && epoch.v8 === null) {
+ continue;
+ }
+ let globalEpoch;
+ if (epoch.v8 !== null) {
+ // Case 1: either a V8 event or a CppGC event nested in a V8 event.
+ globalEpoch = GlobalEpochFromV8(epoch.v8);
+ } else if (cppToV8.has(epoch.cpp)) {
+ // Case 2: A CppGC event of a unified garbage collection.
+ globalEpoch = GlobalEpochFromV8(cppToV8.get(epoch.cpp));
+ } else {
+ // Case 3: An event from a standalone CppGC garbage collection.
+ globalEpoch = GlobalEpochFromCpp(epoch.cpp);
+ }
+ if (result.has(globalEpoch)) {
+ result.get(globalEpoch).push(event);
+ } else {
+ result.set(globalEpoch, [event]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The main entry point of GC metric computation.
+ *
+ * @param {!Array<string} metricNames - A list of metric names to be computed.
+ * @param {!tr.v.HistogramSet} histograms - The histogram set where the new
+ * histograms will be added.
+ * @param {!tr.Model} model
+ */
+ function addGarbageCollectionMetrics(metricNames, histograms, model) {
+ // Parse the metric names and store them in a list.
+ const metrics = metricNames.map(name => new Metric(name));
+
+ // Compute the set of GC related event names.
+ const gcEventNames = new Set(eventsMentionedIn(RULES));
+
+ // Iterate all renderer processes.
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ for (const rendererHelper of Object.values(chromeHelper.rendererHelpers)) {
+ if (rendererHelper.isChromeTracingUI) continue;
+
+ const [threads, threadTypes] =
+ jsExecutionThreadsWithTypes(rendererHelper);
+
+ // Get all GC related events.
+ const events = [];
+ for (const thread of threads) {
+ for (const event of thread.sliceGroup.childEvents()) {
+ if (gcEventNames.has(event.title)) {
+ events.push(event);
+ }
+ }
+ }
+
+ // Group events by GC cycle and consider each cycle separately.
+ for (const cycleEvents of groupByEpoch(events).values()) {
+ // If any event is in the cycle is forced, then the whole cycle
+ // is considered forced. Skip all events in the cycle.
+ if (cycleEvents.some(
+ tr.metrics.v8.utils.isForcedGarbageCollectionEvent)) {
+ continue;
+ }
+
+ for (const metric of metrics) {
+ metric.apply(RULES, cycleEvents, threadTypes);
+ }
+ }
+ }
+
+ // Add the new histograms to the resulting histogram set.
+ for (const metric of metrics) {
+ histograms.addHistogram(metric.histogram);
+ }
+ }
function gcMetric(histograms, model, options) {
options = options || {};
@@ -36,6 +825,7 @@ tr.exportTo('tr.metrics.v8', function() {
addTotalMarkCompactorTime(histograms, model);
addTotalMarkCompactorMarkingTime(histograms, model);
addScavengerSurvivedFromStackEvents(histograms, model);
+ addGarbageCollectionMetrics(METRICS, histograms, model);
}
tr.metrics.MetricRegistry.register(gcMetric);
@@ -380,6 +1170,7 @@ tr.exportTo('tr.metrics.v8', function() {
return {
gcMetric,
WINDOW_SIZE_MS, // For testing purposes only.
+ addGarbageCollectionMetrics, // For testing purposes only.
};
});
</script>