From 1e0025c7774c813681f9b7e9bca92b1657aa25bf Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Mon, 30 Dec 2024 13:53:17 +0800 Subject: [PATCH 01/28] update --- agent/protocol/rocketmq/rocketmq.go | 152 ++++++++++++++++++++++++++++ agent/protocol/rocketmq/types.go | 28 +++++ go.mod | 2 +- go.sum | 2 - 4 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 agent/protocol/rocketmq/rocketmq.go create mode 100644 agent/protocol/rocketmq/types.go diff --git a/agent/protocol/rocketmq/rocketmq.go b/agent/protocol/rocketmq/rocketmq.go new file mode 100644 index 00000000..49a528cf --- /dev/null +++ b/agent/protocol/rocketmq/rocketmq.go @@ -0,0 +1,152 @@ +package rocketmq + +import ( + "encoding/binary" + "errors" + "fmt" + "kyanos/agent/buffer" + "kyanos/agent/protocol" +) + +func init() { + +} + +func (r *RocketMQMessage) FormatToString() string { + return fmt.Sprintf("base=[%s] command=[%s] payload=[%s]", r.FrameBase.String(), "todo", r.Body) +} + +func (r *RocketMQMessage) FormatToSummaryString() string { + return "rocketmq" +} + +func (r *RocketMQMessage) TimestampNs() uint64 { + return 0 +} + +func (r *RocketMQMessage) ByteSize() int { + return 0 +} + +func (r *RocketMQMessage) IsReq() bool { + return r.isReq +} + +func (r *RocketMQMessage) Seq() uint64 { + return 0 +} + +func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, messageType protocol.MessageType) protocol.ParseResult { + head := streamBuffer.Head() + buffer := head.Buffer() + if len(buffer) < 8 { + return protocol.ParseResult{ + ParseState: protocol.NeedsMoreData, + } + } + + frameSize := int(binary.BigEndian.Uint32(buffer[:4])) + if frameSize > len(buffer) { + return protocol.ParseResult{ParseState: protocol.NeedsMoreData} + } + + headerLength := binary.BigEndian.Uint32(buffer[4:8]) + headerDataLen := headerLength & 0xFFFFFF + serializedType := byte((headerLength >> 24) & 0xFF) + + if len(buffer) < 8+int(headerDataLen) { + return protocol.ParseResult{ParseState: protocol.NeedsMoreData} + } + + headerBody := buffer[8 : 8+headerDataLen] + body := buffer[8+headerDataLen : frameSize] + message, err := r.parseHeader(headerBody, serializedType) + if err != nil { + return protocol.ParseResult{ParseState: protocol.Invalid, ReadBytes: int(frameSize)} + } + + message.Body = body + message.isReq = messageType == protocol.Request + fb, ok := protocol.CreateFrameBase(streamBuffer, frameSize) + + if !ok { + return protocol.ParseResult{ + ParseState: protocol.Ignore, + ReadBytes: frameSize, + } + } else { + message.FrameBase = fb + return protocol.ParseResult{ + ParseState: protocol.Success, + ReadBytes: frameSize, + ParsedMessages: []protocol.ParsedMessage{message}, + } + } +} + +func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedType byte) (*RocketMQMessage, error) { + fmt.Println(serializedType) + message := &RocketMQMessage{} + if serializedType == 0 { + if len(headerBody) < 18 { + return nil, errors.New("invalid header size") + } + + message.RequestCode = int16(binary.BigEndian.Uint16(headerBody[:2])) + message.LanguageFlag = headerBody[2] + message.VersionFlag = int16(binary.BigEndian.Uint16(headerBody[3:5])) + message.Opaque = int32(binary.BigEndian.Uint32(headerBody[5:9])) + message.RequestFlag = int32(binary.BigEndian.Uint32(headerBody[9:13])) + message.RemarkLength = int32(binary.BigEndian.Uint32(headerBody[13:17])) + + if int(message.RemarkLength) > len(headerBody[17:]) { + return nil, errors.New("invalid remark length") + } + message.Remark = headerBody[17 : 17+message.RemarkLength] + propertiesStart := 17 + message.RemarkLength + if len(headerBody[propertiesStart:]) < 4 { + return nil, errors.New("invalid properties length") + } + message.PropertiesLen = int32(binary.BigEndian.Uint32(headerBody[propertiesStart:])) + message.Properties = headerBody[propertiesStart+4 : propertiesStart+4+message.PropertiesLen] + } else { + return nil, errors.New("unsupported serialization type") + } + return message, nil +} + +func (r *RocketMQStreamParser) FindBoundary(streamBuffer *buffer.StreamBuffer, messageType protocol.MessageType, startPos int) int { + buffer := streamBuffer.Head().Buffer()[startPos:] + for i := range buffer { + if len(buffer[i:]) < 8 { + return -1 + } + frameSize := binary.BigEndian.Uint32(buffer[i : i+4]) + if int(frameSize) <= len(buffer[i:]) { + return startPos + i + } + } + return -1 +} + +func (r *RocketMQStreamParser) Match(reqStream *[]protocol.ParsedMessage, respStream *[]protocol.ParsedMessage) []protocol.Record { + records := []protocol.Record{} + for i := 0; i < len(*reqStream); i++ { + req := (*reqStream)[i].(*RocketMQMessage) + for j := 0; j < len(*respStream); j++ { + resp := (*respStream)[j].(*RocketMQMessage) + if req.Opaque == resp.Opaque { + records = append(records, protocol.Record{ + Req: req, + Resp: resp, + }) + *reqStream = (*reqStream)[1:] + *respStream = (*respStream)[1:] + break + } else { + continue + } + } + } + return records +} diff --git a/agent/protocol/rocketmq/types.go b/agent/protocol/rocketmq/types.go new file mode 100644 index 00000000..38e171a4 --- /dev/null +++ b/agent/protocol/rocketmq/types.go @@ -0,0 +1,28 @@ +package rocketmq + +import ( + "kyanos/agent/protocol" +) + +var _ protocol.ParsedMessage = &RocketMQMessage{} + +type RocketMQMessage struct { + protocol.FrameBase + RequestCode int16 + LanguageFlag byte + VersionFlag int16 + Opaque int32 + RequestFlag int32 + RemarkLength int32 + Remark []byte + PropertiesLen int32 + Properties []byte + Body []byte + isReq bool +} + +var _ protocol.ProtocolStreamParser = &RocketMQStreamParser{} + +type RocketMQStreamParser struct { + correlationIDMap map[int32]bool // To track request-response matching +} diff --git a/go.mod b/go.mod index c852d663..a913d8e6 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( k8s.io/cri-api v0.31.0 k8s.io/klog/v2 v2.130.1 k8s.io/kubernetes v1.24.17 + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 ) require ( @@ -138,7 +139,6 @@ require ( k8s.io/apimachinery v0.31.1 // indirect k8s.io/apiserver v0.31.1 // indirect k8s.io/component-base v0.31.1 // indirect - k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect rsc.io/binaryregexp v0.2.0 // indirect ) diff --git a/go.sum b/go.sum index d691883e..8e9ecdb1 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,6 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= -github.com/charmbracelet/bubbletea v1.2.2 h1:EMz//Ky/aFS2uLcKqpCst5UOE6z5CFDGRsUpyXz0chs= -github.com/charmbracelet/bubbletea v1.2.2/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= From fc38964a39f56c6b87a820c6435aa8c88d1b72a6 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 31 Dec 2024 11:07:58 +0800 Subject: [PATCH 02/28] update --- agent/protocol/rocketmq/filter.go | 27 ++++++++++++++ agent/protocol/rocketmq/rocketmq.go | 56 ++++++++++++----------------- agent/protocol/rocketmq/types.go | 1 - bpf/agent_x86_bpfel.go | 3 +- bpf/pktlatency.h | 1 + bpf/protocol_inference.h | 24 +++++++++++++ cmd/rocketmq.go | 27 ++++++++++++++ cmd/watch.go | 3 +- docker-compose.yaml | 42 ++++++++++++++++++++++ test_rocketmq.py | 44 +++++++++++++++++++++++ testdata/run_e2e.sh | 3 +- testdata/test_rocketmq.sh | 39 ++++++++++++++++++++ 12 files changed, 232 insertions(+), 38 deletions(-) create mode 100644 agent/protocol/rocketmq/filter.go create mode 100644 cmd/rocketmq.go create mode 100644 docker-compose.yaml create mode 100644 test_rocketmq.py create mode 100755 testdata/test_rocketmq.sh diff --git a/agent/protocol/rocketmq/filter.go b/agent/protocol/rocketmq/filter.go new file mode 100644 index 00000000..87976739 --- /dev/null +++ b/agent/protocol/rocketmq/filter.go @@ -0,0 +1,27 @@ +package rocketmq + +import ( + "kyanos/agent/protocol" + "kyanos/bpf" +) + +type Filter struct { +} + +func (m Filter) Filter(req protocol.ParsedMessage, resp protocol.ParsedMessage) bool { + return true +} + +func (m Filter) FilterByProtocol(p bpf.AgentTrafficProtocolT) bool { + return p == bpf.AgentTrafficProtocolTKProtocolRocketMQ +} + +func (m Filter) FilterByRequest() bool { + return false +} + +func (m Filter) FilterByResponse() bool { + return false +} + +var _ protocol.ProtocolFilter = Filter{} diff --git a/agent/protocol/rocketmq/rocketmq.go b/agent/protocol/rocketmq/rocketmq.go index 49a528cf..d418f2ef 100644 --- a/agent/protocol/rocketmq/rocketmq.go +++ b/agent/protocol/rocketmq/rocketmq.go @@ -6,39 +6,25 @@ import ( "fmt" "kyanos/agent/buffer" "kyanos/agent/protocol" + "kyanos/bpf" ) func init() { - + protocol.ParsersMap[bpf.AgentTrafficProtocolTKProtocolRocketMQ] = func() protocol.ProtocolStreamParser { + return &RocketMQStreamParser{} + } } func (r *RocketMQMessage) FormatToString() string { return fmt.Sprintf("base=[%s] command=[%s] payload=[%s]", r.FrameBase.String(), "todo", r.Body) } -func (r *RocketMQMessage) FormatToSummaryString() string { - return "rocketmq" -} - -func (r *RocketMQMessage) TimestampNs() uint64 { - return 0 -} - -func (r *RocketMQMessage) ByteSize() int { - return 0 -} - func (r *RocketMQMessage) IsReq() bool { return r.isReq } -func (r *RocketMQMessage) Seq() uint64 { - return 0 -} - func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, messageType protocol.MessageType) protocol.ParseResult { - head := streamBuffer.Head() - buffer := head.Buffer() + buffer := streamBuffer.Head().Buffer() if len(buffer) < 8 { return protocol.ParseResult{ ParseState: protocol.NeedsMoreData, @@ -131,22 +117,24 @@ func (r *RocketMQStreamParser) FindBoundary(streamBuffer *buffer.StreamBuffer, m func (r *RocketMQStreamParser) Match(reqStream *[]protocol.ParsedMessage, respStream *[]protocol.ParsedMessage) []protocol.Record { records := []protocol.Record{} - for i := 0; i < len(*reqStream); i++ { - req := (*reqStream)[i].(*RocketMQMessage) - for j := 0; j < len(*respStream); j++ { - resp := (*respStream)[j].(*RocketMQMessage) - if req.Opaque == resp.Opaque { - records = append(records, protocol.Record{ - Req: req, - Resp: resp, - }) - *reqStream = (*reqStream)[1:] - *respStream = (*respStream)[1:] - break - } else { - continue - } + + reqMap := make(map[int32]*RocketMQMessage) + for _, msg := range *reqStream { + req := msg.(*RocketMQMessage) + reqMap[req.Opaque] = req + } + + for _, msg := range *respStream { + resp := msg.(*RocketMQMessage) + if req, ok := reqMap[resp.Opaque]; ok { + records = append(records, protocol.Record{ + Req: req, + Resp: resp, + }) + + delete(reqMap, resp.Opaque) } } + return records } diff --git a/agent/protocol/rocketmq/types.go b/agent/protocol/rocketmq/types.go index 38e171a4..46fbd0fc 100644 --- a/agent/protocol/rocketmq/types.go +++ b/agent/protocol/rocketmq/types.go @@ -24,5 +24,4 @@ type RocketMQMessage struct { var _ protocol.ProtocolStreamParser = &RocketMQStreamParser{} type RocketMQStreamParser struct { - correlationIDMap map[int32]bool // To track request-response matching } diff --git a/bpf/agent_x86_bpfel.go b/bpf/agent_x86_bpfel.go index eb6e1465..1505d877 100644 --- a/bpf/agent_x86_bpfel.go +++ b/bpf/agent_x86_bpfel.go @@ -186,7 +186,8 @@ const ( AgentTrafficProtocolTKProtocolKafka AgentTrafficProtocolT = 11 AgentTrafficProtocolTKProtocolMux AgentTrafficProtocolT = 12 AgentTrafficProtocolTKProtocolAMQP AgentTrafficProtocolT = 13 - AgentTrafficProtocolTKNumProtocols AgentTrafficProtocolT = 14 + AgentTrafficProtocolTKProtocolRocketMQ AgentTrafficProtocolT = 14 + AgentTrafficProtocolTKNumProtocols AgentTrafficProtocolT = 15 ) // LoadAgent returns the embedded CollectionSpec for Agent. diff --git a/bpf/pktlatency.h b/bpf/pktlatency.h index 1cb3f334..e504a6d9 100644 --- a/bpf/pktlatency.h +++ b/bpf/pktlatency.h @@ -49,6 +49,7 @@ enum traffic_protocol_t { kProtocolKafka, kProtocolMux, kProtocolAMQP, + kProtocolRocketMQ, kNumProtocols }; diff --git a/bpf/protocol_inference.h b/bpf/protocol_inference.h index 0912c333..02252dc7 100644 --- a/bpf/protocol_inference.h +++ b/bpf/protocol_inference.h @@ -127,6 +127,28 @@ static __always_inline enum message_type_t is_http_protocol(const char *old_buf, return kUnknown; } +static __always_inline enum message_type_t is_rocketmq_protocol(const char *old_buf, size_t count) { + if (count < 8) { + return 0; + } + + int32_t frame_size = 0; + bpf_probe_read_user(&frame_size, sizeof(int32_t), old_buf); + + if (frame_size <= 0 || frame_size > 64 * 1024 * 1024) { + return kUnknown; + } + + char serialized_type = 0; + bpf_probe_read_user(&serialized_type, 1, old_buf + 4); + + if (serialized_type != 0x0 && serialized_type != 0x1) { + return kUnknown; + } + + return kRequest; +} + static __always_inline struct protocol_message_t infer_protocol(const char *buf, size_t count, struct conn_info_t *conn_info) { struct protocol_message_t protocol_message; protocol_message.protocol = kProtocolUnknown; @@ -137,6 +159,8 @@ static __always_inline struct protocol_message_t infer_protocol(const char *buf, protocol_message.protocol = kProtocolMySQL; } else if (is_redis_protocol(buf, count)) { protocol_message.protocol = kProtocolRedis; + } else if (is_rocketmq_protocol(buf,count)) { + protocol_message.protocol = kProtocolRocketMQ; } conn_info->prev_count = count; if (count == 4) { diff --git a/cmd/rocketmq.go b/cmd/rocketmq.go new file mode 100644 index 00000000..69c19d7c --- /dev/null +++ b/cmd/rocketmq.go @@ -0,0 +1,27 @@ +package cmd + +import ( + "kyanos/agent/protocol/rocketmq" + + "github.com/spf13/cobra" +) + +var rocketmqCmd *cobra.Command = &cobra.Command{ + Use: "rocketmq", + Short: "watch RocketMQ message", + Run: func(cmd *cobra.Command, args []string) { + options.MessageFilter = rocketmq.Filter{} + options.LatencyFilter = initLatencyFilter(cmd) + options.SizeFilter = initSizeFilter(cmd) + startAgent() + + }, +} + +func init() { + rocketmqCmd.PersistentFlags().SortFlags = false + copy := *rocketmqCmd + watchCmd.AddCommand(©) + copy2 := *rocketmqCmd + statCmd.AddCommand(©2) +} diff --git a/cmd/watch.go b/cmd/watch.go index d1e37053..2fcdcbe6 100644 --- a/cmd/watch.go +++ b/cmd/watch.go @@ -9,12 +9,13 @@ import ( var maxRecords int var supportedProtocols = []string{"http", "redis", "mysql"} var watchCmd = &cobra.Command{ - Use: "watch [http|redis|mysql] [flags]", + Use: "watch [http|redis|mysql|rocketmq] [flags]", Example: ` sudo kyanos watch sudo kyanos watch http --side server --pid 1234 --path /foo/bar --host ubuntu.com sudo kyanos watch redis --comands GET,SET --keys foo,bar --key-prefix app1: sudo kyanos watch mysql --latency 100 --req-size 1024 --resp-size 2048 +sudo kyanos watch rocketmq `, Short: "Capture the request/response recrods", PersistentPreRun: func(cmd *cobra.Command, args []string) { Mode = WatchMode }, diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..47dc7956 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,42 @@ +version: '3.8' +services: + namesrv: + image: apache/rocketmq:5.3.1 + container_name: rmqnamesrv + ports: + - 9876:9876 + networks: + - rocketmq + command: sh mqnamesrv + broker: + image: apache/rocketmq:5.3.1 + container_name: rmqbroker + ports: + - 10909:10909 + - 10911:10911 + - 10912:10912 + environment: + - NAMESRV_ADDR=rmqnamesrv:9876 + depends_on: + - namesrv + networks: + - rocketmq + command: sh mqbroker + proxy: + image: apache/rocketmq:5.3.1 + container_name: rmqproxy + networks: + - rocketmq + depends_on: + - broker + - namesrv + ports: + - 8080:8080 + - 8081:8081 + restart: on-failure + environment: + - NAMESRV_ADDR=rmqnamesrv:9876 + command: sh mqproxy +networks: + rocketmq: + driver: bridge \ No newline at end of file diff --git a/test_rocketmq.py b/test_rocketmq.py new file mode 100644 index 00000000..a1ce60e3 --- /dev/null +++ b/test_rocketmq.py @@ -0,0 +1,44 @@ +from rocketmq.client import Producer, Message, PushConsumer + + +def send_message(): + producer = Producer("TestProducerGroup") + producer.set_namesrv_addr("127.0.0.1:9876") + producer.start() + + msg = Message("TestTopic") + msg.set_keys("messageKey") + msg.set_tags("messageTag") + msg.set_body("Hello RocketMQ") + result = producer.send_sync(msg) + print(f"Message sent: {result}") + producer.shutdown() + + +def consume_message(): + consumer = PushConsumer("TestConsumerGroup") + consumer.set_namesrv_addr("127.0.0.1:9876") + + def callback(msg): + print(f"Message received: {msg.body.decode()}") + return True + + consumer.subscribe("TestTopic", callback) + consumer.start() + print("Consumer started. Press Ctrl+C to exit...") + try: + import time + + while True: + time.sleep(1) + except KeyboardInterrupt: + consumer.shutdown() + + +if __name__ == "__main__": + import sys + + if len(sys.argv) > 1 and sys.argv[1] == "consume": + consume_message() + else: + send_message() diff --git a/testdata/run_e2e.sh b/testdata/run_e2e.sh index e09b4433..b6239c8b 100755 --- a/testdata/run_e2e.sh +++ b/testdata/run_e2e.sh @@ -47,9 +47,10 @@ function main() { # bash testdata/test_containerd_filter_by_container_name.sh "$CMD" "$DOCKER_REGISTRY" # bash testdata/test_redis.sh "$CMD" "$DOCKER_REGISTRY" # bash testdata/test_mysql.sh "$CMD" "$DOCKER_REGISTRY" + bash testdata/test_rocketmq.sh "$CMD" "$DOCKER_REGISTRY" # bash testdata/test_https.sh "$CMD" # bash testdata/test_side.sh "$CMD" "$DOCKER_REGISTRY" - bash testdata/test_gotls.sh "$CMD" + # bash testdata/test_gotls.sh "$CMD" echo "success!" } diff --git a/testdata/test_rocketmq.sh b/testdata/test_rocketmq.sh new file mode 100755 index 00000000..0eecabd0 --- /dev/null +++ b/testdata/test_rocketmq.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +. $(dirname "$0")/common.sh +set -ex + +CMD="$1" +DOCKER_REGISTRY="$2" +FILE_PREFIX="/tmp/kyanos" +ROCKETMQ_CLIENT_LNAME="${FILE_PREFIX}_rocketmq_client.log" +ROCKETMQ_SERVER_LNAME="${FILE_PREFIX}_rocketmq_server.log" + +function test_rocketmq() { + if [ -z "$DOCKER_REGISTRY" ]; then + IMAGE_NAME="apache/rocketmq:5.3.1" + else + IMAGE_NAME=$DOCKER_REGISTRY"/apache/rocketmq:5.3.1" + fi + + docker-compose up -d + + timeout 30 ${CMD} watch --debug-output rocketmq --remote-ports 9876,8080 2>&1 | tee "${ROCKETMQ_CLIENT_LNAME}" & + sleep 10 + + python3 test_rocketmq.py + python3 test_rocketmq.py consume & + sleep 10 + pkill -f test_rocketmq.py + + wait + + cat "${ROCKETMQ_CLIENT_LNAME}" + docker rm -f rmqnamesrv rmqbroker || true + check_patterns_in_file "${ROCKETMQ_CLIENT_LNAME}" "Hello RocketMQ" +} + +function main() { + test_rocketmq +} + +main From 28798f332af3c61d2ad4b90489961a132ea97253 Mon Sep 17 00:00:00 2001 From: Spencer Cai Date: Mon, 30 Dec 2024 14:47:55 +0800 Subject: [PATCH 03/28] docs: introduce `prettier` and `md-padding` to format all docs (#221) * docs: introduce prettier and md-padding to format docs Signed-off-by: spencercjh * fix: make github alerts work Signed-off-by: spencercjh * fix: make all markdown extensions work Signed-off-by: spencercjh * fix: reformat new codes from main Signed-off-by: spencercjh --------- Signed-off-by: spencercjh --- .prettierignore | 2 + .prettierrc | 13 ++ CODE_OF_CONDUCT.md | 52 +++---- COMPILATION.md | 68 ++++++--- COMPILATION_CN.md | 59 +++++--- Makefile | 6 + README.md | 118 +++++++++++----- README_CN.md | 120 ++++++++++------ docs/cn/faq.md | 51 ++++--- docs/cn/how-to-add-a-new-protocol.md | 135 +++++++++--------- docs/cn/how-to-build.md | 58 +++++--- docs/cn/how-to.md | 77 ++++++---- docs/cn/index.md | 63 ++++----- docs/cn/quickstart.md | 30 ++-- docs/cn/stat.md | 100 +++++++------ docs/cn/watch.md | 137 +++++++++--------- docs/cn/what-is-kyanos.md | 63 +++++---- docs/faq.md | 54 +++++-- docs/how-to-add-a-new-protocol.md | 167 ++++++++++++++++------ docs/how-to-build.md | 67 ++++++--- docs/how-to.md | 97 +++++++++---- docs/index.md | 78 ++++++----- docs/quickstart.md | 27 ++-- docs/stat.md | 111 ++++++++++----- docs/watch.md | 144 +++++++++++-------- docs/what-is-kyanos.md | 104 ++++++++++---- package-lock.json | 201 ++++++++++++++++++++++++++- package.json | 4 +- 28 files changed, 1474 insertions(+), 732 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..01539786 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +.github +libbpf/*.md diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..8bce4985 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,13 @@ +{ + "proseWrap": "always", + "singleQuote": false, + "trailingComma": "none", + "overrides": [ + { + "files": "*.md", + "options": { + "parser": "markdown" + } + } + ] +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index e0ee7074..88cea644 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -6,8 +6,8 @@ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. +nationality, personal appearance, race, religion, or sexual identity and +orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. @@ -17,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community +- Focusing on what is best not just for us as individuals, but for the overall + community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities @@ -60,8 +60,8 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -hengyoush1@163.com. -All complaints will be reviewed and investigated promptly and fairly. +hengyoush1@163.com. All complaints will be reviewed and investigated promptly +and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. @@ -82,15 +82,15 @@ behavior was inappropriate. A public apology may be requested. ### 2. Warning -**Community Impact**: A violation through a single incident or series -of actions. +**Community Impact**: A violation through a single incident or series of +actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. +like social media. Violating these terms may lead to a temporary or permanent +ban. ### 3. Temporary Ban @@ -106,11 +106,11 @@ Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an +standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. -**Consequence**: A permanent ban from any sort of public interaction within -the community. +**Consequence**: A permanent ban from any sort of public interaction within the +community. ## Attribution @@ -118,8 +118,8 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org diff --git a/COMPILATION.md b/COMPILATION.md index a3ca6ac6..18dbdfd3 100644 --- a/COMPILATION.md +++ b/COMPILATION.md @@ -1,6 +1,7 @@ # Compilation Steps -This document describes the local compilation method for kyanos. My environment is Ubuntu 22.04, and other environments may vary. +This document describes the local compilation method for kyanos. My environment +is Ubuntu 22.04, and other environments may vary. ## Tool Version Requirements @@ -9,54 +10,79 @@ This document describes the local compilation method for kyanos. My environment - llvm 10.0 or above ## Installation of Compilation Environment Dependencies + ### Ubuntu -If you are using Ubuntu 20.04 or later, you can initialize the compilation environment with a single command. + +If you are using Ubuntu 20.04 or later, you can initialize the compilation +environment with a single command. + ``` /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/hengyoush/kyanos/refs/heads/main/init_env.sh)" ``` + ### Other Linux Distributions + clone the project (don't forget to update submodle!): + ```bash git clone https://github.com/hengyoush/kyanos cd kyanos git submodule update --init --recursive ``` -In addition to the toolchain versions listed above, the compilation environment also requires the following software. Please install them manually. + +In addition to the toolchain versions listed above, the compilation environment +also requires the following software. Please install them manually. - linux-tools-common - linux-tools-generic - pkgconf - libelf-dev - ## Compilation Commands If you are just developing and testing locally, you can execute + ``` make build-bpf && make ``` -the kyanos executable file will be generated in the root directory of the project. +the kyanos executable file will be generated in the root directory of the +project. > [!IMPORTANT] -> Note that this binary file does not include the BTF files from [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/). If you run this kyanos on a lower version kernel without BTF support, it may fail to start. You can build a kyanos artifact with embedded BTF files using the following commands: ->x86_64: ->```bash [x86_64] ->make build-bpf && make btfgen BUILD_ARCH=x86_64 ARCH_BPF_NAME=x86 && make ->``` ->arm64: ->```bash [arm64] ->make build-bpf && make btfgen BUILD_ARCH=arm64 ARCH_BPF_NAME=arm64 && make ->``` +> +> Note that this binary file does not include the BTF files from +> [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/). If you run +> this kyanos on a lower version kernel without BTF support, it may fail to +> start. You can build a kyanos artifact with embedded BTF files using the +> following commands: +> x86_64: +> +> ```bash [x86_64] +> make build-bpf && make btfgen BUILD_ARCH=x86_64 ARCH_BPF_NAME=x86 && make +> ``` +> +> arm64: +> +> ```bash [arm64] +> make build-bpf && make btfgen BUILD_ARCH=arm64 ARCH_BPF_NAME=arm64 && make +> ``` > > Note that make btfgen may take more than 15 minutes. - > [!TIP] ->If your kernel does not have BTF enabled, you may not be able to start kyanos successfully. > ->Check if BTF is enabled: ->``` ->grep CONFIG_DEBUG_INFO_BTF "/boot/config-$(uname -r)" ->``` ->If the result is `CONFIG_DEBUG_INFO_BTF=y`, it means BTF is enabled. If not, please download the BTF file corresponding to your kernel version from [mirrors.openanolis.cn](https://mirrors.openanolis.cn/coolbpf/btf/) or [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/), and use the `--btf` option to specify the downloaded BTF file when starting kyanos. \ No newline at end of file +> If your kernel does not have BTF enabled, you may not be able to start kyanos +> successfully. +> +> Check if BTF is enabled: +> +> ``` +> grep CONFIG_DEBUG_INFO_BTF "/boot/config-$(uname -r)" +> ``` +> +> If the result is `CONFIG_DEBUG_INFO_BTF=y`, it means BTF is enabled. If not, +> please download the BTF file corresponding to your kernel version from +> [mirrors.openanolis.cn](https://mirrors.openanolis.cn/coolbpf/btf/) or +> [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/), and use the +> `--btf` option to specify the downloaded BTF file when starting kyanos. diff --git a/COMPILATION_CN.md b/COMPILATION_CN.md index 05869f28..7703d2d8 100644 --- a/COMPILATION_CN.md +++ b/COMPILATION_CN.md @@ -1,6 +1,7 @@ # 编译步骤 -本文介绍 kyanos 的本地编译方法,我的环境是ubuntu 22.04,其他环境可能会有所不同。 +本文介绍 kyanos 的本地编译方法,我的环境是 ubuntu +22.04,其他环境可能会有所不同。 ## 工具版本要求 @@ -9,13 +10,20 @@ - llvm 10.0 以上 ## 编译环境依赖安装 + ### Ubuntu -如果你使用的是ubuntu 20.04以及更新版本,可以使用一条命令即可完成编译环境的初始化。 + +如果你使用的是 ubuntu +20.04 以及更新版本,可以使用一条命令即可完成编译环境的初始化。 + ``` /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/hengyoush/kyanos/refs/heads/main/init_env.sh)" ``` + ### 其他 Linux 发行版 + 用下面的命令 clone 项目: + ```bash git clone https://github.com/hengyoush/kyanos cd kyanos @@ -32,6 +40,7 @@ git submodule update --init --recursive ## 编译命令 如果只是本地开发测试,可以执行 + ``` make build-bpf && make ``` @@ -39,26 +48,36 @@ make build-bpf && make 之后在项目根目录下会生成 kyanos 可执行文件。 > [!IMPORTANT] -> 但是需要注意的是该二进制文件中没有包含 [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/) 中的 btf 文件,如果直接拿这个 kyanos 去没有开启 BTF 支持的低版本内核上执行可能会启动失败,通过下面的命令可以构建出一个内嵌 btf 文件的 kyanos 产物: -> x86_64: ->```bash [x86_64] ->make build-bpf && make btfgen BUILD_ARCH=x86_64 ARCH_BPF_NAME=x86 && make ->``` ->arm64: ->```bash [arm64] ->make build-bpf && make btfgen BUILD_ARCH=arm64 ARCH_BPF_NAME=arm64 && make ->``` +> +> 但是需要注意的是该二进制文件中没有包含 +> [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/) +> 中的 btf 文件,如果直接拿这个 kyanos 去没有开启 BTF 支持的低版本内核上执行可能会启动失败,通过下面的命令可以构建出一个内嵌 btf 文件的 kyanos 产物: +> x86_64: +> +> ```bash [x86_64] +> make build-bpf && make btfgen BUILD_ARCH=x86_64 ARCH_BPF_NAME=x86 && make +> ``` +> +> arm64: +> +> ```bash [arm64] +> make build-bpf && make btfgen BUILD_ARCH=arm64 ARCH_BPF_NAME=arm64 && make +> ``` > > 需要注意 `make btfgen` 耗时可能超过 15min。 - > [!TIP] ->如果你的内核没有开启BTF,你可能无法成功启动 kyanos. > ->检查是否开启BTF: ->``` ->grep CONFIG_DEBUG_INFO_BTF "/boot/config-$(uname -r)" ->``` ->如果结果是`CONFIG_DEBUG_INFO_BTF=y`说明开启了,如果没开启请到 [mirrors.openanolis.cn](https://mirrors.openanolis.cn/coolbpf/btf/) or [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/)上下载对应你的内核版本的 btf 文件,然后启动 kyanos 时使用 `--btf` 选项指定下载的 btf 文件。 - - +> 如果你的内核没有开启 BTF,你可能无法成功启动 kyanos. +> +> 检查是否开启 BTF: +> +> ``` +> grep CONFIG_DEBUG_INFO_BTF "/boot/config-$(uname -r)" +> ``` +> +> 如果结果是 `CONFIG_DEBUG_INFO_BTF=y` 说明开启了,如果没开启请到 +> [mirrors.openanolis.cn](https://mirrors.openanolis.cn/coolbpf/btf/) or +> [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/) +> 上下载对应你的内核版本的 btf 文件,然后启动 kyanos 时使用 `--btf` +> 选项指定下载的 btf 文件。 diff --git a/Makefile b/Makefile index 391532d1..99b36430 100644 --- a/Makefile +++ b/Makefile @@ -104,6 +104,12 @@ format-go: goimports -w . gofmt -s -w . +.PHONY: format-md +format-md: + find . -type f -name "*.md" | xargs npx prettier --write + find docs/cn -type f -name "*.md" | xargs npx md-padding -i + find . -type f -name "*_CN.md" | xargs npx md-padding -i + .PHONY: dlv dlv: chmod +x kyanos && dlv --headless --listen=:2345 --api-version=2 --check-go-version=false exec ./kyanos diff --git a/README.md b/README.md index 11d91e25..0d7383d8 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,13 @@ ![](docs/public/kyanos-demo.gif) +
[![GitHub last commit](https://img.shields.io/github/last-commit/hengyoush/kyanos)](#) [![GitHub release](https://img.shields.io/github/v/release/hengyoush/kyanos)](#) [![Test](https://github.com/hengyoush/kyanos/actions/workflows/test.yml/badge.svg)](https://github.com/hengyoush/kyanos/actions/workflows/test.yml) -[![Twitter](https://img.shields.io/twitter/url/https/x.com/kyanos.svg?style=social&label=Follow%20%40kyanos)](https://x.com/kyanos_github) +[![Twitter](https://img.shields.io/twitter/url/https/x.com/kyanos.svg?style=social&label=Follow%20%40kyanos)](https://x.com/kyanos_github) hengyoush%2Fkyanos | Trendshift [![Featured on Hacker News](https://hackerbadge.now.sh/api?id=42154583)](https://news.ycombinator.com/item?id=42154583) @@ -19,12 +20,12 @@
-[简体中文](./README_CN.md) | English - +[简体中文](./README_CN.md) | English - [English Document](https://kyanos.io/) ## Table of Contents + - [What is kyanos](#-what-is-kyanos) - [Examples](#-examples) - [Requirements](#-requirements) @@ -37,10 +38,15 @@ - [Contacts](#%EF%B8%8F-contacts) ## What is kyanos -Kyanos is an **eBPF-based** network issue analysis tool that enables you to capture network requests, such as HTTP, Redis, and MySQL requests. -It also helps you analyze abnormal network issues and quickly troubleshooting without the complex steps of packet capturing, downloading, and analysis. -1. **Powerful Traffic Filtering**: Not only can filter based on traditional IP/port information, can also filter by process/container, L7 protocol information, request/response byte size, latency, and more. +Kyanos is an **eBPF-based** network issue analysis tool that enables you to +capture network requests, such as HTTP, Redis, and MySQL requests. +It also helps you analyze abnormal network issues and quickly troubleshooting +without the complex steps of packet capturing, downloading, and analysis. + +1. **Powerful Traffic Filtering**: Not only can filter based on traditional + IP/port information, can also filter by process/container, L7 protocol + information, request/response byte size, latency, and more. ```bash # Filter by pid @@ -53,38 +59,54 @@ It also helps you analyze abnormal network issues and quickly troubleshooting wi ./kyanos watch --resp-size 10000 ``` -2. **Advanced Analysis Capabilities** : Unlike tcpdump, which only provides fine-grained packet capture, Kyanos supports aggregating captured packet metrics across various dimensions, quickly providing the critical data most useful for troubleshooting. -Imagine if the bandwidth of your HTTP service is suddenly maxed out—how would you quickly analyze `which IPs` and `which requests` are causing it? -With Kyanos, you just need one command: `kyanos stat http --bigresp` to find the largest response byte sizes sent to remote IPs and view specific data on request and response metrics. -![kyanos find big response](docs/public/whatkyanos.gif) +2. **Advanced Analysis Capabilities** : Unlike tcpdump, which only provides + fine-grained packet capture, Kyanos supports aggregating captured packet + metrics across various dimensions, quickly providing the critical data most + useful for troubleshooting. + Imagine if the bandwidth of your HTTP service is suddenly maxed out—how would + you quickly analyze `which IPs` and `which requests` are causing it? + With Kyanos, you just need one command: `kyanos stat http --bigresp` to find + the largest response byte sizes sent to remote IPs and view specific data on + request and response metrics. + ![kyanos find big response](docs/public/whatkyanos.gif) -3. **In-Depth Kernel-Level Latency Details**: In real-world, slow queries to remote services like Redis can be challenging to diagnose precisely. Kyanos provides kernel trace points from the arrival of requests/responses at the network card to the kernel socket buffer, displaying these details in a visual format. This allows you to identify exactly which stage is causing delays. +3. **In-Depth Kernel-Level Latency Details**: In real-world, slow queries to + remote services like Redis can be challenging to diagnose precisely. Kyanos + provides kernel trace points from the arrival of requests/responses at the + network card to the kernel socket buffer, displaying these details in a + visual format. This allows you to identify exactly which stage is causing + delays. -![kyanos time detail](docs/public/timedetail.jpg) +![kyanos time detail](docs/public/timedetail.jpg) -4. **Lightweight and Dependency-Free**: Almost zero dependencies—just a single binary file and one command, with all results displayed in the command line. +4. **Lightweight and Dependency-Free**: Almost zero dependencies—just a single + binary file and one command, with all results displayed in the command line. -5. **Automatic SSL Traffic Decryption** : All captured requests and responses are presented in plaintext. +5. **Automatic SSL Traffic Decryption** : All captured requests and responses + are presented in plaintext. ## Examples -**Capture HTTP Traffic with Latency Details** +**Capture HTTP Traffic with Latency Details** Run the command: + ```bash ./kyanos watch http ``` + The result is as follows: ![kyanos quick start watch http](docs/public/qs-watch-http.gif) - -**Capture Redis Traffic with Latency Details** +**Capture Redis Traffic with Latency Details** Run the command: + ```bash ./kyanos watch redis ``` + The result is as follows: ![kyanos quick start watch redis](docs/public/qs-redis.gif) @@ -92,35 +114,41 @@ The result is as follows: **Identify the Slowest Requests in the Last 5 Seconds** Run the command: + ```bash - ./kyanos stat --slow --time 5 + ./kyanos stat --slow --time 5 ``` + The result is as follows: ![kyanos stat slow](docs/public/qs-stat-slow.gif) ## ❗ Requirements -Kyanos currently supports kernel versions 3.10(from 3.10.0-957) and 4.14 or above (with plans to support versions between 4.7 and 4.14 in the future). -> You can check your kernel version using `uname -r`. +Kyanos currently supports kernel versions 3.10(from 3.10.0-957) and 4.14 or +above (with plans to support versions between 4.7 and 4.14 in the future). +> You can check your kernel version using `uname -r`. -## 🎯 How to get kyanos +## 🎯 How to get kyanos -You can download a statically linked binary compatible with amd64 and arm64 architectures from the [release page](https://github.com/hengyoush/kyanos/releases): +You can download a statically linked binary compatible with amd64 and arm64 +architectures from the +[release page](https://github.com/hengyoush/kyanos/releases): ```bash tar xvf kyanos_vx.x.x_linux_amd64.tar.gz ``` Then, run kyanos with **root privilege**: + ```bash -sudo ./kyanos watch +sudo ./kyanos watch ``` If the following table appears: -![kyanos quick start success](docs/public/quickstart-success.png) -🎉 Congratulations! Kyanos has started successfully. +![kyanos quick start success](docs/public/quickstart-success.png) 🎉 +Congratulations! Kyanos has started successfully. ## 📝 Documentation @@ -134,17 +162,25 @@ The simplest usage captures all protocols currently supported by Kyanos: sudo ./kyanos watch ``` -Each request-response record is stored as a row in a table, with each column capturing basic information about that request. You can use the arrow keys or `j/k` to move up and down through the records: -![kyanos watch result](docs/public/watch-result.jpg) +Each request-response record is stored as a row in a table, with each column +capturing basic information about that request. You can use the arrow keys or +`j/k` to move up and down through the records: +![kyanos watch result](docs/public/watch-result.jpg) Press `Enter` to access the details view: -![kyanos watch result detail](docs/public/watch-result-detail.jpg) +![kyanos watch result detail](docs/public/watch-result-detail.jpg) -In the details view, the first section shows **Latency Details**. Each block represents a "node" that the data packet passes through, such as the process, network card, and socket buffer. -Each block includes a time value indicating the time elapsed from the previous node to this node, showing the process flow from the process sending the request to the network card, to the response being copied to the socket buffer, and finally read by the process, with each step’s duration displayed. +In the details view, the first section shows **Latency Details**. Each block +represents a "node" that the data packet passes through, such as the process, +network card, and socket buffer. +Each block includes a time value indicating the time elapsed from the previous +node to this node, showing the process flow from the process sending the request +to the network card, to the response being copied to the socket buffer, and +finally read by the process, with each step’s duration displayed. -The second section provides **Detailed Request and Response Content**, split into Request and Response parts, and truncates content over 1024 bytes. +The second section provides **Detailed Request and Response Content**, split +into Request and Response parts, and truncates content over 1024 bytes. For targeted traffic capture, such as HTTP traffic: @@ -155,7 +191,7 @@ For targeted traffic capture, such as HTTP traffic: You can narrow it further to capture traffic for a specific HTTP path: ```bash -./kyanos watch http --path /abc +./kyanos watch http --path /abc ``` Learn more: [Kyanos Docs](https://kyanos.io/) @@ -165,7 +201,10 @@ Learn more: [Kyanos Docs](https://kyanos.io/) 👉 [COMPILATION.md](./COMPILATION.md) ## Roadmap -The Kyanos Roadmap shows the future plans for Kyanos. If you have feature requests or want to prioritize a specific feature, please submit an issue on GitHub. + +The Kyanos Roadmap shows the future plans for Kyanos. If you have feature +requests or want to prioritize a specific feature, please submit an issue on +GitHub. _1.5.0_ @@ -176,19 +215,26 @@ _1.5.0_ 5. Support for kafka protocol parsing 6. Full support for ipv6 - ## 🤝 Feedback and Contributions + > [!IMPORTANT] -> If you encounter any issues or bugs while using the tool, please feel free to ask questions in the issue tracker. +> +> If you encounter any issues or bugs while using the tool, please feel free to +> ask questions in the issue tracker. ## 🙇‍ Special Thanks -During the development of kyanos, some code was borrowed from the following projects: + +During the development of kyanos, some code was borrowed from the following +projects: + - [eCapture](https://ecapture.cc/zh/) - [pixie](https://github.com/pixie-io/pixie) - [ptcpdump](https://github.com/mozillazg/ptcpdump) ## 🗨️ Contacts + For more detailed inquiries, you can use the following contact methods: + - **Twitter:** [https://x.com/kyanos_github](https://x.com/kyanos_github) - **My Email:** [hengyoush1@163.com](mailto:hengyoush1@163.com) - **My Blog:** [http://blog.deadlock.cloud](http://blog.deadlock.cloud/) diff --git a/README_CN.md b/README_CN.md index 3ce82c00..1ebdbc18 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,22 +1,26 @@ - ![](docs/public/kyanos-demo.gif) +
[![GitHub last commit](https://img.shields.io/github/last-commit/hengyoush/kyanos)](#) [![GitHub release](https://img.shields.io/github/v/release/hengyoush/kyanos)](#) [![Test](https://github.com/hengyoush/kyanos/actions/workflows/test.yml/badge.svg)](https://github.com/hengyoush/kyanos/actions/workflows/test.yml) -[![Twitter](https://img.shields.io/twitter/url/https/x.com/kyanos.svg?style=social&label=Follow%20%40kyanos)](https://x.com/kyanos_github) +[![Twitter](https://img.shields.io/twitter/url/https/x.com/kyanos.svg?style=social&label=Follow%20%40kyanos)](https://x.com/kyanos_github) -hengyoush%2Fkyanos | Trendshift + +hengyoush%2Fkyanos | Trendshift + [![Featured on Hacker News](https://hackerbadge.now.sh/api?id=42154583)](https://news.ycombinator.com/item?id=42154583) -Featured|HelloGitHub + +Featured|HelloGitHub +
简体中文 | [English](./README.md) - ## Table of Contents + - [What is Kyanos](#-what-is-kyanos) - [Examples](#-examples) - [Requirements](#-requirements) @@ -29,8 +33,11 @@ - [Contacts](#%EF%B8%8F-contacts) ## 🦜 What is kyanos + Kyanos 是一个网络流量采集和分析工具,它提供如下特性: -1. **强大的流量过滤功能**:不仅可以根据传统 IP/端口 等信息过滤,还支持根据:进程/容器、L7协议信息、请求/响应字节数、耗时等过滤你想要的数据。 + +1. **强大的流量过滤功能**:不仅可以根据传统 IP/端口 等信息过滤,还支持根据:进程/容器、L7 协议信息、请求/响应字节数、耗时等过滤你想要的数据。 + ```bash # 根据 pid 过滤 ./kyanos watch --pids 1234 @@ -41,73 +48,86 @@ Kyanos 是一个网络流量采集和分析工具,它提供如下特性: # 根据响应字节数过滤 ./kyanos watch --resp-size 10000 ``` -2. **强大的分析功能**: 和 tcpdump 只提供细粒度的抓包功能不同,kyanos 还支持以各种维度聚合抓取的数据包的指标信息,快速得到对排查问题最有用的关键数据。想象一下你的 HTTP 服务的带宽突然被打满,你该如何快速的分析是 `哪些 ip` 的 `哪些请求` 造成的? -使用 kyanos 只需要一行命令:`kyanos stat http --bigresp` 即可找到发送给哪些远程 ip 的响应字节数最大,并且还能够发现请求响应的具体数据。 -![kyanos find big response](docs/public/whatkyanos.gif) -3. **深入内核的耗时细节**:在实际业务场景中我们经常遇到远程服务慢查询问题,比如访问 Redis 请求较慢,但是 **具体慢在哪里** 在传统监控方式下很难给出确切答案。而 kyanos 提供了 请求/响应 到达网卡以及从 内核Socket 缓冲区读取的内核埋点,并且以可视化的图形展示出来,你可以方便的判断是哪一个环节出现了问题。 -![kyanos time detail](docs/public/timedetail.jpg) -如上所示,这是一个在容器内执行 `curl http://www.baidu.com` 命令的耗时记录,你可以发现 kyanos 记录了请求经过容器网卡、宿主机网卡,响应经过宿主机网卡、容器网卡、Socket缓冲区每个步骤的耗时。 + +2. **强大的分析功能**: 和 tcpdump 只提供细粒度的抓包功能不同,kyanos 还支持以各种维度聚合抓取的数据包的指标信息,快速得到对排查问题最有用的关键数据。想象一下你的 HTTP 服务的带宽突然被打满,你该如何快速的分析是 + `哪些 ip` 的 `哪些请求` 造成的? + 使用 kyanos 只需要一行命令:`kyanos stat http --bigresp` 即可找到发送给哪些远程 ip 的响应字节数最大,并且还能够发现请求响应的具体数据。 + ![kyanos find big response](docs/public/whatkyanos.gif) +3. **深入内核的耗时细节**:在实际业务场景中我们经常遇到远程服务慢查询问题,比如访问 Redis 请求较慢,但是 + **具体慢在哪里** + 在传统监控方式下很难给出确切答案。而 kyanos 提供了 请求/响应 到达网卡以及从 内核 Socket 缓冲区读取的内核埋点,并且以可视化的图形展示出来,你可以方便的判断是哪一个环节出现了问题。 + ![kyanos time detail](docs/public/timedetail.jpg) + 如上所示,这是一个在容器内执行 `curl http://www.baidu.com` + 命令的耗时记录,你可以发现 kyanos 记录了请求经过容器网卡、宿主机网卡,响应经过宿主机网卡、容器网卡、Socket 缓冲区每个步骤的耗时。 4. **轻量级零依赖**:几乎 0 依赖,只需要单个二进制文件,一行命令,所有结果都展示在命令行中。 -5. **SSL流量自动解密**:kyanos 为你抓取的请求响应结果全部都是明文。 +5. **SSL 流量自动解密**:kyanos 为你抓取的请求响应结果全部都是明文。 ## 🌰 Examples -**抓取 HTTP 流量并且获取耗时细节** +**抓取 HTTP 流量并且获取耗时细节** 执行命令: + ```bash ./kyanos watch http ``` + 演示结果如下: ![kyanos quick start watch http](docs/public/qs-watch-http.gif) - -**抓取 Redis 流量获取耗时细节** +**抓取 Redis 流量获取耗时细节** 执行命令: + ```bash ./kyanos watch redis ``` + 演示结果如下: ![kyanos quick start watch redis](docs/public/qs-redis.gif) -**找到5s内最慢的几个请求** +**找到 5s 内最慢的几个请求** 执行命令: + ```bash - ./kyanos stat --slow --time 5 + ./kyanos stat --slow --time 5 ``` + 演示结果如下: ![kyanos stat slow](docs/public/qs-stat-slow.gif) ## ❗ Requirements -Kyanos当前支持3.10(3.10.0-957以上)及4.14以上版本内核(4.7版本到4.14版本之间的后续计划支持)。 -> 通过`uname -r`查看内核版本 +Kyanos 当前支持 3.10(3.10.0-957 以上)及 4.14 以上版本内核(4.7 版本到 4.14 版本之间的后续计划支持)。 + +> 通过 `uname -r` 查看内核版本 ## 📝 Documentation [Chinese Document](https://kyanos.io/cn/) -## 🎯 How to get kyanos -你可以从 [release page](https://github.com/hengyoush/kyanos/releases) 中下载以静态链接方式编译的适用于 amd64 和 arm64 架构的二进制文件: +## 🎯 How to get kyanos + +你可以从 [release page](https://github.com/hengyoush/kyanos/releases) +中下载以静态链接方式编译的适用于 amd64 和 arm64 架构的二进制文件: ```bash tar xvf kyanos_vx.x.x_linux_amd64.tar.gz ``` -然后以 **root 权限**执行如下命令: +然后以 **root 权限** 执行如下命令: + ```bash -sudo ./kyanos watch +sudo ./kyanos watch ``` - 如果显示了下面的表格: +如果显示了下面的表格: ![kyanos quick start success](docs/public/quickstart-success.png) -🎉 恭喜你,kyanos启动成功了。 - +🎉 恭喜你,kyanos 启动成功了。 ## ⚙ Usage @@ -116,29 +136,31 @@ sudo ./kyanos watch ```bash sudo ./kyanos watch ``` -每个请求响应记录会记录在表格中的一行,每列记录这个请求的基本信息。你可以通过方向键或者j/k上下移动来选择记录: -![kyanos watch result](docs/public/watch-result.jpg) -按下`enter`进入详情界面: +每个请求响应记录会记录在表格中的一行,每列记录这个请求的基本信息。你可以通过方向键或者 j/k 上下移动来选择记录: +![kyanos watch result](docs/public/watch-result.jpg) -![kyanos watch result detail](docs/public/watch-result-detail.jpg) +按下 `enter` 进入详情界面: -详情界面里第一部分是 **耗时详情**,每一个方块代表数据包经过的节点,比如这里有进程、网卡、Socket缓冲区等。 -每个方块下面有一个耗时,这里的耗时指从上个节点到这个节点经过的时间。 -可以清楚的看到请求从进程发送到网卡,响应再从网卡复制到Socket缓冲区并且被进程读取的流程和每一个步骤的耗时。 +![kyanos watch result detail](docs/public/watch-result-detail.jpg) -第二部分是 **请求响应的具体内容**,分为 Request 和 Response 两部分,超过 1024 字节会截断展示。 +详情界面里第一部分是 +**耗时详情**,每一个方块代表数据包经过的节点,比如这里有进程、网卡、Socket 缓冲区等。 +每个方块下面有一个耗时,这里的耗时指从上个节点到这个节点经过的时间。可以清楚的看到请求从进程发送到网卡,响应再从网卡复制到 Socket 缓冲区并且被进程读取的流程和每一个步骤的耗时。 +第二部分是 +**请求响应的具体内容**,分为 Request 和 Response 两部分,超过 1024 字节会截断展示。 -抓取流量时一般会更有针对性,比如抓取HTTP流量: +抓取流量时一般会更有针对性,比如抓取 HTTP 流量: ```bash ./kyanos watch http ``` -更进一步,你可能只想抓取某个HTTP Path的流量: + +更进一步,你可能只想抓取某个 HTTP Path 的流量: ```bash -./kyanos watch http --path /abc +./kyanos watch http --path /abc ``` 了解更多,请参考文档:[Kyanos Docs](kyanos.io) @@ -148,9 +170,11 @@ sudo ./kyanos watch 👉 [COMPILATION_CN.md](./COMPILATION_CN.md) ## Roadmap -Kyanos 的 Roadmap展示了 Kyanos 未来的计划,如果你有功能需求,或者想提高某个特性的优先级,请在 GitHub 上提交 issue。 -_1.5.0_ +Kyanos 的 Roadmap 展示了 Kyanos 未来的计划,如果你有功能需求,或者想提高某个特性的优先级,请在 GitHub 上提交 issue。 + +_1.5.0_ + 1. 支持 openssl 3.4.0 2. 支持解析 ipip 包 3. 支持根据 process name 过滤数据 @@ -158,24 +182,28 @@ _1.5.0_ 5. 支持 kafka 协议解析 6. 完全支持 ipv6 - ## 🤝 Feedback and Contributions -> [!IMPORTANT] -> 如果你遇到了任何使用上的问题、bug都可以在issue中提问。 +> [!IMPORTANT] +> +> 如果你遇到了任何使用上的问题、bug 都可以在 issue 中提问。 ## 🙇‍ Special Thanks + 在开发 kyanos 的过程中,部分代码借用了以下项目: + - [eCapture](https://ecapture.cc/zh/) - [pixie](https://github.com/pixie-io/pixie) - [ptcpdump](https://github.com/mozillazg/ptcpdump) ## 🗨️ Contacts + 如果你有更详细的问题需要咨询,可以用以下联系方式: -- **微信交流群:**: image。 -- **我的邮箱:**: [hengyoush1@163.com](mailto:hengyoush1@163.com)。 -- **我的Blog:**: [http://blog.deadlock.cloud](http://blog.deadlock.cloud/)。 +- **微信交流群:**: + image。 +- **我的邮箱:**: [hengyoush1@163.com](mailto:hengyoush1@163.com)。 +- **我的 Blog:**: [http://blog.deadlock.cloud](http://blog.deadlock.cloud/)。 ## Star History diff --git a/docs/cn/faq.md b/docs/cn/faq.md index b92130dd..18f87c65 100644 --- a/docs/cn/faq.md +++ b/docs/cn/faq.md @@ -5,45 +5,58 @@ prev: false # FAQ - ## 支持运行在 Windows / Mac 上吗 ? + 目前还未支持,但后续有计划支持。([ISSUE-151](https://github.com/hengyoush/kyanos/issues/151)) ## 支持运行在低版本内核上吗 ? -目前支持最低内核版本: 3.10.0-957,而且低版本内核下kyanos的会缺少某些功能。 -目前3.*版本内核不支持根据容器id/容器名称等过滤流量,也无法自动关联NAT前后的流量。 +目前支持最低内核版本: 3.10.0-957,而且低版本内核下 kyanos 的会缺少某些功能。 + +目前 3.\*版本内核不支持根据容器 id/容器名称等过滤流量,也无法自动关联 NAT 前后的流量。 + +## 支持运行在 WSL 上的 Linux 吗 ? + +理论上支持,但一般在 WSL 上的 Linux 发行版默认不带 linux +headers,但 kyanos 会依赖,所以可能需要修改编译选项手动编译内核,具体方法可参考:[Enabling eBPF/XDP for Kernel Tinkering on WSL2](https://dev.to/wiresurfer/unleash-the-forbidden-enabling-ebpfxdp-for-kernel-tinkering-on-wsl2-43fj) -## 支持运行在WSL上的Linux吗 ? -理论上支持,但一般在WSL上的Linux发行版默认不带linux headers,但kyanos会依赖,所以可能需要修改编译选项手动编译内核,具体方法可参考:[Enabling eBPF/XDP for Kernel Tinkering on WSL2](https://dev.to/wiresurfer/unleash-the-forbidden-enabling-ebpfxdp-for-kernel-tinkering-on-wsl2-43fj) +## 可以运行在容器/Pod 里吗 ? -## 可以运行在容器/Pod里吗 ? -必须运行在具有特权模式下的容器/Pod里。 +必须运行在具有特权模式下的容器/Pod 里。 +## 当使用 `--pod-name` 选项时出现 "can not find any running pod by name xxx" 日志 -## 当使用`--pod-name`选项时出现"can not find any running pod by name xxx"日志 Kyanos 必须与目标 Pod 运行在同一主机上。 -## 运行出现`can't find btf file to load!`日志 -可能是因为你的系统缺少了btf文件导致的,可以在这里 https://mirrors.openanolis.cn/coolbpf/btf/ 以及 https://github.com/aquasecurity/btfhub-archive/ 这里手动下载和你的内核匹配的BTF文件,启动kyanos时通过`--btf`选项指定你下载的btf文件即可。 +## 运行出现 `can't find btf file to load!` 日志 +可能是因为你的系统缺少了 btf 文件导致的,可以在这里 +https://mirrors.openanolis.cn/coolbpf/btf/ 以及 +https://github.com/aquasecurity/btfhub-archive/ +这里手动下载和你的内核匹配的 BTF 文件,启动 kyanos 时通过 `--btf` +选项指定你下载的 btf 文件即可。 -## 怎么理解watch结果中内核耗时的可视化部分 ? -![kyanos time detail](/timedetail.jpg) -图中的 `eth0@if483` 是容器NIC,`eth0` 是宿主机NIC。 -图上半部分是请求从进程发送到NIC的过程,下半部分是响应从NIC到进程的过程。 +## 怎么理解 watch 结果中内核耗时的可视化部分 ? + +![kyanos time detail](/timedetail.jpg) +图中的 `eth0@if483` 是容器 NIC,`eth0` 是宿主机 NIC。 +图上半部分是请求从进程发送到 NIC 的过程,下半部分是响应从 NIC 到进程的过程。 ## 运行后终端表格颜色不正确(比如无法选择表格中的记录) -![kyanos missing color](/missing-color.png) +![kyanos missing color](/missing-color.png) -检查是否有`Your terminal does not support 256 colors, ui may display incorrectly`日志,如果有说明终端的颜色配置不正确,kyanos需要256色的终端。 +检查是否有 +`Your terminal does not support 256 colors, ui may display incorrectly` +日志,如果有说明终端的颜色配置不正确,kyanos 需要 256 色的终端。 使用以下命令即可列出系统所支持的所有终端类型,以及他们支持的颜色位数: + ```shell for T in `find /usr/share/terminfo -type f -printf '%f '`;do echo "$T `tput -T $T colors`";done|sort -nk2|tail -n20 ``` 示例输出如下: + ```shell Eterm-88color 88 rxvt-88color 88 @@ -55,9 +68,11 @@ iTerm.app 256 konsole-256color 256 ... ``` -$TERM变量代表当前终端类型,可使用echo $TERM命令查看。 -可通过修改~/.bashrc文件将其改为256色,在.bashrc文件中加入以下代码即可: +$TERM变量代表当前终端类型,可使用echo $ TERM 命令查看。 + +可通过修改~/.bashrc 文件将其改为 256 色,在.bashrc 文件中加入以下代码即可: + ```shell case "$TERM" in xterm) diff --git a/docs/cn/how-to-add-a-new-protocol.md b/docs/cn/how-to-add-a-new-protocol.md index 0629b50f..d54df549 100644 --- a/docs/cn/how-to-add-a-new-protocol.md +++ b/docs/cn/how-to-add-a-new-protocol.md @@ -2,31 +2,28 @@ ## 背景 -kyanos 需要捕获协议消息组成请求响应对以供终端展示,因此 kyanos 需要每个协议的协议解析代码,目前支持HTTP、MySQL和Redis,将来会支持更多。本文将阐述 kyanos 协议解析的整体架构,以协助开发新的协议。 +kyanos 需要捕获协议消息组成请求响应对以供终端展示,因此 kyanos 需要每个协议的协议解析代码,目前支持 HTTP、MySQL 和 Redis,将来会支持更多。本文将阐述 kyanos 协议解析的整体架构,以协助开发新的协议。 ## 协议解析流程总览 - ![kyanos protocol parse flow](/protocol-parse-flow.png) -从左边看起,kyanos 在read、write等系统调用上插桩,获取应用进程读写的数据, -将其通过perf event buffer发送到用户空间。 - -用户空间则根据这个连接是客户端侧还是服务端侧,分别放到reqStreamBuffer或者respStreamBuffer里。 -这些数据是单纯的应用协议数据,没有协议头。 - -kyanos会使用相应协议的解析器解析放到streamBuffer的数据,然后关联解析后的请求和响应,最后根据协议的需要再进行Full Body解析,生成一个“记录”(record)。 +从左边看起,kyanos 在 read、write 等系统调用上插桩,获取应用进程读写的数据,将其通过 perf +event buffer 发送到用户空间。 +用户空间则根据这个连接是客户端侧还是服务端侧,分别放到 reqStreamBuffer 或者 respStreamBuffer 里。这些数据是单纯的应用协议数据,没有协议头。 +kyanos 会使用相应协议的解析器解析放到 streamBuffer 的数据,然后关联解析后的请求和响应,最后根据协议的需要再进行 Full +Body 解析,生成一个“记录”(record)。 ## 一些术语 -Message:协议的最基本数据单元,通常有一个header和body。 +Message:协议的最基本数据单元,通常有一个 header 和 body。 -Request / Response: 请求或响应由一起发送的一个或多个Message组成,这些Message在一起表示一个消息(*Note: 对于简单的协议比如HTTP1.1来说,一个Request/Response可能只对应一个Message,但对于像MySQL这种复杂的协议,多个Message组合起来才对应一个Request/Response*) - -Record:Record代表一个匹配完成的请求响应对。 +Request / +Response: 请求或响应由一起发送的一个或多个 Message 组成,这些 Message 在一起表示一个消息(_Note: 对于简单的协议比如 HTTP1.1 来说,一个 Request/Response 可能只对应一个 Message,但对于像 MySQL 这种复杂的协议,多个 Message 组合起来才对应一个 Request/Response_) +Record:Record 代表一个匹配完成的请求响应对。 ## Step.0-准备工作 @@ -34,18 +31,19 @@ Record:Record代表一个匹配完成的请求响应对。 1. 实现用户态协议解析 1. 定义协议消息类型(即请求和响应的结构) - 2. 实现一个`Parser`, 具体来说需要实现 `ProtocolStreamParser` 接口,该接口实现了协议解析和请求响应匹配等的具体逻辑。 + 2. 实现一个 `Parser`, 具体来说需要实现 `ProtocolStreamParser` + 接口,该接口实现了协议解析和请求响应匹配等的具体逻辑。 2. 实现内核态的协议推断逻辑。 3. 增加命令行子命令,实现过滤逻辑。 -4. 增加e2e测试。 +4. 增加 e2e 测试。 ## Step.1-定义协议消息类型 -在/agent/protocol目录下新建一个目录,名称为协议名称,例如:kafka。 +在/agent/protocol 目录下新建一个目录,名称为协议名称,例如:kafka。 -### 定义Message +### 定义 Message -一般来说,你要实现的协议会存在一个通用的Header,这个Header会包含一些元数据,比如一个标志位记录其是请求还是响应,像这些信息你需要存储到你定义的Message的字段里,比如MySQL协议定义的: +一般来说,你要实现的协议会存在一个通用的 Header,这个 Header 会包含一些元数据,比如一个标志位记录其是请求还是响应,像这些信息你需要存储到你定义的 Message 的字段里,比如 MySQL 协议定义的: ```go type MysqlPacket struct { @@ -57,7 +55,7 @@ type MysqlPacket struct { } ``` -一般来说,Message或者下面的Request/Response都需要嵌入一个FrameBase,FrameBase定义如下,包含了一些基本信息: +一般来说,Message 或者下面的 Request/Response 都需要嵌入一个 FrameBase,FrameBase 定义如下,包含了一些基本信息: ```go type FrameBase struct { @@ -67,12 +65,14 @@ type FrameBase struct { } ``` - -而对于 HTTP 等简单的协议来说,由于其一个Message就对应一个请求或响应,没有必要进行Full Body解析,因此也就没必要定义一个Message了,直接定义Request和Response就可以。 +而对于 HTTP 等简单的协议来说,由于其一个 Message 就对应一个请求或响应,没有必要进行 Full +Body 解析,因此也就没必要定义一个 Message 了,直接定义 Request 和 Response 就可以。 ### 定义请求和响应 -请求和响应在Message的上层,由一个或多个Message组成。`struct Request` 或 `struct Response` 应包含特定于请求/响应的数据, -并且应该实现ParsedMessage接口,接口定义如下: + +请求和响应在 Message 的上层,由一个或多个 Message 组成。`struct Request` 或 +`struct Response` +应包含特定于请求/响应的数据, 并且应该实现 ParsedMessage 接口,接口定义如下: ```go type ParsedMessage interface { @@ -85,16 +85,16 @@ type ParsedMessage interface { } ``` +| 方法名 | 作用 | +| ------------------ | ------------------------------------------------------------------------ | +| `FormatToString()` | 将消息格式化为字符串表示形式。 | +| `TimestampNs()` | 返回消息的时间戳(以纳秒为单位)。 | +| `ByteSize()` | 返回消息的字节大小。 | +| `IsReq()` | 判断消息是否为请求。 | +| `Seq()` | 返回消息的字节流序列号, 可以从 `streamBuffer.Head().LeftBoundary()` 获取。 | -| 方法名 | 作用 | -|-----------------------|----------------------------------------------------------------------| -| `FormatToString()` | 将消息格式化为字符串表示形式。 | -| `TimestampNs()` | 返回消息的时间戳(以纳秒为单位)。 | -| `ByteSize()` | 返回消息的字节大小。 | -| `IsReq()` | 判断消息是否为请求。 | -| `Seq()` | 返回消息的字节流序列号, 可以从`streamBuffer.Head().LeftBoundary()`获取。 | +HTTP 的例子: -HTTP的例子: ```go type ParsedHttpRequest struct { FrameBase @@ -106,7 +106,9 @@ type ParsedHttpRequest struct { } ``` -> Note. 在 `protocol.BinaryDecoder`中 `BinaryDecoder` 类提供了一些方便的实用程序函数,用于从缓冲区中提取字符、字符串或整数。在下面的实现 ParseFrame、FindFrameBoundary 和 Full Body 解析时,我们应该使用这些函数。 +> Note. 在 `protocol.BinaryDecoder` 中 `BinaryDecoder` +> 类提供了一些方便的实用程序函数,用于从缓冲区中提取字符、字符串或整数。在下面的实现 ParseFrame、FindFrameBoundary 和 Full +> Body 解析时,我们应该使用这些函数。 ## Step.2-实现协议解析 @@ -120,49 +122,56 @@ type ProtocolStreamParser interface { } ``` -- ParseStream: 解析Message -- FindBoundary: 寻找Message边界 -- Match: ReqResp匹配 +- ParseStream: 解析 Message +- FindBoundary: 寻找 Message 边界 +- Match: ReqResp 匹配 - Full Body Parse(可选): Full Body 解析 ### ParseStream (Buffer -> Message/ReqResp) -ParseStream 从网络数据解析出来 Message或者直接解析出来ReqResp。 +ParseStream 从网络数据解析出来 Message 或者直接解析出来 ReqResp。 ```go ParseStream(streamBuffer *buffer.StreamBuffer, messageType MessageType) ParseResult ``` -注意 eBPF 数据事件可能会无序到达,或者丢失事件,因此数据缓冲区中的数据可能缺少数据块,参数 streamBuffer 中通过 `streamBuffer.Head` 函数获取到目前为止已接收到的缓冲区前面的所有连续数据。因此,此时**无法保证数据有效或缓冲区与数据包的开头对齐**。 +注意 eBPF 数据事件可能会无序到达,或者丢失事件,因此数据缓冲区中的数据可能缺少数据块,参数 streamBuffer 中通过 +`streamBuffer.Head` +函数获取到目前为止已接收到的缓冲区前面的所有连续数据。因此,此时 **无法保证数据有效或缓冲区与数据包的开头对齐**。 -如果返回 `ParseResult` 中的 `state` 为 `success`,且那么kyanos会自动删除掉`ParseResult.ReadBytes`个字节的数据;如果返回`invalid`,那么通过 `FindBoundary` 找到下一个可能的`Message`边界;如果返回 `needsMoreData`,则不会删除数据,而是稍后重试。 +如果返回 `ParseResult` 中的 `state` 为 +`success`,且那么 kyanos 会自动删除掉 `ParseResult.ReadBytes` 个字节的数据;如果返回 `invalid`,那么通过 +`FindBoundary` 找到下一个可能的 `Message` 边界;如果返回 +`needsMoreData`,则不会删除数据,而是稍后重试。 +### FindBoundary 寻找 Message 边界 -### FindBoundary 寻找Message边界 - -FindBoundary 寻找Message边界,然后从解析失败状态中恢复。 +FindBoundary 寻找 Message 边界,然后从解析失败状态中恢复。 ```go FindBoundary(streamBuffer *buffer.StreamBuffer, messageType MessageType, startPos int) int ``` -conntrack.go 在一个循环中调用ParseStream和FindBoundary,如果ParseStream返回invalid,说明解析遇到了问题,接着会调用FindBoundary找到下一个位置,这个位置一般是Message的开头。如果请求带有一个tag比如request id,那么响应带有相同request id的可能性非常高。 +conntrack.go 在一个循环中调用 ParseStream 和 FindBoundary,如果 ParseStream 返回 invalid,说明解析遇到了问题,接着会调用 FindBoundary 找到下一个位置,这个位置一般是 Message 的开头。如果请求带有一个 tag 比如 request +id,那么响应带有相同 request id 的可能性非常高。 ### Match (Messages -> reqresp -> record) -ParseStream解析成功的Message会放到对应的ReqQueue和RespQueue中,然后再它们匹配在一起创建Record,主要有两种匹配方法:基于顺序 和 基于标签。 +ParseStream 解析成功的 Message 会放到对应的 ReqQueue 和 RespQueue 中,然后再它们匹配在一起创建 Record,主要有两种匹配方法:基于顺序 和 基于标签。 HTTP 等协议使用顺序匹配,而 Kafka 等协议使用基于标签的匹配。 -Note: 如果是使用顺序匹配,可以直接使用`matchByTimestamp`。 +Note: 如果是使用顺序匹配,可以直接使用 `matchByTimestamp`。 ### Full Body Parsing 解析整个消息 -目前,Full Body Parsing 是Match的一部分。对于大多数协议,我们如果需要解析整个消息体,只有在请求响应匹配之后才可以,比如Kafka需要知道请求的opcode,然后才可以根据opcode解析响应。 +目前,Full Body +Parsing 是 Match 的一部分。对于大多数协议,我们如果需要解析整个消息体,只有在请求响应匹配之后才可以,比如 Kafka 需要知道请求的 opcode,然后才可以根据 opcode 解析响应。 ## Step.3-实现协议推断 -在将内核数据抓取到用户态解析之前,我们需要识别出这个流量是什么协议的流量,当连接开启上面有数据传输时,kyanos会基于一些规则判断该流量术语哪种协议,每个协议有自己的规则,HTTP协议如下: +在将内核数据抓取到用户态解析之前,我们需要识别出这个流量是什么协议的流量,当连接开启上面有数据传输时,kyanos 会基于一些规则判断该流量术语哪种协议,每个协议有自己的规则,HTTP 协议如下: + ```c static __always_inline enum message_type_t is_http_protocol(const char *old_buf, size_t count) { if (count < 5) { @@ -193,15 +202,15 @@ Warn: 出于这些原因,你需要注意以下几个事情: -1. 避免在协议中使用过于通用和常见的模式作为推理规则。例如,根据单独的 `0x00` 或 `0x01` 的字节判断就不够严格。 +1. 避免在协议中使用过于通用和常见的模式作为推理规则。例如,根据单独的 `0x00` 或 + `0x01` 的字节判断就不够严格。 2. 将更严格、更健壮的规则(例如 HTTP)放在前面。 - ## Step.4-添加命令行子命令并且实现过滤逻辑 需要添加到 watch 和 stat 命令下,增加需要的协议特定的过滤选项。 -然后实现`protocol.ProtocolFilter`: +然后实现 `protocol.ProtocolFilter`: ```go type ProtocolFilter interface { @@ -212,17 +221,17 @@ type ProtocolFilter interface { } ``` -| 方法名 | 作用 | -|-----------------------|----------------------------------------------------------------------| -| `Filter` | 过滤请求和响应。 | -| `FilterByProtocol` | 是否根据协议类型进行过滤。 | -| `FilterByRequest` | 是否根据请求进行过滤。 | -| `FilterByResponse` | 是否根据响应进行过滤。 | - +| 方法名 | 作用 | +| ------------------ | -------------------------- | +| `Filter` | 过滤请求和响应。 | +| `FilterByProtocol` | 是否根据协议类型进行过滤。 | +| `FilterByRequest` | 是否根据请求进行过滤。 | +| `FilterByResponse` | 是否根据响应进行过滤。 | ## Step.5-注册协议解析器 -在你写的模块下增加init函数,将其写入到 `ParsersMap` 里,例如: +在你写的模块下增加 init 函数,将其写入到 `ParsersMap` 里,例如: + ```go func init() { ParsersMap[bpf.AgentTrafficProtocolTKProtocolHTTP] = func() ProtocolStreamParser { @@ -231,22 +240,24 @@ func init() { } ``` -## Step.6-添加e2e测试 +## Step.6-添加 e2e 测试 -在testdata目录下添加对应协议的e2e测试,可以参考其他协议的写法(比如`test_redis.sh`等)。 +在 testdata 目录下添加对应协议的 e2e 测试,可以参考其他协议的写法(比如 `test_redis.sh` 等)。 ## 其他 ### 调试建议 -打印协议解析日志建议使用 `common.ProtocolParserLog`. 打开 protocol 解析日志:`--protocol-log-level 5` 设置协议解析相关log日志为debug级别。 +打印协议解析日志建议使用 +`common.ProtocolParserLog`. 打开 protocol 解析日志:`--protocol-log-level 5` +设置协议解析相关 log 日志为 debug 级别。 协议解析框架代码在 conntrack.go 的 `addDataToBufferAndTryParse` 函数里。 ### 协议解析持久化信息 -在某些协议中,如果需要在解析过程中保留一些数据(比如kafka中,它存储了请求缓冲区上看到的所有 correlation_id 的集合,而 FindBoundary 只返回respStreamBuffer上之前看到correlation_id的位置。)可以在协议的Parser里自定义一些变量保存(即Parser可以是有状态的),**kyanos会为每个连接开启时创建独立的Parser并保持到连接关闭**。 +在某些协议中,如果需要在解析过程中保留一些数据(比如 kafka 中,它存储了请求缓冲区上看到的所有 correlation_id 的集合,而 FindBoundary 只返回 respStreamBuffer 上之前看到 correlation_id 的位置。)可以在协议的 Parser 里自定义一些变量保存(即 Parser 可以是有状态的),**kyanos 会为每个连接开启时创建独立的 Parser 并保持到连接关闭**。 ## 总结 -恭喜你成功向 Kyanos 添加新协议!由于你的贡献,新的协议解析器将使许多其他人受益! \ No newline at end of file +恭喜你成功向 Kyanos 添加新协议!由于你的贡献,新的协议解析器将使许多其他人受益! diff --git a/docs/cn/how-to-build.md b/docs/cn/how-to-build.md index a81f15a1..7b5effe6 100644 --- a/docs/cn/how-to-build.md +++ b/docs/cn/how-to-build.md @@ -5,7 +5,8 @@ prev: false # 编译步骤 -本文介绍 kyanos 的本地编译方法,我的环境是ubuntu 22.04,其他环境可能会有所不同。 +本文介绍 kyanos 的本地编译方法,我的环境是 ubuntu +22.04,其他环境可能会有所不同。 ## 工具版本要求 @@ -14,18 +15,26 @@ prev: false - llvm 10.0 以上 ## 编译环境依赖安装 + ### Ubuntu -如果你使用的是 ubuntu 20.04 以及更新版本,可以使用一条命令即可完成编译环境的初始化。 + +如果你使用的是 ubuntu +20.04 以及更新版本,可以使用一条命令即可完成编译环境的初始化。 + ``` /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/hengyoush/kyanos/refs/heads/main/init_env.sh)" ``` + ### 其他 Linux 发行版 + 用下面的命令 clone 项目: + ```bash git clone https://github.com/hengyoush/kyanos cd kyanos git submodule update --init --recursive ``` + 编译环境除了上面工具链版本列出的软件外,还需要以下软件,请自行安装。 - linux-tools-common @@ -36,6 +45,7 @@ git submodule update --init --recursive ## 编译命令 如果只是本地开发测试,可以执行 + ``` make build-bpf && make ``` @@ -43,25 +53,37 @@ make build-bpf && make 之后在项目根目录下会生成 kyanos 可执行文件。 > [!IMPORTANT] -> 但是需要注意的是该二进制文件中没有包含 [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/) 中的 btf 文件,如果直接拿这个 kyanos 去没有开启 BTF 支持的低版本内核上执行可能会启动失败,通过下面的命令可以构建出一个内嵌 btf 文件的 kyanos 产物: +> +> 但是需要注意的是该二进制文件中没有包含 +> [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/) +> 中的 btf 文件,如果直接拿这个 kyanos 去没有开启 BTF 支持的低版本内核上执行可能会启动失败,通过下面的命令可以构建出一个内嵌 btf 文件的 kyanos 产物: +> > ::: code-group ->```bash [x86_64] ->make build-bpf && make btfgen BUILD_ARCH=x86_64 ARCH_BPF_NAME=x86 && make ->``` > ->```bash [arm64] ->make build-bpf && make btfgen BUILD_ARCH=arm64 ARCH_BPF_NAME=arm64 && make ->``` ->::: +> ```bash [x86_64] +> make build-bpf && make btfgen BUILD_ARCH=x86_64 ARCH_BPF_NAME=x86 && make +> ``` +> +> ```bash [arm64] +> make build-bpf && make btfgen BUILD_ARCH=arm64 ARCH_BPF_NAME=arm64 && make +> ``` +> +> ::: +> > 需要注意 `make btfgen` 耗时可能超过 15min。 - > [!TIP] ->如果你的内核没有开启BTF,你可能无法成功启动 kyanos. > ->检查是否开启BTF: ->``` ->grep CONFIG_DEBUG_INFO_BTF "/boot/config-$(uname -r)" ->``` ->如果结果是`CONFIG_DEBUG_INFO_BTF=y`说明开启了,如果没开启请到 [mirrors.openanolis.cn](https://mirrors.openanolis.cn/coolbpf/btf/) or [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/)上下载对应你的内核版本的 btf 文件,然后启动 kyanos 时使用 `--btf` 选项指定下载的 btf 文件。 - +> 如果你的内核没有开启 BTF,你可能无法成功启动 kyanos. +> +> 检查是否开启 BTF: +> +> ``` +> grep CONFIG_DEBUG_INFO_BTF "/boot/config-$(uname -r)" +> ``` +> +> 如果结果是 `CONFIG_DEBUG_INFO_BTF=y` 说明开启了,如果没开启请到 +> [mirrors.openanolis.cn](https://mirrors.openanolis.cn/coolbpf/btf/) or +> [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/) +> 上下载对应你的内核版本的 btf 文件,然后启动 kyanos 时使用 `--btf` +> 选项指定下载的 btf 文件。 diff --git a/docs/cn/how-to.md b/docs/cn/how-to.md index e9e9a0db..fa452da2 100644 --- a/docs/cn/how-to.md +++ b/docs/cn/how-to.md @@ -1,13 +1,14 @@ --- next: - text: 'Watch 使用方法' - link: './watch' + text: "Watch 使用方法" + link: "./watch" prev: false --- # 5 分钟学会使用 kyanos kyanos 共有三个子命令:watch、stat、overview。每个命令的作用如下: + 1. watch:根据你指定的条件抓取网络流量并自动解析为请求响应记录。 2. stat:根据你指定的条件抓取请求响应记录并且聚合这些记录,得到更高维度的统计信息。 3. overview:一键展示当前机器依赖的外部资源。 @@ -19,47 +20,56 @@ kyanos 共有三个子命令:watch、stat、overview。每个命令的作用 ```bash ./kyanos watch ``` -每个请求响应记录会记录在表格中的一行,每列记录这个请求的基本信息。你可以通过方向键或者j/k上下移动来选择记录: -![kyanos watch result](/watch-result.jpg) -按下`enter`进入详情界面: +每个请求响应记录会记录在表格中的一行,每列记录这个请求的基本信息。你可以通过方向键或者 j/k 上下移动来选择记录: +![kyanos watch result](/watch-result.jpg) -![kyanos watch result detail](/watch-result-detail.jpg) +按下 `enter` 进入详情界面: -详情界面里第一部分是 **耗时详情**,每一个方块代表数据包经过的节点,比如这里有进程、网卡、Socket缓冲区等。 -每个方块下面有一个耗时,这里的耗时指从上个节点到这个节点经过的时间。 -可以清楚的看到请求从进程发送到网卡,响应再从网卡复制到Socket缓冲区并且被进程读取的流程和每一个步骤的耗时。 +![kyanos watch result detail](/watch-result-detail.jpg) -第二部分是 **请求响应的具体内容**,分为 Request 和 Response 两部分,超过 1024 字节会截断展示。 +详情界面里第一部分是 +**耗时详情**,每一个方块代表数据包经过的节点,比如这里有进程、网卡、Socket 缓冲区等。 +每个方块下面有一个耗时,这里的耗时指从上个节点到这个节点经过的时间。可以清楚的看到请求从进程发送到网卡,响应再从网卡复制到 Socket 缓冲区并且被进程读取的流程和每一个步骤的耗时。 +第二部分是 +**请求响应的具体内容**,分为 Request 和 Response 两部分,超过 1024 字节会截断展示。 -抓取流量时一般会更有针对性,比如抓取HTTP流量: +抓取流量时一般会更有针对性,比如抓取 HTTP 流量: ```bash ./kyanos watch http ``` -更进一步,你可能只想抓取某个HTTP Path的流量: + +更进一步,你可能只想抓取某个 HTTP Path 的流量: ```bash -./kyanos watch http --path /abc +./kyanos watch http --path /abc ``` -每种协议都具有不同的过滤条件,而且watch还可以根据很多其他条件过滤,详情参考:[如何抓取请求响应和耗时细节](./watch) + +每种协议都具有不同的过滤条件,而且 watch 还可以根据很多其他条件过滤,详情参考:[如何抓取请求响应和耗时细节](./watch) ## `stat` 聚合分析简单用法 -在真实场景中,watch 输出的结果过于细粒度,因此 kyanos 提供了 stat 命令用于 **统计分析**。 +在真实场景中,watch 输出的结果过于细粒度,因此 kyanos 提供了 stat 命令用于 +**统计分析**。 简单来说 stat 可以用来回答这些问题:哪些连接的请求数最多?哪些远程服务端上的平均耗时最高?哪些客户端发送的请求带宽占比最高? -现在让我们来尝试找到哪些远程服务端上的平均耗时最高,我们只需指定 `--slow` 选项代表我们关心的指标是耗时,和 watch 一样,stat 也可以使用所有的过滤条件,这里我们只收集 PATH=/abc 的 HTTP 请求 : +现在让我们来尝试找到哪些远程服务端上的平均耗时最高,我们只需指定 `--slow` +选项代表我们关心的指标是耗时,和 watch 一样,stat 也可以使用所有的过滤条件,这里我们只收集 PATH +=/abc 的 HTTP 请求 : + ```bash ./kyanos stat http --slow --path /abc ``` -kyanos 默认会收集 10s(可以通过 `--time` 参数指定收集时间,当然,你也可以按下 `ctrl+c` 提前结束收集): -![kyanos stat slow result](/qs-stat-slow.jpg) + +kyanos 默认会收集 10s(可以通过 `--time` 参数指定收集时间,当然,你也可以按下 +`ctrl+c` 提前结束收集): ![kyanos stat slow result](/qs-stat-slow.jpg) 10s 结束后收集结果展示在表格里: + ```js{6-8} - Colleted events are here! + Colleted events are here! ┌──────────────────────────────────────────────────────────────────────────────────────────────┐ │ id remote-ip max(ms) avg(ms) p50(ms) p90(ms) p99(ms) count │// [!code focus] @@ -75,36 +85,43 @@ kyanos 默认会收集 10s(可以通过 `--time` 参数指定收集时间, 1 sort by name • 2 sort by max • 3 sort by avg • 4 sort by p50 • 5 sort by p90 • 6 sort by p99 • 7 sort by count • 8 sort by total ``` + watch 展示的每一行是一次请求响应,而 stat 会按照某个维度聚合请求响应。 -在这个例子中没有指定,默认采用 **请求的服务端地址(remote-ip)** 作为聚合维度(第二列就是聚合维度列),相同远程服务 IP 的请求响应结果会聚合起来(当然不止这一种聚合方式,了解更多请参考:[流量分析](./stat))。 +在这个例子中没有指定,默认采用 **请求的服务端地址(remote-ip)** +作为聚合维度(第二列就是聚合维度列),相同远程服务 IP 的请求响应结果会聚合起来(当然不止这一种聚合方式,了解更多请参考:[流量分析](./stat))。 -后面的 `max` 列就是这些相同 remote-ip 的请求响应中的最大耗时,`avg` 就是平均耗时等等。所以如果某个远程服务端出现了异常,你可以很快的通过比对不同 remote-ip 的指标发现异常的服务端是 169.254.0.4。 +后面的 `max` 列就是这些相同 remote-ip 的请求响应中的最大耗时,`avg` +就是平均耗时等等。所以如果某个远程服务端出现了异常,你可以很快的通过比对不同 remote-ip 的指标发现异常的服务端是 169.254.0.4。 + +如果想查看这个 remote-ip 上的请求响应的详细信息,可以移动到这行记录上按下 +`enter`,进入这个 remote-ip 上的请求响应列表中: -如果想查看这个 remote-ip 上的请求响应的详细信息,可以移动到这行记录上按下 `enter`,进入这个 remote-ip 上的请求响应列表中: ```js Events Num: 3 ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ id Process Connection Proto TotalTime↓ ReqSize RespSize Net/Internal ReadSocketTime │// [!code focus] +│ id Process Connection Proto TotalTime↓ ReqSize RespSize Net/Internal ReadSocketTime │// [! code focus] │───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│ -│ 1 1315398 10.0.4.9:38458 => 169.254.0.4:80 HTTP 108.59 564 216 107.18 1.36 │// [!code focus] -│ 2 1315398 10.0.4.9:38482 => 169.254.0.4:80 HTTP 45.89 676 216 43.83 2.00 │// [!code focus] +│ 1 1315398 10.0.4.9:38458 => 169.254.0.4:80 HTTP 108.59 564 216 107.18 1.36 │// [! code focus] +│ 2 1315398 10.0.4.9:38482 => 169.254.0.4:80 HTTP 45.89 676 216 43.83 2.00 │// [! code focus] │ 3 1315398 10.0.4.9:38470 => 169.254.0.4:80 HTTP 26.60 588 216 25.21 1.30 │ │ │ └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ↑/k up • ↓/j down ``` -这里实际和 watch 命令展示的结果是相同的形式,每一行代表请求响应记录,之后你可以通过 `enter` 继续选择你感兴趣的一行记录,查看它的耗时细节和具体内容。 +这里实际和 watch 命令展示的结果是相同的形式,每一行代表请求响应记录,之后你可以通过 +`enter` 继续选择你感兴趣的一行记录,查看它的耗时细节和具体内容。 > [!TIP] -> stat的功能十分强大,推荐你阅读:[如何聚合分析](./stat) 查看 stat 命令的其它用法。 - +> +> stat 的功能十分强大,推荐你阅读:[如何聚合分析](./stat) +> 查看 stat 命令的其它用法。 ## 下一步 + 了解每个命令的详细使用方法: + - watch 命令请查看:[如何抓取请求响应和耗时细节](./watch) - stat 命令请查看:[如何聚合分析](./stat) - - diff --git a/docs/cn/index.md b/docs/cn/index.md index 36a2ecf8..480d640a 100644 --- a/docs/cn/index.md +++ b/docs/cn/index.md @@ -21,35 +21,36 @@ hero: link: https://github.com/hengyoush/kyanos features: - - icon: 🚀 - title: 使用简单 - details: 聚焦于7层协议,只需一条命令即可快速查看应用的网络性能,包括MySQL、Redis等常用服务的网络延迟和数据传输大小 - link: ./how-to - linkText: Learn how to use kyanos - - icon: 🎯️ - title: 高级数据过滤 - details: 支持根据协议字段(如HTTP的Path或Redis的Command)过滤数据,以及根据进程PID、容器ID、K8s Pod名称等多维度筛选数据,提供更精确的问题定位。 - link: ./watch#how-to-filter - linkText: Learn how to filter traffic - - icon: 📈️ - title: 强大的聚合分析 - details: 支持根据远程IP、协议等维度自动聚合数据,快速获取特定信息,如特定IP的HTTP路径耗时情况。 - link: ./stat - linkText: Learn how to analysis traffic - - icon: 💻️ - title: 容器网络监控 - details: 在容器化环境中,能够统计数据包从容器网卡到宿主机网卡的耗时。 - link: ./watch#filter-by-container - linkText: Capture container traffic - - icon: 📊️ - title: 直观的用户界面 - details: 基于命令行就地分析,提供可视化的输出,无需复杂的文件下载和分析步骤。 - link: ./how-to - linkText: Learn how to use kyanos - - icon: 🌐️ - title: 轻量级与兼容性 - details: Kyanos作为一个网络问题排查工具,不依赖于任何外部依赖,能够在从3.10版本到最新版本的内核上运行 - link: ./quickstart#prerequire - linkText: Install kyanos + - icon: 🚀 + title: 使用简单 + details: 聚焦于7层协议,只需一条命令即可快速查看应用的网络性能,包括MySQL、Redis等常用服务的网络延迟和数据传输大小 + link: ./how-to + linkText: Learn how to use kyanos + - icon: 🎯️ + title: 高级数据过滤 + details: + 支持根据协议字段(如HTTP的Path或Redis的Command)过滤数据,以及根据进程PID、容器ID、K8s + Pod名称等多维度筛选数据,提供更精确的问题定位。 + link: ./watch#how-to-filter + linkText: Learn how to filter traffic + - icon: 📈️ + title: 强大的聚合分析 + details: 支持根据远程IP、协议等维度自动聚合数据,快速获取特定信息,如特定IP的HTTP路径耗时情况。 + link: ./stat + linkText: Learn how to analysis traffic + - icon: 💻️ + title: 容器网络监控 + details: 在容器化环境中,能够统计数据包从容器网卡到宿主机网卡的耗时。 + link: ./watch#filter-by-container + linkText: Capture container traffic + - icon: 📊️ + title: 直观的用户界面 + details: 基于命令行就地分析,提供可视化的输出,无需复杂的文件下载和分析步骤。 + link: ./how-to + linkText: Learn how to use kyanos + - icon: 🌐️ + title: 轻量级与兼容性 + details: Kyanos作为一个网络问题排查工具,不依赖于任何外部依赖,能够在从3.10版本到最新版本的内核上运行 + link: ./quickstart#prerequire + linkText: Install kyanos --- - diff --git a/docs/cn/quickstart.md b/docs/cn/quickstart.md index 0943d194..0dd6f0d3 100644 --- a/docs/cn/quickstart.md +++ b/docs/cn/quickstart.md @@ -1,47 +1,53 @@ --- prev: - text: 'Kyanos 是什么' - link: './what-is-kyanos' + text: "Kyanos 是什么" + link: "./what-is-kyanos" next: false --- - # 快速开始 + ## 安装要求 **内核版本要求** + - 3.x: 3.10.0-957 版本及以上内核 -- 4.x: 4.14版本以上内核 +- 4.x: 4.14 版本以上内核 - 5.x, 6.x: 全部支持 **处理器架构支持** + - amd64 - arm64 ## 安装并运行 {#prerequire} -你可以从 [release page](https://github.com/hengyoush/kyanos/releases) 中下载以静态链接方式编译的适用于 amd64 和 arm64 架构的二进制文件: +你可以从 [release page](https://github.com/hengyoush/kyanos/releases) +中下载以静态链接方式编译的适用于 amd64 和 arm64 架构的二进制文件: ```bash tar xvf kyanos_vx.x.x_linux_amd64.tar.gz ``` 然后以 **root** 权限执行如下命令: + ```bash -sudo ./kyanos watch +sudo ./kyanos watch ``` - 如果显示了下面的表格: -![kyanos quick start success](/quickstart-success.png) -🎉 恭喜你,kyanos启动成功了。 +如果显示了下面的表格: ![kyanos quick start success](/quickstart-success.png) +🎉 恭喜你,kyanos 启动成功了。 > [!TIP] -> 如果上面的命令执行失败了?没关系,在这个 [FAQ](./faq) 里看看有没有符合你的情况,如果没有欢迎提出 [github issue](https://github.com/hengyoush/kyanos/issues) ! +> +> 如果上面的命令执行失败了?没关系,在这个 [FAQ](./faq) +> 里看看有没有符合你的情况,如果没有欢迎提出 +> [github issue](https://github.com/hengyoush/kyanos/issues) ! ## 常见问题 + 请查看:[常见问题](./faq) ## 下一步 -- 快速了解 kyanos 的使用方法,请查看:[5分钟学会使用kyanos](./how-to) - +- 快速了解 kyanos 的使用方法,请查看:[5 分钟学会使用 kyanos](./how-to) diff --git a/docs/cn/stat.md b/docs/cn/stat.md index c7809f00..7d966835 100644 --- a/docs/cn/stat.md +++ b/docs/cn/stat.md @@ -1,8 +1,8 @@ --- next: false prev: - text: 'Watch 使用方法' - link: './watch' + text: "Watch 使用方法" + link: "./watch" --- # 使用 stat 统计分析异常流量 @@ -14,103 +14,117 @@ watch 命令提供了细粒度的观察视角,在分析单个请求响应维 stat 命令用于解决这种需要分析大量请求响应才能得出结论的问题。 - ## 如何使用 stat 命令 -使用 stat 命令非常简单,你只需要确定**你关心的指标是什么?** +使用 stat 命令非常简单,你只需要确定 **你关心的指标是什么?** -以上述的这个问题为例:"我的 HTTP 请求慢了,是所有服务端都慢了,还是某一个服务端慢了?",在这里我们关心的是**服务端的响应时间**这个指标。 +以上述的这个问题为例:"我的 HTTP 请求慢了,是所有服务端都慢了,还是某一个服务端慢了?",在这里我们关心的是 +**服务端的响应时间** 这个指标。 所以可以输入如下的命令: + ```bash ./kyanos stat --metric total-time --group-by remote-ip ``` -指定 `metric` 选项为 `total-time` 代表我们需要统计的指标是请求响应的总耗时,指定 `group-by` 选项为 `remote-ip` ,代表我们需要观察的响应时间是每个 `remote-ip` 的响应时间,kyanos 会将所有相同 `remote-ip` 的请求响应聚合,最终得出每个 `remote-ip` 的总耗时的相关指标。 + +指定 `metric` 选项为 `total-time` 代表我们需要统计的指标是请求响应的总耗时,指定 +`group-by` 选项为 `remote-ip` ,代表我们需要观察的响应时间是每个 `remote-ip` +的响应时间,kyanos 会将所有相同 `remote-ip` 的请求响应聚合,最终得出每个 +`remote-ip` 的总耗时的相关指标。 一个更简短的命令形式: + ```bash ./kyanos stat -m t -g remote-ip ``` -m 是 metric的缩写,t 是 total-time 的缩写,g 是 group-by 的缩写。 +m 是 metric 的缩写,t 是 total-time 的缩写,g 是 group-by 的缩写。 > [!TIP] +> > **如何过滤流量?** -> stat支持所有watch命令的过滤选项。 +> stat 支持所有 watch 命令的过滤选项。 ## 如何分析 stat 命令的结果 -如下是输入上述 stat 命令后你会看到的表格: -![kyanos stat result](/stat-result.jpg) - -就像 watch 表格的操作方式一样:你可以通过按下数字键对对应的列排序,也可以按`"↑"` `"↓"` 或者 `"k"` `"j"` 可以上下移动选择表格中的记录。 -但和 watch 表格不同的是表格里的记录,stat 命令是将所有请求响应按照 `--group-by` 选项聚合的,所以第二列的名称是 `remote-ip`,其后各列:`max`、`avg`、`p50`等列表示 `--metric` 选项所指定指标(在我们这个例子中指 `total-time` )的最大值、平均值和 P50 等值。 +如下是输入上述 stat 命令后你会看到的表格: +![kyanos stat result](/stat-result.jpg) -按下 `enter` 即可进入这个 `remote-ip` 下具体的请求响应,这里其实就是 watch 命令的结果,操作方式和 watch 完全相同,你可以选择具体的请求响应,然后查看其耗时和请求响应内容,这里不再赘述。 +就像 watch 表格的操作方式一样:你可以通过按下数字键对对应的列排序,也可以按 +`"↑"` `"↓"` 或者 `"k"` `"j"` 可以上下移动选择表格中的记录。 +但和 watch 表格不同的是表格里的记录,stat 命令是将所有请求响应按照 `--group-by` +选项聚合的,所以第二列的名称是 `remote-ip`,其后各列:`max`、`avg`、`p50` +等列表示 `--metric` 选项所指定指标(在我们这个例子中指 `total-time` +)的最大值、平均值和 P50 等值。 +按下 `enter` 即可进入这个 `remote-ip` +下具体的请求响应,这里其实就是 watch 命令的结果,操作方式和 watch 完全相同,你可以选择具体的请求响应,然后查看其耗时和请求响应内容,这里不再赘述。 ## 目前支持的指标 -kyanos目前支持通过 `--metric` 指定的指标如下: -| 观测指标 | short flag |long flag | -| :-------------- | :--- |:--- | -| 总耗时 | t |total-time | -| 响应数据大小 | p | respsize | -| 请求数据大小 | q | reqsize | -| 在网络中的耗时 | n | network-time | -| 在服务进程中的耗时 | i | internal-time | -| 从Socket缓冲区读取的耗时 | s |socket-time | +kyanos 目前支持通过 `--metric` 指定的指标如下: + +| 观测指标 | short flag | long flag | +| :------------------------- | :--------- | :------------ | +| 总耗时 | t | total-time | +| 响应数据大小 | p | respsize | +| 请求数据大小 | q | reqsize | +| 在网络中的耗时 | n | network-time | +| 在服务进程中的耗时 | i | internal-time | +| 从 Socket 缓冲区读取的耗时 | s | socket-time | ## 目前支持的聚合方式 -kyanos目前支持通过 `--group-by` 指定的指标如下: -| 聚合维度 | 值 | -| :-------------- | :--- | -| 聚合到单个连接 | conn | -| 远程ip | remote-ip | -| 远程端口 | remote-port | -| 本地端口 | local-port | -| L7协议 | protocol | -| HTTP PATH | http-path | -| Redis命令 | redis-command | -| 聚合所有的请求响应 | none | +kyanos 目前支持通过 `--group-by` 指定的指标如下: +| 聚合维度 | 值 | +| :----------------- | :------------ | +| 聚合到单个连接 | conn | +| 远程 ip | remote-ip | +| 远程端口 | remote-port | +| 本地端口 | local-port | +| L7 协议 | protocol | +| HTTP PATH | http-path | +| Redis 命令 | redis-command | +| 聚合所有的请求响应 | none | ## 这些选项记不住怎么办? + 如果你记不得这些选项,stat 同样提供了三个选项用于快速分析: - slow:分析慢请求。 - bigreq:分析大请求。 - bigresp:分析大响应。 -同时可以通过`--time`选项指定收集时间,如`--time 10`,stat 命令会收集流量10s: +同时可以通过 `--time` 选项指定收集时间,如 +`--time 10`,stat 命令会收集流量 10s: -![kyanos stat fast](/qs-stat-slow.jpg) +![kyanos stat fast](/qs-stat-slow.jpg) -等收集结束或者按下ctrl+c提前结束,会出现如下的表格: +等收集结束或者按下 ctrl+c 提前结束,会出现如下的表格: -![kyanos stat quich result](/stat-quick-result.jpg) +![kyanos stat quich result](/stat-quick-result.jpg) 之后的操作方式就相同了。 - - ### 分析慢请求 -抓取10s内的流量,快速找到哪一个远程ip的HTTP请求最慢: + +抓取 10s 内的流量,快速找到哪一个远程 ip 的 HTTP 请求最慢: ```bash ./kyanos stat http --slow --time 10 ``` ### 分析大请求和大响应 -快速找到哪一个远程ip的请求最大: + +快速找到哪一个远程 ip 的请求最大: ```bash ./kyanos stat http --bigreq ``` -快速找到哪一个远程ip的响应最大: +快速找到哪一个远程 ip 的响应最大: ```bash ./kyanos stat http --bigresp diff --git a/docs/cn/watch.md b/docs/cn/watch.md index 0057472f..ed460287 100644 --- a/docs/cn/watch.md +++ b/docs/cn/watch.md @@ -1,15 +1,15 @@ --- prev: - text: '5分钟学会使用kyanos' - link: './how-to' + text: "5分钟学会使用kyanos" + link: "./how-to" next: - text: 'Stat 使用方法' - link: './stat' + text: "Stat 使用方法" + link: "./stat" --- # 抓取请求响应和耗时细节 -你可以使用 watch 命令收集你感兴趣的请求响应流量,具体来说,你能够通过watch命令: +你可以使用 watch 命令收集你感兴趣的请求响应流量,具体来说,你能够通过 watch 命令: - 查看请求响应的具体内容。 - 查看耗时细节:包括请求到达网卡、响应到达网卡、响应到达 Socket 缓冲区、应用进程读取响应这几个重要的时间点。 @@ -19,77 +19,80 @@ next: ```bash kyanos watch ``` + 由于没有指定任何过滤条件,因此 kyanos 会尝试采集所有它能够解析的流量,当前 kyanos 支持三种应用层协议的解析:HTTP、Redis 和 MySQL。 当你执行这行命令之后,你会看到一个表格: -![kyanos watch result](/watch-result.jpg) - +![kyanos watch result](/watch-result.jpg) > [!TIP] -> watch 默认采集100条请求响应记录,你可以通过`--max-records`选项来指定 +> +> watch 默认采集 100 条请求响应记录,你可以通过 `--max-records` 选项来指定 +| 每一列的含义如下: | 列名称 | 含义 | 示例 | | :------------- | +| :--------------------------------- | ---------------------------------- | ---- | --------- | ------------------------------ | -------------------- | ---------- | --- | +| :--------------------------------- | | id | 表示序号 | | | Connection | +| 表示这次请求响应的连接 | "10.0.4.9:44526 => 169.254.0.4:80" | | Proto | +| 请求响应的协议 | "HTTP" | | TotalTime | 这次请求响应的总耗时,单位毫秒 | | | +| ReqSize | 请求大小,单位 bytes | | | RespSize | 响应大小,单位 bytes | | | -每一列的含义如下: -| 列名称 | 含义 | 示例 | -| :------------- | :------------------------------------------------------------------------ | :--------------------------------- | -| id | 表示序号 | | -| Connection | 表示这次请求响应的连接 | "10.0.4.9:44526 => 169.254.0.4:80" | -| Proto | 请求响应的协议 | "HTTP" | -| TotalTime | 这次请求响应的总耗时,单位毫秒 | | -| ReqSize | 请求大小,单位bytes | | -| RespSize | 响应大小,单位bytes | | -| Net/Internal | 如果这是本地发起的请求,含义为网络耗时; 如果是作为服务端接收外部请求,含义为本地进程处理的内部耗时 | | -| ReadSocketTime | 如果这是本地发起的请求,含义为从内核Socket缓冲区读取响应的耗时; 如果是作为服务端接收外部请求,含义从内核Socket缓冲区读取请求的耗时。 | | +Net/Internal +| 如果这是本地发起的请求,含义为网络耗时; 如果是作为服务端接收外部请求,含义为本地进程处理的内部耗时 | +| | ReadSocketTime +| 如果这是本地发起的请求,含义为从内核 Socket 缓冲区读取响应的耗时; 如果是作为服务端接收外部请求,含义从内核 Socket 缓冲区读取请求的耗时。 | +| +按下数字键可以排序对应的列。按 `"↑"` `"↓"` 或者 `"k"` `"j"` +可以上下移动选择表格中的记录。按下 enter 进入这次请求响应的详细界面: -按下数字键可以排序对应的列。按`"↑"` `"↓"` 或者 `"k"` `"j"` 可以上下移动选择表格中的记录。按下enter进入这次请求响应的详细界面: +![kyanos watch result detail](/watch-result-detail.jpg) -![kyanos watch result detail](/watch-result-detail.jpg) +详情界面里第一部分是 +**耗时详情**,每一个方块代表数据包经过的节点,比如这里有进程、网卡、Socket 缓冲区等。 +每个方块下面有一个耗时,这里的耗时指从上个节点到这个节点经过的时间。可以清楚的看到请求从进程发送到网卡,响应再从网卡复制到 Socket 缓冲区并且被进程读取的流程和每一个步骤的耗时。 -详情界面里第一部分是 **耗时详情**,每一个方块代表数据包经过的节点,比如这里有进程、网卡、Socket缓冲区等。 -每个方块下面有一个耗时,这里的耗时指从上个节点到这个节点经过的时间。 -可以清楚的看到请求从进程发送到网卡,响应再从网卡复制到 Socket 缓冲区并且被进程读取的流程和每一个步骤的耗时。 - -第二部分是 **请求响应的具体内容**,分为 Request 和 Response 两部分,超过 1024 字节会截断展示(通过`--max-print-bytes`选项可以调整这个限制)。 +第二部分是 +**请求响应的具体内容**,分为 Request 和 Response 两部分,超过 1024 字节会截断展示(通过 +`--max-print-bytes` 选项可以调整这个限制)。 ## 如何发现你感兴趣的请求响应 {#how-to-filter} -默认 kyanos 会抓取所有它目前支持协议的请求响应,在很多场景下,我们需要更加精确的过滤,比如想要发送给某个远程端口的请求,抑或是某个进程或者容器的关联的请求,又或者是某个 Redis 命令或者HTTP 路径相关的请求。下面介绍如何使用 kyanos 的各种选项找到我们感兴趣的请求响应。 +默认 kyanos 会抓取所有它目前支持协议的请求响应,在很多场景下,我们需要更加精确的过滤,比如想要发送给某个远程端口的请求,抑或是某个进程或者容器的关联的请求,又或者是某个 Redis 命令或者 HTTP 路径相关的请求。下面介绍如何使用 kyanos 的各种选项找到我们感兴趣的请求响应。 -### 根据IP端口过滤 -kyanos 支持根据 IP 端口等三/四层信息过滤,可以指定以下选项: +### 根据 IP 端口过滤 -| 过滤条件 | 命令行flag | 示例 | -| :------ | :------------------- | :-------------------------------------------------------------------- | -| 连接的本地端口 | `local-ports` | `--local-ports 6379,16379`
只观察本地端口为6379和16379的连接上的请求响应 | -| 连接的远程端口 | `remote-ports` | `--remote-ports 6379,16379`
只观察远程端口为6379和16379的连接上的请求响应 | -| 连接的远程ip | `remote-ips` | `--remote-ips 10.0.4.5,10.0.4.2`
只观察远程ip为10.0.4.5和10.0.4.2的连接上的请求响应 | -| 客户端/服务端 | `side` | `--side client/server`
只观察作为客户端发起连接/作为服务端接收连接时的请求响应 | +kyanos 支持根据 IP 端口等三/四层信息过滤,可以指定以下选项: +| 过滤条件 | 命令行 flag | 示例 | +| :------------- | :------------- | :---------------------------------------------------------------------------------------------- | +| 连接的本地端口 | `local-ports` | `--local-ports 6379,16379`
只观察本地端口为 6379 和 16379 的连接上的请求响应 | +| 连接的远程端口 | `remote-ports` | `--remote-ports 6379,16379`
只观察远程端口为 6379 和 16379 的连接上的请求响应 | +| 连接的远程 ip | `remote-ips` | `--remote-ips 10.0.4.5,10.0.4.2`
只观察远程 ip 为 10.0.4.5 和 10.0.4.2 的连接上的请求响应 | +| 客户端/服务端 | `side` | `--side client/server`
只观察作为客户端发起连接/作为服务端接收连接时的请求响应 | ### 根据进程/容器过滤 {#filter-by-container} -| 过滤条件 | 命令行flag | 示例 | -| :------ | :------------- | :-------------------------------------------------------------------- | -| 进程pid列表 | `pids` | `--pids 12345,12346` 多个pid按逗号分隔 | -| 进程名称 | `comm` | `--comm 'redis-cli'` | -| 容器id | `container-id` | `--container-id xx` | -| 容器名称 | `container-name` | `--container-name foobar` | -| k8s pod名称 | `pod-name` | `--pod-name nginx-7bds23212-23s1s.default`
格式: NAME.NAMESPACE | +| 过滤条件 | 命令行 flag | 示例 | +| :------------ | :--------------- | :-------------------------------------------------------------------- | +| 进程 pid 列表 | `pids` | `--pids 12345,12346` 多个 pid 按逗号分隔 | +| 进程名称 | `comm` | `--comm 'redis-cli'` | +| 容器 id | `container-id` | `--container-id xx` | +| 容器名称 | `container-name` | `--container-name foobar` | +| k8s pod 名称 | `pod-name` | `--pod-name nginx-7bds23212-23s1s.default`
格式: NAME.NAMESPACE | 值得一提的是,kyanos 也会显示容器网卡和宿主机网卡之间的耗时: -![kyanos time detail](/timedetail.jpg) +![kyanos time detail](/timedetail.jpg) ### 根据请求响应的一般信息过滤 -| 过滤条件 | 命令行flag | 示例 | -| :------ | :------------- | :-------------------------------------------------------------------- | -| 请求响应耗时 | `latency` | `--latency 100` 只观察耗时超过100ms的请求响应 | -| 请求大小字节数 | `req-size` | `--req-size 1024` 只观察请求大小超过1024bytes的请求响应 | -| 响应大小字节数 | `resp-size` | `--resp-size 1024` 只观察响应大小超过1024bytes的请求响应 | - +| 过滤条件 | 命令行 flag | 示例 | +| :------------- | :---------- | :--------------------------------------------------------- | +| 请求响应耗时 | `latency` | `--latency 100` 只观察耗时超过 100ms 的请求响应 | +| 请求大小字节数 | `req-size` | `--req-size 1024` 只观察请求大小超过 1024bytes 的请求响应 | +| 响应大小字节数 | `resp-size` | `--resp-size 1024` 只观察响应大小超过 1024bytes 的请求响应 | ### 根据协议特定信息过滤 + 你可选择只采集某种协议的请求响应,通过在 watch 后面加上具体的协议名称,当前支持: - `http` @@ -98,30 +101,30 @@ kyanos 支持根据 IP 端口等三/四层信息过滤,可以指定以下选 比如:`kyanos watch http --path /foo/bar`, 下面是每种协议你可以使用的选项。 -#### HTTP协议过滤 - -| 过滤条件 | 命令行flag | 示例 | -|:---------|:--------------|:---------------------------------------------------------------| -| 请求Path | `path` | `--path /foo/bar ` 只观察path为/foo/bar的请求 | -| 请求Path前缀 | `path-prefix` | `--path-prefix /foo/bar ` 只观察path前缀为/foo/bar的请求 | -| 请求Path正则 | `path-regex` | `--path-regex "\/foo\/bar\/.*" ` 只观察path匹配 `\/foo\/bar\/.*`的请求 | -| 请求Host | `host` | `--host www.baidu.com ` 只观察Host为www.baidu.com的请求 | -| 请求方法 | `method` | `--method GET` 只观察方法为GET | +#### HTTP 协议过滤 -#### Redis协议过滤 +| 过滤条件 | 命令行 flag | 示例 | +| :------------- | :------------ | :------------------------------------------------------------------------ | +| 请求 Path | `path` | `--path /foo/bar ` 只观察 path 为/foo/bar 的请求 | +| 请求 Path 前缀 | `path-prefix` | `--path-prefix /foo/bar ` 只观察 path 前缀为/foo/bar 的请求 | +| 请求 Path 正则 | `path-regex` | `--path-regex "\/foo\/bar\/.*" ` 只观察 path 匹配 `\/foo\/bar\/.*` 的请求 | +| 请求 Host | `host` | `--host www.baidu.com ` 只观察 Host 为 www.baidu.com 的请求 | +| 请求方法 | `method` | `--method GET` 只观察方法为 GET | -| 过滤条件 | 命令行flag | 示例 | -| :------ | :----------- | :---------------------------------------- | -| 请求命令 | `command` | `--command GET,SET `只观察请求命令为GET和SET | -| 请求Key | `keys` | `--keys foo,bar `只观察请求key为foo和bar | -| 请求key前缀 | `key-prefix` | `--method foo:bar ` 只观察请求的key前缀为foo\:bar | +#### Redis 协议过滤 -#### MYSQL协议过滤 +| 过滤条件 | 命令行 flag | 示例 | +| :------------ | :----------- | :---------------------------------------------------- | +| 请求命令 | `command` | `--command GET,SET ` 只观察请求命令为 GET 和 SET | +| 请求 Key | `keys` | `--keys foo,bar ` 只观察请求 key 为 foo 和 bar | +| 请求 key 前缀 | `key-prefix` | `--method foo:bar ` 只观察请求的 key 前缀为 foo\: bar | -> 已支持MySQL协议抓取,根据条件过滤仍在实现中... +#### MYSQL 协议过滤 +> 已支持 MySQL 协议抓取,根据条件过滤仍在实现中... --- > [!TIP] -> 所有上述选项均可以组合使用,比如:`./kyanos watch redis --keys foo,bar --remote-ports 6379 --pid 12345` \ No newline at end of file +> +> 所有上述选项均可以组合使用,比如:`./kyanos watch redis --keys foo,bar --remote-ports 6379 --pid 12345` diff --git a/docs/cn/what-is-kyanos.md b/docs/cn/what-is-kyanos.md index 3ed2831b..0caef256 100644 --- a/docs/cn/what-is-kyanos.md +++ b/docs/cn/what-is-kyanos.md @@ -1,28 +1,31 @@ --- next: - text: '快速开始' - link: './quickstart' + text: "快速开始" + link: "./quickstart" prev: false --- + # Kyanos 是什么?{#what-is-kyanos} -Kyanos 是一个基于eBPF的网络问题分析工具,你可以用它来抓取业务请求,比如HTTP请求、Redis请求、MYSQL请求等,还可以帮你分析异常链路,快速排查业务故障,而不需要繁琐的抓包,下载,分析等步骤,并且拥有良好的兼容性,大多数情况下无需任何依赖一行命令即可开始分析。 +Kyanos 是一个基于 eBPF 的网络问题分析工具,你可以用它来抓取业务请求,比如 HTTP 请求、Redis 请求、MYSQL 请求等,还可以帮你分析异常链路,快速排查业务故障,而不需要繁琐的抓包,下载,分析等步骤,并且拥有良好的兼容性,大多数情况下无需任何依赖一行命令即可开始分析。 ## 你为什么应该使用 Kyanos? -> 现在已经有很多网络排查工具了比如tcpdump,iftop,netstat等,那么kyanos有什么优势呢? -### 传统的tcpdump抓包排查的缺点 +> 现在已经有很多网络排查工具了比如 tcpdump,iftop,netstat 等,那么 kyanos 有什么优势呢? + +### 传统的 tcpdump 抓包排查的缺点 -1. 难以根据协议特定信息过滤,以 HTTP 协议为例,很难做到根据特定 HTTP Path 抓包,必须依赖 wireshark/tshark 这类工具进行二次过滤。 +1. 难以根据协议特定信息过滤,以 HTTP 协议为例,很难做到根据特定 HTTP + Path 抓包,必须依赖 wireshark/tshark 这类工具进行二次过滤。 2. 难以根据数据包收发的进程/容器等过滤,尤其在一个机器上部署多个进程/容器的时候,只需要抓取某个业务进程的数据包。 3. 排查效率较低,一般排查流程:首先是进入生产环境使用 tcpdump 抓包生成 pcap 文件,然后需要下载到本地再通过 wireshark 工具分析,往往消耗了大量时间。 4. 较弱的分析能力,tcpdump 只提供底层的抓包能力,几乎不具备高阶的分析能力,必须要和 wireshark 搭配使用,而传统的网络监控工具比如 iftop 和 nestat 只能提供粗粒度的监控,想要排查根因很难。 -5. 加密的流量比如使用SSL协议的请求无法看到明文。 +5. 加密的流量比如使用 SSL 协议的请求无法看到明文。 +### kyanos 能为你带来的 -### kyanos能为你带来的 +1. **强大的流量过滤功能**:不仅可以根据传统 IP/端口 等信息过滤,还支持根据:进程/容器、L7 协议信息、请求/响应字节数、耗时等过滤你想要的数据。 -1. **强大的流量过滤功能**:不仅可以根据传统 IP/端口 等信息过滤,还支持根据:进程/容器、L7协议信息、请求/响应字节数、耗时等过滤你想要的数据。 ```bash # 根据 pid 过滤 ./kyanos watch --pids 1234 @@ -33,25 +36,31 @@ Kyanos 是一个基于eBPF的网络问题分析工具,你可以用它来抓取 # 根据响应字节数过滤 ./kyanos watch --resp-size 10000 ``` -2. **强大的分析功能**: 和 tcpdump 只提供细粒度的抓包功能不同,kyanos 还支持以各种维度聚合抓取的数据包的指标信息,快速得到对排查问题最有用的关键数据。想象一下你的 HTTP 服务的带宽突然被打满,你该如何快速的分析是 `哪些 ip` 的 `哪些请求` 造成的? -使用 kyanos 只需要一行命令:`kyanos stat http --bigresp` 即可找到发送给哪些远程 ip 的响应字节数最大,并且还能够发现请求响应的具体数据。 -![kyanos find big response](/whatkyanos.gif) -3. **深入内核的耗时细节**:在实际业务场景中我们经常遇到远程服务慢查询问题,比如访问 Redis 请求较慢,但是 **具体慢在哪里** 在传统监控方式下很难给出确切答案。而 kyanos 提供了 请求/响应 到达网卡以及从 内核Socket 缓冲区读取的内核埋点,并且以可视化的图形展示出来,你可以方便的判断是哪一个环节出现了问题。 -![kyanos time detail](/timedetail.jpg) -如上所示,这是一个在容器内执行 `curl http://www.baidu.com` 命令的耗时记录,你可以发现 kyanos 记录了请求经过容器网卡、宿主机网卡,响应经过宿主机网卡、容器网卡、Socket缓冲区每个步骤的耗时。 -4. **轻量级零依赖**:几乎 0 依赖,只需要单个二进制文件,一行命令,所有结果都展示在命令行中。 -5. **SSL流量自动解密**:kyanos 为你抓取的请求响应结果全部都是明文。 +2. **强大的分析功能**: 和 tcpdump 只提供细粒度的抓包功能不同,kyanos 还支持以各种维度聚合抓取的数据包的指标信息,快速得到对排查问题最有用的关键数据。想象一下你的 HTTP 服务的带宽突然被打满,你该如何快速的分析是 + `哪些 ip` 的 `哪些请求` 造成的? + 使用 kyanos 只需要一行命令:`kyanos stat http --bigresp` 即可找到发送给哪些远程 ip 的响应字节数最大,并且还能够发现请求响应的具体数据。 + ![kyanos find big response](/whatkyanos.gif) +3. **深入内核的耗时细节**:在实际业务场景中我们经常遇到远程服务慢查询问题,比如访问 Redis 请求较慢,但是 + **具体慢在哪里** + 在传统监控方式下很难给出确切答案。而 kyanos 提供了 请求/响应 到达网卡以及从 内核 Socket 缓冲区读取的内核埋点,并且以可视化的图形展示出来,你可以方便的判断是哪一个环节出现了问题。 + ![kyanos time detail](/timedetail.jpg) + 如上所示,这是一个在容器内执行 `curl http://www.baidu.com` + 命令的耗时记录,你可以发现 kyanos 记录了请求经过容器网卡、宿主机网卡,响应经过宿主机网卡、容器网卡、Socket 缓冲区每个步骤的耗时。 +4. **轻量级零依赖**:几乎 0 依赖,只需要单个二进制文件,一行命令,所有结果都展示在命令行中。 +5. **SSL 流量自动解密**:kyanos 为你抓取的请求响应结果全部都是明文。 ## 什么时候你会使用 kyanos {#use-cases} - **抓取请求响应** -kyanos 提供了 watch 命令可以帮助你过滤抓取各种流量,它支持根据进程id、容器id、容器 name、pod name 以及 IP、端口过滤,也支持根据协议特定的字段过滤,比如HTTP PATH,Redis的命令以及key过滤。抓取的流量不仅仅包含请求响应的具体内容,还包括耗时的细节,比如请求从系统调用到网卡,再到响应从网卡到socket缓冲区再到进程去读取它,这些耗时细节你全部都能得到。 +kyanos 提供了 watch 命令可以帮助你过滤抓取各种流量,它支持根据进程 id、容器 id、容器 name、pod +name 以及 IP、端口过滤,也支持根据协议特定的字段过滤,比如 HTTP +PATH,Redis 的命令以及 key 过滤。抓取的流量不仅仅包含请求响应的具体内容,还包括耗时的细节,比如请求从系统调用到网卡,再到响应从网卡到 socket 缓冲区再到进程去读取它,这些耗时细节你全部都能得到。 - **分析异常链路** -kyanos 的 stat 命令可以帮你快速找到异常链路,stat 命令支持根据多种维度聚合,比如kyanos支持根据远程ip聚合,让你可以快速分析出是哪一个远程ip更慢;另外kyanos支持各种指标的统计,比如请求响应耗时、请求响应大小等。结合以上两点,80%的业务网络异常问题都可以快速定位。 +kyanos 的 stat 命令可以帮你快速找到异常链路,stat 命令支持根据多种维度聚合,比如 kyanos 支持根据远程 ip 聚合,让你可以快速分析出是哪一个远程 ip 更慢;另外 kyanos 支持各种指标的统计,比如请求响应耗时、请求响应大小等。结合以上两点,80%的业务网络异常问题都可以快速定位。 - **全局依赖分析** @@ -59,34 +68,38 @@ kyanos 的 stat 命令可以帮你快速找到异常链路,stat 命令支持 ## 基础示例 -**抓取 HTTP 流量并且获取耗时细节** +**抓取 HTTP 流量并且获取耗时细节** 执行命令: + ```bash ./kyanos watch http ``` + 演示结果如下: ![kyanos quick start watch http](/qs-watch-http.gif) - -**抓取 Redis 流量获取耗时细节** +**抓取 Redis 流量获取耗时细节** 执行命令: + ```bash ./kyanos watch redis ``` + 演示结果如下: ![kyanos quick start watch redis](/qs-redis.gif) -**找到5s内最慢的几个请求** +**找到 5s 内最慢的几个请求** 执行命令: + ```bash - ./kyanos stat --slow --time 5 + ./kyanos stat --slow --time 5 ``` + 演示结果如下: ![kyanos stat slow](/qs-stat-slow.gif) - diff --git a/docs/faq.md b/docs/faq.md index 941ed76c..2eee629d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -6,41 +6,67 @@ prev: false # FAQ ## Does it support running on Windows / Mac? -Not yet, but there are plans to support it in the future.([ISSUE-151](https://github.com/hengyoush/kyanos/issues/151)) + +Not yet, but there are plans to support it in the +future.([ISSUE-151](https://github.com/hengyoush/kyanos/issues/151)) ## Does it support running on lower kernel versions? -Currently, it supports the minimum kernel version: 3.10.0-957, but some features may be missing on lower kernel versions. -Currently, 3.* kernel versions do not support filtering traffic by container ID/container name and cannot automatically associate traffic before and after NAT. +Currently, it supports the minimum kernel version: 3.10.0-957, but some features +may be missing on lower kernel versions. + +Currently, 3.\* kernel versions do not support filtering traffic by container +ID/container name and cannot automatically associate traffic before and after +NAT. ## Does it support running on Linux in WSL? -Theoretically yes, but Linux distributions on WSL usually do not include Linux headers by default, which kyanos depends on. You may need to modify the compilation options to manually compile the kernel. For specific methods, refer to: [Enabling eBPF/XDP for Kernel Tinkering on WSL2](https://dev.to/wiresurfer/unleash-the-forbidden-enabling-ebpfxdp-for-kernel-tinkering-on-wsl2-43fj) + +Theoretically yes, but Linux distributions on WSL usually do not include Linux +headers by default, which kyanos depends on. You may need to modify the +compilation options to manually compile the kernel. For specific methods, refer +to: +[Enabling eBPF/XDP for Kernel Tinkering on WSL2](https://dev.to/wiresurfer/unleash-the-forbidden-enabling-ebpfxdp-for-kernel-tinkering-on-wsl2-43fj) ## Can it run in a container/Pod? + It must run in a privileged mode container/Pod. ## When using the --pod-name option, the "can not find any running pod by name xxx" log appears + Kyanos must be running on the same host as the target Pod. ## `can't find btf file to load!` log appears during operation -This may be because your system lacks the BTF file. You can manually download the BTF file that matches your kernel from here: https://mirrors.openanolis.cn/coolbpf/btf/ and https://github.com/aquasecurity/btfhub-archive/. Specify the downloaded BTF file with the `--btf` option when starting kyanos. + +This may be because your system lacks the BTF file. You can manually download +the BTF file that matches your kernel from here: +https://mirrors.openanolis.cn/coolbpf/btf/ and +https://github.com/aquasecurity/btfhub-archive/. Specify the downloaded BTF file +with the `--btf` option when starting kyanos. ## How to understand the visualization of kernel time in the watch results? -![kyanos time detail](/timedetail.jpg) + +![kyanos time detail](/timedetail.jpg) In the image, `eth0@if483` is the container NIC, and `eth0` is the host NIC. -The upper part of the image shows the request from the process sending to the NIC, and the lower part shows the response from the NIC to the process. +The upper part of the image shows the request from the process sending to the +NIC, and the lower part shows the response from the NIC to the process. ## Incorrect terminal table colors after running (e.g., unable to select records in the table) -![kyanos missing color](/missing-color.png) +![kyanos missing color](/missing-color.png) + +Check if there is a +`Your terminal does not support 256 colors, ui may display incorrectly` log. If +so, it means the terminal color configuration is incorrect. Kyanos requires a +256-color terminal. +Use the following command to list all terminal types supported by the system and +their supported color bits: -Check if there is a `Your terminal does not support 256 colors, ui may display incorrectly` log. If so, it means the terminal color configuration is incorrect. Kyanos requires a 256-color terminal. -Use the following command to list all terminal types supported by the system and their supported color bits: ```shell for T in `find /usr/share/terminfo -type f -printf '%f '`;do echo "$T `tput -T $T colors`";done|sort -nk2|tail -n20 ``` Example output: + ```shell Eterm-88color 88 rxvt-88color 88 @@ -52,9 +78,13 @@ iTerm.app 256 konsole-256color 256 ... ``` -The $TERM variable represents the current terminal type, which can be viewed using the echo $TERM command. -You can change it to 256 colors by modifying the ~/.bashrc file. Add the following code to the .bashrc file: +The $TERM variable represents the current terminal type, which can be viewed +using the echo $TERM command. + +You can change it to 256 colors by modifying the ~/.bashrc file. Add the +following code to the .bashrc file: + ```shell case "$TERM" in xterm) diff --git a/docs/how-to-add-a-new-protocol.md b/docs/how-to-add-a-new-protocol.md index cb28471e..085b3338 100644 --- a/docs/how-to-add-a-new-protocol.md +++ b/docs/how-to-add-a-new-protocol.md @@ -2,23 +2,37 @@ ## Background -Kyanos needs to capture protocol messages to form request-response pairs for terminal display. Therefore, Kyanos requires protocol parsing code for each protocol. Currently, it supports HTTP, MySQL, and Redis, and will support more in the future. This document will explain the overall architecture of Kyanos protocol parsing to assist in developing new protocols. +Kyanos needs to capture protocol messages to form request-response pairs for +terminal display. Therefore, Kyanos requires protocol parsing code for each +protocol. Currently, it supports HTTP, MySQL, and Redis, and will support more +in the future. This document will explain the overall architecture of Kyanos +protocol parsing to assist in developing new protocols. ## Overview of Protocol Parsing Process ![kyanos protocol parse flow](/protocol-parse-flow.png) -Starting from the left, Kyanos instruments system calls like read and write to capture the data read and written by the application process and sends it to user space through the perf event buffer. +Starting from the left, Kyanos instruments system calls like read and write to +capture the data read and written by the application process and sends it to +user space through the perf event buffer. -In user space, the data is placed into reqStreamBuffer or respStreamBuffer based on whether the connection is client-side or server-side and data's direction. This data is purely application protocol data without tcp/ip protocol headers. +In user space, the data is placed into reqStreamBuffer or respStreamBuffer based +on whether the connection is client-side or server-side and data's direction. +This data is purely application protocol data without tcp/ip protocol headers. -Kyanos uses the corresponding protocol parser to parse the data placed in the streamBuffer, then associates the parsed requests and responses, and finally performs Full Body parsing as needed by the protocol to generate a "record". +Kyanos uses the corresponding protocol parser to parse the data placed in the +streamBuffer, then associates the parsed requests and responses, and finally +performs Full Body parsing as needed by the protocol to generate a "record". ## Some Terminology -Message: The most basic data unit of a protocol, usually consisting of a header and a body. +Message: The most basic data unit of a protocol, usually consisting of a header +and a body. -Request / Response: A request or response consists of one or more Messages sent together, representing a message. (*Note: For simple protocols like HTTP1.1, a Request/Response may correspond to a single Message, but for complex protocols like MySQL, multiple Messages together correspond to a Request/Response*) +Request / Response: A request or response consists of one or more Messages sent +together, representing a message. (_Note: For simple protocols like HTTP1.1, a +Request/Response may correspond to a single Message, but for complex protocols +like MySQL, multiple Messages together correspond to a Request/Response_) Record: A Record represents a completed request-response pair. @@ -27,19 +41,26 @@ Record: A Record represents a completed request-response pair. We need to do the following: 1. Implement user-space protocol parsing - 1. Define protocol message types (i.e., the structure of requests and responses) - 2. Implement a `Parser`, specifically the `ProtocolStreamParser` interface, which implements the specific logic for protocol parsing and request-response matching. + 1. Define protocol message types (i.e., the structure of requests and + responses) + 2. Implement a `Parser`, specifically the `ProtocolStreamParser` interface, + which implements the specific logic for protocol parsing and + request-response matching. 2. Implement kernel-space protocol inference logic. 3. Add command-line subcommands to implement filtering logic. 4. Add e2e tests. ## Step.1-Define Protocol Message Types -Create a new directory under /agent/protocol, named after the protocol, for example: kafka. +Create a new directory under /agent/protocol, named after the protocol, for +example: kafka. ### Define Message -Generally, the protocol you want to implement will have a common Header, which contains some metadata, such as a flag indicating whether it is a request or a response. You need to store this information in the fields of the Message you define, such as the MySQL protocol definition: +Generally, the protocol you want to implement will have a common Header, which +contains some metadata, such as a flag indicating whether it is a request or a +response. You need to store this information in the fields of the Message you +define, such as the MySQL protocol definition: ```go type MysqlPacket struct { @@ -51,7 +72,9 @@ type MysqlPacket struct { } ``` -Generally, both Message and the following Request/Response need to embed a `FrameBase` struct. `FrameBase` is defined as follows and contains some basic information: +Generally, both Message and the following Request/Response need to embed a +`FrameBase` struct. `FrameBase` is defined as follows and contains some basic +information: ```go type FrameBase struct { @@ -61,11 +84,16 @@ type FrameBase struct { } ``` -For simple protocols like HTTP, since a single Message corresponds to a request or response, there is no need for Full Body parsing, and therefore no need to define a Message. You can directly define Request and Response. +For simple protocols like HTTP, since a single Message corresponds to a request +or response, there is no need for Full Body parsing, and therefore no need to +define a Message. You can directly define Request and Response. ### Define Request and Response -Requests and responses are at a higher level than Messages and consist of one or more Messages. `struct Request` or `struct Response` should contain data specific to the request/response and should implement the ParsedMessage interface, defined as follows: +Requests and responses are at a higher level than Messages and consist of one or +more Messages. `struct Request` or `struct Response` should contain data +specific to the request/response and should implement the ParsedMessage +interface, defined as follows: ```go type ParsedMessage interface { @@ -78,15 +106,16 @@ type ParsedMessage interface { } ``` -| Method Name | Function | -|-----------------------|----------------------------------------------------------------------| -| `FormatToString()` | Formats the message into a string representation. | -| `TimestampNs()` | Returns the timestamp of the message (in nanoseconds). | -| `ByteSize()` | Returns the byte size of the message. | -| `IsReq()` | Determines if the message is a request. | -| `Seq()` | Returns the sequence number of the byte stream.(Obtain Seq from `streamBuffer.Head().LeftBoundary()`) | +| Method Name | Function | +| ------------------ | ----------------------------------------------------------------------------------------------------- | +| `FormatToString()` | Formats the message into a string representation. | +| `TimestampNs()` | Returns the timestamp of the message (in nanoseconds). | +| `ByteSize()` | Returns the byte size of the message. | +| `IsReq()` | Determines if the message is a request. | +| `Seq()` | Returns the sequence number of the byte stream.(Obtain Seq from `streamBuffer.Head().LeftBoundary()`) | Example for HTTP: + ```go type ParsedHttpRequest struct { FrameBase @@ -98,11 +127,16 @@ type ParsedHttpRequest struct { } ``` -> Note. In `protocol.BinaryDecoder`, the `BinaryDecoder` class provides some convenient utility functions for extracting characters, strings, or integers from the buffer. We should use these functions when implementing ParseFrame, FindFrameBoundary, and Full Body parsing below. +> Note. In `protocol.BinaryDecoder`, the `BinaryDecoder` class provides some +> convenient utility functions for extracting characters, strings, or integers +> from the buffer. We should use these functions when implementing ParseFrame, +> FindFrameBoundary, and Full Body parsing below. ## Step.2-Implement Protocol Parsing -After defining the request and response types, you can start implementing the parsing process. Specifically, you need to implement the `ProtocolStreamParser` interface: +After defining the request and response types, you can start implementing the +parsing process. Specifically, you need to implement the `ProtocolStreamParser` +interface: ```go type ProtocolStreamParser interface { @@ -119,41 +153,65 @@ type ProtocolStreamParser interface { ### ParseStream (Buffer -> Message/ReqResp) -ParseStream parses the Message or directly parses the ReqResp from the network data. +ParseStream parses the Message or directly parses the ReqResp from the network +data. ```go ParseStream(streamBuffer *buffer.StreamBuffer, messageType MessageType) ParseResult ``` -Note that eBPF data events may arrive out of order or lose events, so the data in the buffer may be missing chunks. The `streamBuffer` parameter provides all the continuous data received so far through the `streamBuffer.Head` function. Therefore, it is **not guaranteed that the data is valid or aligned with the beginning of the packet**. +Note that eBPF data events may arrive out of order or lose events, so the data +in the buffer may be missing chunks. The `streamBuffer` parameter provides all +the continuous data received so far through the `streamBuffer.Head` function. +Therefore, it is **not guaranteed that the data is valid or aligned with the +beginning of the packet**. -If the `state` in the returned `ParseResult` is `success`, Kyanos will automatically delete the number of bytes specified by `ParseResult.ReadBytes`. If `invalid` is returned, the next possible `Message` boundary is found through `FindBoundary`. If `needsMoreData` is returned, the data is not deleted and will be retried later. +If the `state` in the returned `ParseResult` is `success`, Kyanos will +automatically delete the number of bytes specified by `ParseResult.ReadBytes`. +If `invalid` is returned, the next possible `Message` boundary is found through +`FindBoundary`. If `needsMoreData` is returned, the data is not deleted and will +be retried later. ### FindBoundary - Find Message Boundary -FindBoundary finds the Message boundary and recovers from the parsing failure state. +FindBoundary finds the Message boundary and recovers from the parsing failure +state. ```go FindBoundary(streamBuffer *buffer.StreamBuffer, messageType MessageType, startPos int) int ``` -In conntrack.go, ParseStream and FindBoundary are called in a loop until streamBuffer is empty. If ParseStream returns invalid, it indicates a parsing problem, and FindBoundary is called to find the next position, which is usually the beginning of the Message. If the request has a tag like request id, the response is very likely to have the same request id. +In conntrack.go, ParseStream and FindBoundary are called in a loop until +streamBuffer is empty. If ParseStream returns invalid, it indicates a parsing +problem, and FindBoundary is called to find the next position, which is usually +the beginning of the Message. If the request has a tag like request id, the +response is very likely to have the same request id. ### Match (Messages -> reqresp -> record) -Messages successfully parsed by ParseStream are placed in the corresponding ReqQueue and RespQueue, and then matched together to create a Record. There are two main matching methods: order-based and tag-based. +Messages successfully parsed by ParseStream are placed in the corresponding +ReqQueue and RespQueue, and then matched together to create a Record. There are +two main matching methods: order-based and tag-based. -Protocols like HTTP use order-based matching, while protocols like Kafka use tag-based matching. +Protocols like HTTP use order-based matching, while protocols like Kafka use +tag-based matching. Note: If using order-based matching, you can directly use `matchByTimestamp`. ### Full Body Parsing - Parse the Entire Message -Currently, Full Body Parsing is part of Match. For most protocols, if we need to parse the entire message body, it can only be done after the request-response matching, such as Kafka, which needs to know the request opcode before parsing the response based on the opcode. +Currently, Full Body Parsing is part of Match. For most protocols, if we need to +parse the entire message body, it can only be done after the request-response +matching, such as Kafka, which needs to know the request opcode before parsing +the response based on the opcode. ## Step.3-Implement Protocol Inference -Before capturing kernel data to user space for parsing, we need to identify what protocol the traffic belongs to. When a connection is opened and data is transmitted, Kyanos will determine the protocol based on some rules. Each protocol has its own rules. For example, the HTTP protocol is as follows: +Before capturing kernel data to user space for parsing, we need to identify what +protocol the traffic belongs to. When a connection is opened and data is +transmitted, Kyanos will determine the protocol based on some rules. Each +protocol has its own rules. For example, the HTTP protocol is as follows: + ```c static __always_inline enum message_type_t is_http_protocol(const char *old_buf, size_t count) { if (count < 5) { @@ -179,17 +237,21 @@ static __always_inline enum message_type_t is_http_protocol(const char *old_buf, Warn: -1. The rules of the new protocol may cause false positives or false negatives, affecting the accuracy of other protocols. +1. The rules of the new protocol may cause false positives or false negatives, + affecting the accuracy of other protocols. 2. The order of the rules is important. For these reasons, you need to pay attention to the following: -1. Avoid using overly general and common patterns as inference rules in the protocol. For example, judging based on a single byte like `0x00` or `0x01` is not strict enough. +1. Avoid using overly general and common patterns as inference rules in the + protocol. For example, judging based on a single byte like `0x00` or `0x01` + is not strict enough. 2. Place stricter and more robust rules (such as HTTP) at the front. ## Step.4-Add Command-Line Subcommands and Implement Filtering Logic -Add the necessary protocol-specific filtering options to the watch and stat commands. +Add the necessary protocol-specific filtering options to the watch and stat +commands. Then implement `protocol.ProtocolFilter`: @@ -202,16 +264,18 @@ type ProtocolFilter interface { } ``` -| Method Name | Function | -|-----------------------|----------------------------------------------------------------------| -| `Filter` | Filters requests and responses. | -| `FilterByProtocol` | Filters based on protocol type. | -| `FilterByRequest` | Filters based on requests. | -| `FilterByResponse` | Filters based on responses. | +| Method Name | Function | +| ------------------ | ------------------------------- | +| `Filter` | Filters requests and responses. | +| `FilterByProtocol` | Filters based on protocol type. | +| `FilterByRequest` | Filters based on requests. | +| `FilterByResponse` | Filters based on responses. | ## Step.5-Register Protocol Parser -Add an init function in your module to write it into the `ParsersMap`, for example: +Add an init function in your module to write it into the `ParsersMap`, for +example: + ```go func init() { ParsersMap[bpf.AgentTrafficProtocolTKProtocolHTTP] = func() ProtocolStreamParser { @@ -222,20 +286,31 @@ func init() { ## Step.6-Add e2e Tests -Add e2e tests for the corresponding protocol in the testdata directory. You can refer to the implementation of other protocols (e.g., `test_redis.sh`). +Add e2e tests for the corresponding protocol in the testdata directory. You can +refer to the implementation of other protocols (e.g., `test_redis.sh`). ## Others ### Debugging Suggestions -It is recommended to use `common.ProtocolParserLog` for printing protocol parsing logs. Enable protocol parsing logs with `--protocol-log-level 5` to set protocol parsing-related log levels to debug. +It is recommended to use `common.ProtocolParserLog` for printing protocol +parsing logs. Enable protocol parsing logs with `--protocol-log-level 5` to set +protocol parsing-related log levels to debug. -The protocol parsing framework code is in the `addDataToBufferAndTryParse` function in conntrack.go. +The protocol parsing framework code is in the `addDataToBufferAndTryParse` +function in conntrack.go. ### Persisting Protocol Parsing Information -In some protocols, if you need to retain some data during the parsing process (e.g., in Kafka, it stores a set of all correlation_ids seen on the request buffer, and FindBoundary only returns the position of the previously seen correlation_id on the respStreamBuffer), you can customize some variables in the protocol's Parser to save them (i.e., the Parser can be stateful). **Kyanos will create a separate Parser for each connection when it is opened and keep it until the connection is closed**. +In some protocols, if you need to retain some data during the parsing process +(e.g., in Kafka, it stores a set of all correlation_ids seen on the request +buffer, and FindBoundary only returns the position of the previously seen +correlation_id on the respStreamBuffer), you can customize some variables in the +protocol's Parser to save them (i.e., the Parser can be stateful). **Kyanos will +create a separate Parser for each connection when it is opened and keep it until +the connection is closed**. ## Summary -Congratulations on successfully adding a new protocol to Kyanos! Your contribution will benefit many others with the new protocol parser! \ No newline at end of file +Congratulations on successfully adding a new protocol to Kyanos! Your +contribution will benefit many others with the new protocol parser! diff --git a/docs/how-to-build.md b/docs/how-to-build.md index 42f81d00..fd940cf1 100644 --- a/docs/how-to-build.md +++ b/docs/how-to-build.md @@ -2,9 +2,11 @@ next: false prev: false --- + # Compilation Steps -This document describes the local compilation method for kyanos. My environment is Ubuntu 22.04, and other environments may vary. +This document describes the local compilation method for kyanos. My environment +is Ubuntu 22.04, and other environments may vary. ## Tool Version Requirements @@ -13,53 +15,80 @@ This document describes the local compilation method for kyanos. My environment - llvm 10.0 or above ## Installation of Compilation Environment Dependencies + ### Ubuntu -If you are using Ubuntu 22.04 or later, you can initialize the compilation environment with a single command. + +If you are using Ubuntu 22.04 or later, you can initialize the compilation +environment with a single command. + ``` /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/hengyoush/kyanos/refs/heads/main/init_env.sh)" ``` + ### Other Linux Distributions + clone the project (don't forget to update submodle!): + ```bash git clone https://github.com/hengyoush/kyanos cd kyanos git submodule update --init --recursive ``` -In addition to the toolchain versions listed above, the compilation environment also requires the following software. Please install them manually. + +In addition to the toolchain versions listed above, the compilation environment +also requires the following software. Please install them manually. - linux-tools-common - linux-tools-generic - pkgconf - libelf-dev - ## Compilation Commands If you are just developing and testing locally, you can execute + ``` make build-bpf && make ``` -the kyanos executable file will be generated in the root directory of the project. +the kyanos executable file will be generated in the root directory of the +project. > [!IMPORTANT] -> Note that this binary file does not include the BTF files from [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/). If you run this kyanos on a lower version kernel without BTF support, it may fail to start. You can build a kyanos artifact with embedded BTF files using the following commands: +> +> Note that this binary file does not include the BTF files from +> [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/). If you run +> this kyanos on a lower version kernel without BTF support, it may fail to +> start. You can build a kyanos artifact with embedded BTF files using the +> following commands: +> > ::: code-group ->```bash [x86_64] ->make build-bpf && make btfgen BUILD_ARCH=x86_64 ARCH_BPF_NAME=x86 && make ->``` > ->```bash [arm64] ->make build-bpf && make btfgen BUILD_ARCH=arm64 ARCH_BPF_NAME=arm64 && make ->``` ->::: +> ```bash [x86_64] +> make build-bpf && make btfgen BUILD_ARCH=x86_64 ARCH_BPF_NAME=x86 && make +> ``` +> +> ```bash [arm64] +> make build-bpf && make btfgen BUILD_ARCH=arm64 ARCH_BPF_NAME=arm64 && make +> ``` +> +> ::: +> > Note that make btfgen may take more than 15 minutes. > [!TIP] ->If your kernel does not have BTF enabled, you may not be able to start kyanos successfully. > ->Check if BTF is enabled: ->``` ->grep CONFIG_DEBUG_INFO_BTF "/boot/config-$(uname -r)" ->``` ->If the result is `CONFIG_DEBUG_INFO_BTF=y`, it means BTF is enabled. If not, please download the BTF file corresponding to your kernel version from [mirrors.openanolis.cn](https://mirrors.openanolis.cn/coolbpf/btf/) or [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/), and use the `--btf` option to specify the downloaded BTF file when starting kyanos. \ No newline at end of file +> If your kernel does not have BTF enabled, you may not be able to start kyanos +> successfully. +> +> Check if BTF is enabled: +> +> ``` +> grep CONFIG_DEBUG_INFO_BTF "/boot/config-$(uname -r)" +> ``` +> +> If the result is `CONFIG_DEBUG_INFO_BTF=y`, it means BTF is enabled. If not, +> please download the BTF file corresponding to your kernel version from +> [mirrors.openanolis.cn](https://mirrors.openanolis.cn/coolbpf/btf/) or +> [btfhub-archive](https://github.com/aquasecurity/btfhub-archive/), and use the +> `--btf` option to specify the downloaded BTF file when starting kyanos. diff --git a/docs/how-to.md b/docs/how-to.md index ff03c17b..61553ff9 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -1,16 +1,21 @@ --- next: - text: 'Watch Usage' - link: './watch' + text: "Watch Usage" + link: "./watch" prev: false --- # Learn Kyanos in 5 Minutes -Kyanos has three main subcommands: `watch`, `stat`, and `overview`. Here’s what each command does: -1. **watch**: Captures network traffic according to specified options and automatically parses it into request-response records. -2. **stat**: Aggregates request-response records based on specified conditions, providing higher-level statistical information. -3. **overview**: Displays external resources that the current machine relies on in a single command. +Kyanos has three main subcommands: `watch`, `stat`, and `overview`. Here’s what +each command does: + +1. **watch**: Captures network traffic according to specified options and + automatically parses it into request-response records. +2. **stat**: Aggregates request-response records based on specified conditions, + providing higher-level statistical information. +3. **overview**: Displays external resources that the current machine relies on + in a single command. ## Basic Usage of Traffic Capture with `watch` @@ -20,17 +25,24 @@ The simplest usage captures all protocols traffic currently supported by Kyanos: ./kyanos watch ``` -Each request-response record is displayed as a row in a table. You can use the arrow keys or `j/k` to move up and down through the records: -![kyanos watch result](/watch-result.jpg) +Each request-response record is displayed as a row in a table. You can use the +arrow keys or `j/k` to move up and down through the records: +![kyanos watch result](/watch-result.jpg) Press `Enter` to access the details view: -![kyanos watch result detail](/watch-result-detail.jpg) +![kyanos watch result detail](/watch-result-detail.jpg) -In the details view, the first section is **Latency Details**. Each block represents a "node" that the data packet passes through, such as the process, network interface, and socket buffer. -Each block includes a time value indicating the time elapsed from the previous node to this node, showing the process flow from the process sending the request to the network interface, to the response being copied to the socket buffer, and finally read by the process, with each step’s duration displayed. +In the details view, the first section is **Latency Details**. Each block +represents a "node" that the data packet passes through, such as the process, +network interface, and socket buffer. +Each block includes a time value indicating the time elapsed from the previous +node to this node, showing the process flow from the process sending the request +to the network interface, to the response being copied to the socket buffer, and +finally read by the process, with each step’s duration displayed. -The second section provides **Detailed Request and Response Content**, split into Request and Response parts, and truncates content over `1024` bytes. +The second section provides **Detailed Request and Response Content**, split +into Request and Response parts, and truncates content over `1024` bytes. For more precise traffic capture, such as HTTP traffic: @@ -41,31 +53,38 @@ For more precise traffic capture, such as HTTP traffic: You can narrow it down further to capture traffic for a specific HTTP path: ```bash -./kyanos watch http --path /abc +./kyanos watch http --path /abc ``` -Each protocol has different filtering options. For more details, see: [How to Capture Request-Response and Latency Details](./watch) +Each protocol has different filtering options. For more details, see: +[How to Capture Request-Response and Latency Details](./watch) ## Basic Usage of Aggregated Analysis with `stat` -In real-world scenarios, `watch` output is often too granular. Therefore, Kyanos offers the `stat` command for **statistical analysis**. +In real-world scenarios, `watch` output is often too granular. Therefore, Kyanos +offers the `stat` command for **statistical analysis**. -In short, `stat` can help answer questions like: Which connections have the highest request count? Which remote servers have the highest average latency? Which clients consume the most bandwidth? +In short, `stat` can help answer questions like: Which connections have the +highest request count? Which remote servers have the highest average latency? +Which clients consume the most bandwidth? -For example, to find remote servers with the highest average latency, use the `--slow` option to focus on latency. Like `watch`, `stat` can apply all filtering options supported by `watch`. Here, we’ll collect only HTTP requests with `PATH=/abc`: +For example, to find remote servers with the highest average latency, use the +`--slow` option to focus on latency. Like `watch`, `stat` can apply all +filtering options supported by `watch`. Here, we’ll collect only HTTP requests +with `PATH=/abc`: ```bash ./kyanos stat http --slow --path /abc ``` -By default, Kyanos will collect data for 10 seconds (modifiable with the `--time` option, or press `ctrl+c` to stop early): -![kyanos stat slow result](/qs-stat-slow.jpg) +By default, Kyanos will collect data for 10 seconds (modifiable with the +`--time` option, or press `ctrl+c` to stop early): +![kyanos stat slow result](/qs-stat-slow.jpg) After 10 seconds, the collected results are displayed in a table: - ```js{6-8} - Colleted events are here! + Colleted events are here! ┌──────────────────────────────────────────────────────────────────────────────────────────────┐ │ id remote-ip max(ms) avg(ms) p50(ms) p90(ms) p99(ms) count │// [!code focus] @@ -82,13 +101,25 @@ After 10 seconds, the collected results are displayed in a table: 1 sort by name • 2 sort by max • 3 sort by avg • 4 sort by p50 • 5 sort by p90 • 6 sort by p99 • 7 sort by count • 8 sort by total ``` -Each row in the `watch` output represents a single request-response, while `stat` aggregates request-responses by a specified dimension. +Each row in the `watch` output represents a single request-response, while +`stat` aggregates request-responses by a specified dimension. -In this example, since no specific dimension was set, **the remote server address (remote-ip)** is used as the default aggregation dimension (displayed in the second column). This means that request-responses from the same remote IP are aggregated together (though this is just one way to aggregate; for more options, refer to [Traffic Analysis](./stat)). +In this example, since no specific dimension was set, **the remote server +address (remote-ip)** is used as the default aggregation dimension (displayed in +the second column). This means that request-responses from the same remote IP +are aggregated together (though this is just one way to aggregate; for more +options, refer to [Traffic Analysis](./stat)). -Let's shift our focus to each column of the table: the `max` column shows the maximum latency among the aggregated request-responses for each remote IP, while the `avg` column shows the average latency, and so on. If an issue arises with a remote server, you can quickly identify the problematic server by comparing metrics for different remote IPs, such as noticing an anomaly for IP `169.254.0.4`. +Let's shift our focus to each column of the table: the `max` column shows the +maximum latency among the aggregated request-responses for each remote IP, while +the `avg` column shows the average latency, and so on. If an issue arises with a +remote server, you can quickly identify the problematic server by comparing +metrics for different remote IPs, such as noticing an anomaly for IP +`169.254.0.4`. -To view detailed request-response information for a specific remote IP, move cursor to that row and press `Enter` to access the list of request-responses for that remote-ip: +To view detailed request-response information for a specific remote IP, move +cursor to that row and press `Enter` to access the list of request-responses for +that remote-ip: ```js Events Num: 3 @@ -104,12 +135,20 @@ To view detailed request-response information for a specific remote IP, move cur ↑/k up • ↓/j down ``` -The format of the display here is actually the same as that shown by the `watch` command—each row represents a request-response record. You can further explore each record by pressing `Enter` to view detailed latency and content information for the selected request. +The format of the display here is actually the same as that shown by the `watch` +command—each row represents a request-response record. You can further explore +each record by pressing `Enter` to view detailed latency and content information +for the selected request. > [!TIP] -> The `stat` command offers powerful capabilities, so it’s highly recommended to explore other use cases in [How to Aggregate and Analyze](./stat). +> +> The `stat` command offers powerful capabilities, so it’s highly recommended to +> explore other use cases in [How to Aggregate and Analyze](./stat). ## Next Steps + To learn the details for each command: -- For the `watch` command, see: [How to Capture Request-Response and Latency Details](./watch) -- For the `stat` command, see: [How to Aggregate and Analyze](./stat) \ No newline at end of file + +- For the `watch` command, see: + [How to Capture Request-Response and Latency Details](./watch) +- For the `stat` command, see: [How to Aggregate and Analyze](./stat) diff --git a/docs/index.md b/docs/index.md index 7c9f53f7..77c562ab 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,7 +5,9 @@ layout: home hero: name: "Kyanos" text: "A Simple & Powerful Network Tracing Tool" - tagline: Visualize the time packets spend in the kernel, watch & analyze in command line. + tagline: + Visualize the time packets spend in the kernel, watch & analyze in command + line. image: src: /kyanos.png alt: Kyanos @@ -21,35 +23,47 @@ hero: link: https://github.com/hengyoush/kyanos features: - - icon: 🚀 - title: Easy-to-use - details: Focusing on the Layer 7 protocol, capture and analyze application layer network performance with a single command. - link: ./how-to - linkText: Learn how to use kyanos - - icon: 🎯️ - title: Advanced Traffic Filtering - details: Supports filtering traffic based on protocol fields (such as HTTP Path or Redis Command), process PID, container ID, and K8s Pod names. - link: ./watch#how-to-filter - linkText: Learn how to filter traffic - - icon: 📈️ - title: Powerful Aggregation Analysis - details: Supports automatic traffic aggregation based on various dimensions such as remote IP, protocol, etc., to quickly obtain specific information, such as the latency of certain HTTP paths from specific IPs. - link: ./stat - linkText: Learn how to analysis traffic - - icon: 💻️ - title: Container Network Monitoring - details: In containerized environments, it can measure the latency of packets from the container's network interface to the host's. - link: ./watch#filter-by-container - linkText: Capture container traffic - - icon: 📊️ - title: Intuitive TUI - details: Command-line based, offering visual output without the need for complex file downloads and analysis steps like tcpdump. - link: ./how-to - linkText: Learn how to use kyanos - - icon: 🌐️ - title: Lightweight and Compatible - details: Operates without any external dependencies and is compatible with kernel versions from 3.10 to the latest - link: ./quickstart#prerequire - linkText: Install kyanos + - icon: 🚀 + title: Easy-to-use + details: + Focusing on the Layer 7 protocol, capture and analyze application layer + network performance with a single command. + link: ./how-to + linkText: Learn how to use kyanos + - icon: 🎯️ + title: Advanced Traffic Filtering + details: + Supports filtering traffic based on protocol fields (such as HTTP Path or + Redis Command), process PID, container ID, and K8s Pod names. + link: ./watch#how-to-filter + linkText: Learn how to filter traffic + - icon: 📈️ + title: Powerful Aggregation Analysis + details: + Supports automatic traffic aggregation based on various dimensions such as + remote IP, protocol, etc., to quickly obtain specific information, such as + the latency of certain HTTP paths from specific IPs. + link: ./stat + linkText: Learn how to analysis traffic + - icon: 💻️ + title: Container Network Monitoring + details: + In containerized environments, it can measure the latency of packets from + the container's network interface to the host's. + link: ./watch#filter-by-container + linkText: Capture container traffic + - icon: 📊️ + title: Intuitive TUI + details: + Command-line based, offering visual output without the need for complex + file downloads and analysis steps like tcpdump. + link: ./how-to + linkText: Learn how to use kyanos + - icon: 🌐️ + title: Lightweight and Compatible + details: + Operates without any external dependencies and is compatible with kernel + versions from 3.10 to the latest + link: ./quickstart#prerequire + linkText: Install kyanos --- - diff --git a/docs/quickstart.md b/docs/quickstart.md index 4103b343..9b831965 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1,7 +1,7 @@ --- prev: - text: 'What is kyanos' - link: './what-is-kyanos' + text: "What is kyanos" + link: "./what-is-kyanos" next: false --- @@ -10,37 +10,46 @@ next: false ## Installation Requirements **Kernel Version Requirements** + - 3.x: Kernel version 3.10.0-957 and above - 4.x: Kernel version 4.14 and above - 5.x, 6.x: Fully supported **Architecture Support** + - amd64 - arm64 ## Installation and Running {#prerequire} -You can download a statically linked binary compatible with amd64 and arm64 architectures from the [release page](https://github.com/hengyoush/kyanos/releases): +You can download a statically linked binary compatible with amd64 and arm64 +architectures from the +[release page](https://github.com/hengyoush/kyanos/releases): ```bash tar xvf kyanos_vx.x.x_linux_amd64.tar.gz ``` Then, run kyanos with **root privilege**: + ```bash -sudo ./kyanos watch +sudo ./kyanos watch ``` If the following table appears: -![kyanos quick start success](/quickstart-success.png) -🎉 Congratulations! Kyanos has started successfully. +![kyanos quick start success](/quickstart-success.png) 🎉 Congratulations! +Kyanos has started successfully. > [!TIP] -> Did the command above fail? No worries——check the FAQ below to see if your situation is covered. If not, feel free to open a [GitHub issue](https://github.com/hengyoush/kyanos/issues)! +> +> Did the command above fail? No worries——check the FAQ below to see if your +> situation is covered. If not, feel free to open a +> [GitHub issue](https://github.com/hengyoush/kyanos/issues)! +## FAQ -## FAQ see:[FAQ](./faq) ## Next Steps -- For a quick guide on using Kyanos, see: [Learn Kyanos in 5 Minutes](./how-to) \ No newline at end of file + +- For a quick guide on using Kyanos, see: [Learn Kyanos in 5 Minutes](./how-to) diff --git a/docs/stat.md b/docs/stat.md index dfdc0295..1253b79b 100644 --- a/docs/stat.md +++ b/docs/stat.md @@ -1,94 +1,126 @@ --- next: false prev: - text: 'Watch Usage' - link: './watch' + text: "Watch Usage" + link: "./watch" --- # Use Stat Command to Analyze Traffic -The `watch` command provides a granular view of request-response pairs, which is useful for analyzing issues at a low level. However, some scenarios require a broader analysis, such as: +The `watch` command provides a granular view of request-response pairs, which is +useful for analyzing issues at a low level. However, some scenarios require a +broader analysis, such as: -- If my HTTP requests are slow or timeout, is every server slow, or just a specific one? -- If someone is sending `GET` requests (which value is big) to my Redis instance, causing bandwidth saturation, which client IP is responsible? +- If my HTTP requests are slow or timeout, is every server slow, or just a + specific one? +- If someone is sending `GET` requests (which value is big) to my Redis + instance, causing bandwidth saturation, which client IP is responsible? -The `stat` command is designed to address the need to analyze a large number of request-response pairs to derive conclusions. +The `stat` command is designed to address the need to analyze a large number of +request-response pairs to derive conclusions. ## How to Use the Stat Command -Using the `stat` command is straightforward; you just need to determine **what metric you care about**. +Using the `stat` command is straightforward; you just need to determine **what +metric you care about**. -For example, to solve the question: "If my HTTP requests are slow or timeout, is every server slow, or just a specific one?" so **the metric you care about** is the **response time** of **remote servers(remote-ip)**. +For example, to solve the question: "If my HTTP requests are slow or timeout, is +every server slow, or just a specific one?" so **the metric you care about** is +the **response time** of **remote servers(remote-ip)**. then you can use the following command: + ```bash ./kyanos stat --metric total-time --group-by remote-ip ``` -Here, the `--metric` option is set to `total-time`, indicating that we want to analyze the total time of the request-responses. The `--group-by` option is set to `remote-ip`, meaning we want to observe the response times grouped by each `remote-ip`. Kyanos will aggregate all request-responses with the same `remote-ip` and provide the relevant metrics for total time. + +Here, the `--metric` option is set to `total-time`, indicating that we want to +analyze the total time of the request-responses. The `--group-by` option is set +to `remote-ip`, meaning we want to observe the response times grouped by each +`remote-ip`. Kyanos will aggregate all request-responses with the same +`remote-ip` and provide the relevant metrics for total time. A shorter version of the command would be: + ```bash ./kyanos stat -m t -g remote-ip ``` -Here, `-m` is a shorthand for `metric`, `t` for `total-time`, and `-g` for `group-by`. + +Here, `-m` is a shorthand for `metric`, `t` for `total-time`, and `-g` for +`group-by`. > [!TIP] +> > **How to Filter Traffic?** -> The `stat` command supports all the filtering options available in the `watch` command. +> The `stat` command supports all the filtering options available in the `watch` +> command. ## Analyzing the Results of the Stat Command After entering the above `stat` command, you will see a table like this: ![kyanos stat result](/stat-result.jpg) -Like the `watch` table, you can sort the columns by pressing the corresponding number key. You can also navigate up and down using the `"↑"` `"↓"` or `"k"` `"j"` keys to select records in the table. +Like the `watch` table, you can sort the columns by pressing the corresponding +number key. You can also navigate up and down using the `"↑"` `"↓"` or `"k"` +`"j"` keys to select records in the table. -However, unlike the `watch` table, the records in the `stat` command are aggregated based on the `--group-by` option. Therefore, the second column is labeled `remote-ip`, with subsequent columns such as `max`, `avg`, `p50`, etc., representing the specified metric (in this case, `total-time`), showing the maximum, average, and 50th percentile values. - -Pressing `enter` allows you to dive into the specific request-responses for that `remote-ip`. This view mirrors the results from the `watch` command, so you can examine individual request-responses, their timings, and their content in the same manner. +However, unlike the `watch` table, the records in the `stat` command are +aggregated based on the `--group-by` option. Therefore, the second column is +labeled `remote-ip`, with subsequent columns such as `max`, `avg`, `p50`, etc., +representing the specified metric (in this case, `total-time`), showing the +maximum, average, and 50th percentile values. +Pressing `enter` allows you to dive into the specific request-responses for that +`remote-ip`. This view mirrors the results from the `watch` command, so you can +examine individual request-responses, their timings, and their content in the +same manner. ## Currently Supported Metrics -Kyanos currently supports the following metrics that can be specified with `--metric`: +Kyanos currently supports the following metrics that can be specified with +`--metric`: -| Metric | Short Flag | Long Flag | -| :------------------- | :--------- | :-------------- | -| Total Time | `t` | `total-time` | -| Response Size | `p` | `respsize` | -| Request Size | `q` | `reqsize` | -| Network Time | `n` | `network-time` | -| Internal Time | `i` | `internal-time` | -| Socket Read Time | `s` | `socket-time` | +| Metric | Short Flag | Long Flag | +| :--------------- | :--------- | :-------------- | +| Total Time | `t` | `total-time` | +| Response Size | `p` | `respsize` | +| Request Size | `q` | `reqsize` | +| Network Time | `n` | `network-time` | +| Internal Time | `i` | `internal-time` | +| Socket Read Time | `s` | `socket-time` | ## Currently Supported Grouping Methods -Kyanos supports the following grouping dimensions that can be specified with `--group-by`: +Kyanos supports the following grouping dimensions that can be specified with +`--group-by`: -| Grouping Dimension | Value | -| :------------------- | :---------- | -| Group by Connection | `conn` | -| Remote IP | `remote-ip` | -| Remote Port | `remote-port` | -| Local Port | `local-port` | -| L7 Protocol | `protocol` | -| HTTP Path | `http-path` | -| Redis Command | `redis-command` | -| Aggregate All | `none` | +| Grouping Dimension | Value | +| :------------------ | :-------------- | +| Group by Connection | `conn` | +| Remote IP | `remote-ip` | +| Remote Port | `remote-port` | +| Local Port | `local-port` | +| L7 Protocol | `protocol` | +| HTTP Path | `http-path` | +| Redis Command | `redis-command` | +| Aggregate All | `none` | ## What if You Can’t Remember These Options? -If you find it difficult to remember all these options, the `stat` command offers three quick options for analysis: +If you find it difficult to remember all these options, the `stat` command +offers three quick options for analysis: - `slow`: Analyze slow requests. - `bigreq`: Analyze large requests. - `bigresp`: Analyze large responses. -You can also use the --time option to specify the data collection period. For example, --time 10 will have the stat command collect traffic for 10 seconds. +You can also use the --time option to specify the data collection period. For +example, --time 10 will have the stat command collect traffic for 10 seconds. ![kyanos stat fast](/qs-stat-slow.jpg) -Once the collection is complete or if you press `ctrl+c` to stop early, you’ll see a table like this: +Once the collection is complete or if you press `ctrl+c` to stop early, you’ll +see a table like this: ![kyanos stat quick result](/stat-quick-result.jpg) @@ -97,6 +129,7 @@ From there, the operation proceeds in the same way as before. ### Analyzing Slow Requests To quickly identify which `remote-ip` has the slowest HTTP requests, use: + ```bash ./kyanos stat http --slow ``` @@ -104,11 +137,13 @@ To quickly identify which `remote-ip` has the slowest HTTP requests, use: ### Analyzing Large Requests and Responses To find which `remote-ip` has the largest requests, run: + ```bash ./kyanos stat http --bigreq ``` To identify which `remote-ip` has the largest responses, use: + ```bash ./kyanos stat http --bigresp ``` diff --git a/docs/watch.md b/docs/watch.md index 68b99363..1875834a 100644 --- a/docs/watch.md +++ b/docs/watch.md @@ -1,18 +1,22 @@ --- prev: - text: 'Learn Kyanos in 5 Minutes' - link: './how-to' + text: "Learn Kyanos in 5 Minutes" + link: "./how-to" next: - text: 'Stat Usage' - link: './stat' + text: "Stat Usage" + link: "./stat" --- # Capturing Request-Response and Latency Details -Using the `watch` command, you can collect specific network traffic and parse them into request-response pairs, allowing you to: +Using the `watch` command, you can collect specific network traffic and parse +them into request-response pairs, allowing you to: - View detailed request-response content. -- Observe latency details, including key timestamps for when a request reaches the network interface, when a response reaches the network interface, when it arrives at the Socket buffer, and when the application process reads the response. +- Observe latency details, including key timestamps for when a request reaches + the network interface, when a response reaches the network interface, when it + arrives at the Socket buffer, and when the application process reads the + response. Let's start with a basic example: @@ -20,83 +24,103 @@ Let's start with a basic example: kyanos watch ``` -Since no filter is specified, `kyanos` will attempt to capture all traffic it can analyze. Currently, `kyanos` supports parsing three application-layer protocols: `HTTP`, `Redis`, and `MySQL`. +Since no filter is specified, `kyanos` will attempt to capture all traffic it +can analyze. Currently, `kyanos` supports parsing three application-layer +protocols: `HTTP`, `Redis`, and `MySQL`. When you execute this command, you’ll see a table like this: ![kyanos watch result](/watch-result.jpg) > [!TIP] -> By default, watch collects 100 request-response records. You can specify this using the `--max-records` option. +> +> By default, watch collects 100 request-response records. You can specify this +> using the `--max-records` option. Each column represents: -| Column Name | Description | Example | -|-------------------|-------------------------------------------------------------------------------------------------------------|---------------------------------------| -| id | Table's Sequence number | | -| Connection | The connection for this request-response | "10.0.4.9:44526 => 169.254.0.4:80" | -| Proto | Protocol used for the request-response | "HTTP" | -| TotalTime | Total time for this request-response, in milliseconds | | -| ReqSize | Request size, in bytes | | -| RespSize | Response size, in bytes | | -| Net/Internal | If send request as a client, it shows network latency; if received as a server, it shows internal processing time | | -| ReadSocketTime | For client, time spent reading the response from the Socket buffer; for server , reading requests time from the buffer | | - -You can sort by column using the number keys and navigate through records using the `"↑"`/`"↓"` or `"k"`/`"j"` keys. Pressing `Enter` opens the details view for a specific request-response: +| Column Name | Description | Example | +| -------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | +| id | Table's Sequence number | | +| Connection | The connection for this request-response | "10.0.4.9:44526 => 169.254.0.4:80" | +| Proto | Protocol used for the request-response | "HTTP" | +| TotalTime | Total time for this request-response, in milliseconds | | +| ReqSize | Request size, in bytes | | +| RespSize | Response size, in bytes | | +| Net/Internal | If send request as a client, it shows network latency; if received as a server, it shows internal processing time | | +| ReadSocketTime | For client, time spent reading the response from the Socket buffer; for server , reading requests time from the buffer | | + +You can sort by column using the number keys and navigate through records using +the `"↑"`/`"↓"` or `"k"`/`"j"` keys. Pressing `Enter` opens the details view for +a specific request-response: ![kyanos watch result detail](/watch-result-detail.jpg) -In the details view, the first section shows **latency details** with each block representing a step in the data packet's journey—such as the process, network card, and Socket buffer. -Each block displays the time taken between these points, allowing you to trace the flow from when a request is sent by the process to when a response is received, with step-by-step latency. - -The second section contains the **request and response content**, split into Request and Response parts. Content exceeding 1024 bytes is truncated, but you can adjust this limit using the `--max-print-bytes` option. +In the details view, the first section shows **latency details** with each block +representing a step in the data packet's journey—such as the process, network +card, and Socket buffer. +Each block displays the time taken between these points, allowing you to trace +the flow from when a request is sent by the process to when a response is +received, with step-by-step latency. +The second section contains the **request and response content**, split into +Request and Response parts. Content exceeding 1024 bytes is truncated, but you +can adjust this limit using the `--max-print-bytes` option. ## How to Filter Requests and Responses ? {#how-to-filter} -By default, `kyanos` captures all traffic for the protocols it currently supports. However, in many scenarios, you might need to filter more precisely. For example, you may want to focus on requests sent to a specific remote port, or related to a certain process or container, or queries tied to specific Redis commands or HTTP paths. -Below are the ways to use `kyanos` options to filter request-responses you're interested in. +By default, `kyanos` captures all traffic for the protocols it currently +supports. However, in many scenarios, you might need to filter more precisely. +For example, you may want to focus on requests sent to a specific remote port, +or related to a certain process or container, or queries tied to specific Redis +commands or HTTP paths. +Below are the ways to use `kyanos` options to filter request-responses you're +interested in. ### Filtering by IP and Port -`kyanos` supports filtering based on IP and port at the network layer (Layer 3/4). You can specify the following options: +`kyanos` supports filtering based on IP and port at the network layer (Layer +3/4). You can specify the following options: -| Filter Condition | Command Line Flag | Example | -|-------------------------|---------------------------|-------------------------------------------------------------------------| -| Local Connection Ports | `local-ports` | `--local-ports 6379,16379`
Only observe request-responses on local ports 6379 and 16379. | -| Remote Connection Ports | `remote-ports` | `--remote-ports 6379,16379`
Only observe request-responses on remote ports 6379 and 16379. | -| Remote IP Addresses | `remote-ips` | `--remote-ips 10.0.4.5,10.0.4.2`
Only observe request-responses from remote IPs 10.0.4.5 and 10.0.4.2. | -| Client/Server side | `side` | `--side client/server`
Only observe requests and responses when acting as a client initiating connections or as a server receiving connections. | +| Filter Condition | Command Line Flag | Example | +| ----------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| Local Connection Ports | `local-ports` | `--local-ports 6379,16379`
Only observe request-responses on local ports 6379 and 16379. | +| Remote Connection Ports | `remote-ports` | `--remote-ports 6379,16379`
Only observe request-responses on remote ports 6379 and 16379. | +| Remote IP Addresses | `remote-ips` | `--remote-ips 10.0.4.5,10.0.4.2`
Only observe request-responses from remote IPs 10.0.4.5 and 10.0.4.2. | +| Client/Server side | `side` | `--side client/server`
Only observe requests and responses when acting as a client initiating connections or as a server receiving connections. | ### Filtering by Process/Container {#filter-by-container} -| Filter Condition | Command Line Flag | Example | -|---------------------------|---------------------------|-------------------------------------------------------------------------| -| Process PID List | `pids` | `--pids 12345,12346`
Separate multiple PIDs with commas. | -| Process Name | `comm` | `--comm 'curl'` | -| Container ID | `container-id` | `--container-id xx`
Specify the container ID. | -| Container Name | `container-name` | `--container-name foobar`
Specify the container name. | -| Kubernetes Pod Name | `pod-name` | `--pod-name nginx-7bds23212-23s1s.default`
Format: NAME.NAMESPACE | +| Filter Condition | Command Line Flag | Example | +| ------------------- | ----------------- | ---------------------------------------------------------------------- | +| Process PID List | `pids` | `--pids 12345,12346`
Separate multiple PIDs with commas. | +| Process Name | `comm` | `--comm 'curl'` | +| Container ID | `container-id` | `--container-id xx`
Specify the container ID. | +| Container Name | `container-name` | `--container-name foobar`
Specify the container name. | +| Kubernetes Pod Name | `pod-name` | `--pod-name nginx-7bds23212-23s1s.default`
Format: NAME.NAMESPACE | -It's worth mentioning that `kyanos` also displays latency between the container network card and the host network card: -![kyanos time detail](/timedetail.jpg) +It's worth mentioning that `kyanos` also displays latency between the container +network card and the host network card: ![kyanos time detail](/timedetail.jpg) ### Filtering by Request-Response General Information -| Filter Condition | Command Line Flag | Example | -|--------------------------|---------------------------|-------------------------------------------------------------------------| -| Request-Response Latency | `latency` | `--latency 100`
Only observe request-responses that exceed 100ms in latency. | -| Request Size in Bytes | `req-size` | `--req-size 1024`
Only observe request-responses larger than 1024 bytes. | -| Response Size in Bytes | `resp-size` | `--resp-size 1024`
Only observe request-responses larger than 1024 bytes. | +| Filter Condition | Command Line Flag | Example | +| ------------------------ | ----------------- | --------------------------------------------------------------------------------- | +| Request-Response Latency | `latency` | `--latency 100`
Only observe request-responses that exceed 100ms in latency. | +| Request Size in Bytes | `req-size` | `--req-size 1024`
Only observe request-responses larger than 1024 bytes. | +| Response Size in Bytes | `resp-size` | `--resp-size 1024`
Only observe request-responses larger than 1024 bytes. | ### Filtering by Protocol-Specific Information -You can choose to capture only request-responses for a specific protocol by adding the protocol name as subcommand. The currently supported protocols are: +You can choose to capture only request-responses for a specific protocol by +adding the protocol name as subcommand. The currently supported protocols are: - `http` - `redis` - `mysql` -For example, to capture only HTTP requests to the path `/foo/bar`, you would run: +For example, to capture only HTTP requests to the path `/foo/bar`, you would +run: + ```bash kyanos watch http --path /foo/bar ``` @@ -106,7 +130,7 @@ Here are the options available for filtering by each protocol: #### HTTP Protocol Filtering | Filter Condition | Command Line Flag | Example | -|---------------------|-------------------|------------------------------------------------------------------------------------------------------------| +| ------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------- | | Request Path | `path` | `--path /foo/bar`
Only observe requests with the path `/foo/bar`. | | Request Path Prefix | `path-prefix` | `--path-prefix /foo/bar`
Only observe requests with paths started with `/foo/bar`. | | Request Path Regex | `path-regex` | `--path-regex "\/foo\/bar\/.*"`
Only observe requests with paths matching the regex `\/foo\/bar\/.*`. | @@ -115,22 +139,26 @@ Here are the options available for filtering by each protocol: #### Redis Protocol Filtering -| Filter Condition | Command Line Flag | Example | -|------------------|-------------------|---------------------------------------------------------| -| Request Command | `command` | `--command GET,SET`
Only observe requests with the commands `GET` and `SET`. | -| Request Key | `keys` | `--keys foo,bar`
Only observe requests with the keys `foo` and `bar`. | -| Request Key Prefix | `key-prefix` | `--key-prefix foo:bar`
Only observe requests with keys that have the prefix `foo:bar`. | +| Filter Condition | Command Line Flag | Example | +| ------------------ | ----------------- | ------------------------------------------------------------------------------------------- | +| Request Command | `command` | `--command GET,SET`
Only observe requests with the commands `GET` and `SET`. | +| Request Key | `keys` | `--keys foo,bar`
Only observe requests with the keys `foo` and `bar`. | +| Request Key Prefix | `key-prefix` | `--key-prefix foo:bar`
Only observe requests with keys that have the prefix `foo:bar`. | #### MySQL Protocol Filtering -> MySQL protocol capturing is supported, but filtering by conditions is still in development... +> MySQL protocol capturing is supported, but filtering by conditions is still in +> development... --- > [!TIP] +> > All of the above options can be combined. For example: + ```bash ./kyanos watch redis --keys foo,bar --remote-ports 6379 --pid 12345 -``` +``` -This flexibility allows you to tailor your traffic capture to your specific needs, ensuring you gather only the most relevant request-response data. \ No newline at end of file +This flexibility allows you to tailor your traffic capture to your specific +needs, ensuring you gather only the most relevant request-response data. diff --git a/docs/what-is-kyanos.md b/docs/what-is-kyanos.md index e918f1a7..dda30de3 100644 --- a/docs/what-is-kyanos.md +++ b/docs/what-is-kyanos.md @@ -1,29 +1,48 @@ --- next: - text: 'Quickstart' - link: './quickstart' + text: "Quickstart" + link: "./quickstart" prev: false --- # What is kyanos ?{#what-is-kyanos} -Kyanos is a Network Traffic Analyzer that provides real-time, packet-level to protocol-level visibility into a host's internal network, capturing and analyzing all inbound and outbound traffic. +Kyanos is a Network Traffic Analyzer that provides real-time, packet-level to +protocol-level visibility into a host's internal network, capturing and +analyzing all inbound and outbound traffic. ## Why Kyanos? -> There are already many network troubleshooting tools available, such as tcpdump, iftop, and netstat. So, what benefits does Kyanos offer? +> There are already many network troubleshooting tools available, such as +> tcpdump, iftop, and netstat. So, what benefits does Kyanos offer? ### Drawbacks of Traditional Packet Capture with tcpdump -1. **Difficulty filtering based on protocol-specific information**: For example, in the case of the HTTP protocol, it's challenging to capture packets based on a specific HTTP path, requiring tools like Wireshark/tshark for secondary filtering. -2. **Difficulty filtering packets based on the sending or receiving process/container**: especially when multiple processes or containers are deployed on a single machine and you only need to capture packets for a specific process/container. -3. **Low troubleshooting efficiency**: The typical troubleshooting process involves using tcpdump in the production environment to capture packets and generate a pcap file, then downloading it locally for analysis with tools like Wireshark/tshark, often consuming a significant amount of time. -4. **Limited analysis capabilities**: Tcpdump only provides basic packet capture capabilities with minimal advanced analysis, requiring pairing with Wireshark. Traditional network monitoring tools like iftop and netstat offer only coarse-grained monitoring, making it challenging to identify root causes. -5. **Lacking the functionality to analyze encrypted traffic**: such as SSL protocol requests, cannot be viewed in plain text. +1. **Difficulty filtering based on protocol-specific information**: For example, + in the case of the HTTP protocol, it's challenging to capture packets based + on a specific HTTP path, requiring tools like Wireshark/tshark for secondary + filtering. +2. **Difficulty filtering packets based on the sending or receiving + process/container**: especially when multiple processes or containers are + deployed on a single machine and you only need to capture packets for a + specific process/container. +3. **Low troubleshooting efficiency**: The typical troubleshooting process + involves using tcpdump in the production environment to capture packets and + generate a pcap file, then downloading it locally for analysis with tools + like Wireshark/tshark, often consuming a significant amount of time. +4. **Limited analysis capabilities**: Tcpdump only provides basic packet capture + capabilities with minimal advanced analysis, requiring pairing with + Wireshark. Traditional network monitoring tools like iftop and netstat offer + only coarse-grained monitoring, making it challenging to identify root + causes. +5. **Lacking the functionality to analyze encrypted traffic**: such as SSL + protocol requests, cannot be viewed in plain text. ### What Kyanos Can Offer You -1. **Powerful Traffic Filtering**: Not only can filter based on traditional IP/port information, can also filter by process/container, L7 protocol information, request/response byte size, latency, and more. +1. **Powerful Traffic Filtering**: Not only can filter based on traditional + IP/port information, can also filter by process/container, L7 protocol + information, request/response byte size, latency, and more. ```bash # Filter by pid @@ -36,54 +55,83 @@ Kyanos is a Network Traffic Analyzer that provides real-time, packet-level to pr ./kyanos watch --resp-size 10000 ``` -2. **Advanced Analysis Capabilities** : Unlike tcpdump, which only provides fine-grained packet capture, Kyanos supports aggregating captured packet metrics across various dimensions, quickly providing the critical data most useful for troubleshooting. -Imagine if the bandwidth of your HTTP service is suddenly maxed out—how would you quickly analyze `which IPs` and `which requests` are causing it? -With Kyanos, you just need one command: `kyanos stat http --bigresp` to find the largest response byte sizes sent to remote IPs and view specific data on request and response metrics. -![kyanos find big response](/whatkyanos.gif) +2. **Advanced Analysis Capabilities** : Unlike tcpdump, which only provides + fine-grained packet capture, Kyanos supports aggregating captured packet + metrics across various dimensions, quickly providing the critical data most + useful for troubleshooting. + Imagine if the bandwidth of your HTTP service is suddenly maxed out—how would + you quickly analyze `which IPs` and `which requests` are causing it? + With Kyanos, you just need one command: `kyanos stat http --bigresp` to find + the largest response byte sizes sent to remote IPs and view specific data on + request and response metrics. + ![kyanos find big response](/whatkyanos.gif) -3. **In-Depth Kernel-Level Latency Details**: In real-world, slow queries to remote services like Redis can be challenging to diagnose precisely. Kyanos provides kernel trace points from the arrival of requests/responses at the network card to the kernel socket buffer, displaying these details in a visual format. This allows you to identify exactly which stage is causing delays. +3. **In-Depth Kernel-Level Latency Details**: In real-world, slow queries to + remote services like Redis can be challenging to diagnose precisely. Kyanos + provides kernel trace points from the arrival of requests/responses at the + network card to the kernel socket buffer, displaying these details in a + visual format. This allows you to identify exactly which stage is causing + delays. -![kyanos time detail](/timedetail.jpg) +![kyanos time detail](/timedetail.jpg) -4. **Lightweight and Dependency-Free**: Almost zero dependencies—just a single binary file and one command, with all results displayed in the command line. +4. **Lightweight and Dependency-Free**: Almost zero dependencies—just a single + binary file and one command, with all results displayed in the command line. -5. **Automatic SSL Traffic Decryption** : All captured requests and responses are presented in plaintext. +5. **Automatic SSL Traffic Decryption** : All captured requests and responses + are presented in plaintext. ## When to Use Kyanos {#use-cases} - **Capture Request and Response** -Kyanos provides the **watch** command, allowing you to filter and capture various traffic types. It supports filtering based on process ID, container ID, container name, pod name, as well as IP and port. Additionally, you can filter based on protocol-specific fields, such as HTTP paths, Redis commands, and keys. The captured traffic includes not only the request and response content but also detailed timing information, such as the time taken for requests to go from system calls to the network card and for responses to travel from the network card to the socket buffer and then to the process. +Kyanos provides the **watch** command, allowing you to filter and capture +various traffic types. It supports filtering based on process ID, container ID, +container name, pod name, as well as IP and port. Additionally, you can filter +based on protocol-specific fields, such as HTTP paths, Redis commands, and keys. +The captured traffic includes not only the request and response content but also +detailed timing information, such as the time taken for requests to go from +system calls to the network card and for responses to travel from the network +card to the socket buffer and then to the process. - **Analyze Abnormal Flow Path** -Kyanos’s stat command can help you quickly identify abnormal links. The stat command supports aggregation across multiple dimensions. +Kyanos’s stat command can help you quickly identify abnormal links. The stat +command supports aggregation across multiple dimensions. -For example, it can aggregate by remote IP, allowing you to quickly analyze which remote IP is slower. Kyanos also supports various metrics, such as request-response latency and request-response size. With these features, you can resolve 80% of network issues quickly. +For example, it can aggregate by remote IP, allowing you to quickly analyze +which remote IP is slower. Kyanos also supports various metrics, such as +request-response latency and request-response size. With these features, you can +resolve 80% of network issues quickly. - **Global Dependency Analysis** -Sometimes, you may need to know which external resources a machine depends on. Kyanos offers the `overview` command to capture all external resources a machine relies on and their latency in a single command. +Sometimes, you may need to know which external resources a machine depends on. +Kyanos offers the `overview` command to capture all external resources a machine +relies on and their latency in a single command. ## Basic Examples -**Capture HTTP Traffic with Latency Details** +**Capture HTTP Traffic with Latency Details** Run the command: + ```bash ./kyanos watch http ``` + The result is as follows: ![kyanos quick start watch http](/qs-watch-http.gif) - -**Capture Redis Traffic with Latency Details** +**Capture Redis Traffic with Latency Details** Run the command: + ```bash ./kyanos watch redis ``` + The result is as follows: ![kyanos quick start watch redis](/qs-redis.gif) @@ -91,9 +139,11 @@ The result is as follows: **Identify the Slowest Requests in the Last 5 Seconds** Run the command: + ```bash - ./kyanos stat --slow --time 5 + ./kyanos stat --slow --time 5 ``` + The result is as follows: -![kyanos stat slow](/qs-stat-slow.gif) \ No newline at end of file +![kyanos stat slow](/qs-stat-slow.gif) diff --git a/package-lock.json b/package-lock.json index 5960c0a0..ba8dcda3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,8 +4,11 @@ "requires": true, "packages": { "": { + "name": "kyanos", "devDependencies": { "cz-conventional-changelog": "^3.3.0", + "md-padding": "1.9.2", + "prettier": "3.4.2", "vitepress": "^1.4.1" } }, @@ -1940,6 +1943,21 @@ "node": ">= 10" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/clone": { "version": "1.0.4", "resolved": "https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz", @@ -2236,6 +2254,16 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2393,6 +2421,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", @@ -3051,6 +3089,20 @@ "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", "dev": true }, + "node_modules/md-padding": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/md-padding/-/md-padding-1.9.2.tgz", + "integrity": "sha512-i/ypG9AWvPja9TaVbkEJTAf86NMKMDNO5DkUd0ctO9uDbPeJ6gQfBnxkl6vBlCumE5lbpuH9u2jHBUJ3ordhMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "yargs": "^17.7.2" + }, + "bin": { + "md-padding": "dist/bin/md-padding.js", + "mdp": "dist/bin/md-padding.js" + } + }, "node_modules/mdast-util-to-hast": { "version": "13.2.0", "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", @@ -3229,9 +3281,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -3239,6 +3291,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -3496,6 +3549,22 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/property-information": { "version": "6.5.0", "resolved": "https://registry.npmmirror.com/property-information/-/property-information-6.5.0.tgz", @@ -3526,6 +3595,16 @@ "integrity": "sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ==", "dev": true }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", @@ -4219,6 +4298,45 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", @@ -5570,6 +5688,17 @@ "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, "clone": { "version": "1.0.4", "resolved": "https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz", @@ -5786,6 +5915,12 @@ "@esbuild/win32-x64": "0.21.5" } }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -5912,6 +6047,12 @@ "dev": true, "optional": true }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", @@ -6415,6 +6556,15 @@ "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", "dev": true }, + "md-padding": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/md-padding/-/md-padding-1.9.2.tgz", + "integrity": "sha512-i/ypG9AWvPja9TaVbkEJTAf86NMKMDNO5DkUd0ctO9uDbPeJ6gQfBnxkl6vBlCumE5lbpuH9u2jHBUJ3ordhMA==", + "dev": true, + "requires": { + "yargs": "^17.7.2" + } + }, "mdast-util-to-hast": { "version": "13.2.0", "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", @@ -6527,9 +6677,9 @@ "dev": true }, "nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true }, "once": { @@ -6703,6 +6853,12 @@ "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", "dev": true }, + "prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true + }, "property-information": { "version": "6.5.0", "resolved": "https://registry.npmmirror.com/property-information/-/property-information-6.5.0.tgz", @@ -6726,6 +6882,12 @@ "integrity": "sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ==", "dev": true }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", @@ -7200,6 +7362,33 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, "zwitch": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 4aea0ecd..4f08aebf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,8 @@ { "devDependencies": { "cz-conventional-changelog": "^3.3.0", + "md-padding": "1.9.2", + "prettier": "3.4.2", "vitepress": "^1.4.1" }, "config": { @@ -16,4 +18,4 @@ "docs:build": "vitepress build docs", "docs:preview": "vitepress preview docs" } -} \ No newline at end of file +} From f677d60c84b59982ecb5f5b14ac2b1c2388dc1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=83=88=E9=A6=99?= Date: Tue, 31 Dec 2024 01:02:55 +0800 Subject: [PATCH 04/28] fix: add fallback logic to calculate totaltime when nicin event missed in server side (#232) fix: add fallback logic to calculate totaltime when nicin event missed in server side (#232) --- agent/analysis/stat.go | 6 ++++++ agent/conn/conntrack.go | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/agent/analysis/stat.go b/agent/analysis/stat.go index 4e30e1c2..dbcb3df3 100644 --- a/agent/analysis/stat.go +++ b/agent/analysis/stat.go @@ -178,6 +178,12 @@ func (s *StatRecorder) ReceiveRecord(r protocol.Record, connection *conn.Connect if connection.IsServerSide() { if hasNicInEvents { annotatedRecord.StartTs = events.nicIngressEvents[0].GetTimestamp() + } else if hasTcpInEvents { + annotatedRecord.StartTs = events.tcpInEvents[0].GetTimestamp() + } else if hasUserCopyEvents { + annotatedRecord.StartTs = events.userCopyEvents[0].GetTimestamp() + } else if hasReadSyscallEvents { + annotatedRecord.StartTs = events.readSyscallEvents[0].GetTimestamp() } if hasDevOutEvents { annotatedRecord.EndTs = events.devOutEvents[len(events.devOutEvents)-1].GetTimestamp() diff --git a/agent/conn/conntrack.go b/agent/conn/conntrack.go index 51950471..c03cac30 100644 --- a/agent/conn/conntrack.go +++ b/agent/conn/conntrack.go @@ -433,7 +433,6 @@ func (c *Connection4) OnSyscallEvent(data []byte, event *bpf.SyscallEventData, r func (c *Connection4) parseStreamBuffer(streamBuffer *buffer.StreamBuffer, messageType protocol.MessageType, resultQueue *[]protocol.ParsedMessage, ke *bpf.AgentKernEvt) { parser := c.GetProtocolParser(c.Protocol) if parser == nil { - streamBuffer.Clear() return } if streamBuffer.IsEmpty() { From 537a08a2ff7017021843003aeeefe361ef617b62 Mon Sep 17 00:00:00 2001 From: Spencer Cai Date: Tue, 31 Dec 2024 12:29:22 +0800 Subject: [PATCH 05/28] test: introduce a script to test flag `--comm` (#222) --- .github/workflows/test.yml | 100 ++++++++++++++----------- testdata/https-request/http_request.go | 4 +- testdata/start_http_server.py | 27 +++++++ testdata/test_filter_by_comm.sh | 39 ++++++++++ 4 files changed, 125 insertions(+), 45 deletions(-) create mode 100644 testdata/start_http_server.py create mode 100644 testdata/test_filter_by_comm.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 748199b4..3c9dcc6c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,40 +18,40 @@ jobs: build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - submodules: recursive - - - name: Set up Go - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5 - with: - go-version: '1.22.4' - - - name: Build - run: | - sudo apt update - sudo apt install -y git - sudo apt-get -y install pkg-config - sudo apt install -y libelf-dev - - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - - sudo add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" - sudo apt update - - sudo apt install -y llvm - sudo apt install -y clang - pwd - ls -l - make clean && make build-bpf && make - - # - name: Test - # run: make test - - - name: Store executable - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4 - with: - name: kyanos - path: kyanos + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + submodules: recursive + + - name: Set up Go + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5 + with: + go-version: '1.22.4' + + - name: Build + run: | + sudo apt update + sudo apt install -y git + sudo apt-get -y install pkg-config + sudo apt install -y libelf-dev + + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - + sudo add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" + sudo apt update + + sudo apt install -y llvm + sudo apt install -y clang + pwd + ls -l + make clean && make build-bpf && make + + # - name: Test + # run: make test + + - name: Store executable + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4 + with: + name: kyanos + path: kyanos e2e-test: @@ -101,7 +101,7 @@ jobs: install-dependencies: 'true' cmd: | chmod +x /host/kyanos/kyanos - + - name: download btf file if: ${{ startsWith(matrix.kernel, '4.') }} run: | @@ -113,7 +113,7 @@ jobs: ls -la data/ find data/ -path "*vmlinuz*" -type f find data/ -path "*btf*" -type f - + - name: copy btf file if: ${{ startsWith(matrix.kernel, '4.') }} uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 @@ -125,7 +125,7 @@ jobs: cat /etc/os-release sudo mkdir -p /var/lib/kyanos/btf/ - + sudo cp /host/data/kernels/4.*/boot/btf-4.* /var/lib/kyanos/btf/current.btf # btf_file=$(find /host/ -path "*btf*" -type f) # sudo cp $btf_file /var/lib/ptcpdump/btf/vmlinux @@ -155,8 +155,22 @@ jobs: #install python pip sudo apt install -y python3 python3-pip pipx - - + - name: Test filter by comm + uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 + with: + provision: 'false' + cmd: | + set -euxo pipefail + uname -a + cat /etc/issue + pushd /host + if [ -f "/var/lib/kyanos/btf/current.btf" ]; then + bash /host/testdata/test_filter_by_comm.sh '/host/kyanos/kyanos $kyanos_log_option --btf /var/lib/kyanos/btf/current.btf' + else + bash /host/testdata/test_filter_by_comm.sh '/host/kyanos/kyanos $kyanos_log_option' + fi + popd + - name: Test gotls uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 with: @@ -172,7 +186,7 @@ jobs: bash /host/testdata/test_gotls.sh '/host/kyanos/kyanos $kyanos_log_option' fi popd - + - name: Test https uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 with: @@ -188,7 +202,7 @@ jobs: bash /host/testdata/test_https.sh '/host/kyanos/kyanos $kyanos_log_option' fi popd - + - name: Test side uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 with: @@ -263,7 +277,7 @@ jobs: else bash /host/testdata/test_kern_evt.sh '/host/kyanos/kyanos $kyanos_log_option' fi - + - name: Test test docker filter by container id uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 with: @@ -347,7 +361,7 @@ jobs: else bash /host/testdata/test_redis.sh '/host/kyanos/kyanos $kyanos_log_option' fi - + - name: Test k8s if: ${{ startsWith(matrix.kernel, '6.') }} uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 diff --git a/testdata/https-request/http_request.go b/testdata/https-request/http_request.go index 2445c825..9ece8812 100644 --- a/testdata/https-request/http_request.go +++ b/testdata/https-request/http_request.go @@ -2,7 +2,7 @@ package main import ( "fmt" - "io/ioutil" + "io" "net/http" "os" "strconv" @@ -42,7 +42,7 @@ func main() { } // 读取并打印响应内容 - body, err := ioutil.ReadAll(response.Body) + body, err := io.ReadAll(response.Body) if err != nil { fmt.Printf("Failed to read response %d: %v\n", i+1, err) response.Body.Close() diff --git a/testdata/start_http_server.py b/testdata/start_http_server.py new file mode 100644 index 00000000..0c186a84 --- /dev/null +++ b/testdata/start_http_server.py @@ -0,0 +1,27 @@ +import http.server +import ssl +from socketserver import ThreadingMixIn + +# 创建自定义的 HTTP 服务器类,支持线程以处理多个连接 +class ThreadedHTTPServer(ThreadingMixIn, http.server.HTTPServer): + # 设置 allow_reuse_address 以支持长连接 + allow_reuse_address = True + +class KeepAliveHandler(http.server.SimpleHTTPRequestHandler): + # 设置响应头以启用长连接 + + # 重写 `do_GET` 方法处理 GET 请求 + def do_GET(self): + self.send_response(200) + self.send_header("Content-type", "text/html") + self.send_header("Connection", "keep-alive") + self.send_header("Content-Length", str(len("Hello, this is an HTTP server with keep-alive support!"))) + self.end_headers() + self.wfile.write(b"Hello, this is an HTTP server with keep-alive support!") + +# 服务器地址和端口 +server_address = ('localhost', 8080) +httpd = ThreadedHTTPServer(server_address, KeepAliveHandler) + +print("HTTP server running on http://localhost:8080 with keep-alive support") +httpd.serve_forever() diff --git a/testdata/test_filter_by_comm.sh b/testdata/test_filter_by_comm.sh new file mode 100644 index 00000000..1833a505 --- /dev/null +++ b/testdata/test_filter_by_comm.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +. $(dirname "$0")/common.sh +set -ex + +CMD="$1" +FILE_PREFIX="/tmp/kyanos" +BEFORE_LNAME="${FILE_PREFIX}_filter_by_comm_before.log" +AFTER_LNAME="${FILE_PREFIX}_filter_by_comm_after.log" + +function test_filter_by_server_comm() { + # server start before kyanos + timeout 40 python3 ./testdata/start_http_server.py & + timeout 30 ${CMD} watch --debug-output http --comm python3 2>&1 | tee "${BEFORE_LNAME}" & + sleep 2 + timeout 25 ./testdata/https-request/https-request 'http://127.0.0.1:8080' 40 & + sleep 10 + wait + + cat "${BEFORE_LNAME}" + cat "${BEFORE_LNAME}" | grep "Host: 127.0.0.1:8080" | grep "\\[side\\]=server" +} + +# skip for https://github.com/hengyoush/kyanos/pull/222#issuecomment-2566106756 +function test_filter_by_client_comm() { + # client start after kyanos + timeout 40 ${CMD} watch --debug-output http --comm https-request 2>&1 | tee "${AFTER_LNAME}" & + sleep 10 + timeout 30 ./testdata/https-request/https-request 'http://ipinfo.io' 40 & + wait + + cat "${AFTER_LNAME}" + cat "${AFTER_LNAME}" | grep "Host: ipinfo.io" | grep "\\[side\\]=client" +} + +function main() { + test_filter_by_server_comm +} + +main \ No newline at end of file From fc80653a7fcc8e833d6a610668c94090c5e86133 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 31 Dec 2024 12:50:36 +0800 Subject: [PATCH 06/28] update --- bpf/agent_arm64_bpfel.go | 31 ++++++++++++++++--------------- bpf/agent_x86_bpfel.go | 32 ++++++++++++++++---------------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/bpf/agent_arm64_bpfel.go b/bpf/agent_arm64_bpfel.go index 811a8cf6..1a2877c5 100644 --- a/bpf/agent_arm64_bpfel.go +++ b/bpf/agent_arm64_bpfel.go @@ -172,21 +172,22 @@ const ( type AgentTrafficProtocolT uint32 const ( - AgentTrafficProtocolTKProtocolUnset AgentTrafficProtocolT = 0 - AgentTrafficProtocolTKProtocolUnknown AgentTrafficProtocolT = 1 - AgentTrafficProtocolTKProtocolHTTP AgentTrafficProtocolT = 2 - AgentTrafficProtocolTKProtocolHTTP2 AgentTrafficProtocolT = 3 - AgentTrafficProtocolTKProtocolMySQL AgentTrafficProtocolT = 4 - AgentTrafficProtocolTKProtocolCQL AgentTrafficProtocolT = 5 - AgentTrafficProtocolTKProtocolPGSQL AgentTrafficProtocolT = 6 - AgentTrafficProtocolTKProtocolDNS AgentTrafficProtocolT = 7 - AgentTrafficProtocolTKProtocolRedis AgentTrafficProtocolT = 8 - AgentTrafficProtocolTKProtocolNATS AgentTrafficProtocolT = 9 - AgentTrafficProtocolTKProtocolMongo AgentTrafficProtocolT = 10 - AgentTrafficProtocolTKProtocolKafka AgentTrafficProtocolT = 11 - AgentTrafficProtocolTKProtocolMux AgentTrafficProtocolT = 12 - AgentTrafficProtocolTKProtocolAMQP AgentTrafficProtocolT = 13 - AgentTrafficProtocolTKNumProtocols AgentTrafficProtocolT = 14 + AgentTrafficProtocolTKProtocolUnset AgentTrafficProtocolT = 0 + AgentTrafficProtocolTKProtocolUnknown AgentTrafficProtocolT = 1 + AgentTrafficProtocolTKProtocolHTTP AgentTrafficProtocolT = 2 + AgentTrafficProtocolTKProtocolHTTP2 AgentTrafficProtocolT = 3 + AgentTrafficProtocolTKProtocolMySQL AgentTrafficProtocolT = 4 + AgentTrafficProtocolTKProtocolCQL AgentTrafficProtocolT = 5 + AgentTrafficProtocolTKProtocolPGSQL AgentTrafficProtocolT = 6 + AgentTrafficProtocolTKProtocolDNS AgentTrafficProtocolT = 7 + AgentTrafficProtocolTKProtocolRedis AgentTrafficProtocolT = 8 + AgentTrafficProtocolTKProtocolNATS AgentTrafficProtocolT = 9 + AgentTrafficProtocolTKProtocolMongo AgentTrafficProtocolT = 10 + AgentTrafficProtocolTKProtocolKafka AgentTrafficProtocolT = 11 + AgentTrafficProtocolTKProtocolMux AgentTrafficProtocolT = 12 + AgentTrafficProtocolTKProtocolAMQP AgentTrafficProtocolT = 13 + AgentTrafficProtocolTKProtocolRocketMQ AgentTrafficProtocolT = 14 + AgentTrafficProtocolTKNumProtocols AgentTrafficProtocolT = 15 ) // LoadAgent returns the embedded CollectionSpec for Agent. diff --git a/bpf/agent_x86_bpfel.go b/bpf/agent_x86_bpfel.go index 1505d877..61a242c2 100644 --- a/bpf/agent_x86_bpfel.go +++ b/bpf/agent_x86_bpfel.go @@ -172,22 +172,22 @@ const ( type AgentTrafficProtocolT uint32 const ( - AgentTrafficProtocolTKProtocolUnset AgentTrafficProtocolT = 0 - AgentTrafficProtocolTKProtocolUnknown AgentTrafficProtocolT = 1 - AgentTrafficProtocolTKProtocolHTTP AgentTrafficProtocolT = 2 - AgentTrafficProtocolTKProtocolHTTP2 AgentTrafficProtocolT = 3 - AgentTrafficProtocolTKProtocolMySQL AgentTrafficProtocolT = 4 - AgentTrafficProtocolTKProtocolCQL AgentTrafficProtocolT = 5 - AgentTrafficProtocolTKProtocolPGSQL AgentTrafficProtocolT = 6 - AgentTrafficProtocolTKProtocolDNS AgentTrafficProtocolT = 7 - AgentTrafficProtocolTKProtocolRedis AgentTrafficProtocolT = 8 - AgentTrafficProtocolTKProtocolNATS AgentTrafficProtocolT = 9 - AgentTrafficProtocolTKProtocolMongo AgentTrafficProtocolT = 10 - AgentTrafficProtocolTKProtocolKafka AgentTrafficProtocolT = 11 - AgentTrafficProtocolTKProtocolMux AgentTrafficProtocolT = 12 - AgentTrafficProtocolTKProtocolAMQP AgentTrafficProtocolT = 13 - AgentTrafficProtocolTKProtocolRocketMQ AgentTrafficProtocolT = 14 - AgentTrafficProtocolTKNumProtocols AgentTrafficProtocolT = 15 + AgentTrafficProtocolTKProtocolUnset AgentTrafficProtocolT = 0 + AgentTrafficProtocolTKProtocolUnknown AgentTrafficProtocolT = 1 + AgentTrafficProtocolTKProtocolHTTP AgentTrafficProtocolT = 2 + AgentTrafficProtocolTKProtocolHTTP2 AgentTrafficProtocolT = 3 + AgentTrafficProtocolTKProtocolMySQL AgentTrafficProtocolT = 4 + AgentTrafficProtocolTKProtocolCQL AgentTrafficProtocolT = 5 + AgentTrafficProtocolTKProtocolPGSQL AgentTrafficProtocolT = 6 + AgentTrafficProtocolTKProtocolDNS AgentTrafficProtocolT = 7 + AgentTrafficProtocolTKProtocolRedis AgentTrafficProtocolT = 8 + AgentTrafficProtocolTKProtocolNATS AgentTrafficProtocolT = 9 + AgentTrafficProtocolTKProtocolMongo AgentTrafficProtocolT = 10 + AgentTrafficProtocolTKProtocolKafka AgentTrafficProtocolT = 11 + AgentTrafficProtocolTKProtocolMux AgentTrafficProtocolT = 12 + AgentTrafficProtocolTKProtocolAMQP AgentTrafficProtocolT = 13 + AgentTrafficProtocolTKProtocolRocketMQ AgentTrafficProtocolT = 14 + AgentTrafficProtocolTKNumProtocols AgentTrafficProtocolT = 15 ) // LoadAgent returns the embedded CollectionSpec for Agent. From d8f722cbe62683f445ca2017e32c2da3de47cae3 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Thu, 2 Jan 2025 09:44:12 +0800 Subject: [PATCH 07/28] update --- agent/protocol/rocketmq/rocketmq.go | 63 +++++++++++++++++++++-------- cmd/rocketmq.go | 1 - 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/agent/protocol/rocketmq/rocketmq.go b/agent/protocol/rocketmq/rocketmq.go index d418f2ef..34b99436 100644 --- a/agent/protocol/rocketmq/rocketmq.go +++ b/agent/protocol/rocketmq/rocketmq.go @@ -7,6 +7,7 @@ import ( "kyanos/agent/buffer" "kyanos/agent/protocol" "kyanos/bpf" + "kyanos/common" ) func init() { @@ -25,14 +26,24 @@ func (r *RocketMQMessage) IsReq() bool { func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, messageType protocol.MessageType) protocol.ParseResult { buffer := streamBuffer.Head().Buffer() + common.ProtocolParserLog.Debugf("ParseStream received buffer length: %d", len(buffer)) + common.ProtocolParserLog.Debugln("==============", buffer) + if len(buffer) < 8 { + common.ProtocolParserLog.Warn("Buffer too small for header, needs more data.") return protocol.ParseResult{ ParseState: protocol.NeedsMoreData, } } frameSize := int(binary.BigEndian.Uint32(buffer[:4])) + if frameSize <= 0 { + common.ProtocolParserLog.Warnf("Invalid frame size: %d", frameSize) + return protocol.ParseResult{ParseState: protocol.Invalid, ReadBytes: 4} + } + if frameSize > len(buffer) { + common.ProtocolParserLog.Debugf("Frame size %d exceeds buffer length %d, needs more data.", frameSize, len(buffer)) return protocol.ParseResult{ParseState: protocol.NeedsMoreData} } @@ -40,14 +51,17 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me headerDataLen := headerLength & 0xFFFFFF serializedType := byte((headerLength >> 24) & 0xFF) - if len(buffer) < 8+int(headerDataLen) { + if 8+int(headerDataLen) > frameSize || len(buffer) < 8+int(headerDataLen) { + common.ProtocolParserLog.Warnf("Incomplete header detected: headerDataLen=%d, frameSize=%d.", headerDataLen, frameSize) return protocol.ParseResult{ParseState: protocol.NeedsMoreData} } headerBody := buffer[8 : 8+headerDataLen] body := buffer[8+headerDataLen : frameSize] + message, err := r.parseHeader(headerBody, serializedType) if err != nil { + common.ProtocolParserLog.Errorf("Failed to parse header: %v", err) return protocol.ParseResult{ParseState: protocol.Invalid, ReadBytes: int(frameSize)} } @@ -56,18 +70,21 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me fb, ok := protocol.CreateFrameBase(streamBuffer, frameSize) if !ok { + common.ProtocolParserLog.Warnf("Failed to create FrameBase for frameSize=%d", frameSize) return protocol.ParseResult{ ParseState: protocol.Ignore, ReadBytes: frameSize, } - } else { - message.FrameBase = fb - return protocol.ParseResult{ - ParseState: protocol.Success, - ReadBytes: frameSize, - ParsedMessages: []protocol.ParsedMessage{message}, - } } + + common.ProtocolParserLog.Debugf("Successfully parsed message: %+v", message) + message.FrameBase = fb + return protocol.ParseResult{ + ParseState: protocol.Success, + ReadBytes: frameSize, + ParsedMessages: []protocol.ParsedMessage{message}, + } + } func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedType byte) (*RocketMQMessage, error) { @@ -102,20 +119,29 @@ func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedTyp } func (r *RocketMQStreamParser) FindBoundary(streamBuffer *buffer.StreamBuffer, messageType protocol.MessageType, startPos int) int { - buffer := streamBuffer.Head().Buffer()[startPos:] - for i := range buffer { - if len(buffer[i:]) < 8 { - return -1 + buffer := streamBuffer.Head().Buffer() + common.ProtocolParserLog.Debugf("FindBoundary starting at position: %d, buffer length: %d", startPos, len(buffer)) + + for i := startPos; i <= len(buffer)-8; i++ { + frameSize := int(binary.BigEndian.Uint32(buffer[i : i+4])) + + if frameSize <= 0 || frameSize > len(buffer)-i { + common.ProtocolParserLog.Warnf("Skipping invalid frameSize=%d at position=%d", frameSize, i) + continue } - frameSize := binary.BigEndian.Uint32(buffer[i : i+4]) - if int(frameSize) <= len(buffer[i:]) { - return startPos + i + + if i+frameSize <= len(buffer) { + common.ProtocolParserLog.Debugf("Found boundary at position=%d with frameSize=%d", i, frameSize) + return i } } + + common.ProtocolParserLog.Warn("No valid boundary found, returning -1.") return -1 } func (r *RocketMQStreamParser) Match(reqStream *[]protocol.ParsedMessage, respStream *[]protocol.ParsedMessage) []protocol.Record { + common.ProtocolParserLog.Debugf("Matching %d requests with %d responses.", len(*reqStream), len(*respStream)) records := []protocol.Record{} reqMap := make(map[int32]*RocketMQMessage) @@ -131,10 +157,15 @@ func (r *RocketMQStreamParser) Match(reqStream *[]protocol.ParsedMessage, respSt Req: req, Resp: resp, }) - delete(reqMap, resp.Opaque) + } else { + common.ProtocolParserLog.Warnf("No matching request found for response Opaque=%d", resp.Opaque) } } + if len(reqMap) > 0 { + common.ProtocolParserLog.Warnf("Unmatched requests remain: %d", len(reqMap)) + } + return records } diff --git a/cmd/rocketmq.go b/cmd/rocketmq.go index 69c19d7c..b44beb82 100644 --- a/cmd/rocketmq.go +++ b/cmd/rocketmq.go @@ -14,7 +14,6 @@ var rocketmqCmd *cobra.Command = &cobra.Command{ options.LatencyFilter = initLatencyFilter(cmd) options.SizeFilter = initSizeFilter(cmd) startAgent() - }, } From 6f3256309587ceabc7a136242f8b8af0dfebb73e Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Thu, 2 Jan 2025 15:19:47 +0800 Subject: [PATCH 08/28] update --- agent/protocol/rocketmq/rocketmq.go | 43 ++++++++++++-- bpf/common.go | 7 ++- bpf/protocol_inference.h | 87 ++++++++++++++++++++--------- 3 files changed, 105 insertions(+), 32 deletions(-) diff --git a/agent/protocol/rocketmq/rocketmq.go b/agent/protocol/rocketmq/rocketmq.go index 34b99436..4d2b2819 100644 --- a/agent/protocol/rocketmq/rocketmq.go +++ b/agent/protocol/rocketmq/rocketmq.go @@ -2,6 +2,7 @@ package rocketmq import ( "encoding/binary" + "encoding/json" "errors" "fmt" "kyanos/agent/buffer" @@ -17,6 +18,9 @@ func init() { } func (r *RocketMQMessage) FormatToString() string { + // return "RemotingCommand [code=" + code + ", language=" + language + ", version=" + version + ", opaque=" + opaque + ", flag(B)=" + // + Integer.toBinaryString(flag) + ", remark=" + remark + ", extFields=" + extFields + ", serializeTypeCurrentRPC=" + // + serializeTypeCurrentRPC + "]"; return fmt.Sprintf("base=[%s] command=[%s] payload=[%s]", r.FrameBase.String(), "todo", r.Body) } @@ -90,9 +94,35 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedType byte) (*RocketMQMessage, error) { fmt.Println(serializedType) message := &RocketMQMessage{} - if serializedType == 0 { + switch serializedType { + case 0: // json + var temp struct { + RequestCode int16 `json:"code"` + Language string `json:"language"` + VersionFlag int16 `json:"version"` + Opaque int32 `json:"opaque"` + RequestFlag int32 `json:"flag"` + Remark string `json:"remark,omitempty"` + Properties map[string]string `json:"extFields,omitempty"` + } + + if err := json.Unmarshal(headerBody, &temp); err != nil { + return nil, fmt.Errorf("failed to parse JSON header: %w", err) + } + + message.RequestCode = temp.RequestCode + // message.LanguageFlag = temp.LanguageFlag + message.VersionFlag = temp.VersionFlag + message.Opaque = temp.Opaque + message.RequestFlag = temp.RequestFlag + message.RemarkLength = int32(len(temp.Remark)) + message.Remark = []byte(temp.Remark) + message.PropertiesLen = int32(len(temp.Properties)) + // message.Properties = temp.Properties + + case 1: // custom if len(headerBody) < 18 { - return nil, errors.New("invalid header size") + return nil, errors.New("invalid header size for private serialization") } message.RequestCode = int16(binary.BigEndian.Uint16(headerBody[:2])) @@ -105,16 +135,21 @@ func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedTyp if int(message.RemarkLength) > len(headerBody[17:]) { return nil, errors.New("invalid remark length") } + message.Remark = headerBody[17 : 17+message.RemarkLength] + propertiesStart := 17 + message.RemarkLength if len(headerBody[propertiesStart:]) < 4 { return nil, errors.New("invalid properties length") } + message.PropertiesLen = int32(binary.BigEndian.Uint32(headerBody[propertiesStart:])) message.Properties = headerBody[propertiesStart+4 : propertiesStart+4+message.PropertiesLen] - } else { - return nil, errors.New("unsupported serialization type") + + default: + return nil, fmt.Errorf("unsupported serialization type: %d", serializedType) } + return message, nil } diff --git a/bpf/common.go b/bpf/common.go index 17515fc9..7d7d6b89 100644 --- a/bpf/common.go +++ b/bpf/common.go @@ -5,9 +5,10 @@ import ( ) var ProtocolNamesMap = map[AgentTrafficProtocolT]string{ - AgentTrafficProtocolTKProtocolHTTP: "HTTP", - AgentTrafficProtocolTKProtocolRedis: "Redis", - AgentTrafficProtocolTKProtocolMySQL: "MySQL", + AgentTrafficProtocolTKProtocolHTTP: "HTTP", + AgentTrafficProtocolTKProtocolRedis: "Redis", + AgentTrafficProtocolTKProtocolMySQL: "MySQL", + AgentTrafficProtocolTKProtocolRocketMQ: "RocketMQ", } var StepCNNames [AgentStepTEnd + 1]string = [AgentStepTEnd + 1]string{"开始", "SSLWrite", "系统调用(出)", "TCP层(出)", "IP层(出)", "QDISC", "DEV层(出)", "网卡(出)", "网卡(进)", "DEV层(进)", "IP层(进)", "TCP层(进)", "用户拷贝", "系统调用(进)", "SSLRead", "结束"} diff --git a/bpf/protocol_inference.h b/bpf/protocol_inference.h index 02252dc7..d03b3664 100644 --- a/bpf/protocol_inference.h +++ b/bpf/protocol_inference.h @@ -9,25 +9,28 @@ // +---------+---------+---------+---------+ // | | // . ... body ... . -// . . +// . . // . . // +---------------------------------------- -static __always_inline int is_mysql_protocol(const char *old_buf, size_t count, struct conn_info_t *conn_info) { +static __always_inline int is_mysql_protocol(const char *old_buf, size_t count, + struct conn_info_t *conn_info) { static const uint8_t kComQuery = 0x03; static const uint8_t kComConnect = 0x0b; static const uint8_t kComStmtPrepare = 0x16; static const uint8_t kComStmtExecute = 0x17; static const uint8_t kComStmtClose = 0x19; - // Second statement checks whether suspected header matches the length of current packet. - bool use_prev_buf = (conn_info->prev_count == 4) && (*((uint32_t*)conn_info->prev_buf) == count); + // Second statement checks whether suspected header matches the length of + // current packet. + bool use_prev_buf = (conn_info->prev_count == 4) && + (*((uint32_t *)conn_info->prev_buf) == count); // if (conn_info->prev_count == 4) { // bpf_printk("prevbuf: %d", (*((uint32_t*)conn_info->prev_buf))); // } if (use_prev_buf) { - - // Check the header_state to find out if the header has been read. MySQL server tends to - // read in the 4 byte header and the rest of the packet in a separate read. + // Check the header_state to find out if the header has been read. MySQL + // server tends to read in the 4 byte header and the rest of the packet in a + // separate read. count += 4; } @@ -37,12 +40,13 @@ static __always_inline int is_mysql_protocol(const char *old_buf, size_t count, return kUnknown; } - // Convert 3-byte length to uint32_t. But since the 4th byte is supposed to be \x00, directly - // casting 4-bytes is correct. - // NOLINTNEXTLINE: readability/casting + // Convert 3-byte length to uint32_t. But since the 4th byte is supposed to be + // \x00, directly casting 4-bytes is correct. NOLINTNEXTLINE: + // readability/casting char buf[5] = {}; bpf_probe_read_user(buf, 5, old_buf); - uint32_t len = use_prev_buf ? *((uint32_t*)conn_info->prev_buf) : *((uint32_t*)buf); + uint32_t len = + use_prev_buf ? *((uint32_t *)conn_info->prev_buf) : *((uint32_t *)buf); len = len & 0x00ffffff; uint8_t seq = use_prev_buf ? conn_info->prev_buf[3] : buf[3]; @@ -58,27 +62,28 @@ static __always_inline int is_mysql_protocol(const char *old_buf, size_t count, return kUnknown; } - // Assuming that the length of a request is less than 10k characters to avoid false - // positive flagging as MySQL, which statistically happens frequently for a single-byte - // check. + // Assuming that the length of a request is less than 10k characters to avoid + // false positive flagging as MySQL, which statistically happens frequently + // for a single-byte check. if (len > 10000) { return kUnknown; } // TODO(oazizi): Consider adding more commands (0x00 to 0x1f). // Be careful, though: trade-off is higher rates of false positives. - if (com == kComConnect || com == kComQuery || com == kComStmtPrepare || com == kComStmtExecute || - com == kComStmtClose) { + if (com == kComConnect || com == kComQuery || com == kComStmtPrepare || + com == kComStmtExecute || com == kComStmtClose) { return kRequest; } return kUnknown; } -static __always_inline int is_redis_protocol(const char *old_buf, size_t count) { +static __always_inline int is_redis_protocol(const char *old_buf, + size_t count) { if (count < 3) { return false; } - + char buf[1] = {}; bpf_probe_read_user(buf, 1, old_buf); const char first_byte = buf[0]; @@ -106,7 +111,8 @@ static __always_inline int is_redis_protocol(const char *old_buf, size_t count) return true; } -static __always_inline enum message_type_t is_http_protocol(const char *old_buf, size_t count) { +static __always_inline enum message_type_t is_http_protocol(const char *old_buf, + size_t count) { if (count < 5) { return 0; } @@ -127,9 +133,10 @@ static __always_inline enum message_type_t is_http_protocol(const char *old_buf, return kUnknown; } -static __always_inline enum message_type_t is_rocketmq_protocol(const char *old_buf, size_t count) { - if (count < 8) { - return 0; +static __always_inline enum message_type_t is_rocketmq_protocol( + const char *old_buf, size_t count) { + if (count < 12) { + return kUnknown; } int32_t frame_size = 0; @@ -146,20 +153,50 @@ static __always_inline enum message_type_t is_rocketmq_protocol(const char *old_ return kUnknown; } + int32_t header_length = 0; + bpf_probe_read_user(&header_length, sizeof(int32_t), old_buf + 4); + + int32_t header_data_len = header_length & 0xFFFFFF; + if (header_data_len <= 0 || header_data_len > count - 8) { + return kUnknown; + } + + if (serialized_type == 0x0) { // json format + // TODO + } else if (serialized_type == 0x1) { + uint16_t request_code = 0; + uint8_t l_flag = 0; + uint16_t v_flag = 0; + + bpf_probe_read_user(&request_code, sizeof(uint16_t), old_buf + 8); + bpf_probe_read_user(&l_flag, sizeof(uint8_t), old_buf + 10); + bpf_probe_read_user(&v_flag, sizeof(uint16_t), old_buf + 11); + + // rocketmq/remoting/protocol/RequestCode.java + if (request_code < 10) { + return kUnknown; + } + + if (l_flag > 13) { + return kUnknown; + } + } return kRequest; } -static __always_inline struct protocol_message_t infer_protocol(const char *buf, size_t count, struct conn_info_t *conn_info) { +static __always_inline struct protocol_message_t infer_protocol( + const char *buf, size_t count, struct conn_info_t *conn_info) { struct protocol_message_t protocol_message; protocol_message.protocol = kProtocolUnknown; protocol_message.type = kUnknown; if ((protocol_message.type = is_http_protocol(buf, count)) != kUnknown) { protocol_message.protocol = kProtocolHTTP; - } else if ((protocol_message.type = is_mysql_protocol(buf, count, conn_info)) != kUnknown) { + } else if ((protocol_message.type = + is_mysql_protocol(buf, count, conn_info)) != kUnknown) { protocol_message.protocol = kProtocolMySQL; } else if (is_redis_protocol(buf, count)) { protocol_message.protocol = kProtocolRedis; - } else if (is_rocketmq_protocol(buf,count)) { + } else if (is_rocketmq_protocol(buf, count)) { protocol_message.protocol = kProtocolRocketMQ; } conn_info->prev_count = count; From 541008b6748eaaa4ac8a460bc37a48de89b5e059 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Thu, 2 Jan 2025 15:52:28 +0800 Subject: [PATCH 09/28] update --- bpf/protocol_inference.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bpf/protocol_inference.h b/bpf/protocol_inference.h index d03b3664..40126739 100644 --- a/bpf/protocol_inference.h +++ b/bpf/protocol_inference.h @@ -135,12 +135,13 @@ static __always_inline enum message_type_t is_http_protocol(const char *old_buf, static __always_inline enum message_type_t is_rocketmq_protocol( const char *old_buf, size_t count) { - if (count < 12) { + if (count < 16) { return kUnknown; } int32_t frame_size = 0; bpf_probe_read_user(&frame_size, sizeof(int32_t), old_buf); + bpf_printk("frame_size: %d", frame_size); if (frame_size <= 0 || frame_size > 64 * 1024 * 1024) { return kUnknown; @@ -162,7 +163,12 @@ static __always_inline enum message_type_t is_rocketmq_protocol( } if (serialized_type == 0x0) { // json format - // TODO + if (old_buf[8] != '{' || old_buf[9] != '"' || old_buf[10] != 'c' || + old_buf[11] != 'o' || old_buf[12] != 'd' || old_buf[13] != 'e' || + old_buf[14] != '"' || old_buf[15] != ':') { + // {"code": + return kUnknown; + } } else if (serialized_type == 0x1) { uint16_t request_code = 0; uint8_t l_flag = 0; From 63345cfbfbbea1c2c948646f952c26b499c4b565 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Thu, 2 Jan 2025 15:55:23 +0800 Subject: [PATCH 10/28] update --- bpf/protocol_inference.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bpf/protocol_inference.h b/bpf/protocol_inference.h index 40126739..5efd2669 100644 --- a/bpf/protocol_inference.h +++ b/bpf/protocol_inference.h @@ -147,25 +147,25 @@ static __always_inline enum message_type_t is_rocketmq_protocol( return kUnknown; } - char serialized_type = 0; - bpf_probe_read_user(&serialized_type, 1, old_buf + 4); + int32_t header_length = 0; + bpf_probe_read_user(&header_length, sizeof(int32_t), old_buf + 4); + char serialized_type = (header_length >> 24) & 0xFF; if (serialized_type != 0x0 && serialized_type != 0x1) { return kUnknown; } - int32_t header_length = 0; - bpf_probe_read_user(&header_length, sizeof(int32_t), old_buf + 4); - int32_t header_data_len = header_length & 0xFFFFFF; if (header_data_len <= 0 || header_data_len > count - 8) { return kUnknown; } if (serialized_type == 0x0) { // json format - if (old_buf[8] != '{' || old_buf[9] != '"' || old_buf[10] != 'c' || - old_buf[11] != 'o' || old_buf[12] != 'd' || old_buf[13] != 'e' || - old_buf[14] != '"' || old_buf[15] != ':') { + char buf[8] = {}; + bpf_probe_read_user(buf, 8, old_buf + 8); + if (old_buf[0] != '{' || old_buf[1] != '"' || old_buf[2] != 'c' || + old_buf[3] != 'o' || old_buf[4] != 'd' || old_buf[5] != 'e' || + old_buf[6] != '"' || old_buf[7] != ':') { // {"code": return kUnknown; } From 2e2261764c173196dd83334833ba8a7e8c19554e Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Thu, 2 Jan 2025 16:02:00 +0800 Subject: [PATCH 11/28] update --- bpf/protocol_inference.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bpf/protocol_inference.h b/bpf/protocol_inference.h index 5efd2669..c7a50046 100644 --- a/bpf/protocol_inference.h +++ b/bpf/protocol_inference.h @@ -163,9 +163,8 @@ static __always_inline enum message_type_t is_rocketmq_protocol( if (serialized_type == 0x0) { // json format char buf[8] = {}; bpf_probe_read_user(buf, 8, old_buf + 8); - if (old_buf[0] != '{' || old_buf[1] != '"' || old_buf[2] != 'c' || - old_buf[3] != 'o' || old_buf[4] != 'd' || old_buf[5] != 'e' || - old_buf[6] != '"' || old_buf[7] != ':') { + if (buf[0] != '{' || buf[1] != '"' || buf[2] != 'c' || buf[3] != 'o' || + buf[4] != 'd' || buf[5] != 'e' || buf[6] != '"' || buf[7] != ':') { // {"code": return kUnknown; } From 47d46cab20574c500474577c313728cc38f5c642 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Fri, 3 Jan 2025 10:48:21 +0800 Subject: [PATCH 12/28] update --- agent/protocol/rocketmq/language_code.go | 105 +++++++++++++++++++++++ agent/protocol/rocketmq/rocketmq.go | 73 ++++++++++++---- agent/protocol/rocketmq/types.go | 9 +- bpf/protocol_inference.h | 8 +- 4 files changed, 172 insertions(+), 23 deletions(-) create mode 100644 agent/protocol/rocketmq/language_code.go diff --git a/agent/protocol/rocketmq/language_code.go b/agent/protocol/rocketmq/language_code.go new file mode 100644 index 00000000..84726d31 --- /dev/null +++ b/agent/protocol/rocketmq/language_code.go @@ -0,0 +1,105 @@ +package rocketmq + +import ( + "errors" + "fmt" +) + +type LanguageCode byte + +const ( + JAVA LanguageCode = iota // 0 + CPP // 1 + DOTNET // 2 + PYTHON // 3 + DELPHI // 4 + ERLANG // 5 + RUBY // 6 + OTHER // 7 + HTTP // 8 + GO // 9 + PHP // 10 + OMS // 11 + RUST // 12 + NODE_JS // 13 + UNKNOWN +) + +// convertToLanguageCode converts a string to a LanguageCode. +func convertToLanguageCode(language string) (LanguageCode, error) { + switch language { + case "JAVA": + return JAVA, nil + case "CPP": + return CPP, nil + case "DOTNET": + return DOTNET, nil + case "PYTHON": + return PYTHON, nil + case "DELPHI": + return DELPHI, nil + case "ERLANG": + return ERLANG, nil + case "RUBY": + return RUBY, nil + case "OTHER": + return OTHER, nil + case "HTTP": + return HTTP, nil + case "GO": + return GO, nil + case "PHP": + return PHP, nil + case "OMS": + return OMS, nil + case "RUST": + return RUST, nil + case "NODE_JS": + return NODE_JS, nil + default: + return 13, errors.New("unknown language: " + language) + } +} + +// convertToLanguageCodeFromByte converts a byte to a LanguageCode. +func convertToLanguageCodeFromByte(flag byte) (LanguageCode, error) { + if flag > 13 { + return 0, errors.New("unknown language flag: " + fmt.Sprint(flag)) + } + return LanguageCode(flag), nil +} + +func (lc LanguageCode) String() string { + switch lc { + case JAVA: + return "JAVA" + case CPP: + return "CPP" + case DOTNET: + return "DOTNET" + case PYTHON: + return "PYTHON" + case DELPHI: + return "DELPHI" + case ERLANG: + return "ERLANG" + case RUBY: + return "RUBY" + case OTHER: + return "OTHER" + case HTTP: + return "HTTP" + case GO: + return "GO" + case PHP: + return "PHP" + case OMS: + return "OMS" + case RUST: + return "RUST" + case NODE_JS: + return "NODE_JS" + default: + return "UNKNOWN" + } +} diff --git a/agent/protocol/rocketmq/rocketmq.go b/agent/protocol/rocketmq/rocketmq.go index 4d2b2819..adbbd62d 100644 --- a/agent/protocol/rocketmq/rocketmq.go +++ b/agent/protocol/rocketmq/rocketmq.go @@ -9,6 +9,7 @@ import ( "kyanos/agent/protocol" "kyanos/bpf" "kyanos/common" + "strings" ) func init() { @@ -17,11 +18,41 @@ func init() { } } +func NewRocketMQMessage() *RocketMQMessage { + return &RocketMQMessage{ + LanguageCode: UNKNOWN, + RemarkBuf: make([]byte, 0), + PropertiesBuf: make([]byte, 0), + BodyBuf: make([]byte, 0), + Properties: map[string]string{}, + } +} + func (r *RocketMQMessage) FormatToString() string { - // return "RemotingCommand [code=" + code + ", language=" + language + ", version=" + version + ", opaque=" + opaque + ", flag(B)=" - // + Integer.toBinaryString(flag) + ", remark=" + remark + ", extFields=" + extFields + ", serializeTypeCurrentRPC=" - // + serializeTypeCurrentRPC + "]"; - return fmt.Sprintf("base=[%s] command=[%s] payload=[%s]", r.FrameBase.String(), "todo", r.Body) + remark := string(r.RemarkBuf) + body := string(r.BodyBuf) + + propertiesMap := string(r.PropertiesBuf) + if len(r.Properties) > 0 { + props := make([]string, 0, len(r.Properties)) + for key, value := range r.Properties { + props = append(props, fmt.Sprintf("%s=%s", key, value)) + } + propertiesMap = fmt.Sprintf("{%s}", strings.Join(props, ", ")) + } + + return fmt.Sprintf("base=[%s] detail=[code=%d, language=%s, version=%d, opaque=%d, flag(B)=%b, remark=%s, extFields=%s, body=%s]", + r.FrameBase.String(), + r.RequestCode, + r.LanguageCode, + r.VersionFlag, + r.Opaque, + r.RequestFlag, + remark, + propertiesMap, + body, + ) + } func (r *RocketMQMessage) IsReq() bool { @@ -30,8 +61,9 @@ func (r *RocketMQMessage) IsReq() bool { func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, messageType protocol.MessageType) protocol.ParseResult { buffer := streamBuffer.Head().Buffer() + common.ProtocolParserLog.Debugln("RocketMQ Parser") common.ProtocolParserLog.Debugf("ParseStream received buffer length: %d", len(buffer)) - common.ProtocolParserLog.Debugln("==============", buffer) + common.ProtocolParserLog.Debugln(buffer) if len(buffer) < 8 { common.ProtocolParserLog.Warn("Buffer too small for header, needs more data.") @@ -46,7 +78,7 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me return protocol.ParseResult{ParseState: protocol.Invalid, ReadBytes: 4} } - if frameSize > len(buffer) { + if frameSize+4 > len(buffer) { common.ProtocolParserLog.Debugf("Frame size %d exceeds buffer length %d, needs more data.", frameSize, len(buffer)) return protocol.ParseResult{ParseState: protocol.NeedsMoreData} } @@ -55,13 +87,12 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me headerDataLen := headerLength & 0xFFFFFF serializedType := byte((headerLength >> 24) & 0xFF) - if 8+int(headerDataLen) > frameSize || len(buffer) < 8+int(headerDataLen) { + if 4+int(headerDataLen) > frameSize || len(buffer) < 8+int(headerDataLen) { common.ProtocolParserLog.Warnf("Incomplete header detected: headerDataLen=%d, frameSize=%d.", headerDataLen, frameSize) return protocol.ParseResult{ParseState: protocol.NeedsMoreData} } headerBody := buffer[8 : 8+headerDataLen] - body := buffer[8+headerDataLen : frameSize] message, err := r.parseHeader(headerBody, serializedType) if err != nil { @@ -69,7 +100,11 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me return protocol.ParseResult{ParseState: protocol.Invalid, ReadBytes: int(frameSize)} } - message.Body = body + if frameSize > 4+int(headerDataLen) { + body := buffer[8+headerDataLen : frameSize] + message.BodyBuf = body + } + message.isReq = messageType == protocol.Request fb, ok := protocol.CreateFrameBase(streamBuffer, frameSize) @@ -92,8 +127,9 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me } func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedType byte) (*RocketMQMessage, error) { - fmt.Println(serializedType) - message := &RocketMQMessage{} + message := &RocketMQMessage{ + RemarkBuf: make([]byte, 0), + } switch serializedType { case 0: // json var temp struct { @@ -111,22 +147,25 @@ func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedTyp } message.RequestCode = temp.RequestCode - // message.LanguageFlag = temp.LanguageFlag + lFlag, _ := convertToLanguageCode(temp.Language) + message.LanguageCode = lFlag message.VersionFlag = temp.VersionFlag message.Opaque = temp.Opaque message.RequestFlag = temp.RequestFlag message.RemarkLength = int32(len(temp.Remark)) - message.Remark = []byte(temp.Remark) + message.RemarkBuf = []byte(temp.Remark) message.PropertiesLen = int32(len(temp.Properties)) - // message.Properties = temp.Properties + message.Properties = temp.Properties case 1: // custom + // TODO: NEED TEST if len(headerBody) < 18 { return nil, errors.New("invalid header size for private serialization") } message.RequestCode = int16(binary.BigEndian.Uint16(headerBody[:2])) - message.LanguageFlag = headerBody[2] + lCode, _ := convertToLanguageCodeFromByte(headerBody[2]) + message.LanguageCode = lCode message.VersionFlag = int16(binary.BigEndian.Uint16(headerBody[3:5])) message.Opaque = int32(binary.BigEndian.Uint32(headerBody[5:9])) message.RequestFlag = int32(binary.BigEndian.Uint32(headerBody[9:13])) @@ -136,7 +175,7 @@ func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedTyp return nil, errors.New("invalid remark length") } - message.Remark = headerBody[17 : 17+message.RemarkLength] + message.RemarkBuf = headerBody[17 : 17+message.RemarkLength] propertiesStart := 17 + message.RemarkLength if len(headerBody[propertiesStart:]) < 4 { @@ -144,7 +183,7 @@ func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedTyp } message.PropertiesLen = int32(binary.BigEndian.Uint32(headerBody[propertiesStart:])) - message.Properties = headerBody[propertiesStart+4 : propertiesStart+4+message.PropertiesLen] + message.PropertiesBuf = headerBody[propertiesStart+4 : propertiesStart+4+message.PropertiesLen] default: return nil, fmt.Errorf("unsupported serialization type: %d", serializedType) diff --git a/agent/protocol/rocketmq/types.go b/agent/protocol/rocketmq/types.go index 46fbd0fc..049ad223 100644 --- a/agent/protocol/rocketmq/types.go +++ b/agent/protocol/rocketmq/types.go @@ -9,15 +9,16 @@ var _ protocol.ParsedMessage = &RocketMQMessage{} type RocketMQMessage struct { protocol.FrameBase RequestCode int16 - LanguageFlag byte + LanguageCode LanguageCode VersionFlag int16 Opaque int32 RequestFlag int32 RemarkLength int32 - Remark []byte + RemarkBuf []byte PropertiesLen int32 - Properties []byte - Body []byte + PropertiesBuf []byte + Properties map[string]string + BodyBuf []byte isReq bool } diff --git a/bpf/protocol_inference.h b/bpf/protocol_inference.h index c7a50046..c1b833ef 100644 --- a/bpf/protocol_inference.h +++ b/bpf/protocol_inference.h @@ -139,9 +139,9 @@ static __always_inline enum message_type_t is_rocketmq_protocol( return kUnknown; } - int32_t frame_size = 0; + int32_t frame_size; bpf_probe_read_user(&frame_size, sizeof(int32_t), old_buf); - bpf_printk("frame_size: %d", frame_size); + frame_size = bpf_ntohl(frame_size); if (frame_size <= 0 || frame_size > 64 * 1024 * 1024) { return kUnknown; @@ -149,6 +149,7 @@ static __always_inline enum message_type_t is_rocketmq_protocol( int32_t header_length = 0; bpf_probe_read_user(&header_length, sizeof(int32_t), old_buf + 4); + header_length = bpf_ntohl(header_length); char serialized_type = (header_length >> 24) & 0xFF; if (serialized_type != 0x0 && serialized_type != 0x1) { @@ -156,11 +157,13 @@ static __always_inline enum message_type_t is_rocketmq_protocol( } int32_t header_data_len = header_length & 0xFFFFFF; + bpf_printk("header_data_len : %d", header_data_len); if (header_data_len <= 0 || header_data_len > count - 8) { return kUnknown; } if (serialized_type == 0x0) { // json format + bpf_printk("json"); char buf[8] = {}; bpf_probe_read_user(buf, 8, old_buf + 8); if (buf[0] != '{' || buf[1] != '"' || buf[2] != 'c' || buf[3] != 'o' || @@ -178,6 +181,7 @@ static __always_inline enum message_type_t is_rocketmq_protocol( bpf_probe_read_user(&v_flag, sizeof(uint16_t), old_buf + 11); // rocketmq/remoting/protocol/RequestCode.java + request_code = bpf_ntohl(request_code); if (request_code < 10) { return kUnknown; } From 1b7d62b69514a3836ff6f13cc6dc6bd1a6d1c561 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Fri, 3 Jan 2025 13:23:22 +0800 Subject: [PATCH 13/28] update --- agent/protocol/rocketmq/rocketmq.go | 11 ++----- bpf/protocol_inference.h | 50 ++++++++++++----------------- docker-compose.yaml | 42 ------------------------ test_rocketmq.py | 44 ------------------------- 4 files changed, 24 insertions(+), 123 deletions(-) delete mode 100644 docker-compose.yaml delete mode 100644 test_rocketmq.py diff --git a/agent/protocol/rocketmq/rocketmq.go b/agent/protocol/rocketmq/rocketmq.go index adbbd62d..f94dfc1f 100644 --- a/agent/protocol/rocketmq/rocketmq.go +++ b/agent/protocol/rocketmq/rocketmq.go @@ -41,7 +41,7 @@ func (r *RocketMQMessage) FormatToString() string { propertiesMap = fmt.Sprintf("{%s}", strings.Join(props, ", ")) } - return fmt.Sprintf("base=[%s] detail=[code=%d, language=%s, version=%d, opaque=%d, flag(B)=%b, remark=%s, extFields=%s, body=%s]", + return fmt.Sprintf("base=[%s] detail=[code=%d, language=%s, version=%d, opaque=%d, flag=%d, remark=%s, extFields=%s, body=%s]", r.FrameBase.String(), r.RequestCode, r.LanguageCode, @@ -61,9 +61,7 @@ func (r *RocketMQMessage) IsReq() bool { func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, messageType protocol.MessageType) protocol.ParseResult { buffer := streamBuffer.Head().Buffer() - common.ProtocolParserLog.Debugln("RocketMQ Parser") common.ProtocolParserLog.Debugf("ParseStream received buffer length: %d", len(buffer)) - common.ProtocolParserLog.Debugln(buffer) if len(buffer) < 8 { common.ProtocolParserLog.Warn("Buffer too small for header, needs more data.") @@ -127,9 +125,7 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me } func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedType byte) (*RocketMQMessage, error) { - message := &RocketMQMessage{ - RemarkBuf: make([]byte, 0), - } + message := NewRocketMQMessage() switch serializedType { case 0: // json var temp struct { @@ -157,8 +153,7 @@ func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedTyp message.PropertiesLen = int32(len(temp.Properties)) message.Properties = temp.Properties - case 1: // custom - // TODO: NEED TEST + case 1: // ROCKETMQ if len(headerBody) < 18 { return nil, errors.New("invalid header size for private serialization") } diff --git a/bpf/protocol_inference.h b/bpf/protocol_inference.h index c1b833ef..97c4ef2b 100644 --- a/bpf/protocol_inference.h +++ b/bpf/protocol_inference.h @@ -9,28 +9,25 @@ // +---------+---------+---------+---------+ // | | // . ... body ... . -// . . +// . . // . . // +---------------------------------------- -static __always_inline int is_mysql_protocol(const char *old_buf, size_t count, - struct conn_info_t *conn_info) { +static __always_inline int is_mysql_protocol(const char *old_buf, size_t count, struct conn_info_t *conn_info) { static const uint8_t kComQuery = 0x03; static const uint8_t kComConnect = 0x0b; static const uint8_t kComStmtPrepare = 0x16; static const uint8_t kComStmtExecute = 0x17; static const uint8_t kComStmtClose = 0x19; - // Second statement checks whether suspected header matches the length of - // current packet. - bool use_prev_buf = (conn_info->prev_count == 4) && - (*((uint32_t *)conn_info->prev_buf) == count); + // Second statement checks whether suspected header matches the length of current packet. + bool use_prev_buf = (conn_info->prev_count == 4) && (*((uint32_t*)conn_info->prev_buf) == count); // if (conn_info->prev_count == 4) { // bpf_printk("prevbuf: %d", (*((uint32_t*)conn_info->prev_buf))); // } if (use_prev_buf) { - // Check the header_state to find out if the header has been read. MySQL - // server tends to read in the 4 byte header and the rest of the packet in a - // separate read. + + // Check the header_state to find out if the header has been read. MySQL server tends to + // read in the 4 byte header and the rest of the packet in a separate read. count += 4; } @@ -40,13 +37,12 @@ static __always_inline int is_mysql_protocol(const char *old_buf, size_t count, return kUnknown; } - // Convert 3-byte length to uint32_t. But since the 4th byte is supposed to be - // \x00, directly casting 4-bytes is correct. NOLINTNEXTLINE: - // readability/casting + // Convert 3-byte length to uint32_t. But since the 4th byte is supposed to be \x00, directly + // casting 4-bytes is correct. + // NOLINTNEXTLINE: readability/casting char buf[5] = {}; bpf_probe_read_user(buf, 5, old_buf); - uint32_t len = - use_prev_buf ? *((uint32_t *)conn_info->prev_buf) : *((uint32_t *)buf); + uint32_t len = use_prev_buf ? *((uint32_t*)conn_info->prev_buf) : *((uint32_t*)buf); len = len & 0x00ffffff; uint8_t seq = use_prev_buf ? conn_info->prev_buf[3] : buf[3]; @@ -62,28 +58,27 @@ static __always_inline int is_mysql_protocol(const char *old_buf, size_t count, return kUnknown; } - // Assuming that the length of a request is less than 10k characters to avoid - // false positive flagging as MySQL, which statistically happens frequently - // for a single-byte check. + // Assuming that the length of a request is less than 10k characters to avoid false + // positive flagging as MySQL, which statistically happens frequently for a single-byte + // check. if (len > 10000) { return kUnknown; } // TODO(oazizi): Consider adding more commands (0x00 to 0x1f). // Be careful, though: trade-off is higher rates of false positives. - if (com == kComConnect || com == kComQuery || com == kComStmtPrepare || - com == kComStmtExecute || com == kComStmtClose) { + if (com == kComConnect || com == kComQuery || com == kComStmtPrepare || com == kComStmtExecute || + com == kComStmtClose) { return kRequest; } return kUnknown; } -static __always_inline int is_redis_protocol(const char *old_buf, - size_t count) { +static __always_inline int is_redis_protocol(const char *old_buf, size_t count) { if (count < 3) { return false; } - + char buf[1] = {}; bpf_probe_read_user(buf, 1, old_buf); const char first_byte = buf[0]; @@ -111,8 +106,7 @@ static __always_inline int is_redis_protocol(const char *old_buf, return true; } -static __always_inline enum message_type_t is_http_protocol(const char *old_buf, - size_t count) { +static __always_inline enum message_type_t is_http_protocol(const char *old_buf, size_t count) { if (count < 5) { return 0; } @@ -193,15 +187,13 @@ static __always_inline enum message_type_t is_rocketmq_protocol( return kRequest; } -static __always_inline struct protocol_message_t infer_protocol( - const char *buf, size_t count, struct conn_info_t *conn_info) { +static __always_inline struct protocol_message_t infer_protocol(const char *buf, size_t count, struct conn_info_t *conn_info) { struct protocol_message_t protocol_message; protocol_message.protocol = kProtocolUnknown; protocol_message.type = kUnknown; if ((protocol_message.type = is_http_protocol(buf, count)) != kUnknown) { protocol_message.protocol = kProtocolHTTP; - } else if ((protocol_message.type = - is_mysql_protocol(buf, count, conn_info)) != kUnknown) { + } else if ((protocol_message.type = is_mysql_protocol(buf, count, conn_info)) != kUnknown) { protocol_message.protocol = kProtocolMySQL; } else if (is_redis_protocol(buf, count)) { protocol_message.protocol = kProtocolRedis; diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 47dc7956..00000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,42 +0,0 @@ -version: '3.8' -services: - namesrv: - image: apache/rocketmq:5.3.1 - container_name: rmqnamesrv - ports: - - 9876:9876 - networks: - - rocketmq - command: sh mqnamesrv - broker: - image: apache/rocketmq:5.3.1 - container_name: rmqbroker - ports: - - 10909:10909 - - 10911:10911 - - 10912:10912 - environment: - - NAMESRV_ADDR=rmqnamesrv:9876 - depends_on: - - namesrv - networks: - - rocketmq - command: sh mqbroker - proxy: - image: apache/rocketmq:5.3.1 - container_name: rmqproxy - networks: - - rocketmq - depends_on: - - broker - - namesrv - ports: - - 8080:8080 - - 8081:8081 - restart: on-failure - environment: - - NAMESRV_ADDR=rmqnamesrv:9876 - command: sh mqproxy -networks: - rocketmq: - driver: bridge \ No newline at end of file diff --git a/test_rocketmq.py b/test_rocketmq.py deleted file mode 100644 index a1ce60e3..00000000 --- a/test_rocketmq.py +++ /dev/null @@ -1,44 +0,0 @@ -from rocketmq.client import Producer, Message, PushConsumer - - -def send_message(): - producer = Producer("TestProducerGroup") - producer.set_namesrv_addr("127.0.0.1:9876") - producer.start() - - msg = Message("TestTopic") - msg.set_keys("messageKey") - msg.set_tags("messageTag") - msg.set_body("Hello RocketMQ") - result = producer.send_sync(msg) - print(f"Message sent: {result}") - producer.shutdown() - - -def consume_message(): - consumer = PushConsumer("TestConsumerGroup") - consumer.set_namesrv_addr("127.0.0.1:9876") - - def callback(msg): - print(f"Message received: {msg.body.decode()}") - return True - - consumer.subscribe("TestTopic", callback) - consumer.start() - print("Consumer started. Press Ctrl+C to exit...") - try: - import time - - while True: - time.sleep(1) - except KeyboardInterrupt: - consumer.shutdown() - - -if __name__ == "__main__": - import sys - - if len(sys.argv) > 1 and sys.argv[1] == "consume": - consume_message() - else: - send_message() From 1b874e718032618baebfbf0f6842c9c0e21a1daf Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Fri, 3 Jan 2025 14:31:20 +0800 Subject: [PATCH 14/28] update --- agent/protocol/rocketmq/rocketmq.go | 6 +++--- bpf/protocol_inference.h | 2 +- cmd/watch.go | 2 +- testdata/test_rocketmq.sh | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/agent/protocol/rocketmq/rocketmq.go b/agent/protocol/rocketmq/rocketmq.go index f94dfc1f..074f44e4 100644 --- a/agent/protocol/rocketmq/rocketmq.go +++ b/agent/protocol/rocketmq/rocketmq.go @@ -191,15 +191,15 @@ func (r *RocketMQStreamParser) FindBoundary(streamBuffer *buffer.StreamBuffer, m buffer := streamBuffer.Head().Buffer() common.ProtocolParserLog.Debugf("FindBoundary starting at position: %d, buffer length: %d", startPos, len(buffer)) - for i := startPos; i <= len(buffer)-8; i++ { + for i := startPos; i <= len(buffer)-4; i++ { frameSize := int(binary.BigEndian.Uint32(buffer[i : i+4])) - if frameSize <= 0 || frameSize > len(buffer)-i { + if frameSize <= 0 || (frameSize+4) > len(buffer)-i { common.ProtocolParserLog.Warnf("Skipping invalid frameSize=%d at position=%d", frameSize, i) continue } - if i+frameSize <= len(buffer) { + if (i + frameSize + 4) <= len(buffer) { common.ProtocolParserLog.Debugf("Found boundary at position=%d with frameSize=%d", i, frameSize) return i } diff --git a/bpf/protocol_inference.h b/bpf/protocol_inference.h index 97c4ef2b..1c920d74 100644 --- a/bpf/protocol_inference.h +++ b/bpf/protocol_inference.h @@ -151,7 +151,7 @@ static __always_inline enum message_type_t is_rocketmq_protocol( } int32_t header_data_len = header_length & 0xFFFFFF; - bpf_printk("header_data_len : %d", header_data_len); + // bpf_printk("header_data_len : %d", header_data_len); if (header_data_len <= 0 || header_data_len > count - 8) { return kUnknown; } diff --git a/cmd/watch.go b/cmd/watch.go index 61439da7..ad63bcf7 100644 --- a/cmd/watch.go +++ b/cmd/watch.go @@ -13,7 +13,7 @@ var watchCmd = &cobra.Command{ Example: ` sudo kyanos watch sudo kyanos watch http --side server --pid 1234 --path /foo/bar --host ubuntu.com -sudo kyanos watch redis --comands GET,SET --keys foo,bar --key-prefix app1: +sudo kyanos watch redis --command GET,SET --keys foo,bar --key-prefix app1: sudo kyanos watch mysql --latency 100 --req-size 1024 --resp-size 2048 sudo kyanos watch rocketmq `, diff --git a/testdata/test_rocketmq.sh b/testdata/test_rocketmq.sh index 0eecabd0..a68ef8b1 100755 --- a/testdata/test_rocketmq.sh +++ b/testdata/test_rocketmq.sh @@ -17,7 +17,7 @@ function test_rocketmq() { docker-compose up -d - timeout 30 ${CMD} watch --debug-output rocketmq --remote-ports 9876,8080 2>&1 | tee "${ROCKETMQ_CLIENT_LNAME}" & + timeout 30 ${CMD} watch --debug-output rocketmq 2>&1 | tee "${ROCKETMQ_CLIENT_LNAME}" & sleep 10 python3 test_rocketmq.py From f0bf0d45aa10979119d39ee8ea13f3f6adbf1cee Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Mon, 6 Jan 2025 10:58:06 +0800 Subject: [PATCH 15/28] update --- agent/protocol/rocketmq/rocketmq.go | 100 +++++++++++++++++++++++++--- agent/protocol/rocketmq/types.go | 1 + bpf/protocol_inference.h | 4 +- testdata/rocketmq_producer.py | 13 ++++ testdata/test_rocketmq.sh | 80 +++++++++++++++++----- 5 files changed, 171 insertions(+), 27 deletions(-) create mode 100644 testdata/rocketmq_producer.py diff --git a/agent/protocol/rocketmq/rocketmq.go b/agent/protocol/rocketmq/rocketmq.go index 074f44e4..dd4badb2 100644 --- a/agent/protocol/rocketmq/rocketmq.go +++ b/agent/protocol/rocketmq/rocketmq.go @@ -10,11 +10,14 @@ import ( "kyanos/bpf" "kyanos/common" "strings" + "time" ) func init() { protocol.ParsersMap[bpf.AgentTrafficProtocolTKProtocolRocketMQ] = func() protocol.ProtocolStreamParser { - return &RocketMQStreamParser{} + return &RocketMQStreamParser{ + requestOpaqueMap: make(map[int32]struct{}), + } } } @@ -24,7 +27,7 @@ func NewRocketMQMessage() *RocketMQMessage { RemarkBuf: make([]byte, 0), PropertiesBuf: make([]byte, 0), BodyBuf: make([]byte, 0), - Properties: map[string]string{}, + Properties: make(map[string]string), } } @@ -191,18 +194,70 @@ func (r *RocketMQStreamParser) FindBoundary(streamBuffer *buffer.StreamBuffer, m buffer := streamBuffer.Head().Buffer() common.ProtocolParserLog.Debugf("FindBoundary starting at position: %d, buffer length: %d", startPos, len(buffer)) - for i := startPos; i <= len(buffer)-4; i++ { + for i := startPos; i <= len(buffer)-16; i++ { frameSize := int(binary.BigEndian.Uint32(buffer[i : i+4])) + if frameSize <= 0 { + common.ProtocolParserLog.Warnf("Invalid frameSize=%d at position=%d", frameSize, i) + continue + } - if frameSize <= 0 || (frameSize+4) > len(buffer)-i { - common.ProtocolParserLog.Warnf("Skipping invalid frameSize=%d at position=%d", frameSize, i) + if i+frameSize+4 > len(buffer) { + common.ProtocolParserLog.Debugf("Incomplete frame at position=%d, waiting for more data", i) + return -1 + } + + headerLength := int(binary.BigEndian.Uint32(buffer[i+4 : i+8])) + serializedType := byte((headerLength >> 24) & 0xFF) + headerDataLen := headerLength & 0xFFFFFF + + if serializedType != 0x0 && serializedType != 0x1 { + common.ProtocolParserLog.Warnf("Invalid serializedType=%d at position=%d", serializedType, i) continue } - if (i + frameSize + 4) <= len(buffer) { - common.ProtocolParserLog.Debugf("Found boundary at position=%d with frameSize=%d", i, frameSize) - return i + if headerDataLen <= 0 || headerDataLen != (frameSize-4) { + common.ProtocolParserLog.Warnf("Invalid headerDataLen=%d at position=%d", headerDataLen, i) + continue + } + + if serializedType == 0x0 { + if i+16 > len(buffer) { + continue + } + if buffer[i+8] != '{' || buffer[i+9] != '"' || buffer[i+10] != 'c' || buffer[i+11] != 'o' || + buffer[i+12] != 'd' || buffer[i+13] != 'e' || buffer[i+14] != '"' || buffer[i+15] != ':' { + common.ProtocolParserLog.Warnf("Invalid JSON format at position=%d", i) + continue + } + } + + if serializedType == 0x1 { + if i+14 > len(buffer) { + continue + } + requestCode := binary.BigEndian.Uint16(buffer[i+8 : i+10]) + lFlag := buffer[i+10] + // vFlag := binary.BigEndian.Uint16(buffer[i+11 : i+13]) + + if requestCode < 10 || lFlag > 13 { + common.ProtocolParserLog.Warnf("Invalid requestCode=%d or lFlag=%d at position=%d", requestCode, lFlag, i) + continue + } + } + + if messageType == protocol.Response { + if i+20 > len(buffer) { + continue + } + opaque := int32(binary.BigEndian.Uint32(buffer[i+16 : i+20])) + if _, exists := r.requestOpaqueMap[opaque]; !exists { + common.ProtocolParserLog.Warnf("Opaque=%d not found in request map at position=%d", opaque, i) + continue + } } + + common.ProtocolParserLog.Debugf("Found boundary at position=%d with frameSize=%d", i, frameSize) + return i } common.ProtocolParserLog.Warn("No valid boundary found, returning -1.") @@ -232,6 +287,35 @@ func (r *RocketMQStreamParser) Match(reqStream *[]protocol.ParsedMessage, respSt } } + currentTime := time.Now() + for opaque, req := range reqMap { + reqTime := time.Unix(0, int64(req.TimestampNs())) + if currentTime.Sub(reqTime) > 2*time.Minute { + common.ProtocolParserLog.Warnf("Removing request with Opaque=%d due to timeout", opaque) + delete(reqMap, opaque) + } + } + + // remove matched requests + newReqStream := []protocol.ParsedMessage{} + for _, msg := range *reqStream { + req := msg.(*RocketMQMessage) + if _, exists := reqMap[req.Opaque]; exists { + newReqStream = append(newReqStream, msg) + } + } + *reqStream = newReqStream + + // clear all response + *respStream = []protocol.ParsedMessage{} + + // clean up requestOpaqueMap + for opaque := range r.requestOpaqueMap { + if _, exists := reqMap[opaque]; !exists { + delete(r.requestOpaqueMap, opaque) + } + } + if len(reqMap) > 0 { common.ProtocolParserLog.Warnf("Unmatched requests remain: %d", len(reqMap)) } diff --git a/agent/protocol/rocketmq/types.go b/agent/protocol/rocketmq/types.go index 049ad223..d35ddb59 100644 --- a/agent/protocol/rocketmq/types.go +++ b/agent/protocol/rocketmq/types.go @@ -25,4 +25,5 @@ type RocketMQMessage struct { var _ protocol.ProtocolStreamParser = &RocketMQStreamParser{} type RocketMQStreamParser struct { + requestOpaqueMap map[int32]struct{} } diff --git a/bpf/protocol_inference.h b/bpf/protocol_inference.h index 1c920d74..53c10bc1 100644 --- a/bpf/protocol_inference.h +++ b/bpf/protocol_inference.h @@ -137,7 +137,7 @@ static __always_inline enum message_type_t is_rocketmq_protocol( bpf_probe_read_user(&frame_size, sizeof(int32_t), old_buf); frame_size = bpf_ntohl(frame_size); - if (frame_size <= 0 || frame_size > 64 * 1024 * 1024) { + if (frame_size <= 0 || frame_size > (count - 4)) { return kUnknown; } @@ -152,7 +152,7 @@ static __always_inline enum message_type_t is_rocketmq_protocol( int32_t header_data_len = header_length & 0xFFFFFF; // bpf_printk("header_data_len : %d", header_data_len); - if (header_data_len <= 0 || header_data_len > count - 8) { + if (header_data_len <= 0 || header_data_len != (frame_size - 4)) { return kUnknown; } diff --git a/testdata/rocketmq_producer.py b/testdata/rocketmq_producer.py new file mode 100644 index 00000000..e2856802 --- /dev/null +++ b/testdata/rocketmq_producer.py @@ -0,0 +1,13 @@ +from rocketmq.client import Producer, Message + +producer = Producer("TestProducerGroup") +producer.set_namesrv_addr("127.0.0.1:9876") +producer.start() + +msg = Message("TestTopic") +msg.set_keys("messageKey") +msg.set_tags("messageTag") +msg.set_body("Hello RocketMQ") +result = producer.send_sync(msg) +print(f"Message sent: {result}") +producer.shutdown() diff --git a/testdata/test_rocketmq.sh b/testdata/test_rocketmq.sh index a68ef8b1..ae623638 100755 --- a/testdata/test_rocketmq.sh +++ b/testdata/test_rocketmq.sh @@ -5,35 +5,81 @@ set -ex CMD="$1" DOCKER_REGISTRY="$2" FILE_PREFIX="/tmp/kyanos" -ROCKETMQ_CLIENT_LNAME="${FILE_PREFIX}_rocketmq_client.log" -ROCKETMQ_SERVER_LNAME="${FILE_PREFIX}_rocketmq_server.log" +LNAME="${FILE_PREFIX}_rocketmq_client.log" +DOCKER_COMPOSE_FILE="/tmp/docker-compose-rocketmq.yml" + +function create_docker_compose_file() { + cat < "$DOCKER_COMPOSE_FILE" +version: '3.8' +services: + namesrv: + image: ${DOCKER_REGISTRY:-apache/rocketmq:5.3.1} + container_name: rmqnamesrv + ports: + - 9876:9876 + networks: + - rocketmq + command: sh mqnamesrv + broker: + image: ${DOCKER_REGISTRY:-apache/rocketmq:5.3.1} + container_name: rmqbroker + ports: + - 10909:10909 + - 10911:10911 + - 10912:10912 + environment: + - NAMESRV_ADDR=rmqnamesrv:9876 + depends_on: + - namesrv + networks: + - rocketmq + command: sh mqbroker + proxy: + image: ${DOCKER_REGISTRY:-apache/rocketmq:5.3.1} + container_name: rmqproxy + networks: + - rocketmq + depends_on: + - broker + - namesrv + ports: + - 8080:8080 + - 8081:8081 + restart: on-failure + environment: + - NAMESRV_ADDR=rmqnamesrv:9876 + command: sh mqproxy +networks: + rocketmq: + driver: bridge +EOF +} function test_rocketmq() { - if [ -z "$DOCKER_REGISTRY" ]; then - IMAGE_NAME="apache/rocketmq:5.3.1" - else - IMAGE_NAME=$DOCKER_REGISTRY"/apache/rocketmq:5.3.1" - fi + pip install rocketmq - docker-compose up -d + create_docker_compose_file - timeout 30 ${CMD} watch --debug-output rocketmq 2>&1 | tee "${ROCKETMQ_CLIENT_LNAME}" & - sleep 10 + docker-compose -f "$DOCKER_COMPOSE_FILE" up -d + sleep 20 - python3 test_rocketmq.py - python3 test_rocketmq.py consume & + timeout 30 ${CMD} watch --debug-output rocketmq --remote-ports 9876 2>&1 | tee "${LNAME}" & sleep 10 - pkill -f test_rocketmq.py + echo "Sending test messages..." + python3 ./testdata/rocketmq_producer.py wait + sleep 2 + + cat "${LNAME}" + docker-compose -f "$DOCKER_COMPOSE_FILE" down + rm -f "$DOCKER_COMPOSE_FILE" - cat "${ROCKETMQ_CLIENT_LNAME}" - docker rm -f rmqnamesrv rmqbroker || true - check_patterns_in_file "${ROCKETMQ_CLIENT_LNAME}" "Hello RocketMQ" + check_patterns_in_file "${LNAME}" "TestTopic" } function main() { test_rocketmq } -main +main \ No newline at end of file From 70388cdb9a86c13b05452baffc38d798c0813b61 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 10:32:08 +0800 Subject: [PATCH 16/28] update --- agent/protocol/rocketmq/filter.go | 21 +++++- agent/protocol/rocketmq/language_code.go | 6 +- agent/protocol/rocketmq/rocketmq.go | 91 +++++++++++++----------- bpf/protocol_inference.h | 1 - cmd/rocketmq.go | 30 +++++++- 5 files changed, 101 insertions(+), 48 deletions(-) diff --git a/agent/protocol/rocketmq/filter.go b/agent/protocol/rocketmq/filter.go index 87976739..f922c9d0 100644 --- a/agent/protocol/rocketmq/filter.go +++ b/agent/protocol/rocketmq/filter.go @@ -3,13 +3,28 @@ package rocketmq import ( "kyanos/agent/protocol" "kyanos/bpf" + "kyanos/common" + "slices" ) type Filter struct { + TargetRequestCodes []int32 + TargetLanguageCodes []LanguageCode } func (m Filter) Filter(req protocol.ParsedMessage, resp protocol.ParsedMessage) bool { - return true + rocketMQReq, ok := req.(*RocketMQMessage) + if !ok { + common.ProtocolParserLog.Warnf("[RocketMQFilter] cast to RocketMQMessage failed: %v\n", req) + return false + } + + pass := true + + pass = pass && (len(m.TargetRequestCodes) == 0 || slices.Index(m.TargetRequestCodes, int32(rocketMQReq.RequestCode)) != -1) + pass = pass && (len(m.TargetLanguageCodes) == 0 || slices.Index(m.TargetLanguageCodes, rocketMQReq.LanguageCode) != -1) + + return pass } func (m Filter) FilterByProtocol(p bpf.AgentTrafficProtocolT) bool { @@ -17,11 +32,11 @@ func (m Filter) FilterByProtocol(p bpf.AgentTrafficProtocolT) bool { } func (m Filter) FilterByRequest() bool { - return false + return len(m.TargetRequestCodes) > 0 || len(m.TargetLanguageCodes) > 0 } func (m Filter) FilterByResponse() bool { - return false + return len(m.TargetRequestCodes) > 0 || len(m.TargetLanguageCodes) > 0 } var _ protocol.ProtocolFilter = Filter{} diff --git a/agent/protocol/rocketmq/language_code.go b/agent/protocol/rocketmq/language_code.go index 84726d31..860d0220 100644 --- a/agent/protocol/rocketmq/language_code.go +++ b/agent/protocol/rocketmq/language_code.go @@ -22,11 +22,11 @@ const ( OMS // 11 RUST // 12 NODE_JS // 13 - UNKNOWN + UNKNOWN_LANGUAGE ) -// convertToLanguageCode converts a string to a LanguageCode. -func convertToLanguageCode(language string) (LanguageCode, error) { +// ConvertToLanguageCode converts a string to a LanguageCode. +func ConvertToLanguageCode(language string) (LanguageCode, error) { switch language { case "JAVA": return JAVA, nil diff --git a/agent/protocol/rocketmq/rocketmq.go b/agent/protocol/rocketmq/rocketmq.go index dd4badb2..8cc85432 100644 --- a/agent/protocol/rocketmq/rocketmq.go +++ b/agent/protocol/rocketmq/rocketmq.go @@ -13,6 +13,21 @@ import ( "time" ) +const ( + requestCodeOffset = 0 + languageCodeOffset = 2 + versionFlagOffset = 3 + opaqueOffset = 5 + requestFlagOffset = 9 + remarkLengthOffset = 13 + propertiesLengthOffset = 17 + + serializationTypeJSON = 0 + serializationTypeRocketMQ = 1 + + requestTimeoutDuration = 5 * time.Minute +) + func init() { protocol.ParsersMap[bpf.AgentTrafficProtocolTKProtocolRocketMQ] = func() protocol.ProtocolStreamParser { return &RocketMQStreamParser{ @@ -23,7 +38,7 @@ func init() { func NewRocketMQMessage() *RocketMQMessage { return &RocketMQMessage{ - LanguageCode: UNKNOWN, + LanguageCode: UNKNOWN_LANGUAGE, RemarkBuf: make([]byte, 0), PropertiesBuf: make([]byte, 0), BodyBuf: make([]byte, 0), @@ -67,7 +82,7 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me common.ProtocolParserLog.Debugf("ParseStream received buffer length: %d", len(buffer)) if len(buffer) < 8 { - common.ProtocolParserLog.Warn("Buffer too small for header, needs more data.") + common.ProtocolParserLog.Debugf("Buffer too small for header, needs more data.") return protocol.ParseResult{ ParseState: protocol.NeedsMoreData, } @@ -75,7 +90,7 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me frameSize := int(binary.BigEndian.Uint32(buffer[:4])) if frameSize <= 0 { - common.ProtocolParserLog.Warnf("Invalid frame size: %d", frameSize) + common.ProtocolParserLog.Debugf("Invalid frame size: %d", frameSize) return protocol.ParseResult{ParseState: protocol.Invalid, ReadBytes: 4} } @@ -89,7 +104,7 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me serializedType := byte((headerLength >> 24) & 0xFF) if 4+int(headerDataLen) > frameSize || len(buffer) < 8+int(headerDataLen) { - common.ProtocolParserLog.Warnf("Incomplete header detected: headerDataLen=%d, frameSize=%d.", headerDataLen, frameSize) + common.ProtocolParserLog.Debugf("Incomplete header detected: headerDataLen=%d, frameSize=%d.", headerDataLen, frameSize) return protocol.ParseResult{ParseState: protocol.NeedsMoreData} } @@ -110,7 +125,7 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me fb, ok := protocol.CreateFrameBase(streamBuffer, frameSize) if !ok { - common.ProtocolParserLog.Warnf("Failed to create FrameBase for frameSize=%d", frameSize) + common.ProtocolParserLog.Debugf("Failed to create FrameBase for frameSize=%d", frameSize) return protocol.ParseResult{ ParseState: protocol.Ignore, ReadBytes: frameSize, @@ -130,7 +145,7 @@ func (r *RocketMQStreamParser) ParseStream(streamBuffer *buffer.StreamBuffer, me func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedType byte) (*RocketMQMessage, error) { message := NewRocketMQMessage() switch serializedType { - case 0: // json + case serializationTypeJSON: var temp struct { RequestCode int16 `json:"code"` Language string `json:"language"` @@ -146,7 +161,7 @@ func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedTyp } message.RequestCode = temp.RequestCode - lFlag, _ := convertToLanguageCode(temp.Language) + lFlag, _ := ConvertToLanguageCode(temp.Language) message.LanguageCode = lFlag message.VersionFlag = temp.VersionFlag message.Opaque = temp.Opaque @@ -156,31 +171,31 @@ func (parser *RocketMQStreamParser) parseHeader(headerBody []byte, serializedTyp message.PropertiesLen = int32(len(temp.Properties)) message.Properties = temp.Properties - case 1: // ROCKETMQ + case serializationTypeRocketMQ: if len(headerBody) < 18 { return nil, errors.New("invalid header size for private serialization") } - message.RequestCode = int16(binary.BigEndian.Uint16(headerBody[:2])) - lCode, _ := convertToLanguageCodeFromByte(headerBody[2]) + message.RequestCode = int16(binary.BigEndian.Uint16(headerBody[requestCodeOffset : requestCodeOffset+2])) + lCode, _ := convertToLanguageCodeFromByte(headerBody[languageCodeOffset]) message.LanguageCode = lCode - message.VersionFlag = int16(binary.BigEndian.Uint16(headerBody[3:5])) - message.Opaque = int32(binary.BigEndian.Uint32(headerBody[5:9])) - message.RequestFlag = int32(binary.BigEndian.Uint32(headerBody[9:13])) - message.RemarkLength = int32(binary.BigEndian.Uint32(headerBody[13:17])) + message.VersionFlag = int16(binary.BigEndian.Uint16(headerBody[versionFlagOffset : versionFlagOffset+2])) + message.Opaque = int32(binary.BigEndian.Uint32(headerBody[opaqueOffset : opaqueOffset+4])) + message.RequestFlag = int32(binary.BigEndian.Uint32(headerBody[requestFlagOffset : requestFlagOffset+4])) + message.RemarkLength = int32(binary.BigEndian.Uint32(headerBody[remarkLengthOffset : remarkLengthOffset+4])) - if int(message.RemarkLength) > len(headerBody[17:]) { + if int(message.RemarkLength) > len(headerBody[remarkLengthOffset+4:]) { return nil, errors.New("invalid remark length") } - message.RemarkBuf = headerBody[17 : 17+message.RemarkLength] + message.RemarkBuf = headerBody[remarkLengthOffset+4 : remarkLengthOffset+4+message.RemarkLength] - propertiesStart := 17 + message.RemarkLength + propertiesStart := remarkLengthOffset + 4 + message.RemarkLength if len(headerBody[propertiesStart:]) < 4 { return nil, errors.New("invalid properties length") } - message.PropertiesLen = int32(binary.BigEndian.Uint32(headerBody[propertiesStart:])) + message.PropertiesLen = int32(binary.BigEndian.Uint32(headerBody[propertiesStart : propertiesStart+4])) message.PropertiesBuf = headerBody[propertiesStart+4 : propertiesStart+4+message.PropertiesLen] default: @@ -197,7 +212,7 @@ func (r *RocketMQStreamParser) FindBoundary(streamBuffer *buffer.StreamBuffer, m for i := startPos; i <= len(buffer)-16; i++ { frameSize := int(binary.BigEndian.Uint32(buffer[i : i+4])) if frameSize <= 0 { - common.ProtocolParserLog.Warnf("Invalid frameSize=%d at position=%d", frameSize, i) + common.ProtocolParserLog.Debugf("Invalid frameSize=%d at position=%d", frameSize, i) continue } @@ -211,27 +226,27 @@ func (r *RocketMQStreamParser) FindBoundary(streamBuffer *buffer.StreamBuffer, m headerDataLen := headerLength & 0xFFFFFF if serializedType != 0x0 && serializedType != 0x1 { - common.ProtocolParserLog.Warnf("Invalid serializedType=%d at position=%d", serializedType, i) + common.ProtocolParserLog.Debugf("Invalid serializedType=%d at position=%d", serializedType, i) continue } if headerDataLen <= 0 || headerDataLen != (frameSize-4) { - common.ProtocolParserLog.Warnf("Invalid headerDataLen=%d at position=%d", headerDataLen, i) + common.ProtocolParserLog.Debugf("Invalid headerDataLen=%d at position=%d", headerDataLen, i) continue } - if serializedType == 0x0 { + if serializedType == serializationTypeJSON { if i+16 > len(buffer) { continue } if buffer[i+8] != '{' || buffer[i+9] != '"' || buffer[i+10] != 'c' || buffer[i+11] != 'o' || buffer[i+12] != 'd' || buffer[i+13] != 'e' || buffer[i+14] != '"' || buffer[i+15] != ':' { - common.ProtocolParserLog.Warnf("Invalid JSON format at position=%d", i) + common.ProtocolParserLog.Debugf("Invalid JSON format at position=%d", i) continue } } - if serializedType == 0x1 { + if serializedType == serializationTypeRocketMQ { if i+14 > len(buffer) { continue } @@ -239,8 +254,8 @@ func (r *RocketMQStreamParser) FindBoundary(streamBuffer *buffer.StreamBuffer, m lFlag := buffer[i+10] // vFlag := binary.BigEndian.Uint16(buffer[i+11 : i+13]) - if requestCode < 10 || lFlag > 13 { - common.ProtocolParserLog.Warnf("Invalid requestCode=%d or lFlag=%d at position=%d", requestCode, lFlag, i) + if requestCode < 10 || lFlag >= byte(UNKNOWN_LANGUAGE) { + common.ProtocolParserLog.Debugf("Invalid requestCode=%d or lFlag=%d at position=%d", requestCode, lFlag, i) continue } } @@ -251,7 +266,7 @@ func (r *RocketMQStreamParser) FindBoundary(streamBuffer *buffer.StreamBuffer, m } opaque := int32(binary.BigEndian.Uint32(buffer[i+16 : i+20])) if _, exists := r.requestOpaqueMap[opaque]; !exists { - common.ProtocolParserLog.Warnf("Opaque=%d not found in request map at position=%d", opaque, i) + common.ProtocolParserLog.Debugf("Opaque=%d not found in request map at position=%d", opaque, i) continue } } @@ -260,7 +275,7 @@ func (r *RocketMQStreamParser) FindBoundary(streamBuffer *buffer.StreamBuffer, m return i } - common.ProtocolParserLog.Warn("No valid boundary found, returning -1.") + common.ProtocolParserLog.Debugln("No valid boundary found, returning -1.") return -1 } @@ -274,24 +289,27 @@ func (r *RocketMQStreamParser) Match(reqStream *[]protocol.ParsedMessage, respSt reqMap[req.Opaque] = req } + // matching for _, msg := range *respStream { resp := msg.(*RocketMQMessage) if req, ok := reqMap[resp.Opaque]; ok { + delete(r.requestOpaqueMap, req.Opaque) records = append(records, protocol.Record{ Req: req, Resp: resp, }) - delete(reqMap, resp.Opaque) + delete(reqMap, req.Opaque) } else { - common.ProtocolParserLog.Warnf("No matching request found for response Opaque=%d", resp.Opaque) + common.ProtocolParserLog.Debugf("No matching request found for response Opaque=%d", resp.Opaque) } } + // remove timeout requests currentTime := time.Now() for opaque, req := range reqMap { reqTime := time.Unix(0, int64(req.TimestampNs())) - if currentTime.Sub(reqTime) > 2*time.Minute { - common.ProtocolParserLog.Warnf("Removing request with Opaque=%d due to timeout", opaque) + if currentTime.Sub(reqTime) > requestTimeoutDuration { + common.ProtocolParserLog.Debugf("Removing request with Opaque=%d due to timeout", opaque) delete(reqMap, opaque) } } @@ -309,15 +327,8 @@ func (r *RocketMQStreamParser) Match(reqStream *[]protocol.ParsedMessage, respSt // clear all response *respStream = []protocol.ParsedMessage{} - // clean up requestOpaqueMap - for opaque := range r.requestOpaqueMap { - if _, exists := reqMap[opaque]; !exists { - delete(r.requestOpaqueMap, opaque) - } - } - if len(reqMap) > 0 { - common.ProtocolParserLog.Warnf("Unmatched requests remain: %d", len(reqMap)) + common.ProtocolParserLog.Debugf("Unmatched requests remain: %d", len(reqMap)) } return records diff --git a/bpf/protocol_inference.h b/bpf/protocol_inference.h index 53c10bc1..00c9eb33 100644 --- a/bpf/protocol_inference.h +++ b/bpf/protocol_inference.h @@ -157,7 +157,6 @@ static __always_inline enum message_type_t is_rocketmq_protocol( } if (serialized_type == 0x0) { // json format - bpf_printk("json"); char buf[8] = {}; bpf_probe_read_user(buf, 8, old_buf + 8); if (buf[0] != '{' || buf[1] != '"' || buf[2] != 'c' || buf[3] != 'o' || diff --git a/cmd/rocketmq.go b/cmd/rocketmq.go index b44beb82..3278d5a1 100644 --- a/cmd/rocketmq.go +++ b/cmd/rocketmq.go @@ -2,6 +2,7 @@ package cmd import ( "kyanos/agent/protocol/rocketmq" + "strings" "github.com/spf13/cobra" ) @@ -10,7 +11,31 @@ var rocketmqCmd *cobra.Command = &cobra.Command{ Use: "rocketmq", Short: "watch RocketMQ message", Run: func(cmd *cobra.Command, args []string) { - options.MessageFilter = rocketmq.Filter{} + requestCodes, err := cmd.Flags().GetInt32Slice("request-codes") + if err != nil { + logger.Fatalf("Invalid request codes: %v\n", err) + } + + languageStrings, err := cmd.Flags().GetStringSlice("languages") + if err != nil { + logger.Fatalf("Invalid languages: %v\n", err) + } + + var languageCodes []rocketmq.LanguageCode + for _, lang := range languageStrings { + code, err := rocketmq.ConvertToLanguageCode(strings.ToUpper(lang)) + if err != nil { + logger.Warnf("Invalid language code: %v\n", err) + continue + } + languageCodes = append(languageCodes, code) + } + + options.MessageFilter = rocketmq.Filter{ + TargetRequestCodes: requestCodes, + TargetLanguageCodes: languageCodes, + } + options.LatencyFilter = initLatencyFilter(cmd) options.SizeFilter = initSizeFilter(cmd) startAgent() @@ -18,6 +43,9 @@ var rocketmqCmd *cobra.Command = &cobra.Command{ } func init() { + rocketmqCmd.Flags().Int32Slice("request-codes", []int32{}, "Specify the request codes to monitor (e.g., 100, 200), separated by ','") + rocketmqCmd.Flags().StringSlice("languages", []string{}, "Specify the languages to monitor (e.g., Java, Go, Rust, CPP), separated by ','") + rocketmqCmd.PersistentFlags().SortFlags = false copy := *rocketmqCmd watchCmd.AddCommand(©) From 09283a13339e5f8d8941567e1e9a41fa92477aee Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 13:24:47 +0800 Subject: [PATCH 17/28] update --- .github/workflows/test.yml | 14 ++++++++++++++ cmd/rocketmq.go | 2 +- cmd/watch.go | 2 +- docs/cn/watch.md | 8 ++++++++ docs/watch.md | 7 +++++++ 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 653e2ff1..a1545c4a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -389,6 +389,20 @@ jobs: bash /host/testdata/test_redis.sh 'sudo /host/kyanos/kyanos $kyanos_log_option' fi + - name: Test RocketMQ + uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 + with: + provision: 'false' + cmd: | + set -ex + uname -a + cat /etc/issue + if [ -f "/var/lib/kyanos/btf/current.btf" ]; then + bash /host/testdata/test_rocketmq.sh 'sudo /host/kyanos/kyanos $kyanos_log_option --btf /var/lib/kyanos/btf/current.btf' + else + bash /host/testdata/test_rocketmq.sh 'sudo /host/kyanos/kyanos $kyanos_log_option' + fi + - name: Test k8s if: ${{ startsWith(matrix.kernel, '6.') }} uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 diff --git a/cmd/rocketmq.go b/cmd/rocketmq.go index 3278d5a1..17f13b17 100644 --- a/cmd/rocketmq.go +++ b/cmd/rocketmq.go @@ -43,7 +43,7 @@ var rocketmqCmd *cobra.Command = &cobra.Command{ } func init() { - rocketmqCmd.Flags().Int32Slice("request-codes", []int32{}, "Specify the request codes to monitor (e.g., 100, 200), separated by ','") + rocketmqCmd.Flags().Int32Slice("request-codes", []int32{}, "Specify the request codes to monitor (e.g., 10, 11), separated by ','") rocketmqCmd.Flags().StringSlice("languages", []string{}, "Specify the languages to monitor (e.g., Java, Go, Rust, CPP), separated by ','") rocketmqCmd.PersistentFlags().SortFlags = false diff --git a/cmd/watch.go b/cmd/watch.go index ad63bcf7..dd7c34e7 100644 --- a/cmd/watch.go +++ b/cmd/watch.go @@ -15,7 +15,7 @@ sudo kyanos watch sudo kyanos watch http --side server --pid 1234 --path /foo/bar --host ubuntu.com sudo kyanos watch redis --command GET,SET --keys foo,bar --key-prefix app1: sudo kyanos watch mysql --latency 100 --req-size 1024 --resp-size 2048 -sudo kyanos watch rocketmq +sudo kyanos watch rocketmq --request-codes 10,11 --languages JAVA,Go `, Short: "Capture the request/response recrods", PersistentPreRun: func(cmd *cobra.Command, args []string) { Mode = WatchMode }, diff --git a/docs/cn/watch.md b/docs/cn/watch.md index 8b90346a..6c374bd2 100644 --- a/docs/cn/watch.md +++ b/docs/cn/watch.md @@ -142,6 +142,14 @@ kyanos 支持根据 IP 端口等三/四层信息过滤,可以指定以下选 | 请求 Key | `keys` | `--keys foo,bar ` 只观察请求 key 为 foo 和 bar | | 请求 key 前缀 | `key-prefix` | `--method foo:bar ` 只观察请求的 key 前缀为 foo\: bar | +#### RocketMQ 协议过滤 + +| 过滤条件 | 命令行 flag | 示例 | +| :------- | :-------------- | :---------------------------------------------------------------------- | +| 请求代码 | `request-codes` | `--request-codes 10,11` 只观察请求代码为 10 和 11的 | +| 语言 | `languages` | `--languages Java,Go ` 只观察使用 Java 和 Go 编写的应用程序发出的请求。 | + + #### MYSQL 协议过滤 > 已支持 MySQL 协议抓取,根据条件过滤仍在实现中... diff --git a/docs/watch.md b/docs/watch.md index c8876ee5..c72ea7f8 100644 --- a/docs/watch.md +++ b/docs/watch.md @@ -170,6 +170,13 @@ Here are the options available for filtering by each protocol: | Request Key | `keys` | `--keys foo,bar`
Only observe requests with the keys `foo` and `bar`. | | Request Key Prefix | `key-prefix` | `--key-prefix foo:bar`
Only observe requests with keys that have the prefix `foo:bar`. | +#### RocketMQ Protocol Filtering +| Filter Condition | Command Line Flag | Example | +| ---------------- | ----------------- | ------------------------------------------------------------------------------------------ | +| Request Codes | `request-codes` | `--request-codes 10,11`
Only observe requests with the codes 10 and 11. | +| Languages | `languages` | `--languages Java,Go`
Only observe requests from applications written in Java and Go. | + + #### MySQL Protocol Filtering > MySQL protocol capturing is supported, but filtering by conditions is still in From 508704224d21c5066e563ba3de017c7498dbab7f Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 14:00:08 +0800 Subject: [PATCH 18/28] update --- .github/workflows/test.yml | 28 ++++++++++++++-------------- docs/cn/watch.md | 4 ++++ docs/watch.md | 4 ++++ 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a1545c4a..e416be30 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -181,6 +181,20 @@ jobs: pushd /host bash /host/testdata/run_cap_bpf_test.sh "" "CAP_SYS_ADMIN" popd + + - name: Test RocketMQ + uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 + with: + provision: 'false' + cmd: | + set -ex + uname -a + cat /etc/issue + if [ -f "/var/lib/kyanos/btf/current.btf" ]; then + bash /host/testdata/test_rocketmq.sh 'sudo /host/kyanos/kyanos $kyanos_log_option --btf /var/lib/kyanos/btf/current.btf' + else + bash /host/testdata/test_rocketmq.sh 'sudo /host/kyanos/kyanos $kyanos_log_option' + fi - name: Test filter by comm uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 @@ -389,20 +403,6 @@ jobs: bash /host/testdata/test_redis.sh 'sudo /host/kyanos/kyanos $kyanos_log_option' fi - - name: Test RocketMQ - uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 - with: - provision: 'false' - cmd: | - set -ex - uname -a - cat /etc/issue - if [ -f "/var/lib/kyanos/btf/current.btf" ]; then - bash /host/testdata/test_rocketmq.sh 'sudo /host/kyanos/kyanos $kyanos_log_option --btf /var/lib/kyanos/btf/current.btf' - else - bash /host/testdata/test_rocketmq.sh 'sudo /host/kyanos/kyanos $kyanos_log_option' - fi - - name: Test k8s if: ${{ startsWith(matrix.kernel, '6.') }} uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 diff --git a/docs/cn/watch.md b/docs/cn/watch.md index 6c374bd2..2a5a21ed 100644 --- a/docs/cn/watch.md +++ b/docs/cn/watch.md @@ -149,6 +149,10 @@ kyanos 支持根据 IP 端口等三/四层信息过滤,可以指定以下选 | 请求代码 | `request-codes` | `--request-codes 10,11` 只观察请求代码为 10 和 11的 | | 语言 | `languages` | `--languages Java,Go ` 只观察使用 Java 和 Go 编写的应用程序发出的请求。 | +> 有关请求代码的含义和值,请参阅 [这里](https://github.com/apache/rocketmq/blob/develop/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java)。 + +> 更多支持的语言,请参阅 [这里](https://github.com/apache/rocketmq/blob/develop/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/LanguageCode.java)。 + #### MYSQL 协议过滤 diff --git a/docs/watch.md b/docs/watch.md index c72ea7f8..70b8b0a5 100644 --- a/docs/watch.md +++ b/docs/watch.md @@ -176,6 +176,10 @@ Here are the options available for filtering by each protocol: | Request Codes | `request-codes` | `--request-codes 10,11`
Only observe requests with the codes 10 and 11. | | Languages | `languages` | `--languages Java,Go`
Only observe requests from applications written in Java and Go. | +> For the meaning and usage of Request Codes, please refer to [here](https://github.com/apache/rocketmq/blob/develop/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java). + +> For more supported languages, please refer to [here](https://github.com/apache/rocketmq/blob/develop/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/LanguageCode.java). + #### MySQL Protocol Filtering From 5dbe6d050f8fbf11e31bb128c95e1697719c8f5b Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 14:10:00 +0800 Subject: [PATCH 19/28] update --- testdata/test_rocketmq.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testdata/test_rocketmq.sh b/testdata/test_rocketmq.sh index ae623638..6859458e 100755 --- a/testdata/test_rocketmq.sh +++ b/testdata/test_rocketmq.sh @@ -56,7 +56,7 @@ EOF } function test_rocketmq() { - pip install rocketmq + pip --break-system-packages install rocketmq create_docker_compose_file From 966a7688510ac089566e5a0d83bea5dee92a7798 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 14:16:03 +0800 Subject: [PATCH 20/28] update --- testdata/test_rocketmq.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testdata/test_rocketmq.sh b/testdata/test_rocketmq.sh index 6859458e..ca6657a8 100755 --- a/testdata/test_rocketmq.sh +++ b/testdata/test_rocketmq.sh @@ -56,7 +56,7 @@ EOF } function test_rocketmq() { - pip --break-system-packages install rocketmq + pip install --break-system-packages rocketmq create_docker_compose_file From 85ffaa4bef90c368fa4f205ad56cdc6001cabc07 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 14:27:59 +0800 Subject: [PATCH 21/28] update --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e416be30..43eeac47 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,6 +33,8 @@ jobs: sudo apt install -y git sudo apt-get -y install pkg-config sudo apt install -y libelf-dev + sudo apt install -y docker-compose + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - sudo add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" From 7c4c9fa9d909d17a5d2d9f8a2210148283df47b1 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 14:30:45 +0800 Subject: [PATCH 22/28] update --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 43eeac47..f4610cbf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,6 @@ jobs: sudo apt install -y git sudo apt-get -y install pkg-config sudo apt install -y libelf-dev - sudo apt install -y docker-compose wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - @@ -139,6 +138,7 @@ jobs: cmd: | apt-get update apt-get install -y dnsutils + sudo apt install -y docker-compose # install btf if [ -f "/sys/kernel/btf/vmlinux" ]; then mkdir -p /var/lib/kyanos/btf From e4d718f1b46c601b77293f5dfb413454afcfd1a0 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 14:44:25 +0800 Subject: [PATCH 23/28] update --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f4610cbf..bbc26442 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,6 @@ jobs: sudo apt-get -y install pkg-config sudo apt install -y libelf-dev - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - sudo add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" sudo apt update From f7502fa0829730edc9d0a84e1f4af5262ad5171d Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 14:52:35 +0800 Subject: [PATCH 24/28] update --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bbc26442..f3b2179b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -137,7 +137,7 @@ jobs: cmd: | apt-get update apt-get install -y dnsutils - sudo apt install -y docker-compose + # apt install -y docker-compose # install btf if [ -f "/sys/kernel/btf/vmlinux" ]; then mkdir -p /var/lib/kyanos/btf From cb6cc3e20ef0ecf3937b4c857ced9bc7b00c6fcd Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 14:56:03 +0800 Subject: [PATCH 25/28] update --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f3b2179b..68426112 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -137,7 +137,7 @@ jobs: cmd: | apt-get update apt-get install -y dnsutils - # apt install -y docker-compose + apt-get install docker-compose-plugin # install btf if [ -f "/sys/kernel/btf/vmlinux" ]; then mkdir -p /var/lib/kyanos/btf From 26fd35f86501713fa0649e615f224da2e8d17847 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 15:00:24 +0800 Subject: [PATCH 26/28] update --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 68426112..e3100aeb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -137,7 +137,7 @@ jobs: cmd: | apt-get update apt-get install -y dnsutils - apt-get install docker-compose-plugin + apt-get install -y docker-compose-plugin # install btf if [ -f "/sys/kernel/btf/vmlinux" ]; then mkdir -p /var/lib/kyanos/btf From e88100aba8be6564a1478e1031d4ea92945cbd20 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 15:12:24 +0800 Subject: [PATCH 27/28] update --- .github/workflows/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e3100aeb..05a94782 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -137,7 +137,6 @@ jobs: cmd: | apt-get update apt-get install -y dnsutils - apt-get install -y docker-compose-plugin # install btf if [ -f "/sys/kernel/btf/vmlinux" ]; then mkdir -p /var/lib/kyanos/btf @@ -156,6 +155,12 @@ jobs: #install python pip sudo apt install -y python3 python3-pip pipx + #install docker-compose + # apt-get install -y docker-compose-plugin + sudo curl -L "https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + docker-compose --version + - name: Test CAP_BPF privilege check uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19 if: ${{ !contains(fromJSON('["4.19-20240912.022020", "5.4-20240912.022020"]'), matrix.kernel) }} From 917eed898c1ed51d2be61c24ab465662715de751 Mon Sep 17 00:00:00 2001 From: Tesla <1394466835@qq.com> Date: Tue, 7 Jan 2025 15:37:11 +0800 Subject: [PATCH 28/28] update --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 05a94782..df7c1ced 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -196,11 +196,13 @@ jobs: set -ex uname -a cat /etc/issue + pushd /host if [ -f "/var/lib/kyanos/btf/current.btf" ]; then bash /host/testdata/test_rocketmq.sh 'sudo /host/kyanos/kyanos $kyanos_log_option --btf /var/lib/kyanos/btf/current.btf' else bash /host/testdata/test_rocketmq.sh 'sudo /host/kyanos/kyanos $kyanos_log_option' fi + popd - name: Test filter by comm uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19