openc3 5.10.1 → 5.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -2
  3. data/data/config/target.yaml +9 -0
  4. data/ext/openc3/ext/packet/packet.c +3 -0
  5. data/ext/openc3/ext/reducer_microservice/extconf.rb +13 -0
  6. data/ext/openc3/ext/reducer_microservice/reducer_microservice.c +165 -0
  7. data/ext/openc3/ext/structure/structure.c +7 -9
  8. data/lib/openc3/accessors/accessor.rb +53 -3
  9. data/lib/openc3/accessors/binary_accessor.rb +16 -0
  10. data/lib/openc3/accessors/cbor_accessor.rb +3 -3
  11. data/lib/openc3/accessors/form_accessor.rb +78 -0
  12. data/lib/openc3/accessors/http_accessor.rb +145 -0
  13. data/lib/openc3/accessors/json_accessor.rb +19 -3
  14. data/lib/openc3/accessors/xml_accessor.rb +18 -1
  15. data/lib/openc3/accessors.rb +3 -1
  16. data/lib/openc3/config/config_parser.rb +7 -5
  17. data/lib/openc3/config/meta_config_parser.rb +1 -1
  18. data/lib/openc3/core_ext/string.rb +16 -1
  19. data/lib/openc3/interfaces/http_client_interface.rb +202 -0
  20. data/lib/openc3/interfaces/http_server_interface.rb +183 -0
  21. data/lib/openc3/interfaces/interface.rb +86 -16
  22. data/lib/openc3/interfaces/mqtt_interface.rb +6 -5
  23. data/lib/openc3/interfaces/protocols/burst_protocol.rb +11 -11
  24. data/lib/openc3/interfaces/protocols/cobs_protocol.rb +7 -7
  25. data/lib/openc3/interfaces/protocols/crc_protocol.rb +7 -7
  26. data/lib/openc3/interfaces/protocols/length_protocol.rb +6 -6
  27. data/lib/openc3/interfaces/protocols/preidentified_protocol.rb +9 -5
  28. data/lib/openc3/interfaces/protocols/protocol.rb +8 -6
  29. data/lib/openc3/interfaces/protocols/slip_protocol.rb +8 -8
  30. data/lib/openc3/interfaces/protocols/template_protocol.rb +6 -7
  31. data/lib/openc3/interfaces/protocols/terminated_protocol.rb +4 -4
  32. data/lib/openc3/interfaces/simulated_target_interface.rb +2 -0
  33. data/lib/openc3/interfaces/stream_interface.rb +6 -4
  34. data/lib/openc3/interfaces/tcpip_server_interface.rb +2 -0
  35. data/lib/openc3/interfaces/udp_interface.rb +8 -5
  36. data/lib/openc3/interfaces.rb +2 -0
  37. data/lib/openc3/logs/buffered_packet_log_writer.rb +6 -7
  38. data/lib/openc3/logs/log_writer.rb +2 -10
  39. data/lib/openc3/logs/packet_log_constants.rb +13 -3
  40. data/lib/openc3/logs/packet_log_reader.rb +35 -98
  41. data/lib/openc3/logs/packet_log_writer.rb +24 -62
  42. data/lib/openc3/logs/text_log_writer.rb +32 -6
  43. data/lib/openc3/microservices/cleanup_microservice.rb +23 -16
  44. data/lib/openc3/microservices/decom_microservice.rb +8 -20
  45. data/lib/openc3/microservices/log_microservice.rb +3 -1
  46. data/lib/openc3/microservices/reaction_microservice.rb +22 -11
  47. data/lib/openc3/microservices/reducer_microservice.rb +174 -130
  48. data/lib/openc3/{models/notification_model.rb → microservices/scope_cleanup_microservice.rb} +20 -21
  49. data/lib/openc3/microservices/text_log_microservice.rb +2 -5
  50. data/lib/openc3/microservices/timeline_microservice.rb +0 -1
  51. data/lib/openc3/microservices/trigger_group_microservice.rb +0 -1
  52. data/lib/openc3/migrations/20230915000002_no_scope_log_messages.rb +44 -0
  53. data/lib/openc3/models/microservice_model.rb +1 -1
  54. data/lib/openc3/models/scope_model.rb +65 -34
  55. data/lib/openc3/models/target_model.rb +25 -5
  56. data/lib/openc3/models/tool_model.rb +14 -2
  57. data/lib/openc3/models/widget_model.rb +1 -1
  58. data/lib/openc3/packets/json_packet.rb +10 -2
  59. data/lib/openc3/packets/packet.rb +30 -9
  60. data/lib/openc3/packets/packet_config.rb +6 -2
  61. data/lib/openc3/packets/parsers/packet_item_parser.rb +11 -6
  62. data/lib/openc3/packets/structure.rb +19 -12
  63. data/lib/openc3/script/storage.rb +1 -1
  64. data/lib/openc3/script/web_socket_api.rb +17 -14
  65. data/lib/openc3/topics/telemetry_topic.rb +2 -1
  66. data/lib/openc3/utilities/bucket_utilities.rb +2 -0
  67. data/lib/openc3/utilities/cli_generator.rb +1 -1
  68. data/lib/openc3/utilities/logger.rb +62 -47
  69. data/lib/openc3/utilities/metric.rb +19 -1
  70. data/lib/openc3/utilities/sleeper.rb +3 -1
  71. data/lib/openc3/utilities/throttle.rb +76 -0
  72. data/lib/openc3/version.rb +6 -6
  73. data/templates/tool_angular/package.json +20 -20
  74. data/templates/tool_angular/yarn.lock +112 -106
  75. data/templates/tool_react/package.json +16 -18
  76. data/templates/tool_react/yarn.lock +977 -664
  77. data/templates/tool_svelte/.prettierrc.js +5 -0
  78. data/templates/tool_svelte/package.json +18 -18
  79. data/templates/tool_svelte/src/services/cable.js +1 -1
  80. data/templates/tool_svelte/src/services/openc3-api.js +173 -173
  81. data/templates/tool_svelte/yarn.lock +767 -665
  82. data/templates/tool_vue/package.json +10 -10
  83. data/templates/tool_vue/yarn.lock +225 -43
  84. data/templates/widget/package.json +10 -10
  85. data/templates/widget/yarn.lock +223 -46
  86. metadata +41 -4
  87. data/lib/openc3/topics/notifications_topic.rb +0 -31
