Skip to content

Commit

Permalink
Merge pull request #98 from ymtdzzz/feature/add_metrics_support
Browse files Browse the repository at this point in the history
Add metrics support (with only minimum basic features)
  • Loading branch information
ymtdzzz authored Jul 15, 2024
2 parents f50dc88 + eacb99e commit 398b722
Show file tree
Hide file tree
Showing 19 changed files with 1,308 additions and 16 deletions.
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# otel-tui

🚧 This project is under construction 🚧

A terminal OpenTelemetry viewer inspired by [otel-desktop-viewer](https://github.com/CtrlSpice/otel-desktop-viewer/tree/main)

Traces
![Traces](./docs/traces.png)
![Spans](./docs/spans.png)

Metrics
![Metrics](./docs/metrics.png)

Logs
![Logs](./docs/logs.png)

Expand Down Expand Up @@ -97,7 +98,21 @@ There're a lot of things to do. Here are some of them:
- [x] Show trace information
- [ ] ...
- Metrics
- [ ] Display metrics
- [x] Metric stream
- [x] Display metric stream
- [x] Filter metrics
- [x] Show metric information
- [ ] Display basic chart of the selected metric
- [x] Gauge
- [x] Sum
- [ ] Histogram
- [ ] ExponentialHistogram
- [ ] Summary
- [ ] Metric list
- [ ] Display metric stream
- [ ] Flexible chart (query, selectable dimensions, etc.)
- [ ] Auto refresh chart
- [ ] Asynchronous chart rendering
- [ ] ...
- Logs
- [x] Display logs
Expand Down
Binary file modified docs/logs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/metrics.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/spans.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/traces.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mostynb/go-grpc-compression v1.2.3 // indirect
github.com/navidys/tvxwidgets v0.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I=
github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg=
github.com/navidys/tvxwidgets v0.6.0 h1:ARIXGfx4aURHMhq+LW5vIoCCDx1X/PdTF8AcUq+nWZ0=
github.com/navidys/tvxwidgets v0.6.0/go.mod h1:wd6aS2OzjZczFbg8GCaVuwkFcY1eixlT/y7Lc/YIwlg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
Expand Down
4 changes: 4 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,21 @@ github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBj
github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea/go.mod h1:Y7Vld91/HRbTBm7JwoI7HejdDB0u+e9AUBO9MB7yuZk=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/goccmack/gocc v0.0.0-20230228185258-2292f9e40198/go.mod h1:DTh/Y2+NbnOVVoypCCQrovMPDKUGp4yZpSbWg5D0XIM=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
Expand Down
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ service:
receivers: [otlp]
processors: []
exporters: [tui]
metrics:
receivers: [otlp]
processors: []
exporters: [tui]
`

configProviderSettings := otelcol.ConfigProviderSettings{
Expand Down
7 changes: 7 additions & 0 deletions tuiexporter/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/ymtdzzz/otel-tui/tuiexporter/internal/tui"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)

Expand All @@ -27,6 +28,12 @@ func (e *tuiExporter) pushTraces(_ context.Context, traces ptrace.Traces) error
return nil
}

func (e *tuiExporter) pushMetrics(_ context.Context, metrics pmetric.Metrics) error {
e.app.Store().AddMetric(&metrics)

return nil
}

func (e *tuiExporter) pushLogs(_ context.Context, logs plog.Logs) error {
e.app.Store().AddLog(&logs)

Expand Down
23 changes: 22 additions & 1 deletion tuiexporter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func NewFactory() exporter.Factory {
component.MustNewType("tui"),
createDefaultConfig,
exporter.WithTraces(createTraces, stability),
//exporter.WithMetrics(createMetrics, stability),
exporter.WithMetrics(createMetrics, stability),
exporter.WithLogs(createLogs, stability),
)
}
Expand Down Expand Up @@ -49,6 +49,27 @@ func createTraces(ctx context.Context, set exporter.Settings, cfg component.Conf
)
}

func createMetrics(ctx context.Context, set exporter.Settings, cfg component.Config) (exporter.Metrics, error) {
oCfg := cfg.(*Config)

e, err := exporters.LoadOrStore(
oCfg,
func() (*tuiExporter, error) {
return newTuiExporter(oCfg), nil
},
&set.TelemetrySettings,
)
if err != nil {
return nil, err
}

return exporterhelper.NewMetricsExporter(ctx, set, oCfg,
e.Unwrap().pushMetrics,
exporterhelper.WithStart(e.Start),
exporterhelper.WithShutdown(e.Shutdown),
)
}

func createLogs(ctx context.Context, set exporter.Settings, cfg component.Config) (exporter.Logs, error) {
oCfg := cfg.(*Config)

Expand Down
76 changes: 72 additions & 4 deletions tuiexporter/internal/telemetry/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ type TraceSpanDataMap map[string][]*SpanData
// This is used to quickly look up all spans in a trace for a service
type TraceServiceSpanDataMap map[string]map[string][]*SpanData

// TraceLogDataMap is a map of trace id to a slice of logs
// This is used to quickly look up all logs in a trace
type TraceLogDataMap map[string][]*LogData

// TraceCache is a cache of trace spans
type TraceCache struct {
spanid2span SpanDataMap
Expand Down Expand Up @@ -103,6 +99,10 @@ func (c *TraceCache) flush() {
c.tracesvc2spans = TraceServiceSpanDataMap{}
}

// TraceLogDataMap is a map of trace id to a slice of logs
// This is used to quickly look up all logs in a trace
type TraceLogDataMap map[string][]*LogData

// LogCache is a cache of logs
type LogCache struct {
traceid2logs TraceLogDataMap
Expand Down Expand Up @@ -149,3 +149,71 @@ func (c *LogCache) GetLogsByTraceID(traceID string) ([]*LogData, bool) {
func (c *LogCache) flush() {
c.traceid2logs = TraceLogDataMap{}
}

// MetricServiceMetricDataMap is a map of service name and metric name to a slice of metrics
// This is used to quickly look up datapoints in a service metric
type MetricServiceMetricDataMap map[string]map[string][]*MetricData

// MetricCache is a cache of metrics
type MetricCache struct {
svcmetric2metrics MetricServiceMetricDataMap
}

// NewMetricCache returns a new metric cache
func NewMetricCache() *MetricCache {
return &MetricCache{
svcmetric2metrics: MetricServiceMetricDataMap{},
}
}

// UpdateCache updates the cache with a new metric
func (c *MetricCache) UpdateCache(sname string, data *MetricData) {
mname := data.Metric.Name()
if sms, ok := c.svcmetric2metrics[sname]; ok {
if _, ok := sms[mname]; ok {
c.svcmetric2metrics[sname][mname] = append(c.svcmetric2metrics[sname][mname], data)
} else {
c.svcmetric2metrics[sname][mname] = []*MetricData{data}
}
} else {
c.svcmetric2metrics[sname] = map[string][]*MetricData{mname: {data}}
}
}

// DeleteCache deletes a list of metrics from the cache
func (c *MetricCache) DeleteCache(metrics []*MetricData) {
for _, m := range metrics {
sname := "N/A"
if snameattr, ok := m.ResourceMetric.Resource().Attributes().Get("service.name"); ok {
sname = snameattr.AsString()
}
mname := m.Metric.Name()
if _, ok := c.svcmetric2metrics[sname][mname]; ok {
for i, metric := range c.svcmetric2metrics[sname][mname] {
if metric == m {
c.svcmetric2metrics[sname][mname] = append(c.svcmetric2metrics[sname][mname][:i], c.svcmetric2metrics[sname][mname][i+1:]...)
if len(c.svcmetric2metrics[sname][mname]) == 0 {
delete(c.svcmetric2metrics[sname], mname)
if len(c.svcmetric2metrics[sname]) == 0 {
delete(c.svcmetric2metrics, sname)
}
}
}
}
}
}
}

// GetMetricsBySvcAndMetricName returns all metrics for a given service name and metric name
func (c *MetricCache) GetMetricsBySvcAndMetricName(sname, mname string) ([]*MetricData, bool) {
if sms, ok := c.svcmetric2metrics[sname]; ok {
if ms, ok := sms[mname]; ok {
return ms, ok
}
}
return nil, false
}

func (c *MetricCache) flush() {
c.svcmetric2metrics = MetricServiceMetricDataMap{}
}
44 changes: 44 additions & 0 deletions tuiexporter/internal/telemetry/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,47 @@ func TestGetLogsByTraceID(t *testing.T) {
})
}
}

func TestGetMetricsBySvcAndMetricName(t *testing.T) {
c := NewMetricCache()
metrics := []*MetricData{}
c.svcmetric2metrics["sname"] = map[string][]*MetricData{"mname": metrics}

tests := []struct {
name string
sname string
mname string
wantdata []*MetricData
wantok bool
}{
{
name: "service and metrics exists",
sname: "sname",
mname: "mname",
wantdata: metrics,
wantok: true,
},
{
name: "service exists but metrics does not",
sname: "sname",
mname: "non-existent-metric",
wantdata: nil,
wantok: false,
},
{
name: "service does not exist",
sname: "non-existent-sname",
mname: "mname",
wantdata: nil,
wantok: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotdata, gotok := c.GetMetricsBySvcAndMetricName(tt.sname, tt.mname)
assert.Equal(t, tt.wantdata, gotdata)
assert.Equal(t, tt.wantok, gotok)
})
}
}
Loading

0 comments on commit 398b722

Please sign in to comment.