浅谈Pprof,你了解多少?

开发 开发工具
对于大多数 Gopher 而言,一般平时最主要的工作内容除了实现各种无聊的业务逻辑之外,剩下的就是解决各种琐碎的问题。

[[403923]]

本文转载自微信公众号「架构技术漫谈」,作者LA0WAN9。转载本文请联系架构技术漫谈公众号。

对于大多数 Gopher 而言,一般平时最主要的工作内容除了实现各种无聊的业务逻辑之外,剩下的就是解决各种琐碎的问题。比如:查询性能瓶颈在哪里?查询内存泄漏在哪里?好在 pprof 是处理此类问题的利器,共有两套标准库,分别适用于不同的场景:

  • runtime/pprof[1]:采集工具型应用运行数据进行分析
  • net/http/pprof[2]:采集服务型应用运行时数据进行分析

命令行工具「go test」就包含了 runtime/pprof,相关参数请参考「go help testflag」:

  1. shell> go test -cpuprofile cpu.out -memprofile mem.out -bench . 

不过和 runtime/pprof 相比,更常用的是 net/http/pprof,接下来我们主要通过它来解决一些常见问题,想要激活 net/http/pprof 的话很简单,只要导入对应的包并启动服务即可:

  1. import _ "net/http/pprof" 
  2.  
  3. func main() { 
  4.  _ = http.ListenAndServe("localhost:6060", nil) 

需要注意的是,千万别让外网访问到 pprof,否则可能会导致出现安全问题。有兴趣的读者可以尝试通过 google 搜索「intitle:/debug/pprof/ inurl:/debug/pprof/」看看反面例子。

Profile

pprof 预置了很多种不同类型的 profile,我们可以按照自己的需要选择:

  • allocs:A sampling of all past memory allocations
  • block:Stack traces that led to blocking on synchronization primitives
  • goroutine:Stack traces of all current goroutines
  • heap:A sampling of memory allocations of live objects
  • mutex:Stack traces of holders of contended mutexes
  • profile:CPU profile
  • threadcreate:Stack traces that led to the creation of new OS threads

其中最常用的是 profile 和 heap,分别用来诊断 CPU 和内存问题。

CPU profiling

演示代码模拟了 CPU 密集型任务(onCPU)和耗时的网络请求(offCPU):

  1. package main 
  2.  
  3. import ( 
  4.  "log" 
  5.  "net/http" 
  6.  _ "net/http/pprof" 
  7.  "runtime" 
  8.  "time" 
  9.  
  10.  "github.com/felixge/fgprof" 
  11.  
  12. const cpuTime = 1000 * time.Millisecond 
  13.  
  14. func main() { 
  15.  runtime.SetBlockProfileRate(1) 
  16.  runtime.SetMutexProfileFraction(1) 
  17.  
  18.  go func() { 
  19.   http.Handle("/debug/fgprof", fgprof.Handler()) 
  20.   log.Println(http.ListenAndServe(":6060", nil)) 
  21.  }() 
  22.  
  23.  for { 
  24.   cpuIntensiveTask() 
  25.   slowNetworkRequest() 
  26.  } 
  27.  
  28. func cpuIntensiveTask() { 
  29.  start := time.Now() 
  30.  
  31.  for time.Since(start) <= cpuTime { 
  32.   for i := 0; i < 1000; i++ { 
  33.    _ = i 
  34.   } 
  35.  } 
  36.  
  37. func slowNetworkRequest() { 
  38.  resp, err := http.Get("http://httpbin.org/delay/1"
  39.  
  40.  if err != nil { 
  41.   log.Fatal(err) 
  42.  } 
  43.  
  44.  defer resp.Body.Close() 

通过 go tool pprof 查看 /debug/pprof/profile:

  1. go tool pprof -http :8080 http://localhost:6060/debug/pprof/profile 

结果发现 profile 只能检测到 onCPU(也就是 cpuIntensiveTask)部分,却不能检测到 offCPU (也就是 slowNetworkRequest)部分:

profile

为了检测 offCPU 部分,我们引入 fgprof,通过 go tool pprof 查看 /debug/fgprof:

  1. go tool pprof -http :8080 http://localhost:6060/debug/fgprof 

结果发现 fgprof 不仅能检测到 onCPU(也就是 cpuIntensiveTask)部分,还能检测到 offCPU (也就是 slowNetworkRequest)部分:

fgprof

实际应用中,最好对你的瓶颈是 onCPU 还是 offCPU 有一个大体的认识,进而选择合适的工具,如果不确定就直接用 fgprof,不过需要注意的是 fgprof 对性能的影响较大。

Memory profiling

演示代码模拟了一段有内存泄漏问题的程序:

  1. package main 
  2.  
  3. import ( 
  4.  "log" 
  5.  "net/http" 
  6.  _ "net/http/pprof" 
  7.  "time" 
  8.  
  9. func main() { 
  10.  go func() { 
  11.   log.Println(http.ListenAndServe(":6060", nil)) 
  12.  }() 
  13.  
  14.  for { 
  15.   leak() 
  16.  } 
  17.  
  18. func leak() { 
  19.  s := make([]string, 10) 
  20.  
  21.  for i := 0; i < 10000000; i++ { 
  22.   s = append(s, "leak"
  23.  
  24.   if (i % 10000) == 0 { 
  25.    time.Sleep(1 * time.Second
  26.   } 
  27.  
  28.   _ = s 
  29.  } 

通过 go tool pprof 查看 /debug/pprof/head(这次不用 web,用命令行):

heap

通过 top 命令可以很直观的看出哪里可能出现了内存泄漏问题。不过这里有一个需要说明的问题是内存占用大的地方本身可能是正常的,与内存的绝对值大小相比,我们更应该关注的是不同时间点内存相对变化大小,这里可以使用参数 base 或者 diff_base:

heap

本文篇幅有限,无法列举更多的例子,有兴趣的读者推荐参考「golang pprof 实战[3]」。

参考资料

[1]runtime/pprof: https://golang.org/pkg/runtime/pprof/

[2]net/http/pprof: https://golang.org/pkg/net/http/pprof/

[3]golang pprof 实战: https://blog.wolfogre.com/posts/go-ppof-practice/

 

责任编辑:武晓燕 来源: 架构技术漫谈
相关推荐

2020-03-25 08:47:22

智能边缘边缘计算网络

2023-10-25 08:17:06

Lite模式代理类

2012-12-27 10:58:24

KVMKVM概念

2023-10-29 08:35:47

AndroidAOP编程

2023-09-07 10:26:50

接口测试自动化测试

2022-02-08 12:06:12

云计算

2022-06-07 07:37:40

线程进程开发

2019-08-07 17:18:18

云计算云原生函数

2011-08-23 11:03:35

ATM

2015-11-09 10:44:37

DevOpsIT运维

2023-12-24 12:56:36

协程

2023-08-17 10:12:04

前端整洁架构

2020-12-10 09:00:00

开发.NET工具

2021-12-09 07:47:58

Flink 提交模式

2018-10-15 12:42:21

2011-08-10 09:35:38

2017-05-26 18:30:34

华为

2018-04-25 15:53:12

雾计算

2021-09-07 18:34:14

混合云架构私有云云计算

2021-08-11 10:00:51

缓存MyBatis管理
点赞
收藏

51CTO技术栈公众号