Monday, June 30, 2014

呱呱蜜汁鴨胸


之前跟孔雀一起買了豪野鴨, 因為是難得的合購所以一口氣就買了四片鴨胸XD
本來想說可以多試試幾種煮法,
但是參考了蘿瑞娜的三步驟搞定懶人版蜜汁燒鴨食譜之後,就懶得再找別的來試了XD
因為這個版本真的是簡單又好吃啊

[主材料]
- 豪野鴨特選鴨胸 *1塊約300g
- 青蔥 *1支
[醬汁]
- 李錦記蜜汁烤肉醬
- 李錦記蠔油
- 米酒
- 水
- 蜂蜜

[步驟]
1. 鴨胸用米酒加鹽巴醃半小時
2. 鴨胸先擦乾, 皮朝下先用大火煎3分鐘轉中火煎7分鐘
3. 翻面煎8分鐘
* 煎鴨胸很會噴油, 最好用深一點的鍋子事後才不會難清理
* 這個時間我覺得蠻剛好的, 我只有第一次做的時候比較緊張從頭監控到尾
* 之後就都放著煎然後用手機定時就去做別的事情了
4. 翻面煎的時候另一個平底鍋煮醬汁, 比例可以參考蘿瑞娜的部落格
* 我自己是不太喜歡蜂蜜煮熟的味道, 後幾次做就都沒放了
* 比例的話我是蠻隨性的, 因為跟鴨肉一起煮的時間沒有很長, 基本上味道不會太重
* 如果真的太淡吃的時候就多沾點醬, 太鹹就多配點蔥囉XD
5. 翻面煎完將鴨胸放到醬汁鍋小火煮5-8分鐘
6. 之後關火放在鍋中休息5分鐘後切片, 跟牛排煎完要休息之後才能切片避免肉汁跑掉的道理應該是一樣的吧!
7. 蔥切絲, 將剩餘醬汁呈到小碟子後就可以擺盤啦!

Sunday, June 29, 2014

Gatling筆記: 完成自己的simulation

[基本單元]
1. scenaio: 描述使用情境, 一個simulation可以指定多個scenario
2. exec: 指定要做的事, 通常會是http request, 一個scenario裡面用exec串接多個動作
3. http: 設定request格式, 也可加入response檢查
4. httpConfig: 設定整個scenario共用的protocol(要打哪個domain, 要不要自動redirect等等)
5. users: 要用幾個user來跑
6. ramp: 設定每個user一開始起來跑的間隔
scn.users(10).ramp(10) // 10 users/10s = 1 user/s
scn.users(10).ramp(20) // 10 users/20s = 0.5 user/s = 1 user every 2s
scn.users(1000).ramp(100) // 1000 users/100s = 10 users/s

[HTTP]
1. GET: 以下是基本的HTTP GET範例
val httpConf = httpConfig.baseURL("http://my.website.tld")

