Showing posts with label scala. Show all posts
Showing posts with label scala. Show all posts

Wednesday, June 24, 2015

Scala筆記: 回顧Option以及認識新朋友Either

最近又開始比較仔細的研究playframework,
在追Action的時候不小心認識了Either,(這樣寫起來怎麼有點花心感XD)
就趁機整理一下啦!

Either顧名思義就是不是這個就是那個(繞口令來著)
這樣是不是會讓你想起不是有(Some)就是沒有(None)的Option呢?
以下是相關的class以及他們的繼承關係.
sealed abstract class Option[+A] extends Product with Serializable
final case class Some[+A](x: A) extends Option[A]
case object None extends Option[Nothing]
abstract final class Nothing extends Any

之前在處理error handling的時候有用過Option,
成功回傳Some,失敗就回傳None,
但其實會有些問題, 因為我們有時候會需要針對不同的失敗原因個別處理,
像是我們要從資料庫讀取某筆資料, 可能會因為網路問題, 或是資料庫過於忙碌等等因素造成失敗
或者是我們成功得到資料庫的回應但是該筆資料並不存在
把這些林林總總的狀況概括用None來表示其實會太簡略

這個情況下我們就可以使用Either,
以下是相關的class以及他們的繼承關係.
sealed abstract class Either[+A, +B] extends AnyRef
final case class Left[+A, +B](a: A) extends Either[A, B] with Product with Serializable
final case class Right[+A, +B](b: B) extends Either[A, B] with Product with Serializable
他的值可以是類型A或類型B,
通常回傳Left是用來表示失敗,用Right表示成功

因為Action的部分有點複雜, 這邊就從playframework的OAuth library貼個簡單的範例.
  def retrieveRequestToken(callbackURL: String): Either[OAuthException, RequestToken] = {
    val consumer = new DefaultOAuthConsumer(info.key.key, info.key.secret)
    try {
      provider.retrieveRequestToken(consumer, callbackURL)
      Right(RequestToken(consumer.getToken(), consumer.getTokenSecret()))
    } catch {
      case e: OAuthException => Left(e)
    }
  }
再加上Left跟Right都是case class,
所以我們抓到結果之後可以簡單地用match處理回傳值
val reqTok = retrieveRequestToken("localhost:9000/callback")
reqTok match {
  case Right(tok) => // success, continue 
  case Left(ex) => // fail to get request token, dothing
}
大概是這樣!

另外再補充一些細節,
1. sealed
被加上sealed關鍵字的類別, 只能被定義在同一個原始碼檔案的類別繼承.
2. Option[+A]
是covariance的概念, 指的是D如果是A的子類別, 則Option[D]也是Option[A]的子類別


ref:
- Either
- Left
- Option
- 探索Playframework: 如何正確使用thread pool

Wednesday, February 25, 2015

探索Playframework: 如何正確使用thread pool

在scala的世界, 我們常會用Future來處理某些非同步的工作,
用Future, 必需要透過implict的方式提供execution context, 來執行這些工作.

白話的說就是要指定你的code要在哪個thread pool跑.

使用Playframework, 在比較單純的情況下直接用預設的thread pool就可以了.
然後再透過application.conf設定適當的參數.

import play.api.libs.concurrent.Execution.Implicits._

def someAsyncAction = Action.async {
  import play.api.Play.current
  WS.url("http://www.playframework.com").get().map { response =>
    // This code block is executed in the imported default execution context
    // which happens to be the same thread pool in which the outer block of
    // code in this action will be executed.
    Results.Ok("The response code was " + response.status)
  }
}

play {
  akka {
    akka.loggers = ["akka.event.Logging$DefaultLogger", "akka.event.slf4j.Slf4jLogger"]
    loglevel = WARNING
    actor {
      default-dispatcher = {
        fork-join-executor {
          parallelism-factor = 1.0
          parallelism-max = 24
        }
      }
    }
  }
}

import play.api.libs.concurrent.Execution.Implicits._
先來找找我們用的execution context在哪裡,
defaultContext是ActorSystem的dispatcher
而create ActorSystem用的設定是"play"這個關鍵字下面的所有屬性.
接下來就是看ActorSystem如何設定dispatcher囉~
defaultGlobalDispatcher是用DefaultDispatcherId(即akka.actor.default-dispatcher)抓到設定值

大概是這樣囉!


針對不同類型的工作, 有時候我們會不希望所有事情都塞在預設的thread pool做,
要使用其他的thread pool, 只需要先在application.conf加上一組新的設定.
再用lookup讓Akka能找到它就好.
可以直接在產生Future的時候指定execution context或是直接import讓implicit發生作用
my-context {
  fork-join-executor {
    parallelism-factor = 20.0
    parallelism-max = 200
  }
}

object Contexts {
  implicit val myExecutionContext: ExecutionContext = Akka.system.dispatchers.lookup("my-context")
}