@@ -35,26 +35,14 @@ module OpenC3
35
35
  @sleeper = Sleeper.new
36
36
  end
37
37
 
38
- def run
39
- split_name = @name.split("__")
40
- target_name = split_name[-1]
41
- target = TargetModel.get_model(name: target_name, scope: @scope)
42
-
38
+ def cleanup(areas, cleanup_poll_time)
43
39
  bucket = Bucket.getClient()
44
40
  while true
45
41
  break if @cancel_thread
46
42
 
47
43
  @state = 'GETTING_OBJECTS'
48
44
  start_time = Time.now
49
- [
50
- ["#{@scope}/raw_logs/cmd/#{target_name}", target.cmd_log_retain_time],
51
- ["#{@scope}/decom_logs/cmd/#{target_name}", target.cmd_decom_log_retain_time],
52
- ["#{@scope}/raw_logs/tlm/#{target_name}", target.tlm_log_retain_time],
53
- ["#{@scope}/decom_logs/tlm/#{target_name}", target.tlm_decom_log_retain_time],
54
- ["#{@scope}/reduced_minute_logs/tlm/#{target_name}", target.reduced_minute_log_retain_time],
55
- ["#{@scope}/reduced_hour_logs/tlm/#{target_name}", target.reduced_hour_log_retain_time],
56
- ["#{@scope}/reduced_day_logs/tlm/#{target_name}", target.reduced_day_log_retain_time],
57
- ].each do |prefix, retain_time|
45
+ areas.each do |prefix, retain_time|
58
46
  next unless retain_time
59
47
  time = start_time - retain_time
60
48
  oldest_list = BucketUtilities.files_between_time(ENV['OPENC3_LOGS_BUCKET'], prefix, nil, time)
@@ -62,7 +50,7 @@ module OpenC3
62
50
  @state = 'DELETING_OBJECTS'
63
51
  oldest_list.each_slice(1000) do |slice|
64
52
  bucket.delete_objects(bucket: ENV['OPENC3_LOGS_BUCKET'], keys: slice)
65
- @logger.debug("Deleted #{slice.length} #{target_name} log files")
53
+ @logger.debug("Cleanup deleted #{slice.length} log files")
66
54
  @delete_count += slice.length
67
55
  @metric.set(name: 'cleanup_delete_total', value: @delete_count, type: 'counter')
68
56
  end
@@ -71,11 +59,30 @@ module OpenC3
71
59
 
72
60
  @count += 1
73
61
  @metric.set(name: 'cleanup_total', value: @count, type: 'counter')
62
+
74
63
  @state = 'SLEEPING'
75
- break if @sleeper.sleep(target.cleanup_poll_time)
64
+ break if @sleeper.sleep(cleanup_poll_time)
76
65
  end
77
66
  end
78
67
 
68
+ def run
69
+ split_name = @name.split("__")
70
+ target_name = split_name[-1]
71
+ target = TargetModel.get_model(name: target_name, scope: @scope)
72
+
73
+ areas = [
74
+ ["#{@scope}/raw_logs/cmd/#{target_name}", target.cmd_log_retain_time],
75
+ ["#{@scope}/decom_logs/cmd/#{target_name}", target.cmd_decom_log_retain_time],
76
+ ["#{@scope}/raw_logs/tlm/#{target_name}", target.tlm_log_retain_time],
77
+ ["#{@scope}/decom_logs/tlm/#{target_name}", target.tlm_decom_log_retain_time],
78
+ ["#{@scope}/reduced_minute_logs/tlm/#{target_name}", target.reduced_minute_log_retain_time],
79
+ ["#{@scope}/reduced_hour_logs/tlm/#{target_name}", target.reduced_hour_log_retain_time],
80
+ ["#{@scope}/reduced_day_logs/tlm/#{target_name}", target.reduced_day_log_retain_time],
81
+ ]
82
+
83
+ cleanup(areas, target.cleanup_poll_time)
84
+ end
85
+
79
86
  def shutdown