val scn = scenario("My Scenario")
  .exec(
    http("My Request")
    .get("/my_path") // Will actually make a request on "http://my.website.tld/my_path"
  )
  .exec(
    http("My Other Request")
    .get("http://other.website.tld") // Will make a request on "http://other.website.tld"
  ...

setUp(scn.protocolConfig(httpConf)...)
2. 帶query string:
- 直接串在uri上
- queryParam(key: String, value: String)

3. 加header:
- 加一個: header(key: String, value: String)
- 加多個: header(Map[String, String]())

4.帶request body:
- 直接給字串: body(body: String)
- 從檔案讀: fileBody(fileName: String)
** 要注意檔案要放在galting目錄的這個路徑底下user-files/request-bodies/
- 從檔案讀且檔案內有變數可以替換: fileBody(templateFileName: String, valuesToReplace: Map[String, String])
** 同樣檔案要放在galting目錄的user-files/request-bodies/路徑底下, 且副檔名須為ssp
- 給byte array: byteArrayBody (byteArray : (Session) => Array[Byte])
- 另外還有POST特有的form以及multi-part格式

[Session]
session是一個Map, key為String, value可以是任意類型,
每個user在執行scenario時都有一個專屬的session可以暫存各種之後會用到的東西
1. 儲存:
- saveAs("myKey"), 通常是與Check合在一起用
- setAttribute("myKey", "myValue")
2. 取出:
- "${myKey}"
3. 如果對語法不熟悉或是發生什麼怪狀況需要debug時, 可以在用exec特別處理session
** 因為session是不能修改的, 所以當你動到session的內容時, 它其實是產生一個新的session物件給你
- getAttribute(key: String): Any = getTypedAttribute[Any](key)
- getTypedAttribute[X](key: String)
- getAttributeAsOption[T](key: String): Option[T]
- setAttributes(attributes: Map[String, Any])
- setAttribute(attributeKey: String, attributeValue: Any)
- removeAttribute(attributeKey: String)
- isAttributeDefined(attributeKey: String)
- getCounterValue(counterName: String)
- getTimerValue(timerName: String)
.exec(session =>
  session.setAttribute("foo", "bar")
  println(session)
  session
)

[Feeder]
用來餵參數用的好東西, 通常會是讀檔但也把JDBC或Redis當作來源,
1. 讀檔怎麼讀
- csv( filename: String ), 每個值是用,分隔
- ssv( filename: String ), 每個值是用;分隔
- tsv( filename: String ), 每個值是用TAB分隔
- 自訂分隔符號csv("user_credentials.csv", '#')
2. 每次呼叫feeder時, 會從來源取得一筆資料, 有幾種模式
- queue(預設): 照順序讀進來, 讀到底之後simulation會終止, 使用這個模式的時候要注意給的資料要夠多
- random: 隨機
- circular: 照順序讀進來, 讀到底之後會從頭讀
3. 如何存取
資料被feeder讀進來之後會被放在session裡, 如果是從檔案讀的話, key就是對應到檔案第一行的名稱
username,password
john,smith21
john,doe43
4. 使用自己的feeder
Feeder其實是一個簡單的class, 當你呼叫它的提供一個next函式時會回傳一個Map.
package object myfeeders {
  import com.excilys.ebi.gatling.core.feeder._

  val myAccountFeeder = new Feeder[String] {
    import org.joda.time.DateTime
    import scala.util.Random

    private val RNG = new Random

    // random number in between [a...b]
    private def randInt(a:Int, b:Int) = RNG.nextInt(b-a) + a

    private def daysOfMonth(year:Int, month:Int) = new DateTime(year, month, 1, 0, 0, 0, 000).dayOfMonth.getMaximumValue

    // always return true as this feeder can be polled infinitively
    override def hasNext = true

    override def next: Map[String, String] = {
      val email = scala.math.abs(java.util.UUID.randomUUID.getMostSignificantBits) + "_gatling@dontsend.com"
      val year = randInt(1945, 1994)
      val month = randInt(1, 12)
      val day = randInt(1, daysOfMonth(year, month))

      Map("contactEmail" -> email, 
        "birthdayYear" -> year.toString, 
        "birthdayMonth" -> month.toString, 
        "birthdayDay" -> day.toString)
    }
  }
}

[Check]
就是可以讓你檢查response是否與你預期相同,
檢查的範圍除了http status,還可以檢查payload格式是否正確, 甚至可以抓特定的值出來確認
1. status
- check(status.is(200))
- check(status.not(404), status.not(500)))
2. body
- check(regex("""..."""))
- check(xpath(".."))
- check(jsonPath(""))
3. 另外可以用find把你想要的值抓出來, 是需求決定是否要套上transform將值做些轉換或是用saveAs把它存在session裡

[範例]
最後的最後有一些流程控制的東西懶得寫了, 就直接看範例吧!
package collection

import com.excilys.ebi.gatling.core.Predef._
import com.excilys.ebi.gatling.http.Predef._
import com.excilys.ebi.gatling.http.Headers.Names._
import akka.util.duration._
import bootstrap._
import CLTHeaders._
import myfeeders._

class MyCollectionSimulation extends Simulation {
 val httpConf = httpConfig.baseURL("http://localhost:9000/")

  val get_chain = exec(http("get collection")
          .get("api/collection/${cid}")          
          .headers(auth_headers)
          .check(status.is(200)))

  val update_chain = exec(http("update collection")
          .post("api/")
          .fileBody("Update", Map("cid" -> "${cid}", "title" -> "${title}")).asJSON
          .headers(auth_headers)
          .check(status.is(200))) 

  val my_scenario = scenario("CRUD collection")
        .during(3 minutes) {
          feed(myAccountFeeder)
          .exec(http("create collection")
            .post("api/")
            .fileBody("CreateCollection.json").asJSON
            .headers(auth_headers)
            .check(status.is(200))
            .check(jsonPath("$.collection").saveAs("collection")))
          .exec(session=> {                   
            val tmp = session.getTypedAttribute[String]("collection").split("\"")
            val cid = tmp(1)                   
            session.removeAttribute("collection").setAttribute("cid", cid)
            })
          .repeat(2){
            get_chain      
          }
          .doIf(session=>session.getTypedAttribute[String]("cid").head.toInt>80) {
            update_chain
          }
          .exec(http("delete collection")
            .post("api/")
            .fileBody("DeleteCollection", Map("cid" -> "${cid}")).asJSON
            .headers(auth_headers)
            .check(status.is(200)))
          .exec(http("get collection")
            .get("api/collection/${cid}")          
            .headers(auth_headers)
            .check(status.is(400)))
        }

  setUp(create_scenario.users(100).ramp(10).protocolConfig(httpConf))      
}

上一集: Gatling, 威力強大卻也簡單上手的Http Server壓力測試工具
ref:
- HTTP reference
- Session
- Feeders
- Checks

Friday, June 27, 2014

Scala筆記: Gatling, 威力強大卻也簡單上手的Http Server壓力測試工具

