Browse files

Better type handling.

  • Loading branch information...
1 parent 8a01bb2 commit 176c706dc36740fa53034ddf7efcb4cc46e9d21a @JEG2 committed Jun 18, 2008
Showing with 116 additions and 16 deletions.
  1. +41 −15 lib/rrdb.rb
  2. +39 −0 test/tc_create_database.rb
  3. +7 −0 test/tc_fetch.rb
  4. +29 −1 test/tc_update.rb
View
56 lib/rrdb.rb
@@ -3,7 +3,13 @@
class RRDB
VERSION = "0.0.1"
- class FieldNameConflictError < RuntimeError; end
+ def self.const_missing(error_name)
+ if error_name.to_s =~ /Error\z/
+ const_set(error_name, Class.new(RuntimeError))
+ else
+ super
+ end
+ end
def self.run_command(command)
output = `#{command} 2>&1`
@@ -54,12 +60,27 @@ def path
File.join(self.class.config[:database_directory], "#{id}.rrd")
end
- def fields
- rrdtool(:info).to_s.scan(/^ds\[([^\]]+)\]/).flatten.uniq
+ def fields(include_types = false)
+ schema = rrdtool(:info).to_s
+ fields = schema.scan(/^ds\[([^\]]+)\]/).flatten.uniq
+ if include_types
+ Hash[ *fields.map { |f|
+ [ f, "#{schema[/^ds\[#{f}\]\.type\s*=\s*"([^"]+)"/, 1]}:" +
+ "#{schema[/^ds\[#{f}\]\.minimal_heartbeat\s*=\s*(\d+)/, 1]}:" +
+ "#{schema[/^ds\[#{f}\]\.min\s*=\s*(\S+)/, 1].sub('NaN', 'U')}:" +
+ "#{schema[/^ds\[#{f}\]\.max\s*=\s*(\S+)/, 1].sub('NaN', 'U')}" ]
+ }.flatten ]
+ else
+ fields
+ end
+ rescue InfoError
+ include_types ? Hash.new : Array.new
end
def step
(rrdtool(:info).to_s[/^step\s+=\s+(\d+)/, 1] || 300).to_i
+ rescue InfoError
+ 300
end
def update(time, data)
@@ -109,16 +130,10 @@ def create_database(time, field_names)
schema << " --start '#{(time - 10).to_i}'"
end
end
- field_names.each do |f|
- dst = if (setting = self.class.config[:data_sources]).is_a? String
- setting
- else
- setting[f.to_sym] || setting[f]
- end
- schema << " 'DS:#{f}:#{dst}'"
- end
+ field_names.each { |f| schema << " 'DS:#{f}:#{field_type(f)}'" }
(self.class.config[:reserve_fields].to_i - field_names.size).times do |i|
- schema << " 'DS:_reserved#{i}:GAUGE:600:U:U'"
+ name = "_reserved#{i}"
+ schema << " 'DS:#{name}:#{field_type(name)}'"
end
Array(self.class.config[:round_robin_archives]).each do |a|
schema << " 'RRA:#{a}'"
@@ -133,19 +148,30 @@ def claim_new_fields(field_names)
reserved = old_fields.grep(/\A_reserved\d+\Z/).
sort_by { |f| f[/\d+/].to_i }
if new_fields.size > reserved.size
-
+ raise FieldsExhaustedError,
+ "There are not enough reserved fields to complete this update."
else
claims = new_fields.zip(reserved).
- map { |n, o| " --data-source-rename '#{o}:#{n}'"}.
+ map { |n, o| " -r '#{o}:#{n}'" +
+ " -d '#{n}:#{field_type(n)}'" }.
join.strip
rrdtool(:tune, claims)
end
end
end
+ def field_type(field_name)
+ if (setting = self.class.config[:data_sources]).is_a? String
+ setting
+ else
+ setting[field_name.to_sym] || setting[field_name] || "GAUGE:600:U:U"
+ end
+ end
+
def rrdtool(command, params = nil)
self.class.run_command(
"#{self.class.config[:rrdtool_path]} #{command} '#{path}' #{params}"
- )
+ ) or raise self.class.const_get("#{command.to_s.capitalize}Error"),
+ self.class.last_error
end
end
View
39 test/tc_create_database.rb
@@ -38,4 +38,43 @@ def test_step_can_be_customized_for_creation
@db.update(Time.now, :a => 1)
assert_equal(1_000, @db.step)
end
+
+ def test_step_defaults_to_three_hundred_for_a_non_existent_database
+ assert_equal(300, @db.step)
+ end
+
+ def test_illegal_schema_raise_create_error
+ RRDB.config(:round_robin_archives => nil)
+ assert_raise(RRDB::CreateError) { @db.update(Time.now, :a => 1) }
+ end
+
+ def test_field_type_can_be_set_globally_for_all_created_fields
+ RRDB.config(:reserve_fields => 0, :data_sources => "GAUGE:1200:U:U")
+ @db.update(Time.now, :a => 1, :b => 2, :c => 3)
+ @db.fields(true).each_value do |type|
+ assert_equal("GAUGE:1200:U:U", type)
+ end
+ end
+
+ def test_field_type_can_be_set_with_hash_lookup
+ RRDB.config( :reserve_fields => 0,
+ :data_sources => { :a => "GAUGE:100:U:U",
+ :b => "GAUGE:200:U:U",
+ :c => "GAUGE:300:U:U" } )
+ @db.update(Time.now, :a => 1, :b => 2, :c => 3)
+ types = @db.fields(true)
+ RRDB.config[:data_sources].each do |field, type|
+ assert_equal(type, types[field.to_s])
+ end
+ end
+
+ def test_field_type_can_be_set_with_lambda
+ RRDB.config( :reserve_fields => 0,
+ :data_sources => lambda { |f| f == :a ? "GAUGE:100:U:U" :
+ "GAUGE:200:U:U" } )
+ @db.update(Time.now, :a => 1, :b => 2, :c => 3)
+ @db.fields(true).each do |field, type|
+ assert_equal(RRDB.config[:data_sources][field.to_sym], type)
+ end
+ end
end
View
7 test/tc_fetch.rb
@@ -63,4 +63,11 @@ def test_floats_are_supported
assert_instance_of(Float, float)
assert_in_delta(2.9, float, 2 ** -20)
end
+
+ def test_calling_fetch_on_a_non_existent_database_raises_fetch_error
+ assert_raise(RRDB::FetchError) do
+ @db.fetch( :AVERAGE, :start => Time.at(920804400),
+ :end => Time.at(920809200) )
+ end
+ end
end
View
30 test/tc_update.rb
@@ -8,17 +8,38 @@
class TestUpdate < Test::Unit::TestCase
include RRDManager
+ def test_fields_returns_an_empty_array_for_a_non_existent_database
+ assert_equal(Array.new, @db.fields)
+ end
+
def test_update_claims_new_fields_as_needed
@db.update(Time.now, :a => 1)
%w[b c].each do |field|
assert(!@db.fields.include?(field), "Unused field in database.")
end
- @db.update(Time.now, :b => 2, :c => 3)
+ @db.update(Time.now + 10, :b => 2, :c => 3)
%w[b c].each do |field|
assert(@db.fields.include?(field), "Field not claimed in database.")
end
end
+ def test_fields_are_retyped_on_claim
+ RRDB.config(:reserve_fields => 2)
+ @db.update(Time.now, :a => 1)
+ assert_match(/\AGAUGE:/, @db.fields(true)["_reserved0"])
+ RRDB.config(:data_sources => {:b => "COUNTER:600:U:U"})
+ @db.update(Time.now + 10, :b => 2)
+ assert_match(/\ACOUNTER:/, @db.fields(true)["b"])
+ end
+
+ def test_running_out_of_fields_to_claim_raises_fields_exhausted_error
+ RRDB.config(:reserve_fields => 3)
+ test_update_claims_new_fields_as_needed
+ assert_raise(RRDB::FieldsExhaustedError) do
+ @db.update(Time.now + 20, :d => 0)
+ end
+ end
+
def test_unsupported_characters_are_removed_from_names
@db.update(Time.now, "a'b=" => 4)
assert(@db.fields.include?("ab"), "Trimmed field name not found.")
@@ -49,4 +70,11 @@ def test_you_can_retrieve_the_field_name_used
assert_equal( ("a".."z").to_a.join[0..18],
RRDB.field_name(("a".."z").to_a.join.gsub(/\b/, "'")) )
end
+
+ def test_illegal_updates_raise_update_error
+ test_unsupported_characters_are_removed_from_names # create a db
+ assert_raise(RRDB::UpdateError) do
+ @db.update(Time.now - 10, Hash.new) # time in the past
+ end
+ end
end

0 comments on commit 176c706

Please sign in to comment.