Future {
  // Some blocking or expensive code here
}(Contexts.myExecutionContext)

or

import Contexts.myExecutionContext
Future {
  // Some blocking or expensive code here
}

當然實際上要如何設定thread pool還是要參考測試結果啦!

ref:
- playframework source code (2.3.x)
- akka source code (2.3)
- understanding play thread pools (2.3.5)

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, May 18, 2014

Scala筆記: g0v斧頭幫大挑戰與Regular Expression

前篇才要跟Regular Expression變成好朋友,
剛好看到g0v的斧頭幫大挑戰,
就決定繼續跟Regular Expression培養感情囉XD
(同時也順便跟PlayFramework還有Future拉近距離, 一魚三吃來著!)
第一關的主要任務就是要抓網頁然後爬table最後將資料轉成Json

val tableRegEx = """([^/]*)([^/]*)([^/]*)([^/]*)([^/]*)([^/]*)""".r 

for (tableRegEx(name, chinese, math, science, society, health) <- tableRegEx findAllIn respBody) 
yield {             
  s"""{"name": "${name}", "grades": {"國語": ${chinese}, "數學": ${math}, "自然": ${science}, "社會": ${society}, "健康教育": ${health}}}"""
}   
respBody是拉回來的網頁內容,
首先就根據table的欄位寫出對應的regular expression,
然後用findAllIn找到所有的符合的資料並抓出每個欄位, 回傳的是MatchIterator,
接著用for-yield將每筆資料轉成Json ojbect的樣子,
最後在稍加處理就可以變成題目要求的格式囉!

Thursday, April 17, 2014

Scala筆記: Regular Expression是你的好朋友

如果想要檢查一個字串a是否包含另一字串b, 我們會用a.contains(b)
如果想要檢查一個字串a是否由另一字串b開始, 我們會用a.startsWith(b)
如果想要以字串b為基準對a取子字串, 我們會用a.substring(a.lastIndexOf(b)+b.size)
...
以上種種字串比對, 其實也可以透過Regular Expression來達成(JDK 1.4開始支援)
Regex這個class就是讓你輕輕鬆鬆!?使用Regular Expression

要產生Regex的pattern有幾種方式:
第一種就如同以下的範例, 透過字串的r函式就可以產生Regex的物件, (其實String沒有r函式, 而是透過implicit轉換成WrappedString才找到的, 不知道implicit是什麼的話可以參考這篇)
用三層引號則是要省掉跳脫內層反斜線的麻煩
接下來在比對字串的時候可以直接把比對pattern之後每個對應的群組抽出來,
就像以下範例將日期分成年月日三個群組,
用起來很容易但壞處就是比對失敗就會馬上有錯誤訊息跳出來.
scala> val dateP1 = """(\d\d\d\d)-(\d\d)-(\d\d)""".r
dateP1: scala.util.matching.Regex = (\d\d\d\d)-(\d\d)-(\d\d)

scala> val dateP1(year, month, day) = "2011-07-15"
year: String = 2011
month: String = 07
day: String = 15

scala> val dateP1(year, month, day) = "2011-7-15"
scala.MatchError: 2011-7-15 (of class java.lang.String)
 at .(:11)
 at .()
 at .(:11)
 at .()
 at $print()
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
 at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
 at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
 at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
 at java.lang.Thread.run(Thread.java:680)

或是你可以考慮用findFirstIn, findFirstMatchIn等等Regex提供的函式來比對,
就算比對失敗也是拿到None, 讓你可以比較無痛的做接下來的處理.
scala> val copyright: String = dateP1 findFirstIn "Date of this document: 2011-07-15" match {
     |   case Some(dateP1(year, month, day)) => "Copyright "+year
     |   case None                           => "No copyright"
     | }
copyright: String = Copyright 2011

scala> val copyright: String = dateP1 findFirstIn "Date of this document: 2011-7-15" match {
     |   case Some(dateP1(year, month, day)) => "Copyright "+year
     |   case None                           => "No copyright"
     | }
copyright: String = No copyright
另一種產生Regex的方法就是老老實實的用建構式囉!

ref:
- scala api document: Regex
- regular expression metacharacter syntax
- Java Regular Expression筆記

Wednesday, March 05, 2014

Scala筆記: sbt安裝與版本管理

[1] 什麼是sbt?
sbt(simple build tool)是Scala常用的建置工具(也支援Java)

[2] 怎麼安裝?
Mac可以用Macports或Homebrew裝, 不過我喜歡手動裝啦XDD
主要是因為project多起來之後, 有的得用到最新的sbt, 有的卻只能用特定版本,
所以我會同時裝好幾個版本
手動裝其實也很簡單, 只要到這裡選擇特定版本抓下來解壓縮就好啦!(目錄可以加上版號比較清楚)
可以把你最常用sbt版本的路徑加到PATH

