drone_collectd 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.
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: