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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +47 -3
  3. data/data/config/item_modifiers.yaml +1 -1
  4. data/data/config/microservice.yaml +12 -1
  5. data/data/config/parameter_modifiers.yaml +49 -7
  6. data/data/config/target.yaml +11 -0
  7. data/data/config/target_config.yaml +6 -2
  8. data/lib/openc3/api/cmd_api.rb +2 -1
  9. data/lib/openc3/api/metrics_api.rb +11 -1
  10. data/lib/openc3/api/tlm_api.rb +21 -6
  11. data/lib/openc3/core_ext/faraday.rb +1 -1
  12. data/lib/openc3/io/json_api.rb +1 -1
  13. data/lib/openc3/logs/log_writer.rb +3 -1
  14. data/lib/openc3/microservices/decom_common.rb +128 -0
  15. data/lib/openc3/microservices/decom_microservice.rb +26 -95
  16. data/lib/openc3/microservices/interface_decom_common.rb +6 -2
  17. data/lib/openc3/microservices/interface_microservice.rb +10 -8
  18. data/lib/openc3/microservices/log_microservice.rb +1 -1
  19. data/lib/openc3/microservices/microservice.rb +3 -2
  20. data/lib/openc3/microservices/queue_microservice.rb +1 -1
  21. data/lib/openc3/microservices/scope_cleanup_microservice.rb +60 -46
  22. data/lib/openc3/microservices/text_log_microservice.rb +1 -2
  23. data/lib/openc3/models/cvt_model.rb +24 -13
  24. data/lib/openc3/models/db_sharded_model.rb +110 -0
  25. data/lib/openc3/models/interface_model.rb +9 -0
  26. data/lib/openc3/models/interface_status_model.rb +33 -3
  27. data/lib/openc3/models/metric_model.rb +96 -37
  28. data/lib/openc3/models/microservice_model.rb +7 -0
  29. data/lib/openc3/models/microservice_status_model.rb +30 -3
  30. data/lib/openc3/models/reingest_job_model.rb +153 -0
  31. data/lib/openc3/models/scope_model.rb +3 -2
  32. data/lib/openc3/models/script_status_model.rb +4 -20
  33. data/lib/openc3/models/target_model.rb +113 -100
  34. data/lib/openc3/packets/packet_config.rb +4 -1
  35. data/lib/openc3/script/script.rb +2 -2
  36. data/lib/openc3/script/script_runner.rb +4 -4
  37. data/lib/openc3/script/telemetry.rb +3 -3
  38. data/lib/openc3/script/web_socket_api.rb +29 -22
  39. data/lib/openc3/system/system.rb +20 -3
  40. data/lib/openc3/topics/command_decom_topic.rb +4 -2
  41. data/lib/openc3/topics/command_topic.rb +8 -5
  42. data/lib/openc3/topics/decom_interface_topic.rb +15 -10
  43. data/lib/openc3/topics/interface_topic.rb +71 -29
  44. data/lib/openc3/topics/limits_event_topic.rb +62 -41
  45. data/lib/openc3/topics/router_topic.rb +61 -21
  46. data/lib/openc3/topics/system_events_topic.rb +18 -1
  47. data/lib/openc3/topics/telemetry_decom_topic.rb +2 -1
  48. data/lib/openc3/topics/telemetry_topic.rb +4 -2
  49. data/lib/openc3/topics/topic.rb +77 -5
  50. data/lib/openc3/utilities/aws_bucket.rb +2 -0
  51. data/lib/openc3/utilities/cli_generator.rb +3 -2
  52. data/lib/openc3/utilities/metric.rb +15 -1
  53. data/lib/openc3/utilities/questdb_client.rb +173 -37
  54. data/lib/openc3/utilities/reingest_job.rb +377 -0
  55. data/lib/openc3/utilities/ruby_lex_utils.rb +2 -0
  56. data/lib/openc3/utilities/store_autoload.rb +78 -52
  57. data/lib/openc3/utilities/store_queued.rb +20 -12
  58. data/lib/openc3/version.rb +6 -6
  59. data/templates/plugin/plugin.gemspec +13 -1
  60. data/templates/tool_angular/package.json +2 -2
  61. data/templates/tool_react/package.json +1 -1
  62. data/templates/tool_svelte/package.json +1 -1
  63. data/templates/tool_vue/package.json +3 -3
  64. data/templates/tool_vue/src/router.js +2 -2
  65. data/templates/widget/package.json +2 -2
  66. 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
