资讯专栏INFORMATION COLUMN

后端研发菜鸟成长记 第三章 小试牛刀,编写性能测试工具

mating / 3668人阅读

摘要:命令参数功能和所有的命令一样,我们的工具也支持命令参数,在下有一个函数用于支持对命令行参数的解析,通过这个函数我们能轻易是实现对长短参数的解析和获取。

3 小试牛刀,编写性能测试工具

作为一名后端研发人员,必须具备系统性能评估和分析能力,因为只有对系统总体性能了如指掌,才能知道系统什么时候需要扩容,系统哪里有性能瓶颈需要优化。

本章将介绍如何宏观的评估系统的总体性能,并重点介绍如何编写性能测试工具对系统性能做一个实测,毕竟理论归理论,理论上性能指标还是需要靠实际压测来检验。

3.1 系统的总体性能简述

通常我们使用QPS、平均响应时间、并发数,这三个指标来衡量一个系统的总体性能。

QPS:表示系统每秒请求数。

平均响应时间:表示系统平均单个请求耗时,单位为秒。

并发数:表示系统能同时处理的请求数。

系统能支持的最大QPS是由平均响应时间和并发数决定的,即QPS=并发数/平均响应时间。纯理论的解说可能不太好理解,我们这边举一个例子,比如我们去银行营业厅办存款业务,这时有10个柜台可以办理存款业务,每个人办理存款业务平均需要5分钟,那么这个营业厅每小时可以处理多少的存款业务呢?这里10个柜台可以办理业务也就是说“并发数”为10,我们单位时间设定为小时,那么办理存款业务平均需要5分钟也就是说“平均响应时间”为12分之1小时,那么这个营业厅每小时可以处理的存款业务数也就是QPS=10/(1/12),即120。

由上面的公式“QPS=并发数/平均响应时间”,我们可以看出要提高系统的QPS,可以从两个方面着手,一方面是提高并发数,一方面是降低平均响应时间。

提高并发数的方法
新增更多的服务器,采用多核服务器,单服务器上运行多进程提供服务,进程采用更高效性的IO模型。

降低平均响应时间的方法
响应时间通常由:网络通讯时间(网络io时间)+计算处理时间(cpu时间)+磁盘读写时间(磁盘io时间)构成的。可以通过使用更大的网络带宽,更好的网卡来降低网络通讯时间,可以使用更强大的cpu或者优化算法来减少计算处理时间,可以使用SSD硬盘来替代普通硬盘来降低单次读写磁盘的io时间,可以在业务层和数据库持久化层之间添加一个缓存层来减少磁盘io次数。

3.2 性能测试工具

我们的性能测试工具是命令行工具,命名为“benchMark”,它用于测试一个系统提供的网络服务的总体性能,它具备命令参数功能、支持多客户端并发、支持基于连接的压测、支持基于特定业务请求的压测等的功能。

3.2.1 命令参数功能

和所有Linux的命令一样,我们的工具也支持命令参数,在Linux下有一个getopt_long函数用于支持对命令行参数的解析,通过这个函数我们能轻易是实现对长短参数的解析和获取。getopt_long函数所在的头文件和函数原型如下:

#include 

int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex);

作为C/C++入口函数,main的标准函数原型为“int main(int argc, char * argv[])”,命令行参数也正是通过main函数的argc、argv这两个参数传递进来的。

argc与argv参数

getopt_long函数开头需要传入的两个参数就是main函数的argc和argv参数,argc表示要解析参数的个数,argv是具体要解析的参数列表。

optstring参数

optstring为选项声明串,其中一个字符代表一个选项,如果字符后面跟上一个英文冒号表明这个选项必须有一个选项参数,这个选项参数可以和选项在同一个命令行参数中,即“-oarg”,也可以在下一个命令行参数中,即“-o arg”,这个选项参数的值可以从全局变量optarg中获取到;如果字符后面跟上两个冒号表明这个选项有一个可选的选项参数,此时可选的选项参数要和选项在同一个命令行参数中,比如我们设置o为有可选的选项参数的选项,这时要设置可选的选项参数,可选的选项参数必须和选项在同一个参数中,即“-oarg”,其中arg为o选项的可选的选项参数,它们之间不能有空格,同样的这个可选的选项参数可以在全局变量optarg中获取,没有设置可选的选项参数时,optarg的值为0。

