Skip to content

Commit

Permalink
feat(cli): add overview subcommand to overview the dependencies lik…
Browse files Browse the repository at this point in the history
…e mysql/redis/ in one cmd line
  • Loading branch information
hengyoush committed Oct 20, 2024
1 parent 6ffda5c commit f4cdad7
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 20 deletions.
10 changes: 6 additions & 4 deletions agent/analysis/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,12 @@ func (a *aggregator) receive(record *analysis_common.AnnotatedRecord) error {

if enabled {
MetricExtract := analysis_common.GetMetricExtractFunc[float64](metricType)
samples := a.SamplesMap[metricType]
// only sample if aggregator is sub or no sub classfier
if a.isSub || a.SubClassfierType == analysis_common.None {
samples := a.SamplesMap[metricType]
a.SamplesMap[metricType] = AddToSamples(samples, record, MetricExtract, a.AnalysisOptions.SampleLimit)
} else {
a.SamplesMap[metricType] = AddToSamples(samples, record, MetricExtract, 1)
}

metricValue := MetricExtract(record)
Expand Down Expand Up @@ -131,7 +133,7 @@ func CreateAnalyzer(recordsChannel <-chan *analysis_common.AnnotatedRecord, opts
// ac.AddToFastStopper(stopper)
opts.Init()
analyzer := &Analyzer{
Classfier: getClassfier(opts.ClassfierType),
Classfier: getClassfier(opts.ClassfierType, *opts),
recordsChannel: recordsChannel,
Aggregators: make(map[analysis_common.ClassId]*aggregator),
AnalysisOptions: opts,
Expand All @@ -141,7 +143,7 @@ func CreateAnalyzer(recordsChannel <-chan *analysis_common.AnnotatedRecord, opts
ctx: ctx,
}
if opts.SubClassfierType != analysis_common.None {
analyzer.subClassfier = getClassfier(opts.SubClassfierType)
analyzer.subClassfier = getClassfier(opts.SubClassfierType, *opts)
}
opts.CurrentReceivedSamples = func() int {
return analyzer.recordReceived
Expand Down Expand Up @@ -218,7 +220,7 @@ func (a *Analyzer) analyze(record *analysis_common.AnnotatedRecord) {
fullClassId := class + "||" + subClassId
subAggregator, exists := a.Aggregators[fullClassId]
if !exists {
subHumanReadableFunc, ok := classIdHumanReadableMap[a.AnalysisOptions.SubClassfierType]
subHumanReadableFunc, ok := getClassIdHumanReadableFunc(a.AnalysisOptions.SubClassfierType, *a.AnalysisOptions)
if ok {
subHumanReadableClassId := subHumanReadableFunc(record)
a.Aggregators[fullClassId] = createAggregatorWithHumanReadableClassId(subHumanReadableClassId, fullClassId, a.AnalysisOptions, true)
Expand Down
40 changes: 38 additions & 2 deletions agent/analysis/classfier.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ func init() {
}
}

classfierMap[anc.ProtocolAdaptive] = func(ar *anc.AnnotatedRecord) (anc.ClassId, error) {
redisReq, ok := ar.Record.Request().(*protocol.RedisMessage)
if !ok {
return "_not_a_redis_req_", nil
} else {
return anc.ClassId(redisReq.Command()), nil
}
}

classIdHumanReadableMap = make(map[anc.ClassfierType]ClassIdAsHumanReadable)
classIdHumanReadableMap[anc.RemoteIp] = func(ar *anc.AnnotatedRecord) string {
return ar.ConnDesc.RemoteAddr.String()
Expand Down Expand Up @@ -82,6 +91,33 @@ func init() {
}
}

func getClassfier(classfierType anc.ClassfierType) Classfier {
return classfierMap[classfierType]
func getClassfier(classfierType anc.ClassfierType, options anc.AnalysisOptions) Classfier {
if classfierType == anc.ProtocolAdaptive {
return func(ar *anc.AnnotatedRecord) (anc.ClassId, error) {
c, ok := options.ProtocolSpecificClassfiers[bpf.AgentTrafficProtocolT(ar.Protocol)]
if !ok {
return classfierMap[anc.RemoteIp](ar)
} else {
return classfierMap[c](ar)
}
}
} else {
return classfierMap[classfierType]
}
}

func getClassIdHumanReadableFunc(classfierType anc.ClassfierType, options anc.AnalysisOptions) (ClassIdAsHumanReadable, bool) {
if classfierType == anc.ProtocolAdaptive {
return func(ar *anc.AnnotatedRecord) string {
c, ok := options.ProtocolSpecificClassfiers[bpf.AgentTrafficProtocolT(ar.Protocol)]
if !ok {
return classIdHumanReadableMap[anc.RemoteIp](ar)
} else {
return classIdHumanReadableMap[c](ar)
}
}, true
} else {
f, ok := classIdHumanReadableMap[classfierType]
return f, ok
}
}
21 changes: 12 additions & 9 deletions agent/analysis/common/classfier.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package common

var ClassfierTypeNames = map[ClassfierType]string{
None: "none",
Conn: "conn",
RemotePort: "remote-port",
LocalPort: "local-port",
RemoteIp: "remote-ip",
Protocol: "protocol",
HttpPath: "http-path",
RedisCommand: "redis-command",
Default: "default",
None: "none",
Conn: "conn",
RemotePort: "remote-port",
LocalPort: "local-port",
RemoteIp: "remote-ip",
Protocol: "protocol",
HttpPath: "http-path",
RedisCommand: "redis-command",
ProtocolAdaptive: "protocol-adaptive",
Default: "default",
}

const (
Expand All @@ -26,6 +27,8 @@ const (

// Redis
RedisCommand

ProtocolAdaptive
)

type ClassId string
9 changes: 7 additions & 2 deletions agent/analysis/common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package common
import (
"fmt"
"kyanos/agent/protocol"
"kyanos/bpf"
"kyanos/common"
ac "kyanos/common"

Expand All @@ -14,8 +15,9 @@ type AnalysisOptions struct {
SampleLimit int
Side ac.SideEnum
ClassfierType
SubClassfierType ClassfierType
CleanWhenHarvest bool
SubClassfierType ClassfierType
ProtocolSpecificClassfiers map[bpf.AgentTrafficProtocolT]ClassfierType
CleanWhenHarvest bool

// Fast Inspect Options
SlowMode bool
Expand All @@ -24,6 +26,9 @@ type AnalysisOptions struct {
TargetSamples int
CurrentReceivedSamples func() int
HavestSignal chan struct{}

// overview mode
Overview bool
}

func (a *AnalysisOptions) Init() {
Expand Down
19 changes: 16 additions & 3 deletions agent/render/stat/stat.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"kyanos/agent/analysis/common"
rc "kyanos/agent/render/common"
"kyanos/agent/render/watch"
"kyanos/bpf"
"os"
"slices"
"strconv"
Expand Down Expand Up @@ -143,6 +144,9 @@ func initTable(options common.AnalysisOptions, isSub bool) table.Model {
{Title: fmt.Sprintf("p99(%s)", unit), Width: 10},
{Title: "count", Width: 5},
}
if options.Overview {
columns = slices.Insert(columns, 2, table.Column{Title: "Protocol", Width: 10})
}
if isSub {
columns[1] = table.Column{Title: common.ClassfierTypeNames[options.SubClassfierType], Width: 40}
}
Expand Down Expand Up @@ -190,7 +194,7 @@ func (m *model) updateConnStats() {
m.curConnstats = &topStats
m.curSubConnstats = &subStats
}
func renderToTable(connstats *[]*analysis.ConnStat, t *table.Model, metric common.MetricType, isSub bool) {
func renderToTable(connstats *[]*analysis.ConnStat, t *table.Model, metric common.MetricType, isSub bool, overview bool) {
records := (*connstats)
var row table.Row
rows := make([]table.Row, 0)
Expand All @@ -210,6 +214,15 @@ func renderToTable(connstats *[]*analysis.ConnStat, t *table.Model, metric commo
if metric.IsTotalMeaningful() {
row = append(row, fmt.Sprintf("%.1f", record.SumMap[metric]))
}
if overview {
var protocol string
if records, ok := record.SamplesMap[metric]; ok && len(records) > 0 {
protocol = bpf.ProtocolNamesMap[bpf.AgentTrafficProtocolT(records[0].Protocol)]
} else {
protocol = "unknown"
}
row = slices.Insert(row, 2, protocol)
}
rows = append(rows, row)
}
t.SetRows(rows)
Expand All @@ -220,9 +233,9 @@ func (m *model) updateRowsInTable() {
if m.connstats != nil {
m.updateConnStats()
metric := m.options.EnabledMetricTypeSet.GetFirstEnabledMetricType()
renderToTable(m.curConnstats, &m.statTable, metric, false)
renderToTable(m.curConnstats, &m.statTable, metric, false, m.options.Overview)
if m.enableSubGroup {
renderToTable(m.curSubConnstats, &m.subStatTable, metric, true)
renderToTable(m.curSubConnstats, &m.subStatTable, metric, true, m.options.Overview)
}
}
}
Expand Down
33 changes: 33 additions & 0 deletions cmd/overview.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cmd

import "github.com/spf13/cobra"

var overviewCmd = &cobra.Command{
Use: "overview [--metrics <metric_name>]",
Short: "Overview the dependencies like mysql/redis/.. in one cmd line.",
Example: `
# Basic Usage
sudo kyanos overview
`,
PersistentPreRun: func(cmd *cobra.Command, args []string) { Mode = AnalysisMode },
Run: func(cmd *cobra.Command, args []string) {
overview = true
groupBy = "remote-ip/protocol-adaptive"
startAgent()
},
}

var overview bool

func init() {
overviewCmd.PersistentFlags().StringVarP(&enabledMetricsString, "metrics", "m", "t", `Specify the statistical dimensions, including:
t: total time taken for request response,
q: request size,
p: response size,
n: network device latency,
s: time spent reading from the socket buffer`)

overviewCmd.Flags().SortFlags = false
overviewCmd.PersistentFlags().SortFlags = false
rootCmd.AddCommand(overviewCmd)
}
7 changes: 7 additions & 0 deletions cmd/stat.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"fmt"
anc "kyanos/agent/analysis/common"
"kyanos/bpf"
"slices"
"strings"

Expand Down Expand Up @@ -105,7 +106,13 @@ func createAnalysisOptions() (anc.AnalysisOptions, error) {
options.BigReqMode = bigReqModel
options.BigRespMode = bigRespModel
options.TargetSamples = targetSamples
options.ProtocolSpecificClassfiers = make(map[bpf.AgentTrafficProtocolT]anc.ClassfierType)
// currently only set it hardly
options.ProtocolSpecificClassfiers[bpf.AgentTrafficProtocolTKProtocolHTTP] = anc.HttpPath
options.ProtocolSpecificClassfiers[bpf.AgentTrafficProtocolTKProtocolRedis] = anc.RedisCommand
options.ProtocolSpecificClassfiers[bpf.AgentTrafficProtocolTKProtocolMySQL] = anc.RemoteIp

options.Overview = overview
return options, nil
}
func init() {
Expand Down

0 comments on commit f4cdad7

Please sign in to comment.