80
87
  @sleeper.cancel
81
88
  super()
@@ -24,8 +24,6 @@ require 'openc3/microservices/microservice'
24
24
  require 'openc3/microservices/interface_decom_common'
25
25
  require 'openc3/topics/telemetry_decom_topic'
26
26
  require 'openc3/topics/limits_event_topic'
27
- require 'openc3/topics/notifications_topic'
28
- require 'openc3/models/notification_model'
29
27
 
30
28
  module OpenC3
31
29
  class DecomMicroservice < Microservice
@@ -93,8 +91,14 @@ module OpenC3
93
91
 
94
92
  packet = System.telemetry.packet(target_name, packet_name)
95
93
  packet.stored = ConfigParser.handle_true_false(msg_hash["stored"])
94
+ # Note: Packet time will be recalculated as part of decom so not setting
96
95
  packet.received_time = Time.from_nsec_from_epoch(msg_hash["received_time"].to_i)
97
96
  packet.received_count = msg_hash["received_count"].to_i
97
+ extra = msg_hash["extra"]
98
+ if extra and extra.length > 0
99
+ extra = JSON.parse(extra, allow_nan: true, create_additions: true)
100
+ packet.extra = extra
101
+ end
98
102
  packet.buffer = msg_hash["buffer"]
99
103
  packet.check_limits(System.limits_set) # Process all the limits and call the limits_change_callback (as necessary)
100
104
 
@@ -128,25 +132,9 @@ module OpenC3
128
132
  when :BLUE, :GREEN, :GREEN_LOW, :GREEN_HIGH
129
133
  @logger.info message
130
134
  when :YELLOW, :YELLOW_LOW, :YELLOW_HIGH
131
- notification = NotificationModel.new(
132
- time: time_nsec,
133
- severity: "caution",
134
- url: "/tools/limitsmonitor",
135
- title: "#{packet.target_name} #{packet.packet_name} #{item.name} #{item.limits.state}",
136
- body: "#{item.name} is #{item.limits.state}"
137
- )
138
- NotificationsTopic.write_notification(notification.as_json(:allow_nan => true), scope: @scope)
139
- @logger.warn message
135
+ @logger.warn(message, type: Logger::NOTIFICATION)
140
136
  when :RED, :RED_LOW, :RED_HIGH
141
- notification = NotificationModel.new(
142
- time: time_nsec,
143
- severity: "critical",
144
- url: "/tools/limitsmonitor",
145
- title: "#{packet.target_name} #{packet.packet_name} #{item.name} #{item.limits.state}",
146
- body: "#{item.name} is #{item.limits.state}"
147
- )
148
- NotificationsTopic.write_notification(notification.as_json(:allow_nan => true), scope: @scope)
149
- @logger.error message
137
+ @logger.error(message, type: Logger::ALERT)
150
138
  end
151
139
  end
152
140
 
@@ -110,7 +110,9 @@ module OpenC3
110
110
  packet_type = :JSON_PACKET
111
111
  data_key = "json_data"
112
112
  end
113
- @plws[target_name][rt_or_stored].buffered_write(packet_type, @cmd_or_tlm, target_name, packet_name, msg_hash["time"].to_i, rt_or_stored == :STORED, msg_hash[data_key], nil, topic, msg_id)
113
+ received_time_nsec_since_epoch = msg_hash["received_time"]
114
+ received_time_nsec_since_epoch = received_time_nsec_since_epoch.to_i if received_time_nsec_since_epoch
115
+ @plws[target_name][rt_or_stored].buffered_write(packet_type, @cmd_or_tlm, target_name, packet_name, msg_hash["time"].to_i, rt_or_stored == :STORED, msg_hash[data_key], nil, topic, msg_id, received_time_nsec_since_epoch: received_time_nsec_since_epoch, extra: msg_hash['extra'])
114
116
  rescue => err
115
117
  @error = err
116
118
  @logger.error("#{@name} error: #{err.formatted}")
@@ -22,8 +22,6 @@
22
22
 
23
23
  require 'openc3/microservices/microservice'
24
24
  require 'openc3/models/reaction_model'
25
- require 'openc3/models/notification_model'
26
- require 'openc3/topics/notifications_topic'
27
25
  require 'openc3/models/trigger_model'
28
26
  require 'openc3/topics/autonomic_topic'
29
27
  require 'openc3/utilities/authentication'
@@ -305,15 +303,28 @@ module OpenC3
305
303
  end
306
304
 
307
305
  def run_notify(reaction:, action:)