longopts参数

optstring中包含的只有短选项,当要支持长选项的时候就需要使用longopts参数了,longopts参数是一个指向struct option数组的指针,在struct option数组中设置了长选项的相关信息。长选项在设置选项参数时和短选项有稍微的不同,短选项为“-oarg”或者“-o arg”,而长选项为“--arg=param”或者“--arg param”,短选项设置可选参数时只能使用“-oarg”的形式,长选项设置可选参数时只能使用“--arg=param”的形式。下面我们看一下struct option中各个字段的含义。

struct option 
{
    const char *name; 
    int         has_arg; 
    int        *flag;
    int         val;
};

name字段:表示长选项的名称。

has_arg字段:表示长选项参数设置,如果设置成no_argument(或者0)表示长选项没有参数,如果设置成required_argument(或者1)表示长选项必须有一个参数,如果设置成optional_argument(或者3)表示长选项有一个可选项参数,长选项的可选参数必须使用“--arg=param”来设置。

flag字段:通常为NULL,如果flag为NULL,则当解析到name设置的长选项时getopt_long返回最后一个val字段设置的值,通过这个方式可以使短选项和长选项具备相同的功能;如果flag不为NULL,则当解析到name设置的长选项时getopt_long返回0,flag指向的变量的值将被设置成val设置的值,长选项没解析到,则flag指向的变量的值不会被改变。

val字段:作为匹配到长选项时getopt_long的返回值(flag为NULL),或者作为设置flag指向的变量的值(flag不为NULL)。

longindex参数

可以设置为NULL,不为NULL时用于返回匹配的长选项在长选项数组中的索引值。

3.2.2 多客户端并发

我们使用fork的方式来模拟多客户端并发的场景,子进程和父进程通过pipe函数创建的匿名管道来进行通信,父进程对测试结果进行汇总统计,在所有子进程结束后在父进程中打印测试的汇总结果。

3.2.3 基于连接还是基于请求

我们的测试工具支持基于连接(创建网络连接成功后就马上关闭网络连接)的并发压测;也支持基于请求(创建网络连接成功后还会发起业务请求,并在接收完应答数据后才关闭网络连接)的并发压测,可以针对不同的业务设置不同请求数据和应答数据不同的校验逻辑。

3.2.4 性能工具的局限性

我们的性能工具只能运行在单服务器上,故并发压测能力受限于单服务器的并发能力,如果是测试外网服务还受限于服务器的外网带宽,外网带宽一旦跑满并发数就难再有所提高。

3.3 数据交互过程

性能测试工具和网络服务系统的数据交互过程如下图:

[图3-1 数据交互]

3.4 类关系图

benchMark主要由5个类和1个结构体构成。其中结构体BenchMarkInfo用于保存测试相关的输入参数;BenchMarkFactory是一个简单的工厂类,它用于生成具体的压测类BenchMarkConn(基于连接)和BenchMarkHttp(基于http协议),BenchMarkBase是压测基类;RobustIo类是对网络io的封装,它自带读缓冲区。这5个类和1个结构体的关系如下类关系图:

[图3-2 类关系图]

3.5 http的请求与应答

在BenchMarkHttp类中涉及到了http协议,在BenchMarkHttp中我们压测的是获取web站点根目录接口的性能,发起的是GET请求,url为“/”。http应答解析方面我们使用开源的C语言的http解析api,它没有其他特别库的依赖,支持流式的解析,在流式解析过程中采用回调的方式来通知http协议相关的字段信息。在第9章“网络通信与并发”中我们将自己实现一个简单的http协议解析类,并对http协议做详细的讲解,加深大家对http协议的理解。

3.5.1 发起http请求

http请求报文由三部分组成,它们分别为:请求行(request_line)、请求头集合(headers)、请求体(body);请求行和请求头都是以“rn”为结尾,请求头集合则以一个 空行(“rn”)作为请求头集合结束的标志,其中body为可选部分,我们http压测使用的GET请求就没有body部分。接下来看一下具体的请求数据生成函数。

