Skip to content

Commit

Permalink
Add feature of sorting by the span latency
Browse files Browse the repository at this point in the history
  • Loading branch information
ymtdzzz committed Sep 19, 2024
1 parent 2b90d15 commit 4c7d43c
Show file tree
Hide file tree
Showing 13 changed files with 334 additions and 22 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ require (
github.com/hashicorp/nomad/api v0.0.0-20240717122358-3d93bd3778f3 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/hetznercloud/hcloud-go/v2 v2.10.2 // indirect
github.com/icza/gox v0.0.0-20240829094117-5982a7a6cca1 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/ionos-cloud/sdk-go/v6 v6.1.11 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfE
github.com/hetznercloud/hcloud-go/v2 v2.10.2 h1:9gyTUPhfNbfbS40Spgij5mV5k37bOZgt8iHKCbfGs5I=
github.com/hetznercloud/hcloud-go/v2 v2.10.2/go.mod h1:xQ+8KhIS62W0D78Dpi57jsufWh844gUw1az5OUvaeq8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/icza/gox v0.0.0-20240829094117-5982a7a6cca1 h1:7i7BDcTpFl5LAllCe3lrWyUlNGHc5bC6TF5VLDQI0q4=
github.com/icza/gox v0.0.0-20240829094117-5982a7a6cca1/go.mod h1:VbcN86fRkkUMPX2ufM85Um8zFndLZswoIW1eYtpAcVk=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand Down
4 changes: 4 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/elastic/elastic-transport-go/v8 v8.6.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
github.com/elastic/go-elasticsearch/v8 v8.14.0/go.mod h1:WRvnlGkSuZyp83M2U8El/LGXpCjYLrvlkSgkAH4O5I4=
github.com/elastic/lunes v0.1.0/go.mod h1:xGphYIt3XdZRtyWosHQTErsQTd4OP1p9wsbVoHelrd4=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-fonts/liberation v0.3.2/go.mod h1:N0QsDLVUQPy3UYg9XAc3Uh3UDMp2Z7M1o4+X98dXkmI=
Expand Down Expand Up @@ -327,6 +328,7 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/lightstep/go-expohisto v1.0.0/go.mod h1:xDXD0++Mu2FOaItXtdDfksfgxfV0z1TMPa+e/EUd0cs=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
Expand All @@ -352,9 +354,11 @@ github.com/open-telemetry/opentelemetry-collector-contrib/pkg/batchpersignal v0.
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.106.1/go.mod h1:6MVXAX6OpG01Gb38KJUP/8APe2BCmGYtzKPOua05bTw=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.107.0/go.mod h1:qj9lEtkVjQUzZ7FdJTeDqqTUq9xVU9kE4F8zZnHFB9M=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.108.0/go.mod h1:3ku/cfl0FXMSc/dc9DGrhABhE6/AoYArKtl3I9QEp28=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.109.0/go.mod h1:P7e6ch+uoSfxK+lMwfcndkHE6gWUqvWKpr7mD04KIAA=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.106.1/go.mod h1:St0VVFKzA0fNxo5RmzI4fg7ucGttd840OZ56a+ZECZs=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.107.0/go.mod h1:oG/PliNiIOUHVARyDrFdvxFvG8DUPEjMGlmxjEqeoKM=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.108.0/go.mod h1:G+N43ID1sP2CnffxkYdMyuJpep2UcGQUyq4HiAmcYSw=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.109.0/go.mod h1:KvJWxR0bDk9Qh0ktw4gOFsd/ZrJ7p5KTAQueEJsaK9Q=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.106.1/go.mod h1:ehzaiDdkrww7l1Stvse5GCOAsAZOpFcgeIbB/2PqFs4=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.107.0/go.mod h1:/RtBag3LuHIkqN4bo8Erd3jCzA3gea70l9WyJ9TncXM=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/resourcetotelemetry v0.106.1/go.mod h1:qsYqM+UGm3h2M+MQnaKBeQmsBC+sIGgGNJyyQFfUCUI=
Expand Down
60 changes: 60 additions & 0 deletions tuiexporter/internal/telemetry/sort.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package telemetry

import "sort"

const (
SORT_TYPE_NONE SortType = "none"
SORT_TYPE_LATENCY_DESC SortType = "latency-desc"
SORT_TYPE_LATENCY_ASC SortType = "latency-asc"
)

// SortType is sort type
type SortType string

func (t SortType) IsNone() bool {
return t == SORT_TYPE_NONE
}

func (t SortType) IsDesc() bool {
return t == SORT_TYPE_LATENCY_DESC
}

func (t SortType) GetHeaderLabel() string {
switch t {
case SORT_TYPE_LATENCY_DESC:
return "Latency"
case SORT_TYPE_LATENCY_ASC:
return "Latency"
}
return "N/A"
}

func sortSvcSpans(svcSpans SvcSpans, sortType SortType) {
switch sortType {
case SORT_TYPE_NONE:
sort.Slice(svcSpans, func(i, j int) bool {
// default sort is received_at asc
return svcSpans[i].ReceivedAt.Before(svcSpans[j].ReceivedAt)
})
case SORT_TYPE_LATENCY_DESC:
sort.Slice(svcSpans, func(i, j int) bool {
istart := svcSpans[i].Span.StartTimestamp().AsTime()
iend := svcSpans[i].Span.EndTimestamp().AsTime()
iduration := iend.Sub(istart)
jstart := svcSpans[j].Span.StartTimestamp().AsTime()
jend := svcSpans[j].Span.EndTimestamp().AsTime()
jduration := jend.Sub(jstart)
return iduration > jduration
})
case SORT_TYPE_LATENCY_ASC:
sort.Slice(svcSpans, func(i, j int) bool {
istart := svcSpans[i].Span.StartTimestamp().AsTime()
iend := svcSpans[i].Span.EndTimestamp().AsTime()
iduration := iend.Sub(istart)
jstart := svcSpans[j].Span.StartTimestamp().AsTime()
jend := svcSpans[j].Span.EndTimestamp().AsTime()
jduration := jend.Sub(jstart)
return iduration < jduration
})
}
}
150 changes: 150 additions & 0 deletions tuiexporter/internal/telemetry/sort_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package telemetry

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/ymtdzzz/otel-tui/tuiexporter/internal/test"
)

