From 12695646cfa9f35392f5338133319a16c88d7a38 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cau=C3=AA=20Marcondes?=
<55978943+cauemarcondes@users.noreply.github.com>
Date: Sat, 30 Sep 2023 10:25:55 +0100
Subject: [PATCH] [Profiling] New settings to control CO2 calculation (#166637)
- Added new Profiling settings so users can customize the CO2 variables
- Fixed Embeddable components to also read the new settings
- Moved code from APM to obs-shared to create the custom settings page
in Profiling.
- New Settings Page was created in Profiling UI so users can easily find
the settings:
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../server/collectors/management/schema.ts | 12 ++
.../server/collectors/management/types.ts | 3 +
src/plugins/telemetry/schema/oss_plugins.json | 18 +++
.../app/settings/general_settings/index.tsx | 11 +-
.../labs/labs_flyout.tsx | 11 +-
x-pack/plugins/observability/common/index.ts | 3 +
.../observability/common/ui_settings_keys.ts | 3 +
.../observability/server/ui_settings.ts | 67 ++++++++++
.../plugins/observability_shared/kibana.jsonc | 2 +-
.../public/hooks/use_editable_settings.tsx} | 13 +-
.../observability_shared/public/index.ts | 1 +
.../observability_shared/tsconfig.json | 1 +
.../calculate_impact_estimates/index.ts | 85 -------------
.../differential_functions.cy.ts | 45 ++-----
.../e2e/profiling_views/functions.cy.ts | 94 +++++++++++---
.../e2e/profiling_views/settings.cy.ts | 58 +++++++++
.../profiling/e2e/cypress/support/commands.ts | 14 +++
.../profiling/e2e/cypress/support/types.d.ts | 1 +
x-pack/plugins/profiling/e2e/tsconfig.json | 1 +
x-pack/plugins/profiling/kibana.jsonc | 3 +-
.../flamegraph/flamegraph_tooltip.tsx | 3 +-
.../public/components/flamegraph/index.tsx | 1 -
.../get_impact_rows.tsx | 4 +-
.../frame_information_window/index.tsx | 13 +-
.../profiling_header_action_menu.tsx | 5 +
.../components/topn_functions/index.tsx | 4 +
.../public/components/topn_functions/utils.ts | 13 +-
.../topn_functions_summary/index.tsx | 5 +-
.../flamegraph/embeddable_flamegraph.tsx | 35 ++++--
.../embeddable_flamegraph_factory.ts | 12 +-
.../functions/embeddable_functions.tsx | 26 +++-
.../functions/embeddable_functions_factory.ts | 6 +-
.../profiling_embeddable_provider.tsx | 56 +++++++++
.../embeddables/register_embeddables.ts | 16 ++-
.../use_calculate_impact_estimates.test.ts} | 36 +++++-
.../hooks/use_calculate_impact_estimates.ts | 91 ++++++++++++++
x-pack/plugins/profiling/public/plugin.tsx | 22 +++-
.../profiling/public/routing/index.tsx | 13 ++
.../views/settings/bottom_bar_actions.tsx | 83 +++++++++++++
.../profiling/public/views/settings/index.tsx | 115 ++++++++++++++++++
x-pack/plugins/profiling/tsconfig.json | 3 +-
41 files changed, 816 insertions(+), 192 deletions(-)
rename x-pack/plugins/{apm/public/hooks/use_apm_editable_settings.tsx => observability_shared/public/hooks/use_editable_settings.tsx} (87%)
delete mode 100644 x-pack/plugins/profiling/common/calculate_impact_estimates/index.ts
create mode 100644 x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/settings.cy.ts
create mode 100644 x-pack/plugins/profiling/public/embeddables/profiling_embeddable_provider.tsx
rename x-pack/plugins/profiling/{common/calculate_impact_estimates/calculate_impact_estimates.test.ts => public/hooks/use_calculate_impact_estimates.test.ts} (69%)
create mode 100644 x-pack/plugins/profiling/public/hooks/use_calculate_impact_estimates.ts
create mode 100644 x-pack/plugins/profiling/public/views/settings/bottom_bar_actions.tsx
create mode 100644 x-pack/plugins/profiling/public/views/settings/index.tsx
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
index 0f3db5fc2bba..47309833c7b1 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
@@ -557,6 +557,18 @@ export const stackManagementSchema: MakeSchemaFrom = {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
+ 'observability:profilingPerCoreWatt': {
+ type: 'integer',
+ _meta: { description: 'Non-default value of setting.' },
+ },
+ 'observability:profilingCo2PerKWH': {
+ type: 'integer',
+ _meta: { description: 'Non-default value of setting.' },
+ },
+ 'observability:profilingDatacenterPUE': {
+ type: 'integer',
+ _meta: { description: 'Non-default value of setting.' },
+ },
'observability:apmEnableCriticalPath': {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
index fb3c31bf44d8..b48fa13280e4 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
@@ -155,4 +155,7 @@ export interface UsageStats {
'securitySolution:enableGroupedNav': boolean;
'securitySolution:showRelatedIntegrations': boolean;
'visualization:visualize:legacyGaugeChartsLibrary': boolean;
+ 'observability:profilingPerCoreWatt': number;
+ 'observability:profilingCo2PerKWH': number;
+ 'observability:profilingDatacenterPUE': number;
}
diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json
index e2013ff091c7..da1ed4d1e657 100644
--- a/src/plugins/telemetry/schema/oss_plugins.json
+++ b/src/plugins/telemetry/schema/oss_plugins.json
@@ -10025,6 +10025,24 @@
"description": "Non-default value of setting."
}
},
+ "observability:profilingPerCoreWatt": {
+ "type": "integer",
+ "_meta": {
+ "description": "Non-default value of setting."
+ }
+ },
+ "observability:profilingCo2PerKWH": {
+ "type": "integer",
+ "_meta": {
+ "description": "Non-default value of setting."
+ }
+ },
+ "observability:profilingDatacenterPUE": {
+ "type": "integer",
+ "_meta": {
+ "description": "Non-default value of setting."
+ }
+ },
"observability:apmEnableCriticalPath": {
"type": "boolean",
"_meta": {
diff --git a/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx b/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx
index 52ede89eefba..8926e2155592 100644
--- a/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx
@@ -23,8 +23,11 @@ import {
} from '@kbn/observability-plugin/common';
import { isEmpty } from 'lodash';
import React from 'react';
+import {
+ useEditableSettings,
+ useUiTracker,
+} from '@kbn/observability-shared-plugin/public';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
-import { useApmEditableSettings } from '../../../../hooks/use_apm_editable_settings';
import { BottomBarActions } from '../bottom_bar_actions';
const apmSettingsKeys = [
@@ -42,6 +45,7 @@ const apmSettingsKeys = [
];
export function GeneralSettings() {
+ const trackApmEvent = useUiTracker({ app: 'apm' });
const { docLinks, notifications } = useApmPluginContext().core;
const {
handleFieldChange,
@@ -50,14 +54,15 @@ export function GeneralSettings() {
saveAll,
isSaving,
cleanUnsavedChanges,
- } = useApmEditableSettings(apmSettingsKeys);
+ } = useEditableSettings('apm', apmSettingsKeys);
async function handleSave() {
try {
const reloadPage = Object.keys(unsavedChanges).some((key) => {
return settingsEditableConfig[key].requiresPageReload;
});
- await saveAll({ trackMetricName: 'general_settings_save' });
+ await saveAll();
+ trackApmEvent({ metric: 'general_settings_save' });
if (reloadPage) {
window.location.reload();
}
diff --git a/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/labs/labs_flyout.tsx b/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/labs/labs_flyout.tsx
index d5c151d30518..6ccac9340486 100644
--- a/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/labs/labs_flyout.tsx
+++ b/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/labs/labs_flyout.tsx
@@ -24,8 +24,11 @@ import {
import { LazyField } from '@kbn/advanced-settings-plugin/public';
import { i18n } from '@kbn/i18n';
import React from 'react';
+import {
+ useEditableSettings,
+ useUiTracker,
+} from '@kbn/observability-shared-plugin/public';
import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context';
-import { useApmEditableSettings } from '../../../../../hooks/use_apm_editable_settings';
import { useFetcher, isPending } from '../../../../../hooks/use_fetcher';
interface Props {
@@ -33,6 +36,7 @@ interface Props {
}
export function LabsFlyout({ onClose }: Props) {
+ const trackApmEvent = useUiTracker({ app: 'apm' });
const { docLinks, notifications } = useApmPluginContext().core;
const { data, status } = useFetcher(
@@ -48,7 +52,7 @@ export function LabsFlyout({ onClose }: Props) {
saveAll,
isSaving,
cleanUnsavedChanges,
- } = useApmEditableSettings(labsItems);
+ } = useEditableSettings('apm', labsItems);
async function handleSave() {
try {
@@ -56,7 +60,8 @@ export function LabsFlyout({ onClose }: Props) {
return settingsEditableConfig[key].requiresPageReload;
});
- await saveAll({ trackMetricName: 'labs_save' });
+ await saveAll();
+ trackApmEvent({ metric: 'labs_save' });
if (reloadPage) {
window.location.reload();
diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts
index a90240d95e05..51a92af4b47b 100644
--- a/x-pack/plugins/observability/common/index.ts
+++ b/x-pack/plugins/observability/common/index.ts
@@ -41,6 +41,9 @@ export {
enableCriticalPath,
syntheticsThrottlingEnabled,
apmEnableProfilingIntegration,
+ profilingCo2PerKWH,
+ profilingDatacenterPUE,
+ profilingPerCoreWatt,
} from './ui_settings_keys';
export {
diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts
index 183e9f41030c..46f83d7e9c95 100644
--- a/x-pack/plugins/observability/common/ui_settings_keys.ts
+++ b/x-pack/plugins/observability/common/ui_settings_keys.ts
@@ -27,3 +27,6 @@ export const apmEnableContinuousRollups = 'observability:apmEnableContinuousRoll
export const syntheticsThrottlingEnabled = 'observability:syntheticsThrottlingEnabled';
export const enableLegacyUptimeApp = 'observability:enableLegacyUptimeApp';
export const apmEnableProfilingIntegration = 'observability:apmEnableProfilingIntegration';
+export const profilingPerCoreWatt = 'observability:profilingPerCoreWatt';
+export const profilingCo2PerKWH = 'observability:profilingCo2PerKWH';
+export const profilingDatacenterPUE = 'observability:profilingDatacenterPUE';
diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts
index 39678758bd7e..4bc87fba7949 100644
--- a/x-pack/plugins/observability/server/ui_settings.ts
+++ b/x-pack/plugins/observability/server/ui_settings.ts
@@ -30,6 +30,9 @@ import {
syntheticsThrottlingEnabled,
enableLegacyUptimeApp,
apmEnableProfilingIntegration,
+ profilingCo2PerKWH,
+ profilingDatacenterPUE,
+ profilingPerCoreWatt,
} from '../common/ui_settings_keys';
const betaLabel = i18n.translate('xpack.observability.uiSettings.betaLabel', {
@@ -374,6 +377,70 @@ export const uiSettings: Record = {
schema: schema.boolean(),
requiresPageReload: false,
},
+ [profilingPerCoreWatt]: {
+ category: [observabilityFeatureId],
+ name: i18n.translate('xpack.observability.profilingPerCoreWattUiSettingName', {
+ defaultMessage: 'Per Core Watts',
+ }),
+ value: 7,
+ description: i18n.translate('xpack.observability.profilingPerCoreWattUiSettingDescription', {
+ defaultMessage: `The average amortized per-core power consumption (based on 100% CPU utilization).`,
+ }),
+ schema: schema.number({ min: 0 }),
+ requiresPageReload: false,
+ },
+ [profilingDatacenterPUE]: {
+ category: [observabilityFeatureId],
+ name: i18n.translate('xpack.observability.profilingDatacenterPUEUiSettingName', {
+ defaultMessage: 'Data Center PUE',
+ }),
+ value: 1.7,
+ description: i18n.translate('xpack.observability.profilingDatacenterPUEUiSettingDescription', {
+ defaultMessage: `Data center power usage effectiveness (PUE) measures how efficiently a data center uses energy. Defaults to 1.7, the average on-premise data center PUE according to the {uptimeLink} survey
+
+ You can also use the PUE that corresponds with your cloud provider:
+
+ - AWS: 1.135
+ - GCP: 1.1
+ - Azure: 1.185
+
+ `,
+ values: {
+ uptimeLink:
+ '' +
+ i18n.translate(
+ 'xpack.observability.profilingDatacenterPUEUiSettingDescription.uptimeLink',
+ { defaultMessage: 'Uptime Institute' }
+ ) +
+ '',
+ },
+ }),
+ schema: schema.number({ min: 0 }),
+ requiresPageReload: false,
+ },
+ [profilingCo2PerKWH]: {
+ category: [observabilityFeatureId],
+ name: i18n.translate('xpack.observability.profilingCo2PerKWHUiSettingName', {
+ defaultMessage: 'Regional Carbon Intensity (ton/kWh)',
+ }),
+ value: 0.000379069,
+ description: i18n.translate('xpack.observability.profilingCo2PerKWHUiSettingDescription', {
+ defaultMessage: `Carbon intensity measures how clean your data center electricity is.
+ Specifically, it measures the average amount of CO2 emitted per kilowatt-hour (kWh) of electricity consumed in a particular region.
+ Use the cloud carbon footprint {datasheetLink} to update this value according to your region. Defaults to US East (N. Virginia).`,
+ values: {
+ datasheetLink:
+ '' +
+ i18n.translate(
+ 'xpack.observability.profilingCo2PerKWHUiSettingDescription.datasheetLink',
+ { defaultMessage: 'datasheet' }
+ ) +
+ '',
+ },
+ }),
+ schema: schema.number({ min: 0 }),
+ requiresPageReload: false,
+ },
};
function throttlingDocsLink({ href }: { href: string }) {
diff --git a/x-pack/plugins/observability_shared/kibana.jsonc b/x-pack/plugins/observability_shared/kibana.jsonc
index e1d5b14d2393..1948fca972d2 100644
--- a/x-pack/plugins/observability_shared/kibana.jsonc
+++ b/x-pack/plugins/observability_shared/kibana.jsonc
@@ -9,7 +9,7 @@
"configPath": ["xpack", "observability_shared"],
"requiredPlugins": ["cases", "guidedOnboarding", "uiActions", "embeddable", "share"],
"optionalPlugins": [],
- "requiredBundles": ["data", "inspector", "kibanaReact", "kibanaUtils"],
+ "requiredBundles": ["data", "inspector", "kibanaReact", "kibanaUtils", "advancedSettings"],
"extraPublicDirs": ["common"]
}
}
diff --git a/x-pack/plugins/apm/public/hooks/use_apm_editable_settings.tsx b/x-pack/plugins/observability_shared/public/hooks/use_editable_settings.tsx
similarity index 87%
rename from x-pack/plugins/apm/public/hooks/use_apm_editable_settings.tsx
rename to x-pack/plugins/observability_shared/public/hooks/use_editable_settings.tsx
index 3ee7447eb741..460bb50f57bc 100644
--- a/x-pack/plugins/apm/public/hooks/use_apm_editable_settings.tsx
+++ b/x-pack/plugins/observability_shared/public/hooks/use_editable_settings.tsx
@@ -10,7 +10,7 @@ import { FieldState } from '@kbn/advanced-settings-plugin/public';
import { toEditableConfig } from '@kbn/advanced-settings-plugin/public';
import { IUiSettingsClient } from '@kbn/core/public';
import { isEmpty } from 'lodash';
-import { useUiTracker } from '@kbn/observability-shared-plugin/public';
+import { ObservabilityApp } from '../../typings/common';
function getEditableConfig({
settingsKeys,
@@ -41,15 +41,13 @@ function getEditableConfig({
return config;
}
-export function useApmEditableSettings(settingsKeys: string[]) {
+export function useEditableSettings(app: ObservabilityApp, settingsKeys: string[]) {
const { services } = useKibana();
- const trackApmEvent = useUiTracker({ app: 'apm' });
+
const { uiSettings } = services;
const [isSaving, setIsSaving] = useState(false);
const [forceReloadSettings, setForceReloadSettings] = useState(0);
- const [unsavedChanges, setUnsavedChanges] = useState<
- Record
- >({});
+ const [unsavedChanges, setUnsavedChanges] = useState>({});
const settingsEditableConfig = useMemo(
() => {
@@ -78,7 +76,7 @@ export function useApmEditableSettings(settingsKeys: string[]) {
setUnsavedChanges({});
}
- async function saveAll({ trackMetricName }: { trackMetricName: string }) {
+ async function saveAll() {
if (uiSettings && !isEmpty(unsavedChanges)) {
try {
setIsSaving(true);
@@ -87,7 +85,6 @@ export function useApmEditableSettings(settingsKeys: string[]) {
);
await Promise.all(arr);
- trackApmEvent({ metric: trackMetricName });
setForceReloadSettings((state) => ++state);
cleanUnsavedChanges();
} finally {
diff --git a/x-pack/plugins/observability_shared/public/index.ts b/x-pack/plugins/observability_shared/public/index.ts
index a35cdce8f85e..66492328e4b8 100644
--- a/x-pack/plugins/observability_shared/public/index.ts
+++ b/x-pack/plugins/observability_shared/public/index.ts
@@ -43,6 +43,7 @@ export type { AddInspectorRequest } from './contexts/inspector/inspector_context
export { useInspectorContext } from './contexts/inspector/use_inspector_context';
export { useTheme } from './hooks/use_theme';
+export { useEditableSettings } from './hooks/use_editable_settings';
export { useEsSearch, createEsParams } from './hooks/use_es_search';
export { useFetcher, FETCH_STATUS } from './hooks/use_fetcher';
export type { FetcherResult } from './hooks/use_fetcher';
diff --git a/x-pack/plugins/observability_shared/tsconfig.json b/x-pack/plugins/observability_shared/tsconfig.json
index ee7efc921b6a..cecfb33c9386 100644
--- a/x-pack/plugins/observability_shared/tsconfig.json
+++ b/x-pack/plugins/observability_shared/tsconfig.json
@@ -34,6 +34,7 @@
"@kbn/shared-ux-router",
"@kbn/embeddable-plugin",
"@kbn/profiling-utils",
+ "@kbn/advanced-settings-plugin",
"@kbn/utility-types",
"@kbn/share-plugin"
],
diff --git a/x-pack/plugins/profiling/common/calculate_impact_estimates/index.ts b/x-pack/plugins/profiling/common/calculate_impact_estimates/index.ts
deleted file mode 100644
index 6ebbe26a9fb4..000000000000
--- a/x-pack/plugins/profiling/common/calculate_impact_estimates/index.ts
+++ /dev/null
@@ -1,85 +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.
- */
-
-const ANNUAL_SECONDS = 60 * 60 * 24 * 365;
-
-// The assumed amortized per-core average power consumption (based on 100% CPU Utilization).
-// Reference: https://www.cloudcarbonfootprint.org/docs/methodology/#appendix-i-energy-coefficients
-const PER_CORE_WATT = 7;
-
-// The assumed CO2 emissions in kg per kWh (the reference uses metric tons/kWh).
-// This value represents "regional carbon intensity" and it defaults to AWS us-east-1.
-// Reference: https://www.cloudcarbonfootprint.org/docs/methodology/#appendix-v-grid-emissions-factors
-const CO2_PER_KWH = 0.379069;
-
-// The assumed PUE of the datacenter (1.7 is likely to be an on-prem value).
-const DATACENTER_PUE = 1.7;
-
-// 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/)
-const CORE_COST_PER_HOUR = 0.0425;
-
-export function calculateImpactEstimates({
- countInclusive,
- countExclusive,
- totalSamples,
- totalSeconds,
-}: {
- countInclusive: number;
- countExclusive: number;
- totalSamples: number;
- totalSeconds: number;
-}) {
- return {
- totalSamples: calculateImpact({
- samples: totalSamples,
- totalSamples,
- totalSeconds,
- }),
- totalCPU: calculateImpact({
- samples: countInclusive,
- totalSamples,
- totalSeconds,
- }),
- selfCPU: calculateImpact({
- samples: countExclusive,
- totalSamples,
- totalSeconds,
- }),
- };
-}
-
-function calculateImpact({
- samples,
- totalSamples,
- totalSeconds,
-}: {
- samples: number;
- totalSamples: number;
- totalSeconds: number;
-}) {
- const annualizedScaleUp = ANNUAL_SECONDS / totalSeconds;
- const totalCoreSeconds = totalSamples / 20;
- const percentage = samples / totalSamples;
- const coreSeconds = totalCoreSeconds * percentage;
- const annualizedCoreSeconds = coreSeconds * annualizedScaleUp;
- const coreHours = coreSeconds / (60 * 60);
- const co2 = ((PER_CORE_WATT * coreHours) / 1000.0) * CO2_PER_KWH * DATACENTER_PUE;
- const annualizedCo2 = co2 * annualizedScaleUp;
- const dollarCost = coreHours * CORE_COST_PER_HOUR;
- const annualizedDollarCost = dollarCost * annualizedScaleUp;
-
- return {
- percentage,
- coreSeconds,
- annualizedCoreSeconds,
- co2,
- annualizedCo2,
- dollarCost,
- annualizedDollarCost,
- };
-}
diff --git a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/differential_functions.cy.ts b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/differential_functions.cy.ts
index b1a2df79e3dc..bbbcb6c8abdc 100644
--- a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/differential_functions.cy.ts
+++ b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/differential_functions.cy.ts
@@ -172,6 +172,13 @@ describe('Differential Functions page', () => {
'have.length.gt',
1
);
+ cy.get(
+ '[data-test-subj="topNFunctionsGrid"] [data-test-subj="profilingStackFrameSummaryLink"]'
+ ).contains('vmlinux');
+ cy.get(
+ '[data-test-subj="TopNFunctionsComparisonGrid"] [data-test-subj="profilingStackFrameSummaryLink"]'
+ ).contains('vmlinux');
+
cy.addKqlFilter({
key: 'process.thread.name',
value: '108795321966692',
@@ -183,38 +190,12 @@ describe('Differential Functions page', () => {
});
cy.wait('@getTopNFunctions');
cy.wait('@getTopNFunctions');
- cy.get('[data-test-subj="topNFunctionsGrid"] .euiDataGridRow').should('have.length', 2);
- cy.get('[data-test-subj="TopNFunctionsComparisonGrid"] .euiDataGridRow').should(
- 'have.length',
- 1
- );
- [
- { id: 'overallPerformance', value: '50.00%', icon: 'sortUp_success' },
- {
- id: 'annualizedCo2',
- value: '0.13 lbs / 0.06 kg',
- comparisonValue: '0.07 lbs / 0.03 kg (50.00%)',
- icon: 'comparison_sortUp_success',
- },
- {
- id: 'annualizedCost',
- value: '$1.24',
- comparisonValue: '$0.62 (50.00%)',
- icon: 'comparison_sortUp_success',
- },
- {
- id: 'totalNumberOfSamples',
- value: '2',
- comparisonValue: '1 (50.00%)',
- icon: 'comparison_sortUp_success',
- },
- ].forEach((item) => {
- cy.get(`[data-test-subj="${item.id}_value"]`).contains(item.value);
- cy.get(`[data-test-subj="${item.id}_${item.icon}"]`).should('exist');
- if (item.comparisonValue) {
- cy.get(`[data-test-subj="${item.id}_comparison_value"]`).contains(item.comparisonValue);
- }
- });
+ cy.get(
+ '[data-test-subj="topNFunctionsGrid"] [data-test-subj="profilingStackFrameSummaryLink"]'
+ ).contains('libsystemd-shared-237.so');
+ cy.get(
+ '[data-test-subj="TopNFunctionsComparisonGrid"] [data-test-subj="profilingStackFrameSummaryLink"]'
+ ).contains('libjvm.so');
});
});
});
diff --git a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts
index f928db33e2f1..8b0a66180c7a 100644
--- a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts
+++ b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts
@@ -4,6 +4,11 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+import {
+ profilingCo2PerKWH,
+ profilingDatacenterPUE,
+ profilingPerCoreWatt,
+} from '@kbn/observability-plugin/common';
describe('Functions page', () => {
const rangeFrom = '2023-04-18T00:00:00.000Z';
@@ -80,11 +85,10 @@ describe('Functions page', () => {
cy.intercept('GET', '/internal/profiling/topn/functions?*').as('getTopNFunctions');
cy.visitKibana('/app/profiling/functions', { rangeFrom, rangeTo });
cy.wait('@getTopNFunctions');
- cy.get('.euiDataGridRow').should('have.length.gt', 1);
+ const firstRowSelector = '[data-grid-row-index="0"] [data-test-subj="dataGridRowCell"]';
+ cy.get(firstRowSelector).eq(2).contains('vmlinux');
cy.addKqlFilter({ key: 'Stacktrace.id', value: '-7DvnP1mizQYw8mIIpgbMg' });
cy.wait('@getTopNFunctions');
- cy.get('.euiDataGridRow').should('have.length', 1);
- const firstRowSelector = '[data-grid-row-index="0"] [data-test-subj="dataGridRowCell"]';
cy.get(firstRowSelector).eq(2).contains('libjvm.so');
});
@@ -96,50 +100,50 @@ describe('Functions page', () => {
{
columnKey: 'rank',
columnIndex: 1,
- highRank: 388,
+ highRank: 4481,
lowRank: 1,
- highValue: 388,
+ highValue: 4481,
lowValue: 1,
},
{
columnKey: 'samples',
columnIndex: 7,
highRank: 1,
- lowRank: 44,
+ lowRank: 389,
highValue: 28,
- lowValue: 1,
+ lowValue: 0,
},
{
columnKey: 'selfCPU',
columnIndex: 3,
highRank: 1,
- lowRank: 44,
+ lowRank: 389,
highValue: '5.46%',
- lowValue: '0.19%',
+ lowValue: '0.00%',
},
{
columnKey: 'totalCPU',
columnIndex: 4,
- highRank: 338,
+ highRank: 3623,
lowRank: 44,
- highValue: '10.33%',
+ highValue: '60.43%',
lowValue: '0.19%',
},
{
columnKey: 'annualizedCo2',
columnIndex: 5,
highRank: 1,
- lowRank: 44,
+ lowRank: 389,
highValue: '1.84 lbs / 0.84 kg',
- lowValue: '0.07 lbs / 0.03 kg',
+ lowValue: undefined,
},
{
columnKey: 'annualizedDollarCost',
columnIndex: 6,
highRank: 1,
- lowRank: 44,
+ lowRank: 389,
highValue: '$17.37',
- lowValue: '$0.62',
+ lowValue: undefined,
},
].forEach(({ columnKey, columnIndex, highRank, highValue, lowRank, lowValue }) => {
cy.get(`[data-test-subj="dataGridHeaderCell-${columnKey}"]`).click();
@@ -151,7 +155,11 @@ describe('Functions page', () => {
cy.get(`[data-test-subj="dataGridHeaderCell-${columnKey}"]`).click();
cy.contains('Sort Low-High').click();
cy.get(firstRowSelector).eq(1).contains(lowRank);
- cy.get(firstRowSelector).eq(columnIndex).contains(lowValue);
+ if (lowValue !== undefined) {
+ cy.get(firstRowSelector).eq(columnIndex).contains(lowValue);
+ } else {
+ cy.get(firstRowSelector).eq(columnIndex).should('not.have.value');
+ }
});
cy.get(`[data-test-subj="dataGridHeaderCell-frame"]`).click();
@@ -165,4 +173,58 @@ describe('Functions page', () => {
cy.get(firstRowSelector).eq(1).contains('371');
cy.get(firstRowSelector).eq(2).contains('/');
});
+
+ describe('Test changing CO2 settings', () => {
+ afterEach(() => {
+ cy.updateAdvancedSettings({
+ [profilingCo2PerKWH]: 0.000379069,
+ [profilingDatacenterPUE]: 1.7,
+ [profilingPerCoreWatt]: 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 });
+ cy.wait('@getTopNFunctions');
+ const firstRowSelector = '[data-grid-row-index="0"] [data-test-subj="dataGridRowCell"]';
+ cy.get(firstRowSelector).eq(1).contains('1');
+ cy.get(firstRowSelector).eq(2).contains('vmlinux');
+ cy.get(firstRowSelector).eq(5).contains('1.84 lbs / 0.84 kg');
+ cy.contains('Settings').click();
+ cy.contains('Advanced Settings');
+ cy.get(`[data-test-subj="advancedSetting-editField-${profilingCo2PerKWH}"]`)
+ .clear()
+ .type('0.12345');
+ cy.get(`[data-test-subj="advancedSetting-editField-${profilingDatacenterPUE}"]`)
+ .clear()
+ .type('2.4');
+ cy.get(`[data-test-subj="advancedSetting-editField-${profilingPerCoreWatt}"]`)
+ .clear()
+ .type('20');
+ cy.contains('Save changes').click();
+ cy.go('back');
+ cy.wait('@getTopNFunctions');
+ cy.get(firstRowSelector).eq(5).contains('24.22k lbs / 10.99k');
+ const firstRowSelectorActionButton =
+ '[data-grid-row-index="0"] [data-test-subj="dataGridRowCell"] .euiButtonIcon';
+ cy.get(firstRowSelectorActionButton).click();
+ [
+ { parentKey: 'impactEstimates', key: 'co2Emission', value: '0.02 lbs / 0.01 kg' },
+ { parentKey: 'impactEstimates', key: 'selfCo2Emission', value: '0.02 lbs / 0.01 kg' },
+ {
+ parentKey: 'impactEstimates',
+ key: 'annualizedCo2Emission',
+ value: '24.22k lbs / 10.99k kg',
+ },
+ {
+ parentKey: 'impactEstimates',
+ key: 'annualizedSelfCo2Emission',
+ value: '24.22k lbs / 10.99k kg',
+ },
+ ].forEach(({ parentKey, key, value }) => {
+ cy.get(`[data-test-subj="${parentKey}_${key}"]`).contains(value);
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/settings.cy.ts b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/settings.cy.ts
new file mode 100644
index 000000000000..4863afbca437
--- /dev/null
+++ b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/settings.cy.ts
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+/*
+ * 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 {
+ profilingCo2PerKWH,
+ profilingDatacenterPUE,
+ profilingPerCoreWatt,
+} from '@kbn/observability-plugin/common';
+
+describe('Settings page', () => {
+ beforeEach(() => {
+ cy.loginAsElastic();
+ });
+
+ afterEach(() => {
+ cy.updateAdvancedSettings({
+ [profilingCo2PerKWH]: 0.000379069,
+ [profilingDatacenterPUE]: 1.7,
+ [profilingPerCoreWatt]: 7,
+ });
+ });
+
+ it('opens setting page', () => {
+ cy.visitKibana('/app/profiling/settings');
+ cy.contains('Advanced Settings');
+ cy.contains('CO2');
+ cy.contains('Regional Carbon Intensity (ton/kWh)');
+ cy.contains('Data Center PUE');
+ cy.contains('Per Core Watts');
+ });
+
+ it('updates values', () => {
+ cy.visitKibana('/app/profiling/settings');
+ cy.contains('Advanced Settings');
+ cy.get('[data-test-subj="profilingBottomBarActions"]').should('not.exist');
+ cy.get(`[data-test-subj="advancedSetting-editField-${profilingCo2PerKWH}"]`)
+ .clear()
+ .type('0.12345');
+ cy.get(`[data-test-subj="advancedSetting-editField-${profilingDatacenterPUE}"]`)
+ .clear()
+ .type('2.4');
+ cy.get(`[data-test-subj="advancedSetting-editField-${profilingPerCoreWatt}"]`)
+ .clear()
+ .type('20');
+ cy.get('[data-test-subj="profilingBottomBarActions"]').should('exist');
+ cy.contains('Save changes').click();
+ cy.get('[data-test-subj="profilingBottomBarActions"]').should('not.exist');
+ });
+});
diff --git a/x-pack/plugins/profiling/e2e/cypress/support/commands.ts b/x-pack/plugins/profiling/e2e/cypress/support/commands.ts
index 9a01caf46a8f..ada8681d308d 100644
--- a/x-pack/plugins/profiling/e2e/cypress/support/commands.ts
+++ b/x-pack/plugins/profiling/e2e/cypress/support/commands.ts
@@ -71,3 +71,17 @@ Cypress.Commands.add(
cy.getByTestSubj(dataTestSubj).type('{enter}');
}
);
+
+Cypress.Commands.add('updateAdvancedSettings', (settings: Record) => {
+ const kibanaUrl = Cypress.env('KIBANA_URL');
+ cy.request({
+ log: false,
+ method: 'POST',
+ url: `${kibanaUrl}/internal/kibana/settings`,
+ body: { changes: settings },
+ headers: {
+ 'kbn-xsrf': 'e2e_test',
+ },
+ auth: { user: 'elastic', pass: 'changeme' },
+ });
+});
diff --git a/x-pack/plugins/profiling/e2e/cypress/support/types.d.ts b/x-pack/plugins/profiling/e2e/cypress/support/types.d.ts
index 67bbc4509dc2..d83cce31db87 100644
--- a/x-pack/plugins/profiling/e2e/cypress/support/types.d.ts
+++ b/x-pack/plugins/profiling/e2e/cypress/support/types.d.ts
@@ -27,5 +27,6 @@ declare namespace Cypress {
dataTestSubj?: 'profilingUnifiedSearchBar' | 'profilingComparisonUnifiedSearchBar';
waitForSuggestion?: boolean;
}): void;
+ updateAdvancedSettings(settings: Record): void;
}
}
diff --git a/x-pack/plugins/profiling/e2e/tsconfig.json b/x-pack/plugins/profiling/e2e/tsconfig.json
index c4de3fff85cf..ed7aaac66702 100644
--- a/x-pack/plugins/profiling/e2e/tsconfig.json
+++ b/x-pack/plugins/profiling/e2e/tsconfig.json
@@ -22,5 +22,6 @@
"@kbn/test",
"@kbn/dev-utils",
"@kbn/cypress-config",
+ "@kbn/observability-plugin",
]
}
diff --git a/x-pack/plugins/profiling/kibana.jsonc b/x-pack/plugins/profiling/kibana.jsonc
index 6ebaf5e99832..1eae495f8c85 100644
--- a/x-pack/plugins/profiling/kibana.jsonc
+++ b/x-pack/plugins/profiling/kibana.jsonc
@@ -30,7 +30,8 @@
"requiredBundles": [
"kibanaReact",
"kibanaUtils",
- "observabilityAIAssistant"
+ "observabilityAIAssistant",
+ "advancedSettings"
]
}
}
diff --git a/x-pack/plugins/profiling/public/components/flamegraph/flamegraph_tooltip.tsx b/x-pack/plugins/profiling/public/components/flamegraph/flamegraph_tooltip.tsx
index 7a3b661cdac6..9d00900c1e19 100644
--- a/x-pack/plugins/profiling/public/components/flamegraph/flamegraph_tooltip.tsx
+++ b/x-pack/plugins/profiling/public/components/flamegraph/flamegraph_tooltip.tsx
@@ -18,12 +18,12 @@ import {
import { i18n } from '@kbn/i18n';
import { isNumber } from 'lodash';
import React from 'react';
-import { calculateImpactEstimates } from '../../../common/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 { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_estimates';
interface Props {
isRoot: boolean;
@@ -57,6 +57,7 @@ export function FlameGraphTooltip({
onShowMoreClick,
}: Props) {
const theme = useEuiTheme();
+ const calculateImpactEstimates = useCalculateImpactEstimate();
const impactEstimates = calculateImpactEstimates({
countExclusive,
diff --git a/x-pack/plugins/profiling/public/components/flamegraph/index.tsx b/x-pack/plugins/profiling/public/components/flamegraph/index.tsx
index 76105907f8d5..6da7062b9b38 100644
--- a/x-pack/plugins/profiling/public/components/flamegraph/index.tsx
+++ b/x-pack/plugins/profiling/public/components/flamegraph/index.tsx
@@ -194,7 +194,6 @@ export function FlameGraph({
frame={selected}
totalSeconds={primaryFlamegraph?.TotalSeconds ?? 0}
totalSamples={totalSamples}
- showAIAssistant={!isEmbedded}
showSymbolsStatus={!isEmbedded}
/>
)}
diff --git a/x-pack/plugins/profiling/public/components/frame_information_window/get_impact_rows.tsx b/x-pack/plugins/profiling/public/components/frame_information_window/get_impact_rows.tsx
index 59c5eb0086c7..f0510b2d07b4 100644
--- a/x-pack/plugins/profiling/public/components/frame_information_window/get_impact_rows.tsx
+++ b/x-pack/plugins/profiling/public/components/frame_information_window/get_impact_rows.tsx
@@ -7,24 +7,26 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
-import { calculateImpactEstimates } from '../../../common/calculate_impact_estimates';
import { asCost } from '../../utils/formatters/as_cost';
import { asDuration } from '../../utils/formatters/as_duration';
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';
export function getImpactRows({
countInclusive,
countExclusive,
totalSamples,
totalSeconds,
+ calculateImpactEstimates,
}: {
countInclusive: number;
countExclusive: number;
totalSamples: number;
totalSeconds: number;
+ calculateImpactEstimates: CalculateImpactEstimates;
}) {
const { selfCPU, totalCPU } = calculateImpactEstimates({
countInclusive,
diff --git a/x-pack/plugins/profiling/public/components/frame_information_window/index.tsx b/x-pack/plugins/profiling/public/components/frame_information_window/index.tsx
index 856e30001bec..c5333d147787 100644
--- a/x-pack/plugins/profiling/public/components/frame_information_window/index.tsx
+++ b/x-pack/plugins/profiling/public/components/frame_information_window/index.tsx
@@ -14,6 +14,7 @@ import { getImpactRows } from './get_impact_rows';
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';
export interface Frame {
fileID: string;
@@ -39,9 +40,10 @@ export function FrameInformationWindow({
frame,
totalSamples,
totalSeconds,
- showAIAssistant = true,
showSymbolsStatus = true,
}: Props) {
+ const calculateImpactEstimates = useCalculateImpactEstimate();
+
if (!frame) {
return (
@@ -87,6 +89,7 @@ export function FrameInformationWindow({
countExclusive,
totalSamples,
totalSeconds,
+ calculateImpactEstimates,
});
return (
@@ -95,11 +98,9 @@ export function FrameInformationWindow({
- {showAIAssistant ? (
-
-
-
- ) : null}
+
+
+
{showSymbolsStatus && symbolStatus !== FrameSymbolStatus.SYMBOLIZED ? (
diff --git a/x-pack/plugins/profiling/public/components/profiling_header_action_menu.tsx b/x-pack/plugins/profiling/public/components/profiling_header_action_menu.tsx
index eb50e1b3ea94..2a730a7ffce7 100644
--- a/x-pack/plugins/profiling/public/components/profiling_header_action_menu.tsx
+++ b/x-pack/plugins/profiling/public/components/profiling_header_action_menu.tsx
@@ -76,6 +76,11 @@ export function ProfilingHeaderActionMenu() {
+
+ {i18n.translate('xpack.profiling.headerActionMenu.settings', {
+ defaultMessage: 'Settings',
+ })}
+
);
diff --git a/x-pack/plugins/profiling/public/components/topn_functions/index.tsx b/x-pack/plugins/profiling/public/components/topn_functions/index.tsx
index e1318471913d..90fd422e8d46 100644
--- a/x-pack/plugins/profiling/public/components/topn_functions/index.tsx
+++ b/x-pack/plugins/profiling/public/components/topn_functions/index.tsx
@@ -26,6 +26,7 @@ import { FrameInformationTooltip } from '../frame_information_window/frame_infor
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;
@@ -70,6 +71,7 @@ export const TopNFunctionsGrid = forwardRef(
) => {
const [selectedRow, setSelectedRow] = useState();
const trackProfilingEvent = useUiTracker({ app: 'profiling' });
+ const calculateImpactEstimates = useCalculateImpactEstimate();
function onSort(newSortingColumns: EuiDataGridSorting['columns']) {
const lastItem = last(newSortingColumns);
@@ -93,9 +95,11 @@ export const TopNFunctionsGrid = forwardRef(
comparisonTopNFunctions,
topNFunctions,
totalSeconds,
+ calculateImpactEstimates,
});
}, [
baselineScaleFactor,
+ calculateImpactEstimates,
comparisonScaleFactor,
comparisonTopNFunctions,
topNFunctions,
diff --git a/x-pack/plugins/profiling/public/components/topn_functions/utils.ts b/x-pack/plugins/profiling/public/components/topn_functions/utils.ts
index 5257b41b5ade..788c7397fa79 100644
--- a/x-pack/plugins/profiling/public/components/topn_functions/utils.ts
+++ b/x-pack/plugins/profiling/public/components/topn_functions/utils.ts
@@ -6,7 +6,10 @@
*/
import { keyBy } from 'lodash';
import type { StackFrameMetadata, TopNFunctions } from '@kbn/profiling-utils';
-import { calculateImpactEstimates } from '../../../common/calculate_impact_estimates';
+import {
+ CalculateImpactEstimates,
+ ImpactEstimates,
+} from '../../hooks/use_calculate_impact_estimates';
export function getColorLabel(percent: number) {
if (percent === 0) {
@@ -37,7 +40,7 @@ export interface IFunctionRow {
totalCPU: number;
selfCPUPerc: number;
totalCPUPerc: number;
- impactEstimates?: ReturnType;
+ impactEstimates?: ImpactEstimates;
diff?: {
rank: number;
samples: number;
@@ -45,7 +48,7 @@ export interface IFunctionRow {
totalCPU: number;
selfCPUPerc: number;
totalCPUPerc: number;
- impactEstimates?: ReturnType;
+ impactEstimates?: ImpactEstimates;
};
}
@@ -55,12 +58,14 @@ export function getFunctionsRows({
comparisonTopNFunctions,
topNFunctions,
totalSeconds,
+ calculateImpactEstimates,
}: {
baselineScaleFactor?: number;
comparisonScaleFactor?: number;
comparisonTopNFunctions?: TopNFunctions;
topNFunctions?: TopNFunctions;
totalSeconds: number;
+ calculateImpactEstimates: CalculateImpactEstimates;
}): IFunctionRow[] {
if (!topNFunctions || !topNFunctions.TotalCount || topNFunctions.TotalCount === 0) {
return [];
@@ -70,7 +75,7 @@ export function getFunctionsRows({
? keyBy(comparisonTopNFunctions.TopN, 'Id')
: {};
- return topNFunctions.TopN.filter((topN) => topN.CountExclusive > 0).map((topN, i) => {
+ return topNFunctions.TopN.filter((topN) => topN.CountExclusive >= 0).map((topN, i) => {
const comparisonRow = comparisonDataById?.[topN.Id];
const scaledSelfCPU = scaleValue({
diff --git a/x-pack/plugins/profiling/public/components/topn_functions_summary/index.tsx b/x-pack/plugins/profiling/public/components/topn_functions_summary/index.tsx
index bd1387c2576a..e3994ab442b7 100644
--- a/x-pack/plugins/profiling/public/components/topn_functions_summary/index.tsx
+++ b/x-pack/plugins/profiling/public/components/topn_functions_summary/index.tsx
@@ -9,11 +9,11 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
import type { TopNFunctions } from '@kbn/profiling-utils';
-import { calculateImpactEstimates } from '../../../common/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 { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_estimates';
interface Props {
baselineTopNFunctions?: TopNFunctions;
@@ -38,6 +38,8 @@ export function TopNFunctionsSummary({
baselineDuration,
comparisonDuration,
}: Props) {
+ const calculateImpactEstimates = useCalculateImpactEstimate();
+
const baselineScaledTotalSamples = baselineTopNFunctions
? baselineTopNFunctions.TotalCount * baselineScaleFactor
: 0;
@@ -87,6 +89,7 @@ export function TopNFunctionsSummary({
baselineDuration,
baselineScaledTotalSamples,
baselineTopNFunctions,
+ calculateImpactEstimates,
comparisonDuration,
comparisonScaledTotalSamples,
comparisonTopNFunctions,
diff --git a/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx b/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx
index 8e491af0afe6..b88881ea04cb 100644
--- a/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx
+++ b/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx
@@ -4,14 +4,18 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { Embeddable, EmbeddableOutput } from '@kbn/embeddable-plugin/public';
+import { Embeddable, EmbeddableOutput, IContainer } from '@kbn/embeddable-plugin/public';
import { EMBEDDABLE_FLAMEGRAPH } from '@kbn/observability-shared-plugin/public';
+import { createFlameGraph } from '@kbn/profiling-utils';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
-import { createFlameGraph } from '@kbn/profiling-utils';
import { FlameGraph } from '../../components/flamegraph';
-import { EmbeddableFlamegraphEmbeddableInput } from './embeddable_flamegraph_factory';
import { AsyncEmbeddableComponent } from '../async_embeddable_component';
+import {
+ ProfilingEmbeddableProvider,
+ ProfilingEmbeddablesDependencies,
+} from '../profiling_embeddable_provider';
+import { EmbeddableFlamegraphEmbeddableInput } from './embeddable_flamegraph_factory';
export class EmbeddableFlamegraph extends Embeddable<
EmbeddableFlamegraphEmbeddableInput,
@@ -20,18 +24,29 @@ export class EmbeddableFlamegraph extends Embeddable<
readonly type = EMBEDDABLE_FLAMEGRAPH;
private _domNode?: HTMLElement;
+ constructor(
+ private deps: ProfilingEmbeddablesDependencies,
+ initialInput: EmbeddableFlamegraphEmbeddableInput,
+ parent?: IContainer
+ ) {
+ super(initialInput, {}, parent);
+ }
+
render(domNode: HTMLElement) {
this._domNode = domNode;
const { data, isLoading } = this.input;
const flamegraph = !isLoading && data ? createFlameGraph(data) : undefined;
+
render(
-
- <>
- {flamegraph && (
-
- )}
- >
- ,
+
+
+ <>
+ {flamegraph && (
+
+ )}
+ >
+
+ ,
domNode
);
}
diff --git a/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph_factory.ts b/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph_factory.ts
index 568a4d20acc7..259c61df525e 100644
--- a/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph_factory.ts
+++ b/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph_factory.ts
@@ -5,12 +5,13 @@
* 2.0.
*/
import {
- IContainer,
- EmbeddableInput,
EmbeddableFactoryDefinition,
+ EmbeddableInput,
+ IContainer,
} from '@kbn/embeddable-plugin/public';
-import type { BaseFlameGraph } from '@kbn/profiling-utils';
import { EMBEDDABLE_FLAMEGRAPH } from '@kbn/observability-shared-plugin/public';
+import type { BaseFlameGraph } from '@kbn/profiling-utils';
+import type { GetProfilingEmbeddableDependencies } from '../profiling_embeddable_provider';
interface EmbeddableFlamegraphInput {
data?: BaseFlameGraph;
@@ -24,13 +25,16 @@ export class EmbeddableFlamegraphFactory
{
readonly type = EMBEDDABLE_FLAMEGRAPH;
+ constructor(private getProfilingEmbeddableDependencies: GetProfilingEmbeddableDependencies) {}
+
async isEditable() {
return false;
}
async create(input: EmbeddableFlamegraphEmbeddableInput, parent?: IContainer) {
const { EmbeddableFlamegraph } = await import('./embeddable_flamegraph');
- return new EmbeddableFlamegraph(input, {}, parent);
+ const deps = await this.getProfilingEmbeddableDependencies();
+ return new EmbeddableFlamegraph(deps, input, parent);
}
getDisplayName() {
diff --git a/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions.tsx b/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions.tsx
index fca243f81f56..4cfbe7ceddbb 100644
--- a/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions.tsx
+++ b/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions.tsx
@@ -4,13 +4,17 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { Embeddable, EmbeddableOutput } from '@kbn/embeddable-plugin/public';
+import { Embeddable, EmbeddableOutput, IContainer } from '@kbn/embeddable-plugin/public';
import { EMBEDDABLE_FUNCTIONS } from '@kbn/observability-shared-plugin/public';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { AsyncEmbeddableComponent } from '../async_embeddable_component';
import { EmbeddableFunctionsEmbeddableInput } from './embeddable_functions_factory';
import { EmbeddableFunctionsGrid } from './embeddable_functions_grid';
+import {
+ ProfilingEmbeddableProvider,
+ ProfilingEmbeddablesDependencies,
+} from '../profiling_embeddable_provider';
export class EmbeddableFunctions extends Embeddable<
EmbeddableFunctionsEmbeddableInput,
@@ -19,16 +23,26 @@ export class EmbeddableFunctions extends Embeddable<
readonly type = EMBEDDABLE_FUNCTIONS;
private _domNode?: HTMLElement;
+ constructor(
+ private deps: ProfilingEmbeddablesDependencies,
+ initialInput: EmbeddableFunctionsEmbeddableInput,
+ parent?: IContainer
+ ) {
+ super(initialInput, {}, parent);
+ }
+
render(domNode: HTMLElement) {
this._domNode = domNode;
const { data, isLoading, rangeFrom, rangeTo } = this.input;
const totalSeconds = (rangeTo - rangeFrom) / 1000;
render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
domNode
);
}
diff --git a/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions_factory.ts b/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions_factory.ts
index 8f7397b087c3..99e0d4baa485 100644
--- a/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions_factory.ts
+++ b/x-pack/plugins/profiling/public/embeddables/functions/embeddable_functions_factory.ts
@@ -11,6 +11,7 @@ import {
} from '@kbn/embeddable-plugin/public';
import { EMBEDDABLE_FUNCTIONS } from '@kbn/observability-shared-plugin/public';
import type { TopNFunctions } from '@kbn/profiling-utils';
+import { GetProfilingEmbeddableDependencies } from '../profiling_embeddable_provider';
interface EmbeddableFunctionsInput {
data?: TopNFunctions;
@@ -26,13 +27,16 @@ export class EmbeddableFunctionsFactory
{
readonly type = EMBEDDABLE_FUNCTIONS;
+ constructor(private getProfilingEmbeddableDependencies: GetProfilingEmbeddableDependencies) {}
+
async isEditable() {
return false;
}
async create(input: EmbeddableFunctionsEmbeddableInput, parent?: IContainer) {
const { EmbeddableFunctions } = await import('./embeddable_functions');
- return new EmbeddableFunctions(input, {}, parent);
+ const deps = await this.getProfilingEmbeddableDependencies();
+ return new EmbeddableFunctions(deps, input, parent);
}
getDisplayName() {
diff --git a/x-pack/plugins/profiling/public/embeddables/profiling_embeddable_provider.tsx b/x-pack/plugins/profiling/public/embeddables/profiling_embeddable_provider.tsx
new file mode 100644
index 000000000000..d4db1e2d9fb7
--- /dev/null
+++ b/x-pack/plugins/profiling/public/embeddables/profiling_embeddable_provider.tsx
@@ -0,0 +1,56 @@
+/*
+ * 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 { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
+import { ObservabilityAIAssistantProvider } from '@kbn/observability-ai-assistant-plugin/public';
+import React, { ReactChild, useMemo } from 'react';
+import { CoreSetup, CoreStart } from '@kbn/core/public';
+import { ProfilingDependenciesContextProvider } from '../components/contexts/profiling_dependencies/profiling_dependencies_context';
+import { ProfilingPluginPublicSetupDeps, ProfilingPluginPublicStartDeps } from '../types';
+import { Services } from '../services';
+
+export interface ProfilingEmbeddablesDependencies {
+ coreStart: CoreStart;
+ coreSetup: CoreSetup;
+ pluginsStart: ProfilingPluginPublicStartDeps;
+ pluginsSetup: ProfilingPluginPublicSetupDeps;
+ profilingFetchServices: Services;
+}
+
+export type GetProfilingEmbeddableDependencies = () => Promise;
+
+interface Props {
+ deps: ProfilingEmbeddablesDependencies;
+ children: ReactChild;
+}
+
+export function ProfilingEmbeddableProvider({ deps, children }: Props) {
+ const profilingDependencies = useMemo(
+ () => ({
+ start: {
+ core: deps.coreStart,
+ ...deps.pluginsStart,
+ },
+ setup: {
+ core: deps.coreSetup,
+ ...deps.pluginsSetup,
+ },
+ services: deps.profilingFetchServices,
+ }),
+ [deps]
+ );
+
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/x-pack/plugins/profiling/public/embeddables/register_embeddables.ts b/x-pack/plugins/profiling/public/embeddables/register_embeddables.ts
index 93d57a4e721a..d7b2e947144b 100644
--- a/x-pack/plugins/profiling/public/embeddables/register_embeddables.ts
+++ b/x-pack/plugins/profiling/public/embeddables/register_embeddables.ts
@@ -12,8 +12,18 @@ import {
} from '@kbn/observability-shared-plugin/public';
import { EmbeddableFlamegraphFactory } from './flamegraph/embeddable_flamegraph_factory';
import { EmbeddableFunctionsFactory } from './functions/embeddable_functions_factory';
+import { GetProfilingEmbeddableDependencies } from './profiling_embeddable_provider';
-export function registerEmbeddables(embeddable: EmbeddableSetup) {
- embeddable.registerEmbeddableFactory(EMBEDDABLE_FLAMEGRAPH, new EmbeddableFlamegraphFactory());
- embeddable.registerEmbeddableFactory(EMBEDDABLE_FUNCTIONS, new EmbeddableFunctionsFactory());
+export function registerEmbeddables(
+ embeddable: EmbeddableSetup,
+ getProfilingEmbeddableDependencies: GetProfilingEmbeddableDependencies
+) {
+ embeddable.registerEmbeddableFactory(
+ EMBEDDABLE_FLAMEGRAPH,
+ new EmbeddableFlamegraphFactory(getProfilingEmbeddableDependencies)
+ );
+ embeddable.registerEmbeddableFactory(
+ EMBEDDABLE_FUNCTIONS,
+ new EmbeddableFunctionsFactory(getProfilingEmbeddableDependencies)
+ );
}
diff --git a/x-pack/plugins/profiling/common/calculate_impact_estimates/calculate_impact_estimates.test.ts b/x-pack/plugins/profiling/public/hooks/use_calculate_impact_estimates.test.ts
similarity index 69%
rename from x-pack/plugins/profiling/common/calculate_impact_estimates/calculate_impact_estimates.test.ts
rename to x-pack/plugins/profiling/public/hooks/use_calculate_impact_estimates.test.ts
index a1abe24a79fa..65666b95b7ab 100644
--- a/x-pack/plugins/profiling/common/calculate_impact_estimates/calculate_impact_estimates.test.ts
+++ b/x-pack/plugins/profiling/public/hooks/use_calculate_impact_estimates.test.ts
@@ -4,10 +4,41 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { calculateImpactEstimates } from '.';
+import { useCalculateImpactEstimate } from './use_calculate_impact_estimates';
+import { useProfilingDependencies } from '../components/contexts/profiling_dependencies/use_profiling_dependencies';
+import {
+ profilingCo2PerKWH,
+ profilingDatacenterPUE,
+ profilingPerCoreWatt,
+} from '@kbn/observability-plugin/common';
+
+jest.mock('../components/contexts/profiling_dependencies/use_profiling_dependencies');
+
+describe('useCalculateImpactEstimate', () => {
+ beforeAll(() => {
+ (useProfilingDependencies as jest.Mock).mockReturnValue({
+ start: {
+ core: {
+ uiSettings: {
+ get: (key: string) => {
+ if (key === profilingPerCoreWatt) {
+ return 7;
+ }
+ if (key === profilingCo2PerKWH) {
+ return 0.000379069;
+ }
+ if (key === profilingDatacenterPUE) {
+ return 1.7;
+ }
+ },
+ },
+ },
+ },
+ });
+ });
-describe('calculateImpactEstimates', () => {
it('calculates impact when countExclusive is lower than countInclusive', () => {
+ const calculateImpactEstimates = useCalculateImpactEstimate();
const { selfCPU, totalCPU, totalSamples } = calculateImpactEstimates({
countExclusive: 500,
countInclusive: 1000,
@@ -47,6 +78,7 @@ describe('calculateImpactEstimates', () => {
});
it('calculates impact', () => {
+ const calculateImpactEstimates = useCalculateImpactEstimate();
const { selfCPU, totalCPU, totalSamples } = calculateImpactEstimates({
countExclusive: 1000,
countInclusive: 1000,
diff --git a/x-pack/plugins/profiling/public/hooks/use_calculate_impact_estimates.ts b/x-pack/plugins/profiling/public/hooks/use_calculate_impact_estimates.ts
new file mode 100644
index 000000000000..1a32cf541f54
--- /dev/null
+++ b/x-pack/plugins/profiling/public/hooks/use_calculate_impact_estimates.ts
@@ -0,0 +1,91 @@
+/*
+ * 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 {
+ profilingCo2PerKWH,
+ profilingDatacenterPUE,
+ profilingPerCoreWatt,
+} from '@kbn/observability-plugin/common';
+import { useProfilingDependencies } from '../components/contexts/profiling_dependencies/use_profiling_dependencies';
+
+interface Params {
+ countInclusive: number;
+ countExclusive: number;
+ totalSamples: number;
+ totalSeconds: number;
+}
+
+export type CalculateImpactEstimates = ReturnType;
+export type ImpactEstimates = ReturnType;
+
+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/)
+const CORE_COST_PER_HOUR = 0.0425;
+
+export function useCalculateImpactEstimate() {
+ const {
+ start: { core },
+ } = useProfilingDependencies();
+
+ const perCoreWatts = core.uiSettings.get(profilingPerCoreWatt);
+ const co2PerTonKWH = core.uiSettings.get(profilingCo2PerKWH);
+ const datacenterPUE = core.uiSettings.get(profilingDatacenterPUE);
+
+ function calculateImpact({
+ samples,
+ totalSamples,
+ totalSeconds,
+ }: {
+ samples: number;
+ totalSamples: number;
+ totalSeconds: number;
+ }) {
+ const annualizedScaleUp = ANNUAL_SECONDS / totalSeconds;
+ const totalCoreSeconds = totalSamples / 20;
+ const percentage = samples / totalSamples;
+ const coreSeconds = totalCoreSeconds * percentage;
+ const annualizedCoreSeconds = coreSeconds * annualizedScaleUp;
+ const coreHours = coreSeconds / (60 * 60);
+ const co2PerKWH = co2PerTonKWH * 1000;
+ const co2 = ((perCoreWatts * coreHours) / 1000.0) * co2PerKWH * datacenterPUE;
+ const annualizedCo2 = co2 * annualizedScaleUp;
+ const dollarCost = coreHours * CORE_COST_PER_HOUR;
+ const annualizedDollarCost = dollarCost * annualizedScaleUp;
+
+ return {
+ percentage,
+ coreSeconds,
+ annualizedCoreSeconds,
+ co2,
+ annualizedCo2,
+ dollarCost,
+ annualizedDollarCost,
+ };
+ }
+
+ return (params: Params) => {
+ return {
+ totalSamples: calculateImpact({
+ samples: params.totalSamples,
+ totalSamples: params.totalSamples,
+ totalSeconds: params.totalSeconds,
+ }),
+ totalCPU: calculateImpact({
+ samples: params.countInclusive,
+ totalSamples: params.totalSamples,
+ totalSeconds: params.totalSeconds,
+ }),
+ selfCPU: calculateImpact({
+ samples: params.countExclusive,
+ totalSamples: params.totalSamples,
+ totalSeconds: params.totalSeconds,
+ }),
+ };
+ };
+}
diff --git a/x-pack/plugins/profiling/public/plugin.tsx b/x-pack/plugins/profiling/public/plugin.tsx
index d888ba6ce978..bcbdf3db8e24 100644
--- a/x-pack/plugins/profiling/public/plugin.tsx
+++ b/x-pack/plugins/profiling/public/plugin.tsx
@@ -19,6 +19,7 @@ import { BehaviorSubject, combineLatest, from, map } from 'rxjs';
import { registerEmbeddables } from './embeddables/register_embeddables';
import { getServices } from './services';
import type { ProfilingPluginPublicSetupDeps, ProfilingPluginPublicStartDeps } from './types';
+import { ProfilingEmbeddablesDependencies } from './embeddables/profiling_embeddable_provider';
export type ProfilingPluginSetup = void;
export type ProfilingPluginStart = void;
@@ -81,6 +82,8 @@ export class ProfilingPlugin implements Plugin {
pluginsSetup.observabilityShared.navigation.registerSections(section$);
+ const profilingFetchServices = getServices();
+
coreSetup.application.register({
id: 'profiling',
title: 'Universal Profiling',
@@ -95,7 +98,6 @@ export class ProfilingPlugin implements Plugin {
unknown
];
- const profilingFetchServices = getServices();
const { renderApp } = await import('./app');
function pushKueryToSubject(location: Location) {
@@ -128,7 +130,23 @@ export class ProfilingPlugin implements Plugin {
},
});
- registerEmbeddables(pluginsSetup.embeddable);
+ const getProfilingEmbeddableDependencies =
+ async (): Promise => {
+ const [coreStart, pluginsStart] = (await coreSetup.getStartServices()) as [
+ CoreStart,
+ ProfilingPluginPublicStartDeps,
+ unknown
+ ];
+ return {
+ coreStart,
+ coreSetup,
+ pluginsStart,
+ pluginsSetup,
+ profilingFetchServices,
+ };
+ };
+
+ registerEmbeddables(pluginsSetup.embeddable, getProfilingEmbeddableDependencies);
return {};
}
diff --git a/x-pack/plugins/profiling/public/routing/index.tsx b/x-pack/plugins/profiling/public/routing/index.tsx
index 9e40cdcb6dc0..cb92422a9407 100644
--- a/x-pack/plugins/profiling/public/routing/index.tsx
+++ b/x-pack/plugins/profiling/public/routing/index.tsx
@@ -32,8 +32,21 @@ import { StackTracesView } from '../views/stack_traces_view';
import { StorageExplorerView } from '../views/storage_explorer';
import { RouteBreadcrumb } from './route_breadcrumb';
import { DeleteDataView } from '../views/delete_data_view';
+import { Settings } from '../views/settings';
const routes = {
+ '/settings': {
+ element: (
+
+
+
+ ),
+ },
'/': {
element: (
void;
+ onSave: () => void;
+ saveLabel: string;
+}
+
+export function BottomBarActions({
+ isLoading,
+ onDiscardChanges,
+ onSave,
+ unsavedChangesCount,
+ saveLabel,
+}: Props) {
+ return (
+
+
+
+
+
+ {i18n.translate('xpack.profiling.bottomBarActions.unsavedChanges', {
+ defaultMessage:
+ '{unsavedChangesCount, plural, =0{0 unsaved changes} one {1 unsaved change} other {# unsaved changes}} ',
+ values: { unsavedChangesCount },
+ })}
+
+
+
+
+
+
+ {i18n.translate('xpack.profiling.bottomBarActions.discardChangesButton', {
+ defaultMessage: 'Discard changes',
+ })}
+
+
+
+
+ {saveLabel}
+
+
+
+
+
+
+ );
+}
diff --git a/x-pack/plugins/profiling/public/views/settings/index.tsx b/x-pack/plugins/profiling/public/views/settings/index.tsx
new file mode 100644
index 000000000000..afc32d42e6f3
--- /dev/null
+++ b/x-pack/plugins/profiling/public/views/settings/index.tsx
@@ -0,0 +1,115 @@
+/*
+ * 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 { EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
+import { LazyField } from '@kbn/advanced-settings-plugin/public';
+import { i18n } from '@kbn/i18n';
+import {
+ profilingCo2PerKWH,
+ profilingDatacenterPUE,
+ profilingPerCoreWatt,
+} from '@kbn/observability-plugin/common';
+import { useEditableSettings, useUiTracker } from '@kbn/observability-shared-plugin/public';
+import { isEmpty } from 'lodash';
+import React from 'react';
+import { useProfilingDependencies } from '../../components/contexts/profiling_dependencies/use_profiling_dependencies';
+import { ProfilingAppPageTemplate } from '../../components/profiling_app_page_template';
+import { BottomBarActions } from './bottom_bar_actions';
+
+const settingKeys = [profilingCo2PerKWH, profilingDatacenterPUE, profilingPerCoreWatt];
+
+export function Settings() {
+ const trackProfilingEvent = useUiTracker({ app: 'profiling' });
+ const {
+ start: {
+ core: { docLinks, notifications },
+ },
+ } = useProfilingDependencies();
+
+ const {
+ handleFieldChange,
+ settingsEditableConfig,
+ unsavedChanges,
+ saveAll,
+ isSaving,
+ cleanUnsavedChanges,
+ } = useEditableSettings('profiling', settingKeys);
+
+ async function handleSave() {
+ try {
+ const reloadPage = Object.keys(unsavedChanges).some((key) => {
+ return settingsEditableConfig[key].requiresPageReload;
+ });
+ await saveAll();
+ trackProfilingEvent({ metric: 'general_settings_save' });
+ if (reloadPage) {
+ window.location.reload();
+ }
+ } catch (e) {
+ const error = e as Error;
+ notifications.toasts.addDanger({
+ title: i18n.translate('xpack.profiling.settings.save.error', {
+ defaultMessage: 'An error occurred while saving the settings',
+ }),
+ text: error.message,
+ });
+ }
+ }
+
+ return (
+
+ <>
+
+
+ {i18n.translate('xpack.profiling.settings.title', {
+ defaultMessage: 'Advanced Settings',
+ })}
+
+
+
+
+
+
+
+ {i18n.translate('xpack.profiling.settings.co2Sections', {
+ defaultMessage: 'CO2',
+ })}
+
+
+
+
+ {settingKeys.map((settingKey) => {
+ const editableConfig = settingsEditableConfig[settingKey];
+ return (
+
+ );
+ })}
+
+
+ {!isEmpty(unsavedChanges) && (
+
+ )}
+ >
+
+ );
+}
diff --git a/x-pack/plugins/profiling/tsconfig.json b/x-pack/plugins/profiling/tsconfig.json
index 60c06119bfa6..af7971b5115d 100644
--- a/x-pack/plugins/profiling/tsconfig.json
+++ b/x-pack/plugins/profiling/tsconfig.json
@@ -49,7 +49,8 @@
"@kbn/observability-ai-assistant-plugin",
"@kbn/profiling-data-access-plugin",
"@kbn/embeddable-plugin",
- "@kbn/profiling-utils"
+ "@kbn/profiling-utils",
+ "@kbn/advanced-settings-plugin"
// add references to other TypeScript projects the plugin depends on
// requiredPlugins from ./kibana.json