308
- notification = NotificationModel.new(
309
- time: Time.now.to_nsec_from_epoch,
310
- severity: action['severity'],
311
- url: "/tools/autonomic/reactions",
312
- title: "#{reaction.name} run",
313
- body: action['value']
314
- )
315
- NotificationsTopic.write_notification(notification.as_json(:allow_nan => true), scope: @scope)
316
- @logger.info "ReactionWorker-#{@ident} #{reaction.name} notify action complete, body: #{action['value']}, severity: #{action['severity']}"
306
+ message = "ReactionWorker-#{@ident} #{reaction.name} notify action complete, body: #{action['value']}"
307
+ url = "/tools/autonomic/reactions"
308
+ case action['severity'].to_s.upcase
309
+ when 'FATAL'
310
+ @logger.fatal(message, url: url, type: Logger::ALERT)
311
+ when 'ERROR', 'CRITICAL'
312
+ @logger.error(message, url: url, type: Logger::ALERT)
313
+ when 'WARN', 'CAUTION', 'SERIOUS'
314
+ @logger.warn(message, url: url, type: Logger::NOTIFICATION)
315
+ when 'INFO', 'NORMAL', 'STANDBY', 'OFF'
316
+ @logger.info(message, url: url, type: Logger::NOTIFICATION)
317
+ when 'DEBUG'
318
+ level = @logger.level
319
+ begin
320
+ @logger.level = Logger::DEBUG
321
+ @logger.debug(message, url: url, type: Logger::NOTIFICATION)
322
+ ensure
323
+ @logger.level = level
324
+ end
325
+ else
326
+ raise "Unknown severity: #{action['severity']}"
327
+ end
317
328
  end
318
329
 
319
330
  def run_command(reaction:, action:)
@@ -25,8 +25,10 @@ require 'openc3/topics/topic'
25
25
  require 'openc3/topics/telemetry_reduced_topics'
26
26
  require 'openc3/packets/json_packet'
27
27
  require 'openc3/utilities/bucket_file_cache'
28
+ require 'openc3/utilities/throttle'
28
29
  require 'openc3/models/reducer_model'
29
30
  require 'openc3/logs/buffered_packet_log_writer'
31
+ require 'openc3/ext/reducer_microservice' if RUBY_ENGINE == 'ruby' and !ENV['OPENC3_NO_EXT']
30
32
  require 'rufus-scheduler'
31
33
 
32
34
  module OpenC3
@@ -96,6 +98,8 @@ module OpenC3
96
98
  case option[0].upcase
97
99
  when 'BUFFER_DEPTH' # Buffer depth to write in time order
98
100
  @buffer_depth = option[1].to_i
101
+ when 'MAX_CPU_UTILIZATION'
102
+ @max_cpu_utilization = Float(option[1])
99
103
  else
100
104
  @logger.error("Unknown option passed to microservice #{@name}: #{option}")
101
105
  end
@@ -103,6 +107,7 @@ module OpenC3
103
107
  end
104
108
 
105
109
  @buffer_depth = 10 unless @buffer_depth
110
+ @max_cpu_utilization = 30.0 unless @max_cpu_utilization
106
111
  @target_name = name.split('__')[-1]
107
112
  @packet_logs = {}
108
113
 
@@ -189,12 +194,14 @@ module OpenC3
189
194
  end
190
195
 
191
196
  def process_file(filename, type, entry_nanoseconds, file_nanoseconds)
197
+ throttle = OpenC3::Throttle.new(@max_cpu_utilization)
192
198
  file = BucketFile.new(filename)
193
199
  file.retrieve
194
200
  unless file.local_path
195
201
  @logger.warn("Reducer Warning: #{filename}: Could not be retrieved")
196
202
  return
197
203
  end
204
+ throttle.throttle_sleep
198
205
 
199
206
  # Determine if we already have a PacketLogWriter created
200
207
  _, _, scope, target_name, _, rt_or_stored, _ = File.basename(filename).split('__')
@@ -217,6 +224,7 @@ module OpenC3
217
224
  # The lifetime of all these variables is a single file - single target / multiple packets
218
225
  reducer_state = {}
219
226
  plr = OpenC3::PacketLogReader.new
227
+ throttle.throttle_sleep
220
228
  plr.each(file.local_path) do |packet|
221
229
  # Check to see if we should start a new log file before processing this packet
222
230
  current_time = packet.packet_time.to_nsec_from_epoch
@@ -239,131 +247,27 @@ module OpenC3
239
247
  end
240
248
 
241
249
  if type == 'minute'
242
- state.entry_samples ||= packet.json_hash.dup # Grab all the samples from the first packet
243
- if state.first
244
- state.raw_values = packet.read_all(:RAW, nil, packet.read_all_names(:RAW)).select { |key, value| value.is_a?(Numeric) }
245
- state.raw_keys ||= state.raw_values.keys
246
- state.converted_values = packet.read_all(:CONVERTED, nil, packet.read_all_names(:CONVERTED)).select { |key, value| value.is_a?(Numeric) }
247
- state.converted_keys ||= state.converted_values.keys
248
- else
249
- state.raw_values = packet.read_all(:RAW, nil, state.raw_keys).select { |key, value| value.is_a?(Numeric) }
250
- state.converted_values = packet.read_all(:CONVERTED, nil, state.converted_keys).select { |key, value| value.is_a?(Numeric) }
251
- end
250
+ get_min_samples(packet, state)
252
251
  else
