pencil 0.2.10 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/bin/pencil +2 -6
  2. data/docs/pencil_options.md +3 -1
  3. data/lib/{config.rb → pencil/config.rb} +29 -9
  4. data/lib/pencil/config.ru +2 -0
  5. data/lib/pencil/helpers.rb +211 -0
  6. data/lib/pencil/models/base.rb +78 -0
  7. data/lib/pencil/models/dashboard.rb +124 -0
  8. data/lib/pencil/models/graph.rb +394 -0
  9. data/lib/pencil/models/host.rb +89 -0
  10. data/lib/pencil/models.rb +3 -0
  11. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-bg_flat_30_cccccc_40x100.png +0 -0
  12. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-bg_flat_50_5c5c5c_40x100.png +0 -0
  13. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-bg_glass_20_333333_1x400.png +0 -0
  14. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-bg_glass_40_000000_1x400.png +0 -0
  15. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-bg_glass_40_ffc73d_1x400.png +0 -0
  16. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-bg_gloss-wave_25_333333_500x100.png +0 -0
  17. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-bg_highlight-soft_15_000000_1x100.png +0 -0
  18. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-bg_highlight-soft_80_eeeeee_1x100.png +0 -0
  19. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-bg_inset-soft_30_ff4e0a_1x100.png +0 -0
  20. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-icons_222222_256x240.png +0 -0
  21. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-icons_4b8e0b_256x240.png +0 -0
  22. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-icons_a83300_256x240.png +0 -0
  23. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-icons_cccccc_256x240.png +0 -0
  24. data/lib/{public → pencil/public}/css/jquery_themes/pencil/images/ui-icons_ffffff_256x240.png +0 -0
  25. data/lib/{public → pencil/public}/css/jquery_themes/pencil/jquery-ui-1.8.16.custom.css +0 -0
  26. data/lib/{public → pencil/public}/css/thedoc.css +1 -0
  27. data/lib/{public → pencil/public}/favicon.ico +0 -0
  28. data/lib/{public → pencil/public}/js/cufon.js +0 -0
  29. data/lib/{public → pencil/public}/js/gotham.font.js +0 -0
  30. data/lib/{public → pencil/public}/js/jquery-1.6.2.min.js +0 -0
  31. data/lib/{public → pencil/public}/js/jquery-ui-1.8.16.custom.min.js +0 -0
  32. data/lib/{public → pencil/public}/js/pencil.js +0 -0
  33. data/lib/{public → pencil/public}/js/product-design.font.js +0 -0
  34. data/lib/pencil/public/style.css +307 -0
  35. data/lib/{rubyfixes.rb → pencil/rubyfixes.rb} +0 -0
  36. data/lib/pencil/version.rb +3 -0
  37. data/lib/{views → pencil/views}/cluster.erb +1 -1
  38. data/lib/{views → pencil/views}/dash-cluster-zoom.erb +2 -2
  39. data/lib/{views → pencil/views}/dash-cluster.erb +1 -1
  40. data/lib/{views → pencil/views}/dash-global-zoom.erb +2 -2
  41. data/lib/{views → pencil/views}/dash-global.erb +1 -1
  42. data/lib/{views → pencil/views}/global.erb +1 -1
  43. data/lib/{views → pencil/views}/host.erb +1 -1
  44. data/lib/{views → pencil/views}/layout.erb +2 -2
  45. data/lib/{views → pencil/views}/partials/cluster_selector.erb +0 -0
  46. data/lib/{views → pencil/views}/partials/cluster_switcher.erb +0 -0
  47. data/lib/{views → pencil/views}/partials/dash_switcher.erb +0 -0
  48. data/lib/{views → pencil/views}/partials/graph_switcher.erb +0 -0
  49. data/lib/{views → pencil/views}/partials/hosts_selector.erb +0 -0
  50. data/lib/{views → pencil/views}/partials/input_boxes.erb +0 -0
  51. data/lib/{views → pencil/views}/partials/shortcuts.erb +0 -0
  52. data/lib/{dash.rb → pencil.rb} +14 -17
  53. metadata +57 -58
  54. data/VERSION.rb +0 -1
  55. data/lib/config.ru +0 -2
  56. data/lib/helpers.rb +0 -209
  57. data/lib/models/base.rb +0 -73
  58. data/lib/models/dashboard.rb +0 -111
  59. data/lib/models/graph.rb +0 -367
  60. data/lib/models/host.rb +0 -87
  61. data/lib/models.rb +0 -3
  62. data/lib/namespace.rb +0 -4
  63. data/lib/public/style.css +0 -97
