在軟體開發與維運的漫漫長路中,效能問題如同一隻隻難以捉摸的幽靈,時而顯現,時而隱匿。傳統的效能分析(Profiling)方法,往往像是在特定時間點拍攝一張快照,雖能捕捉到瞬間的狀態,卻難以洞察在真實生產環境中,那些與用戶負載、時間推移相關的效能瓶頸。為了解決這個困境,持續性效能分析(Continuous Profiling) 的理念應運而生。
它主張對應用程式進行全天候、低開銷的效能監控,將無數個效能快照匯集成一部紀錄片,讓我們得以隨時回溯,精準定位任何時間點的效能瓶頸。Grafana Pyroscope 正是實現此理念的佼佼者,它是一個開源的持續性效能分析平台,能持續收集、儲存並以火焰圖(Flame Graph) 的形式,直觀地視覺化應用程式的效能數據。
今天,我們將從零開始,親手搭建一個完整的實驗環境,感受 Pyroscope 如何為我們揭示程式碼的效能秘密。
首先,我們需要一個「被觀察」的對象。這裡,我們編寫一個簡單的 Python 應用程式,它會模擬不同的工作負載。建立一個名為 main.py
的檔案,並填入以下內容:
import os
import time
import threading
import pyroscope
# 從環境變數初始化 Pyroscope
pyroscope.configure(
application_name="my.python.app", # 應用名稱,將顯示在 Grafana 中
server_address=os.environ.get("PYROSCOPE_SERVER_ADDRESS", "http://pyroscope:4040"),
enable_logging=True,
)
def slow_function():
"""一個模擬耗時較長的操作"""
time.sleep(0.1)
def fast_function():
"""一個模擬耗時較短的操作"""
time.sleep(0.05)
def work(tags):
"""主要的工作負載,會呼叫其他函式"""
# 使用 tag_wrapper 為這個執行緒的效能數據打上標籤
with pyroscope.tag_wrapper(tags):
while True:
fast_function()
slow_function()
print(f"Task with tags {tags} finished a cycle.")
time.sleep(1) # 每秒執行一次循環
if __name__ == "__main__":
print("Starting Python application with continuous profiling...")
# 建立並啟動一個執行緒,標記為 "fast-lane"
threading.Thread(target=work, args=({"speed": "fast"},)).start()
# 建立並啟動另一個執行緒,標記為 "slow-lane"
threading.Thread(target=work, args=({"speed": "slow"},)).start()
這個腳本的精華在於 pyroscope.configure
初始化了分析工具,並透過 pyroscope.tag_wrapper
為不同的執行緒(我們用以模擬快、慢兩種任務)打上了 speed="fast"
和 speed="slow"
的標籤。這將在後續的分析中展現出強大的威力。
為了讓這個 Python 應用能順利執行,我們還需要告訴 Python 它依賴哪些套件。建立一個名為 requirements.txt
的檔案,內容僅需一行:
pyroscope-io
接下來,我們使用 Docker Compose 來編排我們的觀測環境,它將包含 Pyroscope 伺服器、我們剛才設計的 Python 應用,以及用於視覺化的 Grafana。建立一個名為 docker-compose.yml
的檔案:
version: '3.8'
services:
grafana:
image: grafana/grafana:10.2.0
ports:
- "3000:3000"
volumes:
- ./grafana-provisioning/datasources:/etc/grafana/provisioning/datasources
environment:
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_DISABLE_LOGIN_FORM=true
networks:
- loki
pyroscope:
image: grafana/pyroscope:1.1.0
ports:
- "4040:4040"
networks:
- loki
app:
image: python:3.9-slim
volumes:
- ./app:/app
working_dir: /app
command: pip install -r requirements.txt && python main.py
environment:
- PYROSCOPE_SERVER_ADDRESS=http://pyroscope:4040
depends_on:
- pyroscope
networks:
- loki
networks:
loki:
driver: bridge
這個設定檔定義了三個服務,並讓它們共享一個名為 loki
的網路,以便互相通訊。
為了讓 Grafana 一啟動就能自動連接到 Pyroscope,我們利用 Grafana 的 Provisioning(自動化佈建)功能。我們需要建立一個設定檔,告訴 Grafana Pyroscope 伺服器的位置。
請建立如下的路徑與檔案:grafana-provisioning/datasources/datasource.yml
,並填入以下內容:
apiVersion: 1
datasources:
- name: Pyroscope
type: grafana-pyroscope-datasource
access: proxy
url: http://pyroscope:4040
isDefault: true
version: 1
editable: false
萬事俱備!現在,請將 main.py
和 requirements.txt
放在名為 app
的子目錄中,並將 datasource.yml
放在 grafana-provisioning/datasources
的路徑下。你的目錄結構看起來應該像這樣:
. (你的工作目錄)
├── docker-compose.yml
├── app/
│ ├── main.py
│ └── requirements.txt
└── grafana-provisioning/
└── datasources/
└── datasource.yml
在你的工作目錄下,執行以下指令啟動所有服務:
docker-compose up -d
等待片刻,待 Docker 下載映像檔並啟動容器後,我們的實驗就正式開始了。
http://localhost:3000
。my.python.app
,然後點擊「Run query」。螢幕上出現的,就是著名的「火焰圖」。圖中每個長條代表一個函式,長條的寬度與它佔用的 CPU 時間成正比。透過這張圖,slow_function
佔用了更多時間的秘密便一目了然。speed
,然後在「Value」中選擇 fast
。再次查詢,火焰圖便只會顯示 speed="fast"
那個執行緒的效能數據。切換到 slow
,你就能精準比較兩種不同任務的效能開銷。恭喜你!今天,你不僅學會了如何搭建一個持續性效能分析環境,更親身體驗了如何像偵探一樣,利用 Pyroscope 和火焰圖,從程式碼的蛛絲馬跡中找出效能的真相。將這項技術融入你的開發流程,將賦予你一雙洞察系統瓶頸的火眼金睛。