errand 0.7.0
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/.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
|