刘沙河 刘沙河
首页
  • Go语言基础

    • 数据类型
    • 反射
    • Go指针
  • Go语言进阶

    • go泛型
    • go条件编译
    • cgo教程
    • Go协程调度原理及GPM模型
    • Go内存管理
    • Go垃圾回收机制
    • Go语言内存对齐
  • Go语言实现原理

    • channel 实现原理
    • slice 实现原理
    • map 实现原理
    • sync.Mutex 实现原理
    • 乐观锁CAS 实现原理
    • singlefight 实现原理
  • gin框架

    • gin中间件原理
    • gin路由原理
  • gorm

    • GORM介绍和使用
    • GORM_CURD操作指南
  • go测试

    • benchmark基准测试
    • pprof 性能分析
  • python进阶

    • Numpy&Pandas
    • celery分布式任务队列
  • Django

    • Django 常见命令
    • middleware中间件
    • Django缓存系统
    • Django信号系统
    • Django REST Framework
  • Flask

    • Flask基础知识总结
    • Flask-SQLAlchemy
  • 爬虫

    • aiohttp
    • scrapy框架
  • Mysql

    • Mysql存储引擎和索引
    • MySQL主从复制
    • Mysql读写分离
    • 数据库分库分表
    • Mysql锁
    • Mysql事务和MVCC原理
    • 分库分表带来的读扩散问题
  • Redis

    • redis基础和数据类型
    • redis主从架构
    • redis哨兵架构
    • redis集群模式
    • 如何保证缓存和数据库双写一致
    • redis底层数据结构
    • redis分布式锁
  • Elasticsearch

    • es基本概念
    • es基础语法
    • es倒排索引
  • etcd

    • Go操作etcd
    • Raft原理
    • etcd分布式锁
  • kafka

    • 消息队列MQ总结
    • kafka 概述及原理
    • kafka 消费问题记录
    • 零拷贝技术
    • kafka分区规范
  • RabbitMQ

    • rabbitMQ基础
    • Go操作rabbitmq
  • RocketMQ

    • 可靠消息队列 rocketMQ
  • Http&Https

    • http&https
    • TCP和UDP
    • Ping 原理
  • RPC

    • RPC初识
    • grpc初识和实现
  • gRPC

    • grpc 初识
    • grpc 上下文 metadata
    • grpc 健康检查
    • grpc keepalive
    • grpc 命名解析
    • grpc 中间件&拦截器
    • grpc 负载均衡
    • grpc 身份认证
    • grpc 超时重试
    • grpc 链路追踪
    • grpc-gw将gRPC转RESTfu api
    • grpc-gw自定义选项
  • protobuf

    • protobuf 进阶
    • protobuf 编码原理
  • Docker

    • Docker基础
    • Docker常用命令
    • Dockerfile
    • Docker-Compose
    • Docker多阶段构建
    • Docker Config 教程
    • Docker Swarm 教程
    • Docker Stack 教程
    • Docker Buildx 教程
  • k8s

    • k8s 基础概念
    • k8s 集群架构
    • k8s 工作负载
    • Pod 网络
    • Service 网络
    • 外部接入网络
    • 一张图搞懂k8s各种pod
    • k8s 存储抽象
    • mac快速启动k8s
    • 自制申威架构k8s-reloader
  • go-kit

    • go-kit初识
    • go-kit启动http服务
    • go-kit集成gin启动服务
    • go-kit集成grpc和protobuf
    • go-kit中间件
    • go-kit服务注册发现与负载均衡
    • go-kit限流和熔断
    • go-kit链路追踪
    • go-kit集成Prometheus
  • 设计模式

    • 初识设计模式
    • 创建型模式
    • 结构型模式
    • 行为模式
  • 数据结构

    • 时间轮
    • 堆、双向链表、环形队列
    • 队列:优先队列
    • 队列:延迟队列
  • 算法

    • 递归算法
    • 枚举算法
    • 动态规划
    • 回溯算法
    • 分治算法
    • 贪心算法
    • LRU和LFU
    • 一致性哈希

