summaryrefslogtreecommitdiffstats
path: root/libnetdata/dyn_conf/tests/test_plugin/test.plugin
blob: b890ab3149b5603732a73d48b1d33a404e3be80a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#!/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