[Gatling]
Gatling的底層是Akka搭配Netty, 會比狂開thread來提升rps的方法來的有效率.
它提供了簡單的DSL就算你不懂Akka不懂Netty不懂Scala, 也可以一眼看懂範例在做什麼.
搭配文件其實蠻容易依樣畫葫蘆寫出自己的測試程式.
測試結果也有很厲害的圖表可以參考(status, rps, latency的總合及分佈等等).

[安裝]
1. 先下載Gatling的package, 我目前是用1.5.5
2. 接下來檢查你的JDK版本, Gatling跟JDK版本對應可以參考這裡, 我用Mac, JDK版本是1.7.0_45
3. 然後只要把抓下來的package解開就可以囉!


[跑跑範例]
1. 執行gatling目錄bin底下的gatling.sh會出現兩組範例可以選
Choose a simulation number:
     [0] advanced.AdvancedExampleSimulation
     [1] basic.BasicExampleSimulation
2. 開始跑之後會定期有狀態回報, 要不然就是有Waring才會顯示.
3. 如果是初學其實可以把log等級調成INFO這樣會比較有參與感XD
4. 跑完之後, 結果會以網頁的方式呈現, 可以看到以下圖表. 另外也可以看個別request的狀況, 相當方便!





ref:
- gatling github wiki page

Sunday, June 08, 2014

西雅圖逛逛 (2) Ravenna Park, Washington Park Arboretum, Kerry Park

[Beach]
離開鮭魚階梯之後, 萱小姐提議順便去鄰近的海灘晃晃
但是因為穿了長褲又沒帶拖鞋, 就只坐在椅子上遠遠的看風景沒下去玩水囉!

晴朗的天空加上寬闊的海面真是百看不厭,
但是沙灘就輸台灣很多耶XD

[Ravenna Park]
接著來到萱小姐最喜歡的公園
入口附近有個大草坪, 很多人就一派悠閒的躺著曬太陽, 遛狗或是野餐

走進去之後發現這根本就是森林啊!
充滿芬多精的感覺好健康~
走一圈大概花了快一個小時, 加上調時差一直調不好我整個人累翻了, 所以就回萱小姐家睡午覺先~


[Washington Park Arboretum]
睡飽了再出發, 但是剩下的時間已經不夠划船就直接到華盛頓大學附近的棧道走走.
但是萬萬沒想到這季節水位高, 淹過了步道沒辦法通行,
地頭蛇萱小姐就當機立斷開車到對岸植物園北邊的濕地,

對岸也是沒法走不過這裡腹地比較大可以在這兒散散步



恰巧碰上一群年輕人(我已經不是了啊TAT)歡樂出遊請我幫忙照相,
想到大學時假日幾乎都也都關在寢室看電影跟日劇,
相較之下真是差太多啦 囧

[Kerry Park]
上飛機前的最後一站來到凱莉公園看夜景
剛好有情侶在這裡結婚感覺很很溫馨,
後來又有一群小屁孩從party bus(車子上就真的是醬寫)衝下來鬼吼鬼叫
小小的公園氣氛頓時變得很混搭.


- 西雅圖逛逛 (1) Pioneer Square, Occidental Park, Hiram M. Chittenden Locks and Fish Ladder

Saturday, June 07, 2014

西雅圖逛逛 (1) Pioneer Square, Occidental Park, Hiram M. Chittenden Locks and Fish Ladder

[Pioneer Square]
兩年前來西雅圖的時候這邊的辦公室還在圖書館附近,
這次來已經搬到Pioneer Square旁邊囉!
道路兩旁綠樹成蔭沒有高高的建築物給人壓迫感
挺喜歡這裡特有的悠閒氣氛,
有名的咖啡店Zeitgeist跟Umbria走幾步就到了.







[Hiram M. Chittenden Locks Fish Ladder]
結束出差行程後在西雅圖多留一天
感謝萱小姐好心收留還帶我到處吃吃逛逛
第一站就是到Ballard Locks Fish Ladder看鮭魚逆流而上,
看人生能不能因此獲得啟發XD
Hiram M. Chittenden(Ballard) Locks Fish Ladder其實是多個景點的集合,
首先, 這邊位於湖(Lake Washington)與海(Puget Sound)的交界,
漁船遊艇帆船等大大小小的船隻非常多,



如果船長的太高, 橋就得打開船才能通過.

這邊有船的電梯, 就是類似巴拿馬運河的機制,
湖跟海的水位高度不一樣, 船要出海的時候要先把閘門關起來,
然後把水位降到與海灣同高, 看起來就跟船隻坐電梯往下一樣感覺超酷,
還有划獨木舟的人也可以跟著出海噢~

除了讓船隻通過的閘門, 旁邊的區域則是讓鮭魚通過的地方
但是因為季節不對, 我今天一隻魚都沒看到(心碎)
倒是看到好幾隻肥滋滋的海豹快樂的在水壩面海的一側悠游
還有在岸邊等待狩獵的海鳥們


- 西雅圖逛逛 (2) Ravenna Park, Washington Park Arboretum, Kerry Park