Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / drive / src / app / utils / performance.ts
blobd414eb980104af38c29eef1c015f01e81f9363bd
1 import metrics from '@proton/metrics';
2 import type Metrics from '@proton/metrics/Metrics';
3 import Histogram from '@proton/metrics/lib/Histogram';
4 import { reportWebVitals } from '@proton/shared/lib/metrics/webvitals';
6 import { NAVIGATION_MARK } from '../hooks/util/useReactRouterNavigationLog';
8 export const getCurrentPageType = (path: string = window.location.pathname) => {
9     // Normalize the path: remove leading/trailing slashes and collapse multiple slashes
10     const normalizedPath = path.replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/');
11     const pathParts = normalizedPath.split('/');
13     const part = pathParts[0] === 'u' ? pathParts[2] : pathParts[0];
14     switch (part) {
15         case 'devices':
16             return 'computers';
17         case 'trash':
18             return 'trash';
19         case 'shared-urls':
20             return 'shared_by_me';
21         case 'shared-with-me':
22             return 'shared_with_me';
23         case 'photos':
24             return 'photos';
25     }
27     if (pathParts[0] === 'urls') {
28         return 'public_page';
29     }
31     if (
32         (pathParts.length === 2 && pathParts[0] === 'u') ||
33         (pathParts.length === 5 && pathParts[3] === 'folder') ||
34         (pathParts.length === 5 && pathParts[3] === 'file') ||
35         path === '/' || // Redirect case
36         normalizedPath === '' // Redirect case (nothing)
37     ) {
38         return 'filebrowser';
39     }
42 const getBrowserTime = (marker: 'loadEventStart' | 'domContentLoadedEventStart'): number | undefined => {
43     const perfEntries = performance.getEntriesByType('navigation');
45     if (perfEntries && perfEntries.length > 0) {
46         const navigationEntry = perfEntries[0];
47         const startTime = navigationEntry.startTime;
48         return navigationEntry[marker] - startTime;
49     }
52 const logBrowserTime = (
53     metric: 'drive_performance_load_histogram' | 'drive_performance_domcontentloaded_histogram',
54     marker: 'loadEventStart' | 'domContentLoadedEventStart'
55 ) => {
56     const time = getBrowserTime(marker);
57     const pageType = getCurrentPageType();
58     if (time && pageType) {
59         metrics[metric].observe({
60             Labels: {
61                 pageType,
62             },
63             Value: time / 1000,
64         });
65     }
68 const initializeBrowserEvents = () => {
69     window.addEventListener('load', () => logBrowserTime('drive_performance_load_histogram', 'loadEventStart'));
70     if (document.readyState === 'complete') {
71         logBrowserTime('drive_performance_domcontentloaded_histogram', 'domContentLoadedEventStart');
72     } else {
73         document.addEventListener('DOMContentLoaded', () =>
74             logBrowserTime('drive_performance_domcontentloaded_histogram', 'domContentLoadedEventStart')
75         );
76     }
79 /**
80  * Initializes performance metrics for the application.
81  *
82  * This function sets up browser event listeners to log performance metrics
83  * and initializes web vitals reporting.
84  *
85  * @param isPublic - A boolean indicating whether the metrics are for a public or private context.
86  */
87 export const initializePerformanceMetrics = (isPublic: boolean) => {
88     initializeBrowserEvents();
89     reportWebVitals(isPublic ? 'public' : 'private');
92 const getNavigationStart = () => {
93     // By default we use the browser navigation
94     const perfEntries = performance.getEntriesByType('navigation');
95     if (perfEntries && perfEntries.length > 0) {
96         const navigationEntry = perfEntries[0];
97         let startTime = navigationEntry.startTime;
99         // If however, we had a react-router navigation, we use this navigation as the beginning of the navigation
100         const reactRouterNavigationEntries = performance
101             .getEntriesByType('mark')
102             .filter((entry) => entry.name === NAVIGATION_MARK);
104         const lastEntry = reactRouterNavigationEntries.at(-1);
105         if (lastEntry) {
106             startTime = lastEntry.startTime;
107         }
109         return startTime;
110     }
113 const getTimeFromNavigationStart = (key: string): number | undefined => {
114     const startTime = getNavigationStart();
115     if (startTime !== undefined) {
116         const measureName = `measure-${key}`;
118         performance.measure(measureName, {
119             start: startTime,
120             end: key,
121         });
123         const measurement = performance.getEntriesByName(measureName)[0];
124         return measurement.duration;
125     }
129  * Logs a performance marker and records its timing metrics.
131  * This function creates a performance mark, measures the time from navigation start
132  * to the mark (or uses a provided time), and records the metric to observability.
134  * @param key - The key of the performance marker, which should correspond to a key in the Metrics type (your observability metric).
135  * @param view - Optional parameter specifying the view type ('list' or 'grid'), used for certain metrics.
136  * @param timeInMs - Optional parameter to provide a specific time in milliseconds instead of measuring from navigation start.
137  * @returns The time recorded for the performance marker in milliseconds.
138  */
139 export const logPerformanceMarker = (key: keyof Metrics, view?: 'list' | 'grid', timeInMs?: number) => {
140     performance.mark(key);
141     const time = timeInMs !== undefined ? timeInMs : getTimeFromNavigationStart(key);
142     const pageType = getCurrentPageType();
144     if (time && pageType && metrics[key] instanceof Histogram) {
145         metrics[key].observe({
146             Labels: {
147                 pageType,
148                 ...(view && { view }),
149             },
150             Value: time / 1000,
151         });
152     }
154     return time;