Skip to content

Commit

Permalink
Merge pull request #4 from lessonnine/TNT-2411/release-gem
Browse files Browse the repository at this point in the history
[TNT-2411] Release gem
  • Loading branch information
Leszek Zalewski authored Dec 18, 2020
2 parents 218e8c7 + fbe3484 commit a1ce466
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 26 deletions.
24 changes: 23 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: build

on: push
on: [push, create]

jobs:
build_matrix:
Expand Down Expand Up @@ -47,3 +47,25 @@ jobs:
- name: Dummy for branch status checks
run: |
echo "build complete"
release:
needs: build
if: contains(github.ref, 'tags') && github.event_name == 'create'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
- name: Prepare Gemfury
run: |
mkdir -p ~/.gem
echo -e "---\n:fury_push_token: ${{ secrets.GEMFURY_TOKEN }}" > ~/.gem/credentials
chmod 0600 ~/.gem/credentials
- name: Build Gem
run: |
gem build *.gemspec
- name: Publish Gem
run: |
gem push *.gem --key fury_push_token \
--host https://push.fury.io/babbel
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.1.0] - 2020-12-18
### Added
- Core Plugin
- Telemetry generation
- IO Target with JSON formatter
- Datadog Statsd Target
80 changes: 74 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# Puma::Plugin::Telemetry

Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/puma/plugin/telemetry`. To experiment with that code, run `bin/console` for an interactive prompt.

TODO: Delete this and the text above, and describe your gem
Puma plugin adding ability to publish various metrics to your prefered targets.

## Install

Expand All @@ -22,17 +20,87 @@ Or install it yourself as:

## Usage

TODO: Write usage instructions here
In your puma configuration file (i.e. `config/puma.rb` or `config/puma/<env>.rb`):

```ruby
plugin "telemetry"

Puma::Plugin::Telemetry.configure do |config|
config.enabled = true

# << here rest of the configuration, examples below
end
```

### Basic

Output telemetry as JSON to STDOUT

```ruby
config.add_target :io
```

### Datadog statsd target

Given gem provides built in target for Datadog Statsd client, that uses batch operation to publish metrics.

**NOTE** Be sure to have `dogstatsd` gem installed.

```ruby
config.add_target :dogstatsd, client: Datadog::Statsd.new
```

You can provide all the tags, namespaces, and other configuration options as always to `Datadog::Statsd.new` method.

### All available options

For detailed documentation checkout [`Puma::Plugin::Telemetry::Config`](./lib/puma/plugin/telemetry/config.rb) class.

```ruby
Puma::Plugin::Telemetry.configure do |config|
config.enabled = true
config.initial_delay = 10
config.frequency = 30
config.puma_telemetry = %w[workers.requests_count queue.backlog queue.capacity]
config.add_target :io, formatter: :json, io: StringIO.new
config.add_target :dogstatsd, client: Datadog::Statsd.new(tags: { env: ENV["RAILS_ENV"] })
end
```

### Custom Targets

Target is a simple object that implements `call` methods that accepts `telemetry` hash object. This means it can be super simple `proc` or some sofisticated class calling some external API.

Just be mindful that if the API takes long to call, it will slow down frequency with which telemetry will get reported.

```ruby
# Example logfmt to stdout target
config.add_target proc { |telemetry| puts telemetry.map { |k, v| "#{k}=#{v.inspect}" }.join(" ") }
```

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
To install this gem onto your local machine, run `bundle exec rake install`.

## Release

All gem releases are manual, in order to create a new release follow:

1. Create new PR (this could be included in feature PR, if it's meant to be released)
- update `VERSION`, we use [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
- update `CHANGELOG`
- merge
2. Draft new release via Github Releases
- use `v#{VERSION}` as a tag, i.e. `v0.1.0`
- add release notes based on the Changelog
- create
3. Gem will get automatically published to given rubygems server

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/puma-plugin-telemetry.
Bug reports and pull requests are welcome on GitHub at https://github.com/lessonnine/puma-plugin-telemetry.

## License

Expand Down
4 changes: 3 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "rubocop/rake_task"

RSpec::Core::RakeTask.new(:spec)
RuboCop::RakeTask.new

task default: :spec
task default: %i[rubocop spec]
5 changes: 3 additions & 2 deletions lib/puma/plugin/telemetry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
require "puma/plugin"

require "puma/plugin/telemetry/version"
require "puma/plugin/telemetry/config"
require "puma/plugin/telemetry/data"
require "puma/plugin/telemetry/target/datadog_statsd_target"
require "puma/plugin/telemetry/targets/datadog_statsd_target"
require "puma/plugin/telemetry/targets/io_target"
require "puma/plugin/telemetry/config"

module Puma
class Plugin
Expand Down
15 changes: 15 additions & 0 deletions lib/puma/plugin/telemetry/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ class Config
"queue.capacity"
].freeze

TARGETS = {
dogstatsd: Telemetry::Targets::DatadogStatsdTarget,
io: Telemetry::Targets::IOTarget
}.freeze

# Whenever telemetry should run with puma
# - default: false
attr_accessor :enabled
Expand Down Expand Up @@ -64,6 +69,16 @@ def initialize
def enabled?
!!@enabled
end

