diff --git a/agent/analysis/analysis.go b/agent/analysis/analysis.go index 8effb93d..2c0d189c 100644 --- a/agent/analysis/analysis.go +++ b/agent/analysis/analysis.go @@ -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) @@ -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, @@ -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 @@ -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) diff --git a/agent/analysis/classfier.go b/agent/analysis/classfier.go index 55d04835..fb1e8039 100644 --- a/agent/analysis/classfier.go +++ b/agent/analysis/classfier.go @@ -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() @@ -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 + } } diff --git a/agent/analysis/common/classfier.go b/agent/analysis/common/classfier.go index 8c482e2a..827ab7df 100644 --- a/agent/analysis/common/classfier.go +++ b/agent/analysis/common/classfier.go @@ -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 ( @@ -26,6 +27,8 @@ const ( // Redis RedisCommand + + ProtocolAdaptive ) type ClassId string diff --git a/agent/analysis/common/types.go b/agent/analysis/common/types.go index 267ffbf5..1011ca3f 100644 --- a/agent/analysis/common/types.go +++ b/agent/analysis/common/types.go @@ -3,6 +3,7 @@ package common import ( "fmt" "kyanos/agent/protocol" + "kyanos/bpf" "kyanos/common" ac "kyanos/common" @@ -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 @@ -24,6 +26,9 @@ type AnalysisOptions struct { TargetSamples int CurrentReceivedSamples func() int HavestSignal chan struct{} + + // overview mode + Overview bool } func (a *AnalysisOptions) Init() { diff --git a/agent/render/stat/stat.go b/agent/render/stat/stat.go index 9d7ad09c..fb00957b 100644 --- a/agent/render/stat/stat.go +++ b/agent/render/stat/stat.go @@ -8,6 +8,7 @@ import ( "kyanos/agent/analysis/common" rc "kyanos/agent/render/common" "kyanos/agent/render/watch" + "kyanos/bpf" "os" "slices" "strconv" @@ -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} } @@ -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) @@ -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) @@ -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) } } } diff --git a/cmd/overview.go b/cmd/overview.go new file mode 100644 index 00000000..c13db5ca --- /dev/null +++ b/cmd/overview.go @@ -0,0 +1,33 @@ +package cmd + +import "github.com/spf13/cobra" + +var overviewCmd = &cobra.Command{ + Use: "overview [--metrics ]", + 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) +} diff --git a/cmd/stat.go b/cmd/stat.go index ad54b16d..e8aaff93 100644 --- a/cmd/stat.go +++ b/cmd/stat.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" anc "kyanos/agent/analysis/common" + "kyanos/bpf" "slices" "strings" @@ -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() {