diff --git a/cmd/query/app/querysvc/adjuster/sort.go b/cmd/query/app/querysvc/adjuster/sort.go new file mode 100644 index 00000000000..6ca367710b5 --- /dev/null +++ b/cmd/query/app/querysvc/adjuster/sort.go @@ -0,0 +1,81 @@ +// Copyright (c) 2024 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package adjuster + +import ( + "sort" + + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/ptrace" +) + +var _ Adjuster = (*SortAttributesAndEventsAdjuster)(nil) + +// SortAttributesAndEvents creates an adjuster that standardizes trace data by sorting elements: +// - Resource attributes are sorted lexicographically by their keys. +// - Span attributes are sorted lexicographically by their keys. +// - Span events are sorted lexicographically by their names. +// - Attributes within each span event are sorted lexicographically by their keys. +func SortAttributesAndEvents() SortAttributesAndEventsAdjuster { + return SortAttributesAndEventsAdjuster{} +} + +type SortAttributesAndEventsAdjuster struct{} + +func (s SortAttributesAndEventsAdjuster) Adjust(traces ptrace.Traces) { + resourceSpans := traces.ResourceSpans() + for i := 0; i < resourceSpans.Len(); i++ { + rs := resourceSpans.At(i) + resource := rs.Resource() + s.sortAttributes(resource.Attributes()) + scopeSpans := rs.ScopeSpans() + for j := 0; j < scopeSpans.Len(); j++ { + ss := scopeSpans.At(j) + s.sortAttributes(ss.Scope().Attributes()) + spans := ss.Spans() + for k := 0; k < spans.Len(); k++ { + span := spans.At(k) + s.sortAttributes(span.Attributes()) + s.sortEvents(span.Events()) + links := span.Links() + for l := 0; l < links.Len(); l++ { + link := links.At(l) + s.sortAttributes(link.Attributes()) + } + } + } + } +} + +func (SortAttributesAndEventsAdjuster) sortAttributes(attributes pcommon.Map) { + entries := make([]struct { + key string + value pcommon.Value + }, 0, attributes.Len()) + attributes.Range(func(k string, v pcommon.Value) bool { + entries = append(entries, struct { + key string + value pcommon.Value + }{key: k, value: v}) + return true + }) + sort.Slice(entries, func(i, j int) bool { + return entries[i].key < entries[j].key + }) + newAttributes := pcommon.NewMap() + for _, entry := range entries { + entry.value.CopyTo(newAttributes.PutEmpty(entry.key)) + } + newAttributes.CopyTo(attributes) +} + +func (s SortAttributesAndEventsAdjuster) sortEvents(events ptrace.SpanEventSlice) { + events.Sort(func(a, b ptrace.SpanEvent) bool { + return a.Name() < b.Name() + }) + for i := 0; i < events.Len(); i++ { + event := events.At(i) + s.sortAttributes(event.Attributes()) + } +} diff --git a/cmd/query/app/querysvc/adjuster/sort_test.go b/cmd/query/app/querysvc/adjuster/sort_test.go new file mode 100644 index 00000000000..d9bef8baa37 --- /dev/null +++ b/cmd/query/app/querysvc/adjuster/sort_test.go @@ -0,0 +1,97 @@ +// Copyright (c) 2024 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package adjuster + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/pdata/ptrace" +) + +func TestSortAttributesAndEventsAdjuster(t *testing.T) { + adjuster := SortAttributesAndEvents() + input := func() ptrace.Traces { + traces := ptrace.NewTraces() + rs := traces.ResourceSpans().AppendEmpty() + + resource := rs.Resource() + resource.Attributes().PutStr("attributeZ", "valA") + resource.Attributes().PutStr("attributeA", "valB") + resource.Attributes().PutInt("attributeY", 1) + resource.Attributes().PutStr("attributeX", "valC") + + ss := rs.ScopeSpans().AppendEmpty() + ss.Scope().Attributes().PutStr("attributeH", "valI") + ss.Scope().Attributes().PutStr("attributeF", "valG") + + span := ss.Spans().AppendEmpty() + span.Attributes().PutStr("attributeW", "valD") + span.Attributes().PutStr("attributeB", "valZ") + span.Attributes().PutInt("attributeV", 2) + + event2 := span.Events().AppendEmpty() + event2.SetName("event2") + event2.Attributes().PutStr("attributeU", "valE") + event2.Attributes().PutStr("attributeT", "valF") + + event1 := span.Events().AppendEmpty() + event1.SetName("event1") + event1.Attributes().PutStr("attributeR", "valE") + event1.Attributes().PutStr("attributeS", "valF") + + link1 := span.Links().AppendEmpty() + link1.Attributes().PutStr("attributeA", "valB") + link1.Attributes().PutStr("attributeB", "valC") + + link2 := span.Links().AppendEmpty() + link2.Attributes().PutStr("attributeD", "valE") + link2.Attributes().PutStr("attributeC", "valD") + + return traces + } + expected := func() ptrace.Traces { + traces := ptrace.NewTraces() + rs := traces.ResourceSpans().AppendEmpty() + + resource := rs.Resource() + resource.Attributes().PutStr("attributeA", "valB") + resource.Attributes().PutStr("attributeX", "valC") + resource.Attributes().PutInt("attributeY", 1) + resource.Attributes().PutStr("attributeZ", "valA") + + ss := rs.ScopeSpans().AppendEmpty() + ss.Scope().Attributes().PutStr("attributeF", "valG") + ss.Scope().Attributes().PutStr("attributeH", "valI") + + span := ss.Spans().AppendEmpty() + span.Attributes().PutStr("attributeB", "valZ") + span.Attributes().PutInt("attributeV", 2) + span.Attributes().PutStr("attributeW", "valD") + + event1 := span.Events().AppendEmpty() + event1.SetName("event1") + event1.Attributes().PutStr("attributeR", "valE") + event1.Attributes().PutStr("attributeS", "valF") + + event2 := span.Events().AppendEmpty() + event2.SetName("event2") + event2.Attributes().PutStr("attributeT", "valF") + event2.Attributes().PutStr("attributeU", "valE") + + link1 := span.Links().AppendEmpty() + link1.Attributes().PutStr("attributeA", "valB") + link1.Attributes().PutStr("attributeB", "valC") + + link2 := span.Links().AppendEmpty() + link2.Attributes().PutStr("attributeC", "valD") + link2.Attributes().PutStr("attributeD", "valE") + + return traces + } + + i := input() + adjuster.Adjust(i) + assert.Equal(t, expected(), i) +}