Skip to content

Commit 7e5582c

Browse files
committed
goroutine&threadcreate
1 parent 94d156b commit 7e5582c

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed

pprof.md

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,199 @@ func scaleHeapSample(count, size, rate int64) (int64, int64) {
304304
return int64(float64(count) * scale), int64(float64(size) * scale)
305305
}
306306
```
307+
307308
为什么要在标题里加个伪? 看上面代码片段也可以注意到, 实质上在 `pprof` 分析的时候并没有扫描所有堆上内存进行分析 (想想也不现实) , 而是通过之前采样出的数据, 进行计算 (现有对象数量, 大小, 采样率等) 来估算出 `heap` 上的情况, 当然给我们参考一般来说是足够了
309+
310+
## goroutine
311+
- debug >= 2 的情况, 直接进行堆栈输出, 详情可以查看 [stack](runtime_stack.md) 章节
312+
313+
```go
314+
// fetch == runtime.GoroutineProfile
315+
func writeRuntimeProfile(w io.Writer, debug int, name string, fetch func([]runtime.StackRecord) (int, bool)) error {
316+
// Find out how many records there are (fetch(nil)),
317+
// allocate that many records, and get the data.
318+
// There's a race—more records might be added between
319+
// the two calls—so allocate a few extra records for safety
320+
// and also try again if we're very unlucky.
321+
// The loop should only execute one iteration in the common case.
322+
var p []runtime.StackRecord
323+
n, ok := fetch(nil)
324+
for {
325+
// Allocate room for a slightly bigger profile,
326+
// in case a few more entries have been added
327+
// since the call to ThreadProfile.
328+
p = make([]runtime.StackRecord, n+10)
329+
n, ok = fetch(p)
330+
if ok {
331+
p = p[0:n]
332+
break
333+
}
334+
// Profile grew; try again.
335+
}
336+
337+
return printCountProfile(w, debug, name, runtimeProfile(p))
338+
}
339+
```
340+
341+
```go
342+
// GoroutineProfile returns n, the number of records in the active goroutine stack profile.
343+
// If len(p) >= n, GoroutineProfile copies the profile into p and returns n, true.
344+
// If len(p) < n, GoroutineProfile does not change p and returns n, false.
345+
//
346+
// Most clients should use the runtime/pprof package instead
347+
// of calling GoroutineProfile directly.
348+
func GoroutineProfile(p []StackRecord) (n int, ok bool) {
349+
gp := getg()
350+
351+
isOK := func(gp1 *g) bool {
352+
// Checking isSystemGoroutine here makes GoroutineProfile
353+
// consistent with both NumGoroutine and Stack.
354+
return gp1 != gp && readgstatus(gp1) != _Gdead && !isSystemGoroutine(gp1, false)
355+
}
356+
// 熟悉的味道, STW 又来了
357+
stopTheWorld("profile")
358+
// 统计有多少 goroutine
359+
n = 1
360+
for _, gp1 := range allgs {
361+
if isOK(gp1) {
362+
n++
363+
}
364+
}
365+
// 当传入的 p 非空的时候, 开始获取各个 goroutine 信息, 整体姿势和 stack api 几乎一模一样
366+
if n <= len(p) {
367+
ok = true
368+
r := p
369+
370+
// Save current goroutine.
371+
sp := getcallersp()
372+
pc := getcallerpc()
373+
systemstack(func() {
374+
saveg(pc, sp, gp, &r[0])
375+
})
376+
r = r[1:]
377+
378+
// Save other goroutines.
379+
for _, gp1 := range allgs {
380+
if isOK(gp1) {
381+
if len(r) == 0 {
382+
// Should be impossible, but better to return a
383+
// truncated profile than to crash the entire process.
384+
break
385+
}
386+
saveg(^uintptr(0), ^uintptr(0), gp1, &r[0])
387+
r = r[1:]
388+
}
389+
}
390+
}
391+
392+
startTheWorld()
393+
394+
return n, ok
395+
}
396+
```
397+
总结下 `pprof/goroutine`
398+
- STW 操作, 如果需要观察详情的需要注意这个 API 带来的风险
399+
- 整体流程基本就是 stackdump 所有协程信息的流程, 差别不大没什么好讲的, 不熟悉的可以去看下 stack 对应章节
400+
401+
## pprof/threadcreate
402+
可能会有人想问, 我们通常只关注 `goroutine` 就够了, 为什么还需要对线程的一些情况进行追踪? 例如无法被抢占的阻塞性[系统调用](syscall.md), `cgo` 相关的线程等等, 都可以利用它来进行一个简单的分析, 当然大多数情况考虑的线程问题(诸如泄露等), 一般都是上层的使用问题所导致的(线程泄露等)
403+
```go
404+
// 还是用之前用过的无法被抢占的阻塞性系统调用来进行一个简单的实验
405+
package main
406+
407+
import (
408+
"fmt"
409+
"net/http"
410+
_ "net/http/pprof"
411+
"os"
412+
"syscall"
413+
"unsafe"
414+
)
415+
416+
const (
417+
SYS_futex = 202
418+
_FUTEX_PRIVATE_FLAG = 128
419+
_FUTEX_WAIT = 0
420+
_FUTEX_WAKE = 1
421+
_FUTEX_WAIT_PRIVATE = _FUTEX_WAIT | _FUTEX_PRIVATE_FLAG
422+
_FUTEX_WAKE_PRIVATE = _FUTEX_WAKE | _FUTEX_PRIVATE_FLAG
423+
)
424+
425+
func main() {
426+
fmt.Println(os.Getpid())
427+
go func() {
428+
b := make([]byte, 1<<20)
429+
_ = b
430+
}()
431+
for i := 1; i < 13; i++ {
432+
go func() {
433+
var futexVar int = 0
434+
for {
435+
// Syscall && RawSyscall, 具体差别分析可自行查看 syscall 章节
436+
fmt.Println(syscall.Syscall6(
437+
SYS_futex, // trap AX 202
438+
uintptr(unsafe.Pointer(&futexVar)), // a1 DI 1
439+
uintptr(_FUTEX_WAIT), // a2 SI 0
440+
0, // a3 DX
441+
0, //uintptr(unsafe.Pointer(&ts)), // a4 R10
442+
0, // a5 R8
443+
0))
444+
}
445+
}()
446+
}
447+
http.ListenAndServe("0.0.0.0:8899", nil)
448+
}
449+
```
450+
```shell
451+
# GET /debug/pprof/threadcreate?debug=1
452+
threadcreate profile: total 18
453+
17 @
454+
# 0x0
455+
456+
1 @ 0x43b818 0x43bfa3 0x43c272 0x43857d 0x467fb1
457+
# 0x43b817 runtime.allocm+0x157 /usr/local/go/src/runtime/proc.go:1414
458+
# 0x43bfa2 runtime.newm+0x42 /usr/local/go/src/runtime/proc.go:1736
459+
# 0x43c271 runtime.startTemplateThread+0xb1 /usr/local/go/src/runtime/proc.go:1805
460+
# 0x43857c runtime.main+0x18c /usr/local/go/src/runtime/proc.go:186
461+
```
462+
```shell
463+
# 再结合诸如 pstack 的工具
464+
ps -efT | grep 22298 # pid = 22298
465+
root 22298 22298 13767 0 16:59 pts/4 00:00:00 ./mstest
466+
root 22298 22299 13767 0 16:59 pts/4 00:00:00 ./mstest
467+
root 22298 22300 13767 0 16:59 pts/4 00:00:00 ./mstest
468+
root 22298 22301 13767 0 16:59 pts/4 00:00:00 ./mstest
469+
root 22298 22302 13767 0 16:59 pts/4 00:00:00 ./mstest
470+
root 22298 22303 13767 0 16:59 pts/4 00:00:00 ./mstest
471+
root 22298 22304 13767 0 16:59 pts/4 00:00:00 ./mstest
472+
root 22298 22305 13767 0 16:59 pts/4 00:00:00 ./mstest
473+
root 22298 22306 13767 0 16:59 pts/4 00:00:00 ./mstest
474+
root 22298 22307 13767 0 16:59 pts/4 00:00:00 ./mstest
475+
root 22298 22308 13767 0 16:59 pts/4 00:00:00 ./mstest
476+
root 22298 22309 13767 0 16:59 pts/4 00:00:00 ./mstest
477+
root 22298 22310 13767 0 16:59 pts/4 00:00:00 ./mstest
478+
root 22298 22311 13767 0 16:59 pts/4 00:00:00 ./mstest
479+
root 22298 22312 13767 0 16:59 pts/4 00:00:00 ./mstest
480+
root 22298 22316 13767 0 16:59 pts/4 00:00:00 ./mstest
481+
root 22298 22317 13767 0 16:59 pts/4 00:00:00 ./mstest
482+
483+
pstack 22299
484+
Thread 1 (process 22299):
485+
#0 runtime.futex () at /usr/local/go/src/runtime/sys_linux_amd64.s:568
486+
#1 0x00000000004326f4 in runtime.futexsleep (addr=0xb2fd78 <runtime.sched+280>, val=0, ns=60000000000) at /usr/local/go/src/runtime/os_linux.go:51
487+
#2 0x000000000040cb3e in runtime.notetsleep_internal (n=0xb2fd78 <runtime.sched+280>, ns=60000000000, ~r2=<optimized out>) at /usr/local/go/src/runtime/lock_futex.go:193
488+
#3 0x000000000040cc11 in runtime.notetsleep (n=0xb2fd78 <runtime.sched+280>, ns=60000000000, ~r2=<optimized out>) at /usr/local/go/src/runtime/lock_futex.go:216
489+
#4 0x00000000004433b2 in runtime.sysmon () at /usr/local/go/src/runtime/proc.go:4558
490+
#5 0x000000000043af33 in runtime.mstart1 () at /usr/local/go/src/runtime/proc.go:1112
491+
#6 0x000000000043ae4e in runtime.mstart () at /usr/local/go/src/runtime/proc.go:1077
492+
#7 0x0000000000401893 in runtime/cgo(.text) ()
493+
#8 0x00007fb1e2d53700 in ?? ()
494+
#9 0x0000000000000000 in ?? ()
495+
```
496+
其他的线程如果感兴趣也可以仔细查看
497+
498+
`pprof/threadcreate` 具体实现和 `pprof/goroutine` 类似, 无非前者遍历的对象是全局 `allm`, 而后者为 `allgs`, 区别在于 `pprof/threadcreate => ThreadCreateProfile` 时不会进行进行 `STW`
499+
500+
308501
# 参考资料
309502
https://go-review.googlesource.com/c/go/+/299671

0 commit comments

Comments
 (0)