metrics-0.15.0/0000755000004100000410000000000015150627442013301 5ustar www-datawww-datametrics-0.15.0/lib/0000755000004100000410000000000015150627442014047 5ustar www-datawww-datametrics-0.15.0/lib/metrics.rb0000644000004100000410000000044715150627442016047 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2025, by Samuel Williams. require_relative "metrics/version" require_relative "metrics/provider" require_relative "metrics/tags" # @namespace module Metrics if self.enabled? Config::DEFAULT.prepare end end metrics-0.15.0/lib/metrics/0000755000004100000410000000000015150627442015515 5ustar www-datawww-datametrics-0.15.0/lib/metrics/tags.rb0000644000004100000410000000064515150627442017005 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2024, by Samuel Williams. module Metrics module Tags def self.normalize(tags, into = nil) return into unless tags&.any? into ||= [] if tags.is_a?(Array) into.concat(tags) else tags.each do |key, value| if value into << "#{key}:#{value}" end end end return into end end end metrics-0.15.0/lib/metrics/config.rb0000644000004100000410000000244115150627442017310 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2024-2025, by Samuel Williams. module Metrics # Represents a configuration for the metrics library. class Config DEFAULT_PATH = ENV.fetch("METRICS_CONFIG_DEFAULT_PATH", "config/metrics.rb") # Load the configuration from the given path. # @parameter path [String] The path to the configuration file. # @returns [Config] The loaded configuration. def self.load(path) config = self.new if File.exist?(path) config.instance_eval(File.read(path), path) end return config end # Load the default configuration. # @returns [Config] The default configuration. def self.default @default ||= self.load(DEFAULT_PATH) end # Prepare the backend, e.g. by loading additional libraries or instrumentation. def prepare end # Require a specific metrics backend implementation. def require_backend(env = ENV) if backend = env["METRICS_BACKEND"] begin if require(backend) Metrics.singleton_class.prepend(Backend::Interface) return true end rescue LoadError => error warn "Unable to load metrics backend: #{backend.inspect}!" end end return false end # Load the default configuration. DEFAULT = self.default end end metrics-0.15.0/lib/metrics/backend.rb0000644000004100000410000000032415150627442017430 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2024, by Samuel Williams. require_relative "config" module Metrics module Backend end Config::DEFAULT.require_backend end metrics-0.15.0/lib/metrics/provider.rb0000644000004100000410000000156015150627442017676 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2025, by Samuel Williams. require_relative "backend" module Metrics # @returns [Boolean] Whether there is an active backend. def self.enabled? Backend.const_defined?(:Interface) end module Provider end # A module which contains tracing specific wrappers. module Singleton def metrics_provider @metrics_provider ||= Module.new end end private_constant :Singleton # Bail out if there is no backend configured. if self.enabled? # Extend the specified class in order to emit traces. def self.Provider(klass, &block) klass.extend(Singleton) provider = klass.metrics_provider klass.prepend(provider) provider.module_exec(&block) if block_given? return provider end else def self.Provider(klass, &block) # Metrics disabled. end end end metrics-0.15.0/lib/metrics/version.rb0000644000004100000410000000022515150627442017526 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2025, by Samuel Williams. module Metrics VERSION = "0.15.0" end metrics-0.15.0/lib/metrics/backend/0000755000004100000410000000000015150627442017104 5ustar www-datawww-datametrics-0.15.0/lib/metrics/backend/capture.rb0000644000004100000410000000221215150627442021071 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2023-2024, by Samuel Williams. require_relative "../metric" module Metrics module Backend module Capture class Metric < Metrics::Metric def initialize(...) super @values = [] @tags = Set.new @sample_rates = [] end attr :values attr :tags attr :sample_rates def emit(value, tags: nil, sample_rate: 1.0) @values << value @tags.merge(tags) if tags @sample_rates << sample_rate end def as_json { name: @name, type: @type, description: @description, unit: @unit, values: @values, tags: @tags.to_a.sort, sample_rates: @sample_rates.sort.uniq } end def to_json(...) as_json.to_json(...) end end def self.metrics @metrics ||= [] end module Interface def metric(name, type, description: nil, unit: nil, &block) metric = Metric.new(name, type, description, unit) Capture.metrics << metric return metric end end end Interface = Capture::Interface end end metrics-0.15.0/lib/metrics/backend/test.rb0000644000004100000410000000361515150627442020415 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2024, by Samuel Williams. require_relative "../metric" module Metrics module Backend module Test VALID_METRIC_NAME = /\A[a-z0-9\-_\.]{1,128}\Z/i VALID_TAG = /\A[a-z][a-z0-9\-_\.:\\]{0,127}\Z/i class Metric < Metrics::Metric def emit(value, tags: nil, sample_rate: 1.0) unless value.is_a?(Numeric) raise ArgumentError, "Value must be numeric!" end tags&.each do |tag| raise ArgumentError, "Invalid tag (must be String): #{tag.inspect}!" unless tag.is_a?(String) # We should let the underlying backend handle any tag limitations, e.g. converting invalid characters to underscores, etc. # # unless tag =~ VALID_TAG # raise ArgumentError, "Invalid tag (must match #{VALID_TAG}): #{tag.inspect}!" # end end end end module Interface def metric(name, type, description: nil, unit: nil, &block) unless name.is_a?(String) raise ArgumentError, "Invalid name (must be String): #{name.inspect}!" end unless name =~ VALID_METRIC_NAME raise ArgumentError, "Invalid name (must match #{VALID_METRIC_NAME}): #{name.inspect}!" end unless type.is_a?(Symbol) raise ArgumentError, "Invalid type (must be Symbol): #{type.inspect}!" end # Description is optional but must be string if given: if description unless description.is_a?(String) raise ArgumentError, "Invalid description (must be String): #{description.inspect}!" end end # Unit is optional but must be string if given: if unit unless unit.is_a?(String) raise ArgumentError, "Invalid unit (must be String): #{unit.inspect}!" end end return Metric.new(name, type, description, unit) end end end Interface = Test::Interface end end metrics-0.15.0/lib/metrics/backend/console.rb0000644000004100000410000000140115150627442021067 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2024, by Samuel Williams. require_relative "../metric" require "console" module Metrics module Backend module Console class Metric < Metrics::Metric def emit(value, tags: nil, sample_rate: 1.0) ::Console.logger.info(self, @name, value, tags) end end module Interface def metric(name, type, description: nil, unit: nil, &block) return Metric.new(name, type, description, unit) end # def metric_call_counter(name, description: nil, tags: nil) # metric = self.metric(...) # # self.define_method(name) do # metric.emit(1) # super # end # end end end Interface = Console::Interface end end metrics-0.15.0/lib/metrics/metric.rb0000644000004100000410000000065315150627442017331 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. module Metrics class Metric def initialize(name, type, description, unit) @name = name @type = type @description = description @unit = unit end attr :name attr :type attr :description attr :unit def emit(value, tags: nil, sample_rate: 1.0) raise NotImplementedError end end end metrics-0.15.0/checksums.yaml.gz.sig0000444000004100000410000000060015150627442017344 0ustar www-datawww-data0E2O^%6ԟazET{YWM8Jހy>3*rqDi(Wfn,250[rblș7:̡Uj:I|g+a|GM锍kɤCP4p\D]3ɛbO7Momiս?/͚ jɜh~N$WDL[Y][QKPՏ٧~mmetrics-0.15.0/metrics.gemspec0000644000004100000410000000645615150627442016327 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: metrics 0.15.0 ruby lib Gem::Specification.new do |s| s.name = "metrics".freeze s.version = "0.15.0".freeze s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "documentation_uri" => "https://socketry.github.io/metrics/", "source_code_uri" => "https://github.com/socketry/metrics.git" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Samuel Williams".freeze] s.cert_chain = ["-----BEGIN CERTIFICATE-----\nMIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11\nZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK\nCZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz\nMjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd\nMBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj\nbzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB\nigKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2\n9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW\nsGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE\ne5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN\nXibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss\nRZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn\ntUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM\nzp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW\nxm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O\nBBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs\naWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs\naWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE\ncBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl\nxCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/\nc1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp\n8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws\nJkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP\neX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt\nQ2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8\nvoD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=\n-----END CERTIFICATE-----\n".freeze] s.date = "1980-01-02" s.files = ["bake/metrics/capture.rb".freeze, "bake/metrics/provider.rb".freeze, "context/capture.md".freeze, "context/getting-started.md".freeze, "context/index.yaml".freeze, "context/testing.md".freeze, "lib/metrics.rb".freeze, "lib/metrics/backend.rb".freeze, "lib/metrics/backend/capture.rb".freeze, "lib/metrics/backend/console.rb".freeze, "lib/metrics/backend/test.rb".freeze, "lib/metrics/config.rb".freeze, "lib/metrics/metric.rb".freeze, "lib/metrics/provider.rb".freeze, "lib/metrics/tags.rb".freeze, "lib/metrics/version.rb".freeze, "license.md".freeze, "readme.md".freeze, "releases.md".freeze] s.homepage = "https://github.com/socketry/metrics".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 3.2".freeze) s.rubygems_version = "3.6.9".freeze s.summary = "Application metrics and instrumentation.".freeze end metrics-0.15.0/readme.md0000644000004100000410000000574715150627442015075 0ustar www-datawww-data# Metrics Capture metrics about code execution in a vendor agnostic way. As the author of many libraries which would benefit from metrics, there are few key priorities: (1) zero overhead if metrics are disabled, minimal overhead if enabled, and (2) a small and opinionated interface with standardised semantics. [![Development Status](https://github.com/socketry/metrics/workflows/Test/badge.svg)](https://github.com/socketry/metrics/actions?workflow=Test) ## Features - Zero-overhead if tracing is disabled and minimal overhead if enabled. - Small opinionated interface with standardised semantics. ## Usage Please see the [project documentation](https://socketry.github.io/metrics/) for more details. - [Getting Started](https://socketry.github.io/metrics/guides/getting-started/index) - This guide explains how to use `metrics` for capturing run-time metrics. - [Capture](https://socketry.github.io/metrics/guides/capture/index) - This guide explains how to use `metrics` for exporting metric definitions from your application. - [Testing](https://socketry.github.io/metrics/guides/testing/index) - This guide explains how to write assertions in your test suite to validate `metrics` are being emitted correctly. ## Releases Please see the [project releases](https://socketry.github.io/metrics/releases/index) for all releases. ### v0.15.0 - Add `into = nil` parameter to `Metrics::Tags.normalize(tags, into = nil)` to allow reusing an existing array. ### v0.14.0 - Don't call `prepare` in `metrics/provider.rb`. It can cause circular loading warnings. ### v0.13.0 - Introduce `metrics:provider:list` command to list all available metrics providers. ### v0.12.1 - [Introduce `Metrics::Config` to Expose `prepare` Hook](https://socketry.github.io/metrics/releases/index#introduce-metrics::config-to-expose-prepare-hook) ## Contributing We welcome contributions to this project. 1. Fork it. 2. Create your feature branch (`git checkout -b my-new-feature`). 3. Commit your changes (`git commit -am 'Add some feature'`). 4. Push to the branch (`git push origin my-new-feature`). 5. Create new Pull Request. ### Developer Certificate of Origin In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed. ### Community Guidelines This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers. ## See Also - [metrics-backend-datadog](https://github.com/socketry/metrics-backend-datadog) — A Metrics backend for Datadog. - [traces](https://github.com/socketry/traces) — A code tracing interface which follows a similar pattern. metrics-0.15.0/data.tar.gz.sig0000444000004100000410000000060015150627442016114 0ustar www-datawww-dataj=&YxoĻɔʼ >ة(SLEoLO⑴, aߪDWعjpv=U8;~I;x0;8ߣ 5sB.EixA:a ]ӽěv

