Back-end research and development of novice growth Chapter 3 small test knife, writing performance testing tools

Posted by rarebit on Fri, 21 Jun 2019 21:03:46 +0200

3 small test knife, compiling performance testing tools

As a back-end developer, he must have the ability of system performance evaluation and analysis, because only when he knows the overall performance of the system well, can he know when the system needs to be expanded and where the performance bottlenecks need to be optimized.

This chapter will introduce how to evaluate the overall performance of the system macroscopically, and focus on how to compile performance testing tools to make a real measurement of the system performance. After all, the theory is based on theory, and the theoretical performance indicators still need to be tested by actual pressure measurement.

Overall Performance of 3.1 System

Usually we use QPS, average response time and concurrency to measure the overall performance of a system.

  • QPS: Represents the number of requests per second.

  • Average response time: Indicates the average system time spent on a single request in seconds.

  • Concurrent Number: Represents the number of requests that the system can handle at the same time.

The maximum QPS supported by the system is determined by the average response time and the number of concurrencies, that is, QPS = concurrency / average response time. The explanation of pure theory may not be very easy to understand. Let's take an example here. For example, we go to the bank business hall to do deposit business. At this time, there are 10 counters to do deposit business. It takes an average of 5 minutes for each person to do deposit business. How much deposit business can this business hall handle per hour? Here 10 counters can handle business, that is, the "concurrent number" is 10, and our unit time is set to an hour. Then it takes 5 minutes to handle the deposit business, that is to say, the "average response time" is 1/13 of an hour. Then the number of deposit business that this business hall can handle per hour is QPS=10/(1/13), that is 130.

From the above formula "QPS = concurrency number / average response time", we can see that to improve the QPS of the system, we can start from two aspects, one is to increase concurrency number, the other is to reduce the average response time.

  • Ways to Increase Concurrency Number
    Adding more servers, using multi-core servers, running multi-processes on a single server to provide services, processes using a more efficient IO model.

  • Method of Reducing Average Response Time
    Response time is usually composed of network communication time (network io time)+computing processing time (cpu time)+disk read-write time (disk io time). We can reduce the network communication time by using larger network bandwidth and better network card. We can use more powerful CPU or optimization algorithm to reduce computing processing time. We can use SSD hard disk instead of ordinary hard disk to reduce the io time of single read and write disk. We can add a buffer layer between business layer and database persistence layer to reduce the io times of disk.

3.2 Performance Testing Tool

Our performance testing tool is a command line tool named "benchMark", which is used to test the overall performance of network services provided by a system. It has the functions of command parameters, supporting multi-client concurrency, supporting connection-based pressure measurement, supporting pressure measurement based on specific business requests, etc.

3.2.1 Command Parameter Function

Like all Linux commands, our tool also supports command parameters. Under Linux, there is a getopt_long function to support the parsing of command line parameters. Through this function, we can easily achieve the parsing and acquisition of long and short parameters. The header file and function prototype of getopt_long function are as follows:

#include <getopt.h>

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

As a C/C++ entry function, the standard function prototype of main is "int main" (int argc, char * argv []). The command line parameters are also passed in through the argc and argv parameters of main function.

  • argc and argv parameters

The two parameters that need to be passed in at the beginning of getopt_long function are argc and argv parameters of main function. argc represents the number of parameters to be parsed, and argv is the list of parameters to be parsed.

  • optstring parameter

Optistring is an option declaration string. One of the characters represents an option. If the character followed by an English colon indicates that the option must have an option parameter, the option parameter can be in the same command-line parameter as the option, i.e. "-o arg", or in the next command-line parameter, i.e. "-o arg", whose value can be obtained from the global variable Op. Obtained in targ; if the character is followed by two colons indicating that the option has an optional option parameter, then the optional option parameter should be in the same command line parameter as the option, such as when we set o as the option with optional option parameter, then the optional option parameter must be in the same parameter as the option, that is, "-oarg" Where arg is an optional parameter of the o option, there can be no spaces between them. The same optional parameter can be obtained in the global variable optarg. When the optional parameter is not set, the value of optarg is 0.

  • longopts parameter

There are only short options in optstring. When long options are supported, the longoptions parameter is used. The longoptions parameter is a pointer to the struct option array, and the information about long options is set in the struct option array. The long option is slightly different from the short option in setting the option parameters. The short option is'- o arg'or'- o arg', while the long option is'- arg=param' or'- arg param'. The short option can only use the form of'- oarg'when setting the optional parameters, and the long option can only use the form of'- arg=param' when setting the optional parameters. Let's look at the meaning of each field in struct option.

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

Name field: the name of a long option.

