blob: cb11f343fc4e1e29198f33f40b7604083f23e4ef [file] [log] [blame]
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package tree
import (
"fmt"
"strings"
d "github.com/apache/skywalking-cli/display/displayable"
"github.com/apache/skywalking-cli/graphql/schema"
"github.com/apache/skywalking-cli/graphql/trace"
ui "github.com/gizak/termui/v3"
"github.com/gizak/termui/v3/widgets"
"github.com/urfave/cli"
"github.com/apache/skywalking-cli/logger"
)
const DefaultPageSize = 15
const keymap = " Keymap "
const cc = "<C-c>"
func DisplayList(ctx *cli.Context, displayable *d.Displayable) error {
data := displayable.Data.(schema.TraceBrief)
condition := displayable.Condition.(*schema.TraceQueryCondition)
if err := ui.Init(); err != nil {
logger.Log.Fatalf("failed to initialize termui: %v", err)
}
defer ui.Close()
list := widgets.NewList()
list.TitleStyle.Fg = ui.ColorRed
list.TextStyle = ui.NewStyle(ui.ColorYellow)
list.WrapText = false
tree := widgets.NewTree()
tree.TextStyle = ui.NewStyle(ui.ColorYellow)
tree.WrapText = false
tree.TitleStyle.Fg = ui.ColorRed
help := widgets.NewParagraph()
help.WrapText = false
help.Title = keymap
help.Text = `[k ](fg:red,mod:bold) Scroll Up
[<Up> ](fg:red,mod:bold) Scroll Up
[j ](fg:red,mod:bold) Scroll Down
[<Down> ](fg:red,mod:bold) Scroll Down
[<Ctr-b> ](fg:red,mod:bold) list Page Up
[<Ctr-f> ](fg:red,mod:bold) list Page Down
[p ](fg:red,mod:bold) list Page Up
[n ](fg:red,mod:bold) list Page Down
[<Ctr-u> ](fg:red,mod:bold) Scroll Half Page Up
[<Ctr-d> ](fg:red,mod:bold) Scroll Half Page Down
[<Home> ](fg:red,mod:bold) Scroll to Top
[<Enter> ](fg:red,mod:bold) Show Trace
[<End> ](fg:red,mod:bold) Scroll to Bottom
[q ](fg:red,mod:bold) Quit
[<Ctr-c> ](fg:red,mod:bold) Quit
`
draw(list, tree, help, data, 0, ctx, condition)
listenTracesKeyboard(list, tree, data, ctx, help, condition)
return nil
}
func draw(list *widgets.List, tree *widgets.Tree, help *widgets.Paragraph, data schema.TraceBrief, showIndex int,
ctx *cli.Context, condition *schema.TraceQueryCondition) {
x, y := ui.TerminalDimensions()
if data.Total != 0 {
var traceID = data.Traces[showIndex].TraceIds[0]
list.Title = fmt.Sprintf("[ %d/%d %s]", *condition.Paging.PageNum, totalPages(data.Total), traceID)
nodes, serviceNames := getNodeData(ctx, traceID)
tree.Title = fmt.Sprintf("[%s]", strings.Join(serviceNames, "->"))
tree.SetNodes(nodes)
list.Rows = rows(data, x/4)
} else {
noData := "no data"
list.Title = noData
tree.Title = noData
}
list.SetRect(0, 0, x, y)
tree.SetRect(x/4, 0, x, y)
help.SetRect(x-x/7, 0, x, y)
tree.ExpandAll()
ui.Render(list, tree, help)
}
func totalPages(total int) int {
if total%DefaultPageSize == 0 {
return total / DefaultPageSize
}
return total/DefaultPageSize + 1
}
func listenTracesKeyboard(list *widgets.List, tree *widgets.Tree, data schema.TraceBrief, ctx *cli.Context,
help *widgets.Paragraph, condition *schema.TraceQueryCondition) {
uiEvents := ui.PollEvents()
for {
showIndex := 0
e := <-uiEvents
switch e.ID {
case "q", cc:
return
case "<C-b>", "p":
pageNum := *condition.Paging.PageNum
if pageNum != 1 {
pageNum--
condition.Paging.PageNum = &pageNum
data = trace.Traces(ctx, condition)
}
case "<C-f>", "n":
pageNum := *condition.Paging.PageNum
if pageNum < totalPages(data.Total) {
pageNum++
condition.Paging.PageNum = &pageNum
data = trace.Traces(ctx, condition)
}
default:
if action := listActions(e.ID, list); action != nil {
action()
}
showIndex = list.SelectedRow
}
draw(list, tree, help, data, showIndex, ctx, condition)
}
}
func listActions(key string, list *widgets.List) func() {
// mostly vim style
actions := map[string]func(){
"k": list.ScrollUp,
"<Up>": list.ScrollUp,
"j": list.ScrollDown,
"<Down>": list.ScrollDown,
"<C-u>": list.ScrollHalfPageUp,
"<C-d>": list.ScrollHalfPageDown,
"<Home>": list.ScrollTop,
"G": list.ScrollBottom,
"<End>": list.ScrollBottom,
}
return actions[key]
}
func getNodeData(ctx *cli.Context, traceID string) (nodes []*widgets.TreeNode, serviceNames []string) {
data := trace.Trace(ctx, traceID)
var roots []*Node
roots, serviceNames = Adapt(data)
nodes = make([]*widgets.TreeNode, len(roots))
for i := range nodes {
nodes[i] = &widgets.TreeNode{}
}
for i, root := range roots {
adapt(root, nodes[i])
}
return nodes, serviceNames
}
func rows(data schema.TraceBrief, subLen int) []string {
var rows []string
for _, t := range data.Traces {
endpointName := t.EndpointNames[0]
if len(endpointName) > subLen-3 {
endpointName = endpointName[0:subLen-3] + "..."
}
rows = append(rows, fmt.Sprintf("[%s](mod:bold,fg:green) ", endpointName))
}
return rows
}