花开半夏,半夏花开
首页
  • Go语言基础

    • 数据类型
    • 反射
    • Go指针
  • Go语言进阶

    • go泛型
    • go条件编译
    • cgo教程
    • Go协程调度原理及GPM模型
    • Go内存管理
    • Go垃圾回收机制
    • Go语言内存对齐
  • Go语言实现原理

    • channel 实现原理
    • slice 实现原理
    • map 实现原理
    • sync.Mutex 实现原理
    • 乐观锁CAS 实现原理
    • singlefight 实现原理
  • gin框架

    • gin中间件原理
    • gin路由原理
  • gorm

    • GORM介绍和使用
    • GORM_CURD操作指南
  • go测试

    • benchmark基准测试
    • pprof 性能分析
  • python进阶

    • Numpy&Pandas
    • celery分布式任务队列
  • Django

    • Django 常见命令
    • middleware中间件
    • Django缓存系统
    • Django信号系统
    • Django REST Framework
  • Flask

    • Flask基础知识总结
    • Flask-SQLAlchemy
  • 爬虫

    • aiohttp
    • scrapy框架
  • Mysql

    • Mysql存储引擎和索引
    • MySQL主从复制
    • Mysql读写分离
    • 数据库分库分表
    • Mysql锁
    • Mysql事务和MVCC原理
    • 分库分表带来的读扩散问题
  • Redis

    • redis基础和数据类型
    • redis主从架构
    • redis哨兵架构
    • redis集群模式
    • 如何保证缓存和数据库双写一致
    • redis底层数据结构
    • redis分布式锁
  • Elasticsearch

    • es基本概念
    • es基础语法
    • es倒排索引
  • etcd

    • Go操作etcd
    • Raft原理
    • etcd分布式锁
  • kafka

    • 消息队列MQ总结
    • kafka 概述及原理
    • kafka 消费问题记录
    • 零拷贝技术
    • kafka分区规范
  • RabbitMQ

    • rabbitMQ基础
    • Go操作rabbitmq
  • RocketMQ

    • 可靠消息队列 rocketMQ
  • Http&Https

    • http&https
    • TCP和UDP
    • Ping 原理
  • RPC

    • RPC初识
    • grpc初识和实现
  • gRPC

    • grpc 初识
    • grpc 上下文 metadata
    • grpc 健康检查
    • grpc keepalive
    • grpc 命名解析
    • grpc 中间件&拦截器
    • grpc 负载均衡
    • grpc 身份认证
    • grpc 超时重试
    • grpc 链路追踪
    • grpc-gw将gRPC转RESTfu api
    • grpc-gw自定义选项
  • protobuf

    • protobuf 进阶
    • protobuf 编码原理
  • Docker

    • Docker基础
    • Docker常用命令
    • Dockerfile
    • Docker-Compose
    • Docker多阶段构建
    • Docker Config 教程
    • Docker Swarm 教程
    • Docker Stack 教程
    • Docker Buildx 教程
  • k8s

    • k8s 基础概念
    • k8s 集群架构
    • k8s 工作负载
    • Pod 网络
    • Service 网络
    • 外部接入网络
    • 一张图搞懂k8s各种pod
    • k8s 存储抽象
    • mac快速启动k8s
    • 自制申威架构k8s-reloader
  • go-kit

    • go-kit初识
    • go-kit启动http服务
    • go-kit集成gin启动服务
    • go-kit集成grpc和protobuf
    • go-kit中间件
    • go-kit服务注册发现与负载均衡
    • go-kit限流和熔断
    • go-kit链路追踪
    • go-kit集成Prometheus
  • 设计模式

    • 初识设计模式
    • 创建型模式
    • 结构型模式
    • 行为模式
  • 数据结构

    • 时间轮
    • 堆、双向链表、环形队列
    • 队列:优先队列
    • 队列:延迟队列
  • 算法

    • 递归算法
    • 枚举算法
    • 动态规划
    • 回溯算法
    • 分治算法
    • 贪心算法
    • LRU和LFU
    • 一致性哈希
  • go语言基础

  • go语言进阶

  • go语言实现原理

  • gin框架

  • gorm

  • go测试

    • 单元测试
    • benchmark基准测试
    • pprof 性能分析
      • pprof简介
      • pprof 监控内容
      • pprof 使用方法
        • 1. 非Web应用程序
        • 1.1 CPU性能分析
        • 1.2 内存性能优化
        • 2. Web应用程序
      • go tool pprof命令
      • Demo
        • 1. 命令行交互界面
        • 2. 图形化
      • 火焰图
      • pprof与性能测试结合
  • Go语言
  • go测试
bigox
2022-06-28
目录

pprof 性能分析

