Grafana Pyroscopeのフレームグラフ解析を試してみた
この記事は
アプリケーションのボトルネック、根本原因から修正内容までGrafana Pyroscopeに解析してもらったという記事です。ISUCONで大活躍しそうな予感がします。
はじめに
継続的プロファイリングにおけるフレームグラフですが、コールスタックが深かったりグラフの数が多いと解析が難しいですよね。
先日Flame graph AIというAIベースの解析機能があることを知り、試してみました。
Grafana Labsによると、ほとんどの人間よりAIの方が優れた回答を出した[1]とのことです。これは期待大です。
事前準備
LLMプラグイン有効化
Grafana Cloudにログインしておきます。
freeプランでもOKです。クレカ登録不要です。
LLMプラグインを以下のステップでインストールします。
https://grafana.com/grafana/plugins/grafana-llm-app/ へアクセス
インストール済みの場合は以下のように表示されます。スキップでOKです。
Grafana Cloud PortalからGrafanaのUIへアクセスします。
LLMプラグインを有効化します。
LLMと検索して表示結果をクリック
Use OpenAI provided by Grafanaをチェック > I Acceptをチェック > Save & test
BYOLな方はUse your own OpenAI accountから設定すると良いでしょう。
解析対象のアプリケーション
まずはexpressとNode.js用のPyroscopeをインストールします。
npm install express @pyroscope/nodejs
次にただただメモリを消費するひどいアプリケーションを作ります。これはひどい。
import Pyroscope from '@pyroscope/nodejs'
import express from 'express'
const app = express()
const port = 3000
Pyroscope.init({
serverAddress: SERVER_ADDRESS,
appName: APP_NAME,
tags: {
version: VERSION,
},
basicAuthUser: PYROSCOPE_BASIC_AUTH_USER',
basicAuthPassword: PYROSCOPE_BASIC_AUTH_PASSWORD,
})
Pyroscope.start()
app.get('/', async (_req, res) => {
try {
await main()
res.sendStatus(200)
} catch (e) {
console.error(e)
res.sendStatus(500)
}
})
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`)
})
const createMemoryHog = (size: number, fillValue: string) => {
const memoryHog = []
for (let i = 0; i < 50000; i++) {
memoryHog.push(new Array(size).fill(fillValue))
}
return memoryHog
}
const foo1 = () => {
createMemoryHog(1000, 'foo1')
bar1()
}
const bar1 = () => {
createMemoryHog(2000, 'bar1')
}
const foo2 = () => {
createMemoryHog(2000, 'foo2')
bar2()
}
const bar2 = () => {
createMemoryHog(4000, 'bar2')
}
const main = async () => {
foo1()
foo2()
}
Pyroscope.init()
のAPP_NAME
は任意の文字列、tags
には任意のキーバリューを指定できます。
その他のパラメータへ入れる値をPyroscopeの設定画面から取得します。
以下が設定値です。
値を置き換えます。
- URL ->
SERVER_ADDRESS
- User ->
PYROSCOPE_BASIC_AUTH_USER
- Password(Generate nowから発行したトークン) ->
PYROSCOPE_BASIC_AUTH_PASSWORD
アプリケーションを実行して localhost:3000 へアクセスします。
解析方法
しばらくすると、Grafanaでプロファイルを確認できます。foo1
, bar1
などのメソッドがフレームグラフに表示されています。
Explain Flame Graphをクリックするとボトルネック、根本原因、推奨する修正内容を教えてくれます。これはすごい。
Explain Flame Graphがクリックできない場合はLLMのプラグインが有効化されていないので、前の手順を参照して有効化してください。
指摘内容
ごもっともな指摘をいただきました。
指摘内容
Performance Bottleneck
The primary performance bottleneck in the system appears to be the significant time spent by the garbage collector (garbage collector:0
). This process consumes a substantial part of the program's execution time, indicating frequent memory allocation and deallocation, or the creation of a large number of short-lived objects.
Root Cause
The excessive time spent in garbage collection is likely a symptom of high memory usage elsewhere in the codebase. A specific function, index.js:createMemoryHog
, is responsible for a notable portion of script execution time and is directly linked to index.js:foo2
and index.js:foo1
, which also contribute significantly to execution time. This suggests that index.js:createMemoryHog
and its related functions might be producing a considerable amount of unnecessary or poorly managed memory allocations, leading to increased pressure on the garbage collection system.
Recommended Fix
-
Optimize Memory Usage:
- Review the implementation of
index.js:createMemoryHog
and its associated functions (index.js:foo2
,index.js:foo1
). Look for opportunities to reduce memory allocation, such as reusing objects or limiting the creation of temporary objects within loops.
- Review the implementation of
-
Improve Garbage Collection Efficiency:
- Implement more efficient data structures that reduce the need for frequent memory allocations/deallocations.
- Consider using pooling techniques for objects that are frequently created and destroyed, which can help in managing memory more efficiently and reduce the workload on the garbage collector.
-
Code Refactoring and Cleanup:
- Examine other areas of the code causing residual delays, such as
index.js:foo1
,index.js:foo2
, andindex.js:main
, to ensure that memory usage is minimized. - Clean up and remove unused variables or data structures that may contribute to unnecessary memory bloat.
- Examine other areas of the code causing residual delays, such as
By focusing on these areas, the system should see reduced memory usage, leading to less frequent and less intensive garbage collection cycles, ultimately enhancing overall application performance and responsiveness.
要約すると、ガベージコレクターの時間が長すぎるので、createMemoryHogなどの実装を見直してメモリの使用量を減らすことを推奨するということでした。
ここまで指南してくれると次の打ち手が見えます。
フレームグラフが多ければさらに精度の高い指摘をしてくれるかもしれません。
おわりに
自社ではプロファイラーにGoogle CloudのCloud Profilerを使っていますが、サンプル数が多かったりコールスタックが深いと分析が大変で改善活動を継続することが難しいなと思っていたので、Flame graph AIのように改善のきっかけを提示してくれる機能はとてもありがたいです。GrafanaのUIからシームレスに解析できるのも良いです。
今回は極めてシンプルなプロファイルを解析しましたが、もっと大きくて複雑な場合どのような結果が出るのか試してみようと思います。
-
"When we compared the responses from the AI system to those from the users, we found that AI is better than (most) humans at interpreting flamegraphs."
AI-powered insights for continuous profiling: introducing Flame graph AI in Grafana Cloud ↩︎
Discussion