mirror of
https://github.com/SigNoz/signoz.git
synced 2026-08-06 13:10:40 +01:00
Split the five telemetry<signal> packages, which mixed three concerns, into three per-signal layers with a cycle-free dependency direction: - telemetryschema/<signal>telemetryschema — primitives (const + table selection, field_mapper, condition_builder, trace helpers); leaf layer. - statementbuilder/<signal>statementbuilder — SQL generation. The parent statementbuilder package is contract-only (the Builders bundle + Config); each sub-package exposes a factory.ProviderFactory[..., statementbuilder.Config] whose New internalizes FieldMapper/ConditionBuilder/AggExprRewriter and reads SkipResourceFingerprint. Traces exposes two factories (query + operator). telemetryresourcefilter moves here as statementbuilder/resourcefilter. - telemetrymetadata — key/value resolution; NewTelemetryMetaStore collapses from 24 args to (settings, telemetrystore, flagger), sourcing table names from the schema constants. Centralize query-stack assembly in signoz.go via newQueryStack: build the single metadata store, run each per-signal statement-builder factory, assemble the statementbuilder.Builders bundle, and build the bucket cache — once. This is the only place that imports the concrete sub-packages (so the edge runs subs -> parent, cycle-free), and it removes the duplicate metadata store that signozquerier used to build, leaving signozquerier a thin querier.New(*statementbuilder.Builders) adapter. Also: - statementbuilder.Config owns SkipResourceFingerprint (moved off querier.Config). YAML key moves querier.skip_resource_fingerprint -> statementbuilder.skip_resource_fingerprint. - Querier interface moves into querier.go (interfaces.go removed); BucketCache -> bucket_cache.go, Handler -> api.go. - Add pkg/querier/queriertest.MockQuerier.
175 lines
5.2 KiB
Go
175 lines
5.2 KiB
Go
package tracesstatementbuilder
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
|
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
|
"github.com/SigNoz/signoz/pkg/querybuilder"
|
|
"github.com/SigNoz/signoz/pkg/telemetryschema/tracestelemetryschema"
|
|
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
|
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
|
"github.com/SigNoz/signoz/pkg/types/telemetrytypes/telemetrytypestest"
|
|
"github.com/SigNoz/signoz/pkg/valuer"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestTraceTimeRangeOptimization(t *testing.T) {
|
|
releaseTime := time.Date(2025, 5, 22, 22, 0, 0, 0, time.UTC)
|
|
|
|
fm := tracestelemetryschema.NewFieldMapper()
|
|
cb := tracestelemetryschema.NewConditionBuilder(fm)
|
|
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
|
|
|
mockMetadataStore.KeysMap = tracestelemetryschema.BuildCompleteFieldKeyMap(releaseTime)
|
|
mockMetadataStore.KeysMap["trace_id"] = []*telemetrytypes.TelemetryFieldKey{{
|
|
Name: "trace_id",
|
|
FieldContext: telemetrytypes.FieldContextSpan,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
Signal: telemetrytypes.SignalTraces,
|
|
}}
|
|
mockMetadataStore.KeysMap["name"] = []*telemetrytypes.TelemetryFieldKey{{
|
|
Name: "name",
|
|
FieldContext: telemetrytypes.FieldContextSpan,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
Signal: telemetrytypes.SignalTraces,
|
|
}}
|
|
|
|
fl := flaggertest.New(t)
|
|
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, fl)
|
|
|
|
statementBuilder := NewTraceQueryStatementBuilder(
|
|
instrumentationtest.New().ToProviderSettings(),
|
|
mockMetadataStore,
|
|
fm,
|
|
cb,
|
|
aggExprRewriter,
|
|
nil, // telemetryStore is nil - adaptive path is disabled
|
|
fl,
|
|
false,
|
|
100000,
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
query qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]
|
|
expectTimeOptimization bool
|
|
}{
|
|
{
|
|
name: "query with trace_id filter",
|
|
query: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
|
|
Signal: telemetrytypes.SignalTraces,
|
|
Filter: &qbtypes.Filter{
|
|
Expression: "trace_id = '12345abc' AND service.name = 'api'",
|
|
},
|
|
SelectFields: []telemetrytypes.TelemetryFieldKey{
|
|
{Name: "name", FieldContext: telemetrytypes.FieldContextSpan},
|
|
},
|
|
},
|
|
expectTimeOptimization: true, // would optimize if telemetryStore was provided
|
|
},
|
|
{
|
|
name: "query with trace_id IN filter",
|
|
query: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
|
|
Signal: telemetrytypes.SignalTraces,
|
|
Filter: &qbtypes.Filter{
|
|
Expression: "trace_id IN ['12345abc', '67890def'] AND service.name = 'api'",
|
|
},
|
|
SelectFields: []telemetrytypes.TelemetryFieldKey{
|
|
{Name: "name", FieldContext: telemetrytypes.FieldContextSpan},
|
|
},
|
|
},
|
|
expectTimeOptimization: true, // would optimize if telemetryStore was provided
|
|
},
|
|
{
|
|
name: "query without trace_id filter",
|
|
query: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
|
|
Signal: telemetrytypes.SignalTraces,
|
|
Filter: &qbtypes.Filter{
|
|
Expression: "service.name = 'api'",
|
|
},
|
|
SelectFields: []telemetrytypes.TelemetryFieldKey{
|
|
{Name: "name", FieldContext: telemetrytypes.FieldContextSpan},
|
|
},
|
|
},
|
|
expectTimeOptimization: false,
|
|
},
|
|
{
|
|
name: "query with empty filter",
|
|
query: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
|
|
Signal: telemetrytypes.SignalTraces,
|
|
SelectFields: []telemetrytypes.TelemetryFieldKey{
|
|
{Name: "name", FieldContext: telemetrytypes.FieldContextSpan},
|
|
},
|
|
},
|
|
expectTimeOptimization: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
stmt, err := statementBuilder.Build(
|
|
ctx,
|
|
valuer.UUID{},
|
|
1747947419000, // start time in ms
|
|
1747983448000, // end time in ms
|
|
qbtypes.RequestTypeRaw,
|
|
tt.query,
|
|
nil,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, stmt)
|
|
|
|
assert.NotEmpty(t, stmt.Query)
|
|
|
|
if tt.query.Filter != nil && tt.query.Filter.Expression != "" {
|
|
traceIDs, found := tracestelemetryschema.ExtractTraceIDsFromFilter(tt.query.Filter.Expression)
|
|
assert.Equal(t, tt.expectTimeOptimization, found && len(traceIDs) > 0)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTraceTimeRangeFinderQuery(t *testing.T) {
|
|
expectedQuery := `
|
|
SELECT
|
|
toUnixTimestamp64Nano(min(timestamp)) as start_time,
|
|
toUnixTimestamp64Nano(max(timestamp)) as end_time
|
|
FROM signoz_traces.distributed_signoz_spans
|
|
WHERE traceID = ?
|
|
AND timestamp >= now() - INTERVAL 30 DAY
|
|
`
|
|
|
|
expectedQuery = normalizeQuery(expectedQuery)
|
|
|
|
actualQuery := `
|
|
SELECT
|
|
toUnixTimestamp64Nano(min(timestamp)) as start_time,
|
|
toUnixTimestamp64Nano(max(timestamp)) as end_time
|
|
FROM signoz_traces.distributed_signoz_spans
|
|
WHERE traceID = ?
|
|
AND timestamp >= now() - INTERVAL 30 DAY
|
|
`
|
|
actualQuery = normalizeQuery(actualQuery)
|
|
|
|
assert.Equal(t, expectedQuery, actualQuery)
|
|
}
|
|
|
|
func normalizeQuery(query string) string {
|
|
lines := []string{}
|
|
for _, line := range strings.Split(strings.TrimSpace(query), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line != "" {
|
|
lines = append(lines, line)
|
|
}
|
|
}
|
|
return strings.Join(lines, " ")
|
|
}
|