mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Profiling] Use CO2 and Cost values provided by the ES Profiling APIs (#170612)
- Removes old flamegraph code replacing it with the ES Flamegraph API - Creates new user settings - Adds a feature flag to use the kibana CO2/Cost calculations instead of the new version - Reads CO2 and Cost from /Stacktraces and /Flamegraph APIs Where do we show the CO2 and Cost values? - Flamegraph toolip - Flamegraph Frame information flyout - Diff Flamegraph Summary - Functions table - Function information flyout - Diff Functions Summary --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
3aac9bd56d
commit
7470d2136d
69 changed files with 1829 additions and 795 deletions
|
@ -1,17 +0,0 @@
|
|||
The stacktrace fixtures in this directory are originally from Elasticsearch's
|
||||
`POST /_profiling/stacktraces` endpoint. They were subsequently filtered
|
||||
through the `shrink_stacktrace_response.js` command in `x-pack/plugins/profiling/scripts/`
|
||||
to reduce the size without losing sampling fidelity (see the script for further
|
||||
details).
|
||||
|
||||
The naming convention for each stacktrace fixture follows this pattern:
|
||||
|
||||
```
|
||||
stacktraces_{seconds}s_{upsampling rate}x.json
|
||||
```
|
||||
|
||||
where `seconds` is the time span of the original query and `upsampling rate` is
|
||||
the reciprocal of the sampling rate returned from the original query.
|
||||
|
||||
To add a new stacktrace fixture to the test suite, update `stacktraces.ts`
|
||||
appropriately.
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { BaseFlameGraph } from '../flamegraph';
|
||||
|
||||
export const baseFlamegraph: BaseFlameGraph = {
|
||||
Edges: [
|
||||
[1],
|
||||
[2],
|
||||
[3],
|
||||
[4],
|
||||
[5],
|
||||
[6],
|
||||
[7],
|
||||
[8],
|
||||
[9],
|
||||
[10],
|
||||
[11],
|
||||
[12],
|
||||
[13],
|
||||
[14],
|
||||
[15],
|
||||
[16],
|
||||
[17],
|
||||
[18],
|
||||
[19],
|
||||
[20],
|
||||
[21],
|
||||
[22],
|
||||
[23],
|
||||
[24],
|
||||
[25],
|
||||
[26],
|
||||
[27],
|
||||
[28],
|
||||
[29],
|
||||
[30],
|
||||
[31],
|
||||
[32],
|
||||
[33],
|
||||
[34],
|
||||
[],
|
||||
],
|
||||
FileID: [
|
||||
'',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
],
|
||||
FrameType: [
|
||||
0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
4, 4, 4,
|
||||
],
|
||||
Inline: [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
],
|
||||
ExeFilename: [
|
||||
'',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
],
|
||||
AddressOrLine: [
|
||||
0, 43443520, 67880745, 67881145, 53704110, 53704665, 53696841, 53697537, 53700683, 53696841,
|
||||
52492674, 67626923, 67629380, 67630226, 51515812, 51512445, 51522994, 44606453, 43747101,
|
||||
43699300, 43538916, 43547623, 42994898, 42994925, 14680216, 14356875, 3732840, 3732678, 3721714,
|
||||
3719260, 3936007, 3897721, 4081162, 4458225, 1712873,
|
||||
],
|
||||
FunctionName: [
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'entry_SYSCALL_64_after_hwframe',
|
||||
'do_syscall_64',
|
||||
'__x64_sys_read',
|
||||
'ksys_read',
|
||||
'vfs_read',
|
||||
'new_sync_read',
|
||||
'seq_read_iter',
|
||||
'm_show',
|
||||
'show_mountinfo',
|
||||
'kernfs_sop_show_path',
|
||||
'cgroup_show_path',
|
||||
],
|
||||
FunctionOffset: [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0,
|
||||
],
|
||||
SourceFilename: [
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
],
|
||||
SourceLine: [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0,
|
||||
],
|
||||
CountInclusive: [
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
7, 7, 7,
|
||||
],
|
||||
CountExclusive: [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 7,
|
||||
],
|
||||
AnnualCO2TonsInclusive: [
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
],
|
||||
AnnualCO2TonsExclusive: [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0.0013627551116480942,
|
||||
],
|
||||
AnnualCostsUSDInclusive: [
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
],
|
||||
AnnualCostsUSDExclusive: [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 61.30240940376492,
|
||||
],
|
||||
Size: 35,
|
||||
SamplingRate: 1,
|
||||
SelfCPU: 7,
|
||||
SelfAnnualCO2Tons: 0.0013627551116480942,
|
||||
TotalAnnualCO2Tons: 0.04769642890768329,
|
||||
SelfAnnualCostsUSD: 61.30240940376492,
|
||||
TotalAnnualCostsUSD: 2145.5843291317715,
|
||||
TotalCPU: 245,
|
||||
TotalSamples: 7,
|
||||
TotalSeconds: 4.980000019073486,
|
||||
};
|
|
@ -8,18 +8,166 @@
|
|||
|
||||
import { StackTraceResponse } from '../stack_traces';
|
||||
|
||||
import stackTraces1x from './stacktraces_60s_1x.json';
|
||||
import stackTraces5x from './stacktraces_3600s_5x.json';
|
||||
import stackTraces125x from './stacktraces_86400s_125x.json';
|
||||
import stackTraces625x from './stacktraces_604800s_625x.json';
|
||||
|
||||
export const stackTraceFixtures: Array<{
|
||||
response: StackTraceResponse;
|
||||
seconds: number;
|
||||
upsampledBy: number;
|
||||
}> = [
|
||||
{ response: stackTraces1x, seconds: 60, upsampledBy: 1 },
|
||||
{ response: stackTraces5x, seconds: 3600, upsampledBy: 5 },
|
||||
{ response: stackTraces125x, seconds: 86400, upsampledBy: 125 },
|
||||
{ response: stackTraces625x, seconds: 604800, upsampledBy: 625 },
|
||||
];
|
||||
export const stacktraces: StackTraceResponse = {
|
||||
stack_traces: {
|
||||
['c2TovSbgCECd_RtKHxMtyQ']: {
|
||||
address_or_lines: [
|
||||
43443520, 67880745, 67881145, 53704110, 53704665, 53696841, 53697537, 53700683, 53696841,
|
||||
52492674, 67626923, 67629380, 67630226, 51515812, 51512445, 51522994, 44606453, 43747101,
|
||||
43699300, 43538916, 43547623, 42994898, 42994925, 14680216, 14356875, 3732840, 3732678,
|
||||
3721714, 3719260, 3936007, 3897721, 4081162, 4458225, 1712873,
|
||||
],
|
||||
file_ids: [
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
],
|
||||
frame_ids: [
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAACluVA',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAAEC8cp',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAAEC8i5',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAADM3Wu',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAADM3fZ',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAADM1lJ',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAADM1wB',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAADM2hL',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAADM1lJ',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAADIPmC',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAAEB-er',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAAEB_FE',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAAEB_SS',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAADEhGk',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAADEgR9',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAADEi2y',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAACqKP1',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAACm4cd',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAACmsxk',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAACmFnk',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAACmHvn',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAACkAzS',
|
||||
'fwIcP8qXDOl7k0VhWU8z9QAAAAACkAzt',
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAA4ACY',
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAA2xGL',
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAOPVo',
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAOPTG',
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAOMny',
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAOMBc',
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAPA8H',
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAO3l5',
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAPkYK',
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAARAbx',
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAGiLp',
|
||||
],
|
||||
type_ids: [
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
4, 4, 4,
|
||||
],
|
||||
annual_co2_tons: 0.0013627551116480942,
|
||||
annual_costs_usd: 61.30240940376492,
|
||||
count: 7,
|
||||
},
|
||||
},
|
||||
stack_frames: {
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAO3l5': {
|
||||
file_name: [],
|
||||
function_name: ['m_show'],
|
||||
function_offset: [],
|
||||
line_number: [],
|
||||
},
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAA4ACY': {
|
||||
file_name: [],
|
||||
function_name: ['entry_SYSCALL_64_after_hwframe'],
|
||||
function_offset: [],
|
||||
line_number: [],
|
||||
},
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAARAbx': {
|
||||
file_name: [],
|
||||
function_name: ['kernfs_sop_show_path'],
|
||||
function_offset: [],
|
||||
line_number: [],
|
||||
},
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAOMBc': {
|
||||
file_name: [],
|
||||
function_name: ['new_sync_read'],
|
||||
function_offset: [],
|
||||
line_number: [],
|
||||
},
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAPA8H': {
|
||||
file_name: [],
|
||||
function_name: ['seq_read_iter'],
|
||||
function_offset: [],
|
||||
line_number: [],
|
||||
},
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAOPVo': {
|
||||
file_name: [],
|
||||
function_name: ['__x64_sys_read'],
|
||||
function_offset: [],
|
||||
line_number: [],
|
||||
},
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAPkYK': {
|
||||
file_name: [],
|
||||
function_name: ['show_mountinfo'],
|
||||
function_offset: [],
|
||||
line_number: [],
|
||||
},
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAGiLp': {
|
||||
file_name: [],
|
||||
function_name: ['cgroup_show_path'],
|
||||
function_offset: [],
|
||||
line_number: [],
|
||||
},
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAOMny': {
|
||||
file_name: [],
|
||||
function_name: ['vfs_read'],
|
||||
function_offset: [],
|
||||
line_number: [],
|
||||
},
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAAOPTG': {
|
||||
file_name: [],
|
||||
function_name: ['ksys_read'],
|
||||
function_offset: [],
|
||||
line_number: [],
|
||||
},
|
||||
'5JfXt00O17Yra2Rwh8HT8QAAAAAA2xGL': {
|
||||
file_name: [],
|
||||
function_name: ['do_syscall_64'],
|
||||
function_offset: [],
|
||||
line_number: [],
|
||||
},
|
||||
},
|
||||
executables: { '5JfXt00O17Yra2Rwh8HT8Q': 'vmlinux', fwIcP8qXDOl7k0VhWU8z9Q: 'metricbeat' },
|
||||
stack_trace_events: { ['c2TovSbgCECd_RtKHxMtyQ']: 7 },
|
||||
total_frames: 34,
|
||||
sampling_rate: 1,
|
||||
};
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { sum } from 'lodash';
|
||||
|
||||
import { createCalleeTree } from './callee';
|
||||
import { decodeStackTraceResponse } from './stack_traces';
|
||||
import { stackTraceFixtures } from './__fixtures__/stacktraces';
|
||||
|
||||
describe('Callee operations', () => {
|
||||
stackTraceFixtures.forEach(({ response, seconds, upsampledBy }) => {
|
||||
const { events, stackTraces, stackFrames, executables, totalFrames, samplingRate } =
|
||||
decodeStackTraceResponse(response);
|
||||
const tree = createCalleeTree(
|
||||
events,
|
||||
stackTraces,
|
||||
stackFrames,
|
||||
executables,
|
||||
totalFrames,
|
||||
samplingRate
|
||||
);
|
||||
|
||||
describe(`stacktraces from ${seconds} seconds and upsampled by ${upsampledBy}`, () => {
|
||||
test('inclusive count of root to be less than or equal total sampled stacktraces', () => {
|
||||
const totalAdjustedSamples = Math.ceil(sum([...events.values()]) / samplingRate);
|
||||
expect(tree.CountInclusive[0]).toBeLessThanOrEqual(totalAdjustedSamples);
|
||||
});
|
||||
|
||||
test('inclusive count for each node should be greater than or equal to its children', () => {
|
||||
const allGreaterThanOrEqual = tree.Edges.map(
|
||||
(children, i) =>
|
||||
tree.CountInclusive[i] >= sum([...children.values()].map((j) => tree.CountInclusive[j]))
|
||||
);
|
||||
expect(allGreaterThanOrEqual).toBeTruthy();
|
||||
});
|
||||
|
||||
test('exclusive count of root is zero', () => {
|
||||
expect(tree.CountExclusive[0]).toEqual(0);
|
||||
});
|
||||
|
||||
test('tree de-duplicates sibling nodes', () => {
|
||||
expect(tree.Size).toBeLessThan(totalFrames);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,201 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { sum } from 'lodash';
|
||||
import { createFrameGroupID, FrameGroupID } from './frame_group';
|
||||
import {
|
||||
emptyExecutable,
|
||||
emptyStackFrame,
|
||||
emptyStackTrace,
|
||||
Executable,
|
||||
FileID,
|
||||
StackFrame,
|
||||
StackFrameID,
|
||||
StackTrace,
|
||||
StackTraceID,
|
||||
} from './profiling';
|
||||
|
||||
type NodeID = number;
|
||||
|
||||
/**
|
||||
* Callee tree
|
||||
*/
|
||||
export interface CalleeTree {
|
||||
/** size */
|
||||
Size: number;
|
||||
/** edges */
|
||||
Edges: Array<Map<FrameGroupID, NodeID>>;
|
||||
/** file ids */
|
||||
FileID: string[];
|
||||
/** frame types */
|
||||
FrameType: number[];
|
||||
/** inlines */
|
||||
Inline: boolean[];
|
||||
/** executable file names */
|
||||
ExeFilename: string[];
|
||||
/** address or lines */
|
||||
AddressOrLine: number[];
|
||||
/** function names */
|
||||
FunctionName: string[];
|
||||
/** function offsets */
|
||||
FunctionOffset: number[];
|
||||
/** source file names */
|
||||
SourceFilename: string[];
|
||||
/** source lines */
|
||||
SourceLine: number[];
|
||||
/** total cpu */
|
||||
CountInclusive: number[];
|
||||
/** self cpu */
|
||||
CountExclusive: number[];
|
||||
TotalSamples: number;
|
||||
TotalCPU: number;
|
||||
SelfCPU: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a callee tree
|
||||
* @param events Map<StackTraceID, number>
|
||||
* @param stackTraces Map<StackTraceID, StackTrace>
|
||||
* @param stackFrames Map<StackFrameID, StackFrame>
|
||||
* @param executables Map<FileID, Executable>
|
||||
* @param totalFrames number
|
||||
* @param samplingRate number
|
||||
* @returns
|
||||
*/
|
||||
export function createCalleeTree(
|
||||
events: Map<StackTraceID, number>,
|
||||
stackTraces: Map<StackTraceID, StackTrace>,
|
||||
stackFrames: Map<StackFrameID, StackFrame>,
|
||||
executables: Map<FileID, Executable>,
|
||||
totalFrames: number,
|
||||
samplingRate: number
|
||||
): CalleeTree {
|
||||
const tree: CalleeTree = {
|
||||
Size: 1,
|
||||
Edges: new Array(totalFrames),
|
||||
FileID: new Array(totalFrames),
|
||||
FrameType: new Array(totalFrames),
|
||||
Inline: new Array(totalFrames),
|
||||
ExeFilename: new Array(totalFrames),
|
||||
AddressOrLine: new Array(totalFrames),
|
||||
FunctionName: new Array(totalFrames),
|
||||
FunctionOffset: new Array(totalFrames),
|
||||
SourceFilename: new Array(totalFrames),
|
||||
SourceLine: new Array(totalFrames),
|
||||
CountInclusive: new Array(totalFrames),
|
||||
CountExclusive: new Array(totalFrames),
|
||||
TotalSamples: 0,
|
||||
SelfCPU: 0,
|
||||
TotalCPU: 0,
|
||||
};
|
||||
|
||||
// The inverse of the sampling rate is the number with which to multiply the number of
|
||||
// samples to get an estimate of the actual number of samples the backend received.
|
||||
const scalingFactor = 1.0 / samplingRate;
|
||||
let totalSamples = 0;
|
||||
tree.Edges[0] = new Map<FrameGroupID, NodeID>();
|
||||
|
||||
tree.FileID[0] = '';
|
||||
tree.FrameType[0] = 0;
|
||||
tree.Inline[0] = false;
|
||||
tree.ExeFilename[0] = '';
|
||||
tree.AddressOrLine[0] = 0;
|
||||
tree.FunctionName[0] = '';
|
||||
tree.FunctionOffset[0] = 0;
|
||||
tree.SourceFilename[0] = '';
|
||||
tree.SourceLine[0] = 0;
|
||||
|
||||
tree.CountInclusive[0] = 0;
|
||||
tree.CountExclusive[0] = 0;
|
||||
|
||||
const sortedStackTraceIDs = new Array<StackTraceID>();
|
||||
for (const trace of stackTraces.keys()) {
|
||||
sortedStackTraceIDs.push(trace);
|
||||
}
|
||||
sortedStackTraceIDs.sort((t1, t2) => {
|
||||
return t1.localeCompare(t2);
|
||||
});
|
||||
|
||||
// Walk through all traces. Increment the count of the root by the count of
|
||||
// that trace. Walk "down" the trace (through the callees) and add the count
|
||||
// of the trace to each callee.
|
||||
|
||||
for (const stackTraceID of sortedStackTraceIDs) {
|
||||
// The slice of frames is ordered so that the leaf function is at the
|
||||
// highest index.
|
||||
|
||||
// It is possible that we do not have a stacktrace for an event,
|
||||
// e.g. when stopping the host agent or on network errors.
|
||||
const stackTrace = stackTraces.get(stackTraceID) ?? emptyStackTrace;
|
||||
const lenStackTrace = stackTrace.FrameIDs.length;
|
||||
const samples = Math.floor((events.get(stackTraceID) ?? 0) * scalingFactor);
|
||||
totalSamples += samples;
|
||||
let currentNode = 0;
|
||||
|
||||
// Increment the count by the number of samples observed, multiplied with the inverse of the
|
||||
// samplingrate (this essentially means scaling up the total samples). It would incur
|
||||
tree.CountInclusive[currentNode] += samples;
|
||||
tree.CountExclusive[currentNode] = 0;
|
||||
|
||||
for (let i = 0; i < lenStackTrace; i++) {
|
||||
const frameID = stackTrace.FrameIDs[i];
|
||||
const fileID = stackTrace.FileIDs[i];
|
||||
const addressOrLine = stackTrace.AddressOrLines[i];
|
||||
const frame = stackFrames.get(frameID) ?? emptyStackFrame;
|
||||
const executable = executables.get(fileID) ?? emptyExecutable;
|
||||
|
||||
const frameGroupID = createFrameGroupID(
|
||||
fileID,
|
||||
addressOrLine,
|
||||
executable.FileName,
|
||||
frame.FileName,
|
||||
frame.FunctionName
|
||||
);
|
||||
|
||||
let node = tree.Edges[currentNode].get(frameGroupID);
|
||||
|
||||
if (node === undefined) {
|
||||
node = tree.Size;
|
||||
|
||||
tree.FileID[node] = fileID;
|
||||
tree.FrameType[node] = stackTrace.Types[i];
|
||||
tree.ExeFilename[node] = executable.FileName;
|
||||
tree.AddressOrLine[node] = addressOrLine;
|
||||
tree.FunctionName[node] = frame.FunctionName;
|
||||
tree.FunctionOffset[node] = frame.FunctionOffset;
|
||||
tree.SourceLine[node] = frame.LineNumber;
|
||||
tree.SourceFilename[node] = frame.FileName;
|
||||
tree.Inline[node] = frame.Inline;
|
||||
tree.CountInclusive[node] = samples;
|
||||
tree.CountExclusive[node] = 0;
|
||||
|
||||
tree.Edges[currentNode].set(frameGroupID, node);
|
||||
tree.Edges[node] = new Map<FrameGroupID, NodeID>();
|
||||
|
||||
tree.Size++;
|
||||
} else {
|
||||
tree.CountInclusive[node] += samples;
|
||||
}
|
||||
|
||||
if (i === lenStackTrace - 1) {
|
||||
// Leaf frame: sum up counts for exclusive CPU.
|
||||
tree.CountExclusive[node] += samples;
|
||||
}
|
||||
currentNode = node;
|
||||
}
|
||||
}
|
||||
const sumSelfCPU = sum(tree.CountExclusive);
|
||||
const sumTotalCPU = sum(tree.CountInclusive);
|
||||
|
||||
return {
|
||||
...tree,
|
||||
TotalSamples: totalSamples,
|
||||
SelfCPU: sumSelfCPU,
|
||||
TotalCPU: sumTotalCPU,
|
||||
};
|
||||
}
|
|
@ -6,49 +6,32 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createCalleeTree } from './callee';
|
||||
import { createBaseFlameGraph, createFlameGraph } from './flamegraph';
|
||||
import { decodeStackTraceResponse } from './stack_traces';
|
||||
import { stackTraceFixtures } from './__fixtures__/stacktraces';
|
||||
import { createFlameGraph } from './flamegraph';
|
||||
import { baseFlamegraph } from './__fixtures__/base_flamegraph';
|
||||
|
||||
describe('Flamegraph operations', () => {
|
||||
stackTraceFixtures.forEach(({ response, seconds, upsampledBy }) => {
|
||||
const { events, stackTraces, stackFrames, executables, totalFrames, samplingRate } =
|
||||
decodeStackTraceResponse(response);
|
||||
const tree = createCalleeTree(
|
||||
events,
|
||||
stackTraces,
|
||||
stackFrames,
|
||||
executables,
|
||||
totalFrames,
|
||||
samplingRate
|
||||
);
|
||||
const baseFlamegraph = createBaseFlameGraph(tree, samplingRate, seconds);
|
||||
const flamegraph = createFlameGraph(baseFlamegraph);
|
||||
describe('Flamegraph', () => {
|
||||
const flamegraph = createFlameGraph(baseFlamegraph);
|
||||
|
||||
describe(`stacktraces from ${seconds} seconds and upsampled by ${upsampledBy}`, () => {
|
||||
test('base flamegraph has non-zero total seconds', () => {
|
||||
expect(baseFlamegraph.TotalSeconds).toEqual(seconds);
|
||||
});
|
||||
it('base flamegraph has non-zero total seconds', () => {
|
||||
expect(baseFlamegraph.TotalSeconds).toEqual(4.980000019073486);
|
||||
});
|
||||
|
||||
test('base flamegraph has one more node than the number of edges', () => {
|
||||
const numEdges = baseFlamegraph.Edges.flatMap((edge) => edge).length;
|
||||
it('base flamegraph has one more node than the number of edges', () => {
|
||||
const numEdges = baseFlamegraph.Edges.flatMap((edge) => edge).length;
|
||||
|
||||
expect(numEdges).toEqual(baseFlamegraph.Size - 1);
|
||||
});
|
||||
expect(numEdges).toEqual(baseFlamegraph.Size - 1);
|
||||
});
|
||||
|
||||
test('all flamegraph IDs are the same non-zero length', () => {
|
||||
// 16 is the length of a 64-bit FNV-1a hash encoded to a hex string
|
||||
const allSameLengthIDs = flamegraph.ID.every((id) => id.length === 16);
|
||||
it('all flamegraph IDs are the same non-zero length', () => {
|
||||
// 16 is the length of a 64-bit FNV-1a hash encoded to a hex string
|
||||
const allSameLengthIDs = flamegraph.ID.every((id) => id.length === 16);
|
||||
|
||||
expect(allSameLengthIDs).toBeTruthy();
|
||||
});
|
||||
expect(allSameLengthIDs).toBeTruthy();
|
||||
});
|
||||
|
||||
test('all flamegraph labels are non-empty', () => {
|
||||
const allNonEmptyLabels = flamegraph.Label.every((id) => id.length > 0);
|
||||
it('all flamegraph labels are non-empty', () => {
|
||||
const allNonEmptyLabels = flamegraph.Label.every((id) => id.length > 0);
|
||||
|
||||
expect(allNonEmptyLabels).toBeTruthy();
|
||||
});
|
||||
});
|
||||
expect(allNonEmptyLabels).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CalleeTree } from './callee';
|
||||
import { createFrameGroupID } from './frame_group';
|
||||
import { fnv1a64 } from './hash';
|
||||
import { createStackFrameMetadata, getCalleeLabel } from './profiling';
|
||||
import { convertTonsToKgs } from './utils';
|
||||
|
||||
/**
|
||||
* Base Flamegraph
|
||||
|
@ -48,63 +48,37 @@ export interface BaseFlameGraph {
|
|||
TotalSamples: number;
|
||||
TotalCPU: number;
|
||||
SelfCPU: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* createBaseFlameGraph encapsulates the tree representation into a serialized form.
|
||||
* @param tree CalleeTree
|
||||
* @param samplingRate number
|
||||
* @param totalSeconds number
|
||||
* @returns BaseFlameGraph
|
||||
*/
|
||||
export function createBaseFlameGraph(
|
||||
tree: CalleeTree,
|
||||
samplingRate: number,
|
||||
totalSeconds: number
|
||||
): BaseFlameGraph {
|
||||
const graph: BaseFlameGraph = {
|
||||
Size: tree.Size,
|
||||
SamplingRate: samplingRate,
|
||||
Edges: new Array<number[]>(tree.Size),
|
||||
|
||||
FileID: tree.FileID.slice(0, tree.Size),
|
||||
FrameType: tree.FrameType.slice(0, tree.Size),
|
||||
Inline: tree.Inline.slice(0, tree.Size),
|
||||
ExeFilename: tree.ExeFilename.slice(0, tree.Size),
|
||||
AddressOrLine: tree.AddressOrLine.slice(0, tree.Size),
|
||||
FunctionName: tree.FunctionName.slice(0, tree.Size),
|
||||
FunctionOffset: tree.FunctionOffset.slice(0, tree.Size),
|
||||
SourceFilename: tree.SourceFilename.slice(0, tree.Size),
|
||||
SourceLine: tree.SourceLine.slice(0, tree.Size),
|
||||
|
||||
CountInclusive: tree.CountInclusive.slice(0, tree.Size),
|
||||
CountExclusive: tree.CountExclusive.slice(0, tree.Size),
|
||||
|
||||
TotalSeconds: totalSeconds,
|
||||
TotalSamples: tree.TotalSamples,
|
||||
SelfCPU: tree.SelfCPU,
|
||||
TotalCPU: tree.TotalCPU,
|
||||
};
|
||||
|
||||
for (let i = 0; i < tree.Size; i++) {
|
||||
let j = 0;
|
||||
const nodes = new Array<number>(tree.Edges[i].size);
|
||||
for (const [, n] of tree.Edges[i]) {
|
||||
nodes[j] = n;
|
||||
j++;
|
||||
}
|
||||
graph.Edges[i] = nodes;
|
||||
}
|
||||
|
||||
return graph;
|
||||
AnnualCO2TonsExclusive: number[];
|
||||
AnnualCO2TonsInclusive: number[];
|
||||
AnnualCostsUSDInclusive: number[];
|
||||
AnnualCostsUSDExclusive: number[];
|
||||
SelfAnnualCO2Tons: number;
|
||||
TotalAnnualCO2Tons: number;
|
||||
SelfAnnualCostsUSD: number;
|
||||
TotalAnnualCostsUSD: number;
|
||||
}
|
||||
|
||||
/** Elasticsearch flamegraph */
|
||||
export interface ElasticFlameGraph extends BaseFlameGraph {
|
||||
export interface ElasticFlameGraph
|
||||
extends Omit<
|
||||
BaseFlameGraph,
|
||||
| 'AnnualCO2TonsExclusive'
|
||||
| 'AnnualCO2TonsInclusive'
|
||||
| 'SelfAnnualCO2Tons'
|
||||
| 'TotalAnnualCO2Tons'
|
||||
| 'AnnualCostsUSDInclusive'
|
||||
| 'AnnualCostsUSDExclusive'
|
||||
> {
|
||||
/** ID */
|
||||
ID: string[];
|
||||
/** Label */
|
||||
Label: string[];
|
||||
SelfAnnualCO2KgsItems: number[];
|
||||
TotalAnnualCO2KgsItems: number[];
|
||||
SelfAnnualCostsUSDItems: number[];
|
||||
TotalAnnualCostsUSDItems: number[];
|
||||
SelfAnnualCO2Kgs: number;
|
||||
TotalAnnualCO2Kgs: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,6 +115,14 @@ export function createFlameGraph(base: BaseFlameGraph): ElasticFlameGraph {
|
|||
TotalSamples: base.TotalSamples,
|
||||
SelfCPU: base.SelfCPU,
|
||||
TotalCPU: base.TotalCPU,
|
||||
SelfAnnualCO2KgsItems: base.AnnualCO2TonsExclusive.map(convertTonsToKgs),
|
||||
TotalAnnualCO2KgsItems: base.AnnualCO2TonsInclusive.map(convertTonsToKgs),
|
||||
SelfAnnualCostsUSDItems: base.AnnualCostsUSDExclusive,
|
||||
TotalAnnualCostsUSDItems: base.AnnualCostsUSDInclusive,
|
||||
SelfAnnualCO2Kgs: convertTonsToKgs(base.SelfAnnualCO2Tons),
|
||||
TotalAnnualCO2Kgs: convertTonsToKgs(base.TotalAnnualCO2Tons),
|
||||
SelfAnnualCostsUSD: base.SelfAnnualCostsUSD,
|
||||
TotalAnnualCostsUSD: base.TotalAnnualCostsUSD,
|
||||
};
|
||||
|
||||
const rootFrameGroupID = createFrameGroupID(
|
||||
|
|
|
@ -9,49 +9,44 @@
|
|||
import { sum } from 'lodash';
|
||||
import { createTopNFunctions } from './functions';
|
||||
import { decodeStackTraceResponse } from '..';
|
||||
import { stackTraceFixtures } from './__fixtures__/stacktraces';
|
||||
import { stacktraces } from './__fixtures__/stacktraces';
|
||||
|
||||
describe('TopN function operations', () => {
|
||||
stackTraceFixtures.forEach(({ response, seconds, upsampledBy }) => {
|
||||
const { events, stackTraces, stackFrames, executables, samplingRate } =
|
||||
decodeStackTraceResponse(response);
|
||||
const { events, stackTraces, stackFrames, executables, samplingRate } =
|
||||
decodeStackTraceResponse(stacktraces);
|
||||
const maxTopN = 5;
|
||||
const topNFunctions = createTopNFunctions({
|
||||
events,
|
||||
stackTraces,
|
||||
stackFrames,
|
||||
executables,
|
||||
startIndex: 0,
|
||||
endIndex: maxTopN,
|
||||
samplingRate,
|
||||
});
|
||||
const exclusiveCounts = topNFunctions.TopN.map((value) => value.CountExclusive);
|
||||
|
||||
describe(`stacktraces upsampled by ${upsampledBy}`, () => {
|
||||
const maxTopN = 5;
|
||||
const topNFunctions = createTopNFunctions({
|
||||
events,
|
||||
stackTraces,
|
||||
stackFrames,
|
||||
executables,
|
||||
startIndex: 0,
|
||||
endIndex: maxTopN,
|
||||
samplingRate,
|
||||
});
|
||||
const exclusiveCounts = topNFunctions.TopN.map((value) => value.CountExclusive);
|
||||
it('samples are less than or equal to original upsampled samples', () => {
|
||||
const totalUpsampledSamples = Math.ceil(sum([...events.values()]) / samplingRate);
|
||||
expect(topNFunctions.TotalCount).toBeLessThanOrEqual(totalUpsampledSamples);
|
||||
});
|
||||
|
||||
test('samples are less than or equal to original upsampled samples', () => {
|
||||
const totalUpsampledSamples = Math.ceil(sum([...events.values()]) / samplingRate);
|
||||
expect(topNFunctions.TotalCount).toBeLessThanOrEqual(totalUpsampledSamples);
|
||||
});
|
||||
it('number of functions is equal to maximum', () => {
|
||||
expect(topNFunctions.TopN.length).toEqual(maxTopN);
|
||||
});
|
||||
|
||||
test('number of functions is equal to maximum', () => {
|
||||
expect(topNFunctions.TopN.length).toEqual(maxTopN);
|
||||
});
|
||||
it('all exclusive counts are numeric', () => {
|
||||
expect(typeof exclusiveCounts[0]).toBe('number');
|
||||
expect(typeof exclusiveCounts[1]).toBe('number');
|
||||
expect(typeof exclusiveCounts[2]).toBe('number');
|
||||
expect(typeof exclusiveCounts[3]).toBe('number');
|
||||
expect(typeof exclusiveCounts[4]).toBe('number');
|
||||
});
|
||||
|
||||
test('all exclusive counts are numeric', () => {
|
||||
expect(typeof exclusiveCounts[0]).toBe('number');
|
||||
expect(typeof exclusiveCounts[1]).toBe('number');
|
||||
expect(typeof exclusiveCounts[2]).toBe('number');
|
||||
expect(typeof exclusiveCounts[3]).toBe('number');
|
||||
expect(typeof exclusiveCounts[4]).toBe('number');
|
||||
});
|
||||
|
||||
test('exclusive counts are sorted from highest to lowest', () => {
|
||||
expect(exclusiveCounts[0]).toBeGreaterThanOrEqual(exclusiveCounts[1]);
|
||||
expect(exclusiveCounts[1]).toBeGreaterThanOrEqual(exclusiveCounts[2]);
|
||||
expect(exclusiveCounts[2]).toBeGreaterThanOrEqual(exclusiveCounts[3]);
|
||||
expect(exclusiveCounts[3]).toBeGreaterThanOrEqual(exclusiveCounts[4]);
|
||||
});
|
||||
});
|
||||
it('exclusive counts are sorted from highest to lowest', () => {
|
||||
expect(exclusiveCounts[0]).toBeGreaterThanOrEqual(exclusiveCounts[1]);
|
||||
expect(exclusiveCounts[1]).toBeGreaterThanOrEqual(exclusiveCounts[2]);
|
||||
expect(exclusiveCounts[2]).toBeGreaterThanOrEqual(exclusiveCounts[3]);
|
||||
expect(exclusiveCounts[3]).toBeGreaterThanOrEqual(exclusiveCounts[4]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,11 +30,21 @@ interface TopNFunctionAndFrameGroup {
|
|||
FrameGroupID: FrameGroupID;
|
||||
CountExclusive: number;
|
||||
CountInclusive: number;
|
||||
selfAnnualCO2kgs: number;
|
||||
selfAnnualCostUSD: number;
|
||||
totalAnnualCO2kgs: number;
|
||||
totalAnnualCostUSD: number;
|
||||
}
|
||||
|
||||
type TopNFunction = Pick<
|
||||
TopNFunctionAndFrameGroup,
|
||||
'Frame' | 'CountExclusive' | 'CountInclusive'
|
||||
| 'Frame'
|
||||
| 'CountExclusive'
|
||||
| 'CountInclusive'
|
||||
| 'selfAnnualCO2kgs'
|
||||
| 'selfAnnualCostUSD'
|
||||
| 'totalAnnualCO2kgs'
|
||||
| 'totalAnnualCostUSD'
|
||||
> & {
|
||||
Id: string;
|
||||
Rank: number;
|
||||
|
@ -46,6 +56,8 @@ export interface TopNFunctions {
|
|||
SamplingRate: number;
|
||||
selfCPU: number;
|
||||
totalCPU: number;
|
||||
totalAnnualCO2Kgs: number;
|
||||
totalAnnualCostUSD: number;
|
||||
}
|
||||
|
||||
export function createTopNFunctions({
|
||||
|
@ -84,6 +96,9 @@ export function createTopNFunctions({
|
|||
// It is possible that we do not have a stacktrace for an event,
|
||||
// e.g. when stopping the host agent or on network errors.
|
||||
const stackTrace = stackTraces.get(stackTraceID) ?? emptyStackTrace;
|
||||
const selfAnnualCO2kgs = stackTrace.selfAnnualCO2Kgs;
|
||||
const selfAnnualCostUSD = stackTrace.selfAnnualCostUSD;
|
||||
|
||||
const lenStackTrace = stackTrace.FrameIDs.length;
|
||||
|
||||
for (let i = 0; i < lenStackTrace; i++) {
|
||||
|
@ -122,6 +137,10 @@ export function createTopNFunctions({
|
|||
FrameGroupID: frameGroupID,
|
||||
CountExclusive: 0,
|
||||
CountInclusive: 0,
|
||||
selfAnnualCO2kgs: 0,
|
||||
totalAnnualCO2kgs: 0,
|
||||
selfAnnualCostUSD: 0,
|
||||
totalAnnualCostUSD: 0,
|
||||
};
|
||||
|
||||
topNFunctions.set(frameGroupID, topNFunction);
|
||||
|
@ -130,11 +149,15 @@ export function createTopNFunctions({
|
|||
if (!uniqueFrameGroupsPerEvent.has(frameGroupID)) {
|
||||
uniqueFrameGroupsPerEvent.add(frameGroupID);
|
||||
topNFunction.CountInclusive += scaledCount;
|
||||
topNFunction.totalAnnualCO2kgs += selfAnnualCO2kgs;
|
||||
topNFunction.totalAnnualCostUSD += selfAnnualCostUSD;
|
||||
}
|
||||
|
||||
if (i === lenStackTrace - 1) {
|
||||
// Leaf frame: sum up counts for exclusive CPU.
|
||||
topNFunction.CountExclusive += scaledCount;
|
||||
topNFunction.selfAnnualCO2kgs += selfAnnualCO2kgs;
|
||||
topNFunction.selfAnnualCostUSD += selfAnnualCostUSD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,21 +184,29 @@ export function createTopNFunctions({
|
|||
endIndex = topN.length;
|
||||
}
|
||||
|
||||
const framesAndCountsAndIds = topN.slice(startIndex, endIndex).map((frameAndCount, i) => {
|
||||
const countExclusive = frameAndCount.CountExclusive;
|
||||
const countInclusive = frameAndCount.CountInclusive;
|
||||
const framesAndCountsAndIds = topN
|
||||
.slice(startIndex, endIndex)
|
||||
.map((frameAndCount, i): TopNFunction => {
|
||||
const countExclusive = frameAndCount.CountExclusive;
|
||||
const countInclusive = frameAndCount.CountInclusive;
|
||||
|
||||
return {
|
||||
Rank: i + 1,
|
||||
Frame: frameAndCount.Frame,
|
||||
CountExclusive: countExclusive,
|
||||
CountInclusive: countInclusive,
|
||||
Id: frameAndCount.FrameGroupID,
|
||||
};
|
||||
});
|
||||
return {
|
||||
Rank: i + 1,
|
||||
Frame: frameAndCount.Frame,
|
||||
CountExclusive: countExclusive,
|
||||
CountInclusive: countInclusive,
|
||||
Id: frameAndCount.FrameGroupID,
|
||||
selfAnnualCO2kgs: frameAndCount.selfAnnualCO2kgs,
|
||||
selfAnnualCostUSD: frameAndCount.selfAnnualCostUSD,
|
||||
totalAnnualCO2kgs: frameAndCount.totalAnnualCO2kgs,
|
||||
totalAnnualCostUSD: frameAndCount.totalAnnualCostUSD,
|
||||
};
|
||||
});
|
||||
|
||||
const sumSelfCPU = sumBy(framesAndCountsAndIds, 'CountExclusive');
|
||||
const sumTotalCPU = sumBy(framesAndCountsAndIds, 'CountInclusive');
|
||||
const totalAnnualCO2Kgs = sumBy(framesAndCountsAndIds, 'totalAnnualCO2kgs');
|
||||
const totalAnnualCostUSD = sumBy(framesAndCountsAndIds, 'totalAnnualCostUSD');
|
||||
|
||||
return {
|
||||
TotalCount: totalCount,
|
||||
|
@ -183,6 +214,8 @@ export function createTopNFunctions({
|
|||
SamplingRate: samplingRate,
|
||||
selfCPU: sumSelfCPU,
|
||||
totalCPU: sumTotalCPU,
|
||||
totalAnnualCO2Kgs,
|
||||
totalAnnualCostUSD,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,9 @@ export interface StackTrace {
|
|||
AddressOrLines: number[];
|
||||
/** types */
|
||||
Types: number[];
|
||||
selfAnnualCO2Kgs: number;
|
||||
selfAnnualCostUSD: number;
|
||||
Count: number;
|
||||
}
|
||||
/**
|
||||
* Empty stack trace
|
||||
|
@ -87,6 +90,9 @@ export const emptyStackTrace: StackTrace = {
|
|||
AddressOrLines: [],
|
||||
/** Types */
|
||||
Types: [],
|
||||
selfAnnualCO2Kgs: 0,
|
||||
selfAnnualCostUSD: 0,
|
||||
Count: 0,
|
||||
};
|
||||
|
||||
/** Stack frame */
|
||||
|
|
|
@ -58,6 +58,9 @@ describe('Stack trace response operations', () => {
|
|||
frame_ids: ['abc123', 'def456'],
|
||||
address_or_lines: [123, 456],
|
||||
type_ids: [0, 1],
|
||||
count: 3,
|
||||
annual_co2_tons: 1,
|
||||
annual_costs_usd: 1,
|
||||
},
|
||||
},
|
||||
stack_frames: {
|
||||
|
@ -92,6 +95,9 @@ describe('Stack trace response operations', () => {
|
|||
FrameIDs: ['abc123', makeFrameID('def456', 0), makeFrameID('def456', 1)],
|
||||
AddressOrLines: [123, 456, 456],
|
||||
Types: [0, 1, 1],
|
||||
Count: 3,
|
||||
selfAnnualCO2Kgs: 1,
|
||||
selfAnnualCostUSD: 1,
|
||||
},
|
||||
],
|
||||
]),
|
||||
|
@ -165,6 +171,9 @@ describe('Stack trace response operations', () => {
|
|||
frame_ids: ['abc123'],
|
||||
address_or_lines: [123],
|
||||
type_ids: [0],
|
||||
count: 3,
|
||||
annual_co2_tons: 1,
|
||||
annual_costs_usd: 1,
|
||||
},
|
||||
},
|
||||
stack_frames: {
|
||||
|
@ -191,6 +200,9 @@ describe('Stack trace response operations', () => {
|
|||
FrameIDs: ['abc123'],
|
||||
AddressOrLines: [123],
|
||||
Types: [0],
|
||||
Count: 3,
|
||||
selfAnnualCO2Kgs: 1,
|
||||
selfAnnualCostUSD: 1,
|
||||
},
|
||||
],
|
||||
]),
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
StackTrace,
|
||||
StackTraceID,
|
||||
} from './profiling';
|
||||
import { convertTonsToKgs } from './utils';
|
||||
|
||||
/** Profiling status response */
|
||||
export interface ProfilingStatusResponse {
|
||||
|
@ -42,6 +43,9 @@ export interface ProfilingStackTrace {
|
|||
['frame_ids']: string[];
|
||||
['address_or_lines']: number[];
|
||||
['type_ids']: number[];
|
||||
['annual_co2_tons']: number;
|
||||
['annual_costs_usd']: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface ProfilingStackTraces {
|
||||
|
@ -140,7 +144,10 @@ const createInlineTrace = (
|
|||
FileIDs: fileIDs,
|
||||
AddressOrLines: addressOrLines,
|
||||
Types: typeIDs,
|
||||
} as StackTrace;
|
||||
selfAnnualCO2Kgs: convertTonsToKgs(trace.annual_co2_tons),
|
||||
selfAnnualCostUSD: trace.annual_costs_usd,
|
||||
Count: trace.count,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
8
packages/kbn-profiling-utils/common/utils.ts
Normal file
8
packages/kbn-profiling-utils/common/utils.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
export const convertTonsToKgs = (value: number) => value * 1000;
|
|
@ -7,8 +7,7 @@
|
|||
*/
|
||||
|
||||
export { decodeStackTraceResponse } from './common/stack_traces';
|
||||
export { createBaseFlameGraph, createFlameGraph } from './common/flamegraph';
|
||||
export { createCalleeTree } from './common/callee';
|
||||
export { createFlameGraph } from './common/flamegraph';
|
||||
export { ProfilingESField } from './common/elasticsearch';
|
||||
export {
|
||||
groupStackFrameMetadataByStackTrace,
|
||||
|
@ -33,8 +32,8 @@ export {
|
|||
TopNComparisonFunctionSortField,
|
||||
topNComparisonFunctionSortFieldRt,
|
||||
} from './common/functions';
|
||||
export { convertTonsToKgs } from './common/utils';
|
||||
|
||||
export type { CalleeTree } from './common/callee';
|
||||
export type {
|
||||
ProfilingStatusResponse,
|
||||
StackTraceResponse,
|
||||
|
|
|
@ -553,7 +553,11 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
|
|||
type: 'boolean',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
'observability:profilingPerCoreWatt': {
|
||||
'observability:profilingPerVCPUWattX86': {
|
||||
type: 'integer',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
'observability:profilingPervCPUWattArm64': {
|
||||
type: 'integer',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
|
@ -589,8 +593,16 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
|
|||
type: 'boolean',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
'observability:profilingUseLegacyFlamegraphAPI': {
|
||||
'observability:profilingUseLegacyCo2Calculation': {
|
||||
type: 'boolean',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
'observability:profilingCostPervCPUPerHour': {
|
||||
type: 'integer',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
'observability:profilingAWSCostDiscountRate': {
|
||||
type: 'integer',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
};
|
||||
|
|
|
@ -154,8 +154,11 @@ export interface UsageStats {
|
|||
'securitySolution:enableGroupedNav': boolean;
|
||||
'securitySolution:showRelatedIntegrations': boolean;
|
||||
'visualization:visualize:legacyGaugeChartsLibrary': boolean;
|
||||
'observability:profilingUseLegacyFlamegraphAPI': boolean;
|
||||
'observability:profilingPerCoreWatt': number;
|
||||
'observability:profilingPerVCPUWattX86': number;
|
||||
'observability:profilingPervCPUWattArm64': number;
|
||||
'observability:profilingCo2PerKWH': number;
|
||||
'observability:profilingDatacenterPUE': number;
|
||||
'observability:profilingUseLegacyCo2Calculation': boolean;
|
||||
'observability:profilingCostPervCPUPerHour': number;
|
||||
'observability:profilingAWSCostDiscountRate': number;
|
||||
}
|
||||
|
|
|
@ -10019,7 +10019,13 @@
|
|||
"description": "Non-default value of setting."
|
||||
}
|
||||
},
|
||||
"observability:profilingPerCoreWatt": {
|
||||
"observability:profilingPerVCPUWattX86": {
|
||||
"type": "integer",
|
||||
"_meta": {
|
||||
"description": "Non-default value of setting."
|
||||
}
|
||||
},
|
||||
"observability:profilingPervCPUWattArm64": {
|
||||
"type": "integer",
|
||||
"_meta": {
|
||||
"description": "Non-default value of setting."
|
||||
|
@ -10073,11 +10079,23 @@
|
|||
"description": "Non-default value of setting."
|
||||
}
|
||||
},
|
||||
"observability:profilingUseLegacyFlamegraphAPI": {
|
||||
"observability:profilingUseLegacyCo2Calculation": {
|
||||
"type": "boolean",
|
||||
"_meta": {
|
||||
"description": "Non-default value of setting."
|
||||
}
|
||||
},
|
||||
"observability:profilingCostPervCPUPerHour": {
|
||||
"type": "integer",
|
||||
"_meta": {
|
||||
"description": "Non-default value of setting."
|
||||
}
|
||||
},
|
||||
"observability:profilingAWSCostDiscountRate": {
|
||||
"type": "integer",
|
||||
"_meta": {
|
||||
"description": "Non-default value of setting."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import { toNumberRt } from '@kbn/io-ts-utils';
|
||||
import type { BaseFlameGraph, TopNFunctions } from '@kbn/profiling-utils';
|
||||
import * as t from 'io-ts';
|
||||
import { profilingUseLegacyFlamegraphAPI } from '@kbn/observability-plugin/common';
|
||||
import { HOST_NAME } from '../../../common/es_fields/apm';
|
||||
import {
|
||||
mergeKueries,
|
||||
|
@ -42,13 +41,10 @@ const profilingFlamegraphRoute = createApmServerRoute({
|
|||
{ flamegraph: BaseFlameGraph; hostNames: string[] } | undefined
|
||||
> => {
|
||||
const { context, plugins, params } = resources;
|
||||
const useLegacyFlamegraphAPI = await (
|
||||
await context.core
|
||||
).uiSettings.client.get<boolean>(profilingUseLegacyFlamegraphAPI);
|
||||
|
||||
const core = await context.core;
|
||||
const [esClient, apmEventClient, profilingDataAccessStart] =
|
||||
await Promise.all([
|
||||
(await context.core).elasticsearch.client,
|
||||
core.elasticsearch.client,
|
||||
await getApmEventClient(resources),
|
||||
await plugins.profilingDataAccess?.start(),
|
||||
]);
|
||||
|
@ -73,6 +69,7 @@ const profilingFlamegraphRoute = createApmServerRoute({
|
|||
|
||||
const flamegraph =
|
||||
await profilingDataAccessStart?.services.fetchFlamechartData({
|
||||
core,
|
||||
esClient: esClient.asCurrentUser,
|
||||
rangeFromMs: start,
|
||||
rangeToMs: end,
|
||||
|
@ -80,7 +77,6 @@ const profilingFlamegraphRoute = createApmServerRoute({
|
|||
`(${toKueryFilterFormat(HOST_NAME, serviceHostNames)})`,
|
||||
kuery,
|
||||
]),
|
||||
useLegacyFlamegraphAPI,
|
||||
});
|
||||
|
||||
return { flamegraph, hostNames: serviceHostNames };
|
||||
|
@ -107,9 +103,10 @@ const profilingFunctionsRoute = createApmServerRoute({
|
|||
resources
|
||||
): Promise<{ functions: TopNFunctions; hostNames: string[] } | undefined> => {
|
||||
const { context, plugins, params } = resources;
|
||||
const core = await context.core;
|
||||
const [esClient, apmEventClient, profilingDataAccessStart] =
|
||||
await Promise.all([
|
||||
(await context.core).elasticsearch.client,
|
||||
core.elasticsearch.client,
|
||||
await getApmEventClient(resources),
|
||||
await plugins.profilingDataAccess?.start(),
|
||||
]);
|
||||
|
@ -141,6 +138,7 @@ const profilingFunctionsRoute = createApmServerRoute({
|
|||
}
|
||||
|
||||
const functions = await profilingDataAccessStart?.services.fetchFunction({
|
||||
core,
|
||||
esClient: esClient.asCurrentUser,
|
||||
rangeFromMs: start,
|
||||
rangeToMs: end,
|
||||
|
|
|
@ -8,24 +8,19 @@
|
|||
import type { CoreRequestHandlerContext } from '@kbn/core-http-request-handler-context-server';
|
||||
import type { ProfilingDataAccessPluginStart } from '@kbn/profiling-data-access-plugin/server';
|
||||
import type { BaseFlameGraph } from '@kbn/profiling-utils';
|
||||
import { profilingUseLegacyFlamegraphAPI } from '@kbn/observability-plugin/common';
|
||||
import type { InfraProfilingFlamegraphRequestParams } from '../../../../common/http_api/profiling_api';
|
||||
import { HOST_FIELD } from '../../../../common/constants';
|
||||
import type { InfraProfilingFlamegraphRequestParams } from '../../../../common/http_api/profiling_api';
|
||||
|
||||
export async function fetchProfilingFlamegraph(
|
||||
{ hostname, from, to }: InfraProfilingFlamegraphRequestParams,
|
||||
profilingDataAccess: ProfilingDataAccessPluginStart,
|
||||
coreRequestContext: CoreRequestHandlerContext
|
||||
): Promise<BaseFlameGraph> {
|
||||
const useLegacyFlamegraphAPI = await coreRequestContext.uiSettings.client.get<boolean>(
|
||||
profilingUseLegacyFlamegraphAPI
|
||||
);
|
||||
|
||||
return await profilingDataAccess.services.fetchFlamechartData({
|
||||
core: coreRequestContext,
|
||||
esClient: coreRequestContext.elasticsearch.client.asCurrentUser,
|
||||
rangeFromMs: from,
|
||||
rangeToMs: to,
|
||||
kuery: `${HOST_FIELD} : "${hostname}"`,
|
||||
useLegacyFlamegraphAPI,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ export async function fetchProfilingFunctions(
|
|||
const { hostname, from, to, startIndex, endIndex } = params;
|
||||
|
||||
return await profilingDataAccess.services.fetchFunction({
|
||||
core: coreRequestContext,
|
||||
esClient: coreRequestContext.elasticsearch.client.asCurrentUser,
|
||||
rangeFromMs: from,
|
||||
rangeToMs: to,
|
||||
|
|
|
@ -41,10 +41,13 @@ export {
|
|||
enableCriticalPath,
|
||||
syntheticsThrottlingEnabled,
|
||||
apmEnableProfilingIntegration,
|
||||
profilingUseLegacyFlamegraphAPI,
|
||||
profilingCo2PerKWH,
|
||||
profilingDatacenterPUE,
|
||||
profilingPerCoreWatt,
|
||||
profilingPervCPUWattX86,
|
||||
profilingUseLegacyCo2Calculation,
|
||||
profilingPervCPUWattArm64,
|
||||
profilingAWSCostDiscountRate,
|
||||
profilingCostPervCPUPerHour,
|
||||
} from './ui_settings_keys';
|
||||
|
||||
export {
|
||||
|
|
|
@ -27,7 +27,10 @@ export const apmEnableContinuousRollups = 'observability:apmEnableContinuousRoll
|
|||
export const syntheticsThrottlingEnabled = 'observability:syntheticsThrottlingEnabled';
|
||||
export const enableLegacyUptimeApp = 'observability:enableLegacyUptimeApp';
|
||||
export const apmEnableProfilingIntegration = 'observability:apmEnableProfilingIntegration';
|
||||
export const profilingUseLegacyFlamegraphAPI = 'observability:profilingUseLegacyFlamegraphAPI';
|
||||
export const profilingPerCoreWatt = 'observability:profilingPerCoreWatt';
|
||||
export const profilingPervCPUWattX86 = 'observability:profilingPerVCPUWattX86';
|
||||
export const profilingPervCPUWattArm64 = 'observability:profilingPervCPUWattArm64';
|
||||
export const profilingCo2PerKWH = 'observability:profilingCo2PerKWH';
|
||||
export const profilingDatacenterPUE = 'observability:profilingDatacenterPUE';
|
||||
export const profilingUseLegacyCo2Calculation = 'observability:profilingUseLegacyCo2Calculation';
|
||||
export const profilingAWSCostDiscountRate = 'observability:profilingAWSCostDiscountRate';
|
||||
export const profilingCostPervCPUPerHour = 'observability:profilingCostPervCPUPerHour';
|
||||
|
|
|
@ -30,10 +30,13 @@ import {
|
|||
syntheticsThrottlingEnabled,
|
||||
enableLegacyUptimeApp,
|
||||
apmEnableProfilingIntegration,
|
||||
profilingUseLegacyFlamegraphAPI,
|
||||
profilingCo2PerKWH,
|
||||
profilingDatacenterPUE,
|
||||
profilingPerCoreWatt,
|
||||
profilingPervCPUWattX86,
|
||||
profilingUseLegacyCo2Calculation,
|
||||
profilingPervCPUWattArm64,
|
||||
profilingAWSCostDiscountRate,
|
||||
profilingCostPervCPUPerHour,
|
||||
} from '../common/ui_settings_keys';
|
||||
|
||||
const betaLabel = i18n.translate('xpack.observability.uiSettings.betaLabel', {
|
||||
|
@ -378,26 +381,33 @@ export const uiSettings: Record<string, UiSettings> = {
|
|||
schema: schema.boolean(),
|
||||
requiresPageReload: false,
|
||||
},
|
||||
[profilingUseLegacyFlamegraphAPI]: {
|
||||
[profilingPervCPUWattX86]: {
|
||||
category: [observabilityFeatureId],
|
||||
name: i18n.translate('xpack.observability.profilingUseLegacyFlamegraphAPI', {
|
||||
defaultMessage: 'Use legacy Flamegraph API in Universal Profiling',
|
||||
}),
|
||||
value: false,
|
||||
schema: schema.boolean(),
|
||||
},
|
||||
[profilingPerCoreWatt]: {
|
||||
category: [observabilityFeatureId],
|
||||
name: i18n.translate('xpack.observability.profilingPerCoreWattUiSettingName', {
|
||||
defaultMessage: 'Per Core Watts',
|
||||
name: i18n.translate('xpack.observability.profilingPervCPUWattX86UiSettingName', {
|
||||
defaultMessage: 'Per vCPU Watts - x86',
|
||||
}),
|
||||
value: 7,
|
||||
description: i18n.translate('xpack.observability.profilingPerCoreWattUiSettingDescription', {
|
||||
defaultMessage: `The average amortized per-core power consumption (based on 100% CPU utilization).`,
|
||||
description: i18n.translate('xpack.observability.profilingPervCPUWattX86UiSettingDescription', {
|
||||
defaultMessage: `The average amortized per-core power consumption (based on 100% CPU utilization) for x86 architecture.`,
|
||||
}),
|
||||
schema: schema.number({ min: 0 }),
|
||||
requiresPageReload: true,
|
||||
},
|
||||
[profilingPervCPUWattArm64]: {
|
||||
category: [observabilityFeatureId],
|
||||
name: i18n.translate('xpack.observability.profilingPervCPUWattArm64UiSettingName', {
|
||||
defaultMessage: 'Per vCPU Watts - arm64',
|
||||
}),
|
||||
value: 2.8,
|
||||
description: i18n.translate(
|
||||
'xpack.observability.profilingPervCPUWattArm64UiSettingDescription',
|
||||
{
|
||||
defaultMessage: `The average amortized per-core power consumption (based on 100% CPU utilization) for arm64 architecture.`,
|
||||
}
|
||||
),
|
||||
schema: schema.number({ min: 0 }),
|
||||
requiresPageReload: true,
|
||||
},
|
||||
[profilingDatacenterPUE]: {
|
||||
category: [observabilityFeatureId],
|
||||
name: i18n.translate('xpack.observability.profilingDatacenterPUEUiSettingName', {
|
||||
|
@ -450,6 +460,45 @@ export const uiSettings: Record<string, UiSettings> = {
|
|||
schema: schema.number({ min: 0 }),
|
||||
requiresPageReload: true,
|
||||
},
|
||||
[profilingUseLegacyCo2Calculation]: {
|
||||
category: [observabilityFeatureId],
|
||||
name: i18n.translate('xpack.observability.profilingUseLegacyCo2Calculation', {
|
||||
defaultMessage: 'Use legacy CO2 and Dollar cost calculations in Universal Profiling',
|
||||
}),
|
||||
value: false,
|
||||
schema: schema.boolean(),
|
||||
},
|
||||
[profilingAWSCostDiscountRate]: {
|
||||
category: [observabilityFeatureId],
|
||||
name: i18n.translate('xpack.observability.profilingAWSCostDiscountRateUiSettingName', {
|
||||
defaultMessage: 'AWS EDP discount rate (%)',
|
||||
}),
|
||||
value: 6,
|
||||
schema: schema.number({ min: 0, max: 100 }),
|
||||
requiresPageReload: true,
|
||||
description: i18n.translate(
|
||||
'xpack.observability.profilingAWSCostDiscountRateUiSettingDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
"If you're enrolled in the AWS Enterprise Discount Program (EDP), enter your discount rate to update the profiling cost calculation.",
|
||||
}
|
||||
),
|
||||
},
|
||||
[profilingCostPervCPUPerHour]: {
|
||||
category: [observabilityFeatureId],
|
||||
name: i18n.translate('xpack.observability.profilingCostPervCPUPerHourUiSettingName', {
|
||||
defaultMessage: 'Cost per vCPU per hour ($)',
|
||||
}),
|
||||
value: 0.0425,
|
||||
description: i18n.translate(
|
||||
'xpack.observability.profilingCostPervCPUPerHourUiSettingNameDescription',
|
||||
{
|
||||
defaultMessage: `Default average cost per CPU core per hour (Non-AWS instances only)`,
|
||||
}
|
||||
),
|
||||
schema: schema.number({ min: 0, max: 100 }),
|
||||
requiresPageReload: true,
|
||||
},
|
||||
};
|
||||
|
||||
function throttlingDocsLink({ href }: { href: string }) {
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
The stacktrace fixtures in this directory are originally from Elasticsearch's
|
||||
`POST /_profiling/stacktraces` endpoint. They were subsequently filtered
|
||||
through the `shrink_stacktrace_response.js` command in `x-pack/plugins/profiling/scripts/`
|
||||
to reduce the size without losing sampling fidelity (see the script for further
|
||||
details).
|
||||
|
||||
The naming convention for each stacktrace fixture follows this pattern:
|
||||
|
||||
```
|
||||
stacktraces_{seconds}s_{upsampling rate}x.json
|
||||
```
|
||||
|
||||
where `seconds` is the time span of the original query and `upsampling rate` is
|
||||
the reciprocal of the sampling rate returned from the original query.
|
||||
|
||||
To add a new stacktrace fixture to the test suite, update `stacktraces.ts`
|
||||
appropriately.
|
297
x-pack/plugins/profiling/common/__fixtures__/base_flamegraph.ts
Normal file
297
x-pack/plugins/profiling/common/__fixtures__/base_flamegraph.ts
Normal file
|
@ -0,0 +1,297 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { BaseFlameGraph } from '@kbn/profiling-utils';
|
||||
|
||||
export const baseFlamegraph: BaseFlameGraph = {
|
||||
Edges: [
|
||||
[1],
|
||||
[2],
|
||||
[3],
|
||||
[4],
|
||||
[5],
|
||||
[6],
|
||||
[7],
|
||||
[8],
|
||||
[9],
|
||||
[10],
|
||||
[11],
|
||||
[12],
|
||||
[13],
|
||||
[14],
|
||||
[15],
|
||||
[16],
|
||||
[17],
|
||||
[18],
|
||||
[19],
|
||||
[20],
|
||||
[21],
|
||||
[22],
|
||||
[23],
|
||||
[24],
|
||||
[25],
|
||||
[26],
|
||||
[27],
|
||||
[28],
|
||||
[29],
|
||||
[30],
|
||||
[31],
|
||||
[32],
|
||||
[33],
|
||||
[34],
|
||||
[],
|
||||
],
|
||||
FileID: [
|
||||
'',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'fwIcP8qXDOl7k0VhWU8z9Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
'5JfXt00O17Yra2Rwh8HT8Q',
|
||||
],
|
||||
FrameType: [
|
||||
0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
4, 4, 4,
|
||||
],
|
||||
Inline: [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
],
|
||||
ExeFilename: [
|
||||
'',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'metricbeat',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
'vmlinux',
|
||||
],
|
||||
AddressOrLine: [
|
||||
0, 43443520, 67880745, 67881145, 53704110, 53704665, 53696841, 53697537, 53700683, 53696841,
|
||||
52492674, 67626923, 67629380, 67630226, 51515812, 51512445, 51522994, 44606453, 43747101,
|
||||
43699300, 43538916, 43547623, 42994898, 42994925, 14680216, 14356875, 3732840, 3732678, 3721714,
|
||||
3719260, 3936007, 3897721, 4081162, 4458225, 1712873,
|
||||
],
|
||||
FunctionName: [
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'entry_SYSCALL_64_after_hwframe',
|
||||
'do_syscall_64',
|
||||
'__x64_sys_read',
|
||||
'ksys_read',
|
||||
'vfs_read',
|
||||
'new_sync_read',
|
||||
'seq_read_iter',
|
||||
'm_show',
|
||||
'show_mountinfo',
|
||||
'kernfs_sop_show_path',
|
||||
'cgroup_show_path',
|
||||
],
|
||||
FunctionOffset: [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0,
|
||||
],
|
||||
SourceFilename: [
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
],
|
||||
SourceLine: [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0,
|
||||
],
|
||||
CountInclusive: [
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
7, 7, 7,
|
||||
],
|
||||
CountExclusive: [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 7,
|
||||
],
|
||||
AnnualCO2TonsInclusive: [
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
0.0013627551116480942, 0.0013627551116480942, 0.0013627551116480942,
|
||||
],
|
||||
AnnualCO2TonsExclusive: [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0.0013627551116480942,
|
||||
],
|
||||
AnnualCostsUSDInclusive: [
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492, 61.30240940376492,
|
||||
],
|
||||
AnnualCostsUSDExclusive: [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 61.30240940376492,
|
||||
],
|
||||
Size: 35,
|
||||
SamplingRate: 1,
|
||||
SelfCPU: 7,
|
||||
TotalCPU: 245,
|
||||
SelfAnnualCO2Tons: 0.0013627551116480942,
|
||||
TotalAnnualCO2Tons: 0.04769642890768329,
|
||||
SelfAnnualCostsUSD: 61.30240940376492,
|
||||
TotalAnnualCostsUSD: 2145.5843291317715,
|
||||
TotalSamples: 7,
|
||||
TotalSeconds: 4.980000019073486,
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { StackTraceResponse } from '@kbn/profiling-utils';
|
||||
|
||||
import stackTraces1x from './stacktraces_60s_1x.json';
|
||||
import stackTraces5x from './stacktraces_3600s_5x.json';
|
||||
import stackTraces125x from './stacktraces_86400s_125x.json';
|
||||
import stackTraces625x from './stacktraces_604800s_625x.json';
|
||||
|
||||
export const stackTraceFixtures: Array<{
|
||||
response: StackTraceResponse;
|
||||
seconds: number;
|
||||
upsampledBy: number;
|
||||
}> = [
|
||||
{ response: stackTraces1x, seconds: 60, upsampledBy: 1 },
|
||||
{ response: stackTraces5x, seconds: 3600, upsampledBy: 5 },
|
||||
{ response: stackTraces125x, seconds: 86400, upsampledBy: 125 },
|
||||
{ response: stackTraces625x, seconds: 604800, upsampledBy: 625 },
|
||||
];
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -5,110 +5,91 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
createBaseFlameGraph,
|
||||
createCalleeTree,
|
||||
createFlameGraph,
|
||||
decodeStackTraceResponse,
|
||||
} from '@kbn/profiling-utils';
|
||||
import { createFlameGraph } from '@kbn/profiling-utils';
|
||||
import { sum } from 'lodash';
|
||||
import { createColumnarViewModel } from './columnar_view_model';
|
||||
import { stackTraceFixtures } from './__fixtures__/stacktraces';
|
||||
import { baseFlamegraph } from './__fixtures__/base_flamegraph';
|
||||
|
||||
describe('Columnar view model operations', () => {
|
||||
stackTraceFixtures.forEach(({ response, seconds, upsampledBy }) => {
|
||||
const { events, stackTraces, stackFrames, executables, totalFrames, samplingRate } =
|
||||
decodeStackTraceResponse(response);
|
||||
const tree = createCalleeTree(
|
||||
events,
|
||||
stackTraces,
|
||||
stackFrames,
|
||||
executables,
|
||||
totalFrames,
|
||||
samplingRate
|
||||
);
|
||||
const graph = createFlameGraph(createBaseFlameGraph(tree, samplingRate, seconds));
|
||||
const graph = createFlameGraph(baseFlamegraph);
|
||||
|
||||
describe(`stacktraces from ${seconds} seconds and upsampled by ${upsampledBy}`, () => {
|
||||
describe('color values are generated by default', () => {
|
||||
const viewModel = createColumnarViewModel(graph);
|
||||
describe('color values are generated by default', () => {
|
||||
const viewModel = createColumnarViewModel(graph);
|
||||
|
||||
test('length of colors is equal to length of labels multipled by 4', () => {
|
||||
expect(viewModel.color.length).toEqual(viewModel.label.length * 4);
|
||||
});
|
||||
it('length of colors is equal to length of labels multipled by 4', () => {
|
||||
expect(viewModel.color.length).toEqual(viewModel.label.length * 4);
|
||||
});
|
||||
|
||||
test('length of position0 is equal to length of labels multipled by 2', () => {
|
||||
expect(viewModel.position0.length).toEqual(viewModel.label.length * 2);
|
||||
});
|
||||
it('length of position0 is equal to length of labels multipled by 2', () => {
|
||||
expect(viewModel.position0.length).toEqual(viewModel.label.length * 2);
|
||||
});
|
||||
|
||||
test('length of position1 is equal to length of labels multipled by 2', () => {
|
||||
expect(viewModel.position1.length).toEqual(viewModel.label.length * 2);
|
||||
});
|
||||
it('length of position1 is equal to length of labels multipled by 2', () => {
|
||||
expect(viewModel.position1.length).toEqual(viewModel.label.length * 2);
|
||||
});
|
||||
|
||||
test('length of size0 is equal to length of labels', () => {
|
||||
expect(viewModel.size0.length).toEqual(viewModel.label.length);
|
||||
});
|
||||
it('length of size0 is equal to length of labels', () => {
|
||||
expect(viewModel.size0.length).toEqual(viewModel.label.length);
|
||||
});
|
||||
|
||||
test('length of size1 is equal to length of labels', () => {
|
||||
expect(viewModel.size1.length).toEqual(viewModel.label.length);
|
||||
});
|
||||
it('length of size1 is equal to length of labels', () => {
|
||||
expect(viewModel.size1.length).toEqual(viewModel.label.length);
|
||||
});
|
||||
|
||||
test('length of values is equal to length of labels', () => {
|
||||
expect(viewModel.value.length).toEqual(viewModel.label.length);
|
||||
});
|
||||
it('length of values is equal to length of labels', () => {
|
||||
expect(viewModel.value.length).toEqual(viewModel.label.length);
|
||||
});
|
||||
|
||||
test('both position arrays are equal', () => {
|
||||
expect(viewModel.position0).toEqual(viewModel.position1);
|
||||
});
|
||||
it('both position arrays are equal', () => {
|
||||
expect(viewModel.position0).toEqual(viewModel.position1);
|
||||
});
|
||||
|
||||
test('both size arrays are equal', () => {
|
||||
expect(viewModel.size0).toEqual(viewModel.size1);
|
||||
});
|
||||
it('both size arrays are equal', () => {
|
||||
expect(viewModel.size0).toEqual(viewModel.size1);
|
||||
});
|
||||
|
||||
test('sum of colors is greater than zero', () => {
|
||||
expect(sum(viewModel.color)).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
it('sum of colors is greater than zero', () => {
|
||||
expect(sum(viewModel.color)).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('color values are not generated when disabled', () => {
|
||||
const viewModel = createColumnarViewModel(graph, false);
|
||||
describe('color values are not generated when disabled', () => {
|
||||
const viewModel = createColumnarViewModel(graph, false);
|
||||
|
||||
test('length of colors is equal to length of labels multipled by 4', () => {
|
||||
expect(viewModel.color.length).toEqual(viewModel.label.length * 4);
|
||||
});
|
||||
it('length of colors is equal to length of labels multipled by 4', () => {
|
||||
expect(viewModel.color.length).toEqual(viewModel.label.length * 4);
|
||||
});
|
||||
|
||||
test('length of position0 is equal to length of labels multipled by 2', () => {
|
||||
expect(viewModel.position0.length).toEqual(viewModel.label.length * 2);
|
||||
});
|
||||
it('length of position0 is equal to length of labels multipled by 2', () => {
|
||||
expect(viewModel.position0.length).toEqual(viewModel.label.length * 2);
|
||||
});
|
||||
|
||||
test('length of position1 is equal to length of labels multipled by 2', () => {
|
||||
expect(viewModel.position1.length).toEqual(viewModel.label.length * 2);
|
||||
});
|
||||
it('length of position1 is equal to length of labels multipled by 2', () => {
|
||||
expect(viewModel.position1.length).toEqual(viewModel.label.length * 2);
|
||||
});
|
||||
|
||||
test('length of size0 is equal to length of labels', () => {
|
||||
expect(viewModel.size0.length).toEqual(viewModel.label.length);
|
||||
});
|
||||
it('length of size0 is equal to length of labels', () => {
|
||||
expect(viewModel.size0.length).toEqual(viewModel.label.length);
|
||||
});
|
||||
|
||||
test('length of size1 is equal to length of labels', () => {
|
||||
expect(viewModel.size1.length).toEqual(viewModel.label.length);
|
||||
});
|
||||
it('length of size1 is equal to length of labels', () => {
|
||||
expect(viewModel.size1.length).toEqual(viewModel.label.length);
|
||||
});
|
||||
|
||||
test('length of values is equal to length of labels', () => {
|
||||
expect(viewModel.value.length).toEqual(viewModel.label.length);
|
||||
});
|
||||
it('length of values is equal to length of labels', () => {
|
||||
expect(viewModel.value.length).toEqual(viewModel.label.length);
|
||||
});
|
||||
|
||||
test('both position arrays are equal', () => {
|
||||
expect(viewModel.position0).toEqual(viewModel.position1);
|
||||
});
|
||||
it('both position arrays are equal', () => {
|
||||
expect(viewModel.position0).toEqual(viewModel.position1);
|
||||
});
|
||||
|
||||
test('both size arrays are equal', () => {
|
||||
expect(viewModel.size0).toEqual(viewModel.size1);
|
||||
});
|
||||
it('both size arrays are equal', () => {
|
||||
expect(viewModel.size0).toEqual(viewModel.size1);
|
||||
});
|
||||
|
||||
test('sum of colors is equal to zero', () => {
|
||||
expect(sum(viewModel.color)).toEqual(0);
|
||||
});
|
||||
});
|
||||
it('sum of colors is equal to zero', () => {
|
||||
expect(sum(viewModel.color)).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,8 +31,8 @@ describe('Differential Functions page', () => {
|
|||
cy.wait('@getTopNFunctions');
|
||||
[
|
||||
{ id: 'overallPerformance', value: '0%' },
|
||||
{ id: 'annualizedCo2', value: '33.79 lbs / 15.33 kg' },
|
||||
{ id: 'annualizedCost', value: '$318.32' },
|
||||
{ id: 'annualizedCo2', value: '2.5k lbs / 1.13k kg' },
|
||||
{ id: 'annualizedCost', value: '$10.66k' },
|
||||
{ id: 'totalNumberOfSamples', value: '513' },
|
||||
].forEach((item) => {
|
||||
cy.get(`[data-test-subj="${item.id}_value"]`).contains(item.value);
|
||||
|
@ -50,8 +50,8 @@ describe('Differential Functions page', () => {
|
|||
cy.wait('@getTopNFunctions');
|
||||
[
|
||||
{ id: 'overallPerformance', value: '0%' },
|
||||
{ id: 'annualizedCo2', value: '0 lbs / 0 kg', comparisonValue: '33.79 lbs / 15.33 kg' },
|
||||
{ id: 'annualizedCost', value: '$0', comparisonValue: '$318.32' },
|
||||
{ id: 'annualizedCo2', value: '0 lbs / 0 kg', comparisonValue: '2.5k lbs / 1.13k kg' },
|
||||
{ id: 'annualizedCost', value: '$0', comparisonValue: '$10.66k' },
|
||||
{ id: 'totalNumberOfSamples', value: '0', comparisonValue: '15,390' },
|
||||
].forEach((item) => {
|
||||
cy.get(`[data-test-subj="${item.id}_value"]`).contains(item.value);
|
||||
|
@ -76,14 +76,14 @@ describe('Differential Functions page', () => {
|
|||
{ id: 'overallPerformance', value: '65.89%', icon: 'sortUp_success' },
|
||||
{
|
||||
id: 'annualizedCo2',
|
||||
value: '33.79 lbs / 15.33 kg',
|
||||
comparisonValue: '11.53 lbs / 5.23 kg (65.89%)',
|
||||
value: '2.5k lbs / 1.13k kg',
|
||||
comparisonValue: '548.84 lbs / 248.95 kg (78.01%',
|
||||
icon: 'comparison_sortUp_success',
|
||||
},
|
||||
{
|
||||
id: 'annualizedCost',
|
||||
value: '$318.32',
|
||||
comparisonValue: '$108.59 (65.89%)',
|
||||
value: '$10.66k',
|
||||
comparisonValue: '$2.35k (78.01%)',
|
||||
icon: 'comparison_sortUp_success',
|
||||
},
|
||||
{
|
||||
|
@ -116,14 +116,14 @@ describe('Differential Functions page', () => {
|
|||
{ id: 'overallPerformance', value: '193.14%', icon: 'sortDown_danger' },
|
||||
{
|
||||
id: 'annualizedCo2',
|
||||
value: '11.53 lbs / 5.23 kg',
|
||||
comparisonValue: '33.79 lbs / 15.33 kg (193.14%)',
|
||||
value: '548.84 lbs / 248.95 kg',
|
||||
comparisonValue: '2.5k lbs / 1.13k kg (354.66%)',
|
||||
icon: 'comparison_sortDown_danger',
|
||||
},
|
||||
{
|
||||
id: 'annualizedCost',
|
||||
value: '$108.59',
|
||||
comparisonValue: '$318.32 (193.14%)',
|
||||
value: '$2.35k',
|
||||
comparisonValue: '$10.66k (354.66%)',
|
||||
icon: 'comparison_sortDown_danger',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import {
|
||||
profilingCo2PerKWH,
|
||||
profilingDatacenterPUE,
|
||||
profilingPerCoreWatt,
|
||||
profilingPervCPUWattX86,
|
||||
} from '@kbn/observability-plugin/common';
|
||||
|
||||
describe('Functions page', () => {
|
||||
|
@ -38,7 +38,7 @@ describe('Functions page', () => {
|
|||
cy.get(firstRowSelector).eq(2).contains('vmlinux');
|
||||
cy.get(firstRowSelector).eq(3).contains('5.46%');
|
||||
cy.get(firstRowSelector).eq(4).contains('5.46%');
|
||||
cy.get(firstRowSelector).eq(5).contains('1.84 lbs / 0.84 kg');
|
||||
cy.get(firstRowSelector).eq(5).contains('4.07 lbs / 1.84 kg');
|
||||
cy.get(firstRowSelector).eq(6).contains('$17.37');
|
||||
cy.get(firstRowSelector).eq(7).contains('28');
|
||||
});
|
||||
|
@ -66,11 +66,11 @@ describe('Functions page', () => {
|
|||
{ parentKey: 'impactEstimates', key: 'annualizedSelfCoreSeconds', value: '17.03 days' },
|
||||
{ parentKey: 'impactEstimates', key: 'co2Emission', value: '~0.00 lbs / ~0.00 kg' },
|
||||
{ parentKey: 'impactEstimates', key: 'selfCo2Emission', value: '~0.00 lbs / ~0.00 kg' },
|
||||
{ parentKey: 'impactEstimates', key: 'annualizedCo2Emission', value: '1.84 lbs / 0.84 kg' },
|
||||
{ parentKey: 'impactEstimates', key: 'annualizedCo2Emission', value: '4.07 lbs / 1.84 kg' },
|
||||
{
|
||||
parentKey: 'impactEstimates',
|
||||
key: 'annualizedSelfCo2Emission',
|
||||
value: '1.84 lbs / 0.84 kg',
|
||||
value: '4.07 lbs / 1.84 kg',
|
||||
},
|
||||
{ parentKey: 'impactEstimates', key: 'dollarCost', value: '$~0.00' },
|
||||
{ parentKey: 'impactEstimates', key: 'selfDollarCost', value: '$~0.00' },
|
||||
|
@ -133,17 +133,17 @@ describe('Functions page', () => {
|
|||
columnKey: 'annualizedCo2',
|
||||
columnIndex: 5,
|
||||
highRank: 1,
|
||||
lowRank: 389,
|
||||
highValue: '1.84 lbs / 0.84 kg',
|
||||
lowValue: undefined,
|
||||
lowRank: 44,
|
||||
highValue: '45.01 lbs / 20.42 kg',
|
||||
lowValue: '0.15 lbs / 0.07 kg',
|
||||
},
|
||||
{
|
||||
columnKey: 'annualizedDollarCost',
|
||||
columnIndex: 6,
|
||||
highRank: 1,
|
||||
lowRank: 389,
|
||||
highValue: '$17.37',
|
||||
lowValue: undefined,
|
||||
lowRank: 44,
|
||||
highValue: '$192.36',
|
||||
lowValue: '$0.62',
|
||||
},
|
||||
].forEach(({ columnKey, columnIndex, highRank, highValue, lowRank, lowValue }) => {
|
||||
cy.get(`[data-test-subj="dataGridHeaderCell-${columnKey}"]`).click();
|
||||
|
@ -174,15 +174,15 @@ describe('Functions page', () => {
|
|||
cy.get(firstRowSelector).eq(2).contains('/');
|
||||
});
|
||||
|
||||
describe('Test changing CO2 settings', () => {
|
||||
// skipping this for now until the values are passed to the ES plugin
|
||||
describe.skip('Test changing CO2 settings', () => {
|
||||
afterEach(() => {
|
||||
cy.updateAdvancedSettings({
|
||||
[profilingCo2PerKWH]: 0.000379069,
|
||||
[profilingDatacenterPUE]: 1.7,
|
||||
[profilingPerCoreWatt]: 7,
|
||||
[profilingPervCPUWattX86]: 7,
|
||||
});
|
||||
});
|
||||
|
||||
it('changes CO2 settings and validate values in the table', () => {
|
||||
cy.intercept('GET', '/internal/profiling/topn/functions?*').as('getTopNFunctions');
|
||||
cy.visitKibana('/app/profiling/functions', { rangeFrom, rangeTo });
|
||||
|
@ -199,7 +199,7 @@ describe('Functions page', () => {
|
|||
cy.get(`[data-test-subj="advancedSetting-editField-${profilingDatacenterPUE}"]`)
|
||||
.clear()
|
||||
.type('2.4');
|
||||
cy.get(`[data-test-subj="advancedSetting-editField-${profilingPerCoreWatt}"]`)
|
||||
cy.get(`[data-test-subj="advancedSetting-editField-${profilingPervCPUWattX86}"]`)
|
||||
.clear()
|
||||
.type('20');
|
||||
cy.contains('Save changes').click();
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
import {
|
||||
profilingCo2PerKWH,
|
||||
profilingDatacenterPUE,
|
||||
profilingPerCoreWatt,
|
||||
profilingPervCPUWattX86,
|
||||
} from '@kbn/observability-plugin/common';
|
||||
|
||||
describe('Settings page', () => {
|
||||
|
@ -25,7 +25,7 @@ describe('Settings page', () => {
|
|||
cy.updateAdvancedSettings({
|
||||
[profilingCo2PerKWH]: 0.000379069,
|
||||
[profilingDatacenterPUE]: 1.7,
|
||||
[profilingPerCoreWatt]: 7,
|
||||
[profilingPervCPUWattX86]: 7,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -35,7 +35,10 @@ describe('Settings page', () => {
|
|||
cy.contains('CO2');
|
||||
cy.contains('Regional Carbon Intensity (ton/kWh)');
|
||||
cy.contains('Data Center PUE');
|
||||
cy.contains('Per Core Watts');
|
||||
cy.contains('Per vCPU Watts - x86');
|
||||
cy.contains('Per vCPU Watts - arm64');
|
||||
cy.contains('AWS EDP discount rate (%)');
|
||||
cy.contains('Cost per vCPU per hour ($)');
|
||||
});
|
||||
|
||||
it('updates values', () => {
|
||||
|
@ -48,7 +51,7 @@ describe('Settings page', () => {
|
|||
cy.get(`[data-test-subj="advancedSetting-editField-${profilingDatacenterPUE}"]`)
|
||||
.clear()
|
||||
.type('2.4');
|
||||
cy.get(`[data-test-subj="advancedSetting-editField-${profilingPerCoreWatt}"]`)
|
||||
cy.get(`[data-test-subj="advancedSetting-editField-${profilingPervCPUWattX86}"]`)
|
||||
.clear()
|
||||
.type('20');
|
||||
cy.get('[data-test-subj="profilingBottomBarActions"]').should('exist');
|
||||
|
|
|
@ -21,48 +21,65 @@ import { css } from '@emotion/react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { isNumber } from 'lodash';
|
||||
import React from 'react';
|
||||
import { profilingUseLegacyCo2Calculation } from '@kbn/observability-plugin/common';
|
||||
import { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_estimates';
|
||||
import { asCost } from '../../utils/formatters/as_cost';
|
||||
import { asPercentage } from '../../utils/formatters/as_percentage';
|
||||
import { asWeight } from '../../utils/formatters/as_weight';
|
||||
import { CPULabelWithHint } from '../cpu_label_with_hint';
|
||||
import { TooltipRow } from './tooltip_row';
|
||||
import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies';
|
||||
|
||||
interface Props {
|
||||
isRoot: boolean;
|
||||
label: string;
|
||||
countInclusive: number;
|
||||
countExclusive: number;
|
||||
totalSamples: number;
|
||||
totalSeconds: number;
|
||||
annualCO2KgsInclusive: number;
|
||||
annualCostsUSDInclusive: number;
|
||||
baselineScaleFactor?: number;
|
||||
comparisonScaleFactor?: number;
|
||||
comparisonCountInclusive?: number;
|
||||
comparisonAnnualCO2KgsInclusive?: number;
|
||||
comparisonAnnualCostsUSDInclusive?: number;
|
||||
comparisonCountExclusive?: number;
|
||||
comparisonCountInclusive?: number;
|
||||
comparisonScaleFactor?: number;
|
||||
comparisonTotalSamples?: number;
|
||||
comparisonTotalSeconds?: number;
|
||||
onShowMoreClick?: () => void;
|
||||
countExclusive: number;
|
||||
countInclusive: number;
|
||||
inline: boolean;
|
||||
isRoot: boolean;
|
||||
label: string;
|
||||
onShowMoreClick?: () => void;
|
||||
parentLabel?: string;
|
||||
totalSamples: number;
|
||||
totalSeconds: number;
|
||||
}
|
||||
|
||||
export function FlameGraphTooltip({
|
||||
isRoot,
|
||||
label,
|
||||
countInclusive,
|
||||
countExclusive,
|
||||
totalSamples,
|
||||
totalSeconds,
|
||||
annualCO2KgsInclusive,
|
||||
annualCostsUSDInclusive,
|
||||
baselineScaleFactor,
|
||||
comparisonScaleFactor,
|
||||
comparisonCountInclusive,
|
||||
comparisonAnnualCO2KgsInclusive,
|
||||
comparisonAnnualCostsUSDInclusive,
|
||||
comparisonCountExclusive,
|
||||
comparisonCountInclusive,
|
||||
comparisonScaleFactor,
|
||||
comparisonTotalSamples,
|
||||
comparisonTotalSeconds,
|
||||
onShowMoreClick,
|
||||
countExclusive,
|
||||
countInclusive,
|
||||
inline,
|
||||
isRoot,
|
||||
label,
|
||||
onShowMoreClick,
|
||||
parentLabel,
|
||||
totalSamples,
|
||||
totalSeconds,
|
||||
}: Props) {
|
||||
const {
|
||||
start: { core },
|
||||
} = useProfilingDependencies();
|
||||
const shouldUseLegacyCo2Calculation = core.uiSettings.get<boolean>(
|
||||
profilingUseLegacyCo2Calculation
|
||||
);
|
||||
|
||||
const theme = useEuiTheme();
|
||||
const calculateImpactEstimates = useCalculateImpactEstimate();
|
||||
|
||||
|
@ -170,9 +187,17 @@ export function FlameGraphTooltip({
|
|||
label={i18n.translate('xpack.profiling.flameGraphTooltip.annualizedCo2', {
|
||||
defaultMessage: `Annualized CO2`,
|
||||
})}
|
||||
value={impactEstimates.totalCPU.annualizedCo2}
|
||||
comparison={comparisonImpactEstimates?.totalCPU.annualizedCo2}
|
||||
formatValue={asWeight}
|
||||
value={
|
||||
shouldUseLegacyCo2Calculation
|
||||
? impactEstimates.totalCPU.annualizedCo2
|
||||
: annualCO2KgsInclusive
|
||||
}
|
||||
comparison={
|
||||
shouldUseLegacyCo2Calculation
|
||||
? comparisonImpactEstimates?.totalCPU.annualizedCo2
|
||||
: comparisonAnnualCO2KgsInclusive
|
||||
}
|
||||
formatValue={(value) => asWeight(value, 'kgs')}
|
||||
showDifference
|
||||
formatDifferenceAsPercentage={false}
|
||||
/>
|
||||
|
@ -180,8 +205,16 @@ export function FlameGraphTooltip({
|
|||
label={i18n.translate('xpack.profiling.flameGraphTooltip.annualizedDollarCost', {
|
||||
defaultMessage: `Annualized dollar cost`,
|
||||
})}
|
||||
value={impactEstimates.totalCPU.annualizedDollarCost}
|
||||
comparison={comparisonImpactEstimates?.totalCPU.annualizedDollarCost}
|
||||
value={
|
||||
shouldUseLegacyCo2Calculation
|
||||
? impactEstimates.totalCPU.annualizedDollarCost
|
||||
: annualCostsUSDInclusive
|
||||
}
|
||||
comparison={
|
||||
shouldUseLegacyCo2Calculation
|
||||
? comparisonImpactEstimates?.totalCPU.annualizedDollarCost
|
||||
: comparisonAnnualCostsUSDInclusive
|
||||
}
|
||||
formatValue={asCost}
|
||||
showDifference
|
||||
formatDifferenceAsPercentage={false}
|
||||
|
|
|
@ -10,23 +10,23 @@ import {
|
|||
Datum,
|
||||
Flame,
|
||||
FlameLayerValue,
|
||||
FlameSpec,
|
||||
PartialTheme,
|
||||
Settings,
|
||||
Tooltip,
|
||||
FlameSpec,
|
||||
} from '@elastic/charts';
|
||||
import { EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Maybe } from '@kbn/observability-plugin/common/typings';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useUiTracker } from '@kbn/observability-shared-plugin/public';
|
||||
import type { ElasticFlameGraph } from '@kbn/profiling-utils';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { getFlamegraphModel } from '../../utils/get_flamegraph_model';
|
||||
import { FlameGraphLegend } from './flame_graph_legend';
|
||||
import { FrameInformationWindow } from '../frame_information_window';
|
||||
import { Frame } from '../frame_information_window';
|
||||
import { FrameInformationTooltip } from '../frame_information_window/frame_information_tooltip';
|
||||
import { FlameGraphTooltip } from './flamegraph_tooltip';
|
||||
import { ComparisonMode } from '../normalization_menu';
|
||||
import { FlameGraphTooltip } from './flamegraph_tooltip';
|
||||
import { FlameGraphLegend } from './flame_graph_legend';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
|
@ -90,7 +90,7 @@ export function FlameGraph({
|
|||
|
||||
const [highlightedVmIndex, setHighlightedVmIndex] = useState<number | undefined>(undefined);
|
||||
|
||||
const selected: undefined | React.ComponentProps<typeof FrameInformationWindow>['frame'] =
|
||||
const selected: Frame | undefined =
|
||||
primaryFlamegraph && highlightedVmIndex !== undefined
|
||||
? {
|
||||
fileID: primaryFlamegraph.FileID[highlightedVmIndex],
|
||||
|
@ -102,6 +102,10 @@ export function FlameGraph({
|
|||
sourceLine: primaryFlamegraph.SourceLine[highlightedVmIndex],
|
||||
countInclusive: primaryFlamegraph.CountInclusive[highlightedVmIndex],
|
||||
countExclusive: primaryFlamegraph.CountExclusive[highlightedVmIndex],
|
||||
selfAnnualCO2Kgs: primaryFlamegraph.SelfAnnualCO2KgsItems[highlightedVmIndex],
|
||||
totalAnnualCO2Kgs: primaryFlamegraph.TotalAnnualCO2KgsItems[highlightedVmIndex],
|
||||
selfAnnualCostUSD: primaryFlamegraph.SelfAnnualCostsUSDItems[highlightedVmIndex],
|
||||
totalAnnualCostUSD: primaryFlamegraph.TotalAnnualCostsUSDItems[highlightedVmIndex],
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
@ -154,23 +158,35 @@ export function FlameGraph({
|
|||
|
||||
return (
|
||||
<FlameGraphTooltip
|
||||
isRoot={valueIndex === 0}
|
||||
label={label}
|
||||
countInclusive={countInclusive}
|
||||
countExclusive={countExclusive}
|
||||
totalSamples={totalSamples}
|
||||
totalSeconds={totalSeconds}
|
||||
comparisonCountInclusive={comparisonNode?.CountInclusive}
|
||||
annualCO2KgsInclusive={
|
||||
primaryFlamegraph.TotalAnnualCO2KgsItems[valueIndex]
|
||||
}
|
||||
annualCostsUSDInclusive={
|
||||
primaryFlamegraph.TotalAnnualCostsUSDItems[valueIndex]
|
||||
}
|
||||
baselineScaleFactor={baseline}
|
||||
comparisonAnnualCO2KgsInclusive={
|
||||
comparisonFlamegraph?.TotalAnnualCO2KgsItems[valueIndex]
|
||||
}
|
||||
comparisonAnnualCostsUSDInclusive={
|
||||
comparisonFlamegraph?.TotalAnnualCostsUSDItems[valueIndex]
|
||||
}
|
||||
comparisonCountExclusive={comparisonNode?.CountExclusive}
|
||||
comparisonCountInclusive={comparisonNode?.CountInclusive}
|
||||
comparisonScaleFactor={comparison}
|
||||
comparisonTotalSamples={comparisonFlamegraph?.CountInclusive[0]}
|
||||
comparisonTotalSeconds={comparisonFlamegraph?.TotalSeconds}
|
||||
baselineScaleFactor={baseline}
|
||||
comparisonScaleFactor={comparison}
|
||||
countExclusive={countExclusive}
|
||||
countInclusive={countInclusive}
|
||||
isRoot={valueIndex === 0}
|
||||
label={label}
|
||||
onShowMoreClick={() => {
|
||||
trackProfilingEvent({ metric: 'flamegraph_node_details_click' });
|
||||
toggleShowInformationWindow();
|
||||
setHighlightedVmIndex(valueIndex);
|
||||
}}
|
||||
totalSamples={totalSamples}
|
||||
totalSeconds={totalSeconds}
|
||||
inline={inline}
|
||||
parentLabel={parentLabel}
|
||||
/>
|
||||
|
|
|
@ -13,7 +13,23 @@ import { asNumber } from '../../utils/formatters/as_number';
|
|||
import { asPercentage } from '../../utils/formatters/as_percentage';
|
||||
import { asWeight } from '../../utils/formatters/as_weight';
|
||||
import { CPULabelWithHint } from '../cpu_label_with_hint';
|
||||
import { CalculateImpactEstimates } from '../../hooks/use_calculate_impact_estimates';
|
||||
import {
|
||||
ANNUAL_SECONDS,
|
||||
CalculateImpactEstimates,
|
||||
} from '../../hooks/use_calculate_impact_estimates';
|
||||
|
||||
interface Params {
|
||||
countInclusive: number;
|
||||
countExclusive: number;
|
||||
totalSamples: number;
|
||||
totalSeconds: number;
|
||||
calculateImpactEstimates: CalculateImpactEstimates;
|
||||
shouldUseLegacyCo2Calculation: boolean;
|
||||
selfAnnualCO2Kgs: number;
|
||||
totalAnnualCO2Kgs: number;
|
||||
selfAnnualCostUSD: number;
|
||||
totalAnnualCostUSD: number;
|
||||
}
|
||||
|
||||
export function getImpactRows({
|
||||
countInclusive,
|
||||
|
@ -21,13 +37,12 @@ export function getImpactRows({
|
|||
totalSamples,
|
||||
totalSeconds,
|
||||
calculateImpactEstimates,
|
||||
}: {
|
||||
countInclusive: number;
|
||||
countExclusive: number;
|
||||
totalSamples: number;
|
||||
totalSeconds: number;
|
||||
calculateImpactEstimates: CalculateImpactEstimates;
|
||||
}) {
|
||||
shouldUseLegacyCo2Calculation,
|
||||
selfAnnualCO2Kgs,
|
||||
totalAnnualCO2Kgs,
|
||||
selfAnnualCostUSD,
|
||||
totalAnnualCostUSD,
|
||||
}: Params) {
|
||||
const { selfCPU, totalCPU } = calculateImpactEstimates({
|
||||
countInclusive,
|
||||
countExclusive,
|
||||
|
@ -35,6 +50,8 @@ export function getImpactRows({
|
|||
totalSeconds,
|
||||
});
|
||||
|
||||
const annualSecondsRatio = ANNUAL_SECONDS / totalSeconds;
|
||||
|
||||
return [
|
||||
{
|
||||
'data-test-subj': 'totalCPU',
|
||||
|
@ -100,7 +117,10 @@ export function getImpactRows({
|
|||
defaultMessage: 'CO2 emission',
|
||||
}
|
||||
),
|
||||
value: asWeight(totalCPU.co2),
|
||||
value: asWeight(
|
||||
shouldUseLegacyCo2Calculation ? totalCPU.co2 : totalAnnualCO2Kgs / annualSecondsRatio,
|
||||
'kgs'
|
||||
),
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'selfCo2Emission',
|
||||
|
@ -108,7 +128,10 @@ export function getImpactRows({
|
|||
'xpack.profiling.flameGraphInformationWindow.co2EmissionExclusiveLabel',
|
||||
{ defaultMessage: 'CO2 emission (excl. children)' }
|
||||
),
|
||||
value: asWeight(selfCPU.co2),
|
||||
value: asWeight(
|
||||
shouldUseLegacyCo2Calculation ? selfCPU.co2 : selfAnnualCO2Kgs / annualSecondsRatio,
|
||||
'kgs'
|
||||
),
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'annualizedCo2Emission',
|
||||
|
@ -116,7 +139,10 @@ export function getImpactRows({
|
|||
'xpack.profiling.flameGraphInformationWindow.annualizedCo2InclusiveLabel',
|
||||
{ defaultMessage: 'Annualized CO2' }
|
||||
),
|
||||
value: asWeight(totalCPU.annualizedCo2),
|
||||
value: asWeight(
|
||||
shouldUseLegacyCo2Calculation ? totalCPU.annualizedCo2 : totalAnnualCO2Kgs,
|
||||
'kgs'
|
||||
),
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'annualizedSelfCo2Emission',
|
||||
|
@ -124,7 +150,10 @@ export function getImpactRows({
|
|||
'xpack.profiling.flameGraphInformationWindow.annualizedCo2ExclusiveLabel',
|
||||
{ defaultMessage: 'Annualized CO2 (excl. children)' }
|
||||
),
|
||||
value: asWeight(selfCPU.annualizedCo2),
|
||||
value: asWeight(
|
||||
shouldUseLegacyCo2Calculation ? selfCPU.annualizedCo2 : selfAnnualCO2Kgs,
|
||||
'kgs'
|
||||
),
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'dollarCost',
|
||||
|
@ -132,7 +161,11 @@ export function getImpactRows({
|
|||
'xpack.profiling.flameGraphInformationWindow.dollarCostInclusiveLabel',
|
||||
{ defaultMessage: 'Dollar cost' }
|
||||
),
|
||||
value: asCost(totalCPU.dollarCost),
|
||||
value: asCost(
|
||||
shouldUseLegacyCo2Calculation
|
||||
? totalCPU.dollarCost
|
||||
: totalAnnualCostUSD / annualSecondsRatio
|
||||
),
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'selfDollarCost',
|
||||
|
@ -140,7 +173,9 @@ export function getImpactRows({
|
|||
'xpack.profiling.flameGraphInformationWindow.dollarCostExclusiveLabel',
|
||||
{ defaultMessage: 'Dollar cost (excl. children)' }
|
||||
),
|
||||
value: asCost(selfCPU.dollarCost),
|
||||
value: asCost(
|
||||
shouldUseLegacyCo2Calculation ? selfCPU.dollarCost : selfAnnualCostUSD / annualSecondsRatio
|
||||
),
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'annualizedDollarCost',
|
||||
|
@ -148,7 +183,9 @@ export function getImpactRows({
|
|||
'xpack.profiling.flameGraphInformationWindow.annualizedDollarCostInclusiveLabel',
|
||||
{ defaultMessage: 'Annualized dollar cost' }
|
||||
),
|
||||
value: asCost(totalCPU.annualizedDollarCost),
|
||||
value: asCost(
|
||||
shouldUseLegacyCo2Calculation ? totalCPU.annualizedDollarCost : totalAnnualCostUSD
|
||||
),
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'annualizedSelfDollarCost',
|
||||
|
@ -156,7 +193,9 @@ export function getImpactRows({
|
|||
'xpack.profiling.flameGraphInformationWindow.annualizedDollarCostExclusiveLabel',
|
||||
{ defaultMessage: 'Annualized dollar cost (excl. children)' }
|
||||
),
|
||||
value: asCost(selfCPU.annualizedDollarCost),
|
||||
value: asCost(
|
||||
shouldUseLegacyCo2Calculation ? selfCPU.annualizedDollarCost : selfAnnualCostUSD
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FrameSymbolStatus, getFrameSymbolStatus } from '@kbn/profiling-utils';
|
||||
import React from 'react';
|
||||
import { profilingUseLegacyCo2Calculation } from '@kbn/observability-plugin/common';
|
||||
import { FrameInformationAIAssistant } from './frame_information_ai_assistant';
|
||||
import { FrameInformationPanel } from './frame_information_panel';
|
||||
import { getImpactRows } from './get_impact_rows';
|
||||
|
@ -15,6 +16,7 @@ import { getInformationRows } from './get_information_rows';
|
|||
import { KeyValueList } from './key_value_list';
|
||||
import { MissingSymbolsCallout } from './missing_symbols_callout';
|
||||
import { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_estimates';
|
||||
import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies';
|
||||
|
||||
export interface Frame {
|
||||
fileID: string;
|
||||
|
@ -26,6 +28,10 @@ export interface Frame {
|
|||
sourceLine: number;
|
||||
countInclusive: number;
|
||||
countExclusive: number;
|
||||
selfAnnualCO2Kgs: number;
|
||||
totalAnnualCO2Kgs: number;
|
||||
selfAnnualCostUSD: number;
|
||||
totalAnnualCostUSD: number;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
|
@ -43,6 +49,12 @@ export function FrameInformationWindow({
|
|||
showSymbolsStatus = true,
|
||||
}: Props) {
|
||||
const calculateImpactEstimates = useCalculateImpactEstimate();
|
||||
const {
|
||||
start: { core },
|
||||
} = useProfilingDependencies();
|
||||
const shouldUseLegacyCo2Calculation = core.uiSettings.get<boolean>(
|
||||
profilingUseLegacyCo2Calculation
|
||||
);
|
||||
|
||||
if (!frame) {
|
||||
return (
|
||||
|
@ -72,6 +84,10 @@ export function FrameInformationWindow({
|
|||
sourceLine,
|
||||
countInclusive,
|
||||
countExclusive,
|
||||
selfAnnualCO2Kgs,
|
||||
totalAnnualCO2Kgs,
|
||||
selfAnnualCostUSD,
|
||||
totalAnnualCostUSD,
|
||||
} = frame;
|
||||
|
||||
const informationRows = getInformationRows({
|
||||
|
@ -90,6 +106,11 @@ export function FrameInformationWindow({
|
|||
totalSamples,
|
||||
totalSeconds,
|
||||
calculateImpactEstimates,
|
||||
shouldUseLegacyCo2Calculation,
|
||||
selfAnnualCO2Kgs,
|
||||
totalAnnualCO2Kgs,
|
||||
selfAnnualCostUSD,
|
||||
totalAnnualCostUSD,
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -16,11 +16,13 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { profilingUseLegacyCo2Calculation } from '@kbn/observability-plugin/common';
|
||||
import { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_estimates';
|
||||
import { asCost } from '../../utils/formatters/as_cost';
|
||||
import { asWeight } from '../../utils/formatters/as_weight';
|
||||
import { calculateBaseComparisonDiff } from '../topn_functions/utils';
|
||||
import { SummaryItem } from './summary_item';
|
||||
import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies';
|
||||
|
||||
interface FrameValue {
|
||||
selfCPU: number;
|
||||
|
@ -28,6 +30,8 @@ interface FrameValue {
|
|||
totalCount: number;
|
||||
duration: number;
|
||||
scaleFactor?: number;
|
||||
totalAnnualCO2Kgs: number;
|
||||
totalAnnualCostUSD: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
@ -45,6 +49,12 @@ function getScaleFactor(scaleFactor: number = 1) {
|
|||
}
|
||||
|
||||
export function FramesSummary({ baseValue, comparisonValue, isLoading }: Props) {
|
||||
const {
|
||||
start: { core },
|
||||
} = useProfilingDependencies();
|
||||
const shouldUseLegacyCo2Calculation = core.uiSettings.get<boolean>(
|
||||
profilingUseLegacyCo2Calculation
|
||||
);
|
||||
const calculateImpactEstimates = useCalculateImpactEstimate();
|
||||
|
||||
const baselineScaledTotalSamples = baseValue
|
||||
|
@ -82,13 +92,25 @@ export function FramesSummary({ baseValue, comparisonValue, isLoading }: Props)
|
|||
comparisonValue: comparisonScaledTotalSamples || 0,
|
||||
}),
|
||||
co2EmissionDiff: calculateBaseComparisonDiff({
|
||||
baselineValue: baseImpactEstimates?.totalSamples?.annualizedCo2 || 0,
|
||||
comparisonValue: comparisonImpactEstimates?.totalSamples.annualizedCo2 || 0,
|
||||
formatValue: asWeight,
|
||||
baselineValue:
|
||||
(shouldUseLegacyCo2Calculation
|
||||
? baseImpactEstimates?.totalSamples?.annualizedCo2
|
||||
: baseValue?.totalAnnualCO2Kgs) || 0,
|
||||
comparisonValue:
|
||||
(shouldUseLegacyCo2Calculation
|
||||
? comparisonImpactEstimates?.totalSamples.annualizedCo2
|
||||
: comparisonValue?.totalAnnualCO2Kgs) || 0,
|
||||
formatValue: (value) => asWeight(value, 'kgs'),
|
||||
}),
|
||||
costImpactDiff: calculateBaseComparisonDiff({
|
||||
baselineValue: baseImpactEstimates?.totalSamples.annualizedDollarCost || 0,
|
||||
comparisonValue: comparisonImpactEstimates?.totalSamples.annualizedDollarCost || 0,
|
||||
baselineValue:
|
||||
(shouldUseLegacyCo2Calculation
|
||||
? baseImpactEstimates?.totalSamples.annualizedDollarCost
|
||||
: baseValue?.totalAnnualCostUSD) || 0,
|
||||
comparisonValue:
|
||||
(shouldUseLegacyCo2Calculation
|
||||
? comparisonImpactEstimates?.totalSamples.annualizedDollarCost
|
||||
: comparisonValue?.totalAnnualCostUSD) || 0,
|
||||
formatValue: asCost,
|
||||
}),
|
||||
};
|
||||
|
@ -98,6 +120,7 @@ export function FramesSummary({ baseValue, comparisonValue, isLoading }: Props)
|
|||
calculateImpactEstimates,
|
||||
comparisonScaledTotalSamples,
|
||||
comparisonValue,
|
||||
shouldUseLegacyCo2Calculation,
|
||||
]);
|
||||
|
||||
const data = [
|
||||
|
|
|
@ -17,8 +17,10 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { TopNFunctionSortField } from '@kbn/profiling-utils';
|
||||
import React, { useEffect } from 'react';
|
||||
import { profilingUseLegacyCo2Calculation } from '@kbn/observability-plugin/common';
|
||||
import { asCost } from '../../utils/formatters/as_cost';
|
||||
import { asWeight } from '../../utils/formatters/as_weight';
|
||||
import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies';
|
||||
import { StackFrameSummary } from '../stack_frame_summary';
|
||||
import { CPUStat } from './cpu_stat';
|
||||
import { SampleStat } from './sample_stat';
|
||||
|
@ -39,6 +41,14 @@ export function FunctionRow({
|
|||
onFrameClick,
|
||||
setCellProps,
|
||||
}: Props) {
|
||||
const {
|
||||
start: { core },
|
||||
} = useProfilingDependencies();
|
||||
|
||||
const shouldUseLegacyCo2Calculation = core.uiSettings.get<boolean>(
|
||||
profilingUseLegacyCo2Calculation
|
||||
);
|
||||
|
||||
if (columnId === TopNFunctionSortField.Diff) {
|
||||
return <DiffColumn diff={functionRow.diff} setCellProps={setCellProps} />;
|
||||
}
|
||||
|
@ -72,16 +82,33 @@ export function FunctionRow({
|
|||
|
||||
if (
|
||||
columnId === TopNFunctionSortField.AnnualizedCo2 &&
|
||||
functionRow.impactEstimates?.selfCPU?.annualizedCo2
|
||||
functionRow.impactEstimates?.totalCPU?.annualizedCo2
|
||||
) {
|
||||
return <div>{asWeight(functionRow.impactEstimates.selfCPU.annualizedCo2)}</div>;
|
||||
return (
|
||||
<div>
|
||||
{asWeight(
|
||||
shouldUseLegacyCo2Calculation
|
||||
? functionRow.impactEstimates.totalCPU.annualizedCo2
|
||||
: functionRow.totalAnnualCO2kgs,
|
||||
'kgs'
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
columnId === TopNFunctionSortField.AnnualizedDollarCost &&
|
||||
functionRow.impactEstimates?.selfCPU?.annualizedDollarCost
|
||||
functionRow.impactEstimates?.totalCPU?.annualizedDollarCost
|
||||
) {
|
||||
return <div>{asCost(functionRow.impactEstimates.selfCPU.annualizedDollarCost)}</div>;
|
||||
return (
|
||||
<div>
|
||||
{asCost(
|
||||
shouldUseLegacyCo2Calculation
|
||||
? functionRow.impactEstimates.totalCPU.annualizedDollarCost
|
||||
: functionRow.totalAnnualCostUSD
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -21,12 +21,14 @@ import { getCalleeFunction, TopNFunctions, TopNFunctionSortField } from '@kbn/pr
|
|||
import { last, orderBy } from 'lodash';
|
||||
import React, { forwardRef, Ref, useMemo, useState } from 'react';
|
||||
import { GridOnScrollProps } from 'react-window';
|
||||
import { profilingUseLegacyCo2Calculation } from '@kbn/observability-plugin/common';
|
||||
import { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_estimates';
|
||||
import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies';
|
||||
import { CPULabelWithHint } from '../cpu_label_with_hint';
|
||||
import { FrameInformationTooltip } from '../frame_information_window/frame_information_tooltip';
|
||||
import { LabelWithHint } from '../label_with_hint';
|
||||
import { FunctionRow } from './function_row';
|
||||
import { getFunctionsRows, IFunctionRow } from './utils';
|
||||
import { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_estimates';
|
||||
|
||||
interface Props {
|
||||
topNFunctions?: TopNFunctions;
|
||||
|
@ -69,6 +71,12 @@ export const TopNFunctionsGrid = forwardRef(
|
|||
}: Props,
|
||||
ref: Ref<EuiDataGridRefProps> | undefined
|
||||
) => {
|
||||
const {
|
||||
start: { core },
|
||||
} = useProfilingDependencies();
|
||||
const shouldUseLegacyCo2Calculation = core.uiSettings.get<boolean>(
|
||||
profilingUseLegacyCo2Calculation
|
||||
);
|
||||
const [selectedRow, setSelectedRow] = useState<IFunctionRow | undefined>();
|
||||
const trackProfilingEvent = useUiTracker({ app: 'profiling' });
|
||||
const calculateImpactEstimates = useCalculateImpactEstimate();
|
||||
|
@ -115,17 +123,27 @@ export const TopNFunctionsGrid = forwardRef(
|
|||
case TopNFunctionSortField.TotalCPU:
|
||||
return orderBy(rows, (row) => row.totalCPUPerc, sortDirection);
|
||||
case TopNFunctionSortField.AnnualizedCo2:
|
||||
return orderBy(rows, (row) => row.impactEstimates?.selfCPU.annualizedCo2, sortDirection);
|
||||
return orderBy(
|
||||
rows,
|
||||
(row) =>
|
||||
shouldUseLegacyCo2Calculation
|
||||
? row.impactEstimates?.totalCPU.annualizedCo2
|
||||
: row.totalAnnualCO2kgs,
|
||||
sortDirection
|
||||
);
|
||||
case TopNFunctionSortField.AnnualizedDollarCost:
|
||||
return orderBy(
|
||||
rows,
|
||||
(row) => row.impactEstimates?.selfCPU.annualizedDollarCost,
|
||||
(row) =>
|
||||
shouldUseLegacyCo2Calculation
|
||||
? row.impactEstimates?.totalCPU.annualizedDollarCost
|
||||
: row.totalAnnualCostUSD,
|
||||
sortDirection
|
||||
);
|
||||
default:
|
||||
return orderBy(rows, sortField, sortDirection);
|
||||
}
|
||||
}, [rows, sortDirection, sortField]);
|
||||
}, [rows, shouldUseLegacyCo2Calculation, sortDirection, sortField]);
|
||||
|
||||
const { columns, leadingControlColumns } = useMemo(() => {
|
||||
const gridColumns: EuiDataGridColumn[] = [
|
||||
|
@ -255,7 +273,11 @@ export const TopNFunctionsGrid = forwardRef(
|
|||
headerCellRender() {
|
||||
return (
|
||||
<EuiScreenReaderOnly>
|
||||
<span>Controls</span>
|
||||
<span>
|
||||
{i18n.translate('xpack.profiling.topNFunctionsGrid.span.controlsLabel', {
|
||||
defaultMessage: 'Controls',
|
||||
})}
|
||||
</span>
|
||||
</EuiScreenReaderOnly>
|
||||
);
|
||||
},
|
||||
|
@ -267,7 +289,10 @@ export const TopNFunctionsGrid = forwardRef(
|
|||
return (
|
||||
<EuiButtonIcon
|
||||
data-test-subj="profilingTopNFunctionsGridButton"
|
||||
aria-label="Show actions"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.profiling.topNFunctionsGrid.euiButtonIcon.showActionsLabel',
|
||||
{ defaultMessage: 'Show actions' }
|
||||
)}
|
||||
iconType="expand"
|
||||
color="text"
|
||||
onClick={handleOnClick}
|
||||
|
@ -306,7 +331,10 @@ export const TopNFunctionsGrid = forwardRef(
|
|||
<EuiDataGrid
|
||||
data-test-subj={dataTestSubj}
|
||||
ref={ref}
|
||||
aria-label="TopN functions"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.profiling.topNFunctionsGrid.euiDataGrid.topNFunctionsLabel',
|
||||
{ defaultMessage: 'TopN functions' }
|
||||
)}
|
||||
columns={columns}
|
||||
columnVisibility={{ visibleColumns, setVisibleColumns }}
|
||||
rowCount={sortedRows.length > 100 ? 100 : sortedRows.length}
|
||||
|
@ -348,6 +376,10 @@ export const TopNFunctionsGrid = forwardRef(
|
|||
functionName: selectedRow.frame.FunctionName,
|
||||
sourceFileName: selectedRow.frame.SourceFilename,
|
||||
sourceLine: selectedRow.frame.SourceLine,
|
||||
selfAnnualCO2Kgs: selectedRow.selfAnnualCO2kgs,
|
||||
totalAnnualCO2Kgs: selectedRow.totalAnnualCO2kgs,
|
||||
selfAnnualCostUSD: selectedRow.selfAnnualCostUSD,
|
||||
totalAnnualCostUSD: selectedRow.totalAnnualCostUSD,
|
||||
}}
|
||||
totalSeconds={totalSeconds}
|
||||
totalSamples={totalCount}
|
||||
|
|
|
@ -42,6 +42,10 @@ export interface IFunctionRow {
|
|||
selfCPUPerc: number;
|
||||
totalCPUPerc: number;
|
||||
impactEstimates?: ImpactEstimates;
|
||||
selfAnnualCO2kgs: number;
|
||||
selfAnnualCostUSD: number;
|
||||
totalAnnualCO2kgs: number;
|
||||
totalAnnualCostUSD: number;
|
||||
diff?: {
|
||||
rank: number;
|
||||
samples: number;
|
||||
|
@ -50,6 +54,10 @@ export interface IFunctionRow {
|
|||
selfCPUPerc: number;
|
||||
totalCPUPerc: number;
|
||||
impactEstimates?: ImpactEstimates;
|
||||
selfAnnualCO2kgs: number;
|
||||
selfAnnualCostUSD: number;
|
||||
totalAnnualCO2kgs: number;
|
||||
totalAnnualCostUSD: number;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -117,6 +125,10 @@ export function getFunctionsRows({
|
|||
totalCPUPerc:
|
||||
totalCPUPerc -
|
||||
(comparisonRow.CountInclusive / comparisonTopNFunctions.TotalCount) * 100,
|
||||
selfAnnualCO2kgs: comparisonRow.selfAnnualCO2kgs,
|
||||
selfAnnualCostUSD: comparisonRow.selfAnnualCostUSD,
|
||||
totalAnnualCO2kgs: comparisonRow.totalAnnualCO2kgs,
|
||||
totalAnnualCostUSD: comparisonRow.totalAnnualCostUSD,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +143,10 @@ export function getFunctionsRows({
|
|||
selfCPU: topN.CountExclusive,
|
||||
totalCPU: topN.CountInclusive,
|
||||
impactEstimates,
|
||||
selfAnnualCO2kgs: topN.selfAnnualCO2kgs,
|
||||
selfAnnualCostUSD: topN.selfAnnualCostUSD,
|
||||
totalAnnualCO2kgs: topN.totalAnnualCO2kgs,
|
||||
totalAnnualCostUSD: topN.totalAnnualCostUSD,
|
||||
diff: calculateDiff(),
|
||||
};
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import { useProfilingDependencies } from '../components/contexts/profiling_depen
|
|||
import {
|
||||
profilingCo2PerKWH,
|
||||
profilingDatacenterPUE,
|
||||
profilingPerCoreWatt,
|
||||
profilingPervCPUWattX86,
|
||||
} from '@kbn/observability-plugin/common';
|
||||
|
||||
jest.mock('../components/contexts/profiling_dependencies/use_profiling_dependencies');
|
||||
|
@ -21,7 +21,7 @@ describe('useCalculateImpactEstimate', () => {
|
|||
core: {
|
||||
uiSettings: {
|
||||
get: (key: string) => {
|
||||
if (key === profilingPerCoreWatt) {
|
||||
if (key === profilingPervCPUWattX86) {
|
||||
return 7;
|
||||
}
|
||||
if (key === profilingCo2PerKWH) {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import {
|
||||
profilingCo2PerKWH,
|
||||
profilingDatacenterPUE,
|
||||
profilingPerCoreWatt,
|
||||
profilingPervCPUWattX86,
|
||||
} from '@kbn/observability-plugin/common';
|
||||
import { useProfilingDependencies } from '../components/contexts/profiling_dependencies/use_profiling_dependencies';
|
||||
|
||||
|
@ -22,7 +22,7 @@ interface Params {
|
|||
export type CalculateImpactEstimates = ReturnType<typeof useCalculateImpactEstimate>;
|
||||
export type ImpactEstimates = ReturnType<CalculateImpactEstimates>;
|
||||
|
||||
const ANNUAL_SECONDS = 60 * 60 * 24 * 365;
|
||||
export const ANNUAL_SECONDS = 60 * 60 * 24 * 365;
|
||||
|
||||
// The cost of an x86 CPU core per hour, in US$.
|
||||
// (ARM is 60% less based graviton 3 data, see https://aws.amazon.com/ec2/graviton/)
|
||||
|
@ -33,7 +33,7 @@ export function useCalculateImpactEstimate() {
|
|||
start: { core },
|
||||
} = useProfilingDependencies();
|
||||
|
||||
const perCoreWatts = core.uiSettings.get<number>(profilingPerCoreWatt);
|
||||
const perCoreWatts = core.uiSettings.get<number>(profilingPervCPUWattX86);
|
||||
const co2PerTonKWH = core.uiSettings.get<number>(profilingCo2PerKWH);
|
||||
const datacenterPUE = core.uiSettings.get<number>(profilingDatacenterPUE);
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { asWeight } from './as_weight';
|
||||
|
||||
describe('asWeight', () => {
|
||||
it('should correctly convert and format weight in pounds to kilograms', () => {
|
||||
const valueInPounds = 150;
|
||||
expect(asWeight(valueInPounds, 'lbs')).toBe('150 lbs / 68.04 kg');
|
||||
});
|
||||
|
||||
it('should correctly convert and format weight in kilograms to pounds', () => {
|
||||
const valueInKilograms = 75;
|
||||
expect(asWeight(valueInKilograms, 'kgs')).toBe('165.35 lbs / 75 kg');
|
||||
});
|
||||
|
||||
it('should handle NaN input', () => {
|
||||
expect(asWeight(NaN, 'lbs')).toBe('N/A lbs / N/A kg');
|
||||
});
|
||||
|
||||
it('should handle zero input', () => {
|
||||
expect(asWeight(0, 'kgs')).toBe('0 lbs / 0 kg');
|
||||
});
|
||||
|
||||
it('should format very small values in pounds as "~0.00"', () => {
|
||||
expect(asWeight(0.0001, 'lbs')).toBe('~0.00 lbs / ~0.00 kg');
|
||||
});
|
||||
});
|
|
@ -8,10 +8,16 @@
|
|||
import { asNumber } from './as_number';
|
||||
|
||||
const ONE_POUND_TO_A_KILO = 0.45359237;
|
||||
const ONE_KILO_TO_A_POUND = 2.20462;
|
||||
|
||||
export function asWeight(valueInPounds: number): string {
|
||||
const lbs = asNumber(valueInPounds);
|
||||
const kgs = asNumber(Number(valueInPounds * ONE_POUND_TO_A_KILO));
|
||||
export function asWeight(value: number, unit: 'kgs' | 'lbs'): string {
|
||||
const formattedValue = asNumber(value);
|
||||
|
||||
return `${lbs} lbs / ${kgs} kg`;
|
||||
if (unit === 'lbs') {
|
||||
const kgs = asNumber(Number(value * ONE_POUND_TO_A_KILO));
|
||||
return `${formattedValue} lbs / ${kgs} kg`;
|
||||
}
|
||||
|
||||
const lbs = asNumber(Number(value * ONE_KILO_TO_A_POUND));
|
||||
return `${lbs} lbs / ${formattedValue} kg`;
|
||||
}
|
||||
|
|
|
@ -131,6 +131,8 @@ export function DifferentialFlameGraphsView() {
|
|||
totalCPU: state.data.primaryFlamegraph.TotalCPU,
|
||||
totalCount: state.data.primaryFlamegraph.TotalSamples,
|
||||
scaleFactor: isNormalizedByTime ? baselineTime : baseline,
|
||||
totalAnnualCO2Kgs: state.data.primaryFlamegraph.TotalAnnualCO2Kgs,
|
||||
totalAnnualCostUSD: state.data.primaryFlamegraph.TotalAnnualCostsUSD,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
@ -142,6 +144,8 @@ export function DifferentialFlameGraphsView() {
|
|||
totalCPU: state.data.comparisonFlamegraph.TotalCPU,
|
||||
totalCount: state.data.comparisonFlamegraph.TotalSamples,
|
||||
scaleFactor: isNormalizedByTime ? comparisonTime : comparison,
|
||||
totalAnnualCO2Kgs: state.data.comparisonFlamegraph.TotalAnnualCO2Kgs,
|
||||
totalAnnualCostUSD: state.data.comparisonFlamegraph.TotalAnnualCostsUSD,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
|
|
@ -180,6 +180,8 @@ export function DifferentialTopNFunctionsView() {
|
|||
totalCount: state.data.TotalCount,
|
||||
totalCPU: state.data.totalCPU,
|
||||
scaleFactor: isNormalizedByTime ? baselineTime : baseline,
|
||||
totalAnnualCO2Kgs: state.data.totalAnnualCO2Kgs,
|
||||
totalAnnualCostUSD: state.data.totalAnnualCostUSD,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
@ -191,6 +193,8 @@ export function DifferentialTopNFunctionsView() {
|
|||
totalCount: comparisonState.data.TotalCount,
|
||||
totalCPU: comparisonState.data.totalCPU,
|
||||
scaleFactor: isNormalizedByTime ? comparisonTime : comparison,
|
||||
totalAnnualCO2Kgs: comparisonState.data.totalAnnualCO2Kgs,
|
||||
totalAnnualCostUSD: comparisonState.data.totalAnnualCostUSD,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
|
|
@ -5,13 +5,26 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { LazyField } from '@kbn/advanced-settings-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
profilingCo2PerKWH,
|
||||
profilingDatacenterPUE,
|
||||
profilingPerCoreWatt,
|
||||
profilingPervCPUWattX86,
|
||||
profilingPervCPUWattArm64,
|
||||
profilingAWSCostDiscountRate,
|
||||
profilingCostPervCPUPerHour,
|
||||
} from '@kbn/observability-plugin/common';
|
||||
import { useEditableSettings, useUiTracker } from '@kbn/observability-shared-plugin/public';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
@ -20,7 +33,13 @@ import { useProfilingDependencies } from '../../components/contexts/profiling_de
|
|||
import { ProfilingAppPageTemplate } from '../../components/profiling_app_page_template';
|
||||
import { BottomBarActions } from './bottom_bar_actions';
|
||||
|
||||
const settingKeys = [profilingCo2PerKWH, profilingDatacenterPUE, profilingPerCoreWatt];
|
||||
const co2Settings = [
|
||||
profilingCo2PerKWH,
|
||||
profilingDatacenterPUE,
|
||||
profilingPervCPUWattX86,
|
||||
profilingPervCPUWattArm64,
|
||||
];
|
||||
const costSettings = [profilingAWSCostDiscountRate, profilingCostPervCPUPerHour];
|
||||
|
||||
export function Settings() {
|
||||
const trackProfilingEvent = useUiTracker({ app: 'profiling' });
|
||||
|
@ -37,7 +56,7 @@ export function Settings() {
|
|||
saveAll,
|
||||
isSaving,
|
||||
cleanUnsavedChanges,
|
||||
} = useEditableSettings('profiling', settingKeys);
|
||||
} = useEditableSettings('profiling', [...co2Settings, ...costSettings]);
|
||||
|
||||
async function handleSave() {
|
||||
try {
|
||||
|
@ -71,33 +90,136 @@ export function Settings() {
|
|||
</EuiText>
|
||||
</EuiTitle>
|
||||
<EuiSpacer />
|
||||
<EuiPanel grow={false} hasShadow={false} hasBorder paddingSize="none">
|
||||
<EuiPanel color="subdued" hasShadow={false}>
|
||||
<EuiTitle size="s">
|
||||
<EuiText>
|
||||
{i18n.translate('xpack.profiling.settings.co2Sections', {
|
||||
defaultMessage: 'CO2',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiTitle>
|
||||
</EuiPanel>
|
||||
<EuiPanel hasShadow={false}>
|
||||
{settingKeys.map((settingKey) => {
|
||||
const editableConfig = settingsEditableConfig[settingKey];
|
||||
return (
|
||||
<LazyField
|
||||
key={settingKey}
|
||||
setting={editableConfig}
|
||||
handleChange={handleFieldChange}
|
||||
enableSaving
|
||||
docLinks={docLinks.links}
|
||||
toasts={notifications.toasts}
|
||||
unsavedChanges={unsavedChanges[settingKey]}
|
||||
{[
|
||||
{
|
||||
label: i18n.translate('xpack.profiling.settings.co2Section', {
|
||||
defaultMessage: 'Custom CO2 settings',
|
||||
}),
|
||||
description: {
|
||||
title: i18n.translate('xpack.profiling.settings.co2.title', {
|
||||
defaultMessage:
|
||||
'The Universal Profiling host agent can detect if your machine is running on AWS, Azure, or Google Cloud Platform.',
|
||||
}),
|
||||
subtitle: (
|
||||
<FormattedMessage
|
||||
id="xpack.profiling.settings.co2.subtitle"
|
||||
defaultMessage="For machines running on AWS, Universal Profiling applies the appropriate {regionalCarbonIntensityLink} for your instance's AWS region and the current AWS data center {pue}."
|
||||
values={{
|
||||
regionalCarbonIntensityLink: (
|
||||
<EuiLink
|
||||
data-test-subj="profilingSettingsLink"
|
||||
href="https://ela.st/grid-datasheet"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.translate('xpack.profiling.settings.co2.subtitle.link', {
|
||||
defaultMessage: 'regional carbon intensity',
|
||||
})}
|
||||
</EuiLink>
|
||||
),
|
||||
pue: (
|
||||
<strong>
|
||||
{i18n.translate('xpack.profiling.settings.co2.subtitle.pue', {
|
||||
defaultMessage: 'PUE',
|
||||
})}
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</EuiPanel>
|
||||
</EuiPanel>
|
||||
),
|
||||
text: i18n.translate('xpack.profiling.settings.co2.text', {
|
||||
defaultMessage:
|
||||
'For all other configurations, Universal Profiling uses the following default configurations. You can update these configurations as needed.',
|
||||
}),
|
||||
},
|
||||
settings: co2Settings,
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.profiling.settings.costSection', {
|
||||
defaultMessage: 'Custom cost settings',
|
||||
}),
|
||||
description: {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.profiling.settings.cost.title"
|
||||
defaultMessage="Universal Profiling sets the cost for AWS configurations using the {awsPriceList} for your EC2 instance type."
|
||||
values={{
|
||||
awsPriceList: (
|
||||
<EuiLink
|
||||
data-test-subj="profilingSettingsLink"
|
||||
href="https://docs.aws.amazon.com/aws-cost-management/latest/APIReference/Welcome.html#Welcome_AWS_Price_List_Service"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.translate('xpack.profiling.settings.cost.subtitle.link', {
|
||||
defaultMessage: 'AWS price list',
|
||||
})}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
settings: costSettings,
|
||||
},
|
||||
].map((item) => (
|
||||
<>
|
||||
<EuiPanel key={item.label} grow={false} hasShadow={false} hasBorder paddingSize="none">
|
||||
<EuiPanel color="subdued" hasShadow={false}>
|
||||
<EuiTitle size="s">
|
||||
<EuiText>{item.label}</EuiText>
|
||||
</EuiTitle>
|
||||
</EuiPanel>
|
||||
<EuiPanel hasShadow={false}>
|
||||
{item.description ? (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="iInCircle" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
{item.description.title && (
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">{item.description.title}</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{item.description.subtitle && (
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">{item.description.subtitle}</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{item.description.text && (
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">
|
||||
<strong>{item.description.text}</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
) : null}
|
||||
{item.settings.map((settingKey) => {
|
||||
const editableConfig = settingsEditableConfig[settingKey];
|
||||
return (
|
||||
<LazyField
|
||||
key={settingKey}
|
||||
setting={editableConfig}
|
||||
handleChange={handleFieldChange}
|
||||
enableSaving
|
||||
docLinks={docLinks.links}
|
||||
toasts={notifications.toasts}
|
||||
unsavedChanges={unsavedChanges[settingKey]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</EuiPanel>
|
||||
</EuiPanel>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
))}
|
||||
|
||||
{!isEmpty(unsavedChanges) && (
|
||||
<BottomBarActions
|
||||
isLoading={isSaving}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { profilingUseLegacyFlamegraphAPI } from '@kbn/observability-plugin/common';
|
||||
import { RouteRegisterParameters } from '.';
|
||||
import { getRoutePaths } from '../../common';
|
||||
import { handleRouteHandlerError } from '../utils/handle_route_error_handler';
|
||||
|
@ -34,18 +33,17 @@ export function registerFlameChartSearchRoute({
|
|||
},
|
||||
async (context, request, response) => {
|
||||
const { timeFrom, timeTo, kuery } = request.query;
|
||||
const useLegacyFlamegraphAPI = await (
|
||||
await context.core
|
||||
).uiSettings.client.get<boolean>(profilingUseLegacyFlamegraphAPI);
|
||||
|
||||
const core = await context.core;
|
||||
|
||||
try {
|
||||
const esClient = await getClient(context);
|
||||
const flamegraph = await profilingDataAccess.services.fetchFlamechartData({
|
||||
core,
|
||||
esClient,
|
||||
rangeFromMs: timeFrom,
|
||||
rangeToMs: timeTo,
|
||||
kuery,
|
||||
useLegacyFlamegraphAPI,
|
||||
});
|
||||
|
||||
return response.ok({ body: flamegraph });
|
||||
|
|
|
@ -37,9 +37,12 @@ export function registerTopNFunctionsSearchRoute({
|
|||
},
|
||||
async (context, request, response) => {
|
||||
try {
|
||||
const core = await context.core;
|
||||
|
||||
const { timeFrom, timeTo, startIndex, endIndex, kuery }: QuerySchemaType = request.query;
|
||||
const esClient = await getClient(context);
|
||||
const topNFunctions = await profilingDataAccess.services.fetchFunction({
|
||||
core,
|
||||
esClient,
|
||||
rangeFromMs: timeFrom,
|
||||
rangeToMs: timeTo,
|
||||
|
|
|
@ -12,12 +12,18 @@ export async function searchStackTraces({
|
|||
client,
|
||||
filter,
|
||||
sampleSize,
|
||||
durationSeconds,
|
||||
}: {
|
||||
client: ProfilingESClient;
|
||||
filter: ProjectTimeQuery;
|
||||
sampleSize: number;
|
||||
durationSeconds: number;
|
||||
}) {
|
||||
const response = await client.profilingStacktraces({ query: filter, sampleSize });
|
||||
const response = await client.profilingStacktraces({
|
||||
query: filter,
|
||||
sampleSize,
|
||||
durationSeconds,
|
||||
});
|
||||
|
||||
return decodeStackTraceResponse(response);
|
||||
}
|
||||
|
|
|
@ -129,10 +129,13 @@ export async function topNElasticSearchQuery({
|
|||
kuery: stackTraceKuery,
|
||||
});
|
||||
|
||||
const totalSeconds = timeTo - timeFrom;
|
||||
|
||||
return searchStackTraces({
|
||||
client,
|
||||
filter: stackTraceFilter,
|
||||
sampleSize: targetSampleSize,
|
||||
durationSeconds: totalSeconds,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -37,6 +37,7 @@ export interface ProfilingESClient {
|
|||
profilingStacktraces({}: {
|
||||
query: QueryDslQueryContainer;
|
||||
sampleSize: number;
|
||||
durationSeconds: number;
|
||||
}): Promise<StackTraceResponse>;
|
||||
profilingStatus(params?: { waitForResourcesCreated?: boolean }): Promise<ProfilingStatusResponse>;
|
||||
getEsClient(): ElasticsearchClient;
|
||||
|
@ -75,7 +76,7 @@ export function createProfilingEsClient({
|
|||
|
||||
return unwrapEsResponse(promise);
|
||||
},
|
||||
profilingStacktraces({ query, sampleSize }) {
|
||||
profilingStacktraces({ query, sampleSize, durationSeconds }) {
|
||||
const controller = new AbortController();
|
||||
|
||||
const promise = withProfilingSpan('_profiling/stacktraces', () => {
|
||||
|
@ -87,6 +88,7 @@ export function createProfilingEsClient({
|
|||
body: {
|
||||
query,
|
||||
sample_size: sampleSize,
|
||||
requested_duration: durationSeconds,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -22,11 +22,25 @@ export interface ProfilingESClient {
|
|||
profilingStacktraces({}: {
|
||||
query: QueryDslQueryContainer;
|
||||
sampleSize: number;
|
||||
durationSeconds: number;
|
||||
co2PerKWH?: number;
|
||||
datacenterPUE?: number;
|
||||
pervCPUWattX86?: number;
|
||||
pervCPUWattArm64?: number;
|
||||
awsCostDiscountRate?: number;
|
||||
costPervCPUPerHour?: number;
|
||||
}): Promise<StackTraceResponse>;
|
||||
profilingStatus(params?: { waitForResourcesCreated?: boolean }): Promise<ProfilingStatusResponse>;
|
||||
getEsClient(): ElasticsearchClient;
|
||||
profilingFlamegraph({}: {
|
||||
query: QueryDslQueryContainer;
|
||||
sampleSize: number;
|
||||
durationSeconds: number;
|
||||
co2PerKWH?: number;
|
||||
datacenterPUE?: number;
|
||||
pervCPUWattX86?: number;
|
||||
pervCPUWattArm64?: number;
|
||||
awsCostDiscountRate?: number;
|
||||
costPervCPUPerHour?: number;
|
||||
}): Promise<BaseFlameGraph>;
|
||||
}
|
||||
|
|
|
@ -5,62 +5,52 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ElasticsearchClient } from '@kbn/core/server';
|
||||
import { createBaseFlameGraph, createCalleeTree } from '@kbn/profiling-utils';
|
||||
import { CoreRequestHandlerContext, ElasticsearchClient } from '@kbn/core/server';
|
||||
import {
|
||||
profilingAWSCostDiscountRate,
|
||||
profilingCo2PerKWH,
|
||||
profilingCostPervCPUPerHour,
|
||||
profilingDatacenterPUE,
|
||||
profilingPervCPUWattArm64,
|
||||
profilingPervCPUWattX86,
|
||||
} from '@kbn/observability-plugin/common';
|
||||
import { percentToFactor } from '../../utils/percent_to_factor';
|
||||
import { kqlQuery } from '../../utils/query';
|
||||
import { withProfilingSpan } from '../../utils/with_profiling_span';
|
||||
import { RegisterServicesParams } from '../register_services';
|
||||
import { searchStackTraces } from '../search_stack_traces';
|
||||
|
||||
export interface FetchFlamechartParams {
|
||||
esClient: ElasticsearchClient;
|
||||
core: CoreRequestHandlerContext;
|
||||
rangeFromMs: number;
|
||||
rangeToMs: number;
|
||||
kuery: string;
|
||||
useLegacyFlamegraphAPI?: boolean;
|
||||
}
|
||||
|
||||
const targetSampleSize = 20000; // minimum number of samples to get statistically sound results
|
||||
|
||||
export function createFetchFlamechart({ createProfilingEsClient }: RegisterServicesParams) {
|
||||
return async ({
|
||||
esClient,
|
||||
rangeFromMs,
|
||||
rangeToMs,
|
||||
kuery,
|
||||
useLegacyFlamegraphAPI = false,
|
||||
}: FetchFlamechartParams) => {
|
||||
return async ({ core, esClient, rangeFromMs, rangeToMs, kuery }: FetchFlamechartParams) => {
|
||||
const rangeFromSecs = rangeFromMs / 1000;
|
||||
const rangeToSecs = rangeToMs / 1000;
|
||||
|
||||
const [
|
||||
co2PerKWH,
|
||||
datacenterPUE,
|
||||
pervCPUWattX86,
|
||||
pervCPUWattArm64,
|
||||
awsCostDiscountRate,
|
||||
costPervCPUPerHour,
|
||||
] = await Promise.all([
|
||||
core.uiSettings.client.get<number>(profilingCo2PerKWH),
|
||||
core.uiSettings.client.get<number>(profilingDatacenterPUE),
|
||||
core.uiSettings.client.get<number>(profilingPervCPUWattX86),
|
||||
core.uiSettings.client.get<number>(profilingPervCPUWattArm64),
|
||||
core.uiSettings.client.get<number>(profilingAWSCostDiscountRate),
|
||||
core.uiSettings.client.get<number>(profilingCostPervCPUPerHour),
|
||||
]);
|
||||
|
||||
const profilingEsClient = createProfilingEsClient({ esClient });
|
||||
|
||||
const totalSeconds = rangeToSecs - rangeFromSecs;
|
||||
// Use legacy stack traces API to fetch the flamegraph
|
||||
if (useLegacyFlamegraphAPI) {
|
||||
const { events, stackTraces, executables, stackFrames, totalFrames, samplingRate } =
|
||||
await searchStackTraces({
|
||||
client: profilingEsClient,
|
||||
rangeFrom: rangeFromSecs,
|
||||
rangeTo: rangeToSecs,
|
||||
kuery,
|
||||
sampleSize: targetSampleSize,
|
||||
});
|
||||
|
||||
return await withProfilingSpan('create_flamegraph', async () => {
|
||||
const tree = createCalleeTree(
|
||||
events,
|
||||
stackTraces,
|
||||
stackFrames,
|
||||
executables,
|
||||
totalFrames,
|
||||
samplingRate
|
||||
);
|
||||
|
||||
return createBaseFlameGraph(tree, samplingRate, totalSeconds);
|
||||
});
|
||||
}
|
||||
|
||||
const flamegraph = await profilingEsClient.profilingFlamegraph({
|
||||
query: {
|
||||
bool: {
|
||||
|
@ -79,6 +69,13 @@ export function createFetchFlamechart({ createProfilingEsClient }: RegisterServi
|
|||
},
|
||||
},
|
||||
sampleSize: targetSampleSize,
|
||||
durationSeconds: totalSeconds,
|
||||
co2PerKWH,
|
||||
datacenterPUE,
|
||||
pervCPUWattX86,
|
||||
pervCPUWattArm64,
|
||||
awsCostDiscountRate: percentToFactor(awsCostDiscountRate),
|
||||
costPervCPUPerHour,
|
||||
});
|
||||
return { ...flamegraph, TotalSeconds: totalSeconds };
|
||||
};
|
||||
|
|
|
@ -4,14 +4,23 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ElasticsearchClient } from '@kbn/core/server';
|
||||
import {
|
||||
profilingAWSCostDiscountRate,
|
||||
profilingCo2PerKWH,
|
||||
profilingCostPervCPUPerHour,
|
||||
profilingDatacenterPUE,
|
||||
profilingPervCPUWattArm64,
|
||||
profilingPervCPUWattX86,
|
||||
} from '@kbn/observability-plugin/common';
|
||||
import { CoreRequestHandlerContext, ElasticsearchClient } from '@kbn/core/server';
|
||||
import { createTopNFunctions } from '@kbn/profiling-utils';
|
||||
import { percentToFactor } from '../../utils/percent_to_factor';
|
||||
import { withProfilingSpan } from '../../utils/with_profiling_span';
|
||||
import { RegisterServicesParams } from '../register_services';
|
||||
import { searchStackTraces } from '../search_stack_traces';
|
||||
|
||||
export interface FetchFunctionsParams {
|
||||
core: CoreRequestHandlerContext;
|
||||
esClient: ElasticsearchClient;
|
||||
rangeFromMs: number;
|
||||
rangeToMs: number;
|
||||
|
@ -24,6 +33,7 @@ const targetSampleSize = 20000; // minimum number of samples to get statisticall
|
|||
|
||||
export function createFetchFunctions({ createProfilingEsClient }: RegisterServicesParams) {
|
||||
return async ({
|
||||
core,
|
||||
esClient,
|
||||
rangeFromMs,
|
||||
rangeToMs,
|
||||
|
@ -33,6 +43,23 @@ export function createFetchFunctions({ createProfilingEsClient }: RegisterServic
|
|||
}: FetchFunctionsParams) => {
|
||||
const rangeFromSecs = rangeFromMs / 1000;
|
||||
const rangeToSecs = rangeToMs / 1000;
|
||||
const totalSeconds = rangeToSecs - rangeFromSecs;
|
||||
|
||||
const [
|
||||
co2PerKWH,
|
||||
datacenterPUE,
|
||||
pervCPUWattX86,
|
||||
pervCPUWattArm64,
|
||||
awsCostDiscountRate,
|
||||
costPervCPUPerHour,
|
||||
] = await Promise.all([
|
||||
core.uiSettings.client.get<number>(profilingCo2PerKWH),
|
||||
core.uiSettings.client.get<number>(profilingDatacenterPUE),
|
||||
core.uiSettings.client.get<number>(profilingPervCPUWattX86),
|
||||
core.uiSettings.client.get<number>(profilingPervCPUWattArm64),
|
||||
core.uiSettings.client.get<number>(profilingAWSCostDiscountRate),
|
||||
core.uiSettings.client.get<number>(profilingCostPervCPUPerHour),
|
||||
]);
|
||||
|
||||
const profilingEsClient = createProfilingEsClient({ esClient });
|
||||
|
||||
|
@ -43,6 +70,13 @@ export function createFetchFunctions({ createProfilingEsClient }: RegisterServic
|
|||
rangeTo: rangeToSecs,
|
||||
kuery,
|
||||
sampleSize: targetSampleSize,
|
||||
durationSeconds: totalSeconds,
|
||||
co2PerKWH,
|
||||
datacenterPUE,
|
||||
pervCPUWattX86,
|
||||
pervCPUWattArm64,
|
||||
awsCostDiscountRate: percentToFactor(awsCostDiscountRate),
|
||||
costPervCPUPerHour,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -15,12 +15,26 @@ export async function searchStackTraces({
|
|||
rangeFrom,
|
||||
rangeTo,
|
||||
kuery,
|
||||
durationSeconds,
|
||||
co2PerKWH,
|
||||
datacenterPUE,
|
||||
pervCPUWattX86,
|
||||
pervCPUWattArm64,
|
||||
awsCostDiscountRate,
|
||||
costPervCPUPerHour,
|
||||
}: {
|
||||
client: ProfilingESClient;
|
||||
sampleSize: number;
|
||||
rangeFrom: number;
|
||||
rangeTo: number;
|
||||
kuery: string;
|
||||
durationSeconds: number;
|
||||
co2PerKWH: number;
|
||||
datacenterPUE: number;
|
||||
pervCPUWattX86: number;
|
||||
pervCPUWattArm64: number;
|
||||
awsCostDiscountRate: number;
|
||||
costPervCPUPerHour: number;
|
||||
}) {
|
||||
const response = await client.profilingStacktraces({
|
||||
query: {
|
||||
|
@ -41,6 +55,13 @@ export async function searchStackTraces({
|
|||
},
|
||||
},
|
||||
sampleSize,
|
||||
durationSeconds,
|
||||
co2PerKWH,
|
||||
datacenterPUE,
|
||||
pervCPUWattX86,
|
||||
pervCPUWattArm64,
|
||||
awsCostDiscountRate,
|
||||
costPervCPUPerHour,
|
||||
});
|
||||
|
||||
return decodeStackTraceResponse(response);
|
||||
|
|
|
@ -39,7 +39,17 @@ export function createProfilingEsClient({
|
|||
|
||||
return unwrapEsResponse(promise);
|
||||
},
|
||||
profilingStacktraces({ query, sampleSize }) {
|
||||
profilingStacktraces({
|
||||
query,
|
||||
sampleSize,
|
||||
durationSeconds,
|
||||
co2PerKWH,
|
||||
datacenterPUE,
|
||||
awsCostDiscountRate,
|
||||
costPervCPUPerHour,
|
||||
pervCPUWattArm64,
|
||||
pervCPUWattX86,
|
||||
}) {
|
||||
const controller = new AbortController();
|
||||
const promise = withProfilingSpan('_profiling/stacktraces', () => {
|
||||
return esClient.transport.request(
|
||||
|
@ -49,6 +59,13 @@ export function createProfilingEsClient({
|
|||
body: {
|
||||
query,
|
||||
sample_size: sampleSize,
|
||||
requested_duration: durationSeconds,
|
||||
co2_per_kwh: co2PerKWH,
|
||||
per_core_watt_x86: pervCPUWattX86,
|
||||
per_core_watt_arm64: pervCPUWattArm64,
|
||||
datacenter_pue: datacenterPUE,
|
||||
aws_cost_factor: awsCostDiscountRate,
|
||||
cost_per_core_hour: costPervCPUPerHour,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -83,7 +100,17 @@ export function createProfilingEsClient({
|
|||
getEsClient() {
|
||||
return esClient;
|
||||
},
|
||||
profilingFlamegraph({ query, sampleSize }) {
|
||||
profilingFlamegraph({
|
||||
query,
|
||||
sampleSize,
|
||||
durationSeconds,
|
||||
co2PerKWH,
|
||||
datacenterPUE,
|
||||
awsCostDiscountRate,
|
||||
costPervCPUPerHour,
|
||||
pervCPUWattArm64,
|
||||
pervCPUWattX86,
|
||||
}) {
|
||||
const controller = new AbortController();
|
||||
|
||||
const promise = withProfilingSpan('_profiling/flamegraph', () => {
|
||||
|
@ -94,6 +121,13 @@ export function createProfilingEsClient({
|
|||
body: {
|
||||
query,
|
||||
sample_size: sampleSize,
|
||||
requested_duration: durationSeconds,
|
||||
co2_per_kwh: co2PerKWH,
|
||||
per_core_watt_x86: pervCPUWattX86,
|
||||
per_core_watt_arm64: pervCPUWattArm64,
|
||||
datacenter_pue: datacenterPUE,
|
||||
aws_cost_factor: awsCostDiscountRate,
|
||||
cost_per_core_hour: costPervCPUPerHour,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { percentToFactor } from './percent_to_factor'; // Replace 'yourFile' with the actual file path
|
||||
|
||||
describe('percentToFactor function', () => {
|
||||
it('should convert 6% to factor 0.94', () => {
|
||||
expect(percentToFactor(6)).toBe(0.94);
|
||||
});
|
||||
|
||||
it('should convert 0% to factor 1', () => {
|
||||
expect(percentToFactor(0)).toBe(1);
|
||||
});
|
||||
|
||||
it('should convert 100% to factor 0', () => {
|
||||
expect(percentToFactor(100)).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle negative input, convert -10% to factor 1.1', () => {
|
||||
expect(percentToFactor(-10)).toBe(1.1);
|
||||
});
|
||||
|
||||
it('should handle decimal input, convert 3.5% to factor 0.965', () => {
|
||||
expect(percentToFactor(3.5)).toBe(0.965);
|
||||
});
|
||||
|
||||
it('should handle large input, convert 1000% to factor -9', () => {
|
||||
expect(percentToFactor(1000)).toBe(-9);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const percentToFactor = (percent: number) => 1 - percent / 100;
|
|
@ -21,5 +21,6 @@
|
|||
"@kbn/fleet-plugin",
|
||||
"@kbn/cloud-plugin",
|
||||
"@kbn/spaces-plugin",
|
||||
"@kbn/observability-plugin",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -29313,9 +29313,6 @@
|
|||
"xpack.observability.profilingDatacenterPUEUiSettingDescription": "L'indicateur d'efficacité énergétique (PUE) des centres de données mesure l'efficacité de l'utilisation de l'énergie par les centres de données. La valeur par défaut de 1,7 correspond au PUE moyen sur site d'un centre de données selon l'enquête {uptimeLink} \n </br></br>\n Vous pouvez également utiliser le PUE qui correspond à votre fournisseur cloud : \n <ul style=\"list-style-type: none;margin-left: 4px;\">\n <li><strong>AWS :</strong> 1,135</li>\n <li><strong>Google Cloud :</strong> 1,1</li>\n <li><strong>Azure :</strong> 1,185</li>\n </ul>\n ",
|
||||
"xpack.observability.profilingDatacenterPUEUiSettingDescription.uptimeLink": "Uptime Institute",
|
||||
"xpack.observability.profilingDatacenterPUEUiSettingName": "PUE des centres de données",
|
||||
"xpack.observability.profilingPerCoreWattUiSettingDescription": "Consommation d'énergie moyenne amortie par cœur (avec une utilisation du CPU à 100 %).",
|
||||
"xpack.observability.profilingPerCoreWattUiSettingName": "Watts par cœur",
|
||||
"xpack.observability.profilingUseLegacyFlamegraphAPI": "Utiliser l'API héritée flame-graph dans Universal Profiling",
|
||||
"xpack.observability.resources.documentation": "Documentation",
|
||||
"xpack.observability.resources.forum": "Forum de discussion",
|
||||
"xpack.observability.resources.quick_start": "Vidéos de démarrage rapide",
|
||||
|
@ -30236,7 +30233,6 @@
|
|||
"xpack.profiling.notAvailableLabel": "S. O.",
|
||||
"xpack.profiling.privilegesWarningDescription": "Des problèmes de privilèges empêchent la vérification du statut d'Universal Profiling. Si vous rencontrez des problèmes ou si les données ne chargent pas, n'hésitez pas à faire appel à votre administrateur.",
|
||||
"xpack.profiling.privilegesWarningTitle": "Limitation des privilèges utilisateur",
|
||||
"xpack.profiling.settings.co2Sections": "CO2",
|
||||
"xpack.profiling.settings.save.error": "Une erreur s'est produite lors de l'enregistrement des paramètres",
|
||||
"xpack.profiling.settings.saveButton": "Enregistrer les modifications",
|
||||
"xpack.profiling.settings.title": "Paramètres avancés",
|
||||
|
|
|
@ -29313,9 +29313,6 @@
|
|||
"xpack.observability.profilingDatacenterPUEUiSettingDescription": "データセンターの電力使用効率(PUE)は、データセンターがいかに効率的にエネルギーを使用しているかを測定します。デフォルトは、{uptimeLink}調査によるオンプレミスデータセンターの平均PUEである1.7です \n </br></br>\n また、クラウドプロバイダーに対応するPUEを使用することもできます。 \n <ul style=\"list-style-type: none;margin-left: 4px;\">\n <li><strong>AWS:</strong>1.135</li>\n <li><strong>GCP:</strong>1.1</li>\n <li><strong>Azure:</strong>1.185</li>\n </ul>\n ",
|
||||
"xpack.observability.profilingDatacenterPUEUiSettingDescription.uptimeLink": "Uptime Institute",
|
||||
"xpack.observability.profilingDatacenterPUEUiSettingName": "データセンターPUE",
|
||||
"xpack.observability.profilingPerCoreWattUiSettingDescription": "コア単位の平均償却消費電力(CPU使用率100%に基づく)。",
|
||||
"xpack.observability.profilingPerCoreWattUiSettingName": "コア単位のワット数",
|
||||
"xpack.observability.profilingUseLegacyFlamegraphAPI": "ユニバーサルプロファイリングでレガシーFlamegraph APIを使用",
|
||||
"xpack.observability.resources.documentation": "ドキュメント",
|
||||
"xpack.observability.resources.forum": "ディスカッションフォーラム",
|
||||
"xpack.observability.resources.quick_start": "クイックスタートビデオ",
|
||||
|
@ -30236,7 +30233,6 @@
|
|||
"xpack.profiling.notAvailableLabel": "N/A",
|
||||
"xpack.profiling.privilegesWarningDescription": "特権の問題により、ユニバーサルプロファイリングのステータスを確認できませんでした。問題が発生した場合、またはデータの読み込みに失敗した場合は、管理者にお問い合わせください。",
|
||||
"xpack.profiling.privilegesWarningTitle": "ユーザー特権の制限",
|
||||
"xpack.profiling.settings.co2Sections": "CO2",
|
||||
"xpack.profiling.settings.save.error": "設定の保存中にエラーが発生しました",
|
||||
"xpack.profiling.settings.saveButton": "変更を保存",
|
||||
"xpack.profiling.settings.title": "高度な設定",
|
||||
|
|
|
@ -29310,9 +29310,6 @@
|
|||
"xpack.observability.profilingDatacenterPUEUiSettingDescription": "数据中心电源使用效率 (PUE) 衡量数据中心的能源利用效率。根据 {uptimeLink} 调查,本地数据中心的平均 PUE 默认为 1.7 \n </br></br>\n 您还可以使用与您的云服务提供商对应的 PUE: \n <ul style=\"list-style-type: none;margin-left: 4px;\">\n <li><strong>AWS:</strong>1.135</li>\n <li><strong>GCP:</strong>1.1</li>\n <li><strong>Azure:</strong>1.185</li>\n </ul>\n ",
|
||||
"xpack.observability.profilingDatacenterPUEUiSettingDescription.uptimeLink": "正常运行时间协会",
|
||||
"xpack.observability.profilingDatacenterPUEUiSettingName": "数据中心 PUE",
|
||||
"xpack.observability.profilingPerCoreWattUiSettingDescription": "平均分摊每核功耗(基于 100% 的 CPU 利用率)。",
|
||||
"xpack.observability.profilingPerCoreWattUiSettingName": "每核功耗(瓦)",
|
||||
"xpack.observability.profilingUseLegacyFlamegraphAPI": "在 Universal Profiling 中使用旧版 Flamegraph API",
|
||||
"xpack.observability.resources.documentation": "文档",
|
||||
"xpack.observability.resources.forum": "讨论论坛",
|
||||
"xpack.observability.resources.quick_start": "快速入门视频",
|
||||
|
@ -30233,7 +30230,6 @@
|
|||
"xpack.profiling.notAvailableLabel": "不可用",
|
||||
"xpack.profiling.privilegesWarningDescription": "由于权限问题,无法查看 Universal Profiling 状态。如果遇到任何问题或无法加载数据,请联系管理员获取帮助。",
|
||||
"xpack.profiling.privilegesWarningTitle": "用户权限限制",
|
||||
"xpack.profiling.settings.co2Sections": "CO2",
|
||||
"xpack.profiling.settings.save.error": "保存设置时出错",
|
||||
"xpack.profiling.settings.saveButton": "保存更改",
|
||||
"xpack.profiling.settings.title": "高级设置",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue