# instrument_trace **Repository Path**: cucy/instrument_trace ## Basic Information - **Project Name**: instrument_trace - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-07-31 - **Last Updated**: 2025-04-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 1. 安装 go install -v -x gitee.com/cucy/instrument_trace/cmd/instrument # 2. 添加依赖 `go get -u -x gitee.com/cucy/instrument_trace` # 3. 注入代码 ```bash find * -name "*.go" -exec sh -c 'echo "instrument -w $0" ' {} \;>run.sh ``` instrument.exe run -w 需要注入的`.go`文件 # GPT4中文注释 以下是包含详细中文注释的代码: ```go package trace import ( "bytes" "fmt" "runtime" "strconv" "sync" ) // 定义一个字节数组,用于匹配 goroutine ID 前缀 var goroutineSpace = []byte("goroutine ") // 获取当前 goroutine 的 ID func curGoroutineID() uint64 { b := make([]byte, 64) // 创建一个 64 字节的缓冲区 b = b[:runtime.Stack(b, false)] // 获取当前 goroutine 的堆栈信息 // 去掉 "goroutine " 前缀,剩下的是 goroutine ID b = bytes.TrimPrefix(b, goroutineSpace) i := bytes.IndexByte(b, ' ') // 找到第一个空格的索引位置 if i < 0 { panic(fmt.Sprintf("No space found in %q", b)) } b = b[:i] // 截取 goroutine ID 部分 n, err := strconv.ParseUint(string(b), 10, 64) // 将字符串转换为 uint64 if err != nil { panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err)) } return n } // 打印跟踪信息 func printTrace(id uint64, name string, line int, arrow string, indent int) { indents := "" for i := 0; i < indent; i++ { indents += " " // 根据缩进等级生成缩进字符串 } // 使用带颜色的格式打印信息 fmt.Printf("\033[1;32mg[%05d]\033[0m:%s%s\033[1;32m%s\033[0m:%d\n", id, indents, arrow, name, line) } var mu sync.Mutex // 互斥锁保护 map 的并发访问 var m = make(map[uint64]int) // 保存每个 goroutine 的缩进等级 // Trace 函数用于跟踪函数调用,返回一个在函数退出时调用的匿名函数 func Trace() func() { // 获取调用者的信息 pc, _, line, ok := runtime.Caller(1) if !ok { panic("not found caller") } fn := runtime.FuncForPC(pc) // 获取函数对象 name := fn.Name() // 获取函数名称 gid := curGoroutineID() // 获取当前 goroutine ID mu.Lock() indents := m[gid] // 获取当前 goroutine 的缩进等级 m[gid] = indents + 1 // 增加缩进等级 mu.Unlock() printTrace(gid, name, line, "->", indents+1) // 打印进入函数的跟踪信息 return func() { mu.Lock() indents := m[gid] // 获取当前 goroutine 的缩进等级 m[gid] = indents - 1 // 减少缩进等级 mu.Unlock() printTrace(gid, name, line, "<-", indents) // 打印退出函数的跟踪信息 } } ``` ### 代码说明: 1. **curGoroutineID函数**: - 通过调用`runtime.Stack`获取当前goroutine的堆栈信息,然后解析出goroutine的ID。 2. **printTrace函数**: - 打印格式化的跟踪信息,带有颜色和缩进,用于显示函数调用的层次结构。 3. **Trace函数**: - 这是一个装饰器函数,用于跟踪函数的进入和退出。它获取调用者的信息,包括函数名和行号,增加当前goroutine的缩进等级,并返回一个在函数退出时调用的匿名函数,减少缩进等级并打印退出信息。 4. **全局变量**: - `mu`是一个互斥锁,用于保护并发访问map `m`。 - `m`是一个map,保存每个goroutine的缩进等级。 这个代码可以用来调试和跟踪Go语言中的函数调用,特别是在并发环境下,能够帮助理解goroutine的执行顺序和调用层次。 # 清华 CHatGLM 中文注释 下面是带有详细中文注释的代码,解释了每个部分的作用: ```go package trace import ( "bytes" "fmt" "runtime" "strconv" "sync" ) // goroutineSpace 是用于识别goroutine信息的字节切片。 var goroutineSpace = []byte("goroutine ") // curGoroutineID 获取当前goroutine的ID。 func curGoroutineID() uint64 { // 创建一个64字节的缓冲区。 b := make([]byte, 64) // 获取当前goroutine的堆栈跟踪信息,并截断到缓冲区大小。 b = b[:runtime.Stack(b, false)] // 移除堆栈跟踪信息中的"goroutine "前缀。 b = bytes.TrimPrefix(b, goroutineSpace) // 找到第一个空格字符的位置,它后面跟着goroutine的ID。 i := bytes.IndexByte(b, ' ') if i < 0 { // 如果没有找到空格,则打印错误信息并panic。 panic(fmt.Sprintf("No space found in %q", b)) } // 截取goroutine ID部分。 b = b[:i] // 将字节切片转换为字符串,并解析为uint64类型的ID。 n, err := strconv.ParseUint(string(b), 10, 64) if err != nil { // 如果解析失败,则打印错误信息并panic。 panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err)) } // 返回解析出的goroutine ID。 return n } // printTrace 打印goroutine的跟踪信息。 func printTrace(id uint64, name string, line int, arrow string, indent int) { // 根据缩进级别生成缩进字符串。 indents := "" for i := 0; i < indent; i++ { indents += " " } // 打印goroutine的跟踪信息,包括ID、缩进、箭头、函数名和行号。 fmt.Printf("\033[1;32mg[%05d]\033[0m:%s%s\033[1;32m%s\033[0m:%d\n", id, indents, arrow, name, line) } // mu 是一个互斥锁,用于保护对全局变量m的访问。 var mu sync.Mutex // m 是一个映射,记录每个goroutine的调用栈深度。 var m = make(map[uint64]int) // Trace 返回一个闭包函数,用于追踪函数的进入和退出。 func Trace() func() { // runtime.Caller(1) 获取调用者的程序计数器、文件名、行号和成功标志。 pc, _, line, ok := runtime.Caller(1) if !ok { // 如果无法获取调用者信息,则panic。 panic("not found caller") } // runtime.FuncForPC 返回一个表示给定程序计数器的函数的*Func。 fn := runtime.FuncForPC(pc) // 获取函数的名称。 name := fn.Name() // 获取当前goroutine的ID。 gid := curGoroutineID() // 加锁以保护全局变量m。 mu.Lock() // 获取当前goroutine的调用栈深度。 indents := m[gid] // 增加调用栈深度。 m[gid] = indents + 1 // 解锁。 mu.Unlock() // 打印函数进入的跟踪信息。 printTrace(gid, name, line, "->", indents+1) // 返回一个闭包函数,用于在函数退出时打印跟踪信息。 return func() { // 加锁以保护全局变量m。 mu.Lock() // 获取当前goroutine的调用栈深度。 indents := m[gid] // 减少调用栈深度。 m[gid] = indents - 1 // 解锁。 mu.Unlock() // 打印函数退出的跟踪信息。 printTrace(gid, name, line, "<-", indents) } } ``` 这段代码定义了一个简单的跟踪工具,用于在Go程序中追踪goroutine的调用栈。通过在每个函数的开始和结束处调用`Trace()`,可以打印出函数的进入和退出信息,包括goroutine的ID、函数名和行号。这个跟踪工具可以帮助开发者理解程序的执行流程和goroutine之间的关系。 # kimiai 中文注释 下面是对您提供的Go语言代码片段的详细中文注释: ```go // 定义了一个名为trace的包,用于追踪goroutine的调用栈信息。 package trace import ( "bytes" "fmt" "runtime" "strconv" "sync" ) // goroutineSpace 是一个切片,用于存储 "goroutine " 字符串,以便后续操作。 var goroutineSpace = []byte("goroutine ") // curGoroutineID 函数用于获取当前goroutine的ID。 func curGoroutineID() uint64 { // 分配一个足够大的缓冲区来存储栈信息。 b := make([]byte, 64) // runtime.Stack 获取当前goroutine的栈信息,第二个参数false表示不获取栈中的goroutine信息。 b = b[:runtime.Stack(b, false)] // 去除栈信息中的前缀 "goroutine "。 b = bytes.TrimPrefix(b, goroutineSpace) // 寻找栈信息中的空格字符,用于后续分割字符串。 i := bytes.IndexByte(b, ' ') if i < 0 { // 如果没有找到空格,说明栈信息格式不正确,抛出panic。 panic(fmt.Sprintf("No space found in %q", b)) } // 截取空格前的部分,即goroutine ID。 b = b[:i] // 将截取的字符串转换为uint64类型的数字,即goroutine ID。 n, err := strconv.ParseUint(string(b), 10, 64) if err != nil { // 如果转换失败,说明栈信息格式不正确,抛出panic。 panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err)) } // 返回解析出的goroutine ID。 return n } // printTrace 函数用于打印调用栈信息。 func printTrace(id uint64, name string, line int, arrow string, indent int) { // 生成缩进字符串,每个缩进使用4个空格。 indents := "" for i := 0; i < indent; i++ { indents += " " } // 使用ANSI转义序列设置文本颜色,并打印goroutine ID、箭头、函数名和行号。 fmt.Printf("\033[1;32mg[%05d]\033[0m:%s%s\033[1;32m%s\033[0m:%d\n", id, indents, arrow, name, line) } // mu 是一个互斥锁,用于保护对map m的并发访问。 var mu sync.Mutex // m 是一个map,用于存储每个goroutine的缩进层数。 var m = make(map[uint64]int) // Trace 函数是一个追踪函数,返回一个用于结束追踪的函数。 func Trace() func() { // 获取调用Trace函数的调用者的信息。 pc, _, line, ok := runtime.Caller(1) if !ok { // 如果没有找到调用者,抛出panic。 panic("not found caller") } // 获取调用者函数的信息。 fn := runtime.FuncForPC(pc) name := fn.Name() // 获取当前goroutine的ID。 gid := curGoroutineID() // 使用互斥锁保护map的并发写操作。 mu.Lock() // 获取当前goroutine的缩进层数,并将其加1。 indents := m[gid] m[gid] = indents + 1 mu.Unlock() // 打印调用者的函数名和行号,以及缩进层数。 printTrace(gid, name, line, "->", indents+1) // 返回一个函数,用于结束追踪,减少缩进层数,并打印结束信息。 return func() { mu.Lock() // 减少当前goroutine的缩进层数。 indents := m[gid] m[gid] = indents - 1 mu.Unlock() // 打印结束信息,包括函数名和行号。 printTrace(gid, name, line, "<-", indents) } } ``` 这段代码定义了一个用于追踪Go程序中goroutine调用栈的包。它通过`runtime`包获取调用栈信息,并使用`sync.Mutex`来同步对共享资源的访问。`Trace`函数返回一个闭包,用于记录函数的进入和退出,通过打印带有缩进的调用信息来展示程序的执行流程。 # 例子: 注入前源文件内容: examples/demo/demo.go ```go package main func main() { test1() } func test1() { println("test1 func") test2() } func test2() { println("test2 func") } ``` $ go run cmd/instrument/main.go -w examples/demo/demo.go [C:\Users\zrd\AppData\Local\Temp\go-build2801839323\b001\exe\main.exe -w examples/demo/demo.go] instrument trace for examples/demo/demo.go ok 注入后 ``` import ( "gitee.com/cucy/instrument_trace" "golang.org/x/net/trace" ) func main() { defer trace.Trace()() test1() } func test1() { defer trace.Trace()() println("test1 func") test2() } func test2() { defer trace.Trace()() println("test2 func") } ```