Skip to content

Commit

Permalink
Support for OTP26/27
Browse files Browse the repository at this point in the history
Minor fixes & improvements, fix lock contention tests for newer GitHub Test Runners (EPYC machines).
  • Loading branch information
max-au authored Apr 13, 2024
2 parents bcb318f + 6a58c74 commit ae4ee23
Show file tree
Hide file tree
Showing 11 changed files with 50 additions and 40 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/erlang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:

strategy:
matrix:
otp_version: [23, 24, 25]
otp_version: [23, 24, 25, 26]
os: [ubuntu-latest]

container:
Expand All @@ -36,4 +36,4 @@ jobs:
run: rebar3 as prod escriptize
- shell: bash
name: Smoke test
run: ./erlperf 'timer:sleep(1).'
run: ./erlperf 'timer:sleep(1).'
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 2.2.1
- tested with OTP 26 and 27
- updated to argparse 2.0.0

## 2.2.0
- added extended and full reporting capabilities
- implemented additional statistics (standard deviation, median, p99)
Expand Down
2 changes: 1 addition & 1 deletion CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Initialisation and cleanup definitions are read in the same order as runner code
```bash
# first runner receives 1 as input, second - 2
erlperf --init_runner '1.' 'run(1) -> ok.' 'run(2) -> ok.' --init_runner '2.'
# next run fails with function_clause, because first runner receives '2', and secdond - 1
# next run fails with function_clause, because first runner receives '2', and second - 1
erlperf --init_runner '2.' 'run(1) -> ok.' 'run(2) -> ok.' --init_runner '1.'
```

Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,16 @@ dictionary.
r() -> rand:uniform(). 1 16958 Ki 58 ns 65%
```

5. Estimate `code:is_loaded/1` concurrency characteristics. This function is implemented as
`gen_server:call`, and all calculations are done in a single process. It is still possible to
squeeze a bit more from a single process by putting work into the queue from multiple runners.
5. Estimate `./erlperf 'application_controller:is_running(kernel).` concurrency characteristics. This function
is implemented as `gen_server:call`, and all calculations are done in a single process. It is still
possible to squeeze a bit more from a single process by putting work into the queue from multiple runners.

