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
data/bin/pencil CHANGED
@@ -1,9 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- $: << File.dirname($0) + "/../lib"
2
+ require 'pencil'
3
3
 
4
- require File.join(File.dirname(__FILE__), "..", "VERSION")
5
- require "dash"
4
+ Pencil::App.run!
6
5
 
7
- STDOUT.puts "pencil v#{PENCIL_VERSION} on port #{Dash::App.port}, PID #{$$}"
8
-
9
- Rack::Handler::Mongrel.run(Dash::App, :Port => Dash::App.port)
@@ -203,7 +203,9 @@ Most of these options take a single argument.
203
203
  * threshold
204
204
  * color
205
205
 
206
- Note: key is interpreted differently from the other options, which are more
206
+ Note: key and color are applied after all other options are applied.
207
+
208
+ key is interpreted differently from the other options, which are more
207
209
  simply translated.
208
210
 
209
211
  color's interpretation depends on whether :use_color is set.
@@ -1,9 +1,9 @@
1
- require "rubygems"
2
- require "models"
1
+ require 'map'
2
+ require "pencil/models"
3
3
 
4
- module Dash
4
+ module Pencil
5
5
  class Config
6
- include Dash::Models
6
+ include Pencil::Models
7
7
 
8
8
  attr_reader :dashboards
9
9
  attr_reader :graphs
@@ -32,11 +32,31 @@ module Dash
32
32
  end
33
33
 
34
34
  def reload!
35
- configs = Dir.glob("#{@confdir}/*.y{a,}ml")
36
- configs.each { |c| @rawconfig.merge!(YAML.load(File.read(c))) }
35
+ configs = Dir.glob("#{@confdir}/**/*.y{a,}ml")
36
+ configs.each do |c|
37
+ yml = YAML.load(File.read(c))
38
+ next unless yml
39
+ @rawconfig[:config] = yml[:config] if yml[:config]
40
+ a = @rawconfig[:dashboards]
41
+ b = yml[:dashboards]
42
+ c = @rawconfig[:graphs]
43
+ d = yml[:graphs]
44
+
45
+ if a && b
46
+ a.merge!(b)
47
+ elsif b
48
+ @rawconfig[:dashboards] = b
49
+ end
50
+ if c && d
51
+ c.merge!(d)
52
+ elsif d
53
+ @rawconfig[:graphs] = d
54
+ end
55
+ end
56
+ @rawconfig = Map(@rawconfig)
37
57
 
38
58
  [:graphs, :dashboards, :config].each do |c|
39
- if not @rawconfig[c]
59
+ if not @rawconfig[c.to_s]
40
60
  raise "Missing config name '#{c.to_s}'"
41
61
  end
42
62
  end
@@ -99,5 +119,5 @@ module Dash
99
119
  @dashboards, @graphs = dashboards_new, graphs_new
100
120
  @hosts, @clusters = hosts_new, clusters_new
101
121
  end
