Dashboard: Tweak interaction performance tracking (#108658)
* Dashboard: Tweak interaction tracking * Bump scenes to canary (https://github.com/grafana/scenes/pull/1195) * Update naming * Use latest canary * Update cenes, introduce singleton dashboard profiler, instrument v2 schema dashboard * Fix an oooooqpsiiii * Bmp scenes to 6.28.5 * Add documentation * Add event start/end time stamps, update documentation. Bump scenes * Docs * Move file * Typos
This commit is contained in:
+2
-2
@@ -290,8 +290,8 @@
|
||||
"@grafana/plugin-ui": "0.10.7",
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/scenes": "^6.27.2",
|
||||
"@grafana/scenes-react": "^6.27.2",
|
||||
"@grafana/scenes": "6.28.6",
|
||||
"@grafana/scenes-react": "6.28.6",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
|
||||
@@ -267,7 +267,7 @@ abstract class DashboardScenePageStateManagerBase<T>
|
||||
const queryController = sceneGraph.getQueryController(dashboard);
|
||||
|
||||
trackDashboardSceneLoaded(dashboard, measure?.duration);
|
||||
queryController?.startProfile('DashboardScene');
|
||||
queryController?.startProfile('dashboard_view');
|
||||
|
||||
if (options.route !== DashboardRoutes.New) {
|
||||
emitDashboardViewEvent({
|
||||
|
||||
+14
-1
@@ -51,6 +51,10 @@ import {
|
||||
DeprecatedInternalId,
|
||||
} from 'app/features/apiserver/types';
|
||||
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
|
||||
import {
|
||||
getDashboardInteractionCallback,
|
||||
getDashboardSceneProfiler,
|
||||
} from 'app/features/dashboard/services/DashboardProfiler';
|
||||
import { DashboardMeta } from 'app/types/dashboard';
|
||||
|
||||
import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior';
|
||||
@@ -157,6 +161,15 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
|
||||
|
||||
//createLayoutManager(dashboard);
|
||||
|
||||
const queryController = new behaviors.SceneQueryController(
|
||||
{
|
||||
enableProfiling:
|
||||
config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === metadata.name) !== -1,
|
||||
onProfileComplete: getDashboardInteractionCallback(metadata.name, dashboard.title),
|
||||
},
|
||||
getDashboardSceneProfiler()
|
||||
);
|
||||
|
||||
const dashboardScene = new DashboardScene(
|
||||
{
|
||||
description: dashboard.description,
|
||||
@@ -184,7 +197,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
|
||||
new behaviors.CursorSync({
|
||||
sync: transformCursorSyncV2ToV1(dashboard.cursorSync),
|
||||
}),
|
||||
new behaviors.SceneQueryController(),
|
||||
queryController,
|
||||
registerDashboardMacro,
|
||||
registerPanelInteractionsReporter,
|
||||
new behaviors.LiveNowTimer({ enabled: dashboard.liveNow }),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { uniqueId } from 'lodash';
|
||||
|
||||
import { DataFrameDTO, DataFrameJSON } from '@grafana/data';
|
||||
import { config, logMeasurement, reportInteraction } from '@grafana/runtime';
|
||||
import { config } from '@grafana/runtime';
|
||||
import {
|
||||
VizPanel,
|
||||
SceneTimePicker,
|
||||
@@ -17,12 +17,15 @@ import {
|
||||
SceneGridItemLike,
|
||||
SceneDataLayerProvider,
|
||||
UserActionEvent,
|
||||
SceneInteractionProfileEvent,
|
||||
SceneObjectState,
|
||||
} from '@grafana/scenes';
|
||||
import { isWeekStart } from '@grafana/ui';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { K8S_V1_DASHBOARD_API_CONFIG } from 'app/features/dashboard/api/v1';
|
||||
import {
|
||||
getDashboardInteractionCallback,
|
||||
getDashboardSceneProfiler,
|
||||
} from 'app/features/dashboard/services/DashboardProfiler';
|
||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||
import { DashboardDTO, DashboardDataDTO } from 'app/types/dashboard';
|
||||
@@ -292,15 +295,20 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const queryController = new behaviors.SceneQueryController(
|
||||
{
|
||||
enableProfiling:
|
||||
config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === oldModel.uid) !== -1,
|
||||
onProfileComplete: getDashboardInteractionCallback(oldModel.uid, oldModel.title),
|
||||
},
|
||||
getDashboardSceneProfiler()
|
||||
);
|
||||
|
||||
const behaviorList: SceneObjectState['$behaviors'] = [
|
||||
new behaviors.CursorSync({
|
||||
sync: oldModel.graphTooltip,
|
||||
}),
|
||||
new behaviors.SceneQueryController({
|
||||
enableProfiling:
|
||||
config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === oldModel.uid) !== -1,
|
||||
onProfileComplete: getDashboardInteractionCallback(oldModel.uid, oldModel.title),
|
||||
}),
|
||||
queryController,
|
||||
registerDashboardMacro,
|
||||
registerPanelInteractionsReporter,
|
||||
new behaviors.LiveNowTimer({ enabled: oldModel.liveNow }),
|
||||
@@ -499,40 +507,3 @@ export const convertOldSnapshotToScenesSnapshot = (panel: PanelModel) => {
|
||||
panel.snapshotData = [];
|
||||
}
|
||||
};
|
||||
|
||||
function getDashboardInteractionCallback(uid: string, title: string) {
|
||||
return (e: SceneInteractionProfileEvent) => {
|
||||
let interactionType = '';
|
||||
|
||||
if (e.origin === 'SceneTimeRange') {
|
||||
interactionType = 'time-range-change';
|
||||
} else if (e.origin === 'SceneRefreshPicker') {
|
||||
interactionType = 'refresh';
|
||||
} else if (e.origin === 'DashboardScene') {
|
||||
interactionType = 'view';
|
||||
} else if (e.origin.indexOf('Variable') > -1) {
|
||||
interactionType = 'variable-change';
|
||||
}
|
||||
reportInteraction('dashboard-render', {
|
||||
interactionType,
|
||||
duration: e.duration,
|
||||
networkDuration: e.networkDuration,
|
||||
totalJSHeapSize: e.totalJSHeapSize,
|
||||
usedJSHeapSize: e.usedJSHeapSize,
|
||||
jsHeapSizeLimit: e.jsHeapSizeLimit,
|
||||
});
|
||||
|
||||
logMeasurement(
|
||||
`dashboard.${interactionType}`,
|
||||
{
|
||||
duration: e.duration,
|
||||
networkDuration: e.networkDuration,
|
||||
totalJSHeapSize: e.totalJSHeapSize,
|
||||
usedJSHeapSize: e.usedJSHeapSize,
|
||||
jsHeapSizeLimit: e.jsHeapSizeLimit,
|
||||
timeSinceBoot: performance.measure('time_since_boot', 'frontend_boot_js_done_time_seconds').duration,
|
||||
},
|
||||
{ dashboard: uid, title: title }
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { logMeasurement, reportInteraction } from '@grafana/runtime';
|
||||
import { SceneInteractionProfileEvent, SceneRenderProfiler } from '@grafana/scenes';
|
||||
|
||||
let dashboardSceneProfiler: SceneRenderProfiler | undefined;
|
||||
|
||||
export function getDashboardSceneProfiler() {
|
||||
if (!dashboardSceneProfiler) {
|
||||
dashboardSceneProfiler = new SceneRenderProfiler();
|
||||
}
|
||||
return dashboardSceneProfiler;
|
||||
}
|
||||
|
||||
export function getDashboardInteractionCallback(uid: string, title: string) {
|
||||
return (e: SceneInteractionProfileEvent) => {
|
||||
const payload = {
|
||||
duration: e.duration,
|
||||
networkDuration: e.networkDuration,
|
||||
startTs: e.startTs,
|
||||
endTs: e.endTs,
|
||||
totalJSHeapSize: e.totalJSHeapSize,
|
||||
usedJSHeapSize: e.usedJSHeapSize,
|
||||
jsHeapSizeLimit: e.jsHeapSizeLimit,
|
||||
timeSinceBoot: performance.measure('time_since_boot', 'frontend_boot_js_done_time_seconds').duration,
|
||||
};
|
||||
|
||||
reportInteraction('dashboard_render', {
|
||||
interactionType: e.origin,
|
||||
uid,
|
||||
...payload,
|
||||
});
|
||||
|
||||
logMeasurement(`dashboard_render`, payload, { interactionType: e.origin, dashboard: uid, title: title });
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
# Grafana Dashboard Render Performance Metrics
|
||||
|
||||
This documentation describes the dashboard render performance metrics exposed from Grafana's frontend.
|
||||
|
||||
## Overview
|
||||
|
||||
The exposed dashboard performance metrics feature provides comprehensive tracking and profiling of dashboard interactions, allowing administrators and developers to analyze dashboard render performance, user interactions, and identify performance bottlenecks.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Enabling Performance Metrics
|
||||
|
||||
Dashboard performance metrics are configured in the Grafana configuration file (`grafana.ini`) under the `[dashboards]` section:
|
||||
|
||||
```ini
|
||||
[dashboards]
|
||||
# Dashboards UIDs to report performance metrics for. * can be used to report metrics for all dashboards
|
||||
dashboard_performance_metrics = *
|
||||
```
|
||||
|
||||
**Configuration Options:**
|
||||
|
||||
- **`*`** - Enable profiling on all dashboards
|
||||
- **`<comma-separated-list-of-dashboard-uid>`** - Enable profiling on specific dashboards only
|
||||
- **`""` (empty)** - Disable performance metrics (default)
|
||||
|
||||
**Examples:**
|
||||
|
||||
```ini
|
||||
# Enable for all dashboards
|
||||
dashboard_performance_metrics = *
|
||||
|
||||
# Enable for specific dashboards
|
||||
dashboard_performance_metrics = dashboard-uid-1,dashboard-uid-2,dashboard-uid-3
|
||||
|
||||
# Disable performance metrics
|
||||
dashboard_performance_metrics =
|
||||
```
|
||||
|
||||
## Tracked Interactions
|
||||
|
||||
The system tracks various dashboard interaction types automatically using the [`@grafana/scenes`](https://github.com/grafana/scenes) library. Each interaction is captured with a specific origin identifier that describes the type of user action performed. In Grafana, these interaction events are then reported as `dashboard_render` events with interaction type information included.
|
||||
|
||||
### Core Performance-Tracked Interactions
|
||||
|
||||
The following dashboard interaction types are tracked for dashboard render performance profiling:
|
||||
|
||||
| Interaction Type | Trigger | When Measured |
|
||||
| ------------------------ | -------------------------- | -------------------------------------------------------- |
|
||||
| `dashboard_view` | Dashboard view | When user loads or navigates to a dashboard |
|
||||
| `refresh` | Manual/Auto refresh | When user clicks refresh button or auto-refresh triggers |
|
||||
| `time_range_change` | Time picker changes | When user changes time range in time picker |
|
||||
| `filter_added` | Ad-hoc filter addition | When user adds a new filter to the dashboard |
|
||||
| `filter_removed` | Ad-hoc filter removal | When user removes a filter from the dashboard |
|
||||
| `filter_changed` | Ad-hoc filter modification | When user changes filter values or operators |
|
||||
| `filter_restored` | Ad-hoc filter restoration | When user restores a previously applied filter |
|
||||
| `variable_value_changed` | Variable value changes | When user changes dashboard variable values |
|
||||
| `scopes_changed` | Scopes modifications | When user modifies dashboard scopes |
|
||||
|
||||
The interactions mentioned above are reported to Echo service as well as sent to [Faro](https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/) as `dashboard_render` measurements:
|
||||
|
||||
```ts
|
||||
const payload = {
|
||||
duration: e.duration,
|
||||
networkDuration: e.networkDuration,
|
||||
startTs: e.startTs,
|
||||
endTs: e.endTs,
|
||||
totalJSHeapSize: e.totalJSHeapSize,
|
||||
usedJSHeapSize: e.usedJSHeapSize,
|
||||
jsHeapSizeLimit: e.jsHeapSizeLimit,
|
||||
timeSinceBoot: performance.measure('time_since_boot', 'frontend_boot_js_done_time_seconds').duration,
|
||||
};
|
||||
|
||||
reportInteraction('dashboard_render', {
|
||||
interactionType: e.origin,
|
||||
uid,
|
||||
...payload,
|
||||
});
|
||||
|
||||
logMeasurement(`dashboard_render`, payload, { interactionType: e.origin, dashboard: uid, title: title });
|
||||
```
|
||||
|
||||
### Interaction Origin Mapping
|
||||
|
||||
The profiling system uses profiler event's `origin` directly as the `interactionType`, providing direct mapping between user actions and performance measurements.
|
||||
|
||||
## Profiling Implementation
|
||||
|
||||
### Profile Data Structure
|
||||
|
||||
Each interaction profile event captures:
|
||||
|
||||
```typescript
|
||||
interface SceneInteractionProfileEvent {
|
||||
origin: string; // Interaction type
|
||||
duration: number; // Total interaction duration
|
||||
networkDuration: number; // Network requests duration
|
||||
totalJSHeapSize: number; // JavaScript heap size metrics
|
||||
usedJSHeapSize: number; // Used JavaScript heap size
|
||||
jsHeapSizeLimit: number; // JavaScript heap size limit
|
||||
startTs: number; // Profile start timestamp
|
||||
endTs: number; // Profile end timestamp
|
||||
}
|
||||
```
|
||||
|
||||
### Collected Metrics
|
||||
|
||||
For each tracked interaction, the system collects:
|
||||
|
||||
- **Dashboard Metadata**: UID, title
|
||||
- **Performance Metrics**: Duration, network duration
|
||||
- **Memory Metrics**: JavaScript heap usage statistics
|
||||
- **Timing Information**: Time since boot, profile start and end timestamps
|
||||
- **Interaction Context**: Type of user interaction
|
||||
|
||||
## Debugging and Development
|
||||
|
||||
### Enable Profiler Debug Logging
|
||||
|
||||
To observe profiling events in the browser console:
|
||||
|
||||
```javascript
|
||||
localStorage.setItem('grafana.debug.scenes', 'true');
|
||||
```
|
||||
|
||||
#### Console Output
|
||||
|
||||
When debug logging is enabled, you'll see console logs for each profiling event:
|
||||
|
||||
```
|
||||
SceneRenderProfiler: Profile started: {origin: <NAME_OF_INTERACTION>, crumbs: Array(0)}
|
||||
... // intermediate steps adding profile crumbs
|
||||
SceneRenderProfiler: Stopped recording, total measured time (network included): 2123
|
||||
```
|
||||
|
||||
### Enable Echo Service Debug Logging
|
||||
|
||||
To observe Echo events in the browser console:
|
||||
|
||||
```javascript
|
||||
_debug.echo.enable();
|
||||
```
|
||||
|
||||
#### Console Output
|
||||
|
||||
When Echo debug logging is enabled, you'll see console logs for each profiling event captured by Echo service:
|
||||
|
||||
```
|
||||
[EchoSrv: interaction event]: {interactionName: 'dashboard_render', properties: {…}, meta: {…}}
|
||||
```
|
||||
|
||||
### Browser Performance Profiler
|
||||
|
||||
Dashboard interactions can be recorded in the browser's performance profiler, where they appear as:
|
||||
|
||||
```
|
||||
Dashboard Interaction <NAME_OF_INTERACTION>
|
||||
```
|
||||
|
||||
## Analytics Integration
|
||||
|
||||
### Interaction Reporting
|
||||
|
||||
Performance data is integrated with Grafana's analytics system through:
|
||||
|
||||
- **`reportInteraction`**: Reports interaction events to Echo service with performance data
|
||||
- **`logMeasurement`**: Records Faro's performance measurements with metadata
|
||||
|
||||
### Data Collection
|
||||
|
||||
The system reports the following data for each interaction:
|
||||
|
||||
```typescript
|
||||
{
|
||||
interactionType: string, // Type of interaction
|
||||
uid: string, // Dashboard UID
|
||||
duration: number, // Total duration
|
||||
networkDuration: number, // Network time
|
||||
startTs: number, // Profile start timestamp
|
||||
endTs: number, // Profile end timestamp
|
||||
totalJSHeapSize: number, // Memory metrics
|
||||
usedJSHeapSize: number,
|
||||
jsHeapSizeLimit: number,
|
||||
timeSinceBoot: number // Time since frontend boot
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The profiler is integrated into dashboard creation paths and uses a singleton pattern to share profiler instances across dashboard reloads. The performance tracking is implemented using the `SceneRenderProfiler` from the `@grafana/scenes` library.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [PR #858 - Add SceneRenderProfiler to scenes](https://github.com/grafana/scenes/pull/858)
|
||||
- [PR #99629 - Dashboard render performance metrics](https://github.com/grafana/grafana/pull/99629)
|
||||
- [PR #108658 - Dashboard: Tweak interaction tracking](https://github.com/grafana/grafana/pull/108658)
|
||||
- [PR #1195 - Enhance SceneRenderProfiler with additional interaction tracking](https://github.com/grafana/scenes/pull/1195)
|
||||
- [PR #1198 - Make SceneRenderProfiler optional and injectable](https://github.com/grafana/scenes/pull/1198)
|
||||
- [PR #1199 - SceneRenderProfiler: add start and end timestamps to profile events](https://github.com/grafana/scenes/pull/1199)
|
||||
@@ -3600,11 +3600,11 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/scenes-react@npm:^6.27.2":
|
||||
version: 6.28.2
|
||||
resolution: "@grafana/scenes-react@npm:6.28.2"
|
||||
"@grafana/scenes-react@npm:6.28.6":
|
||||
version: 6.28.6
|
||||
resolution: "@grafana/scenes-react@npm:6.28.6"
|
||||
dependencies:
|
||||
"@grafana/scenes": "npm:6.28.2"
|
||||
"@grafana/scenes": "npm:6.28.6"
|
||||
lru-cache: "npm:^10.2.2"
|
||||
react-use: "npm:^17.4.0"
|
||||
peerDependencies:
|
||||
@@ -3616,13 +3616,13 @@ __metadata:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
react-router-dom: ^6.28.0
|
||||
checksum: 10/c00730312828639f8a596c9fd9b0336ec57ca5cec624ac4f09a5fa0be93282d180451e3bcbf83c0209ff04486def25ff98d7b0ea6c370e91b8990112c3e903f2
|
||||
checksum: 10/55dfe20a6454f218b7c5ab2dc728678885d48e53656d046c125b555b201891e121e0960e108229372c2d70336cfe3dade9b392ed001fef90aec57cbcc13f76ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/scenes@npm:6.28.2, @grafana/scenes@npm:^6.27.2":
|
||||
version: 6.28.2
|
||||
resolution: "@grafana/scenes@npm:6.28.2"
|
||||
"@grafana/scenes@npm:6.28.6":
|
||||
version: 6.28.6
|
||||
resolution: "@grafana/scenes@npm:6.28.6"
|
||||
dependencies:
|
||||
"@floating-ui/react": "npm:^0.26.16"
|
||||
"@leeoniya/ufuzzy": "npm:^1.0.16"
|
||||
@@ -3642,7 +3642,7 @@ __metadata:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
react-router-dom: ^6.28.0
|
||||
checksum: 10/53370553ac4ac38d41ab1d782ee14cf05f483e35e5338406d619efb61e4c9881fd361c98ee116ae8bf40ab1a5fa2c82ca859fc519f6d532853c275e71949c654
|
||||
checksum: 10/fcfcf663e2eb63ad25fad27ec50af8590b1a6c9e802927e8e457bf2f81d04cf971c2f12f456a04a502c929ec7a1d50ef7118baee095134b133ecc92b269a6907
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -18187,8 +18187,8 @@ __metadata:
|
||||
"@grafana/plugin-ui": "npm:0.10.7"
|
||||
"@grafana/prometheus": "workspace:*"
|
||||
"@grafana/runtime": "workspace:*"
|
||||
"@grafana/scenes": "npm:^6.27.2"
|
||||
"@grafana/scenes-react": "npm:^6.27.2"
|
||||
"@grafana/scenes": "npm:6.28.6"
|
||||
"@grafana/scenes-react": "npm:6.28.6"
|
||||
"@grafana/schema": "workspace:*"
|
||||
"@grafana/sql": "workspace:*"
|
||||
"@grafana/test-utils": "workspace:*"
|
||||
|
||||
Reference in New Issue
Block a user