pencil 0.2.10 → 0.3.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.
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