@@ -0,0 +1,394 @@
1
+ require "pencil/models/base"
2
+ require "uri"
3
+
4
+ module Pencil
5
+ module Models
6
+ class Graph < Base
7
+ def initialize(name, params={})
8
+ super
9
+
10
+ @params["hosts"] ||= ["*"]
11
+ @params["title"] ||= name
12
+
13
+ if not @params["targets"]
14
+ raise ArgumentError, "graph #{name} needs a 'targets' map"
15
+ end
16
+ end
17
+
18
+ def width(opts={})
19
+ opts["width"] || @params[:url_opts][:width]
20
+ end
21
+
22
+ # translate STR into graphite-speak for applying FUNC to STR
23
+ # graphite functions take zero or one argument
24
+ # pass passes STR through, instead of raising an error if FUNC isn't
25
+ # recognized
26
+ def translate(func, str, arg=nil, pass=false)
27
+ # puts "calling translate"
28
+ # puts "func => #{func}"
29
+ # puts "str => #{str}"
30
+ # puts "arg => #{arg}"
31
+ # procs and lambdas don't support default arguments in 1.8, so I have to
32
+ # do this
33
+ z = lambda { |*body| "#{func}(#{body[0]||str})" }
34
+ y = "#{str}, #{arg}"
35
+ x = lambda { z.call(y) }
36
+
37
+ return \
38
+ case func.to_s
39
+ # comb
40
+ when "sumSeries", "averageSeries", "minSeries", "maxSeries", "group"
41
+ z.call
42
+ # transform
43
+ when "scale", "offset"
44
+ # perhaps .to_f
45
+ x.call
46
+ when "derivative", "integral"
47
+ z.call
48
+ when "nonNegativeDerivative"
49
+ z.call("#{str}#{', ' + arg if arg}")
50
+ when "log", "timeShift", "summarize", "hitcount",
51
+ # calculate
52
+ "movingAverage", "stdev", "asPercent"
53
+ x.call
54
+ when "diffSeries", "divideSeries"
55
+ z.call
56
+ # filters
57
+ when "mostDeviant"
58
+ z.call("#{arg}, #{str}")
59
+ when "highestCurrent", "lowestCurrent", "nPercentile", "currentAbove",
60
+ "currentBelow", "highestAverage", "lowestAverage", "averageAbove",
61
+ "averageBelow", "maximumAbove", "maximumBelow"
62
+ x.call
63
+ when "sortByMaxima", "minimalist"
64
+ z.call
65
+ when "limit", "exclude"
66
+ x.call
67
+ when "key", "alias"
68
+ "alias(#{str}, \"#{arg}\")"
69
+ when "cumulative", "drawAsInfinite"
70
+ z.call
71
+ when "lineWidth"
72
+ x.call
73
+ when "dashed", "keepLastValue"
74
+ z.call
75
+ when "substr", "threshold"
76
+ x.call
77
+ when "color"
78
+ @params[:use_color] ? "color(#{str}, \"#{arg}\")" : str
79
+ else
80
+ raise "BAD FUNC #{func}" unless pass
81
+ str
82
+ end
83
+ end
84
+
85
+ # FIXME Map().keys => Array of strings
86
+ # inner means we're dealing with a complex key; @params will be applied
87
+ # make sure to apply alias and color arguments last, if applicable
88
+ def handle_metric(name, opts, inner=false)
89
+ later = []
90
+ ret = name.dup
91
+ if inner
92
+ @params.each do |k, v|
93
+ ret = translate(k, ret, v, true)
94
+ end
95
+ end
96
+ (opts||{}).each do |k, v|
97
+ if k == "color" || k == "key"
98
+ later << [k, v]
99
+ else
100
+ ret = translate(k, ret, v)
101
+ end
102
+ end
103
+ later.each do |k, v|
104
+ ret = translate(k, ret, v)
105
+ end
106
+ ret
107
+ end
108
+
109
+ def render_url(hosts, clusters, opts={})
110
+ opts = {
111
+ :sum => nil,
112
+ :title => @params["title"],
113
+ }.merge(opts)
114
+
115
+ if ! [:global, :cluster, nil].member?(opts[:sum])
116
+ raise ArgumentError, "render graph #{name}: invalid :sum - #{opts[:sum]}"
117
+ end
118
+
119
+ sym_hash = {}
120
+ (opts[:dynamic_url_opts]||[]).each do |k,v|
121
+ sym_hash[k.to_sym] = v
122
+ end
123
+
124
+ # fixme key checking may be necessary
125
+ url_opts = {
126
+ :title => opts[:title],
127
+ }.merge(@params[:url_opts]).merge(sym_hash)
128
+
129
+ url_opts[:from] = url_opts.delete(:stime) || ""
130
+ url_opts[:until] = url_opts.delete(:etime) || ""
131
+ url_opts.delete(:start)
132
+ url_opts.delete(:duration)
133
+
134
+ graphite_opts = [ "vtitle", "yMin", "yMax", "lineWidth", "areaMode",
135
+ "template", "lineMode", "bgcolor", "graphOnly", "hideAxes", "hideGrid",
136
+ "hideLegend", "fgcolor", "fontSize", "fontName", "fontItalic",
137
+ "fontBold", "logBase" ]
138
+
139
+ @params.each do |k, v|
140
+ if graphite_opts.member?(k)
141
+ url_opts[k.to_sym] = v
142
+ end
143
+ end
144
+
145
+ target = []
146
+ colors = []
147
+ #FIXME code duplication
148
+ if opts[:sum] == :global
149
+ @params["targets"].each do |stat_name, opts|
150
+ z = new_map(opts)
151
+
152
+ z[:key] ||= stat_name
153
+ #######################
154
+ if stat_name.instance_of?(Array)
155
+ metric = stat_name.map do |m|
156
+ mm = compose_metric(m.keys.first,
157
+ "{#{clusters.to_a.join(',')}}",
158
+ "{#{hosts.to_a.join(',')}}")
159
+
160
+ if z.keys.member?("divideSeries")
161
+ handle_metric(translate(:sumSeries, mm),
162
+ m[m.keys.first], true)
163
+ else
164
+ handle_metric(mm, m[m.keys.first], true)
165
+ end
166
+ end.join(",")
167
+ else
168
+ metric = compose_metric(stat_name,
169
+ "{#{clusters.to_a.join(',')}}",
170
+ "{#{hosts.to_a.join(',')}}")
171
+ metric = handle_metric(metric, {}, true)
172
+ end
173
+ #######################
174
+ z[:key] = "global #{z[:key]}"
175
+ # target << handle_metric(translate(:sumSeries, metric), z)
176
+
177
+ if z.keys.member?('divideSeries') # special case
178
+ # apply divideSeries, sumSeries then other options
179
+ res = translate(:divideSeries, metric)
180
+ res = translate(:sumSeries, res)
181
+ z.delete(:divideSeries)
182
+ h = YAML::Omap.new
183
+ z.each { |k,v| h[k] = v unless k == 'divideSeries' }
184
+ target << handle_metric(res, h)
185
+ else
186
+ target << handle_metric(translate(:sumSeries, metric), z)
187
+ end
188
+ if !@params[:use_color] ||
189
+ (!z[:color] && @params[:use_color])
190
+ colors << next_color(colors, z[:color])
191
+ end
192
+ end # @params["targets"].each
193
+ elsif opts[:sum] == :cluster # one line per cluster/metric
194
+ clusters.each do |cluster|
195
+ @params["targets"].each do |stat_name, opts|
196
+ z = new_map(opts)
197
+
198
+ metrics = []
199
+ #######################
200
+ h = "{#{hosts.to_a.join(',')}}"
201
+ if stat_name.instance_of?(Array)
202
+ metrics << stat_name.map do |m|
203
+ mm = compose_metric(m.keys.first, cluster, h)
204
+ # note: we take the ratio of the sums in this case, instead of
205
+ # the sums of the ratios
206
+ if z.keys.member?('divideSeries')
207
+ # divideSeries is picky about the number of series given as
208
+ # arguments, so sum them in this case
209
+ handle_metric(translate(:sumSeries, mm),
210
+ m[m.keys.first], true)
211
+ else
212
+ handle_metric(mm, m[m.keys.first], true)
213
+ end
214
+ end.join(",")
215
+ else
216
+ metrics << handle_metric(compose_metric(stat_name,
217
+ cluster, h), {}, true)
218
+ end
219
+ #######################
220
+ z[:key] = "#{cluster} #{z[:key]}"
221
+
222
+ if z.keys.member?('divideSeries') # special case
223
+ # apply divideSeries, sumSeries then other options
224
+ res = translate(:divideSeries, metrics.join(','))
225
+ res = translate(:sumSeries, res)
226
+ z.delete(:divideSeries)
227
+ h = Map.new
228
+ z.each { |k,v| h[k] = v unless k == 'divideSeries' }
229
+ target << handle_metric(res, h)
230
+ else
231
+ target << handle_metric(translate(:sumSeries,
232
+ metrics.join(',')), z)
233
+ end
234
+
235
+ if !@params[:use_color] || (!z[:color] && @params[:use_color])
236
+ colors << next_color(colors, z[:color])
237
+ end
238
+ end # metrics.each
239
+ end # clusters.each
240
+ else # one line per {metric,host,colo}
241
+ @params["targets"].each do |stat_name, opts|
242
+ z = new_map(opts)
243
+ clusters.each do |cluster|
244
+ hosts.each do |host|
245
+ label = "#{host} #{z[:key]}"
246
+ #################
247
+ if stat_name.instance_of?(Array)
248
+ metric = stat_name.map do |m|
249
+ mm = compose_metric(m.keys.first, cluster, host)
250
+ handle_metric(mm, m[m.keys.first], true)
251
+ end.join(",")
252
+ else
253
+ metric = handle_metric(compose_metric(stat_name, cluster, host), {}, true)
254
+ end
255
+ #################
256
+
257
+ if label =~ /\*/
258
+ # for this particular type of graph, don't display a legend,
259
+ # and color with abandon
260
+ url_opts[:hideLegend] = true
261
+
262
+ z.delete(:color)
263
+ # fixme proper labeling... maybe
264
+ # With wildcards let graphite construct the legend (or not).
265
+ # Since we're handling wildcards we don't know how many
266
+ # hosts will match, so just put in the default color list.
267
+ # technically we do know, so this can be fixed
268
+ z.delete(:key)
269
+ target << handle_metric(metric, z)
270
+ colors.concat(@params[:default_colors]) if colors.empty?
271
+ else
272
+ z[:key] = "#{host}/#{cluster} #{z[:key]}"
273
+ target << handle_metric(metric, z)
274
+ if !@params[:use_color] ||
275
+ (!z[:color] && @params[:use_color])
276
+ colors << next_color(colors, z[:color])
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end # @params["targets"].each
282
+ end # if opts[:sum]
283
+
284
+ url_opts[:target] = target
285
+ url_opts[:colorList] = colors.join(",")
286
+
287
+ url = URI.join(@params[:graphite_url], "/render/?").to_s
288
+ url_parts = []
289
+ url_opts.each do |k, v|
290
+ [v].flatten.each do |v|
291
+ url_parts << "#{URI.escape(k.to_s)}=#{URI.escape(v.to_s)}"
292
+ end
293
+ end
294
+ url += url_parts.join("&")
295
+ return url
296
+ end
297
+
298
+ # return an array of all metrics matching the specifications in
299
+ # @params["targets"]
300
+ # metrics are arrays of fields (once delimited by periods)
301
+ def expand
302
+ url = URI.join(@params[:graphite_url], "/metrics/expand/?query=").to_s
303
+ metrics = []
304
+
305
+ require "json"
306
+ @params["targets"].each do |k, v|
307
+ # temporary hack for Map class complaining block parameters
308
+ metric = [k, v]
309
+ unless metric.first.instance_of?(Array)
310
+ # wrap it
311
+ metric[0] = [{metric[0] => nil}]
312
+ end
313
+ metric.first.each do |m|
314
+ composed = compose_metric(m.first.first, "*", "*")
315
+ composed2 = compose_metric2(m.first.first, "*", "*")
316
+ query = open("#{url}#{composed}").read
317
+ query2 = open("#{url}#{composed2}").read
318
+ results = JSON.parse(query)["results"]
319
+ results2 = JSON.parse(query2)["results"].map {|x| x.split('.')[0..-2].join('.')}
320
+ metrics << results - results2
321
+ end
322
+ end
323
+
324
+ return metrics.flatten.map { |x| x.split(".") }
325
+ end
326
+
327
+ def hosts_clusters
328
+ metrics = expand
329
+ clusters = Set.new
330
+
331
+ # find the indicies of the clusters and hosts
332
+ f = @params[:metric_format].dup.split("%m")
333
+ first = f.first.split(".")
334
+ last = f.last.split(".")
335
+ ci = hi = nil
336
+ first.each_with_index do |v, i|
337
+ ci = i if v.match("%c")
338
+ hi = i if v.match("%h")
339
+ end
340
+ unless ci && hi
341
+ last.reverse.each_with_index do |v, i|
342
+ ci = -1 -i if v.match("%c")
343
+ hi = -1 -i if v.match("%h")
344
+ end
345
+ end
346
+ hosts = metrics.map do |m|
347
+ Host.new(m[hi], m[ci], @params)
348
+ end.uniq
349
+
350
+ # filter by what matches the graph definition
351
+ hosts = hosts.select { |h| h.multi_match(@params["hosts"]) }
352
+ hosts.each { |h| clusters << h.cluster }
353
+
354
+ return hosts, clusters
355
+ end
356
+
357
+ private
358
+ def next_color(colors, preferred_color=nil)
359
+ default_colors = @params[:default_colors].clone
360
+
361
+ if preferred_color and !colors.member?(preferred_color)
362
+ return preferred_color
363
+ end
364
+
365
+ if preferred_color and ! default_colors.member?(preferred_color)
366
+ default_colors << preferred_color
367
+ end
368
+
369
+ weights = Hash.new(0)
370
+ colors.each do |c|
371
+ weights[c] += 1
372
+ end
373
+
374
+ i = 0
375
+ loop do
376
+ default_colors.each do |c|
377
+ return c if weights[c] == i
378
+ end
379
+ i += 1
380
+ end
381
+ end
382
+
383
+ # if Map() in config.rb converted a YAML::Omap into an array, we
384
+ # need to turn it back into a Map (which is ordered, conveniently)
385
+ def new_map (opts)
386
+ if opts.is_a?(Array)
387
+ z = Map(opts.flatten)
388
+ else
389
+ z = Map(opts)
390
+ end
391
+ end
392
+ end # Dash::Models::Graph
393
+ end
394
+ end # Pencil::Models
@@ -0,0 +1,89 @@
1
+ require "pencil/models/base"
2
+ require "pencil/models/graph"
3
+
4
+ module Pencil
5
+ module Models
6
+ class Host < Base
7
+ attr_accessor :graphs
8
+ attr_reader :cluster
9
+
10
+ def initialize(name, cluster, params={})
11
+ super(name, params)
12
+ @cluster = cluster
13
+ # hack for the case where colo{1,2}.host1 both exist
14
+ @@objects[self.class.to_s].delete(name)
15
+ @@objects[self.class.to_s]["#{cluster}#{name}"] = self
16
+
17
+ @graphs = []
18
+ Graph.each do |graph_name, graph|
19
+ graph["hosts"].each do |h|
20
+ if match(h)
21
+ @graphs << graph
22
+ break
23
+ end
24
+ end # graph["hosts"].each
25
+ end # Graph.each
26
+ end
27
+
28
+ def self.find(name)
29
+ return @@objects[self.name][key] rescue []
30
+ end
31
+
32
+ def key
33
+ "#{@cluster}#{@name}"
34
+ end
35
+
36
+ def eql?(other)
37
+ key == other.key
38
+ end
39
+
40
+ def ==(other)
41
+ key == other.key
42
+ end
43
+
44
+ def <=>(other)
45
+ if @params[:host_sort] == "builtin"
46
+ return key <=> other.key
47
+ elsif @params[:host_sort] == "numeric"
48
+ regex = /\d+/
49
+ match = @name.match(regex)
50
+ match2 = other.name.match(regex)
51
+ if match.pre_match != match2.pre_match
52
+ return match.pre_match <=> match2.pre_match
53
+ else
54
+ return match[0].to_i <=> match2[0].to_i
55
+ end
56
+ else
57
+ # http://www.bofh.org.uk/2007/12/16/comprehensible-sorting-in-ruby
58
+ sensible = lambda do |k|
59
+ k.to_s.split(
60
+ /((?:(?:^|\s)[-+])?(?:\.\d+|\d+(?:\.\d+?(?:[eE]\d+)?(?:$|(?![eE\.])))?))/ms
61
+ ).map { |v| Float(v) rescue v.downcase }
62
+ end
63
+ return sensible.call(self) <=> sensible.call(other)
64
+ end
65
+ end
66
+
67
+ def hash
68
+ key.hash
69
+ end
70
+
71
+ def self.find_by_name_and_cluster(name, cluster)
72
+ Host.each do |host_name, host|
73
+ next unless host.name == name
74
+ return host if host.cluster == cluster
75
+ end
76
+ return nil
77
+ end
78
+
79
+ def self.find_by_cluster(cluster)
80
+ ret = []
81
+ Host.each do |name, host|
82
+ ret << host if host.cluster == cluster
83
+ end
84
+ return ret
85
+ end
86
+
87
+ end # Pencil::Models::Host
88
+ end # Pencil::Models
89
+ end
@@ -0,0 +1,3 @@
1
+ require "pencil/models/dashboard"
2
+ require "pencil/models/graph"
3
+ require "pencil/models/host"
@@ -3,6 +3,7 @@ body {
3
3
  color: #fff;
4
4
  margin: 0px;
5
5
  font-family: Verdana, Arial, Tahoma, sans-serif;
6
+ min-width: 1200px;
6
7
  }
7
8
 
8
9
  #header {
File without changes
File without changes
File without changes
File without changes