Files
72cfe1b4c3 feat: allow span filtering in service graph processor (#6453)
* added spanfilter to filter out spans

* return the error if the filters are not correctly formatted

* add filters to the userconfigurable overrides

* add core logic for drop items cache

* wired up everything

* some tweaks

* refactor

* fmt and manifest

* tighten names

* rename metric

* drop already buffered counterparts when a span is filter out

* add a new test and documentation

* Update docs/sources/tempo/operations/manage-advanced-systems/user-configurable-overrides.md

Co-authored-by: Suraj Nath <[email protected]>

* Update docs/sources/tempo/configuration/_index.md

Co-authored-by: Suraj Nath <[email protected]>

* Update docs/sources/tempo/configuration/_index.md

Co-authored-by: Suraj Nath <[email protected]>

* bail early when the span kind is not client or server

* Update docs/sources/tempo/operations/manage-advanced-systems/user-configurable-overrides.md

Co-authored-by: Suraj Nath <[email protected]>

---------

Co-authored-by: Suraj Nath <[email protected]>
2026-02-22 00:55:24 -08:00

236 lines
6.8 KiB
Go

package api
import (
"encoding/json"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/grafana/tempo/modules/overrides"
"github.com/grafana/tempo/modules/overrides/histograms"
"github.com/grafana/tempo/pkg/sharedconfig"
filterconfig "github.com/grafana/tempo/pkg/spanfilter/config"
)
func Test_limitsFromOverrides(t *testing.T) {
userID := "foo"
cfg := overrides.Config{
Defaults: overrides.Overrides{
Forwarders: []string{"my-forwarder"},
MetricsGenerator: overrides.MetricsGeneratorOverrides{
Processors: map[string]struct{}{"service-graphs": {}},
CollectionInterval: 15 * time.Second,
IngestionSlack: time.Minute,
DisableCollection: true,
TraceIDLabelName: "trace_id",
GenerateNativeHistograms: histograms.HistogramMethodBoth,
NativeHistogramMaxBucketNumber: 160,
NativeHistogramBucketFactor: 1.2,
NativeHistogramMinResetDuration: 10 * time.Minute,
SpanNameSanitization: "enabled",
Processor: overrides.ProcessorOverrides{
ServiceGraphs: overrides.ServiceGraphsOverrides{
HistogramBuckets: []float64{0.1, 0.2, 0.5},
Dimensions: []string{"my-dim1", "my-dim2"},
PeerAttributes: []string{"db.name"},
FilterPolicies: []filterconfig.FilterPolicy{{Exclude: &filterconfig.PolicyMatch{MatchType: filterconfig.Regex, Attributes: []filterconfig.MatchPolicyAttribute{{Key: "resource.service.name", Value: "unknown_service:myservice"}}}}},
EnableClientServerPrefix: boolPtr(true),
},
SpanMetrics: overrides.SpanMetricsOverrides{
Dimensions: []string{"your-dim1", "your-dim2"},
IntrinsicDimensions: map[string]bool{"service": true, "span_name": false},
DimensionMappings: []sharedconfig.DimensionMappings{{Name: "env", SourceLabel: []string{"k8s.namespace", "foo"}, Join: "/"}},
EnableTargetInfo: boolPtr(true),
EnableInstanceLabel: boolPtr(true),
FilterPolicies: []filterconfig.FilterPolicy{
{
Exclude: &filterconfig.PolicyMatch{
MatchType: filterconfig.Regex,
Attributes: []filterconfig.MatchPolicyAttribute{
{
Key: "resource.service.name",
Value: "unknown_service:myservice",
},
},
},
},
},
HistogramBuckets: []float64{1, 2, 5},
TargetInfoExcludedDimensions: []string{"no"},
},
HostInfo: overrides.HostInfoOverrides{
HostIdentifiers: []string{"k8s.node.name", "host.id"},
MetricName: "traces_host_info",
},
},
},
},
}
overridesInt, err := overrides.NewOverrides(cfg, nil, prometheus.DefaultRegisterer)
assert.NoError(t, err)
limits := limitsFromOverrides(overridesInt, userID)
limitsJSON, err := json.MarshalIndent(limits, "", " ")
assert.NoError(t, err)
expectedJSON := `{
"forwarders": [
"my-forwarder"
],
"cost_attribution": {},
"metrics_generator": {
"processors": [
"service-graphs"
],
"disable_collection": true,
"collection_interval": "15s",
"trace_id_label_name": "trace_id",
"ingestion_time_range_slack": "1m0s",
"generate_native_histograms": "both",
"native_histogram_max_bucket_number": 160,
"native_histogram_bucket_factor": 1.2,
"native_histogram_min_reset_duration": "10m0s",
"span_name_sanitization": "enabled",
"processor": {
"service_graphs": {
"dimensions": [
"my-dim1",
"my-dim2"
],
"enable_client_server_prefix": true,
"peer_attributes": [
"db.name"
],
"filter_policies": [
{
"exclude": {
"match_type": "regex",
"attributes": [
{
"key": "resource.service.name",
"value": "unknown_service:myservice"
}
]
}
}
],
"histogram_buckets": [
0.1,
0.2,
0.5
]
},
"span_metrics": {
"dimensions": [
"your-dim1",
"your-dim2"
],
"intrinsic_dimensions": {
"service": true,
"span_name": false
},
"dimension_mappings": [
{
"name": "env",
"source_labels": [
"k8s.namespace",
"foo"
],
"join": "/"
}
],
"enable_target_info": true,
"filter_policies": [
{
"exclude": {
"match_type": "regex",
"attributes": [
{
"key": "resource.service.name",
"value": "unknown_service:myservice"
}
]
}
}
],
"histogram_buckets": [
1,
2,
5
],
"target_info_excluded_dimensions": [
"no"
],
"enable_instance_label": true
},
"host_info": {
"host_identifiers": [
"k8s.node.name",
"host.id"
],
"metric_name": "traces_host_info"
}
}
}
}`
assert.Equal(t, expectedJSON, string(limitsJSON))
}
func Test_limitsFromOverrides_EmptyOrNilFilterPoliciesDontCrash(t *testing.T) {
userID := "foo"
testCases := []struct {
name string
serviceGraphPolicies []filterconfig.FilterPolicy
spanMetricsPolicies []filterconfig.FilterPolicy
}{
{
name: "nil filter policies for both processors",
},
{
name: "empty service graph filter policies",
serviceGraphPolicies: []filterconfig.FilterPolicy{},
},
{
name: "empty span metrics filter policies",
spanMetricsPolicies: []filterconfig.FilterPolicy{},
},
{
name: "empty filter policies for both processors",
serviceGraphPolicies: []filterconfig.FilterPolicy{},
spanMetricsPolicies: []filterconfig.FilterPolicy{},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cfg := overrides.Config{
Defaults: overrides.Overrides{
MetricsGenerator: overrides.MetricsGeneratorOverrides{
Processor: overrides.ProcessorOverrides{
ServiceGraphs: overrides.ServiceGraphsOverrides{
FilterPolicies: tc.serviceGraphPolicies,
},
SpanMetrics: overrides.SpanMetricsOverrides{
FilterPolicies: tc.spanMetricsPolicies,
},
},
},
},
}
assert.NotPanics(t, func() {
overridesInt, err := overrides.NewOverrides(cfg, nil, prometheus.NewRegistry())
assert.NoError(t, err)
limits := limitsFromOverrides(overridesInt, userID)
_, err = json.Marshal(limits)
assert.NoError(t, err)
})
})
}
}