drone_collectd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ .yardoc/
4
+ Gemfile.lock
5
+ pkg/*
6
+ doc/
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --no-private lib/**/*.rb - README.md LICENSE examples/*.rb
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in drone_collectd.gemspec
4
+ gemspec
5
+
6
+
7
+ gem 'drone', :path => "/Users/schmurfy/Dev/personal/drone"
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011-2011 Julien Ammous
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.md ADDED
@@ -0,0 +1,24 @@
1
+ ## What is this
2
+
3
+ It is an output interface to collectd for Drone.<br/>
4
+ You can find more about Drone [here](https://github.com/schmurfy/drone)
5
+
6
+ # Supported Runtimes
7
+
8
+ - MRI 1.8.7+
9
+ - Rubinius 1.2.2+
10
+
11
+
12
+ # How to use
13
+
14
+ First you obviously need a collectd server (or any server able to receive collectd network packets),
15
+ after that you need to add those lines to your types.db config file if you use collectd:
16
+
17
+ meter mean:GAUGE:U:U, rate1:GAUGE:U:U, rate5:GAUGE:U:U, rate15:GAUGE:U:U
18
+ timer min:GAUGE:0:U, max:GAUGE:0:U, mean:GAUGE:0:U, stddev:GAUGE:U:U, median:GAUGE:0:U, p75:GAUGE:0:U, p95:GAUGE:0:U
19
+
20
+ They are required to be able to understand timers and meters sent from Drone, you can update the timer
21
+ as you wish to add/remove pecentiles (check examples/collectd to see how to configure the interface for that).
22
+
23
+
24
+
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+
5
+ begin
6
+ require 'yard'
7
+ require 'bluecloth'
8
+ YARD::Rake::YardocTask.new(:doc)
9
+ rescue
10
+
11
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "drone_collectd/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "drone_collectd"
7
+ s.version = DroneCollectd::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Julien Ammous"]
10
+ s.email = []
11
+ s.homepage = ""
12
+ s.summary = %q{Drone Collectd Interface}
13
+ s.description = %q{Collectd Interface for Drone}
14
+
15
+ s.rubyforge_project = "drone_collectd"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency('drone', '~> 0.0.3')
23
+ s.add_dependency('eventmachine', '~> 0.12.10')
24
+
25
+ s.add_development_dependency("mocha")
26
+ s.add_development_dependency("bacon")
27
+ s.add_development_dependency("schmurfy-em-spec")
28
+ s.add_development_dependency("delorean")
29
+ s.add_development_dependency("simplecov")
30
+ end
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'drone'
4
+
5
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
6
+ require 'drone_collectd'
7
+
8
+
9
+ Drone::init_drone()
10
+ Drone::register_gauge("cpu:user/gauge"){ rand(200) }
11
+
12
+ class User
13
+ include Drone::Monitoring
14
+
15
+ def initialize(name)
16
+ @name = name
17
+ end
18
+
19
+ monitor_rate("apps:app1/meter")
20
+ def rename(new_name)
21
+ @name = new_name
22
+ end
23
+
24
+ monitor_time("apps:app1/timer")
25
+ def do_something
26
+ # just eat some cpu
27
+ 0.upto(rand(2000)) do |n|
28
+ str = "a"
29
+ 200.times{ str << "b" }
30
+ end
31
+ end
32
+ end
33
+
34
+ EM::run do
35
+ Drone::add_output(:collectd, 2,
36
+ :hostname => 'my_app',
37
+ :address => '127.0.0.1',
38
+ :port => 25826,
39
+ :percentiles => [0.5, 0.75, 0.95]
40
+ )
41
+ Drone::start_monitoring()
42
+
43
+ counter1 = Drone::register_counter("apps:app1/counter")
44
+ counter1.increment()
45
+
46
+ a = User.new("bob")
47
+
48
+ EM::add_periodic_timer(2) do
49
+ rand(100).times{|n| a.rename("user#{n}") }
50
+ counter1.increment()
51
+ end
52
+
53
+ EM::add_periodic_timer(1) do
54
+ a.do_something()
55
+ end
56
+ end
@@ -0,0 +1,98 @@
1
+ require File.expand_path('../parser', __FILE__)
2
+
3
+ module Drone
4
+ module Interfaces
5
+
6
+ ##
7
+ # Send data to collectd periodically, this interface except
8
+ # a specific format for the metric names which is:
9
+ #
10
+ # plugin[:plugin_instance]/type[:type_instance]
11
+ # the simplest form being:
12
+ # plugin/type
13
+ #
14
+ class Collectd < Base
15
+ include DroneCollectd
16
+
17
+ # 1.9 only ...
18
+ # NAME_FORMAT = %r{(?<plugin>\w+)(:(?<plugin_instance>\w+))?/(?<type>\w+)(:(?<type_instance>\w+))?}
19
+
20
+ NAME_FORMAT = %r{(\w+)(?::(\w+))?/(\w+)(?::(\w+))?}
21
+
22
+ ##
23
+ # Instantiate a collectd interface
24
+ #
25
+ # @param [Numeric] period the period passed to the Base class
26
+ # @param [String] hostname the hostname to use for collectd
27
+ # @param [String] address the address where the collectd daemon is listening
28
+ # @param [Numeric] port The collectd daemon port
29
+ #
30
+ def initialize(period, args = {})
31
+ super(period)
32
+ @hostname = args.delete(:hostname)
33
+ @address = args.delete(:address) || '127.0.0.1'
34
+ @port = args.delete(:port) || 25826
35
+ @reported_percentiles = args.delete(:percentiles)
36
+ @socket = EM::open_datagram_socket('0.0.0.0', nil)
37
+
38
+ unless args.empty?
39
+ raise ArgumentError, "unknown keys: #{args.keys}"
40
+ end
41
+ end
42
+
43
+ def output
44
+
45
+ Drone::each_metric do |m|
46
+ # parse the name
47
+ if NAME_FORMAT.match(m.name)
48
+ # build the packet
49
+ data = DroneCollectd::CollectdPacket.new
50
+ data.host = @hostname
51
+ data.time = Time.now.to_i
52
+ data.interval = @period
53
+ data.plugin = $1.to_s
54
+ data.plugin_instance = $2.to_s
55
+ data.type = $3.to_s
56
+ data.type_instance = $4.to_s
57
+
58
+ case m
59
+ when Metrics::Counter
60
+ data.add_value(:counter, m.value )
61
+
62
+ when Metrics::Gauge
63
+ data.add_value(:gauge, m.value )
64
+
65
+ when Metrics::Meter
66
+ # mean:GAUGE:U:U, rate1:GAUGE:U:U, rate5:GAUGE:U:U, rate15:GAUGE:U:U
67
+ data.add_value(:gauge, m.mean_rate )
68
+ data.add_value(:gauge, m.one_minute_rate )
69
+ data.add_value(:gauge, m.five_minutes_rate )
70
+ data.add_value(:gauge, m.fifteen_minutes_rate )
71
+
72
+ when Metrics::Timer
73
+ # min:GAUGE:0:U, max:GAUGE:0:U, mean:GAUGE:0:U, stddev:GAUGE:U:U, median:GAUGE:0:U, p75:GAUGE:0:U, p95:GAUGE:0:U
74
+ data.add_value(:gauge, m.min )
75
+ data.add_value(:gauge, m.max )
76
+ data.add_value(:gauge, m.mean )
77
+ data.add_value(:gauge, m.stdDev )
78
+
79
+ percs = m.percentiles( *@reported_percentiles )
80
+ percs.each do |p|
81
+ data.add_value(:gauge, p )
82
+ end
83
+
84
+ end
85
+
86
+ # and send it
87
+ @socket.send_datagram(data.build_packet, @address, @port)
88
+ else
89
+ puts "Metric with incorrect name ignored: #{m.name}"
90
+ end
91
+ end
92
+
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,86 @@
1
+ module DroneCollectd
2
+ class CollectdPacket
3
+ # part type
4
+ HOST = 0x0000
5
+ TIME = 0x0001
6
+ PLUGIN = 0x0002
7
+ PLUGIN_INSTANCE = 0x0003
8
+ TYPE = 0x0004
9
+ TYPE_INSTANCE = 0x0005
10
+ VALUES = 0x0006
11
+ INTERVAL = 0x0007
12
+ MESSAGE = 0x0100
13
+ SEVERITY = 0x0101
14
+
15
+ # data type
16
+ COUNTER = 0
17
+ GAUGE = 1
18
+ DERIVE = 2
19
+ ABSOLUTE = 3
20
+
21
+
22
+ attr_accessor :host, :time, :interval
23
+ attr_accessor :plugin, :plugin_instance
24
+ attr_accessor :type, :type_instance
25
+
26
+ def initialize
27
+ @values = []
28
+ @values_type = []
29
+ end
30
+
31
+ def add_value(type, value)
32
+ raise(ArgumentError, "unknown type: #{type}") unless CollectdPacket::const_defined?(type.to_s.upcase)
33
+ data_type = CollectdPacket::const_get(type.to_s.upcase)
34
+
35
+ @values_type << data_type
36
+ @values << value
37
+ end
38
+
39
+ def build_packet
40
+ @pkt = CollectdGenerator::string(HOST, @host)
41
+ @pkt << CollectdGenerator::number(TIME, @time)
42
+ @pkt << CollectdGenerator::number(INTERVAL, @interval)
43
+ @pkt << CollectdGenerator::string(PLUGIN, @plugin)
44
+ @pkt << CollectdGenerator::string(PLUGIN_INSTANCE, @plugin_instance)
45
+ @pkt << CollectdGenerator::string(TYPE, @type)
46
+ @pkt << CollectdGenerator::string(TYPE_INSTANCE, @type_instance)
47
+
48
+ # values part header
49
+ @pkt << [VALUES, 4 + 2 + (@values.size * 9), @values.size].pack('nnn')
50
+ # types
51
+ @pkt << @values_type.pack('C*')
52
+
53
+ # and the values
54
+ @values.each.with_index do |v, i|
55
+ case @values_type[i]
56
+ when COUNTER, ABSOLUTE, DERIVE
57
+ @pkt << [v >> 32, v & 0xffffffff].pack("NN")
58
+
59
+ when GAUGE
60
+ @pkt << [v].pack('E')
61
+
62
+ else
63
+ raise "unknown type: #{@values_type[i]}"
64
+ end
65
+ end
66
+
67
+ @pkt
68
+ end
69
+
70
+ end
71
+
72
+ module CollectdGenerator
73
+ # Encode a string (type 0, null terminated string)
74
+ def self.string(type, str)
75
+ str += "\000"
76
+ str_size = str.respond_to?(:bytesize) ? str.bytesize : str.size
77
+ [type, 4 + str_size].pack("nn") + str
78
+ end
79
+
80
+ # Encode an integer
81
+ def self.number(type, num)
82
+ [type, 12].pack("nn") + [num >> 32, num & 0xffffffff].pack("NN")
83
+ end
84
+ end
85
+
86
+ end
@@ -0,0 +1,3 @@
1
+ module DroneCollectd
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,8 @@
1
+ module DroneCollectd
2
+ def self.require_lib(path)
3
+ require File.expand_path("../#{path}", __FILE__)
4
+ end
5
+
6
+ require_lib('drone_collectd/version')
7
+ require_lib('drone_collectd/collectd')
8
+ end
data/specs/common.rb ADDED
@@ -0,0 +1,63 @@
1
+ $:.reject! { |e| e.include? 'TextMate' }
2
+
3
+ require 'rubygems'
4
+
5
+ puts "Testing with ruby #{RUBY_VERSION} and rubygems #{Gem::VERSION}"
6
+
7
+ require 'bundler/setup'
8
+
9
+ if (RUBY_VERSION >= "1.9") && ENV['COVERAGE']
10
+ require 'simplecov'
11
+ ROOT = File.expand_path('../../', __FILE__)
12
+
13
+ puts "[[ SimpleCov enabled ]]"
14
+
15
+ SimpleCov.start do
16
+ add_filter '/gems/'
17
+ add_filter '/specs/'
18
+
19
+ root(ROOT)
20
+ end
21
+ end
22
+
23
+ require 'bacon'
24
+ require 'mocha'
25
+ require 'delorean'
26
+ require 'em-spec/bacon'
27
+ EM.spec_backend = EventMachine::Spec::Bacon
28
+
29
+ $LOAD_PATH << File.expand_path('../../lib', __FILE__)
30
+
31
+ module Bacon
32
+ module MochaRequirementsCounter
33
+ def self.increment
34
+ Counter[:requirements] += 1
35
+ end
36
+ end
37
+
38
+ class Context
39
+ include Mocha::API
40
+
41
+ alias_method :it_before_mocha, :it
42
+
43
+ def it(description)
44
+ it_before_mocha(description) do
45
+ begin
46
+ mocha_setup
47
+ yield
48
+ mocha_verify(MochaRequirementsCounter)
49
+ rescue Mocha::ExpectationError => e
50
+ raise Error.new(:failed, "#{e.message}\n#{e.backtrace[0...10].join("\n")}")
51
+ ensure
52
+ mocha_teardown
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def focus(test_label)
60
+ Bacon.const_set(:RestrictName, %r{#{test_label}})
61
+ end
62
+
63
+ Bacon.summary_on_exit()
@@ -0,0 +1,49 @@
1
+ require File.expand_path('../../common', __FILE__)
2
+
3
+ require 'drone_collectd/parser'
4
+ include DroneCollectd
5
+
6
+ describe 'Packet Parser' do
7
+ before do
8
+ @packet = CollectdPacket.new()
9
+
10
+ @packet.host = "localhost"
11
+ @packet.time = 1
12
+ @packet.interval = 10
13
+ @packet.plugin = "plugin"
14
+ @packet.plugin_instance = "plugin_instance"
15
+ @packet.type = "type"
16
+ @packet.type_instance = "type_instance"
17
+ @packet.add_value(:counter, 42)
18
+ end
19
+
20
+ it 'can generate a packet' do
21
+ expected = [
22
+ "\x00\x00\x00\x0elocalhost\x00", # host
23
+ "\x00\x01\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x01", # time
24
+ "\x00\x07\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x0a", # interval
25
+ "\x00\x02\x00\x0bplugin\x00", # plugin
26
+ "\x00\x03\x00\x14plugin_instance\x00", # plugin_instance
27
+ "\x00\x04\x00\x09type\x00", # type
28
+ "\x00\x05\x00\x12type_instance\x00", # type_instance
29
+ "\x00\x06\x00\x0f\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x2a" # value
30
+
31
+ ]
32
+
33
+ if "".respond_to?(:encode)
34
+ expected = expected.map{|s| s.encode('ASCII') }
35
+ end
36
+
37
+ data = @packet.build_packet
38
+
39
+ data[0,14].should == expected[0]
40
+ data[14,12].should == expected[1]
41
+ data[26,12].should == expected[2]
42
+ data[38,11].should == expected[3]
43
+ data[49,20].should == expected[4]
44
+ data[69, 9].should == expected[5]
45
+ data[78,18].should == expected[6]
46
+ data[96,15].should == expected[7]
47
+ end
48
+
49
+ end
metadata ADDED
@@ -0,0 +1,181 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: drone_collectd
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Julien Ammous
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-22 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: drone
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 25
29
+ segments:
30
+ - 0
31
+ - 0
32
+ - 3
33
+ version: 0.0.3
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: eventmachine
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 59
45
+ segments:
46
+ - 0
47
+ - 12
48
+ - 10
49
+ version: 0.12.10
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: mocha
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ type: :development
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: bacon
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ type: :development
79
+ version_requirements: *id004
80
+ - !ruby/object:Gem::Dependency
81
+ name: schmurfy-em-spec
82
+ prerelease: false
83
+ requirement: &id005 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 3
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ type: :development
93
+ version_requirements: *id005
94
+ - !ruby/object:Gem::Dependency
95
+ name: delorean
96
+ prerelease: false
97
+ requirement: &id006 !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ hash: 3
103
+ segments:
104
+ - 0
105
+ version: "0"
106
+ type: :development
107
+ version_requirements: *id006
108
+ - !ruby/object:Gem::Dependency
109
+ name: simplecov
110
+ prerelease: false
111
+ requirement: &id007 !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ hash: 3
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ type: :development
121
+ version_requirements: *id007
122
+ description: Collectd Interface for Drone
123
+ email: []
124
+
125
+ executables: []
126
+
127
+ extensions: []
128
+
129
+ extra_rdoc_files: []
130
+
131
+ files:
132
+ - .gitignore
133
+ - .yardopts
134
+ - Gemfile
135
+ - LICENSE
136
+ - README.md
137
+ - Rakefile
138
+ - drone_collectd.gemspec
139
+ - examples/collectd.rb
140
+ - lib/drone_collectd.rb
141
+ - lib/drone_collectd/collectd.rb
142
+ - lib/drone_collectd/parser.rb
143
+ - lib/drone_collectd/version.rb
144
+ - specs/common.rb
145
+ - specs/unit/parser_spec.rb
146
+ homepage: ""
147
+ licenses: []
148
+
149
+ post_install_message:
150
+ rdoc_options: []
151
+
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ none: false
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ hash: 3
160
+ segments:
161
+ - 0
162
+ version: "0"
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ none: false
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ hash: 3
169
+ segments:
170
+ - 0
171
+ version: "0"
172
+ requirements: []
173
+
174
+ rubyforge_project: drone_collectd
175
+ rubygems_version: 1.7.2
176
+ signing_key:
177
+ specification_version: 3
178
+ summary: Drone Collectd Interface
179
+ test_files: []
180
+
181
+ has_rdoc: