golang pprof监控memory block mutex使用指南

目录
  • profile
    • trace 网页显示
  • 如何使用
    • http 接口暴露的方式
    • allocs ,heap
    • block
  • mutex
    • 代码生成profile文件的方式
  • 总结

profile

profile的中文被翻译轮廓,对于计算机程序而言,抛开业务逻辑不谈,它的轮廓是是啥呢?不就是cpu,内存,各种阻塞开销,线程,协程概况 这些运行指标或环境。golang语言自带了工具库来帮助我们描述,探测,分析这些指标或者环境信息,让我们来学习它。

在上一篇golang pprof 监控系列(1) —— go trace 统计原理与使用  里我讲解了下golang trace 工具的统计原理,它能够做到对程序运行中的各种事件进行耗时统计。而今天要将的memory,block,mutex 统计的原理与之不同,这是一个在一段时间内进行采样得到的累加值。

还记得之前用go trace 生成的网页图吗?

trace 网页显示

里面是不是也有 3个名字带有blocking的 profile的轮廓图,分别是Network blocking profile,Synchronization blocking profile,Syscall blocking profile ,而它与今天要说的block 统计有没有什么关联?先说下结论,block统计的内容有重合,但是不多,并且统计数据来源是不同的

Synchronization blocking profile 和 今天的block统计 内容 是一致的,都是blobk信息,不过统计方式不同。

然后之所以 memory,block,mutex 把这3类数据放在一起讲,是因为他们统计的原理是很类似的,好的,在了解统计原理之前,先简单看下如何使用golang提供的工具对这些数据类型进行分析。

如何使用

在看使用代码前,还需要了解清楚对这3种类型的指标对哪些数据进行统计。

两种方式都比较常见,首先来看下http 接口暴露这些性能指标的方式。

http 接口暴露的方式

package main
import (
 "log"
  "net/http"
 _ "net/http/pprof"
)
func main() {
  log.Println(http.ListenAndServe(":6060", nil))
}

使用方式相当容易,直接代码引入net/http/pprof ,便在默认的http处理器上注册上了相关路由,引入包的目的就是为了调用对应包的init方法注册路由。下面就是引用包的init方法。

// src/net/http/pprof/pprof.go:80
func init() {
 http.HandleFunc("/debug/pprof/", Index)
 http.HandleFunc("/debug/pprof/cmdline", Cmdline)
 http.HandleFunc("/debug/pprof/profile", Profile)
 http.HandleFunc("/debug/pprof/symbol", Symbol)
 http.HandleFunc("/debug/pprof/trace", Trace)
}

接下来访问路由 http://127.0.0.1:6060/debug/pprof/  便能看到下面网页内容了。

标注为红色的部分就是今天要讲的内容,点击它们的链接会跳到对应的网页看到统计信息。我们挨个来看下。

allocs ,heap

这两个值都是记录程序内存分配的情况。

heap profile: 7: 5536 [110: 2178080] @ heap/1048576
2: 2304 [2: 2304] @ 0x100d7e0ec 0x100d7ea78 0x100d7f260 0x100d7f78c 0x100d811cc 0x100d817d4 0x100d7d6dc 0x100d7d5e4 0x100daba20
# 0x100d7e0eb runtime.allocm+0x8b  /Users/lanpangzi/goproject/src/go/src/runtime/proc.go:1881
# 0x100d7ea77 runtime.newm+0x37  /Users/lanpangzi/goproject/src/go/src/runtime/proc.go:2207
# 0x100d7f25f runtime.startm+0x11f  /Users/lanpangzi/goproject/src/go/src/runtime/proc.go:2491
# 0x100d7f78b runtime.wakep+0xab  /Users/lanpangzi/goproject/src/go/src/runtime/proc.go:2590
# 0x100d811cb runtime.resetspinning+0x7b /Users/lanpangzi/goproject/src/go/src/runtime/proc.go:3222
# 0x100d817d3 runtime.schedule+0x2d3  /Users/lanpangzi/goproject/src/go/src/runtime/proc.go:3383
# 0x100d7d6db runtime.mstart1+0xcb  /Users/lanpangzi/goproject/src/go/src/runtime/proc.go:1419
# 0x100d7d5e3 runtime.mstart0+0x73  /Users/lanpangzi/goproject/src/go/src/runtime/proc.go:1367
# 0x100daba1f runtime.mstart+0xf  /Users/lanpangzi/goproject/src/go/src/runtime/asm_arm64.s:117

