contributed by < csotaku0926
>
古典方法可用 ab
(Apache bending tool) 這項工具進行伺服器的壓力測試
例如測量 sehttpd
伺服器
就以下指令來說
-n
: 在 benchmarking 階段發送 10000 條請求,-c
: 在同一時間發送的請求數量,concurrecny-k
: 開啟 HTTP "Keep Alive" 設置,也就是在同一 HTTP session 執行多個請求部份測量結果數據:
但 ab
無法反映多執行緒特性(自身已消耗單核 100% 運算量)
所以使用 wrk 這項工具,可以針對多核場景測量
這項工具允許開啟多個執行緒,可依據測試端的 CPU 數量進行調整
注意到利用 ab
以及 wrk
所測量出的 transfer rate 相差二十幾個 MBytes
也可以使用 htstress
工具
另外,經過長時間的開啟 sehttpd
,出現以下錯誤訊息
動態追蹤允許「非侵入」的方式,不需更動內部系統的運作,可以獲取需要的資訊
傳統的封包過濾,需要將位於核心的封包傳進 (當然一開始封包是由網卡接收) 使用者空間 (user space),而 BPF 的核心概念為讓使用者透過額外的過濾程式告訴核心,應該過濾哪些封包。
好處顯而易見,可以在封包一進入核心空間就進行過濾,避免無用封包進入網路堆疊 (network stack) 到應用層
以網路封包的獲取為例,tcpdump
將透過 libpcap
轉譯後的濾包條件,送給位於核心的 BPF 模組,再由其將符合條件的封包送回 tcpdump
eBPF 允許使用者以高效率的方式,撰寫程式附加於 Linux 核心內部,以達到事件監聽,追蹤系統呼叫等功能
當我們提到測量 kthread 執行成本,我們的關注點在於其執行花費時間 ,可以使用 eBPF 測量
觀察測試的 BPF 程式碼:
其中 BPF_HASH(start, u64)
(ref) 創建一個名叫 start
的雜湊表,他的 key 是 struct request*
形式,value (這裡為 timestamp) 形式則是 u64
kprobe
允許使用者自行定義 callback function,並動態將探針插入大多核心函式與模組
kretprobe
則是用來取得 kprobe
的回傳值
在 callback 函式中,bpf_trace_printk
將輸出 log 儲存於 /sys/kernel/debug/tracing/trace_pipe
可以用 python bcc.BPF
module 的 trace_print
取得結果
問題是,kthread_run
並不是列於 /proc/kallsyms
中的符號之一,而是巨集
因此,在測量 khttpd
中 kthread_run
時間成本時,需要額外添加一個 function wrapper my_kthread_wrapper
包裝起來
但是 eBPF 應該只能測量系統呼叫,(如 /proc/kallsyms
列舉的呼叫),要怎麼測量這個 wrapper ? 目前無法測量 my_kthread_wrapper
, 但是位於 khttpd
的執行緒函式 http_server_worker
卻可以被測量到
測量的 BCC python code
根據 vax-r 同學的想法,可能是編譯器優化,導致 wrapper 被無視,直接執行裡面的程式
初步解決方案是將 wrapper 內的功能寫的更多一點,還需要涉及記憶體配置,ftrace
才能捕捉到
後續的 my_kthread_wrapper
如下,這次就可以捕捉到了
完成 eBPF 時間測量後,應用 gnuplot 繪圖
kthread_run
的建立成本大多落在 200 us 以下
kecho
的 kthread
與 CMWQ 效能比較壓力測試程式碼:
kthread_based
的版本在收到連線請求後才會建立 kthread
而 CMWQ 中的 workqueue 可以根據任務執行狀況安排執行緒,並且採用 thread pool 的概念,預先建立好執行緒
測量過程為先透過 make
編譯出核心檔 (.ko) 再用 ./bench
進行壓力測試
最後用 gnuplot
繪圖
根據 官方文件,
WQ_UNBOUND
Work items queued to an unbound wq are served by the special worker-pools which host workers which are not bound to any specific CPU.
WQ_UNBOUND
會讓 worker 不與特定 CPU 綁定 (bond)
為了測試 locality 效能,將參數 bench
設為真
user_echo_server
(kthread-based)kecho
(CMWQ-based)
bench=true
bench=false
反而是 bench=false
看起來比較穩定,大多數資料都集中在一處
根據 改進功能與效能 一文 ,CMWQ 版本的實作得益於 locality 以及事先準備的執行緒
面對大量連線時,CMWQ 的優勢較 kthread-based 還要突出
首先在 init_module 處配置 workqueue
khttpd
核心模組效能瓶頸,以及該如何設計相關實驗學習。搭配閱讀《Demystifying the Linux CPU Scheduler》第 6 章
Ftrace 是個位於核心的動態追蹤工具,可用於追蹤函式、事件等
可藉由寫入 /sys/kernel/debug/tracing
內的檔案來設定 ftrace
例如,透過 available_filter_functions
列出 khttpd
核心程式中可以被追蹤的函式:
接著嘗試追蹤 http_server_worker
函式
current_tracer
: 設定或顯示目前使用的 tracer ,如: function
、function_graph
set_ftrace_filter
: 指定要追蹤的函式 (只會追蹤他們)set_graph_function
: 指定要顯示呼叫關係的函式tracing_on
: 設定或顯示使用的 tracer 是否寫入到 ring buffermax_graph_depth
: function graph tracer 最大追蹤深度 (呼叫 kernel function 數量)trace
: 紀錄追蹤輸出結果最後,追蹤結果會在 $TRACE_DIR/trace
裡面
可以看到整個 http_server_worker
函式在各個內部函式所耗費的時間
以下節錄:
kecho
是 Linux 核心模組的 TCP 伺服器
telnet 是 application-layer ,使用 TCP/IP 與遠端伺服器溝通的協議
seHTTPd 是高效網路伺服器
khttpd
與 kecho
在掛載階段時,差異在後者使用 CMWQ 函式 alloc_workqueue
在 open_listen_socket
中,進行 socket 與 TCP 連線相關設定:
TCP_NODELAY
是關閉 Nagle's 算法
TCP_CORK
為了將零碎資料彙整為完整封包後再發送
可以發現伺服器本身,以及每個連線都會以 kthread
創建新的執行緒
建立 socket 後,呼叫 kthread_run
並執行函式 http_server_daemon
與 kecho
邏輯相似
首先利用 allow_signal
登記要接收的 SIGKILL
, SIGTERM
再來以 kthread_should_stop
判斷是否中止負責執行 http_server_daemon
的執行緒
使用函式 kernel_accept
接收連線,若成功建立則使用 kthread_run
建立新的執行緒執行 kthread_worker
kthread_worker
函式執行以下行為:
kthread_should_stop
判斷中止與否http_parser_execute
解讀並傳給客戶至於 kecho
則是透過 create_work
, queue_work
這種使用 struct work_item
的方式處理連線,以取代 khttpd
中kthread_run
建立連線的方式