Skip to content

Commit

Permalink
Start work on documentation
Browse files Browse the repository at this point in the history
Signed-off-by: tiksan <webmaster@deek.sh>
  • Loading branch information
dssecret committed Oct 2, 2024
1 parent dcda181 commit edd8b96
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 15 deletions.
2 changes: 1 addition & 1 deletion lib/promex_plugin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ defmodule Tornex.PromExPlugin do
measurement: :latency,
description: "The time it takes for the Torn API to respond to API calls.",
reporter_options: [
buckets: [0, 100, 250, 500, 1_000, 2_500, 5_000, 10_000],
buckets: [0, 100, 250, 500, 1_000, 2_500, 5_000, 10_000]
],
unit: {:native, :microsecond}
),
Expand Down
2 changes: 2 additions & 0 deletions lib/tornex/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# TODO: Remove all unused HTTP methods from `Tornex.API`

defmodule Tornex.API do
use Tesla

Expand Down
50 changes: 39 additions & 11 deletions lib/tornex_scheduler/bucket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,41 @@
# limitations under the License.

defmodule Tornex.Scheduler.Bucket do
@moduledoc """
`Tornex.Scheduler.Bucket` represents a user's bucket for API requests used for rate-limiting and prioritizing.
A bucket can hold as many API requests as the computer can support. However, each bucket will only process 10
API requests every 6 seconds.
The bucket provides the `enqueue/1` and `enqueue/2` operation that provides the core of the functionality of this module by creating
the bucket if necessary, handling telemetry, and request handling.
## Bucket Creation
Creating buckets is not required, but you can create and store buckets in a different manner if you need to do so.
A buckets can be created with the following two methods:
- `new/1` to create a bucket and store the PID of the GenServer manually
- `enqueue/1` to create a bucket and let Tornex store the PID of the GenServer in a pre-initialized registry `Tornex.Scheduler.BucketRegistry`
## Making API Requests
API requests are made with the `enqueue/1` and the `enqueue/2` operations with a `Tornex.Query` struct. The operations will then wait until the API call has been scheduled and the Torn API has responded instead of ending the invocation early and using a later `await`-like function.
However, for example, this can still be done with the built-in `Task` module (including to handle many API requests at once):
1..10
|> Enum.map(fn n ->
Task.async(fn ->
Tornex.Scheduler.Bucket.enqueue(pid, query)
end)
end)
|> Task.await_many(timeout)
"""

require Logger

use GenServer

@max_size 10
@bucket_capacity 10

# Public API
def start_link(user_id) do
Expand Down Expand Up @@ -54,15 +85,15 @@ defmodule Tornex.Scheduler.Bucket do
end

@spec enqueue(query :: Tornex.Query.t()) :: any()
def enqueue(%Tornex.Query{} = query) do
def enqueue(%Tornex.Query{key_owner: key_owner} = query) when is_integer(key_owner) do
:telemetry.execute([:tornex, :bucket, :enqueue], %{}, %{
selections: query.selections,
resource: query.resource,
resource_id: query.resource_id,
user: query.key_owner
})

case get_by_id(query.key_owner) do
case get_bucket_by_id(query.key_owner) do
{:ok, pid} ->
GenServer.call(pid, {:enqueue, query}, 60_000)

Expand All @@ -79,8 +110,8 @@ defmodule Tornex.Scheduler.Bucket do
GenServer.call(pid, {:enqueue, query}, 60_000)
end

@spec get_by_id(user_id :: integer()) :: {:ok, pid()} | :error
def get_by_id(user_id) when is_integer(user_id) do
@spec get_bucket_by_id(user_id :: integer()) :: {:ok, pid()} | :error
def get_bucket_by_id(user_id) when is_integer(user_id) do
case Registry.lookup(Tornex.Scheduler.BucketRegistry, user_id) do
[{pid, _}] -> {:ok, pid}
[] -> :error
Expand All @@ -103,15 +134,15 @@ defmodule Tornex.Scheduler.Bucket do
query = %{query | origin: from}

