cosmos 5.0.2.pre.beta2 → 5.0.4

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/bin/cosmos +1 -1
  3. data/data/config/microservice.yaml +47 -35
  4. data/data/config/plugins.yaml +3 -150
  5. data/data/config/target.yaml +70 -0
  6. data/data/config/tool.yaml +37 -31
  7. data/lib/cosmos/api/api.rb +1 -25
  8. data/lib/cosmos/api/cmd_api.rb +17 -6
  9. data/lib/cosmos/api/config_api.rb +10 -4
  10. data/lib/cosmos/api/limits_api.rb +1 -1
  11. data/lib/cosmos/api/settings_api.rb +19 -7
  12. data/lib/cosmos/api/target_api.rb +2 -2
  13. data/lib/cosmos/api/tlm_api.rb +69 -41
  14. data/lib/cosmos/config/config_parser.rb +19 -22
  15. data/lib/cosmos/config/meta_config_parser.rb +1 -1
  16. data/lib/cosmos/conversions/generic_conversion.rb +2 -2
  17. data/lib/cosmos/conversions/polynomial_conversion.rb +5 -8
  18. data/lib/cosmos/conversions/segmented_polynomial_conversion.rb +26 -9
  19. data/lib/cosmos/io/json_drb.rb +5 -1
  20. data/lib/cosmos/logs/log_writer.rb +2 -2
  21. data/lib/cosmos/microservices/cleanup_microservice.rb +28 -29
  22. data/lib/cosmos/microservices/decom_microservice.rb +1 -1
  23. data/lib/cosmos/microservices/interface_microservice.rb +0 -1
  24. data/lib/cosmos/microservices/microservice.rb +3 -3
  25. data/lib/cosmos/microservices/reducer_microservice.rb +12 -10
  26. data/lib/cosmos/models/cvt_model.rb +6 -6
  27. data/lib/cosmos/models/gem_model.rb +3 -3
  28. data/lib/cosmos/models/info_model.rb +1 -1
  29. data/lib/cosmos/models/interface_status_model.rb +1 -1
  30. data/lib/cosmos/models/metadata_model.rb +42 -216
  31. data/lib/cosmos/models/metric_model.rb +2 -2
  32. data/lib/cosmos/models/microservice_model.rb +1 -1
  33. data/lib/cosmos/models/microservice_status_model.rb +1 -1
  34. data/lib/cosmos/models/model.rb +16 -16
  35. data/lib/cosmos/models/note_model.rb +124 -0
  36. data/lib/cosmos/models/ping_model.rb +2 -1
  37. data/lib/cosmos/models/plugin_model.rb +1 -1
  38. data/lib/cosmos/models/process_status_model.rb +1 -1
  39. data/lib/cosmos/models/scope_model.rb +9 -26
  40. data/lib/cosmos/models/settings_model.rb +55 -0
  41. data/lib/cosmos/models/sorted_model.rb +165 -0
  42. data/lib/cosmos/models/target_model.rb +120 -13
  43. data/lib/cosmos/models/tool_config_model.rb +38 -0
  44. data/lib/cosmos/models/tool_model.rb +1 -1
  45. data/lib/cosmos/models/widget_model.rb +1 -1
  46. data/lib/cosmos/operators/microservice_operator.rb +2 -1
  47. data/lib/cosmos/packets/packet.rb +23 -0
  48. data/lib/cosmos/packets/packet_config.rb +2 -2
  49. data/lib/cosmos/packets/packet_item.rb +57 -0
  50. data/lib/cosmos/packets/packet_item_limits.rb +14 -2
  51. data/lib/cosmos/packets/parsers/packet_item_parser.rb +1 -1
  52. data/lib/cosmos/packets/parsers/packet_parser.rb +1 -1
  53. data/lib/cosmos/packets/parsers/xtce_parser.rb +1 -1
  54. data/lib/cosmos/packets/structure_item.rb +10 -1
  55. data/lib/cosmos/script/api_shared.rb +30 -25
  56. data/lib/cosmos/script/calendar.rb +26 -15
  57. data/lib/cosmos/script/commands.rb +5 -7
  58. data/lib/cosmos/script/script.rb +19 -39
  59. data/lib/cosmos/script/storage.rb +92 -105
  60. data/lib/cosmos/system/system.rb +2 -1
  61. data/lib/cosmos/tools/table_manager/table_item.rb +1 -1
  62. data/lib/cosmos/top_level.rb +5 -1
  63. data/lib/cosmos/topics/autonomic_topic.rb +2 -2
  64. data/lib/cosmos/topics/calendar_topic.rb +1 -1
  65. data/lib/cosmos/topics/command_decom_topic.rb +35 -1
  66. data/lib/cosmos/topics/command_topic.rb +6 -4
  67. data/lib/cosmos/topics/interface_topic.rb +8 -8
  68. data/lib/cosmos/topics/limits_event_topic.rb +5 -3
  69. data/lib/cosmos/topics/notifications_topic.rb +1 -1
  70. data/lib/cosmos/topics/router_topic.rb +9 -9
  71. data/lib/cosmos/topics/telemetry_decom_topic.rb +5 -1
  72. data/lib/cosmos/topics/telemetry_topic.rb +1 -1
  73. data/lib/cosmos/topics/timeline_topic.rb +1 -1
  74. data/lib/cosmos/topics/topic.rb +23 -8
  75. data/lib/cosmos/utilities/logger.rb +4 -3
  76. data/lib/cosmos/utilities/metric.rb +32 -26
  77. data/lib/cosmos/utilities/s3.rb +61 -0
  78. data/lib/cosmos/utilities/s3_file_cache.rb +12 -6
  79. data/lib/cosmos/utilities/store.rb +1 -0
  80. data/lib/cosmos/utilities/store_autoload.rb +25 -134
  81. data/lib/cosmos/version.rb +6 -5
  82. data/templates/plugin-template/plugin.gemspec +0 -2
  83. metadata +9 -6
  84. data/lib/cosmos/models/narrative_model.rb +0 -280