has_arg field: Indicates long option parameter settings, if no_argument (or 0) means long option has no parameter, if required_argument (or 1) means long option must have one parameter, if optional_argument (or 3) means long option has an optional parameter, the optional parameter of long option must be set with "--arg=param". .

Flag field: Usually NULL, if flag is NULL, getopt_long returns the value set by the last Val field when parsing to the long option set by name, which enables both short and long options to have the same function; if flag is not NULL, getopt_long returns 0 when parsing to the long option set by name, and the value of the variable pointed to by flag is set to val. If the long option is not resolved, the value of the variable pointed to by flag will not be changed.

val field: The return value of getopt_long when matched to a long option (flag is NULL), or the value of the variable to which flag is pointed (flag is not NULL).

  • longindex parameter

It can be set to NULL, not NULL, to return the index value of the matching long option in the long option array.

3.2.2 Multi-client concurrency

We use fork to simulate the multi-client concurrency scenario. The child process and the parent process communicate through anonymous pipes created by the pipe function. The parent process collects the test results and prints the test summary results in the parent process after all the child processes have finished.

3.2.3 Connection-based or Request-based

Our test tool supports concurrent compression based on connection (closing network connection immediately after creating network connection) and concurrent compression based on request (initiating business request after creating network connection successfully and closing network connection after receiving reply data). It can set different validation of request data and reply data for different businesses. Logic.

Limitations of 3.2.4 Performance Tools

Our performance tools can only run on a single server, so the concurrent pressure measurement ability is limited by the concurrent ability of a single server. If the external network service is also limited by the external network bandwidth of the server, once the external network bandwidth runs full, it will be difficult to improve the concurrent number.

3.3 Data Interaction Process

The data interaction process between the performance testing tool and the network service system is as follows:

[Figure 3-1 Data Interaction]

Class 3.4 Diagrams

benchMark consists of five classes and one structure. The structure BenchMarkInfo is used to save the input parameters related to the test; BenchMarkFactory is a simple factory class, which is used to generate specific pressure measurement classes BenchMarkConn (based on connection) and BenchMarkHttp (based on http protocol), BenchMarkBase is the pressure measurement base class; RobustIo class is the encapsulation of network io, which has its own read buffer. The relationships between the five classes and one structure are as follows:

[Class 3-2 diagrams]

3.5 http requests and responses

In BenchMarkHttp class, the HTTP protocol is involved. In BenchMarkHttp, we measure the performance of the root directory interface of a web site, and initiate a GET request with a url of "/". In the aspect of HTTP response parsing, we use HTTP of open source C language to parse api. It has no dependence on other special libraries and supports stream parsing. In the process of stream parsing, we use callback to inform the field information related to http protocol. In Chapter 9, Network Communication and Concurrent, we will implement a simple HTTP protocol parsing class, and explain the HTTP protocol in detail to deepen our understanding of HTTP protocol.

3.5.1 Initiate http requests

The http request message consists of three parts: request_line, headers and body. The request line and body end with rn, while the request header set end with an empty line ("rn"), in which body is an optional part. There is no BOD in our http pressure test GET request. Part y. Next, take a look at the specific request data generation function.

void BenchMarkHttp::getRequestData(string & data, BenchMarkInfo & info)
{
    //http GET request
    data = "GET / HTTP/1.1\r\n"           //Request line, which means GET request is initiated, url is "/", using http 1.1 protocol
        "User-Agent: benchMark v1.0\r\n"  //User-Agent request header representing current client proxy information
        "Host: " + info.host +"\r\n"      //Host request header, which represents the requested server domain name
        "Accept: */*\r\n"                 //Accept request header, which indicates acceptance of response data in any format
        "\r\n";                           //A blank line indicates the end of the request header collection
}

3.5.3 Resolving http responses

The http parsing api of the C language we use is hosted on github and the project link is: https://github.com/nodejs/htt... .

There are several steps to use open source http to parse the api. Here's our code for using http to parse the API in BenchMark http:

static int http_rsp_complete(http_parser * parser)
{
    //The private data set in the parsing variable before it is taken out is a bool pointer.
    //It points to the finish variable in dealHttpReq function
    bool * pFinish = (bool *)parser->data;
    //Setting the value of finish to true indicates that an http reply has been parsed.
    *pFinish = true;
    return 0;
}

