From 9b6d5afc9665b79c0795a763e0029b9fddea2348 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 6 Mar 2025 02:04:15 +1300 Subject: [PATCH] Initial implementation. --- .covered.db | Bin 0 -> 645 bytes .editorconfig | 9 + .github/workflows/documentation-coverage.yaml | 25 +++ .github/workflows/documentation.yaml | 58 +++++ .github/workflows/rubocop.yaml | 24 ++ .github/workflows/test-coverage.yaml | 59 +++++ .github/workflows/test-external.yaml | 37 +++ .github/workflows/test.yaml | 51 +++++ .gitignore | 5 + .rubocop.yml | 53 +++++ config/sus.rb | 9 + fixtures/metrics/backend/statsd/server.rb | 52 +++++ gems.locked | 212 ++++++++++++++++++ gems.rb | 26 +++ lib/metrics/backend/statsd.rb | 7 + lib/metrics/backend/statsd/interface.rb | 101 +++++++++ lib/metrics/backend/statsd/version.rb | 12 + license.md | 21 ++ metrics-backend-statsd-instruments.gemspec | 29 +++ pkg/metrics-backend-datadog-0.2.0.gem | Bin 0 -> 10752 bytes pkg/metrics-backend-datadog-0.2.1.gem | Bin 0 -> 10752 bytes readme.md | 54 +++++ release.cert | 28 +++ test/metrics/backend/statsd.rb | 68 ++++++ 24 files changed, 940 insertions(+) create mode 100644 .covered.db create mode 100644 .editorconfig create mode 100644 .github/workflows/documentation-coverage.yaml create mode 100644 .github/workflows/documentation.yaml create mode 100644 .github/workflows/rubocop.yaml create mode 100644 .github/workflows/test-coverage.yaml create mode 100644 .github/workflows/test-external.yaml create mode 100644 .github/workflows/test.yaml create mode 100644 .gitignore create mode 100644 .rubocop.yml create mode 100644 config/sus.rb create mode 100644 fixtures/metrics/backend/statsd/server.rb create mode 100644 gems.locked create mode 100644 gems.rb create mode 100644 lib/metrics/backend/statsd.rb create mode 100644 lib/metrics/backend/statsd/interface.rb create mode 100644 lib/metrics/backend/statsd/version.rb create mode 100644 license.md create mode 100644 metrics-backend-statsd-instruments.gemspec create mode 100644 pkg/metrics-backend-datadog-0.2.0.gem create mode 100644 pkg/metrics-backend-datadog-0.2.1.gem create mode 100644 readme.md create mode 100644 release.cert create mode 100644 test/metrics/backend/statsd.rb diff --git a/.covered.db b/.covered.db new file mode 100644 index 0000000000000000000000000000000000000000..5b24650b6cbe60e83b0d005d797d544de0228011 GIT binary patch literal 645 zcmZoZ&dgAdnR1SS(eBzchJwVBjJ-LTN&2~|B}JLZ#rjE!$=RuSDf-1Fi6zA;dPPas z8ItqMQi~GPQ;!!Y9#2-d8LA&zoLW?@U!0g*nwq2Ul3JFUlV6Zpq+gs5)LK$h2{TI< zYL+gi2;-`yw{f_xyHRppR=!rSK#LmJEv2N)TF2uy&OP%cOa$UeZx1Qvl15NV(|P?iCM z1S$rKF@PbM2_mqCkSd-K0>)% 5.0) + metrics + +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.2) + async (2.23.0) + console (~> 1.29) + fiber-annotation + io-event (~> 1.9) + metrics (~> 0.12) + traces (~> 0.15) + async-container (0.23.2) + async (~> 2.22) + async-container-supervisor (0.5.1) + async-container (~> 0.22) + async-service + io-endpoint + memory-leak (~> 0.5) + async-http (0.87.0) + async (>= 2.10.2) + async-pool (~> 0.9) + io-endpoint (~> 0.14) + io-stream (~> 0.6) + metrics (~> 0.12) + protocol-http (~> 0.49) + protocol-http1 (~> 0.30) + protocol-http2 (~> 0.22) + traces (~> 0.10) + async-http-cache (0.4.5) + async-http (~> 0.56) + async-pool (0.10.3) + async (>= 1.25) + async-service (0.12.0) + async + async-container (~> 0.16) + bake (0.23.1) + bigdecimal + samovar (~> 2.1) + bake-gem (0.10.0) + console (~> 1.25) + bake-modernize (0.32.0) + async-http + bake + build-files (~> 1.6) + markly (~> 0.8) + rugged + bake-test (0.3.0) + bake + bigdecimal (3.1.9) + build-files (1.9.0) + concurrent-ruby (1.3.5) + console (1.29.3) + fiber-annotation + fiber-local (~> 1.1) + json + covered (0.26.0) + console (~> 1.0) + msgpack (~> 1.0) + date (3.4.1) + decode (0.22.0) + parser + dogstatsd-ruby (5.6.5) + falcon (0.51.0) + async + async-container (~> 0.20) + async-container-supervisor (~> 0.5.0) + async-http (~> 0.75) + async-http-cache (~> 0.4) + async-service (~> 0.10) + bundler + localhost (~> 1.1) + openssl (~> 3.0) + protocol-http (~> 0.31) + protocol-rack (~> 0.7) + samovar (~> 2.3) + fiber-annotation (0.2.0) + fiber-local (1.1.0) + fiber-storage + fiber-storage (1.0.0) + http-accept (2.2.1) + io-endpoint (0.15.2) + io-event (1.9.0) + io-stream (0.6.1) + json (2.10.1) + language_server-protocol (3.17.0.4) + lint_roller (1.1.0) + localhost (1.3.1) + logger (1.6.6) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + mapping (1.1.1) + markly (0.12.1) + memory-leak (0.5.2) + metrics (0.12.1) + mime-types (3.6.0) + logger + mime-types-data (~> 3.2015) + mime-types-data (3.2025.0304) + mini_mime (1.1.5) + msgpack (1.8.0) + net-imap (0.5.6) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + openssl (3.3.0) + parallel (1.26.3) + parser (3.3.7.1) + ast (~> 2.4.1) + racc + protocol-hpack (1.5.1) + protocol-http (0.49.0) + protocol-http1 (0.30.0) + protocol-http (~> 0.22) + protocol-http2 (0.22.1) + protocol-hpack (~> 1.4) + protocol-http (~> 0.47) + protocol-rack (0.11.2) + protocol-http (~> 0.43) + rack (>= 1.0) + racc (1.8.1) + rack (3.1.11) + rackula (1.4.1) + falcon (~> 0.46) + samovar (~> 2.1) + variant + rainbow (3.1.1) + regexp_parser (2.10.0) + rubocop (1.73.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.38.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.38.1) + parser (>= 3.3.1.0) + ruby-progressbar (1.13.0) + rugged (1.9.0) + samovar (2.3.0) + console (~> 1.0) + mapping (~> 1.0) + sus (0.32.0) + sus-fixtures-async (0.2.0) + async + sus (~> 0.10) + thread-local (1.1.0) + timeout (0.4.3) + traces (0.15.2) + unicode-display_width (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + utopia (2.27.0) + bake (~> 0.20) + concurrent-ruby (~> 1.2) + console (~> 1.24) + http-accept (~> 2.1) + mail (~> 2.6) + mime-types (~> 3.0) + msgpack + net-smtp + rack (~> 3.0) + samovar (~> 2.1) + traces (~> 0.10) + variant (~> 0.1) + xrb (~> 0.4) + utopia-project (0.33.1) + decode (~> 0.17) + falcon + markly (~> 0.7) + rackula (~> 1.3) + thread-local + utopia (~> 2.24) + variant (0.1.1) + thread-local + xrb (0.11.1) + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + bake-gem + bake-modernize + bake-test + covered + decode + metrics-backend-statsd! + rubocop + sus + sus-fixtures-async + utopia-project + +BUNDLED WITH + 2.6.2 diff --git a/gems.rb b/gems.rb new file mode 100644 index 0000000..e689e9c --- /dev/null +++ b/gems.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Samuel Williams. + +source "https://rubygems.org" + +gemspec + +group :maintenance, optional: true do + gem "bake-modernize" + gem "bake-gem" + + gem "utopia-project" +end + +group :test do + gem "sus" + gem "covered" + gem "decode" + gem "rubocop" + + gem "sus-fixtures-async" + + gem "bake-test" +end diff --git a/lib/metrics/backend/statsd.rb b/lib/metrics/backend/statsd.rb new file mode 100644 index 0000000..65a8eb2 --- /dev/null +++ b/lib/metrics/backend/statsd.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Samuel Williams. + +require_relative "statsd/version" +require_relative "statsd/interface" diff --git a/lib/metrics/backend/statsd/interface.rb b/lib/metrics/backend/statsd/interface.rb new file mode 100644 index 0000000..f1ba2ff --- /dev/null +++ b/lib/metrics/backend/statsd/interface.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Samuel Williams. + +require "metrics/metric" +require "metrics/tags" + +require "console" +require "io/endpoint/host_endpoint" + +module Metrics + module Backend + module Statsd + ::Thread.attr_accessor :metrics_backend_statsd_instance + + class Client + DEFAULT_ENDPOINT = IO::Endpoint.udp("localhost", 8125) + + def initialize(endpoint = DEFAULT_ENDPOINT) + @endpoint = endpoint + end + + def send_metric(name, value, type:, sample_rate: 1.0, tags: nil) + return if sample_rate < 1.0 && rand > sample_rate + + parts = ["#{name}:#{value}|#{type}"] + parts << "@#{sample_rate}" if sample_rate < 1.0 + parts << format_tags(tags) if tags + + message = parts.compact.join("|") + pp sendmsg: message, endpoint: @endpoint, socket: self.socket + self.socket.sendmsg(message, 0) + end + + private + + def socket + @socket ||= @endpoint.connect + end + + def format_tags(tags) + "##{tags.join(',')}" if tags && !tags.empty? + end + end + + def self.instance + Thread.current.metrics_backend_statsd_instance ||= Client.new + end + + def self.instance=(instance) + Thread.current.metrics_backend_statsd_instance = instance + end + + class Metric < Metrics::Metric + def emit(value, tags: nil, sample_rate: 1.0) + Statsd.instance.send_metric(@name, value, type: "c", sample_rate: sample_rate, tags: Tags.normalize(tags)) + end + end + + class Gauge < Metric + def emit(value, tags: nil, sample_rate: 1.0) + Statsd.instance.send_metric(@name, value, type: "g", sample_rate: sample_rate, tags: Tags.normalize(tags)) + end + end + + class Histogram < Metric + def emit(value, tags: nil, sample_rate: 1.0) + Statsd.instance.send_metric(@name, value, type: "h", sample_rate: sample_rate, tags: Tags.normalize(tags)) + end + end + + class Distribution < Metric + def emit(value, tags: nil, sample_rate: 1.0) + Statsd.instance.send_metric(@name, value, type: "d", sample_rate: sample_rate, tags: Tags.normalize(tags)) + end + end + + module Interface + def metric(name, type, description: nil, unit: nil) + klass = Metric + + case name + when :counter + # klass = Metric + when :guage + klass = Gauge + when :histogram + klass = Histogram + when :distribution + klass = Distribution + end + + klass.new(name, type, description, unit) + end + end + end + + Interface = Statsd::Interface + end +end diff --git a/lib/metrics/backend/statsd/version.rb b/lib/metrics/backend/statsd/version.rb new file mode 100644 index 0000000..904bba6 --- /dev/null +++ b/lib/metrics/backend/statsd/version.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Samuel Williams. + +module Metrics + module Backend + module Statsd + VERSION = "0.2.1" + end + end +end diff --git a/license.md b/license.md new file mode 100644 index 0000000..90c489d --- /dev/null +++ b/license.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright, 2025, by Samuel Williams. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/metrics-backend-statsd-instruments.gemspec b/metrics-backend-statsd-instruments.gemspec new file mode 100644 index 0000000..5522d75 --- /dev/null +++ b/metrics-backend-statsd-instruments.gemspec @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require_relative "lib/metrics/backend/statsd/version" + +Gem::Specification.new do |spec| + spec.name = "metrics-backend-statsd" + spec.version = Metrics::Backend::Statsd::VERSION + + spec.summary = "Application metrics and instrumentation." + spec.authors = ["Samuel Williams"] + spec.license = "MIT" + + spec.cert_chain = ["release.cert"] + spec.signing_key = File.expand_path("~/.gem/release.pem") + + spec.homepage = "https://github.com/socketry/metrics-backend-statsd" + + spec.metadata = { + "documentation_uri" => "https://socketry.github.io/metrics-backend-statsd/", + "source_code_uri" => "https://github.com/socketry/metrics-backend-statsd.git", + } + + spec.files = Dir.glob(["{lib}/**/*", "*.md"], File::FNM_DOTMATCH, base: __dir__) + + spec.required_ruby_version = ">= 3.1" + + spec.add_dependency "dogstatsd-ruby", "~> 5.0" + spec.add_dependency "metrics" +end diff --git a/pkg/metrics-backend-datadog-0.2.0.gem b/pkg/metrics-backend-datadog-0.2.0.gem new file mode 100644 index 0000000000000000000000000000000000000000..8eebc44b9db54fe0908d289e9d872a68f44b4b58 GIT binary patch literal 10752 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 literal 0 HcmV?d00001 diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..b22b62b --- /dev/null +++ b/readme.md @@ -0,0 +1,54 @@ +# Metrics::Backend::Statsd + +A metrics backend for Statsd. + +[![Development Status](https://github.com/socketry/metrics-backend-statsd/workflows/Test/badge.svg)](https://github.com/socketry/metrics-backend-statsd/actions?workflow=Test) + +## Usage + +If you need to specify custom options, you can override the default statsd client instance. + +``` ruby +# config/initializers/metrics/backend/statsd.rb + +require 'metrics/backend/statsd' + +module Metrics + module Backend + module Statsd + # Override the default instance setup: + def self.new + instance = ::Statsd::Statsd.new('localhost', 8125, logger: Console.logger) + + at_exit do + self.close + end + + return instance + end + end + end +end +``` + +## 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](https://github.com/socketry/metrics) — Capture metrics about code execution in a vendor agnostic way. diff --git a/release.cert b/release.cert new file mode 100644 index 0000000..d98e595 --- /dev/null +++ b/release.cert @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11 +ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK +CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz +MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd +MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj +bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB +igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2 +9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW +sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE +e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN +XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss +RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn +tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM +zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW +xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O +BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs +aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs +aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE +cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl +xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/ +c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp +8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws +JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP +eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt +Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8 +voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg= +-----END CERTIFICATE----- diff --git a/test/metrics/backend/statsd.rb b/test/metrics/backend/statsd.rb new file mode 100644 index 0000000..e53e751 --- /dev/null +++ b/test/metrics/backend/statsd.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Samuel Williams. + +require "metrics" +require "metrics/backend/statsd" +require "metrics/backend/statsd/server" + +require "io/endpoint/unix_endpoint" + +require "sus/fixtures/async" +require "async/queue" + +class MyClass + def my_method(argument) + end +end + +Metrics::Provider(MyClass) do + MYCLASS_CALL_COUNT = Metrics.metric("my_class.call", :counter, description: "Call counter.") + + def my_method(argument) + MYCLASS_CALL_COUNT.emit(1, tags: ["foo", "bar"]) + + super + end +end + +describe Metrics do + it "has a version number" do + expect(Metrics::VERSION).to be =~ /\d+\.\d+\.\d+/ + end + + with "mock server" do + include Sus::Fixtures::Async::ReactorContext + + let(:endpoint) {IO::Endpoint.unix("/tmp/metrics-#{Process.pid}-#{Time.now.to_i}.ipc", ::Socket::SOCK_DGRAM)} + + let(:client) {Metrics::Backend::Statsd::Client.new(endpoint)} + + before do + @server = Metrics::Backend::Statsd::Server.new(endpoint) + @server_task = @server.start + end + + after do + @server_task&.stop + end + + it "can invoke metric wrapper" do + Metrics::Backend::Statsd.instance = client + + instance = MyClass.new + + instance.my_method(10) + + message = @server.messages.pop + expect(message).to have_keys( + name: be == "my_class.call", + value: be == 1.0, + type: be == "c", + sample_rate: be == 1.0, + tags: be == ["foo", "bar"] + ) + end + end +end