源码阅读笔记:webbench-1.5

对源码重新排版一下,方便阅读:

/*
 * (C) Radim Kolar 1997-2004
 * This is free software, see GNU Public License version 2 for
 * details.
 *
 * Simple forking WWW Server benchmark:
 *
 * Usage:
 *   webbench --help
 *
 * Return codes:
 *    0 - sucess
 *    1 - benchmark failed (server is not on-line)
 *    2 - bad param
 *    3 - internal error, fork failed
 *
 */
#include "socket.c"
#include <unistd.h>
#include <sys/param.h>
#include <rpc/types.h>
#include <getopt.h>
#include <strings.h>
#include <time.h>
#include <signal.h>

/* 超时标记,当被设置为 1 时,所有子进程退出 */
volatile int timerexpired=0;

/* 成功请求数 */
int speed  = 0;
/* 失败请求数 */
int failed = 0;
/* 读取字节总数 */
int bytes  = 0;

/* 把持的 HTTP 协议版本 */
int http10 = 1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */

/* 支持的 HTTP 方法 */
#define METHOD_GET 0
#define METHOD_HEAD 1
#define METHOD_OPTIONS 2
#define METHOD_TRACE 3
#define PROGRAM_VERSION "1.5"
int method = METHOD_GET; /* 默认为GET方法 */

int clients      = 1; /* 默认启动一个客户端(子进程) */
int force        = 0; /* 是否等待响应数据返回,0 -等待,1 - 不等待 */
int force_reload = 0; /* 是否发送 Pragma: no-cache */
int proxyport    = 80; /* 代理端口 */
char *proxyhost  = NULL; /* 代理服务器名称 */
int benchtime    = 30; /* 执行时间,当子进程执行时间到过这个秒数之后,
                          发送 SIGALRM 信号,将 timerexpired 设置为 1,
                          让所有子进程退出 */

/* 管道,子进程完成一次请求,则将写端写入数据,主进程从读端读取数据 */
int mypipe[2];

char host[MAXHOSTNAMELEN]; /* 主机名(64字节) */
#define REQUEST_SIZE 2048
char request[REQUEST_SIZE]; /* 请求字符串(HTTP头) */

/* 命令行的选项配置表,man getopt_long */
static const struct option long_options[] = {
    {"force",   no_argument,       &force,        1},
    {"reload",  no_argument,       &force_reload, 1},
    {"time",    required_argument, NULL,          ‘t‘},
    {"help",    no_argument,       NULL,          ‘?‘},
    {"http09",  no_argument,       NULL,          ‘9‘},
    {"http10",  no_argument,       NULL,          ‘1‘},
    {"http11",  no_argument,       NULL,          ‘2‘},
    {"get",     no_argument,       &method,       METHOD_GET},
    {"head",    no_argument,       &method,       METHOD_HEAD},
    {"options", no_argument,       &method,       METHOD_OPTIONS},
    {"trace",   no_argument,       &method,       METHOD_TRACE},
    {"version", no_argument,       NULL,          ‘V‘},
    {"proxy",   required_argument, NULL,          ‘p‘},
    {"clients", required_argument, NULL,          ‘c‘},
    {NULL,      0,                 NULL,          0}
};

/* 子进程执行请求任务的函数 */
static void benchcore(const char* host,const int port, const char *request);
/* 执行压测的主要入口函数 */
static int bench(void);
/* 生成 HTTP 头 */
static void build_request(const char *url);

/* 将 timerexpired 设置为1,让所有子进程退出 */
static void alarm_handler(int signal) {
    timerexpired=1;
}

/* 打印帮助 */
static void usage(void) {
    fprintf(stderr,
        "webbench [option]... URL\n"
        "  -f|--force               Don‘t wait for reply from server.\n"
        "  -r|--reload              Send reload request - Pragma: no-cache.\n"
        "  -t|--time <sec>          Run benchmark for <sec> seconds. Default 30.\n"
        "  -p|--proxy <server:port> Use proxy server for request.\n"
        "  -c|--clients <n>         Run <n> HTTP clients at once. Default one.\n"
        "  -9|--http09              Use HTTP/0.9 style requests.\n"
        "  -1|--http10              Use HTTP/1.0 protocol.\n"
        "  -2|--http11              Use HTTP/1.1 protocol.\n"
        "  --get                    Use GET request method.\n"
        "  --head                   Use HEAD request method.\n"
        "  --options                Use OPTIONS request method.\n"
        "  --trace                  Use TRACE request method.\n"
        "  -?|-h|--help             This information.\n"
        "  -V|--version             Display program version.\n"
    );
};

int main(int argc, char *argv[]) {
    int  opt           = 0;
    int  options_index = 0;
    char *tmp          = NULL;

    if (argc == 1) {
        usage();
        return 2;
    } 

    /* 参数解释,参见 man getopt_long */
    while ((opt = getopt_long(argc, argv, "912Vfrt:p:c:?h", long_options, &options_index)) != EOF) {
        switch(opt) {
            case  0 :
                break;
            case ‘f‘:
                force = 1;
                break;
            case ‘r‘:
                force_reload = 1;
                break;
            case ‘9‘:
                http10 = 0;
                break;
            case ‘1‘:
                http10 = 1;
                break;
            case ‘2‘:
                http10 = 2;
                break;
            case ‘V‘:
                printf(PROGRAM_VERSION"\n");
                exit(0);
            case ‘t‘:
                benchtime = atoi(optarg);
                break;
            case ‘p‘:
                /* proxy server parsing server:port */
                tmp       = strrchr(optarg, ‘:‘);
                proxyhost = optarg;
                if (tmp == NULL) {
                    break;
                }
                if (tmp == optarg) {
                    fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg);
                    return 2;
                }
                if (tmp == optarg + strlen(optarg) - 1) {
                    fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg);
                    return 2;
                }
                *tmp=‘\0‘;
                proxyport = atoi(tmp + 1);
                break;
            case ‘:‘:
            case ‘h‘:
            case ‘?‘:
                usage();
                return 2;
                break;
            case ‘c‘:
                clients = atoi(optarg);
                break;
        }
    }

    if (optind == argc) {
        fprintf(stderr, "webbench: Missing URL!\n");
        usage();
        return 2;
    }

    if (clients == 0) {
        clients = 1;
    }

    if (benchtime == 0) {
        benchtime = 60;
    }

    /* Copyright */
    fprintf(stderr,
        "Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n"
        "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n"
    );

    build_request(argv[optind]); /* 最后一个非选项的参数,被视为URL */

    /* print bench info */
    printf("\nBenchmarking: ");
    switch(method) {
        case METHOD_GET:
        default:
            printf("GET");
            break;
        case METHOD_OPTIONS:
            printf("OPTIONS");
            break;
        case METHOD_HEAD:
            printf("HEAD");
            break;
        case METHOD_TRACE:
            printf("TRACE");
            break;
    }
    printf(" %s",argv[optind]);
    switch(http10) {
        case 0:
            printf(" (using HTTP/0.9)");
            break;
        case 2:
            printf(" (using HTTP/1.1)");
            break;
    }
    printf("\n");
    if (clients == 1) {
        printf("1 client");
    } else {
        printf("%d clients", clients);
    }
    printf(", running %d sec", benchtime);
    if (force) {
        printf(", early socket close");
    }
    if (proxyhost != NULL) {
        printf(", via proxy server %s:%d", proxyhost, proxyport);
    }
    if (force_reload) {
        printf(", forcing reload");
    }
    printf(".\n");

    return bench();
}

/* 生成 HTTP 头 */
void build_request (const char *url) {
    char tmp[10];
    int i;

    bzero(host, MAXHOSTNAMELEN);
    bzero(request, REQUEST_SIZE);

    /* 协议适配
     * */
    if (force_reload && proxyhost != NULL && http10 < 1) {
        http10=1;
    }
    if (method == METHOD_HEAD && http10 < 1) {
        http10=1;
    }
    if (method == METHOD_OPTIONS && http10 < 2) {
        http10=2;
    }
    if (method == METHOD_TRACE && http10 < 2) {
        http10=2;
    }

    switch (method) {
        default:
        case METHOD_GET:
            strcpy(request, "GET");
            break;
        case METHOD_HEAD:
            strcpy(request, "HEAD");
            break;
        case METHOD_OPTIONS:
            strcpy(request, "OPTIONS");
            break;
        case METHOD_TRACE:
            strcpy(request, "TRACE");
            break;
    }

    strcat(request, " ");

    if (NULL == strstr(url, "://")) {
        fprintf(stderr, "\n%s: is not a valid URL.\n",url);
        exit(2);
    }
    if (strlen(url) > 1500) {
        fprintf(stderr,"URL is too long.\n");
        exit(2);
    }
    if (proxyhost == NULL) {
        if (0 != strncasecmp("http://", url, 7)) {
            fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n");
            exit(2);
        }
    }

    /* protocol/host delimiter */
    i = strstr(url, "://") - url + 3;
    /* printf("%d\n",i); */

    if (strchr(url + i, ‘/‘) == NULL) {
        fprintf(stderr,"\nInvalid URL syntax - hostname don‘t ends with ‘/‘.\n");
        exit(2);
    }

    if (proxyhost == NULL) {
        /* get port from hostname */
        if (index(url + i, ‘:‘) != NULL && index(url + i, ‘:‘) < index(url + i, ‘/‘)) {
            strncpy(host, url + i, strchr(url + i, ‘:‘) - url - i);
            bzero(tmp, 10);
            strncpy(tmp, index(url + i, ‘:‘) + 1, strchr(url + i, ‘/‘) - index(url + i, ‘:‘) - 1);
            /* printf("tmp=%s\n",tmp); */
            proxyport = atoi(tmp);
            if (proxyport == 0) {
                proxyport=80;
            }
        } else {
            strncpy(host, url + i, strcspn(url + i, "/"));
        }
        // printf("Host=%s\n",host);
        strcat(request + strlen(request), url + i + strcspn(url + i, "/"));
    } else {
        // printf("ProxyHost=%s\nProxyPort=%d\n",proxyhost,proxyport);
        strcat(request, url);
    }

    if (http10 == 1) {
        strcat(request, " HTTP/1.0");
    } else if (http10 == 2) {
        strcat(request, " HTTP/1.1");
    }
    strcat(request,"\r\n");

    if (http10 > 0) {
          strcat(request, "User-Agent: WebBench "PROGRAM_VERSION"\r\n");
    }
    if (proxyhost == NULL && http10 > 0) {
          strcat(request, "Host: ");
          strcat(request, host);
          strcat(request, "\r\n");
    }
    if (force_reload && proxyhost != NULL) {
        strcat(request, "Pragma: no-cache\r\n");
    }
    if (http10 > 1) {
        strcat(request, "Connection: close\r\n");
    }
    /* add empty line at end */
    if (http10 > 0) {
        strcat(request, "\r\n");
    }
    // printf("Req=%s\n",request);
}