在 go1.17.12 这两者的信息输出其实是一样的,实现的代码也基本一致,仅仅是在名称上做了区分

按pprof http服务器启动网页上的显示来说,allocs 是过去内存的采样,heap 是存活对象的采用记录,然而实际上在代码实现上两者是一致的。并没有做区分。

下面来讲下网页输出内容的含义

heap profile: 7: 5536 [110: 2178080] @ heap/1048576

输出的第一行含义分别是: 7 代表 当前活跃的对象个数 5536 代表 当前活跃对象占用的字节数 110 代表 所有(包含历史的对象)对象个数 2178080 代表 所有对象(包含历史的对象)占用的对象字节数 1048576  控制了内存采样的频率,1048576 是两倍的内存采样频率的大小,默认采样频率是512kb 即平均每512kb就会采样一次,注意这个值512kb不是绝对的达到512kb就会进行采样,而是从一段时间内的采样来看的一个平均值。

接下来就是函数调用堆栈信息,来看第一行

2: 2304 [2: 2304] @ 0x100d7e0ec 0x100d7ea78 0x100d7f260 0x100d7f78c 0x100d811cc 0x100d817d4 0x100d7d6dc 0x100d7d5e4 0x100daba20

从左往右看: 2 代表 在该函数栈上当前活跃的对象个数 2304 代表 在该函数栈上当前活跃的对象所占用的字节数 方括号内的2   代表 在该函数栈上所有(包含历史的对象)对象个数 方括号内的2304 代表 在该函数栈上所有(包含历史的对象)对象所占用的字节数

然后是栈上pc寄存器的值。再往后就是具体的栈函数名信息了。

block

接下来看看block会对哪些行为产生记录,每次程序锁阻塞发生时,select 阻塞,channel通道阻塞,wait group 产生阻塞时就会记录一次阻塞行为。对阻塞行为的记录其实是和trace 的Synchronization blocking profile 是一致的,但是和其他两个blocking profile 不一样。

要得到block的输出信息首先要开启下记录block的开关.

   // 1 代表 全部采样,0 代表不进行采用, 大于1则是设置纳秒的采样率
 runtime.SetBlockProfileRate(1)

这个采样率是怎样计算的,我们来看下具体代码。

// src/runtime/mprof.go:409
func blocksampled(cycles, rate int64) bool {
 if rate <= 0 || (rate > cycles && int64(fastrand())%rate > cycles) {
  return false
 }
 return true
}

cycles你可以把它理解成也是一个纳秒级的事件,rate就是我们设置的纳秒时间,如果 cycles大于等于rate则直接记录block行为,如果cycles小于rate的话,则需要fastrand函数乘以设置的纳秒时间rate 来决定是否采样了。

然后回过头来看看网页的输出信息

--- contention:
cycles/second=1000000000
180001216583 1 @ 0x1002a1198 0x1005159b8 0x100299fc4
# 0x1002a1197 sync.(*Mutex).Lock+0xa7 /Users/lanpangzi/goproject/src/go/src/sync/mutex.go:81
# 0x1005159b7 main.main.func2+0x27 /Users/lanpangzi/goproject/src/go/main/main.go:33

contention 是为这个profile文本信息取的名字,总之中文翻译是争用。

cycles/second 是每秒钟的周期数,用它来表示时间也是为了更精确,其实你可以发现在我的机器上每秒是10的9次方个周期,换算成纳秒就是1ns一个周期时间了。

接着的180001216583 是阻塞的周期数,其实周期就是cputicks,那么180001216583除以 cycles/second 1000000000得到的就是阻塞的秒数了。

