服務器性能測試

最近看了coolshell 上面一篇文章,《性能測試應該怎麼做?》
原文鏈接如下:

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

裏面提到三個觀點

  1. 平均值不靠譜,而是應該使用百分比分佈來統計
  2. 響應時間(latency)要和吞吐量(Thoughput)掛鉤
  3. 響應時間要和成功率掛鉤

以及嚴謹測試服務器性能的方法,摘抄如下:

如何嚴謹地做性能測試

一般來說,性能測試要統一考慮這麼幾個因素: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::Histogram;

#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;                                                                                         
}      
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章