ソモサン

私rohkiによる活動や読書の記録をつらつらと書くページです

Rust の CLI ツールで引数の値が特定のものであれば別の引数を必須にしたい

短く

crap.rs の requires_if/requires_ifs を使おう。

        .arg(
            Arg::with_name("iterator-type")
                .short("t")
                .long("iterator-type")
                .possible_values(&IteratorType::variants())
                .requires_ifs(&[
                    ("AT_SEQUENCE_NUMBER", "sequence-number"),
                    ("AFTER_SEQUENCE_NUMBER", "sequence-number"),
                    ("AT_TIMESTAMP", "timestamp"),
                ])
                .default_value("LATEST")
                .value_name("TYPE")
                .help("Sets iterator type."),
        )
        .arg(
            Arg::with_name("sequence-number")
                .long("sequence-number")
                .value_name("NUM")
                .help("Set Sequence number when Iterator Type is AT_SEQUENCE_NUMBER or AFTER_SEQUENCE_NUMBER.")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("timestamp")
                .long("timestamp")
                .value_name("TIMESTAMP")
                .help("Set timestamp(UNIX Epoch milliseconds) when Iterator Type is AT_TIMESTAMP.")
                .takes_value(true),
        )

k-iter/main.rs at f22bd285818a23ec5eb766d53798a584ddaffed2 · ROki1988/k-iter

背景

AWS Kinesis Stream のイテレータは、下記の通り 5 種類あります。

  • LATEST
  • AT_SEQUENCE_NUMBER
  • AFTER_SEQUENCE_NUMBER
  • AT_TIMESTAMP
  • TRIM_HORIZON

このうち 3 種には値が必要なので、追加のオプションで指定してもらいたい感じでした。
なので、そのオプションはイテレータが 3 種のうちのどれかであれば必須、となります。
そんなのかけるのやら…

多機能引数パーザ clap

すごいですねー。ありましたよ。requires_ifs
ある引数に対する追加設定として記述できます。
タプルの1つ目が、その引数がとりうる値で、2つ目が必須となるオプションの名前となります。

            Arg::with_name("iterator-type")
                .short("t")
                .long("iterator-type")
                .possible_values(&IteratorType::variants())
                .requires_ifs(&[
                    ("AT_SEQUENCE_NUMBER", "sequence-number"),
                    ("AFTER_SEQUENCE_NUMBER", "sequence-number"),
                    ("AT_TIMESTAMP", "timestamp"),
                ])

先にあげた例を抜粋しました。
今回であれば、iterator-type に対して、AT_SEQUENCE_NUMBERAFTER_SEQUENCE_NUMBERAT_TIMESTAMP の時にそれぞれ必須となる引数の名前を指定してます。

自分で書いてもよいのですが、あるのであれば使います。
ありがたやありがたや。

というわけで

無事 k-iter でオプションを追加できましたー!やったぜ。
これで追跡がらくになる。はず。

localstack を使ったテストを書きたい。

Rust でクロスプラットフォーム対応するときに便利だった Cargo Plugin: cross

これ

github.com

使い方

cargo install cross
cross build --target i686-unknown-linux-gnu

実際に Travis で動かしている k-iter/.travis.yml at master · ROki1988/k-iter とかを見るとよいかも。

発端

rohki.hatenablog.com

前回 AWS Kinesis Stream をひたすら見るやつを作ったわけですが、バイナリも用意せねば、となりました。
んで、WindowsAppVeyor でサクッとできました。macOS 向けも Travis 上でできました。
でもなぜか、一部の Linux 向けのバイナリができない。 で、調べて cross にいきつきました。

苦悩の跡

f:id:rohki:20180509224755p:plain

めっちゃ頑張った…

