blob: b6bb560c519ef212236cd6e2d901b7066f998a57 [file] [log] [blame]
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04001/**
2 * @license
Ben Rohlfs94fcbbc2022-05-27 10:45:03 +02003 * Copyright 2016 Google LLC
4 * SPDX-License-Identifier: Apache-2.0
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04005 */
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +02006import {FlagsService} from '../flags/flags';
Ben Rohlfsa7ab9502021-02-15 17:45:45 +01007import {EventValue, ReportingService, Timer} from './gr-reporting';
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +02008import {hasOwnProperty} from '../../utils/common-util';
Milutin Kristofic3a844522021-01-21 10:01:55 +01009import {NumericChangeId} from '../../types/common';
Ben Rohlfsbd4c91b2021-12-22 11:37:41 +010010import {Deduping, EventDetails, ReportingOptions} from '../../api/reporting';
Ben Rohlfs94f34a12021-02-26 18:37:32 +010011import {PluginApi} from '../../api/plugin';
Milutin Kristofica7c24c12021-06-07 23:09:26 +020012import {
13 Execution,
14 Interaction,
15 LifeCycle,
16 Timing,
17} from '../../constants/reporting';
Milutin Kristoficd9b92d82023-02-02 21:20:47 +010018import {onCLS, onFID, onLCP, Metric, onINP} from 'web-vitals';
Ben Rohlfsf4313302023-02-23 11:57:05 +010019import {getEventPath, isElementTarget} from '../../utils/dom-util';
Dhruv Srivastava7cfec742023-09-28 10:25:47 +020020import {Finalizable} from '../../types/types';
Viktar Donich5ad42282016-08-26 16:07:24 -070021
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010022// Latency reporting constants.
Milutin Kristofic0fe96792020-03-26 13:17:35 +010023
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010024const TIMING = {
25 TYPE: 'timing-report',
Milutin Kristofic0fe96792020-03-26 13:17:35 +010026 CATEGORY: {
27 UI_LATENCY: 'UI Latency',
28 RPC: 'RPC Timing',
29 },
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010030};
Viktar Donichc1c5f772018-05-04 15:04:44 -070031
Milutin Kristofic0fe96792020-03-26 13:17:35 +010032const LIFECYCLE = {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010033 TYPE: 'lifecycle',
Milutin Kristofic0fe96792020-03-26 13:17:35 +010034 CATEGORY: {
35 DEFAULT: 'Default',
36 EXTENSION_DETECTED: 'Extension detected',
37 PLUGINS_INSTALLED: 'Plugins installed',
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +020038 VISIBILITY: 'Visibility',
Ben Rohlfsbad48d42020-12-17 15:03:33 +010039 EXECUTION: 'Execution',
Milutin Kristofic0fe96792020-03-26 13:17:35 +010040 },
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010041};
Viktar Donich761ce28e2018-05-04 15:34:10 -070042
Milutin Kristofic0fe96792020-03-26 13:17:35 +010043const INTERACTION = {
44 TYPE: 'interaction',
45 CATEGORY: {
46 DEFAULT: 'Default',
47 VISIBILITY: 'Visibility',
48 },
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010049};
Viktar Donich5ad42282016-08-26 16:07:24 -070050
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010051const NAVIGATION = {
52 TYPE: 'nav-report',
Milutin Kristofic0fe96792020-03-26 13:17:35 +010053 CATEGORY: {
54 LOCATION_CHANGED: 'Location Changed',
55 },
56 EVENT: {
57 PAGE: 'Page',
58 },
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010059};
Viktar Donich1c97ee72017-01-17 11:13:20 -080060
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010061const ERROR = {
62 TYPE: 'error',
Milutin Kristofic0fe96792020-03-26 13:17:35 +010063 CATEGORY: {
64 EXCEPTION: 'exception',
65 ERROR_DIALOG: 'Error Dialog',
66 },
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010067};
Viktar Donichd155b8b2018-04-19 22:18:31 -070068
Milutin Kristofice1a562f2021-06-01 19:51:11 +020069const PLUGIN = {
70 TYPE: 'plugin-log',
71 CATEGORY: {
72 LIFECYCLE: 'lifecycle',
73 INTERACTION: 'interaction',
74 },
75};
76
Milutin Kristofic48a0f432021-03-16 22:53:08 +010077const STARTUP_TIMERS: {[name: string]: number} = {
78 [Timing.PLUGINS_LOADED]: 0,
79 [Timing.METRICS_PLUGIN_LOADED]: 0,
80 [Timing.STARTUP_CHANGE_DISPLAYED]: 0,
81 [Timing.STARTUP_CHANGE_LOAD_FULL]: 0,
82 [Timing.STARTUP_DASHBOARD_DISPLAYED]: 0,
83 [Timing.STARTUP_DIFF_VIEW_CONTENT_DISPLAYED]: 0,
84 [Timing.STARTUP_DIFF_VIEW_DISPLAYED]: 0,
Milutin Kristofic48a0f432021-03-16 22:53:08 +010085 [Timing.STARTUP_FILE_LIST_DISPLAYED]: 0,
86 [Timing.APP_STARTED]: 0,
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +020087 // WebComponentsReady timer is triggered from gr-router.
Milutin Kristofic48a0f432021-03-16 22:53:08 +010088 [Timing.WEB_COMPONENTS_READY]: 0,
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +020089};
Wyatt Allenf87244c2017-02-24 15:35:57 -080090
Ben Rohlfs055d4a42023-05-03 13:55:08 +020091// List of timers that should NOT be reset before a location change.
92const LOCATION_CHANGE_OK_TIMERS: (string | Timing)[] = [Timing.SEND_REPLY];
93
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010094const SLOW_RPC_THRESHOLD = 500;
Milutin Kristoficc9a35972019-08-21 12:35:57 +020095
Chris Poucetc6e880b2021-11-15 19:57:06 +010096export function initErrorReporter(reportingService: ReportingService) {
Milutin Kristofic380b9f62020-12-03 09:24:17 +010097 const normalizeError = (err: Error | unknown) => {
98 if (err instanceof Error) {
99 return err;
100 }
101 let msg = '';
102 if (typeof err === 'string') {
103 msg += err;
104 } else {
105 msg += JSON.stringify(err);
106 }
107 const error = new Error(msg);
108 error.stack = 'unknown';
109 return error;
110 };
111 // TODO(dmfilippov): TS-fix-any oldOnError - define correct type
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200112 const onError = function (
113 oldOnError: Function,
Tao Zhou38b3e0d2020-09-09 17:02:21 +0200114 msg: Event | string,
115 url?: string,
116 line?: number,
117 column?: number,
118 error?: Error
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200119 ) {
Milutin Kristoficda88b332020-03-24 10:19:12 +0100120 if (oldOnError) {
121 oldOnError(msg, url, line, column, error);
Viktar Donich1c97ee72017-01-17 11:13:20 -0800122 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100123 if (error) {
Milutin Kristofic380b9f62020-12-03 09:24:17 +0100124 line = line ?? error.lineNumber;
125 column = column ?? error.columnNumber;
Milutin Kristoficda88b332020-03-24 10:19:12 +0100126 }
Kamil Musinf0ece022022-10-14 15:22:44 +0200127 reportingService.error('onError', normalizeError(error), {
Milutin Kristoficda88b332020-03-24 10:19:12 +0100128 line,
129 column,
Milutin Kristofic380b9f62020-12-03 09:24:17 +0100130 url,
131 msg,
132 });
Milutin Kristoficda88b332020-03-24 10:19:12 +0100133 return true;
134 };
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200135 // TODO(dmfilippov): TS-fix-any unclear what is context
Dhruv Srivastavaf97b0552020-11-30 14:14:09 +0100136 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Dhruv Srivastavab3ed8742023-03-24 14:42:06 +0100137 const catchErrors = function (context?: any) {
138 context = context || window;
Ben Rohlfscf59bdd2020-09-11 11:27:54 +0200139 const oldOnError = context.onerror;
Tao Zhou38b3e0d2020-09-09 17:02:21 +0200140 context.onerror = (
141 event: Event | string,
142 source?: string,
143 lineno?: number,
144 colno?: number,
145 error?: Error
Frank Borden6988bdf2021-04-07 14:42:00 +0200146 ) => onError(oldOnError, event, source, lineno, colno, error);
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200147 context.addEventListener(
148 'unhandledrejection',
149 (e: PromiseRejectionEvent) => {
Kamil Musinf0ece022022-10-14 15:22:44 +0200150 reportingService.error('unhandledrejection', normalizeError(e.reason));
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200151 }
152 );
Milutin Kristoficda88b332020-03-24 10:19:12 +0100153 };
154
155 catchErrors();
156
157 // for testing
158 return {catchErrors};
159}
160
Chris Poucetc6e880b2021-11-15 19:57:06 +0100161export function initPerformanceReporter(reportingService: ReportingService) {
Milutin Kristoficda88b332020-03-24 10:19:12 +0100162 // PerformanceObserver interface is a browser API.
163 if (window.PerformanceObserver) {
164 const supportedEntryTypes = PerformanceObserver.supportedEntryTypes || [];
165 // Safari doesn't support longtask yet
166 if (supportedEntryTypes.includes('longtask')) {
167 const catchLongJsTasks = new PerformanceObserver(list => {
168 for (const task of list.getEntries()) {
Milutin Kristofic94625702022-09-09 10:17:18 +0200169 // We are interested in longtask longer than 400 ms (default is 50 ms)
170 if (task.duration > 400) {
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200171 reportingService.reporter(
172 TIMING.TYPE,
173 TIMING.CATEGORY.UI_LATENCY,
174 `Task ${task.name}`,
175 Math.round(task.duration),
176 {},
177 false
178 );
Milutin Kristoficda88b332020-03-24 10:19:12 +0100179 }
180 }
181 });
182 catchLongJsTasks.observe({entryTypes: ['longtask']});
183 }
Tao Zhoud59cb8b2019-11-13 20:01:09 +0000184 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100185}
Tao Zhoud59cb8b2019-11-13 20:01:09 +0000186
Chris Poucetc6e880b2021-11-15 19:57:06 +0100187export function initVisibilityReporter(reportingService: ReportingService) {
Milutin Kristoficda88b332020-03-24 10:19:12 +0100188 document.addEventListener('visibilitychange', () => {
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200189 reportingService.onVisibilityChange();
Milutin Kristoficda88b332020-03-24 10:19:12 +0100190 });
Ben Rohlfs8cabee12023-04-27 13:32:59 +0200191 window.addEventListener('blur', () => {
192 reportingService.onFocusChange();
193 });
194 window.addEventListener('focus', () => {
195 reportingService.onFocusChange();
196 });
Milutin Kristoficda88b332020-03-24 10:19:12 +0100197}
Milutin Kristofic74de4842020-01-29 14:09:01 +0100198
Ben Rohlfsf4313302023-02-23 11:57:05 +0100199export function initClickReporter(reportingService: ReportingService) {
200 document.addEventListener('click', (e: MouseEvent) => {
201 const anchorEl = e
202 .composedPath()
203 .find(el => isElementTarget(el) && el.tagName.toUpperCase() === 'A') as
204 | HTMLAnchorElement
205 | undefined;
206 if (!anchorEl) return;
207 reportingService.reportInteraction(Interaction.LINK_CLICK, {
208 path: getEventPath(e),
209 link: anchorEl.href,
210 text: anchorEl.innerText,
211 });
212 });
213}
214
Ben Rohlfs991c03b2023-04-25 11:20:38 +0200215/**
216 * Reports generic user interaction every x seconds to detect, if the user is
217 * present and is using the application somehow. If you just look at
218 * `document.visibilityState`, then the user may have left the browser open
219 * without locking the screen. So it helps to know whether some interaction is
220 * actually happening.
221 */
222export class InteractionReporter implements Finalizable {
223 /** Accumulates event names until the next round of interaction reporting. */
224 private interactionEvents = new Set<string>();
225
226 /** Allows clearing the interval timer. Mostly useful for tests. */
227 private intervalId?: number;
228
229 constructor(
230 private readonly reportingService: ReportingService,
231 private readonly reportingIntervalMs = 10 * 1000
232 ) {
233 const events = ['mousemove', 'scroll', 'wheel', 'keydown', 'pointerdown'];
234 for (const eventName of events) {
235 document.addEventListener(eventName, () =>
236 this.interactionEvents.add(eventName)
237 );
238 }
239
240 this.intervalId = window.setInterval(
241 () => this.report(),
242 this.reportingIntervalMs
243 );
244 }
245
246 finalize() {
247 window.clearInterval(this.intervalId);
248 }
249
250 private report() {
251 const active = this.interactionEvents.size > 0;
252 if (active) {
253 this.reportingService.reportInteraction(Interaction.USER_ACTIVE, {
254 events: [...this.interactionEvents],
255 });
256 } else if (document.visibilityState === 'visible') {
257 this.reportingService.reportInteraction(Interaction.USER_PASSIVE, {});
258 }
259 this.interactionEvents.clear();
260 }
261}
262
263let interactionReporter: InteractionReporter;
264
265export function initInteractionReporter(reportingService: ReportingService) {
266 if (!interactionReporter) {
267 interactionReporter = new InteractionReporter(reportingService);
268 }
269}
270
Milutin Kristoficaad3ce42022-06-01 20:21:24 +0200271export function initWebVitals(reportingService: ReportingService) {
272 function reportWebVitalMetric(name: Timing, metric: Metric) {
Milutin Kristofice19583e2023-02-07 15:13:21 +0100273 let score = metric.value;
274 // CLS good score is 0.1 and poor score is 0.25. Logging system
275 // prefers integers, so we multiple by 100;
276 if (name === Timing.CLS) {
277 score *= 100;
278 }
Milutin Kristoficaad3ce42022-06-01 20:21:24 +0200279 reportingService.reporter(
280 TIMING.TYPE,
281 TIMING.CATEGORY.UI_LATENCY,
282 name,
Milutin Kristofice19583e2023-02-07 15:13:21 +0100283 score,
284 {
285 navigationType: metric.navigationType,
286 rating: metric.rating,
287 entries: metric.entries,
288 }
Milutin Kristoficaad3ce42022-06-01 20:21:24 +0200289 );
290 }
291
Milutin Kristoficd9b92d82023-02-02 21:20:47 +0100292 onCLS(metric => reportWebVitalMetric(Timing.CLS, metric));
293 onFID(metric => reportWebVitalMetric(Timing.FID, metric));
294 onLCP(metric => reportWebVitalMetric(Timing.LCP, metric));
295 onINP(metric => reportWebVitalMetric(Timing.INP, metric));
Milutin Kristoficaad3ce42022-06-01 20:21:24 +0200296}
297
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200298// Calculates the time of Gerrit being in a background tab. When Gerrit reports
299// a pageLoad metric it’s attached to its details for latency analysis.
300// It resets on locationChange.
301class HiddenDurationTimer {
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200302 public accHiddenDurationMs = 0;
303
304 public lastVisibleTimestampMs: number | null = null;
305
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200306 constructor() {
307 this.reset();
308 }
309
310 reset() {
311 this.accHiddenDurationMs = 0;
312 this.lastVisibleTimestampMs = 0;
313 }
314
315 onVisibilityChange() {
316 if (document.visibilityState === 'hidden') {
317 this.lastVisibleTimestampMs = now();
318 } else if (document.visibilityState === 'visible') {
319 if (this.lastVisibleTimestampMs !== null) {
320 this.accHiddenDurationMs += now() - this.lastVisibleTimestampMs;
321 // Set to null for guarding against two 'visible' events in a row.
322 this.lastVisibleTimestampMs = null;
323 }
324 }
325 }
326
327 get hiddenDurationMs() {
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200328 if (
329 document.visibilityState === 'hidden' &&
330 this.lastVisibleTimestampMs !== null
331 ) {
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200332 return this.accHiddenDurationMs + now() - this.lastVisibleTimestampMs;
333 }
334 return this.accHiddenDurationMs;
335 }
336}
337
338export function now() {
339 return Math.round(window.performance.now());
340}
341
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200342type PeformanceTimingEventName = keyof Omit<PerformanceTiming, 'toJSON'>;
343
344interface EventInfo {
345 type: string;
346 category: string;
347 name: string;
348 value?: EventValue;
349 eventStart: number;
350 eventDetails?: string;
351 repoName?: string;
Milutin Kristofic3a844522021-01-21 10:01:55 +0100352 changeId?: string;
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200353 inBackgroundTab?: boolean;
354 enabledExperiments?: string;
355}
356
357interface PageLoadDetails {
358 rpcList: SlowRpcCall[];
359 hiddenDurationMs: number;
360 screenSize?: {width: number; height: number};
361 viewport?: {width: number; height: number};
362 usedJSHeapSizeMb?: number;
363}
364
365interface SlowRpcCall {
366 anonymizedUrl: string;
367 elapsed: number;
368}
369
370type PendingReportInfo = [EventInfo, boolean | undefined];
371
Chris Poucet55cbccb2021-11-16 03:17:06 +0100372export class GrReporting implements ReportingService, Finalizable {
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200373 private readonly _flagsService: FlagsService;
374
375 private readonly _baselines = STARTUP_TIMERS;
376
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100377 private reportRepoName: string | undefined;
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200378
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100379 private reportChangeId: NumericChangeId | undefined;
Milutin Kristofic3a844522021-01-21 10:01:55 +0100380
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100381 private pending: PendingReportInfo[] = [];
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200382
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100383 private slowRpcList: SlowRpcCall[] = [];
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200384
Ben Rohlfsbad48d42020-12-17 15:03:33 +0100385 /**
Ben Rohlfsbd4c91b2021-12-22 11:37:41 +0100386 * Keeps track of which ids were already reported for events that should only
387 * be reported once per session.
Ben Rohlfsbad48d42020-12-17 15:03:33 +0100388 */
Ben Rohlfsbd4c91b2021-12-22 11:37:41 +0100389 private reportedIds = new Set<string>();
Ben Rohlfsbad48d42020-12-17 15:03:33 +0100390
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200391 public readonly hiddenDurationTimer = new HiddenDurationTimer();
392
393 constructor(flagsService: FlagsService) {
Milutin Kristoficda88b332020-03-24 10:19:12 +0100394 this._flagsService = flagsService;
Milutin Kristoficda88b332020-03-24 10:19:12 +0100395 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100396
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200397 private get performanceTiming() {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100398 return window.performance.timing;
Milutin Kristoficda88b332020-03-24 10:19:12 +0100399 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100400
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200401 private get slowRpcSnapshot() {
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100402 return (this.slowRpcList || []).slice();
Milutin Kristoficda88b332020-03-24 10:19:12 +0100403 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100404
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200405 private _arePluginsLoaded() {
406 return (
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100407 this._baselines && !hasOwnProperty(this._baselines, Timing.PLUGINS_LOADED)
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200408 );
Milutin Kristoficda88b332020-03-24 10:19:12 +0100409 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100410
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200411 private _isMetricsPluginLoaded() {
412 return (
413 this._arePluginsLoaded() ||
414 (this._baselines &&
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100415 !hasOwnProperty(this._baselines, Timing.METRICS_PLUGIN_LOADED))
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200416 );
Milutin Kristoficda88b332020-03-24 10:19:12 +0100417 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100418
Chris Poucet55cbccb2021-11-16 03:17:06 +0100419 finalize() {}
420
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100421 /**
422 * Reporter reports events. Events will be queued if metrics plugin is not
423 * yet installed.
424 *
Dmitrii Filippov486c4882020-08-06 18:34:08 +0200425 * @param noLog If true, the event will not be logged to the JS console.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100426 */
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200427 reporter(
428 type: string,
429 category: string,
430 eventName: string,
431 eventValue?: EventValue,
432 eventDetails?: EventDetails,
Dmitrii Filippov486c4882020-08-06 18:34:08 +0200433 noLog?: boolean
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200434 ) {
435 const eventInfo = this._createEventInfo(
436 type,
437 category,
438 eventName,
439 eventValue,
440 eventDetails
441 );
Milutin Kristofic0fe96792020-03-26 13:17:35 +0100442 if (type === ERROR.TYPE && category === ERROR.CATEGORY.EXCEPTION) {
Dhruv Srivastavab68c85f2020-11-30 19:04:34 +0100443 console.error(
444 (typeof eventValue === 'object' && eventValue.error) || eventName
445 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100446 }
447
448 // We report events immediately when metrics plugin is loaded
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100449 if (this._isMetricsPluginLoaded() && !this.pending.length) {
Dmitrii Filippov486c4882020-08-06 18:34:08 +0200450 this._reportEvent(eventInfo, noLog);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100451 } else {
452 // We cache until metrics plugin is loaded
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100453 this.pending.push([eventInfo, noLog]);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100454 if (this._isMetricsPluginLoaded()) {
Dhruv Srivastavab3ed8742023-03-24 14:42:06 +0100455 this.pending.forEach(([eventInfo, noLog]) => {
456 this._reportEvent(eventInfo, noLog);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100457 });
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100458 this.pending = [];
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100459 }
460 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100461 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100462
Dhruv Srivastavab3ed8742023-03-24 14:42:06 +0100463 private _reportEvent(eventInfo: EventInfo, noLog?: boolean) {
Ben Rohlfs53998552022-03-30 17:47:28 +0200464 const {type, value, name, eventDetails} = eventInfo;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100465 document.dispatchEvent(new CustomEvent(type, {detail: eventInfo}));
Dhruv Srivastavab3ed8742023-03-24 14:42:06 +0100466 if (noLog) {
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200467 return;
468 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100469 if (type !== ERROR.TYPE) {
470 if (value !== undefined) {
Dhruv Srivastava805f49102022-07-19 13:17:20 +0200471 console.debug(
472 `Reporting(${new Date().toISOString()}): ${name}: ${value}`
473 );
Ben Rohlfs53998552022-03-30 17:47:28 +0200474 } else if (eventDetails !== undefined) {
Dhruv Srivastava805f49102022-07-19 13:17:20 +0200475 console.debug(
476 `Reporting(${new Date().toISOString()}): ${name}: ${eventDetails}`
477 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100478 } else {
Dhruv Srivastava805f49102022-07-19 13:17:20 +0200479 console.debug(`Reporting(${new Date().toISOString()}): ${name}`);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100480 }
481 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100482 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100483
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200484 private _createEventInfo(
485 type: string,
486 category: string,
487 name: string,
488 value?: EventValue,
489 eventDetails?: EventDetails
490 ): EventInfo {
491 const eventInfo: EventInfo = {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100492 type,
493 category,
494 name,
495 value,
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200496 eventStart: now(),
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100497 };
498
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200499 if (
500 typeof eventDetails === 'object' &&
501 Object.entries(eventDetails).length !== 0
502 ) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100503 eventInfo.eventDetails = JSON.stringify(eventDetails);
504 }
Tao Zhoucef79172020-04-08 16:05:31 +0200505
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100506 if (this.reportRepoName) {
507 eventInfo.repoName = this.reportRepoName;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100508 }
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100509 if (this.reportChangeId) {
510 eventInfo.changeId = `${this.reportChangeId}`;
Milutin Kristofic3a844522021-01-21 10:01:55 +0100511 }
Tao Zhoucef79172020-04-08 16:05:31 +0200512
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100513 const isInBackgroundTab = document.visibilityState === 'hidden';
514 if (isInBackgroundTab !== undefined) {
515 eventInfo.inBackgroundTab = isInBackgroundTab;
516 }
517
Milutin Kristoficb131af92021-07-13 15:42:34 +0200518 if (
519 name === Timing.APP_STARTED &&
520 this._flagsService.enabledExperiments.length
521 ) {
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200522 eventInfo.enabledExperiments = JSON.stringify(
523 this._flagsService.enabledExperiments
524 );
Tao Zhoucef79172020-04-08 16:05:31 +0200525 }
526
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100527 return eventInfo;
Milutin Kristoficda88b332020-03-24 10:19:12 +0100528 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100529
530 /**
531 * User-perceived app start time, should be reported when the app is ready.
532 */
533 appStarted() {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100534 this.timeEnd(Timing.APP_STARTED);
Milutin Kristofic091ab762020-03-19 16:37:48 +0100535 this._reportNavResTimes();
Milutin Kristoficda88b332020-03-24 10:19:12 +0100536 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100537
Ben Rohlfs8cabee12023-04-27 13:32:59 +0200538 onFocusChange() {
539 this.reporter(
540 LIFECYCLE.TYPE,
541 LIFECYCLE.CATEGORY.VISIBILITY,
542 LifeCycle.FOCUS,
543 undefined,
544 {
545 isVisible: document.visibilityState === 'visible',
546 hasFocus: document.hasFocus(),
547 },
548 false
549 );
550 }
551
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200552 onVisibilityChange() {
553 this.hiddenDurationTimer.onVisibilityChange();
Milutin Kristoficaaef49a2021-03-10 13:30:19 +0100554 let eventName;
555 if (document.visibilityState === 'hidden') {
556 eventName = LifeCycle.VISIBILILITY_HIDDEN;
557 } else if (document.visibilityState === 'visible') {
558 eventName = LifeCycle.VISIBILILITY_VISIBLE;
559 }
560 if (eventName)
561 this.reporter(
562 LIFECYCLE.TYPE,
563 LIFECYCLE.CATEGORY.VISIBILITY,
564 eventName,
565 undefined,
566 {
567 hiddenDurationMs: this.hiddenDurationTimer.hiddenDurationMs,
Ben Rohlfs8cabee12023-04-27 13:32:59 +0200568 isVisible: document.visibilityState === 'visible',
569 hasFocus: document.hasFocus(),
Milutin Kristoficaaef49a2021-03-10 13:30:19 +0100570 },
571 false
572 );
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200573 }
574
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100575 /**
Milutin Kristofic091ab762020-03-19 16:37:48 +0100576 * Browser's navigation and resource timings
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100577 */
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200578 private _reportNavResTimes() {
Milutin Kristofic091ab762020-03-19 16:37:48 +0100579 const perfEvents = Object.keys(this.performanceTiming.toJSON());
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200580 perfEvents.forEach(eventName =>
581 this._reportPerformanceTiming(eventName as PeformanceTimingEventName)
Milutin Kristofic091ab762020-03-19 16:37:48 +0100582 );
Milutin Kristoficda88b332020-03-24 10:19:12 +0100583 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100584
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200585 private _reportPerformanceTiming(
586 eventName: PeformanceTimingEventName,
587 eventDetails?: EventDetails
588 ) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100589 const eventTiming = this.performanceTiming[eventName];
590 if (eventTiming > 0) {
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200591 const elapsedTime = eventTiming - this.performanceTiming.navigationStart;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100592 // NavResTime - Navigation and resource timings.
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200593 this.reporter(
594 TIMING.TYPE,
595 TIMING.CATEGORY.UI_LATENCY,
596 `NavResTime - ${eventName}`,
597 elapsedTime,
598 eventDetails,
599 true
600 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100601 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100602 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100603
604 beforeLocationChanged() {
605 for (const prop of Object.keys(this._baselines)) {
Ben Rohlfs055d4a42023-05-03 13:55:08 +0200606 if (LOCATION_CHANGE_OK_TIMERS.includes(prop)) continue;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100607 delete this._baselines[prop];
608 }
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100609 this.time(Timing.CHANGE_DISPLAYED);
610 this.time(Timing.CHANGE_LOAD_FULL);
611 this.time(Timing.DASHBOARD_DISPLAYED);
612 this.time(Timing.DIFF_VIEW_CONTENT_DISPLAYED);
613 this.time(Timing.DIFF_VIEW_DISPLAYED);
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100614 this.time(Timing.FILE_LIST_DISPLAYED);
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100615 this.reportRepoName = undefined;
616 this.reportChangeId = undefined;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100617 // reset slow rpc list since here start page loads which report these rpcs
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100618 this.slowRpcList = [];
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200619 this.hiddenDurationTimer.reset();
Milutin Kristoficda88b332020-03-24 10:19:12 +0100620 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100621
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200622 locationChanged(page: string) {
623 this.reporter(
624 NAVIGATION.TYPE,
625 NAVIGATION.CATEGORY.LOCATION_CHANGED,
626 NAVIGATION.EVENT.PAGE,
627 page
628 );
Milutin Kristoficda88b332020-03-24 10:19:12 +0100629 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100630
631 dashboardDisplayed() {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100632 if (hasOwnProperty(this._baselines, Timing.STARTUP_DASHBOARD_DISPLAYED)) {
633 this.timeEnd(Timing.STARTUP_DASHBOARD_DISPLAYED, this._pageLoadDetails());
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100634 } else {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100635 this.timeEnd(Timing.DASHBOARD_DISPLAYED, this._pageLoadDetails());
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100636 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100637 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100638
Milutin Kristofic933e6bd2021-06-07 20:19:45 +0200639 changeDisplayed(eventDetails?: EventDetails) {
640 eventDetails = {...eventDetails, ...this._pageLoadDetails()};
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100641 if (hasOwnProperty(this._baselines, Timing.STARTUP_CHANGE_DISPLAYED)) {
Milutin Kristofic933e6bd2021-06-07 20:19:45 +0200642 this.timeEnd(Timing.STARTUP_CHANGE_DISPLAYED, eventDetails);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100643 } else {
Milutin Kristofic933e6bd2021-06-07 20:19:45 +0200644 this.timeEnd(Timing.CHANGE_DISPLAYED, eventDetails);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100645 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100646 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100647
648 changeFullyLoaded() {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100649 if (hasOwnProperty(this._baselines, Timing.STARTUP_CHANGE_LOAD_FULL)) {
650 this.timeEnd(Timing.STARTUP_CHANGE_LOAD_FULL);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100651 } else {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100652 this.timeEnd(Timing.CHANGE_LOAD_FULL);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100653 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100654 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100655
656 diffViewDisplayed() {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100657 if (hasOwnProperty(this._baselines, Timing.STARTUP_DIFF_VIEW_DISPLAYED)) {
658 this.timeEnd(Timing.STARTUP_DIFF_VIEW_DISPLAYED, this._pageLoadDetails());
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100659 } else {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100660 this.timeEnd(Timing.DIFF_VIEW_DISPLAYED, this._pageLoadDetails());
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100661 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100662 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100663
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100664 diffViewContentDisplayed() {
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200665 if (
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100666 hasOwnProperty(
667 this._baselines,
668 Timing.STARTUP_DIFF_VIEW_CONTENT_DISPLAYED
669 )
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200670 ) {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100671 this.timeEnd(Timing.STARTUP_DIFF_VIEW_CONTENT_DISPLAYED);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100672 } else {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100673 this.timeEnd(Timing.DIFF_VIEW_CONTENT_DISPLAYED);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100674 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100675 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100676
677 fileListDisplayed() {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100678 if (hasOwnProperty(this._baselines, Timing.STARTUP_FILE_LIST_DISPLAYED)) {
679 this.timeEnd(Timing.STARTUP_FILE_LIST_DISPLAYED);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100680 } else {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100681 this.timeEnd(Timing.FILE_LIST_DISPLAYED);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100682 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100683 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100684
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200685 private _pageLoadDetails(): PageLoadDetails {
686 const details: PageLoadDetails = {
Milutin Kristofica3580122020-04-14 14:31:48 +0200687 rpcList: this.slowRpcSnapshot,
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200688 hiddenDurationMs: this.hiddenDurationTimer.accHiddenDurationMs,
Milutin Kristofica3580122020-04-14 14:31:48 +0200689 };
690
691 if (window.screen) {
692 details.screenSize = {
693 width: window.screen.width,
694 height: window.screen.height,
695 };
696 }
697
Tao Zhou34b0f122020-08-31 13:44:50 +0200698 if (document?.documentElement) {
Milutin Kristofica3580122020-04-14 14:31:48 +0200699 details.viewport = {
700 width: document.documentElement.clientWidth,
701 height: document.documentElement.clientHeight,
702 };
703 }
704
Tao Zhou34b0f122020-08-31 13:44:50 +0200705 if (window.performance?.memory) {
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200706 const toMb = (bytes: number) =>
707 Math.round((bytes / (1024 * 1024)) * 100) / 100;
708 details.usedJSHeapSizeMb = toMb(window.performance.memory.usedJSHeapSize);
Milutin Kristofica3580122020-04-14 14:31:48 +0200709 }
710
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200711 details.hiddenDurationMs = this.hiddenDurationTimer.hiddenDurationMs;
Milutin Kristofica3580122020-04-14 14:31:48 +0200712 return details;
Milutin Kristoficda88b332020-03-24 10:19:12 +0100713 }
Milutin Kristofica3580122020-04-14 14:31:48 +0200714
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200715 reportExtension(name: string) {
Milutin Kristoficaaef49a2021-03-10 13:30:19 +0100716 this.reporter(
717 LIFECYCLE.TYPE,
718 LIFECYCLE.CATEGORY.EXTENSION_DETECTED,
719 LifeCycle.EXTENSION_DETECTED,
720 undefined,
721 {name}
722 );
Milutin Kristoficda88b332020-03-24 10:19:12 +0100723 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100724
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200725 pluginLoaded(name: string) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100726 if (name.startsWith('metrics-')) {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100727 this.timeEnd(Timing.METRICS_PLUGIN_LOADED);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100728 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100729 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100730
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200731 pluginsLoaded(pluginsList?: string[]) {
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100732 this.timeEnd(Timing.PLUGINS_LOADED);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100733 this.reporter(
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200734 LIFECYCLE.TYPE,
735 LIFECYCLE.CATEGORY.PLUGINS_INSTALLED,
Milutin Kristoficaaef49a2021-03-10 13:30:19 +0100736 LifeCycle.PLUGINS_INSTALLED,
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200737 undefined,
738 {pluginsList: pluginsList || []},
Ben Rohlfs53998552022-03-30 17:47:28 +0200739 false
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200740 );
Milutin Kristoficda88b332020-03-24 10:19:12 +0100741 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100742
Ben Rohlfs89418582021-09-27 12:15:57 +0200743 pluginsFailed(pluginsList?: string[]) {
744 if (!pluginsList || pluginsList.length === 0) return;
745 this.reporter(
746 LIFECYCLE.TYPE,
747 LIFECYCLE.CATEGORY.PLUGINS_INSTALLED,
748 LifeCycle.PLUGINS_FAILED,
749 undefined,
750 {pluginsList: pluginsList || []},
Ben Rohlfs53998552022-03-30 17:47:28 +0200751 false
Ben Rohlfs89418582021-09-27 12:15:57 +0200752 );
753 }
754
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100755 /**
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100756 * Reset named Timing.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100757 */
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100758 time(name: Timing) {
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200759 this._baselines[name] = now();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100760 window.performance.mark(`${name}-start`);
Milutin Kristofic4dc83f92024-04-09 19:29:05 +0200761 // When time(Timing.DASHBOARD_DISPLAYED) is called gr-dashboard-view
762 // we need to clean-up slowRpcList, otherwise it can accumulate to big size
763 if (name === Timing.DASHBOARD_DISPLAYED) {
764 this.slowRpcList = [];
765 this.hiddenDurationTimer.reset();
766 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100767 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100768
769 /**
770 * Finish named timer and report it to server.
771 */
Milutin Kristofic48a0f432021-03-16 22:53:08 +0100772 timeEnd(name: Timing, eventDetails?: EventDetails) {
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200773 if (!hasOwnProperty(this._baselines, name)) {
774 return;
775 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100776 const baseTime = this._baselines[name];
777 delete this._baselines[name];
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200778 this._reportTiming(name, now() - baseTime, eventDetails);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100779
780 // Finalize the interval. Either from a registered start mark or
781 // the navigation start time (if baseTime is 0).
782 if (baseTime !== 0) {
783 window.performance.measure(name, `${name}-start`);
784 } else {
David Ostrovskyf91f9662021-02-22 19:34:37 +0100785 // Microsoft Edge does not handle the 2nd param correctly
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100786 // (if undefined).
787 window.performance.measure(name);
788 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100789 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100790
791 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100792 * Send a timing report with an arbitrary time value.
793 *
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200794 * @param name Timing name.
795 * @param time The time to report as an integer of milliseconds.
796 * @param eventDetails non sensitive details
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100797 */
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200798 private _reportTiming(
799 name: string,
800 time: number,
801 eventDetails?: EventDetails
802 ) {
803 this.reporter(
804 TIMING.TYPE,
805 TIMING.CATEGORY.UI_LATENCY,
806 name,
807 time,
808 eventDetails
809 );
Milutin Kristoficda88b332020-03-24 10:19:12 +0100810 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100811
812 /**
David Ostrovskyf91f9662021-02-22 19:34:37 +0100813 * Get a timer object to for reporting a user timing. The start time will be
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100814 * the time that the object has been created, and the end time will be the
815 * time that the "end" method is called on the object.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100816 */
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200817 getTimer(name: string): Timer {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100818 let called = false;
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200819 let start: number;
820 let max: number | null = null;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100821
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200822 const timer: Timer = {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100823 // Clear the timer and reset the start time.
824 reset: () => {
825 called = false;
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200826 start = now();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100827 return timer;
Viktar Doniche8680ea2016-10-05 11:58:58 -0700828 },
Wyatt Allen18f03542018-06-19 17:03:43 -0700829
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100830 // Stop the timer and report the intervening time.
Ben Rohlfs23152eb2021-04-30 09:01:30 +0200831 end: (eventDetails?: EventDetails) => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100832 if (called) {
833 throw new Error(`Timer for "${name}" already ended.`);
834 }
835 called = true;
Milutin Kristofic0b221a62020-04-30 19:57:10 +0200836 const time = now() - start;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100837
838 // If a maximum is specified and the time exceeds it, do not report.
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200839 if (max && time > max) {
840 return timer;
841 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100842
Ben Rohlfs23152eb2021-04-30 09:01:30 +0200843 this._reportTiming(name, time, eventDetails);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100844 return timer;
Wyatt Allen18f03542018-06-19 17:03:43 -0700845 },
Viktar Donich78b7cd22016-08-18 16:45:05 -0700846
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100847 // Set a maximum reportable time. If a maximum is set and the timer is
848 // ended after the specified amount of time, the value is not reported.
849 withMaximum(maximum) {
850 max = maximum;
851 return timer;
852 },
853 };
Viktar Donich78b7cd22016-08-18 16:45:05 -0700854
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100855 // The timer is initialized to its creation time.
856 return timer.reset();
Milutin Kristoficda88b332020-03-24 10:19:12 +0100857 }
Milutin Kristofic623ecd12020-02-15 21:46:58 +0100858
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100859 /**
860 * Log timing information for an RPC.
861 *
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200862 * @param anonymizedUrl The URL of the RPC with tokens obfuscated.
863 * @param elapsed The time elapsed of the RPC.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100864 */
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200865 reportRpcTiming(anonymizedUrl: string, elapsed: number) {
866 this.reporter(
867 TIMING.TYPE,
868 TIMING.CATEGORY.RPC,
869 'RPC-' + anonymizedUrl,
870 elapsed,
871 {},
872 true
873 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100874 if (elapsed >= SLOW_RPC_THRESHOLD) {
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100875 this.slowRpcList.push({anonymizedUrl, elapsed});
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100876 }
Milutin Kristoficda88b332020-03-24 10:19:12 +0100877 }
Viktar Donich78b7cd22016-08-18 16:45:05 -0700878
Milutin Kristoficaaef49a2021-03-10 13:30:19 +0100879 reportLifeCycle(eventName: LifeCycle, details: EventDetails) {
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200880 this.reporter(
881 LIFECYCLE.TYPE,
882 LIFECYCLE.CATEGORY.DEFAULT,
883 eventName,
884 undefined,
885 details,
Ben Rohlfs53998552022-03-30 17:47:28 +0200886 false
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200887 );
Milutin Kristofic0fe96792020-03-26 13:17:35 +0100888 }
889
Milutin Kristofice1a562f2021-06-01 19:51:11 +0200890 reportPluginLifeCycleLog(eventName: string, details: EventDetails) {
891 this.reporter(
892 PLUGIN.TYPE,
893 PLUGIN.CATEGORY.LIFECYCLE,
894 eventName,
895 undefined,
896 details,
Ben Rohlfs53998552022-03-30 17:47:28 +0200897 false
Milutin Kristofice1a562f2021-06-01 19:51:11 +0200898 );
899 }
900
901 reportPluginInteractionLog(eventName: string, details: EventDetails) {
902 this.reporter(
903 PLUGIN.TYPE,
904 PLUGIN.CATEGORY.INTERACTION,
905 eventName,
906 undefined,
907 details,
908 true
909 );
910 }
911
Ben Rohlfsbd4c91b2021-12-22 11:37:41 +0100912 /**
913 * Returns true when the event was deduped and thus should not be reported.
914 */
915 _dedup(
916 eventName: string | Interaction,
917 details: EventDetails,
918 deduping?: Deduping
919 ): boolean {
920 if (!deduping) return false;
921 let id = '';
922 switch (deduping) {
923 case Deduping.DETAILS_ONCE_PER_CHANGE:
924 id = `${eventName}-${this.reportChangeId}-${JSON.stringify(details)}`;
925 break;
926 case Deduping.DETAILS_ONCE_PER_SESSION:
927 id = `${eventName}-${JSON.stringify(details)}`;
928 break;
929 case Deduping.EVENT_ONCE_PER_CHANGE:
930 id = `${eventName}-${this.reportChangeId}`;
931 break;
932 case Deduping.EVENT_ONCE_PER_SESSION:
933 id = `${eventName}`;
934 break;
935 default:
936 throw new Error(`Invalid 'deduping' option '${deduping}'.`);
937 }
938 if (this.reportedIds.has(id)) return true;
939 this.reportedIds.add(id);
940 return false;
941 }
942
943 reportInteraction(
944 eventName: string | Interaction,
945 details: EventDetails,
946 options?: ReportingOptions
947 ) {
948 if (this._dedup(eventName, details, options?.deduping)) return;
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200949 this.reporter(
950 INTERACTION.TYPE,
951 INTERACTION.CATEGORY.DEFAULT,
952 eventName,
953 undefined,
954 details,
Ben Rohlfs53998552022-03-30 17:47:28 +0200955 false
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200956 );
Milutin Kristoficda88b332020-03-24 10:19:12 +0100957 }
Viktar Donich82ad0ec2018-05-17 14:33:22 -0700958
Milutin Kristofic2fddf922021-03-11 10:35:19 +0100959 reportExecution(name: Execution, details?: EventDetails) {
Ben Rohlfsbd4c91b2021-12-22 11:37:41 +0100960 if (this._dedup(name, details, Deduping.DETAILS_ONCE_PER_SESSION)) return;
Ben Rohlfsbad48d42020-12-17 15:03:33 +0100961 this.reporter(
962 LIFECYCLE.TYPE,
963 LIFECYCLE.CATEGORY.EXECUTION,
Milutin Kristofic2fddf922021-03-11 10:35:19 +0100964 name,
Ben Rohlfsbad48d42020-12-17 15:03:33 +0100965 undefined,
966 details,
Ben Rohlfs94f34a12021-02-26 18:37:32 +0100967 true // skip console log
Ben Rohlfsbad48d42020-12-17 15:03:33 +0100968 );
969 }
970
Ben Rohlfs629d89d2021-03-13 22:04:19 +0100971 trackApi(
972 pluginApi: Pick<PluginApi, 'getPluginName'>,
973 object: string,
974 method: string
975 ) {
Ben Rohlfs13edc7c2021-03-08 08:50:07 +0100976 const plugin = pluginApi?.getPluginName() ?? 'unknown';
Milutin Kristofic2fddf922021-03-11 10:35:19 +0100977 this.reportExecution(Execution.PLUGIN_API, {plugin, object, method});
Ben Rohlfs94f34a12021-02-26 18:37:32 +0100978 }
979
Kamil Musinf0ece022022-10-14 15:22:44 +0200980 error(errorSource: string, error: Error, details?: EventDetails) {
981 const message = `${errorSource}: ${error.message}`;
982 const eventDetails = {
983 errorMessage: message,
984 ...details,
985 stack: error.stack,
986 };
Milutin Kristofic380b9f62020-12-03 09:24:17 +0100987
988 this.reporter(
989 ERROR.TYPE,
990 ERROR.CATEGORY.EXCEPTION,
Kamil Musinf0ece022022-10-14 15:22:44 +0200991 errorSource,
Milutin Kristofic380b9f62020-12-03 09:24:17 +0100992 {error},
Kamil Musinf0ece022022-10-14 15:22:44 +0200993 eventDetails
Milutin Kristofic380b9f62020-12-03 09:24:17 +0100994 );
995 }
996
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +0200997 reportErrorDialog(message: string) {
998 this.reporter(
999 ERROR.TYPE,
1000 ERROR.CATEGORY.ERROR_DIALOG,
Kamil Musinf0ece022022-10-14 15:22:44 +02001001 'ErrorDialog',
1002 {error: new Error(message)},
1003 {errorMessage: message}
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +02001004 );
Milutin Kristoficda88b332020-03-24 10:19:12 +01001005 }
Milutin Kristofic68a27a32020-01-27 15:53:17 +01001006
Dmitrii Filippova5ace2f2020-07-29 09:30:56 +02001007 setRepoName(repoName: string) {
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +01001008 this.reportRepoName = repoName;
Milutin Kristoficda88b332020-03-24 10:19:12 +01001009 }
Milutin Kristofic3a844522021-01-21 10:01:55 +01001010
1011 setChangeId(changeId: NumericChangeId) {
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +01001012 this.reportChangeId = changeId;
Milutin Kristofic3a844522021-01-21 10:01:55 +01001013 }
Milutin Kristoficda88b332020-03-24 10:19:12 +01001014}
Milutin Kristofic68a27a32020-01-27 15:53:17 +01001015
Tao Zhou4cd35cb2020-07-22 11:28:22 +02001016export const DEFAULT_STARTUP_TIMERS = {...STARTUP_TIMERS};