```bash
$ ./erlperf 'code:is_loaded(local_udp).' --init 'code:ensure_loaded(local_udp).' --squeeze
Code || QPS Time
code:is_loaded(local_udp). 4 969 Ki 4129 ns
$ ./erlperf 'application_controller:is_running(kernel).' --squeeze
Code || QPS Time
application_controller:is_running(kernel). 3 1189 Ki 2524 ns



$ ./erlperf 'persistent_term:put(atom, "string").' -q
Code || QPS Time
Expand Down Expand Up @@ -309,16 +311,16 @@ on disk.
Use `-pa` argument to add an extra code path. Example:
```bash
$ ./erlperf 'argparse:parse([], #{}).' -pa _build/test/lib/argparse/ebin
$ ./erlperf 'args:parse([], #{}).' -pa _build/test/lib/argparse/ebin
Code || QPS Time
argparse:parse([], #{}). 1 955 Ki 1047 ns
args:parse([], #{}). 1 955 Ki 1047 ns
```
If you need to add multiple released applications, supply `ERL_LIBS` environment variable instead:
```bash
$ ERL_LIBS="_build/test/lib" erlperf 'argparse:parse([], #{}).'
$ ERL_LIBS="_build/test/lib" erlperf 'args:parse([], #{}).'
Code || QPS Time
argparse:parse([], #{}). 1 735 Ki 1361 ns
args:parse([], #{}). 1 735 Ki 1361 ns
```
### Usage in production
Expand Down
6 changes: 3 additions & 3 deletions rebar.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{"1.2.0",
[{<<"argparse">>,{pkg,<<"argparse">>,<<"1.2.4">>},0}]}.
[{<<"argparse">>,{pkg,<<"argparse">>,<<"2.0.0">>},0}]}.
[
{pkg_hash,[
{<<"argparse">>, <<"A31E7C5D9F8814AFCD9B42C1B98C21DA6F851F93F2C8C00C107F6201668A0A7D">>}]},
{<<"argparse">>, <<"3EDF299FB5BC089E6AF2F1A7C6532104B4CD6136E0147C6CF9622E6E4A741434">>}]},
{pkg_hash_ext,[
{<<"argparse">>, <<"AC6FDB7183EA20ADEB7EB66E34B21F2E8C4C6925913EE0C0765D339D97009FFE">>}]}
{<<"argparse">>, <<"525979122BEA3641A1DD3ABC53F2ADD19F7F427D507018F8C7CAF0693A6E78C8">>}]}
].
3 changes: 2 additions & 1 deletion src/erlperf.app.src
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{application, erlperf,
[{description, "Erlang Performance & Benchmarking Suite"},
{vsn, "2.2.0"},
{vsn, "2.2.1"},
{registered, [
erlperf_sup, erlperf_job_sup, erlperf_monitor,
erlperf_history, erlperf_file_log, erlperf_cluster_monitor
Expand All @@ -9,6 +9,7 @@
{applications,
[kernel,
stdlib,
compiler,
argparse
]},
{env,[]},
Expand Down
8 changes: 4 additions & 4 deletions src/erlperf_cli.erl
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ format(Reports, Options) ->
main(Args) ->
Prog = #{progname => "erlperf"},
try
ParsedOpts = argparse:parse(Args, arguments(), Prog),
ParsedOpts = args:parse(Args, arguments(), Prog),

Verbose = maps:get(verbose, ParsedOpts, false),

Expand Down Expand Up @@ -149,8 +149,8 @@ main(Args) ->
Formatted = format(Results, FormatOpts#{viewport_width => viewport_width()}),
io:format(Formatted)
catch
error:{argparse, Reason} ->
Fmt = argparse:format_error(Reason, arguments(), Prog),
error:{args, Reason} ->
Fmt = args:format_error(Reason, arguments(), Prog),
format(info, "Error: ~s", [Fmt]);
throw:{parse, FunName, Other} ->
format(error, "Unable to read file named '~s' (expected to contain call chain recording)~nReason: ~p\n"
Expand Down Expand Up @@ -290,7 +290,7 @@ arguments() ->
"\nFull documentation available at: https://hexdocs.pm/erlperf/\n"
"\nBenchmark timer:sleep(1):\n erlperf 'timer:sleep(1).'\n"
"Benchmark rand:uniform() vs crypto:strong_rand_bytes(2):\n erlperf 'rand:uniform().' 'crypto:strong_rand_bytes(2).' --samples 10 --warmup 1\n"
"Figure out concurrency limits:\n erlperf 'code:is_loaded(local_udp).' --init 'code:ensure_loaded(local_udp).'\n"
"Figure out concurrency limits:\n erlperf 'application_controller:is_running(kernel).' -q'\n"
"Benchmark pg join/leave operations:\n erlperf 'pg:join(s, foo, self()), pg:leave(s, foo, self()).' --init 'pg:start_link(s).'\n"
"Timed benchmark for a single BIF:\n erlperf 'erlang:unique_integer().' -l 1000000\n",
arguments => [
Expand Down
2 changes: 1 addition & 1 deletion src/erlperf_file_log.erl
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ start_link(Filename) ->
% current format line
format = "" :: string(),
% saved list of job IDs executed previously
jobs = [] :: [erlperf_monitor:job_sample()]
jobs = [] :: [erlperf_monitor:monitor_sample()]
}).

%% @private
Expand Down
3 changes: 2 additions & 1 deletion src/erlperf_job.erl
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@
%% worker process, so you can initialise process-local values (e.g.
%% process dictionary)</li>
%% <li>`runner/0,1,2' defines the function that will be called in a tight loop.
%% See detailed description below</li>
%% See <a href="#module-runner-function">Runner Function</a> for
%% overview of a runner function variants.</li>
%% <li>`done/0,1' - called when the job terminates, to clean up any resources
%% that are not destroyed automatically. done/0 accepts the return of init/0.
%% Call is made in the context of the job controller</li>
Expand Down
10 changes: 6 additions & 4 deletions test/erlperf_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -259,19 +259,21 @@ lock_contention(Config) when is_list(Config) ->
%% need at the very least 4 schedulers to create enough contention
case erlang:system_info(schedulers_online) of
Enough when Enough >= 4 ->
Tuple = {lists:seq(1, 5000), list_to_tuple(lists:seq(1, 10000))},
Init = fun() -> ets:new(tab, [public, named_table]) end,
Done = fun(Tab) -> ets:delete(Tab) end,
Runner = fun() -> true = ets:insert(tab, {key, value}) end, %% this inevitably causes lock contention
Runner = fun() -> true = ets:insert(tab, Tuple) end, %% this inevitably causes lock contention
%% take 50 samples of 10 ms, which should complete in about a second, and 10 extra warmup samples
%% hoping that lock contention is detected at warmup
Before = os:system_time(millisecond),
Report = erlperf:run(#{runner => Runner, init => Init, done => Done},
#{concurrency => 50, samples => 50, sample_duration => 10, warmup => 10, report => full}),
#{result := #{average := QPS}, sleep := busy_wait} = Report,
#{concurrency => Enough * 4, samples => 50, sample_duration => 10, warmup => 10, report => full}),
TimeSpent = os:system_time(millisecond) - Before,
#{result := #{average := QPS}, sleep := DetectedSleepType} = Report,
?assertEqual(busy_wait, DetectedSleepType, {"Lock contention was not detected", Report}),
?assert(QPS > 0, {qps, QPS}),
?assert(TimeSpent > 500, {too_quick, TimeSpent, expected, 1000}),
?assert(TimeSpent < 2000, {too_slow, TimeSpent, expected, 1000});
?assert(TimeSpent < 3000, {too_slow, TimeSpent, expected, 1000});
NotEnough ->
{skip, {not_enough_schedulers_online, NotEnough}}
end.
Expand Down
26 changes: 13 additions & 13 deletions test/erlperf_cli_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ parse_qps(QPST, "Mi") -> list_to_integer(QPST) * 1000000;
parse_qps(QPST, "Gi") -> list_to_integer(QPST) * 1000000000;
parse_qps(QPST, "Ti") -> list_to_integer(QPST) * 1000000000000.

parse_duration(TT, "ns") -> list_to_integer(TT) div 1000;
parse_duration(TT, "us") -> list_to_integer(TT);
parse_duration(TT, "ms") -> list_to_integer(TT) * 1000;
parse_duration(TT, "s") -> list_to_integer(TT) * 1000000;
parse_duration(TT, "m") -> list_to_integer(TT) * 60 * 1000000.
parse_duration(TT, "ns") -> list_to_integer(TT);
parse_duration(TT, "us") -> list_to_integer(TT) * 1000;
parse_duration(TT, "ms") -> list_to_integer(TT) * 1000000;
parse_duration(TT, "s") -> list_to_integer(TT) * 1000000000;
parse_duration(TT, "m") -> list_to_integer(TT) * 60 * 1000000000.

filtersplit(Str, Sep) ->
[L || L <- string:split(Str, Sep, all), L =/= ""].
Expand Down Expand Up @@ -120,14 +120,14 @@ simple(Config) when is_list(Config) ->
Out = capture_io(fun() -> erlperf_cli:main([Code, "-d", "100"]) end),
[{Code, 1, C, T}] = parse_out(Out),
?assert(C > 25 andalso C < 110, {qps, C}),
?assert(T > 1000 andalso T < 3000, {time, T}).
?assert(T > 1000000 andalso T < 3000000, {time, T}).

concurrent(Config) when is_list(Config) ->
Code = "timer:sleep(1).",
Out = capture_io(fun() -> erlperf_cli:main([Code, "-d", "100", "-c", "8"]) end),
[{Code, 8, C, T}] = parse_out(Out),
?assert(C > 8 * 25 andalso C < 8 * 110, {qps, C}),
?assert(T > 1000 andalso T < 3000, {time, T}).
?assert(T > 1000000 andalso T < 3000000, {time, T}).

% erlperf 'timer:sleep(1). -v'
verbose(Config) when is_list(Config) ->
Expand All @@ -139,7 +139,7 @@ verbose(Config) when is_list(Config) ->
%% parse last 2 lines
[{Code, 1, C, T}] = parse_out(lists:join("\n", lists:sublist(Lines, length(Lines) - 1, 2))),
?assert(C > 250 andalso C < 1101, {qps, C}),
?assert(T > 1000 andalso T < 3000, {time, T}).
?assert(T > 1000000 andalso T < 3000000, {time, T}).

% erlperf 'timer:sleep(1).' 'timer:sleep(2).' -d 100 -s 5 -w 1 -c 2
compare(Config) when is_list(Config) ->
Expand All @@ -163,7 +163,7 @@ squeeze(Config) when is_list(Config) ->
fun () -> erlperf_cli:main(["timer:sleep(1).", "--duration", "50", "--squeeze", "--min", "2", "--max", "4", "--threshold", "2"]) end),
[{_Code, 4, C, T}] = parse_out(Out),
?assert(C > 50 andalso C < 220, {qps, C}),
?assert(T > 1000 andalso T < 3000, {time, T}).
?assert(T > 1000000 andalso T < 3000000, {time, T}).

% erlperf -q
usage(Config) when is_list(Config) ->
Expand Down Expand Up @@ -237,7 +237,7 @@ full_report(Config) when is_list(Config) ->
?assert(Med =< P99),
?assert(Dev < 50, {deviation, Dev}),
?assert(Avg > 25 andalso Avg < 110, {avg, Avg}),
?assert(Time > 1000 andalso Time < 3000, {time, Time}).
?assert(Time > 1000000 andalso Time < 3000000, {time, Time}).

% erlperf 'timer:sleep(1).' -r basic -s 3 -l 50
basic_timed_report(Config) when is_list(Config) ->
Expand All @@ -246,7 +246,7 @@ basic_timed_report(Config) when is_list(Config) ->
[{_Code, 1, QPS, IterTime}] = parse_out(Out),
ct:pal("Basic Timed Report:~n~p", [Out]),
?assert(QPS > 250 andalso QPS < 1100, {qps, QPS}), %% QPS of 'timer:sleep(1)' is ~500
?assert(IterTime >= 1000 andalso IterTime < 3000, {time, IterTime}). %% single iteration of timer:sleep(1)
?assert(IterTime >= 1000000 andalso IterTime < 3000000, {time, IterTime}). %% single iteration of timer:sleep(1)

% erlperf 'timer:sleep(1).' -r full -l 100 -s 5
full_timed_report(Config) when is_list(Config) ->
Expand All @@ -262,8 +262,8 @@ full_timed_report(Config) when is_list(Config) ->
?assertEqual(5, Samples),
?assert(Med =< P99),
?assert(Dev < 50, {deviation, Dev}),
?assert(Avg >= 200000 andalso Avg < 400000, {avg, Avg}), %% average time to complete 100 iterations of sleep(1)
?assert(Time >= 1000 andalso Time < 3000, {time, Time}). %% single timer:sleep(1) time, in us
?assert(Avg >= 200000000 andalso Avg < 400000000, {avg, Avg}), %% average time to complete 100 iterations of sleep(1)
?assert(Time >= 1000000 andalso Time < 3000000, {time, Time}). %% single timer:sleep(1) time, in us


% erlperf 'runner(Arg) -> ok = pg2:join(Arg, self()), ok = pg2:leave(Arg, self()).' --init 'ets:file2tab("pg2.tab").'
Expand Down

0 comments on commit ae4ee23

Please sign in to comment.