102
- end # Dash::Config
103
- end # Dash
122
+ end # Pencil::Config
123
+ end # Pencil
@@ -0,0 +1,2 @@
1
+ require 'pencil'
2
+ run Pencil::App
@@ -0,0 +1,211 @@
1
+ module Pencil
2
+ module Helpers
3
+ include Pencil::Models
4
+
5
+ @@prefs = [["Start Time", "start"],
6
+ ["Duration", "duration"],
7
+ ["Width", "width"],
8
+ ["Height", "height"]]
9
+
10
+ # convert keys to symbols before lookup
11
+ def param_lookup(name)
12
+ sym_hash = {}
13
+ session.each { |k,v| sym_hash[k.to_sym] = v unless v.empty? }
14
+ params.each { |k,v| sym_hash[k.to_sym] = v unless v.empty? }
15
+ settings.config.global_config[:url_opts].merge(sym_hash)[name.to_sym]
16
+ end
17
+
18
+ def cluster_graph(g, cluster, title="wtf")
19
+ image_url = \
20
+ @dash.render_cluster_graph(g, cluster,
21
+ :title => title,
22
+ :dynamic_url_opts => merge_opts)
23
+ zoom_url = cluster_graph_link(@dash, g, cluster)
24
+ return image_url, zoom_url
25
+ end
26
+
27
+ def cluster_graph_link(dash, g, cluster)
28
+ link = dash.graph_opts[g]["click"] ||
29
+ "/dash/#{cluster}/#{dash.name}/#{g.name}"
30
+ return append_query_string(link)
31
+ end
32
+
33
+ def cluster_zoom_graph(g, cluster, host, title)
34
+ image_url = g.render_url([host.name], [cluster], :title => title,
35
+ :dynamic_url_opts => merge_opts)
36
+ zoom_url = cluster_zoom_link(cluster, host)
37
+ return image_url, zoom_url
38
+ end
39
+
40
+ def cluster_zoom_link(cluster, host)
41
+ return append_query_string("/host/#{cluster}/#{host}")
42
+ end
43
+
44
+ def suggest_cluster_links(clusters, g)
45
+ links = []
46
+ clusters.each do |c|
47
+ href = append_query_string("/dash/#{c}/#{params[:dashboard]}/#{g.name}")
48
+ links << "<a href=\"#{href}\">#{c}</a>"
49
+ end
50
+ return "zoom (" + links.join(", ") + ")"
51
+ end
52
+
53
+ def suggest_dashboards_links(host, graph)
54
+ suggested = suggest_dashboards(host, graph)
55
+ return "" if suggested.length == 0
56
+
57
+ links = []
58
+ suggested.each do |d|
59
+ links << "<a href=\"/dash/#{host.cluster}/#{append_query_string(d)}\">" +
60
+ "#{d}</a>"
61
+ end
62
+ return "(" + links.join(", ") + ")"
63
+ end
64
+
65
+ # it's mildly annoying that when this set is empty there're no uplinks
66
+ # consider adding a link up to the cluster (which is best we can do)
67
+ def suggest_dashboards(host, graph)
68
+ ret = Set.new
69
+
70
+ host.graphs.each do |g|
71
+ Dashboard.find_by_graph(g).each do |d|
72
+ valid, _ = d.get_valid_hosts(g, host['cluster'])
73
+ ret << d.name if valid.member?(host)
74
+ end
75
+ end
76
+
77
+ return ret
78
+ end
79
+
80
+ # generate the input box fields, filled in to current parameters if specified
81
+ def input_boxes
82
+ @prefs = @@prefs
83
+ erb :'partials/input_boxes', :layout => false
84
+ end
85
+
86
+ def dash_link(dash, cluster)
87
+ return append_query_string("/dash/#{cluster}/#{dash.name}")
88
+ end
89
+
90
+ def cluster_link(cluster)
91
+ return append_query_string("/dash/#{cluster}")
92
+ end
93
+
94
+ # fixme this isn't used anymore, but should
95
+ def css_url
96
+ style = File.join(settings.root, "public/style.css")
97
+ mtime = File.mtime(style).to_i.to_s
98
+ return \
99
+ %Q[<link href="/style.css?#{mtime}" rel="stylesheet" type="text/css">]
100
+ end
101
+
102
+ def refresh
103
+ if settings.config.global_config[:refresh_rate] != false && nowish
104
+ rate = settings.config.global_config[:refresh_rate] || 60
105
+ return %Q[<meta http-equiv="refresh" content="#{rate}">]
106
+ end
107
+ end
108
+
109
+ def hosts_selector(hosts, print_clusters=false)
110
+ @print_clusters = print_clusters
111
+ @hosts = hosts
112
+ erb :'partials/hosts_selector', :layout => false
113
+ end
114
+
115
+ def append_query_string(str)
116
+ v = str.dup
117
+ (v << "?#{request.query_string}") unless request.query_string.empty?
118
+ return v
119
+ end
120
+
121
+ def merge_opts
122
+ static_opts = ["cluster", "dashboard", "zoom", "host", "session_id"]
123
+ opts = params.dup
124
+ session.merge(opts).delete_if { |k,v| static_opts.member?(k) || v.empty? }
125
+ end
126
+
127
+ def shortcuts(str)
128
+ @str = str
129
+ erb :'partials/shortcuts', :layout => false
130
+ end
131
+ def cluster_switcher(clusters)
132
+ @clusters = clusters
133
+ erb :'partials/cluster_switcher', :layout => false
134
+ end
135
+
136
+ def dash_switcher
137
+ erb :'partials/dash_switcher', :layout => false
138
+ end
139
+
140
+ def graph_switcher
141
+ erb :'partials/graph_switcher', :layout => false
142
+ end
143
+
144
+ def cluster_selector
145
+ @clusters = settings.config.clusters.sort + ["global"]
146
+ erb :'partials/cluster_selector', :layout => false
147
+ end
148
+
149
+ def host_uplink
150
+ link = "/dash/#{append_query_string(@host.cluster)}"
151
+ "zoom out: <a href=\"#{link}\">#{@host.cluster}</a>"
152
+ end
153
+
154
+ def graph_uplink
155
+ link = append_query_string(request.path.split("/")[0..-2].join("/"))
156
+ "zoom out: <a href=\"#{link}\">#{@dash}</a>"
157
+ end
158
+
159
+ def dash_uplink
160
+ link = append_query_string(request.path.split("/")[0..-2].join("/"))
161
+ "zoom out: <a href=\"#{link}\">#{@params[:cluster]}</a>"
162
+ end
163
+
164
+ # fixme this is a hack
165
+ def header(str)
166
+ <<-FOO
167
+ <div class="path_container">
168
+ <h2>#{@title}</h2>
169
+ <span class="zoom">
170
+ #{str}
171
+ </span>
172
+ </div>
173
+ <div id="timeslice_container">
174
+ <h3 id="timeslice">#{range_string}</h3>
175
+ #{permalink unless @no_graphs}
176
+ </div>
177
+ FOO
178
+ end
179
+
180
+ def nowish
181
+ if settings.config.global_config[:now_threshold] == false
182
+ return false
183
+ end
184
+ threshold = settings.config.global_config[:now_threshold] || 300
185
+ return @request_time.to_i - @etime.to_i < threshold
186
+ end
187
+
188
+ def range_string
189
+ format = settings.config.global_config[:date_format] || "%X %x"
190
+ if @stime && @etime
191
+ if nowish
192
+ "timeslice: from #{@stime.strftime(format)}"
193
+ else
194
+ "timeslice: #{@stime.strftime(format)} - #{@etime.strftime(format)}"
195
+ end
196
+ else
197
+ "invalid time range"
198
+ end
199
+
200
+ end
201
+
202
+ def permalink
203
+ return "" unless @stime && @duration
204
+ format = "%F %T" # chronic REALLY understands this
205
+ url = request.path + "?"
206
+ url << "&start=#{@stime.strftime(format)}"
207
+ url << "&duration=#{ChronicDuration.output(@duration)}"
208
+ "<a id=\"permalink\" href=\"#{url}\">permalink</a>"
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,78 @@
1
+ module Pencil
2
+ module Models
3
+ class Base
4
+ @@objects = Hash.new { |h, k| h[k] = Hash.new }
5
+ attr_reader :name
6
+
7
+ def initialize(name, params={})
8
+ @name = name
9
+ @match_name = name
10
+ @params = params
11
+ @@objects[self.class.to_s][name] = self
12
+ end
13
+
14
+ def self.find(name)
15
+ return @@objects[self.name][name] rescue []
16
+ end
17
+
18
+ def self.each(&block)
19
+ h = @@objects[self.name] rescue {}
20
+ h.each { |k, v| yield(k, v) }
21
+ end
22
+
23
+ def self.all
24
+ return @@objects[self.name].values
25
+ end
26
+
27
+ def [](key)
28
+ return @params[key] rescue []
29
+ end
30
+
31
+ def match(glob)
32
+ return true if glob == '*'
33
+ # convert glob to a regular expression
34
+ glob_re = /^#{Regexp.escape(glob).gsub('\*', '.*').gsub('\#', '\d+')}$/
35
+ return @match_name.match(glob_re)
36
+ end
37
+
38
+ def multi_match(globs)
39
+ ret = false
40
+
41
+ globs.each do |glob|
42
+ ret = match(glob)
43
+ break if ret
44
+ end
45
+
46
+ return ret
47
+ end
48
+
49
+ def to_s
50
+ return @name
51
+ end
52
+
53
+ def <=>(other)
54
+ return to_s <=> other.to_s
55
+ end
56
+
57
+ def update_params(hash)
58
+ @params.merge!(hash)
59
+ end
60
+
61
+ # compose a metric using a :metric_format
62
+ # format string with %c for metric, %c for cluster, and %h for host
63
+ def compose_metric (m, c, h)
64
+ @params[:metric_format].dup.gsub("%m", m).gsub("%c", c).gsub("%h", h)
65
+ end
66
+
67
+ # used to make sure partial metrics are not considered
68
+ # e.g. foo.bar.baz.colo1.host1 but not
69
+ # foo.bar.baz.subkey.subkey2[.colo1.host1]
70
+ # where the latter is returned by the graphite expand api but not as a full
71
+ # metric
72
+ # essentially equivalent to matching like /[:METRIC:]$/
73
+ def compose_metric2 (m, c, h)
74
+ compose_metric(m, c, h) + ".*"
75
+ end
76
+ end # Pencil::Models::Base
77
+ end
78
+ end # Pencil::Models
@@ -0,0 +1,124 @@
1
+ require "pencil/models/base"
2
+ require "pencil/models/graph"
3
+ require "pencil/models/host"
4
+ require "set"
5
+
6
+ module Pencil
7
+ module Models
8
+ class Dashboard < Base
9
+ attr_accessor :graphs
10
+ attr_accessor :graph_opts
11
+
12
+ def initialize(name, params={})
13
+ super
14
+
15
+ @graphs = []
16
+ @graph_opts = {}
17
+ params["graphs"].each do |name|
18
+ # graphs map to option hashes
19
+ if name.respond_to?(:keys) # could be YAML::Omap
20
+ g = Graph.find(name.keys.first) # should only be one key
21
+ @graph_opts[g] = name[name.keys.first]||{}
22
+ else
23
+ raise "Bad format for graph (must be a hash-y; #{name.class}:#{name.inspect} is not)"
24
+ end
25
+
26
+ @graphs << g if g
27
+ end
28
+
29
+ @valid_hosts_table = {} # cache calls to get_valid_hosts
30
+ end
31
+
32
+ def clusters
33
+ clusters = Set.new
34
+ @graphs.each { |g| clusters += get_valid_hosts(g)[1] }
35
+ clusters.sort
36
+ end
37
+
38
+ def get_all_hosts(cluster=nil)
39
+ hosts = Set.new
40
+ clusters = Set.new
41
+ @graphs.each do |g|
42
+ h, c = get_valid_hosts(g, cluster)
43
+ hosts += h
44
+ clusters += c
45
+ end
46
+ return hosts, clusters
47
+ end
48
+
49
+ def get_valid_hosts(graph, cluster=nil)
50
+ if @valid_hosts_table[[graph, cluster]]
51
+ return @valid_hosts_table[[graph, cluster]]
52
+ end
53
+
54
+ clusters = Set.new
55
+ if cluster
56
+ hosts = Host.find_by_cluster(cluster)
57
+ else
58
+ hosts = Host.all
59
+ end
60
+
61
+ # filter as:
62
+ # - the dashboard graph hosts definition
63
+ # - the dashboard hosts definition
64
+ # - the graph hosts definition
65
+ # this is new behavior: before the filters were additive
66
+ filter = graph_opts[graph]["hosts"] || @params["hosts"] || graph["hosts"]
67
+ if filter
68
+ hosts = hosts.select { |h| h.multi_match(filter) }
69
+ end
70
+
71
+ hosts.each { |h| clusters << h.cluster }
72
+
73
+ @valid_hosts_table[[graph, cluster]] = [hosts, clusters]
74
+ return hosts, clusters
75
+ end
76
+
77
+ def render_cluster_graph(graph, clusters, opts={})
78
+ # FIXME: edge case where the dash filter does not filter to a subset of
79
+ # the hosts filter
80
+
81
+ hosts = get_host_wildcards(graph)
82
+
83
+ # graphite doesn't support strict matching (as /\d+/), so we need to
84
+ # enumerate the hosts if a "#" wildcard is found
85
+ if ! (filter = hosts.select { |h| h =~ /#/ }).empty?
86
+ hosts_new = hosts - filter
87
+ hosts2 = Host.all.select { |h| h.multi_match(filter) }
88
+ hosts = (hosts2.map {|h| h.name } + hosts_new).sort.uniq.join(',')
89
+ end
90
+
91
+ opts[:sum] = :cluster unless opts[:zoom]
92
+ graph_url = graph.render_url(hosts.to_a, clusters, opts)
93
+ return graph_url
94
+ end
95
+
96
+ def get_host_wildcards(graph)
97
+ return graph_opts[graph]["hosts"] || @params["hosts"] || graph["hosts"]
98
+ end
99
+
100
+ def render_global_graph(graph, opts={})
101
+ hosts = get_host_wildcards(graph)
102
+ _, clusters = get_valid_hosts(graph)
103
+
104
+ next_url = ""
105
+ type = opts[:zoom] ? :cluster : :global
106
+ options = opts.merge({:sum => type})
107
+ graph_url = graph.render_url(hosts, clusters, options)
108
+ return graph_url
109
+ end
110
+
111
+ def self.find_by_graph(graph)
112
+ ret = []
113
+ Dashboard.each do |name, dash|
114
+
115
+ if dash["graphs"].map { |x| x.keys.first }.member?(graph.name)
116
+ ret << dash
117
+ end
118
+ end
119
+
120
+ return ret
121
+ end
122
+ end # Pencil::Models::Dashboard
123
+ end
124
+ end # Pencil::Models