入力ファイルから並列負荷評価を実施

### 入力ファイルから、ストレス評価を実施


### 入力ファイル（json）
```
[
    {
        "mail": "test0000@test.jp",
        "userid": "123456",
        "usertoken": "deadbeaf"
    },...
]
```

### go source

```go
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
}
```


## 実際のパフォーマンス

### ストレスクライアント

<img width="670" alt="0006.png" src="https://qiita-image-store.s3.amazonaws.com/0/165490/8b3d9ca3-257c-3e15-bfc7-155cfb8749a4.png">

### 対象サーバー

全体スループットは600〜700程度と、かなり遅い、フロントは11台をオートスケール最大値としたので、１台あたりで換算すると、、、、スループットは。。。60〜70という状態


# 本当にサーバボトルネックなのか

## 比較のために c++/boost実装

### httpクライアント

クライアント側ボトルネックを疑ったので、（余計な処理が少ない）c++/boostで
同様のストレスツールを準備して、計測する

```cpp

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リクエスト発行関数

```cpp

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);
}

```

### 並列処理呼び出し側

```cpp

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にしなければ、、