fix: Fix queries/minute calculation in Services Dashboard (#1457)

Closes HDX-2973

# Summary

This PR updates the queries on the services dashboard to

1. Fix the queries/min calculation, which was previously just the total number of queries
2. Support materialized views in the enterprise edition by separating aggregations from conversions to rates and millisecond values.

## Queries/Min

Before:

<img width="779" height="100" alt="Screenshot 2025-12-09 at 11 28 00 AM" src="https://github.com/user-attachments/assets/52f2581f-5af4-4528-bdb6-5505e5ed7709" />
<img width="1511" height="842" alt="Screenshot 2025-12-09 at 11 27 17 AM" src="https://github.com/user-attachments/assets/94ea121d-02ce-4460-a1c9-cc6f9e9cd8d3" />


After:

<img width="776" height="129" alt="Screenshot 2025-12-09 at 11 28 27 AM" src="https://github.com/user-attachments/assets/5c23bd73-23ea-4d44-a2e9-e4f01b93dc3c" />
<img width="1511" height="787" alt="Screenshot 2025-12-09 at 11 28 33 AM" src="https://github.com/user-attachments/assets/9c8d1932-f1ac-49bc-b122-7229cd8ad360" />
This commit is contained in:
Drew Davis 2025-12-09 12:47:26 -05:00 committed by GitHub
parent b564a369be
commit 776e392777
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 88 additions and 27 deletions

View file

@ -0,0 +1,5 @@
---
"@hyperdx/app": patch
---
fix: Fix queries/minute calculation in Services Dashboard

View file

@ -631,7 +631,9 @@ function HttpTab({
selectGroupBy: false,
groupBy: expressions.endpoint,
orderBy: '"Total (ms)" DESC',
filters: getScopedFilters({ appliedConfig, expressions }),
filters: [
...getScopedFilters({ appliedConfig, expressions }),
],
dateRange: searchedTimeRange,
numberFormat: MS_NUMBER_FORMAT,
limit: { limit: 20 },
@ -803,11 +805,16 @@ function DatabaseTab({
where: appliedConfig.where || '',
whereLanguage: appliedConfig.whereLanguage || 'sql',
select: [
// Separate the aggregations from the conversion to ms so that AggregatingMergeTree MVs can be used
{
alias: 'total_query_time_ns',
aggFn: 'sum',
valueExpression: expressions.duration,
aggCondition: '',
},
{
alias: 'total_query_time_ms',
aggFn: 'sum',
valueExpression: expressions.durationInMillis,
aggCondition: '',
valueExpression: `total_query_time_ns / ${expressions.durationDivisorForMillis}`,
},
{
alias: 'Statement',
@ -1090,6 +1097,12 @@ function DatabaseTab({
valueColumn="Total"
hoverCardPosition="top-start"
getRowSearchLink={getRowSearchLink}
hiddenSeries={[
'total_duration_ns',
'total_queries',
'p95_duration_ns',
'p50_duration_ns',
]}
config={{
...source,
where: appliedConfig.where || '',
@ -1099,35 +1112,51 @@ function DatabaseTab({
selectGroupBy: false,
orderBy: '"Total" DESC',
select: [
// Separate the aggregations from the conversion to ms and rate so that AggregatingMergeTree MVs can be used
{
alias: 'Statement',
valueExpression: expressions.dbStatement,
},
{
alias: 'Total',
alias: 'total_duration_ns',
aggFn: 'sum',
valueExpression: expressions.duration,
aggCondition: '',
valueExpression: expressions.durationInMillis,
},
{
alias: 'Total',
valueExpression: `total_duration_ns / ${expressions.durationDivisorForMillis}`,
},
{
alias: 'total_queries',
aggFn: 'count',
valueExpression: '',
},
{
alias: 'Queries/Min',
aggFn: 'count',
valueExpression: `value / age('mi', toDateTime(${searchedTimeRange[0].getTime() / 1000}), toDateTime(${searchedTimeRange[1].getTime() / 1000}))`,
valueExpression: `total_queries / age('mi', toDateTime(${searchedTimeRange[0].getTime() / 1000}), toDateTime(${searchedTimeRange[1].getTime() / 1000}))`,
},
{
alias: 'p95_duration_ns',
aggFn: 'quantile',
level: 0.95,
valueExpression: expressions.duration,
aggCondition: '',
},
{
alias: 'P95 (ms)',
valueExpression: `p95_duration_ns / ${expressions.durationDivisorForMillis}`,
},
{
alias: 'p50_duration_ns',
aggFn: 'quantile',
valueExpression: expressions.durationInMillis,
level: 0.5,
valueExpression: expressions.duration,
aggCondition: '',
level: 0.95,
},
{
alias: 'Median (ms)',
aggFn: 'quantile',
valueExpression: expressions.durationInMillis,
aggCondition: '',
level: 0.5,
valueExpression: `p50_duration_ns / ${expressions.durationDivisorForMillis}`,
},
],
filters: [
@ -1144,6 +1173,12 @@ function DatabaseTab({
) : (
<DBTableChart
getRowSearchLink={getRowSearchLink}
hiddenColumns={[
'duration_ns',
'total_count',
'p95_duration_ns',
'p50_duration_ns',
]}
config={{
...source,
where: appliedConfig.where || '',
@ -1158,31 +1193,46 @@ function DatabaseTab({
valueExpression: expressions.dbStatement,
},
{
alias: 'Total',
alias: 'duration_ns',
aggFn: 'sum',
valueExpression: expressions.duration,
aggCondition: '',
valueExpression: expressions.durationInMillis,
},
{
alias: 'Total',
valueExpression: `duration_ns / ${expressions.durationDivisorForMillis}`,
},
{
alias: 'total_count',
aggFn: 'count',
valueExpression: '',
},
{
alias: 'Queries/Min',
aggFn: 'count',
valueExpression: `value / age('mi', toDateTime(${searchedTimeRange[0].getTime() / 1000}), toDateTime(${searchedTimeRange[1].getTime() / 1000}))`,
aggCondition: '',
valueExpression: `total_count / age('mi', toDateTime(${searchedTimeRange[0].getTime() / 1000}), toDateTime(${searchedTimeRange[1].getTime() / 1000}))`,
},
{
alias: 'P95 (ms)',
alias: 'p95_duration_ns',
aggFn: 'quantile',
valueExpression: expressions.durationInMillis,
valueExpression: expressions.duration,
aggCondition: '',
level: 0.95,
},
{
alias: 'Median (ms)',
alias: 'P95 (ms)',
valueExpression: `p95_duration_ns / ${expressions.durationDivisorForMillis}`,
},
{
alias: 'p50_duration_ns',
aggFn: 'quantile',
valueExpression: expressions.durationInMillis,
valueExpression: expressions.duration,
aggCondition: '',
level: 0.5,
},
{
alias: 'Median (ms)',
valueExpression: `p50_duration_ns / ${expressions.durationDivisorForMillis}`,
},
],
filters: [
...getScopedFilters({

View file

@ -94,17 +94,23 @@ export default function ServiceDashboardDbQuerySidePanel({
{source && expressions && (
<DBTimeChart
sourceId={sourceId}
hiddenSeries={['total_duration_ns']}
config={{
...source,
where: '',
whereLanguage: 'sql',
select: [
// Separate the aggregations from the conversion to ms so that AggregatingMergeTree MVs can be used
{
aggFn: 'sum' as const,
valueExpression: expressions.durationInMillis,
alias: 'Total Query Time',
aggFn: 'sum',
valueExpression: expressions.duration,
alias: 'total_duration_ns',
aggCondition: '',
},
{
valueExpression: `total_duration_ns / ${expressions.durationDivisorForMillis}`,
alias: 'Total Query Time',
},
],
numberFormat: MS_NUMBER_FORMAT,
filters: dbQueryFilters,
@ -119,7 +125,7 @@ export default function ServiceDashboardDbQuerySidePanel({
<Group justify="space-between" align="center" mb="sm">
<Text size="sm">Query Throughput</Text>
</Group>
{source && (
{source && expressions && (
<DBTimeChart
sourceId={sourceId}
config={{