- Topic.update_topic_offsets(@topics)
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
- # Break packet into subpackets (if necessary)
177
- # Subpackets are typically channelized data
178
- ################################################################################
179
- packet_and_subpackets = packet.subpacketize
180
-
181
- packet_and_subpackets.each do |packet_or_subpacket|
182
- if packet_or_subpacket.subpacket
183
- packet_or_subpacket = handle_subpacket(packet, packet_or_subpacket)
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
- @logger.error e.message
197
- end
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
- values = item.limits.values[System.limits_set]
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
@@ -19,7 +19,7 @@ require 'openc3/api/api'
19
19
 
20
20
  module OpenC3
21
21
  saved_verbose = $VERBOSE
22
- $VERBOSE = false
22
+ $VERBOSE = nil
23
23
  module Script
24
24
  private
25
25
  # Override the prompt_for_hazardous method to always return true since there is no user to prompt
@@ -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
- conn = OpenC3::QuestDBClient.connection
69
- result = conn.exec(TSDB_HEALTH_QUERY)
70
- columns = result.fields
71
- rows = result.values
72
-
73
- table_name_column = columns.index("table_name")
74
- wal_pending_row_count_column = columns.index("wal_pending_row_count")
75
- status_column = columns.index("status")
76
- lag_txns_column = columns.index("lag_txns")
77
-
78
- rows.each do |values|
79
- table_name = values[table_name_column]
80
- wal_pending_row_count = values[wal_pending_row_count_column].to_i
81
- status = values[status_column]
82
- lag_txns = values[lag_txns_column].to_i
83
-
84
- if status != 'OK'
85
- @logger.error("QuestDB: #{table_name} in bad state: #{status}")
86
-
87
- if status == 'SUSPENDED'
88
- # Try to automatically unsuspend
89
- @logger.info("QuestDB: Attempting to unsuspend: #{table_name}")
90
- conn.exec("ALTER TABLE #{table_name} RESUME WAL;")
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
- @wal_pending_row_count[table_name] ||= []
95
- @wal_pending_row_count[table_name] << wal_pending_row_count
96
- @lag_txns[table_name] ||= []
97
- @lag_txns[table_name] << lag_txns
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
- if @wal_pending_row_count[table_name].length > GROWTH_NUM_SAMPLE_PERIODS
100
- if detect_growth(@wal_pending_row_count[table_name], GROWTH_NUM_SAMPLE_PERIODS)
101
- # Crossed threshold of sample periods of growth
102
- @logger.error("QuestDB: #{table_name} has growing wal_pending_row_count: #{wal_pending_row_count}")
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
- # Leave the last GROWTH_NUM_SAMPLE_PERIODS samples
106
- @wal_pending_row_count[table_name] = @wal_pending_row_count[table_name][-GROWTH_NUM_SAMPLE_PERIODS..-1]
107
- end
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
- if @lag_txns[table_name].length > GROWTH_NUM_SAMPLE_PERIODS
110
- if detect_growth(@lag_txns[table_name], GROWTH_NUM_SAMPLE_PERIODS)
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 shards
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
- Store.hdel(key, packet_name)
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
- StoreQueued.hset(key, packet_name, packet_json)
58
+ store_queued_for_target(target_name, scope: scope).hset(key, packet_name, packet_json)
49
59
  else
50
- Store.hset(key, packet_name, packet_json)
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
- StoreQueued.hset(key, packet_name, packet_json)
72
+ store_queued_for_target(target_name, scope: scope).hset(key, packet_name, packet_json)
63
73
  else
64
- Store.hset(key, packet_name, packet_json)
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 = Store.hget(key, packet_name)
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 = Store.hgetall("#{scope}__override__#{target_name}")
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 = Store.hget("#{scope}__override__#{target_name}", packet_name)
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
- Store.hset("#{scope}__override__#{target_name}", packet_name, JSON.generate(hash.as_json, allow_nan: true))
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 = Store.hget("#{scope}__override__#{target_name}", packet_name)
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
- Store.hdel("#{scope}__override__#{target_name}", packet_name)
287
+ store.hdel("#{scope}__override__#{target_name}", packet_name)
277
288
  else
278
289
  @@override_cache[tgt_pkt_key] = [Time.now, hash]
279
- Store.hset("#{scope}__override__#{target_name}", packet_name, JSON.generate(hash.as_json, allow_nan: true))
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 = Store.hget("#{scope}__override__#{target_name}", packet_name)
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