@@ -31,17 +31,17 @@ module Cosmos
31
31
  received_count: packet.received_count,
32
32
  stored: packet.stored,
33
33
  buffer: packet.buffer(false) }
34
- Store.write_topic(topic, msg_hash)
34
+ Topic.write_topic(topic, msg_hash)
35
35
  end
36
36
 
37
37
  # @param command [Hash] Command hash structure read to be written to a topic
38
38
  def self.send_command(command, scope:)
39
39
  ack_topic = "{#{scope}__ACKCMD}TARGET__#{command['target_name']}"
40
- Store.update_topic_offsets([ack_topic])
40
+ Topic.update_topic_offsets([ack_topic])
41
41
  # Save the existing cmd_params Hash and JSON generate before writing to the topic
42
42
  cmd_params = command['cmd_params']
43
43
  command['cmd_params'] = JSON.generate(command['cmd_params'].as_json)
44
- cmd_id = Store.write_topic("{#{scope}__CMD}TARGET__#{command['target_name']}", command, '*', 100)
44
+ cmd_id = Topic.write_topic("{#{scope}__CMD}TARGET__#{command['target_name']}", command, '*', 100)
45
45
  # TODO: This timeout is fine for most but can we get the write_timeout from the interface here?
46
46
  time = Time.now
47
47
  while (Time.now - time) < COMMAND_ACK_TIMEOUT_S
@@ -66,7 +66,7 @@ module Cosmos
66
66
  ###########################################################################
67
67
 
68
68
  def self.raise_hazardous_error(msg_hash, target_name, cmd_name, cmd_params)
69
- _, description, _ = msg_hash["result"].split("\n")
69
+ _, description, formatted = msg_hash["result"].split("\n")
70
70
  # Create and populate a new HazardousError and raise it up
71
71
  # The _cmd method in script/commands.rb rescues this and calls prompt_for_hazardous
72
72
  error = HazardousError.new
@@ -74,6 +74,8 @@ module Cosmos
74
74
  error.cmd_name = cmd_name
75
75
  error.cmd_params = cmd_params
76
76
  error.hazardous_description = description
77
+ error.formatted = formatted
78
+
77
79
  # No Logger.info because the error is already logged by the Logger.info "Ack Received ...
78
80
  raise error
79
81
  end
@@ -34,38 +34,38 @@ module Cosmos
34
34
 
35
35
  def self.receive_commands(interface, scope:)
36
36
  while true
37
- Store.read_topics(InterfaceTopic.topics(interface, scope: scope)) do |topic, msg_id, msg_hash, redis|
37
+ Topic.read_topics(InterfaceTopic.topics(interface, scope: scope)) do |topic, msg_id, msg_hash, redis|
38
38
  result = yield topic, msg_hash
