#!/usr/bin/env ruby # bogus chart that we create just so there is at least one chart CHART_TYPE = 'lines' UPDATE_EVERY = 1 PRIORITY = 100000 CHART_NAME = 'number_of_processes' DIMENSION_NAME = 'running' $plugin_name = "external_plugin" $plugin_version = "0.0.1" $plugin_config = <<-HEREDOC test_plugin_config hableba hableba hableba HEREDOC $array_module_name = 'module_of_the_future' $fixed_job_name = 'fixed_job' $modules = { $array_module_name => { :type => :job_array, :jobs => { $fixed_job_name => { :type => :fixed, :config => <<-HEREDOC fixed_job_config HEREDOC }, }, :config => <<-HEREDOC module_of_the_future_config HEREDOC }, "module_of_the_future_single_type" => { :type => :single, :jobs => {}, :config => <<-HEREDOC module_of_the_future_single_type_config HEREDOC } } def out(str) $log.puts "2 NETDATA> #{str}" $stdout.puts str $stdout.flush $log.flush end def log(str) $log.puts "LOG > #{str}" $log.flush end #TODO this is AI code, verify def split_with_quotes(str) result = [] current_word = "" in_quotes = false escaped = false str.each_char do |char| if char == '\\' && !escaped escaped = true next end if char == '"' && !escaped in_quotes = !in_quotes current_word << char elsif char == ' ' && !in_quotes result << current_word unless current_word.empty? current_word = "" else current_word << char end escaped = false end result << current_word unless current_word.empty? result end def print_startup_messages out "DYNCFG_ENABLE #{$plugin_name}" $modules.each do |name, module_config| out "DYNCFG_REGISTER_MODULE #{name} #{module_config[:type]}" end out "CHART system.#{CHART_NAME} '' 'Number of running processes' 'processes' processes processes.#{CHART_NAME} #{CHART_TYPE} #{PRIORITY} #{UPDATE_EVERY}" out "DIMENSION #{DIMENSION_NAME} '' absolute 1 1" $modules.each do |mod_name, mod| next unless mod[:type] == :job_array mod[:jobs].each do |job_name, job| next unless job[:type] == :fixed out "DYNCFG_REGISTER_JOB #{mod_name} #{job_name} stock 0" out "REPORT_JOB_STATUS #{$array_module_name} #{$fixed_job_name} running 0" end end end def function_result(txid, msg, result) out "FUNCTION_RESULT_BEGIN #{txid} #{result} text/plain 5" out msg out "FUNCTION_RESULT_END" end def process_payload_function(params) log "payload function #{params[:fncname]}, #{params[:fncparams]}" fnc_name, mod_name, job_name = params[:fncparams] case fnc_name when 'set_plugin_config' $plugin_config = params[:payload] function_result(params[:txid], "plugin config set", 1) when 'set_module_config' mod = $modules[mod_name] return function_result(params[:txid], "no such module", 0) if mod.nil? mod[:config] = params[:payload] function_result(params[:txid], "module config set", 1) when 'set_job_config' mod = $modules[mod_name] return function_result(params[:txid], "no such module", 0) if mod.nil? job = mod[:jobs][job_name] if job.nil? job = Hash.new if job.nil? job[:type] = :dynamic mod[:jobs][job_name] = job end job[:config] = params[:payload] function_result(params[:txid], "job config set", 1) end end def process_function(params) log "normal function #{params[:fncname]}, #{params[:fncparams]}" fnc_name, mod_name, job_name = params[:fncparams] case fnc_name when 'get_plugin_config' function_result(params[:txid], $plugin_config, 1) when 'get_module_config' return function_result(params[:txid], "no such module", 0) unless $modules.has_key?(mod_name) function_result(params[:txid], $modules[mod_name][:config], 1) when 'get_job_config' mod = $modules[mod_name] return function_result(params[:txid], "no such module", 0) if mod.nil? job = mod[:jobs][job_name] return function_result(params[:txid], "no such job", 0) if job.nil? function_result(params[:txid], job[:config], 1) when 'delete_job' mod = $modules[mod_name] return function_result(params[:txid], "no such module", 0) if mod.nil? job = mod[:jobs][job_name] return function_result(params[:txid], "no such job", 0) if job.nil? if job[:type] == :fixed return function_result(params[:txid], "this job can't be deleted", 0) else mod[:jobs].delete(job_name) function_result(params[:txid], "job deleted", 1) end end end $inflight_incoming = nil def process_input(input) words = split_with_quotes(input) unless $inflight_incoming.nil? if input == "FUNCTION_PAYLOAD_END" log $inflight_incoming[:payload] process_payload_function($inflight_incoming) $inflight_incoming = nil else $inflight_incoming[:payload] << input $inflight_incoming[:payload] << "\n" end return end case words[0] when "FUNCTION", "FUNCTION_PAYLOAD" params = {} params[:command] = words[0] params[:txid] = words[1] params[:timeout] = words[2].to_i params[:fncname] = words[3] params[:fncname] = params[:fncname][1..-2] if params[:fncname].start_with?('"') && params[:fncname].end_with?('"') if params[:command] == "FUNCTION_PAYLOAD" $inflight_incoming = Hash.new params[:fncparams] = split_with_quotes(params[:fncname]) params[:fncname] = params[:fncparams][0] $inflight_incoming[:txid] = params[:txid] $inflight_incoming[:fncname] = params[:fncname] $inflight_incoming[:params] = params $inflight_incoming[:fncparams] = params[:fncparams] $inflight_incoming[:payload] = "" else params[:fncparams] = split_with_quotes(params[:fncname]) params[:fncname] = params[:fncparams][0] process_function(params) end end end def read_and_output_metric processes = `ps -e | wc -l`.to_i - 1 # -1 to exclude the header line timestamp = Time.now.to_i puts "BEGIN system.#{CHART_NAME}" puts "SET #{DIMENSION_NAME} = #{processes}" puts "END" end def the_main $stderr.reopen("/tmp/test_plugin_err.log", "w") $log = File.open("/tmp/test_plugin.log", "w") $log.puts "Starting plugin" print_startup_messages $log.puts "init done" $log.flush last_metric_time = Time.now loop do time_since_last_metric = Time.now - last_metric_time # If it's been more than 1 second since we collected metrics, collect them now if time_since_last_metric >= 1 read_and_output_metric last_metric_time = Time.now end # Use select to wait for input, but only wait up to the time remaining until we need to collect metrics again remaining_time = [1 - time_since_last_metric, 0].max if select([$stdin], nil, nil, remaining_time) input = $stdin.gets next if input.class != String input.chomp! $log.puts "RAW INPUT< #{input}" $log.flush process_input(input) end end end the_main if __FILE__ == $PROGRAM_NAME