collectd 0.0.12
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/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
|