39
39
  ack_topic = topic.split("__")
40
40
  ack_topic[1] = 'ACK' + ack_topic[1]
41
41
  ack_topic = ack_topic.join("__")
42
- Store.write_topic(ack_topic, { 'result' => result, 'id' => msg_id }, '*', 100)
42
+ Topic.write_topic(ack_topic, { 'result' => result, 'id' => msg_id }, '*', 100)
43
43
  end
44
44
  end
45
45
  end
46
46
 
47
47
  def self.write_raw(interface_name, data, scope:)
48
- Store.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'raw' => data }, '*', 100)
48
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'raw' => data }, '*', 100)
49
49
  end
50
50
 
51
51
  def self.connect_interface(interface_name, scope:)
52
- Store.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'connect' => 'true' }, '*', 100)
52
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'connect' => 'true' }, '*', 100)
53
53
  end
54
54
 
55
55
  def self.disconnect_interface(interface_name, scope:)
56
- Store.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'disconnect' => 'true' }, '*', 100)
56
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'disconnect' => 'true' }, '*', 100)
57
57
  end
58
58
 
59
59
  def self.start_raw_logging(interface_name, scope:)
60
- Store.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'log_raw' => 'true' }, '*', 100)
60
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'log_raw' => 'true' }, '*', 100)
61
61
  end
62
62
 
63
63
  def self.stop_raw_logging(interface_name, scope:)
64
- Store.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'log_raw' => 'false' }, '*', 100)
64
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface_name}", { 'log_raw' => 'false' }, '*', 100)
65
65
  end
66
66
 
67
67
  def self.shutdown(interface, scope:)
68
- Store.write_topic("{#{scope}__CMD}INTERFACE__#{interface.name}", { 'shutdown' => 'true' }, '*', 100)
68
+ Topic.write_topic("{#{scope}__CMD}INTERFACE__#{interface.name}", { 'shutdown' => 'true' }, '*', 100)
69
69
  sleep 1 # Give some time for the interface to shutdown
70
70
  InterfaceTopic.clear_topics(InterfaceTopic.topics(interface, scope: scope))
71
71
  end
@@ -52,13 +52,13 @@ module Cosmos
52
52
  raise "Invalid limits event type '#{event[:type]}'"
53
53
  end
54
54
 
55
- Store.write_topic("#{scope}__cosmos_limits_events", event, '*', 1000)
55
+ Topic.write_topic("#{scope}__cosmos_limits_events", event, '*', 1000)
56
56
  end
57
57
 
58
58
  def self.read(offset = nil, count: 100, scope:)
59
59
  topic = "#{scope}__cosmos_limits_events"
60
60
  if offset
61
- result = Store.xread(topic, offset, count: count)
61
+ result = Topic.read_topics([topic], [offset], nil, count)
62
62
  if result.empty?
63
63
  [] # We want to return an empty array rather than an empty hash
64
64
  else
@@ -67,7 +67,9 @@ module Cosmos
67
67
  result[topic]
68
68
  end
69
69
  else
70
- Store.xrevrange(topic, count: 1)
70
+ result = Topic.get_newest_message(topic)
71
+ return [result] if result
72
+ return []
71
73
  end
72
74
  end
73
75
 
@@ -22,7 +22,7 @@ require 'cosmos/topics/topic'
22
22
  module Cosmos
23
23
  class NotificationsTopic < Topic
24
24
  def self.write_notification(notification, scope:)
25
- Store.write_topic("#{scope}__cosmos_notifications", notification)
25
+ Topic.write_topic("#{scope}__cosmos_notifications", notification)
26
26
  end
27
27
  end
28
28
  end
@@ -36,13 +36,13 @@ module Cosmos
36
36
 
37
37
  def self.receive_telemetry(router, scope:)
38
38
  while true
39
- Store.read_topics(RouterTopic.topics(router, scope: scope)) do |topic, msg_id, msg_hash, redis|
39
+ Topic.read_topics(RouterTopic.topics(router, scope: scope)) do |topic, msg_id, msg_hash, redis|
40
40
  result = yield topic, msg_hash
41
41
  if /CMD}ROUTER/.match?(topic)
42
42
  ack_topic = topic.split("__")
43
43
  ack_topic[1] = 'ACK' + ack_topic[1]
44
44
  ack_topic = ack_topic.join("__")