253
- # Hour or Day
254
- state.entry_samples ||= extract_entry_samples(packet)
255
- if state.first
256
- state.raw_max_values = packet.read_all(:RAW, :MAX, packet.read_all_names(:RAW, :MAX))
257
- state.raw_keys = state.raw_max_values.keys
258
- state.converted_max_values = packet.read_all(:CONVERTED, :MAX, packet.read_all_names(:CONVERTED, :MAX))
259
- state.converted_keys = state.converted_max_values.keys
260
- else
261
- state.raw_max_values = packet.read_all(:RAW, :MAX, state.raw_keys)
262
- state.converted_max_values = packet.read_all(:CONVERTED, :MAX, state.converted_keys)
263
- end
264
- state.raw_min_values = packet.read_all(:RAW, :MIN, state.raw_keys)
265
- state.raw_avg_values = packet.read_all(:RAW, :AVG, state.raw_keys)
266
- state.raw_stddev_values = packet.read_all(:RAW, :STDDEV, state.raw_keys)
267
- state.converted_min_values = packet.read_all(:CONVERTED, :MIN, state.converted_keys)
268
- state.converted_avg_values = packet.read_all(:CONVERTED, :AVG, state.converted_keys)
269
- state.converted_stddev_values = packet.read_all(:CONVERTED, :STDDEV, state.converted_keys)
252
+ get_hour_day_samples(packet, state)
270
253
  end
271
254
 
272
255
  reduced = state.reduced
273
256
  if type == 'minute'
274
- # Update statistics for this packet's raw values
275
- state.raw_values.each do |key, value|
276
- if value
277
- reduced["#{key}__VALS"] ||= []
278
- reduced["#{key}__VALS"] << value
279
- reduced["#{key}__N"] ||= value
280
- reduced["#{key}__N"] = value if value < reduced["#{key}__N"]
281
- reduced["#{key}__X"] ||= value
282
- reduced["#{key}__X"] = value if value > reduced["#{key}__X"]
283
- end
284
- end
285
-
286
- # Update statistics for this packet's converted values
287
- state.converted_values.each do |key, value|
288
- if value
289
- reduced["#{key}__CVALS"] ||= []
290
- reduced["#{key}__CVALS"] << value
291
- reduced["#{key}__CN"] ||= value
292
- reduced["#{key}__CN"] = value if value < reduced["#{key}__CN"]
293
- reduced["#{key}__CX"] ||= value
294
- reduced["#{key}__CX"] = value if value > reduced["#{key}__CX"]
295
- end
296
- end
257
+ update_min_stats(reduced, state)
297
258
  else
298
- # Update statistics for this packet's raw values
299
- state.raw_max_values.each do |key, value|
300
- if value
301
- max_key = "#{key}__X"
302
- reduced[max_key] ||= value
303
- reduced[max_key] = value if value > reduced[max_key]
304
- end
305
- end
306
- state.raw_min_values.each do |key, value|
307
- if value
308
- min_key = "#{key}__N"
309
- reduced[min_key] ||= value
310
- reduced[min_key] = value if value < reduced[min_key]
311
- end
312
- end
313
- state.raw_avg_values.each do |key, value|
314
- if value
315
- avg_values_key = "#{key}__AVGVALS"
316
- reduced[avg_values_key] ||= []
317
- reduced[avg_values_key] << value
318
- end
319
- end
320
- state.raw_stddev_values.each do |key, value|
321
- if value
322
- stddev_values_key = "#{key}__STDDEVVALS"
323
- reduced[stddev_values_key] ||= []
324
- reduced[stddev_values_key] << value
325
- end
326
- end
327
-
328
- # Update statistics for this packet's converted values
329
- state.converted_max_values.each do |key, value|
330
- if value
331
- max_key = "#{key}__CX"
332
- reduced[max_key] ||= value
333
- reduced[max_key] = value if value > reduced[max_key]
334
- end
335
- end
336
- state.converted_min_values.each do |key, value|
337
- if value
338
- min_key = "#{key}__CN"
339
- reduced[min_key] ||= value
340
- reduced[min_key] = value if value < reduced[min_key]
341
- end
342
- end
343
- state.converted_avg_values.each do |key, value|
344
- if value
345
- avg_values_key = "#{key}__CAVGVALS"
346
- reduced[avg_values_key] ||= []
347
- reduced[avg_values_key] << value
348
- end
349
- end
350
- state.converted_stddev_values.each do |key, value|
351
- if value
352
- stddev_values_key = "#{key}__CSTDDEVVALS"
353
- reduced[stddev_values_key] ||= []
354
- reduced[stddev_values_key] << value
355
- end
356
- end
357
-
358
- reduced["_NUM_SAMPLES__VALS"] ||= []
359
- reduced["_NUM_SAMPLES__VALS"] << packet.read('_NUM_SAMPLES')
259
+ update_raw_hour_day_stats(reduced, state)
260
+ update_converted_hour_day_stats(packet, reduced, state)
360
261
  end