void BenchMarkHttp::getRequestData(string & data, BenchMarkInfo & info)
{
    //http GET请求
    data = "GET / HTTP/1.1
"           //请求行,表示发起GET请求,url为"/",使用http 1.1协议
        "User-Agent: benchMark v1.0
"  //User-Agent请求头,表示当前客户端代理信息
        "Host: " + info.host +"
"      //Host请求头,表示请求的服务器域名
        "Accept: */*
"                 //Accept请求头,表示接受任意格式的应答数据
        "
";                           //一个空行表示请求头集合的结束
}

3.5.3 解析http应答

我们使用的C语言的http解析api托管在github上,项目链接为:https://github.com/nodejs/htt... 。

主要分为以下几个步骤来使用开源的http解析api,下面是我们在BenchMarkHttp使用http解析api的相关代码:

static int http_rsp_complete(http_parser * parser)
{
    //取出之前在解析变量中设置的私有数据,它为bool指针,
    //它指向dealHttpReq函数中的finish变量
    bool * pFinish = (bool *)parser->data;
    //设置finish的值为true,表示已经完成了一个http应答的解析。
    *pFinish = true;
    return 0;
}

bool BenchMarkHttp::dealHttpReq(int sock, BenchMarkInfo & info, int33_t & bytes)
{
    size_t ret = 0;
    string data;
    RobustIo rio;   //封装的io类变量
    bool finish = false;    //表示应答是否解析完毕,它的指针会被传递给http解析变量

    http_parser parser;             //声明http解析api的解析变量parser
    http_parser_settings settings;  //声明http解析api的解析设置变量settings
    
    http_parser_init(&parser, HTTP_RESPONSE);   //初始化http解析api的解析变量parser
    http_parser_settings_init(&settings);       //初始化http解析api的解析设置变量settings
    
    settings.on_message_complete = http_rsp_complete; //设置http应答解析完成回调函数
    parser.data = (void *)(&finish);   //在解析变量parser中设置我们的私有数据,在回调函数中会使用

    getRequestData(data, info); //获取http GET请求的发送数据
    //使用封装好的网络io类发送http GET请求
    ret = rio.rioWrite(sock, (void *)data.c_str(), data.size());
    //发送失败则返回false
    if (ret != data.size())
    {
        return false;
    }
    //统计发送字节数
    bytes += data.size();

    char c;
    int status;
    //初始化io读缓存区大小
    rio.rioInit(1034);
    //只要还没解析完应答则一直从网络中获取数据,并解析
    while (!finish)
    {
        //从网络中读取一个字节,rioRead是自带缓冲区的
        //故不会频繁的调用系统read函数,带来性能影响
        if (rio.rioRead(sock, &c, 1) != 1)
        {
            break;
        }
        //统计接收字节数
        ++bytes; 
        //调用http解析核心api对流式数据进行解析,每次解析从网络流中获取的一个字节
        //这个api函数返回解析成功的字节数,如果返回值和传入的要解析的字节数不一致
        //则表明解析过程中发送错误,则返回false
        if (http_parser_execute(&parser, &settings, &c, 1) != 1)
        {
            return false;
        }
    }

    //获取http应答的状态码
    status = parser.status_code;
    if (2 == (status / 100)) //状态码为2xx的都表示请求成功,故返回true?
    {
        return true;
    }
    else
    {
        return false;
    }
}

从上面代码我们可以看到http解析api使用的变量为:parser和settings,它们的类型分别为http_parser和http_parser_settings,在使用它们之前我们需要分别使用http_parser_init和http_parser_settings_init进行初始化,在settings中设置了解析完毕的回调函数http_rsp_complete,在http_rsp_complete函数中我们将dealHttpReq中的finish变量的值设置为true表明http应答已经解析完毕,dealHttpReq就能退出http解析循环。在dealHttpReq函数中我们使用http_parser_execute这个http解析核心api来进行流式解析。

3.6 完整代码

3.6.1 benchMark.cpp

#include 
#include 
#include 
#include "benchMarkBase.h"
#include "benchMarkCommon.h"
#include "benchMarkFactory.h"

using namespace std;

void printVersion()
{
    cout << "benchMark version: 1.0 , auth: rookie" << endl;
}

/*
    输出benchMark的使用方法
 */
