vagrant-global-statusをmrubyで書いた

前提

vagrant にはもともとglobal-statusオプションがあり、以下のように仮想マシンの状態をリストすることができます。

$ vagrant global-status
id       name    provider   state    directory                                       
-------------------------------------------------------------------------------------
941f568  default virtualbox aborted  /Users/juchino/my_vagrant/centos64_oracle
41ed4ec  default virtualbox aborted  /Users/juchino/my_vagrant/mruby_target          
9e4777a  default virtualbox aborted  /Users/juchino/my_vagrant/mruby_host            
00dcd43  default virtualbox poweroff /Users/juchino/my_vagrant/redmine2_5_2          
fc42378  core-01 virtualbox poweroff /Users/juchino/my_vagrant/coreos/coreos-vagrant 
121afc9  default virtualbox aborted  /Users/juchino/my_vagrant/centos56_mruby

・・・

ただ、Rubyの処理系を経由するため、結果が返ってくるまで結構待ちます。 自分のMac Book Airで計測すると、

$ time vagrant global-status
・・・
vagrant global-status  1.33s user 0.26s system 83% cpu 1.908 total

で、この処理速度を改善するため、Goで書き直されたのが、以下です。

github.com

このコマンドの処理速度は、自分のMac Book Airで計測すると、

$ time vagrant-global-status
・・・
vagrant-global-status  0.00s user 0.01s system 71% cpu 0.012 total

でした。 ちなみに、作者さんの環境での測定結果等は以下に載っております。

blog.monochromegane.com

やったこと

このvagrant-global-statusを、mrubyで書いてCの中に組み込んでみました。

github.com

mrubyのコードのほうで大半のことをしています。 $HOME/.vagrant.d/data/machine-index/index をパースして、 必要情報(マシンのid, マシン名, プロバイダ, 状態, ファイルシステム上のパス)のみArrayにどんどんpushしていきます。 mrubyからCに渡すのは、このArrayとなります。

class Vagrant

    attr_accessor :machines

    def initialize()
       @path = ENV['HOME'] + "/.vagrant.d/data/machine-index/index"
       @machines = Array.new()
    end

    def get_machines_status()
        file = File.open(@path)
        text = file.read
        file.close()

        JSON::parse(text)["machines"].each do |machine|
            @machines.push(machine[0].slice(0...7) + " " + sprintf("%-10s", machine[1]["name"].slice(0...10)) + " " + " " + sprintf("%-10s", machine[1]["provider"].slice(0...10)) + " " + sprintf("%-10s", machine[1]["state"].slice(0...10)) + " " + machine[1]["local_data_path"])
        end
    end
end

Cのほうでは、mrubyから受けとったArrayを表示しています。

    mrb_value res = mrb_funcall(mrb, mrb_top_self(mrb), "main", 0);

    for(int i = 0; i < RARRAY_LEN(res); i++){
        char *a = mrb_str_to_cstr(mrb, mrb_ary_ref(mrb, res, i)); 
        printf("%s\n", a);
    }

ビルドの方法は以下の通り。

まず、vagrant-global-status.rbをC言語配列形式のバイトコードにします。

mrbc -Bvagrant vagrant-global-status.rb

これで、以下のようなCのコード(vagrant-global-status.c)ができます。

 /* dumped in little endian order. 
   ¦use `mrbc -E` option for big endian CPU. */
#include <stdint.h>
const uint8_t
#if defined __GNUC__
__attribute__((aligned(4)))
#elif defined _MSC_VER
__declspec(align(4))
#endif
vagrant[] = {
0x45,0x54,0x49,0x52,0x30,0x30,0x30,0x33,0x94,0xaf,0x00,0x00,0x04,0x7b,0x4d,0x41, 
・・・
};

そして、上記を読み込むコードの方では以下のように書きます。

#include <stdio.h>
#include <string.h>
#include "mruby.h"
#include "mruby/compile.h"
#include "mruby/string.h"
#include "mruby/array.h"
#include "mruby/dump.h"
#include "vagrant-global-status.c"

int main (int argc, char *argv[]) {

    extern const uint8_t vagrant[];

    mrb_state* mrb = mrb_open();
    mrb_load_irep(mrb, vagrant);     # ここでバイトコードを読み込んでいる

    mrb_value res = mrb_funcall(mrb, mrb_top_self(mrb), "main", 0);

    for(int i = 0; i < RARRAY_LEN(res); i++){
        char *a = mrb_str_to_cstr(mrb, mrb_ary_ref(mrb, res, i)); 
        printf("%s\n", a);
    }

    mrb_close(mrb);

    return 0; 
}

以上のようにしてできたコード(main.c)をビルドすれば、1つのバイナリでglobal-statusができます!

測定!!

作成したmruby版vagrant-global-statusの速度を見てみました。

vagrant-global-status  0.00s user 0.00s system 65% cpu 0.011 total

Go版と大差ありません!でした。

ちなみに、今回使ったmrubyは、以下のモジュールを組み込む必要があります。

conf.gem :git => 'https://github.com/iij/mruby-io' 
conf.gem :git => 'https://github.com/iij/mruby-dir'
conf.gem :git => 'https://github.com/iij/mruby-env'
conf.gem :git => 'https://github.com/mattn/mruby-json'