Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/gauge-count-distinct-value-expression.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@hyperdx/common-utils': patch
'@hyperdx/app': patch
'@hyperdx/api': patch
---

fix: count distinct attribute values on gauge metric charts

`count_distinct` on a gauge metric ignored its value expression and counted the
distinct metric values instead, so counting `Attributes['name']` on an info
metric returned 1 or 2. It now counts the given expression. The default `Value`
expression and every other aggregation still use the bucketed metric value.
61 changes: 61 additions & 0 deletions packages/common-utils/src/__tests__/renderChartConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,67 @@ describe('renderChartConfig', () => {
expect(actual).toMatchSnapshot();
});

describe('count_distinct on a gauge metric', () => {
const renderGaugeSelect = async (select: {
aggFn?: string;
valueExpression: string;
}) =>
parameterizedQueryToSql(
await renderChartConfig(
{
...gaugeConfiguration,
select: [
{
aggFn: 'count_distinct',
metricName: 'argocd_app_info',
metricType: MetricsDataType.Gauge,
...select,
},
],
},
mockMetadata,
querySettings,
),
);

it('counts the distinct values of an attribute expression', async () => {
const sql = await renderGaugeSelect({
valueExpression: "Attributes['name']",
});
expect(sql).toContain("count(DISTINCT Attributes['name'])");
expect(sql).not.toContain('count(DISTINCT LastValue)');
});

it('still counts the bucketed metric value for the default Value expression', async () => {
const sql = await renderGaugeSelect({ valueExpression: 'Value' });
expect(sql).toContain('count(DISTINCT LastValue)');
});

it('still counts the bucketed metric value for an expression over Value', async () => {
const sql = await renderGaugeSelect({ valueExpression: 'Value * 100' });
expect(sql).toContain('count(DISTINCT LastValue)');
expect(sql).not.toContain('Value * 100');
});

it('counts an attribute whose key is Value', async () => {
const sql = await renderGaugeSelect({
valueExpression: "Attributes['Value']",
});
expect(sql).toContain("count(DISTINCT Attributes['Value'])");
});

it('keeps other aggregations on the bucketed metric value', async () => {
const sql = await renderGaugeSelect({
aggFn: 'max',
valueExpression: "Attributes['name']",
});
expect(sql).toMatch(
/SELECT max\(\s*toFloat64OrDefault\(toString\(LastValue\)\)/,
);
expect(sql).not.toContain("Attributes['name']");
});
});

it('should generate sql for a single sum metric', async () => {
const config: ChartConfigWithOptDateRange = {
displayType: DisplayType.Line,
Expand Down
11 changes: 10 additions & 1 deletion packages/common-utils/src/core/renderChartConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2101,7 +2101,16 @@ async function translateMetricChartConfig(
select: [
{
..._select,
valueExpression: 'LastValue',
// count_distinct may count an attribute, which Bucketed still carries;
// Bucketed has no Value column, only LastValue
valueExpression:
_select.aggFn === 'count_distinct' &&
_select.valueExpression &&
!/\bValue\b/.test(
_select.valueExpression.replace(/'(?:[^'\\]|\\.)*'/g, "''"),
)
Comment on lines +2109 to +2111

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Comment changes distinct count

For a gauge count_distinct expression such as Attributes['name'] /* Value */, the word Value is only in a SQL comment. This check strips quoted strings but not comments, so it replaces the requested attribute with LastValue. The chart then silently counts distinct metric values instead of distinct names.

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this one as is: SQL comments inside a metric value expression aren't a realistic input, and per AGENTS.md this PR has had its two automated review rounds, so a maintainer can decide if it needs handling.

? _select.valueExpression
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved
: 'LastValue',
aggCondition: '', // clear up the condition since the where clause is already applied at the upstream CTE
},
],
Expand Down