void benchMarkUsage()
{
    cout << "Usage: -h host -p port [option]" << endl;
    cout << endl;
    //常规选项
    cout << "general options:" << endl;
    cout << "   --help          print usage" << endl;
    cout << "   -v,--version    print version info" << endl;
    cout << endl;
    //连接选项
    cout << "connection options:" << endl;
    cout << "   -h,--host       server host to connect to" << endl;
    cout << "   -p,--port       server port" << endl;
    cout << endl;
    //并发选项
    cout << "concurrent options:" << endl;
    cout << "   -c,--clients    number of concurrent clients, default 4, max is 100" << endl;
    cout << endl;
    //交互选项
    cout << "interaction options:" << endl;
    cout << "   -r,--request    request type, support http(2) and connection(1), default conection" << endl;
    cout << "   -t,--times      benchMark test time, unit seconds, default 60 sec" << endl;
    cout << endl;
}

int dealArgv(int argc, char * argv[], BenchMarkInfo & info)
{
    int opt = 0;
    //短选项
    const char shortOpts[] = "?vh:p:c:r:t:";
    //长选项
    const struct option longOpts[] = 
    {
        {"help", no_argument, NULL, "?"},
        {"version", no_argument, NULL, "v"},
        {"host", required_argument, NULL, "h"},
        {"port", required_argument, NULL, "p"},
        {"clients", required_argument, NULL, "c"},
        {"request", required_argument, NULL, "r"},
        {"times", required_argument, NULL, "t"},
        {NULL, 0, NULL, 0}  //长选项数组必须以一个空的设置为结束元素
    };

    //一直解析参数直到参数解析完毕
    while ((opt = getopt_long(argc, argv, shortOpts, longOpts, NULL)) != -1)
    {
        switch (opt)
        {
            case "v":
                printVersion();
                exit(0);
                break;
            case "h":
                info.host = optarg;
                break;
            case "p":
                info.port = atoi(optarg);
                break;
            case "c":
                info.clients = atoi(optarg);
                break;
            case "r":
                info.requestType = atoi(optarg);
                break;
            case "t":
                info.times = atoi(optarg);
                break;
            case ":":
            case "?":
                benchMarkUsage();
                return -1;
                break;
        }
    }
    
    return 0;
}

void benchMarkInfoInit(BenchMarkInfo & info)
{
    info.host = "";
    info.port = -1;
    info.clients = 4;          //默认并发4个客户端
    info.requestType = CONN;   //默认是基于连接的压测
    info.times = 60;           //默认压测60秒
}

string getRequestTypeStr(int32_t requestType)
{
    if (CONN == requestType)
    {
        return string("CONN");
    }
    else
    {
        return string("HTTP");
    }
}

int checkArgv(BenchMarkInfo & info)
{
    if ("" == info.host)
    {
        cout << "host is empty" << endl;
        return -1;
    }

    if (info.port <= 0)
    {
        cout << "port parameter is invalid" << endl;
        return -1;
    }

    if (info.clients <= 0)
    {
        cout << "number of clients is invalid" << endl;
        return -1;
    }

    if (info.clients > 100)
    {
        cout << "max number of clients is 100" << endl;
        return -1;
    }

    if (info.requestType != CONN && info.requestType != HTTP)
    {
        cout << "requestType only support 1(connection) and 2(http)" << endl;
        return -1;
    }

    if (info.times <= 0)
    {
        cout << "times is invalid" << endl;
        return -1;
    }

    cout << "benchMark running. "<< endl;
    cout << "	host[" << info.host << "], port[" << info.port 
         << "], clients[" << info.clients << "], time["
         << info.times << "], requestType[" 
         << getRequestTypeStr(info.requestType) << "]" << endl << endl;
    
    return 0;   
}

int main(int argc, char * argv[])
{
    int ret = 0;
    BenchMarkInfo info;
    benchMarkInfoInit(info);    //初始化性能测试信息
    
    ret = dealArgv(argc, argv, info); //解析输入参数
    if (ret != 0)
    {
        return -1;
    }

    ret = checkArgv(info);  //校验参数值是否合法
    if (ret != 0)
    {
        benchMarkUsage();
        return -1;
    }

    //使用BenchMark工厂类生成具体的BenchMark类
    BenchMarkBase * pBase = BenchMarkFactory::getBenchMark(info.requestType);
    //运行run进行压测
    pBase->run(info);
    //释放空间
    delete pBase;
    
    return 0;
}