func TestSortType(t *testing.T) {
tests := []struct {
name string
input SortType
wantIsNone bool
wantIsDesc bool
wantHeaderLabel string
}{
{
name: "SORT_TYPE_NONE",
input: SORT_TYPE_NONE,
wantIsNone: true,
wantIsDesc: false,
wantHeaderLabel: "N/A",
},
{
name: "SORT_TYPE_LATENCY_DESC",
input: SORT_TYPE_LATENCY_DESC,
wantIsNone: false,
wantIsDesc: true,
wantHeaderLabel: "Latency",
},
{
name: "SORT_TYPE_LATENCY_ASC",
input: SORT_TYPE_LATENCY_ASC,
wantIsNone: false,
wantIsDesc: false,
wantHeaderLabel: "Latency",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.wantIsNone, tt.input.IsNone())
assert.Equal(t, tt.wantIsDesc, tt.input.IsDesc())
assert.Equal(t, tt.wantHeaderLabel, tt.input.GetHeaderLabel())
})
}
}

func TestSortSvcSpans(t *testing.T) {
baseSvcSpans := SvcSpans{
&SpanData{
Span: test.GenerateSpanWithDuration(t, "100ms", 100*time.Millisecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "75µs", 50*time.Microsecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "230ms", 230*time.Millisecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "101ms", 101*time.Millisecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "50ns", 50*time.Nanosecond),
},
}

tests := []struct {
name string
sortType SortType
input SvcSpans
want SvcSpans
}{
{
name: "SORT_TYPE_NONE",
sortType: SORT_TYPE_NONE,
input: append(SvcSpans{}, baseSvcSpans...),
want: SvcSpans{
&SpanData{
Span: test.GenerateSpanWithDuration(t, "100ms", 100*time.Millisecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "75µs", 50*time.Microsecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "230ms", 230*time.Millisecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "101ms", 101*time.Millisecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "50ns", 50*time.Nanosecond),
},
},
},
{
name: "SORT_TYPE_LATENCY_DESC",
sortType: SORT_TYPE_LATENCY_DESC,
input: append(SvcSpans{}, baseSvcSpans...),
want: SvcSpans{
&SpanData{
Span: test.GenerateSpanWithDuration(t, "230ms", 230*time.Millisecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "101ms", 101*time.Millisecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "100ms", 100*time.Millisecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "75µs", 50*time.Microsecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "50ns", 50*time.Nanosecond),
},
},
},
{
name: "SORT_TYPE_LATENCY_ASC",
sortType: SORT_TYPE_LATENCY_ASC,
input: append(SvcSpans{}, baseSvcSpans...),
want: SvcSpans{
&SpanData{
Span: test.GenerateSpanWithDuration(t, "50ns", 50*time.Nanosecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "75µs", 50*time.Microsecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "100ms", 100*time.Millisecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "101ms", 101*time.Millisecond),
},
&SpanData{
Span: test.GenerateSpanWithDuration(t, "230ms", 230*time.Millisecond),
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sortSvcSpans(tt.input, tt.sortType)
assert.Equal(t, tt.want, tt.input)
})
}
}
11 changes: 8 additions & 3 deletions tuiexporter/internal/telemetry/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type Store struct {
filterSvc string
filterMetric string
filterLog string
sortTrace SortType
svcspans SvcSpans
svcspansFiltered SvcSpans
tracecache *TraceCache
Expand Down Expand Up @@ -145,13 +146,15 @@ func (s *Store) UpdatedAt() time.Time {
return s.updatedAt
}

// ApplyFilterTraces applies a filter to the traces
func (s *Store) ApplyFilterTraces(svc string) {
// ApplyFilterTraces applies a filter and sort to the traces
func (s *Store) ApplyFilterTraces(svc string, sortType SortType) {
s.filterSvc = svc
s.sortTrace = sortType
s.svcspansFiltered = []*SpanData{}

if svc == "" {
s.svcspansFiltered = s.svcspans
sortSvcSpans(s.svcspansFiltered, sortType)
return
}

Expand All @@ -165,10 +168,12 @@ func (s *Store) ApplyFilterTraces(svc string) {
s.svcspansFiltered = append(s.svcspansFiltered, span)
}
}

sortSvcSpans(s.svcspansFiltered, sortType)
}

func (s *Store) updateFilterService() {
s.ApplyFilterTraces(s.filterSvc)
s.ApplyFilterTraces(s.filterSvc, s.sortTrace)
}

// ApplyFilterMetrics applies a filter to the metrics
Expand Down
4 changes: 2 additions & 2 deletions tuiexporter/internal/telemetry/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func TestStoreSpanFilters(t *testing.T) {
traceID := testdata.Spans[0].TraceID().String()
store.AddSpan(&payload)

store.ApplyFilterTraces("0-0")
store.ApplyFilterTraces("0-0", SORT_TYPE_NONE)
assert.Equal(t, 2, len(store.svcspansFiltered))
assert.Equal(t, traceID, store.GetTraceIDByFilteredIdx(0))
assert.Equal(t, traceID, store.GetTraceIDByFilteredIdx(1))
Expand All @@ -78,7 +78,7 @@ func TestStoreSpanFilters(t *testing.T) {
assert.Equal(t, "span-0-0-1", store.GetFilteredServiceSpansByIdx(0)[1].Span.Name())
// spans in test-service-2
assert.Equal(t, "span-1-0-0", store.GetFilteredServiceSpansByIdx(1)[0].Span.Name())
store.ApplyFilterTraces("service-2")
store.ApplyFilterTraces("service-2", SORT_TYPE_NONE)
assert.Equal(t, 1, len(store.svcspansFiltered))
assert.Equal(t, traceID, store.GetTraceIDByFilteredIdx(0))
assert.Equal(t, "", store.GetTraceIDByFilteredIdx(1))
Expand Down
13 changes: 13 additions & 0 deletions tuiexporter/internal/test/tracegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,16 @@ func fillSpan(t *testing.T, span ptrace.Span, traceID, resourceIndex, scopeIndex
status.SetCode(ptrace.StatusCodeOk)
status.SetMessage("status ok")
}

// GenerateSpanWithDuration returns a span with specified span name and duration.
func GenerateSpanWithDuration(t *testing.T, spanName string, duration time.Duration) *ptrace.Span {
t.Helper()

span := ptrace.NewSpan()
span.SetName(spanName)
endTimeStamp := pcommon.NewTimestampFromTime(spanStartTimestamp.AsTime().Add(duration))
span.SetStartTimestamp(spanStartTimestamp)
span.SetEndTimestamp(endTimeStamp)

return &span
}
3 changes: 2 additions & 1 deletion tuiexporter/internal/tui/component/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ func NewLogDataForTable(logs *[]*telemetry.LogData) LogDataForTable {
// see: https://github.com/rivo/tview/wiki/VirtualTable
func (l LogDataForTable) GetCell(row, column int) *tview.TableCell {
if row == 0 {
return getHeaderCell(logTableHeader[:], column)
sortType := telemetry.SORT_TYPE_NONE
return getHeaderCell(logTableHeader[:], column, &sortType)
}
if row > 0 && row <= len(*l.logs) {
return getCellFromLog((*l.logs)[row-1], column)
Expand Down
3 changes: 2 additions & 1 deletion tuiexporter/internal/tui/component/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ func NewMetricDataForTable(metrics *[]*telemetry.MetricData) MetricDataForTable
// see: https://github.com/rivo/tview/wiki/VirtualTable
func (m MetricDataForTable) GetCell(row, column int) *tview.TableCell {
if row == 0 {
return getHeaderCell(metricTableHeader[:], column)
sortType := telemetry.SORT_TYPE_NONE
return getHeaderCell(metricTableHeader[:], column, &sortType)
}
if row > 0 && row <= len(*m.metrics) {
return getCellFromMetrics((*m.metrics)[row-1], column)
Expand Down
Loading

0 comments on commit 4c7d43c

Please sign in to comment.