collectd 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rst +146 -0
- data/Rakefile +32 -0
- data/VERSION.yml +4 -0
- data/examples/em_sender.rb +19 -0
- data/examples/sender.rb +16 -0
- data/lib/collectd.rb +10 -0
- data/lib/collectd/em_server.rb +24 -0
- data/lib/collectd/em_support.rb +21 -0
- data/lib/collectd/interface.rb +211 -0
- data/lib/collectd/pkt.rb +111 -0
- data/lib/collectd/proc_stats.rb +46 -0
- data/lib/collectd/server.rb +29 -0
- data/spec/interface_spec.rb +197 -0
- metadata +69 -0
data/README.rst
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
ruby-collectd
|
2
|
+
=============
|
3
|
+
|
4
|
+
`ruby-collectd` lets you send statistics to `collectd` from Ruby.
|
5
|
+
|
6
|
+
This can be useful if you want to **instrument data** from within daemons
|
7
|
+
or servers written in Ruby.
|
8
|
+
|
9
|
+
`ruby-collectd` works by talking the `collectd` network protocol, and
|
10
|
+
sending stats periodicially to a network-aware instance of `collectd`.
|
11
|
+
|
12
|
+
Setup
|
13
|
+
-----
|
14
|
+
|
15
|
+
You need to have `collectd` load the network plugin, and listen on UDP
|
16
|
+
port `25826` (so it's acting as a server):
|
17
|
+
|
18
|
+
::
|
19
|
+
|
20
|
+
# /etc/collectd.conf
|
21
|
+
|
22
|
+
LoadPlugin network
|
23
|
+
|
24
|
+
<Plugin "network">
|
25
|
+
Listen "ff18::efc0:4a42"
|
26
|
+
</Plugin>
|
27
|
+
|
28
|
+
|
29
|
+
To install the gem, make sure GitHub's RubyGems server is known to your local
|
30
|
+
RubyGems, then install it:
|
31
|
+
|
32
|
+
::
|
33
|
+
|
34
|
+
gem sources -a http://gems.github.com
|
35
|
+
sudo gem install astro-collectd
|
36
|
+
|
37
|
+
|
38
|
+
Usage
|
39
|
+
-----
|
40
|
+
|
41
|
+
::
|
42
|
+
|
43
|
+
require 'rubygems'
|
44
|
+
require 'collectd'
|
45
|
+
|
46
|
+
First of all, specify a server to send data to:
|
47
|
+
|
48
|
+
::
|
49
|
+
|
50
|
+
Collectd.add_server(interval=10, addr='ff18::efc0:4a42', port=25826)
|
51
|
+
|
52
|
+
Each server you add will receive all the data you push later.
|
53
|
+
An interval of 10 is quite reasonable. Because of UDP and `collectd`'s
|
54
|
+
network buffering, you can set the interval to less than 10, but you
|
55
|
+
won't see much benefit.
|
56
|
+
|
57
|
+
`ruby-collectd` gives you a free data collector out of the box, and it's
|
58
|
+
a nice gentle introduction to instrumenting your app.
|
59
|
+
|
60
|
+
To collect memory and CPU statistics of your Ruby process, do:
|
61
|
+
|
62
|
+
::
|
63
|
+
|
64
|
+
Stats = Collectd.my_process(:woo_data)
|
65
|
+
Stats.with_full_proc_stats
|
66
|
+
|
67
|
+
In the first line, we set up a new plugin. ``my_process`` is the plugin
|
68
|
+
name (magically handled by method_missing), and ``:woo_data`` is the
|
69
|
+
plugin instance.
|
70
|
+
|
71
|
+
A **plugin name** is generally an application's name, and a **plugin instance**
|
72
|
+
is a unique identifier of an instance of an application (i.e. you have
|
73
|
+
multiple daemons or scripts running at the same time).
|
74
|
+
|
75
|
+
In the second line, ``with_full_proc_stats`` is a method provided by
|
76
|
+
`ruby-collectd` that collects stats about the current running process.
|
77
|
+
It makes use of polled gauges, which we talk about later.
|
78
|
+
|
79
|
+
Behind the scenes, ``with_full_proc_stats`` is using a **simple interface**
|
80
|
+
you can use to instrument your own data.
|
81
|
+
|
82
|
+
Back in the first line we set up a plugin which we wanted to record some
|
83
|
+
data on. ``with_full_proc_stats`` sets up **types**, which are a kind of data
|
84
|
+
you are measuring (in this case CPU and memory usage).
|
85
|
+
|
86
|
+
You can do this yourself like this:
|
87
|
+
|
88
|
+
::
|
89
|
+
|
90
|
+
Stats = Collectd.my_daemon(:backend)
|
91
|
+
|
92
|
+
# Set counter absolutely
|
93
|
+
Stats.my_counter(:my_sleep).counter = 0
|
94
|
+
Stats.my_gauge(:my_gauge).gauge = 23
|
95
|
+
|
96
|
+
loop do
|
97
|
+
# Increment counter relatively
|
98
|
+
Stats.my_counter(:my_sleep).count! 5
|
99
|
+
# Set gauge absolutely
|
100
|
+
Stats.my_gauge(:my_stack).gauge = rand(40)
|
101
|
+
sleep 5
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
(Don't worry if this doesn't make sense - gauges and counters are explained
|
106
|
+
below)
|
107
|
+
|
108
|
+
You can also **poll** for your data, if you feel comfortable with that:
|
109
|
+
|
110
|
+
::
|
111
|
+
|
112
|
+
Stats.counter(:seconds_elapsed).polled_counter do
|
113
|
+
Time.now.to_i
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
Glossary / collectd's data model
|
118
|
+
--------------------------------
|
119
|
+
|
120
|
+
`collectd` groups data by **six categories:**
|
121
|
+
|
122
|
+
* *hostname* is grabbed from ``hostname -f``
|
123
|
+
* *plugin* is the application's name
|
124
|
+
* *plugin-instance* is passed from the programs' side with the
|
125
|
+
programs instance identifier, useful if you're running the same
|
126
|
+
script twice (PIDs are quite too random)
|
127
|
+
* *type* is the kind of data you are measuring and must be defined in
|
128
|
+
types.db_ for collectd to understand
|
129
|
+
* *type-instance* provides further distinction and have no relation to
|
130
|
+
other type-instances. Multiple type-instances are only rendered into
|
131
|
+
one graph by collection3 if defined with module GenericStacked.
|
132
|
+
* *values* are one or more field names and types belonging
|
133
|
+
together. The exact amount of fields and their corresponding names
|
134
|
+
(useful to collection3) are specified in collectd's types.db_.
|
135
|
+
|
136
|
+
A value can be either of **two types:**
|
137
|
+
|
138
|
+
* *COUNTER* is for increasing counters where you want to plot the
|
139
|
+
delta. Network interface traffic counters are a good example.
|
140
|
+
* *GAUGE* is values that go up and down to be plotted as-is, like a
|
141
|
+
temperature graph.
|
142
|
+
|
143
|
+
|
144
|
+
.. _types.db: http://collectd.org/documentation/manpages/types.db.5.shtml
|
145
|
+
|
146
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
rescue LoadError
|
4
|
+
raise "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
5
|
+
end
|
6
|
+
|
7
|
+
Jeweler::Tasks.new do |s|
|
8
|
+
s.name = "collectd"
|
9
|
+
s.summary = "Send collectd statistics from Ruby"
|
10
|
+
s.email = "astro@spaceboyz.net"
|
11
|
+
s.homepage = "http://github.com/astro/ruby-collectd"
|
12
|
+
s.authors = ["Stephan Maka"]
|
13
|
+
s.files = FileList["[A-Z]*", "{lib,spec,examples}/**/*"]
|
14
|
+
#s.add_dependency 'eventmachine'
|
15
|
+
end
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'spec/rake/spectask'
|
19
|
+
desc "Run all Spec"
|
20
|
+
Spec::Rake::SpecTask.new('spec') do |spec|
|
21
|
+
spec.spec_files = FileList['spec/*.rb']
|
22
|
+
spec.verbose = true
|
23
|
+
spec.warning = true
|
24
|
+
spec.rcov = true
|
25
|
+
spec.rcov_opts = []
|
26
|
+
spec.rcov_opts = ['--exclude', 'spec']
|
27
|
+
end
|
28
|
+
rescue LoadError
|
29
|
+
task :spec do
|
30
|
+
abort "Rspec is not available. In order to run rspec, you must: sudo gem install rspec"
|
31
|
+
end
|
32
|
+
end
|
data/VERSION.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'collectd'
|
3
|
+
|
4
|
+
EM.run do
|
5
|
+
Collectd::use_eventmachine = true
|
6
|
+
Collectd::add_server 1
|
7
|
+
Stats = Collectd.ruby_collectd(:test)
|
8
|
+
Stats.with_full_proc_stats
|
9
|
+
|
10
|
+
c = 0
|
11
|
+
EM.add_periodic_timer(0.01) do
|
12
|
+
Stats.ping(:fun).gauge = 42 + Math.sin(Time.now.to_f / 600) * 23.5
|
13
|
+
Stats.cpu(:time).counter = c
|
14
|
+
|
15
|
+
c += 1
|
16
|
+
print '.'
|
17
|
+
STDOUT.flush
|
18
|
+
end
|
19
|
+
end
|
data/examples/sender.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'collectd'
|
2
|
+
|
3
|
+
Collectd::add_server 1
|
4
|
+
Stats = Collectd::ruby_collectd(:test)
|
5
|
+
Stats.with_full_proc_stats
|
6
|
+
|
7
|
+
c = 0
|
8
|
+
loop do
|
9
|
+
Stats.ping(:fun).gauge = 42 + Math.sin(Time.now.to_f / 600) * 23.5
|
10
|
+
Stats.cpu(:time).counter = c
|
11
|
+
c += 1
|
12
|
+
Thread.pass
|
13
|
+
sleep 0.01
|
14
|
+
print '.'
|
15
|
+
STDOUT.flush
|
16
|
+
end
|
data/lib/collectd.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module Collectd
|
4
|
+
class EmServer < Values
|
5
|
+
|
6
|
+
def initialize(interval, host, port)
|
7
|
+
super(interval)
|
8
|
+
@sock = UDPSocket.new(host.index(':') ? Socket::AF_INET6 : Socket::AF_INET)
|
9
|
+
@sock.connect(host, port)
|
10
|
+
|
11
|
+
EM.add_periodic_timer(interval) do
|
12
|
+
Collectd.run_pollables_for self
|
13
|
+
Thread.critical = true
|
14
|
+
pkt = make_pkt
|
15
|
+
Thread.critical = false
|
16
|
+
begin
|
17
|
+
@sock.send(pkt, 0)
|
18
|
+
rescue SystemCallError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Collectd
|
2
|
+
# EventMachine support stuff. Included in Collectd::Plugin.
|
3
|
+
module EmPlugin
|
4
|
+
##
|
5
|
+
# Attaches additional callback and errback to deferrable to track
|
6
|
+
# a common set of success/error rate/latency
|
7
|
+
def track_deferrable(name, deferrable)
|
8
|
+
attach_time = Time.now
|
9
|
+
deferrable.callback do |*a|
|
10
|
+
push_deferrable_values("#{name}_success", attach_time)
|
11
|
+
end
|
12
|
+
deferrable.errback do |*a|
|
13
|
+
push_deferrable_values("#{name}_error", attach_time)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
def push_deferrable_values(name, attach_time)
|
17
|
+
latency(name).gauge = Time.now - attach_time
|
18
|
+
counter(name).count! 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'collectd/pkt'
|
2
|
+
require 'collectd/em_support'
|
3
|
+
|
4
|
+
|
5
|
+
module Collectd
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def hostname
|
9
|
+
@@hostname ||= `hostname -f`.strip
|
10
|
+
@@hostname
|
11
|
+
end
|
12
|
+
def hostname=(h)
|
13
|
+
@@hostname = h
|
14
|
+
end
|
15
|
+
|
16
|
+
@@servers = []
|
17
|
+
|
18
|
+
def add_server(interval, addr='ff18::efc0:4a42', port=25826)
|
19
|
+
if defined?(EM) && EM.reactor_running?
|
20
|
+
@@servers << EmServer.new(interval, addr, port)
|
21
|
+
else
|
22
|
+
@@servers << Server.new(interval, addr, port)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def <<(server)
|
27
|
+
@@servers << server
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset!
|
31
|
+
@@servers.each do |server|
|
32
|
+
server.close if server.respond_to?(:close)
|
33
|
+
end
|
34
|
+
@@servers = []
|
35
|
+
@@pollables = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def each_server(&block)
|
39
|
+
@@servers.each(&block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_pollable(&block)
|
43
|
+
@@pollables ||= []
|
44
|
+
@@pollables << block
|
45
|
+
end
|
46
|
+
def run_pollables_for(server)
|
47
|
+
@@pollables ||= []
|
48
|
+
@@pollables.each do |block|
|
49
|
+
block.call(server)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def method_missing(plugin, plugin_instance)
|
54
|
+
Plugin.new(plugin, plugin_instance)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Interface helper
|
61
|
+
class Plugin
|
62
|
+
include ProcStats
|
63
|
+
include EmPlugin
|
64
|
+
def initialize(plugin, plugin_instance)
|
65
|
+
@plugin, @plugin_instance = plugin, plugin_instance
|
66
|
+
end
|
67
|
+
def method_missing(type, type_instance)
|
68
|
+
Type.new(@plugin, @plugin_instance, type, type_instance)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Interface helper
|
74
|
+
class Type
|
75
|
+
def initialize(plugin, plugin_instance, type, type_instance)
|
76
|
+
@plugin, @plugin_instance = plugin, plugin_instance
|
77
|
+
@type, @type_instance = type, type_instance
|
78
|
+
end
|
79
|
+
##
|
80
|
+
# GAUGE
|
81
|
+
def gauge=(values)
|
82
|
+
values = [values] unless values.kind_of? Array
|
83
|
+
Collectd.each_server do |server|
|
84
|
+
server.set_gauge(plugin_type, values)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
##
|
88
|
+
# COUNTER
|
89
|
+
def counter=(values)
|
90
|
+
values = [values] unless values.kind_of? Array
|
91
|
+
Collectd.each_server do |server|
|
92
|
+
server.set_counter(plugin_type, values)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
def count!(*values)
|
96
|
+
Collectd.each_server do |server|
|
97
|
+
server.inc_counter(plugin_type, values)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
def polled_gauge(&block)
|
101
|
+
Collectd.add_pollable do |server|
|
102
|
+
values = block.call
|
103
|
+
if values
|
104
|
+
values = [values] unless values.kind_of? Array
|
105
|
+
server.set_gauge(plugin_type, values)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
def polled_count(&block)
|
110
|
+
Collectd.add_pollable do |server|
|
111
|
+
values = block.call
|
112
|
+
if values
|
113
|
+
values = [values] unless values.kind_of? Array
|
114
|
+
server.inc_counter(plugin_type, values)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
def polled_counter(&block)
|
119
|
+
Collectd.add_pollable do |server|
|
120
|
+
values = block.call
|
121
|
+
if values
|
122
|
+
values = [values] unless values.kind_of? Array
|
123
|
+
server.set_counter(plugin_type, values)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
private
|
128
|
+
##
|
129
|
+
# [plugin, plugin_instance, type, type_instance]
|
130
|
+
def plugin_type
|
131
|
+
[@plugin, @plugin_instance, @type, @type_instance]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
##
|
136
|
+
# Value-holder, baseclass for servers
|
137
|
+
class Values
|
138
|
+
attr_reader :interval
|
139
|
+
def initialize(interval)
|
140
|
+
@interval = interval
|
141
|
+
@counters = {}
|
142
|
+
@gauges = {}
|
143
|
+
end
|
144
|
+
def set_counter(plugin_type, values)
|
145
|
+
@counters[plugin_type] = values
|
146
|
+
end
|
147
|
+
def inc_counter(plugin_type, values)
|
148
|
+
old_values = @counters[plugin_type] || []
|
149
|
+
values.map! { |value|
|
150
|
+
value + (old_values.shift || 0)
|
151
|
+
}
|
152
|
+
@counters[plugin_type] = values
|
153
|
+
end
|
154
|
+
def set_gauge(plugin_type, values)
|
155
|
+
# Use count & sums for average
|
156
|
+
if @gauges.has_key?(plugin_type)
|
157
|
+
old_values = @gauges[plugin_type]
|
158
|
+
count = old_values.shift || 0
|
159
|
+
values.map! { |value| value + (old_values.shift || value) }
|
160
|
+
@gauges[plugin_type] = [count + 1] + values
|
161
|
+
else
|
162
|
+
@gauges[plugin_type] = [1] + values
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def make_pkt
|
167
|
+
plugin_type_values = {}
|
168
|
+
@counters.each do |plugin_types,values|
|
169
|
+
plugin, plugin_instance, type, type_instance = plugin_types
|
170
|
+
plugin_type_values[plugin] ||= {}
|
171
|
+
plugin_type_values[plugin][plugin_instance] ||= {}
|
172
|
+
plugin_type_values[plugin][plugin_instance][type] ||= {}
|
173
|
+
plugin_type_values[plugin][plugin_instance][type][type_instance] =
|
174
|
+
Packet::Values.new(values.map { |value| Packet::Values::Counter.new(value) })
|
175
|
+
end
|
176
|
+
@gauges.each do |plugin_types,values|
|
177
|
+
plugin, plugin_instance, type, type_instance = plugin_types
|
178
|
+
plugin_type_values[plugin] ||= {}
|
179
|
+
plugin_type_values[plugin][plugin_instance] ||= {}
|
180
|
+
plugin_type_values[plugin][plugin_instance][type] ||= {}
|
181
|
+
count = values.shift || next
|
182
|
+
values.map! { |value| value.to_f / count }
|
183
|
+
plugin_type_values[plugin][plugin_instance][type][type_instance] =
|
184
|
+
Packet::Values.new(values.map { |value| Packet::Values::Gauge.new(value) })
|
185
|
+
end
|
186
|
+
pkt = [Packet::Host.new(Collectd.hostname),
|
187
|
+
Packet::Time.new(Time.now.to_i),
|
188
|
+
Packet::Interval.new(10)]
|
189
|
+
plugin_type_values.each do |plugin,plugin_instances|
|
190
|
+
pkt << Packet::Plugin.new(plugin)
|
191
|
+
plugin_instances.each do |plugin_instance,types|
|
192
|
+
pkt << Packet::PluginInstance.new(plugin_instance)
|
193
|
+
types.each do |type,type_instances|
|
194
|
+
pkt << Packet::Type.new(type)
|
195
|
+
type_instances.each do |type_instance,values|
|
196
|
+
pkt << Packet::TypeInstance.new(type_instance)
|
197
|
+
pkt << values
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Reset only gauges. Counters are persistent for incrementing.
|
204
|
+
@gauges = {}
|
205
|
+
|
206
|
+
# And return serialized packet of parts
|
207
|
+
pkt.to_s
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
data/lib/collectd/pkt.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
module Collectd
|
2
|
+
module Packet
|
3
|
+
|
4
|
+
class Part
|
5
|
+
def to_s(content)
|
6
|
+
[type, content.length + 4].pack("nn") + content
|
7
|
+
end
|
8
|
+
|
9
|
+
##
|
10
|
+
# Makes subclasses more declarative
|
11
|
+
def self.type(number=nil)
|
12
|
+
number ? @type = number : @type
|
13
|
+
end
|
14
|
+
|
15
|
+
def type
|
16
|
+
self.class.type
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class String < Part
|
21
|
+
def initialize(s)
|
22
|
+
@s = s
|
23
|
+
end
|
24
|
+
def to_s
|
25
|
+
super "#{@s}\000"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Number < Part
|
30
|
+
def initialize(n)
|
31
|
+
@n = n
|
32
|
+
end
|
33
|
+
def to_s
|
34
|
+
super [@n >> 32, @n & 0xffffffff].pack("NN")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Host < String
|
39
|
+
type 0
|
40
|
+
end
|
41
|
+
|
42
|
+
class Time < Number
|
43
|
+
type 1
|
44
|
+
end
|
45
|
+
|
46
|
+
class Plugin < String
|
47
|
+
type 2
|
48
|
+
end
|
49
|
+
|
50
|
+
class PluginInstance < String
|
51
|
+
type 3
|
52
|
+
end
|
53
|
+
|
54
|
+
class Type < String
|
55
|
+
type 4
|
56
|
+
end
|
57
|
+
|
58
|
+
class TypeInstance < String
|
59
|
+
type 5
|
60
|
+
end
|
61
|
+
|
62
|
+
class Values < Part
|
63
|
+
type 6
|
64
|
+
def initialize(v)
|
65
|
+
@v = v
|
66
|
+
end
|
67
|
+
def to_s
|
68
|
+
types, values = [], []
|
69
|
+
@v.each { |v1|
|
70
|
+
types << [v1.type].pack("C")
|
71
|
+
values << v1.to_s
|
72
|
+
}
|
73
|
+
super [@v.length].pack("n") + types.join + values.join
|
74
|
+
end
|
75
|
+
|
76
|
+
class Counter < Part
|
77
|
+
type 0
|
78
|
+
def initialize(c)
|
79
|
+
@c = c
|
80
|
+
end
|
81
|
+
def to_s
|
82
|
+
[@c >> 32, @c & 0xffffffff].pack("NN")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class Gauge < Part
|
87
|
+
type 1
|
88
|
+
def initialize(f)
|
89
|
+
@f = f
|
90
|
+
end
|
91
|
+
def to_s
|
92
|
+
[@f].pack("d")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Interval < Number
|
98
|
+
type 7
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
class Message < String
|
103
|
+
type 0x100
|
104
|
+
end
|
105
|
+
|
106
|
+
class Severity < Number
|
107
|
+
type 0x101
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Collectd
|
2
|
+
##
|
3
|
+
# Included by Interface
|
4
|
+
module ProcStats
|
5
|
+
def process_status(field)
|
6
|
+
fields = {}
|
7
|
+
begin
|
8
|
+
IO.readlines("/proc/#{$$}/status").each { |line|
|
9
|
+
line.strip!
|
10
|
+
if line =~ /^(.+?):\s+(.+)$/
|
11
|
+
fields[$1] = $2
|
12
|
+
end
|
13
|
+
}
|
14
|
+
rescue Errno::ENOENT
|
15
|
+
nil
|
16
|
+
else
|
17
|
+
fields[field]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_polled_memory
|
22
|
+
memory('VmRSS').polled_gauge do
|
23
|
+
(v = process_status('VmRSS')) ? v.to_i * 1024 : nil
|
24
|
+
end
|
25
|
+
memory('VmSize').polled_gauge do
|
26
|
+
(v = process_status('VmSize')) ? v.to_i * 1024 : nil
|
27
|
+
end
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def with_polled_cpu
|
33
|
+
cpu('user').polled_counter do
|
34
|
+
(Process::times.utime * 100).to_i
|
35
|
+
end
|
36
|
+
cpu('system').polled_counter do
|
37
|
+
(Process::times.stime * 100).to_i
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def with_full_proc_stats
|
42
|
+
with_polled_memory
|
43
|
+
with_polled_cpu
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Collectd
|
5
|
+
class Server < Values
|
6
|
+
|
7
|
+
def initialize(interval, host, port)
|
8
|
+
super(interval)
|
9
|
+
@sock = UDPSocket.new(host.index(':') ? Socket::AF_INET6 : Socket::AF_INET)
|
10
|
+
@sock.connect(host, port)
|
11
|
+
|
12
|
+
Thread.new do
|
13
|
+
loop do
|
14
|
+
sleep interval
|
15
|
+
|
16
|
+
Collectd.run_pollables_for self
|
17
|
+
Thread.critical = true
|
18
|
+
pkt = make_pkt
|
19
|
+
Thread.critical = false
|
20
|
+
begin
|
21
|
+
@sock.send(pkt, 0)
|
22
|
+
rescue SystemCallError
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end.abort_on_exception = true
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
$: << File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'collectd'
|
3
|
+
|
4
|
+
describe Collectd do
|
5
|
+
before(:each) do
|
6
|
+
@server = mock('Server')
|
7
|
+
Collectd << @server
|
8
|
+
end
|
9
|
+
after(:each) do
|
10
|
+
Collectd.reset!
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should set_counter' do
|
14
|
+
@server.should_receive(:set_counter).
|
15
|
+
with([:plugin1, :plugin_instance1,
|
16
|
+
:type1, :type_instance1], [23])
|
17
|
+
Collectd.plugin1(:plugin_instance1).type1(:type_instance1).counter = 23
|
18
|
+
end
|
19
|
+
it 'should inc_counter' do
|
20
|
+
@server.should_receive(:inc_counter).
|
21
|
+
with([:plugin1, :plugin_instance1,
|
22
|
+
:type1, :type_instance1], [23, 42])
|
23
|
+
Collectd.plugin1(:plugin_instance1).type1(:type_instance1).count! 23, 42
|
24
|
+
end
|
25
|
+
it 'should set_gauge' do
|
26
|
+
@server.should_receive(:set_gauge).
|
27
|
+
with([:plugin1, :plugin_instance1,
|
28
|
+
:type1, :type_instance1], [23, 42, 5])
|
29
|
+
Collectd.plugin1(:plugin_instance1).type1(:type_instance1).gauge = [23, 42, 5]
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should poll a pollable' do
|
33
|
+
pollable = mock('Pollable')
|
34
|
+
Collectd.add_pollable { |*a|
|
35
|
+
pollable.call *a
|
36
|
+
}
|
37
|
+
pollable.should_receive(:call).with(@server).and_return(nil)
|
38
|
+
Collectd.run_pollables_for @server
|
39
|
+
end
|
40
|
+
it 'should poll a polled_count' do
|
41
|
+
Collectd.plugin1(:plugin_instance1).type1(:type_instance1).polled_count do
|
42
|
+
[23, 42, 5, 7]
|
43
|
+
end
|
44
|
+
@server.should_receive(:inc_counter).
|
45
|
+
with([:plugin1, :plugin_instance1,
|
46
|
+
:type1, :type_instance1], [23, 42, 5, 7])
|
47
|
+
Collectd.run_pollables_for @server
|
48
|
+
end
|
49
|
+
it 'should poll a polled_counter' do
|
50
|
+
Collectd.plugin1(:plugin_instance1).type1(:type_instance1).polled_counter do
|
51
|
+
[23, 42, 5, 7]
|
52
|
+
end
|
53
|
+
@server.should_receive(:set_counter).
|
54
|
+
with([:plugin1, :plugin_instance1,
|
55
|
+
:type1, :type_instance1], [23, 42, 5, 7])
|
56
|
+
Collectd.run_pollables_for @server
|
57
|
+
end
|
58
|
+
it 'should poll a polled_gauge' do
|
59
|
+
Collectd.plugin1(:plugin_instance1).type1(:type_instance1).polled_gauge do
|
60
|
+
[23, 42, 5, 7]
|
61
|
+
end
|
62
|
+
@server.should_receive(:set_gauge).
|
63
|
+
with([:plugin1, :plugin_instance1,
|
64
|
+
:type1, :type_instance1], [23, 42, 5, 7])
|
65
|
+
Collectd.run_pollables_for @server
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class StubServer < Collectd::Values
|
70
|
+
attr_reader :counters
|
71
|
+
attr_reader :gauges
|
72
|
+
def initialize
|
73
|
+
super(1000)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe Collectd::ProcStats do
|
78
|
+
before(:each) do
|
79
|
+
@server = StubServer.new
|
80
|
+
Collectd << @server
|
81
|
+
Collectd.plugin1(:plugin_instance1).with_full_proc_stats
|
82
|
+
Collectd.run_pollables_for @server
|
83
|
+
end
|
84
|
+
after(:each) do
|
85
|
+
Collectd.reset!
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when polling memory' do
|
89
|
+
it 'should report VmRSS' do
|
90
|
+
g = @server.gauges[[:plugin1, :plugin_instance1, :memory, "VmRSS"]]
|
91
|
+
g[0].should be_kind_of(Fixnum)
|
92
|
+
end
|
93
|
+
it 'should report VmSize' do
|
94
|
+
g = @server.gauges[[:plugin1, :plugin_instance1, :memory, "VmSize"]]
|
95
|
+
g[0].should be_kind_of(Fixnum)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
context 'when polling cpu' do
|
99
|
+
it 'should report user time' do
|
100
|
+
c = @server.counters[[:plugin1, :plugin_instance1, :cpu, "user"]]
|
101
|
+
c[0].should be_kind_of(Fixnum)
|
102
|
+
end
|
103
|
+
it 'should report system time' do
|
104
|
+
c = @server.counters[[:plugin1, :plugin_instance1, :cpu, "system"]]
|
105
|
+
c[0].should be_kind_of(Fixnum)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe Collectd::EmPlugin do
|
111
|
+
before(:each) do
|
112
|
+
@server = StubServer.new
|
113
|
+
Collectd << @server
|
114
|
+
@df = EM::DefaultDeferrable.new
|
115
|
+
Collectd.plugin1(:plugin_instance1).track_deferrable('df', @df)
|
116
|
+
end
|
117
|
+
after(:each) do
|
118
|
+
Collectd.reset!
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'when succeeding' do
|
122
|
+
it 'should callback' do
|
123
|
+
@df.succeed
|
124
|
+
end
|
125
|
+
it 'should report latency' do
|
126
|
+
@df.succeed
|
127
|
+
g = @server.gauges[[:plugin1, :plugin_instance1, :latency, 'df_success']]
|
128
|
+
g[0].should be_kind_of(Numeric)
|
129
|
+
end
|
130
|
+
it 'should increase a counter' do
|
131
|
+
@df.succeed
|
132
|
+
c = @server.counters[[:plugin1, :plugin_instance1, :counter, 'df_success']]
|
133
|
+
c[0].should be_kind_of(Numeric)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
context 'when failing' do
|
137
|
+
it 'should callback' do
|
138
|
+
@df.fail
|
139
|
+
end
|
140
|
+
it 'should report latency' do
|
141
|
+
@df.fail
|
142
|
+
g = @server.gauges[[:plugin1, :plugin_instance1, :latency, 'df_error']]
|
143
|
+
g[0].should be_kind_of(Numeric)
|
144
|
+
end
|
145
|
+
it 'should increase a counter' do
|
146
|
+
@df.fail
|
147
|
+
c = @server.counters[[:plugin1, :plugin_instance1, :counter, 'df_error']]
|
148
|
+
c[0].should be_kind_of(Numeric)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe Collectd::Server do
|
154
|
+
before :all do
|
155
|
+
Collectd.add_server(0.1)
|
156
|
+
end
|
157
|
+
after :all do
|
158
|
+
Collectd.reset!
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should spawn a Collectd::Server" do
|
162
|
+
servers = []
|
163
|
+
Collectd.each_server { |s| servers << s }
|
164
|
+
servers.length.should == 1
|
165
|
+
servers[0].should be_kind_of(Collectd::Server)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should run for 2 seconds" do
|
169
|
+
sleep 2
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe Collectd::EmServer do
|
174
|
+
it "should spawn a Collectd::Server" do
|
175
|
+
EM.run {
|
176
|
+
Collectd.add_server 0.1
|
177
|
+
EM.next_tick {
|
178
|
+
EM.stop
|
179
|
+
servers = []
|
180
|
+
Collectd.each_server { |s| servers << s }
|
181
|
+
servers.length.should == 1
|
182
|
+
servers[0].should be_kind_of(Collectd::EmServer)
|
183
|
+
Collectd.reset!
|
184
|
+
}
|
185
|
+
}
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should run for 2 seconds" do
|
189
|
+
EM.run {
|
190
|
+
Collectd.add_server 0.1
|
191
|
+
EM::Timer.new(2) {
|
192
|
+
EM.stop
|
193
|
+
Collectd.reset!
|
194
|
+
}
|
195
|
+
}
|
196
|
+
end
|
197
|
+
end if defined?(EM)
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: collectd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.12
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stephan Maka
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-12 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: astro@spaceboyz.net
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rst
|
24
|
+
files:
|
25
|
+
- README.rst
|
26
|
+
- Rakefile
|
27
|
+
- VERSION.yml
|
28
|
+
- examples/em_sender.rb
|
29
|
+
- examples/sender.rb
|
30
|
+
- lib/collectd.rb
|
31
|
+
- lib/collectd/em_server.rb
|
32
|
+
- lib/collectd/em_support.rb
|
33
|
+
- lib/collectd/interface.rb
|
34
|
+
- lib/collectd/pkt.rb
|
35
|
+
- lib/collectd/proc_stats.rb
|
36
|
+
- lib/collectd/server.rb
|
37
|
+
- spec/interface_spec.rb
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://github.com/astro/ruby-collectd
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --charset=UTF-8
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.3.5
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Send collectd statistics from Ruby
|
66
|
+
test_files:
|
67
|
+
- spec/interface_spec.rb
|
68
|
+
- examples/sender.rb
|
69
|
+
- examples/em_sender.rb
|