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.
- checksums.yaml +4 -4
- data/lib/logstash-core/version.rb +1 -1
- data/lib/logstash/agent.rb +124 -411
- data/lib/logstash/api/init.ru +31 -0
- data/lib/logstash/api/lib/app.rb +40 -0
- data/lib/logstash/api/lib/app/command.rb +29 -0
- data/lib/logstash/api/lib/app/command_factory.rb +29 -0
- data/lib/logstash/api/lib/app/commands/stats/events_command.rb +13 -0
- data/lib/logstash/api/lib/app/commands/stats/hotthreads_command.rb +120 -0
- data/lib/logstash/api/lib/app/commands/stats/memory_command.rb +25 -0
- data/lib/logstash/api/lib/app/commands/system/basicinfo_command.rb +15 -0
- data/lib/logstash/api/lib/app/commands/system/plugins_command.rb +28 -0
- data/lib/logstash/api/lib/app/modules/node.rb +25 -0
- data/lib/logstash/api/lib/app/modules/node_stats.rb +51 -0
- data/lib/logstash/api/lib/app/modules/plugins.rb +15 -0
- data/lib/logstash/api/lib/app/modules/stats.rb +21 -0
- data/lib/logstash/api/lib/app/root.rb +13 -0
- data/lib/logstash/api/lib/app/service.rb +61 -0
- data/lib/logstash/api/lib/app/stats.rb +56 -0
- data/lib/logstash/api/lib/helpers/app_helpers.rb +23 -0
- data/lib/logstash/codecs/base.rb +1 -29
- data/lib/logstash/config/config_ast.rb +18 -31
- data/lib/logstash/config/loader.rb +3 -5
- data/lib/logstash/config/mixin.rb +25 -64
- data/lib/logstash/filter_delegator.rb +65 -0
- data/lib/logstash/inputs/base.rb +1 -1
- data/lib/logstash/inputs/metrics.rb +47 -0
- data/lib/logstash/instrument/collector.rb +109 -0
- data/lib/logstash/instrument/metric.rb +102 -0
- data/lib/logstash/instrument/metric_store.rb +228 -0
- data/lib/logstash/instrument/metric_type.rb +24 -0
- data/lib/logstash/instrument/metric_type/base.rb +35 -0
- data/lib/logstash/instrument/metric_type/counter.rb +29 -0
- data/lib/logstash/instrument/metric_type/gauge.rb +22 -0
- data/lib/logstash/instrument/metric_type/mean.rb +33 -0
- data/lib/logstash/instrument/namespaced_metric.rb +54 -0
- data/lib/logstash/instrument/null_metric.rb +4 -3
- data/lib/logstash/instrument/periodic_poller/base.rb +57 -0
- data/lib/logstash/instrument/periodic_poller/jvm.rb +92 -0
- data/lib/logstash/instrument/periodic_poller/os.rb +13 -0
- data/lib/logstash/instrument/periodic_poller/periodic_poller_observer.rb +19 -0
- data/lib/logstash/instrument/periodic_pollers.rb +26 -0
- data/lib/logstash/instrument/snapshot.rb +16 -0
- data/lib/logstash/json.rb +2 -3
- data/lib/logstash/namespace.rb +1 -0
- data/lib/logstash/output_delegator.rb +16 -3
- data/lib/logstash/outputs/base.rb +1 -32
- data/lib/logstash/pipeline.rb +67 -8
- data/lib/logstash/plugin.rb +57 -19
- data/lib/logstash/runner.rb +348 -84
- data/lib/logstash/util.rb +9 -0
- data/lib/logstash/util/duration_formatter.rb +15 -0
- data/lib/logstash/util/java_version.rb +2 -4
- data/lib/logstash/util/loggable.rb +29 -0
- data/lib/logstash/version.rb +1 -1
- data/lib/logstash/webserver.rb +98 -0
- data/locales/en.yml +42 -24
- data/logstash-core.gemspec +9 -6
- data/spec/api/lib/api/node_spec.rb +64 -0
- data/spec/api/lib/api/node_stats_spec.rb +68 -0
- data/spec/api/lib/api/plugins_spec.rb +57 -0
- data/spec/api/lib/api/root_spec.rb +20 -0
- data/spec/api/lib/api/stats_spec.rb +19 -0
- data/spec/api/lib/commands/events_spec.rb +17 -0
- data/spec/api/lib/commands/jvm_spec.rb +45 -0
- data/spec/api/spec_helper.rb +128 -0
- data/spec/logstash/agent_spec.rb +62 -169
- data/spec/logstash/config/config_ast_spec.rb +2 -47
- data/spec/logstash/config/mixin_spec.rb +0 -157
- data/spec/logstash/filter_delegator_spec.rb +143 -0
- data/spec/logstash/inputs/metrics_spec.rb +52 -0
- data/spec/logstash/instrument/collector_spec.rb +49 -0
- data/spec/logstash/instrument/metric_spec.rb +110 -0
- data/spec/logstash/instrument/metric_store_spec.rb +163 -0
- data/spec/logstash/instrument/metric_type/counter_spec.rb +40 -0
- data/spec/logstash/instrument/metric_type/gauge_spec.rb +40 -0
- data/spec/logstash/instrument/namespaced_metric_spec.rb +25 -0
- data/spec/logstash/instrument/null_metric_spec.rb +9 -51
- data/spec/logstash/json_spec.rb +14 -0
- data/spec/logstash/output_delegator_spec.rb +6 -3
- data/spec/logstash/outputs/base_spec.rb +0 -107
- data/spec/logstash/pipeline_spec.rb +204 -33
- data/spec/logstash/plugin_spec.rb +80 -15
- data/spec/logstash/runner_spec.rb +134 -38
- data/spec/logstash/shutdown_watcher_spec.rb +0 -1
- data/spec/logstash/util/duration_formatter_spec.rb +11 -0
- data/spec/logstash/util/java_version_spec.rb +10 -2
- data/spec/logstash/util_spec.rb +28 -0
- data/spec/support/matchers.rb +30 -0
- metadata +154 -20
- data/lib/logstash/logging/json.rb +0 -21
- data/lib/logstash/special_agent.rb +0 -8
- data/lib/logstash/util/safe_uri.rb +0 -50
- data/spec/logstash/codecs/base_spec.rb +0 -74
- 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
|
data/lib/logstash/inputs/base.rb
CHANGED
@@ -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
|