Travis CI の設定をパクッt(ry

Travis CI で Linux (x86_64, i686, aarch64) 向け(とついでに macOS 向け)に Rust で書いたツールのバイナリをリリースする - はやくプログラムになりたい
上記のような記事を公開いただいていて、パクれ との記述にありがとうございます!!!と適用してみました。
Build #9 - ROki1988/k-iter - Travis CI がビルド結果になります。
文面に書くと、macOS 向けと x86_64 Linux 向けのビルドに成功し、i686 Linux 向けと AArch64 Linux 向けに失敗しました。なぜだ…

ハッハーン…いつものだな?

OpenSSL を疑いました。
というのも、Rust で OpenSSL を使用する場合、crate が対応しているバージョンがインストールされていないとビルドできなかったり、実行できなかったりするからです。
rust-openssl/build.rs at master · sfackler/rust-openssl を全部見切れていないので事情は定かでないですが、ビルド時にいろいろ見に行っている模様。

ので、

しましたが、ことごとくダメでした。
ただ最後の環境変数指定でもできなかったときのエラーメッセージをボヤっと見てピンときて、クロス環境向けにビルドされた OpenSSL のがいるのか!と仮説を立てました。

とはいうものの

さすがにクロスのバイナリそれぞれ用意して云々はハードル高いぞ…と、なんかあるやろで見つけたのが cross になります。

github.com

上記にて使われているのを発見して、参考にさせていただきました。
結果としてビルドもできて、やったーというところです。
タグ切ったところ、パクってきた拝借してきた設定どおりにきちんと Github に公開されました。

つかってみて

すっごい考えられてるというか、賢くやってはるなーというかんじです。
CLI のコマンドも cargo から cross に変えるだけですし、docker 環境がない場合とある場合も同じように動いてくれますし、CI のキャッシュも効きますし。

AWS Lambda の時も感じましたけど、コンテナホント便利ですね。
あとはテストとかにも使いたいんだよなぁ。こう…#[test] の下とかにアトリビュート書いて実現できると嬉しいかも。

CLI で AWS Kinesis Stream の中身をひたすら追ってくれる k-iter をつくってる

作ったもの

github.com

インストール方法

rustup.rs - The Rust toolchain installer

rustup を設定したうえで、下記コマンドを実行。

cargo install --git https://github.com/ROki1988/k-iter.git

もしくは Releases · ROki1988/k-iter より合致するものをダウンロードして展開。

使い方

k-iter -n event-stream -r ap-northeast-1

n の後にストリーム名を、r の後にリージョン名を入れればガンガン見てくれます。
今のところはコマンド実行以降に Put されたレコードを見ていくだけです。

動機

AWS Kinesis Stream はいったん投入してしまうと、中になにが入ってるか追うためにコードをかく必要があります。コンソールから見れないから。
Lambda の Blueprint に Kinesis のイベントを処理するぜー、てのもあるのですが、CLI に出したい、という欲がでます。
ですので、秘伝のたれのごとき Python コードを実行して CLI 上で出してました。

だがしかし、迂闊に brew upgrade を実行した結果、Python の実行環境が再構築の憂き目にあい、こりゃシングルバイナリで動くやつ作らにゃきつい、となった次第です。

Rust 製なのは趣味です。

やってみたいこと/考えてること/もらえた意見

  • 実行バイナリはよ
  • Verbose モードとかほしい。Put された時間とかパーティションキーとか
    • 綺麗に出さなければできそう。きれいに出そうとすると大変そう。そのあたりのセンス磨いてないし。
      • CSV を整形するコマンドとかあるし、CSV 出力でこと足りる?
  • Iterator Type 対応したい。TRIM_HORIZON とか。
    • オプションに依存関係が出てくるので、ちょいと厄介
      clap-rs の修行が必要そう
  • --exec とか作ってデータに処理かけたい
    • jq かけたりするイメージ。やりたくない?
  • UTF-8 文字列決め打ちで出力してるけど、バイナリ表示の需要もありそう?
    • Big とか Little の切り替えもいるのだろうか。--print-format=string|byte-be|byte-le てな感じ
  • Shard 複数を同時に
    • できっかなぁ…

Rust で Scala の continually ぽいもの

こんな感じ?

extern crate rand;

use rand::{Rng, thread_rng};
use std::usize;

pub struct Iterate<A> {
    func: A
}

impl<B, A> Iterator for Iterate<A> where A: FnMut() -> B {
    type Item = B;

    #[inline]
    fn next(&mut self) -> Option<B> { Some((self.func)()) }
    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) { (usize::MAX, None) }
}

