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
|