diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d0da194624f..91f5a5704a2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -453,6 +453,7 @@ lerna.json @grafana/frontend-ops /public/app/angular/ @torkelo /public/app/app.ts @grafana/frontend-ops /public/app/dev.ts @grafana/frontend-ops +/public/app/core/utils/metrics.ts @grafana/plugins-platform-frontend /public/app/index.ts @grafana/frontend-ops /public/app/AppWrapper.tsx @grafana/frontend-ops /public/app/partials/ @grafana/grafana-frontend-platform diff --git a/public/app/app.ts b/public/app/app.ts index 56c10d5d92f..83734e1c234 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -67,6 +67,7 @@ import { GAEchoBackend } from './core/services/echo/backends/analytics/GABackend import { RudderstackBackend } from './core/services/echo/backends/analytics/RudderstackBackend'; import { GrafanaJavascriptAgentBackend } from './core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend'; import { KeybindingSrv } from './core/services/keybindingSrv'; +import { startMeasure, stopMeasure } from './core/utils/metrics'; import { initDevFeatures } from './dev'; import { getTimeSrv } from './features/dashboard/services/TimeSrv'; import { initGrafanaLive } from './features/live'; @@ -119,6 +120,8 @@ export class GrafanaApp { setBackendSrv(backendSrv); initEchoSrv(); + // This needs to be done after the `initEchoSrv` since it is being used under the hood. + startMeasure('frontend_app_init'); addClassIfNoOverlayScrollbar(); setLocale(config.bootData.user.locale); setWeekStart(config.bootData.user.weekStart); @@ -219,6 +222,8 @@ export class GrafanaApp { } catch (error) { console.error('Failed to start Grafana', error); window.__grafana_load_failed(); + } finally { + stopMeasure('frontend_app_init'); } } } diff --git a/public/app/core/utils/metrics.ts b/public/app/core/utils/metrics.ts new file mode 100644 index 00000000000..97551a721cc --- /dev/null +++ b/public/app/core/utils/metrics.ts @@ -0,0 +1,35 @@ +import { reportPerformance } from '../services/echo/EchoSrv'; + +export function startMeasure(eventName: string) { + if (!performance) { + return; + } + + try { + performance.mark(`${eventName}_started`); + } catch (error) { + console.error(`[Metrics] Failed to startMeasure ${eventName}`, error); + } +} + +export function stopMeasure(eventName: string) { + if (!performance) { + return; + } + + try { + const started = `${eventName}_started`; + const completed = `${eventName}_completed`; + const measured = `${eventName}_measured`; + + performance.mark(completed); + const measure = performance.measure(measured, started, completed); + reportPerformance(`${eventName}_ms`, measure.duration); + + performance.clearMarks(started); + performance.clearMarks(completed); + performance.clearMeasures(measured); + } catch (error) { + console.error(`[Metrics] Failed to stopMeasure ${eventName}`, error); + } +} diff --git a/public/app/features/plugins/pluginPreloader.ts b/public/app/features/plugins/pluginPreloader.ts index ce87dc5b377..b1ca1b35883 100644 --- a/public/app/features/plugins/pluginPreloader.ts +++ b/public/app/features/plugins/pluginPreloader.ts @@ -1,5 +1,6 @@ import type { PluginExtensionLinkConfig } from '@grafana/data'; import type { AppPluginConfig } from '@grafana/runtime'; +import { startMeasure, stopMeasure } from 'app/core/utils/metrics'; import * as pluginLoader from './plugin_loader'; @@ -10,18 +11,24 @@ export type PluginPreloadResult = { }; export async function preloadPlugins(apps: Record = {}): Promise { + startMeasure('frontend_plugins_preload'); const pluginsToPreload = Object.values(apps).filter((app) => app.preload); - return Promise.all(pluginsToPreload.map(preload)); + const result = await Promise.all(pluginsToPreload.map(preload)); + stopMeasure('frontend_plugins_preload'); + return result; } async function preload(config: AppPluginConfig): Promise { const { path, version, id: pluginId } = config; try { + startMeasure(`frontend_plugin_preload_${pluginId}`); const { plugin } = await pluginLoader.importPluginModule(path, version); const { extensionConfigs = [] } = plugin; return { pluginId, extensionConfigs }; } catch (error) { console.error(`[Plugins] Failed to preload plugin: ${path} (version: ${version})`, error); return { pluginId, extensionConfigs: [], error }; + } finally { + stopMeasure(`frontend_plugin_preload_${pluginId}`); } }