361
-
362
262
  state.first = false
263
+
264
+ throttle.throttle_sleep
363
265
  end
364
266
  file.delete # Remove the local copy
365
267
 
366
- write_all_entries(reducer_state, plw, type, target_name, stored)
268
+ write_all_entries(reducer_state, plw, type, target_name, stored, throttle)
269
+
270
+ @logger.debug("Reducer Throttle: #{filename}: total_time: #{Time.now - throttle.reset_time}, sleep_time: #{throttle.total_sleep_time}")
367
271
 
368
272
  @count += 1
369
273
  @metric.set(name: 'reducer_total', value: @count, type: 'counter')
@@ -380,6 +284,140 @@ module OpenC3
380
284
  false
381
285
  end
382
286
 
287
+ def get_min_samples(packet, state)
288
+ state.entry_samples ||= packet.json_hash.dup # Grab all the samples from the first packet
289
+ if state.first
290
+ state.raw_values = packet.read_all(:RAW, nil, packet.read_all_names(:RAW)).select { |key, value| value.is_a?(Numeric) }
291
+ state.raw_keys ||= state.raw_values.keys
292
+ state.converted_values = packet.read_all(:CONVERTED, nil, packet.read_all_names(:CONVERTED)).select { |key, value| value.is_a?(Numeric) }
293
+ state.converted_keys ||= state.converted_values.keys
294
+ else
295
+ state.raw_values = packet.read_all(:RAW, nil, state.raw_keys).select { |key, value| value.is_a?(Numeric) }
296
+ state.converted_values = packet.read_all(:CONVERTED, nil, state.converted_keys).select { |key, value| value.is_a?(Numeric) }
297
+ end
298
+ end
299
+
300
+ def get_hour_day_samples(packet, state)
301
+ # Hour or Day
302
+ state.entry_samples ||= extract_entry_samples(packet)
303
+ if state.first
304
+ state.raw_max_values = packet.read_all(:RAW, :MAX, packet.read_all_names(:RAW, :MAX))
305
+ state.raw_keys = state.raw_max_values.keys
306
+ state.converted_max_values = packet.read_all(:CONVERTED, :MAX, packet.read_all_names(:CONVERTED, :MAX))
307
+ state.converted_keys = state.converted_max_values.keys
308
+ else
309
+ state.raw_max_values = packet.read_all(:RAW, :MAX, state.raw_keys)
310
+ state.converted_max_values = packet.read_all(:CONVERTED, :MAX, state.converted_keys)
311
+ end
312
+ state.raw_min_values = packet.read_all(:RAW, :MIN, state.raw_keys)
313
+ state.raw_avg_values = packet.read_all(:RAW, :AVG, state.raw_keys)
314
+ state.raw_stddev_values = packet.read_all(:RAW, :STDDEV, state.raw_keys)
315
+ state.converted_min_values = packet.read_all(:CONVERTED, :MIN, state.converted_keys)
316
+ state.converted_avg_values = packet.read_all(:CONVERTED, :AVG, state.converted_keys)
317
+ state.converted_stddev_values = packet.read_all(:CONVERTED, :STDDEV, state.converted_keys)
318
+ end
319
+
320
+ if RUBY_ENGINE != 'ruby' or ENV['OPENC3_NO_EXT']
321
+ def update_min_stats(reduced, state)
322
+ # Update statistics for this packet's raw values
323
+ state.raw_values.each do |key, value|
324
+ if value
325
+ vals_key = "#{key}__VALS"
326
+ reduced[vals_key] ||= []
327
+ reduced[vals_key] << value
328
+ n_key = "#{key}__N"
329
+ reduced[n_key] ||= value
330
+ reduced[n_key] = value if value < reduced[n_key]
331
+ x_key = "#{key}__X"
332
+ reduced[x_key] ||= value
333
+ reduced[x_key] = value if value > reduced[x_key]
334
+ end
335
+ end
336
+
337
+ # Update statistics for this packet's converted values
338
+ state.converted_values.each do |key, value|
339
+ if value
340
+ cvals_key = "#{key}__CVALS"
341
+ reduced[cvals_key] ||= []
342
+ reduced[cvals_key] << value
343
+ cn_key = "#{key}__CN"
344
+ reduced[cn_key] ||= value
345
+ reduced[cn_key] = value if value < reduced[cn_key]
346
+ cx_key = "#{key}__CX"
347
+ reduced[cx_key] ||= value
348
+ reduced[cx_key] = value if value > reduced[cx_key]
349
+ end
350
+ end
351
+ end
352
+ end
353
+
354
+ def update_raw_hour_day_stats(reduced, state)
355
+ # Update statistics for this packet's raw values
356
+ state.raw_max_values.each do |key, value|
357
+ if value
358
+ max_key = "#{key}__X"
359
+ reduced[max_key] ||= value
360
+ reduced[max_key] = value if value > reduced[max_key]
361
+ end
362
+ end
363
+ state.raw_min_values.each do |key, value|
364
+ if value
365
+ min_key = "#{key}__N"
366
+ reduced[min_key] ||= value
367
+ reduced[min_key] = value if value < reduced[min_key]
368
+ end
369
+ end
370
+ state.raw_avg_values.each do |key, value|
371
+ if value
372
+ avg_values_key = "#{key}__AVGVALS"
373
+ reduced[avg_values_key] ||= []
374
+ reduced[avg_values_key] << value
375
+ end
376
+ end
377
+ state.raw_stddev_values.each do |key, value|
378
+ if value
379
+ stddev_values_key = "#{key}__STDDEVVALS"
380
+ reduced[stddev_values_key] ||= []
381
+ reduced[stddev_values_key] << value
382
+ end
383
+ end
384
+ end
385
+
386
+ def update_converted_hour_day_stats(packet, reduced, state)
387
+ # Update statistics for this packet's converted values
388
+ state.converted_max_values.each do |key, value|
389
+ if value
390
+ max_key = "#{key}__CX"
391
+ reduced[max_key] ||= value
392
+ reduced[max_key] = value if value > reduced[max_key]
393
+ end
394
+ end
395
+ state.converted_min_values.each do |key, value|
396
+ if value
397
+ min_key = "#{key}__CN"
398
+ reduced[min_key] ||= value
399
+ reduced[min_key] = value if value < reduced[min_key]
400
+ end
401
+ end
402
+ state.converted_avg_values.each do |key, value|
403
+ if value
404
+ avg_values_key = "#{key}__CAVGVALS"
405
+ reduced[avg_values_key] ||= []
406
+ reduced[avg_values_key] << value
407
+ end
408
+ end
409
+ state.converted_stddev_values.each do |key, value|
410
+ if value
411
+ stddev_values_key = "#{key}__CSTDDEVVALS"
412
+ reduced[stddev_values_key] ||= []
413
+ reduced[stddev_values_key] << value
414
+ end
415
+ end
416
+
417
+ reduced["_NUM_SAMPLES__VALS"] ||= []
418
+ reduced["_NUM_SAMPLES__VALS"] << packet.read('_NUM_SAMPLES')
419
+ end
420
+
383
421
  def check_new_file(reducer_state, plw, type, target_name, stored, current_time, file_nanoseconds)
