invoca-metrics 0.0.2 → 1.14.1

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 invoca-metrics might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c61c3ea9d85deef7f1abf581660aad82dee010267632bd522c523a6a52a5b67c
4
- data.tar.gz: 39b26c15860fa3ec223dabb0b0a6a18d7019e9e6e7f1585934d948c0d0453e2c
3
+ metadata.gz: 90b8517fa182fbf84e8067d0f4727aac492ea493804f5913cbd0495a3c1a1a88
4
+ data.tar.gz: f694ecc164dc329381989fbce06d5ea91fb416707d54ca9a571eeda286c3ab52
5
5
  SHA512:
6
- metadata.gz: '009179dfd5fe4f2e69310d7f0aa6dd210b80fd1d8749c55e736162f3c150d78653e139bef8553725a0136667b2444c783a326a76c12ac39bfb2d602fcec60d71'
7
- data.tar.gz: c19157d85d447dcf6496b2751cd0565f7a21bc1559bfb1a59009ff524d20193e28e4a95b197ae97439cf45fd342ed13f8d446c6704fbd025d926611704b1d430
6
+ metadata.gz: db2b4c7f76eb190ce6e2ff8231f5eb1f39103fde83e314d4eb40d18c1b01a73820e0b253be0969bdcbf179ea8cbdb4466845182c952ee1a159f736486bd041ed
7
+ data.tar.gz: b48fd3176d18a7957c6cf98e88513044c275195f8ab93036192f0599b9c338d84c615b94ad8465f468318f4513ecd42fca381a2ef180d17541c09b352c689a2e
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Cary Penniman
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # Invoca::Metrics
2
+
3
+ Publish metrics for observability!
4
+
5
+ ## Dependencies
6
+ * Ruby >= 2.6
7
+ * ActiveSupport >= 4.2, < 7
8
+
9
+ ## Usage
10
+
11
+ Mix in the `Invoca::Metrics::Source` module:
12
+ ```ruby
13
+ class MyClass
14
+ include Invoca::Metrics::Source
15
+ ...
16
+ end
17
+ ```
18
+
19
+ Then declare the metrics that can be published by the class using a `declare_metrics` DSL block.
20
+ Each entry in the block starts with the metric type as the DSL method:
21
+
22
+ | Metric Type | Value | Metric Instance Methods |
23
+ |:-----------:|:----------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
24
+ | `counter` | monotonically incrementing floating point | `.increment(value = 1, **labels)` |
25
+ | `gauge` | arbitrary floating point | `.set(value, **labels)` |
26
+ | `histogram` | histogram counters with configurable bucket cutoffs | `.add(value, **labels)`<br/><br/>`.time(return_timing: false, **labels, &block)`<br/>yields to block and `add()`s the milliseconds spent to the histogram; returns either `block_value` or `[milliseconds, block_value]` depending on the `return_timing` argument |
27
+
28
+ After the metric type, the first argument is the metric name, given as a symbol. This is globally unique.
29
+ Note that other classes in the process are allowed to declare the same metric, as long as the type and labels are the same.
30
+
31
+ Each metric may have any number of labels declared in the optional `labels:` hash, with default values in case that label is not passed when the metric is published.
32
+
33
+ Note: An implicit label of `service: <service_name>` is always added. The `<service_name>` is a configured value.
34
+
35
+ Histograms have a required argument, `buckets: <array>`, where the array is the histogram bucket cutoffs.
36
+
37
+ ```ruby
38
+ class MyClass
39
+ include Invoca::Metrics::Source
40
+
41
+ declare_metrics do
42
+ counter :request_total, labels: { status: '200' }
43
+ gauge :available_memory
44
+ histogram :processing_time, buckets: [30.seconds, 1.hour, 2.hours, 4.hours, 5.hours, 6.hours, 10.hours]
45
+ end
46
+
47
+ ...
48
+ end
49
+ ```
50
+
51
+ After declaring the metrics, you may use `prometheus_metrics.<metric_name>` (in either the class or instance) to publish the metric.
52
+ ```ruby
53
+ prometheus_metrics.request_total.increment # increment the counter by 1
54
+
55
+ prometheus_metrics.request_total.increment(5) # increment the counter by 5
56
+
57
+ prometheus_metrics.available_memory.set(10.25) # set the gauge to 10.25
58
+
59
+ prometheus_metrics.processing_time.add(100) # Increment the bucket closest to the value 100
60
+ prometheus_metrics.processing_time.time { work } # measure the elapsed milliseconds to run the given block and increment the bucket closest to that time period in milliseconds
61
+ ```
62
+
63
+ ## Installation
64
+
65
+ Add this line to your application's Gemfile:
66
+
67
+ gem 'invoca-metrics'
68
+
69
+ And then execute:
70
+
71
+ $ bundle
72
+
73
+ Or install it yourself as:
74
+
75
+ $ gem install invoca-metrics
76
+
77
+ ## Setup
78
+
79
+ ### Default Setup
80
+
81
+ Add the following code to your application...
82
+
83
+ require 'invoca/metrics'
84
+
85
+ Invoca::Metrics.service_name = "tts-service"
86
+ Invoca::Metrics.server_name = Socket.gethostname
87
+ Invoca::Metrics.cluster_name = "production"
88
+ Invoca::Metrics.sub_server_name = "worker_process_1"
89
+ Invoca::Metrics.statsd_host = "255.0.0.123"
90
+ Invoca::Metrics.statsd_port = 200
91
+
92
+ Invoca::Metrics::Client.logger = logger
93
+ Invoca::Metrics::Client.log_send_failures = ['staging', 'production'].include(environment)
94
+
95
+ Note: the `service_name` setting is required; all others are optional.
96
+
97
+ ### Multi Configuration
98
+
99
+ To set multiple configurations, supply a `config` hash with additional settings.
100
+
101
+ Invoca::Metrics.config = {
102
+ deployment_group: {
103
+ server_name: "deployment"
104
+ statsd_host: "255.0.0.456",
105
+ statsd_port: 300
106
+ },
107
+ region: {
108
+ server_name: "region_name"
109
+ statsd_host: "255.0.0.789",
110
+ statsd_port: 400,
111
+ sub_server_name: nil
112
+ cluster_name: nil
113
+ }
114
+ }
115
+
116
+ The settings (`[service_name, server_name, cluster_name, sub_server_name, statsd_host, statsd_port]`) directly set on `Invoca::Metrics` will be the default values supplied if the individual configuration does not supply the option.
117
+ It's highly suggested that each configuration has its own `statsd_host` and `statsd_port` along with unique naming since writing the same metric from one statsd node could be overwritten by the same metric from a separate node.
118
+
119
+ In order to set a configuration as the default, set the configuration key as `default_config_key`.
120
+
121
+ Invoca::Metrics.default_config_key = :deployment_group
122
+
123
+ Any keys missing from the `default_config_key` config will by default have the values from the keys set directly on `Invoca::Metrics`.
124
+
125
+ The full set of default values for the above example would then be
126
+
127
+ service_name: "my_event_worker"
128
+ cluster_name: "production"
129
+ sub_server_name: "worker_process_1"
130
+ server_name: "deployment"
131
+ statsd_host: "255.0.0.456",
132
+ statsd_port: 300
133
+
134
+ ### Prometheus Metric Exporting Setup
135
+ There are 2 ways to set up metrics for being scraped by Prometheus: `:direct` and `:push`).
136
+ `:direct` will expose a web `server directly from the running process in order for metrics to be scraped while `:push` will push metrics over to an aggregate gateway which in turn implements a web server.
137
+
138
+ #### `:direct` Configuration Example
139
+ ```ruby
140
+ Invoca::Metrics::Prometheus.configure do |config|
141
+ config.mode = :direct
142
+ config.port = 9394 # Port to expose metrics from
143
+ end
144
+ ```
145
+
146
+ #### `:push` Configuration
147
+ ```ruby
148
+ Invoca::Metrics::Prometheus.configure do |config|
149
+ config.mode = :push
150
+ config.host = '127.0.0.1' # IP address to push metrics to
151
+ config.port = 9394 # Port to push metrics to
152
+ end
153
+ ```
154
+
155
+ ## Contributing
156
+
157
+ 1. Fork it
158
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
159
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
160
+ 4. Push to the branch (`git push origin my-new-feature`)
161
+ 5. Create new Pull Request
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'invoca/metrics/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "invoca-metrics"
9
+ spec.version = Invoca::Metrics::VERSION
10
+ spec.authors = ["Invoca development"]
11
+ spec.email = ["development@invoca.com"]
12
+ spec.description = 'Invoca metrics reporting library'
13
+ spec.summary = 'Invoca metrics reporting library'
14
+ spec.homepage = "https://github.com/Invoca/invoca-metrics"
15
+ spec.license = "MIT"
16
+
17
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
18
+
19
+ spec.files = [
20
+ *Dir.glob('lib/**/*.rb'),
21
+ 'README.md',
22
+ 'LICENSE.txt',
23
+ 'invoca-metrics.gemspec',
24
+ ]
25
+
26
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
27
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "activesupport", ">= 4.2", "< 7"
31
+ spec.add_dependency "statsd-ruby", "~> 1.2.1"
32
+ spec.add_dependency "prometheus_exporter", "~> 2.0"
33
+ spec.add_dependency "contextual_logger", "~> 1.0"
34
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/module/delegation'
5
+
6
+ module Invoca
7
+ module Metrics
8
+ class Batch < Client
9
+ delegate :batch_size, :batch_size=, to: :statsd_client
10
+
11
+ # @param [Invoca::Metrics::Client] client requires a configured Client instance
12
+ # @param [Statsd::Batch] statsd_batch requires a configured Batch instance
13
+ def initialize(client, statsd_batch)
14
+ super(
15
+ hostname: client.hostname,
16
+ port: client.port,
17
+ cluster_name: client.cluster_name,
18
+ service_name: client.service_name,
19
+ server_label: client.server_label,
20
+ sub_server_name: client.sub_server_name,
21
+ namespace: client.namespace
22
+ )
23
+ @statsd_client = statsd_batch
24
+ end
25
+
26
+ # @yields [Batch] yields itself
27
+ #
28
+ # A convenience method to ensure that data is not lost in the event of an
29
+ # exception being thrown. Batches will be transmitted on the parent socket
30
+ # as soon as the batch is full, and when the block finishes.
31
+ def ensure_send
32
+ yield self
33
+ ensure
34
+ statsd_client.flush
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/module/delegation'
5
+
6
+ module Invoca
7
+ module Metrics
8
+ class Client
9
+ STATSD_DEFAULT_HOSTNAME = "127.0.0.1"
10
+ STATSD_DEFAULT_PORT = 8125
11
+ STATSD_METRICS_SEPARATOR = '.'
12
+
13
+ class << self
14
+ delegate :logger, :logger=, :log_send_failures, :log_send_failures=, to: StatsdClient
15
+
16
+ # Default values are required for backwards compatibility
17
+ def metrics(statsd_host: Invoca::Metrics.default_client_config[:statsd_host],
18
+ statsd_port: Invoca::Metrics.default_client_config[:statsd_port],
19
+ cluster_name: Invoca::Metrics.default_client_config[:cluster_name],
20
+ service_name: Invoca::Metrics.default_client_config[:service_name],
21
+ server_name: Invoca::Metrics.default_client_config[:server_name],
22
+ sub_server_name: Invoca::Metrics.default_client_config[:sub_server_name],
23
+ namespace: nil)
24
+ config = {
25
+ hostname: statsd_host || STATSD_DEFAULT_HOSTNAME,
26
+ port: statsd_port || STATSD_DEFAULT_PORT,
27
+ cluster_name: cluster_name,
28
+ service_name: service_name,
29
+ server_label: server_name,
30
+ sub_server_name: sub_server_name,
31
+ namespace: namespace
32
+ }.freeze
33
+
34
+ client_cache[config] ||= new(**config)
35
+ end
36
+
37
+ def reset_cache
38
+ @client_cache = {}
39
+ end
40
+
41
+ private
42
+
43
+ def client_cache
44
+ @client_cache ||= {}
45
+ end
46
+ end
47
+
48
+ attr_reader :hostname, :port, :server_label, :sub_server_name, :cluster_name, :service_name, :gauge_cache
49
+ delegate :batch_size, :namespace, :timing, :time, to: :statsd_client
50
+
51
+ def initialize(hostname:, port:, cluster_name: nil, service_name: nil, server_label: nil, sub_server_name: nil, namespace: nil)
52
+ @hostname = hostname
53
+ @port = port
54
+ @cluster_name = cluster_name
55
+ @service_name = service_name
56
+ @server_label = server_label
57
+ @sub_server_name = sub_server_name
58
+
59
+ @statsd_client = StatsdClient.new(@hostname, @port)
60
+ @statsd_client.namespace = namespace || [@cluster_name, @service_name].compact.join(STATSD_METRICS_SEPARATOR).presence
61
+
62
+ @gauge_cache = GaugeCache.register(gauge_cache_key, @statsd_client)
63
+ end
64
+
65
+ def gauge_cache_key
66
+ [
67
+ hostname,
68
+ port,
69
+ cluster_name,
70
+ service_name,
71
+ namespace,
72
+ server_name,
73
+ sub_server_name
74
+ ].freeze
75
+ end
76
+
77
+ def server_name # For backwards compatibility
78
+ server_label
79
+ end
80
+
81
+ # This will store the gauge value passed in so that it is reported every GAUGE_REPORT_INTERVAL
82
+ # seconds and post the gauge at the same time to avoid delay in gauges being
83
+ def gauge(name, value)
84
+ log_usage(name, :gauge)
85
+ if (args = normalized_metric_name_and_value(name, value, "gauge"))
86
+ gauge_cache.set(*args)
87
+ statsd_client.gauge(*args)
88
+ end
89
+ end
90
+
91
+ def count(name, value = 1)
92
+ log_usage(name, :counter)
93
+ if (args = normalized_metric_name_and_value(name, value, "counter"))
94
+ statsd_client.count(*args)
95
+ end
96
+ end
97
+
98
+ alias counter count
99
+
100
+ def increment(name)
101
+ count(name, 1)
102
+ end
103
+
104
+ def decrement(name)
105
+ count(name, -1)
106
+ end
107
+
108
+ def set(name, value)
109
+ log_usage(name, :counter)
110
+ if (args = normalized_metric_name_and_value(name, value, nil))
111
+ statsd_client.set(*args)
112
+ end
113
+ end
114
+
115
+ def timer(name, milliseconds = nil, return_timing: false, &block)
116
+ name.present? or raise ArgumentError, "Must specify a metric name."
117
+ (!milliseconds.nil? ^ block_given?) or raise ArgumentError, "Must pass exactly one of milliseconds or block."
118
+ name_and_type = [name, "timer", server_label].join(STATSD_METRICS_SEPARATOR)
119
+ log_usage(name, :timer)
120
+
121
+ if milliseconds.nil?
122
+ result, block_time = time(name_and_type, &block)
123
+ return_timing ? [result, block_time] : result
124
+ else
125
+ timing(name_and_type, milliseconds)
126
+ end
127
+ end
128
+
129
+ def batch(&block)
130
+ statsd_client.batch do |batch|
131
+ Metrics::Batch.new(self, batch).ensure_send(&block)
132
+ end
133
+ end
134
+
135
+ # TODO: - implement transmit method
136
+ def transmit(message, extra_data = {})
137
+ # TODO: - we need to wire up exception data to a monitoring service
138
+ end
139
+
140
+ private
141
+
142
+ attr_reader :statsd_client
143
+
144
+ def normalized_metric_name_and_value(name, value, stat_type)
145
+ name.present? or raise ArgumentError, "Must specify a metric name."
146
+ extended_name = [name, stat_type, @server_label, @sub_server_name].compact.join(STATSD_METRICS_SEPARATOR)
147
+ if value
148
+ [extended_name, value]
149
+ else
150
+ [extended_name]
151
+ end
152
+ end
153
+
154
+ def log_usage(metric_name, metric_type)
155
+ if Invoca::Metrics.graphite_usage_logging_enabled
156
+ call_stack = caller_locations
157
+ unless call_stack.any? { |l| /invoca\/metrics\/prometheus/.match?(l.to_s) }
158
+ self.class.logger&.info(
159
+ "Deprecated usage of grapihite metrics",
160
+ invoca_metrics: {
161
+ metric_name: metric_name,
162
+ metric_type: metric_type,
163
+ metric_source: call_stack.find { |l| !/invoca\/metrics/.match?(l.to_s) }.to_s
164
+ }
165
+ )
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Invoca
4
+ module Metrics
5
+ # Directly reports metrics without sending through graphite. Does not add process information to metric names.
6
+ class DirectMetric
7
+ attr_reader :name, :value, :tick
8
+
9
+ def initialize(name, value, tick = nil)
10
+ @name = name
11
+ @value = value
12
+ @tick = tick || self.class.rounded_tick
13
+ end
14
+
15
+ def to_s
16
+ "#{name} #{value} #{tick}"
17
+ end
18
+
19
+ PERIOD = 60
20
+ DEFAULT_PORT = 2003
21
+ DEFAULT_HOST = '127.0.0.1'
22
+
23
+ class << self
24
+ def report(metrics)
25
+ metrics_output = Array(metrics).map { |metric| "#{metric}\n" }.join
26
+
27
+ send_to_metric_host(metrics_output)
28
+ end
29
+
30
+ def generate_distribution(metric_prefix, metric_values, tick = nil)
31
+ fixed_tick = tick || rounded_tick
32
+ sorted_values = metric_values.sort
33
+ count = sorted_values.count
34
+
35
+ if count == 0
36
+ [
37
+ new("#{metric_prefix}.count", count, fixed_tick)
38
+ ]
39
+ else
40
+ [
41
+ new("#{metric_prefix}.count", count, fixed_tick),
42
+ new("#{metric_prefix}.max", sorted_values[-1], fixed_tick),
43
+ new("#{metric_prefix}.min", sorted_values[0], fixed_tick),
44
+ new("#{metric_prefix}.median", sorted_values[count * 0.5], fixed_tick),
45
+ new("#{metric_prefix}.upper_90", sorted_values[count * 0.9], fixed_tick)
46
+ ]
47
+ end
48
+ end
49
+
50
+ def rounded_tick
51
+ tick = Time.now.to_i
52
+ tick - (tick % PERIOD)
53
+ end
54
+
55
+ private
56
+
57
+ def send_to_metric_host(message)
58
+ host = ENV["DIRECT_METRIC_HOST"] || DEFAULT_HOST
59
+ port = (ENV["DIRECT_METRIC_PORT"] || DEFAULT_PORT).to_i
60
+
61
+ TCPSocket.open(host, port) do |tcp|
62
+ tcp.send(message, 0)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Invoca
4
+ module Metrics
5
+ class GaugeCache
6
+ GAUGE_REPORT_INTERVAL = 60.seconds
7
+
8
+ class << self
9
+ def register(cache_key, statsd_client)
10
+ registered_gauge_caches[cache_key] ||= new(statsd_client)
11
+ end
12
+
13
+ def reset
14
+ @registered_gauge_caches = {}
15
+ end
16
+
17
+ private
18
+
19
+ def registered_gauge_caches
20
+ @registered_gauge_caches ||= {}
21
+ end
22
+ end
23
+
24
+ attr_reader :cache
25
+
26
+ def initialize(statsd_client)
27
+ @statsd_client = statsd_client
28
+ @cache = {}
29
+ start_reporting_thread
30
+ end
31
+
32
+ # Atomic method for setting the value for a particular gauge
33
+ def set(metric, value)
34
+ @cache[metric] = value
35
+ end
36
+
37
+ # Reports all gauges that have been set in the cache
38
+ # To avoid "RuntimeError: can't add a new key into hash during iteration" from occurring we are
39
+ # temporarily duplicating the cache to iterate and send the batch of metrics
40
+ def report
41
+ @statsd_client.batch do |statsd_batch|
42
+ @cache.dup.each do |metric, value|
43
+ statsd_batch.gauge(metric, value) if value
44
+ end
45
+ end
46
+ end
47
+
48
+ def service_environment
49
+ @service_environment ||= ENV['RAILS_ENV'].presence || ENV['SERVICE_ENV'].presence || 'development'
50
+ end
51
+
52
+ private
53
+
54
+ def start_reporting_thread
55
+ Thread.new do
56
+ reporting_loop_with_rescue
57
+ end
58
+ end
59
+
60
+ def reporting_loop_with_rescue
61
+ reporting_loop
62
+ rescue Exception => ex
63
+ Invoca::Metrics::Client.logger.error("GaugeCache#reporting_loop_with_rescue rescued exception:\n#{ex.class}: #{ex.message}")
64
+ end
65
+
66
+ def reporting_loop
67
+ next_time = Time.now.to_i
68
+ loop do
69
+ next_time = (((next_time + GAUGE_REPORT_INTERVAL + 1) / GAUGE_REPORT_INTERVAL) * GAUGE_REPORT_INTERVAL) - 1
70
+ report
71
+ if (delay = next_time - Time.now.to_i) > 0
72
+ sleep(delay)
73
+ else
74
+ warn("Window to report gauge may have been missed.") unless service_environment == 'test'
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prometheus_exporter'
4
+ require 'prometheus_exporter/server'
5
+ require 'prometheus_exporter/client'
6
+
7
+ module Invoca
8
+ module Metrics
9
+ module Prometheus
10
+ class Configuration
11
+ AVAILABLE_MODES = [:direct, :push].freeze
12
+ AVAILABLE_METRIC_TYPES = [:counter, :gauge, :histogram].freeze
13
+
14
+ # The mode to use for exporting Prometheus metrics.
15
+ # Valid modes are:
16
+ # - `:direct` (default): Exports metrics directly using a threaded webserver.
17
+ # - `:push`: Exports metrics using a pushgateway.
18
+ #
19
+ # @return [Symbol]
20
+ attr_reader :mode
21
+
22
+ # The host to use for the Prometheus server. This setting
23
+ # is only used when in :push mode. Defaults to `localhost`.
24
+ #
25
+ # @return [String]
26
+ attr_accessor :host
27
+
28
+ # The port to use when exporting metrics. When in :direct mode
29
+ # this is used to specify the port to listen on. When in :push
30
+ # mode this is used to specify the port to push to. Defaults to 9394.
31
+ #
32
+ # @return [Integer]
33
+ attr_accessor :port
34
+
35
+ # The client to use for exporting Prometheus metrics.
36
+ #
37
+ # @return [PrometheusExporter::Client]
38
+ attr_reader :client
39
+
40
+ # The webserver that is exposed when in :direct mode.
41
+ #
42
+ # @return [PrometheusExporter::Server::WebServer]
43
+ attr_reader :server
44
+
45
+ def initialize
46
+ @host = 'localhost'
47
+ @port = 9394
48
+ end
49
+
50
+ def mode=(new_mode)
51
+ AVAILABLE_MODES.include?(new_mode) or raise ArgumentError, "Invalid mode #{new_mode.inspect}. Allowed modes: #{AVAILABLE_MODES}"
52
+ @mode = new_mode
53
+ end
54
+
55
+ # Initializes the client to use for exporting Prometheus metrics and
56
+ # freezes the configuration object so no more changes can be made to it.
57
+ def freeze
58
+ validate_configuration!
59
+
60
+ case mode
61
+ when :direct
62
+ initialize_direct_client
63
+ when :push
64
+ initialize_push_client
65
+ end
66
+
67
+ PrometheusExporter::Client.default = @client
68
+
69
+ super
70
+ end
71
+
72
+ # Registers a metric against the current Configuration. Once the metric object
73
+ # is registered, metrics can be reported and exported through the metric object.
74
+ #
75
+ # @param type [Symbol] The type of metric to register. Valid types are: :counter, :gauge, :histogram
76
+ # @param name [String] The name of the metric.
77
+ # @param help [String] The help text for the metric.
78
+ # @param opts [Hash] A hash of options to pass to the metric object. This is different depending on the
79
+ # type of metrics being registered.
80
+ #
81
+ # @return metric [PrometheusExporter::Metric]
82
+ def register_metric(type, name, help, opts = nil)
83
+ AVAILABLE_METRIC_TYPES.include?(type) or raise ArgumentError, "Invalid metric type #{type.inspect}. Allowed types: #{AVAILABLE_METRIC_TYPES}"
84
+ client.register(type, name, help, opts)
85
+ end
86
+
87
+ private
88
+
89
+ def initialize_direct_client
90
+ @server = PrometheusExporter::Server::WebServer.new(bind: '0.0.0.0', port: port)
91
+ @server.start
92
+ @client = PrometheusExporter::LocalClient.new(collector: @server.collector)
93
+ end
94
+
95
+ def initialize_push_client
96
+ @client = PrometheusExporter::Client.new(host: host, port: port)
97
+ end
98
+
99
+ def validate_configuration!
100
+ mode.present? or raise ArgumentError, "Mode must be provided. Valid modes: #{AVAILABLE_MODES}"
101
+ port.present? or raise ArgumentError, "Port must be provided when in #{mode.inspect} mode"
102
+ if mode == :push
103
+ host.present? or raise ArgumentError, "Host must be provided when in #{mode.inspect} mode"
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Invoca
4
+ module Metrics
5
+ module Prometheus
6
+ module DeclareMetrics
7
+ class Base
8
+
9
+ attr_reader :name, :type, :source, :metric, :default_labels, :graphite
10
+
11
+ def initialize(name, type, source, metric, labels:, graphite:)
12
+ @name = name
13
+ @type = type
14
+ @source = source
15
+ @metric = metric
16
+ @default_labels = labels
17
+ @graphite = graphite
18
+
19
+ if labels
20
+ labels.is_a?(Hash) or raise ArgumentError, "Labels must be type Hash but got #{labels.inspect}"
21
+ end
22
+ end
23
+
24
+ # TODO: Could default_labels be renamed to labels?
25
+ def settings
26
+ { type: type, labels: default_labels, graphite: graphite }
27
+ end
28
+
29
+ # TODO: This is temporary; the invoca-metrics metrics objects
30
+ # should be made immutable, in which case the entire object
31
+ # would be replaced rather than resetting the Prometheus metric
32
+ # object as is done here
33
+ def reset_metric(new_metric)
34
+ @metric = new_metric
35
+ end
36
+
37
+ private
38
+
39
+ def render_graphite_string(**labels)
40
+ rendered_string = graphite.dup
41
+ labels.map do |key, value|
42
+ rendered_string.gsub!(":#{key}", value.to_s)
43
+ end
44
+
45
+ rendered_string.include?(':') and warn("Graphite string #{rendered_string} not fully rendered. Expecting additional label that has not been rendered.")
46
+ rendered_string
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Invoca
6
+ module Metrics
7
+ module Prometheus
8
+ module DeclareMetrics
9
+ class Counter < Base
10
+
11
+ def increment(value = 1, **labels)
12
+ if metric.present?
13
+ metric.increment(default_labels.merge(labels), value)
14
+ else
15
+ warn("Counter being incremented without metric being present")
16
+ end
17
+
18
+ if graphite
19
+ Invoca::Metrics::Client.metrics.count(render_graphite_string(**default_labels.merge(labels)), value)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../configuration'
4
+ require_relative 'counter'
5
+ require_relative 'gauge'
6
+ require_relative 'histogram'
7
+
8
+ module Invoca
9
+ module Metrics
10
+ module Prometheus
11
+ module DeclareMetrics
12
+ class Dsl
13
+
14
+ attr_reader :declared_metrics, :source, :prometheus_exporter_config
15
+
16
+ def initialize(source)
17
+ @source = source
18
+ @declared_metrics = []
19
+ if Invoca::Metrics::Prometheus.config_present?
20
+ @prometheus_exporter_config = Invoca::Metrics::Prometheus.config
21
+ end
22
+ end
23
+
24
+ def counter(name, labels: {}, graphite: nil, help: nil)
25
+ # TODO: We can drop the 'to_s' when we stop supporting ruby 2.6
26
+ if !name.to_s.end_with?("_total")
27
+ warn("#{source} counter #{name.inspect} should end in \"_total\" so that Grafana can tell that it is a counter.")
28
+ end
29
+ metric =
30
+ if Invoca::Metrics::Prometheus.config_present?
31
+ prometheus_exporter_config.register_metric(:counter, name, help, { labels: labels, graphite: graphite })
32
+ end
33
+ add_metric(Counter.new(name, :counter, source, metric, labels: labels, graphite: graphite))
34
+ end
35
+
36
+ def histogram(name, buckets:, labels: {}, graphite: nil, help: nil)
37
+ metric =
38
+ if Invoca::Metrics::Prometheus.config_present?
39
+ prometheus_exporter_config.register_metric(:histogram, name, help, { labels: labels, graphite: graphite, buckets: buckets })
40
+ end
41
+ add_metric(Histogram.new(name, :histogram, source, metric, buckets: buckets, labels: labels, graphite: graphite))
42
+ end
43
+
44
+ def gauge(name, labels: {}, graphite: nil, help: nil)
45
+ metric =
46
+ if Invoca::Metrics::Prometheus.config_present?
47
+ prometheus_exporter_config.register_metric(:gauge, name, help, { labels: labels, graphite: graphite })
48
+ end
49
+ add_metric(Gauge.new(name, :gauge, source, metric, labels: labels, graphite: graphite))
50
+ end
51
+
52
+ private
53
+
54
+ def add_metric(metric)
55
+ @declared_metrics << metric
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require 'active_support/deprecation'
5
+
6
+ module Invoca
7
+ module Metrics
8
+ module Prometheus
9
+ module DeclareMetrics
10
+ class Gauge < Base
11
+
12
+ def set(value = :'1', **labels)
13
+ if value == :'1'
14
+ value = 1
15
+ ActiveSupport::Deprecation.warn("gauge default value of 1 is deprecated; please pass an explicit value")
16
+ end
17
+
18
+ if metric.present?
19
+ metric.observe(value, default_labels.merge(labels))
20
+ else
21
+ warn("Gauge being set without metric being present")
22
+ end
23
+
24
+ if graphite
25
+ Invoca::Metrics::Client.metrics.gauge(render_graphite_string(**default_labels.merge(labels)), value)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require 'benchmark'
5
+ require 'active_support/deprecation'
6
+
7
+ # TODO: Histogram could override Base#settings to include buckets
8
+ module Invoca
9
+ module Metrics
10
+ module Prometheus
11
+ module DeclareMetrics
12
+ class Histogram < Base
13
+
14
+ attr_reader :buckets
15
+
16
+ def initialize(name, type, source, metric, buckets:, labels:, graphite:)
17
+ @buckets = buckets
18
+ super(name, type, source, metric, labels: labels, graphite: graphite)
19
+ end
20
+
21
+ def add(value = :'1', **labels)
22
+ if value == :'1'
23
+ value = 1
24
+ ActiveSupport::Deprecation.warn("histogram default value of 1 is deprecated; please pass an explicit value")
25
+ end
26
+
27
+ if metric.present?
28
+ metric.observe(value, default_labels.merge(labels)) if metric.present?
29
+ else
30
+ warn("Histogram being used without metric being present")
31
+ end
32
+
33
+ if graphite
34
+ Invoca::Metrics::Client.metrics.timer(render_graphite_string(**default_labels.merge(labels)), value)
35
+ end
36
+ end
37
+
38
+ def time(return_timing: false, **labels)
39
+ result = nil
40
+ elapsed_time = Benchmark.realtime { result = yield }
41
+ elapsed_time_ms = (elapsed_time * 1000).to_i
42
+ add(elapsed_time_ms, **labels)
43
+
44
+ return_timing ? [result, elapsed_time_ms] : result
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Invoca
4
+ module Metrics
5
+ module Prometheus
6
+ class MetricsRegistry
7
+ class ContradictoryMetricRegistration < StandardError; end
8
+
9
+ attr_reader :metrics
10
+
11
+ def initialize
12
+ @metrics = {}
13
+ end
14
+
15
+ def register(metric)
16
+ validate_metric_name!(metric)
17
+ @metrics[metric.name] = metric
18
+ end
19
+
20
+ def method_missing(method_name, *args)
21
+ metrics[method_name] || super
22
+ end
23
+
24
+ def respond_to?(method_name, *args)
25
+ @metrics[method_name] || super
26
+ end
27
+
28
+ def respond_to_missing?(method_name, include_private = false)
29
+ @metrics[method_name] || super
30
+ end
31
+
32
+ private
33
+
34
+ def validate_metric_name!(metric)
35
+ if (existing = @metrics[metric.name])
36
+ metric.settings == existing.settings or
37
+ raise ContradictoryMetricRegistration, "metric #{metric.name} already registered by #{existing.source} with contradictory settings #{existing.settings} vs new registered #{metric.source} with setting #{metric.settings}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './prometheus/configuration'
4
+ require_relative './prometheus/metrics_registry'
5
+
6
+ module Invoca
7
+ module Metrics
8
+ module Prometheus
9
+ class NotConfiguredError < StandardError; end
10
+
11
+ class << self
12
+ # This method is used to configure Invoca::Metrics to export metrics to be
13
+ # pulled into Prometheus.
14
+ #
15
+ # @yield [Invoca::Metrics::Prometheus::Configuration]
16
+ #
17
+ # @void
18
+ def configure
19
+ @config = Invoca::Metrics::Prometheus::Configuration.new.tap { |config| yield config }.freeze
20
+
21
+ # If there are any metrics at this point, it's likely that Prometheus metrics
22
+ # were declared before invoca-metrics was loaded; walk the set of metrics in the
23
+ # registry, resetting their PrometheusExporter::Metric objects
24
+ metrics.metrics.values.each do |metric|
25
+ case metric.type
26
+ when :counter
27
+ metric.reset_metric(@config.register_metric(:counter, metric.name, nil, { labels: metric.default_labels, graphite: metric.graphite }))
28
+ when :gauge
29
+ metric.reset_metric(@config.register_metric(:gauge, metric.name, nil, { labels: metric.default_labels, graphite: metric.graphite }))
30
+ when :histogram
31
+ metric.reset_metric(@config.register_metric(:histogram, metric.name, nil, { labels: metric.default_labels, graphite: metric.graphite, buckets: metric.buckets }))
32
+ else
33
+ warn("Metric type #{metric.type} not one of #{Invoca::Metrics::Prometheus::Configuration::AVAILABLE_METRIC_TYPES}.")
34
+ end
35
+ end
36
+
37
+ @config
38
+ end
39
+
40
+ # Accessor for the existing Prometheus configuration
41
+ #
42
+ # @raise [Invoca::Metrics::Prometheus::NotConfiguredError]
43
+ # @return [Invoca::Metrics::Prometheus::Configuration]
44
+ def config
45
+ @config or raise NotConfiguredError, 'Invoca::Metrics::Prometheus is trying to be used without being configured'
46
+ end
47
+
48
+ # Helper method for checking if configuration is present
49
+ #
50
+ # @return [Boolean]
51
+ def config_present?
52
+ @config.present?
53
+ end
54
+
55
+ # TODO: Rename metrics -> registry so that we don't have things like metrics.metrics
56
+ # (see the configure method above)
57
+
58
+ # This method is used to access the global list of all metrics that were registered
59
+ #
60
+ # @return [Invoca::Metrics::Prometheus::MetricsRegistry]
61
+ def metrics
62
+ @metrics ||= MetricsRegistry.new
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'statsd'
4
+
5
+ module Invoca
6
+ module Metrics
7
+ class StatsdClient < ::Statsd
8
+ MILLISECONDS_IN_SECOND = 1000
9
+
10
+ @log_send_failures = true
11
+
12
+ class << self
13
+ attr_accessor :log_send_failures
14
+ end
15
+
16
+ def time(stat, sample_rate = 1)
17
+ start = Time.now
18
+ result = yield
19
+ length_of_time = ((Time.now - start) * MILLISECONDS_IN_SECOND).round
20
+ timing(stat, length_of_time, sample_rate)
21
+ [result, length_of_time]
22
+ end
23
+
24
+ def send_to_socket(message)
25
+ self.class.logger&.debug { "Statsd: #{message}" }
26
+ socket.send(message, 0)
27
+ rescue => ex
28
+ if self.class.log_send_failures
29
+ self.class.logger&.error { "Statsd exception sending: #{ex.class}: #{ex}" }
30
+ end
31
+
32
+ nil
33
+ end
34
+
35
+ private
36
+
37
+ # Socket connection should be Thread local and not Fiber local
38
+ # Can't use `Thread.current[:statsd_client] ||=` because that will be fiber-local as well.
39
+ def socket
40
+ Thread.current.thread_variable_get(:statsd_socket) || Thread.current.thread_variable_set(:statsd_socket, new_socket)
41
+ end
42
+
43
+ def new_socket
44
+ self.class.logger&.info { "Statsd client connection info -- [hostname: #{@host}, port: #{@port}]" }
45
+ UDPSocket.new.tap { |udp| udp.connect(@host, @port) }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Invoca
4
+ module Metrics
5
+ VERSION = "1.14.1"
6
+ end
7
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
5
+
6
+ require "invoca/metrics/version"
7
+ require "invoca/metrics/statsd_client"
8
+ require "invoca/metrics/client"
9
+ require "invoca/metrics/direct_metric"
10
+ require "invoca/metrics/batch"
11
+ require "invoca/metrics/gauge_cache"
12
+ require "invoca/metrics/prometheus"
13
+ require "invoca/metrics/prometheus/declare_metrics/dsl"
14
+
15
+ module Invoca
16
+ module Metrics
17
+ CONFIG_FIELDS = [:service_name, :server_name, :sub_server_name, :cluster_name, :statsd_host, :statsd_port].freeze
18
+
19
+ class << self
20
+ attr_accessor(*(CONFIG_FIELDS - [:service_name]), :default_config_key)
21
+ attr_writer :service_name, :graphite_usage_logging_enabled
22
+
23
+ def graphite_usage_logging_enabled
24
+ if @graphite_usage_logging_enabled.nil?
25
+ @graphite_usage_logging_enabled = true
26
+ end
27
+ @graphite_usage_logging_enabled
28
+ end
29
+
30
+ def service_name
31
+ @service_name or raise ArgumentError, "You must assign a value to Invoca::Metrics.service_name"
32
+ end
33
+
34
+ def initialized?
35
+ @service_name
36
+ end
37
+
38
+ def config
39
+ @config ||= {}
40
+ end
41
+
42
+ def config=(config_hash)
43
+ config_valid?(config_hash) or raise ArgumentError, "Invalid config #{config_hash}. Allowed fields for config key: #{CONFIG_FIELDS}."
44
+ @config = config_hash
45
+ end
46
+
47
+ def default_client_config
48
+ {
49
+ service_name: Invoca::Metrics.service_name,
50
+ server_name: Invoca::Metrics.server_name,
51
+ cluster_name: Invoca::Metrics.cluster_name,
52
+ statsd_host: Invoca::Metrics.statsd_host,
53
+ statsd_port: Invoca::Metrics.statsd_port,
54
+ sub_server_name: Invoca::Metrics.sub_server_name
55
+ }.merge(config[default_config_key] || {})
56
+ end
57
+
58
+ private
59
+
60
+ def config_valid?(config_hash)
61
+ config_hash.nil? || config_hash.all? { |_config_key, config_key_hash| (config_key_hash.keys - CONFIG_FIELDS).empty? }
62
+ end
63
+ end
64
+
65
+ # mix this module into your classes that need to send metrics
66
+ #
67
+ module Source
68
+ extend ActiveSupport::Concern
69
+
70
+ module ClassMethods
71
+ @metrics_namespace = nil
72
+
73
+ def metrics_namespace(namespace)
74
+ @metrics_namespace = namespace
75
+ end
76
+
77
+ def metrics
78
+ metrics_for(config_key: Invoca::Metrics.default_config_key)
79
+ end
80
+
81
+ def metrics_for(config_key:, namespace: nil)
82
+ config_from_key = Invoca::Metrics.config[config_key] || {}
83
+ metrics_config = if (effective_namespace = namespace || @metrics_namespace)
84
+ config_from_key.merge(namespace: effective_namespace)
85
+ else
86
+ config_from_key
87
+ end
88
+ Client.metrics(**Invoca::Metrics.default_client_config.merge(metrics_config))
89
+ end
90
+
91
+ def declare_metrics(&block)
92
+ dsl = Invoca::Metrics::Prometheus::DeclareMetrics::Dsl.new(self)
93
+ dsl.instance_eval(&block)
94
+ dsl.declared_metrics.each do |metric|
95
+ Invoca::Metrics::Prometheus.metrics.register(metric)
96
+ end
97
+ end
98
+
99
+ def prometheus_metrics
100
+ Invoca::Metrics::Prometheus.metrics
101
+ end
102
+ end
103
+
104
+ def prometheus_metrics
105
+ self.class.prometheus_metrics
106
+ end
107
+
108
+ def metrics
109
+ self.class.metrics
110
+ end
111
+
112
+ def metrics_for(config_key:, namespace: nil)
113
+ self.class.metrics_for(config_key: config_key, namespace: namespace)
114
+ end
115
+ end
116
+ end
117
+ end
metadata CHANGED
@@ -1,24 +1,105 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: invoca-metrics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 1.14.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoca development
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-18 00:00:00.000000000 Z
12
- dependencies: []
13
- description:
11
+ date: 2022-09-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.2'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '7'
33
+ - !ruby/object:Gem::Dependency
34
+ name: statsd-ruby
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 1.2.1
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 1.2.1
47
+ - !ruby/object:Gem::Dependency
48
+ name: prometheus_exporter
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: contextual_logger
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.0'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.0'
75
+ description: Invoca metrics reporting library
14
76
  email:
15
77
  - development@invoca.com
16
78
  executables: []
17
79
  extensions: []
18
80
  extra_rdoc_files: []
19
- files: []
20
- homepage:
21
- licenses: []
81
+ files:
82
+ - LICENSE.txt
83
+ - README.md
84
+ - invoca-metrics.gemspec
85
+ - lib/invoca/metrics.rb
86
+ - lib/invoca/metrics/batch.rb
87
+ - lib/invoca/metrics/client.rb
88
+ - lib/invoca/metrics/direct_metric.rb
89
+ - lib/invoca/metrics/gauge_cache.rb
90
+ - lib/invoca/metrics/prometheus.rb
91
+ - lib/invoca/metrics/prometheus/configuration.rb
92
+ - lib/invoca/metrics/prometheus/declare_metrics/base.rb
93
+ - lib/invoca/metrics/prometheus/declare_metrics/counter.rb
94
+ - lib/invoca/metrics/prometheus/declare_metrics/dsl.rb
95
+ - lib/invoca/metrics/prometheus/declare_metrics/gauge.rb
96
+ - lib/invoca/metrics/prometheus/declare_metrics/histogram.rb
97
+ - lib/invoca/metrics/prometheus/metrics_registry.rb
98
+ - lib/invoca/metrics/statsd_client.rb
99
+ - lib/invoca/metrics/version.rb
100
+ homepage: https://github.com/Invoca/invoca-metrics
101
+ licenses:
102
+ - MIT
22
103
  metadata:
23
104
  allowed_push_host: https://rubygems.org
24
105
  post_install_message:
@@ -39,5 +120,5 @@ requirements: []
39
120
  rubygems_version: 3.1.6
40
121
  signing_key:
41
122
  specification_version: 4
42
- summary: Invoca metrics
123
+ summary: Invoca metrics reporting library
43
124
  test_files: []