浏览代码

Add exampels wrk

ithewei 4 年之前
父节点
当前提交
633b23b4e0
共有 6 个文件被更改,包括 289 次插入21 次删除
  1. 4 12
      .github/workflows/benchmark.yml
  2. 4 1
      Makefile
  3. 12 4
      README-CN.md
  4. 12 4
      README.md
  5. 5 0
      examples/CMakeLists.txt
  6. 252 0
      examples/wrk.cpp

+ 4 - 12
.github/workflows/benchmark.yml

@@ -22,18 +22,10 @@ jobs:
           sudo apt install libssl-dev
           sudo apt install nginx
 
-      - name: make wrk
-        run: |
-          git clone https://github.com/wg/wrk
-          pushd wrk
-          make WITH_OPENSSL=/usr
-          sudo cp wrk /usr/bin
-          popd
-
-      - name: make httpd
+      - name: make httpd wrk
         run: |
           ./configure
-          make httpd
+          make httpd wrk
 
       - name: build echo-servers
         run: |
@@ -50,5 +42,5 @@ jobs:
           bin/httpd -c etc/httpd.conf -d
           ps aux | grep nginx
           ps aux | grep httpd
-          wrk -c 100 -t 2 -d 10s http://127.0.0.1:80/
-          wrk -c 100 -t 2 -d 10s http://127.0.0.1:8080/
+          bin/wrk -c 100 -t 2 -d 10s http://127.0.0.1:80/
+          bin/wrk -c 100 -t 2 -d 10s http://127.0.0.1:8080/

+ 4 - 1
Makefile

@@ -37,7 +37,7 @@ endif
 default: all
 all: libhv examples
 examples: hmain_test htimer_test hloop_test \
-	nc nmap httpd curl wget consul \
+	nc nmap httpd curl wget wrk consul \
 	tcp_echo_server \
 	tcp_chat_server \
 	tcp_proxy_server \
@@ -110,6 +110,9 @@ nc: prepare
 nmap: prepare
 	$(MAKEF) TARGET=$@ SRCDIRS=". base ssl event cpputil examples/nmap" DEFINES="PRINT_DEBUG"
 