45
- Store.write_topic(ack_topic, { 'result' => result }, msg_id, 100)
45
+ Topic.write_topic(ack_topic, { 'result' => result }, msg_id, 100)
46
46
  end
47
47
  end
48
48
  end
@@ -51,33 +51,33 @@ module Cosmos
51
51
  def self.route_command(packet, target_names, scope:)
52
52
  if packet.identified?
53
53
  topic = "{#{scope}__CMD}TARGET__#{packet.target_name}"
54
- Store.write_topic(topic, { 'target_name' => packet.target_name, 'cmd_name' => packet.packet_name, 'cmd_buffer' => packet.buffer(false) }, '*', 100)
54
+ Topic.write_topic(topic, { 'target_name' => packet.target_name, 'cmd_name' => packet.packet_name, 'cmd_buffer' => packet.buffer(false) }, '*', 100)
55
55
  elsif target_names.length == 1
56
56
  topic = "{#{scope}__CMD}TARGET__#{target_names[0]}"
57
- Store.write_topic(topic, { 'target_name' => packet.target_name, 'cmd_name' => 'UNKNOWN', 'cmd_buffer' => packet.buffer(false) }, '*', 100)
57
+ Topic.write_topic(topic, { 'target_name' => packet.target_name, 'cmd_name' => 'UNKNOWN', 'cmd_buffer' => packet.buffer(false) }, '*', 100)
58
58
  else
59
59
  raise "No route for command: #{packet.target_name} #{packet.packet_name}"
60
60
  end
61
61
  end
62
62
 
63
63
  def self.connect_router(router_name, scope:)
64
- Store.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'connect' => true }, '*', 100)
64
+ Topic.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'connect' => true }, '*', 100)
65
65
  end
66
66
 
67
67
  def self.disconnect_router(router_name, scope:)
68
- Store.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'disconnect' => true }, '*', 100)
68
+ Topic.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'disconnect' => true }, '*', 100)
69
69
  end
70
70
 
71
71
  def self.start_raw_logging(router_name, scope:)
72
- Store.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'log_raw' => 'true' }, '*', 100)
72
+ Topic.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'log_raw' => 'true' }, '*', 100)
73
73
  end
74
74
 
75
75
  def self.stop_raw_logging(router_name, scope:)
76
- Store.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'log_raw' => 'false' }, '*', 100)
76
+ Topic.write_topic("{#{scope}__CMD}ROUTER__#{router_name}", { 'log_raw' => 'false' }, '*', 100)
77
77
  end
78
78
 
79
79
  def self.shutdown(router, scope:)
80
- Store.write_topic("{#{scope}__CMD}ROUTER__#{router.name}", { 'shutdown' => 'true' }, '*', 100)
80
+ Topic.write_topic("{#{scope}__CMD}ROUTER__#{router.name}", { 'shutdown' => 'true' }, '*', 100)
81
81
  sleep 1 # Give some time for the interface to shutdown
82
82
  RouterTopic.clear_topics(RouterTopic.topics(router, scope: scope))
83
83
  end
@@ -21,6 +21,10 @@ require 'cosmos/topics/topic'
21
21
 
22
22
  module Cosmos
23
23
  class TelemetryDecomTopic < Topic
24
+ def self.topics(scope:)
25
+ super(scope, 'DECOM')
26
+ end
27
+
24
28
  def self.write_packet(packet, id: nil, scope:)
25
29
  # Need to build a JSON hash of the decommutated data
26
30
  # Support "downward typing"
@@ -39,7 +43,7 @@ module Cosmos
39
43
  :received_count => packet.received_count,
40
44
  :json_data => JSON.generate(json_hash.as_json),
41
45
  }
42
- Store.write_topic("#{scope}__DECOM__{#{packet.target_name}}__#{packet.packet_name}", msg_hash, id)
46
+ Topic.write_topic("#{scope}__DECOM__{#{packet.target_name}}__#{packet.packet_name}", msg_hash, id)
43
47
  # Also update the current value table with the latest decommutated data
44
48
  CvtModel.set(json_hash, target_name: packet.target_name, packet_name: packet.packet_name, scope: scope)
45
49
  end
@@ -30,7 +30,7 @@ module Cosmos
30
30
  :received_count => packet.received_count,
31
31
  :buffer => packet.buffer(false),
32
32
  }
33
- Store.write_topic("#{scope}__TELEMETRY__{#{packet.target_name}}__#{packet.packet_name}", msg_hash)
33
+ Topic.write_topic("#{scope}__TELEMETRY__{#{packet.target_name}}__#{packet.packet_name}", msg_hash)
34
34
  end
35
35
  end
36
36
  end
@@ -39,7 +39,7 @@ module Cosmos
39
39
  # }
40
40
  # ```
41
41
  def self.write_activity(activity, scope:)
42
- Store.write_topic("#{scope}#{PRIMARY_KEY}", activity, '*', 1000)
42
+ Topic.write_topic("#{scope}#{PRIMARY_KEY}", activity, '*', 1000)
43
43
  end
44
44
  end
45
45
  end
@@ -21,18 +21,33 @@ require 'cosmos/utilities/store'
21
21
 
22
22
  module Cosmos
23
23
  class Topic
24
- def self.initialize_streams(topics)
25
- Store.initialize_streams(topics)
24
+ if RUBY_VERSION < "3"
25
+ # Delegate all unknown class methods to delegate to the EphemeralStore
26
+ def self.method_missing(message, *args, &block)
27
+ EphemeralStore.public_send(message, *args, &block)
28
+ end
29
+ else
30
+ # Delegate all unknown class methods to delegate to the EphemeralStore
31
+ def self.method_missing(message, *args, **kwargs, &block)
32
+ EphemeralStore.public_send(message, *args, **kwargs, &block)
33
+ end
26
34
  end
27
35
 
28
- def self.read_topics(topics, offsets = nil, timeout_ms = 1000, &block)
29
- Store.read_topics(topics, offsets, timeout_ms, &block)
36
+ def self.clear_topics(topics, maxlen = 0)
37
+ topics.each { |topic| EphemeralStore.xtrim(topic, maxlen) }
30
38
  end
31
39
 
32
- def self.clear_topics(topics, maxlen = 0)
33
- topics.each do |topic|
34
- Store.xtrim(topic, maxlen)
35
- end
40
+ def self.topics(scope, key)
41
+ EphemeralStore
42
+ .scan_each(match: "#{scope}__#{key}__*", type: 'stream', count: 100)
43
+ .to_a # Change the enumerator into an array
44
+ .uniq # Scan can return duplicates so ensure unique
45
+ .sort # Sort not entirely necessary but nice
46
+ end
47
+
48
+ def self.get_cnt(topic)
49
+ _, packet = EphemeralStore.get_newest_message(topic)
50
+ packet ? packet["received_count"].to_i : 0
36
51
  end
37
52
  end
38
53
  end
@@ -19,10 +19,11 @@
19
19
 
20
20
  require 'cosmos/core_ext/class'
21
21
  require 'cosmos/core_ext/time'
22
- require 'cosmos/utilities/store'
22
+ require 'cosmos/topics/topic'
23
23
  require 'socket'
24
24
  require 'logger'
25
25
  require 'time'
26
+ require 'json'
26
27
 
27
28
  module Cosmos
28
29
  # Supports different levels of logging and only writes if the level
@@ -173,11 +174,11 @@ module Cosmos
173
174
  puts data.to_json if @stdout
174
175
  unless @no_store
175
176
  if scope
176
- Store.write_topic("#{scope}__cosmos_log_messages", data)
177
+ Topic.write_topic("#{scope}__cosmos_log_messages", data)
177
178
  else
178
179
  # The base cosmos_log_messages doesn't have an associated logger
179
180
  # so it must be limited to prevent unbounded stream growth
180
- Store.write_topic("cosmos_log_messages", data, '*', 1000)
181
+ Topic.write_topic("cosmos_log_messages", data, '*', 1000)
181
182
  end
182
183
  end
183
184
  end
@@ -18,6 +18,7 @@
18
18
  # copyright holder
19
19
 
20
20
  require 'cosmos/models/metric_model'
21
+ require 'thread'
21
22
 
22
23
  module Cosmos
23
24
  class Metric
@@ -48,6 +49,7 @@ module Cosmos
48
49
  @scope = scope
49
50
  @microservice = microservice
50
51
  @size = 5000
52
+ @mutex = Mutex.new
51
53
  end
52
54
 
53
55
  def add_sample(name:, value:, labels:)
@@ -69,15 +71,17 @@ module Cosmos
69
71
  # the value is added to @items and the count of the value is increased
70
72
  # if the count of the values exceed the size of the array it sets the
71
73
  # count back to zero and the array will over write older data.
