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

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.

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