壓測工具wrk

在工作中經常用wrk對接口進行簡單的壓測,最近工作中測試接口需要對參數進行簽名校驗,借這個機會,打算仔細研究下wrk;

wrk命令選項

wrk命令選項如下圖所示:


image.png

例子:

./wrk -c 1 -d 10s -t 1 -s test.lua http://10.221.84.140:8080/test/anchor/info

其中-s選項指定lua腳本文件,下面舉一個腳本的例子:

package.path="/Users/allan/Softwares/wrk/?.lua;;"
local sha1=require("sha1")
local timestamp=os.time()*1000
local token=sha1("name=allan".."timestamp"..timestamp.."age=56".."4dfp*&ddddd4445")
wrk.method = 'POST'
wrk.body   = "timestamp="..timestamp.."&name=allan&age=56"
wrk.headers["App-Key"] = "android"
--wrk.headers["Timestamp"]=timestamp
wrk.headers["Authorization"] = token
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"


function response(status, headers, body)
   print(body)
end

可以看到lua腳本中可以調用第三方庫,動態設置參數,而且wrk基于epoll,性能強悍;

wrk源碼

wrk是開源的,其源碼地址為https://github.com/wg/wrk, 采用C語言實現;
wrk定義了全局變量wrk, 提供了如下函數供擴展:

  • setup(thread):
    初始化時調用,在init方法調用之前,thread有get、set、stop方法,有addr屬性,例如:
function setup(thread)
   thread:set("id", counter)
   table.insert(threads, thread)
   counter = counter + 1
end
  • init(args):先調用setup,再調用init,只調用一次
  • request():發送請求之前調用,例如
request = function()
    path = paths[counter]
    counter = counter + 1
    if counter > #paths then
      counter = 0
    end
    return wrk.format(nil, path)
end
  • response(status, headers, body): 接收到請求響應之后執行,例如
function response()
   if counter == 100 then
      wrk.thread:stop()
   end
   counter = counter + 1
end
  • done(summary, latency, requests): 所有線程執行完畢,最后調用
done = function(summary, latency, requests)
   io.write("------------------------------\n")
   for _, p in pairs({ 50, 90, 99, 99.999 }) do
      n = latency:percentile(p)
      io.write(string.format("%g%%,%d\n", p, n))
   end
end

wrk源文件

wrk實現很簡潔,主要的源文件包括:

  • wrk.c:
    啟動入口,包含main方法
  • script.c:
    通過c調用luajit
  • ae.c:
    網絡多路復用層的實現,包括epoll、evport、kqueue和select;FreeBSD和Apple默認使用kqueue,Linux使用epoll,Sun使用evport

下面具體看看wrk是如何實現的:
wrk有幾個重要的數據結構,包括thread和connection:

typedef struct {
    pthread_t thread;//操作系統線程對象
    aeEventLoop *loop;//持有epoll對象
    struct addrinfo *addr;//連接地址信息
    uint64_t connections;//連接數
    uint64_t complete;//完成請求數
    uint64_t requests;//發送請求數
    uint64_t bytes;//發送字節數
    uint64_t start;
    lua_State *L;//lua句柄
    errors errors;
    struct connection *cs;//連接對象
} thread;
typedef struct connection {
    thread *thread; //所屬線程
    http_parser parser;
    enum {
        FIELD, VALUE
    } state;
    int fd;
    SSL *ssl;
    bool delayed;
    uint64_t start;
    char *request;
    size_t length;
    size_t written;
    uint64_t pending;
    buffer headers;
    buffer body;
    char buf[RECVBUF];
} connection;

wrk初始化邏輯:

  1. 通過parse_args函數解析命令行參數;
  2. 創建lua_State,檢查是否可以調用lua,是否可以連接到測試地址;
  3. 根據命令行參數中指定的線程數,初始化線程對象,為每個線程創建lua_State,線程的執行函數定義在thread_main方法:
void *thread_main(void *arg) {
    thread *thread = arg;

    char *request = NULL;
    size_t length = 0;

    if (!cfg.dynamic) {//如果定義了request函數,則在每次發送請求前執行request函數;否則執行wrk.request函數
        script_request(thread->L, &request, &length);
    }

    thread->cs = zcalloc(thread->connections * sizeof(connection));
    connection *c = thread->cs;

    for (uint64_t i = 0; i < thread->connections; i++, c++) {
 //創建connection對象,每個線程要創建的connection對象個數,由命令行參數-c,-t決定,例如-c 100 -t 4,則意味著每個線程創建25個連接
        c->thread = thread;
        c->ssl     = cfg.ctx ? SSL_new(cfg.ctx) : NULL;
        c->request = request;
        c->length  = length;
        c->delayed = cfg.delay;
        connect_socket(thread, c);
    }

    aeEventLoop *loop = thread->loop;
    aeCreateTimeEvent(loop, RECORD_INTERVAL_MS, record_rate, thread, NULL);//每100ms觸發一次record_rate函數

    thread->start = time_us();
    aeMain(loop);

    aeDeleteEventLoop(loop);
    zfree(thread->cs);

    return NULL;
}
//wrk.init中定義的,req是字符串,和我們抓包看到的http請求內容一樣
local req = wrk.format()
   wrk.request = function()
      return req
   end
static void socket_connected(aeEventLoop *loop, int fd, void *data, int mask) {
    connection *c = data;

    switch (sock.connect(c, cfg.host)) {
        case OK:    break;
        case ERROR: goto error;
        case RETRY: return;
    }

    http_parser_init(&c->parser, HTTP_RESPONSE);
    c->written = 0;
   //注冊事件,當有數據可讀時,觸發socket_readable函數,當可以寫入數據時,觸發socket_writeable函數
    aeCreateFileEvent(c->thread->loop, fd, AE_READABLE, socket_readable, c);
    aeCreateFileEvent(c->thread->loop, fd, AE_WRITABLE, socket_writeable, c);

    return;

  error:
    c->thread->errors.connect++;
    reconnect_socket(c->thread, c);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容