# pprof简介

  • golang代码的性能监控使用pprof包来做。pprof有两个包:

    • runtime/pprofpprof的具体实现,所有类型的代码都可以使用。如果不是Web应用程序,建议使用该包。
    • net/http/pprof对runtime/pprof包进行简单封装,并在http端口上暴露出来。适合Web应用程序使用。
  • pprof开启后,每隔一段时间(10ms)就会收集下当前的堆栈信息,获取各个函数占用的CPU以及内存资源;最后通过对这些采样数据进行分析,形成一个性能分析报告。

    注意,我们只应该在性能测试的时候才在代码中引入pprof

# pprof 监控内容

类型 描述 备注
allocs 内存分配情况的采样信息 可以用浏览器打开,但可读性不高
blocks 阻塞操作情况的采样信息 可以用浏览器打开,但可读性不高
cmdline 显示程序启动命令及参数 可以用浏览器打开,但可读性不高
goroutine 当前所有协程的堆栈信息 可以用浏览器打开,但可读性不高
heap 堆上内存使用情况的采样信息 可以用浏览器打开,但可读性不高
mutex 锁争用情况的采样信息 可以用浏览器打开,但可读性不高
profile CPU 占用情况的采样信息 浏览器打开会下载文件
threadcreate 系统线程创建情况的采样信息 可以用浏览器打开,但可读性不高
trace 程序运行跟踪信息 浏览器打开会下载文件

# pprof 使用方法

# 1. 非Web应用程序

  • 如果你的应用程序是运行一段时间就结束退出类型。那么最好的办法是在应用退出的时候把 profiling 的报告保存到文件中,进行分析。对于这种情况,可以使用runtime/pprof库。 首先在代码中导入runtime/pprof工具:

    import "runtime/pprof"
    
    1

# 1.1 CPU性能分析

  • 开启CPU性能分析:

    pprof.StartCPUProfile(w io.Writer)
    
    1
  • 停止CPU性能分析:

    pprof.StopCPUProfile()	
    
    1
  • 应用执行结束后,就会生成一个文件,保存了我们的 CPU profiling 数据。得到采样数据之后,使用go tool pprof工具进行CPU性能分析。

# 1.2 内存性能优化

  • 记录程序的堆栈信息

    pprof.WriteHeapProfile(w io.Writer)
    
    1
  • 得到采样数据之后,使用go tool pprof工具进行内存性能分析。

  • go tool pprof默认是使用-inuse_space进行统计,还可以使用-inuse-objects查看分配对象的数量。

# 2. Web应用程序

  • 如果使用了默认的http.DefaultServeMux(通常是代码直接使用 http.ListenAndServe(“0.0.0.0:8000”, nil)),只需要在你的web server端代码中按如下方式导入net/http/pprof

    import _ "net/http/pprof"
    
    1
  • 如果你使用自定义的 Mux,则需要手动注册一些路由规则:

    r.HandleFunc("/debug/pprof/", pprof.Index)
    r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
    r.HandleFunc("/debug/pprof/profile", pprof.Profile)
    r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
    r.HandleFunc("/debug/pprof/trace", pprof.Trace)
    
    1
    2
    3
    4
    5
  • 如果你使用的是gin框架,那么推荐使用github.com/gin-contrib/pprof (opens new window),在代码中通过以下命令注册pprof相关路由。

    pprof.Register(router)
    
    1
  • 不管哪种方式,你的 HTTP 服务都会多出/debug/pprof endpoint,访问它会得到类似下面的内容:

    debug/pprof

  • 这个路径下还有几个子页面:

    • /debug/pprof/profile:访问这个链接会自动进行 CPU profiling,持续 30s,并生成一个文件供下载

    • /debug/pprof/heap: Memory Profiling 的路径,访问这个链接会得到一个内存 Profiling 结果的文件

    • /debug/pprof/block:block Profiling 的路径

    • /debug/pprof/goroutines:运行的 goroutines 列表,以及调用关系

# go tool pprof命令

  • 不管是工具型应用还是服务型应用,我们使用相应的pprof库获取数据之后,下一步的都要对这些数据进行分析,我们可以使用go tool pprof命令行工具。

  • go tool pprof最简单的使用方式为:

    go tool pprof [binary] [source]
    
    1
    • 其中:

      • binary 是应用的二进制文件,用来解析各种符号;

      • source 表示 profile 数据的来源,可以是本地的文件,也可以是 http 地址。

    注意事项: 获取的 Profiling 数据是动态的,要想获得有效的数据,请保证应用处于较大的负载(比如正在生成中运行的服务,或者通过其他工具模拟访问压力)。否则如果应用处于空闲状态,得到的结果可能没有任何意义。