3.6.2 BenchMarkBase类

benchMarkBase.h

//表示头文件只被编译一次,相对于使用#ifndef ... #define ... #endif更方便
#pragma once

#include "benchMarkCommon.h"

class BenchMarkBase
{
public:
    void run(BenchMarkInfo & info);
protected:
    virtual void childDeal(int writeFd, BenchMarkInfo & info) = 0;
    void parentDeal(int readFd, BenchMarkInfo & info);
private:
    //nothing.
};

benchMarkBase.cpp

#include "robustIo.h"
#include "benchMarkBase.h"
#include 
#include 
#include 

using namespace std;

void BenchMarkBase::parentDeal(int readFd, BenchMarkInfo & info)
{
    RobustIo rio; //封装的io类变量
    rio.rioInit(1024); //初始化读缓冲区
    int32_t childReportData[3]; //子进程的报告数据为3个int32_t变量
    int32_t success = 0;
    int32_t failed = 0;
    int32_t bytes = 0;

    while (true)
    {
        //读取一个子进程的报告
        if (rio.rioRead(readFd, childReportData, 12) != 12)
        {
            break; //读失败或者报告全部读完
        }
        //统计成功次数,成功次数放在第一个int32_t
        success += childReportData[0];
        //统计失败次数,失败次数放在第二个int32_t
        failed += childReportData[1];
        //统计上下行流量,上下行流量放在第三个int32_t
        bytes += childReportData[2];
    }

    //输出压测报告
    cout << "benchMark report:" << endl;
    if (CONN == info.requestType)
    {
        cout << "	speed=" << (success + failed) / info.times << " conn/sec. " 
             << "success=" << success <<", failed=" << failed << endl;
    }
    else
    {
        cout << "	speed=" << (success + failed) / info.times << " pages/sec, "
             << (bytes / (info.times * 1024)) << " kbytes/sec. "
             << "success=" << success <<", failed=" << failed << endl;
    }
}

/*
    核心压测函数
 */
void BenchMarkBase::run(BenchMarkInfo & info)
{
    int fd[2];
    int ret = 0;
    int sockfd = 0;
    pid_t childPid = 0;
    RobustIo rio;

    //先校验在指定的host和port上是否开放了服务。
    //创建tcp连接失败说明没开放服务,直接返回终止压测
    sockfd = rio.newSocket((char *)info.host.c_str(), info.port);
    if (sockfd < 0)
    {
        cout << "connect " << info.host << ":" << info.port
             << " failed. abort benchMark" << endl;
        return;
    }
    close(sockfd);

    ret = pipe(fd); //创建匿名管道用于父子进程间通信
    if (ret != 0)
    {
        cout << "call pipe() failed! error message = " << strerror(errno) << endl;
        return;
    }

    //调用fork创建指定的子进程数
    for (int32_t i = 0; i < info.clients; ++i)
    {
        childPid = fork();
        if (childPid <= 0) //从子进程中返回,或者父进程调用fork失败
        {
            break;
        }
    }

    if (0 == childPid) //从子进程中返回
    {
        close(fd[0]); //关闭匿名管道读端
        childDeal(fd[1], info); //在子进程中进行压测,并传入匿名管道写fd
        return; //压测完子进程返回,并在返回main函数后结束进程运行
    }

    //在父进程中返回(调用fork失败,或者调用fork全部成功)
    
    if (childPid < 0) //调用fork失败的话,打印一下调用失败原因
    {
        cout << "fork childs failed. error message = " << strerror(errno) << endl;
    }

    //父进程关闭匿名管道的写端,这里必须关闭写端,
    //否则在父进程接收子进程的压测报告数据时,无法读到文件结束标志,
    //阻塞在read调用,导致父进程无法退出。
    close(fd[1]); 
    //在父进程中接收子进程报告,汇总统计后输出最后的压测报告
    parentDeal(fd[0], info);
}

3.6.3 BenchMarkConn类

benchMarkConn.h

#pragma once

#include "benchMarkBase.h"

class BenchMarkConn : public BenchMarkBase
{
public:
    //nothing.
protected:
    void childDeal(int writeFd, BenchMarkInfo & info);
public:
    //nothing.
};

