logstash-core 2.4.1-java → 5.0.0.alpha1-java

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of logstash-core might be problematic. Click here for more details.

Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/lib/logstash-core/version.rb +1 -1
  3. data/lib/logstash/agent.rb +124 -411
  4. data/lib/logstash/api/init.ru +31 -0
  5. data/lib/logstash/api/lib/app.rb +40 -0
  6. data/lib/logstash/api/lib/app/command.rb +29 -0
  7. data/lib/logstash/api/lib/app/command_factory.rb +29 -0
  8. data/lib/logstash/api/lib/app/commands/stats/events_command.rb +13 -0
  9. data/lib/logstash/api/lib/app/commands/stats/hotthreads_command.rb +120 -0
  10. data/lib/logstash/api/lib/app/commands/stats/memory_command.rb +25 -0
  11. data/lib/logstash/api/lib/app/commands/system/basicinfo_command.rb +15 -0
  12. data/lib/logstash/api/lib/app/commands/system/plugins_command.rb +28 -0
  13. data/lib/logstash/api/lib/app/modules/node.rb +25 -0
  14. data/lib/logstash/api/lib/app/modules/node_stats.rb +51 -0
  15. data/lib/logstash/api/lib/app/modules/plugins.rb +15 -0
  16. data/lib/logstash/api/lib/app/modules/stats.rb +21 -0
  17. data/lib/logstash/api/lib/app/root.rb +13 -0
  18. data/lib/logstash/api/lib/app/service.rb +61 -0
  19. data/lib/logstash/api/lib/app/stats.rb +56 -0
  20. data/lib/logstash/api/lib/helpers/app_helpers.rb +23 -0
  21. data/lib/logstash/codecs/base.rb +1 -29
  22. data/lib/logstash/config/config_ast.rb +18 -31
  23. data/lib/logstash/config/loader.rb +3 -5
  24. data/lib/logstash/config/mixin.rb +25 -64
  25. data/lib/logstash/filter_delegator.rb +65 -0
  26. data/lib/logstash/inputs/base.rb +1 -1
  27. data/lib/logstash/inputs/metrics.rb +47 -0
  28. data/lib/logstash/instrument/collector.rb +109 -0
  29. data/lib/logstash/instrument/metric.rb +102 -0
  30. data/lib/logstash/instrument/metric_store.rb +228 -0
  31. data/lib/logstash/instrument/metric_type.rb +24 -0
  32. data/lib/logstash/instrument/metric_type/base.rb +35 -0
  33. data/lib/logstash/instrument/metric_type/counter.rb +29 -0
  34. data/lib/logstash/instrument/metric_type/gauge.rb +22 -0
  35. data/lib/logstash/instrument/metric_type/mean.rb +33 -0
  36. data/lib/logstash/instrument/namespaced_metric.rb +54 -0
  37. data/lib/logstash/instrument/null_metric.rb +4 -3
  38. data/lib/logstash/instrument/periodic_poller/base.rb +57 -0
  39. data/lib/logstash/instrument/periodic_poller/jvm.rb +92 -0
  40. data/lib/logstash/instrument/periodic_poller/os.rb +13 -0
  41. data/lib/logstash/instrument/periodic_poller/periodic_poller_observer.rb +19 -0
  42. data/lib/logstash/instrument/periodic_pollers.rb +26 -0
  43. data/lib/logstash/instrument/snapshot.rb +16 -0
  44. data/lib/logstash/json.rb +2 -3
  45. data/lib/logstash/namespace.rb +1 -0
  46. data/lib/logstash/output_delegator.rb +16 -3
  47. data/lib/logstash/outputs/base.rb +1 -32
  48. data/lib/logstash/pipeline.rb +67 -8
  49. data/lib/logstash/plugin.rb +57 -19
  50. data/lib/logstash/runner.rb +348 -84
  51. data/lib/logstash/util.rb +9 -0
  52. data/lib/logstash/util/duration_formatter.rb +15 -0
  53. data/lib/logstash/util/java_version.rb +2 -4
  54. data/lib/logstash/util/loggable.rb +29 -0
  55. data/lib/logstash/version.rb +1 -1
  56. data/lib/logstash/webserver.rb +98 -0
  57. data/locales/en.yml +42 -24
  58. data/logstash-core.gemspec +9 -6
  59. data/spec/api/lib/api/node_spec.rb +64 -0
  60. data/spec/api/lib/api/node_stats_spec.rb +68 -0
  61. data/spec/api/lib/api/plugins_spec.rb +57 -0
  62. data/spec/api/lib/api/root_spec.rb +20 -0
  63. data/spec/api/lib/api/stats_spec.rb +19 -0
  64. data/spec/api/lib/commands/events_spec.rb +17 -0
  65. data/spec/api/lib/commands/jvm_spec.rb +45 -0
  66. data/spec/api/spec_helper.rb +128 -0
  67. data/spec/logstash/agent_spec.rb +62 -169
  68. data/spec/logstash/config/config_ast_spec.rb +2 -47
  69. data/spec/logstash/config/mixin_spec.rb +0 -157
  70. data/spec/logstash/filter_delegator_spec.rb +143 -0
  71. data/spec/logstash/inputs/metrics_spec.rb +52 -0
  72. data/spec/logstash/instrument/collector_spec.rb +49 -0
  73. data/spec/logstash/instrument/metric_spec.rb +110 -0
  74. data/spec/logstash/instrument/metric_store_spec.rb +163 -0
  75. data/spec/logstash/instrument/metric_type/counter_spec.rb +40 -0
  76. data/spec/logstash/instrument/metric_type/gauge_spec.rb +40 -0
  77. data/spec/logstash/instrument/namespaced_metric_spec.rb +25 -0
  78. data/spec/logstash/instrument/null_metric_spec.rb +9 -51
  79. data/spec/logstash/json_spec.rb +14 -0
  80. data/spec/logstash/output_delegator_spec.rb +6 -3
  81. data/spec/logstash/outputs/base_spec.rb +0 -107
  82. data/spec/logstash/pipeline_spec.rb +204 -33
  83. data/spec/logstash/plugin_spec.rb +80 -15
  84. data/spec/logstash/runner_spec.rb +134 -38
  85. data/spec/logstash/shutdown_watcher_spec.rb +0 -1
  86. data/spec/logstash/util/duration_formatter_spec.rb +11 -0
  87. data/spec/logstash/util/java_version_spec.rb +10 -2
  88. data/spec/logstash/util_spec.rb +28 -0
  89. data/spec/support/matchers.rb +30 -0
  90. metadata +154 -20
  91. data/lib/logstash/logging/json.rb +0 -21
  92. data/lib/logstash/special_agent.rb +0 -8
  93. data/lib/logstash/util/safe_uri.rb +0 -50
  94. data/spec/logstash/codecs/base_spec.rb +0 -74
  95. data/spec/static/i18n_spec.rb +0 -25
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ #
3
+ module LogStash
4
+ class FilterDelegator
5
+ extend Forwardable
6
+ DELEGATED_METHODS = [
7
+ :register,
8
+ :close,
9
+ :threadsafe?,
10
+ :do_close,
11
+ :do_stop,
12
+ :periodic_flush
13
+ ]
14
+ def_delegators :@filter, *DELEGATED_METHODS
15
+
16
+ def initialize(logger, klass, metric, *args)
17
+ options = args.reduce({}, :merge)
18
+
19
+ @logger = logger
20
+ @klass = klass
21
+ @filter = klass.new(options)
22
+
23
+ # Scope the metrics to the plugin
24
+ namespaced_metric = metric.namespace(@filter.plugin_unique_name.to_sym)
25
+ @filter.metric = metric
26
+
27
+ @metric_events = namespaced_metric.namespace(:events)
28
+
29
+ # Not all the filters will do bufferings
30
+ define_flush_method if @filter.respond_to?(:flush)
31
+ end
32
+
33
+ def config_name
34
+ @klass.config_name
35
+ end
36
+
37
+ def multi_filter(events)
38
+ @metric_events.increment(:in, events.size)
39
+
40
+ new_events = @filter.multi_filter(events)
41
+
42
+ # There is no garantee in the context of filter
43
+ # that EVENTS_INT == EVENTS_OUT, see the aggregates and
44
+ # the split filter
45
+ c = new_events.count { |event| !event.cancelled? }
46
+ @metric_events.increment(:out, c) if c > 0
47
+
48
+ return new_events
49
+ end
50
+
51
+ private
52
+ def define_flush_method
53
+ define_singleton_method(:flush) do |options = {}|
54
+ # we also need to trace the number of events
55
+ # coming from a specific filters.
56
+ new_events = @filter.flush(options)
57
+
58
+ # Filter plugins that does buffering or spooling of events like the
59
+ # `Logstash-filter-aggregates` can return `NIL` and will flush on the next flush ticks.
60
+ @metric_events.increment(:out, new_events.size) if new_events && new_events.size > 0
61
+ new_events
62
+ end
63
+ end
64
+ end
65
+ end
@@ -78,7 +78,7 @@ class LogStash::Inputs::Base < LogStash::Plugin
78
78
 
