diff --git a/public/app/features/dashboard-scene/behaviors/DashboardAnalyticsInitializerBehavior.ts b/public/app/features/dashboard-scene/behaviors/DashboardAnalyticsInitializerBehavior.ts index f66a3922538..a3918bab232 100644 --- a/public/app/features/dashboard-scene/behaviors/DashboardAnalyticsInitializerBehavior.ts +++ b/public/app/features/dashboard-scene/behaviors/DashboardAnalyticsInitializerBehavior.ts @@ -1,44 +1,55 @@ import { getScenePerformanceTracker, writePerformanceLog } from '@grafana/scenes'; import { getDashboardAnalyticsAggregator } from '../../dashboard/services/DashboardAnalyticsAggregator'; +import { getScenePerformanceLogger } from '../../dashboard/services/ScenePerformanceLogger'; import { DashboardScene } from '../scene/DashboardScene'; /** * Scene behavior function that manages the initialization and lifecycle of - * DashboardAnalyticsAggregator for each dashboard session. + * dashboard performance services for each dashboard session. * - * Initializes the aggregator with dashboard metadata and registers it as a - * performance observer. Returns a cleanup function for deactivation. + * Initializes both the analytics aggregator and performance logger, registering + * them as performance observers. Returns a cleanup function for deactivation. */ -export function dashboardAnalyticsInitializer(dashboard: DashboardScene) { +export function dashboardPerformanceInitializer(dashboard: DashboardScene) { const { uid, title } = dashboard.state; if (!uid) { - console.warn('dashboardAnalyticsInitializer: Dashboard UID is missing'); + console.warn('dashboardPerformanceInitializer: Dashboard UID is missing'); return; } - // writePerformanceLog('DashboardAnalyticsInitializer', 'Initializing dashboard analytics behavior'); + writePerformanceLog('DAI', 'Initializing dashboard performance services'); + // Initialize analytics aggregator const aggregator = getDashboardAnalyticsAggregator(); aggregator.initialize(uid, title || 'Untitled Dashboard'); - // Register as performance observer - const tracker = getScenePerformanceTracker(); - const unsubscribe = tracker.addObserver(aggregator); + // Initialize performance logger + const logger = getScenePerformanceLogger(); + logger.initialize(); - writePerformanceLog('DashboardAnalyticsInitializer', 'DashboardAnalyticsAggregator initialized:', { uid, title }); + // Register both as performance observers + const tracker = getScenePerformanceTracker(); + const unsubscribeAggregator = tracker.addObserver(aggregator); + const unsubscribeLogger = tracker.addObserver(logger); + + writePerformanceLog('DAI', 'Dashboard performance services initialized:', { uid, title }); // Return cleanup function return () => { // Unsubscribe from performance tracker - if (unsubscribe) { - unsubscribe(); + if (unsubscribeAggregator) { + unsubscribeAggregator(); + } + if (unsubscribeLogger) { + unsubscribeLogger(); } - // Clean up aggregator state + // Clean up service states aggregator.destroy(); + logger.destroy(); - writePerformanceLog('DashboardAnalyticsInitializer', 'DashboardAnalyticsAggregator cleaned up'); + writePerformanceLog('DAI', 'Dashboard performance services cleaned up'); }; } diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index 82da2e8bca2..2fa559aff3c 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -60,7 +60,7 @@ import { import { DashboardMeta } from 'app/types/dashboard'; import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior'; -import { dashboardAnalyticsInitializer } from '../behaviors/DashboardAnalyticsInitializerBehavior'; +import { dashboardPerformanceInitializer } from '../behaviors/DashboardAnalyticsInitializerBehavior'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; @@ -228,7 +228,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo, panelMetrics: PanelAnalyticsMetrics[] | null ): void { - // Calculate performance insights if panel metrics are available - let performanceInsights = null; + const panelCount = panelMetrics?.length || 0; + const panelSummary = panelCount ? `${panelCount} panels analyzed` : 'No panel metrics'; + + // Main analytics summary + const slowPanelCount = + panelMetrics?.filter( + (p) => + p.totalQueryTime + p.totalTransformationTime + p.totalRenderTime + p.totalFieldConfigTime + p.pluginLoadTime > + 100 + ).length || 0; + + // eslint-disable-next-line no-console + console.groupCollapsed( + `DAA: [ANALYTICS] ${data.interactionType} | ${panelSummary}${slowPanelCount > 0 ? ` | ${slowPanelCount} slow panels ⚠️` : ''}` + ); + + // Dashboard overview + console.log('📊 Dashboard:', { + duration: `${(data.duration || 0).toFixed(1)}ms`, + network: `${(data.networkDuration || 0).toFixed(1)}ms`, + interactionType: data.interactionType, + slowPanels: slowPanelCount, + }); + + // Analytics payload + console.log('📈 Analytics payload:', payload); + + // Individual collapsible panel logs with detailed breakdown if (panelMetrics && panelMetrics.length > 0) { - const totalPanelTime = panelMetrics.reduce((sum: number, panel) => { - const panelTotal = + panelMetrics.forEach((panel) => { + const totalPanelTime = panel.totalQueryTime + - panel.totalFieldConfigTime + panel.totalTransformationTime + panel.totalRenderTime + - panel.pluginLoadTime; - return sum + panelTotal; - }, 0); - - const avgPanelTime = totalPanelTime / panelMetrics.length; - - const slowestPanel = panelMetrics.reduce((slowest, panel) => { - const panelTotal = - panel.totalQueryTime + panel.totalFieldConfigTime + - panel.totalTransformationTime + - panel.totalRenderTime + panel.pluginLoadTime; - const slowestTotal = - slowest.totalQueryTime + - slowest.totalFieldConfigTime + - slowest.totalTransformationTime + - slowest.totalRenderTime + - slowest.pluginLoadTime; - return panelTotal > slowestTotal ? panel : slowest; + const isSlowPanel = totalPanelTime > 100; + const slowWarning = isSlowPanel ? ' ⚠️ SLOW' : ''; + + // eslint-disable-next-line no-console + console.groupCollapsed( + `🎨 Panel ${panel.pluginId}-${panel.panelId}: ${totalPanelTime.toFixed(1)}ms total${slowWarning}` + ); + + console.log('🔧 Plugin:', { + id: panel.pluginId, + version: panel.pluginVersion || 'unknown', + panelId: panel.panelId, + panelKey: panel.panelKey, + }); + + console.log('⚡ Performance:', { + totalTime: `${totalPanelTime.toFixed(1)}ms`, + isSlowPanel: isSlowPanel, + breakdown: { + query: `${panel.totalQueryTime.toFixed(1)}ms`, + transform: `${panel.totalTransformationTime.toFixed(1)}ms`, + render: `${panel.totalRenderTime.toFixed(1)}ms`, + fieldConfig: `${panel.totalFieldConfigTime.toFixed(1)}ms`, + pluginLoad: `${panel.pluginLoadTime.toFixed(1)}ms`, + }, + }); + + if (panel.queryOperations.length > 0) { + console.log('📊 Queries:', { + count: panel.queryOperations.length, + details: panel.queryOperations.map((op, index) => ({ + operation: index + 1, + duration: `${op.duration.toFixed(1)}ms`, + timestamp: op.timestamp, + queryType: op.queryType || 'unknown', + })), + }); + } + + if (panel.transformationOperations.length > 0) { + console.log('🔄 Transformations:', { + count: panel.transformationOperations.length, + details: panel.transformationOperations.map((op, index) => ({ + operation: index + 1, + duration: `${op.duration.toFixed(1)}ms`, + timestamp: op.timestamp, + transformationId: op.transformationId || 'unknown', + success: op.success !== false, + })), + }); + } + + if (panel.renderOperations.length > 0) { + console.log('🎨 Renders:', { + count: panel.renderOperations.length, + details: panel.renderOperations.map((op, index) => ({ + operation: index + 1, + duration: `${op.duration.toFixed(1)}ms`, + timestamp: op.timestamp, + })), + }); + } + + if (panel.fieldConfigOperations.length > 0) { + console.log('⚙️ FieldConfigs:', { + count: panel.fieldConfigOperations.length, + details: panel.fieldConfigOperations.map((op, index) => ({ + operation: index + 1, + duration: `${op.duration.toFixed(1)}ms`, + timestamp: op.timestamp, + })), + }); + } + + // eslint-disable-next-line no-console + console.groupEnd(); }); - - const slowestPanelTime = - slowestPanel.totalQueryTime + - slowestPanel.totalFieldConfigTime + - slowestPanel.totalTransformationTime + - slowestPanel.totalRenderTime + - slowestPanel.pluginLoadTime; - - performanceInsights = { - totalPanelTime: `${totalPanelTime.toFixed(2)}ms`, - averagePanelTime: `${avgPanelTime.toFixed(2)}ms`, - slowestPanel: { - panelId: slowestPanel.panelId, - pluginId: slowestPanel.pluginId, - time: `${slowestPanelTime.toFixed(2)}ms`, - }, - }; } - writePerformanceLog('DashboardAnalyticsAggregator', '🎯 Dashboard Analytics Event:', { - uid: this.dashboardUID, - title: this.dashboardTitle, - interactionType: data.interactionType, - dashboardMetrics: payload, - panelMetricsMapSize: this.panelMetrics.size, - panelMetricsArrayLength: panelMetrics?.length || 0, - panelMetrics: (panelMetrics?.length ?? 0) > 0 ? panelMetrics : 'No panel metrics found', - performanceInsights: performanceInsights || 'No performance insights available', - }); + // eslint-disable-next-line no-console + console.groupEnd(); } } diff --git a/public/app/features/dashboard/services/DashboardProfiler.ts b/public/app/features/dashboard/services/DashboardProfiler.ts index 1dfdbe25ae7..5332fb53377 100644 --- a/public/app/features/dashboard/services/DashboardProfiler.ts +++ b/public/app/features/dashboard/services/DashboardProfiler.ts @@ -1,7 +1,7 @@ import { logMeasurement, reportInteraction, config } from '@grafana/runtime'; import { SceneRenderProfiler, type SceneObject } from '@grafana/scenes'; -import { initializeScenePerformanceService } from './ScenePerformanceService'; +import { initializeScenePerformanceLogger } from './ScenePerformanceLogger'; interface SceneInteractionProfileEvent { origin: string; @@ -26,9 +26,6 @@ export function getDashboardSceneProfiler() { }; dashboardSceneProfiler = new SceneRenderProfiler(panelProfilingConfig); - - // Initialize the Scene performance service to start listening to events - initializeScenePerformanceService(); } return dashboardSceneProfiler; } diff --git a/public/app/features/dashboard/services/ScenePerformanceService.ts b/public/app/features/dashboard/services/ScenePerformanceLogger.ts similarity index 71% rename from public/app/features/dashboard/services/ScenePerformanceService.ts rename to public/app/features/dashboard/services/ScenePerformanceLogger.ts index b9fa08a28fa..663717da90b 100644 --- a/public/app/features/dashboard/services/ScenePerformanceService.ts +++ b/public/app/features/dashboard/services/ScenePerformanceLogger.ts @@ -17,13 +17,13 @@ import { } from './performanceConstants'; /** - * Grafana service that subscribes to Scene performance events - * and integrates them with Grafana's observability systems. - * Also creates Chrome DevTools performance marks and measurements for debugging. + * Grafana logger that subscribes to Scene performance events + * and logs them to console with Chrome DevTools performance marks and measurements for debugging. */ -export class ScenePerformanceService implements ScenePerformanceObserver { +export class ScenePerformanceLogger implements ScenePerformanceObserver { private isInitialized = false; private unsubscribe: (() => void) | null = null; + private panelGroupsOpen = new Set(); // Track which panels we've seen constructor() { // Bind all observer methods to preserve 'this' context when called by ScenePerformanceTracker @@ -48,7 +48,7 @@ export class ScenePerformanceService implements ScenePerformanceObserver { // Note: Analytics aggregator will be initialized separately with dashboard context this.isInitialized = true; - writePerformanceLog('ScenePerformanceService', 'Initialized and subscribed to Scene performance events'); + writePerformanceLog('SPL', 'Initialized and subscribed to Scene performance events'); } public destroy() { @@ -63,7 +63,7 @@ export class ScenePerformanceService implements ScenePerformanceObserver { } this.isInitialized = false; - writePerformanceLog('ScenePerformanceService', 'Destroyed and unsubscribed from Scene performance events'); + writePerformanceLog('SPL', 'Destroyed and unsubscribed from Scene performance events'); } // Dashboard-level events @@ -72,14 +72,10 @@ export class ScenePerformanceService implements ScenePerformanceObserver { const dashboardStartMark = PERFORMANCE_MARKS.DASHBOARD_INTERACTION_START(data.operationId); createPerformanceMark(dashboardStartMark, data.timestamp); - writePerformanceLog('ScenePerformanceService', '🎯 Dashboard Interaction Started:', { - type: data.interactionType, - uid: data.metadata?.dashboardUID, - title: data.metadata?.dashboardTitle, - panelCount: data.metadata?.panelCount, - timestamp: data.timestamp, - operationId: data.operationId, - }); + const title = data.metadata?.dashboardTitle || 'Unknown Dashboard'; + const panelCount = data.metadata?.panelCount || 0; + + writePerformanceLog('SPL', `[DASHBOARD] ${data.interactionType} started: ${title} (${panelCount} panels)`); } onDashboardInteractionMilestone(data: DashboardInteractionMilestoneData): void { @@ -88,13 +84,8 @@ export class ScenePerformanceService implements ScenePerformanceObserver { const dashboardMilestoneMark = PERFORMANCE_MARKS.DASHBOARD_MILESTONE(data.operationId, milestone); createPerformanceMark(dashboardMilestoneMark, data.timestamp); - writePerformanceLog('ScenePerformanceService', '🔄 Dashboard Milestone:', { - type: data.interactionType, - uid: data.metadata?.dashboardUID, - milestone: data.milestone, - timestamp: data.timestamp, - operationId: data.operationId, - }); + // Log milestones quietly - only when verbose debugging is needed + // Most milestones are covered by the start/complete query logs } onDashboardInteractionComplete(data: DashboardInteractionCompleteData): void { @@ -106,15 +97,11 @@ export class ScenePerformanceService implements ScenePerformanceObserver { createPerformanceMark(dashboardEndMark, data.timestamp); createPerformanceMeasure(dashboardMeasureName, dashboardStartMark, dashboardEndMark); - writePerformanceLog('ScenePerformanceService', '✅ Dashboard Interaction Complete:', { - type: data.interactionType, - uid: data.metadata?.dashboardUID, - title: data.metadata?.dashboardTitle, - duration: data.duration, - networkDuration: data.networkDuration, - timestamp: data.timestamp, - operationId: data.operationId, - }); + // Clear tracking state + this.panelGroupsOpen.clear(); + + // Dashboard completion logging is handled comprehensively by SceneRenderProfiler + // This observer focuses on creating DevTools performance marks/measures } // Panel-level events @@ -122,16 +109,10 @@ export class ScenePerformanceService implements ScenePerformanceObserver { // Create standardized performance marks based on operation type this.createStandardizedPanelMark(data, 'start'); - const operationIcon = this.getOperationIcon(data.operation); - writePerformanceLog('ScenePerformanceService', `${operationIcon} Panel Operation Started [${data.operation}]:`, { - panelId: data.panelId, - panelKey: data.panelKey, - pluginId: data.pluginId, - operation: data.operation, - timestamp: data.timestamp, - metadata: data.metadata, - operationId: data.operationId, - }); + // Track panel for summary logging later + this.panelGroupsOpen.add(data.panelKey); + + // Don't log start events - they're noise. Only log completions with timing. } onPanelOperationComplete(data: PanelPerformanceData): void { @@ -139,17 +120,19 @@ export class ScenePerformanceService implements ScenePerformanceObserver { this.createStandardizedPanelMark(data, 'end'); this.createStandardizedPanelMeasure(data); - const operationIcon = this.getOperationIcon(data.operation); - writePerformanceLog('ScenePerformanceService', `${operationIcon} Panel Operation Complete [${data.operation}]:`, { - panelId: data.panelId, - panelKey: data.panelKey, - pluginId: data.pluginId, - operation: data.operation, - duration: data.duration, - timestamp: data.timestamp, - metadata: data.metadata, - operationId: data.operationId, - }); + const duration = (data.duration || 0).toFixed(1); + const slowWarning = (data.duration || 0) > 100 ? ' ⚠️ SLOW' : ''; + + // For query operations, include the queryId for correlation + let operationDisplay: string = data.operation; + if (data.operation === 'query') { + operationDisplay = `${data.operation} [${data.metadata.queryId}]`; + } + + writePerformanceLog( + 'SPL', + `[PANEL] ${data.pluginId}-${data.panelId} ${operationDisplay}: ${duration}ms${slowWarning}` + ); } // Query-level events @@ -158,14 +141,9 @@ export class ScenePerformanceService implements ScenePerformanceObserver { const queryStartMark = PERFORMANCE_MARKS.QUERY_START(data.origin, data.queryId); createPerformanceMark(queryStartMark, data.timestamp); - writePerformanceLog('ScenePerformanceService', '📊 Non-Panel Query Started:', { - queryId: data.queryId, - queryType: data.queryType, - querySource: data.querySource, - origin: data.origin, - timestamp: data.timestamp, - operationId: data.operationId, - }); + // Mark that we're processing infrastructure queries + + // Don't log start events - they're noise. Only log completions with timing. } onQueryComplete(data: QueryPerformanceData): void { @@ -177,15 +155,12 @@ export class ScenePerformanceService implements ScenePerformanceObserver { createPerformanceMark(queryEndMark, data.timestamp); createPerformanceMeasure(queryMeasureName, queryStartMark, queryEndMark); - writePerformanceLog('ScenePerformanceService', '📊 Non-Panel Query Complete:', { - queryId: data.queryId, - queryType: data.queryType, - querySource: data.querySource, - origin: data.origin, - duration: data.duration, - timestamp: data.timestamp, - operationId: data.operationId, - }); + const duration = (data.duration || 0).toFixed(1); + const slowWarning = (data.duration || 0) > 100 ? ' ⚠️ SLOW' : ''; + + // Simple, clean format + const queryType = data.queryType.replace(/^(getDataSource\/|AnnotationsDataLayer\/)/, ''); // Remove prefixes + writePerformanceLog('SPL', `[QUERY ${data.origin}] ${queryType} [${data.queryId}]: ${duration}ms${slowWarning}`); } // Standardized performance mark creation methods - now with full type safety! @@ -302,44 +277,26 @@ export class ScenePerformanceService implements ScenePerformanceObserver { } } - // Helper method to get appropriate icon for operation type - private getOperationIcon(operation: string): string { - switch (operation) { - case 'query': - return '🔍'; - case 'transform': - return '🔄'; - case 'fieldConfig': - return '🔧'; - case 'render': - return '🎨'; - case 'plugin-load': - return '⚡'; - default: - return '⚡'; - } - } - // All performance marks now use standardized functions from performanceConstants.ts } // Singleton instance -let scenePerformanceService: ScenePerformanceService | null = null; +let scenePerformanceLogger: ScenePerformanceLogger | null = null; -export function getScenePerformanceService(): ScenePerformanceService { - if (!scenePerformanceService) { - scenePerformanceService = new ScenePerformanceService(); +export function getScenePerformanceLogger(): ScenePerformanceLogger { + if (!scenePerformanceLogger) { + scenePerformanceLogger = new ScenePerformanceLogger(); } - return scenePerformanceService; + return scenePerformanceLogger; } -export function initializeScenePerformanceService(): void { - getScenePerformanceService().initialize(); +export function initializeScenePerformanceLogger(): void { + getScenePerformanceLogger().initialize(); } -export function destroyScenePerformanceService(): void { - if (scenePerformanceService) { - scenePerformanceService.destroy(); - scenePerformanceService = null; +export function destroyScenePerformanceLogger(): void { + if (scenePerformanceLogger) { + scenePerformanceLogger.destroy(); + scenePerformanceLogger = null; } }