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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user