openc3 7.0.0 → 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 +105 -13
- data/bin/pipinstall +38 -6
- data/data/config/command_modifiers.yaml +1 -0
- data/data/config/item_modifiers.yaml +2 -1
- data/data/config/microservice.yaml +12 -1
- data/data/config/parameter_modifiers.yaml +49 -7
- data/data/config/table_parameter_modifiers.yaml +3 -1
- data/data/config/target.yaml +11 -0
- data/data/config/target_config.yaml +6 -2
- data/lib/openc3/accessors/template_accessor.rb +9 -0
- 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/interfaces/interface.rb +1 -6
- 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 +27 -96
- data/lib/openc3/microservices/interface_decom_common.rb +28 -10
- data/lib/openc3/microservices/interface_microservice.rb +16 -9
- 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/plugin_model.rb +9 -1
- data/lib/openc3/models/python_package_model.rb +1 -1
- data/lib/openc3/models/reaction_model.rb +27 -9
- 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/models/trigger_model.rb +24 -7
- data/lib/openc3/packets/packet_config.rb +4 -1
- data/lib/openc3/script/api_shared.rb +39 -2
- data/lib/openc3/script/calendar.rb +32 -10
- data/lib/openc3/script/extract.rb +46 -13
- 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 +31 -11
- data/lib/openc3/topics/interface_topic.rb +88 -27
- 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/ctrf.rb +231 -0
- data/lib/openc3/utilities/metric.rb +15 -1
- data/lib/openc3/utilities/questdb_client.rb +177 -40
- 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 +5 -5
- 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 -4
- data/templates/tool_vue/src/router.js +2 -2
- data/templates/widget/package.json +2 -2
- metadata +8 -3
|
@@ -28,42 +28,104 @@ module OpenC3
|
|
|
28
28
|
class QuestDBError < StandardError; end
|
|
29
29
|
|
|
30
30
|
# Thread-local PG connection storage using Concurrent::ThreadLocalVar.
|
|
31
|
-
# Each thread gets its own
|
|
31
|
+
# Each thread gets its own connections (per db_shard) to avoid thread-safety issues with PG::Connection.
|
|
32
32
|
# Connections are automatically garbage collected when threads terminate.
|
|
33
|
-
|
|
33
|
+
# Value is a Hash: { db_shard_number => PG::Connection }
|
|
34
|
+
@thread_conns = Concurrent::ThreadLocalVar.new { Hash.new } # NOSONAR
|
|
35
|
+
|
|
36
|
+
# DB_Shard cache: { "scope__target_name" => [db_shard_number, Time] }
|
|
37
|
+
@db_shard_cache = {}
|
|
38
|
+
@db_shard_cache_mutex = Mutex.new
|
|
39
|
+
DB_SHARD_CACHE_TIMEOUT = 60 # seconds
|
|
40
|
+
|
|
41
|
+
# Resolve the hostname for a given db_shard number.
|
|
42
|
+
# If OPENC3_TSDB_HOSTNAME contains "SHARDNUM", it is replaced with the db_shard number.
|
|
43
|
+
# Otherwise, all db_shards connect to the same host (backward compatible).
|
|
44
|
+
def self.hostname_for_db_shard(db_shard)
|
|
45
|
+
ENV['OPENC3_TSDB_HOSTNAME'].to_s.gsub("SHARDNUM", db_shard.to_s)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Look up the db_shard number for a target from TargetModel with a 1-minute cache.
|
|
49
|
+
# Non-target-specific data (nil target_name) always returns db_shard 0.
|
|
50
|
+
def self.db_shard_for_target(target_name, scope: "DEFAULT")
|
|
51
|
+
return 0 unless target_name
|
|
52
|
+
|
|
53
|
+
cache_key = "#{scope}__#{target_name}"
|
|
54
|
+
now = Time.now
|
|
55
|
+
|
|
56
|
+
@db_shard_cache_mutex.synchronize do
|
|
57
|
+
cached = @db_shard_cache[cache_key]
|
|
58
|
+
if cached
|
|
59
|
+
db_shard, cached_at = cached
|
|
60
|
+
return db_shard if (now - cached_at) < DB_SHARD_CACHE_TIMEOUT
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Cache miss or expired — look up from TargetModel
|
|
65
|
+
begin
|
|
66
|
+
model = TargetModel.get(name: target_name, scope: scope)
|
|
67
|
+
db_shard = model ? model['db_shard'].to_i : 0
|
|
68
|
+
rescue
|
|
69
|
+
db_shard = 0
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
@db_shard_cache_mutex.synchronize do
|
|
73
|
+
@db_shard_cache[cache_key] = [db_shard, now]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
db_shard
|
|
77
|
+
end
|
|
34
78
|
|
|
35
|
-
# Get or create a thread-local PG connection with type mapping configured.
|
|
79
|
+
# Get or create a thread-local PG connection for the given db_shard with type mapping configured.
|
|
36
80
|
# Returns the thread-local connection - callers should not close it.
|
|
37
|
-
def self.connection
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn)
|
|
48
|
-
@thread_conn.value = conn
|
|
81
|
+
def self.connection(db_shard: 0)
|
|
82
|
+
conns = @thread_conns.value
|
|
83
|
+
conn = conns[db_shard]
|
|
84
|
+
if conn and not conn.finished?
|
|
85
|
+
begin
|
|
86
|
+
conn.check_socket
|
|
87
|
+
return conn
|
|
88
|
+
rescue
|
|
89
|
+
# Will need to reconnect
|
|
90
|
+
end
|
|
49
91
|
end
|
|
92
|
+
conn = PG::Connection.new(
|
|
93
|
+
host: hostname_for_db_shard(db_shard),
|
|
94
|
+
port: ENV['OPENC3_TSDB_QUERY_PORT'],
|
|
95
|
+
user: ENV['OPENC3_TSDB_USERNAME'],
|
|
96
|
+
password: ENV['OPENC3_TSDB_PASSWORD'],
|
|
97
|
+
dbname: 'qdb'
|
|
98
|
+
)
|
|
99
|
+
conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn)
|
|
100
|
+
conns[db_shard] = conn
|
|
101
|
+
@thread_conns.value = conns
|
|
50
102
|
conn
|
|
51
103
|
end
|
|
52
104
|
|
|
53
|
-
# Reset the connection for the current thread. Used after errors.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
105
|
+
# Reset the connection(s) for the current thread. Used after errors.
|
|
106
|
+
# If db_shard is nil, closes all db_shard connections. Otherwise closes only the specified db_shard.
|
|
107
|
+
def self.disconnect(db_shard: nil)
|
|
108
|
+
conns = @thread_conns.value
|
|
109
|
+
if db_shard.nil?
|
|
110
|
+
conns.each_value do |conn|
|
|
111
|
+
conn.finish if conn && !conn.finished?
|
|
112
|
+
end
|
|
113
|
+
@thread_conns.value = {}
|
|
114
|
+
else
|
|
115
|
+
conn = conns[db_shard]
|
|
116
|
+
if conn && !conn.finished?
|
|
117
|
+
conn.finish
|
|
118
|
+
end
|
|
119
|
+
conns.delete(db_shard)
|
|
120
|
+
@thread_conns.value = conns
|
|
58
121
|
end
|
|
59
|
-
@thread_conn.value = nil
|
|
60
122
|
end
|
|
61
123
|
|
|
62
124
|
# Health check - attempt to connect and immediately close.
|
|
63
125
|
# Returns true if successful, raises on failure.
|
|
64
|
-
def self.check_connection
|
|
126
|
+
def self.check_connection(db_shard: 0)
|
|
65
127
|
conn = PG::Connection.new(
|
|
66
|
-
host:
|
|
128
|
+
host: hostname_for_db_shard(db_shard),
|
|
67
129
|
port: ENV['OPENC3_TSDB_QUERY_PORT'],
|
|
68
130
|
user: ENV['OPENC3_TSDB_USERNAME'],
|
|
69
131
|
password: ENV['OPENC3_TSDB_PASSWORD'],
|
|
@@ -127,14 +189,15 @@ module OpenC3
|
|
|
127
189
|
# - Arrays are JSON-encoded: "[1, 2, 3]" or '["a", "b"]'
|
|
128
190
|
# - Objects/Hashes are JSON-encoded: '{"key": "value"}'
|
|
129
191
|
# - Binary data (BLOCK) is base64-encoded
|
|
130
|
-
# - Large integers (64-bit) are stored as
|
|
192
|
+
# - Large integers (≥64-bit) are stored as VARCHAR strings
|
|
131
193
|
#
|
|
132
194
|
# @param value [Object] The value to decode
|
|
133
195
|
# @param data_type [String] COSMOS data type (INT, UINT, FLOAT, STRING, BLOCK, DERIVED, etc.)
|
|
134
196
|
# @param array_size [Integer, nil] If not nil, indicates this is an array item
|
|
135
197
|
# @return [Object] The decoded value
|
|
136
198
|
def self.decode_value(value, data_type: nil, array_size: nil)
|
|
137
|
-
# Handle BigDecimal values from QuestDB DECIMAL columns
|
|
199
|
+
# Handle BigDecimal values from legacy QuestDB DECIMAL columns
|
|
200
|
+
# (pre-existing tables may still use DECIMAL; new tables use VARCHAR)
|
|
138
201
|
if value.is_a?(BigDecimal)
|
|
139
202
|
return value.to_i if data_type == 'INT' || data_type == 'UINT'
|
|
140
203
|
return value
|
|
@@ -167,7 +230,7 @@ module OpenC3
|
|
|
167
230
|
end
|
|
168
231
|
end
|
|
169
232
|
|
|
170
|
-
# Integer values stored as strings (
|
|
233
|
+
# Integer values stored as VARCHAR strings (≥64-bit integers)
|
|
171
234
|
if data_type == 'INT' || data_type == 'UINT'
|
|
172
235
|
begin
|
|
173
236
|
return Integer(value)
|
|
@@ -290,14 +353,14 @@ module OpenC3
|
|
|
290
353
|
# @param label [String, nil] Optional label for log messages
|
|
291
354
|
# @return [PG::Result, nil] Query result
|
|
292
355
|
# @raise [RuntimeError] After exhausting retries
|
|
293
|
-
def self.query_with_retry(query, params: [], max_retries: 5, label: nil)
|
|
356
|
+
def self.query_with_retry(query, params: [], max_retries: 5, label: nil, db_shard: 0)
|
|
294
357
|
retry_count = 0
|
|
295
358
|
begin
|
|
296
|
-
conn = connection
|
|
359
|
+
conn = connection(db_shard: db_shard)
|
|
297
360
|
if params.empty?
|
|
298
|
-
conn.exec(query)
|
|
361
|
+
return conn.exec(query)
|
|
299
362
|
else
|
|
300
|
-
conn.exec_params(query, params)
|
|
363
|
+
return conn.exec_params(query, params)
|
|
301
364
|
end
|
|
302
365
|
rescue IOError, PG::Error => e
|
|
303
366
|
retry_count += 1
|
|
@@ -306,7 +369,7 @@ module OpenC3
|
|
|
306
369
|
end
|
|
307
370
|
Logger.warn("TSDB#{label ? " #{label}" : ""}: Retrying due to error: #{e.message}")
|
|
308
371
|
Logger.warn("TSDB#{label ? " #{label}" : ""}: Last query: #{query}")
|
|
309
|
-
disconnect
|
|
372
|
+
disconnect(db_shard: db_shard)
|
|
310
373
|
sleep 0.1
|
|
311
374
|
retry
|
|
312
375
|
end
|
|
@@ -542,11 +605,11 @@ module OpenC3
|
|
|
542
605
|
# @param start_time [Integer] Nanosecond start time
|
|
543
606
|
# @param end_time [Integer, nil] Nanosecond end time
|
|
544
607
|
# @return [Boolean]
|
|
545
|
-
def self.table_has_data?(table_name, start_time, end_time)
|
|
546
|
-
query = "SELECT 1 FROM #{table_name}"
|
|
608
|
+
def self.table_has_data?(table_name, start_time, end_time, db_shard: 0)
|
|
609
|
+
query = "SELECT 1 FROM \"#{table_name}\""
|
|
547
610
|
query += time_where_clause(start_time, end_time)
|
|
548
611
|
query += " LIMIT 1"
|
|
549
|
-
result = query_with_retry(query, max_retries: 1, label: "table_has_data")
|
|
612
|
+
result = query_with_retry(query, max_retries: 1, label: "table_has_data", db_shard: db_shard)
|
|
550
613
|
result && result.ntuples > 0
|
|
551
614
|
rescue RuntimeError
|
|
552
615
|
false
|
|
@@ -559,13 +622,13 @@ module OpenC3
|
|
|
559
622
|
# @param page_size [Integer] Number of rows per page
|
|
560
623
|
# @param label [String] Label for log messages
|
|
561
624
|
# @yield [PG::Result] Each page of results
|
|
562
|
-
def self.paginate_query(query, page_size, label:)
|
|
625
|
+
def self.paginate_query(query, page_size, label:, db_shard: 0)
|
|
563
626
|
min = 0
|
|
564
627
|
max = page_size
|
|
565
628
|
loop do
|
|
566
629
|
query_offset = "#{query} LIMIT #{min}, #{max}"
|
|
567
630
|
Logger.debug("QuestDB #{label}: #{query_offset}")
|
|
568
|
-
result = query_with_retry(query_offset, label: label)
|
|
631
|
+
result = query_with_retry(query_offset, label: label, db_shard: db_shard)
|
|
569
632
|
min += page_size
|
|
570
633
|
max += page_size
|
|
571
634
|
if result.nil? or result.ntuples == 0
|
|
@@ -589,7 +652,7 @@ module OpenC3
|
|
|
589
652
|
names << TIMESTAMP_SELECT
|
|
590
653
|
names << "RECEIVED_TIMESECONDS" if include_received_ts
|
|
591
654
|
names << "COSMOS_EXTRA"
|
|
592
|
-
query = "SELECT #{names.join(', ')} FROM #{table_name}"
|
|
655
|
+
query = "SELECT #{names.join(', ')} FROM \"#{table_name}\""
|
|
593
656
|
query += time_where_clause(start_time, end_time)
|
|
594
657
|
query
|
|
595
658
|
end
|
|
@@ -807,6 +870,8 @@ module OpenC3
|
|
|
807
870
|
|
|
808
871
|
# Query historical telemetry data from QuestDB for a list of items.
|
|
809
872
|
# Builds the SQL query, executes it, and decodes all results.
|
|
873
|
+
# Supports cross-db_shard queries by grouping items by db_shard, executing
|
|
874
|
+
# separate queries per db_shard, and merging results positionally.
|
|
810
875
|
#
|
|
811
876
|
# @param items [Array] Array of [target_name, packet_name, item_name, value_type, limits]
|
|
812
877
|
# item_name may be nil to indicate a placeholder (non-existent item)
|
|
@@ -816,6 +881,78 @@ module OpenC3
|
|
|
816
881
|
# @return [Array, Hash] Array of [value, limits_state] pairs per row, or {} if no results.
|
|
817
882
|
# Single-row results return a flat array; multi-row results return array of arrays.
|
|
818
883
|
def self.tsdb_lookup(items, start_time:, end_time: nil, scope: "DEFAULT")
|
|
884
|
+
# Group items by db_shard number while preserving their original positions
|
|
885
|
+
db_shard_groups = {} # db_shard => { positions: [], items: [] }
|
|
886
|
+
items.each_with_index do |item, pos|
|
|
887
|
+
target_name = item[0]
|
|
888
|
+
db_shard = db_shard_for_target(target_name, scope: scope)
|
|
889
|
+
db_shard_groups[db_shard] ||= { positions: [], items: [] }
|
|
890
|
+
db_shard_groups[db_shard][:positions] << pos
|
|
891
|
+
db_shard_groups[db_shard][:items] << item
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
# Single-db_shard fast path (most common case)
|
|
895
|
+
if db_shard_groups.length == 1
|
|
896
|
+
db_shard, group = db_shard_groups.first
|
|
897
|
+
return tsdb_lookup_single_db_shard(group[:items], start_time: start_time, end_time: end_time, scope: scope, db_shard: db_shard)
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
# Cross-db_shard: execute per-db_shard queries and merge results
|
|
901
|
+
db_shard_results = {} # db_shard => data
|
|
902
|
+
db_shard_groups.each do |db_shard, group|
|
|
903
|
+
result = tsdb_lookup_single_db_shard(group[:items], start_time: start_time, end_time: end_time, scope: scope, db_shard: db_shard)
|
|
904
|
+
db_shard_results[db_shard] = result
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
# If all db_shards returned empty, return empty
|
|
908
|
+
return {} if db_shard_results.values.all? { |r| r == {} }
|
|
909
|
+
|
|
910
|
+
# Merge results positionally back into the original item order.
|
|
911
|
+
# For single-row results (no end_time), merge flat arrays.
|
|
912
|
+
# For multi-row results, each db_shard may have different row counts;
|
|
913
|
+
# use the maximum row count and fill missing positions with [nil, nil].
|
|
914
|
+
if !end_time
|
|
915
|
+
# Single-row mode: each db_shard returns a flat array of [value, limits] pairs.
|
|
916
|
+
# Merge them into the original item order.
|
|
917
|
+
merged = Array.new(items.length) { [nil, nil] }
|
|
918
|
+
db_shard_groups.each do |db_shard, group|
|
|
919
|
+
result = db_shard_results[db_shard]
|
|
920
|
+
next if result == {} || !result.is_a?(Array)
|
|
921
|
+
group[:positions].each_with_index do |orig_pos, db_shard_idx|
|
|
922
|
+
merged[orig_pos] = result[db_shard_idx] if result[db_shard_idx]
|
|
923
|
+
end
|
|
924
|
+
end
|
|
925
|
+
merged
|
|
926
|
+
else
|
|
927
|
+
# Multi-row mode: find max row count across db_shards
|
|
928
|
+
max_rows = 0
|
|
929
|
+
db_shard_groups.each do |db_shard, _group|
|
|
930
|
+
result = db_shard_results[db_shard]
|
|
931
|
+
next if result == {}
|
|
932
|
+
count = result.is_a?(Array) ? result.length : 0
|
|
933
|
+
max_rows = count if count > max_rows
|
|
934
|
+
end
|
|
935
|
+
return {} if max_rows == 0
|
|
936
|
+
|
|
937
|
+
merged = Array.new(max_rows) { Array.new(items.length) { [nil, nil] } }
|
|
938
|
+
db_shard_groups.each do |db_shard, group|
|
|
939
|
+
result = db_shard_results[db_shard]
|
|
940
|
+
next if result == {}
|
|
941
|
+
rows = result.is_a?(Array) ? result : []
|
|
942
|
+
rows.each_with_index do |row, row_num|
|
|
943
|
+
next unless row.is_a?(Array)
|
|
944
|
+
group[:positions].each_with_index do |orig_pos, db_shard_idx|
|
|
945
|
+
merged[row_num][orig_pos] = row[db_shard_idx] if row[db_shard_idx]
|
|
946
|
+
end
|
|
947
|
+
end
|
|
948
|
+
end
|
|
949
|
+
merged
|
|
950
|
+
end
|
|
951
|
+
end
|
|
952
|
+
|
|
953
|
+
# Execute a tsdb_lookup query against a single db_shard.
|
|
954
|
+
# This contains the original ASOF JOIN logic for items all on the same QuestDB instance.
|
|
955
|
+
def self.tsdb_lookup_single_db_shard(items, start_time:, end_time: nil, scope: "DEFAULT", db_shard: 0)
|
|
819
956
|
tables = {}
|
|
820
957
|
names = []
|
|
821
958
|
nil_count = 0
|
|
@@ -887,9 +1024,9 @@ module OpenC3
|
|
|
887
1024
|
query = "SELECT #{names.join(", ")} FROM "
|
|
888
1025
|
tables.each_with_index do |(table_name, _), index|
|
|
889
1026
|
if index == 0
|
|
890
|
-
query += "#{table_name} as T#{index} "
|
|
1027
|
+
query += "\"#{table_name}\" as T#{index} "
|
|
891
1028
|
else
|
|
892
|
-
query += "ASOF JOIN #{table_name} as T#{index} "
|
|
1029
|
+
query += "ASOF JOIN \"#{table_name}\" as T#{index} "
|
|
893
1030
|
end
|
|
894
1031
|
end
|
|
895
1032
|
query_params = []
|
|
@@ -902,7 +1039,7 @@ module OpenC3
|
|
|
902
1039
|
query_params << end_time
|
|
903
1040
|
end
|
|
904
1041
|
|
|
905
|
-
result = query_with_retry(query, params: query_params, label: "tsdb_lookup")
|
|
1042
|
+
result = query_with_retry(query, params: query_params, label: "tsdb_lookup", db_shard: db_shard)
|
|
906
1043
|
if result.nil? or result.ntuples == 0
|
|
907
1044
|
return {}
|
|
908
1045
|
end
|