新しいソリューションを紹介する前に、Go プログラムで pprof がどのように実行されるかを確認しましょう。
プログラムのパフォーマンス サンプリングは、そのサービス機能に影響を与える可能性があります。したがって、オンライン サンプリングは通常、指定された短い時間範囲内で実行され、効果的なスイッチ制御が必要です。
これを実現するために、通常、net/http/pprof パッケージ (その init 関数はルーティング関数関数にバインドされています) をコードに導入します。指定ポートのHTTPサービスに外部からアクセスするとサンプリング機能がオンになり、サンプリング時間が経過すると収集がオフになります。
実装コードは以下の通りです
package main import ( "net/http" _ "net/http/pprof" ) func main() { go func() { _ = http.ListenAndServe(":8080", nil) }() ... }
もちろんこのやり方でも問題ありません。これを学習して、他のスイッチ機能を HTTP サービスに組み込むことができます。しかし、他にもっとクールな方法はあるでしょうか?
在信号处理与 Go 程序的优雅退出一文中,我们谈论过信号机制,它用以向应用程序发送某种事件通知。
我们可以将基于接口触发的方式改为信号通知。
首先,构造采样功能函数(对应于 net/http/pprof 包下 init 函数中绑定的路由功能函数)。
func RegisterSignalForProfiling(sig os.Signal) { ch := make(chan os.Signal) started := false signal.Notify(ch, sig) go func() { var memoryProfile, cpuProfile, traceProfile *os.File for range ch { if started { pprof.StopCPUProfile() trace.Stop() pprof.WriteHeapProfile(memoryProfile) memoryProfile.Close() cpuProfile.Close() traceProfile.Close() started = false } else { cpuProfile, _ = os.Create("cpu.pprof") memoryProfile, _ = os.Create("memory.pprof") traceProfile, _ = os.Create("runtime.trace") pprof.StartCPUProfile(cpuProfile) trace.Start(traceProfile) started = true } } }() }
在上述函数中,我们定义了接收信号通道<span style="font-size: 15px;">ch</span>
,通过<span style="font-size: 15px;">signal.Notify(ch, sig)</span>
将指定的通知信号<span style="font-size: 15px;">sig</span>
与<span style="font-size: 15px;">ch</span>
进行绑定。<span style="font-size: 15px;">for range ch</span>
将阻塞等待外部信号<span style="font-size: 15px;">sig</span>
,随着<span style="font-size: 15px;">sig</span>
信号的到来,交替进入开启或关闭采样的逻辑。
在<span style="font-size: 15px;">main</span>
函数中,就可以这样替代<span style="font-size: 15px;">http.ListenAndServe(":8080", nil)</span>
了。
package main import ( "syscall" ... ) func main() { RegisterSignalForProfiling(syscall.Signal(31)) ... }
在 linux 系统,可以通过<span style="font-size: 15px;">kill -signal_number pid</span>
命令向程序发送指定信号。
如上代码所示,我们硬编码指定的采样开关信号值是 31。因此,当程序运行起来后,我们在控制台输入<span style="font-size: 15px;">kill -31 pid</span>
命令,即可开启采样,再次输入<span style="font-size: 15px;">kill -31 pid</span>
命令,就关闭了采样。
依葫芦画瓢,我们再来一个打印 goroutine 堆栈信息的热开关函数,是不是很酷?
func RegisterSignalForPrintStack(sig os.Signal) { ch := make(chan os.Signal) signal.Notify(ch, sig) go func() { for range ch { buffer := make([]byte, 1024*1024*4) runtime.Stack(buffer, true) fmt.Println(string(buffer)) } }() }
热开关是一个很简单常用的功能,无非是选择何种触发与等待方式。基于接口的调用更适合于远程控制,基于信号则便于本地控制。
以上がGoプログラムのホットスイッチ機能をよりかっこよく実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。