bool BenchMarkHttp::dealHttpReq(int sock, BenchMarkInfo & info, int33_t & bytes)
{
    size_t ret = 0;
    string data;
    RobustIo rio;   //Encapsulated io class variables
    bool finish = false;    //Indicates whether the response has been parsed, and its pointer is passed to the http parsing variable

    http_parser parser;             //Declare parser, the parsing variable of http parsing api
    http_parser_settings settings;  //Declare the parsing settings variable settings for the http parsing api
    
    http_parser_init(&parser, HTTP_RESPONSE);   //parser, the parse variable that initializes the http parsing api
    http_parser_settings_init(&settings);       //Initialize the parsing settings variable settings of the http parsing api
    
    settings.on_message_complete = http_rsp_complete; //Setting http response parsing to complete callback function
    parser.data = (void *)(&finish);   //Setting our private data in parser, the parser variable, will be used in the callback function

    getRequestData(data, info); //Get the sending data of http GET request
    //Sending http GET requests using encapsulated network io classes
    ret = rio.rioWrite(sock, (void *)data.c_str(), data.size());
    //Send failure returns false
    if (ret != data.size())
    {
        return false;
    }
    //Statistics of the number of bytes sent
    bytes += data.size();

    char c;
    int status;
    //Initialize io read buffer size
    rio.rioInit(1034);
    //As long as the answer has not been parsed, the data is always obtained from the network and parsed.
    while (!finish)
    {
        //Read a byte from the network, rioRead is self-buffer
        //Therefore, the read function of the system will not be frequently invoked, resulting in performance impact.
        if (rio.rioRead(sock, &c, 1) != 1)
        {
            break;
        }
        //Statistical Received Bytes
        ++bytes; 
        //Call http to parse the core api to parse the streaming data, each time parsing a byte from the network stream
        //This api function returns the number of bytes parsed successfully if the return value is inconsistent with the number of bytes passed in for parsing
        //If an error is sent during parsing, false is returned.
        if (http_parser_execute(&parser, &settings, &c, 1) != 1)
        {
            return false;
        }
    }

    //Get the status code of the http response
    status = parser.status_code;
    if (3 == (status / 100)) //The status code of 3xx indicates that the request is successful, so it returns true?
    {
        return true;
    }
    else
    {
        return false;
    }
}

From the above code, we can see that the variables used by the HTTP parsing api are: parser and settings. They are http_parser and http_parser_settings. Before using them, we need to initialize them with http_parser_init and http_parser_settings_init respectively. In settings, we set the call back function http_rsp_complete, and in http_rsp_complete. In the number, we set the value of the finish variable in dealHttpReq to true to indicate that the HTTP response has been parsed and dealHttpReq can exit the HTTP parsing loop. In dealHttpReq function, we use http_parser_execute, the core api of HTTP parsing, to perform streaming parsing.

3.6 Complete Code

3.6.1 benchMark.cpp

#include <getopt.h>
#include <stdlib.h>
#include <iostream>
#include "benchMarkBase.h"
#include "benchMarkCommon.h"
#include "benchMarkFactory.h"

using namespace std;

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

/*
    Use of Output benchMark
 */
void benchMarkUsage()
{
    cout << "Usage: -h host -p port [option]" << endl;
    cout << endl;
    //general option
    cout << "general options:" << endl;
    cout << "   --help          print usage" << endl;
    cout << "   -v,--version    print version info" << endl;
    cout << endl;
    //Connection options
    cout << "connection options:" << endl;
    cout << "   -h,--host       server host to connect to" << endl;
    cout << "   -p,--port       server port" << endl;
    cout << endl;
    //Concurrent Options
    cout << "concurrent options:" << endl;
    cout << "   -c,--clients    number of concurrent clients, default 4, max is 100" << endl;
    cout << endl;
    //Interaction options
    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;
    //Short options
    const char shortOpts[] = "?vh:p:c:r:t:";
    //Long options
    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}  //Long options array must end with an empty setting
    };

    //Analyse the parameters until the parameters are resolved.
    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;          //Default concurrency of four clients
    info.requestType = CONN;   //The default is connection-based manometry
    info.times = 60;           //Default manometry 60 seconds
}

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 << "\thost[" << 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);    //Initialize performance test information
    
    ret = dealArgv(argc, argv, info); //Analytical input parameters
    if (ret != 0)
    {
        return -1;
    }

    ret = checkArgv(info);  //Check the validity of parameter values
    if (ret != 0)
    {
        benchMarkUsage();
        return -1;
    }

    //Generate specific BenchMark classes using BenchMark factory classes
    BenchMarkBase * pBase = BenchMarkFactory::getBenchMark(info.requestType);
    //Running run for Pressure Measurement
    pBase->run(info);
    //Release space
    delete pBase;
    
    return 0;
}

3.6.2 BenchMarkBase class

  • benchMarkBase.h

//Represents that the header file is compiled only once, which is more convenient than using 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 <errno.h>
#include <string.h>
#include <iostream>

