読者です 読者をやめる 読者になる 読者になる

GrowthForecastでAPI使って複合グラフ作ったり、グラフの色を変えたりしてみた

最近、大ブームなFluentd+GrowthForecastを試し始めました!

で、サクッと『GrowthForecastで複合グラフ作ったりグラフいじったりするの┌┤´д`├┐ダル〜』となりました。


なんとかサボれないかなーとソースを眺めていたら、JSON APIっぽいものがあるのに気づきました。
GrowthForecast:Web.pm

@tagomorisさんが最近コミットしたようです。
add json api to get/list/edit graph and complex, and to create complex


それっぽいURL叩くとJSONが返ってくる!

[mikeda@fluentd01 api]$ curl -s http://localhost:5125/json/list/graph
[{"service_name"=>"test","graph_name"=>"percentile_95","section_name"=>"access","id"=>88},{"service_name"=>"test","graph_name"=>"avg","section_name"=>"access","id"=>87},{"service_name"=>"test","graph_name"=>"5xx_count","section_name"=>"access","id"=>86},...

というわけでザクっとひと通り使ってみました。


/json/list/graph:グラフのリストを取得

[mikeda@fluentd01 api]$ curl -s http://localhost:5125/json/list/graph | ruby -r json -r pp -e 'pp JSON.parse(ARGF.read)'
...
 {"service_name"=>"test",
  "graph_name"=>"3xx_count",
  "section_name"=>"access",
  "id"=>80},
 {"service_name"=>"test",
  "graph_name"=>"2xx_count",
  "section_name"=>"access",
  "id"=>78},
...

パス情報とIDだけが返ってくるようです。

/json/graph/:グラフの詳細情報を取得

[mikeda@fluentd01 api]$ curl -s http://localhost:5125/json/graph/78 | ruby -r json -r pp -e 'pp JSON.parse(ARGF.read)'
{"llimit"=>-1000000000,
 "number"=>0,
 "stype"=>"AREA",
 "mode"=>"gauge",
 "complex"=>false,
 "adjustval"=>"1",
 "created_at"=>"2012/12/26 12:05:35",
 "color"=>"#99cc33",
 "service_name"=>"test",
 "gmode"=>"gauge",
 "ulimit"=>1000000000,
 "section_name"=>"access",
 "id"=>78,
 "graph_name"=>"2xx_count",
 "description"=>"",
 "sort"=>0,
 "unit"=>"",
 "sulimit"=>100000,
 "updated_at"=>"2012/12/26 15:16:07",
 "adjust"=>"*",
 "sllimit"=>-100000,
 "type"=>"AREA"}

かなりいろんな情報があります。また今度調べてみよう


このAPIの出力は、/api//

/とほぼ同じなので、状況によって使い分けるのがよさげですね。

/json/list/complex:複合グラフのリストを取得

[mikeda@fluentd01 api]$ curl -s http://localhost:5125/json/list/complex | ruby -r json -r pp -e 'pp JSON.parse(ARGF.read)'
...
 {"service_name"=>"test",
  "graph_name"=>"response_time",
  "section_name"=>"access",
  "id"=>7},
 {"service_name"=>"test",
  "graph_name"=>"access_status",
  "section_name"=>"access",
  "id"=>44},
...

/json/list/graphと同じですね。

/json/complex/:複合グラフの詳細情報を取得

[mikeda@fluentd01 api]$ curl -s http://localhost:5125/json/complex/44 | ruby -r json -r pp -e 'pp JSON.parse(ARGF.read)'
{"number"=>0,
 "complex"=>true,
 "created_at"=>"2012/12/30 03:30:55",
 "service_name"=>"test",
 "section_name"=>"access",
 "id"=>44,
 "graph_name"=>"access_status",
 "data"=>
  [{"gmode"=>"gauge", "stack"=>false, "type"=>"AREA", "graph_id"=>78},
   {"gmode"=>"gauge", "stack"=>true, "type"=>"AREA", "graph_id"=>80},
   {"gmode"=>"gauge", "stack"=>true, "type"=>"AREA", "graph_id"=>83},
   {"gmode"=>"gauge", "stack"=>true, "type"=>"AREA", "graph_id"=>86}],
 "sumup"=>false,
 "description"=>"アクセス数(レスポンスコード)",
 "sort"=>10,
 "updated_at"=>"2012/12/30 03:30:55"}

こっちはわかりやすいですね。

/json/list/all:全グラフ、複合グラフの詳細情報を取得

[mikeda@fluentd01 api]$ curl -s http://localhost:5125/json/list/all | ruby -r json -r pp -e 'pp JSON.parse(ARGF.read)'
...
 {"number"=>0,
  "llimit"=>-1000000000,
  "mode"=>"gauge",
  "stype"=>"AREA",
  "complex"=>false,
  "adjustval"=>"1",
  "service_name"=>"test",
  "created_at"=>"2012/12/26 12:05:35",
  "gmode"=>"gauge",
  "color"=>"#99cc33",
  "section_name"=>"access",
  "ulimit"=>1000000000,
  "id"=>78,
  "graph_name"=>"2xx_count",
  "description"=>"",
  "sort"=>0,
  "unit"=>"",
  "sulimit"=>100000,
  "updated_at"=>"2012/12/26 15:16:07",
  "adjust"=>"*",
  "type"=>"AREA",
  "sllimit"=>-100000},
...
 {"number"=>0,
  "complex"=>true,
  "created_at"=>"2012/12/19 09:43:37",
  "service_name"=>"test",
  "section_name"=>"dev04",
  "id"=>1,
  "graph_name"=>"access",
  "data"=>
   [{"gmode"=>"gauge", "stack"=>false, "type"=>"AREA", "graph_id"=>"2"},
    {"gmode"=>"gauge", "stack"=>true, "type"=>"AREA", "graph_id"=>"3"},
    {"gmode"=>"gauge", "stack"=>true, "type"=>"AREA", "graph_id"=>"4"},
    {"gmode"=>"gauge", "stack"=>true, "type"=>"AREA", "graph_id"=>"5"}],
  "sumup"=>false,
  "description"=>"アクセス数",
  "sort"=>19,
  "updated_at"=>"2012/12/19 09:43:37"}]

豪快ダンプですね。通常のグラフか複合グラフ化はcomplexフラグで判別するようです。

/json/create/complex:複合グラフを作成

助かった!とばかりに全サービスに特定の複合グラフを作るスクリプトを作りました。
https://gist.github.com/4406576

ちょっと個人的な用途向けすぎなので、もうちょっとベーシックなサンプルを書いてみました。

#!/usr/lib64/fluent/ruby/bin/ruby
# -*- encoding: utf-8 -*-

require 'net/http'
require 'json'
require 'pp'

GF_HOST = 'localhost'
GF_PORT = 5125

# 複合グラフに統合するグラフ
from_graphs= [
  {:path => 'test/access/2xx_count', :gmode => 'gauge', :stack => true, :type => 'AREA'},
  {:path => 'test/access/3xx_count', :gmode => 'gauge', :stack => true, :type => 'AREA'},
  {:path => 'test/access/4xx_count', :gmode => 'gauge', :stack => true, :type => 'AREA'},
  {:path => 'test/access/5xx_count', :gmode => 'gauge', :stack => true, :type => 'AREA'},
]

# 複合グラフ
to_complex = {
  :path         => 'test/access/access_status',
  :description  => 'アクセス数(レスポンスコード)',
  :sort         => 10,
}

def api_get(path)
  json = ''
  Net::HTTP.start(GF_HOST, GF_PORT) do |http|
    res  = http.get(path)
    json = res.body
  end
  JSON.parse(json)
end

def api_post(path, data)
  json = JSON.generate(data)
  Net::HTTP.start(GF_HOST, GF_PORT) do |http|
    res  = http.post(path, json)
    pp res.body
  end
end


# 全グラフデータの取得
#/api/<sercice>/<section>/<graph>で個別に取るほうが簡単そうだからあとで直す。
all_graphs = api_get('/json/list/graph')

# POSTデータの作成
graph_data = []
from_graphs.each do |from_graph|
  service, section, graph = from_graph[:path].split('/')
  graph_id = all_graphs.detect{|g|
    g['service_name'] == service &&
    g['section_name'] == section &&
    g['graph_name']   == graph
  }['id']

  graph_data << {
    :gmode    => from_graph[:gmode],
    :stack    => from_graph[:stack],
    :type     => from_graph[:type],
    :graph_id => graph_id
  }
end

to_service, to_section, to_graph = to_complex[:path].split('/')
post_params = {
  :service_name => to_service,
  :section_name => to_section,
  :graph_name   => to_graph,
  :description  => to_complex[:description],
  :sort         => to_complex[:sort],
  :data         => graph_data
}

# 複合グラフ作成
pp post_params
api_post('/json/create/complex', post_params)

できた!!!

/json/edit/graph/:グラフの設定を変更

これもサンプルを。

同じグラフなのに、サービスごとに色がバラバラだと見づらいですよね。


作成時に指定しないとランダムになるっぽいです。

...
my @colors = List::Util::shuffle(qw/33 66 99 cc/);
$color ||= '#' . join('', splice(@colors,0,3));
...

とりあえず上のグラフの色を、こんな感じに変更してみましょう!

  • 2xx_count:青
  • 3xx_count:緑
  • 4xx_count:黄色
  • 5xx_count:赤
ソースコード
# -*- encoding: utf-8 -*-

require 'net/http'
require 'json'
require 'pp'

GF_HOST = 'localhost'
GF_PORT = 5125

SERVICE_NAME = 'test'
SECTION_NAME = 'access'
GRAPH_COLORS = {
  '2xx_count' => '#1111cc',
  '3xx_count' => '#11cc11',
  '4xx_count' => '#cccc11',
  '5xx_count' => '#cc1111',
}

def api_get(path)
  json = ''
  Net::HTTP.start(GF_HOST, GF_PORT) do |http|
    res  = http.get(path)
    json = res.body
  end
  JSON.parse(json)
end

def api_post(path, data)
  json = JSON.generate(data)
  Net::HTTP.start(GF_HOST, GF_PORT) do |http|
    res  = http.post(path, json)
    pp res.body
  end
end


GRAPH_COLORS.each do |graph_name, color|
  path = [SERVICE_NAME, SECTION_NAME, graph_name].join("/")
  graph_data = api_get("/api/#{path}")

  # meta情報に変なのが入っちゃうのでいらなそうなの削除
  %w[number adjust adjustval meta md5 created_at updated_at data unit mode].each{|key|
    graph_data.delete(key)
  }
  graph_data['color'] = color

  pp graph_data
  api_post("/json/edit/graph/#{graph_data['id']}", graph_data)
end

でけた!!!

/json/edit/complex/:複合グラフの設定を変更

上と似たようなもんなので割愛!!!



まとめ?

だいぶ手が抜けそうで安心しました!
他にもRRDの設定やデータをダンプするAPIなどなどもあるっぽいです。
また気がついたことあったら追記/修正していきます!


追記

もうすぐRuby/Perlのクライアントライブラリが出るっぽいです!