absperf-collectd_server 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Paul Sadauskas
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ = collectd_server
2
+
3
+ Description goes here.
4
+
5
+ == Copyright
6
+
7
+ Copyright (c) 2009 Paul Sadauskas. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "collectd_server"
8
+ gem.summary = %Q{TODO}
9
+ gem.email = "psadauskas@gmail.com"
10
+ gem.homepage = "http://github.com/paul/collectd_server"
11
+ gem.authors = ["Paul Sadauskas"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+
14
+ gem.add_dependency 'resourceful', '~> 0.5.0'
15
+ gem.add_dependency 'fastercsv', '~> 1.4.0'
16
+
17
+ end
18
+
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
21
+ end
22
+
23
+ require 'spec/rake/spectask'
24
+ Spec::Rake::SpecTask.new(:spec) do |spec|
25
+ spec.libs << 'lib' << 'spec'
26
+ spec.spec_files = FileList['spec/**/*_spec.rb']
27
+ end
28
+
29
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
30
+ spec.libs << 'lib' << 'spec'
31
+ spec.pattern = 'spec/**/*_spec.rb'
32
+ spec.rcov = true
33
+ end
34
+
35
+
36
+ task :default => :spec
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ if File.exist?('VERSION.yml')
41
+ config = YAML.load(File.read('VERSION.yml'))
42
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
43
+ else
44
+ version = ""
45
+ end
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "collectd_server #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
52
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../lib/collectd_server'
3
+
4
+ CollectdServer::Runner.new().run!
@@ -0,0 +1,70 @@
1
+
2
+ require 'rubygems'
3
+ require 'socket'
4
+ require 'resourceful'
5
+ require 'pp'
6
+
7
+ require File.dirname(__FILE__) + '/collectd_server/packet'
8
+ require File.dirname(__FILE__) + '/collectd_server/ssbe_authenticator'
9
+
10
+ PREVIOUS_VALUES = {}
11
+
12
+ module CollectdServer
13
+
14
+ class Runner
15
+
16
+ attr_accessor :port
17
+
18
+ def initialize(port = 1981, services = 'http://auth.v6.localhost/services')
19
+ @port = port
20
+ @services = services
21
+ end
22
+
23
+ def run!
24
+ @socket = UDPSocket.new
25
+ @socket.bind(nil, port)
26
+ @connection = Connection.new(@services)
27
+ loop do
28
+ data = @socket.recvfrom(65535)
29
+ @connection.receive_data(data.first)
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+
36
+ class Connection
37
+
38
+ def initialize(service_uri)
39
+ @http = Resourceful::HttpAccessor.new
40
+ @http.add_authenticator(Resourceful::SSBEAuthenticator.new('admin', 'admin'))
41
+ resp = @http.resource(service_uri).get(:accept => 'application/csv')
42
+
43
+ bulk_datapoint_href = nil
44
+ FasterCSV.parse(resp.body, :headers => true) do |row|
45
+ if row['name'] == 'BulkCreateDatapoints'
46
+ bulk_datapoint_href = row['resource_href']
47
+ end
48
+ end
49
+
50
+ @datapoint_resource = @http.resource(bulk_datapoint_href)
51
+
52
+ end
53
+
54
+ def receive_data(data)
55
+ packet = Packet.new(data)
56
+ dps = packet.to_datapoints
57
+
58
+ csv = FasterCSV.generate do |csv|
59
+ dps.each do |dp|
60
+ csv << dp.to_a
61
+ end
62
+ end
63
+
64
+ @datapoint_resource.post(csv, :content_type => 'text/csv')
65
+
66
+ end
67
+ end
68
+
69
+ end
70
+
@@ -0,0 +1,22 @@
1
+ require 'fastercsv'
2
+
3
+ module CollectdServer
4
+
5
+ class Datapoint
6
+ attr_reader :metric_name, :timestamp, :value
7
+
8
+ def initialize(name, timestamp, value)
9
+ @metric_name, @timestamp, @value = name, timestamp, value
10
+ end
11
+
12
+ def to_csv
13
+ to_a.to_csv
14
+ end
15
+
16
+ def to_a
17
+ [metric_name, timestamp, value]
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,142 @@
1
+ require 'fastercsv'
2
+ require 'tempfile'
3
+ require File.dirname(__FILE__) + '/datapoint'
4
+
5
+ module CollectdServer
6
+
7
+ class DatapointBuilder
8
+ def self.from_parts(parts, values)
9
+ begin
10
+ plugin_name = parts.detect { |p| p.is_a?(Packet::Plugin) }.content
11
+ rescue NoMethodError => e
12
+ puts "failed to find plugin part of #{parts.inspect}"
13
+ return []
14
+ #raise e
15
+ end
16
+
17
+ builder = subclasses.detect { |c| c.plugin_name == plugin_name }
18
+
19
+ if builder
20
+ builder.new(parts, values)
21
+ else
22
+ warn "No builder found for plugin '#{plugin_name}'. Using default"
23
+ self.new(parts, values)
24
+ end.generate_datapoints
25
+ end
26
+
27
+ attr_reader :parts, :values,
28
+ :interval, :timestamp,
29
+ :host, :plugin, :plugin_instance,
30
+ :type, :type_instance
31
+
32
+ def initialize(parts, values)
33
+ @parts = parts.dup
34
+ parts.each do |part|
35
+ next if part.content == ""
36
+ case part
37
+ when Packet::Host then @host = part.content
38
+ when Packet::Time then @timestamp = part.content
39
+ when Packet::Interval then @interval = part.content
40
+ when Packet::Plugin then @plugin = part.content
41
+ when Packet::PluginInstance then @plugin_instance = part.content
42
+ when Packet::Type then @type = part.content
43
+ when Packet::TypeInstance then @type_instance = part.content
44
+ end
45
+ end
46
+
47
+ @values = values
48
+ end
49
+
50
+ def generate_datapoints
51
+ @values.values.map do |val|
52
+ Datapoint.new(metric_name, timestamp, val.value)
53
+ end
54
+ end
55
+
56
+ def metric_name(parts = nil)
57
+ parts ||= [@host, @plugin, @plugin_instance, @type, @type_instance]
58
+ parts.compact.to_csv.chomp
59
+ end
60
+
61
+ def self.subclasses
62
+ @subclasses ||= []
63
+ end
64
+
65
+ def self.inherited(subclass)
66
+ subclasses << subclass
67
+ end
68
+
69
+ class CPU < DatapointBuilder
70
+
71
+ def self.plugin_name
72
+ 'cpu'
73
+ end
74
+
75
+ def metric_name
76
+ super [@host, @plugin, @plugin_instance, @type_instance, 'jiffies/s']
77
+ end
78
+
79
+ def generate_datapoints
80
+ current_value = @values.values.first.value
81
+ datapoints = []
82
+ unless previous_value.nil?
83
+ delta = current_value - previous_value
84
+ rate = delta.to_f / interval
85
+ datapoints = [Datapoint.new(metric_name, timestamp, rate)]
86
+ end
87
+
88
+ self.previous_value = current_value
89
+
90
+ datapoints
91
+ end
92
+
93
+ def previous_value
94
+ @previous_value ||= PREVIOUS_VALUES[metric_name]
95
+ end
96
+
97
+ def previous_value=(value)
98
+ PREVIOUS_VALUES[metric_name] = value
99
+ end
100
+
101
+
102
+ end
103
+
104
+ class LoadAvg < DatapointBuilder
105
+
106
+ def self.plugin_name
107
+ 'load'
108
+ end
109
+
110
+ def generate_datapoints
111
+ datapoints = []
112
+ @values.values.each_with_index do |val, i|
113
+ instance = case i
114
+ when 0 then "1min"
115
+ when 1 then "5min"
116
+ when 2 then "15min"
117
+ end
118
+
119
+ datapoints << Datapoint.new(metric_name([@host, "load", instance]),
120
+ timestamp,
121
+ val.value)
122
+ end
123
+ datapoints
124
+ end
125
+
126
+ end
127
+
128
+ class Memory < DatapointBuilder
129
+
130
+ def self.plugin_name
131
+ 'memory'
132
+ end
133
+
134
+ def metric_name
135
+ super [@host, @plugin, @type_instance, "Bytes"]
136
+ end
137
+
138
+ end
139
+
140
+ end
141
+
142
+ end
@@ -0,0 +1,87 @@
1
+
2
+ module CollectdServer
3
+
4
+ class Mapper
5
+ attr_reader :metric_name, :timestamp, :value
6
+
7
+ def self.parse(line)
8
+ if klass = mapper_class(line)
9
+ klass.parse(line)
10
+ else
11
+ warn "No Mapper found for '#{line}'"
12
+ []
13
+ end
14
+ end
15
+
16
+ def initialize(metric_name, timestamp, value)
17
+ @metric_name, @timestamp, @value = metric_name, timestamp, value
18
+ end
19
+
20
+ def to_datapoint
21
+ %Q{"#{@metric_name}",#{@timestamp},#{@value}}
22
+ end
23
+
24
+ protected
25
+
26
+ def self.mapper_class(line)
27
+ self.subclasses.detect { |c| c.matches?(line) }
28
+ end
29
+
30
+ def self.plugin_name(line)
31
+ _, collectd_type, *_ = line.split
32
+ collectd_type.split('/')[1]
33
+ end
34
+
35
+ def self.parse_line(line)
36
+ _, path, _, data = line.split(' ')
37
+ timestamp, *values = data.split(':')
38
+
39
+ return path, timestamp, values
40
+ end
41
+
42
+ def self.metric_name_from_type(collectd_type)
43
+ @parts = collectd_type.split('/')
44
+
45
+ @host = @parts[0]
46
+ @plugin, @plugin_instance = @parts[1].split('-')
47
+ @type, @type_instance = @parts[2].split('-')
48
+
49
+ return [@host, @plugin, @plugin_instance, @type, @type_instance].join(',')
50
+ end
51
+
52
+ def self.subclasses
53
+ @subclasses ||= []
54
+ end
55
+
56
+ def self.inherited(subclass)
57
+ subclasses << subclass
58
+ end
59
+
60
+ end
61
+
62
+ class CPU < Mapper
63
+
64
+ def self.matches?(line)
65
+ plugin_name(line) =~ /cpu-[\d+]/
66
+ end
67
+
68
+ def self.parse(line)
69
+ path, timestamp, values = parse_line(line)
70
+
71
+ metric_name = metric_name_from_type(path)
72
+ values.map do |val|
73
+ self.new(metric_name, timestamp, val)
74
+ end
75
+ end
76
+
77
+ def self.metric_name_from_type(collectd_type)
78
+ super
79
+
80
+ # apollo, cpu, 0, idle
81
+ [@host, @plugin, @plugin_instance, @type_instance].join(',')
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
@@ -0,0 +1,188 @@
1
+
2
+ require File.dirname(__FILE__) + '/datapoint_builder'
3
+
4
+ module CollectdServer
5
+
6
+ class Packet
7
+ attr_reader :parts
8
+
9
+ def initialize(data)
10
+ @parts = []
11
+ data = data.dup # don't modify the data passed in
12
+ while !data.empty?
13
+ type, length = data.slice!(0,4).unpack('nn')
14
+ content_length = length - 4
15
+ content = data.slice!(0, content_length)
16
+
17
+ @parts << Part.class_for(type).new(content)
18
+ end
19
+ end
20
+
21
+ def to_datapoints
22
+ datapoints = []
23
+ stack = Stack.new
24
+
25
+ @parts.each do |part|
26
+
27
+ if part.is_a?(Values) # Values is the last "part"
28
+ datapoints += DatapointBuilder.from_parts(stack.to_a, part)
29
+ else
30
+ stack.update_part(part)
31
+ end
32
+ end
33
+
34
+ datapoints
35
+ end
36
+
37
+ class Stack
38
+
39
+ ORDER = [:host, :time, :interval, :plugin, :plugin_instance, :type, :type_instance]
40
+
41
+ def initialize
42
+ @stack = {}
43
+ end
44
+
45
+ def update_part(part)
46
+ name = part_name(part)
47
+
48
+ @stack[name] = part
49
+ end
50
+
51
+ def part_name(part)
52
+ case part
53
+ when Packet::Host then :host
54
+ when Packet::Time then :time
55
+ when Packet::Interval then :interval
56
+ when Packet::Plugin then :plugin
57
+ when Packet::PluginInstance then :plugin_instance
58
+ when Packet::Type then :type
59
+ when Packet::TypeInstance then :type_instance
60
+ end
61
+ end
62
+
63
+ def to_a
64
+ ORDER.map{ |field| @stack[field] }.compact
65
+ end
66
+
67
+ end
68
+
69
+ class Part
70
+
71
+ attr_reader :content
72
+
73
+ def self.type(number)
74
+ define_method(:type) { number }
75
+ Part.add_type(self, number)
76
+ end
77
+
78
+ def self.add_type(klass, number)
79
+ @types ||= {}
80
+ @types[number] = klass
81
+ end
82
+
83
+ def self.part_for(type, content)
84
+ if klass = self.class_for(type)
85
+ klass.new(content)
86
+ else
87
+ warn "Unrecognized type %x" % type
88
+ end
89
+ end
90
+
91
+ def self.class_for(type)
92
+ @types[type]
93
+ end
94
+
95
+ def initialize(content)
96
+ @content = content
97
+ end
98
+
99
+ end
100
+
101
+ class String < Part
102
+ def initialize(content)
103
+ @content = content[0..-2] # strip off the null byte at the end
104
+ end
105
+ end
106
+
107
+ class Number < Part
108
+ def initialize(content)
109
+ big, small = content.unpack('NN')
110
+ @content = (big << 32) + (small)
111
+ end
112
+ end
113
+
114
+ class Host < String
115
+ type 0
116
+ end
117
+
118
+ class Time < Number
119
+ type 1
120
+ end
121
+
122
+ class Plugin < String
123
+ type 2
124
+ end
125
+
126
+ class PluginInstance < String
127
+ type 3
128
+ end
129
+
130
+ class Type < String
131
+ type 4
132
+ end
133
+
134
+ class TypeInstance < String
135
+ type 5
136
+ end
137
+
138
+ class Values < Part
139
+ type 6
140
+
141
+ attr_reader :values
142
+
143
+ def initialize(content)
144
+ size = content.slice!(0,2).unpack('n').first
145
+ types = []
146
+ size.times { types << content.slice!(0,1).unpack("C").first }
147
+ @values = []
148
+ size.times do |i|
149
+ @values << Value.new_for_type(types[i], content.slice!(0,8))
150
+ end
151
+ end
152
+
153
+ class Value
154
+ attr_reader :value
155
+
156
+ def self.new_for_type(type, content)
157
+ if type == 0
158
+ Counter.new(content)
159
+ elsif type == 1
160
+ Gauge.new(content)
161
+ else
162
+ raise "Unknown value type #{type}"
163
+ end
164
+ end
165
+ end
166
+
167
+ class Counter < Value
168
+ def initialize(content)
169
+ big, small = content.unpack('NN')
170
+ @value = (big << 32) + (small)
171
+ end
172
+ end
173
+
174
+ class Gauge < Value
175
+ def initialize(content)
176
+ @value = content.unpack('d').first
177
+ end
178
+ end
179
+ end
180
+
181
+ class Interval < Number
182
+ type 7
183
+ end
184
+
185
+ end
186
+
187
+ end
188
+
@@ -0,0 +1,50 @@
1
+ module Resourceful
2
+
3
+ class SSBEAuthenticator
4
+ require 'httpauth'
5
+ require 'addressable/uri'
6
+
7
+
8
+ attr_reader :username, :password, :realm, :domain, :challenge
9
+
10
+ def initialize(username, password)
11
+ @username, @password = username, password
12
+ @realm = 'SystemShepherd'
13
+ @domain = nil
14
+ end
15
+
16
+ def update_credentials(challenge_response)
17
+ @domain = Addressable::URI.parse(challenge_response.uri).host
18
+ @challenge = HTTPAuth::Digest::Challenge.from_header(challenge_response.header['WWW-Authenticate'].first)
19
+ end
20
+
21
+ def valid_for?(challenge_response)
22
+ return false unless challenge_header = challenge_response.header['WWW-Authenticate']
23
+ begin
24
+ challenge = HTTPAuth::Digest::Challenge.from_header(challenge_header.first)
25
+ rescue HTTPAuth::UnwellformedHeader
26
+ return false
27
+ end
28
+ challenge.realm == @realm
29
+ end
30
+
31
+ def can_handle?(request)
32
+ Addressable::URI.parse(request.uri).host == @domain
33
+ end
34
+
35
+ def add_credentials_to(request)
36
+ request.header['Authorization'] = credentials_for(request)
37
+ end
38
+
39
+ def credentials_for(request)
40
+ HTTPAuth::Digest::Credentials.from_challenge(@challenge,
41
+ :username => @username,
42
+ :password => @password,
43
+ :method => request.method.to_s.upcase,
44
+ :uri => Addressable::URI.parse(request.uri).path).to_header
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "CollectdServer" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'collectd_server'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: absperf-collectd_server
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Sadauskas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-11 00:00:00 -07:00
13
+ default_executable: collectd_server
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: resourceful
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 0.5.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: fastercsv
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.4.0
34
+ version:
35
+ description:
36
+ email: psadauskas@gmail.com
37
+ executables:
38
+ - collectd_server
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - .document
46
+ - .gitignore
47
+ - LICENSE
48
+ - README.rdoc
49
+ - Rakefile
50
+ - VERSION
51
+ - bin/collectd_server
52
+ - lib/collectd_server.rb
53
+ - lib/collectd_server/datapoint.rb
54
+ - lib/collectd_server/datapoint_builder.rb
55
+ - lib/collectd_server/mapper.rb
56
+ - lib/collectd_server/packet.rb
57
+ - lib/collectd_server/ssbe_authenticator.rb
58
+ - spec/collectd_server_spec.rb
59
+ - spec/spec_helper.rb
60
+ has_rdoc: false
61
+ homepage: http://github.com/paul/collectd_server
62
+ post_install_message:
63
+ rdoc_options:
64
+ - --charset=UTF-8
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ requirements: []
80
+
81
+ rubyforge_project:
82
+ rubygems_version: 1.2.0
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: TODO
86
+ test_files:
87
+ - spec/spec_helper.rb
88
+ - spec/collectd_server_spec.rb