72
- key = "#{name}|" + labels.map { |k, v| "#{k}=#{v}" }.join(',')
73
- if not @items.has_key?(key)
74
- Logger.debug("new data for #{@scope}, #{key}")
75
- @items[key] = { 'values' => Array.new(@size), 'count' => 0 }
74
+ @mutex.synchronize do
75
+ key = "#{name}|" + labels.map { |k, v| "#{k}=#{v}" }.join(',')
76
+ if not @items.has_key?(key)
77
+ Logger.debug("new data for #{@scope}, #{key}")
78
+ @items[key] = { 'values' => Array.new(@size), 'count' => 0 }
79
+ end
80
+ count = @items[key]['count']
81
+ # Logger.info("adding data for #{@scope}, #{count} #{key}, #{value}")
82
+ @items[key]['values'][count] = value
83
+ @items[key]['count'] = count + 1 >= @size ? 0 : count + 1
76
84
  end
77
- count = @items[key]['count']
78
- # Logger.info("adding data for #{@scope}, #{count} #{key}, #{value}")
79
- @items[key]['values'][count] = value
80
- @items[key]['count'] = count + 1 >= @size ? 0 : count + 1
81
85
  end
82
86
 
83
87
  def percentile(sorted_values, percentile)
@@ -106,24 +110,26 @@ module Cosmos
106
110
  # array. to store the array as the value with the metric name again joined
107
111
  # with the @microservice and @scope.
108
112
  Logger.debug("#{@microservice} #{@scope} sending metrics to redis, #{@items.length}") if @items.length > 0
109
- @items.each do |key, values|
110
- label_list = []
111
- name, labels = key.split('|')
112
- metric_labels = labels.nil? ? {} : labels.split(',').map { |x| x.split('=') }.map { |k, v| { k => v } }.reduce({}, :merge)
113
- sorted_values = values['values'].compact.sort
114
- for percentile_value in [10, 50, 90, 95, 99]
115
- percentile_result = percentile(sorted_values, percentile_value)
116
- labels = metric_labels.clone.merge({ 'scope' => @scope, 'microservice' => @microservice })
117
- labels['percentile'] = percentile_value
118
- labels['metric__value'] = percentile_result
119
- label_list.append(labels)
120
- end
121
- begin
122
- Logger.debug("sending metrics summary to redis key: #{@microservice}")
123
- metric = MetricModel.new(name: @microservice, scope: @scope, metric_name: name, label_list: label_list)
124
- metric.create(force: true)
125
- rescue RuntimeError
126
- Logger.error("failed attempt to update metric, #{key}, #{name} #{@scope}")
113
+ @mutex.synchronize do
114
+ @items.each do |key, values|
115
+ label_list = []
116
+ name, labels = key.split('|')
117
+ metric_labels = labels.nil? ? {} : labels.split(',').map { |x| x.split('=') }.map { |k, v| { k => v } }.reduce({}, :merge)
118
+ sorted_values = values['values'].compact.sort
119
+ for percentile_value in [10, 50, 90, 95, 99]
120
+ percentile_result = percentile(sorted_values, percentile_value)
121
+ labels = metric_labels.clone.merge({ 'scope' => @scope, 'microservice' => @microservice })
122
+ labels['percentile'] = percentile_value
123
+ labels['metric__value'] = percentile_result
124
+ label_list.append(labels)
125
+ end
126
+ begin
127
+ Logger.debug("sending metrics summary to redis key: #{@microservice}")
128
+ metric = MetricModel.new(name: @microservice, scope: @scope, metric_name: name, label_list: label_list)
129
+ metric.create(force: true)
130
+ rescue RuntimeError
131
+ Logger.error("failed attempt to update metric, #{key}, #{name} #{@scope}")
132
+ end
127
133
  end
128
134
  end
129
135
  end
@@ -22,6 +22,67 @@ require 'cosmos/models/reducer_model'
22
22
 
23
23
  module Cosmos
24
24
  class S3Utilities
