LoginSignup
0
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-02-16

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

入力ファイル(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
}

実際のパフォーマンス

ストレスクライアント

0006.png

対象サーバー

全体スループットは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にしなければ、、

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0