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.
@@ -0,0 +1,5 @@
1
+ Makefile
2
+ test.png
3
+ test.rrd
4
+ mkmf.log
5
+ pkg
@@ -0,0 +1,10 @@
1
+ Makefile
2
+ test.png
3
+ test.rrd
4
+ mkmf.log
5
+ pkg
6
+ *~
7
+ *.gem
8
+ *.o
9
+ *.so
10
+ errand.gemspec
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>
@@ -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.
@@ -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
@@ -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
+
@@ -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
@@ -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
@@ -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 */
@@ -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
+ }
@@ -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
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format
3
+ progress
4
+ --diff
@@ -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