# Demo

  • 示例一段有问题的代码

    // pprof/main.go
    package main
    
    import (
    	"flag"
    	"fmt"
    	"os"
    	"runtime/pprof"
    	"time"
    )
    
    // 一段有问题的代码
    func logicCode() {
    	var c chan int
    	for {
    		select {
    		case v := <-c:
    			fmt.Printf("recv from chan, value:%v\n", v)
    		default:
    
    		}
    	}
    }
    
    func main() {
    	var isCPUPprof bool
    	var isMemPprof bool
    
    	flag.BoolVar(&isCPUPprof, "cpu", false, "turn cpu pprof on")
    	flag.BoolVar(&isMemPprof, "mem", false, "turn mem pprof on")
    	flag.Parse()
    
    	if isCPUPprof {
    		file, err := os.Create("./cpu.pprof")
    		if err != nil {
    			fmt.Printf("create cpu pprof failed, err:%v\n", err)
    			return
    		}
    		pprof.StartCPUProfile(file)
    		defer pprof.StopCPUProfile()
    	}
    	for i := 0; i < 8; i++ {
    		go logicCode()
    	}
    	time.Sleep(20 * time.Second)
    	if isMemPprof {
    		file, err := os.Create("./mem.pprof")
    		if err != nil {
    			fmt.Printf("create mem pprof failed, err:%v\n", err)
    			return
    		}
    		pprof.WriteHeapProfile(file)
    		file.Close()
    	}
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    • 打包运行
      • go build main.go
      • ./main -cpu
  • 等待30秒后会在当前目录下生成一个cpu.pprof文件。

# 1. 命令行交互界面

  • 使用go工具链里的pprof来分析一下。

    go tool pprof cpu.pprof
    
    1
  • 执行上面的代码会进入交互界面如下:

    runtime_pprof $ go tool pprof cpu.pprof
    Type: cpu
    Time: Jun 28, 2019 at 11:28am (CST)
    Duration: 20.13s, Total samples = 1.91mins (568.60%)
    Entering interactive mode (type "help" for commands, "o" for options)
    (pprof)  
    
    1
    2
    3
    4
    5
    6
  • 我们可以在交互界面输入top3来查看程序中占用CPU前3位的函数:

    (pprof) top3
    Showing nodes accounting for 100.37s, 87.68% of 114.47s total
    Dropped 17 nodes (cum <= 0.57s)
    Showing top 3 nodes out of 4
          flat  flat%   sum%        cum   cum%
        42.52s 37.15% 37.15%     91.73s 80.13%  runtime.selectnbrecv
        35.21s 30.76% 67.90%     39.49s 34.50%  runtime.chanrecv
        22.64s 19.78% 87.68%    114.37s 99.91%  main.logicCode
    
    1
    2
    3
    4
    5
    6
    7
    8
    • flat:当前函数占用CPU的耗时

    • flat%::当前函数占用CPU的耗时百分比

    • sun%:函数占用CPU的耗时累计百分比

    • cum:当前函数加上调用当前函数的函数占用CPU的总耗时

    • cum%:当前函数加上调用当前函数的函数占用CPU的总耗时百分比

    • 最后一列:函数名称

  • 在大多数的情况下,我们可以通过分析这五列得出一个应用程序的运行情况,并对程序进行优化。

  • 我们还可以使用list 函数名命令查看具体的函数分析,例如执行list logicCode查看我们编写的函数的详细分析。

    (pprof) list logicCode
    Total: 1.91mins
    ROUTINE ================ main.logicCode in .../runtime_pprof/main.go
        22.64s   1.91mins (flat, cum) 99.91% of Total
             .          .     12:func logicCode() {
             .          .     13:   var c chan int
             .          .     14:   for {
             .          .     15:           select {
             .          .     16:           case v := <-c:
        22.64s   1.91mins     17:                   fmt.Printf("recv from chan, value:%v\n", v)
             .          .     18:           default:
             .          .     19:
             .          .     20:           }
             .          .     21:   }
             .          .     22:}
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
  • 通过分析发现大部分CPU资源被17行占用,我们分析出select语句中的default没有内容会导致上面的case v:=<-c:一直执行。我们在default分支添加一行time.Sleep(time.Second)即可。

# 2. 图形化

  • 或者可以直接输入web,通过svg图的方式查看程序中详细的CPU占用情况。 想要查看图形化的界面首先需要安装graphviz (opens new window)图形化工具。
    • Mac:

      brew install graphviz
      
      1
    • Windows: 下载graphviz (opens new window) 将graphviz安装目录下的bin文件夹添加到Path环境变量中。 在终端输入dot -version查看是否安装成功。

image-20220628173750115

  • 关于图形的说明: 每个框代表一个函数,理论上框的越大表示占用的CPU资源越多。 方框之间的线条代表函数之间的调用关系。 线条上的数字表示函数调用的次数。 方框中的第一行数字表示当前函数占用CPU的百分比,第二行数字表示当前函数累计占用CPU的百分比。

  • 除了分析CPU性能数据,pprof也支持分析内存性能数据。比如,使用下面的命令分析http服务的heap性能数据,查看当前程序的内存占用以及热点内存对象使用的情况。

    # 查看内存占用数据
    go tool pprof -inuse_space http://127.0.0.1:8080/debug/pprof/heap
    go tool pprof -inuse_objects http://127.0.0.1:8080/debug/pprof/heap
    # 查看临时内存分配数据
    go tool pprof -alloc_space http://127.0.0.1:8080/debug/pprof/heap
    go tool pprof -alloc_objects http://127.0.0.1:8080/debug/pprof/heap
    
    1
    2
    3
    4
    5
    6

# 火焰图

  • 火焰图(Flame Graph)是 Bredan Gregg 创建的一种性能分析图表,因为它的样子近似火焰而得名。golang性能监控结果可以转换成火焰图来进行直观展示。火焰图 svg 文件可以通过浏览器打开,它展示调用图的最大优点是火焰图动态的——可以通过点击每个方块来分析它上层概况/下层详细的内容。

  • 火焰图的调用顺序从下到上,每个方块代表一个函数,它上面一层表示这个函数会调用哪些函数,方块的大小代表了占用资源值的多少(例如,CPU使用时间的长短,内存使用的大小等)。火焰图的配色并没有特殊的意义,默认的红、黄配色是为了更像火焰而已。 生成火焰图,有两种方式:go-torch(golang version < 1.10)和golang原生的pprof(golang version < 1.10+的pprof集成了火焰图功能)

  • 用go tool pprof可以在Web界面上查看所有类型的资源监控图。

    • 例如,使用pprof查看Web服务器的阻塞监控数据,并将结果展示在6061端口。通过**http://localhost:6062/ui/flamegraph (opens new window)**即可查看生成的火焰图。

      $ go tool pprof -http=:6061 http://localhost:6060/debug/pprof/block
      
      Fetching profile over HTTP from http://localhost:6060/debug/pprof/block
      Saved profile in /home/jerry/pprof/pprof.___go_build_main_go.contentions.delay.005.pb.gz
      
      1
      2
      3
      4
  • 从执行命令的过程,可以看到pprof工具从http://localhost:6060/debug/pprof/block (opens new window)获取监控数据,并保存到本地:/home/jerry/pprof/pprof.___go_build_main_go.contentions.delay.005.pb.gz。然后对该文件进行分析,并启动一个Web服务器:http://localhost:6061 (opens new window)。一般会自动弹出一个浏览器并显示结果——默认显示的是graph。但是可以从第一行的菜单中切换View,选择Flame Graph即可显示火焰图。

    image-20220628192032008

# pprof与性能测试结合

  • go test命令有两个参数和 pprof 相关,它们分别指定生成的 CPU 和 Memory profiling 保存的文件:

    • -cpuprofile:cpu profiling 数据要保存的文件地址

    • -memprofile:memory profiling 数据要报文的文件地址

  • 我们还可以选择将pprof与性能测试相结合,比如:

    • 下面执行测试的同时,也会执行 CPU profiling,并把结果保存在 cpu.prof 文件中:

      go test -bench . -cpuprofile=cpu.prof
      
      1
      • 比如下面执行测试的同时,也会执行 Mem profiling,并把结果保存在 cpu.prof 文件中:
      go test -bench . -memprofile=./mem.prof
      
      1
  • 需要注意的是,Profiling 一般和性能测试一起使用,这个原因在前文也提到过,只有应用在负载高的情况下 Profiling 才有意义。

#Go#
上次更新: 2023/04/16, 18:35:33
benchmark基准测试

← benchmark基准测试

最近更新
01
go与http代理
05-24
02
自制申威架构k8s-reloader
12-06
03
Docker Buildx 教程
12-01
更多文章>
Theme by Vdoing | Copyright © 2020-2024 小刘扎扎 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式