pencil 0.2.0 → 0.2.2
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.
- 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
|