diff --git a/public/app/core/components/help/HelpModal.tsx b/public/app/core/components/help/HelpModal.tsx index e334d23982e..68fa4a53c6a 100644 --- a/public/app/core/components/help/HelpModal.tsx +++ b/public/app/core/components/help/HelpModal.tsx @@ -202,6 +202,10 @@ export const useShortcuts = () => { keys: ['d', 'x'], description: t('help-modal.shortcuts-description.toggle-exemplars', 'Toggle exemplars in all panel'), }, + { + keys: ['d', 'p'], + description: t('help-modal.shortcuts-description.toggle-performance-metrics', 'Toggle performance metrics'), + }, ], }, { diff --git a/public/app/features/dashboard-scene/scene/PanelPerformanceMetrics.tsx b/public/app/features/dashboard-scene/scene/PanelPerformanceMetrics.tsx index b14676213c2..9e4cd149f63 100644 --- a/public/app/features/dashboard-scene/scene/PanelPerformanceMetrics.tsx +++ b/public/app/features/dashboard-scene/scene/PanelPerformanceMetrics.tsx @@ -9,6 +9,7 @@ import { getDashboardAnalyticsAggregator, PanelAnalyticsMetrics, } from '../../dashboard/services/DashboardAnalyticsAggregator'; +import { isPanelProfilingEnabled } from '../../dashboard/services/DashboardProfiler'; import { getPanelIdForVizPanel } from '../utils/utils'; interface PanelPerformanceMetricsState extends SceneObjectState { @@ -68,9 +69,27 @@ function PanelPerformanceMetricsRenderer({ model }: SceneComponentProps(null); const startTimeRef = useRef(null); + // Watch for profiling state changes (when toggled via hotkey) + useEffect(() => { + // Poll for profiling state changes - this allows the component to react when profiling is toggled + const checkProfilingState = () => { + const currentState = isPanelProfilingEnabled(); + if (currentState !== isProfilingEnabled) { + setIsProfilingEnabled(currentState); + } + }; + + // Check immediately and then periodically + checkProfilingState(); + const interval = setInterval(checkProfilingState, 100); + + return () => clearInterval(interval); + }, [isProfilingEnabled]); + // Get last operation times (most recent operation in each array) const lastQueryTime = metrics && metrics.queryOperations.length > 0 @@ -124,16 +143,18 @@ function PanelPerformanceMetricsRenderer({ model }: SceneComponentProps 0 ? lastQueryTime : fakeQueryTime; const lastTotalTime = displayQueryTime + lastRenderTime + lastTransformTime; - // Don't render if panel or metrics are not available (and no fake timer running) - if (!panel || (!metrics && fakeQueryTime === 0)) { + // Don't render if panel is not available + if (!panel) { return null; } - // Show component if we have any metrics or fake query time is running - if (lastTotalTime === 0 && fakeQueryTime === 0) { + // If profiling is disabled, don't show the component at all + if (!isProfilingEnabled) { return null; } + // If profiling is enabled, always show the component (even with 0 values) + const renderMetricRow = (label: string, current: number) => { return (
@@ -143,11 +164,14 @@ function PanelPerformanceMetricsRenderer({ model }: SceneComponentProps 0; + const tooltipContent = (
{renderMetricRow('Query', displayQueryTime)} + {hasTransformations && renderMetricRow('Transform', lastTransformTime)} {renderMetricRow('Render', lastRenderTime)} - {renderMetricRow('Transform', lastTransformTime)}
{/* eslint-disable-next-line @grafana/i18n/no-untranslated-strings */} Total: {formatDuration(lastTotalTime)} @@ -155,13 +179,24 @@ function PanelPerformanceMetricsRenderer({ model }: SceneComponentProps ); - const metricsText = [ - displayQueryTime > 0 && `Q:${formatDuration(displayQueryTime)}`, - lastRenderTime > 0 && `R:${formatDuration(lastRenderTime)}`, - lastTransformTime > 0 && `T:${formatDuration(lastTransformTime)}`, - ] - .filter(Boolean) - .join(' '); + // Show metrics text - if profiling is enabled, show all metrics even if 0 + // Transform is only shown if there are transformation operations + // Otherwise, only show non-zero metrics + const metricsText = isProfilingEnabled + ? [ + `Q:${formatDuration(displayQueryTime)}`, + hasTransformations && `T:${formatDuration(lastTransformTime)}`, + `R:${formatDuration(lastRenderTime)}`, + ] + .filter(Boolean) + .join(' ') + : [ + displayQueryTime > 0 && `Q:${formatDuration(displayQueryTime)}`, + lastTransformTime > 0 && `T:${formatDuration(lastTransformTime)}`, + lastRenderTime > 0 && `R:${formatDuration(lastRenderTime)}`, + ] + .filter(Boolean) + .join(' '); return ( diff --git a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts index 1113b8a8a8c..d52c17b43b5 100644 --- a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts +++ b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts @@ -8,6 +8,7 @@ import { InspectTab } from 'app/features/inspector/types'; import { AccessControlAction } from 'app/types/accessControl'; import { shareDashboardType } from '../../dashboard/components/ShareModal/utils'; +import { enablePanelProfilingForDashboard, togglePanelProfiling } from '../../dashboard/services/DashboardProfiler'; import { PanelInspectDrawer } from '../inspect/PanelInspectDrawer'; import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; @@ -130,6 +131,18 @@ export function setupKeyboardShortcuts(scene: DashboardScene) { onTrigger: () => sceneGraph.getTimeRange(scene).onRefresh(), }); + // Toggle performance metrics + keybindings.addBinding({ + key: 'd p', + onTrigger: () => { + const newState = togglePanelProfiling(); + // If toggling on, enable profiling for the current dashboard + if (newState && scene.state.uid) { + enablePanelProfilingForDashboard(scene, scene.state.uid); + } + }, + }); + if (config.featureToggles.newTimeRangeZoomShortcuts) { keybindings.addBinding({ key: 't +', diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index 23a2b125aac..2dafb58a88e 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -60,6 +60,7 @@ import { getDashboardSceneProfilerWithMetadata, enablePanelProfilingForDashboard, getDashboardComponentInteractionCallback, + isPanelProfilingEnabled, } from 'app/features/dashboard/services/DashboardProfiler'; import { DashboardMeta } from 'app/types/dashboard'; @@ -184,7 +185,9 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo uid === '*' || uid === metadata.name) !== -1; const queryController = new behaviors.SceneQueryController( { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index df36d555ba9..3ce9a0b8b1d 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -26,6 +26,7 @@ import { getDashboardSceneProfilerWithMetadata, enablePanelProfilingForDashboard, getDashboardComponentInteractionCallback, + isPanelProfilingEnabled, } from 'app/features/dashboard/services/DashboardProfiler'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; @@ -313,9 +314,10 @@ export function createDashboardSceneFromDashboardModel( // Create profiler once and reuse to avoid duplicate metadata setting const dashboardProfiler = getDashboardSceneProfilerWithMetadata(oldModel.uid, oldModel.title); - // HACK always on + // Check if profiling should be enabled (global toggle or config) const enableProfiling = - config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === oldModel.uid) !== -1 || true; + isPanelProfilingEnabled() || + config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === oldModel.uid) !== -1; const queryController = new behaviors.SceneQueryController( { enableProfiling, diff --git a/public/app/features/dashboard/services/DashboardAnalyticsAggregator.ts b/public/app/features/dashboard/services/DashboardAnalyticsAggregator.ts index 9a4d242361f..4d77db37b87 100644 --- a/public/app/features/dashboard/services/DashboardAnalyticsAggregator.ts +++ b/public/app/features/dashboard/services/DashboardAnalyticsAggregator.ts @@ -137,13 +137,11 @@ export class DashboardAnalyticsAggregator implements performanceUtils.ScenePerfo // Panel-level events onPanelOperationStart = (data: performanceUtils.PanelPerformanceData): void => { - console.log('onPanelOperationStart', data.operation); // Start events don't need aggregation, just ensure panel exists this.ensurePanelExists(data.panelKey, data.panelId, data.pluginId, data.pluginVersion); }; onPanelOperationComplete = (data: performanceUtils.PanelPerformanceData): void => { - console.log('onPanelOperationComplete', data.operation); // Aggregate panel metrics without verbose logging (handled by ScenePerformanceLogger) // Ensure panel exists - it may not have been created by onPanelOperationStart if the panel // was loaded from saved state or if start events were missed diff --git a/public/app/features/dashboard/services/DashboardProfiler.ts b/public/app/features/dashboard/services/DashboardProfiler.ts index 217172a32f6..bdbbc081b24 100644 --- a/public/app/features/dashboard/services/DashboardProfiler.ts +++ b/public/app/features/dashboard/services/DashboardProfiler.ts @@ -10,6 +10,7 @@ interface SceneInteractionProfileEvent { } let dashboardSceneProfiler: performanceUtils.SceneRenderProfiler | undefined; +let isProfilingEnabled = false; export function getDashboardSceneProfiler() { if (!dashboardSceneProfiler) { @@ -23,6 +24,23 @@ export function getDashboardSceneProfiler() { return dashboardSceneProfiler; } +/** + * Toggle panel profiling on/off globally + * @returns The new profiling state (true if enabled, false if disabled) + */ +export function togglePanelProfiling(): boolean { + isProfilingEnabled = !isProfilingEnabled; + return isProfilingEnabled; +} + +/** + * Get the current panel profiling state + * @returns true if profiling is enabled, false otherwise + */ +export function isPanelProfilingEnabled(): boolean { + return isProfilingEnabled; +} + export function getDashboardComponentInteractionCallback(uid: string, title: string) { return (e: SceneInteractionProfileEvent) => { const payload = { @@ -61,10 +79,11 @@ export function getDashboardSceneProfilerWithMetadata(uid: string, title: string // Function to enable panel profiling for a specific dashboard export function enablePanelProfilingForDashboard(dashboard: SceneObject, uid: string) { - // Check if panel profiling should be enabled for this dashboard - // HACK always on + // Check if panel profiling should be enabled + // First check the global toggle state, then fall back to config const shouldEnablePanelProfiling = - config.dashboardPerformanceMetrics.findIndex((configUid) => configUid === '*' || configUid === uid) !== -1 || true; // HACK always on + isProfilingEnabled || + config.dashboardPerformanceMetrics.findIndex((configUid) => configUid === '*' || configUid === uid) !== -1; if (shouldEnablePanelProfiling) { const profiler = getDashboardSceneProfiler();