benchMarkConn.cpp

#include "robustIo.h"
#include "benchMarkConn.h"
#include 
#include 


void BenchMarkConn::childDeal(int writeFd, BenchMarkInfo & info)
{
    RobustIo rio;
    //子进程压测报告数据,第一个int32_t是成功次数,
    //第二个int32_t是失败次数,第三个int32_t是上下行流量统计
    int32_t statData[3];
    int32_t beginTime = time(NULL);
    memset(statData, 0x0, sizeof(statData)); //初始化统计数据为0

    while (time(NULL) <= beginTime + info.times)
    {
        int sock = rio.newSocket((char *)info.host.c_str(), info.port);
        if (sock < 0)
        {
            if (errno != EINTR)
            {
                ++statData[1]; //连接失败数统计在statData[1]
            }
        }
        else
        {
            ++statData[0]; //连接成功数统计在statData[0]
        }
        close(sock);
    }

    //向匿名管道写入压测报告数据
    rio.rioWrite(writeFd, statData, sizeof(statData));
}

3.6.4 BenchMarkHttp类

benchMarkHttp.h

#pragma once

#include "benchMarkBase.h"

class BenchMarkHttp : public BenchMarkBase
{
public:
    //nothing.
protected:
    bool dealHttpReq(int sock, BenchMarkInfo & info, int32_t & bytes);
    void getRequestData(string & data, BenchMarkInfo & info);
    void childDeal(int writeFd, BenchMarkInfo & info);
private:
    //nothing.
};

benchMarkHttp.cpp

#include "robustIo.h"
#include "http_parser.h"
#include "benchMarkHttp.h"
#include 

using namespace std;

void BenchMarkHttp::getRequestData(string & data, BenchMarkInfo & info)
{
    //http GET请求
    data = "GET / HTTP/1.1
"           //请求行,表示发起GET请求,url为"/",使用http 1.1协议
        "User-Agent: benchMark v1.0
"  //User-Agent请求头,表示当前客户端代理信息
        "Host: " + info.host +"
"      //Host请求头,表示请求的服务器域名
        "Accept: */*
"                 //Accept请求头,表示接受任意格式的应答数据
        "
";                           //一个空行表示请求头集合的结束
}

static int http_rsp_complete(http_parser * parser)
{
    //取出之前在解析变量中设置的私有数据,它为bool指针,
    //它指向dealHttpReq函数中的finish变量
    bool * pFinish = (bool *)parser->data;
    //设置finish的值为true,表示已经完成了一个http应答的解析。
    *pFinish = true;
    return 0;
}

bool BenchMarkHttp::dealHttpReq(int sock, BenchMarkInfo & info, int32_t & bytes)
{
    size_t ret = 0;
    string data;
    RobustIo rio;   //封装的io类变量
    bool finish = false;    //表示应答是否解析完毕,它的指针会被传递给http解析变量

    http_parser parser;             //声明http解析api的解析变量parser
    http_parser_settings settings;  //声明http解析api的解析设置变量settings
    
    http_parser_init(&parser, HTTP_RESPONSE);   //初始化http解析api的解析变量parser
    http_parser_settings_init(&settings);       //初始化http解析api的解析设置变量settings
    
    settings.on_message_complete = http_rsp_complete; //设置http应答解析完成回调函数
    parser.data = (void *)(&finish);   //在解析变量parser中设置我们的私有数据,在回调函数中会使用

    getRequestData(data, info); //获取http GET请求的发送数据
    //使用封装好的网络io类发送http GET请求
    ret = rio.rioWrite(sock, (void *)data.c_str(), data.size());
    //发送失败则返回false
    if (ret != data.size())
    {
        return false;
    }
    //统计发送字节数
    bytes += data.size();

    char c;
    int status;
    //初始化io读缓存区大小
    rio.rioInit(1024);
    //只要还没解析完应答则一直从网络中获取数据,并解析
    while (!finish)
    {
        //从网络中读取一个字节,rioRead是自带缓冲区的
        //故不会频繁的调用系统read函数,带来性能影响
        if (rio.rioRead(sock, &c, 1) != 1)
        {
            break;
        }
        //统计接收字节数
        ++bytes; 
        //调用http解析核心api对流式数据进行解析,每次解析从网络流中获取的一个字节
        //这个api函数返回解析成功的字节数,如果返回值和传入的要解析的字节数不一致
        //则表明解析过程中发送错误,则返回false
        if (http_parser_execute(&parser, &settings, &c, 1) != 1)
        {
            return false;
        }
    }

    //获取http应答的状态码
    status = parser.status_code;
    if (2 == (status / 100)) //状态码为2xx的都表示请求成功,故返回true?
    {
        return true;
    }
    else
    {
        return false;
    }
}

