collectd-interface 0.1.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 (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