fn continually <A, B>(f: A) -> Iterate<A> where A: FnMut() -> B {
    Iterate { func: f }
}

fn main() {
    let mut rng = thread_rng();
    let a = std::iter::repeat(1).into_iter().map(|_| rng.gen::<u32>() % 100u32).take_while(|x| *x != 0u32).collect::<Vec<u32>>();

    let i = continually(|| rng.gen::<u32>() % 100u32).take_while(|x| x != &0u32).collect::<Vec<u32>>();
    println!("{:?}", a);
    println!("{:?}", i);
}

きっかけ

Scala の continually をみて、なるほど都度評価してくれるのか、と納得して、はて Rust では、となった次第。

書いてみて

std::iter::repeat もあるにはあるのですが、Clone trait を実装している必要があって、FnMut を渡せませんでした。たぶん。
ので、軽く書いてみたら動きました。でもありそう。見つけられてないだけで。
あと、表面だけまねてるので、いろいろ考えが足りてなさそうでもあります。
でも動いたから満足。

Elasticsearch のクエリを知るのに elastic4s がよかった

Elasticsearch はややこしい

ややこしいというか難しいというか、面倒くさいというか…
で考えたところ、そも JSON を組み立てるのがそこそこ以上にしんどいんですよね。末尾の , とか。
Kibana も補完はあるのですが、そのあたりが面倒くさくて、なんか学ぶのが億劫になっておりました。
で、表題の話。elastic4s です。

sksamuel/elastic4s: Elasticsearch Scala Client - Non Blocking, Type Safe, HTTP, REST API, TCP

Scala で Elasticsearch を扱うためのライブラリになってます。
かなーり頑張っていて、検索結果を safeTo で変換できたりとか、ややこしい aggs に型の誘導があったりします。
あと 6.0 以上であれば AWS Elasticsearch Service の IAM 認証付きリクエストも行けます。AWS_DEFAULT_REGION環境変数にいれなきゃですが。

Aggregations がすごい

elastic4s/elastic4s-core/src/main/scala/com/sksamuel/elastic4s/searches/aggs at master · sksamuel/elastic4s

いやーえぐい。ほんとえぐい。というかこれだけの量をよく定義しはりました。

    val resp = http.execute {
      search("childrenaggs").matchAllQuery().aggs(
        dateHistogramAgg("agg1", "date").interval(DateHistogramInterval.Month).addSubagg(
          childrenAggregation("agg2", "answer").addSubagg(
            termsAgg("agg3", "text").size(1)
          )
        )
      )
    }.await.result

    val september = resp.aggs.dateHistogram("agg1").buckets.find(_.date == "01/09/2017").get
    val sept_answers = september.children("agg2")
    sept_answers.docCount shouldBe 3
    sept_answers.terms("agg3").buckets.head.key shouldBe "god"

テストコード から抜粋したのが上記です。
入れ子入れ子入れ子、になってるのかな? もはやわからんわけですが、型に沿って書けるのでよかったです。
んで、aggs で返ってくる結果のキー名とか方とかも決まってるので、これもとれます。

テスト支援のモジュールもある

elastic4s/elastic4s-testkit/src/main/scala/com/sksamuel/elastic4s/testkit at master · sksamuel/elastic4s

まだきちんと試せてないですが、テスト用のモジュールもあります。
Docker を起動するタイプとか、組み込みタイプとか。至れり尽くせり。

サンプルもあって試せる

elastic4s/samples at 151fea1fb680bed793bd3c89149b266bcb9c6129 · sksamuel/elastic4s

丁寧なことに Maven やら Sbt のサンプルプロジェクトも用意されてるので、結構手軽にためせます。
ここでクエリを書いて、.show で文字列にして確認してを繰り返して、理解が深まった感じです。

与太話

ElasticDsl.scala ってファイルを見ながら、確かに技術ドメインであってもドメインであって、表現してくれてる言語があれば理解が深まる、ってことなのかなー、ORM とかもそういう役割あるのかなー、などと思考が飛躍しておりました。
与太話おわり!elastic4s よいすよ!!

Scala で Array 等を制限に合わせて分割する

短く

grouped(size) を使いましょう

背景

ScalaAWS Kinesis へ PutRecords しようとしたときに、上限に引っ掛かりました。
で今回抵触した上限というのが、PutRecords 1 回あたりに含められるレコード数で、500 までとのこと。*1
なので 500 毎に分割して PutRecords したいわけだけど、分割…あるはずだよなーと探して見つかりました。

方法

(1 to 10000)
  .grouped(500) // これ
  .foreach(put)

たったこれだけです。
ただまぁ自前で作ると脳みその裏側でいろいろ考えたりするわけですよ。
そも動くようにするところから始まり、速度とか、オブジェクト生成の回数とか、いろんな Collections への対応とか。
あるなら使うがベストです。こんなの絶対にあるはずですし

おわりに

名前がよくない!(理不尽)
splitAt は違うしなぁとかで引っかかってました。
sliding まで行きついてコード書いた後に、あれよく見たら grouped あるやん、でやっとこさ到達しました。
Rust では chunk で、そうか chunk というワードがあったか、という感じ。検索のための語彙が増えました。

Prometheus + Grafana + Elasticsearch + Kibana を Docker でいじり中

prometheus.io

いじってます。

進捗

docker-compose.yml はこんな感じ

version: '3'
services:
  prometheus:
    image: prom/prometheus
    container_name: prometheus
    volumes:
      - .\prometheus.yml:/var/app/prometheus/prometheus.yml
    command:
      - '--config.file=/var/app/prometheus/prometheus.yml'
    ports:
      - 9090:9090
    depends_on:
      - elasticsearch_exporter
    links:
      - elasticsearch_exporter
  grafana:
    image: grafana/grafana
    container_name: grafana
    ports:
      - 3000:3000
    env_file:
      - /prom/grafana.env
  elasticsearch_exporter:
      image: justwatch/elasticsearch_exporter:1.0.2
      command:
        - '-es.uri=http://elasticsearch:9200'
      restart: always
      ports:
        - 9108:9108
      depends_on:
        - elasticsearch
      links:
        - elasticsearch
  elasticsearch:
    image: elasticsearch
    ports: 
      - 9200:9200
  kibana:
    image: kibana
    ports:
      - 5601:5601
    depends_on:
      - elasticsearch

prometheus.yml

global:
  scrape_interval:     15s 
  evaluation_interval: 15s 
  external_labels:
      monitor: 'codelab-monitor'

rule_files:
#   - "/var/app/prometheus/alert.rules"

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: 
          - '127.0.0.1:9090'
  - job_name: 'elasticsearch-exporter'
    static_configs:
      - targets:
          - 'elasticsearch_exporter:9108'

Prometheus + Grafana を Mac 上で docker-compose で起動 - Qiita

上記の記事をベースに、Elasticsearch + Kibana を追加して、Elasticsearch 用の Exporter も入れました。
Docker でのアドレス解決がわからなかったので、そこはこちらを読みながらなるほどー、と書いてみました。
Windows でやったので、一部ファイルがおかしいかも…

メトリクス周りを前から調べていて、Graphite や Influx、Mackerel もさわったし、はやりの Prometheus もやってみよう、という感じ。
去年の 2.0 出たときにいじりたかったけど、まぁほかに触るものがあれやこれやそれやあり遅れました。

Elasticsearch で GC に関連した値もとれるってのが個人的には気になってます。exporter よいなー。
一方で、いろいろ手広くやりすぎててまずいです。*1
ここのところ毎年いってるけど来年度は絞る。

*1:僕のせいだけではない