cond do
Tornex.Query.query_priority(query) == :user_request and pending_count < @max_size ->
Tornex.Query.query_priority(query) == :user_request and pending_count < @bucket_capacity ->
# Request has a niceness indicating it's a user request and there's available space in the
# bucket to perform the request

make_request(query, from)
state = Map.replace(state, :pending_count, pending_count + 1)
{:noreply, state}

Tornex.Query.query_priority(query) in [:user_request, :high_priority] and pending_count < 0.7 * @max_size ->
Tornex.Query.query_priority(query) in [:user_request, :high_priority] and pending_count < 0.7 * @bucket_capacity ->
# Request has a niceness indicating it's a user request or high priority and there's available
# space in the bucket to perform the request

Expand All @@ -136,7 +167,7 @@ defmodule Tornex.Scheduler.Bucket do

@impl true
def handle_info(:dump, state) do
{dumped_queries, remaining_queries} = Enum.split(state.query_priority_queue, @max_size - state.pending_count)
{dumped_queries, remaining_queries} = Enum.split(state.query_priority_queue, @bucket_capacity - state.pending_count)

Map.replace(state, :pending_count, 0)
Map.replace(state, :query_priority_queue, remaining_queries)
Expand All @@ -161,6 +192,3 @@ defmodule Tornex.Scheduler.Bucket do
end)
end
end

# TODO: Add custom await_many that won't error on timeouts
# TODO: Add custom await_many that won't error on timeouts and has a callback
9 changes: 8 additions & 1 deletion lib/tornex_scheduler/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# TODO: Refactor into a Supervisor with static supervisors and children underneath it

defmodule Tornex.Scheduler.Supervisor do
@moduledoc """
Default supervisor to supervise `Tornex.Scheduler.Bucket`, the dump timer, and the bucket registry.
The `Supervisor` can be replaced if necessary to modify bucket storage, the dump timer, etc.
"""

use DynamicSupervisor
# TODO: Refactor into a Supervisor with static supervisors and children underneath it

def start_link(args) do
{:ok, pid} = DynamicSupervisor.start_link(__MODULE__, args, name: __MODULE__)
Expand Down
11 changes: 10 additions & 1 deletion lib/tornex_scheduler/timer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,18 @@
# limitations under the License.

defmodule Tornex.Scheduler.Timer do
@moduledoc """
Timer used to determine when to dump all of the buckets, see `Tornex.Scheduler.Bucket`.
By default, the bucket will be dumped every 6 seconds to allow for a maximum call rate of
60 API requests per minute. Upon the dump timer ending, the GenServer will send a `:dump` signal to
all GenServers under `Tornex.Scheduler.Supervisor`.
"""

use GenServer

# 15 seconds
@timer_interval 15_000
@timer_interval 6_000

# Public API
def start_link(_) do
Expand Down Expand Up @@ -47,6 +55,7 @@ defmodule Tornex.Scheduler.Timer do
end

def handle_info(:dump, state) do
# Fake handle to prevent errors when `handle_info(:dump_signal, _)`sends the signal to this GenServer too
{:noreply, state}
end
end
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ defmodule Tornex.MixProject do
{:jason, "~> 1.4"},
{:hackney, "~> 1.20"},
{:telemetry, "~> 1.3"},
{:prom_ex, "~> 1.10", optional: true}
{:prom_ex, "~> 1.10", optional: true},
{:ex_doc, "~> 0.34", only: :dev, runtime: false}
]
end

Expand Down
6 changes: 6 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
"earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
"ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
"finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
"hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"octo_fetch": {:hex, :octo_fetch, "0.4.0", "074b5ecbc08be10b05b27e9db08bc20a3060142769436242702931c418695b19", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "cf8be6f40cd519d7000bb4e84adcf661c32e59369ca2827c4e20042eda7a7fc6"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
Expand Down

0 comments on commit edd8b96

Please sign in to comment.