require 'eventmachine' require 'ganymed' require 'metriks' require 'metriks-derive' module Ganymed ## # The Collector is a simple thread that polls various system-level metrics # that are not pushed to the {Processor} from third-party # applications. # # The Collector features a very simple DSL to implement custom collector # plugins. Simply create a file with a +.rb+ file extension and a +collect+ # block: # # collect do # File.open('/proc/foobar').each do |line| # parse(line) # end # end # # Copy this plugin to a location of your choice and set # +collector.load_paths+ in your configuration to point to the containing # directory. # # The collector thread will then call this plugin once per resolution tick. # See {file:doc/configuration.md} for details. # # Custom intervals can also be specified for high frequency sampling: # # collect(0.3) do # File.open('/proc/foobar').each do |line| # parse(line) # end # end # class Collector # The configuration object. # @return [Section] attr_reader :config # Create a new collector instance and initialize all configured collectors. # # @param [Section] config The configuration object. def initialize(config) @config = config load_collectors end # @private def load_collectors collectors.each do |file| name = File.basename(file, '.rb') config = @config.collectors[name.to_sym] || Section.new log.debug("loading collector #{name} from #{file}") Plugin.new(config).from_file(file).tap do |collector| log.info("collecting #{name} metrics every #{collector.interval} seconds") collector.run end end end # @private def collectors load_paths = [@config.collector.load_paths.tap{}].flatten.compact load_paths << File.join(Ganymed::LIB_DIR, 'ganymed/collectors') [].tap do |files| load_paths.each do |load_path| log.debug("loading collectors from #{load_path}") Dir[File.join(load_path, '*.rb')].each do |file| files << file end end end.sort.uniq end ## # Base class and DSL for {Collector} plugins. # class Plugin # The configuration object. # @return [Section] attr_accessor :config # Plugin interval # @return [Fixnum,Float] attr_accessor :interval # Create a new plugin instance. # # @param [Section] config The configuration object. def initialize(config) @config = config end # Set the block used to collect metrics with this plugin. # # @param [Fixnum,Float] interval Custom plugin interval. # @return [void] def collect(interval=nil, &block) @interval = interval || config.interval.tap{} || 1 @collector = Proc.new(&block) end # Start the EventMachine timer to collect metrics at the specified # interval. # @return [void] def run EM.add_periodic_timer(interval) do EM.defer { collect! } end end # @private def collect! begin @collector.call if @collector.is_a?(Proc) rescue Exception => exc log.exception(exc) end end # Loads a given ruby file, and runs instance_eval against it in the # context of the current object. # # @param [String] filename The absolute path to a plugin file. # @return [Plugin] The instance for easy method chaining. def from_file(filename) if File.exists?(filename) && File.readable?(filename) self.instance_eval(IO.read(filename), filename, 1) else raise IOError, "Cannot open or read #{filename}!" end self end end end end