Enhancement: Implement panel profiling toggle and update performance metrics display

This commit introduces a global toggle for panel profiling, allowing users to enable or disable performance metrics via keyboard shortcuts. The PanelPerformanceMetrics component now conditionally renders based on the profiling state, ensuring metrics are displayed appropriately. Additionally, the logic for enabling profiling has been refined to check both global settings and specific dashboard configurations.
This commit is contained in:
David Kaltschmidt
2025-12-05 16:43:31 +01:00
parent 6291c18ba1
commit 7bea99a54b
7 changed files with 93 additions and 19 deletions
@@ -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'),
},
],
},
{
@@ -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<PanelPer
const styles = useStyles2(getStyles);
const { metrics } = model.useState();
const [fakeQueryTime, setFakeQueryTime] = useState(0);
const [isProfilingEnabled, setIsProfilingEnabled] = useState(isPanelProfilingEnabled());
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const startTimeRef = useRef<number | null>(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<PanelPer
const displayQueryTime = lastQueryTime > 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 (
<div>
@@ -143,11 +164,14 @@ function PanelPerformanceMetricsRenderer({ model }: SceneComponentProps<PanelPer
);
};
// Check if there are any transformation operations
const hasTransformations = metrics && metrics.transformationOperations.length > 0;
const tooltipContent = (
<div>
{renderMetricRow('Query', displayQueryTime)}
{hasTransformations && renderMetricRow('Transform', lastTransformTime)}
{renderMetricRow('Render', lastRenderTime)}
{renderMetricRow('Transform', lastTransformTime)}
<div style={{ marginTop: '8px', borderTop: '1px solid rgba(255,255,255,0.1)', paddingTop: '8px' }}>
{/* eslint-disable-next-line @grafana/i18n/no-untranslated-strings */}
<strong>Total:</strong> {formatDuration(lastTotalTime)}
@@ -155,13 +179,24 @@ function PanelPerformanceMetricsRenderer({ model }: SceneComponentProps<PanelPer
</div>
);
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 (
<Tooltip content={tooltipContent}>
@@ -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 +',
@@ -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<D
// Create profiler once and reuse to avoid duplicate metadata setting
const dashboardProfiler = getDashboardSceneProfilerWithMetadata(metadata.name, dashboard.title);
// Check if profiling should be enabled (global toggle or config)
const enableProfiling =
isPanelProfilingEnabled() ||
config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === metadata.name) !== -1;
const queryController = new behaviors.SceneQueryController(
{
@@ -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,
@@ -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
@@ -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();