79
79
  public
80
80
  def do_stop
81
- @logger.debug("stopping", :plugin => self.class.name)
81
+ @logger.debug("stopping", :plugin => self)
82
82
  @stop_called.make_true
83
83
  stop
84
84
  end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ require "logstash/event"
3
+ require "logstash/inputs/base"
4
+ require "logstash/instrument/collector"
5
+
6
+ module LogStash module Inputs
7
+ # The Metrics inputs is responable of registring itself to the collector.
8
+ # The collector class will periodically emits new snapshot of the system,
9
+ # The metrics need to take that information and transform it into
10
+ # a `Logstash::Event`, which can be consumed by the shipper and send to
11
+ # Elasticsearch
12
+ class Metrics < LogStash::Inputs::Base
13
+ config_name "metrics"
14
+ milestone 3
15
+
16
+ def register
17
+ end
18
+
19
+ def run(queue)
20
+ @logger.debug("Metric: input started")
21
+ @queue = queue
22
+
23
+ # we register to the collector after receiving the pipeline queue
24
+ LogStash::Instrument::Collector.instance.add_observer(self)
25
+
26
+ # Keep this plugin thread alive,
27
+ # until we shutdown the metric pipeline
28
+ sleep(1) while !stop?
29
+ end
30
+
31
+ def stop
32
+ @logger.debug("Metrics input: stopped")
33
+ LogStash::Instrument::Collector.instance.delete_observer(self)
34
+ end
35
+
36
+ def update(snapshot)
37
+ @logger.debug("Metrics input: received a new snapshot", :created_at => snapshot.created_at, :snapshot => snapshot, :event => snapshot.metric_store.to_event) if @logger.debug?
38
+
39
+ # The back pressure is handled in the collector's
40
+ # scheduled task (running into his own thread) if something append to one of the listener it will
41
+ # will timeout. In a sane pipeline, with a low traffic of events it shouldn't be a problems.
42
+ snapshot.metric_store.each do |metric|
43
+ @queue << LogStash::Event.new({ "@timestamp" => snapshot.created_at }.merge(metric.to_hash))
44
+ end
45
+ end
46
+ end
47
+ end;end
@@ -0,0 +1,109 @@
1
+ # encoding: utf-8
2
+ require "logstash/instrument/snapshot"
3
+ require "logstash/instrument/metric_store"
4
+ require "logstash/util/loggable"
5
+ require "concurrent/timer_task"
6
+ require "observer"
7
+ require "singleton"
8
+ require "thread"
9
+
10
+ module LogStash module Instrument
11
+ # The Collector singleton is the single point of reference for all
12
+ # the metrics collection inside logstash, the metrics library will make
13
+ # direct calls to this class.
14
+ #
15
+ # This class is an observable responsable of periodically emitting view of the system
16
+ # to other components like the internal metrics pipelines.
17
+ class Collector
18
+ include LogStash::Util::Loggable
19
+ include Observable
20
+ include Singleton
21
+
22
+ SNAPSHOT_ROTATION_TIME_SECS = 1 # seconds
23
+ SNAPSHOT_ROTATION_TIMEOUT_INTERVAL_SECS = 10 * 60 # seconds
24
+
25
+ attr_accessor :agent
26
+
27
+ def initialize
28
+ @metric_store = MetricStore.new
29
+ @agent = nil
30
+ start_periodic_snapshotting
31
+ end
32
+
33
+ # The metric library will call this unique interface
34
+ # its the job of the collector to update the store with new metric
35
+ # of update the metric
36
+ #
37
+ # If there is a problem with the key or the type of metric we will record an error
38
+ # but we wont stop processing events, theses errors are not considered fatal.
39
+ #
40
+ def push(namespaces_path, key, type, *metric_type_params)
41
+ begin
42
+ metric = @metric_store.fetch_or_store(namespaces_path, key) do
43
+ LogStash::Instrument::MetricType.create(type, namespaces_path, key)
44
+ end
45
+
46
+ metric.execute(*metric_type_params)
47
+
48
+ changed # we had changes coming in so we can notify the observers
49
+ rescue MetricStore::NamespacesExpectedError => e
50
+ logger.error("Collector: Cannot record metric", :exception => e)
51
+ rescue NameError => e
52
+ logger.error("Collector: Cannot create concrete class for this metric type",
53
+ :type => type,
54
+ :namespaces_path => namespaces_path,
55
+ :key => key,
56
+ :metrics_params => metric_type_params,
57
+ :exception => e,
58
+ :stacktrace => e.backtrace)
59
+ end
60
+ end
61
+
62
+ def clear
63
+ @metric_store = MetricStore.new
64
+ end
65
+
66
+ # Monitor the `Concurrent::TimerTask` this update is triggered on every successful or not
67
+ # run of the task, TimerTask implement Observable and the collector acts as
68
+ # the observer and will keep track if something went wrong in the execution.
69
+ #
70
+ # @param [Time] Time of execution
71
+ # @param [result] Result of the execution
72
+ # @param [Exception] Exception
73
+ def update(time_of_execution, result, exception)
74
+ return true if exception.nil?
75
+ logger.error("Collector: Something went wrong went sending data to the observers",
76
+ :execution_time => time_of_execution,
77
+ :result => result,
78
+ :exception => exception)
79
+ end
80
+
81
+ # Snapshot the current Metric Store and return it immediately,
82
+ # This is useful if you want to get access to the current metric store without
83
+ # waiting for a periodic call.
84
+ #
85
+ # @return [LogStash::Instrument::MetricStore]
86
+ def snapshot_metric
87
+ Snapshot.new(@metric_store)
88
+ end
89
+
90
+ # Configure and start the periodic task for snapshotting the `MetricStore`
91
+ def start_periodic_snapshotting
92
+ @snapshot_task = Concurrent::TimerTask.new { publish_snapshot }
93
+ @snapshot_task.execution_interval = SNAPSHOT_ROTATION_TIME_SECS
94
+ @snapshot_task.timeout_interval = SNAPSHOT_ROTATION_TIMEOUT_INTERVAL_SECS
95
+ @snapshot_task.add_observer(self)
96
+ @snapshot_task.execute
97
+ end
98
+
99
+ # Create a snapshot of the MetricStore and send it to to the registered observers
100
+ # The observer will receive the following signature in the update methode.
101
+ #
102
+ # `#update(created_at, metric_store)`
103
+ def publish_snapshot
104
+ created_at = Time.now
105
+ logger.debug("Collector: Sending snapshot to observers", :created_at => created_at) if logger.debug?
106
+ notify_observers(snapshot_metric)
107
+ end
108
+ end
109
+ end; end
@@ -0,0 +1,102 @@
1
+ # encoding: utf-8
2
+ require "logstash/instrument/collector"
3
+ require "concurrent"
4
+
5
+ module LogStash module Instrument
6
+ class MetricException < Exception; end
7
+ class MetricNoKeyProvided < MetricException; end
8
+ class MetricNoBlockProvided < MetricException; end
9
+ class MetricNoNamespaceProvided < MetricException; end
10
+
11
+ # This class provide the interface between the code, the collector and the format
12
+ # of the recorded metric.
13
+ class Metric
14
+ attr_reader :collector
15
+
16
+ def initialize(collector = LogStash::Instrument::Collector.instance)
17
+ @collector = collector
18
+ end
19
+
20
+ def increment(namespace, key, value = 1)
21
+ validate_key!(key)
22
+ collector.push(namespace, key, :counter, :increment, value)
23
+ end
24
+
25
+ def decrement(namespace, key, value = 1)
26
+ validate_key!(key)
27
+ collector.push(namespace, key, :counter, :decrement, value)
28
+ end
29
+
30
+ def gauge(namespace, key, value)
31
+ validate_key!(key)
32
+ collector.push(namespace, key, :gauge, :set, value)
33
+ end
34
+
35
+ def time(namespace, key)
36
+ validate_key!(key)
37
+
38
+ if block_given?
39
+ timer = TimedExecution.new(self, namespace, key)
40
+ content = yield
41
+ timer.stop
42
+ return content
43
+ else
44
+ TimedExecution.new(self, namespace, key)
45
+ end
46
+ end
47
+
48
+ def report_time(namespace, key, duration)
49
+ collector.push(namespace, key, :mean, :increment, duration)
50
+ end
51
+
52
+ # This method return a metric instance tied to a specific namespace
53
+ # so instead of specifying the namespace on every call.
54
+ #
55
+ # Example:
56
+ # metric.increment(:namespace, :mykey, 200)
57
+ # metric.increment(:namespace, :mykey_2, 200)
58
+ #
59
+ # namespaced_metric = metric.namespace(:namespace)
60
+ # namespaced_metric.increment(:mykey, 200)
61
+ # namespaced_metric.increment(:mykey_2, 200)
62
+ # ```
63
+ #
64
+ # @param name [Array<String>] Name of the namespace
65
+ # @param name [String] Name of the namespace
66
+ def namespace(name)
67
+ raise MetricNoNamespaceProvided if name.nil? || name.empty?
68
+
69
+ NamespacedMetric.new(self, name)
70
+ end
71
+
72
+ private
73
+ def validate_key!(key)
74
+ raise MetricNoKeyProvided if key.nil? || key.empty?
75
+ end
76
+
77
+ # Allow to calculate the execution of a block of code.
78
+ # This class support 2 differents syntax a block or the return of
79
+ # the object itself, but in the later case the metric wont be recorded
80
+ # Until we call `#stop`.
81
+ #
82
+ # @see LogStash::Instrument::Metric#time
83
+ class TimedExecution
84
+ MILLISECONDS = 1_000_000.0.freeze
85
+
86
+ def initialize(metric, namespace, key)
87
+ @metric = metric
88
+ @namespace = namespace
89
+ @key = key
90
+ start
91
+ end
92
+
93
+ def start
94
+ @start_time = Time.now
95
+ end
96
+
97
+ def stop
98
+ @metric.report_time(@namespace, @key, (MILLISECONDS * (Time.now - @start_time)).to_i)
99
+ end
100
+ end
101
+ end
102
+ end; end
@@ -0,0 +1,228 @@
1
+ # encoding: utf-8
2
+ require "concurrent"
3
+ require "logstash/event"
4
+ require "logstash/instrument/metric_type"
5
+
6
+ module LogStash module Instrument
7
+ # The Metric store the data structure that make sure the data is
8
+ # saved in a retrievable way, this is a wrapper around multiples ConcurrentHashMap
9
+ # acting as a tree like structure.
10
+ class MetricStore
11
+ class NamespacesExpectedError < StandardError; end
12
+ class MetricNotFound < StandardError; end
13
+
14
+ KEY_PATH_SEPARATOR = "/".freeze
15
+
16
+ # Lets me a bit flexible on the coma usage in the path
17
+ # definition
18
+ FILTER_KEYS_SEPARATOR = /\s?*,\s*/.freeze
19
+
20
+ def initialize
21
+ # We keep the structured cache to allow
22
+ # the api to search the content of the differents nodes
23
+ @store = Concurrent::Map.new
24
+
25
+ # This hash has only one dimension
26
+ # and allow fast retrieval of the metrics
27
+ @fast_lookup = Concurrent::Map.new
28
+ end
29
+
30
+ # This method use the namespace and key to search the corresponding value of
31
+ # the hash, if it doesn't exist it will create the appropriate namespaces
32
+ # path in the hash and return `new_value`
33
+ #
34
+ # @param [Array] The path where the values should be located
35
+ # @param [Symbol] The metric key
36
+ # @return [Object] Return the new_value of the retrieve object in the tree
37
+ def fetch_or_store(namespaces, key, default_value = nil)
38
+ provided_value = block_given? ? yield(key) : default_value
39
+
40
+ # We first check in the `@fast_lookup` store to see if we have already see that metrics before,
41
+ # This give us a `o(1)` access, which is faster than searching through the structured
42
+ # data store (Which is a `o(n)` operation where `n` is the number of element in the namespace and
43
+ # the value of the key). If the metric is already present in the `@fast_lookup`, the call to
44
+ # `#put_if_absent` will return the value. This value is send back directly to the caller.
45
+ #
46
+ # BUT. If the value is not present in the `@fast_lookup` the value will be inserted and
47
+ # `#puf_if_absent` will return nil. With this returned value of nil we assume that we don't
48
+ # have it in the `@metric_store` for structured search so we add it there too.
49
+ #
50
+ # The problem with only using the `@metric_store` directly all the time would require us
51
+ # to use the mutex around the structure since its a multi-level hash, without that it wont
52
+ # return a consistent value of the metric and this would slow down the code and would
53
+ # complixity the code.
54
+ if found_value = @fast_lookup.put_if_absent([namespaces, key], provided_value)
55
+ return found_value
56
+ else
57
+ # If we cannot find the value this mean we need to save it in the store.
58
+ fetch_or_store_namespaces(namespaces).fetch_or_store(key, provided_value)
59
+ return provided_value
60
+ end
61
+ end
62
+
63
+ # This method allow to retrieve values for a specific path,
64
+ # This method support the following queries
65
+ #
66
+ # stats/pipelines/pipeline_X
67
+ # stats/pipelines/pipeline_X,pipeline_2
68
+ # stats/os,jvm
69
+ #
70
+ # If you use the `,` on a key the metric store will return the both values at that level
71
+ #
72
+ # The returned hash will keep the same structure as it had in the `Concurrent::Map`
73
+ # but will be a normal ruby hash. This will allow the api to easily seriliaze the content
74
+ # of the map
75
+ #
76
+ # @param [Array] The path where values should be located
77
+ # @return [Hash]
78
+ def get_with_path(path)
79
+ key_paths = path.gsub(/^#{KEY_PATH_SEPARATOR}+/, "").split(KEY_PATH_SEPARATOR)
80
+ get(*key_paths)
81
+ end
82
+
83
+ # Similar to `get_with_path` but use symbols instead of string
84
+ #
85
+ # @param [Array<Symbol>
86
+ # @return [Hash]
87
+ def get(*key_paths)
88
+ # Normalize the symbols access
89
+ key_paths.map(&:to_sym)
90
+ new_hash = Hash.new
91
+
92
+ get_recursively(key_paths, @store, new_hash)
93
+
94
+ new_hash
95
+ end
96
+
97
+ # Return all the individuals Metric,
98
+ # This call mimic a Enum's each if a block is provided
99
+ #
100
+ # @param path [String] The search path for metrics
101
+ # @param [Array] The metric for the specific path
102
+ def each(path = nil, &block)
103
+ metrics = if path.nil?
104
+ get_all
105
+ else
106
+ transform_to_array(get_with_path(path))
107
+ end
108
+
109
+ block_given? ? metrics.each(&block) : metrics
110
+ end
111
+ alias_method :all, :each
112
+
113
+ private
114
+ def get_all
115
+ @fast_lookup.values
116
+ end
117
+
118
+ # This method take an array of keys and recursively search the metric store structure
119
+ # and return a filtered hash of the structure. This method also take into consideration
120
+ # getting two different branchs.
121
+ #
122
+ #
123
+ # If one part of the `key_paths` contains a filter key with the following format.
124
+ # "pipeline01, pipeline_02", It know that need to fetch the branch `pipeline01` and `pipeline02`
125
+ #
126
+ # Look at the rspec test for more usage.
127
+ #
128
+ # @param key_paths [Array<Symbol>] The list of keys part to filter
129
+ # @param map [Concurrent::Map] The the part of map to search in
130
+ # @param new_hash [Hash] The hash to populate with the results.
131
+ # @return Hash
132
+ def get_recursively(key_paths, map, new_hash)
133
+ key_candidates = extract_filter_keys(key_paths.shift)
134
+
135
+ key_candidates.each do |key_candidate|
136
+ raise MetricNotFound, "For path: #{key_candidate}" if map[key_candidate].nil?
137
+
138
+ if key_paths.empty? # End of the user requested path
139
+ if map[key_candidate].is_a?(Concurrent::Map)
140
+ new_hash[key_candidate] = transform_to_hash(map[key_candidate])
141
+ else
142
+ new_hash[key_candidate] = map[key_candidate]
143
+ end
144
+ else
145
+ if map[key_candidate].is_a?(Concurrent::Map)
146
+ new_hash[key_candidate] = get_recursively(key_paths, map[key_candidate], {})
147
+ else
148
+ new_hash[key_candidate] = map[key_candidate]
149
+ end
150
+ end
151
+ end
152
+ return new_hash
153
+ end
154
+
155
+ def extract_filter_keys(key)
156
+ key.to_s.strip.split(FILTER_KEYS_SEPARATOR).map(&:to_sym)
157
+ end
158
+
159
+ # Take a hash and recursively flatten it into an array.
160
+ # This is useful if you are only interested in the leaf of the tree.
161
+ # Mostly used with `each` to get all the metrics from a specific namespaces
162
+ #
163
+ # This could be moved to `LogStash::Util` once this api stabilize
164
+ #
165
+ # @return [Array] One dimension array
166
+ def transform_to_array(map)
167
+ map.values.collect do |value|
168
+ value.is_a?(Hash) ? transform_to_array(value) : value
169
+ end.flatten
170
+ end
171
+
172
+ # Transform the Concurrent::Map hash into a ruby hash format,
173
+ # This is used to be serialize at the web api layer.
174
+ #
175
+ # This could be moved to `LogStash::Util` once this api stabilize
176
+ #
177
+ # @return [Hash]
178
+ def transform_to_hash(map, new_hash = Hash.new)
179
+ map.each_pair do |key, value|
180
+ if value.is_a?(Concurrent::Map)
181
+ new_hash[key] = {}
182
+ transform_to_hash(value, new_hash[key])
183
+ else
184
+ new_hash[key] = value
185
+ end
186
+ end
187
+
188
+ return new_hash
189
+ end
190
+
191
+ # This method iterate through the namespace path and try to find the corresponding
192
+ # value for the path, if any part of the path is not found it will
193
+ # create it.
194
+ #
195
+ # @param [Array] The path where values should be located
196
+ # @raise [ConcurrentMapExpected] Raise if the retrieved object isn't a `Concurrent::Map`
197
+ # @return [Concurrent::Map] Map where the metrics should be saved
198
+ def fetch_or_store_namespaces(namespaces_path)
199
+ path_map = fetch_or_store_namespace_recursively(@store, namespaces_path)
200
+
201
+ # This mean one of the namespace and key are colliding
202
+ # and we have to deal it upstream.
203
+ unless path_map.is_a?(Concurrent::Map)
204
+ raise NamespacesExpectedError, "Expecting a `Namespaces` but found class: #{path_map.class.name} for namespaces_path: #{namespaces_path}"
205
+ end
206
+
207
+ return path_map
208
+ end
209
+
210
+ # Recursively fetch or create the namespace paths through the `MetricStove`
211
+ # This algorithm use an index to known which keys to search in the map.
212
+ # This doesn't cloning the array if we want to give a better feedback to the user
213
+ #
214
+ # @param [Concurrent::Map] Map to search for the key
215
+ # @param [Array] List of path to create
216
+ # @param [Fixnum] Which part from the list to create
217
+ #
218
+ def fetch_or_store_namespace_recursively(map, namespaces_path, idx = 0)
219
+ current = namespaces_path[idx]
220
+
221
+ # we are at the end of the namespace path, break out of the recursion
222
+ return map if current.nil?
223
+
224
+ new_map = map.fetch_or_store(current) { Concurrent::Map.new }
225
+ return fetch_or_store_namespace_recursively(new_map, namespaces_path, idx + 1)
226
+ end
227
+ end
228
+ end; end