/* vraci system rc error kod */
static int bench(void) {
    int   i, j, k;
    pid_t pid = 0;
    FILE  *f;

    /* check avaibility of target server */
    i = Socket(proxyhost == NULL ? host : proxyhost, proxyport);
    if (i < 0) {
        fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n");
        return 1;
    }
    close(i);

    /* create pipe */
    if (pipe(mypipe)) {
        perror("pipe failed.");
        return 3;
    }

    /* not needed, since we have alarm() in childrens */
    /* wait 4 next system clock tick */
    /*
    cas=time(NULL);
    while(time(NULL)==cas)
          sched_yield();
    */

    /* fork childs */
    for (i = 0; i < clients; i++) {
        pid = fork();
        if (pid <= (pid_t)0) {
            /* child process or error*/
            sleep(1); /* make childs faster */
            /* 这个 break 很重要,它主要让子进程只能从父进程生成,否则子进程会再生成子进程 */
            break;
        }
    }

    if (pid < (pid_t)0) {
        fprintf(stderr,"problems forking worker no. %d\n",i);
        perror("fork failed.");
        return 3;
    }

    if (pid == (pid_t)0) {
        /* I am a child */
        /* 子进程执行请求 */
        if(proxyhost == NULL) {
            benchcore(host, proxyport, request);
        } else {
            benchcore(proxyhost, proxyport, request);
        }
        /* write results to pipe */
        f = fdopen(mypipe[1], "w");
        if (f == NULL) {
            perror("open pipe for writing failed.");
            return 3;
        }
        /* fprintf(stderr,"Child - %d %d\n",speed,failed); */
        /* 将结果写入到管道中 */
        fprintf(f, "%d %d %d\n", speed, failed, bytes);
        fclose(f);
        return 0;
    } else {
        /* 父进程读取管道,打印结果 */
        printf("parent %d\n", getpid());
        f = fdopen(mypipe[0], "r");
        if (f == NULL) {
            perror("open pipe for reading failed.");
            return 3;
        }
        setvbuf(f, NULL, _IONBF, 0);
        speed  = 0;
        failed = 0;
        bytes  = 0;
        while(1) {
            pid = fscanf(f, "%d %d %d", &i, &j, &k);
            if (pid < 2) {
                fprintf(stderr,"Some of our childrens died.\n");
                break;
            }
            speed  += i;
            failed += j;
            bytes  += k;
            /* fprintf(stderr,"*Knock* %d %d read=%d\n",speed,failed,pid); */
            if (--clients == 0) {
                break;
            }
        }
        fclose(f);

        printf("\nSpeed=%d pages/min, %d bytes/sec.\nRequests: %d susceed, %d failed.\n",
              (int)((speed + failed) / (benchtime / 60.0f)),
              (int)(bytes / (float)benchtime),
              speed,
              failed);
    }
    return i;
}

void benchcore(const char *host, const int port, const char *req) {
    int    rlen;
    char   buf[1500];
    int    s, i;
    struct sigaction sa;

    /* 这个是关键,当程序执行到指定的秒数之后,发送 SIGALRM 信号 */
    sa.sa_handler = alarm_handler;
    sa.sa_flags = 0;
    if (sigaction(SIGALRM, &sa, NULL)) {
        exit(3);
    }
    alarm(benchtime);

    rlen = strlen(req);
    /* 无限执行请求,直到 timerexpired 为 1 时返回 */
    nexttry:
    while (1) {
        if (timerexpired) {
            if (failed > 0) {
                /* fprintf(stderr,"Correcting failed by signal\n"); */
                failed--;
            }
            return;
        }
        s = Socket(host, port);
        if (s < 0) {
            failed++;
            continue;
        }
        if (rlen != write(s, req, rlen)) {
            failed++;
            close(s);
            continue;
        }
        if (http10 == 0) {
            /* 如果是 http/0.9 则关闭 socket 的写操作 */
            if(shutdown(s, 1)) {
                failed++;
                close(s);
                continue;
            }
        }
        /* 如果等待响应数据返回,则读取响应数据,计算传输的字节数 */
        if (force == 0) {
            /* read all available data from socket */
            while(1) {
                if(timerexpired) {
                    break;
                }
                i = read(s, buf, 1500);
                /* fprintf(stderr,"%d\n",i); */
                if (i < 0) {
                    failed++;
                    close(s);
                    goto nexttry;
                } else {
                    if(i==0) {
                        break;
                    } else {
                        bytes += i;
                    }
                }
            }
        }
        if (close(s)) {
            failed++;
            continue;
        }
        /* 成功完成一次请求,并计数 */
        speed++;
    }
}
时间: 2024-12-21 15:15:16

源码阅读笔记:webbench-1.5的相关文章

CI框架源码阅读笔记3 全局函数Common.php