384
422
  plw_first_time_nsec = plw.buffered_first_time_nsec
385
423
  if plw_first_time_nsec && ((current_time - plw_first_time_nsec) >= file_nanoseconds)
@@ -408,9 +446,10 @@ module OpenC3
408
446
  return state
409
447
  end
410
448
 
411
- def write_all_entries(reducer_state, plw, type, target_name, stored)
449
+ def write_all_entries(reducer_state, plw, type, target_name, stored, throttle = nil)
412
450
  reducer_state.each do |packet_name, state|
413
451
  write_entry(state, plw, type, target_name, packet_name, stored)
452
+ throttle.throttle_sleep if throttle
414
453
  end
415
454
  end
416
455
 
@@ -484,26 +523,31 @@ module OpenC3
484
523
  # Calculate Average
485
524
  weighted_sum = 0
486
525
  avg = reduced["#{key}#{avgvals_key}"]
487
- avg.each_with_index do |val, i|
488
- weighted_sum += (val * samples[i])
526
+ if avg
527
+ avg.each_with_index do |val, i|
528
+ weighted_sum += (val * samples[i])
529
+ end
530
+ reduced["#{key}#{avg_key}"] = weighted_sum / samples_sum
489
531
  end
490
- reduced["#{key}#{avg_key}"] = weighted_sum / samples_sum
491
532
 
492
533
  # Do the STDDEV calc last so we can use the previously calculated AVG
493
534
  # See https://math.stackexchange.com/questions/1547141/aggregating-standard-deviation-to-a-summary-point
494
535
  s2 = 0
