기본 콘텐츠로 건너뛰기

Grinder를 사용한 Meteor load testing

https://www.youtube.com/watch?v=tCPBGVI3PdA
Adrian Lanning의 영상을 전에 본적이 있는데 기회가 있어 해보기로 함.

https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein 설치는
curl https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein > /usr/local/bin/lein
chmod 755 /usr/local/bin/lein
대략 이런 식으로 하면 된다.
lein 을 한번 실행하고 https://github.com/alanning/meteor-load-test 를 받았다.
lein deps 를 실행해서 의존성 라이브러리들을 받아놓자.

https://github.com/alanning/meteor-load-test 에서 시키는 대로 해보자.

로컬 Meteor Application을 띄우고
bin/grinder agent start
로 에이전트 먼저 시작 start 뒤에 URL을 적어도 된다고 한다.
tail -f log/agent_1.log
로그 파일도 모니터 하고
bin/grinder console start
grinder 콘솔을 실행하자.
clojure로 만든 되게 불편해 보이는 툴이 뜬다.
그냥 의전용 스샷이 아니니 잘 봐둬야...

script탭을 선택하고 grinder 경로 아래에 working.properties 를 열어보자. OS X 유저들은 cmd 키가 안먹는다고 당황하지 말자. ctrl+c, ctrl+v, ctrl+a 등등 ctrl키를 이용.
grinder.script = meteor.clj
grinder.targetUrl = http://localhost:3000/
가 보인다.
실제 grinder가 구동하는 스크립트는 clj파일이다.
;;
;; Load-testing Meteor apps with The Grinder
;;
(ns meteor-load-test.http
  (:import [net.grinder.script Grinder Test]
           [net.grinder.plugin.http HTTPRequest]
           )
  (:use [meteor-load-test core util subscriptions method_calls])
  )