从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap引导文件都会最先引入全局函数,以便于之后的处理工作). 打开Common.php中,第一行代码就非常诡异: if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 上一篇(CI框架源码阅读笔记2 一切的入口 index

源码阅读笔记 - 1 MSVC2015中的std::sort

大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格式化,去掉或者展开用于条件编译或者debug检查的宏,依重要程度重新排序函数,但是不会改变命名方式(虽然MSVC的STL命名实在是我不能接受的那种),对于代码块的解释会在代码块前(上面)用注释标明. template<class _RanIt, class _Diff, class _Pr> in

CI框架源码阅读笔记5 基准测试 BenchMark.php

上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功能,各模块之间可以相互调用,共同构成了CI的核心骨架. 从本篇开始,将进一步去分析各组件的实现细节,深入CI核心的黑盒内部(研究之后,其实就应该是白盒了,仅仅对于应用来说,它应该算是黑盒),从而更好的去认识.把握这个框架. 按照惯例,在开始之前,我们贴上CI中不完全的核心组件图: 由于BenchMa

CI框架源码阅读笔记2 一切的入口 index.php

上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里这次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中,我们并不会逐行进行解释,而只解释核心的功能和实现. 1.       设置应用程序环境 define('ENVIRONMENT', 'development'); 这里的development可以是任何你喜欢的环境名称(比如dev,再如test),相对应的,你要在下面的switch case代码块中

Apache Storm源码阅读笔记

欢迎转载,转载请注明出处. 楔子 自从建了Spark交流的QQ群之后,热情加入的同学不少,大家不仅对Spark很热衷对于Storm也是充满好奇.大家都提到一个问题就是有关storm内部实现机理的资料比较少,理解起来非常费劲. 尽管自己也陆续对storm的源码走读发表了一些博文,当时写的时候比较匆忙,有时候衔接的不是太好,此番做了一些整理,主要是针对TridentTopology部分,修改过的内容采用pdf格式发布,方便打印. 文章中有些内容的理解得益于徐明明和fxjwind两位的指点,非常感谢.

CI框架源码阅读笔记4 引导文件CodeIgniter.php

到了这里,终于进入CI框架的核心了.既然是"引导"文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.com/usr/reg 经过引导文件,实际上会交给Application中的UsrController控制器的reg方法去处理. 这之中,CodeIgniter.php做了哪些工作?我们一步步来看. 1.    导入预定义常量.框架环境初始化 之前的一篇博客(CI框架源码阅读笔记2 一切的入

IOS测试框架之:athrun的InstrumentDriver源码阅读笔记

athrun的InstrumentDriver源码阅读笔记 作者:唯一 athrun是淘宝的开源测试项目,InstrumentDriver是ios端的实现,之前在公司项目中用过这个框架,没有深入了解,现在回来记录下. 官方介绍:http://code.taobao.org/p/athrun/wiki/instrumentDriver/ 优点:这个框架是对UIAutomation的java实现,在代码提示.用例维护方面比UIAutomation强多了,借junit4的光,我们可以通过junit4的

jdk源码阅读笔记之java集合框架(二)(ArrayList)

关于ArrayList的分析,会从且仅从其添加(add)与删除(remove)方法入手. ArrayList类定义: p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Monaco } span.s1 { color: #931a68 } public class ArrayList<E> extends AbstractList<E> implements List<E> ArrayList基本属性: /** *

Yii源码阅读笔记 - 日志组件

?使用 Yii框架为开发者提供两个静态方法进行日志记录: Yii::log($message, $level, $category);Yii::trace($message, $category); 两者的区别在于后者依赖于应用开启调试模式,即定义常量YII_DEBUG: defined('YII_DEBUG') or define('YII_DEBUG', true); Yii::log方法的调用需要指定message的level和category.category是格式为“xxx.yyy.z

dubbo源码阅读笔记--服务调用时序

上接dubbo源码阅读笔记--暴露服务时序,继续梳理服务调用时序,下图右面红线流程. 整理了调用时序图 分为3步,connect,decode,invoke. 连接 AllChannelHandler.connected(Channel) line: 38 HeartbeatHandler.connected(Channel) line: 47 MultiMessageHandler(AbstractChannelHandlerDelegate).connected(Channel) line: