opentelemetry-api 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +9 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE +201 -0
- data/OVERVIEW.md +66 -0
- data/lib/opentelemetry-api.rb +7 -0
- data/lib/opentelemetry.rb +61 -0
- data/lib/opentelemetry/context.rb +154 -0
- data/lib/opentelemetry/context/key.rb +29 -0
- data/lib/opentelemetry/context/propagation.rb +22 -0
- data/lib/opentelemetry/context/propagation/composite_propagator.rb +73 -0
- data/lib/opentelemetry/context/propagation/default_getter.rb +26 -0
- data/lib/opentelemetry/context/propagation/default_setter.rb +26 -0
- data/lib/opentelemetry/context/propagation/noop_extractor.rb +26 -0
- data/lib/opentelemetry/context/propagation/noop_injector.rb +26 -0
- data/lib/opentelemetry/context/propagation/propagation.rb +27 -0
- data/lib/opentelemetry/context/propagation/propagator.rb +64 -0
- data/lib/opentelemetry/correlation_context.rb +16 -0
- data/lib/opentelemetry/correlation_context/builder.rb +18 -0
- data/lib/opentelemetry/correlation_context/manager.rb +36 -0
- data/lib/opentelemetry/correlation_context/propagation.rb +57 -0
- data/lib/opentelemetry/correlation_context/propagation/context_keys.rb +27 -0
- data/lib/opentelemetry/correlation_context/propagation/text_extractor.rb +60 -0
- data/lib/opentelemetry/correlation_context/propagation/text_injector.rb +55 -0
- data/lib/opentelemetry/error.rb +9 -0
- data/lib/opentelemetry/instrumentation.rb +15 -0
- data/lib/opentelemetry/instrumentation/base.rb +245 -0
- data/lib/opentelemetry/instrumentation/registry.rb +87 -0
- data/lib/opentelemetry/internal.rb +22 -0
- data/lib/opentelemetry/metrics.rb +16 -0
- data/lib/opentelemetry/metrics/handles.rb +44 -0
- data/lib/opentelemetry/metrics/instruments.rb +105 -0
- data/lib/opentelemetry/metrics/meter.rb +72 -0
- data/lib/opentelemetry/metrics/meter_provider.rb +22 -0
- data/lib/opentelemetry/trace.rb +51 -0
- data/lib/opentelemetry/trace/event.rb +46 -0
- data/lib/opentelemetry/trace/link.rb +46 -0
- data/lib/opentelemetry/trace/propagation.rb +17 -0
- data/lib/opentelemetry/trace/propagation/context_keys.rb +35 -0
- data/lib/opentelemetry/trace/propagation/trace_context.rb +59 -0
- data/lib/opentelemetry/trace/propagation/trace_context/text_extractor.rb +58 -0
- data/lib/opentelemetry/trace/propagation/trace_context/text_injector.rb +55 -0
- data/lib/opentelemetry/trace/propagation/trace_context/trace_parent.rb +130 -0
- data/lib/opentelemetry/trace/span.rb +145 -0
- data/lib/opentelemetry/trace/span_context.rb +56 -0
- data/lib/opentelemetry/trace/span_kind.rb +35 -0
- data/lib/opentelemetry/trace/status.rb +114 -0
- data/lib/opentelemetry/trace/trace_flags.rb +50 -0
- data/lib/opentelemetry/trace/tracer.rb +103 -0
- data/lib/opentelemetry/trace/tracer_provider.rb +22 -0
- data/lib/opentelemetry/trace/util/http_to_status.rb +47 -0
- data/lib/opentelemetry/version.rb +10 -0
- metadata +220 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019 OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module OpenTelemetry
|
8
|
+
# @api private
|
9
|
+
#
|
10
|
+
# Internal contains helpers used by the no-op API implementation.
|
11
|
+
module Internal
|
12
|
+
extend self
|
13
|
+
|
14
|
+
def printable_ascii?(string)
|
15
|
+
return false unless string.is_a?(String)
|
16
|
+
|
17
|
+
r = 32..126
|
18
|
+
string.each_codepoint { |c| return false unless r.include?(c) }
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019 OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
require 'opentelemetry/metrics/handles'
|
8
|
+
require 'opentelemetry/metrics/instruments'
|
9
|
+
require 'opentelemetry/metrics/meter'
|
10
|
+
require 'opentelemetry/metrics/meter_provider'
|
11
|
+
|
12
|
+
module OpenTelemetry
|
13
|
+
# The Metrics API allows reporting raw measurements as well as metrics with known aggregation and labels.
|
14
|
+
module Metrics
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019 OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module OpenTelemetry
|
8
|
+
module Metrics
|
9
|
+
# In situations where performance is a requirement and a metric is
|
10
|
+
# repeatedly used with the same labels, the developer may elect to
|
11
|
+
# use instrument {Handles} as an optimization. For handles to be a benefit,
|
12
|
+
# it requires that a specific instrument will be re-used with specific
|
13
|
+
# labels. If an instrument will be used with the same labels more than
|
14
|
+
# once, obtaining an instrument handle corresponding to the labels
|
15
|
+
# ensures the highest performance available.
|
16
|
+
#
|
17
|
+
# To obtain a handle given an instrument and labels, use the #handle
|
18
|
+
# method to return an interface that supports the #add or #record
|
19
|
+
# method of the instrument in question.
|
20
|
+
#
|
21
|
+
# Instrument handles may consume SDK resources indefinitely.
|
22
|
+
module Handles
|
23
|
+
# A float counter handle.
|
24
|
+
class FloatCounter
|
25
|
+
def add(value); end
|
26
|
+
end
|
27
|
+
|
28
|
+
# An integer counter handle.
|
29
|
+
class IntegerCounter
|
30
|
+
def add(value); end
|
31
|
+
end
|
32
|
+
|
33
|
+
# A float measure handle.
|
34
|
+
class FloatMeasure
|
35
|
+
def record(value); end
|
36
|
+
end
|
37
|
+
|
38
|
+
# An integer measure handle.
|
39
|
+
class IntegerMeasure
|
40
|
+
def record(value); end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019 OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module OpenTelemetry
|
8
|
+
module Metrics
|
9
|
+
# The user-facing metrics API supports producing diagnostic measurements
|
10
|
+
# using three basic kinds of instrument. "Metrics" are the thing being
|
11
|
+
# produced -- mathematical, statistical summaries of certain observable
|
12
|
+
# behavior in the program. "Instruments" are the devices used by the
|
13
|
+
# program to record observations about their behavior. Therefore, we use
|
14
|
+
# "metric instrument" to refer to a program object, allocated through the
|
15
|
+
# API, used for recording metrics. There are three distinct instruments in
|
16
|
+
# the Metrics API, commonly known as Counters, Observers, and Measures.
|
17
|
+
module Instruments
|
18
|
+
# TODO: Observers.
|
19
|
+
|
20
|
+
# A float counter instrument.
|
21
|
+
class FloatCounter
|
22
|
+
def add(value, labels = {}); end
|
23
|
+
|
24
|
+
# Obtain a handle from the instrument and labels.
|
25
|
+
#
|
26
|
+
# @param [optional Hash<String, String>] labels A Hash of Strings.
|
27
|
+
# @return [Handles::FloatCounter]
|
28
|
+
def handle(labels = {})
|
29
|
+
Handles::FloatCounter.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return a measurement to be recorded via {Meter#record_batch}.
|
33
|
+
#
|
34
|
+
# @param [Float] value
|
35
|
+
# @return [Object, Measurement]
|
36
|
+
def measurement(value)
|
37
|
+
NOOP_MEASUREMENT
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# An integer counter instrument.
|
42
|
+
class IntegerCounter
|
43
|
+
def add(value, labels = {}); end
|
44
|
+
|
45
|
+
# Obtain a handle from the instrument and labels.
|
46
|
+
#
|
47
|
+
# @param [optional Hash<String, String>] labels A Hash of Strings.
|
48
|
+
# @return [Handles::IntegerCounter]
|
49
|
+
def handle(labels = {})
|
50
|
+
Handles::IntegerCounter.new
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return a measurement to be recorded via {Meter#record_batch}.
|
54
|
+
#
|
55
|
+
# @param [Integer] value
|
56
|
+
# @return [Object, Measurement]
|
57
|
+
def measurement(value)
|
58
|
+
NOOP_MEASUREMENT
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# A float measure instrument.
|
63
|
+
class FloatMeasure
|
64
|
+
def record(value, labels = {}); end
|
65
|
+
|
66
|
+
# Obtain a handle from the instrument and labels.
|
67
|
+
#
|
68
|
+
# @param [optional Hash<String, String>] labels A Hash of Strings.
|
69
|
+
# @return [Handles::FloatMeasure]
|
70
|
+
def handle(labels = {})
|
71
|
+
Handles::FloatMeasure.new
|
72
|
+
end
|
73
|
+
|
74
|
+
# Return a measurement to be recorded via {Meter#record_batch}.
|
75
|
+
#
|
76
|
+
# @param [Float] value
|
77
|
+
# @return [Object, Measurement]
|
78
|
+
def measurement(value)
|
79
|
+
NOOP_MEASUREMENT
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# An integer measure instrument.
|
84
|
+
class IntegerMeasure
|
85
|
+
def record(value, labels = {}); end
|
86
|
+
|
87
|
+
# Obtain a handle from the instrument and labels.
|
88
|
+
#
|
89
|
+
# @param [optional Hash<String, String>] labels A Hash of Strings.
|
90
|
+
# @return [Handles::IntegerMeasure]
|
91
|
+
def handle(labels = {})
|
92
|
+
Handles::IntegerMeasure.new
|
93
|
+
end
|
94
|
+
|
95
|
+
# Return a measurement to be recorded via {Meter#record_batch}.
|
96
|
+
#
|
97
|
+
# @param [Integer] value
|
98
|
+
# @return [Object, Measurement]
|
99
|
+
def measurement(value)
|
100
|
+
NOOP_MEASUREMENT
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019 OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module OpenTelemetry
|
8
|
+
module Metrics
|
9
|
+
# No-op implementation of Meter.
|
10
|
+
class Meter
|
11
|
+
def record_batch(*measurements, labels: nil); end
|
12
|
+
|
13
|
+
# TODO: Observers.
|
14
|
+
|
15
|
+
# Create and return a floating point counter.
|
16
|
+
#
|
17
|
+
# @param [String] name Name of the metric. See {Meter} for required metric name syntax.
|
18
|
+
# @param [optional String] description Descriptive text documenting the instrument.
|
19
|
+
# @param [optional String] unit Unit specified according to http://unitsofmeasure.org/ucum.html.
|
20
|
+
# @param [optional Enumerable<String>] recommended_label_keys Recommended grouping keys for this instrument.
|
21
|
+
# @param [optional Boolean] monotonic Whether the counter accepts only monotonic updates. Defaults to true.
|
22
|
+
# @return [FloatCounter]
|
23
|
+
def create_float_counter(name, description: nil, unit: nil, recommended_label_keys: nil, monotonic: true)
|
24
|
+
raise ArgumentError if name.nil?
|
25
|
+
|
26
|
+
Instruments::FloatCounter.new
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create and return an integer counter.
|
30
|
+
#
|
31
|
+
# @param [String] name Name of the metric. See {Meter} for required metric name syntax.
|
32
|
+
# @param [optional String] description Descriptive text documenting the instrument.
|
33
|
+
# @param [optional String] unit Unit specified according to http://unitsofmeasure.org/ucum.html.
|
34
|
+
# @param [optional Enumerable<String>] recommended_label_keys Recommended grouping keys for this instrument.
|
35
|
+
# @param [optional Boolean] monotonic Whether the counter accepts only monotonic updates. Defaults to true.
|
36
|
+
# @return [IntegerCounter]
|
37
|
+
def create_integer_counter(name, description: nil, unit: nil, recommended_label_keys: nil, monotonic: true)
|
38
|
+
raise ArgumentError if name.nil?
|
39
|
+
|
40
|
+
Instruments::IntegerCounter.new
|
41
|
+
end
|
42
|
+
|
43
|
+
# Create and return a floating point measure.
|
44
|
+
#
|
45
|
+
# @param [String] name Name of the metric. See {Meter} for required metric name syntax.
|
46
|
+
# @param [optional String] description Descriptive text documenting the instrument.
|
47
|
+
# @param [optional String] unit Unit specified according to http://unitsofmeasure.org/ucum.html.
|
48
|
+
# @param [optional Enumerable<String>] recommended_label_keys Recommended grouping keys for this instrument.
|
49
|
+
# @param [optional Boolean] absolute Whether the measure accepts only non-negative updates. Defaults to true.
|
50
|
+
# @return [FloatMeasure]
|
51
|
+
def create_float_measure(name, description: nil, unit: nil, recommended_label_keys: nil, absolute: true)
|
52
|
+
raise ArgumentError if name.nil?
|
53
|
+
|
54
|
+
Instruments::FloatMeasure.new
|
55
|
+
end
|
56
|
+
|
57
|
+
# Create and return an integer measure.
|
58
|
+
#
|
59
|
+
# @param [String] name Name of the metric. See {Meter} for required metric name syntax.
|
60
|
+
# @param [optional String] description Descriptive text documenting the instrument.
|
61
|
+
# @param [optional String] unit Unit specified according to http://unitsofmeasure.org/ucum.html.
|
62
|
+
# @param [optional Enumerable<String>] recommended_label_keys Recommended grouping keys for this instrument.
|
63
|
+
# @param [optional Boolean] absolute Whether the measure accepts only non-negative updates. Defaults to true.
|
64
|
+
# @return [IntegerMeasure]
|
65
|
+
def create_integer_measure(name, description: nil, unit: nil, recommended_label_keys: nil, absolute: true)
|
66
|
+
raise ArgumentError if name.nil?
|
67
|
+
|
68
|
+
Instruments::IntegerMeasure.new
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019 OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module OpenTelemetry
|
8
|
+
module Metrics
|
9
|
+
# No-op implementation of a meter provider.
|
10
|
+
class MeterProvider
|
11
|
+
# Returns a {Meter} instance.
|
12
|
+
#
|
13
|
+
# @param [optional String] name Instrumentation package name
|
14
|
+
# @param [optional String] version Instrumentation package version
|
15
|
+
#
|
16
|
+
# @return [Meter]
|
17
|
+
def meter(name = nil, version = nil)
|
18
|
+
@meter ||= Meter.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019 OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module OpenTelemetry
|
8
|
+
# The Trace API allows recording a set of events, triggered as a result of a
|
9
|
+
# single logical operation, consolidated across various components of an
|
10
|
+
# application.
|
11
|
+
module Trace
|
12
|
+
# An invalid trace identifier, a 16-byte string with all zero bytes.
|
13
|
+
INVALID_TRACE_ID = ("\0" * 16).b
|
14
|
+
|
15
|
+
# An invalid span identifier, an 8-byte string with all zero bytes.
|
16
|
+
INVALID_SPAN_ID = ("\0" * 8).b
|
17
|
+
|
18
|
+
# Generates a valid trace identifier, a 16-byte string with at least one
|
19
|
+
# non-zero byte.
|
20
|
+
#
|
21
|
+
# @return [String] a valid trace ID.
|
22
|
+
def self.generate_trace_id
|
23
|
+
loop do
|
24
|
+
id = Random::DEFAULT.bytes(16)
|
25
|
+
return id unless id == INVALID_TRACE_ID
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Generates a valid span identifier, an 8-byte string with at least one
|
30
|
+
# non-zero byte.
|
31
|
+
#
|
32
|
+
# @return [String] a valid span ID.
|
33
|
+
def self.generate_span_id
|
34
|
+
loop do
|
35
|
+
id = Random::DEFAULT.bytes(8)
|
36
|
+
return id unless id == INVALID_SPAN_ID
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
require 'opentelemetry/trace/event'
|
43
|
+
require 'opentelemetry/trace/link'
|
44
|
+
require 'opentelemetry/trace/propagation'
|
45
|
+
require 'opentelemetry/trace/trace_flags'
|
46
|
+
require 'opentelemetry/trace/span_context'
|
47
|
+
require 'opentelemetry/trace/span_kind'
|
48
|
+
require 'opentelemetry/trace/span'
|
49
|
+
require 'opentelemetry/trace/status'
|
50
|
+
require 'opentelemetry/trace/tracer'
|
51
|
+
require 'opentelemetry/trace/tracer_provider'
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019 OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module OpenTelemetry
|
8
|
+
module Trace
|
9
|
+
# A text annotation with a set of attributes and a timestamp.
|
10
|
+
class Event
|
11
|
+
EMPTY_ATTRIBUTES = {}.freeze
|
12
|
+
|
13
|
+
private_constant :EMPTY_ATTRIBUTES
|
14
|
+
|
15
|
+
# Returns the name of this event
|
16
|
+
#
|
17
|
+
# @return [String]
|
18
|
+
attr_reader :name
|
19
|
+
|
20
|
+
# Returns the frozen attributes for this event
|
21
|
+
#
|
22
|
+
# @return [Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}]
|
23
|
+
attr_reader :attributes
|
24
|
+
|
25
|
+
# Returns the timestamp for this event
|
26
|
+
#
|
27
|
+
# @return [Time]
|
28
|
+
attr_reader :timestamp
|
29
|
+
|
30
|
+
# Returns a new immutable {Event}.
|
31
|
+
#
|
32
|
+
# @param [String] name The name of this event
|
33
|
+
# @param [optional Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}]
|
34
|
+
# attributes A hash of attributes for this event. Attributes will be
|
35
|
+
# frozen during Event initialization.
|
36
|
+
# @param [optional Time] timestamp The timestamp for this event.
|
37
|
+
# Defaults to Time.now.
|
38
|
+
# @return [Event]
|
39
|
+
def initialize(name:, attributes: nil, timestamp: nil)
|
40
|
+
@name = name
|
41
|
+
@attributes = attributes.freeze || EMPTY_ATTRIBUTES
|
42
|
+
@timestamp = timestamp || Time.now
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019 OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module OpenTelemetry
|
8
|
+
module Trace
|
9
|
+
# A link to a {Span}. Used (for example) in batching operations, where a
|
10
|
+
# single batch handler processes multiple requests from different traces.
|
11
|
+
# A Link can be also used to reference spans from the same trace. A Link
|
12
|
+
# and its attributes are immutable.
|
13
|
+
class Link
|
14
|
+
EMPTY_ATTRIBUTES = {}.freeze
|
15
|
+
|
16
|
+
private_constant :EMPTY_ATTRIBUTES
|
17
|
+
|
18
|
+
# Returns the {SpanContext} for this link
|
19
|
+
#
|
20
|
+
# @return [SpanContext]
|
21
|
+
attr_reader :context
|
22
|
+
|
23
|
+
# Returns the frozen attributes for this link.
|
24
|
+
#
|
25
|
+
# @return [Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}]
|
26
|
+
attr_reader :attributes
|
27
|
+
|
28
|
+
# Returns a new immutable {Link}.
|
29
|
+
#
|
30
|
+
# @param [SpanContext] span_context The context of the linked {Span}.
|
31
|
+
# @param [optional Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}]
|
32
|
+
# attributes A hash of attributes for this link. Attributes will be
|
33
|
+
# frozen during Link initialization.
|
34
|
+
# @return [Link]
|
35
|
+
def initialize(span_context, attributes = nil)
|
36
|
+
@context = span_context
|
37
|
+
@attributes = attributes.freeze || EMPTY_ATTRIBUTES
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns true if two {Link}s are equal.
|
41
|
+
def ==(other)
|
42
|
+
other.context == @context && other.attributes == @attributes
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019 OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
require 'opentelemetry/trace/propagation/context_keys'
|
8
|
+
require 'opentelemetry/trace/propagation/trace_context'
|
9
|
+
|
10
|
+
module OpenTelemetry
|
11
|
+
module Trace
|
12
|
+
# The Trace::Propagation module contains injectors and extractors for
|
13
|
+
# sending and receiving span context over the wire
|
14
|
+
module Propagation
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|