using namespace std;

void BenchMarkBase::parentDeal(int readFd, BenchMarkInfo & info)
{
    RobustIo rio; //Encapsulated io class variables
    rio.rioInit(1024); //Initialize read buffer
    int32_t childReportData[3]; //The report data of the subprocess is three int32_t variables
    int32_t success = 0;
    int32_t failed = 0;
    int32_t bytes = 0;

    while (true)
    {
        //Read a report from a subprocess
        if (rio.rioRead(readFd, childReportData, 12) != 12)
        {
            break; //Failure to read or complete the report
        }
        //Statistical success times, success times in the first int32_t
        success += childReportData[0];
        //The number of failures is counted, and the number of failures is placed in the second int32_t.
        failed += childReportData[1];
        //Statistical upstream and downstream traffic, upstream and downstream traffic in the third int32_t
        bytes += childReportData[2];
    }

    //Output Pressure Measurement Report
    cout << "benchMark report:" << endl;
    if (CONN == info.requestType)
    {
        cout << "\tspeed=" << (success + failed) / info.times << " conn/sec. " 
             << "success=" << success <<", failed=" << failed << endl;
    }
    else
    {
        cout << "\tspeed=" << (success + failed) / info.times << " pages/sec, "
             << (bytes / (info.times * 1024)) << " kbytes/sec. "
             << "success=" << success <<", failed=" << failed << endl;
    }
}

/*
    Core piezometric function
 */
void BenchMarkBase::run(BenchMarkInfo & info)
{
    int fd[2];
    int ret = 0;
    int sockfd = 0;
    pid_t childPid = 0;
    RobustIo rio;

    //Check whether the service is open on the specified host and port.
    //Failure to create a tcp connection indicates that there is no open service, and the termination pressure is returned directly.
    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); //Create anonymous pipes for parent-child interprocess communication
    if (ret != 0)
    {
        cout << "call pipe() failed! error message = " << strerror(errno) << endl;
        return;
    }

    //Call fork to create the specified number of child processes
    for (int32_t i = 0; i < info.clients; ++i)
    {
        childPid = fork();
        if (childPid <= 0) //Return from a child process, or the parent process call fork fails
        {
            break;
        }
    }

    if (0 == childPid) //Return from a child process
    {
        close(fd[0]); //Close anonymous pipe reader
        childDeal(fd[1], info); //Pressure measurements are carried out in the sub-process, and anonymous pipes are passed in to write fd
        return; //After the pressure measurement, the sub-process returns and ends the process running after returning the main function.
    }

    //Return in the parent process (call fork failed, or call fork all succeeded)
    
    if (childPid < 0) //If the call fork fails, print the reason for the call failure
    {
        cout << "fork childs failed. error message = " << strerror(errno) << endl;
    }

    //The parent process closes the write end of the anonymous pipeline, which must be closed.
    //Otherwise, the end-of-file flag cannot be read when the parent process receives the pressure test report data of the child process.
    //Blocked the read call, causing the parent process to be unable to exit.
    close(fd[1]); 
    //Receive the child process report in the parent process and output the final manometric report after summing up the data.
    parentDeal(fd[0], info);
}

3.6.3 BenchMarkConn class

  • 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 <errno.h>
#include <string.h>


void BenchMarkConn::childDeal(int writeFd, BenchMarkInfo & info)
{
    RobustIo rio;
    //The first int32_t is the number of successes.
    //The second int32_t is the number of failures, and the third int32_t is the upstream and downstream traffic statistics.
    int32_t statData[3];
    int32_t beginTime = time(NULL);
    memset(statData, 0x0, sizeof(statData)); //Initialization statistics are 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]; //Connection failure statistics in statData[1]
            }
        }
        else
        {
            ++statData[0]; //Connection success statistics in statData[0]
        }
        close(sock);
    }

    //Writing Pressure Measurement Report Data to Anonymous Pipeline
    rio.rioWrite(writeFd, statData, sizeof(statData));
}

3.6.4 BenchMarkHttp class

  • 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 <iostream>

using namespace std;

void BenchMarkHttp::getRequestData(string & data, BenchMarkInfo & info)
{
    //http GET request
    data = "GET / HTTP/1.1\r\n"           //Request line, which means GET request is initiated, url is "/", using http 1.1 protocol
        "User-Agent: benchMark v1.0\r\n"  //User-Agent request header representing current client proxy information
        "Host: " + info.host +"\r\n"      //Host request header, which represents the requested server domain name
        "Accept: */*\r\n"                 //Accept request header, which indicates acceptance of response data in any format
        "\r\n";                           //A blank line indicates the end of the request header collection
}

