openc3 7.0.1 → 7.1.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.
- checksums.yaml +4 -4
- data/bin/openc3cli +47 -3
- data/data/config/item_modifiers.yaml +1 -1
- data/data/config/microservice.yaml +12 -1
- data/data/config/parameter_modifiers.yaml +49 -7
- data/data/config/target.yaml +11 -0
- data/data/config/target_config.yaml +6 -2
- data/lib/openc3/api/cmd_api.rb +2 -1
- data/lib/openc3/api/metrics_api.rb +11 -1
- data/lib/openc3/api/tlm_api.rb +21 -6
- data/lib/openc3/core_ext/faraday.rb +1 -1
- data/lib/openc3/io/json_api.rb +1 -1
- data/lib/openc3/logs/log_writer.rb +3 -1
- data/lib/openc3/microservices/decom_common.rb +128 -0
- data/lib/openc3/microservices/decom_microservice.rb +26 -95
- data/lib/openc3/microservices/interface_decom_common.rb +6 -2
- data/lib/openc3/microservices/interface_microservice.rb +10 -8
- data/lib/openc3/microservices/log_microservice.rb +1 -1
- data/lib/openc3/microservices/microservice.rb +3 -2
- data/lib/openc3/microservices/queue_microservice.rb +1 -1
- data/lib/openc3/microservices/scope_cleanup_microservice.rb +60 -46
- data/lib/openc3/microservices/text_log_microservice.rb +1 -2
- data/lib/openc3/models/cvt_model.rb +24 -13
- data/lib/openc3/models/db_sharded_model.rb +110 -0
- data/lib/openc3/models/interface_model.rb +9 -0
- data/lib/openc3/models/interface_status_model.rb +33 -3
- data/lib/openc3/models/metric_model.rb +96 -37
- data/lib/openc3/models/microservice_model.rb +7 -0
- data/lib/openc3/models/microservice_status_model.rb +30 -3
- data/lib/openc3/models/reingest_job_model.rb +153 -0
- data/lib/openc3/models/scope_model.rb +3 -2
- data/lib/openc3/models/script_status_model.rb +4 -20
- data/lib/openc3/models/target_model.rb +113 -100
- data/lib/openc3/packets/packet_config.rb +4 -1
- data/lib/openc3/script/script.rb +2 -2
- data/lib/openc3/script/script_runner.rb +4 -4
- data/lib/openc3/script/telemetry.rb +3 -3
- data/lib/openc3/script/web_socket_api.rb +29 -22
- data/lib/openc3/system/system.rb +20 -3
- data/lib/openc3/topics/command_decom_topic.rb +4 -2
- data/lib/openc3/topics/command_topic.rb +8 -5
- data/lib/openc3/topics/decom_interface_topic.rb +15 -10
- data/lib/openc3/topics/interface_topic.rb +71 -29
- data/lib/openc3/topics/limits_event_topic.rb +62 -41
- data/lib/openc3/topics/router_topic.rb +61 -21
- data/lib/openc3/topics/system_events_topic.rb +18 -1
- data/lib/openc3/topics/telemetry_decom_topic.rb +2 -1
- data/lib/openc3/topics/telemetry_topic.rb +4 -2
- data/lib/openc3/topics/topic.rb +77 -5
- data/lib/openc3/utilities/aws_bucket.rb +2 -0
- data/lib/openc3/utilities/cli_generator.rb +3 -2
- data/lib/openc3/utilities/metric.rb +15 -1
- data/lib/openc3/utilities/questdb_client.rb +173 -37
- data/lib/openc3/utilities/reingest_job.rb +377 -0
- data/lib/openc3/utilities/ruby_lex_utils.rb +2 -0
- data/lib/openc3/utilities/store_autoload.rb +78 -52
- data/lib/openc3/utilities/store_queued.rb +20 -12
- data/lib/openc3/version.rb +6 -6
- data/templates/plugin/plugin.gemspec +13 -1
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_svelte/package.json +1 -1
- data/templates/tool_vue/package.json +3 -3
- data/templates/tool_vue/src/router.js +2 -2
- data/templates/widget/package.json +2 -2
- metadata +7 -3
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
require 'time'
|
|
19
19
|
require 'thread'
|
|
20
20
|
require 'openc3/microservices/microservice'
|
|
21
|
+
require 'openc3/microservices/decom_common'
|
|
21
22
|
require 'openc3/microservices/interface_decom_common'
|
|
22
23
|
require 'openc3/microservices/interface_microservice'
|
|
23
24
|
require 'openc3/topics/telemetry_decom_topic'
|
|
@@ -90,7 +91,9 @@ module OpenC3
|
|
|
90
91
|
if @name =~ /__DECOM__/
|
|
91
92
|
@topics << "#{@scope}__DECOMINTERFACE__{#{@target_names[0]}}"
|
|
92
93
|
end
|
|
93
|
-
|
|
94
|
+
@limits_event_topic = "#{@scope}__openc3_limits_events"
|
|
95
|
+
@topics << @limits_event_topic
|
|
96
|
+
Topic.update_topic_offsets(@topics, db_shard: @db_shard)
|
|
94
97
|
System.telemetry.limits_change_callback = method(:limits_change_callback)
|
|
95
98
|
LimitsEventTopic.sync_system(scope: @scope)
|
|
96
99
|
@error_count = 0
|
|
@@ -110,10 +113,13 @@ module OpenC3
|
|
|
110
113
|
|
|
111
114
|
begin
|
|
112
115
|
OpenC3.in_span("read_topics") do
|
|
113
|
-
Topic.read_topics(@topics) do |topic, msg_id, msg_hash, redis|
|
|
116
|
+
Topic.read_topics(@topics, db_shard: @db_shard) do |topic, msg_id, msg_hash, redis|
|
|
114
117
|
break if @cancel_thread
|
|
115
118
|
if topic == @microservice_topic
|
|
116
119
|
microservice_cmd(topic, msg_id, msg_hash, redis)
|
|
120
|
+
elsif topic == @limits_event_topic
|
|
121
|
+
event = JSON.parse(msg_hash['event'], allow_nan: true, create_additions: true)
|
|
122
|
+
LimitsEventTopic.process_event(event)
|
|
117
123
|
elsif topic =~ /__DECOMINTERFACE/
|
|
118
124
|
if msg_hash.key?('inject_tlm')
|
|
119
125
|
handle_inject_tlm_with_ack(msg_hash['inject_tlm'], msg_id)
|
|
@@ -134,7 +140,6 @@ module OpenC3
|
|
|
134
140
|
@count += 1
|
|
135
141
|
end
|
|
136
142
|
end
|
|
137
|
-
LimitsEventTopic.sync_system_thread_body(scope: @scope)
|
|
138
143
|
rescue => e
|
|
139
144
|
@error_count += 1
|
|
140
145
|
@metric.set(name: 'decom_error_total', value: @error_count, type: 'counter')
|
|
@@ -153,8 +158,6 @@ module OpenC3
|
|
|
153
158
|
delta = Time.now.to_f - msgid_seconds_from_epoch
|
|
154
159
|
@metric.set(name: 'decom_topic_delta_seconds', value: delta, type: 'gauge', unit: 'seconds', help: 'Delta time between data written to stream and decom start')
|
|
155
160
|
|
|
156
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
157
|
-
|
|
158
161
|
#######################################
|
|
159
162
|
# Build packet object from topic data
|
|
160
163
|
#######################################
|
|
@@ -172,98 +175,21 @@ module OpenC3
|
|
|
172
175
|
end
|
|
173
176
|
packet.buffer = msg_hash["buffer"]
|
|
174
177
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
#####################################################################################
|
|
187
|
-
# Run Processors
|
|
188
|
-
# This must be before the full decom so that processor derived values are available
|
|
189
|
-
#####################################################################################
|
|
190
|
-
begin
|
|
191
|
-
packet_or_subpacket.process # Run processors
|
|
192
|
-
rescue Exception => e
|
|
178
|
+
DecomCommon.decom_and_publish(
|
|
179
|
+
packet,
|
|
180
|
+
scope: @scope,
|
|
181
|
+
target_names: @target_names,
|
|
182
|
+
logger: @logger,
|
|
183
|
+
name: @name,
|
|
184
|
+
check_limits: true,
|
|
185
|
+
metric: @metric,
|
|
186
|
+
error_callback: ->(e) {
|
|
193
187
|
@error_count += 1
|
|
194
188
|
@metric.set(name: 'decom_error_total', value: @error_count, type: 'counter')
|
|
195
189
|
@error = e
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
#############################################################################
|
|
200
|
-
# Process all the limits and call the limits_change_callback (as necessary)
|
|
201
|
-
# This must be before the full decom so that limits states are available
|
|
202
|
-
#############################################################################
|
|
203
|
-
packet_or_subpacket.check_limits(System.limits_set)
|
|
204
|
-
|
|
205
|
-
# This is what actually decommutates the packet and updates the CVT
|
|
206
|
-
TelemetryDecomTopic.write_packet(packet_or_subpacket, scope: @scope)
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
|
|
210
|
-
@metric.set(name: 'decom_duration_seconds', value: diff, type: 'gauge', unit: 'seconds')
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
def handle_subpacket(packet, subpacket)
|
|
215
|
-
# Subpacket received time always = packet.received_time
|
|
216
|
-
# Use packet_time appropriately if another timestamp is needed
|
|
217
|
-
subpacket.received_time = packet.received_time
|
|
218
|
-
subpacket.stored = packet.stored
|
|
219
|
-
subpacket.extra = packet.extra
|
|
220
|
-
|
|
221
|
-
if subpacket.stored
|
|
222
|
-
# Stored telemetry does not update the current value table
|
|
223
|
-
identified_subpacket = System.telemetry.identify_and_define_packet(subpacket, @target_names, subpackets: true)
|
|
224
|
-
else
|
|
225
|
-
# Identify and update subpacket
|
|
226
|
-
if subpacket.identified?
|
|
227
|
-
begin
|
|
228
|
-
# Preidentifed subpacket - place it into the current value table
|
|
229
|
-
identified_subpacket = System.telemetry.update!(subpacket.target_name,
|
|
230
|
-
subpacket.packet_name,
|
|
231
|
-
subpacket.buffer)
|
|
232
|
-
rescue RuntimeError
|
|
233
|
-
# Subpacket identified but we don't know about it
|
|
234
|
-
# Clear packet_name and target_name and try to identify
|
|
235
|
-
@logger.warn "#{@name}: Received unknown identified subpacket: #{subpacket.target_name} #{subpacket.packet_name}"
|
|
236
|
-
subpacket.target_name = nil
|
|
237
|
-
subpacket.packet_name = nil
|
|
238
|
-
identified_subpacket = System.telemetry.identify!(subpacket.buffer,
|
|
239
|
-
@target_names, subpackets: true)
|
|
240
|
-
end
|
|
241
|
-
else
|
|
242
|
-
# Packet needs to be identified
|
|
243
|
-
identified_subpacket = System.telemetry.identify!(subpacket.buffer,
|
|
244
|
-
@target_names, subpackets: true)
|
|
245
|
-
end
|
|
190
|
+
},
|
|
191
|
+
)
|
|
246
192
|
end
|
|
247
|
-
|
|
248
|
-
if identified_subpacket
|
|
249
|
-
identified_subpacket.received_time = subpacket.received_time
|
|
250
|
-
identified_subpacket.stored = subpacket.stored
|
|
251
|
-
identified_subpacket.extra = subpacket.extra
|
|
252
|
-
subpacket = identified_subpacket
|
|
253
|
-
else
|
|
254
|
-
unknown_subpacket = System.telemetry.update!('UNKNOWN', 'UNKNOWN', subpacket.buffer)
|
|
255
|
-
unknown_subpacket.received_time = subpacket.received_time
|
|
256
|
-
unknown_subpacket.stored = subpacket.stored
|
|
257
|
-
unknown_subpacket.extra = subpacket.extra
|
|
258
|
-
subpacket = unknown_subpacket
|
|
259
|
-
num_bytes_to_print = [InterfaceMicroservice::UNKNOWN_BYTES_TO_PRINT, subpacket.length].min
|
|
260
|
-
data = subpacket.buffer(false)[0..(num_bytes_to_print - 1)]
|
|
261
|
-
prefix = data.each_byte.map { | byte | sprintf("%02X", byte) }.join()
|
|
262
|
-
@logger.warn "#{@name} #{subpacket.target_name} packet length: #{subpacket.length} starting with: #{prefix}"
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
TargetModel.sync_tlm_packet_counts(subpacket, @target_names, scope: @scope)
|
|
266
|
-
return subpacket
|
|
267
193
|
end
|
|
268
194
|
|
|
269
195
|
# Called when an item in any packet changes limits states.
|
|
@@ -285,7 +211,12 @@ module OpenC3
|
|
|
285
211
|
if value
|
|
286
212
|
message = "#{packet.target_name} #{packet.packet_name} #{item.name} = #{value} is #{item.limits.state}"
|
|
287
213
|
if item.limits.values
|
|
288
|
-
|
|
214
|
+
selected_limits_set = if item.limits.values.has_key?(System.limits_set)
|
|
215
|
+
System.limits_set
|
|
216
|
+
else
|
|
217
|
+
:DEFAULT
|
|
218
|
+
end
|
|
219
|
+
values = item.limits.values[selected_limits_set]
|
|
289
220
|
# Check if the state is RED_LOW, YELLOW_LOW, YELLOW_HIGH, RED_HIGH, GREEN_LOW, GREEN_HIGH
|
|
290
221
|
if LIMITS_STATE_INDEX[item.limits.state]
|
|
291
222
|
# Directly index into the values and return the value
|
|
@@ -335,4 +266,4 @@ if __FILE__ == $0
|
|
|
335
266
|
OpenC3::DecomMicroservice.run
|
|
336
267
|
OpenC3::ThreadManager.instance.shutdown
|
|
337
268
|
OpenC3::ThreadManager.instance.join
|
|
338
|
-
end
|
|
269
|
+
end
|
|
@@ -31,6 +31,8 @@ module OpenC3
|
|
|
31
31
|
packet.write(name.to_s, value, type)
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
|
+
stored = inject_tlm_hash.fetch('stored', false)
|
|
35
|
+
packet.stored = stored.to_s.downcase == 'true'
|
|
34
36
|
packet.received_time = Time.now.sys
|
|
35
37
|
packet.received_count = TargetModel.increment_telemetry_count(packet.target_name, packet.packet_name, 1, scope: @scope)
|
|
36
38
|
TelemetryTopic.write_packet(packet, scope: @scope)
|
|
@@ -63,6 +65,7 @@ module OpenC3
|
|
|
63
65
|
cmd_params = build_cmd_hash['cmd_params']
|
|
64
66
|
range_check = build_cmd_hash['range_check']
|
|
65
67
|
raw = build_cmd_hash['raw']
|
|
68
|
+
db_shard = Store.db_shard_for_target(target_name, scope: @scope)
|
|
66
69
|
ack_topic = "{#{@scope}__ACKCMD}TARGET__#{target_name}"
|
|
67
70
|
begin
|
|
68
71
|
command = System.commands.build_cmd(target_name, cmd_name, cmd_params, range_check, raw)
|
|
@@ -84,13 +87,14 @@ module OpenC3
|
|
|
84
87
|
result: error.message
|
|
85
88
|
}
|
|
86
89
|
end
|
|
87
|
-
Topic.write_topic(ack_topic, msg_hash)
|
|
90
|
+
Topic.write_topic(ack_topic, msg_hash, db_shard: db_shard)
|
|
88
91
|
end
|
|
89
92
|
|
|
90
93
|
def handle_get_tlm_buffer(get_tlm_buffer_json, msg_id)
|
|
91
94
|
get_tlm_buffer_hash = JSON.parse(get_tlm_buffer_json, allow_nan: true, create_additions: true)
|
|
92
95
|
target_name = get_tlm_buffer_hash['target_name']
|
|
93
96
|
packet_name = get_tlm_buffer_hash['packet_name']
|
|
97
|
+
db_shard = Store.db_shard_for_target(target_name, scope: @scope)
|
|
94
98
|
ack_topic = "{#{@scope}__ACKCMD}TARGET__#{target_name}"
|
|
95
99
|
begin
|
|
96
100
|
packet = System.telemetry.packet(target_name, packet_name)
|
|
@@ -115,7 +119,7 @@ module OpenC3
|
|
|
115
119
|
result: error.message
|
|
116
120
|
}
|
|
117
121
|
end
|
|
118
|
-
Topic.write_topic(ack_topic, msg_hash)
|
|
122
|
+
Topic.write_topic(ack_topic, msg_hash, db_shard: db_shard)
|
|
119
123
|
end
|
|
120
124
|
end
|
|
121
125
|
end
|
|
@@ -42,10 +42,11 @@ module OpenC3
|
|
|
42
42
|
class InterfaceCmdHandlerThread
|
|
43
43
|
include InterfaceDecomCommon
|
|
44
44
|
|
|
45
|
-
def initialize(interface, tlm, logger: nil, metric: nil, scope:)
|
|
45
|
+
def initialize(interface, tlm, logger: nil, metric: nil, db_shard: 0, scope:)
|
|
46
46
|
@interface = interface
|
|
47
47
|
@tlm = tlm
|
|
48
48
|
@scope = scope
|
|
49
|
+
@db_shard = db_shard.to_i
|
|
49
50
|
scope_model = ScopeModel.get_model(name: @scope)
|
|
50
51
|
if scope_model
|
|
51
52
|
@critical_commanding = scope_model.critical_commanding
|
|
@@ -81,7 +82,7 @@ module OpenC3
|
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
def run
|
|
84
|
-
InterfaceTopic.receive_commands(@interface, scope: @scope) do |topic, msg_id, msg_hash, _redis|
|
|
85
|
+
InterfaceTopic.receive_commands(@interface, scope: @scope, db_shard: @db_shard) do |topic, msg_id, msg_hash, _redis|
|
|
85
86
|
OpenC3.with_context(msg_hash) do
|
|
86
87
|
release_critical = false
|
|
87
88
|
critical_model = nil
|
|
@@ -105,7 +106,7 @@ module OpenC3
|
|
|
105
106
|
@metric.set(name: 'interface_directive_total', value: @directive_count, type: 'counter') if @metric
|
|
106
107
|
if msg_hash['shutdown']
|
|
107
108
|
@logger.info "#{@interface.name}: Shutdown requested"
|
|
108
|
-
InterfaceTopic.clear_topics(InterfaceTopic.topics(@interface, scope: @scope))
|
|
109
|
+
InterfaceTopic.clear_topics(InterfaceTopic.topics(@interface, scope: @scope), db_shard: @db_shard)
|
|
109
110
|
return
|
|
110
111
|
end
|
|
111
112
|
if msg_hash['connect']
|
|
@@ -379,10 +380,11 @@ module OpenC3
|
|
|
379
380
|
end
|
|
380
381
|
|
|
381
382
|
class RouterTlmHandlerThread
|
|
382
|
-
def initialize(router, tlm, logger: nil, metric: nil, scope:)
|
|
383
|
+
def initialize(router, tlm, logger: nil, metric: nil, db_shard: 0, scope:)
|
|
383
384
|
@router = router
|
|
384
385
|
@tlm = tlm
|
|
385
386
|
@scope = scope
|
|
387
|
+
@db_shard = db_shard.to_i
|
|
386
388
|
@logger = logger
|
|
387
389
|
@logger = Logger unless @logger
|
|
388
390
|
@metric = metric
|
|
@@ -412,7 +414,7 @@ module OpenC3
|
|
|
412
414
|
end
|
|
413
415
|
|
|
414
416
|
def run
|
|
415
|
-
RouterTopic.receive_telemetry(@router, scope: @scope) do |topic, msg_id, msg_hash, _redis|
|
|
417
|
+
RouterTopic.receive_telemetry(@router, scope: @scope, db_shard: @db_shard) do |topic, msg_id, msg_hash, _redis|
|
|
416
418
|
msgid_seconds_from_epoch = msg_id.split('-')[0].to_i / 1000.0
|
|
417
419
|
delta = Time.now.to_f - msgid_seconds_from_epoch
|
|
418
420
|
@metric.set(name: 'router_topic_delta_seconds', value: delta, type: 'gauge', unit: 'seconds', help: 'Delta time between data written to stream and router tlm start') if @metric
|
|
@@ -424,7 +426,7 @@ module OpenC3
|
|
|
424
426
|
|
|
425
427
|
if msg_hash['shutdown']
|
|
426
428
|
@logger.info "#{@router.name}: Shutdown requested"
|
|
427
|
-
RouterTopic.clear_topics(RouterTopic.topics(@router, scope: @scope))
|
|
429
|
+
RouterTopic.clear_topics(RouterTopic.topics(@router, scope: @scope), db_shard: @db_shard)
|
|
428
430
|
return
|
|
429
431
|
end
|
|
430
432
|
if msg_hash['connect']
|
|
@@ -590,9 +592,9 @@ module OpenC3
|
|
|
590
592
|
@connection_failed_messages = []
|
|
591
593
|
@connection_lost_messages = []
|
|
592
594
|
if @interface_or_router == 'INTERFACE'
|
|
593
|
-
@handler_thread = InterfaceCmdHandlerThread.new(@interface, self, logger: @logger, metric: @metric, scope: @scope)
|
|
595
|
+
@handler_thread = InterfaceCmdHandlerThread.new(@interface, self, logger: @logger, metric: @metric, db_shard: @db_shard, scope: @scope)
|
|
594
596
|
else
|
|
595
|
-
@handler_thread = RouterTlmHandlerThread.new(@interface, self, logger: @logger, metric: @metric, scope: @scope)
|
|
597
|
+
@handler_thread = RouterTlmHandlerThread.new(@interface, self, logger: @logger, metric: @metric, db_shard: @db_shard, scope: @scope)
|
|
596
598
|
end
|
|
597
599
|
@handler_thread.start
|
|
598
600
|
end
|
|
@@ -63,7 +63,7 @@ module OpenC3
|
|
|
63
63
|
while true
|
|
64
64
|
break if @cancel_thread
|
|
65
65
|
|
|
66
|
-
Topic.read_topics(@topics) do |topic, msg_id, msg_hash, redis|
|
|
66
|
+
Topic.read_topics(@topics, db_shard: @db_shard) do |topic, msg_id, msg_hash, redis|
|
|
67
67
|
break if @cancel_thread
|
|
68
68
|
if topic == @microservice_topic
|
|
69
69
|
microservice_cmd(topic, msg_id, msg_hash, redis)
|
|
@@ -127,6 +127,7 @@ module OpenC3
|
|
|
127
127
|
@logger.info("Microservice initialized with config:\n#{@config}")
|
|
128
128
|
@topics ||= []
|
|
129
129
|
@microservice_topic = "MICROSERVICE__#{@name}"
|
|
130
|
+
@db_shard = (@config['db_shard'] || 0).to_i
|
|
130
131
|
|
|
131
132
|
# Get configuration for any targets
|
|
132
133
|
@target_names = @config["target_names"]
|
|
@@ -259,10 +260,10 @@ module OpenC3
|
|
|
259
260
|
else
|
|
260
261
|
raise "Invalid topics given to microservice_cmd: #{topics}"
|
|
261
262
|
end
|
|
262
|
-
Topic.trim_topic(topic, msg_id)
|
|
263
|
+
Topic.trim_topic(topic, msg_id, db_shard: @db_shard)
|
|
263
264
|
return true
|
|
264
265
|
end
|
|
265
|
-
Topic.trim_topic(topic, msg_id)
|
|
266
|
+
Topic.trim_topic(topic, msg_id, db_shard: @db_shard)
|
|
266
267
|
return false
|
|
267
268
|
end
|
|
268
269
|
end
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# if purchased from OpenC3, Inc.
|
|
13
13
|
|
|
14
14
|
require 'openc3/models/scope_model'
|
|
15
|
+
require 'openc3/models/target_model'
|
|
15
16
|
require 'openc3/microservices/cleanup_microservice'
|
|
16
17
|
|
|
17
18
|
module OpenC3
|
|
@@ -62,63 +63,76 @@ ORDER BY
|
|
|
62
63
|
super(areas, bucket)
|
|
63
64
|
end
|
|
64
65
|
|
|
65
|
-
# Always check TSDB health
|
|
66
|
+
# Always check TSDB health across all db_shards
|
|
66
67
|
if @scope == 'DEFAULT'
|
|
68
|
+
# Collect all unique db_shard numbers from targets
|
|
69
|
+
db_shards = Set.new([0]) # Always check db_shard 0
|
|
67
70
|
begin
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
71
|
+
targets = OpenC3::TargetModel.all(scope: @scope)
|
|
72
|
+
targets.each_value { |target| db_shards << target['db_shard'].to_i if target['db_shard'] }
|
|
73
|
+
rescue => e
|
|
74
|
+
@logger.error("QuestDB: Error getting target db_shards: #{e.formatted}")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
db_shards.each do |db_shard|
|
|
78
|
+
begin
|
|
79
|
+
conn = OpenC3::QuestDBClient.connection(db_shard: db_shard)
|
|
80
|
+
result = conn.exec(TSDB_HEALTH_QUERY)
|
|
81
|
+
columns = result.fields
|
|
82
|
+
rows = result.values
|
|
83
|
+
|
|
84
|
+
table_name_column = columns.index("table_name")
|
|
85
|
+
wal_pending_row_count_column = columns.index("wal_pending_row_count")
|
|
86
|
+
status_column = columns.index("status")
|
|
87
|
+
lag_txns_column = columns.index("lag_txns")
|
|
88
|
+
|
|
89
|
+
rows.each do |values|
|
|
90
|
+
table_name = values[table_name_column]
|
|
91
|
+
# Prefix with db_shard to avoid key collisions across db_shards
|
|
92
|
+
tracking_key = "s#{db_shard}__#{table_name}"
|
|
93
|
+
wal_pending_row_count = values[wal_pending_row_count_column].to_i
|
|
94
|
+
status = values[status_column]
|
|
95
|
+
lag_txns = values[lag_txns_column].to_i
|
|
96
|
+
|
|
97
|
+
if status != 'OK'
|
|
98
|
+
@logger.error("QuestDB db_shard #{db_shard}: #{table_name} in bad state: #{status}")
|
|
99
|
+
|
|
100
|
+
if status == 'SUSPENDED'
|
|
101
|
+
# Try to automatically unsuspend
|
|
102
|
+
@logger.info("QuestDB db_shard #{db_shard}: Attempting to unsuspend: #{table_name}")
|
|
103
|
+
conn.exec("ALTER TABLE \"#{table_name}\" RESUME WAL;")
|
|
104
|
+
end
|
|
91
105
|
end
|
|
92
|
-
end
|
|
93
106
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
@wal_pending_row_count[tracking_key] ||= []
|
|
108
|
+
@wal_pending_row_count[tracking_key] << wal_pending_row_count
|
|
109
|
+
@lag_txns[tracking_key] ||= []
|
|
110
|
+
@lag_txns[tracking_key] << lag_txns
|
|
98
111
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
112
|
+
if @wal_pending_row_count[tracking_key].length > GROWTH_NUM_SAMPLE_PERIODS
|
|
113
|
+
if detect_growth(@wal_pending_row_count[tracking_key], GROWTH_NUM_SAMPLE_PERIODS)
|
|
114
|
+
# Crossed threshold of sample periods of growth
|
|
115
|
+
@logger.error("QuestDB db_shard #{db_shard}: #{table_name} has growing wal_pending_row_count: #{wal_pending_row_count}")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Leave the last GROWTH_NUM_SAMPLE_PERIODS samples
|
|
119
|
+
@wal_pending_row_count[tracking_key] = @wal_pending_row_count[tracking_key][-GROWTH_NUM_SAMPLE_PERIODS..-1]
|
|
103
120
|
end
|
|
104
121
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
122
|
+
if @lag_txns[tracking_key].length > GROWTH_NUM_SAMPLE_PERIODS
|
|
123
|
+
if detect_growth(@lag_txns[tracking_key], GROWTH_NUM_SAMPLE_PERIODS)
|
|
124
|
+
# Crossed threshold of sample periods of growth
|
|
125
|
+
@logger.error("QuestDB db_shard #{db_shard}: #{table_name} has growing lag_txns: #{lag_txns}")
|
|
126
|
+
end
|
|
108
127
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# Crossed threshold of sample periods of growth
|
|
112
|
-
@logger.error("QuestDB: #{table_name} has growing lag_txns: #{lag_txns}")
|
|
128
|
+
# Leave the last GROWTH_NUM_SAMPLE_PERIODS samples
|
|
129
|
+
@lag_txns[tracking_key] = @lag_txns[tracking_key][-GROWTH_NUM_SAMPLE_PERIODS..-1]
|
|
113
130
|
end
|
|
114
|
-
|
|
115
|
-
# Leave the last GROWTH_NUM_SAMPLE_PERIODS samples
|
|
116
|
-
@lag_txns[table_name] = @lag_txns[table_name][-GROWTH_NUM_SAMPLE_PERIODS..-1]
|
|
117
131
|
end
|
|
132
|
+
rescue => e
|
|
133
|
+
OpenC3::QuestDBClient.disconnect(db_shard: db_shard)
|
|
134
|
+
@logger.error("QuestDB db_shard #{db_shard} Error: #{e.formatted}")
|
|
118
135
|
end
|
|
119
|
-
rescue => e
|
|
120
|
-
OpenC3::QuestDBClient.disconnect
|
|
121
|
-
@logger.error("QuestDB Error: #{e.formatted}")
|
|
122
136
|
end
|
|
123
137
|
end
|
|
124
138
|
end
|
|
@@ -55,8 +55,7 @@ module OpenC3
|
|
|
55
55
|
while true
|
|
56
56
|
break if @cancel_thread
|
|
57
57
|
|
|
58
|
-
# Read each topic separately to support multiple redis
|
|
59
|
-
# This is needed with Redis cluster because the topics will likely be on different shards and we don't use {} in the topic names
|
|
58
|
+
# Read each topic separately to support multiple redis db_shards
|
|
60
59
|
individual_topics.each do |individual_topic|
|
|
61
60
|
break if @cancel_thread
|
|
62
61
|
# 500ms timeout - To support completing within 1 second with two topics (DEFAULT and NOSCOPE)
|
|
@@ -30,12 +30,22 @@ module OpenC3
|
|
|
30
30
|
packet.decom
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
# Get a Store instance routed to the correct db_shard for a target
|
|
34
|
+
def self.store_for_target(target_name, scope:)
|
|
35
|
+
Store.instance(db_shard: Store.db_shard_for_target(target_name, scope: scope))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Get a StoreQueued instance routed to the correct db_shard for a target
|
|
39
|
+
def self.store_queued_for_target(target_name, scope:)
|
|
40
|
+
StoreQueued.instance(db_shard: Store.db_shard_for_target(target_name, scope: scope))
|
|
41
|
+
end
|
|
42
|
+
|
|
33
43
|
# Delete the current value table for a target
|
|
34
44
|
def self.del(target_name:, packet_name:, scope: $openc3_scope)
|
|
35
45
|
key = "#{scope}__tlm__#{target_name}"
|
|
36
46
|
tgt_pkt_key = key + "__#{packet_name}"
|
|
37
47
|
@@packet_cache[tgt_pkt_key] = nil
|
|
38
|
-
|
|
48
|
+
store_for_target(target_name, scope: scope).hdel(key, packet_name)
|
|
39
49
|
end
|
|
40
50
|
|
|
41
51
|
# Set the current value table for a target, packet
|
|
@@ -45,9 +55,9 @@ module OpenC3
|
|
|
45
55
|
tgt_pkt_key = key + "__#{packet_name}"
|
|
46
56
|
@@packet_cache[tgt_pkt_key] = [Time.now, hash]
|
|
47
57
|
if queued
|
|
48
|
-
|
|
58
|
+
store_queued_for_target(target_name, scope: scope).hset(key, packet_name, packet_json)
|
|
49
59
|
else
|
|
50
|
-
|
|
60
|
+
store_for_target(target_name, scope: scope).hset(key, packet_name, packet_json)
|
|
51
61
|
end
|
|
52
62
|
end
|
|
53
63
|
|
|
@@ -59,9 +69,9 @@ module OpenC3
|
|
|
59
69
|
tgt_pkt_key = key + "__#{packet_name}"
|
|
60
70
|
@@packet_cache[tgt_pkt_key] = [Time.now, hash]
|
|
61
71
|
if queued
|
|
62
|
-
|
|
72
|
+
store_queued_for_target(target_name, scope: scope).hset(key, packet_name, packet_json)
|
|
63
73
|
else
|
|
64
|
-
|
|
74
|
+
store_for_target(target_name, scope: scope).hset(key, packet_name, packet_json)
|
|
65
75
|
end
|
|
66
76
|
end
|
|
67
77
|
|
|
@@ -75,7 +85,7 @@ module OpenC3
|
|
|
75
85
|
cache_time, hash = @@packet_cache[tgt_pkt_key]
|
|
76
86
|
return hash if hash and (now - cache_time) < cache_timeout
|
|
77
87
|
end
|
|
78
|
-
packet =
|
|
88
|
+
packet = store_for_target(target_name, scope: scope).hget(key, packet_name)
|
|
79
89
|
raise "Packet '#{target_name} #{packet_name}' does not exist" unless packet
|
|
80
90
|
hash = JSON.parse(packet, allow_nan: true, create_additions: true)
|
|
81
91
|
@@packet_cache[tgt_pkt_key] = [now, hash]
|
|
@@ -198,7 +208,7 @@ module OpenC3
|
|
|
198
208
|
def self.overrides(scope: $openc3_scope)
|
|
199
209
|
overrides = []
|
|
200
210
|
TargetModel.names(scope: scope).each do |target_name|
|
|
201
|
-
all =
|
|
211
|
+
all = store_for_target(target_name, scope: scope).hgetall("#{scope}__override__#{target_name}")
|
|
202
212
|
next if all.nil? or all.empty?
|
|
203
213
|
all.each do |packet_name, hash|
|
|
204
214
|
items = JSON.parse(hash, allow_nan: true, create_additions: true)
|
|
@@ -227,7 +237,7 @@ module OpenC3
|
|
|
227
237
|
# Override a current value table item such that it always returns the same value
|
|
228
238
|
# for the given type
|
|
229
239
|
def self.override(target_name, packet_name, item_name, value, type: :ALL, scope: $openc3_scope)
|
|
230
|
-
hash =
|
|
240
|
+
hash = store_for_target(target_name, scope: scope).hget("#{scope}__override__#{target_name}", packet_name)
|
|
231
241
|
hash = JSON.parse(hash, allow_nan: true, create_additions: true) if hash
|
|
232
242
|
hash ||= {} # In case the above didn't create anything
|
|
233
243
|
case type
|
|
@@ -247,12 +257,12 @@ module OpenC3
|
|
|
247
257
|
|
|
248
258
|
tgt_pkt_key = "#{scope}__tlm__#{target_name}__#{packet_name}"
|
|
249
259
|
@@override_cache[tgt_pkt_key] = [Time.now, hash]
|
|
250
|
-
|
|
260
|
+
store_for_target(target_name, scope: scope).hset("#{scope}__override__#{target_name}", packet_name, JSON.generate(hash.as_json, allow_nan: true))
|
|
251
261
|
end
|
|
252
262
|
|
|
253
263
|
# Normalize a current value table item such that it returns the actual value
|
|
254
264
|
def self.normalize(target_name, packet_name, item_name, type: :ALL, scope: $openc3_scope)
|
|
255
|
-
hash =
|
|
265
|
+
hash = store_for_target(target_name, scope: scope).hget("#{scope}__override__#{target_name}", packet_name)
|
|
256
266
|
hash = JSON.parse(hash, allow_nan: true, create_additions: true) if hash
|
|
257
267
|
hash ||= {} # In case the above didn't create anything
|
|
258
268
|
case type
|
|
@@ -271,12 +281,13 @@ module OpenC3
|
|
|
271
281
|
end
|
|
272
282
|
|
|
273
283
|
tgt_pkt_key = "#{scope}__tlm__#{target_name}__#{packet_name}"
|
|
284
|
+
store = store_for_target(target_name, scope: scope)
|
|
274
285
|
if hash.empty?
|
|
275
286
|
@@override_cache.delete(tgt_pkt_key)
|
|
276
|
-
|
|
287
|
+
store.hdel("#{scope}__override__#{target_name}", packet_name)
|
|
277
288
|
else
|
|
278
289
|
@@override_cache[tgt_pkt_key] = [Time.now, hash]
|
|
279
|
-
|
|
290
|
+
store.hset("#{scope}__override__#{target_name}", packet_name, JSON.generate(hash.as_json, allow_nan: true))
|
|
280
291
|
end
|
|
281
292
|
end
|
|
282
293
|
|
|
@@ -334,7 +345,7 @@ module OpenC3
|
|
|
334
345
|
return hash
|
|
335
346
|
end
|
|
336
347
|
end
|
|
337
|
-
override_data =
|
|
348
|
+
override_data = store_for_target(target_name, scope: scope).hget("#{scope}__override__#{target_name}", packet_name)
|
|
338
349
|
if override_data
|
|
339
350
|
hash = JSON.parse(override_data, allow_nan: true, create_additions: true)
|
|
340
351
|
overrides[tgt_pkt_key] = hash
|