GODEBUG参数是如何实现的?
in Go with 0 comment

GODEBUG参数是如何实现的?

in Go with 0 comment

GODEBUG 支持如下的选项,还有一个memprofilerate

var dbgvars = []dbgVar{
	{"allocfreetrace", &debug.allocfreetrace},
	{"clobberfree", &debug.clobberfree},
	{"cgocheck", &debug.cgocheck},
	{"efence", &debug.efence},
	{"gccheckmark", &debug.gccheckmark},
	{"gcpacertrace", &debug.gcpacertrace},
	{"gcshrinkstackoff", &debug.gcshrinkstackoff},
	{"gcstoptheworld", &debug.gcstoptheworld},
	{"gctrace", &debug.gctrace},
	{"invalidptr", &debug.invalidptr},
	{"madvdontneed", &debug.madvdontneed},
	{"sbrk", &debug.sbrk},
	{"scavtrace", &debug.scavtrace},
	{"scheddetail", &debug.scheddetail},
	{"schedtrace", &debug.schedtrace},
	{"tracebackancestors", &debug.tracebackancestors},
	{"asyncpreemptoff", &debug.asyncpreemptoff},
	{"inittrace", &debug.inittrace},
}

较常用的是gctrace 用来打印GC信息

可以通过 export GODEBUG=gctrace=1 && ./demo

或者 GODEBUG=gctrace=1 ./demo

来开启GODEBUG 选项来运行程序

GODEBUG还支持两个选项组合使用,比如说可以使用如下的方式开启gc打印和关闭异步抢占

GODEBUG=gctrace=1,asyncpreemptoff=1

何时解析

在程序启动时,调用到schedinit函数时

// The bootstrap sequence is:
//
//	call osinit
//	call schedinit
//	make & queue new G
//	call runtime·mstart
//
// The new G calls runtime·main.
func schedinit() {
    ……
	goargs()
	goenvs()
	parsedebugvars() // 在这个函数中进行解析
	……
}

如何解析

其实就是在对字符串的基本操作

func parsedebugvars() {
	// 有一些默认开启的参数
	debug.cgocheck = 1
	debug.invalidptr = 1
	// 获取GODEBUG环境变量的内容,如果不为空时 进行解析
	for p := gogetenv("GODEBUG"); p != ""; {
		field := ""
		// 先按 , 进行拆分
		i := index(p, ",")
		if i < 0 {
			field, p = p, ""
		} else {
		// 如果存在 “,”  获取第一个 “,” 前的内容,放置到 field 中
			field, p = p[:i], p[i+1:]
		}
		// 在field 中 按“=” 继续拆分
		i = index(field, "=")
		if i < 0 {
			continue
		}
		// 获取 “=” 前后的内容,存储到key和value中
		key, value := field[:i], field[i+1:]

		// Update MemProfileRate directly here since it
		// is int, not int32, and should only be updated
		// if specified in GODEBUG.
		// 对于 memprofilerate 特殊处理,设置memprofile的采样频率
		if key == "memprofilerate" {
			if n, ok := atoi(value); ok {
				MemProfileRate = n
			}
		} else {
		// 查找预定义的dbgvars中的内容,找到name与key值相同时,设置其对应的value
			for _, v := range dbgvars {
				if v.name == key {
					if n, ok := atoi32(value); ok {
						*v.value = n
					}
				}
			}
		}
	}
    
	setTraceback(gogetenv("GOTRACEBACK"))
	traceback_env = traceback_cache
}

所以只有在dbgvars 中预定义的参数才可以正确解析

解析GODEBUG=gctrace=1后,在runtime代码中,对于if debug.gctrace > 0的代码,即可满足这个检查条件,进而进行打印gc信息

如何添加自定义配置选项

当然需要重新编译Go

如何从源码编译安装Go,官方教程

// $GOROOT\src\runtime\runtime1.go
var debug struct {
	asyncpreemptoff    int32
	gocustom 			     int32
}

var dbgvars = []dbgVar{
	{"asyncpreemptoff", &debug.asyncpreemptoff},
	{"gocustom", &debug.gocustom},
}

// $GOROOT\src\runtime\proc.go
// The main goroutine.
func main() {
	fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
	if debug.gocustom >0 {
		println("Hello LeonardWang")
	}
	// 执行用户main函数
	fn()
}

效果如下,当设置了GODEBUG=gocustom=1 时,会先打印 Hello LeonardWang

> cat main.go

package main

import "fmt"

func main() {
        fmt.Println("Hello World")
}

> go build -o main ./main.go

> ./main

Hello World
> GODEBUG=gocustom=1 ./main

Hello LeonardWang
Hello World

添加自定义的GODEBUG选项可以在runtime中记录关心的统计信息,在适当的时机进行输出分析。