Skip to content

Commit de2c8a0

Browse files
authored
feat: Show exponential histogram metrics in the metric name drop-down (#2687)
## Summary This PR updates the chart editor to include Exponential Histogram metric names in the metric name selector. This behavior is gated behind a new `NEXT_PUBLIC_ENABLE_EXPONENTIAL_HISTOGRAMS` environment variable, which is disabled in all environments while support for querying exponential histogram metrics is in progress. ### Screenshots or video <img width="1177" height="320" alt="Screenshot 2026-07-20 at 3 39 54 PM" src="https://github.com/user-attachments/assets/a0c3bf3b-7acc-46af-8c70-64576c52978f" /> ### How to test on Vercel preview This must be tested locally, so that you can enable the NEXT_PUBLIC_ENABLE_EXPONENTIAL_HISTOGRAMS flag. You'll also likely need to insert a metric data point into the `otel_metrics_exponential_histogram` table and ensure your Metrics source has registered that table. ### References - Linear Issue: Closes HDX-4851 - Related PRs:
1 parent fcdcd78 commit de2c8a0

4 files changed

Lines changed: 108 additions & 11 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hyperdx/app': patch
3+
---
4+
5+
feat: Show exponential histogram metrics in the metric name drop-down, gated behind `NEXT_PUBLIC_ENABLE_EXPONENTIAL_HISTOGRAMS`.

‎packages/app/src/components/MetricNameSelect.tsx‎

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from '@hyperdx/common-utils/dist/types';
88
import { Select } from '@mantine/core';
99

10+
import { IS_EXPONENTIAL_HISTOGRAMS_ENABLED } from '@/config';
1011
import { useGetKeyValues } from '@/hooks/useMetadata';
1112
import { capitalizeFirstLetter } from '@/utils';
1213

@@ -60,7 +61,12 @@ function useMetricNames(
6061
metricSource: TMetricSource,
6162
dateRange?: DateRange['dateRange'],
6263
) {
63-
const { gaugeConfig, histogramConfig, sumConfig } = useMemo(() => {
64+
const {
65+
gaugeConfig,
66+
histogramConfig,
67+
sumConfig,
68+
exponentialHistogramConfig,
69+
} = useMemo(() => {
6470
return {
6571
gaugeConfig: chartConfigByMetricType({
6672
dateRange,
@@ -77,6 +83,11 @@ function useMetricNames(
7783
metricSource,
7884
metricType: MetricsDataType.Sum,
7985
}),
86+
exponentialHistogramConfig: chartConfigByMetricType({
87+
dateRange,
88+
metricSource,
89+
metricType: MetricsDataType.ExponentialHistogram,
90+
}),
8091
};
8192
}, [metricSource, dateRange]);
8293

@@ -98,34 +109,55 @@ function useMetricNames(
98109
limit: MAX_METRIC_NAME_OPTIONS,
99110
disableRowLimit: true,
100111
});
112+
const { data: exponentialHistogramMetrics } = useGetKeyValues(
113+
{
114+
chartConfig: exponentialHistogramConfig,
115+
keys: ['MetricName'],
116+
limit: MAX_METRIC_NAME_OPTIONS,
117+
disableRowLimit: true,
118+
},
119+
{
120+
enabled:
121+
IS_EXPONENTIAL_HISTOGRAMS_ENABLED &&
122+
!!metricSource.metricTables?.[MetricsDataType.ExponentialHistogram],
123+
},
124+
);
101125

102126
return {
103127
gaugeMetrics: gaugeMetrics?.[0].value,
104128
histogramMetrics: histogramMetrics?.[0].value,
105129
sumMetrics: sumMetrics?.[0].value,
130+
exponentialHistogramMetrics: IS_EXPONENTIAL_HISTOGRAMS_ENABLED
131+
? exponentialHistogramMetrics?.[0].value
132+
: undefined,
106133
};
107134
}
108135

109136
export function getMetricOptions(
110137
gaugeMetrics: string[] | undefined,
111138
histogramMetrics: string[] | undefined,
112139
sumMetrics: string[] | undefined,
140+
exponentialHistogramMetrics: string[] | undefined,
113141
metricName: string | null | undefined,
114142
metricType: MetricsDataType,
115143
) {
116144
const metricsFromQuery = [
117145
...(gaugeMetrics?.map(metric => ({
118-
value: `${metric}${SEPARATOR}gauge`,
146+
value: `${metric}${SEPARATOR}${MetricsDataType.Gauge}`,
119147
label: `${metric} (Gauge)`,
120148
})) ?? []),
121149
...(histogramMetrics?.map(metric => ({
122-
value: `${metric}${SEPARATOR}histogram`,
150+
value: `${metric}${SEPARATOR}${MetricsDataType.Histogram}`,
123151
label: `${metric} (Histogram)`,
124152
})) ?? []),
125153
...(sumMetrics?.map(metric => ({
126-
value: `${metric}${SEPARATOR}sum`,
154+
value: `${metric}${SEPARATOR}${MetricsDataType.Sum}`,
127155
label: `${metric} (Sum)`,
128156
})) ?? []),
157+
...(exponentialHistogramMetrics?.map(metric => ({
158+
value: `${metric}${SEPARATOR}${MetricsDataType.ExponentialHistogram}`,
159+
label: `${metric} (Exponential Histogram)`,
160+
})) ?? []),
129161
];
130162
// if saved metric does not exist in the available options, assume it exists
131163
// and add it to options
@@ -166,18 +198,30 @@ export function MetricNameSelect({
166198
onFocus?: () => void;
167199
'data-testid'?: string;
168200
}) {
169-
const { gaugeMetrics, histogramMetrics, sumMetrics } =
170-
useMetricNames(metricSource);
201+
const {
202+
gaugeMetrics,
203+
histogramMetrics,
204+
sumMetrics,
205+
exponentialHistogramMetrics,
206+
} = useMetricNames(metricSource);
171207

172208
const options = useMemo(() => {
173209
return getMetricOptions(
174210
gaugeMetrics,
175211
histogramMetrics,
176212
sumMetrics,
213+
exponentialHistogramMetrics,
177214
metricName,
178215
metricType,
179216
);
180-
}, [gaugeMetrics, histogramMetrics, sumMetrics, metricName, metricType]);
217+
}, [
218+
gaugeMetrics,
219+
histogramMetrics,
220+
sumMetrics,
221+
exponentialHistogramMetrics,
222+
metricName,
223+
metricType,
224+
]);
181225

182226
const currentValue =
183227
metricName && metricType ? `${metricName}${SEPARATOR}${metricType}` : null;

‎packages/app/src/components/__tests__/MetricNameSelect.test.ts‎

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,22 @@ describe('getMetricOptions', () => {
1111
undefined,
1212
undefined,
1313
undefined,
14+
undefined,
1415
null,
1516
MetricsDataType.Gauge,
1617
);
1718
expect(result).toEqual([]);
1819
});
1920

2021
it('returns empty array when all metric lists are empty', () => {
21-
const result = getMetricOptions([], [], [], null, MetricsDataType.Gauge);
22+
const result = getMetricOptions(
23+
[],
24+
[],
25+
[],
26+
[],
27+
null,
28+
MetricsDataType.Gauge,
29+
);
2230
expect(result).toEqual([]);
2331
});
2432

@@ -27,6 +35,7 @@ describe('getMetricOptions', () => {
2735
undefined,
2836
undefined,
2937
undefined,
38+
undefined,
3039
'my.metric',
3140
MetricsDataType.Sum,
3241
);
@@ -45,6 +54,7 @@ describe('getMetricOptions', () => {
4554
['cpu.usage'],
4655
undefined,
4756
undefined,
57+
undefined,
4858
null,
4959
MetricsDataType.Gauge,
5060
);
@@ -58,6 +68,7 @@ describe('getMetricOptions', () => {
5868
undefined,
5969
['request.duration'],
6070
undefined,
71+
undefined,
6172
null,
6273
MetricsDataType.Histogram,
6374
);
@@ -74,6 +85,7 @@ describe('getMetricOptions', () => {
7485
undefined,
7586
undefined,
7687
['bytes.sent'],
88+
undefined,
7789
null,
7890
MetricsDataType.Sum,
7991
);
@@ -82,11 +94,29 @@ describe('getMetricOptions', () => {
8294
]);
8395
});
8496

97+
it('returns a single exponential histogram option', () => {
98+
const result = getMetricOptions(
99+
undefined,
100+
undefined,
101+
undefined,
102+
['request.duration'],
103+
null,
104+
MetricsDataType.ExponentialHistogram,
105+
);
106+
expect(result).toEqual([
107+
{
108+
value: `request.duration${SEPARATOR}${MetricsDataType.ExponentialHistogram}`,
109+
label: 'request.duration (Exponential Histogram)',
110+
},
111+
]);
112+
});
113+
85114
it('does not duplicate a saved metricName already present in results', () => {
86115
const result = getMetricOptions(
87116
['cpu.usage'],
88117
undefined,
89118
undefined,
119+
undefined,
90120
'cpu.usage',
91121
MetricsDataType.Gauge,
92122
);
@@ -102,6 +132,7 @@ describe('getMetricOptions', () => {
102132
['cpu.usage'],
103133
undefined,
104134
undefined,
135+
undefined,
105136
'missing.metric',
106137
MetricsDataType.Gauge,
107138
);
@@ -117,17 +148,22 @@ describe('getMetricOptions', () => {
117148
const gaugeMetrics = ['cpu.usage', 'mem.usage', 'disk.usage'];
118149
const histogramMetrics = ['request.duration', 'db.query.duration'];
119150
const sumMetrics = ['bytes.sent', 'bytes.received', 'requests.total'];
151+
const exponentialHistogramMetrics = [
152+
'http.request.duration',
153+
'rpc.server.duration',
154+
];
120155

121156
it('returns all options for all metric types', () => {
122157
const result = getMetricOptions(
123158
gaugeMetrics,
124159
histogramMetrics,
125160
sumMetrics,
161+
exponentialHistogramMetrics,
126162
null,
127163
MetricsDataType.Gauge,
128164
);
129165

130-
expect(result).toHaveLength(8);
166+
expect(result).toHaveLength(10);
131167

132168
expect(result).toContainEqual({
133169
value: `cpu.usage${SEPARATOR}gauge`,
@@ -161,17 +197,26 @@ describe('getMetricOptions', () => {
161197
value: `requests.total${SEPARATOR}sum`,
162198
label: 'requests.total (Sum)',
163199
});
200+
expect(result).toContainEqual({
201+
value: `http.request.duration${SEPARATOR}${MetricsDataType.ExponentialHistogram}`,
202+
label: 'http.request.duration (Exponential Histogram)',
203+
});
204+
expect(result).toContainEqual({
205+
value: `rpc.server.duration${SEPARATOR}${MetricsDataType.ExponentialHistogram}`,
206+
label: 'rpc.server.duration (Exponential Histogram)',
207+
});
164208
});
165209

166210
it('does not duplicate a saved metricName already present among multiple options', () => {
167211
const result = getMetricOptions(
168212
gaugeMetrics,
169213
histogramMetrics,
170214
sumMetrics,
215+
exponentialHistogramMetrics,
171216
'mem.usage',
172217
MetricsDataType.Gauge,
173218
);
174-
expect(result).toHaveLength(8);
219+
expect(result).toHaveLength(10);
175220
const values = result.map(r => r.value);
176221
expect(
177222
values.filter(v => v === `mem.usage${SEPARATOR}gauge`),
@@ -183,10 +228,11 @@ describe('getMetricOptions', () => {
183228
gaugeMetrics,
184229
histogramMetrics,
185230
sumMetrics,
231+
exponentialHistogramMetrics,
186232
'absent.metric',
187233
MetricsDataType.Histogram,
188234
);
189-
expect(result).toHaveLength(9);
235+
expect(result).toHaveLength(11);
190236
expect(result).toContainEqual({
191237
value: `absent.metric${SEPARATOR}histogram`,
192238
label: 'absent.metric (Histogram)',

‎packages/app/src/config.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,5 @@ export const IS_METRICS_ENABLED = true;
6666
export const IS_MTVIEWS_ENABLED = false;
6767
export const IS_SESSIONS_ENABLED = true;
6868
export const IS_PROMQL_ENABLED = env('NEXT_PUBLIC_ENABLE_PROMQL') === 'true';
69+
export const IS_EXPONENTIAL_HISTOGRAMS_ENABLED =
70+
env('NEXT_PUBLIC_ENABLE_EXPONENTIAL_HISTOGRAMS') === 'true';

0 commit comments

Comments
 (0)