接着 1代表阻塞的次数。

无论是阻塞周期时长还是次数,都是一个累加值,即在相同的地方阻塞会导致这个值变大,并且次数会增加。剩下的部分就是函数堆栈信息了。

mutex

接着来看mutex相关内容,block也在锁阻塞时记录阻塞行为,那么mutex与它有什么不同?

直接说下结论,mutex是在解锁unlock时才会记录一次阻塞行为,而block在记录mutex锁阻塞信息时,是在开始执行lock调用的时候记录的。

要想记录mutex信息,和block类似,也需要开启mutex采样开关。

   // 0 代表不进行采用, 1则全部采用,大于1则是一个随机采用
    runtime.SetMutexProfileFraction(1)

来看看采样的细节代码,代码版本go1.17.12

if rate > 0 && int64(fastrand())%rate == 0 {
  saveblockevent(cycles, rate, skip+1, mutexProfile)
 }

可以看到fastrand() 与rate取模等于0才会去采样,rate如果设置成1,则任何数与整数与1取模都会得到0,所以设置为1为完全采用,大于1比如rate设置为2,则要求fastrand必须是2的倍数才能被采样。

接着来看下网页的输出信息。

--- mutex:
cycles/second=1000000812
sampling period=1
180001727833 1 @ 0x100b9175c 0x100e05840 0x100b567ec 0x100b89fc4
# 0x100b9175b sync.(*Mutex).Unlock+0x8b /Users/lanpangzi/goproject/src/go/src/sync/mutex.go:190
# 0x100e0583f main.main+0x19f   /Users/lanpangzi/goproject/src/go/main/main.go:39
# 0x100b567eb runtime.main+0x25b  /Users/lanpangzi/goproject/src/go/src/runtime/proc.go:255

第一行mutex就是profile文本信息的名称了,同样也和block一样,采用cpu周期数计时,但是多了一个sampling period ,这个就是我们设置的采用频率。

接下来的数据都和block类似,180001727833就是锁阻塞的周期, 1为解锁的次数。然后是解锁的堆栈信息。

介绍完利用http服务查看pprof性能指标的方式来看看,如何用代码来生成这些pprof的二进制或者文本文件。

代码生成profile文件的方式

首先来看看如何对内存信息生成pprof文件。

os.Remove("heap.out")
 f, _ := os.Create("heap.out")
 defer f.Close()

 err := pprof.Lookup("heap").WriteTo(f, 1)
 if err != nil {
  log.Fatal(err)
 }

lookup 传递的heap参数也可以改成allocs,不过输出的内容是一致的。

WriteTo 第二个参数叫做debug,传1代表 会生成可读的文本文件,就和http服务看heap allocs的网页一样的。传0代表是要生成一个二进制文件。生成的二进制文件可以用go tool pprof pprof文件名 去分析,关于go tool pprof 工具的使用网上有相当多的资料,这里就不再展开了。

然后来看看如何生成block的pprof文件。

runtime.SetBlockProfileRate(1)
 os.Remove("block.out")
 f, _ := os.Create("block.out")
 defer f.Close()

 err := pprof.Lookup("block").WriteTo(f, 1)
 if err != nil {
  log.Fatal(err)
 }

pprof.Lookup方法传递block即可生成文件了,使用方式和生成内存分析文件一致,,传1代表 会生成可读的文本文件,就和http服务看block的网页一样的。传0代表是要生成一个二进制文件。生成的二进制文件可以用go tool pprof pprof文件名 去分析。

最后看看mutex,和前面两者基本一致仅仅是Lookup传递的参数有变化,这里就不再叙述了。

 runtime.SetMutexProfileFraction(1)
 os.Remove("mutex.out")
 f, _ := os.Create("mutex.out")
 defer f.Close()

 err := pprof.Lookup("mutex").WriteTo(f, 1)
 if err != nil {
  log.Fatal(err)
 }

总结

