Skip to content
Open
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
7 changes: 7 additions & 0 deletions .changeset/tiny-forks-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@hyperdx/common-utils': patch
'@hyperdx/api': patch
'@hyperdx/app': patch
---

feat: support sample-weighted aggregations for sampled trace data
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ packages/app/next-env.d.ts
# optional npm cache directory
**/.npm

# npm lockfile (project uses yarn)
package-lock.json

# dependency directories
**/node_modules

Expand Down
1 change: 1 addition & 0 deletions docker/clickhouse/local/init-db-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ CREATE TABLE IF NOT EXISTS ${DATABASE}.e2e_otel_traces
\`Links.TraceState\` Array(String) CODEC(ZSTD(1)),
\`Links.Attributes\` Array(Map(LowCardinality(String), String)) CODEC(ZSTD(1)),
\`__hdx_materialized_rum.sessionId\` String MATERIALIZED ResourceAttributes['rum.sessionId'] CODEC(ZSTD(1)),
\`SampleRate\` UInt64 MATERIALIZED greatest(toUInt64OrZero(SpanAttributes['SampleRate']), 1) CODEC(T64, ZSTD(1)),
INDEX idx_trace_id TraceId TYPE bloom_filter(0.001) GRANULARITY 1,
INDEX idx_rum_session_id __hdx_materialized_rum.sessionId TYPE bloom_filter(0.001) GRANULARITY 1,
INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
Expand Down
1 change: 1 addition & 0 deletions docker/otel-collector/schema/seed/00005_otel_traces.sql
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ CREATE TABLE IF NOT EXISTS ${DATABASE}.otel_traces
`Links.TraceState` Array(String) CODEC(ZSTD(1)),
`Links.Attributes` Array(Map(LowCardinality(String), String)) CODEC(ZSTD(1)),
`__hdx_materialized_rum.sessionId` String MATERIALIZED ResourceAttributes['rum.sessionId'] CODEC(ZSTD(1)),
`SampleRate` UInt64 MATERIALIZED greatest(toUInt64OrZero(SpanAttributes['SampleRate']), 1) CODEC(T64, ZSTD(1)),
INDEX idx_trace_id TraceId TYPE bloom_filter(0.001) GRANULARITY 1,
INDEX idx_rum_session_id __hdx_materialized_rum.sessionId TYPE bloom_filter(0.001) GRANULARITY 1,
INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE ${DATABASE}.otel_traces DROP COLUMN IF EXISTS `SampleRate`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ALTER TABLE ${DATABASE}.otel_traces
ADD COLUMN IF NOT EXISTS `SampleRate` UInt64
MATERIALIZED greatest(toUInt64OrZero(SpanAttributes['SampleRate']), 1)
CODEC(T64, ZSTD(1));
6 changes: 6 additions & 0 deletions packages/api/src/controllers/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
AILineTableResponse,
AssistantLineTableConfigSchema,
ChartConfigWithDateRange,
SourceKind,
} from '@hyperdx/common-utils/dist/types';
import type { LanguageModel } from 'ai';
import * as chrono from 'chrono-node';
Expand Down Expand Up @@ -328,6 +329,11 @@ export function getChartConfigFromResolvedConfig(
connection: source.connection.toString(),
groupBy: resObject.groupBy,
timestampValueExpression: source.timestampValueExpression,
...(source.kind === SourceKind.Trace &&
'sampleRateExpression' in source &&
source.sampleRateExpression && {
sampleWeightExpression: source.sampleRateExpression,
}),
dateRange: [dateRange[0].toString(), dateRange[1].toString()],
markdown: resObject.markdown,
granularity: 'auto',
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/models/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export const TraceSource = Source.discriminator<ITraceSource>(
parentSpanIdExpression: String,
spanNameExpression: String,
spanKindExpression: String,
sampleRateExpression: String,
logSourceId: String,
sessionSourceId: String,
metricSourceId: String,
Expand Down
5 changes: 5 additions & 0 deletions packages/api/src/routers/external-api/v2/charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,11 @@ const buildChartConfigFromRequest = async (
],
where: '',
timestampValueExpression: source.timestampValueExpression,
...(source.kind === SourceKind.Trace &&
'sampleRateExpression' in source &&
source.sampleRateExpression && {
sampleWeightExpression: source.sampleRateExpression,
}),
dateRange: [new Date(params.startTime), new Date(params.endTime)] as [
Date,
Date,
Expand Down
17 changes: 17 additions & 0 deletions packages/api/src/tasks/checkAlerts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ export async function computeAliasWithClauses(
source.kind === SourceKind.Log || source.kind === SourceKind.Trace
? source.implicitColumnExpression
: undefined,
...(source.kind === SourceKind.Trace &&
'sampleRateExpression' in source &&
source.sampleRateExpression && {
sampleWeightExpression: source.sampleRateExpression,
}),
timestampValueExpression: source.timestampValueExpression,
};
const query = await renderChartConfig(config, metadata, source.querySettings);
Expand Down Expand Up @@ -453,6 +458,11 @@ const getChartConfigFromAlert = (
source.kind === SourceKind.Log || source.kind === SourceKind.Trace
? source.implicitColumnExpression
: undefined,
...(source.kind === SourceKind.Trace &&
'sampleRateExpression' in source &&
source.sampleRateExpression && {
sampleWeightExpression: source.sampleRateExpression,
}),
timestampValueExpression: source.timestampValueExpression,
};
} else if (details.taskType === AlertTaskType.TILE) {
Expand All @@ -474,6 +484,12 @@ const getChartConfigFromAlert = (
source.kind === SourceKind.Log || source.kind === SourceKind.Trace
? source.implicitColumnExpression
: undefined;
const sampleWeightExpression =
source.kind === SourceKind.Trace &&
'sampleRateExpression' in source &&
source.sampleRateExpression
? source.sampleRateExpression
: undefined;
const metricTables =
source.kind === SourceKind.Metric ? source.metricTables : undefined;
return {
Expand All @@ -486,6 +502,7 @@ const getChartConfigFromAlert = (
granularity: `${windowSizeInMins} minute`,
groupBy: tile.config.groupBy,
implicitColumnExpression,
sampleWeightExpression,
metricTables,
select: tile.config.select,
timestampValueExpression: source.timestampValueExpression,
Expand Down
5 changes: 5 additions & 0 deletions packages/api/src/tasks/checkAlerts/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,11 @@ ${targetTemplate}`;
where: savedSearch.where,
whereLanguage: savedSearch.whereLanguage,
implicitColumnExpression: source.implicitColumnExpression,
...(source.kind === SourceKind.Trace &&
'sampleRateExpression' in source &&
source.sampleRateExpression && {
sampleWeightExpression: source.sampleRateExpression,
}),
timestampValueExpression: source.timestampValueExpression,
orderBy: savedSearch.orderBy,
limit: {
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/DBDashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ const Tile = forwardRef(
...chart.config,
// Populate these two columns from the source to support Lucene-based filters
...pick(source, ['implicitColumnExpression', 'from']),
sampleWeightExpression: isTraceSource(source)
? source.sampleRateExpression
: undefined,
dateRange,
granularity,
filters,
Expand Down Expand Up @@ -265,6 +268,9 @@ const Tile = forwardRef(
isLogSource(source) || isTraceSource(source)
? source.implicitColumnExpression
: undefined,
sampleWeightExpression: isTraceSource(source)
? source.sampleRateExpression
: undefined,
filters,
metricTables: isMetricSource ? source.metricTables : undefined,
});
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/DBSearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,9 @@ function useSearchedConfigToChartConfig(
whereLanguage: whereLanguage ?? 'sql',
timestampValueExpression: sourceObj.timestampValueExpression,
implicitColumnExpression: sourceObj.implicitColumnExpression,
...(isTraceSource(sourceObj) && sourceObj.sampleRateExpression
? { sampleWeightExpression: sourceObj.sampleRateExpression }
: {}),
connection: sourceObj.connection,
displayType: DisplayType.Search,
orderBy: orderBy || defaultSearchConfig?.orderBy || defaultOrderBy,
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/ServicesDashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ function pickSourceConfigFields(source: TSource) {
...(isLogSource(source) || isTraceSource(source)
? { implicitColumnExpression: source.implicitColumnExpression }
: {}),
...(isTraceSource(source) && source.sampleRateExpression
? { sampleWeightExpression: source.sampleRateExpression }
: {}),
};
}
import {
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/SessionSubpanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ function useSessionChartConfigs({
where,
timestampValueExpression: traceSource.timestampValueExpression,
implicitColumnExpression: traceSource.implicitColumnExpression,
...(traceSource.sampleRateExpression && {
sampleWeightExpression: traceSource.sampleRateExpression,
}),
connection: traceSource.connection,
orderBy: `${traceSource.timestampValueExpression} ASC`,
limit: {
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/components/AlertPreviewChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ export const AlertPreviewChart = ({
isLogSource(source) || isTraceSource(source)
? source.implicitColumnExpression
: undefined,
sampleWeightExpression: isTraceSource(source)
? source.sampleRateExpression
: undefined,
groupBy,
with: aliasWith,
select: [
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/components/ChartEditor/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ export function convertFormStateToChartConfig(
isLogSource(source) || isTraceSource(source)
? source.implicitColumnExpression
: undefined,
sampleWeightExpression: isTraceSource(source)
? source.sampleRateExpression
: undefined,
metricTables: isMetricSource(source) ? source.metricTables : undefined,
where: form.where ?? '',
select: isSelectEmpty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ export default function ServiceDashboardDbQuerySidePanel({
'connection',
'from',
]),
...(source.sampleRateExpression && {
sampleWeightExpression: source.sampleRateExpression,
}),
where: '',
whereLanguage: 'sql',
select: [
Expand Down Expand Up @@ -146,6 +149,9 @@ export default function ServiceDashboardDbQuerySidePanel({
'connection',
'from',
]),
...(source.sampleRateExpression && {
sampleWeightExpression: source.sampleRateExpression,
}),
where: '',
whereLanguage: 'sql',
select: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ export default function ServiceDashboardEndpointPerformanceChart({
config={{
source: source.id,
...pick(source, ['timestampValueExpression', 'connection', 'from']),
...(source.sampleRateExpression && {
sampleWeightExpression: source.sampleRateExpression,
}),
where: '',
whereLanguage: 'sql',
select: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ export default function ServiceDashboardEndpointSidePanel({
'connection',
'from',
]),
...(source.sampleRateExpression && {
sampleWeightExpression: source.sampleRateExpression,
}),
where: '',
whereLanguage: 'sql',
select: [
Expand Down Expand Up @@ -159,6 +162,9 @@ export default function ServiceDashboardEndpointSidePanel({
'connection',
'from',
]),
...(source.sampleRateExpression && {
sampleWeightExpression: source.sampleRateExpression,
}),
where: '',
whereLanguage: 'sql',
select: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export default function SlowestEventsTile({
{
source: source.id,
...pick(source, ['timestampValueExpression', 'connection', 'from']),
...(source.sampleRateExpression && {
sampleWeightExpression: source.sampleRateExpression,
}),
where: '',
whereLanguage: 'sql',
select: [
Expand Down Expand Up @@ -117,6 +120,9 @@ export default function SlowestEventsTile({
'connection',
'from',
]),
...(source.sampleRateExpression && {
sampleWeightExpression: source.sampleRateExpression,
}),
where: '',
whereLanguage: 'sql',
select: [
Expand Down
15 changes: 15 additions & 0 deletions packages/app/src/components/Sources/SourceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1545,6 +1545,21 @@ export function TraceTableModelForm(props: TableModelProps) {
placeholder="SpanAttributes"
/>
</FormRow>
<FormRow
label={'Sample Rate Expression'}
helpText="Column or expression for upstream sampling weight (1/N). When set, aggregations (count, avg, sum, quantile) are corrected for sampling. Percentiles use quantileTDigestWeighted, which is an approximation -- exact values may differ slightly. Leave empty if spans are not sampled."
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
control={control}
name="sampleRateExpression"
placeholder="SampleRate"
/>
</FormRow>
<FormRow
label={'Span Events Expression'}
helpText="Expression to extract span events. Used to capture events associated with spans. Expected to be Nested ( Timestamp DateTime64(9), Name LowCardinality(String), Attributes Map(LowCardinality(String), String)"
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ export function useSessions(
}),
timestampValueExpression: traceSource.timestampValueExpression,
implicitColumnExpression: traceSource.implicitColumnExpression,
...(traceSource.sampleRateExpression && {
sampleWeightExpression: traceSource.sampleRateExpression,
}),
connection: traceSource.connection,
groupBy: 'serviceName, sessionId',
},
Expand Down
Loading
Loading