기본 콘텐츠로 건너뛰기

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 구현 해놓은 소스를 보니 재미있다. 

이 블로그의 인기 게시물

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가 잘 나온다.
여기서부터 시작하는게 좋아보인다.

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 …

MQTT 접속해제 - LWT(Last will and testament)

통신에서 중요하지만 구현이 까다로운 문제로 "상대방이 예상치 못한 상황으로 인하여 접속이 끊어졌을때"의 처리가 있다.

이것이 까다로운 이유는 상대방이 의도적으로 접속을 종료한 경우는 접속 종료 직전에 자신의 종료 여부를 알리고 나갈 수 있지만 프로그램 오류/네트웍 연결 강제 종료와 같은 의도치 않은 상황에선 자신의 종료를 알릴 수 있는 방법 자체가 없기 때문이다.
그래서 전통적 방식으로는 자신의 생존 여부를 계속 ping을 통해 서버가 물어보고 timeout 시간안에 pong이 안올 경우 서버에서 접속 종료를 인식하는 번거로운 방식을 취하는데

MQTT의 경우 subscribe 시점에서 자신이 접속 종료가 되었을 때 특정 topic으로 지정한 메시지를 보내도록 미리 설정할 수 있다.
이를 LWT(Last will and testament) 라고 한다. 선언을 먼저하고 브로커가 처리하게 하는 방식인 것이다.

Last Will And Testament 라는 말 자체도 흥미롭다.
법률용어인데 http://www.investopedia.com/terms/l/last-will-and-testament.asp
대략 내가 죽으면 뒷산 xx평은 작은 아들에게 물려주고 어쩌고 하는 상속 문서 같은 내용이다.

즉, 내가 죽었을(연결이 끊어졌을) 때에 변호사(MQTT Broker - ex. mosquitto/mosca/rabbitMQ등)로 하여금 나의 유언(메시지)를 상속자(해당 토픽에 가입한 subscriber)에게 전달한다라는 의미가 된다.

MQTT Client 가 있다면 한번 실습해보자.
여러가지가 있겠지만 다른 글에서처럼 https://www.npmjs.com/package/mqtt 을 사용하도록 한다.

npm install mqtt --save 로 설치해도 되고 내 경우는 자주 사용하는 편이어서 npm install -g mqtt 로 전역설치를 했다.

호스트는 무료 제공하고 있는 test.mosquitto.org 를 사용한다.
실 사용시엔 -h 옵션을 …