collectd-rest-server 0.0.1

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.
@@ -0,0 +1,334 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #---------------------------------------------------------------
4
+ #
5
+ # REST interface to the RRD stored by 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 2011
26
+ # Version: 0.0.1
27
+ #----------------------------------------------------------------
28
+
29
+
30
+ require 'rubygems'
31
+ require 'json'
32
+ require 'getoptlong'
33
+ require 'ostruct'
34
+
35
+ exec_name = File.split(__FILE__)[-1]
36
+
37
+ help = <<-EOF
38
+ Synopsis
39
+ ========
40
+
41
+ #{exec_name}: Run a REST interface to your local Collectd.
42
+
43
+ Usage
44
+ -----
45
+
46
+ #{exec_name} [OPTIONS]
47
+
48
+ OPTIONS: see below.
49
+
50
+ Options
51
+ -------
52
+
53
+ --help,-h:
54
+ Show this help information.
55
+ --debug, -d:
56
+ More verbose output while running this.
57
+ --port,-p NUMBER:
58
+ Start the REST server at port NUMBER.
59
+ --log,-l PATH:
60
+ Write the log output to PATH.
61
+ --files, -f PATH:
62
+ RRD files are located in PATH.
63
+ Default is /var/lib/collectd/rrd
64
+ --pid-file, -P PATH:
65
+ Write the process ID to a file in PATH.
66
+
67
+ Examples
68
+ --------
69
+
70
+ Run the stuff in debug-mode:
71
+
72
+ #{exec_name} -d
73
+
74
+ Start the server as root service:
75
+
76
+
77
+ #{exec_name} -p 5000 -l /var/log/ -P /var/run/
78
+
79
+ REST
80
+ ====
81
+
82
+ List all available values:
83
+
84
+ http://../list-values
85
+
86
+ Get a the time-series of a specific value.
87
+
88
+ http://../interface/if_packets-eth0/rx
89
+
90
+ By default the severs will answer with averages
91
+ of all data-points and an HTML document.
92
+
93
+ Parameters
94
+ ----------
95
+
96
+ http://../value?last=36h&resolution=3600
97
+
98
+ Request only values from the last 36 hours, with a
99
+ data point resolution of 1 hour (3600 seconds).
100
+ Default resolution is the highest possible and you
101
+ will get by default values of the last 24 hours.
102
+ In the time specification you can also use (w)eeks,
103
+ (d)ays and (m)inutes.
104
+
105
+ http://../rx?function=max&format=json
106
+
107
+ Request the maximum of all data points for the past
108
+ in the JSON format. Other consolidation functions
109
+ are average (default) and min, for the smallest value
110
+ in a time-frame.
111
+
112
+ http://../value?callback=parseResponse
113
+
114
+ The server then wraps its JSON response with this
115
+ prefix, or "padding", before sending it to the browser.
116
+
117
+ EOF
118
+
119
+ options = OpenStruct.new
120
+ options.debug = false
121
+ options.port = 4567
122
+ options.log = nil
123
+ options.files = '/var/lib/collectd/rrd'
124
+ options.pid_file = nil
125
+
126
+ GetoptLong.new(
127
+ ['--help','-h',GetoptLong::NO_ARGUMENT],
128
+ ['--debug','-d',GetoptLong::NO_ARGUMENT],
129
+ ['--port','-p',GetoptLong::REQUIRED_ARGUMENT],
130
+ ['--log','-l',GetoptLong::REQUIRED_ARGUMENT],
131
+ ['--files','-f',GetoptLong::REQUIRED_ARGUMENT],
132
+ ['--pid-file','-P',GetoptLong::REQUIRED_ARGUMENT]
133
+ ).each do |opt,arg|
134
+ case opt
135
+ when '--port'
136
+ options.port = arg
137
+ when '--log'
138
+ options.log = arg
139
+ when '--files'
140
+ options.files = arg
141
+ when '--pid-file'
142
+ options.pid_file = arg
143
+ when '--debug'
144
+ options.debug = true
145
+ $DEBUG = true
146
+ when '--help'
147
+ $stdout.puts help
148
+ exit 0
149
+ end
150
+ end
151
+
152
+ # Didn't found a better way to pass configuration to the Sinatra instance
153
+ ENV['COLLECTD_RRD_FILES'] = options.files
154
+
155
+ # Make sure to parse ARGV before loading Sinatra!
156
+ require 'sinatra'
157
+
158
+ #---------------------------------------------------------------
159
+ # REST provider
160
+ #---------------------------------------------------------------
161
+
162
+ class Collecter < Sinatra::Base
163
+
164
+ configure do
165
+ mime_type :json, 'application/json' # JSON as supported output format
166
+ enable :inline_templates # HTML templates at the bottom of this file
167
+ enable :run # this will be executed by wrapper scripts
168
+ hostname = `hostname -f`.chop
169
+ set :path, "#{ENV['COLLECTD_RRD_FILES']}/#{hostname}/"
170
+ # list available RRD databases of this host
171
+ values = Array.new
172
+ Dir["#{settings.path}/**/*.rrd"].each do |file|
173
+ plugin = file.gsub(%r<#{settings.path}>,'').split('/')[1]
174
+ rrd = File.basename(file,'.rrd')
175
+ `rrdtool info #{file}`.scan(%r{ds\[(\w*)\]}).uniq.flatten.each do |set|
176
+ values << "#{plugin}/#{rrd}/#{set}"
177
+ end
178
+ end
179
+ set :values, values
180
+ end
181
+
182
+ get '/list-values' do
183
+ if params.has_key? 'callback'
184
+ content_type :js
185
+ json = JSON.pretty_generate settings.values
186
+ response = "#{params['callback']}(#{json})"
187
+ response
188
+ elsif params['format'] == 'json'
189
+ content_type :json
190
+ JSON.pretty_generate settings.values
191
+ else
192
+ @data = settings.values
193
+ erb :values
194
+ end
195
+ end
196
+
197
+ get '/*' do |path|
198
+ redirect '/list-values' unless settings.values.include?(path)
199
+ plugin,type,value = path.split("/")
200
+ file = "#{settings.path}/#{plugin}/#{type}.rrd"
201
+ if File.exists? file
202
+ data = Array.new
203
+ # construct the RRD query
204
+ function = 'AVERAGE'
205
+ if params.has_key? 'function'
206
+ param = params['function'].upcase
207
+ puts param
208
+ if %w(AVERAGE MIN MAX).include? param
209
+ function = param
210
+ end
211
+ end
212
+ command = "rrdtool fetch #{file} #{function}"
213
+ command << " --end Now --start Now-#{params['last']}" if params.has_key? 'last'
214
+ command << " --r #{params['resolution']}"if params.has_key? 'resolution'
215
+ $stderr.puts command if $DEBUG
216
+ # get the data
217
+ output = `#{command}`.split("\n")
218
+ # select the value of interest
219
+ headers = output.shift.split # remove header
220
+ key = headers.index(value)
221
+ output.delete_at 0 # remove empty line
222
+ # collect the data
223
+ output.each do |line|
224
+ line = line.delete(':').split
225
+ time = line[0].to_i
226
+ value = line[key+1].to_f # omit time stamp
227
+ lv = data[-1]
228
+ data << [time, value]
229
+ end
230
+ #remove most time wrong elements
231
+ data.slice!(0)
232
+ data.slice!(-1)
233
+ #filter values which are the same for multiple timestamps
234
+ final_data = []
235
+ data.each_index do |el|
236
+ cur_e = data[el][1]
237
+ if data[el-1] != nil and data[el+1] != nil and el != 0 and el != data.size-1
238
+ last_e = data[el-1][1]
239
+ next_e = data[el+1][1]
240
+ if cur_e != last_e or cur_e != next_e then
241
+ final_data.push(data[el])
242
+ end
243
+ else
244
+ final_data.push(data[el])
245
+ end
246
+ end
247
+ data = final_data
248
+ if params.has_key? 'callback'
249
+ content_type :js
250
+ json = {'name'=>"#{plugin} #{type}",'type'=>'spline' ,'data'=>data, 'stack'=>"#{plugin}"}
251
+ json = JSON.pretty_generate json
252
+ response = "#{params['callback']}(#{json})"
253
+ response
254
+ elsif params['format'] == 'json'
255
+ content_type :json
256
+ json = {'name'=>"#{plugin} #{file}",'type'=>'spline' ,'data'=>data, 'stack'=>"#{plugin}"}
257
+ JSON.pretty_generate json
258
+ else
259
+ @data = data
260
+ erb :data
261
+ end
262
+ end
263
+ end
264
+
265
+ error do
266
+ flash[:error] = env['sinatra.error'].to_s
267
+ redirect '/list-values'
268
+ end
269
+ end
270
+
271
+ #--------------------------------------------------------------
272
+ # Main program
273
+ #--------------------------------------------------------------
274
+
275
+
276
+ begin
277
+
278
+ unless options.log.nil?
279
+ file = File.join(options.log,"#{exec_name}.log")
280
+ $stdout.reopen(file, 'w')
281
+ $stdout.sync = true
282
+ $stderr.reopen($stdout)
283
+ end
284
+
285
+ unless options.pid_file.nil?
286
+ File.open(File.join(options.pid_file,"#{exec_name}.pid"),'w') do |f|
287
+ f.puts $$
288
+ end
289
+ end
290
+
291
+ # start the REST interface
292
+ Collecter.run!( :port => options.port )
293
+
294
+ rescue => exc
295
+ $stderr.puts "ERROR: #{exc.message}"
296
+ $stderr.puts " use -h for detailed instructions"
297
+ if options.debug
298
+ $stderr.puts '-- Stack Trace --'
299
+ $stderr.puts exc.backtrace
300
+ else
301
+ $stderr.puts 'You may want run this in debug mode with \'-d\''
302
+ end
303
+ exit 1
304
+ end
305
+
306
+ exit 0
307
+
308
+ #--------------------------------------------------------
309
+ # Templates for the REST provider
310
+ #--------------------------------------------------------
311
+
312
+ __END__
313
+ @@ layout
314
+ <html>
315
+ <body>
316
+ <%= yield %>
317
+ </body>
318
+ </html>
319
+
320
+ @@ values
321
+ <ul style='list-style-type:none'>
322
+ <% for val in @data %>
323
+ <li><%= val %></li>
324
+ <% end %>
325
+ </ul>
326
+
327
+ @@ data
328
+ <table>
329
+ <% for entry in @data %>
330
+ <tr><td><%= entry[0] %></td><td><%= entry[1]%></td></tr>
331
+ <% end %>
332
+ </table>
333
+
334
+
@@ -0,0 +1,50 @@
1
+ #!/bin/bash
2
+
3
+ HOME=/opt/collectd
4
+ PORT=5000
5
+ LOG_PATH=/var/log
6
+ PID_PATH=/var/run
7
+ TIME=`date +'%Y-%m-%d %H:%M'`
8
+
9
+ PID=-1 # non existing
10
+ PID_FILE=$PID_PATH/collectd-rest-server.pid
11
+ if [ -e $PID_FILE ]; then
12
+ PID=$(cat $PID_FILE)
13
+ fi
14
+
15
+ start() {
16
+ $HOME/collectd-rest-server -p $PORT -l $LOG_PATH -P $PID_PATH &
17
+ }
18
+ stop() {
19
+ if ps -p $PID > /dev/null 2>&1; then
20
+ kill -TERM ${PID}
21
+ fi
22
+ }
23
+
24
+ case "$1" in
25
+ start)
26
+ start
27
+ ;;
28
+ stop)
29
+ stop
30
+ rm $PID_FILE
31
+ ;;
32
+ restart)
33
+ stop
34
+ sleep 2
35
+ start
36
+ ;;
37
+ status)
38
+ if [ -e /proc/${PID} -a /proc/${PID}/exe ]; then
39
+ exit 0
40
+ else
41
+ exit 1
42
+ fi
43
+ ;;
44
+ *)
45
+ echo "Usage: $0 start|stop|restart|status"
46
+ exit 1
47
+ ;;
48
+ esac
49
+
50
+ exit 0
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: collectd-rest-server
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Victor Penso
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-01 00:00:00.000000000 +02:00
13
+ default_executable: collectd-rest-server
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: sinatra
17
+ requirement: &19329200 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 1.2.6
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *19329200
26
+ description: ''
27
+ email: v.penso@gsi.de
28
+ executables:
29
+ - collectd-rest-server
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - init.d/collectd-rest-server
34
+ - bin/collectd-rest-server
35
+ has_rdoc: true
36
+ homepage: ''
37
+ licenses:
38
+ - GPLv3
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --line-numbers
42
+ - --inline-source
43
+ - --title
44
+ - Collectd-Rest-Server
45
+ require_paths:
46
+ - ''
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements:
60
+ - Collectd (http://collectd.org)
61
+ rubyforge_project: collectd-rest-server
62
+ rubygems_version: 1.6.2
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Servers a REST interface exposing data from RRD files stored by Collectd
66
+ test_files: []