collectd-interface 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/README.md +134 -0
  2. data/bin/collectd-interface-daemon +401 -0
  3. data/bin/collectd-interface-plugins +95 -0
  4. data/graphs/cpus.erb +76 -0
  5. data/graphs/disk-traffic-root.erb +30 -0
  6. data/graphs/disk-traffic-srv.erb.disabled +30 -0
  7. data/graphs/disk-traffic-tmp.erb.disabled +30 -0
  8. data/graphs/disk-traffic-var.erb.disabled +30 -0
  9. data/graphs/gridengine-jobs.erb.disabled +32 -0
  10. data/graphs/load.erb +36 -0
  11. data/graphs/memory.erb +47 -0
  12. data/graphs/network-eth0.erb +26 -0
  13. data/graphs/network-lo.erb +26 -0
  14. data/graphs/processes.erb +66 -0
  15. data/public/images/cpus.png +0 -0
  16. data/public/images/cpus.svg +946 -0
  17. data/public/images/disk-traffic-root.png +0 -0
  18. data/public/images/disk-traffic-srv.png +0 -0
  19. data/public/images/disk-traffic-var.png +0 -0
  20. data/public/images/load.png +0 -0
  21. data/public/images/load.svg +638 -0
  22. data/public/images/memory.png +0 -0
  23. data/public/images/memory.svg +741 -0
  24. data/public/images/network-eth0.png +0 -0
  25. data/public/images/network-eth0.svg +609 -0
  26. data/public/images/network-lo.png +0 -0
  27. data/public/images/network-lo.svg +644 -0
  28. data/public/images/processes.png +0 -0
  29. data/public/images/processes.svg +832 -0
  30. data/public/readme/user-interface.png +0 -0
  31. data/public/script/toggle.js +9 -0
  32. data/public/style/default.css +275 -0
  33. data/public/style/nav.css +265 -0
  34. data/views/README.md +134 -0
  35. data/views/data.erb +3 -0
  36. data/views/graph.erb +19 -0
  37. data/views/readme.erb +3 -0
  38. data/views/report.erb +17 -0
  39. data/views/reports/disk-free.erb +20 -0
  40. data/views/reports/list-open-files-lustre.erb.disabled +44 -0
  41. data/views/reports/list-open-files-tmp.erb +44 -0
  42. data/views/reports/processes-cpu-usage.erb +39 -0
  43. data/views/reports/system-sockets.erb +32 -0
  44. data/views/show_values.erb +11 -0
  45. data/views/template/default.erb +21 -0
  46. data/views/template/header.erb +3 -0
  47. data/views/template/navigation.erb +9 -0
  48. data/views/template/options/data.erb +31 -0
  49. data/views/template/options/graph.erb +23 -0
  50. data/views/template/options/readme.erb +0 -0
  51. data/views/template/options/report.erb +11 -0
  52. metadata +128 -0