+wrk: prepare
+	$(MAKEF) TARGET=$@ SRCDIRS=". base ssl event util cpputil evpp http" SRCS="examples/wrk.cpp"
+
 httpd: prepare
 	$(RM) examples/httpd/*.o
 	$(MAKEF) TARGET=$@ SRCDIRS=". base ssl event util cpputil evpp http http/client http/server examples/httpd"

+ 12 - 4
README-CN.md

@@ -99,6 +99,9 @@ bin/curl -v localhost:8080/test -H "Content-Type:application/json" -d '{"bool":t
 bin/curl -v localhost:8080/test -F 'bool=1 int=123 float=3.14 string=hello'
 # RESTful API: /group/:group_name/user/:user_id
 bin/curl -v -X DELETE localhost:8080/group/test/user/123
+
+# 压力测试
+bin/wrk -c 1000 -d 10 -t 4 http://127.0.0.1:8080/
 ```
 
 ### TCP
@@ -182,7 +185,6 @@ static void on_recv(hio_t* io, void* buf, int readbytes) {
 }
 
 static void on_connect(hio_t* io) {
-    hio_setcb_close(io, on_close);
     hio_setcb_read(io, on_recv);
     hio_read(io);
 
@@ -190,12 +192,17 @@ static void on_connect(hio_t* io) {
 }
 
 int main() {
+    const char host[] = "127.0.0.1";
     int port = 1234;
     hloop_t* loop = hloop_new(0);
-    hio_t* connio = hloop_create_tcp_client(loop, "127.0.0.1", port, on_connect);
-    if (connio == NULL) {
-        return -1;
+    hio_t* io = hio_create_socket(hloop, host, port, HIO_TYPE_TCP, HIO_CLIENT_SIDE);
+    if (io == NULL) {
+        perror("socket");
+        exit(1);
     }
+    hio_setcb_connect(io, on_connect);
+    hio_setcb_close(io, on_close);
+    hio_connect(io);
     hloop_run(loop);
     hloop_free(&loop);
     return 0;
@@ -380,6 +387,7 @@ int main() {
 - 网络连接工具: [examples/nc](examples/nc.c)
 - 网络扫描工具: [examples/nmap](examples/nmap)
 - HTTP服务程序: [examples/httpd](examples/httpd)
+- HTTP压测工具: [examples/wrk](examples/wrk.cpp)
 - URL请求工具: [examples/curl](examples/curl.cpp)
 - 文件下载工具: [examples/wget](examples/wget.cpp)
 - 服务注册与发现: [examples/consul](examples/consul)

+ 12 - 4
README.md

@@ -96,6 +96,9 @@ bin/curl -v localhost:8080/test -H "Content-Type:application/json" -d '{"bool":t
 bin/curl -v localhost:8080/test -F 'bool=1 int=123 float=3.14 string=hello'
 # RESTful API: /group/:group_name/user/:user_id
 bin/curl -v -X DELETE localhost:8080/group/test/user/123
+
+# benchmark
+bin/wrk -c 1000 -d 10 -t 4 http://127.0.0.1:8080/
 ```
 
 ### TCP
@@ -179,7 +182,6 @@ static void on_recv(hio_t* io, void* buf, int readbytes) {
 }
 
 static void on_connect(hio_t* io) {
-    hio_setcb_close(io, on_close);
     hio_setcb_read(io, on_recv);
     hio_read(io);
 
@@ -187,12 +189,17 @@ static void on_connect(hio_t* io) {
 }
 
 int main() {
+    const char host[] = "127.0.0.1";
     int port = 1234;
     hloop_t* loop = hloop_new(0);
-    hio_t* connio = hloop_create_tcp_client(loop, "127.0.0.1", port, on_connect);
-    if (connio == NULL) {
-        return -1;
+    hio_t* io = hio_create_socket(hloop, host, port, HIO_TYPE_TCP, HIO_CLIENT_SIDE);
+    if (io == NULL) {
+        perror("socket");
+        exit(1);
     }
+    hio_setcb_connect(io, on_connect);
+    hio_setcb_close(io, on_close);
+    hio_connect(io);
     hloop_run(loop);
     hloop_free(&loop);
     return 0;
@@ -376,6 +383,7 @@ int main() {
 - [examples/nc](examples/nc.c)
 - [examples/nmap](examples/nmap)
 - [examples/httpd](examples/httpd)
+- [examples/wrk](examples/wrk.cpp)
 - [examples/curl](examples/curl.cpp)
 - [examples/wget](examples/wget.cpp)
 - [examples/consul](examples/consul)

+ 5 - 0
examples/CMakeLists.txt

@@ -55,6 +55,11 @@ if(WITH_EVPP)
     list(APPEND EXAMPLES hmain_test nmap)
 if(WITH_HTTP)
     include_directories(../http)
+
+    # wrk
+    add_executable(wrk wrk.cpp)
+    target_link_libraries(wrk ${HV_LIBRARIES})
+
 if(WITH_HTTP_SERVER)
     include_directories(../http/server)
 

+ 252 - 0
examples/wrk.cpp

@@ -0,0 +1,252 @@
+/*
+ * @build: make examples
+ * @server bin/httpd -s restart -d
+ * @client bin/curl -v http://127.0.0.1:8080/
+ * @usage: bin/wrk -c 1000 -d 10 -t 4 http://127.0.0.1:8080/
+ *
+ */
+
+#include "hv.h"
+#include "hmain.h"  // import parse_opt
+#include "hloop.h"
+
+#include "EventLoopThreadPool.h"
+#include "HttpMessage.h"
+#include "HttpParser.h"
+using namespace hv;
+
+static const char options[] = "hvc:d:t:";
+
+static const char detail_options[] = R"(
+  -h                Print help infomation
+  -v                Show verbose infomation
+  -c <connections>  Number of connections, default: 1000
+  -d <duration>     Duration of test, default: 10s
+  -t <threads>      Number of threads, default: 4
+)";
+
+static int connections = 1000;
+static int duration = 10;
+static int threads = 4;
+
+static bool verbose = false;
+static const char* url = NULL;
+static bool https = false;
+static char ip[64] = "127.0.0.1";
+static int  port = 80;
+
+static HttpRequestPtr   request;
+static std::string      request_msg;
+
+typedef struct connection_s {
+    hio_t*          io;
+    HttpParserPtr   parser;
+    HttpResponsePtr response;
+    uint64_t request_cnt;
+    uint64_t ok_cnt;
+    uint64_t readbytes;
+
+    connection_s()
+        : parser(HttpParser::New(HTTP_CLIENT, HTTP_V1))
+        , response(new HttpResponse)
+        , request_cnt(0)
+        , ok_cnt(0)
+        , readbytes(0)
+    {
+        response->body_cb = [](const char* data, size_t size) {
+            // No need to save data
+        };
+    }
+
+    void SendRequest() {
+        hio_write(io, request_msg.data(), request_msg.size());
+        ++request_cnt;
+        parser->InitResponse(response.get());
+    }
+
+    bool RecvResponse(const char* data, int size) {
+        readbytes += size;
+        int nparse = parser->FeedRecvData(data, size);
+        if (nparse != size) {
+            fprintf(stderr, "http parse error!\n");
+            hio_close(io);
+            return false;
+        }
+        if (parser->IsComplete()) {
+            if (response->status_code == HTTP_STATUS_OK) {
+                ++ok_cnt;
+            }
+            return true;
+        }
+        return false;
+    }
+} connection_t;
+static connection_t** conns = NULL;
+
+static std::atomic<int> connected_num(0);
+static std::atomic<int> disconnected_num(0);
+
+static void print_help() {
+    printf("Usage: wrk [%s] <url>\n", options);
+    printf("Options:\n%s\n", detail_options);
+}
+
+static void print_cmd() {
+    printf("Running %ds test @ %s\n", duration, url);
+    printf("%d threads and %d connections\n", threads, connections);
+}
+
+static void print_result() {
+    uint64_t total_request_cnt = 0;
+    uint64_t total_ok_cnt = 0;
+    uint64_t total_readbytes = 0;
+    connection_t* conn = NULL;
+    for (int i = 0; i < connections; ++i) {
+        conn = conns[i];
+        total_request_cnt += conn->request_cnt;
+        total_ok_cnt += conn->ok_cnt;
+        total_readbytes += conn->readbytes;
+    }
+    printf("%llu requests, %llu OK, %lluMB read in %ds\n",
+            LLU(total_request_cnt),
+            LLU(total_ok_cnt),
+            LLU(total_readbytes >> 20),
+            duration);
+    printf("Requests/sec: %8llu\n", LLU(total_request_cnt / duration));
+    printf("Transfer/sec: %8lluMB\n", LLU((total_readbytes / duration) >> 20));
+}
+
+static void on_close(hio_t* io) {
+    if (++disconnected_num == connections) {
+        if (verbose) {
+            printf("all disconnected\n");
+        }
+    }
+}
+
+static void on_recv(hio_t* io, void* buf, int readbytes) {
+    connection_t* conn = (connection_t*)hevent_userdata(io);
+    if (conn->RecvResponse((const char*)buf, readbytes)) {
+        conn->SendRequest();
+    }
+}
+
+static void on_connect(hio_t* io) {
+    if (++connected_num == connections) {
+        if (verbose) {
+            printf("all connected\n");
+        }
+    }
+
+    connection_t* conn = (connection_t*)hevent_userdata(io);
+    conn->SendRequest();
+
+    hio_setcb_read(io, on_recv);
+    hio_read(io);
+}
+
+int main(int argc, char** argv) {
+    // parse cmdline
+    main_ctx_init(argc, argv);
+    int ret = parse_opt(argc, argv, options);
+    if (ret != 0) {
+        print_help();
+        exit(ret);
+    }
+
+    if (get_arg("h") || g_main_ctx.arg_list.size() != 1) {
+        print_help();
+        exit(0);
+    }
+    url = g_main_ctx.arg_list[0].c_str();
+
+    if (get_arg("v")) {
+        verbose = true;
+    }
+
+    const char* strConnections = get_arg("c");
+    const char* strDuration = get_arg("d");
+    const char* strThreads = get_arg("t");
+
+    if (strConnections) connections = atoi(strConnections);
+    if (strDuration)    duration = atoi(strDuration);
+    if (strThreads)     threads = atoi(strThreads);
+
+    print_cmd();
+
+    // ParseUrl
+    request.reset(new HttpRequest);
+    request->url = url;
+    request->ParseUrl();
+    https = request->scheme == "https";
+    const char* host = request->host.c_str();
+    port = request->port;
+
+    // Resolver
+    if (is_ipaddr(host)) {
+        strcpy(ip, host);
+    } else {
+        sockaddr_u addr;
+        if (Resolver(host, &addr) != 0) {
+            fprintf(stderr, "Could not resolve host: %s\n", host);
+            exit(1);
+        }
+        sockaddr_ip(&addr, ip, sizeof(ip));
+    }
+
+    // Test connect
+    printf("Connect to %s:%d ...\n", ip, port);
+    int connfd = ConnectTimeout(ip, port);
+    if (connfd < 0) {
+        fprintf(stderr, "Could not connect to %s:%d\n", ip, port);
+        exit(1);
+    } else {
+        closesocket(connfd);
+    }
+
+    // Dump request
+    request->headers["User-Agent"] = std::string("libhv/") + hv_version();
+    request_msg = request->Dump(true, true);
+    printf("%s", request_msg.c_str());
+
+    // EventLoopThreadPool
+    EventLoopThreadPool loop_threads(threads);
+    loop_threads.start(true);
+
+    // connections
+    conns = (connection_t**)malloc(sizeof(connection_t*) * connections);
+    for (int i = 0; i < connections; ++i) {
+        conns[i] = new connection_t;
+
+        EventLoopPtr loop = loop_threads.nextLoop();
+        hloop_t* hloop = loop->loop();
+        loop->runInLoop([i, hloop](){
+            hio_t* io = hio_create_socket(hloop, ip, port, HIO_TYPE_TCP, HIO_CLIENT_SIDE);
+            if (io == NULL) {
+                perror("socket");
+                exit(1);
+            }
+            conns[i]->io = io;
+            hevent_set_userdata(io, conns[i]);
+            if (https) {
+                hio_enable_ssl(io);
+            }
+            tcp_nodelay(hio_fd(io), 1);
+            hio_setcb_connect(io, on_connect);
+            hio_setcb_close(io, on_close);
+            hio_connect(io);
+        });
+    }
+
+    // stop after duration
+    loop_threads.loop()->setTimeout(duration * 1000, [&loop_threads](TimerID timerID){
+        loop_threads.stop(false);
+    });
+
+    // wait loop_threads exit
+    loop_threads.join();
+
+    print_result();
+
+    return 0;
+}