[3] 如何指定版本?
只要用-sbt-jar就能夠指定要用哪一版的sbt launcher
範例如下:
sbt -sbt-jar ~/Documents/dev_tools/sbt-0.13/bin/sbt-launch.jar {command}

ref:
- sbt: Getting Started, SetUp

Saturday, February 08, 2014

Scala筆記: implicit的奧秘

[1] implicit是什麼? 如何運作?
implicit具有型別轉換的功能, 但他的特殊之處在於, 他是由compiler觸發,
在compile階段若是發現型別相關的錯誤, complier會試圖利用宣告為implict的東西轉型藉此完成compile.

此種自動轉型有以下幾個規則:
1. Marking Rule: 只能使用被宣告為implicit的東西
- 可以是任何variable, function, 或object
2. Scope Rule: 只能利用在同一個scope中, 或是被宣告在companion object可見的scope中, 而且必須能用一層identifier呼叫(用以下例子說明比較清楚)
scala> object test2{
     | implicit def doubleToInt(x: Double) = x.toInt
     | }
defined module test2

scala> object test{
     | import test2._
     | }
defined module test

scala> import test._
import test._

scala> val i: Int = 3.5
<console>:12: error: type mismatch;
 found   : Double(3.5)
 required: Int
       val i: Int = 3.5
                    ^

scala> import test2._
import test2._

scala> val i: Int = 3.5
i: Int = 3
3. Non-Ambiguity Rule: 若是在scope中有超過一種implicit轉換的方式, complie會失敗.
4. One-at-a-time Rule: 一次只能套用一個implicit轉換, chaining(ex: A->B->C)是不行的!
5. Explicits-First Rule: 明確的型別指定具有絕對的優先權, 只有錯誤發生時, compiler才會透過implicit的方式修補.

[2] 甚麼情況會觸發implicit檢查?
1. conversions to an expected type: 型別不對時
scala> val i: Int = 3.5
<console>:7: error: type mismatch;
 found   : Double(3.5)
 required: Int
       val i: Int = 3.5
                    ^

scala> implicit def doubleToInt(x: Double) = x.toInt
doubleToInt: (x: Double)Int

scala> val i: Int = 3.5
i: Int = 3
2. conversions of the receiver of a selection: 找不到對應的函式
- 當沒有某個method時, 會利用implicit去轉型, 檢查是否有對應的method

3. implicit parameters: 找不到對應的參數
- 被宣告為implicit的參數, 在呼叫時沒有被傳入時, 會去尋找被宣告為implicit的東西(有點類似default值的感覺)

[3] 什麼時候適合使用implicit
當你的程式想要與一些現成的library整合時, 就例如你可能有一個購物網站, 系統定義了使用者各種屬性, 也有許多針對使用者資料的操作已經完成. 但現在你的網站希望能提供Facebook或Google帳號登入, 但他們的使用者屬性各不相同, 這時你可以加入implicit的方式自動把透過Facebook或Google api拉回來的型別轉成你的使用者型別, 也同時可以直接享用你已經寫好的各種method.

ref:
- Chapter 21 of Programming in Scala, First Edition Implicit Conversions and Parameters
- A Tour of Scala: Implicit Parameters
- Implicit Classes
- Scala Implicit Conversion

Monday, January 27, 2014

Scala筆記: lazy的奧祕

有感於自己Scala的根基不是很紮實, 於是開始看一些Scala G+群組推薦的文章,
希望能把code寫的更好一些.
今天看的是Design Patterns in Scala其中提到了lazy的運用, 重新溫習了一下就順便做筆記囉!

一般的val是在定義的時候, 就會馬上執行而得到值.
加了lazy, 顧名思義就是會變懶惰XD
要等到它首次被存取的時候才會被執行而得到值.

用console可以簡單看出差異
scala> lazy val y = {println("i'm y"); "y"}
y: java.lang.String = <lazy>
scala> y
i'm y
res22: java.lang.String = y
scala> y
res23: java.lang.String = y
scala> val x = { println("i'm x"); "x"}
i'm x
x: java.lang.String = x
scala> x
res24: java.lang.String = x

欸, 那lazy val跟def是不是感覺有點像勒!!??
lazy val是首次被存取的時候才會被執行, 但也只會被執行一次.
下面Date的例子就可以明顯看出差異啦!
scala> lazy val foo = new java.util.Date
foo: java.util.Date = 
scala> foo
res13: java.util.Date = Mon Jan 27 17:48:35 CST 2014
scala> foo
res14: java.util.Date = Mon Jan 27 17:48:35 CST 2014
scala> def foo = new java.util.Date
foo: java.util.Date
scala> foo
res16: java.util.Date = Mon Jan 27 17:48:54 CST 2014
scala> foo
res17: java.util.Date = Mon Jan 27 17:48:57 CST 2014
ref:
- What does a lazy val do?
advanced:
- What's the (hidden) cost of Scala's lazy val?
- if the initialization of a lazy val throws an exception, it will attempt to reinitialize the val at next access.