495
- reduced["#{key}#{stddevvals_key}"].each_with_index do |val, i|
496
- # puts "i:#{i} val:#{val} samples[i]:#{samples[i]} avg[i]:#{avg[i]}"
497
- s2 += (samples[i] * avg[i]**2 + val**2)
498
- end
499
-
500
- # Note: For very large numbers with very small deviations this sqrt can fail.
501
- # If so then just set the stddev to 0.
502
- begin
503
- reduced["#{key}#{stddev_key}"] =
504
- Math.sqrt(s2 / samples_sum - reduced["#{key}#{avg_key}"])
505
- rescue Exception
506
- reduced["#{key}#{stddev_key}"] = 0.0
536
+ stddev = reduced["#{key}#{stddevvals_key}"]
537
+ if stddev
538
+ stddev.each_with_index do |val, i|
539
+ # puts "i:#{i} val:#{val} samples[i]:#{samples[i]} avg[i]:#{avg[i]}"
540
+ s2 += (samples[i] * avg[i]**2 + val**2)
541
+ end
542
+
543
+ # Note: For very large numbers with very small deviations this sqrt can fail.
544
+ # If so then just set the stddev to 0.
545
+ begin
546
+ reduced["#{key}#{stddev_key}"] =
547
+ Math.sqrt(s2 / samples_sum - reduced["#{key}#{avg_key}"])
548
+ rescue Exception
549
+ reduced["#{key}#{stddev_key}"] = 0.0
550
+ end
507
551
  end
508
552
 
509
553
  reduced.delete("#{key}#{avgvals_key}")
@@ -1,6 +1,6 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- # Copyright 2022 Ball Aerospace & Technologies Corp.
3
+ # Copyright 2023 OpenC3, Inc.
4
4
  # All Rights Reserved.
5
5
  #
6
6
  # This program is free software; you can modify and/or redistribute it
@@ -12,32 +12,31 @@
12
12
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
13
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
14
  # GNU Affero General Public License for more details.
15
-
16
- # Modified by OpenC3, Inc.
17
- # All changes Copyright 2022, OpenC3, Inc.
18
- # All Rights Reserved
19
15
  #
20
- # This file may also be used under the terms of a commercial license
16
+ # This file may also be used under the terms of a commercial license
21
17
  # if purchased from OpenC3, Inc.
22
18
 
19
+ require 'openc3/models/scope_model'
20
+ require 'openc3/microservices/cleanup_microservice'
21
+
23
22
  module OpenC3
24
- class NotificationModel
25
- attr_reader :time, :severity, :url, :title, :body
23
+ class ScopeCleanupMicroservice < CleanupMicroservice
24
+ def run
25
+ scope = ScopeModel.get_model(name: @scope, scope: @scope)
26
26
 
27
- def initialize(time:, severity:, url:, title:, body:)
28
- @time = time
29
- @severity = severity
30
- @url = url
31
- @title = title
32
- @body = body
33
- end
27
+ areas = [
28
+ ["#{@scope}/text_logs", scope.text_log_retain_time],
29
+ ["#{@scope}/tool_logs", scope.tool_log_retain_time],
30
+ ]
34
31
 
35
- def as_json(*a)
36
- { "time" => @time,
37
- "severity" => @severity,
38
- "url" => @url,
39
- "title" => @title,
40
- "body" => @body }
32
+ if @scope == 'DEFAULT'
33
+ areas << ["NOSCOPE/text_logs", scope.text_log_retain_time]
34
+ areas << ["NOSCOPE/tool_logs", scope.tool_log_retain_time]
35
+ end
36
+
37
+ cleanup(areas, scope.cleanup_poll_time)
41
38
  end
42
39
  end
43
40
  end
41
+
42
+ OpenC3::CleanupMicroservice.run if __FILE__ == $0
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'openc3/microservices/microservice'
24
24
  require 'openc3/topics/topic'
25
+ require 'openc3/io/json_rpc'
25
26
 
26
27
  module OpenC3
27
28
  class TextLogMicroservice < Microservice
@@ -77,11 +78,7 @@ module OpenC3
77
78
  msgid_seconds_from_epoch = msg_id.split('-')[0].to_i / 1000.0
78
79
  delta = Time.now.to_f - msgid_seconds_from_epoch
79
80
  @metric.set(name: 'text_log_topic_delta_seconds', value: delta, type: 'gauge', unit: 'seconds', help: 'Delta time between data written to stream and text log start')
80
-
81
- keys = msg_hash.keys
82
- keys.delete("time")
83
- entry = keys.reduce("") { |data, key| data + "#{key}: #{msg_hash[key]}\t" }
84
- @tlws[topic].write(msg_hash["time"].to_i, entry, topic, msg_id)
81
+ @tlws[topic].write(msg_hash["time"].to_i, msg_hash.as_json(allow_nan: true).to_json(allow_nan: true), topic, msg_id)
85
82
  @count += 1
86
83
  rescue => err
87
84
  @error = err
@@ -23,7 +23,6 @@
23
23
  require 'openc3/utilities/authentication'
24
24
  require 'openc3/microservices/microservice'
25
25
  require 'openc3/models/activity_model'
26
- require 'openc3/models/notification_model'
27
26
  require 'openc3/models/timeline_model'
28
27
  require 'openc3/topics/timeline_topic'
29
28
 
@@ -21,7 +21,6 @@
21
21
  # if purchased from OpenC3, Inc.
22
22
 
23
23
  require 'openc3/microservices/microservice'
24
- require 'openc3/models/notification_model'
25
24
  require 'openc3/models/trigger_model'
26
25
  require 'openc3/topics/autonomic_topic'
27
26
  require 'openc3/utilities/authentication'