Skip to content

Commit

Permalink
Merge branch 'release/v0.9.0' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
general-CbIC committed Apr 24, 2024
2 parents 38e2ad7 + 3368067 commit 94894be
Show file tree
Hide file tree
Showing 20 changed files with 423 additions and 91 deletions.
10 changes: 6 additions & 4 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ jobs:
otp: '25'
- elixir: '1.15'
otp: '26'
- elixir: '1.16'
otp: '26'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{matrix.otp}}
Expand All @@ -48,13 +50,13 @@ jobs:
runs-on: ubuntu-latest
name: Static analysis
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set mix.lock file hash
run: |
mix_hash="${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}"
echo "mix_hash=$mix_hash" >> $GITHUB_ENV
- name: Cache PLT files
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
_build/dev/*.plt
Expand All @@ -66,6 +68,6 @@ jobs:
uses: erlef/setup-beam@v1
with:
otp-version: '26'
elixir-version: '1.15'
elixir-version: '1.16'
- run: mix do deps.get, compile
- run: mix check
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
erlang 26.0.2
elixir 1.15.4-otp-26
erlang 26.2.1
elixir 1.16.0-otp-26
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.9.0] - 2024-04-24

### Added

- Added sending of pools size metrics via `telemetry`. [Working with metrics guide](https://hexdocs.pm/poolex/pool-metrics.html)

## [0.8.0] - 2023-08-30

### Changed
Expand Down Expand Up @@ -233,7 +239,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Supported main interface `Poolex.run/3` with `:timeout` option.
[unreleased]: https://github.com/general-CbIC/poolex/compare/v0.8.0...HEAD
[unreleased]: https://github.com/general-CbIC/poolex/compare/v0.9.0...HEAD
[0.9.0]: https://github.com/general-CbIC/poolex/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/general-CbIC/poolex/compare/v0.7.6...v0.8.0
[0.7.6]: https://github.com/general-CbIC/poolex/compare/v0.7.5...v0.7.6
[0.7.5]: https://github.com/general-CbIC/poolex/compare/v0.7.4...v0.7.5
Expand Down
50 changes: 31 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@

Poolex is a library for managing pools of workers. Inspired by [poolboy](https://github.com/devinus/poolboy).

## Table of Contents

- [Poolex](#poolex)
- [Table of Contents](#table-of-contents)
- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Usage](#usage)
- [Guides](#guides)
- [Contributions](#contributions)

## Features

With `poolex` you can:
Expand All @@ -31,32 +42,14 @@ With `poolex` you can:
| Erlang/OTP | >= 22 |
| Elixir | >= 1.7 |

## Table of Contents

- [Installation](#installation)
- [Getting Started](https://hexdocs.pm/poolex/getting-started.html)
- [Starting pool of workers](https://hexdocs.pm/poolex/getting-started.html#starting-pool-of-workers)
- [Poolex configuration options](https://hexdocs.pm/poolex/getting-started.html#starting-pool-of-workers)
- [Working with the pool](https://hexdocs.pm/poolex/getting-started.html#working-with-the-pool)
- [Migration from `:poolboy`](https://hexdocs.pm/poolex/migration-from-poolboy.html)
- [Example of use](https://hexdocs.pm/poolex/example-of-use.html)
- [Defining the worker](https://hexdocs.pm/poolex/example-of-use.html#defining-the-worker)
- [Configuring Poolex](https://hexdocs.pm/poolex/example-of-use.html#configuring-poolex)
- [Using Poolex](https://hexdocs.pm/poolex/example-of-use.html#using-poolex)
- [Workers and callers implementations](https://hexdocs.pm/poolex/workers-and-callers-implementations.html)
- [Callers](https://hexdocs.pm/poolex/workers-and-callers-implementations.html#callers)
- [Workers](https://hexdocs.pm/poolex/workers-and-callers-implementations.html#workers)
- [Writing custom implementations](https://hexdocs.pm/poolex/workers-and-callers-implementations.html#writing-custom-implementations)
- [Contributions](#contributions)

## Installation

Add `:poolex` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:poolex, "~> 0.7.0"}
{:poolex, "~> 0.9.0"}
]
end
```
Expand Down Expand Up @@ -85,6 +78,25 @@ iex> Poolex.run(:worker_pool, &(is_pid?(&1)), checkout_timeout: 1_000)

A detailed description of the available configuration or examples of use can be found in [documentation](https://hexdocs.pm/poolex/getting-started.html).

## Guides

- [Getting Started](https://hexdocs.pm/poolex/getting-started.html)
- [Starting pool of workers](https://hexdocs.pm/poolex/getting-started.html#starting-pool-of-workers)
- [Poolex configuration options](https://hexdocs.pm/poolex/getting-started.html#starting-pool-of-workers)
- [Working with the pool](https://hexdocs.pm/poolex/getting-started.html#working-with-the-pool)
- [Migration from `:poolboy`](https://hexdocs.pm/poolex/migration-from-poolboy.html)
- [Example of use](https://hexdocs.pm/poolex/example-of-use.html)
- [Defining the worker](https://hexdocs.pm/poolex/example-of-use.html#defining-the-worker)
- [Configuring Poolex](https://hexdocs.pm/poolex/example-of-use.html#configuring-poolex)
- [Using Poolex](https://hexdocs.pm/poolex/example-of-use.html#using-poolex)
- [Working with metrics](https://hexdocs.pm/poolex/pool-metrics.html)
- [Pool size metrics](https://hexdocs.pm/poolex/pool-metrics.html#pool-size-metrics)
- [Integration with PromEx](https://hexdocs.pm/poolex/pool-metrics.html#integration-with-promex)
- [Workers and callers implementations](https://hexdocs.pm/poolex/workers-and-callers-implementations.html)
- [Callers](https://hexdocs.pm/poolex/workers-and-callers-implementations.html#callers)
- [Workers](https://hexdocs.pm/poolex/workers-and-callers-implementations.html#workers)
- [Writing custom implementations](https://hexdocs.pm/poolex/workers-and-callers-implementations.html#writing-custom-implementations)

## Contributions

If you feel something can be improved or have any questions about specific behaviors or pieces of implementation, please feel free to file an issue. Proposed changes should be taken to issues before any PRs to save time on code that might not be merged upstream.
Expand Down
21 changes: 21 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Ideas for implementing

## Pool Metrics

I want to make a simple way to analyze running pools to set their optimal configuration. For example, we launched a `pool` in production with a maximum overflow of 0 (we do not want to create more processes than the designated number) and a pool size 200.

Using metrics, we see that typically, our application uses 10-20 processes, and there are spikes when up to 180 workers are exploited. If our processes are heavyweight and, for example, open persistent connections to storage, then by analyzing metrics, we can significantly save resources. In this case, we can set the pool size to 20 and `max_overflow` to 180. This way, we will have one overall pool size limit of 200, and we will avoid uncontrolled waste of all resources, but at the same time, we will only keep up to 20 processes in memory at times when this is not required.

### Metrics to be implemented

- [x] Pool size
- [x] Idle workers count
- [x] Busy workers count
- [x] Is max_overflow used?
- [ ] Usage time
- [ ] How long are workers busy?
- [ ] How long the application waits of workers from pool?

## Implementations metrics

To be described...
21 changes: 11 additions & 10 deletions docs/guides/getting-started.cheatmd
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@ The second argument should contain a set of options for starting the pool.
## Poolex configuration options

| Option | Description | Example | Default value |
|------------------------|------------------------------------------------------|-----------------------|-----------------------------------|
| `pool_id` | Identifier by which you will access the pool | `:my_pool` | **option is required** |
| `worker_module` | Name of module that implements our worker | `MyApp.Worker` | **option is required** |
| `workers_count` | How many workers should be running in the pool | `5` | **option is required** |
| `max_overflow` | How many workers can be created over the limit | `2` | `0` |
| `worker_args` | List of arguments passed to the start function | `[:gg, "wp"]` | `[]` |
| `worker_start_fun` | Name of the function that starts the worker | `:run` | `:start_link` |
| `busy_workers_impl` | Module that describes how to work with busy workers | `SomeBusyWorkersImpl` | `Poolex.Workers.Impl.List` |
| `idle_workers_impl` | Module that describes how to work with idle workers | `SomeIdleWorkersImpl` | `Poolex.Workers.Impl.List` |
| `waiting_callers_impl` | Module that describes how to work with callers queue | `WaitingCallersImpl` | `Poolex.Callers.Impl.ErlangQueue` |
|------------------------|------------------------------------------------------|-----------------------|-------------------------|
| `pool_id` | Identifier by which you will access the pool | `:my_pool` | **option is required** |
| `worker_module` | Name of module that implements our worker | `MyApp.Worker` | **option is required** |
| `workers_count` | How many workers should be running in the pool | `5` | **option is required** |
| `max_overflow` | How many workers can be created over the limit | `2` | `0` |
| `worker_args` | List of arguments passed to the start function | `[:gg, "wp"]` | `[]` |
| `worker_start_fun` | Name of the function that starts the worker | `:run` | `:start_link` |
| `busy_workers_impl` | Module that describes how to work with busy workers | `SomeBusyWorkersImpl` | `Poolex.Workers.Impl.List` |
| `idle_workers_impl` | Module that describes how to work with idle workers | `SomeIdleWorkersImpl` | `Poolex.Workers.Impl.List` |
| `waiting_callers_impl` | Module that describes how to work with callers queue | `WaitingCallersImpl` | `Poolex.Callers.Impl.ErlangQueue` |
| `pool_size_metrics` | Whether to dispatch pool size metrics | `true` | `false` |

## Working with the pool

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/migration-from-poolboy.cheatmd
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ If you are using `:poolboy` and want to use `Poolex` instead, then you need to f
defp deps do
[
- {:poolboy, "~> 1.5.0"}
+ {:poolex, "~> 0.7.0"}
+ {:poolex, "~> 0.9.0"}
]
end
```
Expand Down
30 changes: 30 additions & 0 deletions docs/guides/pool-metrics.cheatmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Working with metrics

You can quickly analyze and optimize your pool's production settings with the metrics presented by the library.

## Pool size metrics

The Poolex library presents **an idle/busy worker count metric**. These metrics help estimate a pool load and the number of workers used.

Also, there is **an overflow metric**. It shows how long pools are forced to use additional workers.

You can handle them by using `:telemetry.attach/4`:

```elixir
:telemetry.attach(
"my-lovely-pool-size-metrics",
[:poolex, :metrics, :pool_size],
&MyApp.handle_event/4,
nil
)
```

For example, your application can write metrics to the console: [PoolexExample.MetricsHandler](https://github.com/general-CbIC/poolex/blob/develop/examples/poolex_example/lib/poolex_example/metrics_handler.ex).

More about using `telemetry` [here](https://hexdocs.pm/telemetry/readme.html).

## Integration with PromEx

There is a plugin that works with the [PromEx](https://github.com/akoutmos/prom_ex) library: [Poolex.PromEx](https://hex.pm/packages/poolex_prom_ex).

About installation of this plugin you can read [here](https://hexdocs.pm/poolex_prom_ex/readme.html#installation).
10 changes: 9 additions & 1 deletion examples/poolex_example/lib/poolex_example/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,17 @@ defmodule PoolexExample.Application do
pool_id: :worker_pool,
worker_module: PoolexExample.Worker,
workers_count: 5,
max_overflow: 2}
max_overflow: 2,
pool_size_metrics: true}
]

:telemetry.attach(
"poolex_metrics",
[:poolex, :metrics, :pool_size],
&PoolexExample.MetricsHandler.handle_event/4,
nil
)

Supervisor.start_link(children, strategy: :one_for_one)
end
end
10 changes: 10 additions & 0 deletions examples/poolex_example/lib/poolex_example/metrics_handler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule PoolexExample.MetricsHandler do
def handle_event([:poolex, :metrics, :pool_size], measurements, metadata, _config) do
IO.puts("""
[Pool: #{metadata.pool_id}]:
- Idle workers: #{measurements.idle_workers_count}
- Busy workers: #{measurements.busy_workers_count}
- Overflowed: #{measurements.overflowed}
""")
end
end
3 changes: 2 additions & 1 deletion examples/poolex_example/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ defmodule PoolexExample.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false},
{:poolex, path: "../.."},
{:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false}
{:telemetry, "~> 1.0"}
]
end
end
4 changes: 3 additions & 1 deletion examples/poolex_example/mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
%{
"dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"},
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"poolex": {:hex, :poolex, "0.4.0", "cc3c5eb921a2ea5886953a2ea3beb0e36148d7d91dd9b628df74c3abb5ea7981", [:mix], [], "hexpm", "0441bf433f8fca4127b68c5985354cb772bba049057bca842f6e73df4f631c17"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
}
12 changes: 11 additions & 1 deletion lib/poolex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ defmodule Poolex do
alias Poolex.Private.BusyWorkers
alias Poolex.Private.DebugInfo
alias Poolex.Private.IdleWorkers
alias Poolex.Private.Metrics
alias Poolex.Private.Monitoring
alias Poolex.Private.State
alias Poolex.Private.WaitingCallers
Expand All @@ -47,6 +48,7 @@ defmodule Poolex do
| `busy_workers_impl` | Module that describes how to work with busy workers | `SomeBusyWorkersImpl` | `Poolex.Workers.Impl.List` |
| `idle_workers_impl` | Module that describes how to work with idle workers | `SomeIdleWorkersImpl` | `Poolex.Workers.Impl.List` |
| `waiting_callers_impl` | Module that describes how to work with callers queue | `WaitingCallersImpl` | `Poolex.Callers.Impl.ErlangQueue` |
| `pool_size_metrics` | Whether to dispatch pool size metrics | `true` | `false` |
"""

@typedoc """
Expand All @@ -66,6 +68,7 @@ defmodule Poolex do
| {:busy_workers_impl, module()}
| {:idle_workers_impl, module()}
| {:waiting_callers_impl, module()}
| {:pool_size_metrics, boolean()}

@typedoc """
Process id of `worker`.
Expand Down Expand Up @@ -290,7 +293,14 @@ defmodule Poolex do
|> BusyWorkers.init(busy_workers_impl)
|> WaitingCallers.init(waiting_callers_impl)

{:ok, state}
{:ok, state, {:continue, opts}}
end

@impl GenServer
def handle_continue(opts, state) do
Metrics.start_poller(opts)

{:noreply, state}
end

@spec start_workers(non_neg_integer(), State.t(), Monitoring.monitor_id()) :: [pid]
Expand Down
59 changes: 59 additions & 0 deletions lib/poolex/private/metrics.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule Poolex.Private.Metrics do
@moduledoc """
Functions for dispatching metrics.
"""

@doc """
Dispatches metrics with current count of idle workers.
"""
@spec dispatch_pool_size_metrics(Poolex.pool_id()) :: :ok
def dispatch_pool_size_metrics(pool_id) do
debug_info = Poolex.get_debug_info(pool_id)

:telemetry.execute(
[:poolex, :metrics, :pool_size],
%{
idle_workers_count: debug_info.idle_workers_count,
busy_workers_count: debug_info.busy_workers_count,
overflowed: convert_overflow_to_number(debug_info.overflow > 0)
},
%{pool_id: pool_id}
)
end

@spec convert_overflow_to_number(boolean()) :: integer()
defp convert_overflow_to_number(true), do: 1
defp convert_overflow_to_number(false), do: 0

@doc """
Starts a telemetry poller for dispatching metrics.
"""
@spec start_poller(list(Poolex.poolex_option())) :: GenServer.on_start()
def start_poller(opts) do
pool_id = Keyword.fetch!(opts, :pool_id)
measurements = collect_measurements(opts)

if measurements == [] do
:ok
else
:telemetry_poller.start_link(
measurements: measurements,
period: :timer.seconds(1),
name: :"#{pool_id}_metrics_poller"
)
end
end

@spec collect_measurements(list(Poolex.poolex_option())) :: list()
defp collect_measurements(opts) do
pool_id = Keyword.fetch!(opts, :pool_id)

if Keyword.get(opts, :pool_size_metrics, false) do
[
{Poolex.Private.Metrics, :dispatch_pool_size_metrics, [pool_id]}
]
else
[]
end
end
end
Loading

0 comments on commit 94894be

Please sign in to comment.