Dashboard: New measurements on variable timings (#110709)
* create new interaction profiler behaviour and also use it to measure scopes fetching times * PR mods * refactor * add canary scenes * refactor * refactor * canary version * add config flag for scope measurements * refactor * fix
This commit is contained in:
+10
@@ -172,6 +172,15 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
|
||||
getDashboardSceneProfiler()
|
||||
);
|
||||
|
||||
const interactionTracker = new behaviors.SceneInteractionTracker(
|
||||
{
|
||||
enableInteractionTracking:
|
||||
config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === metadata.name) !== -1,
|
||||
onInteractionComplete: getDashboardInteractionCallback(metadata.name, dashboard.title),
|
||||
},
|
||||
getDashboardSceneProfiler()
|
||||
);
|
||||
|
||||
const dashboardScene = new DashboardScene(
|
||||
{
|
||||
description: dashboard.description,
|
||||
@@ -200,6 +209,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
|
||||
sync: transformCursorSyncV2ToV1(dashboard.cursorSync),
|
||||
}),
|
||||
queryController,
|
||||
interactionTracker,
|
||||
registerDashboardMacro,
|
||||
registerPanelInteractionsReporter,
|
||||
new behaviors.LiveNowTimer({ enabled: dashboard.liveNow }),
|
||||
|
||||
@@ -305,11 +305,21 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
|
||||
getDashboardSceneProfiler()
|
||||
);
|
||||
|
||||
const interactionTracker = new behaviors.SceneInteractionTracker(
|
||||
{
|
||||
enableInteractionTracking:
|
||||
config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === oldModel.uid) !== -1,
|
||||
onInteractionComplete: getDashboardInteractionCallback(oldModel.uid, oldModel.title),
|
||||
},
|
||||
getDashboardSceneProfiler()
|
||||
);
|
||||
|
||||
const behaviorList: SceneObjectState['$behaviors'] = [
|
||||
new behaviors.CursorSync({
|
||||
sync: oldModel.graphTooltip,
|
||||
}),
|
||||
queryController,
|
||||
interactionTracker,
|
||||
registerDashboardMacro,
|
||||
registerPanelInteractionsReporter,
|
||||
new behaviors.LiveNowTimer({ enabled: oldModel.liveNow }),
|
||||
|
||||
@@ -10,6 +10,26 @@ export function getDashboardSceneProfiler() {
|
||||
return dashboardSceneProfiler;
|
||||
}
|
||||
|
||||
export function getDashboardComponentInteractionCallback(uid: string, title: string) {
|
||||
return (e: SceneInteractionProfileEvent) => {
|
||||
const payload = {
|
||||
duration: e.duration,
|
||||
networkDuration: e.networkDuration,
|
||||
startTs: e.startTs,
|
||||
endTs: e.endTs,
|
||||
timeSinceBoot: performance.measure('time_since_boot', 'frontend_boot_js_done_time_seconds').duration,
|
||||
};
|
||||
|
||||
reportInteraction('dashboard_interaction', {
|
||||
interactionType: e.origin,
|
||||
uid,
|
||||
...payload,
|
||||
});
|
||||
|
||||
logMeasurement(`dashboard_interaction`, payload, { interactionType: e.origin, dashboard: uid, title: title });
|
||||
};
|
||||
}
|
||||
|
||||
export function getDashboardInteractionCallback(uid: string, title: string) {
|
||||
return (e: SceneInteractionProfileEvent) => {
|
||||
const payload = {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Scope, ScopeNode, store as storeImpl } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { SceneRenderProfiler } from '@grafana/scenes';
|
||||
import { getDashboardSceneProfiler } from 'app/features/dashboard/services/DashboardProfiler';
|
||||
|
||||
import { ScopesApiClient } from '../ScopesApiClient';
|
||||
import { ScopesServiceBase } from '../ScopesServiceBase';
|
||||
@@ -49,7 +52,10 @@ export class ScopesSelectorService extends ScopesServiceBase<ScopesSelectorServi
|
||||
constructor(
|
||||
private apiClient: ScopesApiClient,
|
||||
private dashboardsService: ScopesDashboardsService,
|
||||
private store = storeImpl
|
||||
private store = storeImpl,
|
||||
private interactionProfiler: SceneRenderProfiler | undefined = config.dashboardPerformanceMetrics.length
|
||||
? getDashboardSceneProfiler()
|
||||
: undefined
|
||||
) {
|
||||
super({
|
||||
loading: false,
|
||||
@@ -94,32 +100,40 @@ export class ScopesSelectorService extends ScopesServiceBase<ScopesSelectorServi
|
||||
};
|
||||
|
||||
private expandOrFilterNode = async (scopeNodeId: string, query?: string) => {
|
||||
this.interactionProfiler?.startInteraction('scopeNodeDiscovery');
|
||||
|
||||
const path = getPathOfNode(scopeNodeId, this.state.nodes);
|
||||
|
||||
const nodeToExpand = treeNodeAtPath(this.state.tree!, path);
|
||||
|
||||
if (!nodeToExpand) {
|
||||
throw new Error(`Node ${scopeNodeId} not found in tree`);
|
||||
}
|
||||
try {
|
||||
if (!nodeToExpand) {
|
||||
throw new Error(`Node ${scopeNodeId} not found in tree`);
|
||||
}
|
||||
|
||||
if (nodeToExpand.scopeNodeId !== '' && !isNodeExpandable(this.state.nodes[nodeToExpand.scopeNodeId])) {
|
||||
throw new Error(`Trying to expand node at id ${scopeNodeId} that is not expandable`);
|
||||
}
|
||||
if (nodeToExpand.scopeNodeId !== '' && !isNodeExpandable(this.state.nodes[nodeToExpand.scopeNodeId])) {
|
||||
throw new Error(`Trying to expand node at id ${scopeNodeId} that is not expandable`);
|
||||
}
|
||||
|
||||
// Check if this is first expansion or filtering within existing children
|
||||
const haveChildrenLoaded = nodeToExpand.children && Object.keys(nodeToExpand.children).length > 0;
|
||||
// Check if this is first expansion or filtering within existing children
|
||||
const haveChildrenLoaded = nodeToExpand.children && Object.keys(nodeToExpand.children).length > 0;
|
||||
|
||||
if (!nodeToExpand.expanded || nodeToExpand.query !== query || !haveChildrenLoaded) {
|
||||
const newTree = modifyTreeNodeAtPath(this.state.tree!, path, (treeNode) => {
|
||||
treeNode.expanded = true;
|
||||
// Reset query on first expansion, keep it only when filtering within existing children
|
||||
treeNode.query = '';
|
||||
});
|
||||
this.updateState({ tree: newTree });
|
||||
if (!nodeToExpand.expanded || nodeToExpand.query !== query || !haveChildrenLoaded) {
|
||||
const newTree = modifyTreeNodeAtPath(this.state.tree!, path, (treeNode) => {
|
||||
treeNode.expanded = true;
|
||||
// Reset query on first expansion, keep it only when filtering within existing children
|
||||
treeNode.query = '';
|
||||
});
|
||||
this.updateState({ tree: newTree });
|
||||
|
||||
// For API call: only pass query if filtering within existing children
|
||||
const queryForAPI = haveChildrenLoaded ? query : query === '' ? '' : undefined;
|
||||
await this.loadNodeChildren(path, nodeToExpand, queryForAPI, haveChildrenLoaded);
|
||||
// For API call: only pass query if filtering within existing children
|
||||
const queryForAPI = haveChildrenLoaded ? query : query === '' ? '' : undefined;
|
||||
await this.loadNodeChildren(path, nodeToExpand, queryForAPI, haveChildrenLoaded);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
this.interactionProfiler?.stopInteraction();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user