入力ファイルから、ストレス評価を実施
入力ファイル(json)
[
{
"mail": "test0000@test.jp",
"userid": "123456",
"usertoken": "deadbeaf"
},...
]
go source
package storess
// Package storess : ストレス評価
// -*- coding: utf-8 -*-
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"sync"
"testing"
"time"
)
///// テストに使う値
var (
trgtDir = "./"
debug_counter int = 0
prev_counter int = 0
starttm time.Time
totalstarttm time.Time
)
const (
GAEHOST = "https://gae-storess-test.appspot.com/%s"
APPTOKENKEY = "<your app token>"
APPTOKENVAL = "X-APP-TOKEN"
USERTOKENKEY = "X-USER-TOKEN"
USERIDKEY = "X-USER-ID"
PARALLELCNT = 16
)
type token struct {
Account string
Uid string
Token string
}
/*
ユーザ情報読出し、書込みをシミュレート
PATCH /api/v1/users/{user_id}
GET /api/v1/users/{user_id}
*/
type wraplocreq struct {
Location locreq `json:"location"`
}
type container_type int
// Request methods
const (
NEEDJSON container_type = iota
WITHOUTJSON
)
type locreq struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Accuracy float64 `json:"accuracy"`
}
type container struct {
T *testing.T
Uid string
Token string
Path string
Method string
Req locreq
Type container_type
}
func TestStoress(t *testing.T) {
trgtList := []string{"target00000.json", "target00001.json", "target00002.json", "target00003.json"}
// json ターゲットユーザ:objectを一度mapへ格納
alltarget := make(map[string][]token)
//
for _, v := range trgtList {
path := trgtDir + v
f, e := ioutil.ReadFile(path)
if e != nil {
t.Fatalf("read failed.(%+v): %+v", e, path)
}
var tokens []token
if e := json.Unmarshal(f, &tokens); e != nil {
t.Fatalf("unmarshal (%+v)", e)
}
alltarget[path] = tokens
}
// 16個のチャネルを起動
var workers sync.WaitGroup
workerchannels := make(chan container, PARALLELCNT)
for n := 0; n < PARALLELCNT; n++ {
workers.Add(1)
go callApiWorker(t, &workers, workerchannels)
}
starttm = time.Now()
totalstarttm = starttm
// ファイル毎
for _, v := range alltarget {
for i, tk := range v {
// ユーザ位置更新
workerchannels <- container{T: t,
Uid: tk.Uid,
Token: tk.Token,
Path: fmt.Sprintf("/api/v1/users/%s", tk.Uid),
Method: http.MethodPatch,
Req: locreq{Latitude: float64(40.1234),
Longitude: float64(138.1234),
Accuracy: float64((i % 311) + 1)},
Type: NEEDJSON}
// ユーザ情報読み出し
workerchannels <- container{T: t,
Uid: tk.Uid,
Token: tk.Token,
Path: fmt.Sprintf("/api/v1/users/%s", tk.Uid),
Method: http.MethodGet,
Req: locreq{},
Type: WITHOUTJSON}
}
}
close(workerchannels)
workers.Wait()
endtm := time.Now()
costsec := (endtm.Sub(totalstarttm)).Seconds()
t.Logf("%f sec", costsec)
t.Logf("%f tps", float64(debug_counter)/float64(costsec))
}
//
func callApiWorker(t *testing.T, worker *sync.WaitGroup, c chan container) {
defer worker.Done()
//
for {
p, ok := <-c
if !ok {
return
}
//
var res *http.Response
if p.Type == NEEDJSON {
json, _ := json.MarshalIndent(wraplocreq{Location: p.Req}, "", " ")
res = callApi(p.T, p.Uid, p.Token, p.Path, p.Method, bytes.NewReader(json))
} else {
res = callApi(p.T, p.Uid, p.Token, p.Path, p.Method, nil)
}
if http.StatusOK != res.StatusCode {
t.Fatalf("http response (%+v)", res)
}
debug_counter++
if debug_counter%1000 == 0 {
//
endtm := time.Now()
costsec := (endtm.Sub(starttm)).Seconds()
starttm = endtm
fmt.Printf("%f sec\n", costsec)
fmt.Printf("%f tps\n", float64(debug_counter-prev_counter)/float64(costsec))
fmt.Printf("calledApi: %v / counter :%v\n", p.Path, debug_counter)
prev_counter = debug_counter
}
}
}
func callApi(t *testing.T, uid string, token string, path string, method string, body io.Reader) *http.Response {
urlStr := fmt.Sprintf(GAEHOST, path)
headers := map[string]string{
APPTOKENKEY: APPTOKENVAL,
USERTOKENKEY: uid,
USERIDKEY: token}
req, _ := http.NewRequest(method, urlStr, body)
for k, v := range headers {
req.Header.Set(k, v)
}
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
t.Errorf("%+v", err)
}
return res
}
実際のパフォーマンス
ストレスクライアント
対象サーバー
全体スループットは600〜700程度と、かなり遅い、フロントは11台をオートスケール最大値としたので、1台あたりで換算すると、、、、スループットは。。。60〜70という状態
本当にサーバボトルネックなのか
比較のために c++/boost実装
httpクライアント
クライアント側ボトルネックを疑ったので、(余計な処理が少ない)c++/boostで
同様のストレスツールを準備して、計測する
namespace BAIP=boost::asio::ip;
// --------
class Client{
public:
Client(boost::asio::io_service& ,const std::string& ,const std::string& , const std::string& , const std::string& , const std::string& );
public:
std::string responsed_contents(void){ return(response_contents_); }
int responsed_statuscode(void) { return(response_statuscode_); }
int responsed_complete(void) { return(response_complete_); }
private:
void handle_resolve(const boost::system::error_code&, BAIP::tcp::resolver::iterator);
void handle_connect(const boost::system::error_code&, BAIP::tcp::resolver::iterator);
void handle_write_request(const boost::system::error_code&);
void handle_read_status_line(const boost::system::error_code&);
void handle_read_headers(const boost::system::error_code&);
void handle_read_content(const boost::system::error_code&);
private:
BAIP::tcp::resolver resolver_;
BAIP::tcp::socket socket_;
boost::asio::streambuf request_;
boost::asio::streambuf response_;
std::vector<std::string> response_header_;
std::string response_contents_;
int response_contentlen_;
int response_statuscode_;
int response_complete_;
}; // class Client
この簡単、軽量httpクライアントを、boost::thread_groupでロックなし
(golangのファイルグローバル変数も同期ロックしてないので)で並列実行する
httpリクエスト発行関数
static std::string lcurl(const char* method, const char* server, const char* path, const char* jsn){
std::string resstr;
try{
boost::asio::io_service ios;
int64_t stat = 0;
Client cli(ios, method, server, path, jsn, "80");
ios.run();
//
resstr = cli.responsed_contents();
EXPECT_EQ(cli.responsed_statuscode(), 200);
}catch(std::exception& e){
LOGERR("lcurl : (%s)", e.what());
}
return(resstr);
}
並列処理呼び出し側
static boost::thread_group tg_;
// startup storress thread
for(int n = 0;n < 16;n++){
tg_.create_thread([&n]{ req_http(n); });
}
static void req_http(int idx){
// ここで、httpリクエストを発行する
}
を書いて初回httpリクエストを発行すると、302 Redirect 、、あ、yaml設定で
https Redirect をoffにしなければ、、