您的位置:首页 > 编程语言 > C语言/C++

服务器性能测试

2016-07-10 23:04 507 查看
最近看了coolshell 上面一篇文章,《性能测试应该怎么做?》

原文链接如下:

http://coolshell.cn/articles/17381.html?from=groupmessage&isappinstalled=0

里面提到三个观点

平均值不靠谱,而是应该使用百分比分布来统计

响应时间(latency)要和吞吐量(Thoughput)挂钩

响应时间要和成功率挂钩

以及严谨测试服务器性能的方法,摘抄如下:

如何严谨地做性能测试

一般来说,性能测试要统一考虑这么几个因素:Thoughput吞吐量,Latency响应时间,资源利用(CPU/MEM/IO/Bandwidth…),成功率,系统稳定性

下面的这些性能测试的方式基本上来源自我的老老东家汤森路透,一家做real-time的金融数据系统的公司。

一,你得定义一个系统的响应时间latency,建议是TP99,以及成功率。比如路透的定义:99.9%的响应时间必需在1ms之内,平均响应时间在1ms以内,100%的请求成功。

二,在这个响应时间的限制下,找到最高的吞吐量。测试用的数据,需要有大中小各种尺寸的数据,并可以混合。最好使用生产线上的测试数据。

三,在这个吞吐量做Soak Test,比如:使用第二步测试得到的吞吐量连续7天的不间断的压测系统。然后收集CPU,内存,硬盘/网络IO,等指标,查看系统是否稳定,比如,CPU是平稳的,内存使用也是平稳的。那么,这个值就是系统的性能

四,找到系统的极限值。比如:在成功率100%的情况下(不考虑响应时间的长短),系统能坚持10分钟的吞吐量

五,做Burst Test。用第二步得到的吞吐量执行5分钟,然后在第四步得到的极限值执行1分钟,再回到第二步的吞吐量执行5钟,再到第四步的权限值执行1分钟,如此往复个一段时间,比如2天。收集系统数据:CPU、内存、硬盘/网络IO等,观察他们的曲线,以及相应的响应时间,确保系统是稳定的。

六、低吞吐量和网络小包的测。有时候,在低吞吐量的时候,可能会导致latency上升,比如TCP_NODELAY的参数没有开启会导致latency上升(详见TCP的那些事),而网络小包会导致带宽用不满也会导致性能上不去,所以,性能测试还需要根据实际情况有选择的测试一下这两场景。

近期正好在做thrift 服务器相关latency性能的测试的工作。

于是按照测试方法, 写了简单的demo_client 和 demo_server 来测试相应的latency, 测出每次query 响应的latency 做成一个直方图histogram 来统计 latency 的百分比份额 :

我写的histogram如下:

//histogram.h

#ifndef COMMON_ALGORITHM_HISTOGRAM_H_
#define COMMON_ALGORITHM_HISTOGRAM_H_

#include <vector>
#include <string>

namespace common{
namespace algorithm{
class Histogram{
public:
Histogram(float _bucketwildth, int32_t _bucketnums);
void AddData(float latency);
void ShowHistogram();
std::vector<int32_t>GetDataVector();
int32_t GetDataNums();
private:
std::vector<int32_t>bucket;
float bucketwidth;
int32_t bucketnums;
int32_t datanums;
};
}
}
using common::algorithm::Histogram;
#endif


//histogram.cc

#include "common/algorithm/histogram.h"
#include "boost/shared_ptr.hpp"
#include <iostream>
namespace common{
namespace algorithm{

Histogram::Histogram(float _bucketwidth, int32_t _bucketnums){
bucketwidth = _bucketwidth;
bucketnums = _bucketnums;
bucket.resize(bucketnums+1,0);
}

void Histogram::AddData(float latency){
bucket[static_cast<int32_t>(latency/bucketwidth)]++;
datanums++;
}

int32_t Histogram::GetDataNums(){
return datanums;
}

void Histogram::ShowHistogram(){
printf("Range  DataNums  Percentage \n\n");
for(int32_t i=0;i< bucket.size();++i){
printf("%.3f -- %.3f : ", bucketwidth*i , bucketwidth*(i+1 ));
printf("%d ", bucket[i]);
printf("%.3f%\n", (1.0*bucket[i])/(1.0*datanums)*100);

}
}
std::vector<int32_t>  Histogram::GetDataVector(){
return bucket;
}

}
}


下面是测试文件

histogram_test.cc

#include<iostream>
#include<vector>
#include<string>
#include"common/algorithm/histogram.h"
#include"boost/shared_ptr.hpp"

int main(){
auto p1 = std::make_shared<Histogram>(5.0,30);
for(int32_t i = 0; i<100; i+=2){
p1->AddData(static_cast<float>(i));
}
p1->ShowHistogram();
return 0;
}


更新

上面的程序有很多问题

1. 溢出没有再函数里判断,使得其他用户很容易犯错

2. 线程不安全,多线程使用会出现问题

3. 程序代码不规范

4. ..

下面贴出更改后的代码

//histogram.h

#ifndef COMMON_ALGORITHM_HISTOGRAM_H_
#define COMMON_ALGORITHM_HISTOGRAM_H_

#include <atomic>
#include <string>
#include <vector>
#include "common/base/stl_util.h"

namespace common {
namespace algorithm {

class Histogram {
public:
Histogram(double min, double max, double step);
void AddData(double data);
std::string ToString();
size_t data_num() const;
private:
std::vector<AtomicWrapper<size_t> > buckets_;
double bucket_width_;
double min_;
double max_;
size_t bucket_num_;
size_t data_num_;
};
}  // namespace algorithm
}  // namespace common

using common::algorithm::Histogra
fab0
m;

#endif  // COMMON_ALGORITHM_HISTOGRAM_H_


使用atomic 来实现线程安全。

由于atomic 对象不能复制,所以不能直接放入std::vector中,需要写一个AtomicWrapper , 才能放入vector中

template <typename T>
struct AtomicWrapper {
AtomicWrapper(): a_() {}
AtomicWrapper(const std::atomic<T>& a): a_(a.load()) {}
AtomicWrapper(const AtomicWrapper& other): a_(other.a_.load()) {}
AtomicWrapper& operator=(const AtomicWrapper& rhs) {
a_.store(rhs.load());
}
std::atomic<T> a_;
};


//histogram.cc

#include "common/algorithm/histogram.h"
#include <string>
#include "common/base/log.h"
#include "common/string/concat.h"

namespace common {
namespace algorithm {

using std::atomic;
using std::to_string;
using std::string;

Histogram::Histogram(double min, double max, double step)
: min_(min), max_(max), bucket_width_(step), data_num_(0) {
bucket_num_ = static_cast<size_t>(abs((max - min) / step) + 2);
buckets_.resize(bucket_num_);
}

void Histogram::AddData(double data) {
if (buckets_.empty()) return;
if (data < min_) {
buckets_[0].a_++;
} else if (data < min_ + bucket_width_ * (bucket_num_ - 2)) {
buckets_[static_cast<size_t>((data - min_) / bucket_width_) + 1].a_++;
} else {
buckets_[bucket_num_ - 1].a_++;
}
data_num_++;
}

size_t Histogram::data_num() const {
return data_num_;
}

string Histogram::ToString() {
string res = " -NaN -- ";
res = Concat(res, to_string(min_), " : ", to_string(buckets_[0].a_), "  ",
to_string(static_cast<double>(buckets_[0].a_) / data_num_ * 100), "%\n");
for (size_t i = 1; i < bucket_num_ - 1; ++i) {
res = Concat(res, to_string(bucket_width_ * (i - 1) + min_),
" -- ", to_string(bucket_width_ * i + min_), " : ", to_string(buckets_[i].a_), "  ",
to_string(static_cast<double>(buckets_[i].a_) / data_num_ * 100), "%\n");
}
res = Concat(res, to_string(bucket_width_ * (bucket_num_-2) + min_),
" -- NaN : ", to_string(buckets_[bucket_num_-1].a_), "  ",
to_string(static_cast<double>(buckets_[bucket_num_-1].a_) / data_num_ * 100), "%\n");
return res;
}

}  // namespace algorithm
}  // namespace common


使用gtest书写测试单元 unit.

//histogram_test.cc

#include "common/algorithm/histogram.h"
#include <memory>
#include "thirdparty/glog/logging.h"
#include "thirdparty/gtest/gtest.h"

using std::string;

TEST(HistogramTest, HistogramBaseTest) {
Histogram histogram(-5.0, 10, 2);
for (int32_t i = -10; i < 20; i += 1) {
histogram.AddData(static_cast<double>(i));
}
string res =" -NaN -- -5.000000 : 5  16.666667%\n\
-5.000000 -- -3.000000 : 2  6.666667%\n\
-3.000000 -- -1.000000 : 2  6.666667%\n\
-1.000000 -- 1.000000 : 2  6.666667%\n\
1.000000 -- 3.000000 : 2  6.666667%\n\
3.000000 -- 5.000000 : 2  6.666667%\n\
5.000000 -- 7.000000 : 2  6.666667%\n\
7.000000 -- 9.000000 : 2  6.666667%\n\
9.000000 -- NaN : 11  36.666667%\n";
EXPECT_STREQ(res.c_str(), histogram.ToString().c_str());
}


下面是client 端和server 端的代码

// demo_multi_client.cc

#include <memory>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include "common/thrift/echo_handler.h"
#include "common/string/convert.h"
#include "common/algorithm/histogram.h"
#include "common/base/log.h"
#include "common/system/time/stop_watch.h"
#include "common/base/sys_utils.h"
#include "common/thread/thread.h"

using std::make_shared;
using std::shared_ptr;

void MakeClientEcho(shared_ptr<Histogram> histogram, size_t nPipelines,size_t port) {
auto client = std::make_shared<TEchoClient>("localhost",port);
client->Connect();
LOG(INFO) << "Client Connected!";
for (size_t k =0; k<nPipelines; ++k) {
Request req;
Response resp;
req.__set_msg("lanfengrequest");
req.__set_seq(k);
StopWatch sw;
client->client()->Echo(resp,req);
histogram->AddData(sw.GetElapsedUs());
if (req.seq != resp.seq) {
LOG(INFO) <<"Echo sequence wrong!";
}
}
}
int32_t main (int32_t argc, char * argv []) {
LOG(INFO) << "pid = " << getpid();
if (argc > 0) {
int32_t nClients = argc > 1 ? atoi(argv[1]) : 1;
int32_t nPipelines = argc > 2 ? atoi(argv[2]) : 1;
double min = argc > 3 ? atof(argv[3]) : 0;
double max = argc > 4 ? atof(argv[4]) : 1000;
double step = argc > 5 ? atof(argv[5]) : 50;
int32_t port  = argc > 6 ? atoi(argv[6]) : 9090;
LOG(INFO) << "Clientnum = " << nClients << "Pipesnum = " << nPipelines ;
LOG(INFO) << "Start ";
std::vector<shared_ptr<common::Thread> > threadPtrs;
auto histogram = make_shared<Histogram>(min, max, step);
threadPtrs.resize(nClients);
for (size_t i = 0; i < nClients; ++i) {
threadPtrs[i] = make_shared<common::Thread>();
threadPtrs[i]->Start(std::bind(MakeClientEcho, histogram, nPipelines, port));
}
for (auto thread: threadPtrs) {
thread->Join();
}
LOG(INFO) << "all finished";
LOG(INFO) << histogram->ToString();
}else{
printf("Usage: clientnum, pipelines, [min], [max], [step], [port]\n");
}
}


// demo_monitor_server.h

#include <gtest/gtest.h>
#include "common/thrift/echo_monitor_handler.h"
#include "common/string/convert.h"
#include "common/base/log.h"
#include "common/monitor/statistic_monitor.h"
#include "common/thrift/monitor_names.h"
#include "protoss/common/protoss_service.h"

namespace thrift {
class MonitorService : public ::protoss::ProtossService {
public:
MonitorService(const int32_t argc, const char* argv[]) : ProtossService(argc, argv) {
}

virtual ~MonitorService() {
LOG(INFO) << "exiting monitor service ...";
}

virtual void Run() {
LOG(INFO) << "Start monitor Service ...";
printf("start monitor service ...\n");
ProtossService::Run();
echo_server_->Start();
}

void InitGlobal () {
echo_server_ = std::make_shared<TEchoServer> (FLAGS_port, FLAGS_thread_num);
MonitorNames::RegisterAll();
}

private:
std::shared_ptr<TEchoServer> echo_server_;
};
} // namespace thrift
} // namespace common

int32_t main(int32_t argc, const char* argv[]) {
common::thrift::MonitorService mymonitor(argc, argv);
mymonitor.InitGlobal();
mymonitor.Run();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息