pencil 0.2.0 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/pencil +1 -1
- data/docs/pencil_options.md +176 -0
- data/lib/config.rb +103 -0
- data/lib/config.ru +2 -0
- data/lib/dash.rb +153 -0
- data/lib/helpers.rb +197 -0
- data/lib/models/base.rb +73 -0
- data/lib/models/dashboard.rb +106 -0
- data/lib/models/graph.rb +304 -0
- data/lib/models/host.rb +82 -0
- data/lib/models.rb +3 -0
- data/lib/namespace.rb +4 -0
- data/lib/public/favicon.ico +0 -0
- data/lib/public/style.css +97 -0
- data/lib/views/cluster.erb +23 -0
- data/lib/views/dash-cluster-zoom.erb +36 -0
- data/lib/views/dash-cluster.erb +18 -0
- data/lib/views/dash-global-zoom.erb +30 -0
- data/lib/views/dash-global.erb +20 -0
- data/lib/views/global.erb +31 -0
- data/lib/views/host.erb +14 -0
- data/lib/views/layout.erb +55 -0
- data/lib/views/partials/cluster_selector.erb +6 -0
- data/lib/views/partials/cluster_switcher.erb +12 -0
- data/lib/views/partials/cookies_form.erb +12 -0
- data/lib/views/partials/dash_switcher.erb +8 -0
- data/lib/views/partials/graph_switcher.erb +8 -0
- data/lib/views/partials/hosts_selector.erb +7 -0
- data/lib/views/partials/input_boxes.erb +25 -0
- data/lib/views/partials/refresh_button.erb +8 -0
- metadata +52 -10
data/bin/pencil
CHANGED
@@ -0,0 +1,176 @@
|
|
1
|
+
# Pencil Options
|
2
|
+
Pencil configuration files are written in YAML. When pencil starts up, it
|
3
|
+
searches for these files and loads them. See the main README.md for how
|
4
|
+
these files should look.
|
5
|
+
|
6
|
+
## General Configuration
|
7
|
+
|
8
|
+
These are options that go under the :config key in pencil configuration files.
|
9
|
+
|
10
|
+
* :graphite_url [String, required, no default]
|
11
|
+
|
12
|
+
The url of your graphite instance.
|
13
|
+
|
14
|
+
* :url_opts [Hash, required, no default]
|
15
|
+
|
16
|
+
A map of default graph options.
|
17
|
+
|
18
|
+
In addition to <a href="#gopts">graph-level options</a>, an important default option
|
19
|
+
you should set under :url_opts is
|
20
|
+
|
21
|
+
:start: TIMESPEC
|
22
|
+
|
23
|
+
TIMESPEC should be a
|
24
|
+
[chronic](http://chronic.rubyforge.org/)-parsable time, and to be useful
|
25
|
+
should be relative to the current time (e.g. "8 hours ago")
|
26
|
+
|
27
|
+
* :refresh_rate [Fixnum, optional, default 60]
|
28
|
+
|
29
|
+
How often to refresh a changing view, in seconds.
|
30
|
+
|
31
|
+
This doesn't apply to timeslices that aren't varying (i.e not current, see
|
32
|
+
<a href="#threshold">:now_threshold</a>).
|
33
|
+
|
34
|
+
Set this to false to disable automatic refreshing.
|
35
|
+
|
36
|
+
* :host_sort [["builtin", "numeric", "sensible"], optional, default "sensible"]
|
37
|
+
|
38
|
+
Set to "builtin" to sort using ruby's builtin String sort.
|
39
|
+
|
40
|
+
Set to "numeric" to sort hosts numerically (i.e. match secondarily on the
|
41
|
+
first \d+).
|
42
|
+
|
43
|
+
Set to "sensible" if you want to sort like this:
|
44
|
+
|
45
|
+
http://www.bofh.org.uk/2007/12/16/comprehensible-sorting-in-ruby
|
46
|
+
|
47
|
+
* :quantum [Fixnum, optional, no default value]
|
48
|
+
|
49
|
+
Map requests to NUM second intervals. Pencil floors request times to the
|
50
|
+
minute, and does some modular arithmetic to do this mapping. This is
|
51
|
+
especially useful for implementing a caching layer, so that many requests
|
52
|
+
coming in near-simultaneously won't require graphite to generate different
|
53
|
+
images for each request.
|
54
|
+
|
55
|
+
Adding &noq=1 to a pencil url will disable this in case you need
|
56
|
+
super-granularity for some reason, but you didn't hear it from me.
|
57
|
+
|
58
|
+
* :date_format [String, optional, default "%X %x"]
|
59
|
+
|
60
|
+
strftime format for displaying dates.
|
61
|
+
|
62
|
+
* :metric_format [String, optional, default "%m.%c.%h"]
|
63
|
+
|
64
|
+
The format your graphite metrics are stored in. For pencil to work your
|
65
|
+
metrics need to be composed of three distinct pieces, concatenated in some
|
66
|
+
regular fashion. The format strings are
|
67
|
+
|
68
|
+
* %m metric
|
69
|
+
* %c cluster
|
70
|
+
* %h host
|
71
|
+
|
72
|
+
If you want a literal %[mch] in your metric format string you likely have
|
73
|
+
bigger problems than not being able to do so.
|
74
|
+
|
75
|
+
* <a name="threshold"/> :now\_threshold: [Fixnum, optional, default 300]
|
76
|
+
|
77
|
+
How many seconds before Time.now an end time is considered to still be 'now',
|
78
|
+
for the purposes of adding meta-refresh and displaying time intervals.
|
79
|
+
|
80
|
+
## <a name="gopts"/> Graph-level Options
|
81
|
+
This is a list of the supported graph-level options for pencil, which
|
82
|
+
correspond to request(image)-level options for graphite. These options are
|
83
|
+
key-value pairs, and are passed directly to graphite. Here is the list, with
|
84
|
+
minor annotations:
|
85
|
+
|
86
|
+
* vtitle: String (y-axis label)
|
87
|
+
* yMin: Fixnum
|
88
|
+
* yMax: Fixnum
|
89
|
+
* lineWidth: Fixnum (line thickness in pixels)
|
90
|
+
* areaMode: \[first, all, stacked\] (see graphite documentation)
|
91
|
+
* template: \[noc, alphas\] (alphas inverts colors)
|
92
|
+
* lineMode: staircase
|
93
|
+
* bgcolor: String
|
94
|
+
* graphOnly: bool (hide legend, axes, grid)
|
95
|
+
* hideAxes: bool
|
96
|
+
* hideGrid: bool
|
97
|
+
* hideLegend: bool
|
98
|
+
* fgcolor: String
|
99
|
+
* fontSize: Fixnum
|
100
|
+
* fontName: String (see your graphite instance for available fonts)
|
101
|
+
* fontItalic: bool
|
102
|
+
* fontBold: bool
|
103
|
+
|
104
|
+
## Target-level Options
|
105
|
+
This is a list of the supported target-level options for pencil. These are
|
106
|
+
mosly a list of transformations graphite supports, including summation and
|
107
|
+
scaling of metrics. You can apply them to individual metrics, or lists of
|
108
|
+
metrics. See the example configs for how this works. Also see the graphite
|
109
|
+
composer for the effects of these options, many of which are untested.
|
110
|
+
|
111
|
+
### Combinations
|
112
|
+
These functions take an arbitrary number of targets (usually simple metrics)
|
113
|
+
for arguments.
|
114
|
+
|
115
|
+
* sumSeries
|
116
|
+
* averageSeries
|
117
|
+
* minSeries
|
118
|
+
* maxSeries
|
119
|
+
* group
|
120
|
+
|
121
|
+
### Transformations
|
122
|
+
Some of these options take a single argument.
|
123
|
+
|
124
|
+
* scale
|
125
|
+
* offset
|
126
|
+
* derivative
|
127
|
+
* integral
|
128
|
+
* nonNegativeDerivative
|
129
|
+
* log BASE
|
130
|
+
* timeShift
|
131
|
+
* summarize
|
132
|
+
* hitcount
|
133
|
+
|
134
|
+
### Calculations
|
135
|
+
These functions take an arbitrary number of targets (usually simple metrics)
|
136
|
+
for arguments.
|
137
|
+
|
138
|
+
* movingAverage
|
139
|
+
* stdev
|
140
|
+
* asPercent
|
141
|
+
* diffSeries
|
142
|
+
* ratio
|
143
|
+
|
144
|
+
### Filters
|
145
|
+
Most of these options take a single argument.
|
146
|
+
|
147
|
+
* highestCurrent
|
148
|
+
* lowestCurrent
|
149
|
+
* nPercentile
|
150
|
+
* currentAbove
|
151
|
+
* currentBelow
|
152
|
+
* highestAverage
|
153
|
+
* lowestAverage
|
154
|
+
* averageAbove
|
155
|
+
* averageBelow
|
156
|
+
* maximumAbove
|
157
|
+
* maximumBelow
|
158
|
+
* sortByMaxima
|
159
|
+
* minimalist
|
160
|
+
* limit
|
161
|
+
* exclude
|
162
|
+
|
163
|
+
### Special Operations
|
164
|
+
* alias
|
165
|
+
* key (alias for alias)
|
166
|
+
* cumulative
|
167
|
+
* drawAsInfinite
|
168
|
+
* lineWidth
|
169
|
+
* dashed
|
170
|
+
* keepLastValue
|
171
|
+
* substr
|
172
|
+
* threshold
|
173
|
+
* color
|
174
|
+
|
175
|
+
Note: key and color are interpreted differently from the other options, which
|
176
|
+
are more simply translated.
|
data/lib/config.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "models"
|
3
|
+
|
4
|
+
module Dash
|
5
|
+
class Config
|
6
|
+
include Dash::Models
|
7
|
+
|
8
|
+
attr_reader :dashboards
|
9
|
+
attr_reader :graphs
|
10
|
+
attr_reader :hosts
|
11
|
+
attr_reader :clusters
|
12
|
+
attr_reader :global_config
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
port = 9292
|
16
|
+
@rawconfig = {}
|
17
|
+
@confdir = "."
|
18
|
+
|
19
|
+
optparse = OptionParser.new do |o|
|
20
|
+
o.on("-d", "--config-dir DIR",
|
21
|
+
"location of the config directory (default .)") do |arg|
|
22
|
+
@confdir = arg
|
23
|
+
end
|
24
|
+
o.on("-p", "--port PORT", "port to bind to (default 9292)") do |arg|
|
25
|
+
port = arg.to_i
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
optparse.parse!
|
30
|
+
reload!
|
31
|
+
@global_config[:port] = port
|
32
|
+
end
|
33
|
+
|
34
|
+
def reload!
|
35
|
+
configs = Dir.glob("#{@confdir}/*.y{a,}ml")
|
36
|
+
configs.each { |c| @rawconfig.merge!(YAML.load(File.read(c))) }
|
37
|
+
|
38
|
+
[:graphs, :dashboards, :config].each do |c|
|
39
|
+
if not @rawconfig[c]
|
40
|
+
raise "Missing config name '#{c.to_s}'"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@global_config = @rawconfig[:config]
|
45
|
+
# do some sanity checking of other configuration parameters
|
46
|
+
[:graphite_url, :url_opts].each do |c|
|
47
|
+
if not @global_config[c]
|
48
|
+
raise "Missing config name '#{c.to_s}'"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# possibly check more url_opts here as well
|
53
|
+
if @global_config[:url_opts][:start]
|
54
|
+
if !ChronicDuration.parse(@global_config[:url_opts][:start])
|
55
|
+
raise "bad default timespec in :url_opts"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
@global_config[:default_colors] ||=
|
60
|
+
["blue", "green", "yellow", "red", "purple", "brown", "aqua", "gold"]
|
61
|
+
|
62
|
+
if @global_config[:refresh_rate]
|
63
|
+
duration = ChronicDuration.parse(@global_config[:refresh_rate].to_s)
|
64
|
+
if !duration
|
65
|
+
raise "couldn't parse key :refresh_rate"
|
66
|
+
end
|
67
|
+
@global_config[:refresh_rate] = duration
|
68
|
+
end
|
69
|
+
|
70
|
+
@global_config[:metric_format] ||= "%m.%c.%h"
|
71
|
+
if @global_config[:metric_format] !~ /%m/
|
72
|
+
raise "missing metric (%m) in :metric_format"
|
73
|
+
elsif @global_config[:metric_format] !~ /%c/
|
74
|
+
raise "missing cluster (%c) in :metric_format"
|
75
|
+
elsif @global_config[:metric_format] !~ /%h/
|
76
|
+
raise "missing host (%h) in :metric_format"
|
77
|
+
end
|
78
|
+
|
79
|
+
graphs_new = []
|
80
|
+
@rawconfig[:graphs].each do |name, config|
|
81
|
+
graphs_new << Graph.new(name, config.merge(@global_config))
|
82
|
+
end
|
83
|
+
|
84
|
+
dashboards_new = []
|
85
|
+
@rawconfig[:dashboards].each do |name, config|
|
86
|
+
dashboards_new << Dashboard.new(name, config.merge(@global_config))
|
87
|
+
end
|
88
|
+
|
89
|
+
hosts_new = Set.new
|
90
|
+
clusters_new = Set.new
|
91
|
+
|
92
|
+
# generate host and cluster information at init time
|
93
|
+
graphs_new.each do |g|
|
94
|
+
hosts, clusters = g.hosts_clusters
|
95
|
+
hosts.each { |h| hosts_new << h }
|
96
|
+
clusters.each { |h| clusters_new << h }
|
97
|
+
end
|
98
|
+
|
99
|
+
@dashboards, @graphs = dashboards_new, graphs_new
|
100
|
+
@hosts, @clusters = hosts_new, clusters_new
|
101
|
+
end
|
102
|
+
end # Dash::Config
|
103
|
+
end # Dash
|
data/lib/config.ru
ADDED
data/lib/dash.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "namespace"
|
5
|
+
|
6
|
+
require "config"
|
7
|
+
require "erb"
|
8
|
+
require "helpers"
|
9
|
+
require "models"
|
10
|
+
require "rack"
|
11
|
+
require "sinatra/base"
|
12
|
+
require "json"
|
13
|
+
require "open-uri"
|
14
|
+
require "yaml"
|
15
|
+
require "chronic"
|
16
|
+
require "chronic_duration"
|
17
|
+
require "optparse"
|
18
|
+
|
19
|
+
# fixme style.css isn't actually cached, you need to set something up with
|
20
|
+
# rack to cache static files
|
21
|
+
|
22
|
+
$:.unshift(File.dirname(__FILE__))
|
23
|
+
|
24
|
+
module Dash
|
25
|
+
class App < Sinatra::Base
|
26
|
+
include Dash::Models
|
27
|
+
helpers Dash::Helpers
|
28
|
+
config = Dash::Config.new
|
29
|
+
set :config, config
|
30
|
+
set :port, config.global_config[:port]
|
31
|
+
set :run, true
|
32
|
+
use Rack::Session::Cookie, :expire_after => 126227700 # 4 years
|
33
|
+
set :root, File.dirname(__FILE__)
|
34
|
+
set :static, true
|
35
|
+
|
36
|
+
def initialize(settings={})
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
before do
|
41
|
+
session[:not] #fixme kludge is back
|
42
|
+
@request_time = Time.now
|
43
|
+
@dashboards = Dashboard.all
|
44
|
+
@no_graphs = false
|
45
|
+
# time stuff
|
46
|
+
start = param_lookup("start")
|
47
|
+
duration = param_lookup("duration")
|
48
|
+
@stime = Chronic.parse(start)
|
49
|
+
if @stime
|
50
|
+
@stime -= @stime.sec unless @params["noq"]
|
51
|
+
end
|
52
|
+
if duration
|
53
|
+
@duration = ChronicDuration.parse(duration)
|
54
|
+
else
|
55
|
+
@duration = @request_time.to_i - @stime.to_i
|
56
|
+
end
|
57
|
+
|
58
|
+
unless @params["noq"]
|
59
|
+
@duration -= (@duration % settings.config.global_config[:quantum]||1)
|
60
|
+
end
|
61
|
+
|
62
|
+
if @stime
|
63
|
+
@etime = Time.at(@stime + @duration)
|
64
|
+
@etime = @request_time if @etime > @request_time
|
65
|
+
else
|
66
|
+
@etime = @request_time
|
67
|
+
end
|
68
|
+
|
69
|
+
params[:stime] = @stime.to_i.to_s
|
70
|
+
params[:etime] = @etime.to_i.to_s
|
71
|
+
# fixme reload hosts after some expiry
|
72
|
+
end
|
73
|
+
|
74
|
+
get %r[^/(dash/?)?$] do
|
75
|
+
@no_graphs = true
|
76
|
+
redirect '/dash/global'
|
77
|
+
end
|
78
|
+
|
79
|
+
get '/dash/:cluster/:dashboard/:zoom/?' do
|
80
|
+
@cluster = params[:cluster]
|
81
|
+
@dash = Dashboard.find(params[:dashboard])
|
82
|
+
raise "Unknown dashboard: #{params[:dashboard].inspect}" unless @dash
|
83
|
+
|
84
|
+
@zoom = nil
|
85
|
+
@dash.graphs.each do |graph|
|
86
|
+
@zoom = graph if graph.name == params[:zoom]
|
87
|
+
end
|
88
|
+
raise "Unknown zoom parameter: #{params[:zoom]}" unless @zoom
|
89
|
+
|
90
|
+
@title = "dashboard :: #{@cluster} :: #{@dash['title']} :: #{params[:zoom]}"
|
91
|
+
|
92
|
+
if @cluster == "global"
|
93
|
+
erb :'dash-global-zoom'
|
94
|
+
else
|
95
|
+
erb :'dash-cluster-zoom'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
get '/dash/:cluster/:dashboard/?' do
|
100
|
+
@cluster = params[:cluster]
|
101
|
+
@dash = Dashboard.find(params[:dashboard])
|
102
|
+
raise "Unknown dashboard: #{params[:dashboard].inspect}" unless @dash
|
103
|
+
|
104
|
+
@title = "dashboard :: #{@cluster} :: #{@dash['title']}"
|
105
|
+
|
106
|
+
if @cluster == "global"
|
107
|
+
erb :'dash-global'
|
108
|
+
else
|
109
|
+
erb :'dash-cluster'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
get '/dash/:cluster/?' do
|
114
|
+
@no_graphs = true
|
115
|
+
@cluster = params[:cluster]
|
116
|
+
if @cluster == "global"
|
117
|
+
@title = "Overview"
|
118
|
+
erb :global
|
119
|
+
else
|
120
|
+
@title = "cluster :: #{params[:cluster]}"
|
121
|
+
erb :cluster
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
get '/host/:cluster/:host/?' do
|
126
|
+
@host = Host.new(params[:host], { "cluster" => params[:cluster] })
|
127
|
+
@cluster = params[:cluster]
|
128
|
+
raise "Unknown host: #{params[:host]} in #{params[:cluster]}" unless @host
|
129
|
+
|
130
|
+
@title = "#{@host.cluster} :: host :: #{@host.name}"
|
131
|
+
|
132
|
+
erb :host
|
133
|
+
end
|
134
|
+
|
135
|
+
# fixme make sure not to save shitty values for :start, :duration
|
136
|
+
# remove stime, etime
|
137
|
+
# there is definitely something wrong here
|
138
|
+
# disallow saving from, until
|
139
|
+
get '/saveprefs' do
|
140
|
+
puts 'saving prefs'
|
141
|
+
params.each do |k,v|
|
142
|
+
session[k] = v unless v.empty?
|
143
|
+
end
|
144
|
+
redirect URI.parse(request.referrer).path
|
145
|
+
end
|
146
|
+
|
147
|
+
get '/clear' do
|
148
|
+
puts "clearing prefs"
|
149
|
+
session.clear
|
150
|
+
redirect URI.parse(request.referrer).path
|
151
|
+
end
|
152
|
+
end # Dash::App
|
153
|
+
end # Dash
|
data/lib/helpers.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
module Dash::Helpers
|
2
|
+
include Dash::Models
|
3
|
+
|
4
|
+
@@prefs = [["Start", "start"],
|
5
|
+
["Duration", "duration"],
|
6
|
+
["Width", "width"],
|
7
|
+
["Height", "height"]]
|
8
|
+
|
9
|
+
# convert keys to symbols before lookup
|
10
|
+
def param_lookup(name)
|
11
|
+
sym_hash = {}
|
12
|
+
session.each { |k,v| sym_hash[k.to_sym] = v unless v.empty? }
|
13
|
+
params.each { |k,v| sym_hash[k.to_sym] = v unless v.empty? }
|
14
|
+
settings.config.global_config[:url_opts].merge(sym_hash)[name.to_sym]
|
15
|
+
end
|
16
|
+
|
17
|
+
def cluster_graph(g, cluster, title="wtf")
|
18
|
+
image_url = \
|
19
|
+
@dash.render_cluster_graph(g, cluster,
|
20
|
+
:title => title,
|
21
|
+
:dynamic_url_opts => merge_opts)
|
22
|
+
zoom_url = cluster_graph_link(@dash, g, cluster)
|
23
|
+
return image_url, zoom_url
|
24
|
+
end
|
25
|
+
|
26
|
+
def cluster_graph_link(dash, g, cluster)
|
27
|
+
link = dash.graph_opts[g]["click"] ||
|
28
|
+
"/dash/#{cluster}/#{dash.name}/#{g.name}"
|
29
|
+
return append_query_string(link)
|
30
|
+
end
|
31
|
+
|
32
|
+
def cluster_zoom_graph(g, cluster, host, title)
|
33
|
+
image_url = g.render_url([host.name], [cluster], :title => title,
|
34
|
+
:dynamic_url_opts => merge_opts)
|
35
|
+
zoom_url = cluster_zoom_link(cluster, host)
|
36
|
+
return image_url, zoom_url
|
37
|
+
end
|
38
|
+
|
39
|
+
def cluster_zoom_link(cluster, host)
|
40
|
+
return append_query_string("/host/#{cluster}/#{host}")
|
41
|
+
end
|
42
|
+
|
43
|
+
def suggest_cluster_links(clusters, g)
|
44
|
+
links = []
|
45
|
+
clusters.each do |c|
|
46
|
+
href = append_query_string("/dash/#{c}/#{params[:dashboard]}/#{g.name}")
|
47
|
+
links << "<a href=\"#{href}\">#{c}</a>"
|
48
|
+
end
|
49
|
+
return "zoom (" + links.join(", ") + ")"
|
50
|
+
end
|
51
|
+
|
52
|
+
def suggest_dashboards_links(host, graph)
|
53
|
+
suggested = suggest_dashboards(host, graph)
|
54
|
+
return "" if suggested.length == 0
|
55
|
+
|
56
|
+
links = []
|
57
|
+
suggested.each do |d|
|
58
|
+
links << "<a href=\"/dash/#{host.cluster}/#{append_query_string(d)}\">" +
|
59
|
+
"#{d}</a>"
|
60
|
+
end
|
61
|
+
return "(" + links.join(", ") + ")"
|
62
|
+
end
|
63
|
+
|
64
|
+
# it's mildly annoying that when this set is empty there're no uplinks
|
65
|
+
# consider adding a link up to the cluster (which is best we can do)
|
66
|
+
def suggest_dashboards(host, graph)
|
67
|
+
ret = Set.new
|
68
|
+
|
69
|
+
host.graphs.each do |g|
|
70
|
+
Dashboard.find_by_graph(g).each do |d|
|
71
|
+
valid, _ = d.get_valid_hosts(g, host['cluster'])
|
72
|
+
ret << d.name if valid.member?(host)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
return ret
|
77
|
+
end
|
78
|
+
|
79
|
+
# generate the input box fields, filled in to current parameters if specified
|
80
|
+
def input_boxes
|
81
|
+
@prefs = @@prefs
|
82
|
+
erb :'partials/input_boxes', :layout => false
|
83
|
+
end
|
84
|
+
|
85
|
+
def cookies_form
|
86
|
+
@prefs = @@prefs
|
87
|
+
erb :'partials/cookies_form', :layout => false
|
88
|
+
end
|
89
|
+
|
90
|
+
def refresh_button
|
91
|
+
@prefs = @@prefs
|
92
|
+
erb :'partials/refresh_button', :layout => false
|
93
|
+
end
|
94
|
+
|
95
|
+
def dash_link(dash, cluster)
|
96
|
+
return append_query_string("/dash/#{cluster}/#{dash.name}")
|
97
|
+
end
|
98
|
+
|
99
|
+
def cluster_link(cluster)
|
100
|
+
return append_query_string("/dash/#{cluster}")
|
101
|
+
end
|
102
|
+
|
103
|
+
def css_url
|
104
|
+
style = File.join(settings.root, "public/style.css")
|
105
|
+
mtime = File.mtime(style).to_i.to_s
|
106
|
+
return \
|
107
|
+
%Q[<link href="/style.css?#{mtime}" rel="stylesheet" type="text/css">]
|
108
|
+
end
|
109
|
+
|
110
|
+
def refresh
|
111
|
+
if settings.config.global_config[:refresh_rate] != false && nowish
|
112
|
+
rate = settings.config.global_config[:refresh_rate] || 60
|
113
|
+
return %Q[<meta http-equiv="refresh" content="#{rate}">]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def hosts_selector(hosts)
|
118
|
+
@hosts = hosts
|
119
|
+
erb :'partials/hosts_selector', :layout => false
|
120
|
+
end
|
121
|
+
|
122
|
+
def append_query_string(str)
|
123
|
+
v = str.dup
|
124
|
+
query = request.query_string.chomp("&permalink=1")
|
125
|
+
(v << "?#{query}") unless request.query_string.empty?
|
126
|
+
return v
|
127
|
+
end
|
128
|
+
|
129
|
+
def merge_opts
|
130
|
+
static_opts = ["cluster", "dashboard", "zoom", "host", "session_id"]
|
131
|
+
opts = params.dup
|
132
|
+
session.merge(opts).delete_if { |k,v| static_opts.member?(k) || v.empty? }
|
133
|
+
end
|
134
|
+
|
135
|
+
def cluster_switcher
|
136
|
+
erb :'partials/cluster_switcher', :layout => false
|
137
|
+
end
|
138
|
+
|
139
|
+
def dash_switcher
|
140
|
+
erb :'partials/dash_switcher', :layout => false
|
141
|
+
end
|
142
|
+
|
143
|
+
def graph_switcher
|
144
|
+
erb :'partials/graph_switcher', :layout => false
|
145
|
+
end
|
146
|
+
|
147
|
+
def cluster_selector
|
148
|
+
@clusters = settings.config.clusters.sort + ["global"]
|
149
|
+
erb :'partials/cluster_selector', :layout => false
|
150
|
+
end
|
151
|
+
|
152
|
+
def host_uplink
|
153
|
+
link = "/dash/#{append_query_string(@host.cluster)}"
|
154
|
+
"zoom out: <a href=\"#{link}\">#{@host.cluster}</a>"
|
155
|
+
end
|
156
|
+
|
157
|
+
def graph_uplink
|
158
|
+
link = append_query_string(request.path.split("/")[0..-2].join("/"))
|
159
|
+
"zoom out: <a href=\"#{link}\">#{@dash}</a>"
|
160
|
+
end
|
161
|
+
|
162
|
+
def dash_uplink
|
163
|
+
link = append_query_string(request.path.split("/")[0..-2].join("/"))
|
164
|
+
"zoom out: <a href=\"#{link}\">#{@params[:cluster]}</a>"
|
165
|
+
end
|
166
|
+
|
167
|
+
def nowish
|
168
|
+
if settings.config.global_config[:now_threshold] == false
|
169
|
+
return false
|
170
|
+
end
|
171
|
+
threshold = settings.config.global_config[:now_threshold] || 300
|
172
|
+
return @request_time.to_i - @etime.to_i < threshold
|
173
|
+
end
|
174
|
+
|
175
|
+
def range_string
|
176
|
+
format = settings.config.global_config[:date_format] || "%X %x"
|
177
|
+
if @stime && @etime
|
178
|
+
if nowish
|
179
|
+
"timeslice: from #{@stime.strftime(format)}"
|
180
|
+
else
|
181
|
+
"timeslice: #{@stime.strftime(format)} - #{@etime.strftime(format)}"
|
182
|
+
end
|
183
|
+
else
|
184
|
+
"invalid time range"
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
def permalink
|
190
|
+
return "" unless @stime && @duration
|
191
|
+
format = "%x %X" # chronic understands this
|
192
|
+
url = request.path + "?"
|
193
|
+
url << "&start=#{@stime.strftime(format)}"
|
194
|
+
url << "&duration=#{ChronicDuration.output(@duration)}"
|
195
|
+
"<a href=\"#{url}\">permalink</a>"
|
196
|
+
end
|
197
|
+
end
|
data/lib/models/base.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Dash::Models
|
2
|
+
class Base
|
3
|
+
@@objects = Hash.new { |h, k| h[k] = Hash.new }
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize(name, params={})
|
7
|
+
@name = name
|
8
|
+
@match_name = name
|
9
|
+
@params = params
|
10
|
+
@@objects[self.class.to_s][name] = self
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.find(name)
|
14
|
+
return @@objects[self.name][name] rescue []
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.each(&block)
|
18
|
+
h = @@objects[self.name] rescue {}
|
19
|
+
h.each { |k, v| yield(k, v) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.all
|
23
|
+
return @@objects[self.name].values
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](key)
|
27
|
+
return @params[key] rescue []
|
28
|
+
end
|
29
|
+
|
30
|
+
def match(glob)
|
31
|
+
return true if glob == '*'
|
32
|
+
# convert glob to a regular expression
|
33
|
+
glob_parts = glob.split('*').collect { |s| Regexp.escape(s) }
|
34
|
+
if glob[0].chr == '*'
|
35
|
+
glob_re = /^.*#{glob_parts.join('.*')}$/
|
36
|
+
elsif glob[-1].chr == '*'
|
37
|
+
glob_re = /^#{glob_parts.join('.*')}.*$/
|
38
|
+
else
|
39
|
+
glob_re = /^#{glob_parts.join('.*')}$/
|
40
|
+
end
|
41
|
+
return @match_name.match(glob_re)
|
42
|
+
end
|
43
|
+
|
44
|
+
def multi_match(globs)
|
45
|
+
ret = false
|
46
|
+
|
47
|
+
globs.each do |glob|
|
48
|
+
ret = match(glob)
|
49
|
+
break if ret
|
50
|
+
end
|
51
|
+
|
52
|
+
return ret
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
return @name
|
57
|
+
end
|
58
|
+
|
59
|
+
def <=>(other)
|
60
|
+
return to_s <=> other.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
def update_params(hash)
|
64
|
+
@params.merge!(hash)
|
65
|
+
end
|
66
|
+
|
67
|
+
# compose a metric using a :metric_format
|
68
|
+
# format string with %c for metric, %c for cluster, and %h for host
|
69
|
+
def compose_metric (m, c, h)
|
70
|
+
@params[:metric_format].dup.gsub("%m", m).gsub("%c", c).gsub("%h", h)
|
71
|
+
end
|
72
|
+
end # Dash::Models::Base
|
73
|
+
end # Dash::Models
|