void BenchMarkHttp::childDeal(int writeFd, BenchMarkInfo & info)
{
    int sock = 0;
    RobustIo rio;
    //子进程压测报告数据,第一个int32_t是成功次数,
    //第二个int32_t是失败次数,第三个int32_t是上下行流量统计
    int32_t statData[3];
    int32_t beginTime = time(NULL);
    memset(statData, 0x0, sizeof(statData)); //初始化统计数据为0
    

    while (time(NULL) <= beginTime + info.times)
    {
        sock = rio.newSocket((char *)info.host.c_str(), info.port);
        if (sock < 0)
        {
            ++statData[1]; //发起连接失败统计在statData[1]
        }
        else
        {
            //发起http请求,并统计上下行流量
            if (dealHttpReq(sock, info, statData[2]))
            {
                ++statData[0]; //http请求成功统计在statData[0]
            }
            else
            {
                ++statData[1]; //http请求失败统计在statData[1]
            }
        }
        close(sock);
    }

    //向匿名管道写入压测报告数据
    rio.rioWrite(writeFd, statData, sizeof(statData));
}

3.6.5 BenchMarkFactory类

benchMarkFactory.h

#pragma once

#include 
#include "benchMarkBase.h"
#include "benchMarkConn.h"
#include "benchMarkHttp.h"
#include "benchMarkCommon.h"

class BenchMarkFactory
{
public:
    static BenchMarkBase * getBenchMark(int32_t requestType);
protected:
    //nothing.
private:
    //nothing.
};

benchMarkFactory.cpp

#include "benchMarkFactory.h"

BenchMarkBase * BenchMarkFactory::getBenchMark(int32_t requestType)
{
    if (CONN == requestType)
    {
        return new BenchMarkConn;
    }
    else if (HTTP == requestType)
    {
        return new BenchMarkHttp;
    }

    return NULL;
}

3.6.6 BenchMarkInfo结构体

benchMarkCommon.h

#pragma once        

#include 
#include 

using namespace std;

enum RequestType
{
    CONN = 1,       //基于连接
    HTTP = 2        //基于http
};

struct BenchMarkInfo
{
    string host;
    int32_t port;
    int32_t clients;
    int32_t requestType;
    int32_t times;
};
3.7 实测性能工具

3.7.1 编译

[root@rookie_centos benchMark]# ls
Makefile       benchMark.o        benchMarkBase.o    benchMarkConn.h       benchMarkFactory.h  benchMarkHttp.h  http_parser.h  robustIo.h
benchMark      benchMarkBase.cpp  benchMarkCommon.h  benchMarkConn.o       benchMarkFactory.o  benchMarkHttp.o  http_parser.o  robustIo.o
benchMark.cpp  benchMarkBase.h    benchMarkConn.cpp  benchMarkFactory.cpp  benchMarkHttp.cpp   http_parser.c    robustIo.cpp
[root@rookie_centos benchMark]# 
[root@rookie_centos benchMark]# make
cc  -g -O2 -Wall -Werror -Wshadow    -c http_parser.c -o http_parser.o
g++ -g -O2 -Wall -Werror -Wshadow  -c benchMark.cpp -o benchMark.o
g++ -g -O2 -Wall -Werror -Wshadow  -c benchMarkBase.cpp -o benchMarkBase.o
g++ -g -O2 -Wall -Werror -Wshadow  -c benchMarkConn.cpp -o benchMarkConn.o
g++ -g -O2 -Wall -Werror -Wshadow  -c benchMarkFactory.cpp -o benchMarkFactory.o
g++ -g -O2 -Wall -Werror -Wshadow  -c benchMarkHttp.cpp -o benchMarkHttp.o
g++ -g -O2 -Wall -Werror -Wshadow  -c robustIo.cpp -o robustIo.o
g++ -g -O2 -Wall -Werror -Wshadow   ./http_parser.o ./benchMark.o ./benchMarkBase.o ./benchMarkConn.o ./benchMarkFactory.o ./benchMarkHttp.o ./robustIo.o -o benchMark
Type ./benchMark to execute the program.

