absperf-collectd_server 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.
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