graphite-dashboard-api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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