forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Perfomance] Track time range picker with
onPageReady
function (ela…
…stic#202889) ## Summary closes elastic/observability-dev#3377 ## Metrics #### `meta.query_range_secs` - The duration of the selected time range in seconds. #### `meta.query_offset_secs` - The offset from "now" to the 'rangeTo'/end' time picker value in seconds. ____ Extend the `onPageReady` function to support date ranges in the meta field. The function should compute the query range in seconds based on the provided time range and report it to telemetry as meta.query_range_secs. If the `rangeTo` is different from 'now', calculate the offset. - A negative offset indicates that the rangeTo is in the past, - a positive offset means it is in the future, - and zero indicates that the rangeTo is exactly 'now'." ### How to instrument To report the selected time range, pass the `rangeFrom` and `rangeTo` . > Failing to pass the correct type will result in TS error. Then, use this data when invoking onPageReady: ``` onPageReady({ meta: { rangeFrom, rangeTo }, }); ``` ### Analysis Meta is flatten field. In order to aggregate the data it's necessary to create a run time field. You can add a field in the 1. select data view (`ebt-kibana-*-performance-metrics`) 2. Add a new field 3. Type double 4. Set value `query_range_secs` ``` def meta = doc[“meta”].size(); if (meta > 0) { def range = doc[“meta.query_range_secs”].size(); if (range > 0) { // Emit the value of ‘meta.target’ emit(Double.parseDouble(doc[“meta.query_range_secs”].value)); } } ``` `query_offset_secs` ``` def meta = doc[“meta”].size(); if (meta > 0) { def offset = doc[“meta.query_offset_secs”].size(); if (offset > 0) { emit(Double.parseDouble(doc[“meta.query_offset_secs”].value)); } } ``` ### Examples <img width="1478" alt="Screenshot 2024-12-09 at 19 51 32" src="https://github.com/user-attachments/assets/72f796e1-4f20-487f-b62a-b6a4aead9a4a"> <img width="1478" alt="Screenshot 2024-12-09 at 19 56 08" src="https://github.com/user-attachments/assets/c278dc3b-e6f3-47ed-9c90-954d71b59161"> <img width="1478" alt="Screenshot 2024-12-09 at 19 53 45 1" src="https://github.com/user-attachments/assets/ef42ecef-48cd-4396-9f5d-c971098d5219"> ### Notes - Instrumented only 2 solutions as an example (dataset and apm services) ### TODO - [x] Update documentation - elastic#204179 - [ ] Update dashboards (create a runtime field) - [x] Track offset ( we need to know if the user selected now or now) --------- Co-authored-by: kibanamachine <[email protected]>
- Loading branch information
1 parent
492d4d2
commit bd1c00f
Showing
18 changed files
with
398 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
packages/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the "Elastic License | ||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
* Public License v 1"; you may not use this file except in compliance with, at | ||
* your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
* License v3.0 only", or the "Server Side Public License, v 1". | ||
*/ | ||
|
||
import { | ||
getDateRange, | ||
getOffsetFromNowInSeconds, | ||
getTimeDifferenceInSeconds, | ||
} from '@kbn/timerange'; | ||
import { perfomanceMarkers } from '../../performance_markers'; | ||
import { EventData } from '../performance_context'; | ||
|
||
interface PerformanceMeta { | ||
queryRangeSecs: number; | ||
queryOffsetSecs: number; | ||
} | ||
|
||
export function measureInteraction() { | ||
performance.mark(perfomanceMarkers.startPageChange); | ||
const trackedRoutes: string[] = []; | ||
return { | ||
/** | ||
* Marks the end of the page ready state and measures the performance between the start of the page change and the end of the page ready state. | ||
* @param pathname - The pathname of the page. | ||
* @param customMetrics - Custom metrics to be included in the performance measure. | ||
*/ | ||
pageReady(pathname: string, eventData?: EventData) { | ||
let performanceMeta: PerformanceMeta | undefined; | ||
performance.mark(perfomanceMarkers.endPageReady); | ||
|
||
if (eventData?.meta) { | ||
const { rangeFrom, rangeTo } = eventData.meta; | ||
|
||
// Convert the date range to epoch timestamps (in milliseconds) | ||
const dateRangesInEpoch = getDateRange({ | ||
from: rangeFrom, | ||
to: rangeTo, | ||
}); | ||
|
||
performanceMeta = { | ||
queryRangeSecs: getTimeDifferenceInSeconds(dateRangesInEpoch), | ||
queryOffsetSecs: | ||
rangeTo === 'now' ? 0 : getOffsetFromNowInSeconds(dateRangesInEpoch.endDate), | ||
}; | ||
} | ||
|
||
if (!trackedRoutes.includes(pathname)) { | ||
performance.measure(pathname, { | ||
detail: { | ||
eventName: 'kibana:plugin_render_time', | ||
type: 'kibana:performance', | ||
customMetrics: eventData?.customMetrics, | ||
meta: performanceMeta, | ||
}, | ||
start: perfomanceMarkers.startPageChange, | ||
end: perfomanceMarkers.endPageReady, | ||
}); | ||
trackedRoutes.push(pathname); | ||
} | ||
}, | ||
}; | ||
} |
161 changes: 161 additions & 0 deletions
161
...bt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the "Elastic License | ||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
* Public License v 1"; you may not use this file except in compliance with, at | ||
* your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
* License v3.0 only", or the "Server Side Public License, v 1". | ||
*/ | ||
|
||
import { measureInteraction } from '.'; | ||
import { perfomanceMarkers } from '../../performance_markers'; | ||
|
||
describe('measureInteraction', () => { | ||
afterAll(() => { | ||
jest.restoreAllMocks(); | ||
}); | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
performance.mark = jest.fn(); | ||
performance.measure = jest.fn(); | ||
}); | ||
|
||
it('should mark the start of the page change', () => { | ||
measureInteraction(); | ||
expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.startPageChange); | ||
}); | ||
|
||
it('should mark the end of the page ready state and measure performance', () => { | ||
const interaction = measureInteraction(); | ||
const pathname = '/test-path'; | ||
interaction.pageReady(pathname); | ||
|
||
expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); | ||
expect(performance.measure).toHaveBeenCalledWith(pathname, { | ||
detail: { | ||
eventName: 'kibana:plugin_render_time', | ||
type: 'kibana:performance', | ||
}, | ||
start: perfomanceMarkers.startPageChange, | ||
end: perfomanceMarkers.endPageReady, | ||
}); | ||
}); | ||
|
||
it('should include custom metrics and meta in the performance measure', () => { | ||
const interaction = measureInteraction(); | ||
const pathname = '/test-path'; | ||
const eventData = { | ||
customMetrics: { key1: 'foo-metric', value1: 100 }, | ||
meta: { rangeFrom: 'now-15m', rangeTo: 'now' }, | ||
}; | ||
|
||
interaction.pageReady(pathname, eventData); | ||
|
||
expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); | ||
expect(performance.measure).toHaveBeenCalledWith(pathname, { | ||
detail: { | ||
eventName: 'kibana:plugin_render_time', | ||
type: 'kibana:performance', | ||
customMetrics: eventData.customMetrics, | ||
meta: { | ||
queryRangeSecs: 900, | ||
queryOffsetSecs: 0, | ||
}, | ||
}, | ||
end: 'end::pageReady', | ||
start: 'start::pageChange', | ||
}); | ||
}); | ||
|
||
it('should handle absolute date format correctly', () => { | ||
const interaction = measureInteraction(); | ||
const pathname = '/test-path'; | ||
jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z | ||
|
||
const eventData = { | ||
meta: { rangeFrom: '2024-12-09T00:00:00Z', rangeTo: '2024-12-09T00:30:00Z' }, | ||
}; | ||
|
||
interaction.pageReady(pathname, eventData); | ||
|
||
expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); | ||
expect(performance.measure).toHaveBeenCalledWith(pathname, { | ||
detail: { | ||
eventName: 'kibana:plugin_render_time', | ||
type: 'kibana:performance', | ||
customMetrics: undefined, | ||
meta: { | ||
queryRangeSecs: 1800, | ||
queryOffsetSecs: 0, | ||
}, | ||
}, | ||
end: 'end::pageReady', | ||
start: 'start::pageChange', | ||
}); | ||
}); | ||
|
||
it('should handle negative offset when rangeTo is in the past', () => { | ||
const interaction = measureInteraction(); | ||
const pathname = '/test-path'; | ||
jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z | ||
|
||
const eventData = { | ||
meta: { rangeFrom: '2024-12-08T00:00:00Z', rangeTo: '2024-12-09T00:00:00Z' }, | ||
}; | ||
|
||
interaction.pageReady(pathname, eventData); | ||
|
||
expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); | ||
expect(performance.measure).toHaveBeenCalledWith(pathname, { | ||
detail: { | ||
eventName: 'kibana:plugin_render_time', | ||
type: 'kibana:performance', | ||
customMetrics: undefined, | ||
meta: { | ||
queryRangeSecs: 86400, | ||
queryOffsetSecs: -1800, | ||
}, | ||
}, | ||
end: 'end::pageReady', | ||
start: 'start::pageChange', | ||
}); | ||
}); | ||
|
||
it('should handle positive offset when rangeTo is in the future', () => { | ||
const interaction = measureInteraction(); | ||
const pathname = '/test-path'; | ||
jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z | ||
|
||
const eventData = { | ||
meta: { rangeFrom: '2024-12-08T01:00:00Z', rangeTo: '2024-12-09T01:00:00Z' }, | ||
}; | ||
|
||
interaction.pageReady(pathname, eventData); | ||
|
||
expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); | ||
expect(performance.measure).toHaveBeenCalledWith(pathname, { | ||
detail: { | ||
eventName: 'kibana:plugin_render_time', | ||
type: 'kibana:performance', | ||
customMetrics: undefined, | ||
meta: { | ||
queryRangeSecs: 86400, | ||
queryOffsetSecs: 1800, | ||
}, | ||
}, | ||
end: 'end::pageReady', | ||
start: 'start::pageChange', | ||
}); | ||
}); | ||
|
||
it('should not measure the same route twice', () => { | ||
const interaction = measureInteraction(); | ||
const pathname = '/test-path'; | ||
|
||
interaction.pageReady(pathname); | ||
interaction.pageReady(pathname); | ||
|
||
expect(performance.measure).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.