static int http_rsp_complete(http_parser * parser)
{
    //The private data set in the parsing variable before it is taken out is a bool pointer.
    //It points to the finish variable in dealHttpReq function
    bool * pFinish = (bool *)parser->data;
    //Setting the value of finish to true indicates that an http reply has been parsed.
    *pFinish = true;
    return 0;
}

bool BenchMarkHttp::dealHttpReq(int sock, BenchMarkInfo & info, int32_t & bytes)
{
    size_t ret = 0;
    string data;
    RobustIo rio;   //Encapsulated io class variables
    bool finish = false;    //Indicates whether the response has been parsed, and its pointer is passed to the http parsing variable

    http_parser parser;             //Declare parser, the parsing variable of http parsing api
    http_parser_settings settings;  //Declare the parsing settings variable settings for the http parsing api
    
    http_parser_init(&parser, HTTP_RESPONSE);   //parser, the parse variable that initializes the http parsing api
    http_parser_settings_init(&settings);       //Initialize the parsing settings variable settings of the http parsing api
    
    settings.on_message_complete = http_rsp_complete; //Setting http response parsing to complete callback function
    parser.data = (void *)(&finish);   //Setting our private data in parser, the parser variable, will be used in the callback function

    getRequestData(data, info); //Get the sending data of http GET request
    //Sending http GET requests using encapsulated network io classes
    ret = rio.rioWrite(sock, (void *)data.c_str(), data.size());
    //Send failure returns false
    if (ret != data.size())
    {
        return false;
    }
    //Statistics of the number of bytes sent
    bytes += data.size();

    char c;
    int status;
    //Initialize io read buffer size
    rio.rioInit(1024);
    //As long as the answer has not been parsed, the data is always obtained from the network and parsed.
    while (!finish)
    {
        //Read a byte from the network, rioRead is self-buffer
        //Therefore, the read function of the system will not be frequently invoked, resulting in performance impact.
        if (rio.rioRead(sock, &c, 1) != 1)
        {
            break;
        }
        //Statistical Received Bytes
        ++bytes; 
        //Call http to parse the core api to parse the streaming data, each time parsing a byte from the network stream
        //This api function returns the number of bytes parsed successfully if the return value is inconsistent with the number of bytes passed in for parsing
        //If an error is sent during parsing, false is returned.
        if (http_parser_execute(&parser, &settings, &c, 1) != 1)
        {
            return false;
        }
    }

    //Get the status code of the http response
    status = parser.status_code;
    if (2 == (status / 100)) //The status code 2xx indicates that the request is successful, so it returns true?
    {
        return true;
    }
    else
    {
        return false;
    }
}

void BenchMarkHttp::childDeal(int writeFd, BenchMarkInfo & info)
{
    int sock = 0;
    RobustIo rio;
    //The first int32_t is the number of successes.
    //The second int32_t is the number of failures, and the third int32_t is the upstream and downstream traffic statistics.
    int32_t statData[3];
    int32_t beginTime = time(NULL);
    memset(statData, 0x0, sizeof(statData)); //Initialization statistics are 0
    

    while (time(NULL) <= beginTime + info.times)
    {
        sock = rio.newSocket((char *)info.host.c_str(), info.port);
        if (sock < 0)
        {
            ++statData[1]; //Initiation connection failure statistics in statData[1]
        }
        else
        {
            //Initiate http requests and count up and down traffic
            if (dealHttpReq(sock, info, statData[2]))
            {
                ++statData[0]; //http request success statistics in statData[0]
            }
            else
            {
                ++statData[1]; //http request failure statistics in statData[1]
            }
        }
        close(sock);
    }

    //Writing Pressure Measurement Report Data to Anonymous Pipeline
    rio.rioWrite(writeFd, statData, sizeof(statData));
}

3.6.5 BenchMarkFactory class

  • benchMarkFactory.h

#pragma once

#include <stdint.h>
#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 structure

  • benchMarkCommon.h

#pragma once        

#include <stdint.h>
#include <string>

using namespace std;

enum RequestType
{
    CONN = 1,       //Connection-based
    HTTP = 2        //Based on http
};

struct BenchMarkInfo
{
    string host;
    int32_t port;
    int32_t clients;
    int32_t requestType;
    int32_t times;
};

3.7 Measured Performance Tool

3.7.1 Compilation

[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 Running Test

We press Baidu Home Page

  • Connection-based pressure measurement

[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-based pressure measurement

[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 Performance Tool Extension

In addition to the pressure measurement of http interface, we can also expand other business pressure measurement classes, as long as the corresponding pressure measurement classes are written and put into the project.

Topics: Linux network github C