require File.dirname(__FILE__) + '/datapoint_builder' module CollectdServer class Packet attr_reader :parts def initialize(data) @parts = [] data = data.dup # don't modify the data passed in while !data.empty? type, length = data.slice!(0,4).unpack('nn') content_length = length - 4 content = data.slice!(0, content_length) @parts << Part.class_for(type).new(content) end end def to_datapoints datapoints = [] stack = Stack.new @parts.each do |part| if part.is_a?(Values) # Values is the last "part" datapoints += DatapointBuilder.from_parts(stack.to_a, part) else stack.update_part(part) end end datapoints end class Stack ORDER = [:host, :time, :interval, :plugin, :plugin_instance, :type, :type_instance] def initialize @stack = {} end def update_part(part) name = part_name(part) @stack[name] = part end def part_name(part) case part when Packet::Host then :host when Packet::Time then :time when Packet::Interval then :interval when Packet::Plugin then :plugin when Packet::PluginInstance then :plugin_instance when Packet::Type then :type when Packet::TypeInstance then :type_instance end end def to_a ORDER.map{ |field| @stack[field] }.compact end end class Part attr_reader :content def self.type(number) define_method(:type) { number } Part.add_type(self, number) end def self.add_type(klass, number) @types ||= {} @types[number] = klass end def self.part_for(type, content) if klass = self.class_for(type) klass.new(content) else warn "Unrecognized type %x" % type end end def self.class_for(type) @types[type] end def initialize(content) @content = content end end class String < Part def initialize(content) @content = content[0..-2] # strip off the null byte at the end end end class Number < Part def initialize(content) big, small = content.unpack('NN') @content = (big << 32) + (small) end end class Host < String type 0 end class Time < Number type 1 end class Plugin < String type 2 end class PluginInstance < String type 3 end class Type < String type 4 end class TypeInstance < String type 5 end class Values < Part type 6 attr_reader :values def initialize(content) size = content.slice!(0,2).unpack('n').first types = [] size.times { types << content.slice!(0,1).unpack("C").first } @values = [] size.times do |i| @values << Value.new_for_type(types[i], content.slice!(0,8)) end end class Value attr_reader :value def self.new_for_type(type, content) if type == 0 Counter.new(content) elsif type == 1 Gauge.new(content) else raise "Unknown value type #{type}" end end end class Counter < Value def initialize(content) big, small = content.unpack('NN') @value = (big << 32) + (small) end end class Gauge < Value def initialize(content) @value = content.unpack('d').first end end end class Interval < Number type 7 end end end