errand 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.cvsignore +5 -0
- data/.gitignore +10 -0
- data/AUTHORS +18 -0
- data/README.md +94 -0
- data/Rakefile +27 -0
- data/VERSION +1 -0
- data/extconf.rb +30 -0
- data/lib/errand.rb +138 -0
- data/lib/graph.rb +75 -0
- data/rrd_info.h +47 -0
- data/rrdtool_main.c +260 -0
- data/spec/errand_spec.rb +108 -0
- data/spec/spec.opts +4 -0
- data/test/test_rrdtool.rb +172 -0
- metadata +69 -0
data/.gitignore
ADDED
data/AUTHORS
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Based on work of
|
2
|
+
----------------
|
3
|
+
Miles Egan <miles at caddr.com>
|
4
|
+
|
5
|
+
|
6
|
+
RubyRRDtool authors + contributors
|
7
|
+
----------------------------------
|
8
|
+
David Bacher <drbacher at alum.mit.edu>
|
9
|
+
Mark Probert <probertm at acm.org>
|
10
|
+
|
11
|
+
Eric Lindvall (http://github.com/eric)
|
12
|
+
Michael Gebetsroither (http://github.com/gebi)
|
13
|
+
schmurfy (http://github.com/schmurfy/)
|
14
|
+
|
15
|
+
|
16
|
+
Errand is:
|
17
|
+
----------
|
18
|
+
Lindsay Holmwood <lindsay@holmwood.id.au>
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
Errand
|
2
|
+
======
|
3
|
+
|
4
|
+
Errand provides Ruby bindings for RRD functions (via librrd), and a clear
|
5
|
+
API for interacting with RRDs.
|
6
|
+
|
7
|
+
Check under spec/ for usage examples.
|
8
|
+
|
9
|
+
Using
|
10
|
+
=====
|
11
|
+
|
12
|
+
To set up an RRD to work with (whether it exists or otherwise):
|
13
|
+
|
14
|
+
@rrd = Errand.new(:filename => "data.rrd")
|
15
|
+
|
16
|
+
To create an RRD:
|
17
|
+
|
18
|
+
@rrd.create(:sources => [
|
19
|
+
{:name => "Counter", :type => :counter, :heartbeat => 1800, :min => 0, :max => 4294967295}],
|
20
|
+
:archives => [
|
21
|
+
{:function => :average, :xff => 0.5, :steps => 1, :rows => 2400}])
|
22
|
+
|
23
|
+
To update said RRD:
|
24
|
+
|
25
|
+
@rrd.update(:sources => [{:name => "Counter", :value => 1}]
|
26
|
+
|
27
|
+
To fetch that data:
|
28
|
+
|
29
|
+
@rrd.fetch # <= {:start => Time, :end => Time,
|
30
|
+
:data => {"Counter" => [nil, nil, nil, 1]}
|
31
|
+
|
32
|
+
Dependencies
|
33
|
+
============
|
34
|
+
|
35
|
+
**Errand** requires RRDtool version 1.2 or later. Some RRD functions such
|
36
|
+
as rrddump are only available with the latest RRDtool.
|
37
|
+
|
38
|
+
Installation is standard. If you've installed the gem, you should be ready
|
39
|
+
to go.
|
40
|
+
|
41
|
+
Otherwise, simply run:
|
42
|
+
|
43
|
+
ruby extconf.rb
|
44
|
+
make
|
45
|
+
make install
|
46
|
+
|
47
|
+
This should build a library named `rrd.so` in the current directory. If it
|
48
|
+
doesn't, please report bugs at [http://github.com/eric/rubyrrdtool/issues](http://github.com/eric/rubyrrdtool/issues)!
|
49
|
+
|
50
|
+
Building gem
|
51
|
+
============
|
52
|
+
|
53
|
+
gem build errand.gemspec
|
54
|
+
|
55
|
+
Testing
|
56
|
+
=======
|
57
|
+
|
58
|
+
Testing is done with RSpec.
|
59
|
+
|
60
|
+
To run tests:
|
61
|
+
|
62
|
+
rake spec
|
63
|
+
|
64
|
+
Todo
|
65
|
+
====
|
66
|
+
|
67
|
+
* Extend documentation with examples
|
68
|
+
|
69
|
+
|
70
|
+
License
|
71
|
+
=======
|
72
|
+
|
73
|
+
(the MIT license)
|
74
|
+
|
75
|
+
Copyright (c) 2006
|
76
|
+
|
77
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
78
|
+
a copy of this software and associated documentation files (the
|
79
|
+
"Software"), to deal in the Software without restriction, including
|
80
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
81
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
82
|
+
permit persons to whom the Software is furnished to do so, subject to
|
83
|
+
the following conditions:
|
84
|
+
|
85
|
+
The above copyright notice and this permission notice shall be
|
86
|
+
included in all copies or substantial portions of the Software.
|
87
|
+
|
88
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
89
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
90
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
91
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
92
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
93
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
94
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gemspec|
|
6
|
+
gemspec.name = "errand"
|
7
|
+
gemspec.summary = "Ruby language binding for RRD tool version 1.2+"
|
8
|
+
gemspec.description = "Errand provides Ruby bindings for RRD functions (via librrd), and a concise DSL for interacting with RRDs."
|
9
|
+
gemspec.email = "lindsay@holmwood.id.au"
|
10
|
+
gemspec.homepage = "http://auxesis.github.com/errand"
|
11
|
+
gemspec.authors = ["Lindsay Holmwood"]
|
12
|
+
gemspec.extensions = ["extconf.rb"]
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'spec/rake/spectask'
|
20
|
+
|
21
|
+
Spec::Rake::SpecTask.new do |t|
|
22
|
+
t.spec_opts = ["--options", "spec/spec.opts"]
|
23
|
+
end
|
24
|
+
rescue LoadError
|
25
|
+
puts "RSpec not available. Install it with: gem install rspec"
|
26
|
+
end
|
27
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.7.0
|
data/extconf.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# $Id: extconf.rb,v 1.4 2008/03/11 22:47:06 dbach Exp $
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
|
5
|
+
# If there are build errors, be sure to say where your rrdtool lives:
|
6
|
+
#
|
7
|
+
# ruby ./extconf.rb --with-rrd-dir=/usr/local/rrdtool-1.2.12
|
8
|
+
|
9
|
+
libpaths=%w(/lib /usr/lib /usr/local/lib /sw/lib /opt/local/lib)
|
10
|
+
%w(art_lgpl_2 freetype png z).sort.reverse.each do |lib|
|
11
|
+
find_library(lib, nil, *libpaths)
|
12
|
+
end
|
13
|
+
|
14
|
+
dir_config("rrd")
|
15
|
+
# rrd_first is only defined rrdtool >= 1.2.x
|
16
|
+
have_library("rrd", "rrd_first")
|
17
|
+
|
18
|
+
# rrd_dump_r has 2nd arg in rrdtool >= 1.2.14
|
19
|
+
if try_link(<<EOF)
|
20
|
+
#include <rrd.h>
|
21
|
+
main()
|
22
|
+
{
|
23
|
+
rrd_dump_r("/dev/null", NULL);
|
24
|
+
}
|
25
|
+
EOF
|
26
|
+
$CFLAGS += " -DHAVE_RRD_DUMP_R_2"
|
27
|
+
end
|
28
|
+
|
29
|
+
create_makefile("errand_backend")
|
30
|
+
|
data/lib/errand.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$: << File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
4
|
+
require 'errand_backend'
|
5
|
+
|
6
|
+
class Errand
|
7
|
+
def initialize(opts={})
|
8
|
+
raise ArgumentError unless opts[:filename]
|
9
|
+
@filename = opts[:filename]
|
10
|
+
@backend = ::ErrandBackend
|
11
|
+
end
|
12
|
+
|
13
|
+
def dump(opts={})
|
14
|
+
output = opts[:filename] || ""
|
15
|
+
@backend.dump(@filename, output)
|
16
|
+
end
|
17
|
+
|
18
|
+
def last
|
19
|
+
@backend.last(@filename)
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch(opts={})
|
23
|
+
start = (opts[:start] || Time.now.to_i - 3600).to_s
|
24
|
+
finish = (opts[:finish] || Time.now.to_i).to_s
|
25
|
+
function = opts[:function] ? opts[:function].to_s.upcase : "AVERAGE"
|
26
|
+
|
27
|
+
args = [@filename, "--start", start, "--end", finish, function]
|
28
|
+
|
29
|
+
data = @backend.fetch(*args)
|
30
|
+
start = data[0]
|
31
|
+
finish = data[1]
|
32
|
+
labels = data[2]
|
33
|
+
values = data[3]
|
34
|
+
points = {}
|
35
|
+
|
36
|
+
# compose a saner representation of the data
|
37
|
+
labels.each_with_index do |label, index|
|
38
|
+
points[label] = []
|
39
|
+
values.each do |tuple|
|
40
|
+
value = tuple[index].nan? ? nil : tuple[index]
|
41
|
+
points[label] << value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
{:start => start, :finish => finish, :data => points}
|
46
|
+
end
|
47
|
+
|
48
|
+
def create(opts={})
|
49
|
+
step = (opts[:step] || 300).to_s
|
50
|
+
start = (opts[:start] || Time.now.to_i - 10).to_s
|
51
|
+
|
52
|
+
options = ["--step", step, "--start", start]
|
53
|
+
|
54
|
+
sources = opts[:sources].map { |source|
|
55
|
+
name = source[:name]
|
56
|
+
type = source[:type].to_s.upcase
|
57
|
+
heartbeat = source[:heartbeat]
|
58
|
+
min = source[:min]
|
59
|
+
max = source[:max]
|
60
|
+
|
61
|
+
ds = ["DS", name, type]
|
62
|
+
ds += [heartbeat, min, max] if heartbeat && min && max
|
63
|
+
|
64
|
+
ds.join(':')
|
65
|
+
}
|
66
|
+
|
67
|
+
archives = opts[:archives].map { |archive|
|
68
|
+
function = archive[:function].to_s.upcase
|
69
|
+
xff = archive[:xff]
|
70
|
+
steps = archive[:steps]
|
71
|
+
rows = archive[:rows]
|
72
|
+
|
73
|
+
rra = ["RRA", function]
|
74
|
+
rra += [xff, steps, rows] if xff && steps && rows
|
75
|
+
|
76
|
+
rra.join(':')
|
77
|
+
}
|
78
|
+
|
79
|
+
args = options + sources + archives
|
80
|
+
@backend.create(@filename, *args) # * "flattens" the array
|
81
|
+
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
def update(opts={})
|
86
|
+
time_specified = opts[:sources].find { |source| source[:time] }
|
87
|
+
times = opts[:sources].map {|s| s[:time]}.sort.uniq if time_specified
|
88
|
+
|
89
|
+
sources = opts[:sources].map { |source|
|
90
|
+
source[:name]
|
91
|
+
}.join(':')
|
92
|
+
|
93
|
+
case
|
94
|
+
when time_specified && times.size == 1
|
95
|
+
values = "#{times.first}:" + opts[:sources].map { |s|
|
96
|
+
s[:value]
|
97
|
+
}.join(':')
|
98
|
+
|
99
|
+
args = ["--template", sources, values]
|
100
|
+
@backend.update(@filename, *args)
|
101
|
+
when time_specified && times.size > 1
|
102
|
+
times.each do |t|
|
103
|
+
points = opts[:sources].find_all { |source| source[:time] == t }
|
104
|
+
|
105
|
+
sources = points.map { |p|
|
106
|
+
p[:name]
|
107
|
+
}.join(':')
|
108
|
+
|
109
|
+
values = "#{t}:" + points.map { |p|
|
110
|
+
p[:value]
|
111
|
+
}.join(':')
|
112
|
+
|
113
|
+
args = ["--template", sources, values]
|
114
|
+
@backend.update(@filename, *args)
|
115
|
+
end
|
116
|
+
|
117
|
+
when !time_specified
|
118
|
+
values = "N:" + opts[:sources].map { |source|
|
119
|
+
source[:value]
|
120
|
+
}.join(':')
|
121
|
+
|
122
|
+
args = ["--template", sources, values]
|
123
|
+
@backend.update(@filename, *args)
|
124
|
+
end
|
125
|
+
|
126
|
+
true
|
127
|
+
end
|
128
|
+
|
129
|
+
def info
|
130
|
+
@backend.info(@filename)
|
131
|
+
end
|
132
|
+
|
133
|
+
# ordered array of data sources as defined in rrd
|
134
|
+
def data_sources
|
135
|
+
self.info.keys.grep(/^ds\[/).map { |ds| ds[3..-1].split(']').first}.uniq
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
data/lib/graph.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
#
|
2
|
+
# ideas for a graphing DSL
|
3
|
+
#
|
4
|
+
|
5
|
+
module RRDtool
|
6
|
+
|
7
|
+
# Simple Ruby object to generate the RRD graph parameters
|
8
|
+
#
|
9
|
+
class Graph
|
10
|
+
|
11
|
+
# Graph attributes can appear in any order
|
12
|
+
attr :image_file, :image_format
|
13
|
+
attr :title
|
14
|
+
attr :vertical_label
|
15
|
+
|
16
|
+
attr :width, :height
|
17
|
+
attr :start, :duration, :step
|
18
|
+
attr :upper_limit, :lower_limit
|
19
|
+
|
20
|
+
# LATER: there are lots of other RRDtool graph attributes
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@graph_command = []
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# The graph commands must be preserved in order
|
28
|
+
#
|
29
|
+
|
30
|
+
# Create a DEF
|
31
|
+
def fetch(name, rrd_file, value, type)
|
32
|
+
# format: DEF:name=rrd_file:value:type
|
33
|
+
@graph_command << "DEF:#{name}=#{rrd_file}:#{value}:#{type}"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create a CDEF
|
37
|
+
def compute(name, expr)
|
38
|
+
# format: CDEF:name=expr
|
39
|
+
@graph_command << "CDEF:#{name}=#{expr}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create GPRINT
|
43
|
+
def gprint(name, type, print_expr)
|
44
|
+
# format: GPRINTF:name:type:print_expr
|
45
|
+
@graph_command << "GPRINTF:#{name}:#{type}:#{print_expr}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create a LINE(1,2,...)
|
49
|
+
def line(name, width, color, label)
|
50
|
+
# format: LINEwidth:name#color:label
|
51
|
+
@graph_command << "LINE#{width}:#{name}##{color}:#{label}"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Create a AREA
|
55
|
+
def area(name, color)
|
56
|
+
# format: AREA:name#color
|
57
|
+
@graph_command << "AREA:#{name}##{color}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Create a STACK
|
61
|
+
def stack(name, color)
|
62
|
+
# format: STACK:name#color
|
63
|
+
@graph_command << "STACK:#{name}##{color}"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Turn the graph definition into a real image
|
67
|
+
def publish
|
68
|
+
# LATER: add in the graph attributes --
|
69
|
+
# i.e., turn the graph attribute hash into an array of [ '--key', 'value', ...]
|
70
|
+
RRDtool.graph @graph_command
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
data/rrd_info.h
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
/* -*- C -*-
|
2
|
+
* file: rrd_info.h
|
3
|
+
* date: $Date: 2008/03/11 22:47:06 $
|
4
|
+
* init: 2005-07-26
|
5
|
+
* vers: $Version$
|
6
|
+
* auth: $Author: dbach $
|
7
|
+
* -----
|
8
|
+
*
|
9
|
+
* Support file to add rrd_info() to rrd.h
|
10
|
+
*
|
11
|
+
*/
|
12
|
+
#ifndef __RRD_INFO_H
|
13
|
+
#define __RRD_INFO_H
|
14
|
+
|
15
|
+
#if defined(__cplusplus) || defined(c_plusplus)
|
16
|
+
extern "C" {
|
17
|
+
#endif
|
18
|
+
|
19
|
+
/* rrd info interface
|
20
|
+
enum info_type { RD_I_VAL=0,
|
21
|
+
RD_I_CNT,
|
22
|
+
RD_I_STR,
|
23
|
+
RD_I_INT };
|
24
|
+
*/
|
25
|
+
|
26
|
+
typedef union infoval {
|
27
|
+
unsigned long u_cnt;
|
28
|
+
rrd_value_t u_val;
|
29
|
+
char *u_str;
|
30
|
+
int u_int;
|
31
|
+
} infoval;
|
32
|
+
|
33
|
+
typedef struct info_t {
|
34
|
+
char *key;
|
35
|
+
rrd_info_type_t type;
|
36
|
+
union infoval value;
|
37
|
+
struct info_t *next;
|
38
|
+
} info_t;
|
39
|
+
|
40
|
+
//info_t *rrd_info(int, char **);
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
#if defined(__cplusplus) || defined(c_plusplus)
|
45
|
+
}
|
46
|
+
#endif
|
47
|
+
#endif /* __RRD_INFO_H */
|
data/rrdtool_main.c
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
/* $Id: rrdtool_main.c,v 1.1 2008/03/11 22:47:06 dbach Exp $
|
2
|
+
* Substantial penalty for early withdrawal.
|
3
|
+
*/
|
4
|
+
|
5
|
+
#include <unistd.h>
|
6
|
+
#include <math.h> /* for isnan */
|
7
|
+
#include <ruby.h>
|
8
|
+
#include <rrd.h>
|
9
|
+
#include "rrd_info.h"
|
10
|
+
|
11
|
+
typedef struct string_arr_t {
|
12
|
+
int len;
|
13
|
+
char **strings;
|
14
|
+
} string_arr;
|
15
|
+
|
16
|
+
VALUE mRRD;
|
17
|
+
VALUE rb_eRRDError;
|
18
|
+
|
19
|
+
typedef int (*RRDFUNC)(int argc, char ** argv);
|
20
|
+
#define RRD_CHECK_ERROR \
|
21
|
+
if (rrd_test_error()) \
|
22
|
+
rb_raise(rb_eRRDError, rrd_get_error()); \
|
23
|
+
rrd_clear_error();
|
24
|
+
|
25
|
+
string_arr string_arr_new(VALUE rb_strings)
|
26
|
+
{
|
27
|
+
string_arr a;
|
28
|
+
char buf[64];
|
29
|
+
int i;
|
30
|
+
|
31
|
+
Check_Type(rb_strings, T_ARRAY);
|
32
|
+
a.len = RARRAY_LEN(rb_strings) + 1;
|
33
|
+
|
34
|
+
a.strings = malloc(a.len * sizeof(char *));
|
35
|
+
a.strings[0] = "dummy"; /* first element is a dummy element */
|
36
|
+
|
37
|
+
for (i = 0; i < a.len - 1; i++) {
|
38
|
+
VALUE v = rb_ary_entry(rb_strings, i);
|
39
|
+
switch (TYPE(v)) {
|
40
|
+
case T_STRING:
|
41
|
+
a.strings[i + 1] = strdup(STR2CSTR(v));
|
42
|
+
break;
|
43
|
+
case T_FIXNUM:
|
44
|
+
snprintf(buf, 63, "%d", FIX2INT(v));
|
45
|
+
a.strings[i + 1] = strdup(buf);
|
46
|
+
break;
|
47
|
+
default:
|
48
|
+
rb_raise(rb_eTypeError, "invalid argument - %s, expected T_STRING or T_FIXNUM on index %d", rb_class2name(CLASS_OF(v)), i);
|
49
|
+
break;
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
return a;
|
54
|
+
}
|
55
|
+
|
56
|
+
void string_arr_delete(string_arr a)
|
57
|
+
{
|
58
|
+
int i;
|
59
|
+
|
60
|
+
/* skip dummy first entry */
|
61
|
+
for (i = 1; i < a.len; i++) {
|
62
|
+
free(a.strings[i]);
|
63
|
+
}
|
64
|
+
|
65
|
+
free(a.strings);
|
66
|
+
}
|
67
|
+
|
68
|
+
void reset_rrd_state()
|
69
|
+
{
|
70
|
+
optind = 0;
|
71
|
+
opterr = 0;
|
72
|
+
rrd_clear_error();
|
73
|
+
}
|
74
|
+
|
75
|
+
VALUE rrd_call(RRDFUNC func, VALUE args)
|
76
|
+
{
|
77
|
+
string_arr a;
|
78
|
+
|
79
|
+
a = string_arr_new(args);
|
80
|
+
reset_rrd_state();
|
81
|
+
func(a.len, a.strings);
|
82
|
+
string_arr_delete(a);
|
83
|
+
|
84
|
+
RRD_CHECK_ERROR
|
85
|
+
|
86
|
+
return Qnil;
|
87
|
+
}
|
88
|
+
|
89
|
+
VALUE rb_rrd_create(VALUE self, VALUE args)
|
90
|
+
{
|
91
|
+
return rrd_call(rrd_create, args);
|
92
|
+
}
|
93
|
+
|
94
|
+
VALUE rb_rrd_dump(VALUE self, VALUE args)
|
95
|
+
{
|
96
|
+
return rrd_call(rrd_dump, args);
|
97
|
+
}
|
98
|
+
|
99
|
+
VALUE rb_rrd_fetch(VALUE self, VALUE args)
|
100
|
+
{
|
101
|
+
string_arr a;
|
102
|
+
unsigned long i, j, k, step, ds_cnt;
|
103
|
+
rrd_value_t *raw_data;
|
104
|
+
char **raw_names;
|
105
|
+
VALUE data, names, result;
|
106
|
+
time_t start, end;
|
107
|
+
|
108
|
+
a = string_arr_new(args);
|
109
|
+
reset_rrd_state();
|
110
|
+
rrd_fetch(a.len, a.strings, &start, &end, &step, &ds_cnt, &raw_names, &raw_data);
|
111
|
+
string_arr_delete(a);
|
112
|
+
|
113
|
+
RRD_CHECK_ERROR
|
114
|
+
|
115
|
+
names = rb_ary_new();
|
116
|
+
for (i = 0; i < ds_cnt; i++) {
|
117
|
+
rb_ary_push(names, rb_str_new2(raw_names[i]));
|
118
|
+
free(raw_names[i]);
|
119
|
+
}
|
120
|
+
free(raw_names);
|
121
|
+
|
122
|
+
k = 0;
|
123
|
+
data = rb_ary_new();
|
124
|
+
for (i = start; i < end; i += step) {
|
125
|
+
VALUE line = rb_ary_new2(ds_cnt);
|
126
|
+
for (j = 0; j < ds_cnt; j++) {
|
127
|
+
rb_ary_store(line, j, rb_float_new(raw_data[k]));
|
128
|
+
k++;
|
129
|
+
}
|
130
|
+
rb_ary_push(data, line);
|
131
|
+
}
|
132
|
+
free(raw_data);
|
133
|
+
|
134
|
+
result = rb_ary_new2(4);
|
135
|
+
rb_ary_store(result, 0, INT2NUM(start));
|
136
|
+
rb_ary_store(result, 1, INT2NUM(end));
|
137
|
+
rb_ary_store(result, 2, names);
|
138
|
+
rb_ary_store(result, 3, data);
|
139
|
+
return result;
|
140
|
+
}
|
141
|
+
|
142
|
+
VALUE rb_rrd_graph(VALUE self, VALUE args)
|
143
|
+
{
|
144
|
+
string_arr a;
|
145
|
+
char **calcpr, **p;
|
146
|
+
VALUE result, print_results;
|
147
|
+
int xsize, ysize;
|
148
|
+
double ymin, ymax;
|
149
|
+
|
150
|
+
a = string_arr_new(args);
|
151
|
+
reset_rrd_state();
|
152
|
+
rrd_graph(a.len, a.strings, &calcpr, &xsize, &ysize, NULL, &ymin, &ymax);
|
153
|
+
string_arr_delete(a);
|
154
|
+
|
155
|
+
RRD_CHECK_ERROR
|
156
|
+
|
157
|
+
result = rb_ary_new2(3);
|
158
|
+
print_results = rb_ary_new();
|
159
|
+
p = calcpr;
|
160
|
+
for (p = calcpr; p && *p; p++) {
|
161
|
+
rb_ary_push(print_results, rb_str_new2(*p));
|
162
|
+
free(*p);
|
163
|
+
}
|
164
|
+
free(calcpr);
|
165
|
+
rb_ary_store(result, 0, print_results);
|
166
|
+
rb_ary_store(result, 1, INT2FIX(xsize));
|
167
|
+
rb_ary_store(result, 2, INT2FIX(ysize));
|
168
|
+
return result;
|
169
|
+
}
|
170
|
+
|
171
|
+
VALUE rb_rrd_info(VALUE self, VALUE args)
|
172
|
+
{
|
173
|
+
string_arr a;
|
174
|
+
info_t *p, *data;
|
175
|
+
VALUE result;
|
176
|
+
|
177
|
+
a = string_arr_new(args);
|
178
|
+
data = rrd_info(a.len, a.strings);
|
179
|
+
string_arr_delete(a);
|
180
|
+
|
181
|
+
RRD_CHECK_ERROR
|
182
|
+
|
183
|
+
result = rb_hash_new();
|
184
|
+
while (data) {
|
185
|
+
VALUE key = rb_str_new2(data->key);
|
186
|
+
switch (data->type) {
|
187
|
+
case RD_I_VAL:
|
188
|
+
if (isnan(data->value.u_val)) {
|
189
|
+
rb_hash_aset(result, key, Qnil);
|
190
|
+
}
|
191
|
+
else {
|
192
|
+
rb_hash_aset(result, key, rb_float_new(data->value.u_val));
|
193
|
+
}
|
194
|
+
break;
|
195
|
+
case RD_I_CNT:
|
196
|
+
rb_hash_aset(result, key, INT2FIX(data->value.u_cnt));
|
197
|
+
break;
|
198
|
+
case RD_I_STR:
|
199
|
+
rb_hash_aset(result, key, rb_str_new2(data->value.u_str));
|
200
|
+
free(data->value.u_str);
|
201
|
+
break;
|
202
|
+
}
|
203
|
+
p = data;
|
204
|
+
data = data->next;
|
205
|
+
free(p);
|
206
|
+
}
|
207
|
+
return result;
|
208
|
+
}
|
209
|
+
|
210
|
+
VALUE rb_rrd_last(VALUE self, VALUE args)
|
211
|
+
{
|
212
|
+
string_arr a;
|
213
|
+
time_t last;
|
214
|
+
|
215
|
+
a = string_arr_new(args);
|
216
|
+
reset_rrd_state();
|
217
|
+
last = rrd_last(a.len, a.strings);
|
218
|
+
string_arr_delete(a);
|
219
|
+
|
220
|
+
RRD_CHECK_ERROR
|
221
|
+
|
222
|
+
return rb_funcall(rb_cTime, rb_intern("at"), 1, INT2FIX(last));
|
223
|
+
}
|
224
|
+
|
225
|
+
VALUE rb_rrd_resize(VALUE self, VALUE args)
|
226
|
+
{
|
227
|
+
return rrd_call(rrd_resize, args);
|
228
|
+
}
|
229
|
+
|
230
|
+
VALUE rb_rrd_restore(VALUE self, VALUE args)
|
231
|
+
{
|
232
|
+
return rrd_call(rrd_restore, args);
|
233
|
+
}
|
234
|
+
|
235
|
+
VALUE rb_rrd_tune(VALUE self, VALUE args)
|
236
|
+
{
|
237
|
+
return rrd_call(rrd_tune, args);
|
238
|
+
}
|
239
|
+
|
240
|
+
VALUE rb_rrd_update(VALUE self, VALUE args)
|
241
|
+
{
|
242
|
+
return rrd_call(rrd_update, args);
|
243
|
+
}
|
244
|
+
|
245
|
+
void Init_errand_backend()
|
246
|
+
{
|
247
|
+
mRRD = rb_define_module("ErrandBackend");
|
248
|
+
rb_eRRDError = rb_define_class("ErrandError", rb_eStandardError);
|
249
|
+
|
250
|
+
rb_define_module_function(mRRD, "create", rb_rrd_create, -2);
|
251
|
+
rb_define_module_function(mRRD, "dump", rb_rrd_dump, -2);
|
252
|
+
rb_define_module_function(mRRD, "fetch", rb_rrd_fetch, -2);
|
253
|
+
rb_define_module_function(mRRD, "graph", rb_rrd_graph, -2);
|
254
|
+
rb_define_module_function(mRRD, "last", rb_rrd_last, -2);
|
255
|
+
rb_define_module_function(mRRD, "resize", rb_rrd_resize, -2);
|
256
|
+
rb_define_module_function(mRRD, "restore", rb_rrd_restore, -2);
|
257
|
+
rb_define_module_function(mRRD, "tune", rb_rrd_tune, -2);
|
258
|
+
rb_define_module_function(mRRD, "update", rb_rrd_update, -2);
|
259
|
+
rb_define_module_function(mRRD, "info", rb_rrd_info, -2);
|
260
|
+
}
|
data/spec/errand_spec.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$: << File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
4
|
+
require 'lib/errand'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
describe Errand do
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
@tmpdir = Dir.mktmpdir
|
11
|
+
end
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
@rrd = Errand.new(:filename => File.join(@tmpdir, 'test.rrd'))
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should create data" do
|
18
|
+
|
19
|
+
@rrd.create(:start => Time.now.to_i - 1, :step => 300,
|
20
|
+
:sources => [
|
21
|
+
{:name => "Counter", :type => :counter, :heartbeat => 1800, :min => 0, :max => 4294967295},
|
22
|
+
{:name => "Total", :type => :derive, :heartbeat => 1800, :min => 0, :max => 'U'} ],
|
23
|
+
:archives => [
|
24
|
+
{:function => :average, :xff => 0.5, :steps => 1, :rows => 2400}]).should be_true
|
25
|
+
|
26
|
+
sources = @rrd.info.keys.grep(/^ds\[/).map { |ds| ds[3..-1].split(']').first}.uniq
|
27
|
+
sources.size.should == 2
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should dump data" do
|
32
|
+
@rrd.create(:start => Time.now.to_i - 1, :step => 300,
|
33
|
+
:sources => [
|
34
|
+
{:name => "Counter", :type => :counter, :heartbeat => 1800, :min => 0, :max => 4294967295},
|
35
|
+
{:name => "Total", :type => :derive, :heartbeat => 1800, :min => 0, :max => 'U'} ],
|
36
|
+
:archives => [
|
37
|
+
{:function => :average, :xff => 0.5, :steps => 1, :rows => 2400}]).should be_true
|
38
|
+
|
39
|
+
tmpfile = File.join(@tmpdir, 'test.out')
|
40
|
+
lambda {
|
41
|
+
@rrd.dump(:filename => tmpfile)
|
42
|
+
@rrd.dump # <= no args
|
43
|
+
}.should_not raise_error
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should update rrds" do
|
47
|
+
@rrd.create(:start => Time.now.to_i - 1, :step => 1,
|
48
|
+
:sources => [
|
49
|
+
{:name => "Sum", :type => :gauge, :heartbeat => 1800, :min => 0, :max => 4294967295},
|
50
|
+
{:name => "Total", :type => :gauge, :heartbeat => 1800, :min => 0, :max => 'U'} ],
|
51
|
+
:archives => [
|
52
|
+
{:function => :average, :xff => 0.5, :steps => 1, :rows => 2400}]).should be_true
|
53
|
+
|
54
|
+
@rrd.update(:sources => [
|
55
|
+
{:name => "Sum", :value => 1},
|
56
|
+
{:name => "Total", :value => 30}]).should be_true
|
57
|
+
|
58
|
+
@rrd.fetch[:data].each_pair do |source, values|
|
59
|
+
values.compact.size.should > 0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should update with weird data" do
|
64
|
+
time = Time.now.to_i - 300
|
65
|
+
@rrd.create(:start => time - 1, :step => 1,
|
66
|
+
:sources => [
|
67
|
+
{:name => "Sum", :type => :gauge, :heartbeat => 1800, :min => 0, :max => 4294967295},
|
68
|
+
{:name => "Total", :type => :gauge, :heartbeat => 1800, :min => 0, :max => 'U'} ],
|
69
|
+
:archives => [
|
70
|
+
{:function => :average, :xff => 0.5, :steps => 1, :rows => 2400}]).should be_true
|
71
|
+
|
72
|
+
updates = []
|
73
|
+
100.times do |i|
|
74
|
+
updates << {:name => "Sum", :time => time + i , :value => 1}
|
75
|
+
updates << {:name => "Total", :time => time + i * 4, :value => 30}
|
76
|
+
end
|
77
|
+
|
78
|
+
@rrd.update(:sources => updates)
|
79
|
+
|
80
|
+
@rrd.fetch[:data].each_pair do |source, values|
|
81
|
+
values.compact.size.should > 0
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should fetch data" do
|
86
|
+
time = Time.now.to_i - 300
|
87
|
+
@rrd.create(:start => time - 1, :step => 1,
|
88
|
+
:sources => [
|
89
|
+
{:name => "Sum", :type => :gauge, :heartbeat => 1800, :min => 0, :max => 4294967295},
|
90
|
+
{:name => "Total", :type => :gauge, :heartbeat => 1800, :min => 0, :max => 'U'} ],
|
91
|
+
:archives => [
|
92
|
+
{:function => :average, :xff => 0.5, :steps => 1, :rows => 2400}]).should be_true
|
93
|
+
|
94
|
+
100.times do |i|
|
95
|
+
@rrd.update(:sources => [
|
96
|
+
{:name => "Sum", :time => time + i, :value => 1},
|
97
|
+
{:name => "Total", :time => time + i, :value => 30}]).should be_true
|
98
|
+
end
|
99
|
+
|
100
|
+
@rrd.fetch[:data].each_pair do |source, values|
|
101
|
+
values.compact.size.should > 0
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should return timestamp of first value"
|
106
|
+
it "should return timestamp of last value"
|
107
|
+
|
108
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
# Code Generated by ZenTest v. 3.2.0
|
2
|
+
# classname: asrt / meth = ratio%
|
3
|
+
|
4
|
+
$:.unshift '..'
|
5
|
+
|
6
|
+
require 'test/unit' unless defined? $ZENTEST and $ZENTEST
|
7
|
+
require 'rrd'
|
8
|
+
|
9
|
+
class TestRRDtool < Test::Unit::TestCase
|
10
|
+
def setup
|
11
|
+
@f = 'test.rrd'
|
12
|
+
@g = (File.basename @f, '.rrd') + '.png'
|
13
|
+
File.delete @f if File.exists? @f
|
14
|
+
File.delete @g if File.exists? @g
|
15
|
+
@r = RRDtool.new @f
|
16
|
+
end
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
File.delete @f if File.exists? @f
|
20
|
+
# note: leave the graph file around to check it by hand
|
21
|
+
@r = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_file
|
25
|
+
@start = Time.now.to_i - 1
|
26
|
+
@step = 300
|
27
|
+
@r.create @step, @start, [ "DS:a:GAUGE:#{2*@step}:0:1",
|
28
|
+
"DS:b:GAUGE:#{2*@step}:-10:10",
|
29
|
+
"RRA:AVERAGE:0.5:1:300",
|
30
|
+
"RRA:LAST:0.5:1:10" ]
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_data
|
34
|
+
scale = 10
|
35
|
+
ts = Time.now.to_i + 1
|
36
|
+
while (ts < (@start.to_i + @step*300)) do
|
37
|
+
@r.update "a:b", ["#{ts}:#{rand()}:#{Math.sin(ts / @step / 10)}"]
|
38
|
+
ts += @step
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_create
|
43
|
+
create_file
|
44
|
+
assert File.readable?(@f)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_dump
|
48
|
+
# RRDtool.dump is not defined for older versions of RRDtool
|
49
|
+
dump_file = "#{@f}.out"
|
50
|
+
create_file
|
51
|
+
create_data
|
52
|
+
if RRDtool.version < 1.2015 then
|
53
|
+
assert_raise NoMethodError do
|
54
|
+
@r.dump dump_file
|
55
|
+
end
|
56
|
+
else
|
57
|
+
@r.dump dump_file
|
58
|
+
assert File.readable? dump_file
|
59
|
+
end
|
60
|
+
# cleanup
|
61
|
+
File.delete dump_file if File.exists? dump_file
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_fetch
|
65
|
+
assert_raise RRDtoolError do
|
66
|
+
@r.fetch ["AVERAGE"]
|
67
|
+
end
|
68
|
+
create_file
|
69
|
+
create_data
|
70
|
+
fary = @r.fetch ["AVERAGE"]
|
71
|
+
assert_not_nil fary
|
72
|
+
assert !fary.empty?
|
73
|
+
#assert_equals expected_data_points, fary[3]
|
74
|
+
# LATER: test rrd.fetch ["AVERAGE", "LAST"]
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_first
|
78
|
+
# first and last should raise an exception without an RRD file
|
79
|
+
assert_raise RRDtoolError do
|
80
|
+
@r.first 0
|
81
|
+
end
|
82
|
+
create_file
|
83
|
+
(0..1).each do |i|
|
84
|
+
f = @r.first i
|
85
|
+
assert_not_nil f
|
86
|
+
assert f > 0
|
87
|
+
end
|
88
|
+
assert_raise RRDtoolError do
|
89
|
+
f2 = @r.first 2
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_graph
|
94
|
+
create_file
|
95
|
+
create_data
|
96
|
+
RRDtool.graph(
|
97
|
+
[@g, "--title", " RubyRRD Demo",
|
98
|
+
"--start", "#{@start} + 1 h",
|
99
|
+
"--end", "#{@start} + 1000 min",
|
100
|
+
"--interlace",
|
101
|
+
"--imgformat", "PNG",
|
102
|
+
"--width=450",
|
103
|
+
"DEF:a=#{@f}:a:AVERAGE",
|
104
|
+
"DEF:b=#{@f}:b:AVERAGE",
|
105
|
+
"CDEF:line=TIME,2400,%,300,LT,a,UNKN,IF",
|
106
|
+
"AREA:b#00b6e4:beta",
|
107
|
+
"AREA:line#0022e9:alpha",
|
108
|
+
"LINE3:line#ff0000"])
|
109
|
+
assert File.exists?(@g)
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_info
|
113
|
+
create_file
|
114
|
+
info = @r.info
|
115
|
+
assert_not_nil info
|
116
|
+
assert_equal info['filename'], @f
|
117
|
+
assert_equal info['step'], @step
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_last
|
121
|
+
# first and last should raise an exception without an RRD file
|
122
|
+
assert_raise RRDtoolError do
|
123
|
+
@r.last
|
124
|
+
end
|
125
|
+
create_file
|
126
|
+
l = @r.last
|
127
|
+
assert l > 0
|
128
|
+
assert_equal @start, l
|
129
|
+
create_data
|
130
|
+
l = @r.last
|
131
|
+
assert l > @start
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_resize
|
135
|
+
raise NotImplementedError, 'Need to write test_resize'
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_restore
|
139
|
+
raise NotImplementedError, 'Need to write test_restore'
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_rrdname
|
143
|
+
r = RRDtool.new 'foo'
|
144
|
+
assert_equal r.rrdname, 'foo'
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_tune
|
148
|
+
raise NotImplementedError, 'Need to write test_tune'
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_update
|
152
|
+
create_file
|
153
|
+
# first, test the simple update case (now)
|
154
|
+
@r.update "a:b", ["N:0:0"]
|
155
|
+
assert_in_delta Time.now.to_i, @r.last, 1
|
156
|
+
# next, create a bunch of data entries
|
157
|
+
create_data
|
158
|
+
l = @r.last
|
159
|
+
@r.update "a:b", ["#{l+1}:0:0", "#{l+2}:1:1"]
|
160
|
+
assert_equal @r.last, l+2
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_version
|
164
|
+
v = RRDtool.version
|
165
|
+
assert_instance_of Float, v
|
166
|
+
assert_not_nil v
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_xport
|
170
|
+
raise NotImplementedError, 'Need to write test_xport'
|
171
|
+
end
|
172
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: errand
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lindsay Holmwood
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-22 00:00:00 +13:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Errand provides Ruby bindings for RRD functions (via librrd), and a concise DSL for interacting with RRDs.
|
17
|
+
email: lindsay@holmwood.id.au
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions:
|
21
|
+
- extconf.rb
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.md
|
24
|
+
files:
|
25
|
+
- .cvsignore
|
26
|
+
- .gitignore
|
27
|
+
- AUTHORS
|
28
|
+
- README.md
|
29
|
+
- Rakefile
|
30
|
+
- VERSION
|
31
|
+
- extconf.rb
|
32
|
+
- lib/errand.rb
|
33
|
+
- lib/graph.rb
|
34
|
+
- rrd_info.h
|
35
|
+
- rrdtool_main.c
|
36
|
+
- spec/errand_spec.rb
|
37
|
+
- spec/spec.opts
|
38
|
+
- test/test_rrdtool.rb
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://auxesis.github.com/errand
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --charset=UTF-8
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.3.5
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: Ruby language binding for RRD tool version 1.2+
|
67
|
+
test_files:
|
68
|
+
- spec/errand_spec.rb
|
69
|
+
- test/test_rrdtool.rb
|