graphite-dashboard-api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 026018d8a4341a1ebef7965fec8089f395266064
4
+ data.tar.gz: 17b8708e169cce2ae098298de0f37ba871876191
5
+ SHA512:
6
+ metadata.gz: 3ed9217aff65cb7e6c300419f9ee322e4d0809cbb431dbe9e6c48c347b4c2f324ddd7a2c0e2fe2b390f40101ff91264686ba0106e9ef77800e92d507de81ffce
7
+ data.tar.gz: 3eb99d69b1ca9184726d5697b48cf1f30238b247a260801ca4ce06d243e489008b3994239af2b082b6d1ed6a2bd91a6188b7bb83fd8fcfb1871c2f7bed543a79
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.1.0
5
+ - ruby-head
6
+ script: bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gemspec
5
+
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ graphite-dashboard-api (0.1.0)
5
+ rest-client
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.2.5)
11
+ mime-types (1.25.1)
12
+ rest-client (1.6.7)
13
+ mime-types (>= 1.16)
14
+ rspec (2.14.1)
15
+ rspec-core (~> 2.14.0)
16
+ rspec-expectations (~> 2.14.0)
17
+ rspec-mocks (~> 2.14.0)
18
+ rspec-core (2.14.7)
19
+ rspec-expectations (2.14.5)
20
+ diff-lcs (>= 1.1.3, < 2.0)
21
+ rspec-mocks (2.14.6)
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ graphite-dashboard-api!
28
+ rspec
@@ -0,0 +1,32 @@
1
+ Graphite Dashboard API
2
+ ----------------------
3
+
4
+ [![Build Status](https://travis-ci.org/criteo/graphite-dashboard-api.png?branch=master)](https://travis-ci.org/criteo/graphite-dashboard-api)
5
+
6
+
7
+ Graphite dashboard api is a ruby gem which help to create and update graphite dashboards.
8
+
9
+ It allows to create dashboard with code instead of long manipulations in graphite dashboard UI.
10
+
11
+ How to
12
+ ------
13
+
14
+ Simple example:
15
+ ```ruby
16
+ my_graphs = %w(preprod prod).map do |env|
17
+ GraphiteDashboardApi::Graph.new "chef-client run time" do
18
+ targets [
19
+ "alias(averageSeries(storage.#{env}.chef.*.elapsed_time),\"average run time\")",
20
+ "alias(maxSeries(storage.#{env}.chef.*.elapsed_time),\"max run time\")"
21
+ ]
22
+ end
23
+ end
24
+
25
+ dashboard = GraphiteDashboardApi::Dashboard.new 'chef-run-time' do
26
+ graphs my_graphs
27
+ end
28
+
29
+ dashboard.save!('http://mygraphite.server.com')
30
+ ```
31
+
32
+ For more complex examples, see `spec/` folder
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "graphite-dashboard-api"
6
+ s.version = '0.1.0'
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Grégoire Seux"]
9
+ s.license = "Apache License v2"
10
+ s.summary = %q{DSL over graphite dashboard api}
11
+ s.homepage = "http://github.com/criteo/graphite-dashboard-api"
12
+ s.description = %q{}
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- spec/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+
17
+ s.add_development_dependency 'rspec'
18
+
19
+ s.add_dependency 'rest-client'
20
+ end
@@ -0,0 +1,3 @@
1
+ require 'graphite-dashboard-api/graph'
2
+ require 'graphite-dashboard-api/api'
3
+ require 'graphite-dashboard-api/dashboard'
@@ -0,0 +1,54 @@
1
+ require 'uri'
2
+ require 'rest_client'
3
+ require 'json'
4
+
5
+ module GraphiteDashboardApi
6
+ module Api
7
+ # this mixin requires a from_hash! and to_hash methods + @name
8
+ def save!(uri)
9
+ data = encode
10
+ response = rest_request(uri, "save/#{@name}", :post, data)
11
+ response
12
+ end
13
+
14
+ def load!(uri, name = nil)
15
+ response = rest_request(uri, "load/#{name || @name}", :get)
16
+ self.from_hash!(response)
17
+ end
18
+
19
+ def search(uri, pattern)
20
+ response = rest_request(uri, "find/?query=#{pattern}", :get)
21
+ response['dashboards']
22
+ end
23
+
24
+ def exists?(uri, name = nil)
25
+ pattern = name || @name
26
+ search(uri, pattern).map { |el| el['name'] }.include? (pattern)
27
+ end
28
+
29
+ private
30
+ def rest_request(uri, endpoint, method, payload = nil)
31
+ uri = "#{uri}/dashboard/#{endpoint}"
32
+ begin
33
+ r = RestClient::Request.new(
34
+ url: uri,
35
+ payload: payload,
36
+ method: method,
37
+ timeout: 2,
38
+ open_timeout: 2,
39
+ headers: {
40
+ accept: :json,
41
+ content_type: :json,
42
+ }
43
+ )
44
+ response = r.execute
45
+ rescue => e
46
+ raise "Rest client error: #{e}"
47
+ end
48
+ if response.code != 200 || JSON.parse(response.body)['error']
49
+ fail "Error calling dashboard API: #{response.code} #{response.body}"
50
+ end
51
+ JSON.parse(response.body)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,139 @@
1
+ module GraphiteDashboardApi
2
+ class Dashboard
3
+
4
+ include Api
5
+
6
+ attr_accessor :name
7
+ attr_accessor :graphs
8
+ attr_accessor :stringify_graphSize
9
+
10
+ def initialize(name = nil, &block)
11
+ @name = name
12
+ @graphs = []
13
+ @defaultGraphParams_width = '800'
14
+ @defaultGraphParams_from = '-5minutes'
15
+ @defaultGraphParams_until = 'now'
16
+ @defaultGraphParams_height = '400'
17
+ @refreshConfig_interval = 600000
18
+ @refreshConfig_enabled = false
19
+ @graphSize_width = 400
20
+ @graphSize_height = 250
21
+ @stringify_graphSize = false # tweaking option
22
+
23
+ # default timeConfig
24
+ @timeConfig_type = 'relative'
25
+ @timeConfig_relativeStartUnits = 'minutes'
26
+ @timeConfig_relativeStartQuantity = '5'
27
+ @timeConfig_relativeUntilUnits = 'now'
28
+ @timeConfig_relativeUntilQuantity = ''
29
+
30
+ @timeConfig_startDate = '1970-01-01T00:00:00'
31
+ @timeConfig_endDate = '1970-01-01T00:05:00'
32
+ @timeConfig_startTime = '9:00 AM'
33
+ @timeConfig_endTime = '5:00 PM'
34
+
35
+ instance_eval(&block) if block
36
+ end
37
+
38
+ DEFAULT_GRAPH_PARAMS = [:width, :from, :until, :height]
39
+ REFRESH_CONFIG = [:interval, :enabled]
40
+ TIME_CONFIG = [:startDate, :relativeStartUnits, :endDate, :relativeStartQuantity, :relativeUntilQuantity, :startTime, :endTime, :type, :relativeUntilUnits]
41
+ GRAPH_SIZE = [:width, :height]
42
+
43
+ OPTIONS = {
44
+ 'defaultGraphParams' => DEFAULT_GRAPH_PARAMS,
45
+ 'refreshConfig' => REFRESH_CONFIG,
46
+ 'timeConfig' => TIME_CONFIG,
47
+ 'graphSize' => GRAPH_SIZE,
48
+ }
49
+
50
+ def stringify_graphSize(arg = nil)
51
+ if arg
52
+ @stringify_graphSize = arg
53
+ end
54
+ @stringify_graphSize
55
+ end
56
+
57
+ def graphs(arg = nil)
58
+ if arg
59
+ @graphs = arg
60
+ end
61
+ @graphs
62
+ end
63
+
64
+ OPTIONS.each do |k, options|
65
+ options.each do |v|
66
+ accessor_name = k + '_' + v.to_s
67
+ attr_accessor accessor_name
68
+ define_method(accessor_name) do |arg = nil|
69
+ if arg
70
+ instance_variable_set("@#{accessor_name}".to_sym, arg)
71
+ else
72
+ instance_variable_get "@#{accessor_name}".to_sym
73
+ end
74
+ end
75
+ define_method((accessor_name.to_s + '_').to_sym) do |arg = nil|
76
+ send(accessor_name, arg)
77
+ end
78
+ end
79
+ end
80
+
81
+ def to_hash
82
+ state = {}
83
+ state['name'] = @name
84
+ OPTIONS.each do |k, options|
85
+ state[k] = Hash.new
86
+ options.each do |kk|
87
+ state[k][kk.to_s] = instance_variable_get "@#{k}_#{kk}".to_sym
88
+ state[k][kk.to_s] = state[k][kk.to_s].to_s if (k.eql? 'graphSize' and @stringify_graphSize)
89
+ end
90
+ end
91
+
92
+ state['graphs'] = @graphs.map do |graph|
93
+
94
+ [graph.leading_entries, graph.to_hash, graph.url(state['graphSize'])]
95
+ end
96
+
97
+ hash = { 'state' => state }
98
+ hash
99
+ end
100
+
101
+ def from_hash!(hash)
102
+ state = hash['state']
103
+ @name = state['name']
104
+ OPTIONS.each do |k, options|
105
+ if state[k]
106
+ options.each do |kk|
107
+ value = state[k][kk.to_s]
108
+ instance_variable_set("@#{k}_#{kk}".to_sym, value) if value
109
+ end
110
+ end
111
+ end
112
+ if state['graphs']
113
+ state['graphs'].each do |graph_entry|
114
+ fail 'Graph entry is supposed to have 3 elements' unless graph_entry.size.eql? 3
115
+ graph = graph_entry[1]
116
+ new_graph = Graph.new
117
+ new_graph.from_hash!(graph)
118
+ @graphs << new_graph
119
+ end
120
+ end
121
+ self
122
+ end
123
+
124
+ def encode
125
+ json = JSON.generate(to_hash['state'])
126
+ 'state=' + URI.encode(json, my_unsafe)
127
+ end
128
+
129
+ def decode(encoded_string)
130
+ json = URI.decode(encoded_string[/=(.*)/, 1], my_unsafe)
131
+ JSON.parse(json)
132
+ end
133
+
134
+ def my_unsafe
135
+ Regexp.union(URI::UNSAFE, /[,&+]/)
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,85 @@
1
+ require 'uri'
2
+
3
+ module GraphiteDashboardApi
4
+ class Graph
5
+ PROPS = [:from, :until, :width, :height]
6
+ [PROPS, :targets, :titles, :compact_leading].flatten.each do |a|
7
+ attr_accessor a
8
+ define_method(a) do |arg = nil|
9
+ if arg
10
+ instance_variable_set("@#{a}".to_sym, arg)
11
+ else
12
+ instance_variable_get "@#{a}".to_sym
13
+ end
14
+ end
15
+ define_method((a.to_s + '_').to_sym) do |arg = nil|
16
+ send(a, arg)
17
+ end
18
+ end
19
+
20
+ def initialize(title = nil, &block)
21
+ @title = title
22
+ @targets = []
23
+ @compact_leading = false # this is tweaking stuff
24
+ instance_eval(&block) if block
25
+ end
26
+
27
+ def url(default_options)
28
+ '/render?' + render_options(default_options) + '&' + target_encode + "&title=#{@title || default_title}"
29
+ end
30
+
31
+ # This is probably an over simplification TODO
32
+ def default_title
33
+ @targets.first
34
+ end
35
+
36
+ def render_options(default_options)
37
+ opts = []
38
+ PROPS.each do |k|
39
+ v = instance_variable_get "@#{k}".to_sym
40
+ v ||= default_options[k.to_s]
41
+ opts << "#{k.to_s}=#{v}" if v
42
+ end
43
+ opts.join('&')
44
+ end
45
+
46
+ def leading_entries
47
+ if @compact_leading
48
+ leading_entries = target_encode
49
+ else
50
+ leading_entries = @targets
51
+ end
52
+ end
53
+
54
+ def target_encode
55
+ @targets.map do |target|
56
+ 'target=' + target
57
+ end.join('&')
58
+ end
59
+
60
+ def to_hash
61
+ hash = {}
62
+ hash['title'] = @title if @title
63
+ PROPS.each do |k|
64
+ v = instance_variable_get "@#{k}".to_sym
65
+ hash[k.to_s] = v if v
66
+ end
67
+ hash['target'] = @targets
68
+ hash
69
+ end
70
+
71
+ def from_hash!(hash)
72
+ @title = hash['title']
73
+ PROPS.each do |k|
74
+ value = hash[k.to_s]
75
+ instance_variable_set("@#{k}".to_sym, value) if value
76
+ end
77
+ if hash['target']
78
+ hash['target'].each do |target|
79
+ @targets << target
80
+ end
81
+ end
82
+ self
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,97 @@
1
+ require 'rspec'
2
+ require_relative '../lib/graphite-dashboard-api'
3
+ require 'json'
4
+
5
+ describe GraphiteDashboardApi::Dashboard do
6
+
7
+ it 'can generate complex dashboards' do
8
+ part1 = 'state=%7B%22name%22:%22chef-storage-automatic%22%2C%22defaultGraphParams%22:%7B%22width%22:%22350%22%2C%22from%22:%22-4days%22%2C%22until%22:%22now%22%2C%22height%22:%22250%22%7D%2C%22refreshConfig%22:%7B%22interval%22:600000%2C%22enabled%22:true%7D%2C%22timeConfig%22:%7B%22startDate%22:%221970-01-01T00:00:00%22%2C%22relativeStartUnits%22:%22days%22%2C%22endDate%22:%221970-01-01T00:05:00%22%2C%22relativeStartQuantity%22:%224%22%2C%22relativeUntilQuantity%22:%22%22%2C%22startTime%22:%229:00%20AM%22%2C%22endTime%22:%225:00%20PM%22%2C%22type%22:%22relative%22%2C%22relativeUntilUnits%22:%22now%22%7D%2C%22graphSize%22:%7B%22width%22:%22350%22%2C%22height%22:%22250%22%7D%2C%22graphs%22:[[[%22cactiStyle(alias(averageSeries(storage.chef.mdb*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%22%2C%22alias(dashed(drawAsInfinite(sumSeries(storage.chef.mdb*.fail)))%2C%5C%22fails%5C%22)%22]%2C%7B%22title%22:%22mongo%20pool%22%2C%22from%22:%22-4days%22%2C%22until%22:%22now%22%2C%22target%22:[%22cactiStyle(alias(averageSeries(storage.chef.mdb*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%22%2C%22alias(dashed(drawAsInfinite(sumSeries(storage.chef.mdb*.fail)))%2C%5C%22fails%5C%22)%22]%7D%2C%22/render?from=-4days%26until=now%26width=350%26height=250%26target=cactiStyle(alias(averageSeries(storage.chef.mdb*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%26target=alias(dashed(drawAsInfinite(sumSeries(storage.chef.mdb*.fail)))%2C%5C%22fails%5C%22)%26title=mongo%20pool%22]%2C[[%22cactiStyle(alias(averageSeries(storage.chef.rmq*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%22%2C%22alias(dashed(drawAsInfinite(sumSeries(storage.chef.rmq*.fail)))%2C%5C%22fails%5C%22)%22]%2C%7B%22title%22:%22Rabbit%20pool%22%2C%22from%22:%22-4days%22%2C%22until%22:%22now%22%2C%22target%22:[%22cactiStyle(alias(averageSeries(storage.chef.rmq*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%22%2C%22alias(dashed(drawAsInfinite(sumSeries(storage.chef.rmq*.fail)))%2C%5C%22fails%5C%22)%22]%7D%2C%22/render?from=-4days%26until=now%26width=350%26height=250%26target=cactiStyle(alias(averageSeries(storage.chef.rmq*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%26target=alias(dashed(drawAsInfinite(sumSeries(storage.chef.rmq*.fail)))%2C%5C%22fails%5C%22)%26title=Rabbit%20pool%22]%2C[[%22cactiStyle(alias(averageSeries(storage.chef.%7Bcouch%2Cmem%7D*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%22%2C%22alias(dashed(drawAsInfinite(sumSeries(storage.chef.%7Bcouch%2Cmem%7D*.fail)))%2C%5C%22fails%5C%22)%22]%2C%7B%22title%22:%22couchbase%20pool%22%2C%22from%22:%22-4days%22%2C%22until%22:%22now%22%2C%22target%22:[%22cactiStyle(alias(averageSeries(storage.chef.%7Bcouch%2Cmem%7D*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%22%2C%22alias(dashed(drawAsInfinite(sumSeries(storage.chef.%7Bcouch%2Cmem%7D*.fail)))%2C%5C%22fails%5C%22)%22]%7D%2C%22/render?from=-4days%26until=now%26width=350%26height=250%26target=cactiStyle(alias(averageSeries(storage.chef.%7Bcouch%2Cmem%7D*.elapsed_time)'
9
+ part2 = '%2C%5C%22mean%20run%20time%5C%22))%26target=alias(dashed(drawAsInfinite(sumSeries(storage.chef.%7Bcouch%2Cmem%7D*.fail)))%2C%5C%22fails%5C%22)%26title=couchbase%20pool%22]%2C[[%22cactiStyle(alias(averageSeries(storage.chef.%7Bkestrel%2Cbungee%7D*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%22%2C%22alias(dashed(drawAsInfinite(sumSeries(storage.chef.%7Bkestrel%2Cbungee%7D*.fail)))%2C%5C%22fails%5C%22)%22]%2C%7B%22title%22:%22kestrel%2Bbungee%20pool%22%2C%22from%22:%22-4days%22%2C%22until%22:%22now%22%2C%22target%22:[%22cactiStyle(alias(averageSeries(storage.chef.%7Bkestrel%2Cbungee%7D*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%22%2C%22alias(dashed(drawAsInfinite(sumSeries(storage.chef.%7Bkestrel%2Cbungee%7D*.fail)))%2C%5C%22fails%5C%22)%22]%7D%2C%22/render?from=-4days%26until=now%26width=350%26height=250%26target=cactiStyle(alias(averageSeries(storage.chef.%7Bkestrel%2Cbungee%7D*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%26target=alias(dashed(drawAsInfinite(sumSeries(storage.chef.%7Bkestrel%2Cbungee%7D*.fail)))%2C%5C%22fails%5C%22)%26title=kestrel%2Bbungee%20pool%22]%2C[[%22cactiStyle(alias(averageSeries(storage.chef.%7Bsquirrel%2Cgraphite%7D*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%22%2C%22alias(dashed(drawAsInfinite(sumSeries(storage.chef.%7Bsquirrel%2Cgraphite%7D*.fail)))%2C%5C%22fails%5C%22)%22]%2C%7B%22title%22:%22Graphites%20pool%22%2C%22from%22:%22-4days%22%2C%22until%22:%22now%22%2C%22target%22:[%22cactiStyle(alias(averageSeries(storage.chef.%7Bsquirrel%2Cgraphite%7D*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%22%2C%22alias(dashed(drawAsInfinite(sumSeries(storage.chef.%7Bsquirrel%2Cgraphite%7D*.fail)))%2C%5C%22fails%5C%22)%22]%7D%2C%22/render?from=-4days%26until=now%26width=350%26height=250%26target=cactiStyle(alias(averageSeries(storage.chef.%7Bsquirrel%2Cgraphite%7D*.elapsed_time)%2C%5C%22mean%20run%20time%5C%22))%26target=alias(dashed(drawAsInfinite(sumSeries(storage.chef.%7Bsquirrel%2Cgraphite%7D*.fail)))%2C%5C%22fails%5C%22)%26title=Graphites%20pool%22]%2C[[%22hitcount(sumSeries(storage.chef.%7Bkestrel%2Cbungee%7D*.fail)%2C%5C%2230min%5C%22)%22%2C%22hitcount(sumSeries(storage.chef.%7Bgraphite%2Csquirrel%7D*.fail)%2C%5C%2230min%5C%22)%22%2C%22hitcount(sumSeries(storage.chef.rmq*.fail)%2C%5C%2230min%5C%22)%22%2C%22hitcount(sumSeries(storage.chef.%7Bcouch%2Cmem%7D*.fail)%2C%5C%2230min%5C%22)%22%2C%22hitcount(sumSeries(storage.chef.mdb*.fail)%2C%5C%2230min%5C%22)%22%2C%22color(secondYAxis(sumSeries(storage.chef.*.success))%2C%5C%22green%5C%22)%22]%2C%7B%22title%22:%22Sucess%20%26%20failure%22%2C%22from%22:%22-4days%22%2C%22until%22:%22now%22%2C%22target%22:[%22hitcount(sumSeries(storage.chef.%7Bkestrel%2Cbungee%7D*.fail)%2C%5C%2230min%5C%22)%22%2C%22hitcount(sumSeries(storage.chef.%7Bgraphite%2Csquirrel%7D*.fail)%2C%5C%2230min%5C%22)%22%2C%22hitcount(sumSeries(storage.chef.rmq*.fail)%2C%5C%2230min%5C%22)%22%2C%22hitcount(sumSeries(storage.chef.%7Bcouch%2Cmem%7D*.fail)%2C%5C%2230min'
10
+ part3 = '%5C%22)%22%2C%22hitcount(sumSeries(storage.chef.mdb*.fail)%2C%5C%2230min%5C%22)%22%2C%22color(secondYAxis(sumSeries(storage.chef.*.success))%2C%5C%22green%5C%22)%22]%7D%2C%22/render?from=-4days%26until=now%26width=350%26height=250%26target=hitcount(sumSeries(storage.chef.%7Bkestrel%2Cbungee%7D*.fail)%2C%5C%2230min%5C%22)%26target=hitcount(sumSeries(storage.chef.%7Bgraphite%2Csquirrel%7D*.fail)%2C%5C%2230min%5C%22)%26target=hitcount(sumSeries(storage.chef.rmq*.fail)%2C%5C%2230min%5C%22)%26target=hitcount(sumSeries(storage.chef.%7Bcouch%2Cmem%7D*.fail)%2C%5C%2230min%5C%22)%26target=hitcount(sumSeries(storage.chef.mdb*.fail)%2C%5C%2230min%5C%22)%26target=color(secondYAxis(sumSeries(storage.chef.*.success))%2C%5C%22green%5C%22)%26title=Sucess%20%26%20failure%22]%2C[[%22keepLastValue(averageSeries(storage.chef.%7Bkestrel%2Cbungee%7D*.updated_resources))%22%2C%22keepLastValue(averageSeries(storage.chef.%7Bgraphite%2Csquirrel%7D%7D*.updated_resources))%22%2C%22keepLastValue(averageSeries(storage.chef.rmq*.updated_resources))%22%2C%22keepLastValue(averageSeries(storage.chef.%7Bcouch%2Cmem%7D*.updated_resources))%22%2C%22keepLastValue(averageSeries(storage.chef.mdb*.updated_resources))%22]%2C%7B%22title%22:%22Updated%20resources%22%2C%22from%22:%22-4days%22%2C%22until%22:%22now%22%2C%22target%22:[%22keepLastValue(averageSeries(storage.chef.%7Bkestrel%2Cbungee%7D*.updated_resources))%22%2C%22keepLastValue(averageSeries(storage.chef.%7Bgraphite%2Csquirrel%7D%7D*.updated_resources))%22%2C%22keepLastValue(averageSeries(storage.chef.rmq*.updated_resources))%22%2C%22keepLastValue(averageSeries(storage.chef.%7Bcouch%2Cmem%7D*.updated_resources))%22%2C%22keepLastValue(averageSeries(storage.chef.mdb*.updated_resources))%22]%7D%2C%22/render?from=-4days%26until=now%26width=350%26height=250%26target=keepLastValue(averageSeries(storage.chef.%7Bkestrel%2Cbungee%7D*.updated_resources))%26target=keepLastValue(averageSeries(storage.chef.%7Bgraphite%2Csquirrel%7D%7D*.updated_resources))%26target=keepLastValue(averageSeries(storage.chef.rmq*.updated_resources))%26target=keepLastValue(averageSeries(storage.chef.%7Bcouch%2Cmem%7D*.updated_resources))%26target=keepLastValue(averageSeries(storage.chef.mdb*.updated_resources))%26title=Updated%20resources%22]]%7D'
11
+
12
+ expected_encoded_string = part1 + part2 + part3 # vim syntaxic coloration does not handle long lines well
13
+
14
+ pool_definitions = {
15
+ 'mongo' => %w(mdb),
16
+ 'Rabbit' => %w(rmq),
17
+ 'couchbase' => %w(couch mem),
18
+ 'kestrel+bungee' => %w(kestrel bungee),
19
+ 'Graphites' => %w(squirrel graphite),
20
+ }
21
+ graphs = pool_definitions.map do |pool, prefixes|
22
+ prefixes_ = '{' + prefixes.join(',') + '}'
23
+ prefixes_ = prefixes.first if prefixes.size.eql? 1
24
+ GraphiteDashboardApi::Graph.new "#{pool} pool" do
25
+ from '-4days'
26
+ until_ 'now'
27
+ targets [
28
+ "cactiStyle(alias(averageSeries(storage.chef.#{prefixes_}*.elapsed_time),\"mean run time\"))",
29
+ "alias(dashed(drawAsInfinite(sumSeries(storage.chef.#{prefixes_}*.fail))),\"fails\")",
30
+ ]
31
+ end
32
+ end
33
+ success_failure = GraphiteDashboardApi::Graph.new 'Sucess & failure' do
34
+ from '-4days'
35
+ until_ 'now'
36
+ targets [
37
+ "hitcount(sumSeries(storage.chef.{kestrel,bungee}*.fail),\"30min\")",
38
+ "hitcount(sumSeries(storage.chef.{graphite,squirrel}*.fail),\"30min\")",
39
+ "hitcount(sumSeries(storage.chef.rmq*.fail),\"30min\")",
40
+ "hitcount(sumSeries(storage.chef.{couch,mem}*.fail),\"30min\")",
41
+ "hitcount(sumSeries(storage.chef.mdb*.fail),\"30min\")",
42
+ "color(secondYAxis(sumSeries(storage.chef.*.success)),\"green\")"
43
+ ]
44
+ end
45
+ updated_resources = GraphiteDashboardApi::Graph.new 'Updated resources' do
46
+ from '-4days'
47
+ until_ 'now'
48
+ targets [
49
+ 'keepLastValue(averageSeries(storage.chef.{kestrel,bungee}*.updated_resources))',
50
+ 'keepLastValue(averageSeries(storage.chef.{graphite,squirrel}}*.updated_resources))',
51
+ 'keepLastValue(averageSeries(storage.chef.rmq*.updated_resources))',
52
+ 'keepLastValue(averageSeries(storage.chef.{couch,mem}*.updated_resources))',
53
+ 'keepLastValue(averageSeries(storage.chef.mdb*.updated_resources))'
54
+ ]
55
+ end
56
+
57
+ dashboard = GraphiteDashboardApi::Dashboard.new 'chef-storage-automatic' do
58
+ defaultGraphParams_width '350'
59
+ defaultGraphParams_height '250'
60
+ graphSize_width 350
61
+ stringify_graphSize true
62
+ defaultGraphParams_from '-4days'
63
+ timeConfig_relativeStartUnits 'days'
64
+ timeConfig_relativeStartQuantity '4'
65
+ refreshConfig_enabled true
66
+ graphs ([graphs, success_failure, updated_resources].flatten)
67
+ end
68
+
69
+ expected_json = { 'state' => JSON.parse(URI.decode(expected_encoded_string)[/^state=(.*)/, 1]) }
70
+ comparison_by_block(dashboard, expected_json)
71
+ # expect(dashboard.save!(a_graphite_url)['success']).to eq true
72
+
73
+ end
74
+ end
75
+
76
+ def comparison_by_block(dashboard, expected_json)
77
+ result = JSON.parse(JSON.generate(dashboard.to_hash))['state']
78
+ expected_json['state'].each do |k, v|
79
+ expect(result[k]).to eq v
80
+ end
81
+ expect(JSON.parse(JSON.generate(dashboard.to_hash))).to eq expected_json
82
+
83
+ # also check idempotency of (to_hash o from_hash)
84
+ # we have to be careful with tweaking options
85
+ new_dashboard = GraphiteDashboardApi::Dashboard.new
86
+ new_dashboard.from_hash! dashboard.to_hash
87
+ expect(new_dashboard.graphs.size).to eq dashboard.graphs.size
88
+ dashboard.graphs.each_with_index do |el, index|
89
+ if el.compact_leading
90
+ new_dashboard.graphs[index].compact_leading = true
91
+ end
92
+ end
93
+ new_dashboard.to_hash['state'].each do |k, v|
94
+ expect(dashboard.to_hash['state'][k]).to eq v
95
+ end
96
+ expect(new_dashboard.to_hash).to eq dashboard.to_hash
97
+ end
@@ -0,0 +1,54 @@
1
+ require 'rspec'
2
+ require_relative '../lib/graphite-dashboard-api'
3
+ require 'json'
4
+
5
+ describe GraphiteDashboardApi::Graph do
6
+
7
+ let(:graph_with_title) do
8
+ graph = GraphiteDashboardApi::Graph.new 'title' do
9
+ from '-5minutes'
10
+ until_ 'now'
11
+ targets ['storage.rsyslog.action1.processed.kestrel01-am5']
12
+ end
13
+ expected_json = JSON.parse('{"until": "now","from": "-5minutes","target": ["storage.rsyslog.action1.processed.kestrel01-am5"],"title": "title"}')
14
+ [graph, expected_json]
15
+ end
16
+
17
+ let(:graph_without_title) do
18
+ graph = GraphiteDashboardApi::Graph.new do
19
+ from '-5minutes'
20
+ until_ 'now'
21
+ targets ['storage.rsyslog.action1.processed.kestrel01-am5']
22
+ end
23
+ expected_json = JSON.parse('{"until": "now","from": "-5minutes","target": ["storage.rsyslog.action1.processed.kestrel01-am5"]}')
24
+ [graph, expected_json]
25
+ end
26
+
27
+ it 'can be constructed' do
28
+ graph = GraphiteDashboardApi::Graph.new('Test graph') do
29
+ from '-4hours'
30
+ until_ '-2hours'
31
+ end
32
+
33
+ expect(graph.from).to eq('-4hours')
34
+ expect(graph.until).to eq('-2hours')
35
+ end
36
+
37
+ it 'can be converted to json' do
38
+ graph, expected_json = graph_with_title
39
+ expect(JSON.parse(JSON.generate(graph.to_hash))).to eq expected_json
40
+ # also check idempotency of (to_hash o from_hash)
41
+ new_graph = GraphiteDashboardApi::Graph.new
42
+ expect((new_graph.from_hash! graph.to_hash).to_hash).to eq graph.to_hash
43
+ end
44
+
45
+ it 'can be converted to json when no title' do
46
+ graph, expected_json = graph_without_title
47
+ expect(graph.target_encode).to eq 'target=storage.rsyslog.action1.processed.kestrel01-am5'
48
+ expect(JSON.parse(JSON.generate(graph.to_hash))).to eq expected_json
49
+
50
+ # also check idempotency of (to_hash o from_hash)
51
+ new_graph = GraphiteDashboardApi::Graph.new
52
+ expect((new_graph.from_hash! graph.to_hash).to_hash).to eq graph.to_hash
53
+ end
54
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphite-dashboard-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Grégoire Seux
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rest-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: ''
42
+ email:
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - .travis.yml
48
+ - Gemfile
49
+ - Gemfile.lock
50
+ - README.md
51
+ - graphite_dashboard_api.gemspec
52
+ - lib/graphite-dashboard-api.rb
53
+ - lib/graphite-dashboard-api/api.rb
54
+ - lib/graphite-dashboard-api/dashboard.rb
55
+ - lib/graphite-dashboard-api/graph.rb
56
+ - spec/dashboard_spec.rb
57
+ - spec/graph_spec.rb
58
+ homepage: http://github.com/criteo/graphite-dashboard-api
59
+ licenses:
60
+ - Apache License v2
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 2.0.3
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: DSL over graphite dashboard api
82
+ test_files:
83
+ - spec/dashboard_spec.rb
84
+ - spec/graph_spec.rb