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
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