Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: selectable details view #11

Merged
merged 5 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
26 changes: 21 additions & 5 deletions tuiexporter/internal/tui/component/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ func (p *TUIPages) createTracePage(store *telemetry.Store) *tview.Flex {
page := tview.NewFlex().SetDirection(tview.FlexColumn)

details := tview.NewFlex().SetDirection(tview.FlexRow)
details.SetTitle("Details").SetBorder(true)
details.SetTitle("Details (d)").SetBorder(true)

tableContainer := tview.NewFlex().SetDirection(tview.FlexRow)
tableContainer.SetTitle("Traces").SetBorder(true)
tableContainer.SetTitle("Traces (t)").SetBorder(true)
table := tview.NewTable().
SetBorders(false).
SetSelectable(true, false).
Expand All @@ -91,7 +91,7 @@ func (p *TUIPages) createTracePage(store *telemetry.Store) *tview.Flex {
input := ""
inputConfirmed := ""
search := tview.NewInputField().
SetLabel("Service Name: ").
SetLabel("Service Name (/): ").
SetFieldWidth(20)
search.SetChangedFunc(func(text string) {
// remove the suffix '/' from input because it is passed from SetInputCapture()
Expand All @@ -114,7 +114,7 @@ func (p *TUIPages) createTracePage(store *telemetry.Store) *tview.Flex {

table.SetSelectionChangedFunc(func(row, _ int) {
details.Clear()
details.AddItem(GetTraceInfoTree(store.GetFilteredServiceSpansByIdx(row)), 0, 1, false)
details.AddItem(getTraceInfoTree(store.GetFilteredServiceSpansByIdx(row)), 0, 1, true)
log.Printf("selected row: %d", row)
})
tableContainer.
Expand All @@ -126,12 +126,28 @@ func (p *TUIPages) createTracePage(store *telemetry.Store) *tview.Flex {
if !search.HasFocus() {
p.setFocusFn(search)
}
return nil
}

return event
})

page.AddItem(tableContainer, 0, 6, true).AddItem(details, 0, 4, false)
page.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Rune() {
case 'd':
if !search.HasFocus() {
p.setFocusFn(details)
}
// don't return nil here, because we want to pass the event to the search input
case 't':
if !search.HasFocus() {
p.setFocusFn(table)
}
// don't return nil here, because we want to pass the event to the search input
}
return event
})
page = attatchCommandList(page, KeyMaps{
*tcell.NewEventKey(tcell.KeyCtrlL, ' ', tcell.ModNone): "Toggle Log",
*tcell.NewEventKey(tcell.KeyRune, '/', tcell.ModNone): "Search traces",
Expand All @@ -144,7 +160,7 @@ func (p *TUIPages) createTracePage(store *telemetry.Store) *tview.Flex {

func (p *TUIPages) createTimelinePage() *tview.Flex {
page := tview.NewFlex().SetDirection(tview.FlexRow)
page.Box.SetTitle("Timeline").SetBorder(true)
page.Box.SetBorder(false)
page.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyEsc {
p.pages.SwitchToPage(PAGE_TRACES)
Expand Down
56 changes: 41 additions & 15 deletions tuiexporter/internal/tui/component/timeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import (
"github.com/ymtdzzz/otel-tui/tuiexporter/internal/telemetry"
)

const (
TIMELINE_DETAILS_IDX = 1 // index of details in the base flex container
TIMELINE_TREE_TITLE = "Details (d)"
)

type spanTreeNode struct {
span *telemetry.SpanData
label string
Expand All @@ -29,9 +34,6 @@ func DrawTimeline(traceID string, cache *telemetry.TraceCache, setFocusFn func(p

base := tview.NewFlex().SetDirection(tview.FlexColumn)

// details
details := tview.NewTreeView()

// draw timeline
title := tview.NewTextView().SetTextAlign(tview.AlignCenter).SetText("Spans")
tree, duration := newSpanTree(traceID, cache)
Expand Down Expand Up @@ -61,10 +63,11 @@ func DrawTimeline(traceID string, cache *telemetry.TraceCache, setFocusFn func(p

// place spans on the timeline
grid := tview.NewGrid().
SetColumns(30, 0).
SetColumns(30, 0). // TODO: dynamic width
SetBorders(true).
AddItem(title, 0, 0, 1, 1, 0, 0, false).
AddItem(timeline, 0, 1, 1, 1, 0, 0, false)
grid.SetTitle("Trace Timeline (t)").SetBorder(true)

var (
tvs []*tview.TextView
Expand All @@ -75,6 +78,9 @@ func DrawTimeline(traceID string, cache *telemetry.TraceCache, setFocusFn func(p
totalRow = placeSpan(grid, n, totalRow, 0, &tvs, &nodes)
}

// details
details := getSpanInfoTree(nodes[0].span, TIMELINE_TREE_TITLE)

rows := make([]int, totalRow+2)
for i := 0; i < totalRow+1; i++ {
rows[i] = 1
Expand All @@ -88,7 +94,6 @@ func DrawTimeline(traceID string, cache *telemetry.TraceCache, setFocusFn func(p
if totalRow > 0 {
currentRow := 0
setFocusFn(tvs[currentRow])
treeTitle := "Span Details"

grid.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
// FIXME: key 'j' and 'k' should be used to move the focus
Expand All @@ -100,22 +105,22 @@ func DrawTimeline(traceID string, cache *telemetry.TraceCache, setFocusFn func(p
setFocusFn(tvs[currentRow])

// update details
oldDetails := base.GetItem(1)
oldDetails := base.GetItem(TIMELINE_DETAILS_IDX)
base.RemoveItem(oldDetails)
details := getSpanInfoTree(nodes[currentRow].span, treeTitle)
details := getSpanInfoTree(nodes[currentRow].span, TIMELINE_TREE_TITLE)
base.AddItem(details, 0, 3, false)
}
return nil
case tcell.KeyUp:
if currentRow > 0 {
currentRow--
setFocusFn(tvs[currentRow])
details = getSpanInfoTree(nodes[currentRow].span, treeTitle)
details = getSpanInfoTree(nodes[currentRow].span, TIMELINE_TREE_TITLE)

// update details
oldDetails := base.GetItem(1)
oldDetails := base.GetItem(TIMELINE_DETAILS_IDX)
base.RemoveItem(oldDetails)
details := getSpanInfoTree(nodes[currentRow].span, treeTitle)
details := getSpanInfoTree(nodes[currentRow].span, TIMELINE_TREE_TITLE)
base.AddItem(details, 0, 3, false)
}
return nil
Expand All @@ -124,11 +129,25 @@ func DrawTimeline(traceID string, cache *telemetry.TraceCache, setFocusFn func(p
})
}

details.SetBorder(true).SetTitle("Span Details")
details.SetBorder(true).SetTitle("Details (d)")

base.AddItem(grid, 0, 7, true).
AddItem(details, 0, 3, false)

base.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Rune() {
case 'd':
log.Printf("d key pressed")
setFocusFn(base.GetItem(TIMELINE_DETAILS_IDX))
return nil
case 't':
log.Printf("t key pressed")
setFocusFn(grid)
return nil
}
return event
})

return base, KeyMaps{
*tcell.NewEventKey(tcell.KeyUp, ' ', tcell.ModNone): "Move up",
*tcell.NewEventKey(tcell.KeyDown, ' ', tcell.ModNone): "Move down",
Expand Down Expand Up @@ -160,13 +179,15 @@ func roundDownDuration(d time.Duration) time.Duration {
func placeSpan(grid *tview.Grid, node *spanTreeNode, row, depth int, tvs *[]*tview.TextView, nodes *[]*spanTreeNode) int {
row++
label := node.label
prefix := ""
for i := 0; i < depth; i++ {
if i == depth-1 {
label = string(tview.BoxDrawingsLightUpAndRight) + label
prefix = prefix + string(tview.BoxDrawingsLightUpAndRight)
break
}
label = " " + label
prefix = prefix + " "
}
tv := newTextView(label)
tv := newTextView(prefix + label)
*tvs = append(*tvs, tv)
*nodes = append(*nodes, node)
grid.AddItem(tv, row, 0, 1, 1, 0, 0, false)
Expand Down Expand Up @@ -233,7 +254,8 @@ func newSpanTree(traceID string, cache *telemetry.TraceCache) (rootNodes []*span
func newTextView(text string) *tview.TextView {
tv := tview.NewTextView().
SetTextAlign(tview.AlignLeft).
SetText(text)
SetText(text).
SetWordWrap(false)
tv.SetFocusFunc(func() {
tv.SetBackgroundColor(tcell.ColorWhite)
tv.SetTextColor(tcell.ColorBlack)
Expand Down Expand Up @@ -398,5 +420,9 @@ func getSpanInfoTree(span *telemetry.SpanData, title string) *tview.TreeView {
}
root.AddChild(links)

tree.SetSelectedFunc(func(node *tview.TreeNode) {
node.SetExpanded(!node.IsExpanded())
})

return tree
}
16 changes: 9 additions & 7 deletions tuiexporter/internal/tui/component/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func getCellFromSpan(span *telemetry.SpanData, column int) *tview.TableCell {
return tview.NewTableCell(text)
}

func GetTraceInfoTree(spans []*telemetry.SpanData) *tview.TreeView {
func getTraceInfoTree(spans []*telemetry.SpanData) *tview.TreeView {
if len(spans) == 0 {
return nil
}
Expand Down Expand Up @@ -92,25 +92,27 @@ func GetTraceInfoTree(spans []*telemetry.SpanData) *tview.TreeView {
scopes := tview.NewTreeNode("Scopes")
for si := 0; si < rs.ScopeSpans().Len(); si++ {
ss := rs.ScopeSpans().At(si)
scope := tview.NewTreeNode(fmt.Sprintf("Scope %d", si))
scope := tview.NewTreeNode(ss.Scope().Name())
sschema := tview.NewTreeNode(fmt.Sprintf("schema url: %s", ss.SchemaUrl()))
scope.AddChild(sschema)

isc := tview.NewTreeNode("Instrumentation Scope")
isc.AddChild(tview.NewTreeNode(fmt.Sprintf("name: %s", ss.Scope().Name())))
isc.AddChild(tview.NewTreeNode(fmt.Sprintf("version: %s", ss.Scope().Version())))
isc.AddChild(tview.NewTreeNode(fmt.Sprintf("dropped attributes count: %d", ss.Scope().DroppedAttributesCount())))
scope.AddChild(tview.NewTreeNode(fmt.Sprintf("version: %s", ss.Scope().Version())))
scope.AddChild(tview.NewTreeNode(fmt.Sprintf("dropped attributes count: %d", ss.Scope().DroppedAttributesCount())))

attrs := tview.NewTreeNode("Attributes")
appendAttrsSorted(attrs, ss.Scope().Attributes().AsRaw())
isc.AddChild(attrs)
scope.AddChild(attrs)

scopes.AddChild(scope)
}
resource.AddChild(scopes)

root.AddChild(resource)

tree.SetSelectedFunc(func(node *tview.TreeNode) {
node.SetExpanded(!node.IsExpanded())
})

return tree
}

Expand Down
19 changes: 12 additions & 7 deletions tuiexporter/internal/tui/component/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@ func TestGetTraceInfoTree(t *testing.T) {
ResourceSpan: testdata.RSpans[1],
ScopeSpans: testdata.SSpans[2],
})
sw, sh := 55, 15
sw, sh := 55, 20
screen := tcell.NewSimulationScreen("")
screen.Init()
screen.SetSize(sw, sh)

gottree := GetTraceInfoTree(spans)
gottree := getTraceInfoTree(spans)
gottree.SetRect(0, 0, sw, sh)
gottree.Draw(screen)
screen.Sync()
Expand Down Expand Up @@ -172,14 +172,19 @@ func TestGetTraceInfoTree(t *testing.T) {
│ ├──resource index: %!s(int64=0)
│ └──service.name: test-service-1
└──Scopes
├──Scope 0
│ └──schema url:
└──Scope 1
└──schema url:
├──test-scope-1-1
│ ├──schema url:
│ ├──version: v0.0.1
│ ├──dropped attributes count: 2
│ └──Attributes
│ └──scope index: %!s(int64=0)
└──test-scope-1-2
├──schema url:
└──version: v0.0.1
`
assert.Equal(t, want, got.String())
}

func TestGetTraceInfoTreeNoSpans(t *testing.T) {
assert.Nil(t, GetTraceInfoTree(nil))
assert.Nil(t, getTraceInfoTree(nil))
}
Loading