Go runtime/metrics 包详解
概述
runtime/metrics 包提供了访问 Go 运行时导出的实现定义指标的稳定接口。它类似于现有的 runtime.ReadMemStats 和 runtime/debug.ReadGCStats 函数,但更加通用。
重要说明:
- 提供统一的指标访问接口
- 指标集合可能随运行时演化而变化
- 支持跨不同 Go 实现的变体
- 指标通过字符串键标识
- 每种指标都有“kind“(值类型)
与现有 API 的区别:
runtime.ReadMemStats- 固定的内存统计结构runtime/debug.ReadGCStats- 固定的 GC 统计结构runtime/metrics.Read- 通用的动态指标访问
包导入
import "runtime/metrics"
基本使用
package main
import (
"fmt"
"runtime/metrics"
)
func main() {
// 获取所有支持的指标描述
descs := metrics.All()
fmt.Printf("Supported metrics: %d\n", len(descs))
// 读取特定指标
samples := make([]metrics.Sample, 3)
samples[0].Name = "/gc/heap/allocs:bytes"
samples[1].Name = "/gc/heap/objects:objects"
samples[2].Name = "/sched/goroutines:goroutines"
metrics.Read(samples)
for _, s := range samples {
fmt.Printf("%s = %v\n", s.Name, s.Value)
}
}
运行结果:
Supported metrics: 83
/gc/heap/allocs:bytes = 1234567
/gc/heap/objects:objects = 5678
/sched/goroutines:goroutines = 5
函数详解
Read
func Read(m []Sample)
说明:填充给定指标样本切片中的每个 Value 字段。
使用示例:
package main
import (
"fmt"
"runtime/metrics"
)
func main() {
// 创建样本切片
samples := make([]metrics.Sample, 2)
samples[0].Name = "/gc/cycles/total:gc-cycles"
samples[1].Name = "/memory/classes/total:bytes"
// 读取指标值
metrics.Read(samples)
// 打印结果
for _, s := range samples {
switch s.Value.Kind() {
case metrics.KindUint64:
fmt.Printf("%s = %d\n", s.Name, s.Value.Uint64())
case metrics.KindFloat64:
fmt.Printf("%s = %f\n", s.Name, s.Value.Float64())
case metrics.KindFloat64Histogram:
fmt.Printf("%s = %v\n", s.Name, s.Value.Float64Histogram())
}
}
}
运行结果:
/gc/cycles/total:gc-cycles = 10
/memory/classes/total:bytes = 8388608
批量读取示例:
package main
import (
"fmt"
"runtime/metrics"
)
func readAllMetrics() {
descs := metrics.All()
samples := make([]metrics.Sample, len(descs))
for i, desc := range descs {
samples[i].Name = desc.Name
}
metrics.Read(samples)
for _, s := range samples {
if s.Value.Kind() != metrics.KindBad {
fmt.Printf("%-50s = %v\n", s.Name, s.Value)
}
}
}
func main() {
readAllMetrics()
}
重用切片示例:
package main
import (
"fmt"
"runtime/metrics"
"time"
)
func main() {
// 预分配切片并重复使用
samples := []metrics.Sample{
{Name: "/sched/goroutines:goroutines"},
{Name: "/gc/heap/allocs:bytes"},
}
// 定期读取
for i := 0; i < 5; i++ {
metrics.Read(samples)
goroutines := samples[0].Value.Uint64()
allocs := samples[1].Value.Uint64()
fmt.Printf("[%d] Goroutines: %d, Allocs: %d bytes\n",
i, goroutines, allocs)
time.Sleep(100 * time.Millisecond)
}
}
运行结果:
[0] Goroutines: 1, Allocs: 1234 bytes
[1] Goroutines: 1, Allocs: 1234 bytes
[2] Goroutines: 1, Allocs: 1234 bytes
[3] Goroutines: 1, Allocs: 1234 bytes
[4] Goroutines: 1, Allocs: 1234 bytes
类型详解
Description
type Description struct {
Name string
Description string
Kind ValueKind
Cumulative bool
Unit string
}
说明:描述运行时指标。
All
func All() []Description
说明:返回包含所有支持指标的描述切片。
使用示例:
package main
import (
"fmt"
"runtime/metrics"
"strings"
)
func main() {
descs := metrics.All()
fmt.Printf("Total metrics: %d\n\n", len(descs))
// 按类别分组显示
categories := make(map[string]int)
for _, desc := range descs {
parts := strings.Split(desc.Name, "/")
if len(parts) > 1 {
category := parts[1]
categories[category]++
}
}
fmt.Println("Categories:")
for cat, count := range categories {
fmt.Printf(" /%s/* : %d metrics\n", cat, count)
}
// 显示部分指标详情
fmt.Println("\nSample metrics:")
for i := 0; i < 5 && i < len(descs); i++ {
desc := descs[i]
fmt.Printf("\nName: %s\n", desc.Name)
fmt.Printf("Description: %s\n", desc.Description)
fmt.Printf("Kind: %v\n", desc.Kind)
fmt.Printf("Cumulative: %v\n", desc.Cumulative)
fmt.Printf("Unit: %s\n", desc.Unit)
}
}
运行结果:
Total metrics: 83
Categories:
/cpu/classes/* : 11 metrics
/gc/* : 25 metrics
/godebug/* : 30 metrics
/memory/classes/* : 13 metrics
/sched/* : 9 metrics
Sample metrics:
Name: /cgo/go-to-c-calls:calls
Description: Count of calls made from Go to C by the current process.
Kind: 1
Cumulative: true
Unit: calls
Float64Histogram
type Float64Histogram struct {
Counts []uint64
Buckets []float64
}
说明:表示 float64 值的分布。
字段说明:
Counts- 每个桶的计数Buckets- 桶边界(单调递增)
使用示例:
package main
import (
"fmt"
"runtime/metrics"
)
func main() {
// 读取直方图指标
samples := []metrics.Sample{
{Name: "/gc/pauses:seconds"},
{Name: "/sched/latencies:seconds"},
}
metrics.Read(samples)
for _, s := range samples {
if s.Value.Kind() == metrics.KindFloat64Histogram {
hist := s.Value.Float64Histogram()
fmt.Printf("\n%s:\n", s.Name)
fmt.Printf("Buckets: %v\n", hist.Buckets)
fmt.Printf("Counts: %v\n", hist.Counts)
// 计算总数
var total uint64
for _, count := range hist.Counts {
total += count
}
fmt.Printf("Total samples: %d\n", total)
}
}
}
Sample
type Sample struct {
Name string
Value Value
}
说明:捕获单个指标样本。
使用示例:
package main
import (
"fmt"
"runtime/metrics"
)
func main() {
// 创建样本
sample := metrics.Sample{
Name: "/sched/goroutines:goroutines",
}
// 读取值
metrics.Read([]metrics.Sample{sample})
fmt.Printf("Name: %s\n", sample.Name)
fmt.Printf("Kind: %v\n", sample.Value.Kind())
fmt.Printf("Value: %d\n", sample.Value.Uint64())
}
Value
type Value struct{}
说明:表示运行时返回的指标值。
Float64
func (v Value) Float64() float64
说明:返回指标的 float64 值。如果 Kind 不是 KindFloat64 会 panic。
使用示例:
package main
import (
"fmt"
"runtime/metrics"
)
func main() {
samples := []metrics.Sample{
{Name: "/cpu/classes/user:cpu-seconds"},
}
metrics.Read(samples)
value := samples[0].Value
if value.Kind() == metrics.KindFloat64 {
fmt.Printf("CPU seconds: %f\n", value.Float64())
}
}
Float64Histogram
func (v Value) Float64Histogram() *Float64Histogram
说明:返回指标的 *Float64Histogram 值。如果 Kind 不是 KindFloat64Histogram 会 panic。
使用示例:
package main
import (
"fmt"
"runtime/metrics"
)
func main() {
samples := []metrics.Sample{
{Name: "/sched/pauses/total/gc:seconds"},
}
metrics.Read(samples)
value := samples[0].Value
if value.Kind() == metrics.KindFloat64Histogram {
hist := value.Float64Histogram()
fmt.Printf("GC pause histogram: %+v\n", hist)
}
}
Kind
func (v Value) Kind() ValueKind
说明:返回表示值类型的标签。
使用示例:
package main
import (
"fmt"
"runtime/metrics"
)
func main() {
samples := []metrics.Sample{
{Name: "/gc/heap/allocs:bytes"},
{Name: "/cpu/classes/user:cpu-seconds"},
{Name: "/sched/pauses/total/gc:seconds"},
}
metrics.Read(samples)
for _, s := range samples {
fmt.Printf("%-40s Kind: %v\n", s.Name, s.Value.Kind())
}
}
运行结果:
/gc/heap/allocs:bytes Kind: 1
/cpu/classes/user:cpu-seconds Kind: 2
/sched/pauses/total/gc:seconds Kind: 3
Uint64
func (v Value) Uint64() uint64
说明:返回指标的 uint64 值。如果 Kind 不是 KindUint64 会 panic。
使用示例:
package main
import (
"fmt"
"runtime/metrics"
)
func main() {
samples := []metrics.Sample{
{Name: "/gc/cycles/total:gc-cycles"},
{Name: "/sched/goroutines:goroutines"},
}
metrics.Read(samples)
for _, s := range samples {
fmt.Printf("%s = %d\n", s.Name, s.Value.Uint64())
}
}
运行结果:
/gc/cycles/total:gc-cycles = 15
/sched/goroutines:goroutines = 3
ValueKind
type ValueKind int
说明:表示指标 Value 类型的标签。
常量:
const (
KindBad ValueKind = iota // 未知类型
KindUint64 // uint64
KindFloat64 // float64
KindFloat64Histogram // *Float64Histogram
)
使用示例:
package main
import (
"fmt"
"runtime/metrics"
)
func main() {
fmt.Printf("KindBad: %d\n", metrics.KindBad)
fmt.Printf("KindUint64: %d\n", metrics.KindUint64)
fmt.Printf("KindFloat64: %d\n", metrics.KindFloat64)
fmt.Printf("KindFloat64Histogram: %d\n", metrics.KindFloat64Histogram)
}
运行结果:
KindBad: 0
KindUint64: 1
KindFloat64: 2
KindFloat64Histogram: 3
支持的指标分类
/cgo/*
/cgo/go-to-c-calls:calls- Go 到 C 的调用次数
/cpu/classes/*
/cpu/classes/gc/mark/assist:cpu-seconds- GC 辅助 CPU 时间/cpu/classes/gc/mark/dedicated:cpu-seconds- GC 专用 CPU 时间/cpu/classes/gc/mark/idle:cpu-seconds- GC 空闲 CPU 时间/cpu/classes/gc/pause:cpu-seconds- GC 暂停 CPU 时间/cpu/classes/gc/total:cpu-seconds- GC 总 CPU 时间/cpu/classes/idle:cpu-seconds- 空闲 CPU 时间/cpu/classes/scavenge/assist:cpu-seconds- 内存回收辅助 CPU 时间/cpu/classes/scavenge/background:cpu-seconds- 后台内存回收 CPU 时间/cpu/classes/scavenge/total:cpu-seconds- 内存回收总 CPU 时间/cpu/classes/total:cpu-seconds- 总可用 CPU 时间/cpu/classes/user:cpu-seconds- 用户代码 CPU 时间
/gc/*
/gc/cleanups/executed:cleanups- 执行的清理函数数/gc/cleanups/queued:cleanups- 排队的清理函数数/gc/cycles/automatic:gc-cycles- 自动 GC 周期数/gc/cycles/forced:gc-cycles- 强制 GC 周期数/gc/cycles/total:gc-cycles- 总 GC 周期数/gc/finalizers/executed:finalizers- 执行的 finalizer 数/gc/finalizers/queued:finalizers- 排队的 finalizer 数/gc/gogc:percent- GC 目标百分比/gc/gomemlimit:bytes- 内存限制/gc/heap/allocs-by-size:bytes- 按大小分布的堆分配/gc/heap/allocs:bytes- 累计堆分配字节数/gc/heap/allocs:objects- 累计堆分配对象数/gc/heap/frees-by-size:bytes- 按大小分布的堆释放/gc/heap/frees:bytes- 累计堆释放字节数/gc/heap/frees:objects- 累计堆释放对象数/gc/heap/goal:bytes- GC 周期结束的堆大小目标/gc/heap/live:bytes- 上一个 GC 标记的存活对象占用的堆内存/gc/heap/objects:objects- 堆中的对象数/gc/heap/tiny/allocs:objects- 小分配数/gc/limiter/last-enabled:gc-cycle- GC CPU 限制器最后启用的周期/gc/pauses:seconds- GC 暂停时间(已弃用)/gc/scan/globals:bytes- 可扫描的全局变量空间/gc/scan/heap:bytes- 可扫描的堆空间/gc/scan/stack:bytes- 上一个 GC 周期扫描的栈字节数/gc/scan/total:bytes- 可扫描的总空间/gc/stack/starting-size:bytes- 新 goroutine 的栈大小
/godebug/non-default-behavior/*
- 各种 GODEBUG 非默认行为的事件计数(30+ 个指标)
/memory/classes/*
/memory/classes/heap/free:bytes- 完全空闲且可返回给系统的内存/memory/classes/heap/objects:bytes- 存活和死亡对象占用的内存/memory/classes/heap/released:bytes- 已返回给系统的空闲内存/memory/classes/heap/stacks:bytes- 为栈预留的堆内存/memory/classes/heap/unused:bytes- 预留但未使用的堆内存/memory/classes/metadata/mcache/free:bytes- 预留但未使用的 mcache 内存/memory/classes/metadata/mcache/inuse:bytes- 正在使用的 mcache 内存/memory/classes/metadata/mspan/free:bytes- 预留但未使用的 mspan 内存/memory/classes/metadata/mspan/inuse:bytes- 正在使用的 mspan 内存/memory/classes/metadata/other:bytes- 运行时元数据内存/memory/classes/os-stacks:bytes- 操作系统分配的栈内存/memory/classes/other:bytes- 其他内存/memory/classes/profiling/buckets:bytes- profiling 使用的内存/memory/classes/total:bytes- 运行时映射的总内存
/sched/*
/sched/gomaxprocs:threads- 当前 GOMAXPROCS 设置/sched/goroutines-created:goroutines- 程序启动后创建的 goroutine 数/sched/goroutines/not-in-go:goroutines- 系统调用或 cgo 中的 goroutine 数/sched/goroutines/runnable:goroutines- 准备执行的 goroutine 数/sched/goroutines/running:goroutines- 正在执行的 goroutine 数/sched/goroutines/waiting:goroutines- 等待资源的 goroutine 数/sched/goroutines:goroutines- 存活的 goroutine 数/sched/latencies:seconds- goroutine 调度延迟分布/sched/pauses/stopping/gc:seconds- GC 停止延迟分布/sched/pauses/stopping/other:seconds- 非 GC 停止延迟分布/sched/pauses/total/gc:seconds- GC 暂停延迟分布/sched/pauses/total/other:seconds- 非 GC 暂停延迟分布/sched/threads/total:threads- 运行时拥有的线程数/sync/mutex/wait/total:seconds- goroutine 阻塞在锁上的累计时间
典型示例
示例 1:监控 Goroutine 数量
package main
import (
"fmt"
"runtime/metrics"
"time"
)
func main() {
sample := metrics.Sample{
Name: "/sched/goroutines:goroutines",
}
for i := 0; i < 10; i++ {
metrics.Read([]metrics.Sample{sample})
count := sample.Value.Uint64()
fmt.Printf("[%d] Goroutines: %d\n", i, count)
time.Sleep(100 * time.Millisecond)
}
}
示例 2:内存使用监控
package main
import (
"fmt"
"runtime/metrics"
"time"
)
func main() {
samples := []metrics.Sample{
{Name: "/memory/classes/total:bytes"},
{Name: "/memory/classes/heap/objects:bytes"},
{Name: "/memory/classes/heap/free:bytes"},
{Name: "/memory/classes/heap/stacks:bytes"},
}
for i := 0; i < 5; i++ {
metrics.Read(samples)
total := samples[0].Value.Uint64()
heapObjects := samples[1].Value.Uint64()
heapFree := samples[2].Value.Uint64()
stacks := samples[3].Value.Uint64()
fmt.Printf("[%d] Memory Stats:\n", i)
fmt.Printf(" Total: %d MB\n", total/1024/1024)
fmt.Printf(" Heap Objects: %d MB\n", heapObjects/1024/1024)
fmt.Printf(" Heap Free: %d MB\n", heapFree/1024/1024)
fmt.Printf(" Stacks: %d MB\n", stacks/1024/1024)
fmt.Println()
time.Sleep(1 * time.Second)
}
}
示例 3:GC 活动监控
package main
import (
"fmt"
"runtime/metrics"
"time"
)
func main() {
samples := []metrics.Sample{
{Name: "/gc/cycles/total:gc-cycles"},
{Name: "/gc/heap/allocs:bytes"},
{Name: "/gc/heap/frees:bytes"},
{Name: "/gc/heap/live:bytes"},
{Name: "/gc/gogc:percent"},
}
var lastGC uint64
for i := 0; i < 10; i++ {
metrics.Read(samples)
currentGC := samples[0].Value.Uint64()
allocs := samples[1].Value.Uint64()
frees := samples[2].Value.Uint64()
live := samples[3].Value.Uint64()
gogc := samples[4].Value.Uint64()
if currentGC > lastGC {
fmt.Printf("[%d] GC Cycle #%d\n", i, currentGC)
fmt.Printf(" GOGC: %d%%\n", gogc)
fmt.Printf(" Allocs: %d bytes\n", allocs)
fmt.Printf(" Frees: %d bytes\n", frees)
fmt.Printf(" Live: %d bytes\n", live)
fmt.Printf(" Net: %d bytes\n", allocs-frees)
fmt.Println()
lastGC = currentGC
}
time.Sleep(100 * time.Millisecond)
}
}
示例 4:CPU 时间分析
package main
import (
"fmt"
"runtime/metrics"
)
func main() {
samples := []metrics.Sample{
{Name: "/cpu/classes/total:cpu-seconds"},
{Name: "/cpu/classes/user:cpu-seconds"},
{Name: "/cpu/classes/gc/total:cpu-seconds"},
{Name: "/cpu/classes/idle:cpu-seconds"},
}
metrics.Read(samples)
total := samples[0].Value.Float64()
user := samples[1].Value.Float64()
gc := samples[2].Value.Float64()
idle := samples[3].Value.Float64()
fmt.Printf("CPU Time Analysis:\n")
fmt.Printf(" Total: %.2f seconds\n", total)
fmt.Printf(" User: %.2f seconds (%.1f%%)\n", user, user/total*100)
fmt.Printf(" GC: %.2f seconds (%.1f%%)\n", gc, gc/total*100)
fmt.Printf(" Idle: %.2f seconds (%.1f%%)\n", idle, idle/total*100)
}
示例 5:调度延迟监控
package main
import (
"fmt"
"runtime/metrics"
)
func main() {
samples := []metrics.Sample{
{Name: "/sched/latencies:seconds"},
}
metrics.Read(samples)
hist := samples[0].Value.Float64Histogram()
fmt.Printf("Scheduler Latency Distribution:\n")
fmt.Printf("Buckets: %v seconds\n", hist.Buckets)
fmt.Printf("Counts: %v\n", hist.Counts)
// 计算百分位数
var total uint64
for _, count := range hist.Counts {
total += count
}
if total > 0 {
fmt.Printf("\nTotal samples: %d\n", total)
// 找到中位数
var cumulative uint64
for i, count := range hist.Counts {
cumulative += count
if cumulative >= total/2 {
fmt.Printf("Median latency: < %.6f seconds\n", hist.Buckets[i])
break
}
}
}
}
示例 6:Finalizer 和 Cleanup 监控
package main
import (
"fmt"
"runtime/metrics"
"time"
)
func main() {
samples := []metrics.Sample{
{Name: "/gc/finalizers/executed:finalizers"},
{Name: "/gc/finalizers/queued:finalizers"},
{Name: "/gc/cleanups/executed:cleanups"},
{Name: "/gc/cleanups/queued:cleanups"},
}
for i := 0; i < 5; i++ {
metrics.Read(samples)
finalizersExec := samples[0].Value.Uint64()
finalizersQueue := samples[1].Value.Uint64()
cleanupsExec := samples[2].Value.Uint64()
cleanupsQueue := samples[3].Value.Uint64()
fmt.Printf("[%d] Finalizers: %d executed, %d queued\n",
i, finalizersExec, finalizersQueue)
fmt.Printf(" Cleanups: %d executed, %d queued\n",
cleanupsExec, cleanupsQueue)
fmt.Println()
time.Sleep(100 * time.Millisecond)
}
}
示例 7:内存分类详细分析
package main
import (
"fmt"
"runtime/metrics"
)
func formatBytes(bytes uint64) string {
const (
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
)
switch {
case bytes >= GB:
return fmt.Sprintf("%.2f GB", float64(bytes)/GB)
case bytes >= MB:
return fmt.Sprintf("%.2f MB", float64(bytes)/MB)
case bytes >= KB:
return fmt.Sprintf("%.2f KB", float64(bytes)/KB)
default:
return fmt.Sprintf("%d B", bytes)
}
}
func main() {
samples := []metrics.Sample{
{Name: "/memory/classes/heap/objects:bytes"},
{Name: "/memory/classes/heap/free:bytes"},
{Name: "/memory/classes/heap/released:bytes"},
{Name: "/memory/classes/heap/stacks:bytes"},
{Name: "/memory/classes/heap/unused:bytes"},
{Name: "/memory/classes/metadata/mcache/inuse:bytes"},
{Name: "/memory/classes/metadata/mspan/inuse:bytes"},
{Name: "/memory/classes/metadata/other:bytes"},
{Name: "/memory/classes/os-stacks:bytes"},
{Name: "/memory/classes/other:bytes"},
{Name: "/memory/classes/total:bytes"},
}
metrics.Read(samples)
fmt.Printf("Memory Classification:\n")
fmt.Printf(" Heap Objects: %s\n", formatBytes(samples[0].Value.Uint64()))
fmt.Printf(" Heap Free: %s\n", formatBytes(samples[1].Value.Uint64()))
fmt.Printf(" Heap Released: %s\n", formatBytes(samples[2].Value.Uint64()))
fmt.Printf(" Heap Stacks: %s\n", formatBytes(samples[3].Value.Uint64()))
fmt.Printf(" Heap Unused: %s\n", formatBytes(samples[4].Value.Uint64()))
fmt.Printf(" MCache Inuse: %s\n", formatBytes(samples[5].Value.Uint64()))
fmt.Printf(" MSpan Inuse: %s\n", formatBytes(samples[6].Value.Uint64()))
fmt.Printf(" Metadata Other: %s\n", formatBytes(samples[7].Value.Uint64()))
fmt.Printf(" OS Stacks: %s\n", formatBytes(samples[8].Value.Uint64()))
fmt.Printf(" Other: %s\n", formatBytes(samples[9].Value.Uint64()))
fmt.Printf(" Total: %s\n", formatBytes(samples[10].Value.Uint64()))
}
示例 8:GODEBUG 行为监控
package main
import (
"fmt"
"runtime/metrics"
"strings"
)
func main() {
descs := metrics.All()
// 收集所有 godebug 指标
var godebugSamples []metrics.Sample
for _, desc := range descs {
if strings.HasPrefix(desc.Name, "/godebug/") {
godebugSamples = append(godebugSamples, metrics.Sample{
Name: desc.Name,
})
}
}
if len(godebugSamples) == 0 {
fmt.Println("No GODEBUG metrics found")
return
}
metrics.Read(godebugSamples)
fmt.Printf("GODEBUG Non-default Behaviors:\n\n")
for _, s := range godebugSamples {
count := s.Value.Uint64()
if count > 0 {
// 提取行为名称
parts := strings.Split(s.Name, "/")
name := parts[len(parts)-1]
name = strings.TrimSuffix(name, ":events")
fmt.Printf("%-40s: %d events\n", name, count)
}
}
}
最佳实践
1. 重用样本切片
// ✅ 推荐:重用切片
samples := []metrics.Sample{
{Name: "/sched/goroutines:goroutines"},
}
for {
metrics.Read(samples)
process(samples[0].Value.Uint64())
}
// ❌ 不推荐:每次都创建新切片
for {
samples := []metrics.Sample{
{Name: "/sched/goroutines:goroutines"},
}
metrics.Read(samples)
process(samples[0].Value.Uint64())
}
2. 检查 Kind 类型
// ✅ 推荐:检查类型
sample := metrics.Sample{Name: "/gc/heap/allocs:bytes"}
metrics.Read([]metrics.Sample{sample})
switch sample.Value.Kind() {
case metrics.KindUint64:
value := sample.Value.Uint64()
case metrics.KindFloat64:
value := sample.Value.Float64()
case metrics.KindFloat64Histogram:
hist := sample.Value.Float64Histogram()
}
// ❌ 不推荐:不检查类型直接调用
value := sample.Value.Uint64() // 可能 panic
3. 使用 All() 发现指标
// ✅ 推荐:动态发现
descs := metrics.All()
for _, desc := range descs {
if strings.Contains(desc.Name, "heap") {
// 处理堆相关指标
}
}
4. 并发安全读取
// ✅ 推荐:每个 goroutine 使用独立的切片
go func() {
samples := []metrics.Sample{{Name: "/sched/goroutines:goroutines"}}
metrics.Read(samples)
}()
go func() {
samples := []metrics.Sample{{Name: "/gc/cycles/total:gc-cycles"}}
metrics.Read(samples)
}()
// ❌ 不推荐:共享底层内存
samples := []metrics.Sample{{Name: "/sched/goroutines:goroutines"}}
go func() {
metrics.Read(samples)
}()
go func() {
metrics.Read(samples) // 数据竞争
}()
与其他包配合
runtime/pprof
package main
import (
"os"
"runtime/metrics"
"runtime/pprof"
)
func main() {
// 读取内存指标
samples := []metrics.Sample{
{Name: "/memory/classes/total:bytes"},
}
metrics.Read(samples)
// 创建 CPU profile
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// 执行代码...
}
runtime/debug
package main
import (
"fmt"
"runtime/debug"
"runtime/metrics"
)
func main() {
// 使用 metrics 包
samples := []metrics.Sample{
{Name: "/gc/gogc:percent"},
}
metrics.Read(samples)
gogc := samples[0].Value.Uint64()
// 使用 debug 包
old := debug.SetGCPercent(50)
fmt.Printf("GOGC: %d -> 50\n", gogc)
fmt.Printf("Previous setting: %d\n", old)
}
快速参考
函数
| 函数 | 参数 | 返回值 | 说明 |
|---|---|---|---|
| Read | m []Sample | - | 读取指标值 |
类型
| 类型 | 说明 |
|---|---|
| Description | 指标描述 |
| Float64Histogram | float64 直方图 |
| Sample | 指标样本 |
| Value | 指标值 |
| ValueKind | 值类型标签 |
Value 方法
| 方法 | 返回值 | 说明 |
|---|---|---|
| Float64 | float64 | 返回 float64 值 |
| Float64Histogram | *Float64Histogram | 返回直方图 |
| Kind | ValueKind | 返回值类型 |
| Uint64 | uint64 | 返回 uint64 值 |
ValueKind 常量
| 常量 | 值 | 说明 |
|---|---|---|
| KindBad | 0 | 未知类型 |
| KindUint64 | 1 | uint64 |
| KindFloat64 | 2 | float64 |
| KindFloat64Histogram | 3 | 直方图 |
主要指标类别
| 类别 | 说明 | 指标数 |
|---|---|---|
| /cgo/* | CGO 相关 | 1 |
| /cpu/classes/* | CPU 时间分类 | 11 |
| /gc/* | 垃圾回收 | 25 |
| /godebug/* | GODEBUG 行为 | 30 |
| /memory/classes/* | 内存分类 | 13 |
| /sched/* | 调度器 | 9 |
| /sync/* | 同步原语 | 1 |
注意事项
1. 指标可用性
- 指标集合可能随 Go 版本变化
- 某些指标可能被弃用或移除
- 使用 All() 动态发现更兼容
2. 值类型安全
- 调用 Uint64/Float64 前必须检查 Kind
- 错误的类型调用会 panic
- Kind 保证不会改变
3. 并发使用
- 多个 Read 调用并发安全
- 但参数不能共享底层内存
- 重用时注意数据竞争
4. 性能考虑
- Read 调用有少量开销
- 避免过于频繁的读取
- 重用切片提高效率
5. 直方图解读
- 桶边界单调递增
- 计数也是单调递增(累积分布)
- 需要计算差值得到每个桶的实际计数
总结
runtime/metrics 包提供了访问 Go 运行时指标的统一接口。
核心要点:
- 通过字符串键访问指标
- 使用 All() 发现支持的指标
- 检查 Value 的 Kind 后再访问值
- 重用样本切片提高效率
- 注意并发使用时的数据安全
主要用途:
- 运行时性能监控
- 内存使用分析
- GC 行为观察
- 调度器性能分析
- 调试和故障排查
优势:
- 统一的访问接口
- 可扩展的指标集合
- 跨 Go 实现兼容
- 稳定的 API 保证