-
Notifications
You must be signed in to change notification settings - Fork 71
/
list_handlers_for_event.ex
161 lines (124 loc) · 4.99 KB
/
list_handlers_for_event.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
defmodule Mix.Tasks.Bench.ListHandlersForEvent do
@moduledoc """
Runs a benchmark of `list_handlers_for_event/1` callback of various implementations.
The benchmark spawns processes executing `Events.Impl.list_events_for_event/1` callback in a loop
using `Benchee`. The number of spawned processes can be configured using the `--parallelism`
option. You can also specify how many handlers will be attached using the `--handlers-count`
option. Additionally, you can control how many handlers should be matched using the `-m` option.
## Command line options
* `--parallelism`, `-p` - how many simultaneous processes will be executing the function, defaults
to number of core the benchmark is running on
* `--handlers-count`, `-h` - how many handlers will be attached, defaults to 100
* `--matching-handlers-count`, `-m` - how many handlers will invoked, defaults to number of
attached handlers
* `--duration`, `-d` - how long the benchmark will run (in seconds), defaults to 10 seconds
* `--save` - path to a file where the results of the benchmark will be saved. The same path may
be provided in subsequent runs with the `--load` option to compare the new results with the
saved ones
* `--load` - path to a file where the results will be loaded from
* `--save-tag` - "name" for the benchmark results helping to differentiate multiple runs when
using the `--load` option. Does not have any effect unless `--save` is provided as well
"""
use Mix.Task
@shortdoc "Runs a benchmark of `list_handlers_for_event/1` callback of various implementations"
@switches [
parallelism: :integer,
duration: :integer,
handlers_count: :integer,
matching_handlers_count: :integer,
save: :string,
save_tag: :string,
load: :string
]
@aliases [p: :parallelism, d: :duration, h: :handlers_count, m: :matching_handlers_count]
@impls %{
"Agent" => Events.Impl.Agent,
"ETS" => Events.Impl.Ets
}
def run(argv) do
{opts, _, _} = OptionParser.parse(argv, switches: @switches, aliases: @aliases)
parallelism = Keyword.get(opts, :parallelism, System.schedulers_online())
handlers_count = opts |> Keyword.get(:handlers_count, 100) |> normalize_handlers_count()
matching_handlers_count =
opts
|> Keyword.get(:matching_handlers_count, handlers_count)
|> normalize_matching_handlers_count(handlers_count)
duration = Keyword.get(opts, :duration, 10)
event = setup(handlers_count, matching_handlers_count)
benchee_opts =
[parallel: parallelism, time: duration] ++ maybe_save_opts(opts) ++ maybe_load_opts(opts)
Benchee.run(benchmark_spec(event), benchee_opts)
end
defp setup(handlers_count, matching_handlers_count) do
Mix.shell().info("Setting up benchmark...")
impl_modules = Map.values(@impls)
Mix.shell().info("Starting implementations...")
{:ok, _pid} = Supervisor.start_link(impl_modules, strategy: :one_for_one, max_restarts: 0)
Mix.shell().info("Attaching #{handlers_count} handlers...")
for num <- handler_numbers(handlers_count) do
for impl <- impl_modules do
:ok = impl.attach(num, handler_prefix(num), Handler, :handle, nil)
end
end
covering_event_name(matching_handlers_count)
end
defp normalize_matching_handlers_count(count, handlers_count) when count < 0 do
Mix.shell().info(
"Requested handlers count is less than 0 (#{count}). Falling back to #{handlers_count}."
)
handlers_count
end
defp normalize_matching_handlers_count(count, handlers_count) when count > handlers_count do
Mix.shell().info(
"Requested handlers count is greater than #{handlers_count}. Falling back to #{
handlers_count
}."
)
handlers_count
end
defp normalize_matching_handlers_count(count, _), do: count
defp normalize_handlers_count(count) when count < 0 do
Mix.shell().info(
"Requested handlers count is less than 0 (#{count}). Falling back to default 100."
)
100
end
defp normalize_handlers_count(count) do
count
end
defp handler_numbers(0) do
[]
end
defp handler_numbers(count) do
1..count
end
defp handler_prefix(num) do
Enum.map(1..num, &(&1 |> to_string() |> :erlang.binary_to_atom(:latin1)))
end
defp covering_event_name(0), do: []
defp covering_event_name(count), do: handler_prefix(count)
defp benchmark_spec(event) do
for {name, impl_module} <- @impls, into: %{} do
{name, fn -> impl_module.list_handlers_for_event(event) end}
end
end
defp maybe_save_opts(opts) do
case Keyword.fetch(opts, :save) do
{:ok, save_path} ->
current_ts =
:calendar.local_time() |> NaiveDateTime.from_erl!() |> NaiveDateTime.to_iso8601()
save_tag = Keyword.get(opts, :save_tag, current_ts)
[save: [path: save_path, tag: save_tag]]
:error ->
[]
end
end
defp maybe_load_opts(opts) do
case Keyword.fetch(opts, :load) do
{:ok, load_path} ->
[load: load_path]
:error ->
[]
end
end
end