def add_target(name_or_target, **args)
return @targets.push(name_or_target) unless name_or_target.is_a?(Symbol)

target = TARGETS.fetch(name_or_target) do
raise Telemetry::Error, "Unknown Target: #{name_or_target.inspect}, #{args.inspect}"
end

@targets.push(target.new(**args))
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module Puma
class Plugin
module Telemetry
module Target
module Targets
# Target wrapping Datadog Statsd client. You can configure
# all details like _metrics prefix_ and _tags_ in the client
# itself.
Expand All @@ -19,16 +19,13 @@ module Target
# version: ENV["CODE_VERSION"]
# })
#
# DatadogStatsdTarget.new(client)
# DatadogStatsdTarget.new(client: client)
#
class DatadogStatsdTarget
def initialize(client)
def initialize(client:)
@client = client
end

# TODO: Support other metric types, like `counter` backed into
# telemetry. Best example that would use this is `request_count`
#
def call(telemetry)
client.batch do |statsd|
telemetry.each do |metric, value|
Expand Down
35 changes: 35 additions & 0 deletions lib/puma/plugin/telemetry/targets/io_target.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require "json"

module Puma
class Plugin
module Telemetry
module Targets
# Simple IO Target, publishing metrics to STDOUT or logs
#
class IOTarget
# JSON formatter for IO, expects `call` method accepting telemetry hash
#
class JSONFormatter
def self.call(telemetry)
::JSON.dump(telemetry.merge(name: "Puma::Plugin::Telemetry", message: "Publish telemetry"))
end
end

def initialize(io: $stdout, formatter: :json)
@io = io
@formatter = case formatter
when :json then JSONFormatter
else formatter
end
end

def call(telemetry)
@io.puts(@formatter.call(telemetry))
end
end
end
end
end
end
4 changes: 2 additions & 2 deletions spec/fixtures/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ def call(telemetry)
end

Puma::Plugin::Telemetry.configure do |config|
config.targets << Target.new("01")
config.targets << Target.new("02")
config.add_target Target.new("01")
config.add_target Target.new("02")
config.frequency = 0.2
config.enabled = true
config.initial_delay = 0
Expand Down
2 changes: 1 addition & 1 deletion spec/fixtures/puma_telemetry_subset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
plugin "telemetry"

Puma::Plugin::Telemetry.configure do |config|
config.targets << ->(telemetry) { puts "telemetry=#{telemetry.inspect}" }
config.add_target :io, formatter: :json
config.frequency = 0.2
config.enabled = true

Expand Down
10 changes: 3 additions & 7 deletions spec/integration/plugin_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,12 @@ class Plugin
context "when subset of telemetry" do
let(:config) { "puma_telemetry_subset" }
let(:expected_telemetry) do
{
"queue.backlog" => 0,
"workers.spawned_threads" => 2,
"workers.max_threads" => 4
}
"{\"queue.backlog\":0,\"workers.spawned_threads\":2,\"workers.max_threads\":4,\"name\":\"Puma::Plugin::Telemetry\",\"message\":\"Publish telemetry\"}\n" # rubocop:disable Layout/LineLength
end

it "logs only selected telemetry" do
true while (line = @server.next_line) !~ /telemetry=/
expect(line).to start_with "telemetry=#{expected_telemetry.inspect}"
true while (line = @server.next_line) !~ /Puma::Plugin::Telemetry/
expect(line).to start_with expected_telemetry
end
end
end
Expand Down
76 changes: 76 additions & 0 deletions spec/puma/plugin/telemetry/config_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

module Puma
class Plugin
module Telemetry
RSpec.describe Config do
subject(:config) { described_class.new }

describe "#enabled?" do
context "when default" do
it { expect(config.enabled?).to eq false }
end

context "when enabled" do
before { config.enabled = true }

it { expect(config.enabled?).to eq true }
end
end

describe "#add_target" do
context "when built in: IO" do
it "adds new target" do
expect { config.add_target(:io) }.to change(config.targets, :size).by(1)
end

it "adds new IO Target" do
config.add_target(:io)
expect(config.targets.first).to be_a(Telemetry::Targets::IOTarget)
end
end

context "when built in: Datadog" do
let(:client) { instance_double("statsd") }

it "adds new target" do
expect do
config.add_target(:dogstatsd, client: client)
end.to change(config.targets, :size).by(1)
end

it "adds new Datadog Target" do
config.add_target(:dogstatsd, client: client)
expect(config.targets.first).to be_a(Telemetry::Targets::DatadogStatsdTarget)
end
end

context "when custom" do
let(:target) { proc { |telemetry| puts telemetry.inspect } }

it "adds new target" do
expect do
config.add_target(target)
end.to change(config.targets, :size).by(1)
end

it "adds new Custom Target" do
config.add_target(target)
expect(config.targets.first).to be_a(Proc)
end
end

context "when multiple targets" do
it "adds new targets" do
expect do
config.add_target(proc { |telemetry| puts telemetry.inspect })
config.add_target(:io)
config.add_target(:io)
end.to change(config.targets, :size).by(3)
end
end
end
end
end
end
end

0 comments on commit a1ce466

Please sign in to comment.