经过上述的讲解,应该是能够明白相关的指标究竟是采集了哪些内容并且何时采集的了,而go tool pprof 工具的使用不是此文的重点,所以我直接省略掉了,也是没想到即使省略了,还是内容有那么多,那就把这部分的统计原理留到下一篇文章了,下一篇,memory,block,mutex  统计的原理介绍。

以上就是golang pprof监控memory block mutex使用指南的详细内容,更多关于golang pprof监控的资料请关注我们其它相关文章!

(0)

相关推荐

  • golang pprof 监控系列 go trace统计原理与使用解析

    目录 引言 go trace 使用 统计原理介绍 Goroutine analysis Execution Network wait Sync block,Blocking syscall,Scheduler wait 各种profile 图 引言 服务监控系列文章 服务监控系列视频 关于go tool trace的使用,网上有相当多的资料,但拿我之前初学golang的经验来讲,很多资料都没有把go tool trace中的相关指标究竟是统计的哪些方法,统计了哪段区间讲解清楚.所以这篇文章不仅仅

  • Golang pprof性能测试与分析讲解

    目录 一.性能分析类型 1.CPU性能分析 2.内存性能分析 3.阻塞性能分析 二.cpu性能分析 1.生成pporf 2.分析数据 三.内存性能分析 四.benchmark 生成 profile 一.性能分析类型 1.CPU性能分析 CPU性能分析是最常见的性能分析类型.启动CPU分析时,运行时每隔10ms中断一次,采集正在运行协程的堆栈信息. 程序运行结束后,可以根据收集的数据,找到最热代码路径. 一个函数在分析阶段出现的次数越多,则该函数的代码路径(code path)花费的时间占总运行时

  • golang利用pprof与go-torch如何做性能分析

    前言 软件开发过程中,项目上线并不是终点.上线后,还要对程序的取样分析运行情况,并重构现有的功能,让程序执行更高效更稳写. golang的工具包内自带pprof功能,使找出程序中占内存和CPU较多的部分功能方便了不少.加上uber的火焰图,可视化显示,让我们在分析程序时更简单明了. pprof有两个包用来分析程序一个是net/http/pprof另一个是runtime/pprof,net/http/pprof只是对runtime/pprof包进行封装并用http暴露出来,如下图源码所示: 使用n

  • golang pprof 监控goroutine thread统计原理详解

    目录 引言 http 接口暴露的方式 goroutine profile 输出信息介绍 threadcreate 输出信息介绍 程序代码暴露指标信息 统计原理介绍 goroutine fetch 函数实现 threadcreate fetch 函数实现 总结 引言 在之前 golang pprof监控 系列文章里我分别介绍了go trace以及go pprof工具对memory,block,mutex这些维度的统计原理,今天我们接着来介绍golang pprof工具对于goroutine 和th

  • golang pprof监控memory block mutex统计原理分析

    目录 引言 bucket结构体介绍 记录指标细节介绍 memory block mutex 总结 引言 在上一篇文章 golang pprof监控系列(2) —— memory,block,mutex 使用里我讲解了这3种性能指标如何在程序中暴露以及各自监控的范围.也有提到memory,block,mutex 把这3类数据放在一起讲,是因为他们统计的原理是很类似的.今天来看看它们究竟是如何统计的. 先说下结论,这3种类型在runtime内部都是通过一个叫做bucket的结构体做的统计,bucke

  • golang pprof监控memory block mutex使用指南

    目录 profile trace 网页显示 如何使用 http 接口暴露的方式 allocs ,heap block mutex 代码生成profile文件的方式 总结 profile profile的中文被翻译轮廓,对于计算机程序而言,抛开业务逻辑不谈,它的轮廓是是啥呢?不就是cpu,内存,各种阻塞开销,线程,协程概况 这些运行指标或环境.golang语言自带了工具库来帮助我们描述,探测,分析这些指标或者环境信息,让我们来学习它. 在上一篇golang pprof 监控系列(1) —— go

  • Golang pprof监控之cpu占用率统计原理详解

    目录 http 接口暴露的方式 程序代码生成profile cpu 统计原理分析 线程处理信号的时机 内核发送信号的方式 采样数据的公平性 总结 经过前面的几节对pprof的介绍,对pprof统计的原理算是掌握了七八十了,我们对memory,block,mutex,trace,goroutine,threadcreate这些维度的统计原理都进行了分析,但唯独还没有分析pprof 工具是如何统计cpu使用情况的,今天我们来分析下这部分. http 接口暴露的方式 还记得 golang pprof监

  • web项目中golang性能监控解析

    目录 性能监控 一.web项目(如gin中) 二.单个的go文件如果查看gc 性能监控 一.web项目(如gin中) 1.使用ginpprof import "github.com/DeanThompson/ginpprof" router := gin.Default() ginpprof.Wrap(router) 2.使用pprof 只需要在main.go中引入:_ “net/http/pprof” 访问:127.0.0.1:8080/debug/pprof /debug/ppro

  • golang连接sqlx库的操作使用指南

    目录 安装sqlx 基本使用 连接数据库 查询 插入.更新和删除 NamedExec NamedQuery 事务操作 sqlx.In的批量插入示例 表结构 结构体 bindvars(绑定变量) 自己拼接语句实现批量插入 使用sqlx.In实现批量插入 使用NamedExec实现批量插入 sqlx.In的查询示例 in查询 in查询和FIND_IN_SET函数 sqlx库使用指南 在项目中我们通常可能会使用database/sql连接MySQL数据库.本文借助使用sqlx实现批量插入数据的例子,介

  • GoLang日志监控系统实现

    目录 日志监控系统 项目简答介绍 系统架构 读取模块具体实现 日志解析模块 日志监控系统 Nginx(日志文件) -> log_process (实时读取解析写入) -> influxdb(存储) ->grafana(前端日志展示器) influxdb 属于GO语言编写的开源的时序型数据,着力于高性能 查询与存储时序型数据,influxdb 广泛的应用于存储系统的监控数据,IOT行业的实时数据. 目前市面上流行 TSDB(时序型处理数据库):influxDB, TimescaleDB,

  • GoLang中的互斥锁Mutex和读写锁RWMutex使用教程

    目录 一.竞态条件与临界区和同步工具 (1)竞态条件 (2)临界区 (3)同步工具 二.互斥量 三.使用互斥锁的注意事项 (1)使用互斥锁的注意事项 (2)使用defer语句解锁 (3)sync.Mutex是值类型 四.读写锁与互斥锁的异同 (1)读/写互斥锁 (2)读写锁规则 (3)解锁读写锁 一.竞态条件与临界区和同步工具 (1)竞态条件 一旦数据被多个线程共享,那么就会产生冲突和争用的情况,这种情况被称为竞态条件.这往往会破坏数据的一致性. 同步的用途有两个,一个是避免多线程在同一时刻操作

  • golang中sync.Mutex的实现方法

    目录 mutex 的实现思想 golang 中 mutex 的实现思想 mutex 的结构以及一些 const 常量值 Mutex 没有被锁住,第一个协程来拿锁 Mutex 仅被协程 A 锁住,没有其他协程抢锁,协程 A 释放锁 Mutex 已经被协程 A 锁住,协程 B 来拿锁 lockSlow() runtime_doSpin() runtime_canSpin() Mutex 被协程 A 锁住,协程 B 来抢锁但失败被放入等待队列,此时协程 A 释放锁 unlockSlow() Mutex

  • 浅谈golang slice 切片原理

    slice介绍 数组的长度在定义之后无法再次修改:数组是值类型,每次传递都将产生一份副本.显然这种数据结构无法完全满足开发者的真实需求.在初始定义数组时,我们并不知道需要多大的数组,因此我们就需要"动态数组".在Go里面这种数据结构叫slice,slice并不是真正意义上的动态数组,而是一个引用类型.slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度,它是可变长的,可以随时往slice里面加数据. 初看起来,数组切片就像一个指向数组的指针,实际

随机推荐