(let [grinder Grinder/grinder
      test1 (Test. 1 "Retrieve initial payload")
      test2 (Test. 2 "DDP subscriptions")
      test3 (Test. 3 "DDP calls")
      properties (.getProperties grinder)
      ]
  (defn instrumented-get [url]
    (log "Requesting url: " url)
    (.. (HTTPRequest.) (GET url)))
  ;; record calls to the instrumented function
  (.. test1 (record instrumented-get))
  (.. test2 (record subscribe))
  (.. test3 (record call-method))
  (defn get-client-id []
    (str (.getAgentNumber grinder) "-"
         (.getProcessName grinder) "-"
         (.getThreadNumber grinder)))
  (defn get-run-id []
    (.getRunNumber grinder))
  (defn stop-fn []
    (#(.stopThisWorkerThread grinder)))
  (defn sleep-fn [ms]
    (.sleep grinder ms))
  ;; return function that is executed once per thread by each worker process
  (worker-thread-factory
    stop-fn
    sleep-fn
    properties
    get-client-id
    get-run-id
    instrumented-get
    ))
내용을 보니 이렇다. 대략보니 DDP Subscription과 DDP call을 테스트할 수 있어 보인다.
툴바 왼쪽에서 첫번째인 Start the worker processes를 눌러서 테스트를 돌려보자.
여기!
tail로 열어보자.
[thread 0] INFO worker.jhAir.local-1 - starting, will do 1000 runs
[main] INFO worker.jhAir.local-1 - start time is 1487643542814 ms since Epoch
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-0", "type" "client"}]
[thread 0] INFO data - 0, 0, 3, 1487643542835, 9, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-1", "type" "client"}]
[thread 0] INFO data - 0, 1, 3, 1487643542855, 1, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-2", "type" "client"}]
[thread 0] INFO data - 0, 2, 3, 1487643542868, 1, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-3", "type" "client"}]
[thread 0] INFO data - 0, 3, 3, 1487643542881, 2, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-4", "type" "client"}]
[thread 0] INFO data - 0, 4, 3, 1487643542895, 1, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-5", "type" "client"}]
[thread 0] INFO data - 0, 5, 3, 1487643542907, 2, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-6", "type" "client"}]
[thread 0] INFO data - 0, 6, 3, 1487643542920, 6, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-7", "type" "client"}]
[thread 0] INFO data - 0, 7, 3, 1487643542941, 1, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-8", "type" "client"}]
[thread 0] INFO data - 0, 8, 3, 1487643542953, 2, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-9", "type" "client"}]
[thread 0] INFO data - 0, 9, 3, 1487643542966, 1, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-10", "type" "client"}]
[thread 0] INFO data - 0, 10, 3, 1487643542978, 3, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-11", "type" "client"}]
[thread 0] INFO data - 0, 11, 3, 1487643542990, 1, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-12", "type" "client"}]
[thread 0] INFO data - 0, 12, 3, 1487643542992, 0, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-13", "type" "client"}]
[thread 0] INFO data - 0, 13, 3, 1487643542993, 1, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-14", "type" "client"}]
[thread 0] INFO data - 0, 14, 3, 1487643542996, 0, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-15", "type" "client"}]
[thread 0] INFO data - 0, 15, 3, 1487643543000, 1, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-16", "type" "client"}]
[thread 0] INFO data - 0, 16, 3, 1487643543012, 1, 0, 0, 0, 0, 0, 0, 0, 0
calling: addEntry with args: [{"ownerId" "0-jhAir.local-1-0", "name" "load-17", "type" "client"}]
뭔가 마구 갈려나가고 있다.
꽤 괜찮아 보인다.
grinder.subscriptions = ["entry-count", {"latest-entries":[60]}]
grinder.calls = [{"addEntry":[{"ownerId":"CLIENTID","name":"load-RUNID","type":"client"}]}]
실제 subscription 하고 call을 어떻게 하나 했더니 JSON으로 말아놓는다. array 타입이니 여러개 지정할 수 있는데
["entry-count", {"latest-entries":[60]}]
먼저, subscription을 보자
서버쪽을 확인해보면 entry-count와 latest-entries 라는 publish를 발견할 수 있다.
[
  "entry-count",
  {
    "latest-entries": [60]
  }
]
이렇게 보면 좀 편할 것 같다.
인자가 없는 경우는 "entry-count" 식으로 publish 이름만 적고 인자가 있는 경우 publish명을 key로 적고 인자는 배열 형태로 넘겨준다.
grinder.calls = [{"addEntry":[{"ownerId":"CLIENTID","name":"load-RUNID","type":"client"}]}]
call도 크게 다르지 않은데
[
  {
    "addEntry": [
      {
        "ownerId": "CLIENTID",
        "name": "load-RUNID",
        "type": "client"
      }
    ]
  }
]
단순 call .. 인 경우는 없겠지만 있다면 마찬가지로 call 이름만 갈것 같고 인자가 있는 경우는 call 이름이 키, 인자가 배열형으로 넘어간다고 보면 되겠다.
여기서 주목할 부분이 있는데 CLIENTID와 RUNID처럼 대문자로 쓴 부분은 위 log 처럼 CLIENTID와 RUNID는 grinder가 지정해준다.
# These keywords will be replaced automatically:
#   CLIENTID - id unique to executing thread
#   RUNID - number corresponding to current test run
CLIENTID는 thread를 실행하는 ID. RUNID는 현제 테스트를 수행하고 있는 숫자와 같다.

그 다음으로 processes, threads, runs 을 살펴보자.
grinder.processes = 1
grinder.threads = 1
grinder.runs = 1000
총 실행 횟수는 processes*threads*runs 라고 보면 된다.
# Collection updates from server will always be logged
grinder.debug = true
# for some reason, the log directory required restating
grinder.logDirectory = log
한번 돌리고 잘 재연이 안되는데 log에 쌓이는 결과물들을 잘 관찰해보면 된다.
clojure는 잘 모르는데 clojure로 DDP 구현 해놓은 소스를 보니 재미있다. 

이 블로그의 인기 게시물

ESP32 DevBoard 개봉기

오늘 드디어 손에 넣었다. ESP32 DevBoard!
Adafruit 에서 15개 한정 재입고 트윗을 보고 광속 결제.
그리고 1주일의 기다림. 사랑해요 USPS <3
알리를 이용하다보니 1주일 정도는 광속 배송임.
물론 배송비도 무자비함 -_ㅜ
15개 한정판 adafruit 발 dev board
그놈이 틀림없으렸다.
오오 강려크한 포스
ESP32_Core_board_V2라고 적혀있군요.
ESP32 맞구요. 네네. ESP32-D0WDQ6 라고 써있는데 D → Dual-core 0 → No internal flash W → Wi-Fi D → Dual-mode Bluetooth Q → Quad Flat No-leads (QFN) package 6 → 6 mm × 6 mm package body size 라고 함.
길이는 이정도
모듈크기는 이정도
코어는 6mm밖에 안해! 여기에 전기만 넣으면 BLE+WIFI!
밑에 크고 발 8개 달린 놈은 FM25Q32라고 32Mbit 플래시메모리
ESP8266 DevBoard 동생이랑 비교 크고 아름다운 레귤레이터랑 CP2102 USB Driver가 붙어있음.
ESP8266 DevBoard엔 CH340G 인데 확 작아졌네.
머리를 맞대어 보았음.
모듈크기는 아주 약간 ESP32가 더 큰데 워낙에 핀이 많고 촘촘함. ESP8266인 ESP12는 핀 간격이 2.00mm인데 비해
ESP32는 1.27mm 밖에 안함.
딱봐도 비교가 될 정도.
https://www.sparkfun.com/news/2017 크고 아름다운 Pinouts

ESP8266 보드랑 별로 안달라보인다.
http://www.silabs.com/products/mcu/pages/usbtouartbridgevcpdrivers.aspx#mac
에서 CP2102 드라이버를 설치하고
screen 으로 연결해보자.
내 경우엔 tty.SLAB_USBtoUART 로 잡혔다.
어디서 기본 속도가 115200bps 라고 들은 적이 있어서
screen /dev/tty.SLAB_USBtoUART …

Rinkeby Test Network에 접근하는 간단한 방법.

dApp 개발 시 실제 계정으로 트랜젝션을 보내면 너무나 비싸므로
Rinkeby나 Ropsten 같은 테스트 네트워크에 연결하여 마이닝 없이 faucet을 통해 ether를 받고
그걸로 트랜젝션 테스트를 하면 편리하다.

보통 https://github.com/ethereum/wiki/wiki/Dapp-using-Meteor#create-your-%C3%90app 문서를 보고 시작하는데
geth --rpc --rpccorsdomain "http://localhost:3000" 이렇게 하면 마이닝부터 해야하니 귀찮다.
https://infura.io/#how-to 를 보고 계정을 신청하자. 이런 것도 호스팅이 되다니 좋은 세상이네.
간단한 개인 정보 몇가지를 입력하고 나면 Access Token이 나온다.

가입 후  https://infura.io/register.html 화면

Access Token이 있는 네트워크 주소로 geth를 연결한다.
geth --rpc --rpccorsdomain "https://rinkeby.infura.io/<YOUR_ACCESS_TOKEN>" 이러면 오케이.

meteor project를 만들고
meteor add ethereum:web3 추가한 다음 console에서
web3.eth.getBalance(web3.eth.coinbase, (error,result)=>console.log(
  error, result.toFormat()
)); 자신의 coinbase의 잔액을 구해보자.
6eth가 최소단위인 wei로 보면 6,000,000,000,000,000,000 정도.
https://faucet.rinkeby.io/ 여기에서 받아온 (무료로/마이닝없이) ether가 잘 나온다.
여기서부터 시작하는게 좋아보인다.

meteor로 nw.js 개발하기.

실제로 nw.js 어플리케이션을 개발하다보면 UI구현하기 막막하고 수동으로 리프레쉬 하는 것도 귀찮아서 Meteor 연동을 하려고 했더니 생각보다 간단했다.

디렉토리 구조는 먼저 이렇게 잡았다.
`- app
  `-client
  `-public
`- dist
  `- 배포용 html,css,js
  `- package.json
`- package.json 아이디어는 이렇다. nw.js의 시작페이지를 http://localhost:3000으로 두고 배포시엔 meteor client 배포툴인 meteor-build-client를 사용하여 html,css,js 로 분리하는 계획이다.

가장 중요한 nw.js 용 package.json 파일은 아래와 같이 구성한다.
{
  "main": "http://localhost:3000",
  "node-remote": "http://localhost:3000",
  "name": "<앱이름>"
} 이게 전부. 어떻게 보면 Web과 nw.js를 동시에 개발할 수도 있는 환경이기도 한 것이다.
meteor create app 을 해서 meteor용 앱을 만들고 meteor 를 시작한다.
그리고, 위의 package.json이 있는 경로로 돌아가서 nw . 으로 nwjs를 실행한다.

한번 번쩍하더니 잘 된다.
cordova 등에서 index.html 대신 http://localhost:3000을 하는 것도 비슷한 느낌이다.

즐겁게 개발을 일단 마구 하고 실제로 배포하기 위해서는 개발환경이 아니라 html,css,js로 구성된 배포본을 만들어야한다.
npm install -g 해도 되지만 어짜피 Meteor에서만 쓸거
meteor npm install -g meteor-build-client 해버릴거다.

개발은 문제 없어 보이고 배포판을 한번 만들어보자. meteor app 이 있는 경로(meteor run으로 …