3.7.2 运行测试

我们对百度www.baidu.com站点进行压测

基于连接的压测

[root@rookie_centos benchMark]# ./benchMark -h www.baidu.com -p 30 -c 30 -t 10     
benchMark running. 
    host[www.baidu.com], port[30], clients[30], time[10], requestType[CONN]

benchMark report:
    speed=453 conn/sec. success=4536, failed=0
[root@rookie_centos benchMark]# 

基于http的压测

[root@rookie_centos benchMark]# ./benchMark -h www.baidu.com -p 30 -c 30 -t 10 -r 2
benchMark running. 
    host[www.baidu.com], port[30], clients[30], time[10], requestType[HTTP]

benchMark report:
    speed=104 pages/sec, 11327 kbytes/sec. success=1042, failed=0
[root@rookie_centos benchMark]#
3.8 性能工具扩展

除了对http接口进行压测外,我们还可以扩展出其他的业务压测类,只要编写好相应的压测类,并放入项目中即可。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/10062.html

相关文章

  • 后端研发菜鸟成长 第一章 入门 之 云服务器

    摘要:下一节后端研发菜鸟成长记第一章入门之存活下来后端研发菜鸟成长记交流群 1. 写在最前面 1.1 不会涉及的内容 这里不会有Linux系统装机过程介绍 这里不会有Linux操作系统的详细介绍 这里不会有Linux繁多的运维操作介绍 1.2 专注的内容 这里有Linux研发实战的经验分享,帮你避过前人踩过的坑 这里有Linux研发技术要点和核心概念的详细讲解 这里有Linux研发涉及...

    lykops 评论0 收藏0
  • 后端研发菜鸟成长 第一章 入门 之 "存活下来"

    摘要:命令行下存活下来的基本技能通过前面的操作我们已经有了自己的云服务器,并能上下传文件,那么接下来我们需要通过各种命令来操作和管理这个云服务器。不得不承认学习后端开发不像端或者端口的开发那样很快有反馈并有成就感,希望大家能坚持下来。 2.4 命令行下存活下来的基本技能 通过前面的操作我们已经有了自己的云服务器,并能上下传文件,那么接下来我们需要通过各种命令来操作和管理这个云服务器。 下面...

    VincentFF 评论0 收藏0
  • Linux后台研发超实用命令总结

    摘要:排序执行即可,默认是按照占用排序,也可以执行从内存大小排序转换为排序。传送门后端研发菜鸟成长记第一章入门之云服务器 作者 码龙喵 转载请注明出处 1.概述 计算机领域水太深了,不可能什么都记住,所以只需要理解并记住核心的原理,其他的交给网络,用的时候查一下就可以了。因此整理了平时工作中自己觉得好用的命令。 2.系统相关 查看手册命令 man 只查看当前下一级目录占用大小 du --ma...

    TesterHome 评论0 收藏0
  • Android 研发工程师图书一览(2016年版)

    摘要:番茄工作法简约而不简单,本书亦然。在番茄工作法一个个短短的分钟内,你收获的不仅仅是效率,还会有意想不到的成就感。 @author ASCE1885的 Github 简书 微博 CSDN 知乎本文由于潜在的商业目的,不开放全文转载许可,谢谢! showImg(/img/remote/1460000007319503?w=728&h=792); 广而告之时间:我的新书《Android 高...

    MadPecker 评论0 收藏0
  • Android 研发工程师图书一览(2016年版)

    摘要:番茄工作法简约而不简单,本书亦然。在番茄工作法一个个短短的分钟内,你收获的不仅仅是效率,还会有意想不到的成就感。 @author ASCE1885的 Github 简书 微博 CSDN 知乎本文由于潜在的商业目的,不开放全文转载许可,谢谢! showImg(https://segmentfault.com/img/remote/1460000007319503?w=728&h=792...

    NoraXie 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<