Golang守护进程的覆盖率测试与优化

Golang守护进程的覆盖率测试与优化 你好,

我正在尝试为一个守护进程生成覆盖率报告。构建时我使用了 -cover 参数。

是否有办法(类似于 GCC 中的 gcov_flush)来转储覆盖率数据,或者优雅地终止进程,以便获取覆盖率信息?

8 回复

更多关于Golang守护进程的覆盖率测试与优化的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


抱歉,我之前提供的链接似乎没有帮助,也许 Go 语言目前不支持此功能。如果你愿意,我们可以发起一个提案,与 Go 语言社区讨论这个问题。

根据文档[1],我认为这或许是你需要的工具。

[1] Goc 可以为你的长期运行的 Go 语言应用程序在运行时收集代码覆盖率。

// 代码示例应放置于此

我也成功让它运行了。谢谢。

但这给我带来了新的问题,因为它强制我使用 goc 进行构建。而我无法在我的编译机器上安装 goc。有没有办法修改输出二进制文件以使用 goc?或者有其他想法吗?

是的,与社区讨论是一个不错的选择。 另外,我发现了 GitHub - qiniu/goc: Go 编程语言的综合覆盖率测试系统 目前还没能让它运行起来,但你觉得它可能有用吗?

我刚试用了goc工具,它似乎运行良好。如果你遇到问题,随时可以和我交流。

image

据我所知,建议在编译机器上安装 goc

goc 背后的工作机制如下:编译器(goc)在将代码送入编译器之前,会向你的代码中插入与覆盖率相关的语句。

  1. 它将项目复制到 /tmp/goc-build-${hash}/。这个文件夹在编译后会被删除,但你可以使用 goc build --debug 来保留它以进行进一步调查。
  2. 它将源代码解析为 AST(抽象语法树)。
  3. 在每个基本块的开头添加覆盖率计数语句。
  4. 专用的 Web 服务器(http_cover_apis_auto_generated.go)的源代码被复制并导入到你的项目中,负责收集覆盖率数据。
  5. 最后,它编译修改后的项目。

这个过程类似于使用 go test -cover 时收集覆盖率数据的方式。你可以在这篇博客文章中阅读更多相关信息。

你可以在 /tmp/goc-build-${hash} 下观察修改后的源代码。修改后的源代码将类似于以下内容(注意计数器变量,例如 GoCover_0_373131613736336566316238.Count):

func main() { GoCover_0_373131613736336566316238.Count[2]++;
    total := 256
    scheduler := gojob.New().Start()
    for i := 0; i < total; i++ { GoCover_0_373131613736336566316238.Count[4]++;
        scheduler.Submit(New(i, rand.Intn(10)))
    }
    GoCover_0_373131613736336566316238.Count[3]++; scheduler.Wait()
}

关于你的问题,是否有办法修改输出二进制文件以使用 goc

答案是肯定的,但这会很棘手且不优雅,特别是如果你需要将修改后的项目发送到编译机器上。

在Go中,可以通过signal.Notify捕获中断信号,并在退出前调用runtime/cover包的相关函数来转储覆盖率数据。以下是示例代码:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "runtime/cover"
    "syscall"
    "time"
)

func main() {
    // 设置信号处理
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

    // 启动守护进程逻辑
    go func() {
        for {
            fmt.Println("守护进程运行中...")
            time.Sleep(2 * time.Second)
        }
    }()

    // 等待信号
    sig := <-sigCh
    fmt.Printf("接收到信号: %v\n", sig)

    // 转储覆盖率数据
    dumpCoverageData()
}

func dumpCoverageData() {
    // 获取覆盖率计数器
    counters := cover.Counters
    if len(counters) == 0 {
        fmt.Println("无覆盖率数据")
        return
    }

    // 将覆盖率数据写入文件
    f, err := os.Create("coverage.out")
    if err != nil {
        fmt.Printf("创建覆盖率文件失败: %v\n", err)
        return
    }
    defer f.Close()

    // 写入覆盖率数据
    for _, counter := range counters {
        _, err := f.Write(counter.Bytes())
        if err != nil {
            fmt.Printf("写入覆盖率数据失败: %v\n", err)
            return
        }
    }

    fmt.Println("覆盖率数据已转储到 coverage.out")
}

编译时需要添加覆盖率标志:

go build -cover -o mydaemon main.go

运行守护进程后,通过发送SIGINT或SIGTERM信号来优雅终止:

./mydaemon &
kill -SIGTERM <pid>

覆盖率数据将保存在coverage.out文件中,可以使用go tool cover命令分析:

go tool cover -func=coverage.out

对于需要定期转储覆盖率数据的场景,可以结合runtime/cover包的Snapshot函数:

func periodicCoverageDump(interval time.Duration) {
    ticker := time.NewTicker(interval)
    defer ticker.Stop()

    for range ticker.C {
        snap := cover.Snapshot()
        fname := fmt.Sprintf("coverage_%d.out", time.Now().Unix())
        os.WriteFile(fname, snap, 0644)
    }
}
回到顶部