m!10h[Zq}lrOD˽Fd?8{Fvoi莦+oT փE[%Ez3sNK-\M%4 3yk8NnR^{z^ѐ Lyƻ蝷c1u |52t?wLjÓ^X'A0tOLGMdLJ1]\;Xa[t=@6;y {Vgdhyb&ʨ3 8 4.FW $(F(`:b EN$<3X aj5@I8r;eOߓ !'N 25metrics-0.15.0/releases.md0000644000004100000410000000210315150627442015422 0ustar www-datawww-data# Releases ## v0.15.0 - Add `into = nil` parameter to `Metrics::Tags.normalize(tags, into = nil)` to allow reusing an existing array. ## v0.14.0 - Don't call `prepare` in `metrics/provider.rb`. It can cause circular loading warnings. ## v0.13.0 - Introduce `metrics:provider:list` command to list all available metrics providers. ## v0.12.1 ### Introduce `Metrics::Config` to Expose `prepare` Hook The `metrics` gem uses aspect-oriented programming to wrap existing methods to emit metrics. However, while there are some reasonable defaults for emitting metrics, it can be useful to customize the behavior and level of detail. To that end, the `metrics` gem now optionally loads a `config/metrics.rb` which includes a `prepare` hook that can be used to load additional providers. ``` ruby # config/metrics.rb def prepare require 'metrics/provider/async' require 'metrics/provider/async/http' end ``` The `prepare` method is called immediately after the metrics backend is loaded. You can require any provider you want in this file, or even add your own custom providers. metrics-0.15.0/context/0000755000004100000410000000000015150627442014765 5ustar www-datawww-datametrics-0.15.0/context/index.yaml0000644000004100000410000000152615150627442016764 0ustar www-datawww-data# Automatically generated context index for Utopia::Project guides. # Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`. --- description: Application metrics and instrumentation. metadata: documentation_uri: https://socketry.github.io/metrics/ source_code_uri: https://github.com/socketry/metrics.git files: - path: getting-started.md title: Getting Started description: This guide explains how to use `metrics` for capturing run-time metrics. - path: capture.md title: Capture description: This guide explains how to use `metrics` for exporting metric definitions from your application. - path: testing.md title: Testing description: This guide explains how to write assertions in your test suite to validate `metrics` are being emitted correctly. metrics-0.15.0/context/testing.md0000644000004100000410000000146415150627442016771 0ustar www-datawww-data# Testing This guide explains how to write assertions in your test suite to validate `metrics` are being emitted correctly. ## Application Code In your application code, you should emit metrics, e.g. ```ruby require 'metrics/provider' class MyApplication def work # ... end Metrics::Provider(self) do WORK_METRIC = Metrics.metric('my_application.work.count', :counter, description: 'Work counter') def work WORK_METRIC.emit(1) super end end end ``` ## Test Code In your test code, you should assert that the metrics are being emitted correctly, e.g. ```ruby ENV['METRICS_BACKEND'] ||= 'metrics/backend/test' require_relative 'app' describe MyApplication do it 'should emit metrics' do expect(MyApplication::WORK_METRIC).to receive(:emit).with(1) MyApplication.new.work end end ``` metrics-0.15.0/context/capture.md0000644000004100000410000000242015150627442016750 0ustar www-datawww-data# Capture This guide explains how to use `metrics` for exporting metric definitions from your application. ## With Provider Metrics If your application defines one or more metrics, you can export them using the `bake metrics:document` command. This command will generate a list of metrics which you can export. ```bash $ cd test/metrics/backend/.capture/ $ bake metrics:capture environment metrics:capture:list output --format json [ { "name": "my_metric", "type": "gauge", "description": "My metric", "unit": "seconds", "values": [ ], "tags": [ ], "sample_rates": [ ] } ] ``` ## With Test Suite If your application has a test suite which emits metrics, you can capture those as samples for the purpose of your documentation. This includes fields like tags. ```bash $ cd test/metrics/backend/.capture/ $ bake metrics:capture run metrics:capture:list output --format json [ { "name": "my_metric", "type": "gauge", "description": "My metric", "unit": "seconds", "values": [ 1 ], "tags": [ "environment:test" ], "sample_rates": [ 1.0 ] } ] ``` This uses a custom task called `run` in the above example, but you should probably consider using `bake test` which runs your test suite. metrics-0.15.0/context/getting-started.md0000644000004100000410000000413415150627442020416 0ustar www-datawww-data# Getting Started This guide explains how to use `metrics` for capturing run-time metrics. ## Installation Add the gem to your project: ~~~ bash $ bundle add metrics ~~~ ## Core Concepts `metrics` has several core concepts: - A {ruby Metrics::Provider} which implements custom logic for extracting metrics from existing code. - A {ruby Metrics::Backend} which connects metrics to a specific backend system for processing. ## Usage There are two main aspects to integrating within this gem. 1. Libraries and applications must expose metrics. 2. Those metrics must be consumed or emitted somewhere. ### Exposing Metrics Adding metrics to libraries requires the use of {ruby Metrics::Provider}: ~~~ ruby require 'metrics' class MyClass def my_method puts "Hello World" end end # If metrics are disabled, this is a no-op. Metrics::Provider(MyClass) do CALL_COUNT = Metrics.metric('call_count', :counter, description: 'Number of times invoked.') def my_method CALL_COUNT.emit(1) super end end MyClass.new.my_method ~~~ This code by itself will not create any metrics. In order to execute it and output metrics, you must set up a backend to consume them. #### Class Methods You can also expose metrics for class methods: ~~~ ruby require 'metrics' class MyClass def self.my_method puts "Hello World" end end Metrics::Provider(MyClass.singleton_class) do CALL_COUNT = Metrics.metric('call_count', :counter, description: 'Number of times invoked.') def my_method CALL_COUNT.emit(1) super end end MyClass.my_method ~~~ ### Consuming Metrics Consuming metrics means proving a backend implementation which can record those metrics to some log or service. There are several options, but two backends are included by default: - `metrics/backend/test` does not emit any metrics, but validates the usage of the metric interface. - `metrics/backend/console` emits metrics using the [`console`](https://github.com/socketry/console) gem. In order to use a specific backend, set the `METRICS_BACKEND` environment variable, e.g. ~~~ shell $ METRICS_BACKEND=metrics/backend/console ./my_script.rb ~~~