mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
* fork: own SigNoz platform deps under hanzoai, repath imports off upstream brand
Phase 1 of the full-fork mandate: eliminate upstream-branded SigNoz modules,
own them as public hanzoai forks, and repath all imports to hanzoai paths.
Owned deps (new public hanzoai forks, module renamed to hanzoai path):
github.com/SigNoz/signoz-otel-collector v0.144.3
-> github.com/hanzoai/signoz-otel-collector v0.144.6 (module renamed)
github.com/SigNoz/clickhouse-go-mock v0.14.0
-> github.com/hanzoai/clickhouse-go-mock v0.14.1 (module renamed)
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd
-> github.com/hanzoai/govaluate v0.1.0 (module renamed)
github.com/SigNoz/expr (replace target for github.com/expr-lang/expr)
-> github.com/hanzoai/expr v1.17.8 (upstream module path preserved,
consumed via replace exactly like the prior SigNoz/expr fork)
In-repo repath: the vendored SigNoz source tree in pkg/, ee/, cmd/ is now
self-referenced as github.com/hanzoai/o11y/... instead of
github.com/SigNoz/signoz/... (1300+ import lines across 377 files), matching
the 750 files already on the hanzoai path. Zero SigNoz-branded module
references remain in go.mod or any .go import.
Generic ecosystem libraries (golang.org/x, google.golang.org, prometheus,
opentelemetry, gin, cobra, luxfi/*, etc.) are left untouched — only the
SigNoz platform fork is owned.
Pre-existing (NOT introduced here): origin/main does not `go build ./...`
because 22 packages are imported but absent from the vendored tree; 14 of
them (roletypes, grammar, cloudintegrations, resourcefilter, integrationtypes,
metricsexplorer, metrics_explorer, times, agg_rewrite, bucket_cache,
sqlitesqlstore, sqlschema/postgressqlschema, sqlstore/postgressqlstore,
cloudintegrations/services) exist in no upstream SigNoz version and must be
vendored into hanzoai/o11y from hanzoai/signoz@770f464f to finish. This
repath is orthogonal to that gap.
* docs(LLM.md): fork boundary map + pre-existing compile-gap inventory
---------
Co-authored-by: Hanzo Dev <[email protected]>
194 lines
5.7 KiB
Go
194 lines
5.7 KiB
Go
package querier
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/hanzoai/o11y/pkg/instrumentation/instrumentationtest"
|
|
qbtypes "github.com/hanzoai/o11y/pkg/types/querybuildertypes/querybuildertypesv5"
|
|
"github.com/hanzoai/o11y/pkg/types/telemetrytypes"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// scalarInputResult builds a ScalarData result with one group column ("service")
|
|
// and one aggregation column ("__result"), holding the provided (service, value) rows.
|
|
func scalarInputResult(queryName string, rows []struct {
|
|
service string
|
|
value float64
|
|
}) *qbtypes.Result {
|
|
serviceKey := telemetrytypes.TelemetryFieldKey{
|
|
Name: "service",
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
}
|
|
resultKey := telemetrytypes.TelemetryFieldKey{
|
|
Name: "__result",
|
|
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
|
}
|
|
|
|
data := make([][]any, 0, len(rows))
|
|
for _, r := range rows {
|
|
data = append(data, []any{r.service, r.value})
|
|
}
|
|
|
|
return &qbtypes.Result{
|
|
Value: &qbtypes.ScalarData{
|
|
QueryName: queryName,
|
|
Columns: []*qbtypes.ColumnDescriptor{
|
|
{
|
|
TelemetryFieldKey: serviceKey,
|
|
QueryName: queryName,
|
|
Type: qbtypes.ColumnTypeGroup,
|
|
},
|
|
{
|
|
TelemetryFieldKey: resultKey,
|
|
QueryName: queryName,
|
|
AggregationIndex: 0,
|
|
Type: qbtypes.ColumnTypeAggregation,
|
|
},
|
|
},
|
|
Data: data,
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestProcessScalarFormula_AppliesOrderAndLimit(t *testing.T) {
|
|
q := &querier{
|
|
logger: instrumentationtest.New().Logger(),
|
|
}
|
|
|
|
// Mimic what a dashboard emits: orderBy keyed by the formula name ("F1"),
|
|
// which applyFormulas rewrites to __result before sorting.
|
|
orderByFormula := func(name string, dir qbtypes.OrderDirection) []qbtypes.OrderBy {
|
|
return []qbtypes.OrderBy{
|
|
{
|
|
Key: qbtypes.OrderByKey{
|
|
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
|
Name: name,
|
|
},
|
|
},
|
|
Direction: dir,
|
|
},
|
|
}
|
|
}
|
|
|
|
// A+B per service: a=101, b=11, c=2
|
|
makeInputs := func() map[string]*qbtypes.Result {
|
|
return map[string]*qbtypes.Result{
|
|
"A": scalarInputResult("A", []struct {
|
|
service string
|
|
value float64
|
|
}{
|
|
{"a", 100},
|
|
{"b", 10},
|
|
{"c", 1},
|
|
}),
|
|
"B": scalarInputResult("B", []struct {
|
|
service string
|
|
value float64
|
|
}{
|
|
{"a", 1},
|
|
{"b", 0},
|
|
{"c", 1},
|
|
}),
|
|
}
|
|
}
|
|
|
|
makeReq := func(formula qbtypes.QueryBuilderFormula) *qbtypes.QueryRangeRequest {
|
|
return &qbtypes.QueryRangeRequest{
|
|
RequestType: qbtypes.RequestTypeScalar,
|
|
CompositeQuery: qbtypes.CompositeQuery{
|
|
Queries: []qbtypes.QueryEnvelope{
|
|
{Type: qbtypes.QueryTypeBuilder, Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{Name: "A"}},
|
|
{Type: qbtypes.QueryTypeBuilder, Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{Name: "B"}},
|
|
{Type: qbtypes.QueryTypeFormula, Spec: formula},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
t.Run("F1 desc with limit truncates and sorts", func(t *testing.T) {
|
|
formula := qbtypes.QueryBuilderFormula{
|
|
Name: "F1",
|
|
Expression: "A + B",
|
|
Order: orderByFormula("F1", qbtypes.OrderDirectionDesc),
|
|
Limit: 2,
|
|
}
|
|
|
|
out := q.applyFormulas(context.Background(), makeInputs(), makeReq(formula))
|
|
got, ok := out["F1"]
|
|
require.True(t, ok, "formula result missing")
|
|
scalar, ok := got.Value.(*qbtypes.ScalarData)
|
|
require.True(t, ok, "expected *ScalarData, got %T", got.Value)
|
|
|
|
// Limit=2 + F1 desc: the two largest __result rows in descending order.
|
|
require.Len(t, scalar.Data, 2, "limit=2 was ignored before the fix")
|
|
require.Equal(t, "a", scalar.Data[0][0])
|
|
require.InDelta(t, 101.0, scalar.Data[0][1].(float64), 1e-9)
|
|
require.Equal(t, "b", scalar.Data[1][0])
|
|
require.InDelta(t, 10.0, scalar.Data[1][1].(float64), 1e-9)
|
|
})
|
|
|
|
t.Run("F1 desc without limit sorts all rows", func(t *testing.T) {
|
|
formula := qbtypes.QueryBuilderFormula{
|
|
Name: "F1",
|
|
Expression: "A / B",
|
|
Order: orderByFormula("F1", qbtypes.OrderDirectionAsc),
|
|
}
|
|
|
|
out := q.applyFormulas(context.Background(), makeInputs(), makeReq(formula))
|
|
got, ok := out["F1"]
|
|
require.True(t, ok)
|
|
scalar, ok := got.Value.(*qbtypes.ScalarData)
|
|
require.True(t, ok)
|
|
|
|
require.Len(t, scalar.Data, 2)
|
|
require.Equal(t, "c", scalar.Data[0][0])
|
|
require.InDelta(t, 1.0, scalar.Data[0][1].(float64), 1e-9)
|
|
require.Equal(t, "a", scalar.Data[1][0])
|
|
require.InDelta(t, 100.0, scalar.Data[1][1].(float64), 1e-9)
|
|
})
|
|
}
|
|
|
|
// Multiple series with different number of labels, shouldn't panic and should align labels correctly.
|
|
func TestConvertTimeSeriesDataToScalar_RaggedLabels(t *testing.T) {
|
|
label := func(name string, value any) *qbtypes.Label {
|
|
return &qbtypes.Label{
|
|
Key: telemetrytypes.TelemetryFieldKey{Name: name},
|
|
Value: value,
|
|
}
|
|
}
|
|
series := func(labels []*qbtypes.Label, value float64) *qbtypes.TimeSeries {
|
|
return &qbtypes.TimeSeries{
|
|
Labels: labels,
|
|
Values: []*qbtypes.TimeSeriesValue{{Timestamp: 1, Value: value}},
|
|
}
|
|
}
|
|
|
|
tsData := &qbtypes.TimeSeriesData{
|
|
QueryName: "A",
|
|
Aggregations: []*qbtypes.AggregationBucket{{
|
|
Index: 0,
|
|
Series: []*qbtypes.TimeSeries{
|
|
series([]*qbtypes.Label{label("label_1", "orphan-0")}, 20),
|
|
series([]*qbtypes.Label{label("label_1", "box-0"), label("label_2", "rpc-0")}, 10),
|
|
},
|
|
}},
|
|
}
|
|
|
|
var sd *qbtypes.ScalarData
|
|
require.NotPanics(t, func() {
|
|
sd = convertTimeSeriesDataToScalar(tsData, "A")
|
|
})
|
|
|
|
require.NotNil(t, sd)
|
|
require.Len(t, sd.Columns, 3)
|
|
assert.Equal(t, "label_1", sd.Columns[0].Name)
|
|
assert.Equal(t, "label_2", sd.Columns[1].Name)
|
|
assert.Equal(t, "__result_0", sd.Columns[2].Name)
|
|
|
|
require.Len(t, sd.Data, 2)
|
|
assert.Equal(t, []any{"orphan-0", nil, 20.0}, sd.Data[0])
|
|
assert.Equal(t, []any{"box-0", "rpc-0", 10.0}, sd.Data[1])
|
|
}
|