25
+ def self.list_files_before_time(bucket, prefix, time)
26
+ rubys3_client = Aws::S3::Client.new
27
+ oldest_list = []
28
+ total_size = 0
29
+
30
+ # Return nothing if bucket doesn't exist (it won't at the very beginning)
31
+ begin
32
+ rubys3_client.head_bucket(bucket: bucket)
33
+ rescue Aws::S3::Errors::NotFound
34
+ return total_size, oldest_list
35
+ end
36
+
37
+ # Get List of Packet Names - Assumes prefix gets us to a folder of packet names
38
+ token = nil
39
+ folder_list = []
40
+ while true
41
+ resp = rubys3_client.list_objects_v2({
42
+ bucket: bucket,
43
+ max_keys: 1000,
44
+ prefix: prefix,
45
+ delimiter: '/',
46
+ continuation_token: token
47
+ })
48
+
49
+ resp.common_prefixes.each do |item|
50
+ folder_list << item.prefix
51
+ end
52
+ break unless resp.is_truncated
53
+ token = resp.next_continuation_token
54
+ end
55
+
56
+ # Go through each folder and keep files that end before time
57
+ folder_list.each do |folder|
58
+ token = nil
59
+ next_folder = false
60
+ while true
61
+ resp = rubys3_client.list_objects_v2({
62
+ bucket: bucket,
63
+ max_keys: 1000,
64
+ prefix: folder,
65
+ continuation_token: token
66
+ })
67
+ resp.contents.each do |item|
68
+ t = item.key.split('__')[1]
69
+ file_end_time = Time.utc(t[0..3], t[4..5], t[6..7], t[8..9], t[10..11], t[12..13])
70
+ if file_end_time < time
71
+ oldest_list << item
72
+ total_size += item.size
73
+ else
74
+ next_folder = true
75
+ break
76
+ end
77
+ end
78
+ break if !resp.is_truncated or next_folder
79
+
80
+ token = resp.next_continuation_token
81
+ end
82
+ end
83
+ return total_size, oldest_list
84
+ end
85
+
25
86
  def self.get_total_size_and_oldest_list(bucket, prefix, max_list_length = 10000)
26
87
  rubys3_client = Aws::S3::Client.new
27
88
  oldest_list = []
@@ -18,6 +18,7 @@
18
18
  # copyright holder
19
19
 
20
20
  require 'fileutils'
21
+ require 'tmpdir'
21
22
  require 'cosmos'
22
23
  require 'cosmos/utilities/s3'
23
24
 
@@ -48,7 +49,7 @@ class S3File
48
49
 
49
50
  def retrieve
50
51
  local_path = "#{S3FileCache.instance.cache_dir}/#{File.basename(@s3_path)}"
51
- Cosmos::Logger.info "Retrieving #{@s3_path} from logs bucket"
52
+ Cosmos::Logger.debug "Retrieving #{@s3_path} from logs bucket"
52
53
  @rubys3_client.get_object(bucket: "logs", key: @s3_path, response_target: local_path)
53
54
  if File.exist?(local_path)
54
55
  @size = File.size(local_path)
@@ -57,6 +58,7 @@ class S3File
57
58
  rescue => err
58
59
  @error = err
59
60
  Cosmos::Logger.error "Failed to retrieve #{@s3_path}\n#{err.formatted}"
61
+ raise err
60
62
  end
61
63
 
62
64
  def reserve
@@ -165,11 +167,11 @@ class S3FileCache
165
167
  end
166
168
 
167
169
  # Create local file cache location
168
- @cache_dir = File.join(Dir.tmpdir, 'cosmos', 'file_cache', name)
170
+ @cache_dir = Dir.mktmpdir
169
171
  FileUtils.mkdir_p(@cache_dir)
170
-
171
- # Clear out local file cache
172
- FileUtils.rm_f Dir.glob("#{@cache_dir}/*")
172
+ at_exit do
173
+ FileUtils.remove_dir(@cache_dir, true)
174
+ end
173
175
 
174
176
  @cached_files = S3FileCollection.new
175
177
 
@@ -178,7 +180,11 @@ class S3FileCache
178
180
  file = @cached_files.get_next_to_retrieve
179
181
  # Cosmos::Logger.debug "Next file: #{file}"
180
182
  if file and (file.size + @cached_files.current_disk_usage()) <= @max_disk_usage
181
- file.retrieve
183
+ begin
184
+ file.retrieve
185
+ rescue
186
+ # Will be automatically retried
187
+ end
182
188
  else
183
189
  sleep(1)
184
190
  end
@@ -19,4 +19,5 @@
19
19
 
20
20
  module Cosmos
21
21
  autoload(:Store, "cosmos/utilities/store_autoload.rb")
22
+ autoload(:EphemeralStore, "cosmos/utilities/store_autoload.rb")
22
23
  end