data/README.md ADDED
@@ -0,0 +1,134 @@
1
+ Created 10 Feb 2012 -- Last change 14 Feb 2012
2
+ By Victor Penso
3
+
4
+ Description
5
+ ===========
6
+
7
+ _Collectd-Interface_ serves a web user-interface to data stored
8
+ by [Collectd](http://collectd.org/). Furthermore it provides REST
9
+ access to all data.
10
+
11
+ Installation
12
+ ============
13
+
14
+ Download and install Collectd following the instructions
15
+ from the developers.
16
+
17
+ On Debian flavored Linux use:
18
+
19
+ apt-get install collectd rrdtool
20
+
21
+ You will need the [RRDtool](http://oss.oetiker.ch/rrdtool/) too.
22
+
23
+ Usage
24
+ =====
25
+
26
+ Get help:
27
+
28
+ collectd-interface-daemon --help
29
+
30
+ Start the Collectd Interface in fore-ground:
31
+
32
+ collectd-interface-daemon
33
+
34
+ Open the web-interface <a href='localhost:4567'>localhost:4567</a>.
35
+
36
+ Start the Collectd Interface as daemon:
37
+
38
+ collectd-interface-daemon -p 5000 -l /var/log/ -P /var/run/ &
39
+
40
+
41
+ Interfaces
42
+ ==========
43
+
44
+ Collectd-Interface servers three different kinds of output:
45
+
46
+ 1. `/graph` is the user-interface showing line charts of many
47
+ of the data accumulated by Collectd.
48
+ 2. `/report` presents tables of system specific information
49
+ like disk capacity of a list of network sockets.
50
+ 3. `/data` servers an REST API to all data available from
51
+ Collectd.
52
+
53
+ All content from `/graph`, `/report` and `/data` is accessible
54
+ by a REST interface, to allow embedding this content into other
55
+ applications.
56
+
57
+ Graph
58
+ -----
59
+
60
+ ![Screenshot of User-Interface](https://github.com/vpenso/collectd-interface/raw/master/public/readme/user-interface.png "Screenshot of the User-Interface")
61
+
62
+ You can select individual graphs using the drop-down menu followed by
63
+ clicking the "Show" button. In case you just want to have the image, to
64
+ embed it into another web-page, use the links beneath the graph.
65
+
66
+ Once the graph is generated the caller will be redirected to `/image/`.
67
+
68
+ **Parameters**
69
+
70
+ It is possible to limit the time-frame of the graph using the option
71
+ `last=10h`(us (m)inutes,(d)ays or (w)eeks).
72
+
73
+ This simple example:
74
+
75
+ http://.../memory?last=1w&image=svg
76
+
77
+ Requests an SVG image with the memory graph for the last week.
78
+
79
+ **Plugin**
80
+
81
+ You can create add a custom graph rendering data from Collectd
82
+ by creating a template which is used to generate the <tt>rrdtool graph</tt>.
83
+ Take a look to the <tt>graphs/</tt> and <tt>disabled/graphs/</tt> directories
84
+ for examples. I recommend you the start the Collectd-Interface in
85
+ debug mode (option <tt>-d</tt>) while you develop new graph templates.
86
+
87
+ In case you want to enable graphs from <tt>disabled/graphs/</tt> create
88
+ a soft link from <tt>graphs/</tt>. The <tt>collectd-interface</tt> daemon will
89
+ automatically recognize new templates within <tt>graphs</tt> on start.
90
+
91
+ Report
92
+ ------
93
+
94
+ Reports a basically wrappers around commands like <tt>df -l</tt> or
95
+ <tt>ss -ar</tt>. The output is available as HTML in the "Report" section
96
+ of the user-interface.
97
+
98
+ TODO
99
+
100
+ Data
101
+ ----
102
+
103
+ List all available values <a href="/data/">/data/</a> from
104
+ Collectd. Get the time-series of a specific value.
105
+
106
+ http://.../data/interface/if_packets-eth0/rx
107
+
108
+ By default the severs will answer with averages
109
+ of all data-points and an HTML document.
110
+
111
+ **Parameters**
112
+
113
+ http://.../value?last=36h&resolution=3600
114
+
115
+ Request only values from the last 36 hours, with a
116
+ data point resolution of 1 hour (3600 seconds).
117
+ Default resolution is the highest possible and you
118
+ will get by default values of the last 24 hours.
119
+ In the time specification you can also use (w)eeks,
120
+ (d)ays and (m)inutes.
121
+
122
+ http://.../rx?function=max&format=json
123
+
124
+ Request the maximum of all data points for the past
125
+ in the JSON format. Other consolidation functions
126
+ are average (default) and min, for the smallest value
127
+ in a time-frame.
128
+
129
+
130
+ Copying
131
+ =======
132
+
133
+ Copyright 2011 Victor Penso
134
+ License [GPLv3](http://www.gnu.org/licenses/gpl-3.0.html) (see LICENSE file)
@@ -0,0 +1,401 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #---------------------------------------------------------------
4
+ #
5
+ # Interface to Collectd
6
+ #
7
+ # This is free software: you can redistribute it
8
+ # and/or modify it under the terms of the GNU General Public
9
+ # License as published by the Free Software Foundation,
10
+ # either version 3 of the License, or (at your option) any
11
+ # later version.
12
+ #
13
+ # This program is distributed in the hope that it will be
14
+ # useful, but WITHOUT ANY WARRANTY; without even the implied
15
+ # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
16
+ # PURPOSE. See the GNU General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public
19
+ # License along with this program. If not, see
20
+ #
21
+ # <http://www.gnu.org/licenses/>.
22
+ #
23
+ #----------------------------------------------------------------
24
+ # Author: Victor Penso
25
+ # Copyright 2012
26
+ # Version: 0.1.0
27
+ #----------------------------------------------------------------
28
+
29
+
30
+ require 'rubygems'
31
+ require 'getoptlong'
32
+ require 'erb'
33
+ require 'ostruct'
34
+ require 'json'
35
+
36
+ exec_name = File.split(__FILE__)[-1]
37
+
38
+ help = <<-EOF
39
+ Synopsis
40
+ ========
41
+
42
+ #{exec_name}: Web Interface to your local Collectd data.
43
+
44
+ Usage
45
+ -----
46
+
47
+ #{exec_name} [OPTIONS]
48
+
49
+ OPTIONS: see below.
50
+
51
+ Options
52
+ -------
53
+
54
+ --help,-h:
55
+ Show this help information.
56
+ --debug, -d:
57
+ More verbose output while running this.
58
+ --port,-p NUMBER:
59
+ Start the REST server at port NUMBER.
60
+ --log,-l PATH:
61
+ Write the log output to PATH.
62
+ --files, -f PATH:
63
+ RRD files are located in PATH.
64
+ Default is /var/lib/collectd/rrd
65
+ --pid-file, -P PATH:
66
+ Write the process ID to a file in PATH.
67
+
68
+ Examples
69
+ --------
70
+
71
+ Run the stuff in debug-mode:
72
+
73
+ #{exec_name} -d
74
+
75
+ Start the server as root service:
76
+
77
+
78
+ #{exec_name} -p 5000 -l /var/log/ -P /var/run/
79
+
80
+ EOF
81
+
82
+ options = OpenStruct.new
83
+ options.debug = false
84
+ options.port = 4567
85
+ options.log = nil
86
+ options.files = '/var/lib/collectd/rrd'
87
+ options.pid_file = nil
88
+
89
+ GetoptLong.new(
90
+ ['--help','-h',GetoptLong::NO_ARGUMENT],
91
+ ['--debug','-d',GetoptLong::NO_ARGUMENT],
92
+ ['--port','-p',GetoptLong::REQUIRED_ARGUMENT],
93
+ ['--log','-l',GetoptLong::REQUIRED_ARGUMENT],
94
+ ['--files','-f',GetoptLong::REQUIRED_ARGUMENT],
95
+ ['--pid-file','-P',GetoptLong::REQUIRED_ARGUMENT]
96
+ ).each do |opt,arg|
97
+ case opt
98
+ when '--port'
99
+ options.port = arg
100
+ when '--log'
101
+ options.log = arg
102
+ when '--files'
103
+ options.files = arg
104
+ when '--pid-file'
105
+ options.pid_file = arg
106
+ when '--debug'
107
+ options.debug = true
108
+ $DEBUG = true
109
+ when '--help'
110
+ $stdout.puts help
111
+ exit 0
112
+ end
113
+ end
114
+
115
+ # Didn't found a better way to pass configuration to the Sinatra instance
116
+ ENV['COLLECTD_RRD_FILES'] = options.files
117
+
118
+ require 'sinatra/base'
119
+
120
+ class CollectdInterface < Sinatra::Base
121
+
122
+ configure do
123
+ hostname = `hostname -f`.chop
124
+ # root directory of this software
125
+ root = %Q[#{File.dirname(File.expand_path(__FILE__))}/..]
126
+ # Sinatra configuration
127
+ mime_type :json, 'application/json' # JSON as supported output format
128
+ mime_type :plain, 'text/plain'
129
+ disable :logging
130
+
131
+ # Settings
132
+ set :root, root
133
+ set :rrd_path, "#{ENV['COLLECTD_RRD_FILES']}/#{hostname}/"
134
+ set :public_folder, "#{root}/public"
135
+ set :static, true
136
+ set :environment, :production
137
+
138
+ # Load all graphic plug-ins available
139
+ graphs = Hash.new
140
+ Dir["#{settings.root}/graphs/*.erb"].each do |file|
141
+ graphs[File.basename(file,'.erb')] = file
142
+ end
143
+ set :plugins, graphs
144
+ # List all RRD files available
145
+ rrd_values = Array.new
146
+ Dir["#{settings.rrd_path}/**/*.rrd"].each do |file|
147
+ plugin = file.gsub(%r<#{settings.rrd_path}>,'').split('/')[1]
148
+ rrd = File.basename(file,'.rrd')
149
+ `rrdtool info "#{file}"`.scan(%r{ds\[(\w*)\]}).uniq.flatten.each do |set|
150
+ rrd_values << "#{plugin}/#{rrd}/#{set}"
151
+ end
152
+ end
153
+ set :rrd_values, rrd_values
154
+
155
+ # List all report templates
156
+ reports = Hash.new
157
+ Dir["#{settings.root}/views/reports/*.erb"].each { |f| reports[File.basename(f,'.erb')] = f }
158
+ set :reports, reports
159
+
160
+ end
161
+
162
+ get '/data' do
163
+ @data = settings.rrd_values
164
+ if params.has_key?('display')
165
+ @target = params['display']
166
+ params.delete('display')
167
+ p = Array.new; params.each_pair { |k,v| p << "#{k}=#{v}" }
168
+ @args = p.join('&')
169
+ redirect "/data/#{@target}?#{@args}"
170
+ elsif params.has_key?('format')
171
+ case params['format']
172
+ when 'json'
173
+ content_type :json
174
+ JSON.pretty_generate @data.map! { |d| "/data/#{d}" }
175
+ else
176
+ content_type :plain
177
+ @data.join("\n")
178
+ end
179
+ else
180
+ @target = 'data'
181
+ erb :data, :layout => "template/default".to_sym
182
+ end
183
+ end
184
+
185
+ get '/data/*' do |path|
186
+ if path.empty?
187
+ redirect '/data'
188
+ else
189
+ plugin,type,value = path.split("/")
190
+ file = "#{settings.rrd_path}#{plugin}/#{type}.rrd"
191
+ if File.exists? file
192
+ data = Array.new
193
+ # construct the RRD query
194
+ function = 'AVERAGE'
195
+ if params.has_key? 'function'
196
+ param = params['function'].upcase
197
+ puts param
198
+ if %w(AVERAGE MIN MAX).include? param
199
+ function = param
200
+ end
201
+ end
202
+ command = "rrdtool fetch #{file} #{function}"
203
+ command << " --end Now --start Now-#{params['last']}" if params.has_key? 'last'
204
+ command << " --r #{params['resolution']}"if params.has_key? 'resolution'
205
+ #$stderr.puts command if $DEBUG
206
+ # get the data
207
+ output = `#{command}`.split("\n")
208
+ # select the value of interest
209
+ headers = output.shift.split # remove header
210
+ key = headers.index(value)
211
+ output.delete_at 0 # remove empty line
212
+ # collect the data
213
+ output.each do |line|
214
+ line = line.delete(':').split
215
+ time = line[0].to_i
216
+ value = line[key+1].to_f # omit time stamp
217
+ lv = data[-1]
218
+ data << [time, value]
219
+ end
220
+ #remove most time wrong elements
221
+ data.slice!(0)
222
+ data.slice!(-1)
223
+ #filter values which are the same for multiple timestamps
224
+ final_data = []
225
+ data.each_index do |el|
226
+ cur_e = data[el][1]
227
+ if data[el-1] != nil and data[el+1] != nil and el != 0 and el != data.size-1
228
+ last_e = data[el-1][1]
229
+ next_e = data[el+1][1]
230
+ if cur_e != last_e or cur_e != next_e then
231
+ final_data.push(data[el])
232
+ end
233
+ else
234
+ final_data.push(data[el])
235
+ end
236
+ end
237
+ data = final_data
238
+ if params.has_key? 'format'
239
+ if params['format'] == 'json'
240
+ content_type :json
241
+ json = {'name'=>"#{plugin} #{file}",'data'=>data}
242
+ JSON.pretty_generate json
243
+ else
244
+ content_type 'text/plain'
245
+ output = String.new
246
+ data.each { |line| output << "#{line[0]}: #{line[1..-1].join(' ')}\n" }
247
+ output
248
+ end
249
+ else
250
+ @data = data
251
+ erb :show_values
252
+ end
253
+ end
254
+ end
255
+ end
256
+
257
+ get '/graph' do
258
+ if params.has_key? 'format'
259
+ graph_list = settings.plugins.keys.sort.map! { |g| "/graph/#{g}" }
260
+ if params['format'] == 'json'
261
+ content_type :json
262
+ JSON.pretty_generate graph_list
263
+ else
264
+ content_type :text
265
+ graph_list.join("\n")
266
+ end
267
+ else
268
+ # default parameters
269
+ params['last'] = '12h' unless params.has_key?('last')
270
+ params['image'] = 'png' unless params.has_key?('image')
271
+ @display = params.has_key?('display') ? params['display'] : ['all']
272
+ # this parameters should not be inherited to /graph/*
273
+ params.delete('display') if params.has_key?('display')
274
+ @graphs = settings.plugins
275
+ p = Array.new; params.each_pair { |k,v| p << "#{k}=#{v}" }
276
+ @args = p.join('&')
277
+ @target = 'graph'
278
+ erb :graph, :layout => "template/default".to_sym
279
+ end
280
+ end
281
+
282
+ get '/graph/*' do |path|
283
+ unless settings.plugins.has_key? path
284
+ redirect '/'
285
+ else
286
+ color = {
287
+ :red_light => '#FF000044', :red_dark => '#FF0000AA',
288
+ :green_light => '#00F00022', :green_dark => '#00F000AA',
289
+ :yellow_light => '#FFFF0022', :yellow_dark => '#FFFF00AA',
290
+ :blue_light => '#0000FF22', :blue_dark => '#0000FFAA',
291
+ :orange_light => '#FF450022', :orange_dark => '#FF4500AA',
292
+ :cyan_light => '#00FFFF22', :cyan_dark => '#00FFFFAA',
293
+ :purple_light => '#FF00FF22', :purple_dark => '#FF00FFAA'
294
+ }
295
+ type = params.has_key?('image') ? params['image'] : 'png'
296
+ last = params.has_key?('last') ? params['last'] : '10800s'
297
+ target = %Q[#{settings.public_folder}/images/#{path}.#{type}]
298
+ rrd_path = settings.rrd_path
299
+ command = ERB.new(File.read(settings.plugins[path])).result(binding)
300
+ puts command if $DEBUG
301
+ system("#{command} > /dev/null 2>&1")
302
+ redirect %Q[/images/#{path}.#{type}]
303
+ end
304
+ end
305
+
306
+ get '/report' do
307
+ @reports = settings.reports.keys.sort
308
+ # List all available path to reports
309
+ if params.has_key? 'format'
310
+ report_list = @reports.map! { |r| "/report/#{r}" }
311
+ if params['format'] == 'json'
312
+ content_type :json
313
+ JSON.pretty_generate report_list
314
+ else # default is plain text
315
+ content_type :text
316
+ report_list.join("\n")
317
+ end
318
+ # Render a HTML representation of all/a single report(s)
319
+ else
320
+ if params.has_key? 'display' and params['display'] != 'all'
321
+ @display = params['display']
322
+ else
323
+ @display = 'all'
324
+ end
325
+ @target = 'report'
326
+ erb :report, :layout => "template/default".to_sym
327
+ end
328
+ end
329
+
330
+ get '/report/*' do |path|
331
+ redirect '/report' if path.empty?
332
+ # user asks for a specific output format
333
+ if params.has_key?('format')
334
+ case params['format']
335
+ when 'json'
336
+ content_type :json
337
+ @type = 'json'
338
+ when 'html'
339
+ @type = 'html'
340
+ else
341
+ content_type :plain
342
+ @type = 'text'
343
+ end
344
+ else
345
+ content_type :plain
346
+ @type = 'text'
347
+ end
348
+ erb "reports/#{path}".to_sym
349
+ end
350
+
351
+ get '/' do
352
+ redirect '/graph'
353
+ end
354
+
355
+ get '/help' do
356
+ @target = 'readme'
357
+ erb :readme, :layout => "template/default".to_sym
358
+ end
359
+
360
+ error do
361
+ flash[:error] = env['sinatra.error'].to_s
362
+ redirect '/'
363
+ end
364
+
365
+ end
366
+
367
+ #--------------------------------------------------------------
368
+ # Main program
369
+ #--------------------------------------------------------------
370
+
371
+
372
+ begin
373
+
374
+ unless options.log.nil?
375
+ file = File.join(options.log,"#{exec_name}.log")
376
+ $stdout.reopen(file, 'w')
377
+ $stdout.sync = true
378
+ $stderr.reopen($stdout)
379
+ end
380
+
381
+ unless options.pid_file.nil?
382
+ File.open(File.join(options.pid_file,"#{exec_name}.pid"),'w') do |f|
383
+ f.puts $$
384
+ end
385
+ end
386
+
387
+ CollectdInterface.run!( :port => options.port )
388
+
389
+ rescue => exc
390
+ $stderr.puts "ERROR: #{exc.message}"
391
+ $stderr.puts " use -h for detailed instructions"
392
+ if options.debug
393
+ $stderr.puts '-- Stack Trace --'
394
+ $stderr.puts exc.backtrace
395
+ else
396
+ $stderr.puts 'You may want run this in debug mode with \'-d\''
397
+ end
398
+ exit 1
399
+ end
400
+
401
+ exit 0