Codebase list rabbitmq-server / ad452a29-3a19-4b84-9229-33db98b5405b/main
New upstream release. Debian Janitor 2 years ago
301 changed file(s) with 6550 addition(s) and 4616 deletion(s). Raw diff Collapse all Expand all
00 build --incompatible_strict_action_env
1 build --local_test_jobs=1
12
23 build:buildbuddy --bes_results_url=https://app.buildbuddy.io/invocation/
34 build:buildbuddy --bes_backend=grpcs://cloud.buildbuddy.io
2626 exec_properties = {
2727 "OSFamily": "Linux",
2828 # linux-erlang-23.3
29 "container-image": "docker://pivotalrabbitmq/rabbitmq-server-buildenv@sha256:16439a9c4a519d39acc098fdc4d4002f60d6f19762423e71eac24af85f5fddb7",
29 "container-image": "docker://pivotalrabbitmq/rabbitmq-server-buildenv@sha256:5de95518e8d5f3724839ad46e450b80d89cb0e7e546872a63b7ce4fd482a696e",
3030 },
3131 )
3232
00 load("@//:rabbitmq_package_generic_unix.bzl", "rabbitmq_package_generic_unix")
1 load("@//:rabbitmq_run.bzl", "rabbitmq_run")
1 load("@//:rabbitmq_run.bzl", "rabbitmq_run", "rabbitmq_run_command")
2 load("@//:rabbitmqctl.bzl", "rabbitmqctl")
23
34 rabbitmq_package_generic_unix(
45 name = "broker-home",
5 sbin = glob(["sbin/*"]),
6 escript = glob(["escript/*"]),
7 plugins = [
8 "//plugins:standard_plugins",
9 "//plugins:inet_tcp_proxy_ez",
10 ],
6 additional_files =
7 glob(
8 [
9 "sbin/*",
10 "escript/*",
11 ],
12 exclude = ["sbin/rabbitmqctl"],
13 ) + [
14 "//plugins:standard_plugins",
15 "//plugins:inet_tcp_proxy_ez",
16 ],
17 rabbitmqctl = "sbin/rabbitmqctl",
1118 )
1219
1320 rabbitmq_run(
1522 home = ":broker-home",
1623 visibility = ["//visibility:public"],
1724 )
25
26 rabbitmq_run_command(
27 name = "broker",
28 rabbitmq_run = ":rabbitmq-run",
29 subcommand = "run-broker",
30 )
31
32 rabbitmqctl(
33 name = "rabbitmqctl",
34 home = ":broker-home",
35 )
36
37 rabbitmqctl(
38 name = "rabbitmq-diagnostics",
39 home = ":broker-home",
40 )
41
42 rabbitmqctl(
43 name = "rabbitmq-plugins",
44 home = ":broker-home",
45 )
3131 app_file(
3232 name = "app_file",
3333 app_name = "ranch",
34 app_src = ["src/ranch.app.src"],
34 app_version = "2.1.0",
3535 modules = [":first_beam_files", ":beam_files"],
3636 )
3737
3030
3131 http_archive(
3232 name = "bazel-erlang",
33 sha256 = "422a9222522216f59a01703a13f578c601d6bddf5617bee8da3c43e3b299fc4e",
34 strip_prefix = "bazel-erlang-1.1.0",
35 urls = ["https://github.com/rabbitmq/bazel-erlang/archive/refs/tags/1.1.0.zip"],
33 sha256 = "03b75fae7e3d3e80644ba1b0f1bf43b9da3c7c36435ea7baba0d0f87b3c05482",
34 strip_prefix = "bazel-erlang-1.2.0",
35 urls = ["https://github.com/rabbitmq/bazel-erlang/archive/refs/tags/1.2.0.zip"],
3636 )
3737
3838 load("@bazel-erlang//:bazel_erlang.bzl", "bazel_erlang_deps")
4545
4646 git_repository(
4747 name = "rabbitmq_ct_helpers",
48 branch = "v3.9.x",
48 commit = "f709a6f6a2345d3ab5076469cb8d8e42e89b2b59",
4949 remote = "https://github.com/rabbitmq/rabbitmq-ct-helpers.git",
5050 repo_mapping = {
5151 "@rabbitmq-server": "@",
5454
5555 git_repository(
5656 name = "rabbitmq_ct_client_helpers",
57 branch = "v3.9.x",
57 commit = "51ed1e59d725816cd82a3bd6211c043d385f180e",
5858 remote = "https://github.com/rabbitmq/rabbitmq-ct-client-helpers.git",
5959 repo_mapping = {
6060 "@rabbitmq-server": "@",
8989 """
9090
9191 http_archive(
92 name = "rabbitmq-server-generic-unix-3.8.18",
92 name = "rabbitmq-server-generic-unix-3.8.22",
9393 build_file = "@//:BUILD.package_generic_unix",
9494 patch_cmds = [ADD_PLUGINS_DIR_BUILD_FILE],
95 strip_prefix = "rabbitmq_server-3.8.18",
96 urls = ["https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.18/rabbitmq-server-generic-unix-3.8.18.tar.xz"],
95 strip_prefix = "rabbitmq_server-3.8.22",
96 urls = ["https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.22/rabbitmq-server-generic-unix-3.8.22.tar.xz"],
9797 )
0 rabbitmq-server (3.9.7-1) UNRELEASED; urgency=low
1
2 * New upstream release.
3
4 -- Debian Janitor <janitor@jelmer.uk> Sat, 25 Sep 2021 15:19:11 -0000
5
06 rabbitmq-server (3.9.4-1.2) unstable; urgency=medium
17
28 * Non-maintainer upload.
00 [{read_concurrency,false},{write_concurrency,false},{compressed,false},{heir,none},{name,'Elixir.Hex.Registry.Server'},{named_table,false},{type,set},{keypos,1},{protection,protected}].
1 [{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,50>>},<<218,31,129,51,81,87,77,55,61,179,235,87,157,45,159,228,185,29,239,194,50,224,8,39,238,90,163,139,86,196,193,210>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,51>>},<<222,146,226,188,133,188,9,236,103,111,25,246,236,45,170,89,186,31,1,3,209,218,150,207,12,202,54,52,44,144,217,63>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,50>>},<<194,18,58,158,240,37,55,126,12,183,146,202,226,3,176,251,111,81,83,62,45,9,138,157,207,126,43,240,18,221,33,194>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48,45,114,99,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,50>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,50,46,48>>},<<72,227,29,177,140,35,191,51,200,56,93,63,114,178,112,129,75,142,94,101,7,17,152,154,152,201,40,95,125,184,34,154>>},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,50>>},[]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,49>>},[]},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,50>>},nil},{{registry_etag,<<104,101,120,112,109>>,<<99,115,118>>},<<34,99,53,97,50,55,50,50,50,48,53,50,50,102,56,55,53,98,98,101,55,57,54,100,101,52,99,52,98,54,51,57,54,34>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,48>>},[]},{{versions,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>},[<<48,46,49,46,48>>,<<48,46,50,46,48>>,<<48,46,50,46,49>>,<<48,46,50,46,50>>,<<48,46,50,46,51>>,<<48,46,50,46,52>>]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,53>>},nil},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,48>>},{{2021,8,18},{12,33,12}}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,52>>},<<196,149,38,14,55,137,176,144,88,147,32,164,221,109,151,69,54,147,217,186,43,222,120,167,11,98,32,205,246,88,203,255>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,51>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>,false}]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,49>>},[]},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,51>>},nil},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,49>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,52>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,50>>},<<34,47,127,11,94,117,136,177,233,233,183,62,72,148,15,18,212,59,190,8,196,116,176,143,213,126,249,135,71,209,197,102>>},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,49>>},<<17,125,88,18,60,172,60,104,2,0,82,72,191,3,237,32,165,177,77,221,30,57,216,108,122,229,40,150,2,53,82,144>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,52>>},nil},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,49,46,48>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,50>>},<<206,219,47,35,1,113,230,209,4,35,174,232,206,144,81,40,51,176,126,151,135,71,204,131,107,15,44,3,127,207,119,157>>},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,48>>},<<197,103,12,1,118,217,143,197,134,60,112,72,163,173,92,172,44,1,81,110,133,253,40,176,162,35,166,106,125,235,140,234>>},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,49>>},<<40,31,44,187,67,159,227,192,67,119,62,230,200,49,114,171,216,3,38,220,198,21,63,100,48,228,212,230,177,77,126,157>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,49>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,49,46,48>>},nil},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,49>>},[]},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>},[]},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,49>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,53>>},<<110,77,136,127,221,142,139,234,129,238,215,150,170,88,155,200,14,52,183,137,97,121,182,132,249,117,202,184,58,27,23,62>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,48>>},<<227,19,177,246,201,71,212,213,150,103,66,219,100,159,247,226,103,54,224,132,176,162,127,161,246,74,202,120,50,120,28,196>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,49>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,49>>},<<80,227,39,73,149,59,107,249,129,141,191,237,129,207,17,144,227,140,223,36,249,88,145,48,49,8,8,116,134,197,146,94>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,48>>},<<72,36,218,219,1,225,67,149,246,94,157,24,218,41,77,83,67,38,215,238,215,121,150,243,194,47,116,169,135,1,121,235>>},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,48>>},{{2021,8,18},{12,33,12}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,50>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>,false}]},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,48>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,53>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,50>>},<<6,79,119,21,219,121,136,118,175,30,184,40,124,132,98,109,32,87,82,237,176,225,163,143,209,38,34,47,103,105,237,172>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,51>>},<<51,221,215,227,240,137,141,26,124,212,235,117,88,77,41,16,14,43,149,75,115,25,59,120,255,172,203,118,182,37,87,124>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,49>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>,<<48,46,50,46,49>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,48>>},<<3,204,80,111,88,227,177,218,36,255,228,114,121,197,14,205,7,16,167,169,40,52,192,58,35,38,244,226,34,80,213,77>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,50>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,52>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,49,45,83,78,65,80,83,72,79,84>>},<<107,98,70,96,40,214,224,171,62,198,128,92,81,244,168,134,41,230,152,183,29,248,224,31,165,92,92,74,49,174,190,107>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,49>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>},{{2021,8,18},{12,33,12}}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,51>>},<<61,45,231,167,16,185,190,212,207,189,174,4,25,217,139,25,133,99,75,216,204,31,38,239,149,118,194,235,154,166,179,94>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,51>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,48>>},<<128,143,97,68,230,177,7,27,6,94,239,120,144,128,132,98,39,236,140,208,209,97,22,203,135,221,149,155,216,18,239,43>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,50>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,48>>},<<60,135,105,131,179,53,62,116,172,85,245,4,171,228,244,113,115,162,199,86,112,89,188,95,36,84,45,187,182,47,19,218>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,49>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>,false}]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,51>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,52>>},<<18,52,153,51,188,38,72,236,120,159,155,27,62,139,58,235,92,234,198,54,131,247,95,139,203,7,135,0,36,91,135,76>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,49,46,48>>},nil},{{deps,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,48>>},[]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,48>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,51>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,49>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,48>>},<<162,54,21,16,103,251,60,169,201,96,54,128,105,50,209,5,210,224,199,188,179,119,133,19,239,254,106,108,59,35,58,96>>},{{outer_checksum,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>,<<48,46,50,46,49>>},<<40,42,138,50,60,162,168,69,201,230,247,135,209,102,52,143,119,108,29,74,65,237,230,48,70,215,45,66,46,61,169,70>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,50>>},nil},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,49,46,48>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,48>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,52>>},[]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,51>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,50>>},<<221,158,21,194,160,108,229,114,231,154,103,96,57,12,160,35,72,192,241,193,134,58,34,145,8,98,133,129,134,36,121,114>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,52,45,83,78,65,80,83,72,79,84>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,52>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,48>>},<<205,13,176,11,249,85,42,18,87,148,80,64,244,222,235,41,152,176,92,122,1,134,215,152,246,224,234,5,150,141,178,69>>},{{retired,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,50>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,49>>},<<58,232,236,78,152,164,72,80,212,148,216,68,28,136,150,99,28,41,91,53,65,202,199,145,229,110,160,136,91,167,223,63>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,51>>},nil},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,48>>},<<120,45,32,1,18,21,106,160,173,174,245,209,65,156,42,204,229,182,91,99,69,210,69,48,241,27,87,153,24,77,152,231>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,48,45,83,78,65,80,83,72,79,84>>},<<82,54,119,31,28,24,178,146,208,95,163,243,177,122,136,230,32,65,105,41,167,111,111,118,7,160,99,84,190,131,223,24>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,49>>},<<52,24,227,25,118,75,157,255,31,70,158,67,203,223,253,127,213,78,164,124,191,118,80,39,197,87,171,209,70,161,159,179>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,48>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,51>>},[]},{{timestamp,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>,<<48,46,50,46,49>>},{{2021,8,18},{12,33,13}}},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,48>>},<<110,216,76,213,101,167,236,112,146,0,220,53,152,58,23,181,31,118,152,56,180,196,142,200,193,64,28,253,16,50,224,254>>},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,48>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,51>>},<<60,195,226,58,205,234,110,131,64,115,25,39,91,193,250,174,216,30,159,132,1,73,217,241,199,203,174,78,19,145,128,169>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,48>>},<<135,70,99,76,186,2,83,179,168,68,50,96,128,215,141,77,124,129,171,147,194,248,161,18,241,10,234,167,248,181,81,137>>},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>},<<59,50,222,86,143,18,144,1,225,207,137,204,91,207,214,219,177,38,15,171,178,207,138,52,16,205,192,246,52,235,161,130>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,51,46,48>>},<<139,206,65,63,178,82,52,228,110,97,243,99,163,106,208,125,204,229,50,39,152,45,198,78,39,33,182,228,14,218,121,129>>},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,50>>},[]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,52>>},nil},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,48>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,49>>},{{2021,8,18},{12,33,14}}},{{versions,<<104,101,120,112,109>>,<<114,101,99,111,110>>},[<<50,46,50,46,48>>,<<50,46,50,46,49>>,<<50,46,51,46,48>>,<<50,46,51,46,49>>,<<50,46,51,46,50>>,<<50,46,51,46,51>>,<<50,46,51,46,52>>,<<50,46,51,46,53>>,<<50,46,51,46,54>>,<<50,46,52,46,48>>,<<50,46,53,46,48>>,<<50,46,53,46,49>>,<<50,46,53,46,50>>]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,49>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,50>>},<<44,117,35,200,222,233,29,255,65,246,179,214,60,186,43,212,158,182,210,254,91,241,238,192,223,127,135,235,94,35,14,28>>},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,49>>},<<220,199,223,5,177,219,110,77,26,62,164,3,245,148,210,223,129,195,79,185,24,146,201,196,194,204,109,44,245,156,34,154>>},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,52>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,51>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,51>>},<<139,0,144,177,58,66,52,58,215,9,237,8,129,17,253,64,169,228,194,209,129,158,246,193,230,1,52,113,52,237,52,208>>},{{retired,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,51>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,52>>},<<167,118,4,141,83,165,143,243,181,212,72,231,82,105,45,47,47,255,248,233,98,241,72,127,19,182,62,184,86,155,14,126>>},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,48>>},<<47,127,203,236,44,53,3,75,173,226,249,113,127,119,5,157,197,78,180,233,41,163,4,156,167,186,103,117,192,189,102,205>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,50>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,48>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,50>>},nil},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,48>>},nil},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,50>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,53>>},<<207,101,129,50,228,228,150,173,232,111,35,114,94,237,64,16,52,240,120,216,88,212,173,10,147,76,123,58,58,204,20,178>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,48>>},<<227,14,75,190,99,54,83,165,194,218,67,202,180,42,97,98,62,10,204,128,239,64,195,33,188,41,240,214,152,105,123,211>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,48>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,48>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>},<<166,252,251,218,190,26,239,145,25,184,113,48,72,70,177,206,40,45,154,65,36,188,221,175,199,97,108,26,37,108,117,150>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,48>>},<<153,68,136,43,113,245,91,37,3,102,61,156,181,77,63,28,123,189,247,204,109,208,28,196,14,168,239,81,32,118,1,236>>},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,48>>},[]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,50>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,51>>},<<28,55,228,101,104,121,52,189,79,135,20,25,68,145,107,252,74,110,96,231,95,211,42,107,78,234,230,154,136,205,203,158>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,53>>},<<64,60,123,48,80,27,93,51,205,168,188,107,254,48,127,89,219,1,95,199,37,86,122,86,4,218,231,246,5,165,30,23>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,50>>},<<221,82,137,92,76,143,33,54,94,138,69,110,63,63,180,18,123,84,143,1,54,24,160,87,216,253,118,164,42,200,244,239>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,49>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,51>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,53>>},<<76,120,211,230,117,249,239,248,133,203,226,82,200,154,143,193,210,251,128,60,13,3,169,20,40,30,88,120,52,224,148,49>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,48>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,49,46,48>>},<<247,30,210,221,230,58,44,168,206,231,133,5,145,64,254,121,160,122,142,67,65,133,52,199,94,32,21,56,185,12,31,185>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,48>>},<<158,206,25,186,234,42,11,160,204,226,167,220,175,197,212,98,51,88,47,253,21,126,117,89,227,31,149,135,213,169,181,63>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,51>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,49>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,49>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,48>>},<<144,237,193,93,248,153,119,172,98,173,124,194,8,202,22,226,66,122,169,193,130,137,238,155,243,134,208,96,117,166,219,140>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,50>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,51>>},<<204,30,48,54,44,84,234,91,230,196,12,140,160,37,46,105,227,103,226,22,134,157,107,154,241,123,89,58,246,167,104,36>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,51>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,48>>},<<16,133,39,148,221,224,88,193,239,122,91,157,83,212,8,217,4,194,7,110,159,171,116,172,67,252,103,109,50,227,218,211>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,49>>},<<84,80,137,56,172,103,226,121,102,177,14,244,150,6,227,173,89,149,214,101,215,252,38,136,239,179,234,177,48,124,144,121>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,51>>},nil},{{retired,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,48>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,51,46,48>>},<<44,107,172,83,3,113,61,27,67,202,111,60,136,23,103,245,123,42,223,102,140,222,48,19,189,113,87,220,101,239,41,156>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,52>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,53>>},<<79,89,119,17,222,247,188,179,135,133,25,245,127,149,88,219,60,157,134,138,238,65,83,94,220,220,32,170,192,179,158,104>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,49>>},<<67,15,250,96,104,90,193,239,223,177,254,76,151,184,118,124,146,208,217,46,110,124,62,134,33,85,155,167,117,152,103,138>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,49>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,50>>},[]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,51>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,49,45,83,78,65,80,83,72,79,84>>},<<232,37,108,31,91,115,43,205,72,90,169,75,192,19,113,243,36,225,20,154,143,38,38,35,64,124,162,204,19,171,8,74>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,51>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,49>>},[]},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,50>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,48>>},<<253,251,221,94,157,163,22,77,45,83,151,15,50,140,123,100,157,118,74,72,185,111,245,238,167,113,163,222,82,148,155,177>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>},<<68,68,200,121,190,50,59,27,19,62,236,82,65,203,132,189,56,33,234,25,76,116,13,117,97,126,16,107,228,116,67,24>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,51>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,50,46,48>>},nil},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,49>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>,false}]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,49>>},[]},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,51>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,49>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,48>>},<<37,176,134,181,185,240,219,77,168,137,42,78,21,67,87,112,109,29,219,116,166,190,176,5,234,235,228,11,96,67,46,140>>},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,49>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,52>>},<<136,22,143,146,203,209,35,224,41,13,135,208,161,202,128,3,232,99,170,67,10,225,114,87,177,231,195,222,195,33,119,158>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,49,46,48>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,49>>},<<121,180,219,142,142,93,115,110,206,124,216,109,238,5,68,39,61,188,79,192,147,122,157,207,219,65,51,176,151,41,238,29>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,57>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,49>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,50>>},nil},{version,3},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,48>>},<<105,239,141,192,74,76,174,199,209,37,41,162,55,199,180,155,139,177,255,48,198,228,44,186,234,71,245,179,206,145,253,189>>},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,53>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,50>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,48>>},nil},{{registry_etag,<<104,101,120,112,109>>,<<114,101,99,111,110>>},<<34,57,51,50,99,100,100,53,52,102,102,55,57,49,54,57,98,54,50,97,55,50,48,50,56,100,56,98,53,48,53,49,48,34>>},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,52>>},<<102,254,225,234,245,54,52,125,73,57,66,203,94,8,83,33,197,30,128,222,18,79,240,15,75,38,81,135,11,145,155,153>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,49>>},<<34,76,89,87,237,225,240,215,135,68,94,165,238,52,99,79,186,176,6,253,161,30,77,66,240,37,11,97,16,216,237,49>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,51,46,48>>},[]},{{retired,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,50>>},nil},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48,45,114,99,46,48>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,48>>},<<172,88,31,32,233,197,199,224,203,127,65,143,86,7,39,141,113,97,28,101,221,242,233,48,101,241,244,206,17,221,84,24>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,48>>},[]},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,49,46,48,45,83,78,65,80,83,72,79,84>>},#{message => <<82,101,116,105,114,101,100,32,82,67,32,118,101,114,115,105,111,110>>,reason => 'RETIRED_OTHER'}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,55>>},<<225,176,196,235,165,145,73,137,247,161,169,173,159,137,147,207,116,118,14,70,72,134,240,253,34,10,98,51,126,52,121,20>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,48>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,53>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,50>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,49>>},{{2021,8,18},{12,33,12}}},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,54>>},<<185,103,190,43,35,240,246,120,127,171,126,214,129,180,196,90,33,90,129,72,31,182,43,1,165,183,80,250,143,48,247,108>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,52>>},<<244,130,93,55,75,34,242,88,173,17,74,116,121,107,121,184,110,32,160,54,17,5,23,25,246,6,35,83,192,83,137,203>>},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,49>>},<<222,84,131,50,37,176,230,127,33,219,71,51,58,8,104,90,237,80,215,120,162,241,28,247,227,106,233,227,254,78,225,7>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,50>>},<<110,147,67,127,116,169,151,8,6,138,50,183,103,176,36,50,209,19,137,98,117,66,20,9,227,1,141,152,146,54,166,90>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48,45,114,99,46,48>>},<<36,170,165,33,61,173,49,64,42,227,123,151,158,11,91,183,126,152,210,219,190,163,38,237,145,161,0,12,145,196,17,19>>},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,49>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,53>>},<<56,183,89,231,65,206,79,134,87,102,151,38,186,237,121,114,96,39,96,147,19,252,40,47,90,103,30,25,132,140,33,53>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,52,45,83,78,65,80,83,72,79,84>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48,45,114,99,46,48>>},<<83,185,186,191,253,24,107,180,251,159,221,71,232,164,66,184,241,141,184,20,151,255,163,165,91,90,95,89,111,90,200,20>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,48>>},<<206,105,157,49,102,44,52,218,135,131,255,23,171,152,12,99,80,186,80,189,130,71,85,105,219,224,255,146,149,83,217,202>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,49>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,49>>},<<109,146,154,92,130,248,99,214,32,28,161,252,218,171,38,179,85,143,41,221,224,211,160,34,229,242,200,221,175,180,203,22>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,53>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,50>>},<<185,80,65,252,17,144,118,152,187,77,64,117,190,80,75,6,0,168,234,119,129,196,236,127,27,240,142,115,235,238,108,37>>},{{registry_etag,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>},<<34,99,54,54,102,51,57,48,54,52,52,101,101,49,50,97,100,54,101,97,50,56,50,48,49,56,52,101,48,54,55,55,100,34>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,49>>},<<209,118,249,103,201,120,171,139,138,41,195,92,18,82,79,120,183,187,54,253,78,155,130,118,221,117,201,203,86,224,126,66>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,51>>},<<84,125,88,219,189,134,76,10,86,12,12,163,120,158,232,132,137,116,232,197,23,205,183,130,124,51,3,227,180,46,117,77>>},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,53>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,57>>},<<110,125,164,69,241,34,142,178,227,58,182,153,25,226,63,248,245,119,212,177,213,64,199,18,55,58,247,109,196,24,107,86>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,52>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>},<<245,81,152,101,10,142,192,29,62,252,4,121,122,190,85,12,125,2,62,127,248,181,9,243,115,207,147,48,50,4,155,216>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,48>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,48>>},[]},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,48>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,49,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>,false}]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,49>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,52>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,49>>},<<222,148,84,174,200,249,97,163,28,227,188,38,197,39,87,121,10,152,130,7,85,93,208,130,110,90,96,165,229,244,16,167>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,52>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,51>>},<<54,23,39,196,84,172,47,207,55,141,38,16,79,102,79,157,134,244,254,184,32,110,4,45,190,24,116,214,109,132,163,224>>},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,48>>},<<114,243,132,15,237,217,79,6,49,92,82,63,108,236,245,180,130,114,51,190,215,174,63,225,53,178,160,235,234,181,225,150>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,51>>},<<243,239,123,26,226,138,85,229,59,140,181,193,29,14,11,100,231,110,56,213,243,232,48,191,46,59,242,204,10,137,216,72>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,49>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,50>>},nil},{{retired,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,52>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,50>>},<<72,67,100,98,6,104,157,95,209,160,235,61,14,8,188,194,70,39,52,17,101,196,9,111,104,119,219,243,240,116,38,190>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,48>>},<<188,191,142,48,184,96,93,175,22,10,140,42,41,213,74,23,46,82,15,75,160,227,94,17,122,106,162,64,96,208,159,6>>},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,48>>},<<59,28,140,11,227,44,39,32,207,5,135,132,129,87,236,95,14,20,247,84,219,99,34,156,52,187,19,90,94,236,181,40>>},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,48>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,49>>},<<33,232,214,152,48,145,102,15,57,79,68,167,193,31,140,241,244,103,175,178,170,218,31,207,89,194,241,181,144,164,47,104>>},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,50>>},<<7,70,231,172,135,119,124,3,107,104,136,211,75,235,196,163,110,83,65,104,59,87,163,225,184,103,14,93,9,182,129,82>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,50>>},[]},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,49>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,51>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,51>>},<<24,229,217,170,84,18,236,6,60,249,113,155,207,231,59,249,144,197,254,213,201,163,200,66,44,43,93,149,41,252,139,13>>},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,53>>},[]},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,55>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,56>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,49>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,52>>},<<153,47,46,20,24,132,154,50,111,209,217,40,120,1,250,45,134,9,29,180,249,97,31,96,120,29,166,210,54,246,76,212>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,51>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,50>>},<<10,15,244,148,212,41,101,199,80,103,93,163,225,174,2,244,58,151,127,76,237,209,79,159,115,152,247,177,247,37,52,29>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,49>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,52>>},<<144,235,14,1,159,220,17,222,131,168,140,108,26,139,190,153,105,68,81,76,246,187,206,222,7,44,143,34,45,28,113,35>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,52,45,83,78,65,80,83,72,79,84>>},[]},{{versions,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>},[<<49,46,48,46,50>>,<<49,46,48,46,51>>,<<49,46,48,46,52>>,<<49,46,48,46,53>>,<<49,46,48,46,55>>,<<49,46,48,46,56>>,<<49,46,48,46,57>>,<<49,46,49,46,48>>,<<49,46,50,46,48>>,<<49,46,50,46,49>>,<<49,46,50,46,50>>,<<49,46,51,46,48>>,<<49,46,51,46,49>>,<<49,46,51,46,50>>,<<49,46,51,46,51>>,<<49,46,51,46,52>>,<<49,46,52,46,48>>,<<49,46,52,46,49>>,<<49,46,52,46,50>>,<<49,46,52,46,51>>,<<49,46,52,46,52>>,<<49,46,52,46,53>>,<<49,46,53,46,48>>,<<49,46,53,46,49>>,<<49,46,53,46,50>>,<<49,46,53,46,51>>,<<49,46,53,46,52>>,<<49,46,54,46,48>>,<<49,46,54,46,49>>,<<49,46,54,46,50>>]},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,48>>},<<241,44,133,153,45,45,129,212,251,122,96,34,90,55,175,239,58,240,6,152,35,204,50,26,37,233,223,157,59,203,110,210>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,50>>},<<43,187,237,224,72,117,138,214,127,122,117,84,47,144,246,148,202,128,50,50,162,182,127,221,127,159,218,152,141,186,154,137>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,48,45,83,78,65,80,83,72,79,84>>},<<102,119,195,9,132,22,101,46,0,16,158,203,221,72,26,117,90,45,1,123,86,124,29,233,255,150,17,77,155,211,112,222>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,51>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,48>>},<<77,136,166,207,49,69,174,71,48,206,151,157,195,38,52,182,223,150,54,59,81,222,161,94,32,206,80,184,22,1,35,229>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,49>>},<<130,13,172,184,217,233,253,211,206,164,132,51,239,127,49,178,246,251,249,233,63,255,40,220,35,138,5,190,195,76,57,87>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48>>},<<137,173,179,41,68,72,116,128,150,65,204,47,100,90,236,43,211,141,253,132,30,127,228,193,179,188,162,230,249,26,26,243>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,52>>},<<223,18,100,181,135,27,186,248,218,79,17,254,173,250,13,198,97,244,213,17,73,58,37,248,249,199,214,21,224,214,20,213>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,50>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,51>>},<<212,46,32,5,65,22,196,157,82,66,211,255,158,25,19,172,204,235,230,1,95,68,157,110,49,42,91,193,96,231,154,98>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,55>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,32,50,46,51,46,49>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,49>>},<<61,98,119,99,233,68,81,236,33,113,230,208,52,205,163,1,55,45,225,205,240,11,189,224,214,126,14,174,103,116,102,219>>},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,48>>},[]},{{versions,<<104,101,120,112,109>>,<<106,115,111,110>>},[<<48,46,51,46,48>>,<<48,46,51,46,50>>,<<48,46,51,46,51>>,<<49,46,48,46,48>>,<<49,46,48,46,49>>,<<49,46,48,46,50>>,<<49,46,48,46,51>>,<<49,46,48,46,52,45,83,78,65,80,83,72,79,84>>,<<49,46,49,46,48>>,<<49,46,50,46,48>>,<<49,46,50,46,49>>,<<49,46,50,46,50>>,<<49,46,50,46,51>>,<<49,46,50,46,52>>,<<49,46,50,46,53>>,<<49,46,51,46,48>>,<<49,46,52,46,48>>,<<49,46,52,46,49>>,<<50,46,48,46,48,45,83,78,65,80,83,72,79,84>>,<<50,46,48,46,49,45,83,78,65,80,83,72,79,84>>,<<50,46,49,46,48,45,83,78,65,80,83,72,79,84>>]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,51>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,49,46,48>>},<<130,163,223,22,68,204,169,183,106,75,126,192,23,189,164,63,18,214,96,37,232,245,60,212,77,210,176,19,164,69,251,147>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,48>>},<<176,130,101,218,0,52,39,160,71,207,77,50,70,74,228,200,25,201,193,58,33,248,106,182,237,228,254,60,75,17,130,13>>},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,52>>},[]},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,50>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,51>>},<<119,62,225,102,102,158,178,27,82,151,206,65,179,199,180,116,190,9,110,184,237,5,45,69,112,129,40,154,28,242,80,122>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,50>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,50>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,52>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,49>>,false}]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,49>>},[]},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,48>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,49>>},<<159,1,192,129,120,215,122,101,122,54,82,135,8,70,212,202,238,122,123,113,202,50,151,29,247,206,87,192,121,218,111,222>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,49>>},nil},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,53>>},[]},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,49>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,53>>},<<16,58,136,179,90,253,98,119,4,200,46,74,189,12,206,172,147,54,164,224,65,214,227,196,113,15,233,60,72,73,65,147>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,49>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,48>>},<<0,214,237,215,48,254,153,35,132,121,150,70,157,1,139,250,210,149,54,105,189,43,99,91,7,84,206,45,213,159,166,37>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,53>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,52>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,49>>},<<155,164,209,166,168,242,208,178,113,109,226,178,165,204,43,170,204,126,246,97,224,35,89,184,212,159,28,14,122,140,94,203>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48,45,114,99,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,50>>},<<99,11,135,164,61,90,213,41,40,103,253,138,103,49,217,123,60,131,58,11,150,140,210,3,25,183,9,32,254,241,64,107>>},{{registry_etag,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>},<<34,56,100,48,55,102,97,49,50,50,98,102,51,57,57,56,49,56,102,50,101,49,57,101,57,98,56,54,48,98,55,100,54,34>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,52,45,83,78,65,80,83,72,79,84>>},<<239,82,71,134,18,4,215,101,218,196,201,174,239,141,242,228,84,208,44,85,221,176,130,198,199,103,181,203,151,45,101,25>>},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,54>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,51>>},<<9,86,102,121,241,147,49,21,246,24,158,108,78,32,94,53,170,234,28,174,220,248,67,135,92,62,182,94,236,131,228,136>>},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,54>>},[]},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>},{{2021,8,18},{12,33,12}}},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,51>>,false}]},{{deps,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,52>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,49>>},<<244,176,215,37,57,47,193,87,159,2,33,179,207,112,170,174,89,243,238,225,104,54,215,40,142,180,84,243,238,92,236,34>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,50>>},nil},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,48>>},{{2021,8,18},{12,33,12}}},{{deps,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,49,46,48>>},[]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,48,45,83,78,65,80,83,72,79,84>>},[]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,50,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,52>>},<<105,102,54,18,176,225,123,172,122,29,127,26,69,235,225,149,15,19,218,24,10,69,123,7,223,63,175,139,223,24,200,149>>},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,48>>},<<59,151,167,246,49,247,35,53,197,202,105,173,82,159,74,171,245,78,9,177,14,190,12,114,5,182,175,122,13,146,27,148>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,50>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,55>>},<<84,1,54,83,199,46,117,255,59,75,72,115,66,223,229,103,21,201,116,159,249,195,26,125,232,248,77,24,160,158,28,60>>},{{inner_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,49>>},<<24,34,98,146,249,177,109,241,247,196,117,36,59,152,185,133,59,170,233,18,101,88,198,253,222,53,77,27,159,120,134,56>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,49,45,83,78,65,80,83,72,79,84>>},[]},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,48>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,48>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,53>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,53>>},<<54,130,193,140,107,7,72,13,242,18,45,13,175,92,5,69,123,66,193,153,15,25,124,227,222,83,136,78,139,168,52,196>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,49>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,51>>,false}]},{{registry_etag,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>},<<34,52,54,57,51,51,55,102,53,98,99,52,97,98,101,52,53,101,102,102,102,52,49,100,55,102,51,53,97,56,101,56,99,34>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,50>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,48>>},<<132,183,103,162,219,142,204,47,7,187,132,252,180,245,79,37,22,12,7,161,218,226,124,70,11,63,12,64,191,203,247,159>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,49>>},<<127,124,167,45,243,210,158,82,45,55,170,37,73,44,171,71,77,10,229,214,151,31,14,228,151,173,141,53,228,113,230,18>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,49>>},nil},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,51,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,51,46,48>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,52>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,32,50,46,50,46,49>>,false}]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,51>>},nil},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,49>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,49>>},[]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,51>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,52,45,83,78,65,80,83,72,79,84>>},<<170,203,72,169,240,220,55,111,21,223,48,101,202,33,132,115,141,215,201,81,80,174,216,60,40,206,11,171,181,110,91,152>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>,false}]},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,50>>},nil},{{deps,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,50>>},[]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,52>>},<<98,26,26,62,83,107,158,33,99,236,107,176,120,77,253,229,236,19,218,138,190,196,107,183,101,189,126,177,248,173,21,2>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,49>>},<<0,37,224,56,226,242,232,197,200,36,13,24,188,252,17,177,239,225,162,86,113,101,111,197,178,253,214,112,245,72,222,52>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,50>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,49>>,false}]},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,51>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,48>>},<<126,106,131,77,27,49,48,195,34,70,205,62,157,56,3,252,52,28,167,175,148,54,47,51,33,56,139,197,165,119,161,26>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,50>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,51>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,51>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>},<<180,6,194,252,205,234,160,217,78,35,181,227,10,227,214,53,162,212,97,227,99,165,201,198,49,104,151,3,124,240,80,210>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>},nil},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,49>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,49>>},<<134,72,240,74,148,57,118,90,212,73,188,86,163,255,125,139,17,221,68,255,8,255,205,239,196,50,159,124,147,132,61,250>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,49>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,51>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,50>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,49>>},nil},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,56>>},[]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,49>>},nil},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,49,45,83,78,65,80,83,72,79,84>>},#{message => <<82,101,116,105,114,101,100,32,82,67,32,118,101,114,115,105,111,110>>,reason => 'RETIRED_OTHER'}},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,50>>},[]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,49,46,48>>},<<226,111,246,64,124,87,46,227,117,253,144,19,151,219,230,226,40,138,182,110,71,245,89,90,225,173,188,186,123,187,147,88>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,48>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,50>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,50>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,51>>},[{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101>>,<<98,101,110,99,104,101,101>>,<<126,62,32,48,46,56>>,true},{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<126,62,32,48,46,49>>,true},{<<104,101,120,112,109>>,<<101,120,106,115,120>>,<<101,120,106,115,120>>,<<126,62,32,52,46,48>>,true},{<<104,101,120,112,109>>,<<106,97,115,111,110>>,<<106,97,115,111,110>>,<<126,62,32,49,46,48>>,true},{<<104,101,120,112,109>>,<<106,115,111,110,101>>,<<106,115,111,110,101>>,<<126,62,32,49,46,52>>,true},{<<104,101,120,112,109>>,<<112,111,105,115,111,110>>,<<112,111,105,115,111,110>>,<<126,62,32,51,46,48>>,true},{<<104,101,120,112,109>>,<<116,105,110,121>>,<<116,105,110,121>>,<<126,62,32,49,46,48>>,true}]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,48>>},<<237,244,27,181,79,195,77,80,10,31,149,28,153,82,162,59,134,174,136,12,189,252,117,59,72,225,183,14,63,184,238,19>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,51>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,49>>},<<156,225,30,255,90,116,160,123,175,55,135,178,177,157,215,152,114,77,41,169,195,164,146,164,29,243,159,106,246,134,218,14>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,48>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,49>>},nil},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,49>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,53>>,false}]},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,49>>},[]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,48>>},nil},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>},[]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,50>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,48>>,false}]},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,50>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,48>>},<<143,86,163,52,2,26,42,207,236,45,147,7,8,208,240,25,53,241,101,76,138,222,121,119,187,241,248,235,72,67,9,114>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,48>>},<<211,69,204,226,233,242,86,144,250,158,83,33,6,112,113,98,221,93,141,153,138,197,64,226,68,231,158,219,138,143,60,44>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,50>>},[]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,51>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,52>>},nil},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,52>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,50,46,48>>},<<145,11,107,111,136,138,168,176,247,155,220,168,171,244,206,33,214,76,152,252,135,182,186,139,106,231,242,237,34,85,28,250>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,49>>},<<133,128,81,127,114,25,171,182,235,26,100,132,19,211,194,49,151,164,153,232,238,12,184,91,99,120,17,14,55,59,18,213>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,53>>},<<29,227,205,145,60,190,39,174,240,172,159,191,122,176,110,194,117,248,93,162,139,55,96,100,128,73,153,209,214,51,54,122>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,49>>},<<97,30,5,50,254,221,69,36,250,157,80,45,165,247,59,154,203,174,132,156,144,9,236,40,125,217,158,208,156,33,90,65>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,50>>},<<228,10,21,153,48,36,160,203,79,94,34,129,109,107,227,64,2,204,68,99,198,23,137,237,53,236,148,196,25,17,235,238>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,53>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,52>>},<<81,241,223,146,27,4,119,39,94,167,18,118,48,66,21,93,188,116,172,199,93,150,72,219,213,73,133,196,92,145,59,41>>},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,49>>},[]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,53,46,48>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,51>>},<<71,184,128,141,153,7,132,52,53,26,145,255,10,183,239,226,166,131,80,36,207,53,156,206,25,80,127,16,58,100,69,117>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,50>>},<<253,239,107,136,195,29,76,34,224,117,107,33,94,103,115,31,76,39,57,250,253,19,168,240,150,226,247,69,122,201,252,195>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,50>>},[{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101>>,<<98,101,110,99,104,101,101>>,<<126,62,32,48,46,56>>,true},{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<126,62,32,48,46,49>>,true},{<<104,101,120,112,109>>,<<101,120,106,115,120>>,<<101,120,106,115,120>>,<<126,62,32,52,46,48>>,true},{<<104,101,120,112,109>>,<<106,97,115,111,110>>,<<106,97,115,111,110>>,<<126,62,32,49,46,48>>,true},{<<104,101,120,112,109>>,<<106,115,111,110,101>>,<<106,115,111,110,101>>,<<126,62,32,49,46,52>>,true},{<<104,101,120,112,109>>,<<112,111,105,115,111,110>>,<<112,111,105,115,111,110>>,<<126,62,32,51,46,48>>,true},{<<104,101,120,112,109>>,<<116,105,110,121>>,<<116,105,110,121>>,<<126,62,32,49,46,48>>,true}]},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>},{{2021,8,18},{12,33,12}}},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,51>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,50>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,49>>},<<154,191,33,141,190,78,164,252,184,117,224,135,213,249,4,239,38,61,1,46,229,237,33,212,110,157,188,166,63,5,61,22>>},{{versions,<<104,101,120,112,109>>,<<99,115,118>>},[<<48,46,49,46,48>>,<<48,46,49,46,49>>,<<48,46,50,46,48>>,<<48,46,50,46,49>>,<<48,46,50,46,50>>,<<49,46,48,46,48>>,<<49,46,48,46,49>>,<<49,46,49,46,48>>,<<49,46,49,46,49>>,<<49,46,49,46,50>>,<<49,46,49,46,51>>,<<49,46,49,46,52>>,<<49,46,49,46,53>>,<<49,46,50,46,48>>,<<49,46,50,46,49>>,<<49,46,50,46,50>>,<<49,46,50,46,51>>,<<49,46,50,46,52>>,<<49,46,51,46,48>>,<<49,46,51,46,49>>,<<49,46,51,46,50>>,<<49,46,51,46,51>>,<<49,46,52,46,48>>,<<49,46,52,46,49>>,<<49,46,52,46,50>>,<<49,46,52,46,51>>,<<49,46,52,46,52>>,<<50,46,48,46,48,45,114,99,46,48>>,<<50,46,48,46,48>>,<<50,46,49,46,48>>,<<50,46,49,46,49>>,<<50,46,50,46,48>>,<<50,46,51,46,48>>,<<50,46,51,46,49>>,<<50,46,52,46,48>>,<<50,46,52,46,49>>]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>},{{2021,8,18},{12,33,12}}},{{versions,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>},[<<48,46,50,46,49>>]},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,48>>},<<2,74,88,8,199,219,172,173,7,228,200,251,223,183,96,201,108,45,40,115,94,67,214,95,26,241,22,67,164,211,163,242>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,49,46,48,45,83,78,65,80,83,72,79,84>>},<<16,4,241,79,86,196,247,98,133,76,72,46,15,233,45,98,116,227,208,7,139,61,112,228,178,238,179,183,158,39,81,7>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,53>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,48>>},<<120,201,76,85,220,135,233,31,246,237,228,245,244,103,37,10,222,151,49,232,233,82,110,222,32,154,82,16,238,82,134,146>>},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,51>>},<<221,84,236,175,169,116,61,21,164,128,166,157,237,246,104,174,139,137,135,221,19,11,237,83,29,1,222,45,224,219,114,231>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,51>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,48>>},<<156,4,35,121,62,187,118,245,82,245,191,173,131,219,120,194,17,60,104,78,153,237,178,24,144,111,162,40,179,216,152,119>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,50>>},<<233,250,128,26,6,80,103,232,165,96,68,74,199,144,121,115,174,138,214,151,226,6,200,187,26,66,45,159,37,206,202,197>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,49>>},<<146,48,109,88,81,27,235,226,12,242,50,205,65,94,151,159,216,143,175,226,159,194,63,30,158,27,67,127,162,208,6,180>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,49,46,48>>},[{<<104,101,120,112,109>>,<<100,101,99,105,109,97,108>>,<<100,101,99,105,109,97,108>>,<<126,62,32,49,46,48>>,true}]},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,52>>},<<251,60,197,214,171,222,88,2,7,87,132,147,178,216,96,62,100,143,44,93,168,223,234,187,26,186,196,121,147,150,228,33>>},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,49>>},<<164,193,167,195,13,33,81,182,228,151,108,178,245,44,10,29,73,236,150,90,251,115,126,216,74,104,75,196,40,77,22,39>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,50>>},<<101,91,237,185,64,198,75,180,213,141,96,128,203,130,4,177,249,36,215,227,22,137,13,7,134,172,127,144,251,154,104,212>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,48>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,53>>},<<87,63,110,101,253,118,8,187,24,230,175,36,91,213,105,165,181,56,126,4,204,76,7,123,31,218,233,153,8,74,169,85>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,48>>},<<53,139,202,226,196,66,253,64,191,71,65,138,14,15,148,73,5,216,206,18,44,194,156,98,62,109,252,217,190,194,181,19>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,52>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,49,46,48>>},<<243,164,124,34,50,116,36,41,23,88,22,125,243,196,96,13,207,139,243,71,149,163,120,118,18,1,154,165,184,106,92,33>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,49>>},<<22,126,93,61,210,231,113,110,88,101,245,168,208,100,215,169,247,0,69,22,199,150,104,64,131,241,205,24,12,45,66,150>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,49,46,48,45,83,78,65,80,83,72,79,84>>},<<64,95,116,217,92,226,6,190,163,171,188,216,241,63,34,144,224,191,223,138,75,210,50,253,14,103,194,242,36,158,40,119>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,56>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,51>>},<<209,152,101,72,132,113,137,181,31,30,251,101,209,150,230,171,159,46,136,166,135,138,54,58,236,14,60,119,226,85,6,22>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,48>>},<<230,66,147,177,199,46,50,58,218,21,109,71,23,74,239,14,251,80,38,58,21,246,26,15,152,146,235,24,81,15,119,123>>},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,50>>},<<203,165,63,168,219,131,173,150,140,154,101,46,9,195,237,125,220,196,218,67,79,39,195,234,169,202,71,255,178,177,255,3>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,51>>},[]},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,49>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,49>>},<<78,125,235,70,12,170,104,171,2,130,210,4,228,58,85,50,98,180,93,64,169,61,208,16,154,133,137,103,171,28,143,191>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,52>>},<<7,202,4,16,92,47,46,71,50,244,235,97,134,62,24,231,241,183,181,11,109,30,227,30,126,23,205,198,18,154,77,225>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,50>>},<<217,196,187,165,244,253,121,45,191,150,34,0,67,83,251,201,249,118,86,33,218,135,58,172,135,166,143,252,81,205,108,102>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,50>>},{{2021,8,18},{12,33,12}}},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,51>>},nil},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,53>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,32,50,46,50,46,49>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,52>>},<<37,105,199,33,33,80,209,87,165,122,53,119,190,202,207,162,227,70,163,175,234,105,135,8,133,22,134,172,220,210,141,4>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,49,46,48>>},<<200,135,134,160,114,7,168,11,7,30,163,142,26,113,168,171,215,50,62,203,247,5,102,1,39,0,155,32,56,197,161,209>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,50>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,49>>},{{2021,8,18},{12,33,12}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,49>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,50>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,50,46,48>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,49>>},<<109,44,115,99,187,137,175,235,209,36,27,0,51,60,161,178,176,156,97,30,253,148,104,172,0,236,207,120,210,64,58,4>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,57>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,50>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,51>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,52>>},<<232,72,155,58,124,119,194,21,92,11,103,250,159,144,217,175,167,107,241,92,36,251,123,49,42,221,204,17,119,113,209,84>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,52>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,49>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,49,46,48>>},<<139,29,56,94,0,231,8,1,161,47,107,212,62,182,79,116,124,79,114,200,142,82,63,143,13,50,118,165,19,92,160,11>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,50>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,49>>},<<18,120,55,62,72,14,61,187,172,80,45,35,75,247,111,77,173,185,164,174,22,208,182,58,172,75,199,249,79,211,178,228>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,48>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,52>>},<<195,39,7,44,190,185,154,8,86,82,207,17,10,122,61,93,123,153,249,69,104,209,73,124,167,105,113,200,128,97,85,90>>},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>},{{2021,8,18},{12,33,12}}},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,48>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,48>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48>>},<<198,111,234,137,186,120,98,185,73,1,186,240,135,18,133,233,183,60,173,137,197,253,181,122,99,134,210,173,207,41,89,62>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,49>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,51>>},<<124,16,141,227,48,56,16,40,44,161,17,236,192,147,138,120,230,2,246,180,240,203,107,144,253,245,47,149,209,152,5,159>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,51>>},<<203,247,114,93,87,123,181,54,15,26,132,29,231,129,200,59,140,131,56,79,158,10,194,88,127,8,19,128,121,36,154,200>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,50>>},<<245,131,207,95,146,14,6,187,25,227,36,29,139,227,242,66,199,98,16,105,185,125,1,19,130,88,232,183,165,73,55,89>>},{{inner_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,51>>},<<236,36,134,141,134,25,117,122,104,240,121,131,87,199,25,8,7,161,207,196,44,233,12,24,194,55,96,229,146,73,162,26>>},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>},[]},{{versions,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>},[<<48,46,49,46,48>>,<<48,46,49,46,49>>,<<48,46,49,46,50>>,<<49,46,48,46,48>>,<<49,46,48,46,49>>,<<49,46,48,46,50>>,<<49,46,48,46,51>>,<<49,46,48,46,52>>,<<49,46,48,46,53>>,<<49,46,48,46,54>>]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,48>>},<<8,68,93,118,243,173,53,7,188,74,243,14,175,216,130,96,192,243,92,80,56,84,42,242,119,171,208,151,91,225,234,38>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,50>>},<<44,187,218,100,149,12,13,159,201,60,175,106,184,221,155,49,232,55,50,164,156,21,248,17,153,78,214,142,229,155,45,213>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,57>>},<<219,220,236,81,73,70,188,141,202,177,215,125,255,119,25,67,122,133,217,134,23,144,134,74,177,33,85,237,163,253,111,175>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,48,45,83,78,65,80,83,72,79,84>>},#{message => <<82,101,116,105,114,101,100,32,82,67,32,118,101,114,115,105,111,110>>,reason => 'RETIRED_OTHER'}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,48>>},<<126,52,227,187,132,18,57,63,249,247,246,37,143,69,206,99,36,123,203,172,231,142,225,73,214,152,89,118,13,216,14,92>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,49>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,49>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,49>>,false}]},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,50>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,50>>},<<140,102,178,169,55,154,72,235,237,246,174,23,188,43,114,195,171,233,203,219,247,130,73,57,128,105,103,50,97,1,195,113>>},{{repo,<<104,101,120,112,109>>},<<104,116,116,112,115,58,47,47,114,101,112,111,46,104,101,120,46,112,109>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,50>>},<<14,193,72,255,99,89,118,57,179,255,140,195,239,82,60,251,70,105,244,250,26,46,146,30,5,132,156,143,149,12,124,109>>},{{inner_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,52>>},<<217,57,74,179,245,173,205,169,122,75,139,28,11,121,141,203,40,107,7,145,60,64,32,234,127,120,200,135,35,251,20,94>>},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,49>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,48>>},<<38,30,64,121,8,118,140,103,246,243,173,85,119,190,179,66,64,69,24,207,58,141,28,37,249,112,76,8,167,126,137,109>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,49>>},<<33,182,157,95,117,31,169,225,236,145,71,77,68,247,183,80,73,20,184,249,214,168,53,45,56,133,111,119,129,224,124,53>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,51>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,49>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,49>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,50>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,49>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,51>>},<<21,65,73,21,16,131,0,130,100,90,163,191,158,38,141,151,254,169,191,217,126,7,137,84,112,244,23,246,108,243,192,116>>},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,54>>},nil},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,52>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,49,45,83,78,65,80,83,72,79,84>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,49,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,48>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,48>>},<<253,149,195,248,33,112,38,177,155,78,193,134,22,8,84,223,29,230,41,51,192,33,56,76,84,98,69,66,3,239,206,224>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,49>>},<<154,36,158,30,159,221,180,243,75,252,43,207,43,251,67,191,243,170,98,165,95,128,124,114,203,34,73,177,227,145,74,233>>},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,51>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,49>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,48>>},<<137,28,32,233,180,214,31,0,187,29,21,255,46,29,244,81,14,48,42,7,254,77,205,184,84,193,108,34,59,72,29,163>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,50>>},<<227,235,78,191,17,38,38,39,240,183,155,79,209,224,120,250,78,194,190,249,12,248,59,124,125,33,79,110,230,167,191,174>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,48,45,83,78,65,80,83,72,79,84>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>},<<144,31,247,139,57,199,84,251,77,111,215,45,207,13,189,57,137,103,187,210,228,213,156,8,212,215,170,68,167,61,233,29>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,50>>},<<78,149,60,214,171,60,247,71,60,37,144,249,155,72,255,149,21,190,25,113,189,191,237,213,92,218,18,14,251,45,35,205>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,53>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,48>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,52>>},<<18,192,208,123,207,0,180,26,155,61,161,233,207,82,235,5,192,76,185,237,23,20,177,174,34,9,208,212,27,25,175,60>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,49>>},{{2021,8,18},{12,33,14}}},{{outer_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,50>>},<<125,82,45,99,193,63,55,10,240,85,62,39,241,17,175,46,151,249,79,82,241,156,152,189,85,126,220,205,83,211,23,237>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,50>>},<<4,169,229,159,101,162,157,117,225,224,34,182,226,186,4,222,45,15,34,138,192,11,179,255,223,212,21,128,10,36,196,109>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,49>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,51>>},<<107,156,170,216,147,0,6,249,187,53,104,12,93,51,17,145,122,198,118,144,195,175,27,160,24,98,51,36,192,21,171,229>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,51>>},<<89,208,170,245,32,158,59,80,103,69,138,80,59,168,11,115,136,213,101,180,134,161,131,77,199,158,216,113,202,46,175,195>>},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,48>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,49>>},<<122,235,9,94,73,65,128,174,217,87,243,88,187,139,146,21,54,10,58,167,226,61,174,125,154,240,251,21,72,66,17,100>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,48>>},<<42,36,205,38,187,111,153,246,43,113,23,68,183,101,166,2,86,8,105,191,53,227,14,136,204,113,61,233,238,209,143,84>>},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,49>>},<<87,33,198,182,213,1,34,216,246,140,204,172,113,44,170,18,49,249,120,148,186,183,121,239,245,255,15,136,108,180,70,72>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,48>>},[{<<104,101,120,112,109>>,<<101,97,114,109,97,114,107>>,<<101,97,114,109,97,114,107>>,<<62,61,32,48,46,48,46,48>>,false}]},{{deps,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,49>>},[]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,53>>},[{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101>>,<<98,101,110,99,104,101,101>>,<<126,62,32,48,46,56>>,true},{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<126,62,32,48,46,49>>,true},{<<104,101,120,112,109>>,<<101,120,106,115,120>>,<<101,120,106,115,120>>,<<126,62,32,52,46,48>>,true},{<<104,101,120,112,109>>,<<106,97,115,111,110>>,<<106,97,115,111,110>>,<<126,62,32,49,46,48>>,true},{<<104,101,120,112,109>>,<<106,115,111,110,101>>,<<106,115,111,110,101>>,<<126,62,32,49,46,52>>,true},{<<104,101,120,112,109>>,<<112,111,105,115,111,110>>,<<112,111,105,115,111,110>>,<<126,62,32,51,46,48>>,true},{<<104,101,120,112,109>>,<<116,105,110,121>>,<<116,105,110,121>>,<<126,62,32,49,46,48>>,true}]},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,50>>},[]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,52>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,48>>},<<247,255,225,201,212,59,123,176,236,219,241,88,208,221,33,29,68,170,80,93,85,16,190,88,13,31,37,220,165,98,122,8>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,50>>},nil},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,49,46,48,45,83,78,65,80,83,72,79,84>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,50>>},<<121,81,11,127,243,186,89,124,117,140,230,243,151,226,59,63,194,252,114,157,214,51,40,129,196,104,183,103,183,28,119,193>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,48>>},[]},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,50>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,50>>},<<1,101,136,233,169,102,36,116,1,188,191,2,151,109,70,143,30,111,6,137,29,222,68,248,115,201,37,156,100,150,204,161>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,51>>},<<160,88,176,66,166,90,171,75,107,220,254,73,23,150,200,208,67,105,54,44,172,223,141,93,75,234,156,81,112,151,208,56>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,52>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>},<<75,178,27,173,213,26,50,173,80,237,204,229,78,83,18,172,240,121,163,188,119,15,38,108,25,151,7,28,127,202,223,23>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,56>>},<<215,201,232,38,14,156,220,31,178,203,119,226,217,3,149,105,98,193,247,97,87,235,130,0,126,198,189,229,218,171,101,210>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,48>>},<<121,217,236,185,73,178,123,204,43,90,3,121,139,93,113,250,129,148,13,153,252,240,217,106,194,6,142,94,242,154,218,192>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,50>>},<<9,34,102,42,94,22,87,147,197,30,24,201,196,184,166,184,228,130,97,241,131,192,210,137,82,91,53,245,135,195,225,84>>},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>},{{2021,8,18},{12,33,12}}},{{inner_checksum,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>,<<48,46,50,46,49>>},<<239,80,163,97,241,27,95,38,181,241,109,6,150,228,106,158,70,97,117,100,146,201,129,247,178,34,158,244,47,241,205,21>>},{{registry_etag,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>},<<34,53,97,98,101,48,97,101,50,49,54,56,57,56,49,52,99,101,101,57,53,54,55,50,50,99,100,98,101,53,99,100,49,34>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,52>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>,<<48,46,50,46,49>>},nil},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>,false}]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,49>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,48>>,false}]},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,51>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>},<<81,222,127,212,160,25,247,150,107,51,19,62,173,183,197,186,145,158,93,130,159,185,169,5,79,1,43,225,62,143,157,1>>},{{timestamp,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>},{{2021,8,18},{12,33,13}}},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>},<<108,84,138,208,244,145,100,149,167,137,119,103,74,37,24,71,134,159,133,181,18,91,124,42,68,218,49,120,149,90,223,209>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,48>>},<<83,93,248,66,116,16,163,147,66,189,186,61,17,181,60,80,132,232,115,81,105,99,126,190,213,196,76,215,14,171,0,35>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,52>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>,false}]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,48>>},[]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,52>>},[]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,51>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,48>>,false}]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,49,46,48,45,83,78,65,80,83,72,79,84>>},[{<<104,101,120,112,109>>,<<100,101,99,105,109,97,108>>,<<100,101,99,105,109,97,108>>,<<126,62,32,49,46,48>>,true}]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,49>>},<<139,58,59,201,53,11,160,69,50,244,82,198,7,170,36,213,157,224,58,54,162,131,139,30,161,124,39,70,70,226,243,213>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,49>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,49>>},<<23,26,137,155,34,80,224,72,191,89,178,41,171,37,52,247,77,137,44,170,243,29,184,245,90,242,156,26,6,1,71,84>>},{last_update,{{2021,8,18},{10,42,0}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,52>>},<<174,118,224,106,125,26,72,9,28,95,84,35,200,94,240,134,206,108,19,85,140,94,14,149,211,176,78,31,86,195,90,134>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,51>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,50>>},<<142,100,15,158,141,162,129,223,90,1,24,144,127,209,142,52,140,31,35,205,199,71,41,100,142,232,55,174,217,27,140,41>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,50>>},<<165,235,69,46,242,66,123,154,112,20,171,100,152,141,178,66,81,134,95,142,111,174,125,163,240,100,177,227,178,61,51,219>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,49>>},<<217,80,154,44,34,147,65,79,12,138,178,35,105,23,194,141,154,6,6,227,139,49,72,11,47,118,177,203,206,191,151,106>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,52>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,51>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,51>>},{{2021,8,18},{12,33,12}}},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,53>>},<<158,233,172,54,50,92,173,255,53,126,95,219,121,194,116,101,7,77,182,127,154,240,15,64,126,21,191,100,146,57,149,10>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,55>>},{{2021,8,18},{12,33,14}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,52>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>,false}]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,52>>},[{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101>>,<<98,101,110,99,104,101,101>>,<<126,62,32,48,46,56>>,true},{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<126,62,32,48,46,49>>,true},{<<104,101,120,112,109>>,<<101,120,106,115,120>>,<<101,120,106,115,120>>,<<126,62,32,52,46,48>>,true},{<<104,101,120,112,109>>,<<106,97,115,111,110>>,<<106,97,115,111,110>>,<<126,62,32,49,46,48>>,true},{<<104,101,120,112,109>>,<<106,115,111,110,101>>,<<106,115,111,110,101>>,<<126,62,32,49,46,52>>,true},{<<104,101,120,112,109>>,<<112,111,105,115,111,110>>,<<112,111,105,115,111,110>>,<<126,62,32,51,46,48>>,true},{<<104,101,120,112,109>>,<<116,105,110,121>>,<<116,105,110,121>>,<<126,62,32,49,46,48>>,true}]},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>},<<43,202,208,207,98,31,178,119,202,187,182,65,49,89,205,58,163,2,101,194,222,228,44,150,134,151,152,139,48,16,134,4>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,51>>},<<87,98,95,24,53,198,99,53,131,106,213,107,2,224,21,248,208,190,199,234,139,100,211,5,241,134,255,216,60,134,175,63>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,53>>},[]},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,53>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,54>>},<<99,155,46,135,73,225,27,135,185,235,66,242,173,50,93,22,28,23,11,57,178,136,172,141,4,196,243,31,143,8,35,235>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,48>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,51>>},{{2021,8,18},{12,33,14}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,51>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,53>>},<<81,54,235,143,128,32,164,64,245,248,148,241,193,30,127,169,72,3,21,178,230,227,152,176,228,102,9,203,150,62,118,138>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,49>>},<<93,43,197,39,177,127,29,127,117,31,104,29,56,234,57,136,166,142,206,162,41,215,41,123,71,219,82,241,176,26,149,42>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,50>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,51>>},<<62,26,107,110,184,70,116,187,13,10,189,144,160,61,7,208,27,242,46,141,86,147,149,102,74,41,138,252,89,164,234,49>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,57>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,49>>},<<144,94,191,180,229,77,90,141,108,61,184,90,151,227,68,244,0,142,188,59,117,227,37,235,243,184,143,105,204,82,97,188>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,56>>},<<153,157,191,5,166,154,48,207,230,159,28,50,23,123,19,188,187,118,12,110,235,200,231,70,167,54,130,189,127,223,71,171>>},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,48>>},[]},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,53>>},{{2021,8,18},{12,33,12}}},{{registry_etag,<<104,101,120,112,109>>,<<106,115,111,110>>},<<34,101,102,101,101,99,52,55,102,57,50,98,97,99,55,54,55,55,57,97,50,55,49,99,48,102,98,98,51,54,51,98,55,34>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,50>>},<<194,61,185,228,204,160,232,73,173,196,43,10,9,154,255,185,230,38,124,95,35,168,113,252,111,20,67,72,178,73,52,31>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,50>>},<<250,117,237,79,53,92,212,171,70,140,37,88,129,113,248,0,42,114,116,211,213,251,55,108,24,130,45,223,199,195,63,31>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,48>>},{{2021,8,18},{12,33,14}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,48>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,48>>},<<114,192,119,214,28,104,4,106,65,50,246,197,185,28,62,118,157,147,111,245,38,78,44,46,155,203,221,7,141,223,92,139>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,49>>},<<228,42,101,145,72,233,247,177,231,215,28,123,93,36,153,124,215,189,123,114,215,227,30,165,194,219,22,116,184,238,231,136>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,50>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,49>>},<<134,98,110,28,137,164,173,154,150,208,217,198,56,249,232,140,35,70,184,155,75,161,97,25,136,89,78,190,114,181,213,238>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,51>>},{{2021,8,18},{12,33,14}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,49>>},<<140,130,155,178,142,63,49,106,54,18,22,49,68,5,254,41,109,141,210,253,116,9,223,165,63,164,172,28,255,55,151,160>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,48>>},[]},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,49>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,51>>},<<80,233,140,137,253,228,76,91,121,125,96,45,137,62,163,15,126,225,180,91,77,1,174,170,62,167,66,214,185,139,30,238>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,51>>},<<55,62,180,247,50,31,137,138,214,119,41,153,243,13,175,101,249,243,142,29,61,212,215,152,215,168,45,59,18,63,225,211>>}].
1 [{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,51>>},nil},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,50>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,50>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48,45,114,99,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,50>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,50,46,48>>},<<145,11,107,111,136,138,168,176,247,155,220,168,171,244,206,33,214,76,152,252,135,182,186,139,106,231,242,237,34,85,28,250>>},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,50>>},[]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,49>>},[]},{{registry_etag,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>},<<34,56,100,48,55,102,97,49,50,50,98,102,51,57,57,56,49,56,102,50,101,49,57,101,57,98,56,54,48,98,55,100,54,34>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,50>>},<<44,187,218,100,149,12,13,159,201,60,175,106,184,221,155,49,232,55,50,164,156,21,248,17,153,78,214,142,229,155,45,213>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,48>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,53>>},<<56,183,89,231,65,206,79,134,87,102,151,38,186,237,121,114,96,39,96,147,19,252,40,47,90,103,30,25,132,140,33,53>>},{{versions,<<104,101,120,112,109>>,<<106,115,111,110>>},[<<48,46,51,46,48>>,<<48,46,51,46,50>>,<<48,46,51,46,51>>,<<49,46,48,46,48>>,<<49,46,48,46,49>>,<<49,46,48,46,50>>,<<49,46,48,46,51>>,<<49,46,48,46,52,45,83,78,65,80,83,72,79,84>>,<<49,46,49,46,48>>,<<49,46,50,46,48>>,<<49,46,50,46,49>>,<<49,46,50,46,50>>,<<49,46,50,46,51>>,<<49,46,50,46,52>>,<<49,46,50,46,53>>,<<49,46,51,46,48>>,<<49,46,52,46,48>>,<<49,46,52,46,49>>,<<50,46,48,46,48,45,83,78,65,80,83,72,79,84>>,<<50,46,48,46,49,45,83,78,65,80,83,72,79,84>>,<<50,46,49,46,48,45,83,78,65,80,83,72,79,84>>]},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,48>>},{{2021,9,24},{5,33,51}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,52>>},<<105,102,54,18,176,225,123,172,122,29,127,26,69,235,225,149,15,19,218,24,10,69,123,7,223,63,175,139,223,24,200,149>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,51>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>,false}]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,49>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,51>>},<<61,45,231,167,16,185,190,212,207,189,174,4,25,217,139,25,133,99,75,216,204,31,38,239,149,118,194,235,154,166,179,94>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,52>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,49>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,50>>},nil},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,49>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,52>>},<<244,130,93,55,75,34,242,88,173,17,74,116,121,107,121,184,110,32,160,54,17,5,23,25,246,6,35,83,192,83,137,203>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,49,46,48>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,50>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,48>>},<<143,86,163,52,2,26,42,207,236,45,147,7,8,208,240,25,53,241,101,76,138,222,121,119,187,241,248,235,72,67,9,114>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,49>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,49,46,48>>},<<226,111,246,64,124,87,46,227,117,253,144,19,151,219,230,226,40,138,182,110,71,245,89,90,225,173,188,186,123,187,147,88>>},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>},<<245,81,152,101,10,142,192,29,62,252,4,121,122,190,85,12,125,2,62,127,248,181,9,243,115,207,147,48,50,4,155,216>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,49>>},[]},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>},[]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,49>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,53>>},<<207,101,129,50,228,228,150,173,232,111,35,114,94,237,64,16,52,240,120,216,88,212,173,10,147,76,123,58,58,204,20,178>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,48>>},<<172,88,31,32,233,197,199,224,203,127,65,143,86,7,39,141,113,97,28,101,221,242,233,48,101,241,244,206,17,221,84,24>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,49>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,49>>},<<133,128,81,127,114,25,171,182,235,26,100,132,19,211,194,49,151,164,153,232,238,12,184,91,99,120,17,14,55,59,18,213>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,48>>},{{2021,9,24},{5,33,51}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,50>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>,false}]},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,48>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,51>>},<<222,146,226,188,133,188,9,236,103,111,25,246,236,45,170,89,186,31,1,3,209,218,150,207,12,202,54,52,44,144,217,63>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,53>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,50>>},<<228,10,21,153,48,36,160,203,79,94,34,129,109,107,227,64,2,204,68,99,198,23,137,237,53,236,148,196,25,17,235,238>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,49>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>,<<48,46,50,46,49>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,48>>},<<206,105,157,49,102,44,52,218,135,131,255,23,171,152,12,99,80,186,80,189,130,71,85,105,219,224,255,146,149,83,217,202>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,50>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,52>>},<<196,149,38,14,55,137,176,144,88,147,32,164,221,109,151,69,54,147,217,186,43,222,120,167,11,98,32,205,246,88,203,255>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,48>>},<<211,69,204,226,233,242,86,144,250,158,83,33,6,112,113,98,221,93,141,153,138,197,64,226,68,231,158,219,138,143,60,44>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,49,45,83,78,65,80,83,72,79,84>>},<<232,37,108,31,91,115,43,205,72,90,169,75,192,19,113,243,36,225,20,154,143,38,38,35,64,124,162,204,19,171,8,74>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,49>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>},{{2021,9,24},{5,33,51}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,51>>},<<212,46,32,5,65,22,196,157,82,66,211,255,158,25,19,172,204,235,230,1,95,68,157,110,49,42,91,193,96,231,154,98>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,48>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,51>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,50>>},<<245,131,207,95,146,14,6,187,25,227,36,29,139,227,242,66,199,98,16,105,185,125,1,19,130,88,232,183,165,73,55,89>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,51>>},<<243,239,123,26,226,138,85,229,59,140,181,193,29,14,11,100,231,110,56,213,243,232,48,191,46,59,242,204,10,137,216,72>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,49>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>,false}]},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,52>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,48>>},<<230,66,147,177,199,46,50,58,218,21,109,71,23,74,239,14,251,80,38,58,21,246,26,15,152,146,235,24,81,15,119,123>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,49,46,48>>},<<130,163,223,22,68,204,169,183,106,75,126,192,23,189,164,63,18,214,96,37,232,245,60,212,77,210,176,19,164,69,251,147>>},{{deps,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,48>>},[]},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,51>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,48>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,49>>},<<84,80,137,56,172,103,226,121,102,177,14,244,150,6,227,173,89,149,214,101,215,252,38,136,239,179,234,177,48,124,144,121>>},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,48>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>,<<48,46,50,46,49>>},<<239,80,163,97,241,27,95,38,181,241,109,6,150,228,106,158,70,97,117,100,146,201,129,247,178,34,158,244,47,241,205,21>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,50>>},<<253,239,107,136,195,29,76,34,224,117,107,33,94,103,115,31,76,39,57,250,253,19,168,240,150,226,247,69,122,201,252,195>>},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,49,46,48>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,48>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,52>>},[]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,51>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,50>>},<<9,34,102,42,94,22,87,147,197,30,24,201,196,184,166,184,228,130,97,241,131,192,210,137,82,91,53,245,135,195,225,84>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,52>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,52,45,83,78,65,80,83,72,79,84>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,48>>},<<120,201,76,85,220,135,233,31,246,237,228,245,244,103,37,10,222,151,49,232,233,82,110,222,32,154,82,16,238,82,134,146>>},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>},<<59,50,222,86,143,18,144,1,225,207,137,204,91,207,214,219,177,38,15,171,178,207,138,52,16,205,192,246,52,235,161,130>>},{{outer_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,49>>},<<23,26,137,155,34,80,224,72,191,89,178,41,171,37,52,247,77,137,44,170,243,29,184,245,90,242,156,26,6,1,71,84>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,50>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,51>>},<<160,88,176,66,166,90,171,75,107,220,254,73,23,150,200,208,67,105,54,44,172,223,141,93,75,234,156,81,112,151,208,56>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,49>>},<<164,193,167,195,13,33,81,182,228,151,108,178,245,44,10,29,73,236,150,90,251,115,126,216,74,104,75,196,40,77,22,39>>},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,48>>},<<114,192,119,214,28,104,4,106,65,50,246,197,185,28,62,118,157,147,111,245,38,78,44,46,155,203,221,7,141,223,92,139>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,48,45,83,78,65,80,83,72,79,84>>},<<102,119,195,9,132,22,101,46,0,16,158,203,221,72,26,117,90,45,1,123,86,124,29,233,255,150,17,77,155,211,112,222>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,49>>},<<209,118,249,103,201,120,171,139,138,41,195,92,18,82,79,120,183,187,54,253,78,155,130,118,221,117,201,203,86,224,126,66>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,48>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,51>>},[]},{{timestamp,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>,<<48,46,50,46,49>>},{{2021,9,24},{5,33,52}}},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,48>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,51>>},<<80,233,140,137,253,228,76,91,121,125,96,45,137,62,163,15,126,225,180,91,77,1,174,170,62,167,66,214,185,139,30,238>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,48>>},<<156,4,35,121,62,187,118,245,82,245,191,173,131,219,120,194,17,60,104,78,153,237,178,24,144,111,162,40,179,216,152,119>>},{{versions,<<104,101,120,112,109>>,<<114,101,99,111,110>>},[<<50,46,50,46,48>>,<<50,46,50,46,49>>,<<50,46,51,46,48>>,<<50,46,51,46,49>>,<<50,46,51,46,50>>,<<50,46,51,46,51>>,<<50,46,51,46,52>>,<<50,46,51,46,53>>,<<50,46,51,46,54>>,<<50,46,52,46,48>>,<<50,46,53,46,48>>,<<50,46,53,46,49>>,<<50,46,53,46,50>>]},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>},<<144,31,247,139,57,199,84,251,77,111,215,45,207,13,189,57,137,103,187,210,228,213,156,8,212,215,170,68,167,61,233,29>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,48>>},<<241,44,133,153,45,45,129,212,251,122,96,34,90,55,175,239,58,240,6,152,35,204,50,26,37,233,223,157,59,203,110,210>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,51,46,48>>},<<44,107,172,83,3,113,61,27,67,202,111,60,136,23,103,245,123,42,223,102,140,222,48,19,189,113,87,220,101,239,41,156>>},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,50>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,52>>},<<18,192,208,123,207,0,180,26,155,61,161,233,207,82,235,5,192,76,185,237,23,20,177,174,34,9,208,212,27,25,175,60>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,48>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,49>>},{{2021,9,24},{5,33,54}}},{{registry_etag,<<104,101,120,112,109>>,<<114,101,99,111,110>>},<<34,57,51,50,99,100,100,53,52,102,102,55,57,49,54,57,98,54,50,97,55,50,48,50,56,100,56,98,53,48,53,49,48,34>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,49>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,49>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,50>>},<<203,165,63,168,219,131,173,150,140,154,101,46,9,195,237,125,220,196,218,67,79,39,195,234,169,202,71,255,178,177,255,3>>},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,52>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,51>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,51>>},<<119,62,225,102,102,158,178,27,82,151,206,65,179,199,180,116,190,9,110,184,237,5,45,69,112,129,40,154,28,242,80,122>>},{{outer_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,51>>},<<107,156,170,216,147,0,6,249,187,53,104,12,93,51,17,145,122,198,118,144,195,175,27,160,24,98,51,36,192,21,171,229>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,52>>},nil},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,50>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,48>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,48>>},<<114,243,132,15,237,217,79,6,49,92,82,63,108,236,245,180,130,114,51,190,215,174,63,225,53,178,160,235,234,181,225,150>>},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,50>>},<<7,70,231,172,135,119,124,3,107,104,136,211,75,235,196,163,110,83,65,104,59,87,163,225,184,103,14,93,9,182,129,82>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,50>>},[]},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,53>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,48>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>},<<68,68,200,121,190,50,59,27,19,62,236,82,65,203,132,189,56,33,234,25,76,116,13,117,97,126,16,107,228,116,67,24>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,48>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,48>>},nil},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,48>>},[]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,50>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>,false}]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,50>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,51>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,53>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,49>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,51>>,false}]},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,53>>},nil},{{retired,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,49,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,48>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,51>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,49>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,49>>,false}]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,50>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,51>>},<<71,184,128,141,153,7,132,52,53,26,145,255,10,183,239,226,166,131,80,36,207,53,156,206,25,80,127,16,58,100,69,117>>},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,48>>},<<162,54,21,16,103,251,60,169,201,96,54,128,105,50,209,5,210,224,199,188,179,119,133,19,239,254,106,108,59,35,58,96>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,51>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,48>>},<<121,217,236,185,73,178,123,204,43,90,3,121,139,93,113,250,129,148,13,153,252,240,217,106,194,6,142,94,242,154,218,192>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,49>>},<<80,227,39,73,149,59,107,249,129,141,191,237,129,207,17,144,227,140,223,36,249,88,145,48,49,8,8,116,134,197,146,94>>},{{outer_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,48>>},<<205,13,176,11,249,85,42,18,87,148,80,64,244,222,235,41,152,176,92,122,1,134,215,152,246,224,234,5,150,141,178,69>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,51>>},<<62,26,107,110,184,70,116,187,13,10,189,144,160,61,7,208,27,242,46,141,86,147,149,102,74,41,138,252,89,164,234,49>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,52>>},<<98,26,26,62,83,107,158,33,99,236,107,176,120,77,253,229,236,19,218,138,190,196,107,183,101,189,126,177,248,173,21,2>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,51,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,55,46,49>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,53>>},<<54,130,193,140,107,7,72,13,242,18,45,13,175,92,5,69,123,66,193,153,15,25,124,227,222,83,136,78,139,168,52,196>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,48>>},<<176,130,101,218,0,52,39,160,71,207,77,50,70,74,228,200,25,201,193,58,33,248,106,182,237,228,254,60,75,17,130,13>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,49>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,50>>},[]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,51>>},[]},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,49,45,83,78,65,80,83,72,79,84>>},#{message => <<82,101,116,105,114,101,100,32,82,67,32,118,101,114,115,105,111,110>>,reason => 'RETIRED_OTHER'}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,51>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,49>>},[]},{{versions,<<104,101,120,112,109>>,<<99,115,118>>},[<<48,46,49,46,48>>,<<48,46,49,46,49>>,<<48,46,50,46,48>>,<<48,46,50,46,49>>,<<48,46,50,46,50>>,<<49,46,48,46,48>>,<<49,46,48,46,49>>,<<49,46,49,46,48>>,<<49,46,49,46,49>>,<<49,46,49,46,50>>,<<49,46,49,46,51>>,<<49,46,49,46,52>>,<<49,46,49,46,53>>,<<49,46,50,46,48>>,<<49,46,50,46,49>>,<<49,46,50,46,50>>,<<49,46,50,46,51>>,<<49,46,50,46,52>>,<<49,46,51,46,48>>,<<49,46,51,46,49>>,<<49,46,51,46,50>>,<<49,46,51,46,51>>,<<49,46,52,46,48>>,<<49,46,52,46,49>>,<<49,46,52,46,50>>,<<49,46,52,46,51>>,<<49,46,52,46,52>>,<<50,46,48,46,48,45,114,99,46,48>>,<<50,46,48,46,48>>,<<50,46,49,46,48>>,<<50,46,49,46,49>>,<<50,46,50,46,48>>,<<50,46,51,46,48>>,<<50,46,51,46,49>>,<<50,46,52,46,48>>,<<50,46,52,46,49>>]},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,50>>},<<72,67,100,98,6,104,157,95,209,160,235,61,14,8,188,194,70,39,52,17,101,196,9,111,104,119,219,243,240,116,38,190>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>},nil},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,51>>},<<209,152,101,72,132,113,137,181,31,30,251,101,209,150,230,171,159,46,136,166,135,138,54,58,236,14,60,119,226,85,6,22>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,50,46,48>>},<<72,227,29,177,140,35,191,51,200,56,93,63,114,178,112,129,75,142,94,101,7,17,152,154,152,201,40,95,125,184,34,154>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,49>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>,false}]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,49>>},[]},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,51>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,49>>},<<217,80,154,44,34,147,65,79,12,138,178,35,105,23,194,141,154,6,6,227,139,49,72,11,47,118,177,203,206,191,151,106>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,55,46,49>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,49>>},<<121,180,219,142,142,93,115,110,206,124,216,109,238,5,68,39,61,188,79,192,147,122,157,207,219,65,51,176,151,41,238,29>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,52>>},<<37,105,199,33,33,80,209,87,165,122,53,119,190,202,207,162,227,70,163,175,234,105,135,8,133,22,134,172,220,210,141,4>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,48>>},<<135,70,99,76,186,2,83,179,168,68,50,96,128,215,141,77,124,129,171,147,194,248,161,18,241,10,234,167,248,181,81,137>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,49,46,48>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,49>>},<<17,125,88,18,60,172,60,104,2,0,82,72,191,3,237,32,165,177,77,221,30,57,216,108,122,229,40,150,2,53,82,144>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,49>>},<<61,98,119,99,233,68,81,236,33,113,230,208,52,205,163,1,55,45,225,205,240,11,189,224,214,126,14,174,103,116,102,219>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,57>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,49>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,50>>},<<185,80,65,252,17,144,118,152,187,77,64,117,190,80,75,6,0,168,234,119,129,196,236,127,27,240,142,115,235,238,108,37>>},{version,3},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,48>>},<<8,68,93,118,243,173,53,7,188,74,243,14,175,216,130,96,192,243,92,80,56,84,42,242,119,171,208,151,91,225,234,38>>},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,53>>},<<158,233,172,54,50,92,173,255,53,126,95,219,121,194,116,101,7,77,182,127,154,240,15,64,126,21,191,100,146,57,149,10>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,48>>},<<83,93,248,66,116,16,163,147,66,189,186,61,17,181,60,80,132,232,115,81,105,99,126,190,213,196,76,215,14,171,0,35>>},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,50>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,48>>},<<144,237,193,93,248,153,119,172,98,173,124,194,8,202,22,226,66,122,169,193,130,137,238,155,243,134,208,96,117,166,219,140>>},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,50>>},<<125,82,45,99,193,63,55,10,240,85,62,39,241,17,175,46,151,249,79,82,241,156,152,189,85,126,220,205,83,211,23,237>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,49>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,52>>},<<195,39,7,44,190,185,154,8,86,82,207,17,10,122,61,93,123,153,249,69,104,209,73,124,167,105,113,200,128,97,85,90>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,51,46,48>>},[]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48,45,114,99,46,48>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,48>>},nil},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,48>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,49,46,48,45,83,78,65,80,83,72,79,84>>},<<64,95,116,217,92,226,6,190,163,171,188,216,241,63,34,144,224,191,223,138,75,210,50,253,14,103,194,242,36,158,40,119>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,55>>},<<84,1,54,83,199,46,117,255,59,75,72,115,66,223,229,103,21,201,116,159,249,195,26,125,232,248,77,24,160,158,28,60>>},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,50>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,53>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,48>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,49>>},{{2021,9,24},{5,33,51}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,52>>},<<232,72,155,58,124,119,194,21,92,11,103,250,159,144,217,175,167,107,241,92,36,251,123,49,42,221,204,17,119,113,209,84>>},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,54>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,50>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,49>>},<<220,199,223,5,177,219,110,77,26,62,164,3,245,148,210,223,129,195,79,185,24,146,201,196,194,204,109,44,245,156,34,154>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48,45,114,99,46,48>>},<<83,185,186,191,253,24,107,180,251,159,221,71,232,164,66,184,241,141,184,20,151,255,163,165,91,90,95,89,111,90,200,20>>},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,49>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,52,45,83,78,65,80,83,72,79,84>>},<<239,82,71,134,18,4,215,101,218,196,201,174,239,141,242,228,84,208,44,85,221,176,130,198,199,103,181,203,151,45,101,25>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,53>>},<<87,63,110,101,253,118,8,187,24,230,175,36,91,213,105,165,181,56,126,4,204,76,7,123,31,218,233,153,8,74,169,85>>},{{versions,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>},[<<48,46,50,46,49>>]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48,45,114,99,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,49>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,53>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,50>>},<<221,82,137,92,76,143,33,54,94,138,69,110,63,63,180,18,123,84,143,1,54,24,160,87,216,253,118,164,42,200,244,239>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,49>>},nil},{{versions,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>},[<<49,46,48,46,50>>,<<49,46,48,46,51>>,<<49,46,48,46,52>>,<<49,46,48,46,53>>,<<49,46,48,46,55>>,<<49,46,48,46,56>>,<<49,46,48,46,57>>,<<49,46,49,46,48>>,<<49,46,50,46,48>>,<<49,46,50,46,49>>,<<49,46,50,46,50>>,<<49,46,51,46,48>>,<<49,46,51,46,49>>,<<49,46,51,46,50>>,<<49,46,51,46,51>>,<<49,46,51,46,52>>,<<49,46,52,46,48>>,<<49,46,52,46,49>>,<<49,46,52,46,50>>,<<49,46,52,46,51>>,<<49,46,52,46,52>>,<<49,46,52,46,53>>,<<49,46,53,46,48>>,<<49,46,53,46,49>>,<<49,46,53,46,50>>,<<49,46,53,46,51>>,<<49,46,53,46,52>>,<<49,46,54,46,48>>,<<49,46,54,46,49>>,<<49,46,54,46,50>>,<<49,46,55,46,48>>,<<49,46,55,46,49>>]},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,53>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,51>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,57>>},<<219,220,236,81,73,70,188,141,202,177,215,125,255,119,25,67,122,133,217,134,23,144,134,74,177,33,85,237,163,253,111,175>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,52>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>},<<43,202,208,207,98,31,178,119,202,187,182,65,49,89,205,58,163,2,101,194,222,228,44,150,134,151,152,139,48,16,134,4>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48>>},<<137,173,179,41,68,72,116,128,150,65,204,47,100,90,236,43,211,141,253,132,30,127,228,193,179,188,162,230,249,26,26,243>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,48>>},<<3,204,80,111,88,227,177,218,36,255,228,114,121,197,14,205,7,16,167,169,40,52,192,58,35,38,244,226,34,80,213,77>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,48>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,51>>},<<89,208,170,245,32,158,59,80,103,69,138,80,59,168,11,115,136,213,101,180,134,161,131,77,199,158,216,113,202,46,175,195>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,51>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,48>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,49,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>,false}]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,49>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,52>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,52>>},<<7,202,4,16,92,47,46,71,50,244,235,97,134,62,24,231,241,183,181,11,109,30,227,30,126,23,205,198,18,154,77,225>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,49>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,48>>},<<47,127,203,236,44,53,3,75,173,226,249,113,127,119,5,157,197,78,180,233,41,163,4,156,167,186,103,117,192,189,102,205>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,49>>},<<58,232,236,78,152,164,72,80,212,148,216,68,28,136,150,99,28,41,91,53,65,202,199,145,229,110,160,136,91,167,223,63>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,50>>},<<121,81,11,127,243,186,89,124,117,140,230,243,151,226,59,63,194,252,114,157,214,51,40,129,196,104,183,103,183,28,119,193>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,48>>},<<42,36,205,38,187,111,153,246,43,113,23,68,183,101,166,2,86,8,105,191,53,227,14,136,204,113,61,233,238,209,143,84>>},{{outer_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,52>>},<<81,241,223,146,27,4,119,39,94,167,18,118,48,66,21,93,188,116,172,199,93,150,72,219,213,73,133,196,92,145,59,41>>},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,50>>},<<218,31,129,51,81,87,77,55,61,179,235,87,157,45,159,228,185,29,239,194,50,224,8,39,238,90,163,139,86,196,193,210>>},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,49>>},nil},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,48>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,50>>},<<10,15,244,148,212,41,101,199,80,103,93,163,225,174,2,244,58,151,127,76,237,209,79,159,115,152,247,177,247,37,52,29>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,50>>},[]},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,49>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,51>>},<<24,229,217,170,84,18,236,6,60,249,113,155,207,231,59,249,144,197,254,213,201,163,200,66,44,43,93,149,41,252,139,13>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,51>>},<<203,247,114,93,87,123,181,54,15,26,132,29,231,129,200,59,140,131,56,79,158,10,194,88,127,8,19,128,121,36,154,200>>},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,53>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,55>>},<<225,176,196,235,165,145,73,137,247,161,169,173,159,137,147,207,116,118,14,70,72,134,240,253,34,10,98,51,126,52,121,20>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,56>>},<<153,157,191,5,166,154,48,207,230,159,28,50,23,123,19,188,187,118,12,110,235,200,231,70,167,54,130,189,127,223,71,171>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,49>>},<<33,182,157,95,117,31,169,225,236,145,71,77,68,247,183,80,73,20,184,249,214,168,53,45,56,133,111,119,129,224,124,53>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,51>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,52>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,55,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,50>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,52>>},<<18,52,153,51,188,38,72,236,120,159,155,27,62,139,58,235,92,234,198,54,131,247,95,139,203,7,135,0,36,91,135,76>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,49>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,52,45,83,78,65,80,83,72,79,84>>},[]},{{registry_etag,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>},<<34,53,99,48,99,48,48,55,97,55,55,54,55,101,50,51,49,97,48,50,98,55,54,56,49,57,48,98,54,57,55,49,97,34>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,48>>},<<72,36,218,219,1,225,67,149,246,94,157,24,218,41,77,83,67,38,215,238,215,121,150,243,194,47,116,169,135,1,121,235>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,51>>},<<204,30,48,54,44,84,234,91,230,196,12,140,160,37,46,105,227,103,226,22,134,157,107,154,241,123,89,58,246,167,104,36>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,50>>},<<233,250,128,26,6,80,103,232,165,96,68,74,199,144,121,115,174,138,214,151,226,6,200,187,26,66,45,159,37,206,202,197>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,48,45,83,78,65,80,83,72,79,84>>},#{message => <<82,101,116,105,114,101,100,32,82,67,32,118,101,114,115,105,111,110>>,reason => 'RETIRED_OTHER'}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,48>>},<<153,68,136,43,113,245,91,37,3,102,61,156,181,77,63,28,123,189,247,204,109,208,28,196,14,168,239,81,32,118,1,236>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,51>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{registry_etag,<<104,101,120,112,109>>,<<106,115,111,110>>},<<34,101,102,101,101,99,52,55,102,57,50,98,97,99,55,54,55,55,57,97,50,55,49,99,48,102,98,98,51,54,51,98,55,34>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,49>>},<<97,30,5,50,254,221,69,36,250,157,80,45,165,247,59,154,203,174,132,156,144,9,236,40,125,217,158,208,156,33,90,65>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48>>},<<198,111,234,137,186,120,98,185,73,1,186,240,135,18,133,233,183,60,173,137,197,253,181,122,99,134,210,173,207,41,89,62>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,52>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,50>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,55>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,32,50,46,51,46,49>>,false}]},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,51>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,49>>},<<33,232,214,152,48,145,102,15,57,79,68,167,193,31,140,241,244,103,175,178,170,218,31,207,89,194,241,181,144,164,47,104>>},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,48>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,49,46,48>>},<<139,29,56,94,0,231,8,1,161,47,107,212,62,182,79,116,124,79,114,200,142,82,63,143,13,50,118,165,19,92,160,11>>},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,53>>},[]},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,52>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,48>>},<<253,149,195,248,33,112,38,177,155,78,193,134,22,8,84,223,29,230,41,51,192,33,56,76,84,98,69,66,3,239,206,224>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,50>>},<<221,158,21,194,160,108,229,114,231,154,103,96,57,12,160,35,72,192,241,193,134,58,34,145,8,98,133,129,134,36,121,114>>},{{retired,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,51>>},nil},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,50>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,50>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,52>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,49>>,false}]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,49>>},[]},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,48>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,49>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,49>>},<<87,33,198,182,213,1,34,216,246,140,204,172,113,44,170,18,49,249,120,148,186,183,121,239,245,255,15,136,108,180,70,72>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,49>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,48>>},<<126,52,227,187,132,18,57,63,249,247,246,37,143,69,206,99,36,123,203,172,231,142,225,73,214,152,89,118,13,216,14,92>>},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>},{{2021,9,24},{5,33,51}}},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,53>>},<<81,54,235,143,128,32,164,64,245,248,148,241,193,30,127,169,72,3,21,178,230,227,152,176,228,102,9,203,150,62,118,138>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,49>>},<<52,24,227,25,118,75,157,255,31,70,158,67,203,223,253,127,213,78,164,124,191,118,80,39,197,87,171,209,70,161,159,179>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,48>>},<<158,206,25,186,234,42,11,160,204,226,167,220,175,197,212,98,51,88,47,253,21,126,117,89,227,31,149,135,213,169,181,63>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,53>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,52>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48,45,114,99,46,48>>},<<36,170,165,33,61,173,49,64,42,227,123,151,158,11,91,183,126,152,210,219,190,163,38,237,145,161,0,12,145,196,17,19>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,50>>},<<250,117,237,79,53,92,212,171,70,140,37,88,129,113,248,0,42,114,116,211,213,251,55,108,24,130,45,223,199,195,63,31>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,52,45,83,78,65,80,83,72,79,84>>},<<170,203,72,169,240,220,55,111,21,223,48,101,202,33,132,115,141,215,201,81,80,174,216,60,40,206,11,171,181,110,91,152>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,51>>},nil},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,54>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,54>>},[]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,50,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,52>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,51>>,false}]},{{deps,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,52>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,49>>},<<34,76,89,87,237,225,240,215,135,68,94,165,238,52,99,79,186,176,6,253,161,30,77,66,240,37,11,97,16,216,237,49>>},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,48>>},{{2021,9,24},{5,33,51}}},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,50>>},<<6,79,119,21,219,121,136,118,175,30,184,40,124,132,98,109,32,87,82,237,176,225,163,143,209,38,34,47,103,105,237,172>>},{{deps,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,49,46,48>>},[]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,48,45,83,78,65,80,83,72,79,84>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,48>>},<<59,28,140,11,227,44,39,32,207,5,135,132,129,87,236,95,14,20,247,84,219,99,34,156,52,187,19,90,94,236,181,40>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,50>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,53>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,55>>},nil},{{retired,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,49>>},nil},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,49,45,83,78,65,80,83,72,79,84>>},[]},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,48>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,48>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,53>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>,false}]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,49>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,51>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,49>>},<<228,42,101,145,72,233,247,177,231,215,28,123,93,36,153,124,215,189,123,114,215,227,30,165,194,219,22,116,184,238,231,136>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,50>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,51,46,48>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,51,46,48>>},<<139,206,65,63,178,82,52,228,110,97,243,99,163,106,208,125,204,229,50,39,152,45,198,78,39,33,182,228,14,218,121,129>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,52>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,32,50,46,50,46,49>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,51>>},<<21,65,73,21,16,131,0,130,100,90,163,191,158,38,141,151,254,169,191,217,126,7,137,84,112,244,23,246,108,243,192,116>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,49>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,49>>},[]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,51>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,48>>},<<120,45,32,1,18,21,106,160,173,174,245,209,65,156,42,204,229,182,91,99,69,210,69,48,241,27,87,153,24,77,152,231>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,52,45,83,78,65,80,83,72,79,84>>},nil},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,50>>},<<44,117,35,200,222,233,29,255,65,246,179,214,60,186,43,212,158,182,210,254,91,241,238,192,223,127,135,235,94,35,14,28>>},{{deps,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,50>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,48>>},<<105,239,141,192,74,76,174,199,209,37,41,162,55,199,180,155,139,177,255,48,198,228,44,186,234,71,245,179,206,145,253,189>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,52>>},<<223,18,100,181,135,27,186,248,218,79,17,254,173,250,13,198,97,244,213,17,73,58,37,248,249,199,214,21,224,214,20,213>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,49>>},<<159,1,192,129,120,215,122,101,122,54,82,135,8,70,212,202,238,122,123,113,202,50,151,29,247,206,87,192,121,218,111,222>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,50>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,49>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,51>>},<<60,195,226,58,205,234,110,131,64,115,25,39,91,193,250,174,216,30,159,132,1,73,217,241,199,203,174,78,19,145,128,169>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,48>>},<<227,14,75,190,99,54,83,165,194,218,67,202,180,42,97,98,62,10,204,128,239,64,195,33,188,41,240,214,152,105,123,211>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,50>>},<<165,235,69,46,242,66,123,154,112,20,171,100,152,141,178,66,81,134,95,142,111,174,125,163,240,100,177,227,178,61,51,219>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,51>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,51>>,false}]},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>},<<166,252,251,218,190,26,239,145,25,184,113,48,72,70,177,206,40,45,154,65,36,188,221,175,199,97,108,26,37,108,117,150>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,48>>},<<0,214,237,215,48,254,153,35,132,121,150,70,157,1,139,250,210,149,54,105,189,43,99,91,7,84,206,45,213,159,166,37>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,49>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,49>>},<<18,120,55,62,72,14,61,187,172,80,45,35,75,247,111,77,173,185,164,174,22,208,182,58,172,75,199,249,79,211,178,228>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,51>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,50>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,48>>},<<77,136,166,207,49,69,174,71,48,206,151,157,195,38,52,182,223,150,54,59,81,222,161,94,32,206,80,184,22,1,35,229>>},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,48>>},<<197,103,12,1,118,217,143,197,134,60,112,72,163,173,92,172,44,1,81,110,133,253,40,176,162,35,166,106,125,235,140,234>>},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,49>>},<<222,84,131,50,37,176,230,127,33,219,71,51,58,8,104,90,237,80,215,120,162,241,28,247,227,106,233,227,254,78,225,7>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,48>>},<<237,244,27,181,79,195,77,80,10,31,149,28,153,82,162,59,134,174,136,12,189,252,117,59,72,225,183,14,63,184,238,19>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,56>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,55,46,49>>},<<201,202,31,98,58,62,240,21,130,131,163,195,124,215,183,35,91,254,133,146,122,214,226,99,150,221,36,126,32,87,245,161>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,49>>},<<22,126,93,61,210,231,113,110,88,101,245,168,208,100,215,169,247,0,69,22,199,150,104,64,131,241,205,24,12,45,66,150>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,49,45,83,78,65,80,83,72,79,84>>},<<107,98,70,96,40,214,224,171,62,198,128,92,81,244,168,134,41,230,152,183,29,248,224,31,165,92,92,74,49,174,190,107>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,50>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,48>>},<<227,19,177,246,201,71,212,213,150,103,66,219,100,159,247,226,103,54,224,132,176,162,127,161,246,74,202,120,50,120,28,196>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,48>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,49,46,48>>},<<247,30,210,221,230,58,44,168,206,231,133,5,145,64,254,121,160,122,142,67,65,133,52,199,94,32,21,56,185,12,31,185>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,50>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,50>>},<<194,61,185,228,204,160,232,73,173,196,43,10,9,154,255,185,230,38,124,95,35,168,113,252,111,20,67,72,178,73,52,31>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,51>>},[{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101>>,<<98,101,110,99,104,101,101>>,<<126,62,32,48,46,56>>,true},{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<126,62,32,48,46,49>>,true},{<<104,101,120,112,109>>,<<101,120,106,115,120>>,<<101,120,106,115,120>>,<<126,62,32,52,46,48>>,true},{<<104,101,120,112,109>>,<<106,97,115,111,110>>,<<106,97,115,111,110>>,<<126,62,32,49,46,48>>,true},{<<104,101,120,112,109>>,<<106,115,111,110,101>>,<<106,115,111,110,101>>,<<126,62,32,49,46,52>>,true},{<<104,101,120,112,109>>,<<112,111,105,115,111,110>>,<<112,111,105,115,111,110>>,<<126,62,32,51,46,48>>,true},{<<104,101,120,112,109>>,<<116,105,110,121>>,<<116,105,110,121>>,<<126,62,32,49,46,48>>,true}]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,48>>},<<37,176,134,181,185,240,219,77,168,137,42,78,21,67,87,112,109,29,219,116,166,190,176,5,234,235,228,11,96,67,46,140>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,51>>,false}]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,48>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,49>>},<<134,98,110,28,137,164,173,154,150,208,217,198,56,249,232,140,35,70,184,155,75,161,97,25,136,89,78,190,114,181,213,238>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,49>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,53>>,false}]},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,49>>},[]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,48>>},<<53,139,202,226,196,66,253,64,191,71,65,138,14,15,148,73,5,216,206,18,44,194,156,98,62,109,252,217,190,194,181,19>>},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>},[]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,50>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,48>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,50>>},<<227,235,78,191,17,38,38,39,240,183,155,79,209,224,120,250,78,194,190,249,12,248,59,124,125,33,79,110,230,167,191,174>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,48>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,48>>},<<38,30,64,121,8,118,140,103,246,243,173,85,119,190,179,66,64,69,24,207,58,141,28,37,249,112,76,8,167,126,137,109>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,50>>},[]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,51>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,52>>},<<251,60,197,214,171,222,88,2,7,87,132,147,178,216,96,62,100,143,44,93,168,223,234,187,26,186,196,121,147,150,228,33>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,50,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,52>>},<<144,235,14,1,159,220,17,222,131,168,140,108,26,139,190,153,105,68,81,76,246,187,206,222,7,44,143,34,45,28,113,35>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,49>>},<<78,125,235,70,12,170,104,171,2,130,210,4,228,58,85,50,98,180,93,64,169,61,208,16,154,133,137,103,171,28,143,191>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,53>>},<<64,60,123,48,80,27,93,51,205,168,188,107,254,48,127,89,219,1,95,199,37,86,122,86,4,218,231,246,5,165,30,23>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,49>>},nil},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,50>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,53>>},<<110,77,136,127,221,142,139,234,129,238,215,150,170,88,155,200,14,52,183,137,97,121,182,132,249,117,202,184,58,27,23,62>>},{{inner_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,52>>},<<217,57,74,179,245,173,205,169,122,75,139,28,11,121,141,203,40,107,7,145,60,64,32,234,127,120,200,135,35,251,20,94>>},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,49>>},[]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,53,46,48>>,false}]},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,51>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,50>>},<<14,193,72,255,99,89,118,57,179,255,140,195,239,82,60,251,70,105,244,250,26,46,146,30,5,132,156,143,149,12,124,109>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,50>>},[{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101>>,<<98,101,110,99,104,101,101>>,<<126,62,32,48,46,56>>,true},{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<126,62,32,48,46,49>>,true},{<<104,101,120,112,109>>,<<101,120,106,115,120>>,<<101,120,106,115,120>>,<<126,62,32,52,46,48>>,true},{<<104,101,120,112,109>>,<<106,97,115,111,110>>,<<106,97,115,111,110>>,<<126,62,32,49,46,48>>,true},{<<104,101,120,112,109>>,<<106,115,111,110,101>>,<<106,115,111,110,101>>,<<126,62,32,49,46,52>>,true},{<<104,101,120,112,109>>,<<112,111,105,115,111,110>>,<<112,111,105,115,111,110>>,<<126,62,32,51,46,48>>,true},{<<104,101,120,112,109>>,<<116,105,110,121>>,<<116,105,110,121>>,<<126,62,32,49,46,48>>,true}]},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>},{{2021,9,24},{5,33,51}}},{{versions,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>},[<<48,46,49,46,48>>,<<48,46,50,46,48>>,<<48,46,50,46,49>>,<<48,46,50,46,50>>,<<48,46,50,46,51>>,<<48,46,50,46,52>>]},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,51>>},<<139,0,144,177,58,66,52,58,215,9,237,8,129,17,253,64,169,228,194,209,129,158,246,193,230,1,52,113,52,237,52,208>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,50>>},<<78,149,60,214,171,60,247,71,60,37,144,249,155,72,255,149,21,190,25,113,189,191,237,213,92,218,18,14,251,45,35,205>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,49>>},<<134,72,240,74,148,57,118,90,212,73,188,86,163,255,125,139,17,221,68,255,8,255,205,239,196,50,159,124,147,132,61,250>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,48>>},<<128,143,97,68,230,177,7,27,6,94,239,120,144,128,132,98,39,236,140,208,209,97,22,203,135,221,149,155,216,18,239,43>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,49>>},<<93,43,197,39,177,127,29,127,117,31,104,29,56,234,57,136,166,142,206,162,41,215,41,123,71,219,82,241,176,26,149,42>>},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>},{{2021,9,24},{5,33,51}}},{{registry_etag,<<104,101,120,112,109>>,<<99,115,118>>},<<34,99,53,97,50,55,50,50,50,48,53,50,50,102,56,55,53,98,98,101,55,57,54,100,101,52,99,52,98,54,51,57,54,34>>},{{registry_etag,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>},<<34,53,97,98,101,48,97,101,50,49,54,56,57,56,49,52,99,101,101,57,53,54,55,50,50,99,100,98,101,53,99,100,49,34>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,49,46,48,45,83,78,65,80,83,72,79,84>>},#{message => <<82,101,116,105,114,101,100,32,82,67,32,118,101,114,115,105,111,110>>,reason => 'RETIRED_OTHER'}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,51>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>,false}]},{{retired,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,53>>},<<79,89,119,17,222,247,188,179,135,133,25,245,127,149,88,219,60,157,134,138,238,65,83,94,220,220,32,170,192,179,158,104>>},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,51>>},<<54,23,39,196,84,172,47,207,55,141,38,16,79,102,79,157,134,244,254,184,32,110,4,45,190,24,116,214,109,132,163,224>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,50>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,49>>},nil},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,49,46,48>>},[{<<104,101,120,112,109>>,<<100,101,99,105,109,97,108>>,<<100,101,99,105,109,97,108>>,<<126,62,32,49,46,48>>,true}]},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,52>>},<<167,118,4,141,83,165,143,243,181,212,72,231,82,105,45,47,47,255,248,233,98,241,72,127,19,182,62,184,86,155,14,126>>},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,50>>},<<217,196,187,165,244,253,121,45,191,150,34,0,67,83,251,201,249,118,86,33,218,135,58,172,135,166,143,252,81,205,108,102>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,48>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,53>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,48>>},<<253,251,221,94,157,163,22,77,45,83,151,15,50,140,123,100,157,118,74,72,185,111,245,238,167,113,163,222,82,148,155,177>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,52>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,49,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>},<<81,222,127,212,160,25,247,150,107,51,19,62,173,183,197,186,145,158,93,130,159,185,169,5,79,1,43,225,62,143,157,1>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,49>>},<<139,58,59,201,53,11,160,69,50,244,82,198,7,170,36,213,157,224,58,54,162,131,139,30,161,124,39,70,70,226,243,213>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,49,46,48,45,83,78,65,80,83,72,79,84>>},<<16,4,241,79,86,196,247,98,133,76,72,46,15,233,45,98,116,227,208,7,139,61,112,228,178,238,179,183,158,39,81,7>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,56>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,51>>},<<55,62,180,247,50,31,137,138,214,119,41,153,243,13,175,101,249,243,142,29,61,212,215,152,215,168,45,59,18,63,225,211>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,50>>},nil},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,51>>},[]},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,49>>},[]},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,50>>},{{2021,9,24},{5,33,51}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,52>>},<<174,118,224,106,125,26,72,9,28,95,84,35,200,94,240,134,206,108,19,85,140,94,14,149,211,176,78,31,86,195,90,134>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,50>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,49>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,48>>},<<2,74,88,8,199,219,172,173,7,228,200,251,223,183,96,201,108,45,40,115,94,67,214,95,26,241,22,67,164,211,163,242>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,51>>},<<51,221,215,227,240,137,141,26,124,212,235,117,88,77,41,16,14,43,149,75,115,25,59,120,255,172,203,118,182,37,87,124>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,52>>},nil},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,53>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,32,50,46,50,46,49>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,49,46,48>>},<<243,164,124,34,50,116,36,41,23,88,22,125,243,196,96,13,207,139,243,71,149,163,120,118,18,1,154,165,184,106,92,33>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,55,46,48>>},<<224,46,111,121,220,82,7,59,144,156,91,229,51,110,126,35,25,122,216,162,152,132,87,94,173,123,5,68,102,84,30,2>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,50>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,49>>},<<109,44,115,99,187,137,175,235,209,36,27,0,51,60,161,178,176,156,97,30,253,148,104,172,0,236,207,120,210,64,58,4>>},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,49>>},{{2021,9,24},{5,33,51}}},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,50>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,49>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,50,46,48>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,49>>},<<40,31,44,187,67,159,227,192,67,119,62,230,200,49,114,171,216,3,38,220,198,21,63,100,48,228,212,230,177,77,126,157>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,57>>},<<110,125,164,69,241,34,142,178,227,58,182,153,25,226,63,248,245,119,212,177,213,64,199,18,55,58,247,109,196,24,107,86>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,50>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,51>>,false}]},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,52>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,52>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,49>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,49,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,50>>},<<99,11,135,164,61,90,213,41,40,103,253,138,103,49,217,123,60,131,58,11,150,140,210,3,25,183,9,32,254,241,64,107>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,49>>},<<154,36,158,30,159,221,180,243,75,252,43,207,43,251,67,191,243,170,98,165,95,128,124,114,203,34,73,177,227,145,74,233>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,48>>},[]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,52>>},nil},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>},{{2021,9,24},{5,33,51}}},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,48>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,49>>},<<144,94,191,180,229,77,90,141,108,61,184,90,151,227,68,244,0,142,188,59,117,227,37,235,243,184,143,105,204,82,97,188>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,48,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,48>>},<<60,135,105,131,179,53,62,116,172,85,245,4,171,228,244,113,115,162,199,86,112,89,188,95,36,84,45,187,182,47,19,218>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,51>>},<<9,86,102,121,241,147,49,21,246,24,158,108,78,32,94,53,170,234,28,174,220,248,67,135,92,62,182,94,236,131,228,136>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,50>>},<<142,100,15,158,141,162,129,223,90,1,24,144,127,209,142,52,140,31,35,205,199,71,41,100,142,232,55,174,217,27,140,41>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,51>>},nil},{{retired,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,51>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,55,46,48>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>},[]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,48>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,50>>},<<194,18,58,158,240,37,55,126,12,183,146,202,226,3,176,251,111,81,83,62,45,9,138,157,207,126,43,240,18,221,33,194>>},{{registry_etag,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>},<<34,52,54,57,51,51,55,102,53,98,99,52,97,98,101,52,53,101,102,102,102,52,49,100,55,102,51,53,97,56,101,56,99,34>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,57>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,55,46,48>>},<<34,136,7,241,80,124,193,231,171,163,101,61,18,237,57,253,168,150,159,119,240,14,201,84,24,81,241,206,105,167,229,224>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,49,46,49>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,48>>},<<247,255,225,201,212,59,123,176,236,219,241,88,208,221,33,29,68,170,80,93,85,16,190,88,13,31,37,220,165,98,122,8>>},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,48,45,83,78,65,80,83,72,79,84>>},<<82,54,119,31,28,24,178,146,208,95,163,243,177,122,136,230,32,65,105,41,167,111,111,118,7,160,99,84,190,131,223,24>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,49>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,48>>},<<137,28,32,233,180,214,31,0,187,29,21,255,46,29,244,81,14,48,42,7,254,77,205,184,84,193,108,34,59,72,29,163>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,49>>,false}]},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,50>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,50>>},nil},{{repo,<<104,101,120,112,109>>},<<104,116,116,112,115,58,47,47,114,101,112,111,46,104,101,120,46,112,109>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,50>>},nil},{{retired,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,52>>},nil},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,49>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,51>>},<<221,84,236,175,169,116,61,21,164,128,166,157,237,246,104,174,139,137,135,221,19,11,237,83,29,1,222,45,224,219,114,231>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,49>>},<<146,48,109,88,81,27,235,226,12,242,50,205,65,94,151,159,216,143,175,226,159,194,63,30,158,27,67,127,162,208,6,180>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,52,46,49>>},[{<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<126,62,32,49,46,48,46,52>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,48>>},<<16,133,39,148,221,224,88,193,239,122,91,157,83,212,8,217,4,194,7,110,159,171,116,172,67,252,103,109,50,227,218,211>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,49>>},<<122,235,9,94,73,65,128,174,217,87,243,88,187,139,146,21,54,10,58,167,226,61,174,125,154,240,251,21,72,66,17,100>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,50>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,49>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,54>>},<<99,155,46,135,73,225,27,135,185,235,66,242,173,50,93,22,28,23,11,57,178,136,172,141,4,196,243,31,143,8,35,235>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,51>>},<<87,98,95,24,53,198,99,53,131,106,213,107,2,224,21,248,208,190,199,234,139,100,211,5,241,134,255,216,60,134,175,63>>},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,52>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,49,45,83,78,65,80,83,72,79,84>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,49,46,48>>},<<200,135,134,160,114,7,168,11,7,30,163,142,26,113,168,171,215,50,62,203,247,5,102,1,39,0,155,32,56,197,161,209>>},{{outer_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,48>>},<<59,151,167,246,49,247,35,53,197,202,105,173,82,159,74,171,245,78,9,177,14,190,12,114,5,182,175,122,13,146,27,148>>},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,49>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,48>>},nil},{{timestamp,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,51>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,49>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,48>>},<<110,216,76,213,101,167,236,112,146,0,220,53,152,58,23,181,31,118,152,56,180,196,142,200,193,64,28,253,16,50,224,254>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,50>>},<<34,47,127,11,94,117,136,177,233,233,183,62,72,148,15,18,212,59,190,8,196,116,176,143,213,126,249,135,71,209,197,102>>},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,49>>},<<0,37,224,56,226,242,232,197,200,36,13,24,188,252,17,177,239,225,162,86,113,101,111,197,178,253,214,112,245,72,222,52>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,48,46,48,45,83,78,65,80,83,72,79,84>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,55,46,49>>},<<76,202,250,170,44,224,27,133,221,209,69,145,244,213,246,115,27,78,19,182,16,167,15,184,65,240,112,17,120,71,130,128>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,50>>},<<4,169,229,159,101,162,157,117,225,224,34,182,226,186,4,222,45,15,34,138,192,11,179,255,223,212,21,128,10,36,196,109>>},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,53>>},<<16,58,136,179,90,253,98,119,4,200,46,74,189,12,206,172,147,54,164,224,65,214,227,196,113,15,233,60,72,73,65,147>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,48>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,52>>},<<153,47,46,20,24,132,154,50,111,209,217,40,120,1,250,45,134,9,29,180,249,97,31,96,120,29,166,210,54,246,76,212>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,49>>},{{2021,9,24},{5,33,54}}},{{inner_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,50>>},<<140,102,178,169,55,154,72,235,237,246,174,23,188,43,114,195,171,233,203,219,247,130,73,57,128,105,103,50,97,1,195,113>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,50>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,49>>},<<244,176,215,37,57,47,193,87,159,2,33,179,207,112,170,174,89,243,238,225,104,54,215,40,142,180,84,243,238,92,236,34>>},{{inner_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,51>>},<<236,36,134,141,134,25,117,122,104,240,121,131,87,199,25,8,7,161,207,196,44,233,12,24,194,55,96,229,146,73,162,26>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>,false}]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,51>>},nil},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<48,46,49,46,48>>},[]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,48>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,49>>},<<155,164,209,166,168,242,208,178,113,109,226,178,165,204,43,170,204,126,246,97,224,35,89,184,212,159,28,14,122,140,94,203>>},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,49>>},<<67,15,250,96,104,90,193,239,223,177,254,76,151,184,118,124,146,208,217,46,110,124,62,134,33,85,155,167,117,152,103,138>>},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,48>>},[{<<104,101,120,112,109>>,<<101,97,114,109,97,114,107>>,<<101,97,114,109,97,114,107>>,<<62,61,32,48,46,48,46,48>>,false}]},{{deps,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,49>>},[]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,53>>},[{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101>>,<<98,101,110,99,104,101,101>>,<<126,62,32,48,46,56>>,true},{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<126,62,32,48,46,49>>,true},{<<104,101,120,112,109>>,<<101,120,106,115,120>>,<<101,120,106,115,120>>,<<126,62,32,52,46,48>>,true},{<<104,101,120,112,109>>,<<106,97,115,111,110>>,<<106,97,115,111,110>>,<<126,62,32,49,46,48>>,true},{<<104,101,120,112,109>>,<<106,115,111,110,101>>,<<106,115,111,110,101>>,<<126,62,32,49,46,52>>,true},{<<104,101,120,112,109>>,<<112,111,105,115,111,110>>,<<112,111,105,115,111,110>>,<<126,62,32,51,46,48>>,true},{<<104,101,120,112,109>>,<<116,105,110,121>>,<<116,105,110,121>>,<<126,62,32,49,46,48>>,true}]},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,53,46,50>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,52>>},<<102,254,225,234,245,54,52,125,73,57,66,203,94,8,83,33,197,30,128,222,18,79,240,15,75,38,81,135,11,145,155,153>>},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,48>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,50>>},<<43,187,237,224,72,117,138,214,127,122,117,84,47,144,246,148,202,128,50,50,162,182,127,221,127,159,218,152,141,186,154,137>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,49,46,48,45,83,78,65,80,83,72,79,84>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,48>>},<<188,191,142,48,184,96,93,175,22,10,140,42,41,213,74,23,46,82,15,75,160,227,94,17,122,106,162,64,96,208,159,6>>},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,50,46,48>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,50>>},<<110,147,67,127,116,169,151,8,6,138,50,183,103,176,36,50,209,19,137,98,117,66,20,9,227,1,141,152,146,54,166,90>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,50>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,50>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,51>>},<<84,125,88,219,189,134,76,10,86,12,12,163,120,158,232,132,137,116,232,197,23,205,183,130,124,51,3,227,180,46,117,77>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,52>>},<<136,22,143,146,203,209,35,224,41,13,135,208,161,202,128,3,232,99,170,67,10,225,114,87,177,231,195,222,195,33,119,158>>},{{versions,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>},[<<48,46,49,46,48>>,<<48,46,49,46,49>>,<<48,46,49,46,50>>,<<49,46,48,46,48>>,<<49,46,48,46,49>>,<<49,46,48,46,50>>,<<49,46,48,46,51>>,<<49,46,48,46,52>>,<<49,46,48,46,53>>,<<49,46,48,46,54>>]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,55,46,49>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,49>>,false}]},{{outer_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>},<<108,84,138,208,244,145,100,149,167,137,119,103,74,37,24,71,134,159,133,181,18,91,124,42,68,218,49,120,149,90,223,209>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>},nil},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,48>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,56>>},nil},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,50>>},nil},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>},{{2021,9,24},{5,33,51}}},{{retired,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>,<<48,46,50,46,49>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,52>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>,<<48,46,50,46,49>>},<<40,42,138,50,60,162,168,69,201,230,247,135,209,102,52,143,119,108,29,74,65,237,230,48,70,215,45,66,46,61,169,70>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>,false}]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,49>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,48>>,false}]},{{deps,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,51>>},[]},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>},<<180,6,194,252,205,234,160,217,78,35,181,227,10,227,214,53,162,212,97,227,99,165,201,198,49,104,151,3,124,240,80,210>>},{{timestamp,<<104,101,120,112,109>>,<<113,117,97,110,116,105,108,101,95,101,115,116,105,109,97,116,111,114>>},{{2021,9,24},{5,33,52}}},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<48,46,49,46,48>>},<<132,183,103,162,219,142,204,47,7,187,132,252,180,245,79,37,22,12,7,161,218,226,124,70,11,63,12,64,191,203,247,159>>},{{inner_checksum,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,49>>},<<75,178,27,173,213,26,50,173,80,237,204,229,78,83,18,172,240,121,163,188,119,15,38,108,25,151,7,28,127,202,223,23>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,52>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>,false}]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,48>>},[]},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,52>>},[]},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,51>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,48>>,false}]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<50,46,49,46,48,45,83,78,65,80,83,72,79,84>>},[{<<104,101,120,112,109>>,<<100,101,99,105,109,97,108>>,<<100,101,99,105,109,97,108>>,<<126,62,32,49,46,48>>,true}]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,49>>},nil},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,49>>},<<130,13,172,184,217,233,253,211,206,164,132,51,239,127,49,178,246,251,249,233,63,255,40,220,35,138,5,190,195,76,57,87>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,48>>},<<126,106,131,77,27,49,48,195,34,70,205,62,157,56,3,252,52,28,167,175,148,54,47,51,33,56,139,197,165,119,161,26>>},{{inner_checksum,<<104,101,120,112,109>>,<<115,116,100,111,117,116,95,102,111,114,109,97,116,116,101,114>>,<<48,46,50,46,49>>},<<24,34,98,146,249,177,109,241,247,196,117,36,59,152,185,133,59,170,233,18,101,88,198,253,222,53,77,27,159,120,134,56>>},{last_update,{{2021,9,23},{21,9,23}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,52>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,51>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,50>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,52,46,50>>},<<206,219,47,35,1,113,230,209,4,35,174,232,206,144,81,40,51,176,126,151,135,71,204,131,107,15,44,3,127,207,119,157>>},{{inner_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,49>>},<<109,146,154,92,130,248,99,214,32,28,161,252,218,171,38,179,85,143,41,221,224,211,160,34,229,242,200,221,175,180,203,22>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,52>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,51>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,51>>},{{2021,9,24},{5,33,51}}},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,53>>},<<76,120,211,230,117,249,239,248,133,203,226,82,200,154,143,193,210,251,128,60,13,3,169,20,40,30,88,120,52,224,148,49>>},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,55>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,52>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,52,46,48>>,false}]},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,50,46,52>>},[{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101>>,<<98,101,110,99,104,101,101>>,<<126,62,32,48,46,56>>,true},{<<104,101,120,112,109>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<98,101,110,99,104,101,101,95,104,116,109,108>>,<<126,62,32,48,46,49>>,true},{<<104,101,120,112,109>>,<<101,120,106,115,120>>,<<101,120,106,115,120>>,<<126,62,32,52,46,48>>,true},{<<104,101,120,112,109>>,<<106,97,115,111,110>>,<<106,97,115,111,110>>,<<126,62,32,49,46,48>>,true},{<<104,101,120,112,109>>,<<106,115,111,110,101>>,<<106,115,111,110,101>>,<<126,62,32,49,46,52>>,true},{<<104,101,120,112,109>>,<<112,111,105,115,111,110>>,<<112,111,105,115,111,110>>,<<126,62,32,51,46,48>>,true},{<<104,101,120,112,109>>,<<116,105,110,121>>,<<116,105,110,121>>,<<126,62,32,49,46,48>>,true}]},{{retired,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,51>>},nil},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,54>>},nil},{{deps,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,53>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,53>>},<<29,227,205,145,60,190,39,174,240,172,159,191,122,176,110,194,117,248,93,162,139,55,96,100,128,73,153,209,214,51,54,122>>},{{inner_checksum,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,54>>},<<185,103,190,43,35,240,246,120,127,171,126,214,129,180,196,90,33,90,129,72,31,182,43,1,165,183,80,250,143,48,247,108>>},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,48,46,48>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,51>>},{{2021,9,24},{5,33,54}}},{{outer_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,51>>},<<124,16,141,227,48,56,16,40,44,161,17,236,192,147,138,120,230,2,246,180,240,203,107,144,253,245,47,149,209,152,5,159>>},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,53>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,50,46,51>>},<<28,55,228,101,104,121,52,189,79,135,20,25,68,145,107,252,74,110,96,231,95,211,42,107,78,234,230,154,136,205,203,158>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,50,46,50>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,52>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,49>>},<<222,148,84,174,200,249,97,163,28,227,188,38,197,39,87,121,10,152,130,7,85,93,208,130,110,90,96,165,229,244,16,167>>},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,57>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<50,46,51,46,50>>,false}]},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,49>>},<<127,124,167,45,243,210,158,82,45,55,170,37,73,44,171,71,77,10,229,214,151,31,14,228,151,173,141,53,228,113,230,18>>},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,48,46,56>>},<<215,201,232,38,14,156,220,31,178,203,119,226,217,3,149,105,98,193,247,97,87,235,130,0,126,198,189,229,218,171,101,210>>},{{deps,<<104,101,120,112,109>>,<<112,97,114,97,108,108,101,108,95,115,116,114,101,97,109>>,<<49,46,48,46,48>>},[]},{{timestamp,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,51,46,53>>},{{2021,9,24},{5,33,51}}},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,54,46,50>>},<<1,101,136,233,169,102,36,116,1,188,191,2,151,109,70,143,30,111,6,137,29,222,68,248,115,201,37,156,100,150,204,161>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,48,46,50>>},nil},{{timestamp,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,53,46,48>>},{{2021,9,24},{5,33,54}}},{{timestamp,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,51,46,48>>},{{2021,9,24},{5,33,54}}},{{deps,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,55,46,48>>},[{<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<114,101,99,111,110>>,<<126,62,50,46,53,46,49>>,false}]},{{retired,<<104,101,120,112,109>>,<<114,101,99,111,110>>,<<50,46,50,46,48>>},nil},{{inner_checksum,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,49>>},<<140,130,155,178,142,63,49,106,54,18,22,49,68,5,254,41,109,141,210,253,116,9,223,165,63,164,172,28,255,55,151,160>>},{{outer_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<49,46,49,46,50>>},<<101,91,237,185,64,198,75,180,213,141,96,128,203,130,4,177,249,36,215,227,22,137,13,7,134,172,127,144,251,154,104,212>>},{{inner_checksum,<<104,101,120,112,109>>,<<99,115,118>>,<<50,46,51,46,49>>},<<156,225,30,255,90,116,160,123,175,55,135,178,177,157,215,152,114,77,41,169,195,164,146,164,29,243,159,106,246,134,218,14>>},{{timestamp,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,51>>},{{2021,9,24},{5,33,54}}},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,52,46,49>>},nil},{{retired,<<104,101,120,112,109>>,<<111,98,115,101,114,118,101,114,95,99,108,105>>,<<49,46,51,46,51>>},nil},{{deps,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,48>>},[]},{{outer_checksum,<<104,101,120,112,109>>,<<106,115,111,110>>,<<49,46,52,46,49>>},<<154,191,33,141,190,78,164,252,184,117,224,135,213,249,4,239,38,61,1,46,229,237,33,212,110,157,188,166,63,5,61,22>>},{{retired,<<104,101,120,112,109>>,<<106,115,111,110>>,<<48,46,51,46,51>>},nil}].
deps/.hex/packages/hexpm/observer_cli-1.6.2.tar less more
Binary diff not shown
+0
-1
deps/.mix/archives/hex-0.21.2/hex-0.21.2/.elixir less more
0 ~> 1.0
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.API.Auth.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.API.Key.Organization.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.API.Key.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.API.Package.Owner.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.API.Package.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.API.Release.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.API.ReleaseDocs.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.API.ShortURL.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.API.User.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.API.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Application.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Config.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Crypto.AES_CBC_HMAC_SHA2.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Crypto.AES_GCM.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Crypto.ContentEncryptor.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Crypto.Encryption.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Crypto.KeyManager.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Crypto.PBES2_HMAC_SHA2.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Crypto.PKCS5.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Crypto.PublicKey.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Crypto.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.HTTP.Certs.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.HTTP.SSL.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.HTTP.VerifyHostname.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.HTTP.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Mix.TaskDescription.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Mix.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.OptionParser.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Parallel.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Registry.Server.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Registry.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.RemoteConverger.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Repo.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Resolver.Backtracks.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Resolver.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.SCM.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Server.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Set.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Shell.Process.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Shell.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.State.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Stdlib.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Tar.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.UpdateChecker.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Utils.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Version.InvalidRequirementError.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Version.InvalidVersionError.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Version.Requirement.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.Version.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Hex.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Audit.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Build.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Config.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Docs.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Info.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Install.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Organization.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Outdated.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Owner.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Package.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Publish.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Registry.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Repo.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Retire.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Search.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.Sponsor.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.User.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/Elixir.Mix.Tasks.Hex.beam less more
Binary diff not shown
+0
-61
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/hex.app less more
0 {application,hex,
1 [{applications,[kernel,stdlib,elixir,ssl,inets]},
2 {description,"hex"},
3 {modules,['Elixir.Hex','Elixir.Hex.API','Elixir.Hex.API.Auth',
4 'Elixir.Hex.API.Key',
5 'Elixir.Hex.API.Key.Organization',
6 'Elixir.Hex.API.Package',
7 'Elixir.Hex.API.Package.Owner',
8 'Elixir.Hex.API.Release','Elixir.Hex.API.ReleaseDocs',
9 'Elixir.Hex.API.ShortURL','Elixir.Hex.API.User',
10 'Elixir.Hex.Application','Elixir.Hex.Config',
11 'Elixir.Hex.Crypto',
12 'Elixir.Hex.Crypto.AES_CBC_HMAC_SHA2',
13 'Elixir.Hex.Crypto.AES_GCM',
14 'Elixir.Hex.Crypto.ContentEncryptor',
15 'Elixir.Hex.Crypto.Encryption',
16 'Elixir.Hex.Crypto.KeyManager',
17 'Elixir.Hex.Crypto.PBES2_HMAC_SHA2',
18 'Elixir.Hex.Crypto.PKCS5',
19 'Elixir.Hex.Crypto.PublicKey','Elixir.Hex.HTTP',
20 'Elixir.Hex.HTTP.Certs','Elixir.Hex.HTTP.SSL',
21 'Elixir.Hex.HTTP.VerifyHostname','Elixir.Hex.Mix',
22 'Elixir.Hex.Mix.TaskDescription',
23 'Elixir.Hex.OptionParser','Elixir.Hex.Parallel',
24 'Elixir.Hex.Registry','Elixir.Hex.Registry.Server',
25 'Elixir.Hex.RemoteConverger','Elixir.Hex.Repo',
26 'Elixir.Hex.Resolver',
27 'Elixir.Hex.Resolver.Backtracks','Elixir.Hex.SCM',
28 'Elixir.Hex.Server','Elixir.Hex.Set',
29 'Elixir.Hex.Shell','Elixir.Hex.Shell.Process',
30 'Elixir.Hex.State','Elixir.Hex.Stdlib',
31 'Elixir.Hex.Tar','Elixir.Hex.UpdateChecker',
32 'Elixir.Hex.Utils','Elixir.Hex.Version',
33 'Elixir.Hex.Version.InvalidRequirementError',
34 'Elixir.Hex.Version.InvalidVersionError',
35 'Elixir.Hex.Version.Requirement',
36 'Elixir.Mix.Tasks.Hex','Elixir.Mix.Tasks.Hex.Audit',
37 'Elixir.Mix.Tasks.Hex.Build',
38 'Elixir.Mix.Tasks.Hex.Config',
39 'Elixir.Mix.Tasks.Hex.Docs',
40 'Elixir.Mix.Tasks.Hex.Info',
41 'Elixir.Mix.Tasks.Hex.Install',
42 'Elixir.Mix.Tasks.Hex.Organization',
43 'Elixir.Mix.Tasks.Hex.Outdated',
44 'Elixir.Mix.Tasks.Hex.Owner',
45 'Elixir.Mix.Tasks.Hex.Package',
46 'Elixir.Mix.Tasks.Hex.Publish',
47 'Elixir.Mix.Tasks.Hex.Registry',
48 'Elixir.Mix.Tasks.Hex.Repo',
49 'Elixir.Mix.Tasks.Hex.Retire',
50 'Elixir.Mix.Tasks.Hex.Search',
51 'Elixir.Mix.Tasks.Hex.Sponsor',
52 'Elixir.Mix.Tasks.Hex.User',mix_hex_core,
53 mix_hex_erl_tar,mix_hex_filename,mix_hex_http,
54 mix_hex_http_httpc,mix_hex_pb_names,
55 mix_hex_pb_package,mix_hex_pb_signed,
56 mix_hex_pb_versions,mix_hex_registry,mix_hex_repo,
57 mix_hex_tarball,mix_safe_erl_term]},
58 {registered,[]},
59 {vsn,"0.21.2"},
60 {mod,{'Elixir.Hex.Application',[]}}]}.
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_hex_core.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_hex_erl_tar.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_hex_filename.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_hex_http.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_hex_http_httpc.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_hex_pb_names.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_hex_pb_package.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_hex_pb_signed.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_hex_pb_versions.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_hex_registry.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_hex_repo.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_hex_tarball.beam less more
Binary diff not shown
deps/.mix/archives/hex-0.21.2/hex-0.21.2/ebin/mix_safe_erl_term.beam less more
Binary diff not shown
0 {application,hex,
1 [{applications,[kernel,stdlib,elixir,ssl,inets]},
2 {description,"hex"},
3 {modules,['Elixir.Hex','Elixir.Hex.API','Elixir.Hex.API.Auth',
4 'Elixir.Hex.API.Key',
5 'Elixir.Hex.API.Key.Organization',
6 'Elixir.Hex.API.Package',
7 'Elixir.Hex.API.Package.Owner',
8 'Elixir.Hex.API.Release','Elixir.Hex.API.ReleaseDocs',
9 'Elixir.Hex.API.ShortURL','Elixir.Hex.API.User',
10 'Elixir.Hex.Application','Elixir.Hex.Config',
11 'Elixir.Hex.Crypto',
12 'Elixir.Hex.Crypto.AES_CBC_HMAC_SHA2',
13 'Elixir.Hex.Crypto.AES_GCM',
14 'Elixir.Hex.Crypto.ContentEncryptor',
15 'Elixir.Hex.Crypto.Encryption',
16 'Elixir.Hex.Crypto.KeyManager',
17 'Elixir.Hex.Crypto.PBES2_HMAC_SHA2',
18 'Elixir.Hex.Crypto.PKCS5',
19 'Elixir.Hex.Crypto.PublicKey','Elixir.Hex.HTTP',
20 'Elixir.Hex.HTTP.Certs','Elixir.Hex.HTTP.SSL',
21 'Elixir.Hex.HTTP.VerifyHostname','Elixir.Hex.Mix',
22 'Elixir.Hex.Mix.TaskDescription',
23 'Elixir.Hex.OptionParser','Elixir.Hex.Parallel',
24 'Elixir.Hex.Registry','Elixir.Hex.Registry.Server',
25 'Elixir.Hex.RemoteConverger','Elixir.Hex.Repo',
26 'Elixir.Hex.Resolver',
27 'Elixir.Hex.Resolver.Backtracks','Elixir.Hex.SCM',
28 'Elixir.Hex.Server','Elixir.Hex.Set',
29 'Elixir.Hex.Shell','Elixir.Hex.Shell.Process',
30 'Elixir.Hex.Sponsor','Elixir.Hex.State',
31 'Elixir.Hex.Stdlib','Elixir.Hex.Tar',
32 'Elixir.Hex.UpdateChecker','Elixir.Hex.Utils',
33 'Elixir.Hex.Version',
34 'Elixir.Hex.Version.InvalidRequirementError',
35 'Elixir.Hex.Version.InvalidVersionError',
36 'Elixir.Hex.Version.Requirement',
37 'Elixir.Mix.Tasks.Hex','Elixir.Mix.Tasks.Hex.Audit',
38 'Elixir.Mix.Tasks.Hex.Build',
39 'Elixir.Mix.Tasks.Hex.Config',
40 'Elixir.Mix.Tasks.Hex.Docs',
41 'Elixir.Mix.Tasks.Hex.Info',
42 'Elixir.Mix.Tasks.Hex.Install',
43 'Elixir.Mix.Tasks.Hex.Organization',
44 'Elixir.Mix.Tasks.Hex.Outdated',
45 'Elixir.Mix.Tasks.Hex.Owner',
46 'Elixir.Mix.Tasks.Hex.Package',
47 'Elixir.Mix.Tasks.Hex.Publish',
48 'Elixir.Mix.Tasks.Hex.Registry',
49 'Elixir.Mix.Tasks.Hex.Repo',
50 'Elixir.Mix.Tasks.Hex.Retire',
51 'Elixir.Mix.Tasks.Hex.Search',
52 'Elixir.Mix.Tasks.Hex.Sponsor',
53 'Elixir.Mix.Tasks.Hex.User',mix_hex_core,
54 mix_hex_erl_tar,mix_hex_filename,mix_hex_http,
55 mix_hex_http_httpc,mix_hex_pb_names,
56 mix_hex_pb_package,mix_hex_pb_signed,
57 mix_hex_pb_versions,mix_hex_registry,mix_hex_repo,
58 mix_hex_tarball,mix_safe_erl_term]},
59 {registered,[]},
60 {vsn,"0.21.3"},
61 {mod,{'Elixir.Hex.Application',[]}}]}.
3939 deps = DEPS,
4040 )
4141
42 xref(tags = ["xref"])
42 xref(
43 additional_libs = [
44 "@ranch//:bazel_erlang_lib",
45 ],
46 tags = ["xref"],
47 )
4348
4449 plt(
4550 name = "base_plt",
194194
195195 maybe_ssl_info(Sock) ->
196196 RealSocket = rabbit_net:unwrap_socket(Sock),
197 case rabbit_net:is_ssl(RealSocket) of
198 true -> [{ssl, true}] ++ ssl_info(RealSocket) ++ ssl_cert_info(RealSocket);
199 false -> [{ssl, false}]
197 case rabbit_net:proxy_ssl_info(RealSocket, rabbit_net:maybe_get_proxy_socket(Sock)) of
198 nossl -> [{ssl, false}];
199 Info -> [{ssl, true}] ++ ssl_info(Info) ++ ssl_cert_info(RealSocket)
200200 end.
201201
202 ssl_info(Sock) ->
202 ssl_info(Info) ->
203203 {Protocol, KeyExchange, Cipher, Hash} =
204 case rabbit_net:ssl_info(Sock) of
204 case Info of
205205 {ok, Infos} ->
206206 {_, P} = lists:keyfind(protocol, 1, Infos),
207207 #{cipher := C,
2323 %% rebar.config
2424 {deps, [observer_cli]}
2525 %% erlang.mk
26 dep_observer_cli = hex 1.6.2
26 dep_observer_cli = hex 1.7.1
2727 ```
2828 **Elixir**
2929 ```elixir
3030 # mix.exs
3131 def deps do
32 [{:observer_cli, "~> 1.6"}]
32 [{:observer_cli, "~> 1.7"}]
3333 end
3434 ```
3535 ------------------
8787
8888 {plugins,
8989 [
90 #{module => observer_cli_plug_behaviour1, title => "XPlug",
91 interval => 1500, shortcut => "X", sort_column => 3},
92 #{module => observer_cli_plug_behaviour2, title => "YPlug",
93 interval => 1600, shortcut => "Y", sort_column => 3}
90 #{module => observer_cli_plug_behaviour_x, title => "XPlug",
91 interval => 1600, shortcut => "X", sort_column => 3},
92 #{module => observer_cli_plug_behaviour_y, title => "YPlug",
93 interval =>2000, shortcut => "Y", sort_column => 3}
9494 ]
9595 }
96
97 ```
98 <img src="https://user-images.githubusercontent.com/3116225/46514684-ebff7280-c891-11e8-820e-90c3302f9108.jpg" width="90%"></img>
99
96 ```
97 The main view is `HOME` by default(`observer_cli:start()`).
98 If you want to plugin view as main view, DO:`your_cli:start().`
99 ```erlang
100 % your_cli.erl
101 start() -> observer_cli:start_plugin().
102 ```
100103 2. Write observer_cli_plugin behaviour.
101104 observer_cli_plugin has 3 callbacks.
102
105
103106 2.1 attributes.
104107 ```erlang
105108 -callback atributes(PrevState) -> {[Rows], NewState} when
109112 for example:
110113 ```erlang
111114 attributes(PrevState) ->
112 Attrs =
115 Attrs = [
113116 [
114 [
115 #{content => "XXX Ets Size", width => 20},
116 #{content => ets:info(xxx,size), width => 10},
117 #{content => "Pool1 Size", width => 15},
118 #{content => application:get_env(app,pool1_size), width => 30},
119 #{content => "XYZ1 Process Mem", width => 18,
120 #{content => {byte, element(2, erlang:process_info(xyz1, memory))}, width => 16}
121 ],
122 [
123 #{content => "YYY Ets Size", width =>20},
124 #{content => ets:info(yyy,size), width => 10},
125 #{content => "Pool2 Size", width =>15},
126 #{content => application:get_env(app,pool2_size), width => 30},
127 #{content =>"XYZ2 Process Mem", width =>18},
128 #{content => {byte, element(2, erlang:process_info(xyz2, memory))}, width => 16}
129 ],
130 [
131 #{content => "ZZZ Ets Size", width =>20},
132 #{content => ets:info(zzz,size), width => 10},
133 #{content => "Pool3 Size", width =>15},
134 #{content => application:get_env(app,pool3_size), width => 30},
135 #{content => "XYZ3 Process Mem", width =>18},
136 #{content => {byte, element(2, erlang:process_info(xyz3, memory))}, width => 16}
137 ]
117 #{content => "XXX Ets Size", width => 15},
118 #{content => 122, width => 10},
119 #{content => "Memory Capcity", width => 15},
120 #{content => {percent, 0.12}, width => 16},
121 #{content => "XYZ1 Process Mem", width => 19},
122 #{content => {byte, 1023 * 1203}, width => 19}
138123 ],
139 NewState = PrevState,
140 {Attrs, NewState}.
141 ```
142 <img src="https://user-images.githubusercontent.com/3116225/46514685-ebff7280-c891-11e8-915e-67f558694328.jpg" width="90%"></img>
124 [
125 #{content => "YYY Ets Size", width => 15},
126 #{content => 43, width => 10},
127 #{content => "Disk Capcity", width => 15},
128 #{content => {percent, 0.23}, width => 16},
129 #{content => "XYZ2 Process Mem", width => 19},
130 #{content => {byte, 2034 * 220}, width => 19}
131 ],
132 [
133 #{content => "ZZZ Ets Size", width => 15},
134 #{content => 108, width => 10},
135 #{content => "Volume Capcity", width => 15},
136 #{content => {percent, 0.101}, width => 16},
137 #{content => "XYZ3 Process Mem", width => 19},
138 #{content => {byte, 12823}, width => 19}
139 ]
140 ],
141 NewState = PrevState,
142 {Attrs, NewState}.
143 ```
144
145 ```markdown
146 |Home(H)|XPlug(X)|YPlug(Y)| | 0Days 3:34:50 |
147 |XXX Ets Size | 122 | Memory Capcity | 12.00% | XYZ1 Process Mem | 1.1737 MB |
148 |YYY Ets Size | 43 | Disk Capcity | 23.00% | XYZ2 Process Mem | 436.9922 KB |
149 |ZZZ Ets Size | 108 | Volume Capcity | 10.10% | XYZ3 Process Mem | 12.5225 KB |
150 ```
143151
144152 ```erlang
145153 -callback sheet_header() -> [SheetHeader] when
148156 for example:
149157 ```erlang
150158 sheet_header() ->
151 [
152 #{title => "Pid", width => 25},
153 #{title => "Status", width => 25},
154 #{title => "Memory", width => 24, shortcut => "S"},
155 #{title => "Reductions", width => 24, shortcut => "R"},
156 #{title => "Message Queue Len", width => 25, shortcut => "Q"}
157 ].
159 [
160 #{title => "Pid", width => 15},
161 #{title => "Register", width => 20},
162 #{title => "Memory", width => 20, shortcut => "S"},
163 #{title => "Reductions", width => 23, shortcut => "R"},
164 #{title => "Message Queue Len", width => 23, shortcut => "Q"}
165 ].
166 ```
167 ```markdown
168 |No |Pid |Register |Memory(S) |Reductions(R) |Message Queue Len(Q) |
158169 ```
159170
160171 ```erlang
166177 ```
167178
168179 for example:
169
170180 ```erlang
171181 sheet_body(PrevState) ->
172 Body =
173 [begin
174 [
175 Pid,
176 element(2, erlang:process_info(Pid, status)),
177 element(2, erlang:process_info(Pid, memory)),
178 element(2, erlang:process_info(Pid, reductions)),
179 element(2, erlang:process_info(Pid, message_queue_len))
180 ]
181 end||Pid <- erlang:processes()
182 ],
183 NewState = PrevState,
184 {Body, NewState}.
185 ```
186
182 Body = [
183 begin
184 Register =
185 case erlang:process_info(Pid, registered_name) of
186 [] -> [];
187 {_, Name} -> Name
188 end,
189 [
190 Pid,
191 Register,
192 {byte, element(2, erlang:process_info(Pid, memory))},
193 element(2, erlang:process_info(Pid, reductions)),
194 element(2, erlang:process_info(Pid, message_queue_len))
195 ]
196 end
197 || Pid <- erlang:processes()
198 ],
199 NewState = PrevState,
200 {Body, NewState}.
201 ```
202 Support `{byte, 1024*10}` to ` 10.0000 KB`; `{percent, 0.12}` to `12.00%`.
203
204 ```markdown
205 |No |Pid |Register |Memory(S) |Reductions(R) |Message Queue Len(Q) |
206 |1 |<0.242.0> | | 4.5020 MB | 26544288 | 0 |
207 |2 | <0.206.0> | | 1.2824 MB | 13357885 | 0 |
208 |3 | <0.10.0> | erl_prim_loader | 1.0634 MB | 10046775 | 0 |
209 |4 | <0.434.0> | | 419.1719 KB | 10503690 | 0 |
210 |5 | <0.44.0> | application_contro | 416.6250 KB | 153598 | 0 |
211 |6 | <0.50.0> | code_server | 416.4219 KB | 301045 | 0 |
212 |7 | <0.9.0> | rebar_agent | 136.7031 KB | 1337603 | 0 |
213 |8 | <0.207.0> | | 99.3125 KB | 9629 | 0 |
214 |9 | <0.58.0> | file_server_2 | 41.3359 KB | 34303 | 0 |
215 |10 | <0.209.0> | | 27.3438 KB | 31210 | 0 |
216 |11 | <0.0.0> | init | 25.8516 KB | 8485 | 0 |
217 |refresh: 1600ms q(quit) Positive Number(set refresh interval time ms) F/B(forward/back) Current pages is 1 |
218 ```
187219 Support F/B to page up/down.
188
189 <img src="https://user-images.githubusercontent.com/3116225/46514686-ec980900-c891-11e8-8232-f6ad98fd2e5c.jpg" width="90%"></img>
190
191 <img src="https://user-images.githubusercontent.com/3116225/46514783-96779580-c892-11e8-872a-1a44e4d92b76.jpg" width="90%"></img>
192220
193221 [A more specific plugin](https://github.com/zhongwencool/os_stats) can collect linux system information such as kernel vsn, loadavg, disk, memory usage, cpu utilization, IO statistics.
194222
195223 ----------------
196224 ### Changelog
225 - 1.7.1
226 - application view show starting/loading/startPfalse/loaded/started application.
227 - fixed badarg when staring by rpc and stop by `ctrl+c`.
228 - fixed mix.exe version error
229 - 1.7.0
230 - application view support reductions/memory/process_count sort
231 - plugin support `{byte, 1024}` to `10.0000 KB`
232 - plugin support `{percent, 0.1234` to `12.34%`
233 - plugin support dig deep process view.
197234 - 1.6.2
198235 - fixed crash when ps command not found on windows.
199236 - 1.6.1
1313 interval = ?DEFAULT_INTERVAL :: pos_integer(),
1414 scheduler_usage = application:get_env(observer_cli, scheduler_usage, ?DISABLE) ::
1515 ?DISABLE | ?ENABLE
16 }).
17
18 -record(app, {
19 type = {proc_count, 1} :: {atom(), pos_integer()},
20 cur_page = 1 :: pos_integer(),
21 interval = ?DEFAULT_INTERVAL :: pos_integer()
1622 }).
1723
1824 -record(ets, {
4753 home = #home{} :: home(),
4854 ets = #ets{} :: ets(),
4955 sys = #system{} :: system(),
56 app = #app{} :: app(),
5057 db = #db{} :: db(),
5158 help = #help{} :: help(),
5259 inet = #inet{} :: inet(),
6067
6168 -type view_opts() :: #view_opts{}.
6269 -type home() :: #home{}.
70 -type app() :: #app{}.
6371 -type system() :: #system{}.
6472 -type ets() :: #ets{}.
6573 -type db() :: #db{}.
8795 -define(NEW_LINE, "\e[0m\n|").
8896 -define(I, <<" | ">>).
8997 -define(I2, <<"|">>).
90 -define(W(_C_, _A_, _W_), {extend_color, _C_, _A_, _W_}).
91 -define(W2(_C_, _A_, _W_), {extend_color_2, _C_, _A_, _W_}).
92 -define(W(_A_, _W_), {extend, _A_, _W_}).
98 -define(W(_C_, _A_, _W_), {width_color, _C_, _A_, _W_}).
99 -define(W2(_C_, _A_, _W_), {width_color_2, _C_, _A_, _W_}).
100 -define(W(_A_, _W_), {width, _A_, _W_}).
93101
94102 -define(SELECT(Text), observer_cli_lib:select(Text)).
95103 -define(UNSELECT(Text), observer_cli_lib:unselect(Text)).
33 def project do
44 [
55 app: :observer_cli,
6 version: "1.6.2",
6 version: "1.7.1",
77 language: :erlang,
88 description: "observer in shell",
99 deps: [
00 {application,observer_cli,
11 [{description,"Visualize Erlang Nodes On The Command Line"},
2 {vsn,"1.6.2"},
2 {vsn,"1.7.1"},
33 {modules,[]},
44 {registered,[]},
55 {applications,[kernel,stdlib,recon]},
0 %%% @author zhongwen <zhongwencool@gmail.com>
1
2 -module(observer_cli).
3
4 -include("observer_cli.hrl").
5
6 %% API
7 -export([start/0]).
8 -export([start/1]).
9 -export([start/2]).
10 -export([start_plugin/0]).
11 -export([clean/1]).
12
13 %% cpu >= this value will be highlight
14 -define(CPU_ALARM_THRESHOLD, 0.8).
15 %% port or process reach max_limit * 0.85 will be highlight
16 -define(COUNT_ALARM_THRESHOLD, 0.85).
17 -define(LAST_LINE,
18 "q(quit) p(pause) r/rr(reduction) m/mm(mem)"
19 "b/bb(binary mem) t/tt(total heap size) mq/mmq(msg queue) 9(proc 9 info) F/B(page forward/back)"
20 ).
21
22 -spec start() -> no_return | {badrpc, term()}.
23 start() -> start(#view_opts{}).
24
25 -spec start(Node) -> no_return | {badrpc, term()} when Node :: atom() | non_neg_integer().
26 start(Node) when Node =:= node() ->
27 start(#view_opts{});
28 start(Node) when is_atom(Node) ->
29 rpc_start(Node, ?DEFAULT_INTERVAL);
30 start(#view_opts{home = Home} = Opts) ->
31 erlang:process_flag(trap_exit, true),
32 AutoRow = check_auto_row(),
33 #home{scheduler_usage = SchUsage} = Home,
34 StorePid = observer_cli_store:start(),
35 LastSchWallFlag = set_scheduler_wall_time(true, SchUsage),
36 PsCmd = io_lib:format("ps -o pcpu,pmem ~s", [os:getpid()]),
37 RenderPid = spawn_link(fun() -> render_worker(PsCmd, StorePid, Home, AutoRow) end),
38 manager(StorePid, RenderPid, Opts#view_opts{auto_row = AutoRow}, LastSchWallFlag);
39 start(Interval) when is_integer(Interval), Interval >= ?MIN_INTERVAL ->
40 start(#view_opts{
41 home = #home{interval = Interval},
42 ets = #ets{interval = Interval},
43 sys = #system{interval = Interval},
44 db = #db{interval = Interval},
45 help = #help{interval = Interval},
46 inet = #inet{interval = Interval},
47 process = #process{interval = Interval},
48 port = Interval
49 }).
50
51 -spec start(Node, Cookies | Options) -> no_return | {badrpc, term()} when
52 Node :: atom(),
53 Cookies :: atom(),
54 Options :: proplists:proplist().
55 start(Node, _Cookie) when Node =:= node() ->
56 start(#view_opts{});
57 start(Node, Cookie) when is_atom(Node) andalso is_atom(Cookie) ->
58 start(Node, [{cookie, Cookie}]);
59 start(Node, Options) when is_atom(Node) andalso is_list(Options) ->
60 case proplists:get_value(cookie, Options) of
61 undefined -> ok;
62 Cookie -> erlang:set_cookie(Node, Cookie)
63 end,
64 Interval = proplists:get_value(interval, Options, ?DEFAULT_INTERVAL),
65 rpc_start(Node, Interval).
66
67 -spec start_plugin() -> no_return.
68 start_plugin() ->
69 erlang:process_flag(trap_exit, true),
70 application:ensure_all_started(observer_cli),
71 observer_cli_plugin:start(#view_opts{}).
72
73 -spec clean(list()) -> boolean().
74 clean([RenderPid, StorePid, SchWallFlag, SchUsage]) ->
75 observer_cli_lib:exit_processes([RenderPid, StorePid]),
76 set_scheduler_wall_time(SchWallFlag, SchUsage).
77
78 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
79 %%% Private
80 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
81
82 rpc_start(Node, Interval) ->
83 case net_kernel:hidden_connect_node(Node) of
84 true ->
85 rpc:call(Node, ?MODULE, start, [Interval]);
86 false ->
87 Msg = <<"Node(~p) refuse to be connected, make sure cookie is valid~n">>,
88 connect_error(Msg, Node),
89 {badrpc, nodedown};
90 ignored ->
91 Msg = <<"Ignored by node(~p), local node is not alive!~n">>,
92 connect_error(Msg, Node),
93 {badrpc, nodedown}
94 end.
95
96 manager(StorePid, RenderPid, Opts, LastSchWallFlag) ->
97 #view_opts{home = Home = #home{cur_page = CurPage, pages = Pages, scheduler_usage = SchUsage}} =
98 Opts,
99 Resource = [RenderPid, StorePid, LastSchWallFlag, SchUsage],
100 case observer_cli_lib:parse_cmd(Opts, ?MODULE, Resource) of
101 quit ->
102 erlang:unlink(RenderPid),
103 erlang:send(RenderPid, quit),
104 set_scheduler_wall_time(LastSchWallFlag, SchUsage),
105 observer_cli_lib:exit_processes([StorePid]),
106 quit;
107 pause_or_resume ->
108 erlang:send(RenderPid, pause_or_resume),
109 manager(StorePid, RenderPid, Opts, LastSchWallFlag);
110 {new_interval, NewInterval} ->
111 clean(Resource),
112 start(Opts#view_opts{home = Home#home{interval = NewInterval}});
113 scheduler_usage ->
114 NewSchUsage =
115 case SchUsage of
116 ?DISABLE -> ?ENABLE;
117 ?ENABLE -> ?DISABLE
118 end,
119 clean(Resource),
120 start(Opts#view_opts{home = Home#home{scheduler_usage = NewSchUsage}});
121 {jump, NewPos} ->
122 NewPages = observer_cli_lib:update_page_pos(CurPage, NewPos, Pages),
123 NewOpts = Opts#view_opts{home = Home#home{pages = NewPages}},
124 start_process_view(StorePid, RenderPid, NewOpts, LastSchWallFlag, false);
125 jump ->
126 start_process_view(StorePid, RenderPid, Opts, LastSchWallFlag, true);
127 {func, Func, Type} ->
128 clean(Resource),
129 start(Opts#view_opts{home = Home#home{func = Func, type = Type}});
130 page_down_top_n ->
131 NewPage = max(CurPage + 1, 1),
132 NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages),
133 clean(Resource),
134 start(Opts#view_opts{home = Home#home{cur_page = NewPage, pages = NewPages}});
135 page_up_top_n ->
136 NewPage = max(CurPage - 1, 1),
137 NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages),
138 clean(Resource),
139 start(Opts#view_opts{home = Home#home{cur_page = NewPage, pages = NewPages}});
140 _ ->
141 manager(StorePid, RenderPid, Opts, LastSchWallFlag)
142 end.
143
144 render_worker(PsCmd, Manager, Home = #home{scheduler_usage = SchUsage}, AutoRow) ->
145 ?output(?CLEAR),
146 StableInfo = get_stable_system_info(),
147 LastStats = get_incremental_stats(SchUsage),
148 redraw_running(PsCmd, Manager, Home, StableInfo, LastStats, erlang:make_ref(), AutoRow, true).
149
150 %% pause status waiting to be resume
151 redraw_pause(PsCmd, StorePid, Home, StableInfo, LastStats, LastTimeRef, AutoRow) ->
152 notify_pause_status(),
153 erlang:cancel_timer(LastTimeRef),
154 #home{func = Func, type = Type} = Home,
155 receive
156 quit ->
157 quit;
158 {Func, Type} ->
159 redraw_running(
160 PsCmd,
161 StorePid,
162 Home,
163 StableInfo,
164 LastStats,
165 LastTimeRef,
166 AutoRow,
167 false
168 );
169 pause_or_resume ->
170 ?output(?CLEAR),
171 redraw_running(PsCmd, StorePid, Home, StableInfo, LastStats, LastTimeRef, AutoRow, true)
172 end.
173
174 %% running status
175 redraw_running(PsCmd, StorePid, Home, StableInfo, LastStats, LastTimeRef, AutoRow, IsFirstTime) ->
176 #home{
177 interval = Interval,
178 func = Func,
179 type = Type,
180 pages = RankPos,
181 cur_page = CurPage,
182 scheduler_usage = SchUsage
183 } = Home,
184 erlang:cancel_timer(LastTimeRef),
185 TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow),
186 {Diffs, Schedulers, NewStats} = node_stats(LastStats, SchUsage),
187 {CPURow, CPULine} = render_scheduler_usage(Schedulers),
188 ProcessRows = max(TerminalRow - 14 - CPURow, 0),
189 TopLen = ProcessRows * CurPage,
190 TopList = get_top_n(Func, Type, Interval, TopLen, IsFirstTime),
191 Text = get_refresh_prompt(Func, Type, Interval, TopLen),
192 MenuLine = observer_cli_lib:render_menu(home, Text),
193 SystemLine = render_system_line(PsCmd, element(1, StableInfo)),
194 MemLine = render_memory_process_line(Diffs, element(2, StableInfo), Interval),
195 {TopNList, RankLine} = render_top_n_view(Type, TopList, ProcessRows, RankPos, CurPage),
196 LastLine = observer_cli_lib:render_last_line(?LAST_LINE),
197 ?output([?CURSOR_TOP, MenuLine, SystemLine, MemLine, CPULine, RankLine, LastLine]),
198
199 observer_cli_store:update(StorePid, ProcessRows, TopNList),
200 TimeRef = refresh_next_time(Func, Type, Interval),
201 receive
202 quit ->
203 quit;
204 pause_or_resume ->
205 redraw_pause(PsCmd, StorePid, Home, StableInfo, NewStats, TimeRef, AutoRow);
206 {Func, Type} ->
207 redraw_running(PsCmd, StorePid, Home, StableInfo, NewStats, TimeRef, AutoRow, false)
208 end.
209
210 render_system_line(PsCmd, StableInfo) ->
211 [Version, SysVersion, ProcLimit, PortLimit, EtsLimit] = StableInfo,
212 ActiveTask = erlang:statistics(total_active_tasks),
213 {ContextSwitch, _} = erlang:statistics(context_switches),
214 AtomStatus = get_atom_status(),
215 Reductions = erlang:statistics(reductions),
216 {PortWarning, ProcWarning, PortCount, ProcCount} =
217 get_port_proc_info(PortLimit, ProcLimit),
218 CmdValue = case string:split(os:cmd(PsCmd), "\n", all) of
219 [_, CmdValueTmp | _] -> CmdValueTmp;
220 _ -> ""
221 end,
222
223 [CpuPsV, MemPsV] =
224 case lists:filter(fun(Y) -> Y =/= [] end, string:split(CmdValue, " ", all)) of
225 [] -> ["--", "--"];
226 [V1, V2] -> [V1, V2]
227 end,
228 Title = ?render([
229 ?W(SysVersion, 136),
230 ?NEW_LINE,
231 ?GRAY_BG,
232 ?W("System", 10),
233 ?W("Count/Limit", 21),
234 ?W("System", 25),
235 ?W("Status", 21),
236 ?W("Stat Info", 20),
237 ?W("Size", 24)
238 ]),
239 Row1 = ?render([
240 ?W("Proc Count", 10),
241 ?W2(ProcWarning, ProcCount, 22),
242 ?W(" Version", 26),
243 ?W(Version, 21),
244 ?W("Active Task", 20),
245 ?W(ActiveTask, 24),
246 ?NEW_LINE,
247 ?W("Port Count", 10),
248 ?W2(PortWarning, PortCount, 22),
249 ?W(" ps -o pcpu", 26),
250 ?W([CpuPsV, "%"], 21),
251 ?W("Context Switch", 20),
252 ?W(ContextSwitch, 24)
253 ]),
254 {Reds, AddReds} = Reductions,
255 Row2 =
256 case AtomStatus of
257 {ok, AtomLimit, AtomCount} ->
258 {AtomWarning, Atom} = format_atom_info(AtomLimit, AtomCount),
259 ?render([
260 ?UNDERLINE,
261 ?W("Atom Count", 10),
262 ?W2(AtomWarning, Atom, 22),
263 ?W(" ps -o pmem", 26),
264 ?W([MemPsV, "%"], 21),
265 ?W("Reds(Total/SinceLastCall)", 20),
266 ?W([integer_to_list(Reds), "/", integer_to_list(AddReds)], 24)
267 ]);
268 {error, unsupported} ->
269 ?render([
270 ?UNDERLINE,
271 ?W("Ets Limit", 10),
272 ?W(EtsLimit, 21),
273 ?W(" ps -o pmem", 25),
274 ?W([MemPsV, "%"], 21),
275 ?W("Reductions", 20),
276 ?W(Reductions, 24)
277 ])
278 end,
279 [Title, Row1, Row2].
280
281 render_memory_process_line(MemSum, PortParallelism, Interval) ->
282 RunQ = erlang:statistics(run_queue),
283 Mem = erlang:memory(),
284 TotalMem = proplists:get_value(total, Mem),
285 ProcMem = proplists:get_value(processes_used, Mem),
286 CodeMem = proplists:get_value(code, Mem),
287 AtomMem = proplists:get_value(atom_used, Mem),
288 BinMem = proplists:get_value(binary, Mem),
289 EtsMem = proplists:get_value(ets, Mem),
290 {
291 BytesIn,
292 BytesOut,
293 GcCount,
294 GcWordsReclaimed
295 } = MemSum,
296
297 {Queue, LogKey} =
298 case whereis(error_logger) of
299 undefined ->
300 {erlang:integer_to_list(RunQ), "RunQueue"};
301 Pid ->
302 {_, Q} = process_info(Pid, message_queue_len),
303 {[erlang:integer_to_list(RunQ), "/", erlang:integer_to_list(Q)],
304 "RunQueue/ErrorLoggerQueue"}
305 end,
306 ProcMemPercent = observer_cli_lib:to_percent(ProcMem / TotalMem),
307 AtomMemPercent = observer_cli_lib:to_percent(AtomMem / TotalMem),
308 BinMemPercent = observer_cli_lib:to_percent(BinMem / TotalMem),
309 CodeMemPercent = observer_cli_lib:to_percent(CodeMem / TotalMem),
310 EtsMemPercent = observer_cli_lib:to_percent(EtsMem / TotalMem),
311
312 Title = ?render([
313 ?GRAY_BG,
314 ?W("Mem Type", 10),
315 ?W("Size", 21),
316 ?W("Mem Type", 25),
317 ?W("Size", 21),
318 ?W(["IO/GC:(", integer_to_binary(Interval), "ms)"], 20),
319 ?W("Total/Increments", 24)
320 ]),
321 Row = ?render([
322 ?W("Total", 10),
323 ?W({byte, TotalMem}, 12),
324 ?W("100.0%", 6),
325 ?W("Binary", 25),
326 ?W({byte, BinMem}, 12),
327 ?W(BinMemPercent, 6),
328 ?W("IO Output", 20),
329 ?W(BytesOut, 24),
330 ?NEW_LINE,
331 ?W("Process", 10),
332 ?W({byte, ProcMem}, 12),
333 ?W(ProcMemPercent, 6),
334 ?W("Code", 25),
335 ?W({byte, CodeMem}, 12),
336 ?W(CodeMemPercent, 6),
337 ?W("IO Input", 20),
338 ?W(BytesIn, 24),
339 ?NEW_LINE,
340 ?W("Atom", 10),
341 ?W({byte, AtomMem}, 12),
342 ?W(AtomMemPercent, 6),
343 ?W("Port Parallelism (+spp)", 25),
344 ?W(PortParallelism, 21),
345 ?W("Gc Count", 20),
346 ?W(GcCount, 24),
347 ?NEW_LINE,
348 ?W("Ets", 10),
349 ?W({byte, EtsMem}, 12),
350 ?W(EtsMemPercent, 6),
351 ?W(LogKey, 25),
352 ?W(Queue, 21),
353 ?W("Gc Words Reclaimed", 20),
354 ?W(GcWordsReclaimed, 24)
355 ]),
356 [Title, Row].
357
358 render_scheduler_usage(undefined) ->
359 {0, []};
360 render_scheduler_usage(SchedulerUsage) ->
361 SchedulerNum = erlang:length(SchedulerUsage),
362 render_scheduler_usage(SchedulerUsage, SchedulerNum).
363
364 %% < 8 core split 2 part
365 render_scheduler_usage(SchedulerUsage, SchedulerNum) when SchedulerNum < 8 ->
366 Column =
367 case SchedulerNum rem 2 =:= 0 of
368 true -> SchedulerNum div 2;
369 false -> (SchedulerNum div 2) + 1
370 end,
371 CPU = [
372 begin
373 Seq2 = transform_seq(Seq1, Column, SchedulerNum),
374 Percent1 = proplists:get_value(Seq1, SchedulerUsage, 0.0),
375 Percent2 = proplists:get_value(Seq2, SchedulerUsage, 0.0),
376 CPU1 = observer_cli_lib:to_percent(Percent1),
377 CPU2 = observer_cli_lib:to_percent(Percent2),
378 Process1 = lists:duplicate(trunc(Percent1 * 57), "|"),
379 Process2 = lists:duplicate(trunc(Percent2 * 57), "|"),
380 IsLastLine = Seq1 =:= Column,
381 Format = process_bar_format_style([Percent1, Percent2], IsLastLine),
382 io_lib:format(Format, [
383 Seq1,
384 Process1,
385 CPU1,
386 Seq2,
387 Process2,
388 CPU2
389 ])
390 end
391 || Seq1 <- lists:seq(1, Column)
392 ],
393 {Column, CPU};
394 %% 100 >= scheduler >= 8 split 4 part
395 render_scheduler_usage(SchedulerUsage, SchedulerNum) when SchedulerNum =< 100 ->
396 Column =
397 case SchedulerNum rem 4 =:= 0 of
398 true -> SchedulerNum div 4;
399 false -> (SchedulerNum div 4) + 1
400 end,
401 CPU = [
402 begin
403 Seq2 = transform_seq(Seq1, Column, SchedulerNum),
404 Seq3 = transform_seq(Seq2, Column, SchedulerNum),
405 Seq4 = transform_seq(Seq3, Column, SchedulerNum),
406 Percent1 = proplists:get_value(Seq1, SchedulerUsage, 0.0),
407 Percent2 = proplists:get_value(Seq2, SchedulerUsage, 0.0),
408 Percent3 = proplists:get_value(Seq3, SchedulerUsage, 0.0),
409 Percent4 = proplists:get_value(Seq4, SchedulerUsage, 0.0),
410 CPU1 = observer_cli_lib:to_percent(Percent1),
411 CPU2 = observer_cli_lib:to_percent(Percent2),
412 CPU3 = observer_cli_lib:to_percent(Percent3),
413 CPU4 = observer_cli_lib:to_percent(Percent4),
414 Process1 = lists:duplicate(trunc(Percent1 * 22), "|"),
415 Process2 = lists:duplicate(trunc(Percent2 * 22), "|"),
416 Process3 = lists:duplicate(trunc(Percent3 * 22), "|"),
417 Process4 = lists:duplicate(trunc(Percent4 * 23), "|"),
418 IsLastLine = Seq1 =:= Column,
419 Format = process_bar_format_style([Percent1, Percent2, Percent3, Percent4], IsLastLine),
420 io_lib:format(Format, [
421 Seq1,
422 Process1,
423 CPU1,
424 Seq2,
425 Process2,
426 CPU2,
427 Seq3,
428 Process3,
429 CPU3,
430 Seq4,
431 Process4,
432 CPU4
433 ])
434 end
435 || Seq1 <- lists:seq(1, Column)
436 ],
437 {Column, CPU};
438 %% scheduler > 100 don't show process bar.
439 render_scheduler_usage(SchedulerUsage, SchedulerNum) ->
440 Column =
441 case SchedulerNum rem 10 =:= 0 of
442 true -> SchedulerNum div 10;
443 false -> (SchedulerNum div 10) + 1
444 end,
445 CPU = [
446 begin
447 Seq2 = transform_seq(Seq1, Column, SchedulerNum),
448 Seq3 = transform_seq(Seq2, Column, SchedulerNum),
449 Seq4 = transform_seq(Seq3, Column, SchedulerNum),
450 Seq5 = transform_seq(Seq4, Column, SchedulerNum),
451 Seq6 = transform_seq(Seq5, Column, SchedulerNum),
452 Seq7 = transform_seq(Seq6, Column, SchedulerNum),
453 Seq8 = transform_seq(Seq7, Column, SchedulerNum),
454 Seq9 = transform_seq(Seq8, Column, SchedulerNum),
455 Seq10 = transform_seq(Seq9, Column, SchedulerNum),
456 Percent1 = proplists:get_value(Seq1, SchedulerUsage),
457 Percent2 = proplists:get_value(Seq2, SchedulerUsage),
458 Percent3 = proplists:get_value(Seq3, SchedulerUsage),
459 Percent4 = proplists:get_value(Seq4, SchedulerUsage),
460 Percent5 = proplists:get_value(Seq5, SchedulerUsage),
461 Percent6 = proplists:get_value(Seq6, SchedulerUsage),
462 Percent7 = proplists:get_value(Seq7, SchedulerUsage),
463 Percent8 = proplists:get_value(Seq8, SchedulerUsage),
464 Percent9 = proplists:get_value(Seq9, SchedulerUsage),
465 Percent10 = proplists:get_value(Seq10, SchedulerUsage),
466 CPU1 = observer_cli_lib:to_percent(Percent1),
467 CPU2 = observer_cli_lib:to_percent(Percent2),
468 CPU3 = observer_cli_lib:to_percent(Percent3),
469 CPU4 = observer_cli_lib:to_percent(Percent4),
470 CPU5 = observer_cli_lib:to_percent(Percent5),
471 CPU6 = observer_cli_lib:to_percent(Percent6),
472 CPU7 = observer_cli_lib:to_percent(Percent7),
473 CPU8 = observer_cli_lib:to_percent(Percent8),
474 CPU9 = observer_cli_lib:to_percent(Percent9),
475 CPU10 = observer_cli_lib:to_percent(Percent10),
476 IsLastLine = Seq1 =:= Column,
477 Percents = [
478 Percent1,
479 Percent2,
480 Percent3,
481 Percent4,
482 Percent5,
483 Percent6,
484 Percent7,
485 Percent8,
486 Percent9,
487 Percent10
488 ],
489 Format = process_bar_format_style(Percents, IsLastLine),
490 io_lib:format(Format, [
491 Seq1,
492 CPU1,
493 Seq2,
494 CPU2,
495 Seq3,
496 CPU3,
497 Seq4,
498 CPU4,
499 Seq5,
500 CPU5,
501 Seq6,
502 CPU6,
503 Seq7,
504 CPU7,
505 Seq8,
506 CPU8,
507 Seq9,
508 CPU9,
509 Seq10,
510 CPU10
511 ])
512 end
513 || Seq1 <- lists:seq(1, Column)
514 ],
515 {Column, CPU}.
516
517 transform_seq(Seq, Column, Total) ->
518 Num = Seq + Column,
519 case Num > Total of
520 true -> 1000;
521 false -> Num
522 end.
523
524 render_top_n_view(memory, MemoryList, Num, Pages, Page) ->
525 Title = ?render([
526 ?W2(?GRAY_BG, "No | Pid", 16),
527 ?W2(?RED_BG, " Memory", 14),
528 ?W(?GRAY_BG, "Name or Initial Call", 38),
529 ?W(?GRAY_BG, " Reductions", 21),
530 ?W(?GRAY_BG, " MsgQueue", 10),
531 ?W(?GRAY_BG, "Current Function", 32)
532 ]),
533 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(MemoryList)),
534 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
535 {Pid, MemVal, CurFun, NameOrCall} = get_top_n_info(Item),
536 {Reductions, MsgQueueLen} = get_pid_info(Pid, [reductions, message_queue_len]),
537 Format = get_memory_format(ChoosePos, Pos),
538 R = io_lib:format(
539 Format,
540 [
541 Pos,
542 erlang:pid_to_list(Pid),
543 observer_cli_lib:to_byte(MemVal),
544 NameOrCall,
545 observer_cli_lib:to_list(Reductions),
546 observer_cli_lib:to_list(MsgQueueLen),
547 CurFun
548 ]
549 ),
550 {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1}
551 end,
552 {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(MemoryList, Start, Num)),
553 {PidList, [Title | lists:reverse(Rows)]};
554 render_top_n_view(binary_memory, MemoryList, Num, Pages, Page) ->
555 Title = ?render([
556 ?W2(?GRAY_BG, "No | Pid", 16),
557 ?W2(?RED_BG, " BinMemory", 14),
558 ?W(?GRAY_BG, "Name or Initial Call", 38),
559 ?W(?GRAY_BG, " Reductions", 21),
560 ?W(?GRAY_BG, " MsgQueue", 10),
561 ?W(?GRAY_BG, "Current Function", 32)
562 ]),
563 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(MemoryList)),
564 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
565 {Pid, MemVal, CurFun, NameOrCall} = get_top_n_info(Item),
566 {Reductions, MsgQueueLen} = get_pid_info(Pid, [reductions, message_queue_len]),
567 Format = get_memory_format(ChoosePos, Pos),
568 R = io_lib:format(
569 Format,
570 [
571 Pos,
572 pid_to_list(Pid),
573 observer_cli_lib:to_byte(MemVal),
574 NameOrCall,
575 observer_cli_lib:to_list(Reductions),
576 observer_cli_lib:to_list(MsgQueueLen),
577 CurFun
578 ]
579 ),
580 {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1}
581 end,
582 {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(MemoryList, Start, Num)),
583 {PidList, [Title | lists:reverse(Rows)]};
584 render_top_n_view(reductions, ReductionList, Num, Pages, Page) ->
585 Title = ?render([
586 ?W2(?GRAY_BG, "No | Pid", 16),
587 ?W2(?RED_BG, " Reductions", 21),
588 ?W(?GRAY_BG, "Name or Initial Call", 38),
589 ?W(?GRAY_BG, " Memory", 13),
590 ?W(?GRAY_BG, " MsgQueue", 10),
591 ?W(?GRAY_BG, "Current Function", 33)
592 ]),
593 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(ReductionList)),
594 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
595 {Pid, Reductions, CurFun, NameOrCall} = get_top_n_info(Item),
596 {Memory, MsgQueueLen} = get_pid_info(Pid, [memory, message_queue_len]),
597 Format = get_reduction_format(ChoosePos, Pos),
598 R = io_lib:format(
599 Format,
600 [
601 Pos,
602 pid_to_list(Pid),
603 observer_cli_lib:to_list(Reductions),
604 NameOrCall,
605 observer_cli_lib:to_byte(Memory),
606 observer_cli_lib:to_list(MsgQueueLen),
607 CurFun
608 ]
609 ),
610 {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1}
611 end,
612 {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(ReductionList, Start, Num)),
613 {PidList, [Title | lists:reverse(Rows)]};
614 render_top_n_view(total_heap_size, HeapList, Num, Pages, Page) ->
615 Title = ?render([
616 ?W2(?GRAY_BG, "No | Pid", 16),
617 ?W2(?RED_BG, " TotalHeapSize", 14),
618 ?W(?GRAY_BG, "Name or Initial Call", 38),
619 ?W(?GRAY_BG, " Reductions", 21),
620 ?W(?GRAY_BG, " MsgQueue", 10),
621 ?W(?GRAY_BG, "Current Function", 32)
622 ]),
623 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(HeapList)),
624 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
625 {Pid, HeapSize, CurFun, NameOrCall} = get_top_n_info(Item),
626 {Reductions, MsgQueueLen} = get_pid_info(Pid, [reductions, message_queue_len]),
627 Format = get_memory_format(ChoosePos, Pos),
628 R = io_lib:format(
629 Format,
630 [
631 Pos,
632 pid_to_list(Pid),
633 observer_cli_lib:to_byte(HeapSize),
634 NameOrCall,
635 observer_cli_lib:to_list(Reductions),
636 observer_cli_lib:to_list(MsgQueueLen),
637 CurFun
638 ]
639 ),
640 {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1}
641 end,
642 {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(HeapList, Start, Num)),
643 {PidList, [Title | lists:reverse(Rows)]};
644 render_top_n_view(message_queue_len, MQLenList, Num, Pages, Page) ->
645 Title = ?render([
646 ?W2(?GRAY_BG, "No | Pid", 16),
647 ?W2(?RED_BG, " MsgQueue", 11),
648 ?W(?GRAY_BG, "Name or Initial Call", 37),
649 ?W(?GRAY_BG, " Memory", 13),
650 ?W(?GRAY_BG, " Reductions", 21),
651 ?W(?GRAY_BG, "Current Function", 33)
652 ]),
653 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(MQLenList)),
654 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
655 {Pid, MQLen, CurFun, NameOrCall} = get_top_n_info(Item),
656 {Reductions, Memory} = get_pid_info(Pid, [reductions, memory]),
657 Format = get_message_queue_format(ChoosePos, Pos),
658 R = io_lib:format(
659 Format,
660 [
661 Pos,
662 pid_to_list(Pid),
663 observer_cli_lib:to_list(MQLen),
664 NameOrCall,
665 observer_cli_lib:to_byte(Memory),
666 observer_cli_lib:to_list(Reductions),
667 CurFun
668 ]
669 ),
670 {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1}
671 end,
672 {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(MQLenList, Start, Num)),
673 {PidList, [Title | lists:reverse(Rows)]}.
674
675 top_n_rows(FormatFunc, Start, List) ->
676 {Row, PidList, _} = lists:foldl(FormatFunc, {[], [], Start}, List),
677 {Row, PidList}.
678
679 notify_pause_status() ->
680 ?output("\e[31;1m PAUSE INPUT (p, r/rr, b/bb, h/hh, m/mm) to resume or q to quit \e[0m~n").
681
682 get_memory_format(Pos, Pos) ->
683 "|\e[42m~-3.3w|~-12.12s|~13.13s |~-38.38s|~21.21s| ~-9.9s|~-33.33s\e[49m|~n";
684 get_memory_format(_Pos, _RankPos) ->
685 "|~-3.3w|~-12.12s|~13.13s |~-38.38s|~21.21s| ~-9.9s|~-33.33s|~n".
686
687 get_reduction_format(Pos, Pos) ->
688 "|\e[42m~-3.3w|~-12.12s|~-21.21s|~-38.38s|~13.13s| ~-9.9s|~-34.34s\e[49m|~n";
689 get_reduction_format(_Pos, _RankPos) ->
690 "|~-3.3w|~-12.12s|~-21.21s|~-38.38s|~13.13s| ~-9.9s|~-34.34s|~n".
691
692 get_message_queue_format(Pos, Pos) ->
693 "|\e[42m~-3.3w|~-12.12s|~-11.11s|~-37.37s|~13.13s| ~-20.20s|~-34.34s\e[49m|~n";
694 get_message_queue_format(_Pos, _RankPos) ->
695 "|~-3.3w|~-12.12s|~-11.11s|~-37.37s|~13.13s| ~-20.20s|~-34.34s|~n".
696
697 refresh_next_time(proc_count, Type, Interval) ->
698 erlang:send_after(Interval, self(), {proc_count, Type});
699 refresh_next_time(proc_window, Type, _Interval) ->
700 erlang:send_after(10, self(), {proc_window, Type}).
701
702 get_current_initial_call(Call) ->
703 {_, CurFun} = lists:keyfind(current_function, 1, Call),
704 {_, InitialCall} = lists:keyfind(initial_call, 1, Call),
705 {observer_cli_lib:mfa_to_list(CurFun), InitialCall}.
706
707 get_port_proc_info(PortLimit, ProcLimit) ->
708 ProcCount = erlang:system_info(process_count),
709 PortCount = erlang:system_info(port_count),
710 PortCountStr = [integer_to_list(PortCount), "/", integer_to_list(PortLimit)],
711 ProcCountStr = [integer_to_list(ProcCount), "/", integer_to_list(ProcLimit)],
712 PortWarning =
713 case PortCount > PortLimit * ?COUNT_ALARM_THRESHOLD of
714 true -> ?RED;
715 false -> <<"">>
716 end,
717 ProcWarning =
718 case ProcCount > ProcLimit * ?COUNT_ALARM_THRESHOLD of
719 true -> ?RED;
720 false -> <<"">>
721 end,
722 {PortWarning, ProcWarning, PortCountStr, ProcCountStr}.
723
724 format_atom_info(AtomLimit, AtomCount) ->
725 Atom = [integer_to_list(AtomCount), "/", integer_to_list(AtomLimit)],
726 case AtomCount > AtomLimit * ?COUNT_ALARM_THRESHOLD of
727 true -> {?RED, Atom};
728 false -> {<<"">>, Atom}
729 end.
730
731 warning_color(Percent) when Percent >= ?CPU_ALARM_THRESHOLD -> ?RED;
732 warning_color(_Percent) -> ?GREEN.
733
734 process_bar_format_style(Percents, IsLastLine) ->
735 Format =
736 case
737 [
738 begin
739 warning_color(P)
740 end
741 || P <- Percents
742 ]
743 of
744 [W1, W2] ->
745 <<"|", W1/binary, "|~2..0w ~-57.57s~s", W2/binary,
746 " |~2..0w ~-57.57s ~s \e[0m|~n">>;
747 [W1, W2, W3, W4] ->
748 <<"|", W1/binary, "|~-2.2w ~-22.22s ~s", W2/binary, " |~-2.2w ~-22.22s ~s",
749 W3/binary, " |~-2.2w ~-22.22s ~s", W4/binary, " |~-2.2w ~-23.23s ~s \e[0m|~n">>;
750 [W1, W2, W3, W4, W5, W6, W7, W8, W9, W10] ->
751 <<"|", W1/binary, " | ~-3.3w ~s", W2/binary, " | ~-3.3w ~s", W3/binary,
752 " | ~-3.3w ~s", W4/binary, " | ~-3.3w ~s", W5/binary, " | ~-3.3w ~s", W6/binary,
753 " |=====| ~-3.3w ~s", W7/binary, " | ~-3.3w ~s", W8/binary, " | ~-3.3w ~s",
754 W9/binary, " | ~-3.3w ~s", W10/binary, " | ~-3.3w ~s \e[0m|~n">>
755 end,
756 case IsLastLine of
757 true -> <<?UNDERLINE/binary, Format/binary>>;
758 false -> Format
759 end.
760
761 get_top_n_info(Item) ->
762 {Pid, Val, Call = [IsName | _]} = Item,
763 {CurFun, InitialCall} = get_current_initial_call(Call),
764 NameOrCall = display_name_or_initial_call(IsName, InitialCall, Pid),
765 {Pid, Val, CurFun, NameOrCall}.
766
767 display_name_or_initial_call(IsName, _Call, _Pid) when is_atom(IsName) ->
768 atom_to_list(IsName);
769 display_name_or_initial_call(_IsName, {proc_lib, init_p, 5}, Pid) ->
770 %% translate gen_xxx behavior
771 observer_cli_lib:mfa_to_list(proc_lib:translate_initial_call(Pid));
772 display_name_or_initial_call(_IsName, Call, _Pid) ->
773 observer_cli_lib:mfa_to_list(Call).
774
775 get_refresh_prompt(proc_count, Type, Interval, Rows) ->
776 io_lib:format("recon:proc_count(~p, ~w) Interval:~wms", [Type, Rows, Interval]);
777 get_refresh_prompt(proc_window, Type, Interval, Rows) ->
778 io_lib:format("recon:proc_window(~p, ~w, ~w) Interval:~wms", [Type, Rows, Interval, Interval]).
779
780 get_stable_system_info() ->
781 OtpRelease = erlang:system_info(otp_release),
782 SysVersion = erlang:system_info(system_version) -- "\n",
783 {[
784 OtpRelease,
785 SysVersion,
786 erlang:system_info(process_limit),
787 erlang:system_info(port_limit),
788 erlang:system_info(ets_limit)
789 ],
790 erlang:system_info(port_parallelism)}.
791
792 get_atom_status() ->
793 try erlang:system_info(atom_limit) of
794 Limit ->
795 Count = erlang:system_info(atom_count),
796 {ok, Limit, Count}
797 catch
798 _:badarg -> {error, unsupported}
799 end.
800
801 get_pid_info(Pid, Keys) ->
802 case recon:info(Pid, Keys) of
803 undefined -> {"die", "die"};
804 [{_, Val1}, {_, Val2}] -> {Val1, Val2}
805 end.
806
807 get_top_n(proc_window, Type, Interval, Rows, IsFirstTime) when not IsFirstTime ->
808 recon:proc_window(Type, Rows, Interval);
809 get_top_n(_Func, Type, _Interval, Rows, _FirstTime) ->
810 recon:proc_count(Type, Rows).
811
812 connect_error(Prompt, Node) ->
813 Prop = <<?RED/binary, Prompt/binary, ?RESET/binary>>,
814 ?output(Prop, [Node]).
815
816 start_process_view(StorePid, RenderPid, Opts = #view_opts{home = Home}, LastSchWallFlag, AutoJump) ->
817 #home{cur_page = CurPage, pages = Pages, scheduler_usage = SchUsage} = Home,
818 {_, CurPos} = lists:keyfind(CurPage, 1, Pages),
819 case observer_cli_store:lookup_pos(StorePid, CurPos) of
820 {CurPos, ChoosePid} ->
821 clean([RenderPid, StorePid, LastSchWallFlag, SchUsage]),
822 observer_cli_process:start(ChoosePid, Opts);
823 {_, ChoosePid} when AutoJump ->
824 clean([RenderPid, StorePid, LastSchWallFlag, SchUsage]),
825 observer_cli_process:start(ChoosePid, Opts);
826 _ ->
827 manager(StorePid, RenderPid, Opts, LastSchWallFlag)
828 end.
829
830 set_scheduler_wall_time(_Flag, ?DISABLE) -> false;
831 set_scheduler_wall_time(Flag, ?ENABLE) -> erlang:system_flag(scheduler_wall_time, Flag).
832
833 check_auto_row() ->
834 case io:rows() of
835 {ok, _} -> true;
836 {error, _} -> false
837 end.
838
839 node_stats({LastIn, LastOut, LastGCs, LastWords, LastScheduleWall}, SchUsage) ->
840 New = {In, Out, GCs, Words, ScheduleWall} = get_incremental_stats(SchUsage),
841 BytesInDiff = In - LastIn,
842 BytesOutDiff = Out - LastOut,
843 GCCountDiff = GCs - LastGCs,
844 GCWordsDiff = Words - LastWords,
845 {
846 {
847 [observer_cli_lib:to_byte(In), "/", observer_cli_lib:to_byte(BytesInDiff)],
848 [observer_cli_lib:to_byte(Out), "/", observer_cli_lib:to_byte(BytesOutDiff)],
849 [integer_to_list(GCs), "/", integer_to_list(GCCountDiff)],
850 [integer_to_list(Words), "/", integer_to_list(GCWordsDiff)]
851 },
852 recon_lib:scheduler_usage_diff(LastScheduleWall, ScheduleWall),
853 New
854 }.
855
856 get_incremental_stats(SchUsage) ->
857 {{input, In}, {output, Out}} = erlang:statistics(io),
858 {GCs, Words, _} = erlang:statistics(garbage_collection),
859 ScheduleWall =
860 case SchUsage of
861 ?ENABLE -> erlang:statistics(scheduler_wall_time);
862 ?DISABLE -> undefined
863 end,
864 {In, Out, GCs, Words, ScheduleWall}.
0 %%% @author zhongwen <zhongwencool@gmail.com>
1
2 -module(observer_cli).
3
4 -include("observer_cli.hrl").
5
6 %% API
7 -export([start/0]).
8 -export([start/1]).
9 -export([start/2]).
10 -export([start_plugin/0]).
11 -export([clean/1]).
12
13 %% cpu >= this value will be highlight
14 -define(CPU_ALARM_THRESHOLD, 0.8).
15 %% port or process reach max_limit * 0.85 will be highlight
16 -define(COUNT_ALARM_THRESHOLD, 0.85).
17 -define(LAST_LINE,
18 "q(quit) p(pause) r/rr(reduction) m/mm(mem)"
19 "b/bb(binary mem) t/tt(total heap size) mq/mmq(msg queue) 9(proc 9 info) F/B(page forward/back)"
20 ).
21
22 -spec start() -> no_return | {badrpc, term()}.
23 start() -> start(#view_opts{}).
24
25 -spec start(Node) -> no_return | {badrpc, term()} when Node :: atom() | non_neg_integer().
26 start(Node) when Node =:= node() ->
27 start(#view_opts{});
28 start(Node) when is_atom(Node) ->
29 rpc_start(Node, ?DEFAULT_INTERVAL);
30 start(#view_opts{home = Home} = Opts) ->
31 erlang:process_flag(trap_exit, true),
32 AutoRow = check_auto_row(),
33 #home{scheduler_usage = SchUsage} = Home,
34 StorePid = observer_cli_store:start(),
35 LastSchWallFlag = set_scheduler_wall_time(true, SchUsage),
36 PsCmd = io_lib:format("ps -o pcpu,pmem ~s", [os:getpid()]),
37 RenderPid = spawn_link(fun() -> render_worker(PsCmd, StorePid, Home, AutoRow) end),
38 manager(StorePid, RenderPid, Opts#view_opts{auto_row = AutoRow}, LastSchWallFlag);
39 start(Interval) when is_integer(Interval), Interval >= ?MIN_INTERVAL ->
40 start(#view_opts{
41 home = #home{interval = Interval},
42 ets = #ets{interval = Interval},
43 sys = #system{interval = Interval},
44 db = #db{interval = Interval},
45 help = #help{interval = Interval},
46 inet = #inet{interval = Interval},
47 process = #process{interval = Interval},
48 port = Interval
49 }).
50
51 -spec start(Node, Cookies | Options) -> no_return | {badrpc, term()} when
52 Node :: atom(),
53 Cookies :: atom(),
54 Options :: proplists:proplist().
55 start(Node, _Cookie) when Node =:= node() ->
56 start(#view_opts{});
57 start(Node, Cookie) when is_atom(Node) andalso is_atom(Cookie) ->
58 start(Node, [{cookie, Cookie}]);
59 start(Node, Options) when is_atom(Node) andalso is_list(Options) ->
60 case proplists:get_value(cookie, Options) of
61 undefined -> ok;
62 Cookie -> erlang:set_cookie(Node, Cookie)
63 end,
64 Interval = proplists:get_value(interval, Options, ?DEFAULT_INTERVAL),
65 rpc_start(Node, Interval).
66
67 -spec start_plugin() -> no_return.
68 start_plugin() ->
69 erlang:process_flag(trap_exit, true),
70 application:ensure_all_started(observer_cli),
71 observer_cli_plugin:start(#view_opts{}).
72
73 -spec clean(list()) -> boolean().
74 clean([RenderPid, StorePid, SchWallFlag, SchUsage]) ->
75 observer_cli_lib:exit_processes([RenderPid, StorePid]),
76 set_scheduler_wall_time(SchWallFlag, SchUsage).
77
78 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
79 %%% Private
80 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
81
82 rpc_start(Node, Interval) ->
83 case net_kernel:hidden_connect_node(Node) of
84 true ->
85 rpc:call(Node, ?MODULE, start, [Interval]);
86 false ->
87 Msg = <<"Node(~p) refuse to be connected, make sure cookie is valid~n">>,
88 connect_error(Msg, Node),
89 {badrpc, nodedown};
90 ignored ->
91 Msg = <<"Ignored by node(~p), local node is not alive!~n">>,
92 connect_error(Msg, Node),
93 {badrpc, nodedown}
94 end.
95
96 manager(StorePid, RenderPid, Opts, LastSchWallFlag) ->
97 #view_opts{home = Home = #home{cur_page = CurPage, pages = Pages, scheduler_usage = SchUsage}} =
98 Opts,
99 Resource = [RenderPid, StorePid, LastSchWallFlag, SchUsage],
100 case observer_cli_lib:parse_cmd(Opts, ?MODULE, Resource) of
101 quit ->
102 erlang:unlink(RenderPid),
103 erlang:send(RenderPid, quit),
104 set_scheduler_wall_time(LastSchWallFlag, SchUsage),
105 observer_cli_lib:exit_processes([StorePid]),
106 quit;
107 pause_or_resume ->
108 erlang:send(RenderPid, pause_or_resume),
109 manager(StorePid, RenderPid, Opts, LastSchWallFlag);
110 {new_interval, NewInterval} ->
111 clean(Resource),
112 start(Opts#view_opts{home = Home#home{interval = NewInterval}});
113 scheduler_usage ->
114 NewSchUsage =
115 case SchUsage of
116 ?DISABLE -> ?ENABLE;
117 ?ENABLE -> ?DISABLE
118 end,
119 clean(Resource),
120 start(Opts#view_opts{home = Home#home{scheduler_usage = NewSchUsage}});
121 {jump, NewPos} ->
122 NewPages = observer_cli_lib:update_page_pos(CurPage, NewPos, Pages),
123 NewOpts = Opts#view_opts{home = Home#home{pages = NewPages}},
124 start_process_view(StorePid, RenderPid, NewOpts, LastSchWallFlag, false);
125 jump ->
126 start_process_view(StorePid, RenderPid, Opts, LastSchWallFlag, true);
127 {func, Func, Type} ->
128 clean(Resource),
129 start(Opts#view_opts{home = Home#home{func = Func, type = Type}});
130 page_down_top_n ->
131 NewPage = max(CurPage + 1, 1),
132 NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages),
133 clean(Resource),
134 start(Opts#view_opts{home = Home#home{cur_page = NewPage, pages = NewPages}});
135 page_up_top_n ->
136 NewPage = max(CurPage - 1, 1),
137 NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages),
138 clean(Resource),
139 start(Opts#view_opts{home = Home#home{cur_page = NewPage, pages = NewPages}});
140 _ ->
141 manager(StorePid, RenderPid, Opts, LastSchWallFlag)
142 end.
143
144 render_worker(PsCmd, Manager, Home = #home{scheduler_usage = SchUsage}, AutoRow) ->
145 ?output(?CLEAR),
146 StableInfo = get_stable_system_info(),
147 LastStats = get_incremental_stats(SchUsage),
148 redraw_running(PsCmd, Manager, Home, StableInfo, LastStats, erlang:make_ref(), AutoRow, true).
149
150 %% pause status waiting to be resume
151 redraw_pause(PsCmd, StorePid, Home, StableInfo, LastStats, LastTimeRef, AutoRow) ->
152 notify_pause_status(),
153 erlang:cancel_timer(LastTimeRef),
154 #home{func = Func, type = Type} = Home,
155 receive
156 quit ->
157 quit;
158 {Func, Type} ->
159 redraw_running(
160 PsCmd,
161 StorePid,
162 Home,
163 StableInfo,
164 LastStats,
165 LastTimeRef,
166 AutoRow,
167 false
168 );
169 pause_or_resume ->
170 ?output(?CLEAR),
171 redraw_running(PsCmd, StorePid, Home, StableInfo, LastStats, LastTimeRef, AutoRow, true)
172 end.
173
174 %% running status
175 redraw_running(PsCmd, StorePid, Home, StableInfo, LastStats, LastTimeRef, AutoRow, IsFirstTime) ->
176 #home{
177 interval = Interval,
178 func = Func,
179 type = Type,
180 pages = RankPos,
181 cur_page = CurPage,
182 scheduler_usage = SchUsage
183 } = Home,
184 erlang:cancel_timer(LastTimeRef),
185 TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow),
186 {Diffs, Schedulers, NewStats} = node_stats(LastStats, SchUsage),
187 {CPURow, CPULine} = render_scheduler_usage(Schedulers),
188 ProcessRows = max(TerminalRow - 14 - CPURow, 0),
189 TopLen = ProcessRows * CurPage,
190 TopList = get_top_n(Func, Type, Interval, TopLen, IsFirstTime),
191 Text = get_refresh_prompt(Func, Type, Interval, TopLen),
192 MenuLine = observer_cli_lib:render_menu(home, Text),
193 SystemLine = render_system_line(PsCmd, element(1, StableInfo)),
194 MemLine = render_memory_process_line(Diffs, element(2, StableInfo), Interval),
195 {TopNList, RankLine} = render_top_n_view(Type, TopList, ProcessRows, RankPos, CurPage),
196 LastLine = observer_cli_lib:render_last_line(?LAST_LINE),
197 ?output([?CURSOR_TOP, MenuLine, SystemLine, MemLine, CPULine, RankLine, LastLine]),
198
199 observer_cli_store:update(StorePid, ProcessRows, TopNList),
200 TimeRef = refresh_next_time(Func, Type, Interval),
201 receive
202 quit ->
203 quit;
204 pause_or_resume ->
205 redraw_pause(PsCmd, StorePid, Home, StableInfo, NewStats, TimeRef, AutoRow);
206 {Func, Type} ->
207 redraw_running(PsCmd, StorePid, Home, StableInfo, NewStats, TimeRef, AutoRow, false)
208 end.
209
210 render_system_line(PsCmd, StableInfo) ->
211 [Version, SysVersion, ProcLimit, PortLimit, EtsLimit] = StableInfo,
212 ActiveTask = erlang:statistics(total_active_tasks),
213 {ContextSwitch, _} = erlang:statistics(context_switches),
214 AtomStatus = get_atom_status(),
215 Reductions = erlang:statistics(reductions),
216 {PortWarning, ProcWarning, PortCount, ProcCount} =
217 get_port_proc_info(PortLimit, ProcLimit),
218 CmdValue =
219 case string:split(os:cmd(PsCmd), "\n", all) of
220 [_, CmdValueTmp | _] -> CmdValueTmp;
221 _ -> ""
222 end,
223
224 [CpuPsV, MemPsV] =
225 case lists:filter(fun(Y) -> Y =/= [] end, string:split(CmdValue, " ", all)) of
226 [V1, V2] -> [V1, V2];
227 _ -> ["--", "--"]
228 end,
229 Title = ?render([
230 ?W(SysVersion, 136),
231 ?NEW_LINE,
232 ?GRAY_BG,
233 ?W("System", 10),
234 ?W("Count/Limit", 21),
235 ?W("System", 25),
236 ?W("Status", 21),
237 ?W("Stat Info", 20),
238 ?W("Size", 24)
239 ]),
240 Row1 = ?render([
241 ?W("Proc Count", 10),
242 ?W2(ProcWarning, ProcCount, 22),
243 ?W(" Version", 26),
244 ?W(Version, 21),
245 ?W("Active Task", 20),
246 ?W(ActiveTask, 24),
247 ?NEW_LINE,
248 ?W("Port Count", 10),
249 ?W2(PortWarning, PortCount, 22),
250 ?W(" ps -o pcpu", 26),
251 ?W([CpuPsV, "%"], 21),
252 ?W("Context Switch", 20),
253 ?W(ContextSwitch, 24)
254 ]),
255 {Reds, AddReds} = Reductions,
256 Row2 =
257 case AtomStatus of
258 {ok, AtomLimit, AtomCount} ->
259 {AtomWarning, Atom} = format_atom_info(AtomLimit, AtomCount),
260 ?render([
261 ?UNDERLINE,
262 ?W("Atom Count", 10),
263 ?W2(AtomWarning, Atom, 22),
264 ?W(" ps -o pmem", 26),
265 ?W([MemPsV, "%"], 21),
266 ?W("Reds(Total/SinceLastCall)", 20),
267 ?W([integer_to_list(Reds), "/", integer_to_list(AddReds)], 24)
268 ]);
269 {error, unsupported} ->
270 ?render([
271 ?UNDERLINE,
272 ?W("Ets Limit", 10),
273 ?W(EtsLimit, 21),
274 ?W(" ps -o pmem", 25),
275 ?W([MemPsV, "%"], 21),
276 ?W("Reductions", 20),
277 ?W(Reductions, 24)
278 ])
279 end,
280 [Title, Row1, Row2].
281
282 render_memory_process_line(MemSum, PortParallelism, Interval) ->
283 RunQ = erlang:statistics(run_queue),
284 Mem = erlang:memory(),
285 TotalMem = proplists:get_value(total, Mem),
286 ProcMem = proplists:get_value(processes_used, Mem),
287 CodeMem = proplists:get_value(code, Mem),
288 AtomMem = proplists:get_value(atom_used, Mem),
289 BinMem = proplists:get_value(binary, Mem),
290 EtsMem = proplists:get_value(ets, Mem),
291 {
292 BytesIn,
293 BytesOut,
294 GcCount,
295 GcWordsReclaimed
296 } = MemSum,
297
298 {Queue, LogKey} =
299 case whereis(error_logger) of
300 undefined ->
301 {erlang:integer_to_list(RunQ), "RunQueue"};
302 Pid ->
303 {_, Q} = process_info(Pid, message_queue_len),
304 {[erlang:integer_to_list(RunQ), "/", erlang:integer_to_list(Q)],
305 "RunQueue/ErrorLoggerQueue"}
306 end,
307 ProcMemPercent = observer_cli_lib:to_percent(ProcMem / TotalMem),
308 AtomMemPercent = observer_cli_lib:to_percent(AtomMem / TotalMem),
309 BinMemPercent = observer_cli_lib:to_percent(BinMem / TotalMem),
310 CodeMemPercent = observer_cli_lib:to_percent(CodeMem / TotalMem),
311 EtsMemPercent = observer_cli_lib:to_percent(EtsMem / TotalMem),
312
313 Title = ?render([
314 ?GRAY_BG,
315 ?W("Mem Type", 10),
316 ?W("Size", 21),
317 ?W("Mem Type", 25),
318 ?W("Size", 21),
319 ?W(["IO/GC:(", integer_to_binary(Interval), "ms)"], 20),
320 ?W("Total/Increments", 24)
321 ]),
322 Row = ?render([
323 ?W("Total", 10),
324 ?W({byte, TotalMem}, 12),
325 ?W("100.0%", 6),
326 ?W("Binary", 25),
327 ?W({byte, BinMem}, 12),
328 ?W(BinMemPercent, 6),
329 ?W("IO Output", 20),
330 ?W(BytesOut, 24),
331 ?NEW_LINE,
332 ?W("Process", 10),
333 ?W({byte, ProcMem}, 12),
334 ?W(ProcMemPercent, 6),
335 ?W("Code", 25),
336 ?W({byte, CodeMem}, 12),
337 ?W(CodeMemPercent, 6),
338 ?W("IO Input", 20),
339 ?W(BytesIn, 24),
340 ?NEW_LINE,
341 ?W("Atom", 10),
342 ?W({byte, AtomMem}, 12),
343 ?W(AtomMemPercent, 6),
344 ?W("Port Parallelism (+spp)", 25),
345 ?W(PortParallelism, 21),
346 ?W("Gc Count", 20),
347 ?W(GcCount, 24),
348 ?NEW_LINE,
349 ?W("Ets", 10),
350 ?W({byte, EtsMem}, 12),
351 ?W(EtsMemPercent, 6),
352 ?W(LogKey, 25),
353 ?W(Queue, 21),
354 ?W("Gc Words Reclaimed", 20),
355 ?W(GcWordsReclaimed, 24)
356 ]),
357 [Title, Row].
358
359 render_scheduler_usage(undefined) ->
360 {0, []};
361 render_scheduler_usage(SchedulerUsage) ->
362 SchedulerNum = erlang:length(SchedulerUsage),
363 render_scheduler_usage(SchedulerUsage, SchedulerNum).
364
365 %% < 8 core split 2 part
366 render_scheduler_usage(SchedulerUsage, SchedulerNum) when SchedulerNum < 8 ->
367 Column =
368 case SchedulerNum rem 2 =:= 0 of
369 true -> SchedulerNum div 2;
370 false -> (SchedulerNum div 2) + 1
371 end,
372 CPU = [
373 begin
374 Seq2 = transform_seq(Seq1, Column, SchedulerNum),
375 Percent1 = proplists:get_value(Seq1, SchedulerUsage, 0.0),
376 Percent2 = proplists:get_value(Seq2, SchedulerUsage, 0.0),
377 CPU1 = observer_cli_lib:to_percent(Percent1),
378 CPU2 = observer_cli_lib:to_percent(Percent2),
379 Process1 = lists:duplicate(trunc(Percent1 * 57), "|"),
380 Process2 = lists:duplicate(trunc(Percent2 * 57), "|"),
381 IsLastLine = Seq1 =:= Column,
382 Format = process_bar_format_style([Percent1, Percent2], IsLastLine),
383 io_lib:format(Format, [
384 Seq1,
385 Process1,
386 CPU1,
387 Seq2,
388 Process2,
389 CPU2
390 ])
391 end
392 || Seq1 <- lists:seq(1, Column)
393 ],
394 {Column, CPU};
395 %% 100 >= scheduler >= 8 split 4 part
396 render_scheduler_usage(SchedulerUsage, SchedulerNum) when SchedulerNum =< 100 ->
397 Column =
398 case SchedulerNum rem 4 =:= 0 of
399 true -> SchedulerNum div 4;
400 false -> (SchedulerNum div 4) + 1
401 end,
402 CPU = [
403 begin
404 Seq2 = transform_seq(Seq1, Column, SchedulerNum),
405 Seq3 = transform_seq(Seq2, Column, SchedulerNum),
406 Seq4 = transform_seq(Seq3, Column, SchedulerNum),
407 Percent1 = proplists:get_value(Seq1, SchedulerUsage, 0.0),
408 Percent2 = proplists:get_value(Seq2, SchedulerUsage, 0.0),
409 Percent3 = proplists:get_value(Seq3, SchedulerUsage, 0.0),
410 Percent4 = proplists:get_value(Seq4, SchedulerUsage, 0.0),
411 CPU1 = observer_cli_lib:to_percent(Percent1),
412 CPU2 = observer_cli_lib:to_percent(Percent2),
413 CPU3 = observer_cli_lib:to_percent(Percent3),
414 CPU4 = observer_cli_lib:to_percent(Percent4),
415 Process1 = lists:duplicate(trunc(Percent1 * 22), "|"),
416 Process2 = lists:duplicate(trunc(Percent2 * 22), "|"),
417 Process3 = lists:duplicate(trunc(Percent3 * 22), "|"),
418 Process4 = lists:duplicate(trunc(Percent4 * 23), "|"),
419 IsLastLine = Seq1 =:= Column,
420 Format = process_bar_format_style([Percent1, Percent2, Percent3, Percent4], IsLastLine),
421 io_lib:format(Format, [
422 Seq1,
423 Process1,
424 CPU1,
425 Seq2,
426 Process2,
427 CPU2,
428 Seq3,
429 Process3,
430 CPU3,
431 Seq4,
432 Process4,
433 CPU4
434 ])
435 end
436 || Seq1 <- lists:seq(1, Column)
437 ],
438 {Column, CPU};
439 %% scheduler > 100 don't show process bar.
440 render_scheduler_usage(SchedulerUsage, SchedulerNum) ->
441 Column =
442 case SchedulerNum rem 10 =:= 0 of
443 true -> SchedulerNum div 10;
444 false -> (SchedulerNum div 10) + 1
445 end,
446 CPU = [
447 begin
448 Seq2 = transform_seq(Seq1, Column, SchedulerNum),
449 Seq3 = transform_seq(Seq2, Column, SchedulerNum),
450 Seq4 = transform_seq(Seq3, Column, SchedulerNum),
451 Seq5 = transform_seq(Seq4, Column, SchedulerNum),
452 Seq6 = transform_seq(Seq5, Column, SchedulerNum),
453 Seq7 = transform_seq(Seq6, Column, SchedulerNum),
454 Seq8 = transform_seq(Seq7, Column, SchedulerNum),
455 Seq9 = transform_seq(Seq8, Column, SchedulerNum),
456 Seq10 = transform_seq(Seq9, Column, SchedulerNum),
457 Percent1 = proplists:get_value(Seq1, SchedulerUsage),
458 Percent2 = proplists:get_value(Seq2, SchedulerUsage),
459 Percent3 = proplists:get_value(Seq3, SchedulerUsage),
460 Percent4 = proplists:get_value(Seq4, SchedulerUsage),
461 Percent5 = proplists:get_value(Seq5, SchedulerUsage),
462 Percent6 = proplists:get_value(Seq6, SchedulerUsage),
463 Percent7 = proplists:get_value(Seq7, SchedulerUsage),
464 Percent8 = proplists:get_value(Seq8, SchedulerUsage),
465 Percent9 = proplists:get_value(Seq9, SchedulerUsage),
466 Percent10 = proplists:get_value(Seq10, SchedulerUsage),
467 CPU1 = observer_cli_lib:to_percent(Percent1),
468 CPU2 = observer_cli_lib:to_percent(Percent2),
469 CPU3 = observer_cli_lib:to_percent(Percent3),
470 CPU4 = observer_cli_lib:to_percent(Percent4),
471 CPU5 = observer_cli_lib:to_percent(Percent5),
472 CPU6 = observer_cli_lib:to_percent(Percent6),
473 CPU7 = observer_cli_lib:to_percent(Percent7),
474 CPU8 = observer_cli_lib:to_percent(Percent8),
475 CPU9 = observer_cli_lib:to_percent(Percent9),
476 CPU10 = observer_cli_lib:to_percent(Percent10),
477 IsLastLine = Seq1 =:= Column,
478 Percents = [
479 Percent1,
480 Percent2,
481 Percent3,
482 Percent4,
483 Percent5,
484 Percent6,
485 Percent7,
486 Percent8,
487 Percent9,
488 Percent10
489 ],
490 Format = process_bar_format_style(Percents, IsLastLine),
491 io_lib:format(Format, [
492 Seq1,
493 CPU1,
494 Seq2,
495 CPU2,
496 Seq3,
497 CPU3,
498 Seq4,
499 CPU4,
500 Seq5,
501 CPU5,
502 Seq6,
503 CPU6,
504 Seq7,
505 CPU7,
506 Seq8,
507 CPU8,
508 Seq9,
509 CPU9,
510 Seq10,
511 CPU10
512 ])
513 end
514 || Seq1 <- lists:seq(1, Column)
515 ],
516 {Column, CPU}.
517
518 transform_seq(Seq, Column, Total) ->
519 Num = Seq + Column,
520 case Num > Total of
521 true -> 1000;
522 false -> Num
523 end.
524
525 render_top_n_view(memory, MemoryList, Num, Pages, Page) ->
526 Title = ?render([
527 ?W2(?GRAY_BG, "No | Pid", 16),
528 ?W2(?RED_BG, " Memory", 14),
529 ?W(?GRAY_BG, "Name or Initial Call", 38),
530 ?W(?GRAY_BG, " Reductions", 21),
531 ?W(?GRAY_BG, " MsgQueue", 10),
532 ?W(?GRAY_BG, "Current Function", 32)
533 ]),
534 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(MemoryList)),
535 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
536 {Pid, MemVal, CurFun, NameOrCall} = get_top_n_info(Item),
537 {Reductions, MsgQueueLen} = get_pid_info(Pid, [reductions, message_queue_len]),
538 Format = get_memory_format(ChoosePos, Pos),
539 R = io_lib:format(
540 Format,
541 [
542 Pos,
543 erlang:pid_to_list(Pid),
544 observer_cli_lib:to_byte(MemVal),
545 NameOrCall,
546 observer_cli_lib:to_list(Reductions),
547 observer_cli_lib:to_list(MsgQueueLen),
548 CurFun
549 ]
550 ),
551 {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1}
552 end,
553 {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(MemoryList, Start, Num)),
554 {PidList, [Title | lists:reverse(Rows)]};
555 render_top_n_view(binary_memory, MemoryList, Num, Pages, Page) ->
556 Title = ?render([
557 ?W2(?GRAY_BG, "No | Pid", 16),
558 ?W2(?RED_BG, " BinMemory", 14),
559 ?W(?GRAY_BG, "Name or Initial Call", 38),
560 ?W(?GRAY_BG, " Reductions", 21),
561 ?W(?GRAY_BG, " MsgQueue", 10),
562 ?W(?GRAY_BG, "Current Function", 32)
563 ]),
564 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(MemoryList)),
565 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
566 {Pid, MemVal, CurFun, NameOrCall} = get_top_n_info(Item),
567 {Reductions, MsgQueueLen} = get_pid_info(Pid, [reductions, message_queue_len]),
568 Format = get_memory_format(ChoosePos, Pos),
569 R = io_lib:format(
570 Format,
571 [
572 Pos,
573 pid_to_list(Pid),
574 observer_cli_lib:to_byte(MemVal),
575 NameOrCall,
576 observer_cli_lib:to_list(Reductions),
577 observer_cli_lib:to_list(MsgQueueLen),
578 CurFun
579 ]
580 ),
581 {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1}
582 end,
583 {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(MemoryList, Start, Num)),
584 {PidList, [Title | lists:reverse(Rows)]};
585 render_top_n_view(reductions, ReductionList, Num, Pages, Page) ->
586 Title = ?render([
587 ?W2(?GRAY_BG, "No | Pid", 16),
588 ?W2(?RED_BG, " Reductions", 21),
589 ?W(?GRAY_BG, "Name or Initial Call", 38),
590 ?W(?GRAY_BG, " Memory", 13),
591 ?W(?GRAY_BG, " MsgQueue", 10),
592 ?W(?GRAY_BG, "Current Function", 33)
593 ]),
594 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(ReductionList)),
595 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
596 {Pid, Reductions, CurFun, NameOrCall} = get_top_n_info(Item),
597 {Memory, MsgQueueLen} = get_pid_info(Pid, [memory, message_queue_len]),
598 Format = get_reduction_format(ChoosePos, Pos),
599 R = io_lib:format(
600 Format,
601 [
602 Pos,
603 pid_to_list(Pid),
604 observer_cli_lib:to_list(Reductions),
605 NameOrCall,
606 observer_cli_lib:to_byte(Memory),
607 observer_cli_lib:to_list(MsgQueueLen),
608 CurFun
609 ]
610 ),
611 {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1}
612 end,
613 {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(ReductionList, Start, Num)),
614 {PidList, [Title | lists:reverse(Rows)]};
615 render_top_n_view(total_heap_size, HeapList, Num, Pages, Page) ->
616 Title = ?render([
617 ?W2(?GRAY_BG, "No | Pid", 16),
618 ?W2(?RED_BG, " TotalHeapSize", 14),
619 ?W(?GRAY_BG, "Name or Initial Call", 38),
620 ?W(?GRAY_BG, " Reductions", 21),
621 ?W(?GRAY_BG, " MsgQueue", 10),
622 ?W(?GRAY_BG, "Current Function", 32)
623 ]),
624 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(HeapList)),
625 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
626 {Pid, HeapSize, CurFun, NameOrCall} = get_top_n_info(Item),
627 {Reductions, MsgQueueLen} = get_pid_info(Pid, [reductions, message_queue_len]),
628 Format = get_memory_format(ChoosePos, Pos),
629 R = io_lib:format(
630 Format,
631 [
632 Pos,
633 pid_to_list(Pid),
634 observer_cli_lib:to_byte(HeapSize),
635 NameOrCall,
636 observer_cli_lib:to_list(Reductions),
637 observer_cli_lib:to_list(MsgQueueLen),
638 CurFun
639 ]
640 ),
641 {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1}
642 end,
643 {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(HeapList, Start, Num)),
644 {PidList, [Title | lists:reverse(Rows)]};
645 render_top_n_view(message_queue_len, MQLenList, Num, Pages, Page) ->
646 Title = ?render([
647 ?W2(?GRAY_BG, "No | Pid", 16),
648 ?W2(?RED_BG, " MsgQueue", 11),
649 ?W(?GRAY_BG, "Name or Initial Call", 37),
650 ?W(?GRAY_BG, " Memory", 13),
651 ?W(?GRAY_BG, " Reductions", 21),
652 ?W(?GRAY_BG, "Current Function", 33)
653 ]),
654 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(MQLenList)),
655 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
656 {Pid, MQLen, CurFun, NameOrCall} = get_top_n_info(Item),
657 {Reductions, Memory} = get_pid_info(Pid, [reductions, memory]),
658 Format = get_message_queue_format(ChoosePos, Pos),
659 R = io_lib:format(
660 Format,
661 [
662 Pos,
663 pid_to_list(Pid),
664 observer_cli_lib:to_list(MQLen),
665 NameOrCall,
666 observer_cli_lib:to_byte(Memory),
667 observer_cli_lib:to_list(Reductions),
668 CurFun
669 ]
670 ),
671 {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1}
672 end,
673 {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(MQLenList, Start, Num)),
674 {PidList, [Title | lists:reverse(Rows)]}.
675
676 top_n_rows(FormatFunc, Start, List) ->
677 {Row, PidList, _} = lists:foldl(FormatFunc, {[], [], Start}, List),
678 {Row, PidList}.
679
680 notify_pause_status() ->
681 ?output("\e[31;1m PAUSE INPUT (p, r/rr, b/bb, h/hh, m/mm) to resume or q to quit \e[0m~n").
682
683 get_memory_format(Pos, Pos) ->
684 "|\e[42m~-3.3w|~-12.12s|~13.13s |~-38.38s|~21.21s| ~-9.9s|~-33.33s\e[49m|~n";
685 get_memory_format(_Pos, _RankPos) ->
686 "|~-3.3w|~-12.12s|~13.13s |~-38.38s|~21.21s| ~-9.9s|~-33.33s|~n".
687
688 get_reduction_format(Pos, Pos) ->
689 "|\e[42m~-3.3w|~-12.12s|~-21.21s|~-38.38s|~13.13s| ~-9.9s|~-34.34s\e[49m|~n";
690 get_reduction_format(_Pos, _RankPos) ->
691 "|~-3.3w|~-12.12s|~-21.21s|~-38.38s|~13.13s| ~-9.9s|~-34.34s|~n".
692
693 get_message_queue_format(Pos, Pos) ->
694 "|\e[42m~-3.3w|~-12.12s|~-11.11s|~-37.37s|~13.13s| ~-20.20s|~-34.34s\e[49m|~n";
695 get_message_queue_format(_Pos, _RankPos) ->
696 "|~-3.3w|~-12.12s|~-11.11s|~-37.37s|~13.13s| ~-20.20s|~-34.34s|~n".
697
698 refresh_next_time(proc_count, Type, Interval) ->
699 erlang:send_after(Interval, self(), {proc_count, Type});
700 refresh_next_time(proc_window, Type, _Interval) ->
701 erlang:send_after(10, self(), {proc_window, Type}).
702
703 get_current_initial_call(Call) ->
704 {_, CurFun} = lists:keyfind(current_function, 1, Call),
705 {_, InitialCall} = lists:keyfind(initial_call, 1, Call),
706 {observer_cli_lib:mfa_to_list(CurFun), InitialCall}.
707
708 get_port_proc_info(PortLimit, ProcLimit) ->
709 ProcCount = erlang:system_info(process_count),
710 PortCount = erlang:system_info(port_count),
711 PortCountStr = [integer_to_list(PortCount), "/", integer_to_list(PortLimit)],
712 ProcCountStr = [integer_to_list(ProcCount), "/", integer_to_list(ProcLimit)],
713 PortWarning =
714 case PortCount > PortLimit * ?COUNT_ALARM_THRESHOLD of
715 true -> ?RED;
716 false -> <<"">>
717 end,
718 ProcWarning =
719 case ProcCount > ProcLimit * ?COUNT_ALARM_THRESHOLD of
720 true -> ?RED;
721 false -> <<"">>
722 end,
723 {PortWarning, ProcWarning, PortCountStr, ProcCountStr}.
724
725 format_atom_info(AtomLimit, AtomCount) ->
726 Atom = [integer_to_list(AtomCount), "/", integer_to_list(AtomLimit)],
727 case AtomCount > AtomLimit * ?COUNT_ALARM_THRESHOLD of
728 true -> {?RED, Atom};
729 false -> {<<"">>, Atom}
730 end.
731
732 warning_color(Percent) when Percent >= ?CPU_ALARM_THRESHOLD -> ?RED;
733 warning_color(_Percent) -> ?GREEN.
734
735 process_bar_format_style(Percents, IsLastLine) ->
736 Format =
737 case [warning_color(P) || P <- Percents] of
738 [W1, W2] ->
739 <<"|", W1/binary, "|~2..0w ~-57.57s~s", W2/binary,
740 " |~2..0w ~-57.57s ~s \e[0m|~n">>;
741 [W1, W2, W3, W4] ->
742 <<"|", W1/binary, "|~-2.2w ~-22.22s ~s", W2/binary, " |~-2.2w ~-22.22s ~s",
743 W3/binary, " |~-2.2w ~-22.22s ~s", W4/binary, " |~-2.2w ~-23.23s ~s \e[0m|~n">>;
744 [W1, W2, W3, W4, W5, W6, W7, W8, W9, W10] ->
745 <<"|", W1/binary, " | ~-3.3w ~s", W2/binary, " | ~-3.3w ~s", W3/binary,
746 " | ~-3.3w ~s", W4/binary, " | ~-3.3w ~s", W5/binary, " | ~-3.3w ~s", W6/binary,
747 " |=====| ~-3.3w ~s", W7/binary, " | ~-3.3w ~s", W8/binary, " | ~-3.3w ~s",
748 W9/binary, " | ~-3.3w ~s", W10/binary, " | ~-3.3w ~s \e[0m|~n">>
749 end,
750 case IsLastLine of
751 true -> <<?UNDERLINE/binary, Format/binary>>;
752 false -> Format
753 end.
754
755 get_top_n_info(Item) ->
756 {Pid, Val, Call = [IsName | _]} = Item,
757 {CurFun, InitialCall} = get_current_initial_call(Call),
758 NameOrCall = display_name_or_initial_call(IsName, InitialCall, Pid),
759 {Pid, Val, CurFun, NameOrCall}.
760
761 display_name_or_initial_call(IsName, _Call, _Pid) when is_atom(IsName) ->
762 atom_to_list(IsName);
763 display_name_or_initial_call(_IsName, {proc_lib, init_p, 5}, Pid) ->
764 %% translate gen_xxx behavior
765 observer_cli_lib:mfa_to_list(proc_lib:translate_initial_call(Pid));
766 display_name_or_initial_call(_IsName, Call, _Pid) ->
767 observer_cli_lib:mfa_to_list(Call).
768
769 get_refresh_prompt(proc_count, Type, Interval, Rows) ->
770 io_lib:format("recon:proc_count(~p, ~w) Interval:~wms", [Type, Rows, Interval]);
771 get_refresh_prompt(proc_window, Type, Interval, Rows) ->
772 io_lib:format("recon:proc_window(~p, ~w, ~w) Interval:~wms", [Type, Rows, Interval, Interval]).
773
774 get_stable_system_info() ->
775 OtpRelease = erlang:system_info(otp_release),
776 SysVersion = erlang:system_info(system_version) -- "\n",
777 {[
778 OtpRelease,
779 SysVersion,
780 erlang:system_info(process_limit),
781 erlang:system_info(port_limit),
782 erlang:system_info(ets_limit)
783 ],
784 erlang:system_info(port_parallelism)}.
785
786 get_atom_status() ->
787 try erlang:system_info(atom_limit) of
788 Limit ->
789 Count = erlang:system_info(atom_count),
790 {ok, Limit, Count}
791 catch
792 _:badarg -> {error, unsupported}
793 end.
794
795 get_pid_info(Pid, Keys) ->
796 case recon:info(Pid, Keys) of
797 undefined -> {"die", "die"};
798 [{_, Val1}, {_, Val2}] -> {Val1, Val2}
799 end.
800
801 get_top_n(proc_window, Type, Interval, Rows, IsFirstTime) when not IsFirstTime ->
802 recon:proc_window(Type, Rows, Interval);
803 get_top_n(_Func, Type, _Interval, Rows, _FirstTime) ->
804 recon:proc_count(Type, Rows).
805
806 connect_error(Prompt, Node) ->
807 Prop = <<?RED/binary, Prompt/binary, ?RESET/binary>>,
808 ?output(Prop, [Node]).
809
810 start_process_view(StorePid, RenderPid, Opts = #view_opts{home = Home}, LastSchWallFlag, AutoJump) ->
811 #home{cur_page = CurPage, pages = Pages, scheduler_usage = SchUsage} = Home,
812 {_, CurPos} = lists:keyfind(CurPage, 1, Pages),
813 case observer_cli_store:lookup_pos(StorePid, CurPos) of
814 {CurPos, ChoosePid} ->
815 clean([RenderPid, StorePid, LastSchWallFlag, SchUsage]),
816 observer_cli_process:start(home, ChoosePid, Opts);
817 {_, ChoosePid} when AutoJump ->
818 clean([RenderPid, StorePid, LastSchWallFlag, SchUsage]),
819 observer_cli_process:start(home, ChoosePid, Opts);
820 _ ->
821 manager(StorePid, RenderPid, Opts, LastSchWallFlag)
822 end.
823
824 set_scheduler_wall_time(_Flag, ?DISABLE) -> false;
825 set_scheduler_wall_time(Flag, ?ENABLE) -> erlang:system_flag(scheduler_wall_time, Flag).
826
827 check_auto_row() ->
828 case io:rows() of
829 {ok, _} -> true;
830 {error, _} -> false
831 end.
832
833 node_stats({LastIn, LastOut, LastGCs, LastWords, LastScheduleWall}, SchUsage) ->
834 New = {In, Out, GCs, Words, ScheduleWall} = get_incremental_stats(SchUsage),
835 BytesInDiff = In - LastIn,
836 BytesOutDiff = Out - LastOut,
837 GCCountDiff = GCs - LastGCs,
838 GCWordsDiff = Words - LastWords,
839 {
840 {
841 [observer_cli_lib:to_byte(In), "/", observer_cli_lib:to_byte(BytesInDiff)],
842 [observer_cli_lib:to_byte(Out), "/", observer_cli_lib:to_byte(BytesOutDiff)],
843 [integer_to_list(GCs), "/", integer_to_list(GCCountDiff)],
844 [integer_to_list(Words), "/", integer_to_list(GCWordsDiff)]
845 },
846 recon_lib:scheduler_usage_diff(LastScheduleWall, ScheduleWall),
847 New
848 }.
849
850 get_incremental_stats(SchUsage) ->
851 {{input, In}, {output, Out}} = erlang:statistics(io),
852 {GCs, Words, _} = erlang:statistics(garbage_collection),
853 ScheduleWall =
854 case SchUsage of
855 ?ENABLE -> erlang:statistics(scheduler_wall_time);
856 ?DISABLE -> undefined
857 end,
858 {In, Out, GCs, Words, ScheduleWall}.
66 -export([start/1]).
77 -export([clean/1]).
88
9 -define(INTERVAL, 2000).
9 %% API
10 -define(LAST_LINE,
11 "refresh: ~wms q(quit) Positive Number(set refresh interval time ms) F/B(forward/back) Current pages is ~w"
12 ).
1013
1114 %% @doc List application info
1215
1316 -spec start(ViewOpts) -> no_return when ViewOpts :: view_opts().
14 start(#view_opts{} = ViewOpts) ->
17 start(#view_opts{app = App, auto_row = AutoRow} = ViewOpts) ->
1518 Pid = spawn_link(fun() ->
1619 ?output(?CLEAR),
17 render_worker(?INTERVAL)
20 render_worker(App, AutoRow)
1821 end),
1922 manager(Pid, ViewOpts).
2023
2427 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2528 %%% Private
2629 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
27 manager(Pid, ViewOpts) ->
28 case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [Pid]) of
29 quit -> erlang:send(Pid, quit);
30 _ -> manager(Pid, ViewOpts)
31 end.
32
33 render_worker(Interval) ->
30 manager(Pid, Opts = #view_opts{app = App = #app{cur_page = CurPage}}) ->
31 case observer_cli_lib:parse_cmd(Opts, ?MODULE, [Pid]) of
32 quit ->
33 erlang:unlink(Pid),
34 erlang:send(Pid, quit),
35 quit;
36 {func, proc_count, message_queue_len} ->
37 clean([Pid]),
38 start(Opts#view_opts{app = App#app{type = {message_queue_len, 4}}});
39 {func, proc_count, reductions} ->
40 clean([Pid]),
41 start(Opts#view_opts{app = App#app{type = {reductions, 3}}});
42 {func, proc_count, memory} ->
43 clean([Pid]),
44 start(Opts#view_opts{app = App#app{type = {memory, 2}}});
45 pause_or_resume ->
46 clean([Pid]),
47 start(Opts#view_opts{app = App#app{type = {proc_count, 1}}});
48 {new_interval, NewInterval} ->
49 clean([Pid]),
50 start(Opts#view_opts{app = App#app{interval = NewInterval}});
51 page_down_top_n ->
52 NewPage = max(CurPage + 1, 1),
53 clean([Pid]),
54 start(Opts#view_opts{app = App#app{cur_page = NewPage}});
55 page_up_top_n ->
56 NewPage = max(CurPage - 1, 1),
57 clean([Pid]),
58 start(Opts#view_opts{app = App#app{cur_page = NewPage}});
59 _ ->
60 manager(Pid, Opts)
61 end.
62
63 render_worker(App, AutoRow) ->
64 #app{type = Type, interval = Interval, cur_page = CurPage} = App,
65 TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow),
66 Rows = erlang:max(TerminalRow - 5, 0),
3467 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
3568 Menu = observer_cli_lib:render_menu(app, Text),
36 Info = render_application_info(),
37 LastLine = observer_cli_lib:render_last_line("q(quit)"),
69 Info = render_app_info(Rows, CurPage, Type),
70 LastText = io_lib:format(?LAST_LINE, [Interval, CurPage]),
71 LastLine = observer_cli_lib:render_last_line(LastText),
3872 ?output([?CURSOR_TOP, Menu, Info, LastLine]),
3973 erlang:send_after(Interval, self(), redraw),
4074 receive
4175 quit -> quit;
42 redraw -> render_worker(Interval)
43 end.
44
45 render_application_info() ->
76 redraw -> render_worker(App, AutoRow)
77 end.
78
79 render_app_info(Row, CurPage, {Type, N}) ->
80 List = [
81 begin
82 {0, {element(N, I), S}, [App, C, M, R, Q, S, V]}
83 end
84 || {App, I = {C, M, R, Q, S, V}} <- maps:to_list(app_info())
85 ],
86 {StartPos, SortList} = observer_cli_lib:sublist(List, Row, CurPage),
87 InitColor = [
88 {memory, ?GRAY_BG},
89 {proc_count, ?GRAY_BG},
90 {reductions, ?GRAY_BG},
91 {message_queue_len, ?GRAY_BG}
92 ],
4693 [
47 {loaded, LoadedApps},
48 {loading, LoadingApps},
49 {started, StartedApps},
50 {start_p_false, StartPFlase},
51 {running, RunningApps},
52 {starting, StartingApps}
53 ] =
54 application_controller:info(),
55 {LoadedNotRunning, RunView} = render_running_apps(RunningApps, StartedApps, LoadedApps),
56 LoadedView = draw_loaded_apps(LoadedNotRunning),
57 StartingView = render_starting_apps(StartingApps),
58 SPFView = render_start_p_false(StartPFlase),
59 LoadingView = render_loading_apps(LoadingApps),
60 [RunView, LoadedView, StartingView, SPFView, LoadingView].
61
62 render_running_apps(RunningApps, StartedApps, LoadedApps) ->
94 {_, MemColor},
95 {_, ProcessColor},
96 {_, RedColor},
97 {_, MsgQColor}
98 ] = lists:keyreplace(Type, 1, InitColor, {Type, ?RED_BG}),
6399 Title = ?render([
64100 ?UNDERLINE,
65 ?GRAY_BG,
66 ?W("Status", 7),
67 ?W("Application", 16),
68 ?W("Vsn", 8),
69 ?W("Type", 10),
70 ?W("Application", 16),
71 ?W("Vsn", 8),
72 ?W("Type", 10),
73 ?W("Application", 16),
74 ?W("Vsn", 8),
75 ?W("Type", 10)
101 ?W2(?GRAY_BG, "Id", 3),
102 ?UNDERLINE,
103 ?W2(?GRAY_BG, "App", 31),
104 ?UNDERLINE,
105 ?W2(ProcessColor, "ProcessCount(p)", 20),
106 ?UNDERLINE,
107 ?W2(MemColor, "Memory(m)", 20),
108 ?UNDERLINE,
109 ?W2(RedColor, "Reductions(r)", 17),
110 ?UNDERLINE,
111 ?W2(MsgQColor, "MsgQ(mq)", 10),
112 ?UNDERLINE,
113 ?W2(?GRAY_BG, "Status", 12),
114 ?UNDERLINE,
115 ?W2(?GRAY_BG, "version", 16)
76116 ]),
77 draw_running_apps_1(RunningApps, StartedApps, LoadedApps, Title).
78
79 draw_running_apps_1([], _StartedApps, LoadedApps, Render) ->
80 {LoadedApps, Render};
81 draw_running_apps_1([{RunningApp, _}], StartedApps, LoadedApps, Acc) ->
82 {_, Type} = lists:keyfind(RunningApp, 1, StartedApps),
83 {value, {_, _Desc, Vsn}, NewLoadedApps} = lists:keytake(RunningApp, 1, LoadedApps),
84 {NewLoadedApps,
85 Acc ++
117 {_, View} = lists:foldl(
118 fun({_, _, Item}, {Pos, Acc}) ->
119 [App, C, R, M, Q, S, V] = Item,
120 {Pos + 1, [
121 ?render([
122 ?W(Pos, 2),
123 ?W(App, 29),
124 ?W(C, 18),
125 ?W({byte, M}, 18),
126 ?W(R, 15),
127 ?W(Q, 8),
128 ?W(S, 10),
129 ?W(V, 15)
130 ])
131 | Acc
132 ]}
133 end,
134 {StartPos, []},
135 SortList
136 ),
137 [Title | lists:reverse(View)].
138
139 app_info() ->
140 Info = application:info(),
141 AllApps = app_status(Info),
142 Leaders = leader_info(Info),
143 app_info(AllApps, Leaders, erlang:processes(), self()).
144
145 app_info(AllApps, _Leaders, [], _Self) ->
146 AllApps;
147 app_info(AllApps, Leaders, [Self | Process], Self) ->
148 app_info(AllApps, Leaders, Process, Self);
149 app_info(AllApps, Leaders, [Pid | Process], Self) ->
150 case erlang:process_info(Pid, [group_leader, memory, reductions, message_queue_len]) of
151 undefined ->
152 app_info(AllApps, Leaders, Process, Self);
153 Prop ->
86154 [
87 ?render([
88 ?W("Running", 7),
89 ?W2(?GREEN, RunningApp, 17),
90 ?W(Vsn, 10),
91 ?W(Type, 95)
92 ])
93 ]};
94 draw_running_apps_1([{App1, _}, {App2, _}], StartedApps, LoadedApps, Acc) ->
95 {_, Type1} = lists:keyfind(App1, 1, StartedApps),
96 {_, Type2} = lists:keyfind(App2, 1, StartedApps),
97 {value, {_, _Desc1, Vsn1}, LoadedApps1} = lists:keytake(App1, 1, LoadedApps),
98 {value, {_, _Desc2, Vsn2}, LoadedApps2} = lists:keytake(App2, 1, LoadedApps1),
99 {LoadedApps2,
100 Acc ++
101 [
102 ?render([
103 ?W("Running", 7),
104 ?W2(?GREEN, App1, 17),
105 ?W(Vsn1, 9),
106 ?W(Type1, 10),
107 ?W2(?GREEN, App2, 17),
108 ?W(Vsn2, 9),
109 ?W(Type2, 53)
110 ])
111 ]};
112 draw_running_apps_1([{App1, _}, {App2, _}, {App3, _} | Rest], StartedApps, LoadedApps, Acc) ->
113 {_, Type1} = lists:keyfind(App1, 1, StartedApps),
114 {_, Type2} = lists:keyfind(App2, 1, StartedApps),
115 {_, Type3} = lists:keyfind(App3, 1, StartedApps),
116 {value, {_, _Desc1, Vsn1}, LoadedApps1} = lists:keytake(App1, 1, LoadedApps),
117 {value, {_, _Desc2, Vsn2}, LoadedApps2} = lists:keytake(App2, 1, LoadedApps1),
118 {value, {_, _Desc3, Vsn3}, LoadedApps3} = lists:keytake(App3, 1, LoadedApps2),
155 {group_leader, Group},
156 {memory, Memory},
157 {reductions, Reds},
158 {message_queue_len, MsgQ}
159 ] = Prop,
160 NewAllApps =
161 case maps:find(Group, Leaders) of
162 error ->
163 {ok, {C1, M1, R1, Q1, S1, V1}} = maps:find(unknown, AllApps),
164 NewInfo = {C1 + 1, M1 + Memory, R1 + Reds, Q1 + MsgQ, S1, V1},
165 maps:put(unknown, NewInfo, AllApps);
166 {ok, App} ->
167 {ok, {C, M, R, Q, S, V}} = maps:find(App, AllApps),
168 maps:put(App, {C + 1, M + Memory, R + Reds, Q + MsgQ, S, V}, AllApps)
169 end,
170 app_info(NewAllApps, Leaders, Process, Self)
171 end.
172
173 leader_info(Info) ->
174 {running, Running} = lists:keyfind(running, 1, Info),
175 leader_info(Running, #{}).
176
177 leader_info([{App, Sup} | Running], Acc) when is_pid(Sup) ->
119178 NewAcc =
120 Acc ++
121 [
122 ?render([
123 ?W("Running", 7),
124 ?W2(?GREEN, App1, 17),
125 ?W(Vsn1, 9),
126 ?W(Type1, 10),
127 ?W2(?GREEN, App2, 17),
128 ?W(Vsn2, 9),
129 ?W(Type2, 10),
130 ?W2(?GREEN, App3, 17),
131 ?W(Vsn3, 9),
132 ?W(Type3, 10)
133 ])
134 ],
135 draw_running_apps_1(Rest, StartedApps, LoadedApps3, NewAcc).
136
137 draw_loaded_apps([]) ->
138 [];
139 draw_loaded_apps(Apps) ->
140 Title = ?render([
141 ?UNDERLINE,
142 ?GRAY_BG,
143 ?W("Status", 7),
144 ?W("Application", 16),
145 ?W("Vsn", 8),
146 ?W("Type", 10),
147 ?W("Application", 16),
148 ?W("Vsn", 8),
149 ?W("Type", 10),
150 ?W("Application", 16),
151 ?W("Vsn", 8),
152 ?W("Type", 10)
153 ]),
154 draw_loaded_apps_1(Apps, Title).
155
156 draw_loaded_apps_1([], Acc) ->
157 Acc;
158 draw_loaded_apps_1([{App, _Desc, Vsn}], Acc) ->
159 Acc ++
160 [
161 ?render([
162 ?W("Loaded", 7),
163 ?W2(?YELLOW, App, 17),
164 ?W(Vsn, 10),
165 ?W("******", 95)
166 ])
167 ];
168 draw_loaded_apps_1([{App1, _Desc1, Vsn1}, {App2, _Desc2, Vsn2}], Acc) ->
169 Acc ++
170 [
171 ?render([
172 ?W("Loaded", 7),
173 ?W2(?YELLOW, App1, 17),
174 ?W(Vsn1, 9),
175 ?W("******", 10),
176 ?W2(?YELLOW, App2, 17),
177 ?W(Vsn2, 9),
178 ?W("******", 53)
179 ])
180 ];
181 draw_loaded_apps_1([{App1, _Desc1, Vsn1}, {App2, _Desc2, Vsn2}, {App3, _Desc3, Vsn3} | Apps], Acc) ->
182 NewAcc =
183 Acc ++
184 [
185 ?render([
186 ?W("Loaded", 7),
187 ?W2(?YELLOW, App1, 17),
188 ?W(Vsn1, 9),
189 ?W("******", 10),
190 ?W2(?YELLOW, App2, 17),
191 ?W(Vsn2, 9),
192 ?W("******", 10),
193 ?W2(?YELLOW, App3, 17),
194 ?W(Vsn3, 9),
195 ?W("******", 10)
196 ])
197 ],
198 draw_loaded_apps_1(Apps, NewAcc).
199
200 render_starting_apps([]) ->
201 [];
202 render_starting_apps(Apps) ->
203 Title = ?render([
204 ?UNDERLINE,
205 ?GRAY_BG,
206 ?W("Status", 7),
207 ?W("Application", 18),
208 ?W("RestartType", 11),
209 ?W("Type", 11),
210 ?W("From", 13),
211 ?W("Application", 18),
212 ?W("RestartType", 11),
213 ?W("Type", 11),
214 ?W("From", 12)
215 ]),
216 draw_starting_apps_1(Apps, Title).
217
218 draw_starting_apps_1([], Acc) ->
219 Acc;
220 draw_starting_apps_1([{App, RestartType, Type, From}], Acc) ->
221 Acc ++
222 [
223 ?render([
224 ?W("Starting", 7),
225 ?W2(?L_GREEN, App, 19),
226 ?W(RestartType, 12),
227 ?W(Type, 11),
228 ?W(From, 77)
229 ])
230 ];
231 draw_starting_apps_1(
232 [
233 {App1, RestartType1, Type1, From1},
234 {App2, RestartType2, Type2, From2}
235 | Apps
236 ],
237 Acc
238 ) ->
239 NewAcc =
240 Acc ++
241 [
242 ?render([
243 ?W("Starting", 7),
244 ?W2(?L_GREEN, App1, 19),
245 ?W(RestartType1, 12),
246 ?W(Type1, 11),
247 ?W(From1, 13),
248 ?W2(?L_GREEN, App2, 19),
249 ?W(RestartType2, 12),
250 ?W(Type2, 11),
251 ?W(From2, 12)
252 ])
253 ],
254 draw_starting_apps_1(Apps, NewAcc).
255
256 render_start_p_false([]) -> [];
257 render_start_p_false(Apps) -> draw_start_p_false_1(Apps, []).
258
259 draw_start_p_false_1([], Acc) ->
260 Acc;
261 draw_start_p_false_1([{App, RestartType, Type, From}], Acc) ->
262 Acc ++
263 [
264 ?render([
265 ?W("SPFalse", 7),
266 ?W2(?L_GREEN, App, 19),
267 ?W(RestartType, 12),
268 ?W(Type, 11),
269 ?W(From, 77)
270 ])
271 ];
272 draw_start_p_false_1(
273 [{App1, RestartType1, Type1, From1}, {App2, RestartType2, Type2, From2} | Apps],
274 Acc
275 ) ->
276 NewAcc =
277 Acc ++
278 [
279 ?render([
280 ?W("SPFalse", 7),
281 ?W2(?L_GREEN, App1, 19),
282 ?W(RestartType1, 12),
283 ?W(Type1, 11),
284 ?W(From1, 13),
285 ?W2(?L_GREEN, App2, 19),
286 ?W(RestartType2, 12),
287 ?W(Type2, 11),
288 ?W(From2, 12)
289 ])
290 ],
291 draw_start_p_false_1(Apps, NewAcc).
292
293 render_loading_apps([]) ->
294 [];
295 render_loading_apps(Apps) ->
296 Title = ?render([
297 ?UNDERLINE,
298 ?GRAY_BG,
299 ?W("Status", 7),
300 ?W("Application", 16),
301 ?W("From", 11),
302 ?W("Application", 16),
303 ?W("From", 10),
304 ?W("Application", 16),
305 ?W("From", 11),
306 ?W("Application", 16),
307 ?W("From", 9)
308 ]),
309 draw_loading_apps_1(Apps, Title).
310
311 draw_loading_apps_1([], Acc) ->
312 Acc;
313 draw_loading_apps_1([{App1, From1}], Acc) ->
314 Acc ++
315 [
316 ?render([
317 ?W("Loading", 7),
318 ?W2(?L_GREEN, App1, 17),
319 ?W(From1, 108)
320 ])
321 ];
322 draw_loading_apps_1([{App1, From1}, {App2, From2}], Acc) ->
323 Acc ++
324 [
325 ?render([
326 ?W("Loading", 7),
327 ?W2(?L_GREEN, App1, 17),
328 ?W(From1, 12),
329 ?W2(?L_GREEN, App2, 17),
330 ?W(From2, 75)
331 ])
332 ];
333 draw_loading_apps_1([{App1, From1}, {App2, From2}, {App3, From3}], Acc) ->
334 Acc ++
335 [
336 ?render([
337 ?W("Loading", 7),
338 ?W2(?L_GREEN, App1, 17),
339 ?W(From1, 12),
340 ?W2(?L_GREEN, App2, 17),
341 ?W(From2, 11),
342 ?W2(?L_GREEN, App3, 17),
343 ?W(From3, 43)
344 ])
345 ];
346 draw_loading_apps_1([{App1, From1}, {App2, From2}, {App3, From3}, {App4, From4} | Apps], Acc) ->
347 NewAcc =
348 Acc ++
349 [
350 ?render([
351 ?W("Loading", 7),
352 ?W2(?L_GREEN, App1, 17),
353 ?W(From1, 12),
354 ?W2(?L_GREEN, App2, 17),
355 ?W(From2, 11),
356 ?W2(?L_GREEN, App3, 17),
357 ?W(From3, 11),
358 ?W2(?L_GREEN, App4, 17),
359 ?W(From4, 11)
360 ])
361 ],
362 draw_loading_apps_1(Apps, NewAcc).
179 case erlang:process_info(Sup, group_leader) of
180 undefined ->
181 Acc;
182 {group_leader, Pid} ->
183 Acc#{Pid => App}
184 end,
185 leader_info(Running, NewAcc);
186 leader_info([_ | Running], Acc) ->
187 leader_info(Running, Acc);
188 leader_info([], Acc) ->
189 Acc.
190
191 app_status(Info) ->
192 {loaded, Loaded} = lists:keyfind(loaded, 1, Info),
193 {loading, Loading} = lists:keyfind(loading, 1, Info),
194 {started, Started} = lists:keyfind(started, 1, Info),
195 {start_p_false, StartPFalse} = lists:keyfind(start_p_false, 1, Info),
196 {starting, Starting} = lists:keyfind(starting, 1, Info),
197 R0 = #{unknown => {0, 0, 0, 0, "Unknown", "unknown"}},
198 R1 = lists:foldl(
199 fun({App, _From}, Acc) ->
200 Acc#{App => {0, 0, 0, 0, "Loading", "unknown"}}
201 end,
202 R0,
203 Loading
204 ),
205 R2 = lists:foldl(
206 fun({App, _Desc, Version}, Acc) ->
207 Acc#{App => {0, 0, 0, 0, "Loaded", Version}}
208 end,
209 R1,
210 Loaded
211 ),
212 R3 = lists:foldl(
213 fun({App, _RestartType, _Type, _From}, Acc) ->
214 Version = get_version(App, Acc),
215 Acc#{App => {0, 0, 0, 0, "Starting", Version}}
216 end,
217 R2,
218 Starting
219 ),
220 R4 = lists:foldl(
221 fun({App, _RestartType}, Acc) ->
222 Version = get_version(App, Acc),
223 Acc#{App => {0, 0, 0, 0, "Started", Version}}
224 end,
225 R3,
226 Started
227 ),
228 lists:foldl(
229 fun({App, _RestartType, _Type, _From}, Acc) ->
230 Version = get_version(App, Acc),
231 Acc#{App => {0, 0, 0, 0, "StartPFlase", Version}}
232 end,
233 R4,
234 StartPFalse
235 ).
236
237 get_version(App, Maps) ->
238 case maps:find(App, Maps) of
239 {ok, {_, _, _, _, _, V}} -> V;
240 _ -> "unknown"
241 end.
0 -module(observer_cli_escriptize).
1
2 -export([main/1]).
3
4 %% @doc escript main
5 -spec main(list()) -> 'ok'.
6
7 -define(BEAM_MODS, [
8 recon,
9 recon_alloc,
10 recon_lib,
11 recon_trace,
12 observer_cli,
13 observer_cli_ets,
14 observer_cli_lib,
15 observer_cli_process,
16 observer_cli_application,
17 observer_cli_help,
18 observer_cli_mnesia,
19 observer_cli_store,
20 observer_cli_escriptize,
21 observer_cli_inet,
22 observer_cli_port,
23 observer_cli_system
24 ]).
25
26 main([TargetNode]) ->
27 run(TargetNode, undefined, 1500);
28 main([TargetNode, Cookie, Interval]) ->
29 CookieAtom = list_to_atom(Cookie),
30 IntervalInt = list_to_integer(Interval),
31 run(TargetNode, CookieAtom, IntervalInt);
32 main(_Options) ->
33 io:format("Usage: observer_cli TARGETNODE [TARGETCOOKIE REFRESHMS]~n").
34
35 run(TargetNode, Cookie, Interval) ->
36 {TargetNodeAtom, NameOpt} = resolve_target_name(TargetNode),
37 LocalNode = random_local_node_name(),
38 MyName =
39 case NameOpt of
40 shortnames -> list_to_atom(LocalNode);
41 longnames -> list_to_atom(LocalNode ++ "@127.0.0.1")
42 end,
43 {ok, _} = net_kernel:start([MyName, NameOpt]),
44 Start = fun() ->
45 Options = [{cookie, Cookie}, {interval, Interval}],
46 observer_cli:start(TargetNodeAtom, Options)
47 end,
48 case Start() of
49 {badrpc, _} ->
50 remote_load(TargetNodeAtom),
51 io:format("~p~n", [Start()]);
52 _ ->
53 ok
54 end.
55
56 remote_load(Node) ->
57 [
58 begin
59 recon:remote_load([Node], Mod)
60 end
61 || Mod <- ?BEAM_MODS
62 ].
63
64 random_local_node_name() ->
65 {_, {H, M, S}} = calendar:local_time(),
66 lists:flatten(io_lib:format("observer_cli_~2.2.0p_~2.2.0p_~2.2.0p", [H, M, S])).
67
68 resolve_target_name(TargetNode) ->
69 case string:tokens(TargetNode, "@") of
70 [_Name, Host] ->
71 Node = list_to_atom(TargetNode),
72 case string:tokens(Host, ".") of
73 [Host] -> {Node, shortnames};
74 [_ | _] -> {Node, longnames}
75 end;
76 [Name] ->
77 %% only a name without host given, assume shortname
78 {ok, Host} = inet:gethostname(),
79 {list_to_atom(Name ++ "@" ++ Host), shortnames}
80 end.
0 -module(observer_cli_escriptize).
1
2 -export([main/1]).
3
4 %% @doc escript main
5 -spec main(list()) -> 'ok'.
6
7 -define(BEAM_MODS, [
8 recon,
9 recon_alloc,
10 recon_lib,
11 recon_trace,
12 observer_cli,
13 observer_cli_ets,
14 observer_cli_lib,
15 observer_cli_process,
16 observer_cli_application,
17 observer_cli_help,
18 observer_cli_mnesia,
19 observer_cli_store,
20 observer_cli_escriptize,
21 observer_cli_inet,
22 observer_cli_port,
23 observer_cli_system
24 ]).
25
26 main([TargetNode]) ->
27 run(TargetNode, undefined, 1500);
28 main([TargetNode, Cookie, Interval]) ->
29 CookieAtom = list_to_atom(Cookie),
30 IntervalInt = list_to_integer(Interval),
31 run(TargetNode, CookieAtom, IntervalInt);
32 main(_Options) ->
33 io:format("Usage: observer_cli TARGETNODE [TARGETCOOKIE REFRESHMS]~n").
34
35 run(TargetNode, Cookie, Interval) ->
36 {TargetNodeAtom, NameOpt} = resolve_target_name(TargetNode),
37 LocalNode = random_local_node_name(),
38 MyName =
39 case NameOpt of
40 shortnames -> list_to_atom(LocalNode);
41 longnames -> list_to_atom(LocalNode ++ "@127.0.0.1")
42 end,
43 {ok, _} = net_kernel:start([MyName, NameOpt]),
44 Start = fun() ->
45 Options = [{cookie, Cookie}, {interval, Interval}],
46 observer_cli:start(TargetNodeAtom, Options)
47 end,
48 case Start() of
49 {badrpc, _} ->
50 remote_load(TargetNodeAtom),
51 io:format("~p~n", [Start()]);
52 _ ->
53 ok
54 end.
55
56 remote_load(Node) ->
57 [
58 begin
59 recon:remote_load([Node], Mod)
60 end
61 || Mod <- ?BEAM_MODS
62 ].
63
64 random_local_node_name() ->
65 {_, {H, M, S}} = calendar:local_time(),
66 lists:flatten(io_lib:format("observer_cli_~2.2.0p_~2.2.0p_~2.2.0p", [H, M, S])).
67
68 resolve_target_name(TargetNode) ->
69 case string:tokens(TargetNode, "@") of
70 [_Name, Host] ->
71 Node = list_to_atom(TargetNode),
72 case string:tokens(Host, ".") of
73 [Host] -> {Node, shortnames};
74 [_ | _] -> {Node, longnames}
75 end;
76 [Name] ->
77 %% only a name without host given, assume shortname
78 {ok, Host} = inet:gethostname(),
79 {list_to_atom(Name ++ "@" ++ Host), shortnames}
80 end.
0 %%% @author zhongwen <zhongwencool@gmail.com>
1 -module(observer_cli_ets).
2
3 -include("observer_cli.hrl").
4
5 %% API
6 -export([start/1]).
7 -export([clean/1]).
8
9 -define(LAST_LINE,
10 "q(quit) s(sort by size) m(sort by memory) pd/pu(page:down/up) F/B(forward/back)"
11 ).
12
13 -spec start(ViewOpts) -> no_return() when ViewOpts :: view_opts().
14 start(
15 #view_opts{
16 ets = #ets{interval = Interval, attr = Attr, cur_page = CurPage},
17 auto_row = AutoRow
18 } = ViewOpts
19 ) ->
20 Pid = spawn_link(fun() ->
21 ?output(?CLEAR),
22 render_worker(Interval, ?INIT_TIME_REF, Attr, CurPage, AutoRow)
23 end),
24 manager(Pid, ViewOpts).
25
26 -spec clean(list()) -> ok.
27 clean(Pids) -> observer_cli_lib:exit_processes(Pids).
28
29 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
30 %%% Private
31 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
32 manager(ChildPid, #view_opts{ets = EtsOpts = #ets{cur_page = CurPage}} = ViewOpts) ->
33 case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [ChildPid]) of
34 quit ->
35 erlang:send(ChildPid, quit);
36 {new_interval, NewMs} ->
37 clean([ChildPid]),
38 start(ViewOpts#view_opts{ets = EtsOpts#ets{interval = NewMs}});
39 size ->
40 clean([ChildPid]),
41 start(ViewOpts#view_opts{ets = EtsOpts#ets{attr = size}});
42 %% Home
43 {func, proc_count, memory} ->
44 clean([ChildPid]),
45 start(ViewOpts#view_opts{ets = EtsOpts#ets{attr = memory}});
46 page_down_top_n ->
47 NewPage = max(CurPage + 1, 1),
48 clean([ChildPid]),
49 start(ViewOpts#view_opts{ets = EtsOpts#ets{cur_page = NewPage}});
50 page_up_top_n ->
51 NewPage = max(CurPage - 1, 1),
52 clean([ChildPid]),
53 start(ViewOpts#view_opts{ets = EtsOpts#ets{cur_page = NewPage}});
54 _ ->
55 manager(ChildPid, ViewOpts)
56 end.
57
58 render_worker(Interval, LastTimeRef, Attr, CurPage, AutoRow) ->
59 TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow),
60 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
61 Menu = observer_cli_lib:render_menu(ets, Text),
62 Ets = render_ets_info(erlang:max(0, TerminalRow - 4), CurPage, Attr),
63 LastLine = observer_cli_lib:render_last_line(?LAST_LINE),
64 ?output([?CURSOR_TOP, Menu, Ets, LastLine]),
65 NextTimeRef = observer_cli_lib:next_redraw(LastTimeRef, Interval),
66 receive
67 quit -> quit;
68 _ -> render_worker(Interval, NextTimeRef, Attr, CurPage, AutoRow)
69 end.
70
71 render_ets_info(Rows, CurPage, Attr) ->
72 AllEts = [
73 begin
74 get_ets_info(Tab, Attr)
75 end
76 || Tab <- ets:all()
77 ],
78 WordSize = erlang:system_info(wordsize),
79 SortEts = observer_cli_lib:sublist(AllEts, Rows, CurPage),
80 {MemColor, SizeColor} =
81 case Attr of
82 memory -> {?RED_BG, ?GRAY_BG};
83 _ -> {?GRAY_BG, ?RED_BG}
84 end,
85 Title = ?render([
86 ?UNDERLINE,
87 ?W2(?GRAY_BG, "Table Name", 37),
88 ?UNDERLINE,
89 ?W2(SizeColor, "Size", 14),
90 ?UNDERLINE,
91 ?W2(MemColor, " Memory ", 14),
92 ?UNDERLINE,
93 ?W2(?GRAY_BG, "Type", 15),
94 ?UNDERLINE,
95 ?W2(?GRAY_BG, "Protection", 12),
96 ?UNDERLINE,
97 ?W2(?GRAY_BG, "KeyPos", 8),
98 ?UNDERLINE,
99 ?W2(?GRAY_BG, "Write/Read", 14),
100 ?UNDERLINE,
101 ?W2(?GRAY_BG, "Owner Pid", 15)
102 ]),
103 RowView = [
104 begin
105 Name = proplists:get_value(name, Ets),
106 Memory = proplists:get_value(memory, Ets),
107 Size = proplists:get_value(size, Ets),
108 Type = proplists:get_value(type, Ets),
109 Protect = proplists:get_value(protection, Ets),
110 KeyPos = proplists:get_value(keypos, Ets),
111 Write = observer_cli_lib:to_list(proplists:get_value(write_concurrency, Ets)),
112 Read = observer_cli_lib:to_list(proplists:get_value(read_concurrency, Ets)),
113 Owner = proplists:get_value(owner, Ets),
114 ?render([
115 ?W(Name, 36),
116 ?W(Size, 12),
117 ?W({byte, Memory * WordSize}, 12),
118 ?W(Type, 13),
119 ?W(Protect, 10),
120 ?W(KeyPos, 6),
121 ?W(Write ++ "/" ++ Read, 12),
122 ?W(Owner, 14)
123 ])
124 end
125 || {_, _, Ets} <- SortEts
126 ],
127 [Title | RowView].
128
129 get_ets_info(Tab, Attr) ->
130 case catch ets:info(Tab) of
131 {'EXIT', _} ->
132 {
133 0,
134 0,
135 [
136 %%it maybe die
137 {name, unread},
138 {write_concurrency, unread},
139 {read_concurrency, unread},
140 {compressed, unread},
141 {memory, unread},
142 {owner, unread},
143 {heir, unread},
144 {size, unread},
145 {node, unread},
146 {named_table, unread},
147 {type, unread},
148 {keypos, unread},
149 {protection, unread}
150 ]
151 };
152 Info when is_list(Info) ->
153 Owner = proplists:get_value(owner, Info),
154 NewInfo =
155 case is_reg(Owner) of
156 Owner -> Info;
157 Reg -> lists:keyreplace(Owner, 1, Info, {owner, Reg})
158 end,
159 {
160 0,
161 proplists:get_value(Attr, NewInfo),
162 NewInfo
163 }
164 end.
165
166 is_reg(Owner) ->
167 case process_info(Owner, registered_name) of
168 {registered_name, Name} -> Name;
169 _ -> Owner
170 end.
0 %%% @author zhongwen <zhongwencool@gmail.com>
1 -module(observer_cli_ets).
2
3 -include("observer_cli.hrl").
4
5 %% API
6 -export([start/1]).
7 -export([clean/1]).
8
9 -define(LAST_LINE,
10 "q(quit) s(sort by size) m(sort by memory) pd/pu(page:down/up) F/B(forward/back)"
11 ).
12
13 -spec start(ViewOpts) -> no_return() when ViewOpts :: view_opts().
14 start(
15 #view_opts{
16 ets = #ets{interval = Interval, attr = Attr, cur_page = CurPage},
17 auto_row = AutoRow
18 } = ViewOpts
19 ) ->
20 Pid = spawn_link(fun() ->
21 ?output(?CLEAR),
22 render_worker(Interval, ?INIT_TIME_REF, Attr, CurPage, AutoRow)
23 end),
24 manager(Pid, ViewOpts).
25
26 -spec clean(list()) -> ok.
27 clean(Pids) -> observer_cli_lib:exit_processes(Pids).
28
29 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
30 %%% Private
31 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
32 manager(ChildPid, #view_opts{ets = EtsOpts = #ets{cur_page = CurPage}} = ViewOpts) ->
33 case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [ChildPid]) of
34 quit ->
35 erlang:send(ChildPid, quit);
36 {new_interval, NewMs} ->
37 clean([ChildPid]),
38 start(ViewOpts#view_opts{ets = EtsOpts#ets{interval = NewMs}});
39 size ->
40 clean([ChildPid]),
41 start(ViewOpts#view_opts{ets = EtsOpts#ets{attr = size}});
42 %% Home
43 {func, proc_count, memory} ->
44 clean([ChildPid]),
45 start(ViewOpts#view_opts{ets = EtsOpts#ets{attr = memory}});
46 page_down_top_n ->
47 NewPage = max(CurPage + 1, 1),
48 clean([ChildPid]),
49 start(ViewOpts#view_opts{ets = EtsOpts#ets{cur_page = NewPage}});
50 page_up_top_n ->
51 NewPage = max(CurPage - 1, 1),
52 clean([ChildPid]),
53 start(ViewOpts#view_opts{ets = EtsOpts#ets{cur_page = NewPage}});
54 _ ->
55 manager(ChildPid, ViewOpts)
56 end.
57
58 render_worker(Interval, LastTimeRef, Attr, CurPage, AutoRow) ->
59 TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow),
60 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
61 Menu = observer_cli_lib:render_menu(ets, Text),
62 Ets = render_ets_info(erlang:max(0, TerminalRow - 4), CurPage, Attr),
63 LastLine = observer_cli_lib:render_last_line(?LAST_LINE),
64 ?output([?CURSOR_TOP, Menu, Ets, LastLine]),
65 NextTimeRef = observer_cli_lib:next_redraw(LastTimeRef, Interval),
66 receive
67 quit -> quit;
68 _ -> render_worker(Interval, NextTimeRef, Attr, CurPage, AutoRow)
69 end.
70
71 render_ets_info(Rows, CurPage, Attr) ->
72 AllEts = [
73 begin
74 get_ets_info(Tab, Attr)
75 end
76 || Tab <- ets:all()
77 ],
78 WordSize = erlang:system_info(wordsize),
79 {_StartPos, SortEts} = observer_cli_lib:sublist(AllEts, Rows, CurPage),
80 {MemColor, SizeColor} =
81 case Attr of
82 memory -> {?RED_BG, ?GRAY_BG};
83 _ -> {?GRAY_BG, ?RED_BG}
84 end,
85 Title = ?render([
86 ?UNDERLINE,
87 ?W2(?GRAY_BG, "Table Name", 37),
88 ?UNDERLINE,
89 ?W2(SizeColor, "Size", 14),
90 ?UNDERLINE,
91 ?W2(MemColor, " Memory ", 14),
92 ?UNDERLINE,
93 ?W2(?GRAY_BG, "Type", 15),
94 ?UNDERLINE,
95 ?W2(?GRAY_BG, "Protection", 12),
96 ?UNDERLINE,
97 ?W2(?GRAY_BG, "KeyPos", 8),
98 ?UNDERLINE,
99 ?W2(?GRAY_BG, "Write/Read", 14),
100 ?UNDERLINE,
101 ?W2(?GRAY_BG, "Owner Pid", 15)
102 ]),
103 RowView = [
104 begin
105 Name = proplists:get_value(name, Ets),
106 Memory = proplists:get_value(memory, Ets),
107 Size = proplists:get_value(size, Ets),
108 Type = proplists:get_value(type, Ets),
109 Protect = proplists:get_value(protection, Ets),
110 KeyPos = proplists:get_value(keypos, Ets),
111 Write = observer_cli_lib:to_list(proplists:get_value(write_concurrency, Ets)),
112 Read = observer_cli_lib:to_list(proplists:get_value(read_concurrency, Ets)),
113 Owner = proplists:get_value(owner, Ets),
114 ?render([
115 ?W(Name, 36),
116 ?W(Size, 12),
117 ?W({byte, Memory * WordSize}, 12),
118 ?W(Type, 13),
119 ?W(Protect, 10),
120 ?W(KeyPos, 6),
121 ?W(Write ++ "/" ++ Read, 12),
122 ?W(Owner, 14)
123 ])
124 end
125 || {_, _, Ets} <- SortEts
126 ],
127 [Title | RowView].
128
129 get_ets_info(Tab, Attr) ->
130 case catch ets:info(Tab) of
131 {'EXIT', _} ->
132 {
133 0,
134 0,
135 [
136 %%it maybe die
137 {name, unread},
138 {write_concurrency, unread},
139 {read_concurrency, unread},
140 {compressed, unread},
141 {memory, unread},
142 {owner, unread},
143 {heir, unread},
144 {size, unread},
145 {node, unread},
146 {named_table, unread},
147 {type, unread},
148 {keypos, unread},
149 {protection, unread}
150 ]
151 };
152 Info when is_list(Info) ->
153 Owner = proplists:get_value(owner, Info),
154 NewInfo =
155 case is_reg(Owner) of
156 Owner -> Info;
157 Reg -> lists:keyreplace(Owner, 1, Info, {owner, Reg})
158 end,
159 {
160 0,
161 proplists:get_value(Attr, NewInfo),
162 NewInfo
163 }
164 end.
165
166 is_reg(Owner) ->
167 case process_info(Owner, registered_name) of
168 {registered_name, Name} -> Name;
169 _ -> Owner
170 end.
0 %%% @author zhongwen <zhongwencool@gmail.com>
1 -module(observer_cli_help).
2
3 -include("observer_cli.hrl").
4
5 %% API
6 -export([start/1]).
7 -export([clean/1]).
8
9 -define(HELP_COLUMN_WIDTH, 85).
10
11 -spec start(view_opts()) -> no_return.
12 start(#view_opts{help = #help{interval = Interval}} = ViewOpts) ->
13 ChildPid = spawn_link(fun() ->
14 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
15 Menu = observer_cli_lib:render_menu(doc, Text),
16 Help = render_help(),
17 LastLine = observer_cli_lib:render_last_line("q(quit)"),
18 ?output([?CLEAR, Menu, Help, ?UNDERLINE, ?GRAY_BG, LastLine, ?RESET_BG, ?RESET]),
19 render_worker(Interval)
20 end),
21 manager(ChildPid, ViewOpts).
22
23 -spec clean(list()) -> ok.
24 clean(Pids) -> observer_cli_lib:exit_processes(Pids).
25
26 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
27 %%% Private
28 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
29 manager(ChildPid, ViewOpts) ->
30 case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [ChildPid]) of
31 quit -> erlang:send(ChildPid, quit);
32 _ -> manager(ChildPid, ViewOpts)
33 end.
34
35 render_worker(Interval) ->
36 ?output(?CURSOR_TOP),
37 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
38 Menu = observer_cli_lib:render_menu(doc, Text),
39 ?output([?CURSOR_TOP, Menu]),
40 erlang:send_after(Interval, self(), redraw),
41 receive
42 redraw ->
43 render_worker(Interval);
44 quit ->
45 MenuQ = observer_cli_lib:render_menu(doc, Text),
46 HelpQ = render_help(),
47 LastLine = observer_cli_lib:render_last_line("q(quit)"),
48 ?output([?CURSOR_TOP, MenuQ, HelpQ, ?UNDERLINE, ?GRAY_BG, LastLine, ?RESET_BG, ?RESET])
49 end.
50
51 render_help() ->
52 [
53 "|\e[44m1. Start Mode\e[49m \n",
54 "| \e[48;2;80;80;80m1.1\e[0m observer_cli:start(). \n",
55 "| \e[48;2;80;80;80m1.2\e[0m observer_cli:start(Node).\n",
56 "| \e[48;2;80;80;80m1.3\e[0m observer_start:start(Node, Cookie).\n",
57
58 "|\e[44m2. HOME(H) Commands\e[49m \n",
59 "| \e[48;2;80;80;80m` \e[0m enable/disable schedule usage.\n",
60 "| \e[48;2;80;80;80mPageDown \e[0m pd or F(forward).\n",
61 "| \e[48;2;80;80;80mPageUp \e[0m pu or B(back).\n",
62 "| \e[48;2;80;80;80mr \e[0m switch mode to reduction(proc_count).\n",
63 "| \e[48;2;80;80;80mrr \e[0m switch mode to reduction(proc_window).\n",
64 "| \e[48;2;80;80;80mm \e[0m switch mode to memory(proc_count).\n",
65 "| \e[48;2;80;80;80mmm \e[0m switch mode to memory(proc_window).\n",
66 "| \e[48;2;80;80;80mb \e[0m switch mode to bin memory(proc_count).\n",
67 "| \e[48;2;80;80;80mbb \e[0m switch mode to bin memory(proc_window).\n",
68 "| \e[48;2;80;80;80mmq \e[0m switch mode to message queue len(proc_count).\n",
69 "| \e[48;2;80;80;80mmmq \e[0m switch mode to message queue len(proc_window).\n",
70 "| \e[48;2;80;80;80mt \e[0m switch mode to total heap size(proc_count).\n",
71 "| \e[48;2;80;80;80mtt \e[0m switch mode to total heap size(proc_window).\n",
72 "| \e[48;2;80;80;80m3000 \e[0m set interval time to 3000ms, the integer must >= 1500.\n",
73 "| \e[48;2;80;80;80m13 \e[0m choose the 13th process(green line), the integer must in top list.\n",
74 "| \e[48;2;80;80;80mp \e[0m pause/unpause the view.\n",
75
76 "|\e[44m5. Reference\e[49m \n",
77 "|More information about recon:proc_count/2 and recon:proc_window/3 \n",
78 "|refer to https://github.com/ferd/recon/blob/master/src/recon.erl \n",
79 "|Any issue please visit: https://github.com/zhongwencool/observer_cli/issues \n"
80 ].
0 %%% @author zhongwen <zhongwencool@gmail.com>
1 -module(observer_cli_help).
2
3 -include("observer_cli.hrl").
4
5 %% API
6 -export([start/1]).
7 -export([clean/1]).
8
9 -define(HELP_COLUMN_WIDTH, 85).
10
11 -spec start(view_opts()) -> no_return.
12 start(#view_opts{help = #help{interval = Interval}} = ViewOpts) ->
13 ChildPid = spawn_link(fun() ->
14 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
15 Menu = observer_cli_lib:render_menu(doc, Text),
16 Help = render_help(),
17 LastLine = observer_cli_lib:render_last_line("q(quit)"),
18 ?output([?CLEAR, Menu, Help, ?UNDERLINE, ?GRAY_BG, LastLine, ?RESET_BG, ?RESET]),
19 render_worker(Interval)
20 end),
21 manager(ChildPid, ViewOpts).
22
23 -spec clean(list()) -> ok.
24 clean(Pids) -> observer_cli_lib:exit_processes(Pids).
25
26 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
27 %%% Private
28 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
29 manager(ChildPid, ViewOpts) ->
30 case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [ChildPid]) of
31 quit -> erlang:send(ChildPid, quit);
32 _ -> manager(ChildPid, ViewOpts)
33 end.
34
35 render_worker(Interval) ->
36 ?output(?CURSOR_TOP),
37 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
38 Menu = observer_cli_lib:render_menu(doc, Text),
39 ?output([?CURSOR_TOP, Menu]),
40 erlang:send_after(Interval, self(), redraw),
41 receive
42 redraw ->
43 render_worker(Interval);
44 quit ->
45 MenuQ = observer_cli_lib:render_menu(doc, Text),
46 HelpQ = render_help(),
47 LastLine = observer_cli_lib:render_last_line("q(quit)"),
48 ?output([?CURSOR_TOP, MenuQ, HelpQ, ?UNDERLINE, ?GRAY_BG, LastLine, ?RESET_BG, ?RESET])
49 end.
50
51 render_help() ->
52 [
53 "|\e[44m1. Start Mode\e[49m \n",
54 "| \e[48;2;80;80;80m1.1\e[0m observer_cli:start(). \n",
55 "| \e[48;2;80;80;80m1.2\e[0m observer_cli:start(Node).\n",
56 "| \e[48;2;80;80;80m1.3\e[0m observer_start:start(Node, Cookie).\n",
57
58 "|\e[44m2. HOME(H) Commands\e[49m \n",
59 "| \e[48;2;80;80;80m` \e[0m enable/disable schedule usage.\n",
60 "| \e[48;2;80;80;80mPageDown \e[0m pd or F(forward).\n",
61 "| \e[48;2;80;80;80mPageUp \e[0m pu or B(back).\n",
62 "| \e[48;2;80;80;80mr \e[0m switch mode to reduction(proc_count).\n",
63 "| \e[48;2;80;80;80mrr \e[0m switch mode to reduction(proc_window).\n",
64 "| \e[48;2;80;80;80mm \e[0m switch mode to memory(proc_count).\n",
65 "| \e[48;2;80;80;80mmm \e[0m switch mode to memory(proc_window).\n",
66 "| \e[48;2;80;80;80mb \e[0m switch mode to bin memory(proc_count).\n",
67 "| \e[48;2;80;80;80mbb \e[0m switch mode to bin memory(proc_window).\n",
68 "| \e[48;2;80;80;80mmq \e[0m switch mode to message queue len(proc_count).\n",
69 "| \e[48;2;80;80;80mmmq \e[0m switch mode to message queue len(proc_window).\n",
70 "| \e[48;2;80;80;80mt \e[0m switch mode to total heap size(proc_count).\n",
71 "| \e[48;2;80;80;80mtt \e[0m switch mode to total heap size(proc_window).\n",
72 "| \e[48;2;80;80;80m3000 \e[0m set interval time to 3000ms, the integer must >= 1500.\n",
73 "| \e[48;2;80;80;80m13 \e[0m choose the 13th process(green line), the integer must in top list.\n",
74 "| \e[48;2;80;80;80mp \e[0m pause/unpause the view.\n",
75
76 "|\e[44m5. Reference\e[49m \n",
77 "|More information about recon:proc_count/2 and recon:proc_window/3 \n",
78 "|refer to https://github.com/ferd/recon/blob/master/src/recon.erl \n",
79 "|Any issue please visit: https://github.com/zhongwencool/observer_cli/issues \n"
80 ].
0 %%% @author zhongwen <zhongwencool@gmail.com>
1 -module(observer_cli_inet).
2
3 -include("observer_cli.hrl").
4
5 %% API
6 -export([start/1]).
7 -export([clean/1]).
8
9 -define(LAST_LINE,
10 "q(quit) ic(inet_count) iw(inet_window) rc(recv_cnt) ro(recv_oct)"
11 " sc(send_cnt) so(send_oct) cnt oct 9(port 9 info) pd/pu(page:down/up)"
12 ).
13
14 -spec start(view_opts()) -> no_return.
15 start(#view_opts{inet = InetOpt, auto_row = AutoRow} = ViewOpts) ->
16 StorePid = observer_cli_store:start(),
17 RenderPid = spawn_link(
18 fun() ->
19 ?output(?CLEAR),
20 {{input, In}, {output, Out}} = erlang:statistics(io),
21 render_worker(StorePid, InetOpt, ?INIT_TIME_REF, 0, {In, Out}, AutoRow)
22 end
23 ),
24 manager(StorePid, RenderPid, ViewOpts).
25
26 -spec clean(list()) -> ok.
27 clean(Pids) -> observer_cli_lib:exit_processes(Pids).
28
29 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
30 %%% Private
31 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
32 manager(StorePid, RenderPid, ViewOpts = #view_opts{inet = InetOpts}) ->
33 #inet{cur_page = CurPage, pages = Pages} = InetOpts,
34 case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [RenderPid, StorePid]) of
35 quit ->
36 observer_cli_lib:exit_processes([StorePid]),
37 erlang:send(RenderPid, quit);
38 {new_interval, NewInterval} ->
39 erlang:send(RenderPid, {new_interval, NewInterval}),
40 NewInet = InetOpts#inet{interval = NewInterval},
41 manager(StorePid, RenderPid, ViewOpts#view_opts{inet = NewInet});
42 Func when Func =:= inet_count; Func =:= inet_window ->
43 clean([StorePid, RenderPid]),
44 start(ViewOpts#view_opts{inet = InetOpts#inet{func = Func}});
45 Type when
46 Type =:= recv_cnt;
47 Type =:= recv_oct;
48 Type =:= send_cnt;
49 Type =:= send_oct;
50 Type =:= cnt;
51 Type =:= oct
52 ->
53 clean([StorePid, RenderPid]),
54 start(ViewOpts#view_opts{inet = InetOpts#inet{type = Type}});
55 {jump, NewPos} ->
56 NewPages = observer_cli_lib:update_page_pos(CurPage, NewPos, Pages),
57 NewInetOpts = InetOpts#inet{pages = NewPages},
58 start_port_view(StorePid, RenderPid, ViewOpts#view_opts{inet = NewInetOpts}, false);
59 jump ->
60 start_port_view(StorePid, RenderPid, ViewOpts, true);
61 page_down_top_n ->
62 NewPage = max(CurPage + 1, 1),
63 NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages),
64 clean([StorePid, RenderPid]),
65 start(ViewOpts#view_opts{inet = InetOpts#inet{cur_page = NewPage, pages = NewPages}});
66 page_up_top_n ->
67 NewPage = max(CurPage - 1, 1),
68 NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages),
69 clean([StorePid, RenderPid]),
70 start(ViewOpts#view_opts{inet = InetOpts#inet{cur_page = NewPage, pages = NewPages}});
71 _ ->
72 manager(StorePid, RenderPid, ViewOpts)
73 end.
74
75 render_worker(StorePid, InetOpt, LastTimeRef, Count, LastIO, AutoRow) ->
76 #inet{func = Function, type = Type, interval = Interval, cur_page = CurPage} = InetOpt,
77 TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow),
78 Row = erlang:max(TerminalRow - 5, 0),
79 Text = get_menu_str(Function, Type, Interval, Row),
80 Menu = observer_cli_lib:render_menu(inet, Text),
81 TopLen = Row * CurPage,
82 InetList = inet_info(Function, Type, TopLen, Interval, Count),
83 {IORows, NewIO} = render_io_rows(LastIO),
84 {PortList, InetRows} = render_inet_rows(InetList, TopLen, InetOpt),
85 LastLine = observer_cli_lib:render_last_line(?LAST_LINE),
86 ?output([?CURSOR_TOP, Menu, IORows, InetRows, LastLine]),
87 observer_cli_store:update(StorePid, Row, PortList),
88 NewInterval =
89 case Function of
90 inet_count -> Interval;
91 inet_window -> 10
92 end,
93 TimeRef = observer_cli_lib:next_redraw(LastTimeRef, NewInterval),
94 receive
95 {new_interval, NewInterval} ->
96 ?output(?CLEAR),
97 render_worker(
98 StorePid,
99 InetOpt#inet{interval = NewInterval},
100 TimeRef,
101 Count + 1,
102 NewIO,
103 AutoRow
104 );
105 quit ->
106 quit;
107 _ ->
108 render_worker(StorePid, InetOpt, TimeRef, Count + 1, NewIO, AutoRow)
109 end.
110
111 render_io_rows({LastIn, LastOut}) ->
112 {{input, In}, {output, Out}} = erlang:statistics(io),
113 {
114 ?render([
115 ?YELLOW,
116 ?W("Byte Input", 14),
117 ?W({byte, In - LastIn}, 12),
118 ?W("Byte Output", 13),
119 ?W({byte, Out - LastOut}, 12),
120 ?W("Total Input", 15),
121 ?W({byte, In}, 17),
122 ?W("Total Output", 15),
123 ?W({byte, Out}, 17)
124 ]),
125 {In, Out}
126 }.
127
128 render_inet_rows([], Rows, #inet{func = inet_count, type = Type}) ->
129 {[], io_lib:format("\e[32;1mGet nothing for recon:inet_count(~p, ~p)\e[0m~n", [Type, Rows])};
130 render_inet_rows([], Rows, #inet{func = inet_window, type = Type, interval = Interval}) ->
131 {[],
132 io_lib:format("\e[32;1mGet nothing for recon:inet_window(~p, ~p, ~p)\e[0m~n", [
133 Type,
134 Rows,
135 Interval
136 ])};
137 render_inet_rows(InetList, Num, #inet{
138 type = Type,
139 pages = Pages,
140 cur_page = Page
141 }) when Type =:= cnt orelse Type =:= oct ->
142 {Unit, RecvType, SendType} = trans_type(Type),
143 Title = title(Type, RecvType, SendType),
144 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(InetList)),
145 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
146 {Port, Value, [{_, Recv}, {_, Send}]} = Item,
147 {memory_used, MemoryUsed} = recon:port_info(Port, memory_used),
148 {io, IO} = recon:port_info(Port, io),
149 Input = proplists:get_value(input, IO),
150 Output = proplists:get_value(output, IO),
151 QueueSize = proplists:get_value(queue_size, MemoryUsed),
152 Memory = proplists:get_value(memory, MemoryUsed),
153 IP = get_remote_ip(Port),
154 {ValueFormat, RecvFormat, SendFormat} = trans_format(Unit, Value, Recv, Send),
155 R = [
156 ?W(Pos, 2),
157 ?W(Port, 16),
158 ValueFormat,
159 RecvFormat,
160 SendFormat,
161 ?W({byte, Output}, 12),
162 ?W({byte, Input}, 12),
163 ?W(QueueSize, 6),
164 ?W({byte, Memory}, 12),
165 ?W(IP, 19)
166 ],
167 Rows = add_choose_color(ChoosePos, Pos, R),
168 {[?render(Rows) | Acc], [{Pos, Port} | Acc1], Pos + 1}
169 end,
170 {Rows, PortList, _} = lists:foldl(
171 FormatFunc,
172 {[], [], Start},
173 lists:sublist(InetList, Start, Num)
174 ),
175 {PortList, [Title | lists:reverse(Rows)]};
176 render_inet_rows(InetList, Num, #inet{type = Type, pages = Pages, cur_page = Page}) ->
177 {Unit, Type1, Type2} = trans_type(Type),
178 Title = title(Type, Type1, Type2),
179 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(InetList)),
180 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
181 {Port, Value, _} = Item,
182 {memory_used, MemoryUsed} = recon:port_info(Port, memory_used),
183 {io, IO} = recon:port_info(Port, io),
184 Input = proplists:get_value(input, IO),
185 Output = proplists:get_value(output, IO),
186 QueueSize = proplists:get_value(queue_size, MemoryUsed),
187 Memory = proplists:get_value(memory, MemoryUsed),
188 IP = get_remote_ip(Port),
189 Packet1 = getstat(Port, erlang:list_to_existing_atom(Type1)),
190 AllPacket =
191 case is_integer(Packet1) of
192 true -> Value + Packet1;
193 false -> Value
194 end,
195 {ValueFormat, Packet1Format, AllFormat} = trans_format(Unit, Value, Packet1, AllPacket),
196 R = [
197 ?W(Pos, 2),
198 ?W(Port, 16),
199 ValueFormat,
200 Packet1Format,
201 AllFormat,
202 ?W({byte, Input}, 12),
203 ?W({byte, Output}, 12),
204 ?W(QueueSize, 6),
205 ?W({byte, Memory}, 12),
206 ?W(IP, 19)
207 ],
208 Rows = add_choose_color(ChoosePos, Pos, R),
209 {[?render(Rows) | Acc], [{Pos, Port} | Acc1], Pos + 1}
210 end,
211 {Rows, PortList, _} = lists:foldl(
212 FormatFunc,
213 {[], [], Start},
214 lists:sublist(InetList, Start, Num)
215 ),
216 {PortList, [Title | lists:reverse(Rows)]}.
217
218 get_menu_str(inet_count, Type, Interval, Rows) ->
219 io_lib:format("recon:inet_count(~p, ~w) Interval:~wms", [Type, Rows, Interval]);
220 get_menu_str(inet_window, Type, Interval, Rows) ->
221 io_lib:format("recon:inet_window(~p, ~w, ~w) Interval:~wms", [Type, Rows, Interval, Interval]).
222
223 inet_info(inet_count, Type, Num, _, _) -> recon:inet_count(Type, Num);
224 inet_info(inet_window, Type, Num, _, 0) -> recon:inet_count(Type, Num);
225 inet_info(inet_window, Type, Num, Ms, _) -> recon:inet_window(Type, Num, Ms).
226
227 getstat(Port, Attr) ->
228 case inet:getstat(Port, [Attr]) of
229 {ok, [{_, Value}]} -> Value;
230 {error, Err} -> inet:format_error(Err)
231 end.
232
233 start_port_view(StorePid, RenderPid, Opts = #view_opts{inet = InetOpt}, AutoJump) ->
234 #inet{cur_page = CurPage, pages = Pages} = InetOpt,
235 {_, CurPos} = lists:keyfind(CurPage, 1, Pages),
236 case observer_cli_store:lookup_pos(StorePid, CurPos) of
237 {CurPos, ChoosePort} ->
238 clean([StorePid, RenderPid]),
239 observer_cli_port:start(ChoosePort, Opts);
240 {_, ChoosePort} when AutoJump ->
241 clean([StorePid, RenderPid]),
242 observer_cli_port:start(ChoosePort, Opts);
243 _ ->
244 manager(StorePid, RenderPid, Opts)
245 end.
246
247 trans_type(cnt) ->
248 {number, "recv_cnt", "send_cnt"};
249 trans_type(oct) ->
250 {byte, "recv_oct", "send_oct"};
251 trans_type(send_cnt) ->
252 {number, "recv_cnt", "cnt"};
253 trans_type(recv_cnt) ->
254 {number, "send_cnt", "cnt"};
255 trans_type(send_oct) ->
256 {byte, "recv_oct", "oct"};
257 trans_type(recv_oct) ->
258 {byte, "send_oct", "oct"}.
259
260 trans_format(byte, Val, Val1, Val2) ->
261 {
262 ?W({byte, Val}, 10),
263 ?W({byte, Val1}, 10),
264 ?W({byte, Val2}, 10)
265 };
266 trans_format(number, Val, Val1, Val2) ->
267 {
268 ?W(Val, 10),
269 ?W(Val1, 10),
270 ?W(Val2, 10)
271 }.
272
273 title(Type, Type1, Type2) ->
274 ?render([
275 ?UNDERLINE,
276 ?GRAY_BG,
277 ?W("NO", 2),
278 ?W("Port", 16),
279 ?W(erlang:atom_to_list(Type), 10),
280 ?W(Type1, 10),
281 ?W(Type2, 10),
282 ?W("output", 12),
283 ?W("input", 12),
284 ?W("queuesize", 6),
285 ?W("memory", 12),
286 ?W("Peername(ip:port)", 19)
287 ]).
288
289 get_remote_ip(P) ->
290 case inet:peername(P) of
291 {ok, {Addr, Port}} ->
292 AddrList = [
293 begin
294 erlang:integer_to_list(A)
295 end
296 || A <- erlang:tuple_to_list(Addr)
297 ],
298 string:join(AddrList, ".") ++ ":" ++ erlang:integer_to_list(Port);
299 {error, Err} ->
300 inet:format_error(Err)
301 end.
302
303 add_choose_color(ChoosePos, ChoosePos, R) -> [?CHOOSE_BG | R];
304 add_choose_color(_ChoosePos, _CurPos, R) -> R.
0 %%% @author zhongwen <zhongwencool@gmail.com>
1 -module(observer_cli_inet).
2
3 -include("observer_cli.hrl").
4
5 %% API
6 -export([start/1]).
7 -export([clean/1]).
8
9 -define(LAST_LINE,
10 "q(quit) ic(inet_count) iw(inet_window) rc(recv_cnt) ro(recv_oct)"
11 " sc(send_cnt) so(send_oct) cnt oct 9(port 9 info) pd/pu(page:down/up)"
12 ).
13
14 -spec start(view_opts()) -> no_return.
15 start(#view_opts{inet = InetOpt, auto_row = AutoRow} = ViewOpts) ->
16 StorePid = observer_cli_store:start(),
17 RenderPid = spawn_link(
18 fun() ->
19 ?output(?CLEAR),
20 {{input, In}, {output, Out}} = erlang:statistics(io),
21 render_worker(StorePid, InetOpt, ?INIT_TIME_REF, 0, {In, Out}, AutoRow)
22 end
23 ),
24 manager(StorePid, RenderPid, ViewOpts).
25
26 -spec clean(list()) -> ok.
27 clean(Pids) -> observer_cli_lib:exit_processes(Pids).
28
29 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
30 %%% Private
31 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
32 manager(StorePid, RenderPid, ViewOpts = #view_opts{inet = InetOpts}) ->
33 #inet{cur_page = CurPage, pages = Pages} = InetOpts,
34 case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [RenderPid, StorePid]) of
35 quit ->
36 observer_cli_lib:exit_processes([StorePid]),
37 erlang:send(RenderPid, quit);
38 {new_interval, NewInterval} ->
39 erlang:send(RenderPid, {new_interval, NewInterval}),
40 NewInet = InetOpts#inet{interval = NewInterval},
41 manager(StorePid, RenderPid, ViewOpts#view_opts{inet = NewInet});
42 Func when Func =:= inet_count; Func =:= inet_window ->
43 clean([StorePid, RenderPid]),
44 start(ViewOpts#view_opts{inet = InetOpts#inet{func = Func}});
45 Type when
46 Type =:= recv_cnt;
47 Type =:= recv_oct;
48 Type =:= send_cnt;
49 Type =:= send_oct;
50 Type =:= cnt;
51 Type =:= oct
52 ->
53 clean([StorePid, RenderPid]),
54 start(ViewOpts#view_opts{inet = InetOpts#inet{type = Type}});
55 {jump, NewPos} ->
56 NewPages = observer_cli_lib:update_page_pos(CurPage, NewPos, Pages),
57 NewInetOpts = InetOpts#inet{pages = NewPages},
58 start_port_view(StorePid, RenderPid, ViewOpts#view_opts{inet = NewInetOpts}, false);
59 jump ->
60 start_port_view(StorePid, RenderPid, ViewOpts, true);
61 page_down_top_n ->
62 NewPage = max(CurPage + 1, 1),
63 NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages),
64 clean([StorePid, RenderPid]),
65 start(ViewOpts#view_opts{inet = InetOpts#inet{cur_page = NewPage, pages = NewPages}});
66 page_up_top_n ->
67 NewPage = max(CurPage - 1, 1),
68 NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages),
69 clean([StorePid, RenderPid]),
70 start(ViewOpts#view_opts{inet = InetOpts#inet{cur_page = NewPage, pages = NewPages}});
71 _ ->
72 manager(StorePid, RenderPid, ViewOpts)
73 end.
74
75 render_worker(StorePid, InetOpt, LastTimeRef, Count, LastIO, AutoRow) ->
76 #inet{func = Function, type = Type, interval = Interval, cur_page = CurPage} = InetOpt,
77 TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow),
78 Row = erlang:max(TerminalRow - 5, 0),
79 Text = get_menu_str(Function, Type, Interval, Row),
80 Menu = observer_cli_lib:render_menu(inet, Text),
81 TopLen = Row * CurPage,
82 InetList = inet_info(Function, Type, TopLen, Interval, Count),
83 {IORows, NewIO} = render_io_rows(LastIO),
84 {PortList, InetRows} = render_inet_rows(InetList, TopLen, InetOpt),
85 LastLine = observer_cli_lib:render_last_line(?LAST_LINE),
86 ?output([?CURSOR_TOP, Menu, IORows, InetRows, LastLine]),
87 observer_cli_store:update(StorePid, Row, PortList),
88 NewInterval =
89 case Function of
90 inet_count -> Interval;
91 inet_window -> 10
92 end,
93 TimeRef = observer_cli_lib:next_redraw(LastTimeRef, NewInterval),
94 receive
95 {new_interval, NewInterval} ->
96 ?output(?CLEAR),
97 render_worker(
98 StorePid,
99 InetOpt#inet{interval = NewInterval},
100 TimeRef,
101 Count + 1,
102 NewIO,
103 AutoRow
104 );
105 quit ->
106 quit;
107 _ ->
108 render_worker(StorePid, InetOpt, TimeRef, Count + 1, NewIO, AutoRow)
109 end.
110
111 render_io_rows({LastIn, LastOut}) ->
112 {{input, In}, {output, Out}} = erlang:statistics(io),
113 {
114 ?render([
115 ?YELLOW,
116 ?W("Byte Input", 14),
117 ?W({byte, In - LastIn}, 12),
118 ?W("Byte Output", 13),
119 ?W({byte, Out - LastOut}, 12),
120 ?W("Total Input", 15),
121 ?W({byte, In}, 17),
122 ?W("Total Output", 15),
123 ?W({byte, Out}, 17)
124 ]),
125 {In, Out}
126 }.
127
128 render_inet_rows([], Rows, #inet{func = inet_count, type = Type}) ->
129 {[], io_lib:format("\e[32;1mGet nothing for recon:inet_count(~p, ~p)\e[0m~n", [Type, Rows])};
130 render_inet_rows([], Rows, #inet{func = inet_window, type = Type, interval = Interval}) ->
131 {[],
132 io_lib:format("\e[32;1mGet nothing for recon:inet_window(~p, ~p, ~p)\e[0m~n", [
133 Type,
134 Rows,
135 Interval
136 ])};
137 render_inet_rows(InetList, Num, #inet{
138 type = Type,
139 pages = Pages,
140 cur_page = Page
141 }) when Type =:= cnt orelse Type =:= oct ->
142 {Unit, RecvType, SendType} = trans_type(Type),
143 Title = title(Type, RecvType, SendType),
144 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(InetList)),
145 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
146 {Port, Value, [{_, Recv}, {_, Send}]} = Item,
147 {memory_used, MemoryUsed} = recon:port_info(Port, memory_used),
148 {io, IO} = recon:port_info(Port, io),
149 Input = proplists:get_value(input, IO),
150 Output = proplists:get_value(output, IO),
151 QueueSize = proplists:get_value(queue_size, MemoryUsed),
152 Memory = proplists:get_value(memory, MemoryUsed),
153 IP = get_remote_ip(Port),
154 {ValueFormat, RecvFormat, SendFormat} = trans_format(Unit, Value, Recv, Send),
155 R = [
156 ?W(Pos, 2),
157 ?W(Port, 16),
158 ValueFormat,
159 RecvFormat,
160 SendFormat,
161 ?W({byte, Output}, 12),
162 ?W({byte, Input}, 12),
163 ?W(QueueSize, 6),
164 ?W({byte, Memory}, 12),
165 ?W(IP, 19)
166 ],
167 Rows = add_choose_color(ChoosePos, Pos, R),
168 {[?render(Rows) | Acc], [{Pos, Port} | Acc1], Pos + 1}
169 end,
170 {Rows, PortList, _} = lists:foldl(
171 FormatFunc,
172 {[], [], Start},
173 lists:sublist(InetList, Start, Num)
174 ),
175 {PortList, [Title | lists:reverse(Rows)]};
176 render_inet_rows(InetList, Num, #inet{type = Type, pages = Pages, cur_page = Page}) ->
177 {Unit, Type1, Type2} = trans_type(Type),
178 Title = title(Type, Type1, Type2),
179 {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(InetList)),
180 FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
181 {Port, Value, _} = Item,
182 {memory_used, MemoryUsed} = recon:port_info(Port, memory_used),
183 {io, IO} = recon:port_info(Port, io),
184 Input = proplists:get_value(input, IO),
185 Output = proplists:get_value(output, IO),
186 QueueSize = proplists:get_value(queue_size, MemoryUsed),
187 Memory = proplists:get_value(memory, MemoryUsed),
188 IP = get_remote_ip(Port),
189 Packet1 = getstat(Port, erlang:list_to_existing_atom(Type1)),
190 AllPacket =
191 case is_integer(Packet1) of
192 true -> Value + Packet1;
193 false -> Value
194 end,
195 {ValueFormat, Packet1Format, AllFormat} = trans_format(Unit, Value, Packet1, AllPacket),
196 R = [
197 ?W(Pos, 2),
198 ?W(Port, 16),
199 ValueFormat,
200 Packet1Format,
201 AllFormat,
202 ?W({byte, Input}, 12),
203 ?W({byte, Output}, 12),
204 ?W(QueueSize, 6),
205 ?W({byte, Memory}, 12),
206 ?W(IP, 19)
207 ],
208 Rows = add_choose_color(ChoosePos, Pos, R),
209 {[?render(Rows) | Acc], [{Pos, Port} | Acc1], Pos + 1}
210 end,
211 {Rows, PortList, _} = lists:foldl(
212 FormatFunc,
213 {[], [], Start},
214 lists:sublist(InetList, Start, Num)
215 ),
216 {PortList, [Title | lists:reverse(Rows)]}.
217
218 get_menu_str(inet_count, Type, Interval, Rows) ->
219 io_lib:format("recon:inet_count(~p, ~w) Interval:~wms", [Type, Rows, Interval]);
220 get_menu_str(inet_window, Type, Interval, Rows) ->
221 io_lib:format("recon:inet_window(~p, ~w, ~w) Interval:~wms", [Type, Rows, Interval, Interval]).
222
223 inet_info(inet_count, Type, Num, _, _) -> recon:inet_count(Type, Num);
224 inet_info(inet_window, Type, Num, _, 0) -> recon:inet_count(Type, Num);
225 inet_info(inet_window, Type, Num, Ms, _) -> recon:inet_window(Type, Num, Ms).
226
227 getstat(Port, Attr) ->
228 case inet:getstat(Port, [Attr]) of
229 {ok, [{_, Value}]} -> Value;
230 {error, Err} -> inet:format_error(Err)
231 end.
232
233 start_port_view(StorePid, RenderPid, Opts = #view_opts{inet = InetOpt}, AutoJump) ->
234 #inet{cur_page = CurPage, pages = Pages} = InetOpt,
235 {_, CurPos} = lists:keyfind(CurPage, 1, Pages),
236 case observer_cli_store:lookup_pos(StorePid, CurPos) of
237 {CurPos, ChoosePort} ->
238 clean([StorePid, RenderPid]),
239 observer_cli_port:start(ChoosePort, Opts);
240 {_, ChoosePort} when AutoJump ->
241 clean([StorePid, RenderPid]),
242 observer_cli_port:start(ChoosePort, Opts);
243 _ ->
244 manager(StorePid, RenderPid, Opts)
245 end.
246
247 trans_type(cnt) ->
248 {number, "recv_cnt", "send_cnt"};
249 trans_type(oct) ->
250 {byte, "recv_oct", "send_oct"};
251 trans_type(send_cnt) ->
252 {number, "recv_cnt", "cnt"};
253 trans_type(recv_cnt) ->
254 {number, "send_cnt", "cnt"};
255 trans_type(send_oct) ->
256 {byte, "recv_oct", "oct"};
257 trans_type(recv_oct) ->
258 {byte, "send_oct", "oct"}.
259
260 trans_format(byte, Val, Val1, Val2) ->
261 {
262 ?W({byte, Val}, 10),
263 ?W({byte, Val1}, 10),
264 ?W({byte, Val2}, 10)
265 };
266 trans_format(number, Val, Val1, Val2) ->
267 {
268 ?W(Val, 10),
269 ?W(Val1, 10),
270 ?W(Val2, 10)
271 }.
272
273 title(Type, Type1, Type2) ->
274 ?render([
275 ?UNDERLINE,
276 ?GRAY_BG,
277 ?W("NO", 2),
278 ?W("Port", 16),
279 ?W(erlang:atom_to_list(Type), 10),
280 ?W(Type1, 10),
281 ?W(Type2, 10),
282 ?W("output", 12),
283 ?W("input", 12),
284 ?W("queuesize", 6),
285 ?W("memory", 12),
286 ?W("Peername(ip:port)", 19)
287 ]).
288
289 get_remote_ip(P) ->
290 case inet:peername(P) of
291 {ok, {Addr, Port}} ->
292 AddrList = [
293 begin
294 erlang:integer_to_list(A)
295 end
296 || A <- erlang:tuple_to_list(Addr)
297 ],
298 string:join(AddrList, ".") ++ ":" ++ erlang:integer_to_list(Port);
299 {error, Err} ->
300 inet:format_error(Err)
301 end.
302
303 add_choose_color(ChoosePos, ChoosePos, R) -> [?CHOOSE_BG | R];
304 add_choose_color(_ChoosePos, _CurPos, R) -> R.
234234
235235 tidy_format_args([], _NeedLine, FAcc, AAcc) ->
236236 {FAcc, AAcc};
237 tidy_format_args([{extend, A, W} | Rest], true, FAcc, AAcc) ->
237 tidy_format_args([{width, A, W} | Rest], true, FAcc, AAcc) ->
238238 WBin = erlang:integer_to_binary(W),
239239 F = <<"~-", WBin/binary, ".", WBin/binary, "ts">>,
240240 tidy_format_args(Rest, false, [F | FAcc], [to_str(A) | AAcc]);
241 tidy_format_args([{extend, A, W} | Rest], false, FAcc, AAcc) ->
241 tidy_format_args([{width, A, W} | Rest], false, FAcc, AAcc) ->
242242 WBin = erlang:integer_to_binary(W),
243243 F = <<"~-", WBin/binary, ".", WBin/binary, "ts", ?I/binary>>,
244244 tidy_format_args(Rest, false, [F | FAcc], [to_str(A) | AAcc]);
245 tidy_format_args([{extend_color, C, A, W} | Rest], true, FAcc, AAcc) ->
245 tidy_format_args([{width_color, C, A, W} | Rest], true, FAcc, AAcc) ->
246246 WBin = erlang:integer_to_binary(W),
247247 F = <<C/binary, "~-", WBin/binary, ".", WBin/binary, "ts">>,
248248 tidy_format_args(Rest, false, [F | FAcc], [to_str(A) | AAcc]);
249 tidy_format_args([{extend_color, C, A, W} | Rest], false, FAcc, AAcc) ->
249 tidy_format_args([{width_color, C, A, W} | Rest], false, FAcc, AAcc) ->
250250 WBin = erlang:integer_to_binary(W),
251251 F = <<C/binary, "~-", WBin/binary, ".", WBin/binary, "ts", ?I2/binary>>,
252252 tidy_format_args(Rest, false, [F | FAcc], [to_str(A) | AAcc]);
253 tidy_format_args([{extend_color_2, C, A, W} | Rest], true, FAcc, AAcc) ->
253 tidy_format_args([{width_color_2, C, A, W} | Rest], true, FAcc, AAcc) ->
254254 WBin = erlang:integer_to_binary(W),
255255 F = <<C/binary, "~-", WBin/binary, ".", WBin/binary, "ts">>,
256256 tidy_format_args(Rest, false, [F | FAcc], [to_str(A) | AAcc]);
257 tidy_format_args([{extend_color_2, C, A, W} | Rest], false, FAcc, AAcc) ->
257 tidy_format_args([{width_color_2, C, A, W} | Rest], false, FAcc, AAcc) ->
258258 WBin = erlang:integer_to_binary(W),
259259 F = <<C/binary, "~-", WBin/binary, ".", WBin/binary, "ts", ?RESET/binary, ?I2/binary>>,
260260 tidy_format_args(Rest, false, [F | FAcc], [to_str(A) | AAcc]);
361361 hide;
362362 "`\n" ->
363363 scheduler_usage;
364 %% {error, estale}|{error, terminated}
365 {error, _Reason} ->
366 quit;
364367 Number ->
365368 parse_integer(Number)
366369 end.
443446 after 100 -> ok
444447 end.
445448
446 -spec sublist(list(), integer(), integer()) -> list().
449 -spec sublist(list(), integer(), integer()) -> {integer(), list()}.
447450 sublist(AllEts, Rows, CurPage) ->
448451 SortEts = recon_lib:sublist_top_n_attrs(AllEts, Rows * CurPage),
449452 Start = Rows * (CurPage - 1) + 1,
450453 case erlang:length(SortEts) >= Start of
451454 true ->
452 lists:sublist(SortEts, Start, Rows);
455 {Start, lists:sublist(SortEts, Start, Rows)};
453456 false ->
454 []
457 {Start, []}
455458 end.
456459
457460 -spec sbcs_to_mbcs(list(), list()) -> list().
0 %%% @author zhongwen <zhongwencool@gmail.com>
1 -module(observer_cli_mnesia).
2
3 %% API
4 -export([start/1]).
5 -export([clean/1]).
6
7 -include("observer_cli.hrl").
8
9 -define(LAST_LINE,
10 "q(quit) s(sort by size) m(sort by memory) pd/pu(page:down/up) F/B(forward/back)"
11 " hide(swith between hide and display system table)"
12 ).
13
14 -spec start(#view_opts{}) -> any().
15 start(
16 #view_opts{
17 db = #db{
18 interval = MillSecond,
19 hide_sys = HideSys,
20 cur_page = CurPage,
21 attr = Attr
22 },
23 auto_row = AutoRow
24 } = HomeOpts
25 ) ->
26 Pid = spawn_link(fun() ->
27 ?output(?CLEAR),
28 render_worker(MillSecond, ?INIT_TIME_REF, HideSys, AutoRow, Attr, CurPage)
29 end),
30 manager(Pid, HomeOpts).
31
32 -spec clean(list()) -> ok.
33 clean(Pids) -> observer_cli_lib:exit_processes(Pids).
34
35 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
36 %%% Private
37 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
38
39 manager(ChildPid, #view_opts{db = DBOpts = #db{cur_page = CurPage, hide_sys = Hide}} = HomeOpts) ->
40 case observer_cli_lib:parse_cmd(HomeOpts, ?MODULE, [ChildPid]) of
41 quit ->
42 erlang:send(ChildPid, quit);
43 {new_interval, NewMs} = Msg ->
44 erlang:send(ChildPid, Msg),
45 manager(ChildPid, HomeOpts#view_opts{db = DBOpts#db{interval = NewMs}});
46 hide ->
47 NewHide = not Hide,
48 erlang:send(ChildPid, {system_table, NewHide}),
49 manager(ChildPid, HomeOpts#view_opts{db = DBOpts#db{hide_sys = NewHide}});
50 size ->
51 clean([ChildPid]),
52 start(HomeOpts#view_opts{db = DBOpts#db{attr = size}});
53 %% Home
54 {func, proc_count, memory} ->
55 clean([ChildPid]),
56 start(HomeOpts#view_opts{db = DBOpts#db{attr = memory}});
57 page_down_top_n ->
58 NewPage = max(CurPage + 1, 1),
59 clean([ChildPid]),
60 start(HomeOpts#view_opts{db = DBOpts#db{cur_page = NewPage}});
61 page_up_top_n ->
62 NewPage = max(CurPage - 1, 1),
63 clean([ChildPid]),
64 start(HomeOpts#view_opts{db = DBOpts#db{cur_page = NewPage}});
65 _ ->
66 manager(ChildPid, HomeOpts)
67 end.
68
69 render_worker(Interval, LastTimeRef, HideSystemTable, AutoRow, Attr, CurPage) ->
70 TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow),
71 Rows = erlang:max(TerminalRow - 5, 0),
72 Text =
73 "Interval: " ++
74 integer_to_list(Interval) ++
75 "ms" ++
76 " HideSystemTable:" ++ atom_to_list(HideSystemTable),
77 Menu = observer_cli_lib:render_menu(mnesia, Text),
78 LastLine = observer_cli_lib:render_last_line(?LAST_LINE),
79 case get_table_list(HideSystemTable, Attr) of
80 {error, Reason} ->
81 ErrInfo = io_lib:format("Mnesia Error ~p~n", [Reason]),
82 ?output([?CURSOR_TOP, Menu, ErrInfo, LastLine]);
83 MnesiaList ->
84 Info = render_mnesia(MnesiaList, Attr, Rows, CurPage),
85 ?output([?CURSOR_TOP, Menu, Info, LastLine])
86 end,
87 TimeRef = observer_cli_lib:next_redraw(LastTimeRef, Interval),
88 receive
89 quit ->
90 quit;
91 {new_interval, NewInterval} ->
92 render_worker(NewInterval, TimeRef, HideSystemTable, AutoRow, Attr, CurPage);
93 {system_table, NewHideSystemTable} ->
94 render_worker(Interval, TimeRef, NewHideSystemTable, AutoRow, Attr, CurPage);
95 _ ->
96 render_worker(Interval, TimeRef, HideSystemTable, AutoRow, Attr, CurPage)
97 end.
98
99 render_mnesia(MnesiaList, Attr, Rows, CurPage) ->
100 SortMnesia = observer_cli_lib:sublist(MnesiaList, Rows, CurPage),
101 {MemColor, SizeColor} =
102 case Attr of
103 memory -> {?RED_BG, ?GRAY_BG};
104 _ -> {?GRAY_BG, ?RED_BG}
105 end,
106 Title = ?render([
107 ?UNDERLINE,
108 ?W2(?GRAY_BG, "Name", 25),
109 ?UNDERLINE,
110 ?W2(MemColor, " Memory ", 16),
111 ?UNDERLINE,
112 ?W2(SizeColor, "Size", 16),
113 ?UNDERLINE,
114 ?W2(?GRAY_BG, "Type", 12),
115 ?UNDERLINE,
116 ?W2(?GRAY_BG, "Storage", 15),
117 ?UNDERLINE,
118 ?W2(?GRAY_BG, "Owner", 14),
119 ?UNDERLINE,
120 ?W2(?GRAY_BG, "Index", 11),
121 ?UNDERLINE,
122 ?W2(?GRAY_BG, "Reg_name", 20)
123 ]),
124 View = [
125 begin
126 Name = proplists:get_value(name, Mnesia),
127 Memory = proplists:get_value(memory, Mnesia),
128 Size = proplists:get_value(size, Mnesia),
129 Type = proplists:get_value(type, Mnesia),
130 RegName = proplists:get_value(reg_name, Mnesia),
131 Index = proplists:get_value(index, Mnesia),
132 Owner = proplists:get_value(owner, Mnesia),
133 Storage = proplists:get_value(storage, Mnesia),
134 ?render([
135 ?W(Name, 24),
136 ?W({byte, Memory}, 14),
137 ?W(Size, 14),
138 ?W(Type, 10),
139 ?W(Storage, 13),
140 ?W(Owner, 12),
141 ?W(Index, 9),
142 ?W(RegName, 19)
143 ])
144 end
145 || {_, _, Mnesia} <- SortMnesia
146 ],
147 [Title | View].
148
149 mnesia_tables() ->
150 [
151 ir_AliasDef,
152 ir_ArrayDef,
153 ir_AttributeDef,
154 ir_ConstantDef,
155 ir_Contained,
156 ir_Container,
157 ir_EnumDef,
158 ir_ExceptionDef,
159 ir_IDLType,
160 ir_IRObject,
161 ir_InterfaceDef,
162 ir_ModuleDef,
163 ir_ORB,
164 ir_OperationDef,
165 ir_PrimitiveDef,
166 ir_Repository,
167 ir_SequenceDef,
168 ir_StringDef,
169 ir_StructDef,
170 ir_TypedefDef,
171 ir_UnionDef,
172 logTable,
173 logTransferTable,
174 mesh_meas,
175 mesh_type,
176 mnesia_clist,
177 orber_CosNaming,
178 orber_objkeys,
179 user
180 ].
181
182 get_table_list(HideSys, Attr) ->
183 Owner = ets:info(schema, owner),
184 case Owner of
185 undefined -> {error, "Mnesia is not running on: " ++ atom_to_list(node())};
186 _ -> get_table_list2(Owner, HideSys, Attr)
187 end.
188
189 get_table_list2(Owner, HideSys, Attr) ->
190 {registered_name, RegName} = process_info(Owner, registered_name),
191 WordSize = erlang:system_info(wordsize),
192 CollectFun = fun(Id, Acc) ->
193 case HideSys andalso ordsets:is_element(Id, mnesia_tables()) orelse Id =:= schema of
194 %% ignore system table
195 true ->
196 Acc;
197 false ->
198 Storage = mnesia:table_info(Id, storage_type),
199 Size = mnesia:table_info(Id, size),
200 Memory = mnesia:table_info(Id, memory) * WordSize,
201 Tab0 = [
202 {name, Id},
203 {owner, Owner},
204 {size, Size},
205 {reg_name, RegName},
206 {type, mnesia:table_info(Id, type)},
207 {memory, Memory},
208 {storage, Storage},
209 {index, mnesia:table_info(Id, index)}
210 ],
211 Tab =
212 case Storage of
213 _ when Storage =:= ram_copies orelse Storage =:= disc_copies ->
214 [
215 {fixed, ets:info(Id, fixed)},
216 {compressed, ets:info(Id, compressed)}
217 | Tab0
218 ];
219 disc_only_copies ->
220 [{fixed, dets:info(Id, safe_fixed)} | Tab0];
221 _ ->
222 Tab0
223 end,
224 [{0, proplists:get_value(Attr, Tab), Tab} | Acc]
225 end
226 end,
227 lists:foldl(CollectFun, [], mnesia:system_info(tables)).
0 %%% @author zhongwen <zhongwencool@gmail.com>
1 -module(observer_cli_mnesia).
2
3 %% API
4 -export([start/1]).
5 -export([clean/1]).
6
7 -include("observer_cli.hrl").
8
9 -define(LAST_LINE,
10 "q(quit) s(sort by size) m(sort by memory) pd/pu(page:down/up) F/B(forward/back)"
11 " hide(swith between hide and display system table)"
12 ).
13
14 -spec start(#view_opts{}) -> any().
15 start(
16 #view_opts{
17 db = #db{
18 interval = MillSecond,
19 hide_sys = HideSys,
20 cur_page = CurPage,
21 attr = Attr
22 },
23 auto_row = AutoRow
24 } = HomeOpts
25 ) ->
26 Pid = spawn_link(fun() ->
27 ?output(?CLEAR),
28 render_worker(MillSecond, ?INIT_TIME_REF, HideSys, AutoRow, Attr, CurPage)
29 end),
30 manager(Pid, HomeOpts).
31
32 -spec clean(list()) -> ok.
33 clean(Pids) -> observer_cli_lib:exit_processes(Pids).
34
35 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
36 %%% Private
37 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
38
39 manager(ChildPid, #view_opts{db = DBOpts = #db{cur_page = CurPage, hide_sys = Hide}} = HomeOpts) ->
40 case observer_cli_lib:parse_cmd(HomeOpts, ?MODULE, [ChildPid]) of
41 quit ->
42 erlang:send(ChildPid, quit);
43 {new_interval, NewMs} = Msg ->
44 erlang:send(ChildPid, Msg),
45 manager(ChildPid, HomeOpts#view_opts{db = DBOpts#db{interval = NewMs}});
46 hide ->
47 NewHide = not Hide,
48 erlang:send(ChildPid, {system_table, NewHide}),
49 manager(ChildPid, HomeOpts#view_opts{db = DBOpts#db{hide_sys = NewHide}});
50 size ->
51 clean([ChildPid]),
52 start(HomeOpts#view_opts{db = DBOpts#db{attr = size}});
53 %% Home
54 {func, proc_count, memory} ->
55 clean([ChildPid]),
56 start(HomeOpts#view_opts{db = DBOpts#db{attr = memory}});
57 page_down_top_n ->
58 NewPage = max(CurPage + 1, 1),
59 clean([ChildPid]),
60 start(HomeOpts#view_opts{db = DBOpts#db{cur_page = NewPage}});
61 page_up_top_n ->
62 NewPage = max(CurPage - 1, 1),
63 clean([ChildPid]),
64 start(HomeOpts#view_opts{db = DBOpts#db{cur_page = NewPage}});
65 _ ->
66 manager(ChildPid, HomeOpts)
67 end.
68
69 render_worker(Interval, LastTimeRef, HideSystemTable, AutoRow, Attr, CurPage) ->
70 TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow),
71 Rows = erlang:max(TerminalRow - 5, 0),
72 Text =
73 "Interval: " ++
74 integer_to_list(Interval) ++
75 "ms" ++
76 " HideSystemTable:" ++ atom_to_list(HideSystemTable),
77 Menu = observer_cli_lib:render_menu(mnesia, Text),
78 LastLine = observer_cli_lib:render_last_line(?LAST_LINE),
79 case get_table_list(HideSystemTable, Attr) of
80 {error, Reason} ->
81 ErrInfo = io_lib:format("Mnesia Error ~p~n", [Reason]),
82 ?output([?CURSOR_TOP, Menu, ErrInfo, LastLine]);
83 MnesiaList ->
84 Info = render_mnesia(MnesiaList, Attr, Rows, CurPage),
85 ?output([?CURSOR_TOP, Menu, Info, LastLine])
86 end,
87 TimeRef = observer_cli_lib:next_redraw(LastTimeRef, Interval),
88 receive
89 quit ->
90 quit;
91 {new_interval, NewInterval} ->
92 render_worker(NewInterval, TimeRef, HideSystemTable, AutoRow, Attr, CurPage);
93 {system_table, NewHideSystemTable} ->
94 render_worker(Interval, TimeRef, NewHideSystemTable, AutoRow, Attr, CurPage);
95 _ ->
96 render_worker(Interval, TimeRef, HideSystemTable, AutoRow, Attr, CurPage)
97 end.
98
99 render_mnesia(MnesiaList, Attr, Rows, CurPage) ->
100 {_StartPos, SortMnesia} = observer_cli_lib:sublist(MnesiaList, Rows, CurPage),
101 {MemColor, SizeColor} =
102 case Attr of
103 memory -> {?RED_BG, ?GRAY_BG};
104 _ -> {?GRAY_BG, ?RED_BG}
105 end,
106 Title = ?render([
107 ?UNDERLINE,
108 ?W2(?GRAY_BG, "Name", 25),
109 ?UNDERLINE,
110 ?W2(MemColor, " Memory ", 16),
111 ?UNDERLINE,
112 ?W2(SizeColor, "Size", 16),
113 ?UNDERLINE,
114 ?W2(?GRAY_BG, "Type", 12),
115 ?UNDERLINE,
116 ?W2(?GRAY_BG, "Storage", 15),
117 ?UNDERLINE,
118 ?W2(?GRAY_BG, "Owner", 14),
119 ?UNDERLINE,
120 ?W2(?GRAY_BG, "Index", 11),
121 ?UNDERLINE,
122 ?W2(?GRAY_BG, "Reg_name", 20)
123 ]),
124 View = [
125 begin
126 Name = proplists:get_value(name, Mnesia),
127 Memory = proplists:get_value(memory, Mnesia),
128 Size = proplists:get_value(size, Mnesia),
129 Type = proplists:get_value(type, Mnesia),
130 RegName = proplists:get_value(reg_name, Mnesia),
131 Index = proplists:get_value(index, Mnesia),
132 Owner = proplists:get_value(owner, Mnesia),
133 Storage = proplists:get_value(storage, Mnesia),
134 ?render([
135 ?W(Name, 24),
136 ?W({byte, Memory}, 14),
137 ?W(Size, 14),
138 ?W(Type, 10),
139 ?W(Storage, 13),
140 ?W(Owner, 12),
141 ?W(Index, 9),
142 ?W(RegName, 19)
143 ])
144 end
145 || {_, _, Mnesia} <- SortMnesia
146 ],
147 [Title | View].
148
149 mnesia_tables() ->
150 [
151 ir_AliasDef,
152 ir_ArrayDef,
153 ir_AttributeDef,
154 ir_ConstantDef,
155 ir_Contained,
156 ir_Container,
157 ir_EnumDef,
158 ir_ExceptionDef,
159 ir_IDLType,
160 ir_IRObject,
161 ir_InterfaceDef,
162 ir_ModuleDef,
163 ir_ORB,
164 ir_OperationDef,
165 ir_PrimitiveDef,
166 ir_Repository,
167 ir_SequenceDef,
168 ir_StringDef,
169 ir_StructDef,
170 ir_TypedefDef,
171 ir_UnionDef,
172 logTable,
173 logTransferTable,
174 mesh_meas,
175 mesh_type,
176 mnesia_clist,
177 orber_CosNaming,
178 orber_objkeys,
179 user
180 ].
181
182 get_table_list(HideSys, Attr) ->
183 Owner = ets:info(schema, owner),
184 case Owner of
185 undefined -> {error, "Mnesia is not running on: " ++ atom_to_list(node())};
186 _ -> get_table_list2(Owner, HideSys, Attr)
187 end.
188
189 get_table_list2(Owner, HideSys, Attr) ->
190 {registered_name, RegName} = process_info(Owner, registered_name),
191 WordSize = erlang:system_info(wordsize),
192 CollectFun = fun(Id, Acc) ->
193 case HideSys andalso ordsets:is_element(Id, mnesia_tables()) orelse Id =:= schema of
194 %% ignore system table
195 true ->
196 Acc;
197 false ->
198 Storage = mnesia:table_info(Id, storage_type),
199 Size = mnesia:table_info(Id, size),
200 Memory = mnesia:table_info(Id, memory) * WordSize,
201 Tab0 = [
202 {name, Id},
203 {owner, Owner},
204 {size, Size},
205 {reg_name, RegName},
206 {type, mnesia:table_info(Id, type)},
207 {memory, Memory},
208 {storage, Storage},
209 {index, mnesia:table_info(Id, index)}
210 ],
211 Tab =
212 case Storage of
213 _ when Storage =:= ram_copies orelse Storage =:= disc_copies ->
214 [
215 {fixed, ets:info(Id, fixed)},
216 {compressed, ets:info(Id, compressed)}
217 | Tab0
218 ];
219 disc_only_copies ->
220 [{fixed, dets:info(Id, safe_fixed)} | Tab0];
221 _ ->
222 Tab0
223 end,
224 [{0, proplists:get_value(Attr, Tab), Tab} | Acc]
225 end
226 end,
227 lists:foldl(CollectFun, [], mnesia:system_info(tables)).
2929 -spec start(ViewOpts) -> no_return() when ViewOpts :: view_opts().
3030 start(#view_opts{plug = Plugs, auto_row = AutoRow} = ViewOpts) ->
3131 NewPlugs = init_config(Plugs),
32 SheetCache = ets:new(?MODULE, [set, public]),
3233 Pid = spawn_link(fun() ->
3334 ?output(?CLEAR),
34 render_worker(?INIT_TIME_REF, NewPlugs, AutoRow, undefined, undefined)
35 render_worker(?INIT_TIME_REF, NewPlugs, AutoRow, SheetCache, undefined, undefined)
3536 end),
36 manager(Pid, ViewOpts#view_opts{plug = NewPlugs}).
37 manager(Pid, SheetCache, ViewOpts#view_opts{plug = NewPlugs}).
3738
3839 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3940 %%% Private
4849 Config = maps:merge(
4950 #{
5051 cur_page => 1,
52 cur_row => 1,
5153 sort_column => 2,
5254 interval => 1500,
5355 sheet_width => SheetWidth
6365 init_config(Plugs) ->
6466 Plugs.
6567
66 manager(ChildPid, ViewOpts) ->
68 manager(ChildPid, SheetCache, ViewOpts) ->
6769 #view_opts{plug = PlugOpts = #plug{cur_index = CurIndex, plugs = Plugs}} = ViewOpts,
6870 case parse_cmd() of
6971 quit ->
72 ets:delete(SheetCache),
7073 erlang:send(ChildPid, quit);
7174 go_home ->
7275 observer_cli_lib:exit_processes([ChildPid]),
76 ets:delete(SheetCache),
7377 observer_cli:start(ViewOpts);
7478 {new_interval, NewMs} ->
7579 observer_cli_lib:exit_processes([ChildPid]),
8993 NewPlugs = update_plugins(CurIndex, Plugs, #{cur_page => NewPage}),
9094 observer_cli_lib:exit_processes([ChildPid]),
9195 start(ViewOpts#view_opts{plug = PlugOpts#plug{plugs = NewPlugs}});
96 {jump, CurRow} ->
97 case ets:lookup(SheetCache, CurRow) of
98 [{CurRow, Items}] ->
99 case [I || I <- Items, is_pid(I)] of
100 [ChoosePid | _] ->
101 observer_cli_lib:exit_processes([ChildPid]),
102 ets:delete(SheetCache),
103 NewPlugs = update_plugins(CurIndex, Plugs, #{cur_row => CurRow}),
104 NewViewOpts = ViewOpts#view_opts{
105 plug = PlugOpts#plug{plugs = NewPlugs}
106 },
107 observer_cli_process:start(plugin, ChoosePid, NewViewOpts);
108 [] ->
109 manager(ChildPid, SheetCache, ViewOpts)
110 end;
111 _ ->
112 manager(ChildPid, SheetCache, ViewOpts)
113 end;
114 jump ->
115 CurPlugs = maps:get(CurIndex, Plugs),
116 CurRow = maps:get(cur_row, CurPlugs),
117 case ets:lookup(SheetCache, CurRow) of
118 [{CurRow, Items}] ->
119 case [I || I <- Items, is_pid(I)] of
120 [ChoosePid | _] ->
121 observer_cli_lib:exit_processes([ChildPid]),
122 ets:delete(SheetCache),
123 observer_cli_process:start(plugin, ChoosePid, ViewOpts);
124 [] ->
125 manager(ChildPid, SheetCache, ViewOpts)
126 end;
127 _ ->
128 manager(ChildPid, SheetCache, ViewOpts)
129 end;
92130 {input_str, Cmd} ->
93131 case maybe_shortcut(Cmd, ViewOpts) of
94132 {ok, menu, Index} ->
99137 observer_cli_lib:exit_processes([ChildPid]),
100138 start(ViewOpts#view_opts{plug = PlugOpts#plug{plugs = NewPlugs}});
101139 {error, _} ->
102 manager(ChildPid, ViewOpts)
140 manager(ChildPid, SheetCache, ViewOpts)
103141 end;
104142 _ ->
105 manager(ChildPid, ViewOpts)
143 manager(ChildPid, SheetCache, ViewOpts)
106144 end.
107145
108146 update_plugins(CurIndex, Lists, UpdateItems) ->
148186 LastTimeRef,
149187 #plug{cur_index = CurIndex, plugs = Plugs} = PlugInfo,
150188 AutoRow,
189 SheetCache,
151190 PrevAttrs,
152191 PrevSheet
153192 ) ->
159198 {SheetLine, NewSheet} = render_sheet(
160199 erlang:max(0, TerminalRow - LabelLine - 4),
161200 CurPlug,
201 SheetCache,
162202 PrevSheet
163203 ),
164204 LastText = io_lib:format(?LAST_LINE, [Interval, CurPage]),
165 LastLine = ?render([?UNDERLINE, ?GRAY_BG, ?W(LastText, SheetWidth)]),
205 LastLine = ?render([?UNDERLINE, ?GRAY_BG, ?W(LastText, SheetWidth + 4)]),
166206 ?output([?CURSOR_TOP, Menu, Labels, SheetLine, LastLine]),
167207 NextTimeRef = observer_cli_lib:next_redraw(LastTimeRef, Interval),
168208 receive
169209 quit -> quit;
170 _ -> render_worker(NextTimeRef, PlugInfo, AutoRow, NewAttrs, NewSheet)
210 _ -> render_worker(NextTimeRef, PlugInfo, AutoRow, SheetCache, NewAttrs, NewSheet)
171211 end;
172212 error ->
173213 Menu = ?render([
188228 %% forward
189229 "F\n" -> page_down_top_n;
190230 "q\n" -> quit;
231 "\n" -> jump;
232 %% {error, estale}|{error, terminated}
233 {error, _Reason} -> quit;
191234 Number -> observer_cli_lib:parse_integer(Number)
192235 end.
193236
203246 "|",
204247 Title
205248 ],
206 SheetWidth + Num * 21 + 1
249 SheetWidth + Num * 21 + 5
207250 ),
208251 Time
209252 ]).
231274 L = [
232275 begin
233276 #{content := Content, width := Width} = Item,
277 Value =
278 case Content of
279 {percent, Float} -> observer_cli_lib:to_percent(Float);
280 _ -> Content
281 end,
234282 case maps:find(color, Item) of
235 {ok, Color} -> ?W2(Color, Content, Width);
236 error -> ?W(Content, Width)
283 {ok, Color} -> ?W2(Color, Value, Width);
284 error -> ?W(Value, Width)
237285 end
238286 end
239287 || Item <- Label
248296 {[], 0, PrevAttrs}
249297 end.
250298
251 render_sheet(Rows, #{sort_column := SortColumn, cur_page := CurPage, module := Module}, PrevSheet) ->
299 render_sheet(Rows, Plug, SheetCache, PrevSheet) ->
300 #{
301 sort_column := SortColumn,
302 cur_page := CurPage,
303 cur_row := CurRow,
304 module := Module
305 } = Plug,
252306 try
253307 {Headers, Widths} = render_sheet_header(Module, SortColumn),
254 {Body, NewSheet} = render_sheet_body(Module, CurPage, Rows, SortColumn, Widths, PrevSheet),
308 {Body, NewSheet} = render_sheet_body(
309 Module,
310 CurPage,
311 CurRow,
312 Rows,
313 SortColumn,
314 Widths,
315 SheetCache,
316 PrevSheet
317 ),
255318 {[Headers | Body], NewSheet}
256319 catch
257320 error:undef ->
258 []
321 {[], []}
259322 end.
260323
261324 render_sheet_header(Module, SortRow) ->
270333 "" -> Header;
271334 Shortcut -> Header ++ "(" ++ Shortcut ++ ")"
272335 end,
273 case Index =:= SortRow of
274 true ->
275 {
276 [?UNDERLINE, ?W2(?RED_BG, Title, Width) | HeaderAcc],
277 [Width | WidthAcc],
278 Index - 1
279 };
280 false ->
281 {
282 [?UNDERLINE, ?W2(?GRAY_BG, Title, Width) | HeaderAcc],
283 [Width | WidthAcc],
284 Index - 1
285 }
286 end
336 Line =
337 case Index =:= SortRow of
338 true -> [?UNDERLINE, ?W2(?RED_BG, Title, Width) | HeaderAcc];
339 false -> [?UNDERLINE, ?W2(?GRAY_BG, Title, Width) | HeaderAcc]
340 end,
341 {Line, [Width | WidthAcc], Index - 1}
287342 end,
288343 {[], [], length(SheetHeader)},
289344 lists:reverse(SheetHeader)
290345 ),
291 {?render(Headers), Widths}.
292
293 render_sheet_body(Module, CurPage, Rows, SortRow, Widths, PrevSheet) ->
346 {?render([?W2(?GRAY_BG, "No ", 3) | Headers]), Widths}.
347
348 render_sheet_body(Module, CurPage, CurRow, Rows, SortRow, Widths, SheetCache, PrevSheet) ->
294349 {Diff, NewSheet} = Module:sheet_body(PrevSheet),
295 DataSet = lists:map(
296 fun(I) ->
297 {0, lists:nth(SortRow, I), I}
350 DataSet = lists:map(fun(I) -> {0, lists:nth(SortRow, I), I} end, Diff),
351 {StartAt, SortData} = observer_cli_lib:sublist(DataSet, Rows, CurPage),
352 {Line, _} = lists:foldl(
353 fun({_, _, Item}, {List, Pos}) ->
354 L = mix_content_width(Item, Widths, []),
355 ets:insert(SheetCache, {Pos, Item}),
356 case CurRow =:= Pos of
357 false -> {[?render([?W(Pos, 2) | L])] ++ List, Pos + 1};
358 true -> {[?render([?W(?CHOOSE_BG, Pos, 4) | L])] ++ List, Pos + 1}
359 end
298360 end,
299 Diff
361 {[], StartAt},
362 SortData
300363 ),
301 SortData = observer_cli_lib:sublist(DataSet, Rows, CurPage),
302 Line = [
303 begin
304 List = mix_content_width(Item, Widths, []),
305 ?render(List)
306 end
307 || {_, _, Item} <- SortData
308 ],
309 {Line, NewSheet}.
364 {lists:reverse(Line), NewSheet}.
310365
311366 mix_content_width([], _, Acc) ->
312367 lists:reverse(Acc);
313368 %% first
314369 mix_content_width([I | IRest], [W | WRest], []) ->
315370 IList = observer_cli_lib:to_list(I),
316 mix_content_width(IRest, WRest, [?W(IList, W - 1)]);
371 mix_content_width(IRest, WRest, [?W(IList, W - 2)]);
317372 %% last
318373 mix_content_width([I], [W], Acc) ->
319374 IList = observer_cli_lib:to_list(I),
0 -module(observer_cli_port).
1
2 -include("observer_cli.hrl").
3
4 -export([start/2]).
5
6 -spec start(pid(), view_opts()) -> no_return.
7 start(Port, Opts) ->
8 #view_opts{port = RefreshMs} = Opts,
9 RenderPid = spawn_link(fun() ->
10 ?output(?CLEAR),
11 render_worker(Port, RefreshMs, ?INIT_TIME_REF)
12 end),
13 manager(RenderPid, Opts).
14
15 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
16 %%% Private
17 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
18 manager(RenderPid, Opts) ->
19 case parse_cmd(Opts, RenderPid) of
20 quit ->
21 erlang:send(RenderPid, quit);
22 {new_interval, NewInterval} ->
23 erlang:send(RenderPid, {new_interval, NewInterval}),
24 manager(RenderPid, Opts#view_opts{port = NewInterval});
25 ViewAction ->
26 erlang:send(RenderPid, ViewAction),
27 manager(RenderPid, Opts)
28 end.
29
30 render_worker(Port, Interval, TimeRef) ->
31 PortInfo = recon:port_info(Port),
32 Meta = proplists:get_value(meta, PortInfo),
33 case lists:member(undefined, Meta) of
34 true ->
35 output_die_view(Port, Interval),
36 next_draw_view(TimeRef, Interval, Port);
37 false ->
38 Id = proplists:get_value(id, Meta),
39 Name = proplists:get_value(name, Meta),
40 OsPid = proplists:get_value(os_pid, Meta),
41
42 Signals = proplists:get_value(signals, PortInfo),
43 Link = proplists:get_value(links, Signals),
44 Monitors = proplists:get_value(monitors, Signals),
45 Connected = proplists:get_value(connected, Signals),
46
47 IO = proplists:get_value(io, PortInfo),
48 Input = proplists:get_value(input, IO),
49 Output = proplists:get_value(output, IO),
50
51 MemoryUsed = proplists:get_value(memory_used, PortInfo),
52 Memory = proplists:get_value(memory, MemoryUsed),
53 QueueSize = proplists:get_value(queue_size, MemoryUsed),
54 Menu = render_menu(info, Interval),
55
56 Line1 = render_port_info(
57 Port,
58 Id,
59 Name,
60 OsPid,
61 Input,
62 Output,
63 Memory,
64 QueueSize,
65 Connected
66 ),
67 Line2 = render_link_monitor(Link, Monitors),
68 Line3 = render_type_line(proplists:get_value(type, PortInfo)),
69 LastLine = render_last_line(),
70
71 ?output([?CURSOR_TOP, Menu, Line1, Line2, Line3, LastLine]),
72 next_draw_view(TimeRef, Interval, Port)
73 end.
74
75 next_draw_view(TimeRef, Interval, Port) ->
76 NewTimeRef = observer_cli_lib:next_redraw(TimeRef, Interval),
77 next_draw_view_2(NewTimeRef, Interval, Port).
78
79 next_draw_view_2(TimeRef, Interval, Port) ->
80 receive
81 quit ->
82 quit;
83 {new_interval, NewInterval} ->
84 ?output(?CLEAR),
85 render_worker(Port, NewInterval, TimeRef);
86 _ ->
87 ?output(?CLEAR),
88 render_worker(Port, Interval, TimeRef)
89 end.
90
91 render_port_info(
92 Port,
93 Id,
94 Name,
95 OsPid,
96 Input,
97 Output,
98 Memory,
99 QueueSize,
100 Connected
101 ) ->
102 QueueSizeColor =
103 case QueueSize > 0 of
104 true -> ?RED;
105 false -> ?GREEN
106 end,
107 Title =
108 ?render([
109 ?GRAY_BG,
110 ?W("Attr", 18),
111 ?W("Value", 20),
112 ?W("Attr", 18),
113 ?W("Value", 20),
114 ?W("Attr", 19),
115 ?W("Value", 21)
116 ]),
117 Rows =
118 ?render([
119 ?W("port", 18),
120 ?W(Port, 20),
121 ?W("id", 18),
122 ?W(Id, 20),
123 ?W("name", 19),
124 ?W(Name, 21),
125 ?NEW_LINE,
126 ?W("queue_size", 18),
127 ?W2(QueueSizeColor, QueueSize, 21),
128 ?W(" input", 19),
129 ?W({byte, Input}, 20),
130 ?W("output", 19),
131 ?W({byte, Output}, 21),
132 ?NEW_LINE,
133 ?W("connected", 18),
134 ?W(Connected, 20),
135 ?W("memory", 18),
136 ?W({byte, Memory}, 20),
137 ?W("os_pid", 19),
138 ?W(OsPid, 21)
139 ]),
140 [Title, Rows].
141
142 render_link_monitor(Link, Monitors) ->
143 LinkStr = [
144 begin
145 observer_cli_lib:to_list(P)
146 end
147 || P <- lists:sublist(Link, 30)
148 ],
149 MonitorsStr = [
150 begin
151 case P of
152 {process, Pid} ->
153 observer_cli_lib:to_list(Pid);
154 {RegName, Node} ->
155 observer_cli_lib:to_list(RegName) ++ "/" ++ observer_cli_lib:to_list(Node)
156 end
157 end
158 || P <- lists:sublist(Monitors, 30)
159 ],
160 LinkInfo = "Links(" ++ erlang:integer_to_list(erlang:length(Link)) ++ ")",
161 MonitorInfo = "Monitors(" ++ erlang:integer_to_list(erlang:length(Monitors)) ++ ")",
162 ?render([
163 ?W(LinkInfo, 18),
164 ?W(LinkStr, 110),
165 ?NEW_LINE,
166 ?W2(?UNDERLINE, MonitorInfo, 19),
167 ?W2(?UNDERLINE, MonitorsStr, 111)
168 ]).
169
170 render_type_line(List) ->
171 PeerName =
172 case lists:keyfind(peername, 1, List) of
173 {_, Peer} -> addr_to_str(Peer);
174 false -> "undefined"
175 end,
176 SockName =
177 case lists:keyfind(sockname, 1, List) of
178 {_, Sock} -> addr_to_str(Sock);
179 false -> "undefined"
180 end,
181 Line1 =
182 ?render([
183 ?UNDERLINE,
184 ?W(" " ++ SockName ++ "(sockname)", 55),
185 ?W("<=============>", 15),
186 ?W(" " ++ PeerName ++ "(peername)", 55)
187 ]),
188 Line2 =
189 case lists:keyfind(statistics, 1, List) of
190 {_, Stats} -> [Line1, render_stats(Stats)];
191 false -> Line1
192 end,
193 case lists:keyfind(options, 1, List) of
194 {_, Opts} -> Line2 ++ [render_opts(Opts)];
195 false -> Line2
196 end.
197
198 render_stats(Stats) ->
199 RecvOct = proplists:get_value(recv_oct, Stats),
200 RecvCnt = proplists:get_value(recv_cnt, Stats),
201 RecvMax = proplists:get_value(recv_max, Stats),
202 RecvAvg = proplists:get_value(recv_avg, Stats),
203 RecvDvi = proplists:get_value(recv_dvi, Stats),
204 SendOct = proplists:get_value(send_oct, Stats),
205 SendCnt = proplists:get_value(send_cnt, Stats),
206 SendMax = proplists:get_value(send_max, Stats),
207 SendAvg = proplists:get_value(send_avg, Stats),
208 SendPend = proplists:get_value(send_pend, Stats),
209 ?render([
210 ?W("recv_cnt", 9),
211 ?W(RecvCnt, 12),
212 ?W("recv_oct", 8),
213 ?W({byte, RecvOct}, 12),
214 ?W("recv_max", 9),
215 ?W({byte, RecvMax}, 12),
216 ?W("recv_avg", 9),
217 ?W({byte, RecvAvg}, 12),
218 ?W("recv_dvi", 9),
219 ?W({byte, RecvDvi}, 12),
220 ?NEW_LINE,
221 ?W("send_cnt", 9),
222 ?W(SendCnt, 12),
223 ?W("send_oct", 8),
224 ?W({byte, SendOct}, 12),
225 ?W("send_max", 9),
226 ?W({byte, SendMax}, 12),
227 ?W("send_avg", 9),
228 ?W({byte, SendAvg}, 12),
229 ?W("send_pend", 9),
230 ?W(SendPend, 12)
231 ]).
232
233 render_opts(Opts) ->
234 Active = proplists:get_value(active, Opts),
235 Broadcast = proplists:get_value(broadcast, Opts),
236 Buffer = proplists:get_value(buffer, Opts),
237 DelaySend = proplists:get_value(delay_send, Opts),
238 DontRoute = proplists:get_value(dontroute, Opts),
239
240 ExitOnClose = proplists:get_value(exit_on_close, Opts),
241 Header = proplists:get_value(header, Opts),
242 HighWatermark = proplists:get_value(high_watermark, Opts),
243 KeepAlive = proplists:get_value(keepalive, Opts),
244 Linger = io_lib:format("~p", [proplists:get_value(linger, Opts)]),
245
246 LowWatermark = proplists:get_value(low_watermark, Opts),
247 Mode = proplists:get_value(mode, Opts),
248 NoDelay = proplists:get_value(nodelay, Opts),
249 Packet = proplists:get_value(packet, Opts),
250 PacketSize = proplists:get_value(packet_size, Opts),
251
252 Priority = proplists:get_value(priority, Opts),
253 RecBuf = proplists:get_value(recbuf, Opts),
254 ReuseAddr = proplists:get_value(reuseaddr, Opts),
255 SendTimeout = proplists:get_value(send_timeout, Opts),
256 SndBuf = proplists:get_value(sndbuf, Opts),
257 Title =
258 ?render([
259 ?GRAY_BG,
260 ?W("Option", 9),
261 ?W("Value", 6),
262 ?W("Option", 14),
263 ?W("Value", 12),
264 ?W("Option", 9),
265 ?W("Value", 12),
266 ?W("Option", 13),
267 ?W("Value", 8),
268 ?W("Option", 9),
269 ?W("Value", 12)
270 ]),
271 Rows =
272 ?render([
273 ?W("mode", 9),
274 ?W(Mode, 6),
275 ?W("recbuf", 14),
276 ?W({byte, RecBuf}, 12),
277 ?W("sndbuf", 9),
278 ?W({byte, SndBuf}, 12),
279 ?W("delay_send", 13),
280 ?W(DelaySend, 8),
281 ?W("dontroute", 9),
282 ?W(DontRoute, 12),
283 ?NEW_LINE,
284 ?W("reuseaddr", 9),
285 ?W(ReuseAddr, 6),
286 ?W("packet_size", 14),
287 ?W({byte, PacketSize}, 12),
288 ?W("buffer", 9),
289 ?W({byte, Buffer}, 12),
290 ?W("exit_on_close", 13),
291 ?W(ExitOnClose, 8),
292 ?W("priority", 9),
293 ?W(Priority, 12),
294 ?NEW_LINE,
295 ?W("active", 9),
296 ?W(Active, 6),
297 ?W("low_watermark", 14),
298 ?W({byte, LowWatermark}, 12),
299 ?W("header", 9),
300 ?W(Header, 12),
301 ?W("keepalive", 13),
302 ?W(KeepAlive, 8),
303 ?W("linger", 9),
304 ?W(Linger, 12),
305 ?NEW_LINE,
306 ?W("nodelay", 9),
307 ?W(NoDelay, 6),
308 ?W("high_watermark", 14),
309 ?W({byte, HighWatermark}, 12),
310 ?W("broadcast", 9),
311 ?W(Broadcast, 12),
312 ?W("send_timeout", 13),
313 ?W(SendTimeout, 8),
314 ?W("packet", 9),
315 ?W(Packet, 12)
316 ]),
317 [Title, Rows].
318
319 render_last_line() ->
320 io_lib:format("|\e[7mq(quit) ~124.124s\e[0m|~n", [" "]).
321
322 render_menu(Type, Interval) ->
323 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
324 Title = get_menu_title(Type),
325 UpTime = observer_cli_lib:uptime(),
326 TitleWidth = ?COLUMN + 41 - erlang:length(UpTime),
327 ?render([?W([Title | Text], TitleWidth) | UpTime]).
328
329 get_menu_title(Type) ->
330 [Home, Net, Port] = get_menu_title2(Type),
331 [Home, "|", Net, "|", Port].
332
333 get_menu_title2(info) ->
334 [?UNSELECT("Home(H)"), ?UNSELECT("Network(N)"), ?SELECT("Port Info(P)")].
335
336 parse_cmd(ViewOpts, Pid) ->
337 case observer_cli_lib:to_list(io:get_line("")) of
338 "q\n" ->
339 quit;
340 "Q\n" ->
341 quit;
342 "P\n" ->
343 info_view;
344 "H\n" ->
345 erlang:exit(Pid, stop),
346 observer_cli:start(ViewOpts);
347 "N\n" ->
348 erlang:exit(Pid, stop),
349 observer_cli_inet:start(ViewOpts);
350 Number ->
351 observer_cli_lib:parse_integer(Number)
352 end.
353
354 output_die_view(Port, Interval) ->
355 Menu = render_menu(info, Interval),
356 Line = io_lib:format("\e[31mPort(~p) has already die.\e[0m~n", [Port]),
357 LastLine = render_last_line(),
358 ?output([?CURSOR_TOP, Menu, Line, LastLine]).
359
360 addr_to_str({Addr, Port}) ->
361 AddrList = [
362 begin
363 erlang:integer_to_list(A)
364 end
365 || A <- erlang:tuple_to_list(Addr)
366 ],
367 string:join(AddrList, ".") ++ ":" ++ erlang:integer_to_list(Port).
0 -module(observer_cli_port).
1
2 -include("observer_cli.hrl").
3
4 -export([start/2]).
5
6 -spec start(pid(), view_opts()) -> no_return.
7 start(Port, Opts) ->
8 #view_opts{port = RefreshMs} = Opts,
9 RenderPid = spawn_link(fun() ->
10 ?output(?CLEAR),
11 render_worker(Port, RefreshMs, ?INIT_TIME_REF)
12 end),
13 manager(RenderPid, Opts).
14
15 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
16 %%% Private
17 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
18 manager(RenderPid, Opts) ->
19 case parse_cmd(Opts, RenderPid) of
20 quit ->
21 erlang:send(RenderPid, quit);
22 {new_interval, NewInterval} ->
23 erlang:send(RenderPid, {new_interval, NewInterval}),
24 manager(RenderPid, Opts#view_opts{port = NewInterval});
25 ViewAction ->
26 erlang:send(RenderPid, ViewAction),
27 manager(RenderPid, Opts)
28 end.
29
30 render_worker(Port, Interval, TimeRef) ->
31 PortInfo = recon:port_info(Port),
32 Meta = proplists:get_value(meta, PortInfo),
33 case lists:member(undefined, Meta) of
34 true ->
35 output_die_view(Port, Interval),
36 next_draw_view(TimeRef, Interval, Port);
37 false ->
38 Id = proplists:get_value(id, Meta),
39 Name = proplists:get_value(name, Meta),
40 OsPid = proplists:get_value(os_pid, Meta),
41
42 Signals = proplists:get_value(signals, PortInfo),
43 Link = proplists:get_value(links, Signals),
44 Monitors = proplists:get_value(monitors, Signals),
45 Connected = proplists:get_value(connected, Signals),
46
47 IO = proplists:get_value(io, PortInfo),
48 Input = proplists:get_value(input, IO),
49 Output = proplists:get_value(output, IO),
50
51 MemoryUsed = proplists:get_value(memory_used, PortInfo),
52 Memory = proplists:get_value(memory, MemoryUsed),
53 QueueSize = proplists:get_value(queue_size, MemoryUsed),
54 Menu = render_menu(info, Interval),
55
56 Line1 = render_port_info(
57 Port,
58 Id,
59 Name,
60 OsPid,
61 Input,
62 Output,
63 Memory,
64 QueueSize,
65 Connected
66 ),
67 Line2 = render_link_monitor(Link, Monitors),
68 Line3 = render_type_line(proplists:get_value(type, PortInfo)),
69 LastLine = render_last_line(),
70
71 ?output([?CURSOR_TOP, Menu, Line1, Line2, Line3, LastLine]),
72 next_draw_view(TimeRef, Interval, Port)
73 end.
74
75 next_draw_view(TimeRef, Interval, Port) ->
76 NewTimeRef = observer_cli_lib:next_redraw(TimeRef, Interval),
77 next_draw_view_2(NewTimeRef, Interval, Port).
78
79 next_draw_view_2(TimeRef, Interval, Port) ->
80 receive
81 quit ->
82 quit;
83 {new_interval, NewInterval} ->
84 ?output(?CLEAR),
85 render_worker(Port, NewInterval, TimeRef);
86 _ ->
87 ?output(?CLEAR),
88 render_worker(Port, Interval, TimeRef)
89 end.
90
91 render_port_info(
92 Port,
93 Id,
94 Name,
95 OsPid,
96 Input,
97 Output,
98 Memory,
99 QueueSize,
100 Connected
101 ) ->
102 QueueSizeColor =
103 case QueueSize > 0 of
104 true -> ?RED;
105 false -> ?GREEN
106 end,
107 Title =
108 ?render([
109 ?GRAY_BG,
110 ?W("Attr", 18),
111 ?W("Value", 20),
112 ?W("Attr", 18),
113 ?W("Value", 20),
114 ?W("Attr", 19),
115 ?W("Value", 21)
116 ]),
117 Rows =
118 ?render([
119 ?W("port", 18),
120 ?W(Port, 20),
121 ?W("id", 18),
122 ?W(Id, 20),
123 ?W("name", 19),
124 ?W(Name, 21),
125 ?NEW_LINE,
126 ?W("queue_size", 18),
127 ?W2(QueueSizeColor, QueueSize, 21),
128 ?W(" input", 19),
129 ?W({byte, Input}, 20),
130 ?W("output", 19),
131 ?W({byte, Output}, 21),
132 ?NEW_LINE,
133 ?W("connected", 18),
134 ?W(Connected, 20),
135 ?W("memory", 18),
136 ?W({byte, Memory}, 20),
137 ?W("os_pid", 19),
138 ?W(OsPid, 21)
139 ]),
140 [Title, Rows].
141
142 render_link_monitor(Link, Monitors) ->
143 LinkStr = [
144 begin
145 observer_cli_lib:to_list(P)
146 end
147 || P <- lists:sublist(Link, 30)
148 ],
149 MonitorsStr = [
150 begin
151 case P of
152 {process, Pid} ->
153 observer_cli_lib:to_list(Pid);
154 {RegName, Node} ->
155 observer_cli_lib:to_list(RegName) ++ "/" ++ observer_cli_lib:to_list(Node)
156 end
157 end
158 || P <- lists:sublist(Monitors, 30)
159 ],
160 LinkInfo = "Links(" ++ erlang:integer_to_list(erlang:length(Link)) ++ ")",
161 MonitorInfo = "Monitors(" ++ erlang:integer_to_list(erlang:length(Monitors)) ++ ")",
162 ?render([
163 ?W(LinkInfo, 18),
164 ?W(LinkStr, 110),
165 ?NEW_LINE,
166 ?W2(?UNDERLINE, MonitorInfo, 19),
167 ?W2(?UNDERLINE, MonitorsStr, 111)
168 ]).
169
170 render_type_line(List) ->
171 PeerName =
172 case lists:keyfind(peername, 1, List) of
173 {_, Peer} -> addr_to_str(Peer);
174 false -> "undefined"
175 end,
176 SockName =
177 case lists:keyfind(sockname, 1, List) of
178 {_, Sock} -> addr_to_str(Sock);
179 false -> "undefined"
180 end,
181 Line1 =
182 ?render([
183 ?UNDERLINE,
184 ?W(" " ++ SockName ++ "(sockname)", 55),
185 ?W("<=============>", 15),
186 ?W(" " ++ PeerName ++ "(peername)", 55)
187 ]),
188 Line2 =
189 case lists:keyfind(statistics, 1, List) of
190 {_, Stats} -> [Line1, render_stats(Stats)];
191 false -> Line1
192 end,
193 case lists:keyfind(options, 1, List) of
194 {_, Opts} -> Line2 ++ [render_opts(Opts)];
195 false -> Line2
196 end.
197
198 render_stats(Stats) ->
199 RecvOct = proplists:get_value(recv_oct, Stats),
200 RecvCnt = proplists:get_value(recv_cnt, Stats),
201 RecvMax = proplists:get_value(recv_max, Stats),
202 RecvAvg = proplists:get_value(recv_avg, Stats),
203 RecvDvi = proplists:get_value(recv_dvi, Stats),
204 SendOct = proplists:get_value(send_oct, Stats),
205 SendCnt = proplists:get_value(send_cnt, Stats),
206 SendMax = proplists:get_value(send_max, Stats),
207 SendAvg = proplists:get_value(send_avg, Stats),
208 SendPend = proplists:get_value(send_pend, Stats),
209 ?render([
210 ?W("recv_cnt", 9),
211 ?W(RecvCnt, 12),
212 ?W("recv_oct", 8),
213 ?W({byte, RecvOct}, 12),
214 ?W("recv_max", 9),
215 ?W({byte, RecvMax}, 12),
216 ?W("recv_avg", 9),
217 ?W({byte, RecvAvg}, 12),
218 ?W("recv_dvi", 9),
219 ?W({byte, RecvDvi}, 12),
220 ?NEW_LINE,
221 ?W("send_cnt", 9),
222 ?W(SendCnt, 12),
223 ?W("send_oct", 8),
224 ?W({byte, SendOct}, 12),
225 ?W("send_max", 9),
226 ?W({byte, SendMax}, 12),
227 ?W("send_avg", 9),
228 ?W({byte, SendAvg}, 12),
229 ?W("send_pend", 9),
230 ?W(SendPend, 12)
231 ]).
232
233 render_opts(Opts) ->
234 Active = proplists:get_value(active, Opts),
235 Broadcast = proplists:get_value(broadcast, Opts),
236 Buffer = proplists:get_value(buffer, Opts),
237 DelaySend = proplists:get_value(delay_send, Opts),
238 DontRoute = proplists:get_value(dontroute, Opts),
239
240 ExitOnClose = proplists:get_value(exit_on_close, Opts),
241 Header = proplists:get_value(header, Opts),
242 HighWatermark = proplists:get_value(high_watermark, Opts),
243 KeepAlive = proplists:get_value(keepalive, Opts),
244 Linger = io_lib:format("~p", [proplists:get_value(linger, Opts)]),
245
246 LowWatermark = proplists:get_value(low_watermark, Opts),
247 Mode = proplists:get_value(mode, Opts),
248 NoDelay = proplists:get_value(nodelay, Opts),
249 Packet = proplists:get_value(packet, Opts),
250 PacketSize = proplists:get_value(packet_size, Opts),
251
252 Priority = proplists:get_value(priority, Opts),
253 RecBuf = proplists:get_value(recbuf, Opts),
254 ReuseAddr = proplists:get_value(reuseaddr, Opts),
255 SendTimeout = proplists:get_value(send_timeout, Opts),
256 SndBuf = proplists:get_value(sndbuf, Opts),
257 Title =
258 ?render([
259 ?GRAY_BG,
260 ?W("Option", 9),
261 ?W("Value", 6),
262 ?W("Option", 14),
263 ?W("Value", 12),
264 ?W("Option", 9),
265 ?W("Value", 12),
266 ?W("Option", 13),
267 ?W("Value", 8),
268 ?W("Option", 9),
269 ?W("Value", 12)
270 ]),
271 Rows =
272 ?render([
273 ?W("mode", 9),
274 ?W(Mode, 6),
275 ?W("recbuf", 14),
276 ?W({byte, RecBuf}, 12),
277 ?W("sndbuf", 9),
278 ?W({byte, SndBuf}, 12),
279 ?W("delay_send", 13),
280 ?W(DelaySend, 8),
281 ?W("dontroute", 9),
282 ?W(DontRoute, 12),
283 ?NEW_LINE,
284 ?W("reuseaddr", 9),
285 ?W(ReuseAddr, 6),
286 ?W("packet_size", 14),
287 ?W({byte, PacketSize}, 12),
288 ?W("buffer", 9),
289 ?W({byte, Buffer}, 12),
290 ?W("exit_on_close", 13),
291 ?W(ExitOnClose, 8),
292 ?W("priority", 9),
293 ?W(Priority, 12),
294 ?NEW_LINE,
295 ?W("active", 9),
296 ?W(Active, 6),
297 ?W("low_watermark", 14),
298 ?W({byte, LowWatermark}, 12),
299 ?W("header", 9),
300 ?W(Header, 12),
301 ?W("keepalive", 13),
302 ?W(KeepAlive, 8),
303 ?W("linger", 9),
304 ?W(Linger, 12),
305 ?NEW_LINE,
306 ?W("nodelay", 9),
307 ?W(NoDelay, 6),
308 ?W("high_watermark", 14),
309 ?W({byte, HighWatermark}, 12),
310 ?W("broadcast", 9),
311 ?W(Broadcast, 12),
312 ?W("send_timeout", 13),
313 ?W(SendTimeout, 8),
314 ?W("packet", 9),
315 ?W(Packet, 12)
316 ]),
317 [Title, Rows].
318
319 render_last_line() ->
320 io_lib:format("|\e[7mq(quit) ~124.124s\e[0m|~n", [" "]).
321
322 render_menu(Type, Interval) ->
323 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
324 Title = get_menu_title(Type),
325 UpTime = observer_cli_lib:uptime(),
326 TitleWidth = ?COLUMN + 41 - erlang:length(UpTime),
327 ?render([?W([Title | Text], TitleWidth) | UpTime]).
328
329 get_menu_title(Type) ->
330 [Home, Net, Port] = get_menu_title2(Type),
331 [Home, "|", Net, "|", Port].
332
333 get_menu_title2(info) ->
334 [?UNSELECT("Home(H)"), ?UNSELECT("Network(N)"), ?SELECT("Port Info(P)")].
335
336 parse_cmd(ViewOpts, Pid) ->
337 case observer_cli_lib:to_list(io:get_line("")) of
338 "q\n" ->
339 quit;
340 "Q\n" ->
341 quit;
342 "P\n" ->
343 info_view;
344 "H\n" ->
345 erlang:exit(Pid, stop),
346 observer_cli:start(ViewOpts);
347 "N\n" ->
348 erlang:exit(Pid, stop),
349 observer_cli_inet:start(ViewOpts);
350 Number ->
351 observer_cli_lib:parse_integer(Number)
352 end.
353
354 output_die_view(Port, Interval) ->
355 Menu = render_menu(info, Interval),
356 Line = io_lib:format("\e[31mPort(~p) has already die.\e[0m~n", [Port]),
357 LastLine = render_last_line(),
358 ?output([?CURSOR_TOP, Menu, Line, LastLine]).
359
360 addr_to_str({Addr, Port}) ->
361 AddrList = [
362 begin
363 erlang:integer_to_list(A)
364 end
365 || A <- erlang:tuple_to_list(Addr)
366 ],
367 string:join(AddrList, ".") ++ ":" ++ erlang:integer_to_list(Port).
11
22 -include("observer_cli.hrl").
33
4 -export([start/2]).
4 -export([start/3]).
55
66 %% lists:foldl(fun(_X, Acc) -> queue:in('NaN', Acc) end, queue:new(), lists:seq(1, 5))
77 -define(INIT_QUEUE, {['NaN', 'NaN', 'NaN', 'NaN'], ['NaN']}).
88
9 -spec start(pid(), view_opts()) -> no_return.
10 start(Pid, Opts) ->
9 -spec start(Type, pid(), view_opts()) -> no_return when Type :: home | plugin.
10 start(Type, Pid, Opts) ->
1111 #view_opts{process = #process{interval = RefreshMs}} = Opts,
1212 RenderPid = spawn_link(fun() ->
1313 ?output(?CLEAR),
14 render_worker(info, RefreshMs, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE)
14 render_worker(info, Type, RefreshMs, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE)
1515 end),
16 manager(RenderPid, Opts).
16 manager(RenderPid, Type, Opts).
1717
1818 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1919 %%% Private
2020 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
21 manager(RenderPid, #view_opts{process = ProcOpts} = Opts) ->
22 case parse_cmd(Opts, RenderPid) of
21 manager(RenderPid, Type, #view_opts{process = ProcOpts} = Opts) ->
22 case parse_cmd() of
2323 quit ->
2424 erlang:send(RenderPid, quit);
2525 {new_interval, NewInterval} ->
2626 erlang:send(RenderPid, {new_interval, NewInterval}),
27 manager(RenderPid, Opts#view_opts{process = ProcOpts#process{interval = NewInterval}});
27 NewOpt = Opts#view_opts{process = ProcOpts#process{interval = NewInterval}},
28 manager(RenderPid, Type, NewOpt);
29 home ->
30 erlang:exit(RenderPid, stop),
31 observer_cli:start(Opts);
32 back when Type =:= home ->
33 erlang:exit(RenderPid, stop),
34 observer_cli:start(Opts);
35 back when Type =:= plugin ->
36 erlang:exit(RenderPid, stop),
37 observer_cli_plugin:start(Opts);
2838 ViewAction ->
2939 erlang:send(RenderPid, ViewAction),
30 manager(RenderPid, Opts)
40 manager(RenderPid, Type, Opts)
3141 end.
3242
33 render_worker(info, Interval, Pid, TimeRef, RedQ, MemQ) ->
43 render_worker(info, Type, Interval, Pid, TimeRef, RedQ, MemQ) ->
3444 ProcessInfo = recon:info(Pid),
3545 Meta = proplists:get_value(meta, ProcessInfo),
3646 case Meta of
3747 undefined ->
38 output_die_view(Pid, Interval),
39 next_draw_view(info, TimeRef, Interval, Pid, RedQ, MemQ);
48 output_die_view(Pid, Type, Interval),
49 next_draw_view(info, Type, TimeRef, Interval, Pid, RedQ, MemQ);
4050 _ ->
4151 RegisteredName = proplists:get_value(registered_name, Meta),
4252 GroupLeader = proplists:get_value(group_leader, Meta),
6171 Work = proplists:get_value(work, ProcessInfo),
6272 Reductions = proplists:get_value(reductions, Work),
6373
64 Menu = render_menu(info, Interval),
74 Menu = render_menu(info, Type, Interval),
6575
6676 Line1 = render_process_info(
6777 Pid,
8393 LastLine = render_last_line(),
8494
8595 ?output([?CURSOR_TOP, Menu, Line1, Line2, Line3, LastLine]),
86 next_draw_view(info, TimeRef, Interval, Pid, NewRedQ, NewMemQ)
96 next_draw_view(info, Type, TimeRef, Interval, Pid, NewRedQ, NewMemQ)
8797 end;
88 render_worker(message, Interval, Pid, TimeRef, RedQ, MemQ) ->
98 render_worker(message, Type, Interval, Pid, TimeRef, RedQ, MemQ) ->
8999 case erlang:process_info(Pid, message_queue_len) of
90100 {message_queue_len, Len} ->
91101 Line =
101111 truncate_str(Messages)
102112 ]
103113 end,
104 Menu = render_menu(message, Interval),
114 Menu = render_menu(message, Type, Interval),
105115 LastLine = render_last_line(),
106116 ?output([?CURSOR_TOP, Menu, Line, LastLine]),
107 next_draw_view(message, TimeRef, Interval, Pid, RedQ, MemQ);
117 next_draw_view(message, Type, TimeRef, Interval, Pid, RedQ, MemQ);
108118 undefined ->
109 render_worker(info, Interval, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE)
119 render_worker(info, Type, Interval, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE)
110120 end;
111 render_worker(dict, Interval, Pid, TimeRef, RedQ, MemQ) ->
121 render_worker(dict, Type, Interval, Pid, TimeRef, RedQ, MemQ) ->
112122 case erlang:process_info(Pid, dictionary) of
113123 {dictionary, List} ->
114124 Len = erlang:length(List),
121131 0 -> "\e[32;1mNo dictionary was found\e[0m\n";
122132 _ -> truncate_str(List)
123133 end,
124 Menu = render_menu(dict, Interval),
134 Menu = render_menu(dict, Type, Interval),
125135 LastLine = render_last_line(),
126136 ?output([?CURSOR_TOP, Menu, Line1, Line2, LastLine]),
127 next_draw_view(dict, TimeRef, Interval, Pid, RedQ, MemQ);
137 next_draw_view(dict, Type, TimeRef, Interval, Pid, RedQ, MemQ);
128138 undefined ->
129 render_worker(info, Interval, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE)
139 render_worker(info, Type, Interval, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE)
130140 end;
131 render_worker(stack, Interval, Pid, TimeRef, RedQ, MemQ) ->
141 render_worker(stack, Type, Interval, Pid, TimeRef, RedQ, MemQ) ->
132142 case erlang:process_info(Pid, current_stacktrace) of
133143 {current_stacktrace, StackTrace} ->
134 Menu = render_menu(stack, Interval),
144 Menu = render_menu(stack, Type, Interval),
135145 Prompt = io_lib:format("erlang:process_info(~p, current_stacktrace). ~n", [Pid]),
136146 LastLine = render_last_line(),
137147 {_, Line} =
150160 lists:sublist(StackTrace, 30)
151161 ),
152162 ?output([?CURSOR_TOP, Menu, Prompt, ?render(Line), LastLine]),
153 next_draw_view(stack, TimeRef, Interval, Pid, RedQ, MemQ);
163 next_draw_view(stack, Type, TimeRef, Interval, Pid, RedQ, MemQ);
154164 undefined ->
155 render_worker(info, Interval, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE)
165 render_worker(info, Type, Interval, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE)
156166 end;
157 render_worker(state, Interval, Pid, TimeRef, RedQ, MemQ) ->
158 case render_state(Pid, Interval) of
159 ok -> next_draw_view(state, TimeRef, Interval, Pid, RedQ, MemQ);
160 error -> next_draw_view_2(state, TimeRef, Interval, Pid, RedQ, MemQ)
167 render_worker(state, Type, Interval, Pid, TimeRef, RedQ, MemQ) ->
168 case render_state(Pid, Type, Interval) of
169 ok -> next_draw_view(state, Type, TimeRef, Interval, Pid, RedQ, MemQ);
170 error -> next_draw_view_2(state, Type, TimeRef, Interval, Pid, RedQ, MemQ)
161171 end.
162172
163 next_draw_view(Status, TimeRef, Interval, Pid, NewRedQ, NewMemQ) ->
173 next_draw_view(Status, Type, TimeRef, Interval, Pid, NewRedQ, NewMemQ) ->
164174 NewTimeRef = observer_cli_lib:next_redraw(TimeRef, Interval),
165 next_draw_view_2(Status, NewTimeRef, Interval, Pid, NewRedQ, NewMemQ).
166
167 next_draw_view_2(Status, TimeRef, Interval, Pid, NewRedQ, NewMemQ) ->
175 next_draw_view_2(Status, Type, NewTimeRef, Interval, Pid, NewRedQ, NewMemQ).
176
177 next_draw_view_2(Status, Type, TimeRef, Interval, Pid, NewRedQ, NewMemQ) ->
168178 receive
169179 quit ->
170180 quit;
171181 {new_interval, NewInterval} ->
172182 ?output(?CLEAR),
173 render_worker(Status, NewInterval, Pid, TimeRef, NewRedQ, NewMemQ);
183 render_worker(Status, Type, NewInterval, Pid, TimeRef, NewRedQ, NewMemQ);
174184 info_view ->
175185 ?output(?CLEAR),
176 render_worker(info, Interval, Pid, TimeRef, NewRedQ, NewMemQ);
186 render_worker(info, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ);
177187 message_view ->
178188 ?output(?CLEAR),
179 render_worker(message, Interval, Pid, TimeRef, NewRedQ, NewMemQ);
189 render_worker(message, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ);
180190 dict_view ->
181191 ?output(?CLEAR),
182 render_worker(dict, Interval, Pid, TimeRef, NewRedQ, NewMemQ);
192 render_worker(dict, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ);
183193 stack_view ->
184194 ?output(?CLEAR),
185 render_worker(stack, Interval, Pid, TimeRef, NewRedQ, NewMemQ);
195 render_worker(stack, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ);
186196 state_view ->
187197 ?output(?CLEAR),
188 render_worker(state, Interval, Pid, TimeRef, NewRedQ, NewMemQ);
198 render_worker(state, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ);
189199 _Msg ->
190 render_worker(Status, Interval, Pid, TimeRef, NewRedQ, NewMemQ)
200 render_worker(Status, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ)
191201 end.
192202
193203 render_process_info(
333343 chart_format([R1, R2 | RestRed], Lines) when R1 < R2 ->
334344 chart_format([R2 | RestRed], Lines ++ observer_cli_lib:to_list(R1) ++ "->").
335345
336 render_menu(Type, Interval) ->
346 render_menu(Type, Menu, Interval) ->
337347 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
338 Title = get_menu_title(Type),
348 Title = get_menu_title(Type, Menu),
339349 UpTime = observer_cli_lib:uptime(),
340350 TitleWidth = ?COLUMN + 104 - erlang:length(UpTime),
341351 ?render([?W([Title | Text], TitleWidth) | UpTime]).
342352
343 get_menu_title(Type) ->
344 [Home, Process, Messages, Dict, Stack, State] = get_menu_title2(Type),
353 get_menu_title(Type, Menu) ->
354 MenuStr =
355 case Menu of
356 home -> "Home(H)";
357 plugin -> "Back(B)"
358 end,
359 [Home, Process, Messages, Dict, Stack, State] = get_menu_title2(Type, MenuStr),
345360 [Home, "|", Process, "|", Messages, "|", Dict, "|", Stack, "|", State, "|"].
346361
347 get_menu_title2(info) ->
348 [
349 ?UNSELECT("Home(H)"),
362 get_menu_title2(info, Menu) ->
363 [
364 ?UNSELECT(Menu),
350365 ?SELECT("Process Info(P)"),
351366 ?UNSELECT("Messages(M)"),
352367 ?UNSELECT("Dictionary(D)"),
353368 ?UNSELECT("Current Stack(C)"),
354369 ?UNSELECT("State(S)")
355370 ];
356 get_menu_title2(message) ->
357 [
358 ?UNSELECT("Home(H)"),
371 get_menu_title2(message, Menu) ->
372 [
373 ?UNSELECT(Menu),
359374 ?UNSELECT("Process Info(P)"),
360375 ?SELECT("Messages(M)"),
361376 ?UNSELECT("Dictionary(D)"),
362377 ?UNSELECT("Current Stack(C)"),
363378 ?UNSELECT("State(S)")
364379 ];
365 get_menu_title2(dict) ->
366 [
367 ?UNSELECT("Home(H)"),
380 get_menu_title2(dict, Menu) ->
381 [
382 ?UNSELECT(Menu),
368383 ?UNSELECT("Process Info(P)"),
369384 ?UNSELECT("Messages(M)"),
370385 ?SELECT("Dictionary(D)"),
371386 ?UNSELECT("Current Stack(C)"),
372387 ?UNSELECT("State(S)")
373388 ];
374 get_menu_title2(stack) ->
375 [
376 ?UNSELECT("Home(H)"),
389 get_menu_title2(stack, Menu) ->
390 [
391 ?UNSELECT(Menu),
377392 ?UNSELECT("Process Info(P)"),
378393 ?UNSELECT("Messages(M)"),
379394 ?UNSELECT("Dictionary(D)"),
380395 ?SELECT("Current Stack(C)"),
381396 ?UNSELECT("State(S)")
382397 ];
383 get_menu_title2(state) ->
384 [
385 ?UNSELECT("Home(H)"),
398 get_menu_title2(state, Menu) ->
399 [
400 ?UNSELECT(Menu),
386401 ?UNSELECT("Process Info(P)"),
387402 ?UNSELECT("Messages(M)"),
388403 ?UNSELECT("Dictionary(D)"),
390405 ?SELECT("State(S)")
391406 ].
392407
393 parse_cmd(ViewOpts, Pid) ->
408 parse_cmd() ->
394409 case observer_cli_lib:to_list(io:get_line("")) of
395410 "q\n" ->
396411 quit;
407422 "S\n" ->
408423 state_view;
409424 "H\n" ->
410 erlang:exit(Pid, stop),
411 observer_cli:start(ViewOpts);
425 home;
426 "B\n" ->
427 back;
428 %% {error, estale}|{error, terminated}
429 {error, _Reason} ->
430 quit;
412431 Number ->
413432 observer_cli_lib:parse_integer(Number)
414433 end.
415434
416 render_state(Pid, Interval) ->
417 Menu = render_menu(state, Interval),
435 render_state(Pid, Type, Interval) ->
436 Menu = render_menu(state, Type, Interval),
418437 PromptRes = io_lib:format("recon:get_state(~p, 2500). ~n", [Pid]),
419438 PromptBefore = io_lib:format("\e[32;1mWaiting recon:get_state(~p, 2500) return...\e[0m~n", [Pid]),
420439 LastLine = render_last_line(),
432451 error
433452 end.
434453
435 output_die_view(Pid, Interval) ->
436 Menu = render_menu(info, Interval),
454 output_die_view(Pid, Type, Interval) ->
455 Menu = render_menu(info, Type, Interval),
437456 Line = io_lib:format("\e[31mProcess(~p) has already die.\e[0m~n", [Pid]),
438457 LastLine = render_last_line(),
439458 ?output([?CURSOR_TOP, Menu, Line, LastLine]).
0 %%% @author zhongwen <zhongwencool@gmail.com>
1
2 -module(observer_cli_store).
3
4 %% API
5 -export([start/0]).
6 -export([update/3]).
7 -export([lookup_pos/2]).
8 -export([lookup_row/1]).
9
10 -define(LOOKUP_PID, lookup_pid).
11 -define(LOOKUP_PID_RES, lookup_pid_result).
12 -define(LOOKUP_ROW, lookup_row).
13 -define(LOOKUP_ROW_RES, lookup_row_result).
14 -define(UPDATE_TOP_N, update_top_n).
15
16 -spec start() -> pid().
17 start() ->
18 spawn_link(fun() -> loop({1, []}) end).
19
20 -spec update(pid(), pos_integer(), list()) -> ok.
21 update(StorePid, Row, TopNList) ->
22 erlang:send(StorePid, {?UPDATE_TOP_N, Row, TopNList}),
23 ok.
24
25 -spec lookup_pos(pid(), pos_integer()) -> {pos_integer(), pid()}.
26 lookup_pos(StorePid, CurPos) ->
27 erlang:send(StorePid, {?LOOKUP_PID, CurPos, self()}),
28 receive
29 {?LOOKUP_PID_RES, Res} -> Res
30 end.
31
32 -spec lookup_row(pid()) -> pos_integer().
33 lookup_row(StorePid) ->
34 erlang:send(StorePid, {?LOOKUP_ROW, self()}),
35 receive
36 {?LOOKUP_ROW_RES, Res} -> Res
37 end.
38
39 loop({Row, PidList} = TopList) ->
40 NewTopList =
41 receive
42 {?UPDATE_TOP_N, NewRow, NewPidList} ->
43 {NewRow, NewPidList};
44 {?LOOKUP_PID, Pos, From} ->
45 Res =
46 case PidList of
47 [] ->
48 {error, undefined};
49 _ ->
50 case lists:keyfind(Pos, 1, PidList) of
51 false -> lists:last(PidList);
52 Item -> Item
53 end
54 end,
55 erlang:send(From, {?LOOKUP_PID_RES, Res}),
56 TopList;
57 {?LOOKUP_ROW, From} ->
58 erlang:send(From, {?LOOKUP_ROW_RES, Row}),
59 TopList
60 end,
61 loop(NewTopList).
0 %%% @author zhongwen <zhongwencool@gmail.com>
1
2 -module(observer_cli_store).
3
4 %% API
5 -export([start/0]).
6 -export([update/3]).
7 -export([lookup_pos/2]).
8 -export([lookup_row/1]).
9
10 -define(LOOKUP_PID, lookup_pid).
11 -define(LOOKUP_PID_RES, lookup_pid_result).
12 -define(LOOKUP_ROW, lookup_row).
13 -define(LOOKUP_ROW_RES, lookup_row_result).
14 -define(UPDATE_TOP_N, update_top_n).
15
16 -spec start() -> pid().
17 start() ->
18 spawn_link(fun() -> loop({1, []}) end).
19
20 -spec update(pid(), pos_integer(), list()) -> ok.
21 update(StorePid, Row, TopNList) ->
22 erlang:send(StorePid, {?UPDATE_TOP_N, Row, TopNList}),
23 ok.
24
25 -spec lookup_pos(pid(), pos_integer()) -> {pos_integer(), pid()}.
26 lookup_pos(StorePid, CurPos) ->
27 erlang:send(StorePid, {?LOOKUP_PID, CurPos, self()}),
28 receive
29 {?LOOKUP_PID_RES, Res} -> Res
30 end.
31
32 -spec lookup_row(pid()) -> pos_integer().
33 lookup_row(StorePid) ->
34 erlang:send(StorePid, {?LOOKUP_ROW, self()}),
35 receive
36 {?LOOKUP_ROW_RES, Res} -> Res
37 end.
38
39 loop({Row, PidList} = TopList) ->
40 NewTopList =
41 receive
42 {?UPDATE_TOP_N, NewRow, NewPidList} ->
43 {NewRow, NewPidList};
44 {?LOOKUP_PID, Pos, From} ->
45 Res =
46 case PidList of
47 [] ->
48 {error, undefined};
49 _ ->
50 case lists:keyfind(Pos, 1, PidList) of
51 false -> lists:last(PidList);
52 Item -> Item
53 end
54 end,
55 erlang:send(From, {?LOOKUP_PID_RES, Res}),
56 TopList;
57 {?LOOKUP_ROW, From} ->
58 erlang:send(From, {?LOOKUP_ROW_RES, Row}),
59 TopList
60 end,
61 loop(NewTopList).
0 %%% @author zhongwen <zhongwencool@gmail.com>
1 -module(observer_cli_system).
2
3 -include("observer_cli.hrl").
4
5 %% API
6 -export([start/1]).
7 -export([clean/1]).
8
9 -define(UTIL_ALLOCATORS, [
10 binary_alloc,
11 driver_alloc,
12 eheap_alloc,
13 ets_alloc,
14 fix_alloc,
15 ll_alloc,
16 sl_alloc,
17 std_alloc,
18 temp_alloc
19 ]).
20
21 %% @doc List System and Architecture, CPU's and Threads metrics in observer's system
22 %% List Memory Allocators: std, ll, eheap, ets, fix, binary, driver.
23 -spec start(ViewOpts) -> no_return when ViewOpts :: view_opts().
24 start(#view_opts{sys = #system{interval = Interval}} = ViewOpts) ->
25 Pid = spawn_link(fun() ->
26 ?output(?CLEAR),
27 Cmd = io_lib:format("ps -o pcpu,pmem,rss,vsz ~s", [os:getpid()]),
28 render_worker(Cmd, Interval, ?INIT_TIME_REF)
29 end),
30 manager(Pid, ViewOpts).
31
32 -spec clean(list()) -> ok.
33 clean(Pids) -> observer_cli_lib:exit_processes(Pids).
34
35 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
36 %%% Private
37 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
38 manager(Pid, #view_opts{sys = AllocatorOpts} = ViewOpts) ->
39 case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [Pid]) of
40 quit ->
41 erlang:send(Pid, quit);
42 {new_interval, NewInterval} ->
43 erlang:send(Pid, {new_interval, NewInterval}),
44 NewAllocate = AllocatorOpts#system{interval = NewInterval},
45 manager(Pid, ViewOpts#view_opts{sys = NewAllocate});
46 _ ->
47 manager(Pid, ViewOpts)
48 end.
49
50 render_worker(Cmd, Interval, LastTimeRef) ->
51 CacheHitInfo = recon_alloc:cache_hit_rates(),
52 AverageBlockCurs = recon_alloc:average_block_sizes(current),
53 AverageBlockMaxes = recon_alloc:average_block_sizes(max),
54 SbcsToMbcsCurs = observer_cli_lib:sbcs_to_mbcs(
55 ?UTIL_ALLOCATORS,
56 recon_alloc:sbcs_to_mbcs(current)
57 ),
58 SbcsToMbcsMaxs = observer_cli_lib:sbcs_to_mbcs(?UTIL_ALLOCATORS, recon_alloc:sbcs_to_mbcs(max)),
59 Sys = render_sys_info(Cmd),
60 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
61 Menu = observer_cli_lib:render_menu(allocator, Text),
62 BlockView = render_block_size_info(
63 AverageBlockCurs,
64 AverageBlockMaxes,
65 SbcsToMbcsCurs,
66 SbcsToMbcsMaxs
67 ),
68 HitView = render_cache_hit_rates(CacheHitInfo, erlang:length(CacheHitInfo)),
69 LastLine = observer_cli_lib:render_last_line("q(quit)"),
70 ?output([?CURSOR_TOP, Menu, Sys, BlockView, HitView, LastLine]),
71 NextTimeRef = observer_cli_lib:next_redraw(LastTimeRef, Interval),
72 receive
73 quit -> quit;
74 {new_interval, NewInterval} -> render_worker(Cmd, NewInterval, NextTimeRef);
75 redraw -> render_worker(Cmd, Interval, NextTimeRef)
76 end.
77
78 render_cache_hit_rates(CacheHitInfo, Len) when Len =< 8 ->
79 Title = ?render([
80 ?UNDERLINE,
81 ?GRAY_BG,
82 ?W("Instance", 8),
83 ?W("Hits", 10),
84 ?W("Calls", 11),
85 ?W("Hit Rate", 98)
86 ]),
87 View = [
88 begin
89 [{hit_rate, HitRate}, {hits, Hit}, {calls, Call}] = proplists:get_value(
90 {instance, Seq},
91 CacheHitInfo
92 ),
93 HitRateStr = observer_cli_lib:to_percent(HitRate),
94 SeqStr = lists:flatten(io_lib:format("~2..0w", [Seq])),
95 TrueHitRate =
96 case Hit == 0 andalso Call == 0 of
97 true -> 0;
98 false -> HitRate
99 end,
100 Process = lists:duplicate(trunc(TrueHitRate * 91), "|"),
101 ?render([
102 ?W(SeqStr, 8),
103 ?W(Hit, 10),
104 ?W(Call, 11),
105 ?W(Process, 89),
106 ?W(HitRateStr, 6)
107 ])
108 end
109 || Seq <- lists:seq(0, Len - 1)
110 ],
111 [Title | View];
112 render_cache_hit_rates(CacheHitInfo, Len) ->
113 Title = ?render([
114 ?UNDERLINE,
115 ?GRAY_BG,
116 "IN|",
117 ?W(" Hits/Calls", 20),
118 ?W("HitRate", 6),
119 "IN|",
120 ?W(" Hits/Calls", 20),
121 ?W("HitRate", 6),
122 "IN|",
123 ?W(" Hits/Calls", 20),
124 ?W("HitRate", 6),
125 "IN|",
126 ?W(" Hits/Calls", 19),
127 ?W("HitRate", 6)
128 ]),
129 Num = Len div 4,
130 Rows = [
131 begin
132 Seq2 = Seq1 + Num,
133 Seq3 = Seq2 + Num,
134 Seq4 = Seq3 + Num,
135 {SeqStr1, Hit1, Call1, HitRateStr1} = get_cachehit_info(Seq1, CacheHitInfo),
136 {SeqStr2, Hit2, Call2, HitRateStr2} = get_cachehit_info(Seq2, CacheHitInfo),
137 {SeqStr3, Hit3, Call3, HitRateStr3} = get_cachehit_info(Seq3, CacheHitInfo),
138 {SeqStr4, Hit4, Call4, HitRateStr4} = get_cachehit_info(Seq4, CacheHitInfo),
139 ?render([
140 SeqStr1,
141 ?W([Hit1, "/", Call1], 19),
142 ?W(HitRateStr1, 6),
143 SeqStr2,
144 ?W([Hit2, "/", Call2], 19),
145 ?W(HitRateStr2, 6),
146 SeqStr3,
147 ?W([Hit3, "/", Call3], 19),
148 ?W(HitRateStr3, 6),
149 SeqStr4,
150 ?W([Hit4, "/", Call4], 18),
151 ?W(HitRateStr4, 6)
152 ])
153 end
154 || Seq1 <- lists:seq(1, Num)
155 ],
156 [Title | Rows].
157
158 render_block_size_info(AverageBlockCurs, AverageBlockMaxes, SbcsToMbcsCurs, SbcsToMbcsMaxs) ->
159 Title = ?render([
160 ?UNDERLINE,
161 ?GRAY_BG,
162 ?W("Allocator Type", 16),
163 ?W("Current Mbcs", 16),
164 ?W("Max Mbcs", 16),
165 ?W("Current Sbcs", 16),
166 ?W("Max Sbcs", 16),
167 ?W("Current SbcsToMbcs", 19),
168 ?W("Max SbcsToMbcs", 19)
169 ]),
170 View = [
171 begin
172 [Type, CMC, MMC, CSC, MSBC, CSTM, MSTM] =
173 get_alloc(
174 AllocKey,
175 AverageBlockCurs,
176 AverageBlockMaxes,
177 SbcsToMbcsCurs,
178 SbcsToMbcsMaxs
179 ),
180 ?render([
181 ?W(Type, 16),
182 ?W(CMC, 16),
183 ?W(MMC, 16),
184 ?W(CSC, 16),
185 ?W(MSBC, 16),
186 ?W(CSTM, 19),
187 ?W(MSTM, 19)
188 ])
189 end
190 || AllocKey <- ?UTIL_ALLOCATORS
191 ],
192 [Title | View].
193
194 get_alloc(Key, Curs, Maxes, STMCurs, STMMaxes) ->
195 CurRes = proplists:get_value(Key, Curs),
196 MaxRes = proplists:get_value(Key, Maxes),
197 CurMbcs = proplists:get_value(mbcs, CurRes),
198 CurSbcs = proplists:get_value(sbcs, CurRes),
199 MaxMbcs = proplists:get_value(mbcs, MaxRes),
200 MaxSbcs = proplists:get_value(sbcs, MaxRes),
201 [
202 atom_to_list(Key),
203 observer_cli_lib:to_byte(CurMbcs),
204 observer_cli_lib:to_byte(MaxMbcs),
205 observer_cli_lib:to_byte(CurSbcs),
206 observer_cli_lib:to_byte(MaxSbcs),
207 proplists:get_value(Key, STMCurs),
208 proplists:get_value(Key, STMMaxes)
209 ].
210
211 render_sys_info(Cmd) ->
212 SysInfo = sys_info(Cmd),
213 {Info, Stat} = info_fields(),
214 SystemAndCPU = fill_info(Info, SysInfo),
215 MemAndStatistics = fill_info(Stat, SysInfo),
216 System = proplists:get_value("System and Architecture", SystemAndCPU),
217 CPU = proplists:get_value("CPU's and Threads", SystemAndCPU),
218 {_, _, Memory} = lists:keyfind("Memory Usage", 1, MemAndStatistics),
219 {_, _, Statistics} = lists:keyfind("Statistics", 1, MemAndStatistics),
220 render_sys_info(System, CPU, Memory, Statistics).
221
222 render_sys_info(System, CPU, Memory, Statistics) ->
223 Title = ?render([
224 ?GRAY_BG,
225 ?UNDERLINE,
226 ?W("System/Architecture", 22),
227 ?W("State", 8),
228 ?W("CPU's and Threads", 23),
229 ?W("State", 7),
230 ?W("Memory Usage", 11),
231 ?W("State", 22),
232 ?W("Statistics", 11),
233 ?W("State", 11)
234 ]),
235 NewSystem = [
236 begin
237 {Key, Value}
238 end
239 || {Key, Value} <- System,
240 Key =/= "Compiled for" andalso Key =/= "smp Support"
241 ],
242 [{_, TotalMem} | _R] = Memory,
243 {bytes, TotalMemInt} = TotalMem,
244 Row = [
245 begin
246 {SysKey, SysVal} = lists:nth(Pos, NewSystem),
247 {CpuKey, CpuVal} = lists:nth(Pos, CPU),
248 {MemKey, MemVal} = lists:nth(Pos, Memory),
249 {StatisticsKey, StatisticsVal} = lists:nth(Pos, Statistics),
250
251 {bytes, MemValInt} = MemVal,
252 Percent = observer_cli_lib:to_percent(MemValInt / TotalMemInt),
253 ?render([
254 ?W(SysKey, 22),
255 ?W(to_list(SysVal), 8),
256 ?W(CpuKey, 23),
257 ?W(to_list(CpuVal), 7),
258 ?W(MemKey, 11),
259 ?W(observer_cli_lib:to_byte(MemValInt), 13),
260 ?W(Percent, 6),
261 ?W(StatisticsKey, 11),
262 ?W(to_list(StatisticsVal), 11)
263 ])
264 end
265 || Pos <- lists:seq(1, 6)
266 ],
267 Compile = ?render([
268 ?UNDERLINE,
269 ?W("compiled for", 22),
270 ?W(to_list(proplists:get_value("Compiled for", System)), 111)
271 ]),
272 [Title | (Row ++ [Compile])].
273
274 sys_info(Cmd) ->
275 MemInfo =
276 try erlang:memory() of
277 Mem -> Mem
278 catch
279 _:_ -> []
280 end,
281
282 SchedulersOnline = erlang:system_info(schedulers_online),
283 SchedulersAvailable =
284 case erlang:system_info(multi_scheduling) of
285 enabled -> SchedulersOnline;
286 _ -> 1
287 end,
288 [_, CmdValue | _] = string:split(os:cmd(Cmd), "\n", all),
289 [CpuPsV, MemPsV, RssPsV, VszPsV] =
290 case lists:filter(fun(Y) -> Y =/= [] end, string:split(CmdValue, " ", all)) of
291 [] -> ["--", "--", "--", "--"];
292 [V1, V2, V3, V4] -> [V1, V2, V3, V4]
293 end,
294
295 {{_, Input}, {_, Output}} = erlang:statistics(io),
296 [
297 {ps_cpu, CpuPsV ++ "%"},
298 {ps_mem, MemPsV ++ "%"},
299 {ps_rss, list_to_integer(RssPsV) * 1024},
300 {ps_vsz, list_to_integer(VszPsV) * 1024},
301 {io_input, Input},
302 {io_output, Output},
303
304 {logical_processors, erlang:system_info(logical_processors)},
305 {logical_processors_online, erlang:system_info(logical_processors_online)},
306 {logical_processors_available, erlang:system_info(logical_processors_available)},
307 {schedulers, erlang:system_info(schedulers)},
308 {schedulers_online, SchedulersOnline},
309 {schedulers_available, SchedulersAvailable},
310
311 {otp_release, erlang:system_info(otp_release)},
312 {version, erlang:system_info(version)},
313 {system_architecture, erlang:system_info(system_architecture)},
314 {kernel_poll, erlang:system_info(kernel_poll)},
315 {smp_support, erlang:system_info(smp_support)},
316 {threads, erlang:system_info(threads)},
317 {thread_pool_size, erlang:system_info(thread_pool_size)},
318 {wordsize_internal, erlang:system_info({wordsize, internal})},
319 {wordsize_external, erlang:system_info({wordsize, external})},
320 {alloc_info, alloc_info()}
321 | MemInfo
322 ].
323
324 alloc_info() ->
325 Alloc = erlang:system_info(alloc_util_allocators),
326 try erlang:system_info({allocator_sizes, Alloc}) of
327 Allocators -> Allocators
328 catch
329 _:_ -> []
330 end.
331
332 fill_info([{dynamic, Key} | Rest], Data) when is_atom(Key) ->
333 case proplists:get_value(Key, Data) of
334 undefined -> [undefined | fill_info(Rest, Data)];
335 {Str, Value} -> [{Str, Value} | fill_info(Rest, Data)]
336 end;
337 fill_info([{Str, Key} | Rest], Data) when is_atom(Key) ->
338 case proplists:get_value(Key, Data) of
339 undefined -> [undefined | fill_info(Rest, Data)];
340 Value -> [{Str, Value} | fill_info(Rest, Data)]
341 end;
342 fill_info([{Str, Attrib, Key} | Rest], Data) when is_atom(Key) ->
343 case proplists:get_value(Key, Data) of
344 undefined -> [undefined | fill_info(Rest, Data)];
345 Value -> [{Str, Attrib, Value} | fill_info(Rest, Data)]
346 end;
347 fill_info([{Str, {Format, Key}} | Rest], Data) when is_atom(Key) ->
348 case proplists:get_value(Key, Data) of
349 undefined -> [undefined | fill_info(Rest, Data)];
350 Value -> [{Str, {Format, Value}} | fill_info(Rest, Data)]
351 end;
352 fill_info([{Str, Attrib, {Format, Key}} | Rest], Data) when is_atom(Key) ->
353 case proplists:get_value(Key, Data) of
354 undefined -> [undefined | fill_info(Rest, Data)];
355 Value -> [{Str, Attrib, {Format, Value}} | fill_info(Rest, Data)]
356 end;
357 fill_info([{Str, SubStructure} | Rest], Data) when is_list(SubStructure) ->
358 [{Str, fill_info(SubStructure, Data)} | fill_info(Rest, Data)];
359 fill_info([{Str, Attrib, SubStructure} | Rest], Data) ->
360 [{Str, Attrib, fill_info(SubStructure, Data)} | fill_info(Rest, Data)];
361 fill_info([], _) ->
362 [].
363
364 to_list(Val) when is_integer(Val) -> integer_to_list(Val);
365 to_list(Val) when is_atom(Val) -> atom_to_list(Val);
366 to_list({bytes, Val}) -> observer_cli_lib:to_byte(Val);
367 to_list(Val) -> Val.
368
369 info_fields() ->
370 Info = [
371 {"System and Architecture", [
372 {"System Version", otp_release},
373 {"Erts Version", version},
374 {"Compiled for", system_architecture},
375 {"Emulator Wordsize", wordsize_external},
376 {"Process Wordsize", wordsize_internal},
377 {"Smp Support", smp_support},
378 {"Thread Support", threads},
379 {"Async thread pool size", thread_pool_size}
380 ]},
381 {"CPU's and Threads", [
382 {"Logical CPU's", logical_processors},
383 {"Online Logical CPU's", logical_processors_online},
384 {"Available Logical CPU's", logical_processors_available},
385 {"Schedulers", schedulers},
386 {"Online schedulers", schedulers_online},
387 {"Available schedulers", schedulers_available}
388 ]}
389 ],
390 Stat = [
391 {"Memory Usage", right, [
392 {"Total", {bytes, total}},
393 {"Processes", {bytes, processes}},
394 {"Atoms", {bytes, atom}},
395 {"Binaries", {bytes, binary}},
396 {"Code", {bytes, code}},
397 {"Ets", {bytes, ets}}
398 ]},
399 {"Statistics", right, [
400 {"ps -o pcpu", ps_cpu},
401 {"ps -o pmem", ps_mem},
402 {"ps -o rss", {bytes, ps_rss}},
403 {"ps -o vsz", {bytes, ps_vsz}},
404 {"Total IOIn", {bytes, io_input}},
405 {"Total IOOut", {bytes, io_output}}
406 ]}
407 ],
408 {Info, Stat}.
409
410 get_cachehit_info(Seq, CacheHitInfo) ->
411 [{hit_rate, HitRate}, {hits, Hit}, {calls, Call}] = proplists:get_value(
412 {instance, Seq},
413 CacheHitInfo
414 ),
415 SeqStr = lists:flatten(io_lib:format("\e[92m~2..0w| \e[0m", [Seq])),
416 HitRateStr = observer_cli_lib:to_percent(HitRate),
417 {SeqStr, integer_to_list(Hit), integer_to_list(Call), HitRateStr}.
0 %%% @author zhongwen <zhongwencool@gmail.com>
1 -module(observer_cli_system).
2
3 -include("observer_cli.hrl").
4
5 %% API
6 -export([start/1]).
7 -export([clean/1]).
8
9 -define(UTIL_ALLOCATORS, [
10 binary_alloc,
11 driver_alloc,
12 eheap_alloc,
13 ets_alloc,
14 fix_alloc,
15 ll_alloc,
16 sl_alloc,
17 std_alloc,
18 temp_alloc
19 ]).
20
21 %% @doc List System and Architecture, CPU's and Threads metrics in observer's system
22 %% List Memory Allocators: std, ll, eheap, ets, fix, binary, driver.
23 -spec start(ViewOpts) -> no_return when ViewOpts :: view_opts().
24 start(#view_opts{sys = #system{interval = Interval}} = ViewOpts) ->
25 Pid = spawn_link(fun() ->
26 ?output(?CLEAR),
27 Cmd = io_lib:format("ps -o pcpu,pmem,rss,vsz ~s", [os:getpid()]),
28 render_worker(Cmd, Interval, ?INIT_TIME_REF)
29 end),
30 manager(Pid, ViewOpts).
31
32 -spec clean(list()) -> ok.
33 clean(Pids) -> observer_cli_lib:exit_processes(Pids).
34
35 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
36 %%% Private
37 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
38 manager(Pid, #view_opts{sys = AllocatorOpts} = ViewOpts) ->
39 case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [Pid]) of
40 quit ->
41 erlang:send(Pid, quit);
42 {new_interval, NewInterval} ->
43 erlang:send(Pid, {new_interval, NewInterval}),
44 NewAllocate = AllocatorOpts#system{interval = NewInterval},
45 manager(Pid, ViewOpts#view_opts{sys = NewAllocate});
46 _ ->
47 manager(Pid, ViewOpts)
48 end.
49
50 render_worker(Cmd, Interval, LastTimeRef) ->
51 CacheHitInfo = recon_alloc:cache_hit_rates(),
52 AverageBlockCurs = recon_alloc:average_block_sizes(current),
53 AverageBlockMaxes = recon_alloc:average_block_sizes(max),
54 SbcsToMbcsCurs = observer_cli_lib:sbcs_to_mbcs(
55 ?UTIL_ALLOCATORS,
56 recon_alloc:sbcs_to_mbcs(current)
57 ),
58 SbcsToMbcsMaxs = observer_cli_lib:sbcs_to_mbcs(?UTIL_ALLOCATORS, recon_alloc:sbcs_to_mbcs(max)),
59 Sys = render_sys_info(Cmd),
60 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
61 Menu = observer_cli_lib:render_menu(allocator, Text),
62 BlockView = render_block_size_info(
63 AverageBlockCurs,
64 AverageBlockMaxes,
65 SbcsToMbcsCurs,
66 SbcsToMbcsMaxs
67 ),
68 HitView = render_cache_hit_rates(CacheHitInfo, erlang:length(CacheHitInfo)),
69 LastLine = observer_cli_lib:render_last_line("q(quit)"),
70 ?output([?CURSOR_TOP, Menu, Sys, BlockView, HitView, LastLine]),
71 NextTimeRef = observer_cli_lib:next_redraw(LastTimeRef, Interval),
72 receive
73 quit -> quit;
74 {new_interval, NewInterval} -> render_worker(Cmd, NewInterval, NextTimeRef);
75 redraw -> render_worker(Cmd, Interval, NextTimeRef)
76 end.
77
78 render_cache_hit_rates(CacheHitInfo, Len) when Len =< 8 ->
79 Title = ?render([
80 ?UNDERLINE,
81 ?GRAY_BG,
82 ?W("Instance", 8),
83 ?W("Hits", 10),
84 ?W("Calls", 11),
85 ?W("Hit Rate", 98)
86 ]),
87 View = [
88 begin
89 [{hit_rate, HitRate}, {hits, Hit}, {calls, Call}] = proplists:get_value(
90 {instance, Seq},
91 CacheHitInfo
92 ),
93 HitRateStr = observer_cli_lib:to_percent(HitRate),
94 SeqStr = lists:flatten(io_lib:format("~2..0w", [Seq])),
95 TrueHitRate =
96 case Hit == 0 andalso Call == 0 of
97 true -> 0;
98 false -> HitRate
99 end,
100 Process = lists:duplicate(trunc(TrueHitRate * 91), "|"),
101 ?render([
102 ?W(SeqStr, 8),
103 ?W(Hit, 10),
104 ?W(Call, 11),
105 ?W(Process, 89),
106 ?W(HitRateStr, 6)
107 ])
108 end
109 || Seq <- lists:seq(0, Len - 1)
110 ],
111 [Title | View];
112 render_cache_hit_rates(CacheHitInfo, Len) ->
113 Title = ?render([
114 ?UNDERLINE,
115 ?GRAY_BG,
116 "IN|",
117 ?W(" Hits/Calls", 20),
118 ?W("HitRate", 6),
119 "IN|",
120 ?W(" Hits/Calls", 20),
121 ?W("HitRate", 6),
122 "IN|",
123 ?W(" Hits/Calls", 20),
124 ?W("HitRate", 6),
125 "IN|",
126 ?W(" Hits/Calls", 19),
127 ?W("HitRate", 6)
128 ]),
129 Num = Len div 4,
130 Rows = [
131 begin
132 Seq2 = Seq1 + Num,
133 Seq3 = Seq2 + Num,
134 Seq4 = Seq3 + Num,
135 {SeqStr1, Hit1, Call1, HitRateStr1} = get_cachehit_info(Seq1, CacheHitInfo),
136 {SeqStr2, Hit2, Call2, HitRateStr2} = get_cachehit_info(Seq2, CacheHitInfo),
137 {SeqStr3, Hit3, Call3, HitRateStr3} = get_cachehit_info(Seq3, CacheHitInfo),
138 {SeqStr4, Hit4, Call4, HitRateStr4} = get_cachehit_info(Seq4, CacheHitInfo),
139 ?render([
140 SeqStr1,
141 ?W([Hit1, "/", Call1], 19),
142 ?W(HitRateStr1, 6),
143 SeqStr2,
144 ?W([Hit2, "/", Call2], 19),
145 ?W(HitRateStr2, 6),
146 SeqStr3,
147 ?W([Hit3, "/", Call3], 19),
148 ?W(HitRateStr3, 6),
149 SeqStr4,
150 ?W([Hit4, "/", Call4], 18),
151 ?W(HitRateStr4, 6)
152 ])
153 end
154 || Seq1 <- lists:seq(1, Num)
155 ],
156 [Title | Rows].
157
158 render_block_size_info(AverageBlockCurs, AverageBlockMaxes, SbcsToMbcsCurs, SbcsToMbcsMaxs) ->
159 Title = ?render([
160 ?UNDERLINE,
161 ?GRAY_BG,
162 ?W("Allocator Type", 16),
163 ?W("Current Mbcs", 16),
164 ?W("Max Mbcs", 16),
165 ?W("Current Sbcs", 16),
166 ?W("Max Sbcs", 16),
167 ?W("Current SbcsToMbcs", 19),
168 ?W("Max SbcsToMbcs", 19)
169 ]),
170 View = [
171 begin
172 [Type, CMC, MMC, CSC, MSBC, CSTM, MSTM] =
173 get_alloc(
174 AllocKey,
175 AverageBlockCurs,
176 AverageBlockMaxes,
177 SbcsToMbcsCurs,
178 SbcsToMbcsMaxs
179 ),
180 ?render([
181 ?W(Type, 16),
182 ?W(CMC, 16),
183 ?W(MMC, 16),
184 ?W(CSC, 16),
185 ?W(MSBC, 16),
186 ?W(CSTM, 19),
187 ?W(MSTM, 19)
188 ])
189 end
190 || AllocKey <- ?UTIL_ALLOCATORS
191 ],
192 [Title | View].
193
194 get_alloc(Key, Curs, Maxes, STMCurs, STMMaxes) ->
195 CurRes = proplists:get_value(Key, Curs),
196 MaxRes = proplists:get_value(Key, Maxes),
197 CurMbcs = proplists:get_value(mbcs, CurRes),
198 CurSbcs = proplists:get_value(sbcs, CurRes),
199 MaxMbcs = proplists:get_value(mbcs, MaxRes),
200 MaxSbcs = proplists:get_value(sbcs, MaxRes),
201 [
202 atom_to_list(Key),
203 observer_cli_lib:to_byte(CurMbcs),
204 observer_cli_lib:to_byte(MaxMbcs),
205 observer_cli_lib:to_byte(CurSbcs),
206 observer_cli_lib:to_byte(MaxSbcs),
207 proplists:get_value(Key, STMCurs),
208 proplists:get_value(Key, STMMaxes)
209 ].
210
211 render_sys_info(Cmd) ->
212 SysInfo = sys_info(Cmd),
213 {Info, Stat} = info_fields(),
214 SystemAndCPU = fill_info(Info, SysInfo),
215 MemAndStatistics = fill_info(Stat, SysInfo),
216 System = proplists:get_value("System and Architecture", SystemAndCPU),
217 CPU = proplists:get_value("CPU's and Threads", SystemAndCPU),
218 {_, _, Memory} = lists:keyfind("Memory Usage", 1, MemAndStatistics),
219 {_, _, Statistics} = lists:keyfind("Statistics", 1, MemAndStatistics),
220 render_sys_info(System, CPU, Memory, Statistics).
221
222 render_sys_info(System, CPU, Memory, Statistics) ->
223 Title = ?render([
224 ?GRAY_BG,
225 ?UNDERLINE,
226 ?W("System/Architecture", 22),
227 ?W("State", 8),
228 ?W("CPU's and Threads", 23),
229 ?W("State", 7),
230 ?W("Memory Usage", 11),
231 ?W("State", 22),
232 ?W("Statistics", 11),
233 ?W("State", 11)
234 ]),
235 NewSystem = [
236 begin
237 {Key, Value}
238 end
239 || {Key, Value} <- System,
240 Key =/= "Compiled for" andalso Key =/= "smp Support"
241 ],
242 [{_, TotalMem} | _R] = Memory,
243 {bytes, TotalMemInt} = TotalMem,
244 Row = [
245 begin
246 {SysKey, SysVal} = lists:nth(Pos, NewSystem),
247 {CpuKey, CpuVal} = lists:nth(Pos, CPU),
248 {MemKey, MemVal} = lists:nth(Pos, Memory),
249 {StatisticsKey, StatisticsVal} = lists:nth(Pos, Statistics),
250
251 {bytes, MemValInt} = MemVal,
252 Percent = observer_cli_lib:to_percent(MemValInt / TotalMemInt),
253 ?render([
254 ?W(SysKey, 22),
255 ?W(to_list(SysVal), 8),
256 ?W(CpuKey, 23),
257 ?W(to_list(CpuVal), 7),
258 ?W(MemKey, 11),
259 ?W(observer_cli_lib:to_byte(MemValInt), 13),
260 ?W(Percent, 6),
261 ?W(StatisticsKey, 11),
262 ?W(to_list(StatisticsVal), 11)
263 ])
264 end
265 || Pos <- lists:seq(1, 6)
266 ],
267 Compile = ?render([
268 ?UNDERLINE,
269 ?W("compiled for", 22),
270 ?W(to_list(proplists:get_value("Compiled for", System)), 111)
271 ]),
272 [Title | (Row ++ [Compile])].
273
274 sys_info(Cmd) ->
275 MemInfo =
276 try erlang:memory() of
277 Mem -> Mem
278 catch
279 _:_ -> []
280 end,
281
282 SchedulersOnline = erlang:system_info(schedulers_online),
283 SchedulersAvailable =
284 case erlang:system_info(multi_scheduling) of
285 enabled -> SchedulersOnline;
286 _ -> 1
287 end,
288 [_, CmdValue | _] = string:split(os:cmd(Cmd), "\n", all),
289 [CpuPsV, MemPsV, RssPsV, VszPsV] =
290 case lists:filter(fun(Y) -> Y =/= [] end, string:split(CmdValue, " ", all)) of
291 [] -> ["--", "--", "--", "--"];
292 [V1, V2, V3, V4] -> [V1, V2, V3, V4]
293 end,
294
295 {{_, Input}, {_, Output}} = erlang:statistics(io),
296 [
297 {ps_cpu, CpuPsV ++ "%"},
298 {ps_mem, MemPsV ++ "%"},
299 {ps_rss, list_to_integer(RssPsV) * 1024},
300 {ps_vsz, list_to_integer(VszPsV) * 1024},
301 {io_input, Input},
302 {io_output, Output},
303
304 {logical_processors, erlang:system_info(logical_processors)},
305 {logical_processors_online, erlang:system_info(logical_processors_online)},
306 {logical_processors_available, erlang:system_info(logical_processors_available)},
307 {schedulers, erlang:system_info(schedulers)},
308 {schedulers_online, SchedulersOnline},
309 {schedulers_available, SchedulersAvailable},
310
311 {otp_release, erlang:system_info(otp_release)},
312 {version, erlang:system_info(version)},
313 {system_architecture, erlang:system_info(system_architecture)},
314 {kernel_poll, erlang:system_info(kernel_poll)},
315 {smp_support, erlang:system_info(smp_support)},
316 {threads, erlang:system_info(threads)},
317 {thread_pool_size, erlang:system_info(thread_pool_size)},
318 {wordsize_internal, erlang:system_info({wordsize, internal})},
319 {wordsize_external, erlang:system_info({wordsize, external})},
320 {alloc_info, alloc_info()}
321 | MemInfo
322 ].
323
324 alloc_info() ->
325 Alloc = erlang:system_info(alloc_util_allocators),
326 try erlang:system_info({allocator_sizes, Alloc}) of
327 Allocators -> Allocators
328 catch
329 _:_ -> []
330 end.
331
332 fill_info([{dynamic, Key} | Rest], Data) when is_atom(Key) ->
333 case proplists:get_value(Key, Data) of
334 undefined -> [undefined | fill_info(Rest, Data)];
335 {Str, Value} -> [{Str, Value} | fill_info(Rest, Data)]
336 end;
337 fill_info([{Str, Key} | Rest], Data) when is_atom(Key) ->
338 case proplists:get_value(Key, Data) of
339 undefined -> [undefined | fill_info(Rest, Data)];
340 Value -> [{Str, Value} | fill_info(Rest, Data)]
341 end;
342 fill_info([{Str, Attrib, Key} | Rest], Data) when is_atom(Key) ->
343 case proplists:get_value(Key, Data) of
344 undefined -> [undefined | fill_info(Rest, Data)];
345 Value -> [{Str, Attrib, Value} | fill_info(Rest, Data)]
346 end;
347 fill_info([{Str, {Format, Key}} | Rest], Data) when is_atom(Key) ->
348 case proplists:get_value(Key, Data) of
349 undefined -> [undefined | fill_info(Rest, Data)];
350 Value -> [{Str, {Format, Value}} | fill_info(Rest, Data)]
351 end;
352 fill_info([{Str, Attrib, {Format, Key}} | Rest], Data) when is_atom(Key) ->
353 case proplists:get_value(Key, Data) of
354 undefined -> [undefined | fill_info(Rest, Data)];
355 Value -> [{Str, Attrib, {Format, Value}} | fill_info(Rest, Data)]
356 end;
357 fill_info([{Str, SubStructure} | Rest], Data) when is_list(SubStructure) ->
358 [{Str, fill_info(SubStructure, Data)} | fill_info(Rest, Data)];
359 fill_info([{Str, Attrib, SubStructure} | Rest], Data) ->
360 [{Str, Attrib, fill_info(SubStructure, Data)} | fill_info(Rest, Data)];
361 fill_info([], _) ->
362 [].
363
364 to_list(Val) when is_integer(Val) -> integer_to_list(Val);
365 to_list(Val) when is_atom(Val) -> atom_to_list(Val);
366 to_list({bytes, Val}) -> observer_cli_lib:to_byte(Val);
367 to_list(Val) -> Val.
368
369 info_fields() ->
370 Info = [
371 {"System and Architecture", [
372 {"System Version", otp_release},
373 {"Erts Version", version},
374 {"Compiled for", system_architecture},
375 {"Emulator Wordsize", wordsize_external},
376 {"Process Wordsize", wordsize_internal},
377 {"Smp Support", smp_support},
378 {"Thread Support", threads},
379 {"Async thread pool size", thread_pool_size}
380 ]},
381 {"CPU's and Threads", [
382 {"Logical CPU's", logical_processors},
383 {"Online Logical CPU's", logical_processors_online},
384 {"Available Logical CPU's", logical_processors_available},
385 {"Schedulers", schedulers},
386 {"Online schedulers", schedulers_online},
387 {"Available schedulers", schedulers_available}
388 ]}
389 ],
390 Stat = [
391 {"Memory Usage", right, [
392 {"Total", {bytes, total}},
393 {"Processes", {bytes, processes}},
394 {"Atoms", {bytes, atom}},
395 {"Binaries", {bytes, binary}},
396 {"Code", {bytes, code}},
397 {"Ets", {bytes, ets}}
398 ]},
399 {"Statistics", right, [
400 {"ps -o pcpu", ps_cpu},
401 {"ps -o pmem", ps_mem},
402 {"ps -o rss", {bytes, ps_rss}},
403 {"ps -o vsz", {bytes, ps_vsz}},
404 {"Total IOIn", {bytes, io_input}},
405 {"Total IOOut", {bytes, io_output}}
406 ]}
407 ],
408 {Info, Stat}.
409
410 get_cachehit_info(Seq, CacheHitInfo) ->
411 [{hit_rate, HitRate}, {hits, Hit}, {calls, Call}] = proplists:get_value(
412 {instance, Seq},
413 CacheHitInfo
414 ),
415 SeqStr = lists:flatten(io_lib:format("\e[92m~2..0w| \e[0m", [Seq])),
416 HitRateStr = observer_cli_lib:to_percent(HitRate),
417 {SeqStr, integer_to_list(Hit), integer_to_list(Call), HitRateStr}.
385385 timestamp :: non_neg_integer(),
386386 epoch :: epoch(),
387387 num :: non_neg_integer(),
388 type :: chunk_type()
388 type :: chunk_type(),
389 %% size of data + trailer
390 size :: non_neg_integer(),
391 %% position in segment file
392 pos :: integer()
389393 }).
390394 -record(seg_info,
391395 {file :: file:filename(),
794798 empty | {offset(), offset()}}} |
795799 {error,
796800 {invalid_last_offset_epoch, offset(), offset()}}.
797 init_data_reader({StartOffset, PrevEO}, #{dir := Dir,
798 readers_counter_fun := Fun} = Config) ->
801 init_data_reader({StartChunkId, PrevEOT}, #{dir := Dir} = Config) ->
799802 SegInfos = build_log_overview(Dir),
800803 Range = offset_range_from_segment_infos(SegInfos),
801804 ?DEBUG("osiris_segment:init_data_reader/2 at ~b prev "
802 "~w range: ~w",
803 [StartOffset, PrevEO, Range]),
805 "~w local range: ~w",
806 [StartChunkId, PrevEOT, Range]),
804807 %% Invariant: there is always at least one segment left on disk
805808 case Range of
806 {F, _L} when StartOffset < F ->
807 %% if a lower than exisiting is request simply forward
808 %% it to the first offset of the log
809 %% in this case we cannot validate PrevEO - instead
810 %% the replica should truncate all of it's exisiting log
811 case find_segment_for_offset(F, SegInfos) of
812 not_found ->
813 %% this is unexpected and thus an error
814 exit({segment_not_found, F, SegInfos});
815 {_, StartSegmentInfo} ->
816 {ok,
817 init_data_reader_from_segment(Config, StartSegmentInfo, F, Fun)}
818 end;
819 empty when StartOffset > 0 ->
809 {FstOffs, LastOffs}
810 when StartChunkId < FstOffs
811 orelse StartChunkId > LastOffs + 1 ->
820812 {error, {offset_out_of_range, Range}};
821 {_F, L} when StartOffset > L + 1 ->
822 %% if we are trying to attach to anything larger than
823 %% the next offset (i.e last +1) this is in out of range
824 %% error
813 empty when StartChunkId > 0 ->
825814 {error, {offset_out_of_range, Range}};
826 _ ->
815 _ when PrevEOT == empty ->
827816 %% this assumes the offset is in range
828817 %% first we need to validate PrevEO
829 case PrevEO of
830 empty ->
831 case find_segment_for_offset(StartOffset, SegInfos) of
832 not_found ->
833 %% this is unexpected and thus an error
834 exit({segment_not_found, StartOffset, SegInfos});
835 {_, StartSegmentInfo} ->
836 {ok,
837 init_data_reader_from_segment(Config,
838 StartSegmentInfo,
839 StartOffset,
840 Fun)}
841 end;
842 {PrevE, PrevO, _PrevTs} ->
843 case find_segment_for_offset(PrevO, SegInfos) of
844 not_found ->
845 %% this is unexpected and thus an error
846 {error,
847 {invalid_last_offset_epoch, PrevE, unknown}};
848 {_, SegmentInfo = #seg_info{file = PrevSeg}} ->
849 %% prev segment exists, does it have the correct
850 %% epoch?
851 {ok, Fd} = file:open(PrevSeg, [raw, binary, read]),
852 %% TODO: next offset needs to be a chunk offset
853 {_, FilePos} = scan_idx(PrevO, SegmentInfo),
854 {ok, FilePos} = file:position(Fd, FilePos),
855 case file:read(Fd, ?HEADER_SIZE_B) of
856 {ok,
857 <<?MAGIC:4/unsigned,
858 ?VERSION:4/unsigned,
859 _ChType:8/unsigned,
860 _NumEntries:16/unsigned,
861 _NumRecords:32/unsigned,
862 _Timestamp:64/signed,
863 PrevE:64/unsigned,
864 PrevO:64/unsigned,
865 _Crc:32/integer,
866 _DataSize:32/unsigned,
867 _TrailerSize:32/unsigned,
868 _Reserved:32>>} ->
869 ok = file:close(Fd),
870 {ok,
871 init_data_reader_from_segment(Config,
872 element(2,
873 find_segment_for_offset(StartOffset,
874 SegInfos)),
875 StartOffset,
876 Fun)};
877 {ok,
878 <<?MAGIC:4/unsigned,
879 ?VERSION:4/unsigned,
880 _ChType:8/unsigned,
881 _NumEntries:16/unsigned,
882 _NumRecords:32/unsigned,
883 _Timestamp:64/signed,
884 OtherE:64/unsigned,
885 PrevO:64/unsigned,
886 _Crc:32/integer,
887 _DataSize:32/unsigned,
888 _TrailerSize:32/unsigned,
889 _Reserved:32>>} ->
890 ok = file:close(Fd),
891 {error,
892 {invalid_last_offset_epoch, PrevE, OtherE}}
893 end
894 end
818 {ok, init_data_reader_from(
819 StartChunkId,
820 find_segment_for_offset(StartChunkId, SegInfos),
821 Config)};
822 _ ->
823 {PrevEpoch, PrevChunkId, _PrevTs} = PrevEOT,
824 case check_chunk_has_expected_epoch(PrevChunkId, PrevEpoch, SegInfos) of
825 ok ->
826 {ok, init_data_reader_from(
827 StartChunkId,
828 find_segment_for_offset(StartChunkId, SegInfos),
829 Config)};
830 {error, _} = Err ->
831 Err
895832 end
896833 end.
897834
898 init_data_reader_from_segment(#{dir := Dir, name := Name} = Config,
899 SegmentInfo = #seg_info{file = StartSegment},
900 NextOffs, CounterFun) ->
901 {ok, Fd} = file:open(StartSegment, [raw, binary, read]),
902 %% TODO: next offset needs to be a chunk offset
903 {_, FilePos} = scan_idx(NextOffs, SegmentInfo),
904 {ok, _Pos} = file:position(Fd, FilePos),
835 check_chunk_has_expected_epoch(ChunkId, Epoch, SegInfos) ->
836 case find_segment_for_offset(ChunkId, SegInfos) of
837 not_found ->
838 %% this is unexpected and thus an error
839 {error,
840 {invalid_last_offset_epoch, Epoch, unknown}};
841 {found, SegmentInfo = #seg_info{file = _PrevSeg}} ->
842 %% prev segment exists, does it have the correct
843 %% epoch?
844 case scan_idx(ChunkId, SegmentInfo) of
845 {ChunkId, Epoch, _PrevPos} ->
846 ok;
847 {ChunkId, OtherEpoch, _} ->
848 {error,
849 {invalid_last_offset_epoch, Epoch, OtherEpoch}}
850 end
851 end.
852
853 init_data_reader_at(ChunkId, FilePos, File,
854 #{dir := Dir, name := Name,
855 readers_counter_fun := CountersFun} = Config) ->
856 {ok, Fd} = file:open(File, [raw, binary, read]),
857 {ok, FilePos} = file:position(Fd, FilePos),
905858 Cnt = make_counter(Config),
906 counters:put(Cnt, ?C_OFFSET, NextOffs - 1),
907 CounterFun(1),
859 counters:put(Cnt, ?C_OFFSET, ChunkId - 1),
860 CountersFun(1),
908861 #?MODULE{cfg =
909862 #cfg{directory = Dir,
910863 counter = Cnt,
911864 name = Name,
912 readers_counter_fun = CounterFun,
865 readers_counter_fun = CountersFun,
913866 first_offset_fun = fun (_) -> ok end},
914867 mode =
915868 #read{type = data,
916869 offset_ref = maps:get(offset_ref, Config, undefined),
917 next_offset = NextOffs,
870 next_offset = ChunkId,
918871 chunk_selector = all,
919872 transport = maps:get(transport, Config, tcp)},
920873 fd = Fd}.
874
875 init_data_reader_from(ChunkId,
876 {end_of_log, #seg_info{file = File,
877 last = LastChunk}},
878 Config) ->
879 {ChunkId, AttachPos} = next_location(LastChunk),
880 init_data_reader_at(ChunkId, AttachPos, File, Config);
881 init_data_reader_from(ChunkId,
882 {found, #seg_info{file = File} = SegInfo},
883 Config) ->
884 {ChunkId, _Epoch, FilePos} = scan_idx(ChunkId, SegInfo),
885 init_data_reader_at(ChunkId, FilePos, File, Config).
921886
922887 %% @doc Initialise a new offset reader
923888 %% @param OffsetSpec specifies where in the log to attach the reader
939904 {ok, state()} |
940905 {error,
941906 {offset_out_of_range,
942 empty | {From :: offset(), To :: offset()}}}.
943 init_offset_reader({abs, Offs}, #{dir := Dir} = Conf) ->
944 %% TODO: some unnecessary computation here
907 empty | {From :: offset(), To :: offset()}}} |
908 {error, {invalid_chunk_header, term()}}.
909 init_offset_reader(OffsetSpec, Conf) ->
910 try
911 init_offset_reader0(OffsetSpec, Conf)
912 catch
913 missing_file ->
914 init_offset_reader0(OffsetSpec, Conf)
915 end.
916
917 init_offset_reader0({abs, Offs}, #{dir := Dir} = Conf) ->
945918 Range = offset_range_from_segment_infos(build_log_overview(Dir)),
946919 case Range of
947920 empty ->
950923 {error, {offset_out_of_range, Range}};
951924 _ ->
952925 %% it is in range, convert to standard offset
953 init_offset_reader(Offs, Conf)
926 init_offset_reader0(Offs, Conf)
954927 end;
955 init_offset_reader({timestamp, Ts}, #{dir := Dir} = Conf) ->
928 init_offset_reader0({timestamp, Ts}, #{dir := Dir} = Conf) ->
956929 case build_log_overview(Dir) of
957930 [] ->
958 init_offset_reader(next, Conf);
959 [#seg_info{first = #chunk_info{timestamp = Fst}} | _]
931 init_offset_reader0(next, Conf);
932 [#seg_info{file = SegmentFile,
933 first = #chunk_info{timestamp = Fst,
934 pos = FilePos,
935 id = ChunkId}} | _]
960936 when is_integer(Fst) andalso Fst > Ts ->
961937 %% timestamp is lower than the first timestamp available
962 init_offset_reader(first, Conf);
938 open_offset_reader_at(SegmentFile, ChunkId, FilePos, Conf);
963939 SegInfos ->
964940 case lists:search(fun (#seg_info{first = #chunk_info{timestamp = F},
965941 last = #chunk_info{timestamp = L}})
971947 end,
972948 SegInfos)
973949 of
974 {value, Info} ->
950 {value, #seg_info{file = SegmentFile} = Info} ->
975951 %% segment was found, now we need to scan index to
976952 %% find nearest offset
977 ChunkId = chunk_id_for_timestamp(Info, Ts),
978 init_offset_reader(ChunkId, Conf);
953 {ChunkId, FilePos} = chunk_location_for_timestamp(Info, Ts),
954 open_offset_reader_at(SegmentFile, ChunkId, FilePos, Conf);
979955 false ->
980956 %% segment was not found, attach next
981 init_offset_reader(next, Conf)
957 %% this should be rare so no need to call the more optimal
958 %% open_offset_reader_at/4 function
959 init_offset_reader0(next, Conf)
982960 end
983961 end;
984 init_offset_reader(OffsetSpec,
985 #{dir := Dir,
986 name := Name,
987 offset_ref := OffsetRef,
988 readers_counter_fun := ReaderCounterFun,
989 options := Options} =
990 Conf) ->
962 init_offset_reader0(first, #{dir := Dir} = Conf) ->
963 case build_log_overview(Dir) of
964 [#seg_info{file = File,
965 first = undefined}] ->
966 %% empty log, attach at 0
967 open_offset_reader_at(File, 0, ?LOG_HEADER_SIZE, Conf);
968 [#seg_info{file = File,
969 first = #chunk_info{id = FirstChunkId,
970 pos = FilePos}} | _] ->
971 open_offset_reader_at(File, FirstChunkId, FilePos, Conf);
972 _ ->
973 exit(no_segments_found)
974 end;
975 init_offset_reader0(next, #{dir := Dir} = Conf) ->
976 SegInfos = build_log_overview(Dir),
977 case lists:reverse(SegInfos) of
978 [#seg_info{file = File,
979 last = LastChunk} | _] ->
980 {NextChunkId, FilePos} = next_location(LastChunk),
981 open_offset_reader_at(File, NextChunkId, FilePos, Conf);
982 _ ->
983 exit(no_segments_found)
984 end;
985 init_offset_reader0(last, #{dir := Dir} = Conf) ->
986 SegInfos = build_log_overview(Dir),
987 case lists:reverse(SegInfos) of
988 [#seg_info{file = File,
989 last = undefined}] ->
990 %% empty log, attach at 0
991 open_offset_reader_at(File, 0, ?LOG_HEADER_SIZE, Conf);
992 [#seg_info{file = File,
993 last = #chunk_info{type = ?CHNK_USER,
994 id = LastChunkId,
995 pos = FilePos}} | _] ->
996 open_offset_reader_at(File, LastChunkId, FilePos, Conf);
997 _ ->
998 case last_user_chunk_location(SegInfos) of
999 not_found ->
1000 ?DEBUG("~s:~s use chunk not found, fall back to next",
1001 [?MODULE, ?FUNCTION_NAME]),
1002 %% no user chunks in stream, this is awkward, fall back to next
1003 init_offset_reader0(next, Conf);
1004 {ChunkId, FilePos, #seg_info{file = File}} ->
1005 open_offset_reader_at(File, ChunkId, FilePos, Conf)
1006 end
1007 end;
1008 init_offset_reader0(OffsetSpec, #{dir := Dir} = Conf)
1009 when is_integer(OffsetSpec) ->
9911010 SegInfos = build_log_overview(Dir),
9921011 ChunkRange = chunk_range_from_segment_infos(SegInfos),
9931012 Range = offset_range_from_chunk_range(ChunkRange),
994 ?DEBUG("osiris_log:init_offset_reader/2 spec ~w range "
995 "~w ",
1013 ?DEBUG("osiris_log:init_offset_reader0/2 spec ~w range ~w ",
9961014 [OffsetSpec, Range]),
997 StartOffset =
998 case {OffsetSpec, Range} of
999 {_, empty} ->
1000 0;
1001 {first, {F, _}} ->
1002 F;
1003 {last, {_, L}} ->
1004 case ChunkRange of
1005 {_FirstChunk, #chunk_info{type = ?CHNK_USER}} ->
1006 %% Last chunk in last segment is a user chunk.
1007 L;
1008 _ ->
1009 %% Last chunk in last segment is not a user chunk (but e.g. a tracking chunk).
1010 %% Therefore, find the last user chunk by searching the index files backwards.
1011 last_user_chunk_id(SegInfos)
1012 end;
1013 {next, {_, L}} ->
1014 L + 1;
1015 {Offset, {S, E}} when is_integer(Offset) ->
1016 max(S, min(Offset, E + 1))
1017 end,
1018 %% find the appopriate segment and scan the index to find the
1019 %% postition of the next chunk to read
1020 case find_segment_for_offset(StartOffset, SegInfos) of
1021 not_found ->
1022 {error, {offset_out_of_range, Range}};
1023 {_, SegmentInfo = #seg_info{file = StartSegment}} ->
1024 try
1025 {ok, Fd} = open(StartSegment, [raw, binary, read]),
1026 {ChOffs, FilePos} =
1015 try
1016 StartOffset =
1017 case {OffsetSpec, Range} of
1018 {_, empty} ->
1019 0;
1020 {Offset, {_, LastOffs}}
1021 when Offset > LastOffs ->
1022 %% out of range, clamp as `next`
1023 throw({retry_with, next, Conf});
1024 {Offset, {FirstOffs, _LastOffs}} ->
1025 max(FirstOffs, Offset)
1026 end,
1027 %% find the appopriate segment and scan the index to find the
1028 %% postition of the next chunk to read
1029 case find_segment_for_offset(StartOffset, SegInfos) of
1030 not_found ->
1031 {error, {offset_out_of_range, Range}};
1032 {end_of_log, #seg_info{file = SegmentFile,
1033 last = LastChunk}} ->
1034 {ChunkId, FilePos} = next_location(LastChunk),
1035 open_offset_reader_at(SegmentFile, ChunkId, FilePos, Conf);
1036 {found, SegmentInfo = #seg_info{file = SegmentFile}} ->
1037 {ChunkId, _Epoch, FilePos} =
10271038 case scan_idx(StartOffset, SegmentInfo) of
10281039 eof ->
1029 {StartOffset, 0};
1040 exit(offset_out_of_range);
10301041 enoent ->
10311042 %% index file was not found
1032 %% just retry
1033 _ = file:close(Fd),
1034 init_offset_reader(OffsetSpec, Conf);
1043 %% throw should be caught and trigger a retry
1044 throw(missing_file);
1045 offset_out_of_range ->
1046 exit(offset_out_of_range);
10351047 IdxResult when is_tuple(IdxResult) ->
10361048 IdxResult
10371049 end,
1038 {ok, _Pos} = file:position(Fd, FilePos),
1039 Cnt = make_counter(Conf),
1040 ReaderCounterFun(1),
1041 {ok,
1042 #?MODULE{cfg =
1043 #cfg{directory = Dir,
1044 counter = Cnt,
1045 name = Name,
1046 readers_counter_fun = ReaderCounterFun,
1047 first_offset_fun = fun (_) -> ok end
1048 },
1049 mode =
1050 #read{type = offset,
1051 chunk_selector = maps:get(chunk_selector, Options, user_data),
1052 offset_ref = OffsetRef,
1053 next_offset = ChOffs,
1054 transport = maps:get(transport, Options, tcp)},
1055 fd = Fd}}
1056 catch
1057 missing_file ->
1058 %% Retention policies are likely being applied, let's try again
1059 %% TODO: should we limit the number of retries?
1060 init_offset_reader(OffsetSpec, Conf)
1061 end
1062 end.
1050 ?DEBUG("osiris_log:init_offset_reader0/2 resolved chunk_id ~b"
1051 " at file pos: ~w ", [ChunkId, FilePos]),
1052 open_offset_reader_at(SegmentFile, ChunkId, FilePos, Conf)
1053 end
1054 catch
1055 missing_file ->
1056 %% Retention policies are likely being applied, let's try again
1057 %% TODO: should we limit the number of retries?
1058 init_offset_reader0(OffsetSpec, Conf);
1059 {retry_with, NewOffsSpec, NewConf} ->
1060 init_offset_reader0(NewOffsSpec, NewConf)
1061 end.
1062
1063 open_offset_reader_at(SegmentFile, NextChunkId, FilePos,
1064 #{dir := Dir,
1065 name := Name,
1066 offset_ref := OffsetRef,
1067 readers_counter_fun := ReaderCounterFun,
1068 options := Options} =
1069 Conf) ->
1070 {ok, Fd} = open(SegmentFile, [raw, binary, read]),
1071 {ok, FilePos} = file:position(Fd, FilePos),
1072 Cnt = make_counter(Conf),
1073 ReaderCounterFun(1),
1074 {ok, #?MODULE{cfg = #cfg{directory = Dir,
1075 counter = Cnt,
1076 name = Name,
1077 readers_counter_fun = ReaderCounterFun,
1078 first_offset_fun = fun (_) -> ok end
1079 },
1080 mode = #read{type = offset,
1081 chunk_selector = maps:get(chunk_selector, Options,
1082 user_data),
1083 offset_ref = OffsetRef,
1084 next_offset = NextChunkId,
1085 transport = maps:get(transport, Options, tcp)},
1086 fd = Fd}}.
10631087
10641088 %% Searches the index files backwards for the ID of the last user chunk.
1065 last_user_chunk_id(SegInfos) when is_list(SegInfos) ->
1089 last_user_chunk_location(SegInfos) when is_list(SegInfos) ->
10661090 {Time, Result} = timer:tc(
10671091 fun() ->
10681092 last_user_chunk_id0(lists:reverse(SegInfos))
10721096
10731097 last_user_chunk_id0([]) ->
10741098 %% There are no user chunks in any index files.
1075 0;
1076 last_user_chunk_id0([#seg_info{index = IdxFile} | Rest]) ->
1099 not_found;
1100 last_user_chunk_id0([#seg_info{index = IdxFile} = Info | Rest]) ->
10771101 try
10781102 %% Do not read-ahead since we read the index file backwards chunk by chunk.
10791103 {ok, IdxFd} = open(IdxFile, [read, raw, binary]),
10801104 file:position(IdxFd, eof),
10811105 Last = last_user_chunk_id_in_index(IdxFd),
1082 file:close(IdxFd),
1106 _ = file:close(IdxFd),
10831107 case Last of
1084 {ok, Id} ->
1085 Id;
1108 {ok, Id, Pos} ->
1109 {Id, Pos, Info};
10861110 {error, Reason} ->
10871111 ?DEBUG("Could not find user chunk in index file ~s (~p)", [IdxFile, Reason]),
10881112 last_user_chunk_id0(Rest)
11041128 <<Offset:64/unsigned,
11051129 _Timestamp:64/signed,
11061130 _Epoch:64/unsigned,
1107 _FileOffset:32/unsigned,
1131 FilePos:32/unsigned,
11081132 ?CHNK_USER:8/unsigned>>} ->
1109 {ok, Offset};
1133 {ok, Offset, FilePos};
11101134 {ok,
11111135 <<_Offset:64/unsigned,
11121136 _Timestamp:64/signed,
11131137 _Epoch:64/unsigned,
1114 _FileOffset:32/unsigned,
1138 _FilePos:32/unsigned,
11151139 _ChType:8/unsigned>>} ->
11161140 last_user_chunk_id_in_index(IdxFd);
11171141 {error, _} = Error ->
14231447 fun() ->
14241448 try
14251449 IdxFiles =
1426 lists:sort(
1427 filelib:wildcard(
1428 filename:join(Dir, "*.index"))),
1450 lists:sort(
1451 filelib:wildcard(
1452 filename:join(Dir, "*.index"))),
14291453 build_log_overview0(IdxFiles, [])
14301454 catch
14311455 missing_file ->
14901514 FirstTs:64/signed,
14911515 FirstEpoch:64/unsigned,
14921516 FirstChId:64/unsigned,
1517 _FirstCrc:32/integer,
1518 FirstSize:32/unsigned,
1519 FirstTSize:32/unsigned,
14931520 _/binary>>} ->
14941521 {ok, LastChunkPos} = file:position(Fd, LastChunkPos),
14951522 {ok,
15201547 timestamp = FirstTs,
15211548 id = FirstChId,
15221549 num = FirstNumRecords,
1523 type = FirstChType},
1550 type = FirstChType,
1551 size = FirstSize + FirstTSize,
1552 pos = ?LOG_HEADER_SIZE},
15241553 last =
15251554 #chunk_info{epoch = LastEpoch,
15261555 timestamp = LastTs,
15271556 id = LastChId,
15281557 num = LastNumRecords,
1529 type = LastChType}}
1558 type = LastChType,
1559 size = LastSize + LastTSize,
1560 pos = LastChunkPos}}
15301561 | Acc0]
15311562 end
15321563 catch
19111942 _ = file:read(Fd, ?IDX_HEADER_SIZE),
19121943 Fd.
19131944
1914 scan_idx(Offset, SegmentInfo = #seg_info{index = IndexFile, last = LastChunkInSegment}) ->
1945 scan_idx(Offset, #seg_info{index = IndexFile,
1946 last = LastChunkInSegment} = SegmentInfo) ->
19151947 {Time, Result} = timer:tc(
19161948 fun() ->
19171949 case offset_range_from_segment_infos([SegmentInfo]) of
19181950 empty ->
1919 %% if the index is empty do we really know the offset will be next
1920 %% this relies on us always reducing the Offset to within the log range
1921 {0, ?LOG_HEADER_SIZE};
1922 {SegmentStart, SegmentEnd} ->
1923 case Offset < SegmentStart orelse Offset > SegmentEnd + 1 of
1924 true -> offset_out_of_range;
1951 eof;
1952 {SegmentStartOffs, SegmentEndOffs} ->
1953 case Offset < SegmentStartOffs orelse
1954 Offset > SegmentEndOffs of
1955 true ->
1956 offset_out_of_range;
19251957 false ->
19261958 IndexFd = open_index_read(IndexFile),
1927 Result = scan_idx(IndexFd, Offset, LastChunkInSegment),
1928 file:close(IndexFd),
1959 Result = scan_idx(IndexFd, Offset,
1960 LastChunkInSegment),
1961 _ = file:close(IndexFd),
19291962 Result
19301963 end
19311964 end
19321965 end),
1933 ?DEBUG("~s:~s/~b completed in ~fs", [?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY, Time/1000000]),
1966 ?DEBUG("~s:~s/~b completed in ~fs",
1967 [?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY, Time/1000000]),
19341968 Result.
19351969
1936 scan_idx(Fd, Offset, #chunk_info{id = LastChunkInSegmentId, num = LastChunkInSegmentNum}) ->
1970 scan_idx(Fd, Offset, #chunk_info{id = LastChunkInSegmentId,
1971 num = LastChunkInSegmentNum}) ->
19371972 case file:read(Fd, ?INDEX_RECORD_SIZE_B) of
1938 {ok,
1939 <<ChunkId:64/unsigned,
1940 _Timestamp:64/signed,
1941 _Epoch:64/unsigned,
1942 FilePos:32/unsigned,
1943 _ChType:8/unsigned>>} ->
1944 case Offset < ChunkId of
1973 {ok, <<ChunkId:64/unsigned,
1974 _Timestamp:64/signed,
1975 Epoch:64/unsigned,
1976 FilePos:32/unsigned,
1977 _ChType:8/unsigned>>} ->
1978 LastOffsetInSegment = LastChunkInSegmentId + LastChunkInSegmentNum - 1,
1979 case Offset < ChunkId orelse Offset > LastOffsetInSegment of
19451980 true ->
1946 %% offset is lower than the first chunk in this segment
19471981 %% shouldn't really happen as we check the range above
19481982 offset_out_of_range;
19491983 false ->
1950 case Offset > (LastChunkInSegmentId + LastChunkInSegmentNum - 1) of
1951 true ->
1952 %% offset higher than the last chunk in this segment
1953 {LastChunkInSegmentId + LastChunkInSegmentNum, eof};
1954 false ->
1955 %% offset is in this segment
1956 scan_idx(Fd, Offset, {ChunkId, FilePos})
1957 end
1984 %% offset is in this segment
1985 scan_idx0(Fd, Offset, {ChunkId, Epoch, FilePos})
19581986 end;
19591987 eof ->
19601988 %% this should never happen - offset is in the range and we are reading the first record
19611989 eof;
19621990 {error, Posix} ->
19631991 Posix
1964 end;
1965 scan_idx(Fd, Offset, PreviousChunk) ->
1992 end.
1993
1994 scan_idx0(Fd, Offset, PreviousChunk) ->
19661995 case file:read(Fd, ?INDEX_RECORD_SIZE_B) of
19671996 {ok,
19681997 <<ChunkId:64/unsigned,
19691998 _Timestamp:64/signed,
1970 _Epoch:64/unsigned,
1999 Epoch:64/unsigned,
19712000 FilePos:32/unsigned,
19722001 _ChType:8/unsigned>>} ->
19732002 case Offset < ChunkId of
19742003 true ->
1975 %% offset we are looking for is higher or equal to the start of the previous chunk
1976 %% but lower than the start of the current chunk -> return the previous chunk
2004 %% offset we are looking for is higher or equal
2005 %% to the start of the previous chunk
2006 %% but lower than the start of the current chunk ->
2007 %% return the previous chunk
19772008 PreviousChunk;
19782009 false ->
1979 scan_idx(Fd, Offset, {ChunkId, FilePos})
2010 scan_idx0(Fd, Offset, {ChunkId, Epoch, FilePos})
19802011 end;
19812012 eof ->
1982 %% Offset is in the last chunk
1983 PreviousChunk
2013 %% Offset must be in the last chunk as there is no more data
2014 PreviousChunk;
2015 {error, Posix} ->
2016 Posix
19842017 end.
19852018
19862019 throw_missing({error, enoent}) ->
19912024 open(SegFile, Options) ->
19922025 throw_missing(file:open(SegFile, Options)).
19932026
1994 chunk_id_for_timestamp(#seg_info{index = Idx}, Ts) ->
2027 chunk_location_for_timestamp(#seg_info{index = Idx}, Ts) ->
19952028 Fd = open_index_read(Idx),
19962029 %% scan index file for nearest timestamp
1997 {ChunkId, _Timestamp, _Epoch, _FilePos} = timestamp_idx_scan(Fd, Ts),
1998 ChunkId.
2030 {ChunkId, _Timestamp, _Epoch, FilePos} = timestamp_idx_scan(Fd, Ts),
2031 {ChunkId, FilePos}.
19992032
20002033 timestamp_idx_scan(Fd, Ts) ->
20012034 case file:read(Fd, ?INDEX_RECORD_SIZE_B) of
22262259 end),
22272260 State.
22282261
2262 next_location(undefined) ->
2263 {0, ?LOG_HEADER_SIZE};
2264 next_location(#chunk_info{id = Id,
2265 num = Num,
2266 pos = Pos,
2267 size = Size}) ->
2268 {Id + Num, Pos + Size + ?HEADER_SIZE_B}.
2269
22292270 -ifdef(TEST).
22302271
22312272 % -include_lib("eunit/include/eunit.hrl").
131131 case rpc:call(Node, osiris_writer, overview, [LeaderPid]) of
132132 {error, _} = Err ->
133133 {stop, Err};
134 {badrpc, Reason} ->
135 {error, Reason};
134136 {ok, {LeaderRange, LeaderEpochOffs}} ->
135137 {ok, {Min, Max}} = application:get_env(port_range),
136138 RecBuf = application:get_env(osiris, replica_recbuf, ?DEF_REC_BUF),
436436 size = "large",
437437 flaky = True,
438438 shard_count = 5,
439 runtime_deps = [
440 "//deps/rabbit/test/feature_flags_SUITE_data/my_plugin:bazel_erlang_lib",
441 ],
442 ),
443 rabbitmq_integration_suite(
444 PACKAGE,
445 name = "feature_flags_with_unpriveleged_user_SUITE",
446 size = "large",
447 additional_beam = [
448 ":feature_flags_SUITE_beam_files",
449 ],
450 flaky = True,
451 shard_count = 2,
439452 # The enabling_* tests chmod files and then expect writes to be blocked.
440453 # This probably doesn't work because we are root in the remote docker image.
441454 tags = ["exclusive"],
141141 PLT_APPS += mnesia
142142
143143 dep_syslog = git https://github.com/schlagert/syslog 4.0.0
144 dep_osiris = git https://github.com/rabbitmq/osiris v1.1.0
144 dep_osiris = git https://github.com/rabbitmq/osiris v1.2.0
145145 dep_systemd = hex 0.6.1
146146 dep_seshat = git https://github.com/rabbitmq/seshat 0.1.0
147147
2727
2828 xref(
2929 additional_libs = [
30 "@ranch//:bazel_erlang_lib",
3031 "@systemd//:bazel_erlang_lib",
3132 ],
3233 tags = ["xref"],
742742
743743
744744 {mapping, "max_message_size", "rabbit.max_message_size",
745 [{datatype, integer}, {validators, ["less_then_512MB"]}]}.
745 [{datatype, integer}, {validators, ["max_message_size"]}]}.
746746
747747 %% Customising Socket Options.
748748 %%
10181018 %% {mirroring_sync_batch_size, 4096},
10191019
10201020 {mapping, "mirroring_sync_batch_size", "rabbit.mirroring_sync_batch_size",
1021 [{datatype, bytesize}, {validators, ["size_less_than_2G"]}]}.
1021 [{datatype, bytesize}, {validators, ["mirroring_sync_batch_size"]}]}.
10221022
10231023 %% Peer discovery backend used by cluster formation.
10241024 %%
20492049 % Validators
20502050 % ===============================
20512051
2052 {validator, "size_less_than_2G", "Byte size should be less than 2G and greater than 0",
2052 {validator, "mirroring_sync_batch_size", "Batch size should be greater than 0 and less than 1M",
20532053 fun(Size) when is_integer(Size) ->
2054 Size > 0 andalso Size < 2147483648
2055 end}.
2056
2057 {validator, "less_then_512MB", "Max message size should be less than 512MB and gre than 0",
2054 Size > 0 andalso Size =< 1000000
2055 end}.
2056
2057 {validator, "max_message_size", "Max message size should be between 0 and 512MB",
20582058 fun(Size) when is_integer(Size) ->
2059 Size > 0 andalso Size < 536870912
2059 Size > 0 andalso Size =< 536870912
20602060 end}.
20612061
20622062 {validator, "less_than_1", "Float is not between 0 and 1",
17781778 node_permits_offline_promotion(Node) ->
17791779 case node() of
17801780 Node -> not rabbit:is_running(); %% [1]
1781 _ -> All = rabbit_mnesia:cluster_nodes(all),
1781 _ -> All = rabbit_nodes:all(),
17821782 Running = rabbit_nodes:all_running(),
17831783 lists:member(Node, All) andalso
17841784 not lists:member(Node, Running) %% [2]
2323 Marker = spawn_link(fun() -> receive stop -> ok end end),
2424 ChildSpec = {rabbit_amqqueue,
2525 {rabbit_prequeue, start_link, [Q, StartMode, Marker]},
26 intrinsic, ?WORKER_WAIT, worker, [rabbit_amqqueue_process,
27 rabbit_mirror_queue_slave]},
26 intrinsic, ?CLASSIC_QUEUE_WORKER_WAIT, worker,
27 [rabbit_amqqueue_process, rabbit_mirror_queue_slave]},
2828 {ok, SupPid} = supervisor2:start_link(?MODULE, []),
2929 {ok, QPid} = supervisor2:start_child(SupPid, ChildSpec),
3030 unlink(Marker),
143143 end.
144144
145145 leader() ->
146 [Leader | _] = lists:usort(rabbit_mnesia:cluster_nodes(all)),
146 [Leader | _] = lists:usort(rabbit_nodes:all()),
147147 Leader.
148148
149149 %% This is the winner receiving its last notification that a node has
409409 %% only know which nodes we have been partitioned from, not which
410410 %% nodes are partitioned from each other.
411411 check_other_nodes(LocalPartitions) ->
412 Nodes = rabbit_mnesia:cluster_nodes(all),
412 Nodes = rabbit_nodes:all(),
413413 {Results, Bad} = rabbit_node_monitor:status(Nodes -- [node()]),
414414 RemotePartitions = [{Node, proplists:get_value(partitions, Res)}
415415 || {Node, Res} <- Results],
178178 lists:foldl(
179179 fun (Node, Acc) ->
180180 Tab = tracked_channel_table_name_for(Node),
181 Acc ++ mnesia:dirty_match_object(Tab, #tracked_channel{_ = '_'})
181 try
182 Acc ++
183 mnesia:dirty_match_object(Tab, #tracked_channel{_ = '_'})
184 catch
185 exit:{aborted, {no_exists, [Tab, _]}} ->
186 %% The table might not exist yet (or is already gone)
187 %% between the time rabbit_nodes:all_running() runs and
188 %% returns a specific node, and
189 %% mnesia:dirty_match_object() is called for that node's
190 %% table.
191 Acc
192 end
182193 end, [], rabbit_nodes:all_running()).
183194
184195 -spec list_of_user(rabbit_types:username()) -> [rabbit_types:tracked_channel()].
350350 lists:foldl(
351351 fun (Node, Acc) ->
352352 Tab = tracked_connection_table_name_for(Node),
353 Acc ++ mnesia:dirty_match_object(Tab, #tracked_connection{_ = '_'})
353 try
354 Acc ++
355 mnesia:dirty_match_object(Tab, #tracked_connection{_ = '_'})
356 catch
357 exit:{aborted, {no_exists, [Tab, _]}} ->
358 %% The table might not exist yet (or is already gone)
359 %% between the time rabbit_nodes:all_running() runs and
360 %% returns a specific node, and
361 %% mnesia:dirty_match_object() is called for that node's
362 %% table.
363 Acc
364 end
354365 end, [], rabbit_nodes:all_running()).
355366
356367 -spec count() -> non_neg_integer().
359370 lists:foldl(
360371 fun (Node, Acc) ->
361372 Tab = tracked_connection_table_name_for(Node),
373 %% mnesia:table_info() returns 0 if the table doesn't exist. We
374 %% don't need the same kind of protection as the list() function
375 %% above.
362376 Acc + mnesia:table_info(Tab, size)
363377 end, 0, rabbit_nodes:all_running()).
364378
101101 gc_process_and_entity(channel_exchange_metrics, GbSet).
102102
103103 gc_nodes() ->
104 Nodes = rabbit_mnesia:cluster_nodes(all),
104 Nodes = rabbit_nodes:all(),
105105 GbSet = gb_sets:from_list(Nodes),
106106 gc_entity(node_node_metrics, GbSet).
107107
480480 -spec add_vhost(map(), rabbit_types:username()) -> ok.
481481
482482 add_vhost(VHost, ActingUser) ->
483 VHostName = maps:get(name, VHost, undefined),
484 VHostTrace = maps:get(tracing, VHost, undefined),
485 VHostDefinition = maps:get(definition, VHost, undefined),
486 VHostTags = maps:get(tags, VHost, undefined),
487 rabbit_vhost:put_vhost(VHostName, VHostDefinition, VHostTags, VHostTrace, ActingUser).
483 Name = maps:get(name, VHost, undefined),
484 IsTracingEnabled = maps:get(tracing, VHost, undefined),
485 Metadata = rabbit_data_coercion:atomize_keys(maps:get(metadata, VHost, #{})),
486 Description = maps:get(description, VHost, maps:get(description, Metadata, <<"">>)),
487 Tags = maps:get(tags, VHost, maps:get(tags, Metadata, [])),
488
489 rabbit_vhost:put_vhost(Name, Description, Tags, IsTracingEnabled, ActingUser).
488490
489491 add_permission(Permission, ActingUser) ->
490492 rabbit_auth_backend_internal:set_permissions(maps:get(user, Permission, undefined),
339339 {State, Reply, Effects}
340340 end
341341 end;
342 apply(Meta, #checkout{spec = cancel, consumer_id = ConsumerId}, State0) ->
343 {State, Effects} = cancel_consumer(Meta, ConsumerId, State0, [],
344 consumer_cancel),
345 checkout(Meta, State0, State, Effects);
342 apply(#{index := Idx} = Meta,
343 #checkout{spec = cancel,
344 consumer_id = ConsumerId}, State0) ->
345 {State1, Effects1} = cancel_consumer(Meta, ConsumerId, State0, [],
346 consumer_cancel),
347 {State, Reply, Effects} = checkout(Meta, State0, State1, Effects1),
348 update_smallest_raft_index(Idx, Reply, State, Effects);
346349 apply(Meta, #checkout{spec = Spec, meta = ConsumerMeta,
347350 consumer_id = {_, Pid} = ConsumerId},
348351 State0) ->
371374 {State, _, Effects} = evaluate_limit(Index, false, State0,
372375 State1, Effects0),
373376 update_smallest_raft_index(Index, Reply, State, Effects);
374 apply(_Meta, #garbage_collection{}, State) ->
375 {State, ok, [{aux, garbage_collection}]};
377 apply(#{index := Idx}, #garbage_collection{}, State) ->
378 update_smallest_raft_index(Idx, ok, State, [{aux, garbage_collection}]);
376379 apply(#{system_time := Ts} = Meta, {down, Pid, noconnection},
377380 #?MODULE{consumers = Cons0,
378381 cfg = #cfg{consumer_strategy = single_active},
505508 checkout(Meta, State0, State, Effects);
506509 apply(_, {nodedown, _Node}, State) ->
507510 {State, ok};
508 apply(Meta, #purge_nodes{nodes = Nodes}, State0) ->
511 apply(#{index := Idx} = Meta, #purge_nodes{nodes = Nodes}, State0) ->
509512 {State, Effects} = lists:foldl(fun(Node, {S, E}) ->
510513 purge_node(Meta, Node, S, E)
511514 end, {State0, []}, Nodes),
512 {State, ok, Effects};
513 apply(Meta, #update_config{config = Conf}, State) ->
514 checkout(Meta, State, update_config(Conf, State), []);
515 update_smallest_raft_index(Idx, ok, State, Effects);
516 apply(#{index := Idx} = Meta, #update_config{config = Conf}, State0) ->
517 {State, Reply, Effects} = checkout(Meta, State0, update_config(Conf, State0), []),
518 update_smallest_raft_index(Idx, Reply, State, Effects);
515519 apply(_Meta, {machine_version, 0, 1}, V0State) ->
516520 State = convert_v0_to_v1(V0State),
517521 {State, ok, []};
17491753 messages = Messages0,
17501754 consumers = Cons0} = InitState) ->
17511755 case priority_queue:out(SQ0) of
1752 {{value, ConsumerId}, SQ1} ->
1756 {{value, ConsumerId}, SQ1}
1757 when is_map_key(ConsumerId, Cons0) ->
17531758 case take_next_msg(InitState) of
17541759 {ConsumerMsg, State0} ->
17551760 %% there are consumers waiting to be serviced
17561761 %% process consumer checkout
1757 case maps:find(ConsumerId, Cons0) of
1758 {ok, #consumer{credit = 0}} ->
1762 case maps:get(ConsumerId, Cons0) of
1763 #consumer{credit = 0} ->
17591764 %% no credit but was still on queue
17601765 %% can happen when draining
17611766 %% recurse without consumer on queue
17621767 checkout_one(Meta, InitState#?MODULE{service_queue = SQ1});
1763 {ok, #consumer{status = cancelled}} ->
1768 #consumer{status = cancelled} ->
17641769 checkout_one(Meta, InitState#?MODULE{service_queue = SQ1});
1765 {ok, #consumer{status = suspected_down}} ->
1770 #consumer{status = suspected_down} ->
17661771 checkout_one(Meta, InitState#?MODULE{service_queue = SQ1});
1767 {ok, #consumer{checked_out = Checked0,
1768 next_msg_id = Next,
1769 credit = Credit,
1770 delivery_count = DelCnt} = Con0} ->
1772 #consumer{checked_out = Checked0,
1773 next_msg_id = Next,
1774 credit = Credit,
1775 delivery_count = DelCnt} = Con0 ->
17711776 Checked = maps:put(Next, ConsumerMsg, Checked0),
17721777 Con = Con0#consumer{checked_out = Checked,
17731778 next_msg_id = Next + 1,
17941799 add_bytes_checkout(Header, State1)),
17951800 M}
17961801 end,
1797 {success, ConsumerId, Next, Msg, State};
1798 error ->
1799 %% consumer did not exist but was queued, recurse
1800 checkout_one(Meta, InitState#?MODULE{service_queue = SQ1})
1802 {success, ConsumerId, Next, Msg, State}
18011803 end;
18021804 empty ->
18031805 {nochange, InitState}
18041806 end;
1807 {{value, _ConsumerId}, SQ1} ->
1808 %% consumer did not exist but was queued, recurse
1809 checkout_one(Meta, InitState#?MODULE{service_queue = SQ1});
18051810 {empty, _} ->
18061811 case lqueue:len(Messages0) of
18071812 0 -> {nochange, InitState};
9999
100100 %% Check that we are in the cluster, all old nodes are in the
101101 %% cluster, and no new nodes are.
102 Nodes = rabbit_mnesia:cluster_nodes(all),
102 Nodes = rabbit_nodes:all(),
103103 case {FromNodes -- Nodes, ToNodes -- (ToNodes -- Nodes),
104104 lists:member(Node, Nodes ++ ToNodes)} of
105105 {[], [], true} -> ok;
231231 Term.
232232
233233 rename_in_running_mnesia(FromNode, ToNode) ->
234 All = rabbit_mnesia:cluster_nodes(all),
234 All = rabbit_nodes:all(),
235235 Running = rabbit_nodes:all_running(),
236236 case {lists:member(FromNode, Running), lists:member(ToNode, All)} of
237237 {false, true} -> ok;
165165 -spec notify_joined_cluster() -> 'ok'.
166166
167167 notify_joined_cluster() ->
168 Nodes = rabbit_nodes:all_running() -- [node()],
168 NewMember = node(),
169 Nodes = rabbit_nodes:all_running() -- [NewMember],
169170 gen_server:abcast(Nodes, ?SERVER,
170171 {joined_cluster, node(), rabbit_mnesia:node_type()}),
172
171173 ok.
172174
173175 -spec notify_left_cluster(node()) -> 'ok'.
535537 ram -> DiscNodes
536538 end,
537539 RunningNodes}),
540 rabbit_log:debug("Node '~p' has joined the cluster", [Node]),
541 rabbit_event:notify(node_added, [{node, Node}]),
538542 {noreply, State};
539543
540544 handle_cast({left_cluster, Node}, State) ->
871875 majority([]).
872876
873877 majority(NodesDown) ->
874 Nodes = rabbit_mnesia:cluster_nodes(all),
878 Nodes = rabbit_nodes:all(),
875879 AliveNodes = alive_nodes(Nodes) -- NodesDown,
876880 length(AliveNodes) / length(Nodes) > 0.5.
877881
884888 in_preferred_partition(PreferredNodes, []).
885889
886890 in_preferred_partition(PreferredNodes, NodesDown) ->
887 Nodes = rabbit_mnesia:cluster_nodes(all),
891 Nodes = rabbit_nodes:all(),
888892 RealPreferredNodes = [N || N <- PreferredNodes, lists:member(N, Nodes)],
889893 AliveNodes = alive_nodes(RealPreferredNodes) -- NodesDown,
890894 RealPreferredNodes =:= [] orelse AliveNodes =/= [].
891895
892896 all_nodes_up() ->
893 Nodes = rabbit_mnesia:cluster_nodes(all),
897 Nodes = rabbit_nodes:all(),
894898 length(alive_nodes(Nodes)) =:= length(Nodes).
895899
896900 -spec all_rabbit_nodes_up() -> boolean().
897901
898902 all_rabbit_nodes_up() ->
899 Nodes = rabbit_mnesia:cluster_nodes(all),
903 Nodes = rabbit_nodes:all(),
900904 length(alive_rabbit_nodes(Nodes)) =:= length(Nodes).
901905
902 alive_nodes() -> alive_nodes(rabbit_mnesia:cluster_nodes(all)).
906 alive_nodes() -> alive_nodes(rabbit_nodes:all()).
903907
904908 -spec alive_nodes([node()]) -> [node()].
905909
906910 alive_nodes(Nodes) -> [N || N <- Nodes, lists:member(N, [node()|nodes()])].
907911
908 alive_rabbit_nodes() -> alive_rabbit_nodes(rabbit_mnesia:cluster_nodes(all)).
912 alive_rabbit_nodes() -> alive_rabbit_nodes(rabbit_nodes:all()).
909913
910914 -spec alive_rabbit_nodes([node()]) -> [node()].
911915
917921 -spec ping_all() -> 'ok'.
918922
919923 ping_all() ->
920 [net_adm:ping(N) || N <- rabbit_mnesia:cluster_nodes(all)],
924 [net_adm:ping(N) || N <- rabbit_nodes:all()],
921925 ok.
922926
923927 possibly_partitioned_nodes() ->
1313 await_running_count/2, is_single_node_cluster/0,
1414 boot/0]).
1515 -export([persistent_cluster_id/0, seed_internal_cluster_id/0, seed_user_provided_cluster_name/0]).
16 -export([all_running_with_hashes/0]).
16 -export([all/0, all_running_with_hashes/0]).
1717 -export([lock_id/1, lock_retries/0]).
1818
1919 -include_lib("kernel/include/inet.hrl").
136136 ensure_epmd() ->
137137 rabbit_nodes_common:ensure_epmd().
138138
139 -spec all() -> [node()].
140 all() -> rabbit_mnesia:cluster_nodes(all).
141
139142 -spec all_running() -> [node()].
140143 all_running() -> rabbit_mnesia:cluster_nodes(running).
141144
143146 running_count() -> length(all_running()).
144147
145148 -spec total_count() -> integer().
146 total_count() -> length(rabbit_mnesia:cluster_nodes(all)).
149 total_count() -> length(rabbit_nodes:all()).
147150
148151 -spec is_single_node_cluster() -> boolean().
149152 is_single_node_cluster() ->
3737 -export([validate/5, notify/5, notify_clear/4]).
3838 -export([parse_set/7, set/7, delete/3, lookup/2, list/0, list/1,
3939 list_formatted/1, list_formatted/3, info_keys/0]).
40 -export([parse_set_op/7, set_op/7, delete_op/3, lookup_op/2, list_op/0, list_op/1,
40 -export([parse_set_op/7, set_op/7, delete_op/3, lookup_op/2, list_op/0, list_op/1, list_op/2,
4141 list_formatted_op/1, list_formatted_op/3,
4242 match_all/2, match_as_map/1, match_op_as_map/1, definition_keys/1,
4343 list_in/1, list_in/2, list_as_maps/0, list_as_maps/1, list_op_as_maps/1
4444 ]).
45 -export([sort_by_priority/1]).
4546
4647 -rabbit_boot_step({?MODULE,
4748 [{description, "policy parameters"},
134135
135136 list_op(VHost) ->
136137 list0_op(VHost, fun ident/1).
138
139 list_op(VHost, DefinitionKeys) ->
140 [P || P <- list_op(VHost), keys_overlap(definition_keys(P), DefinitionKeys)].
137141
138142 list_formatted_op(VHost) ->
139143 sort_by_priority(list0_op(VHost, fun rabbit_json:encode/1)).
175175 rabbit_data_coercion:to_atom(ra:new_uid(N))
176176 end,
177177 Id = {RaName, node()},
178 Nodes = select_quorum_nodes(QuorumSize, rabbit_mnesia:cluster_nodes(all)),
178 Nodes = select_quorum_nodes(QuorumSize, rabbit_nodes:all()),
179179 NewQ0 = amqqueue:set_pid(Q, Id),
180180 NewQ1 = amqqueue:set_type_state(NewQ0, #{nodes => Nodes}),
181181
439439 {messages_unacknowledged, MU},
440440 {reductions, R}]),
441441 ok = repair_leader_record(QName, Self),
442 ExpectedNodes = rabbit_mnesia:cluster_nodes(all),
442 ExpectedNodes = rabbit_nodes:all(),
443443 case Nodes -- ExpectedNodes of
444444 [] ->
445445 ok;
15051505 SockStat =:= send_pend ->
15061506 socket_info(fun (Sock) -> rabbit_net:getstat(Sock, [SockStat]) end,
15071507 fun ([{_, I}]) -> I end, S);
1508 i(ssl, #v1{sock = Sock}) -> rabbit_net:is_ssl(Sock);
1508 i(ssl, #v1{sock = Sock, proxy_socket = ProxySock}) ->
1509 rabbit_net:proxy_ssl_info(Sock, ProxySock) /= nossl;
15091510 i(ssl_protocol, S) -> ssl_info(fun ({P, _}) -> P end, S);
15101511 i(ssl_key_exchange, S) -> ssl_info(fun ({_, {K, _, _}}) -> K end, S);
15111512 i(ssl_cipher, S) -> ssl_info(fun ({_, {_, C, _}}) -> C end, S);
15751576 {error, _} -> 0
15761577 end.
15771578
1578 ssl_info(F, #v1{sock = Sock}) ->
1579 case rabbit_net:ssl_info(Sock) of
1579 ssl_info(F, #v1{sock = Sock, proxy_socket = ProxySock}) ->
1580 case rabbit_net:proxy_ssl_info(Sock, ProxySock) of
15801581 nossl -> '';
15811582 {error, _} -> '';
15821583 {ok, Items} ->
254254 process_command([Server | Servers], Cmd) ->
255255 case ra:process_command(Server, Cmd, ?CMD_TIMEOUT) of
256256 {timeout, _} ->
257 rabbit_log:warning("Coordinator timeout on server ~p when processing command ~p",
258 [Server, element(1, Cmd)]),
257 rabbit_log:warning("Coordinator timeout on server ~s when processing command ~W",
258 [Server, element(1, Cmd), 10]),
259259 process_command(Servers, Cmd);
260260 {error, noproc} ->
261261 process_command(Servers, Cmd);
469469 [{aux, maybe_resize_coordinator_cluster}].
470470
471471 maybe_resize_coordinator_cluster() ->
472 spawn_link(fun() ->
472 spawn(fun() ->
473473 case ra:members({?MODULE, node()}) of
474474 {_, Members, _} ->
475475 MemberNodes = [Node || {_, Node} <- Members],
476476 Running = rabbit_mnesia:cluster_nodes(running),
477 All = rabbit_mnesia:cluster_nodes(all),
477 All = rabbit_nodes:all(),
478478 case Running -- MemberNodes of
479479 [] ->
480480 ok;
510510 add_members(Members, Nodes)
511511 end;
512512 Error ->
513 rabbit_log:warning("Stream coordinator failed to start on node ~p : ~p",
514 [Node, Error]),
513 rabbit_log:warning("Stream coordinator failed to start on node ~s : ~W",
514 [Node, Error, 10]),
515515 add_members(Members, Nodes)
516516 end.
517517
528528 -record(aux, {actions = #{} ::
529529 #{pid() := {stream_id(), #{node := node(),
530530 index := non_neg_integer(),
531 epoch := osiris:epoch()}}},
531 epoch := osiris:epoch()}}},
532532 resizer :: undefined | pid()}).
533533
534534 init_aux(_Name) ->
535535 #aux{}.
536 % {#{}, undefined}.
537536
538537 %% TODO ensure the dead writer is restarted as a replica at some point in time, increasing timeout?
539538 handle_aux(leader, _, maybe_resize_coordinator_cluster,
629628 run_action(Action, StreamId, #{node := _Node,
630629 epoch := _Epoch} = Args,
631630 ActionFun, #aux{actions = Actions0} = Aux, Log) ->
632 Pid = spawn_link(ActionFun),
633 Effects = [],
631 Coordinator = self(),
632 Pid = spawn_link(fun() ->
633 ActionFun(),
634 unlink(Coordinator)
635 end),
636 Effects = [{monitor, process, aux, Pid}],
634637 Actions = Actions0#{Pid => {StreamId, Action, Args}},
635638 {no_reply, Aux#aux{actions = Actions}, Log, Effects}.
636639
642645 fun() ->
643646 try osiris_replica:start(Node, Conf0) of
644647 {ok, Pid} ->
645 rabbit_log:debug("~s: ~s: replica started on ~s in ~b pid ~w",
646 [?MODULE, StreamId, Node, Epoch, Pid]),
648 rabbit_log:info("~s: ~s: replica started on ~s in ~b pid ~w",
649 [?MODULE, StreamId, Node, Epoch, Pid]),
647650 send_self_command({member_started, StreamId,
648651 Args#{pid => Pid}});
649652 {error, already_present} ->
658661 send_self_command({member_started, StreamId,
659662 Args#{pid => Pid}});
660663 {error, Reason} ->
664 rabbit_log:warning("~s: Error while starting replica for ~s on node ~s in ~b : ~W",
665 [?MODULE, maps:get(name, Conf0), Node, Epoch, Reason, 10]),
661666 maybe_sleep(Reason),
662 rabbit_log:warning("~s: Error while starting replica for ~s : ~W",
663 [?MODULE, maps:get(name, Conf0), Reason, 10]),
664667 send_action_failed(StreamId, starting, Args)
665 catch _:E ->
666 rabbit_log:warning("~s: Error while starting replica for ~s : ~p",
667 [?MODULE, maps:get(name, Conf0), E]),
668 maybe_sleep(E),
668 catch _:Error ->
669 rabbit_log:warning("~s: Error while starting replica for ~s on node ~s in ~b : ~W",
670 [?MODULE, maps:get(name, Conf0), Node, Epoch, Error, 10]),
671 maybe_sleep(Error),
669672 send_action_failed(StreamId, starting, Args)
670673 end
671674 end.
686689 _ ->
687690 send_action_failed(StreamId, deleting, Arg)
688691 catch _:E ->
689 rabbit_log:warning("~s: Error while deleting member for ~s : on node ~s ~p",
690 [?MODULE, StreamId, Node, E]),
692 rabbit_log:warning("~s: Error while deleting member for ~s : on node ~s ~W",
693 [?MODULE, StreamId, Node, E, 10]),
691694 maybe_sleep(E),
692695 send_action_failed(StreamId, deleting, Arg)
693696 end
706709 [?MODULE, StreamId, Node, Epoch, Tail]),
707710 send_self_command({member_stopped, StreamId, Arg});
708711 Err ->
709 rabbit_log:warning("Stream coordinator failed to get tail
710 of member ~s ~w Error: ~w",
711 [StreamId, Node, Err]),
712 rabbit_log:warning("~s: failed to get tail of member ~s on ~s in ~b Error: ~w",
713 [?MODULE, StreamId, Node, Epoch, Err]),
714 maybe_sleep(Err),
712715 send_action_failed(StreamId, stopping, Arg0)
713716 catch _:Err ->
714 rabbit_log:warning("Stream coordinator failed to get
715 tail of member ~s ~w Error: ~w",
716 [StreamId, Node, Err]),
717 rabbit_log:warning("~s: failed to get tail of member ~s on ~s in ~b Error: ~w",
718 [?MODULE, StreamId, Node, Epoch, Err]),
719 maybe_sleep(Err),
717720 send_action_failed(StreamId, stopping, Arg0)
718721 end;
719722 Err ->
720 rabbit_log:warning("Stream coordinator failed to stop
721 member ~s ~w Error: ~w",
722 [StreamId, Node, Err]),
723 rabbit_log:warning("~s: failed to stop "
724 "member ~s ~w Error: ~w",
725 [?MODULE, StreamId, Node, Err]),
726 maybe_sleep(Err),
723727 send_action_failed(StreamId, stopping, Arg0)
724728 catch _:Err ->
725 rabbit_log:warning("Stream coordinator failed to stop
726 member ~s ~w Error: ~w",
727 [StreamId, Node, Err]),
728 maybe_sleep(Err),
729 send_action_failed(StreamId, stopping, Arg0)
729 rabbit_log:warning("~s: failed to stop member ~s ~w Error: ~w",
730 [?MODULE, StreamId, Node, Err]),
731 maybe_sleep(Err),
732 send_action_failed(StreamId, stopping, Arg0)
730733 end
731734 end.
732735
736739 try osiris_writer:start(Conf) of
737740 {ok, Pid} ->
738741 Args = Args0#{epoch => Epoch, pid => Pid},
739 rabbit_log:warning("~s: started writer ~s on ~w in ~b",
740 [?MODULE, StreamId, Node, Epoch]),
742 rabbit_log:info("~s: started writer ~s on ~w in ~b",
743 [?MODULE, StreamId, Node, Epoch]),
741744 send_self_command({member_started, StreamId, Args});
742745 Err ->
743 %% no sleep for writer failures
744 rabbit_log:warning("~s: failed to start
745 writer ~s ~w Error: ~w",
746 [?MODULE, StreamId, Node, Err]),
746 %% no sleep for writer failures as we want to trigger a new
747 %% election asap
748 rabbit_log:warning("~s: failed to start writer ~s on ~s in ~b Error: ~w",
749 [?MODULE, StreamId, Node, Epoch, Err]),
747750 send_action_failed(StreamId, starting, Args0)
748751 catch _:Err ->
749 rabbit_log:warning("~s: failed to start
750 writer ~s ~w Error: ~w",
751 [?MODULE, StreamId, Node, Err]),
752 rabbit_log:warning("~s: failed to start writer ~s on ~s in ~b Error: ~w",
753 [?MODULE, StreamId, Node, Epoch, Err]),
752754 send_action_failed(StreamId, starting, Args0)
753755 end
754756 end.
759761 try osiris:update_retention(Pid, Retention) of
760762 ok ->
761763 send_self_command({retention_updated, StreamId, Args});
762 {error, Err} ->
763 rabbit_log:warning("~s: failed to update
764 retention for ~s ~w Error: ~w",
765 [?MODULE, StreamId, node(Pid), Err]),
764 {error, Reason} = Err ->
765 rabbit_log:warning("~s: failed to update retention for ~s ~w Reason: ~w",
766 [?MODULE, StreamId, node(Pid), Reason]),
767 maybe_sleep(Err),
766768 send_action_failed(StreamId, update_retention, Args)
767769 catch _:Err ->
768 rabbit_log:warning("~s: failed to update
769 retention for ~s ~w Error: ~w",
770 rabbit_log:warning("~s: failed to update retention for ~s ~w Error: ~w",
770771 [?MODULE, StreamId, node(Pid), Err]),
771772 maybe_sleep(Err),
772773 send_action_failed(StreamId, update_retention, Args)
777778 case rpc:call(Node, ?MODULE, log_overview, [Conf]) of
778779 {badrpc, nodedown} ->
779780 {error, nodedown};
781 {error, _} = Err ->
782 Err;
780783 {_Range, Offsets} ->
781784 {ok, select_highest_offset(Offsets)}
782785 end.
787790 lists:last(Offsets).
788791
789792 log_overview(Config) ->
790 Dir = osiris_log:directory(Config),
791 osiris_log:overview(Dir).
793 case whereis(osiris_sup) of
794 undefined ->
795 {error, app_not_running};
796 _ ->
797 Dir = osiris_log:directory(Config),
798 osiris_log:overview(Dir)
799 end.
792800
793801
794802 replay(L) when is_list(L) ->
859867 case maps:size(Members) =< 1 of
860868 true ->
861869 rabbit_log:warning(
862 "~s failed to delete replica on node ~p for stream ~s: refusing to delete the only replica",
870 "~s failed to delete replica on node ~s for stream ~s: refusing to delete the only replica",
863871 [?MODULE, Node, StreamId]),
864872 {error, last_stream_member};
865873 false ->
874882 catch
875883 _:E:Stacktrace ->
876884 rabbit_log:warning(
877 "~s failed to update stream:~n~p~n~p",
878 [?MODULE, E, Stacktrace]),
885 "~s failed to update stream:~n~W~n~W",
886 [?MODULE, E, 10, Stacktrace, 10]),
879887 Stream
880888 end.
881889
15011509 Node.
15021510
15031511 maybe_sleep({{nodedown, _}, _}) ->
1512 timer:sleep(10000);
1513 maybe_sleep({noproc, _}) ->
15041514 timer:sleep(5000);
1505 maybe_sleep({noproc, _}) ->
1515 maybe_sleep({error, nodedown}) ->
1516 timer:sleep(5000);
1517 maybe_sleep({error, _}) ->
15061518 timer:sleep(5000);
15071519 maybe_sleep(_) ->
15081520 ok.
00
11 -define(STREAM_COORDINATOR_STARTUP, {stream_coordinator_startup, self()}).
2 -define(TICK_TIMEOUT, 1000).
2 -define(TICK_TIMEOUT, 30000).
33 -define(RESTART_TIMEOUT, 1000).
44 -define(PHASE_RETRY_TIMEOUT, 10000).
55 -define(CMD_TIMEOUT, 30000).
749749 InitialClusterSize = initial_cluster_size(
750750 args_policy_lookup(<<"initial-cluster-size">>,
751751 fun policy_precedence/2, Q)),
752 Replicas0 = rabbit_mnesia:cluster_nodes(all) -- [Node],
752 Replicas0 = rabbit_nodes:all() -- [Node],
753753 %% TODO: try to avoid nodes that are not connected
754754 Replicas = select_stream_nodes(InitialClusterSize - 1, Replicas0),
755755 Formatter = {?MODULE, format_osiris_event, [QName]},
106106 ok ->
107107 ok;
108108 {timeout, BadTabs} ->
109 AllNodes = rabbit_mnesia:cluster_nodes(all),
109 AllNodes = rabbit_nodes:all(),
110110 {error, {timeout_waiting_for_tables, AllNodes, BadTabs}};
111111 {error, Reason} ->
112 AllNodes = rabbit_mnesia:cluster_nodes(all),
112 AllNodes = rabbit_nodes:all(),
113113 {error, {failed_waiting_for_tables, AllNodes, Reason}}
114114 end,
115115 case {Retries, Result} of
110110 -spec maybe_upgrade_mnesia() -> 'ok'.
111111
112112 maybe_upgrade_mnesia() ->
113 AllNodes = rabbit_mnesia:cluster_nodes(all),
113 AllNodes = rabbit_nodes:all(),
114114 ok = rabbit_mnesia_rename:maybe_finish(AllNodes),
115115 %% Mnesia upgrade is the first upgrade scope,
116116 %% so we should create a backup here if there are any upgrades
1212 -export([recover/0, recover/1, read_config/1]).
1313 -export([add/2, add/4, delete/2, exists/1, with/2, with_user_and_vhost/3, assert/1, update/2,
1414 set_limits/2, vhost_cluster_state/1, is_running_on_all_nodes/1, await_running_on_all_nodes/2,
15 list/0, count/0, list_names/0, all/0]).
15 list/0, count/0, list_names/0, all/0, all_tagged_with/1]).
1616 -export([parse_tags/1, update_metadata/2, tag_with/2, untag_from/2, update_tags/2, update_tags/3]).
1717 -export([lookup/1]).
1818 -export([info/1, info/2, info_all/0, info_all/1, info_all/2, info_all/3]).
128128 case hd(Val) of
129129 Bin when is_binary(Bin) ->
130130 %% this is a list of binaries
131 [trim_tag(Tag) || Tag <- Val];
132 Atom when is_atom(Atom) ->
133 %% this is a list of atoms
131134 [trim_tag(Tag) || Tag <- Val];
132135 Int when is_integer(Int) ->
133136 %% this is a string/charlist
202205 {error, Msg}
203206 end.
204207
208 -spec update(vhost:name(), binary(), [atom()], rabbit_types:username()) -> rabbit_types:ok_or_error(any()).
209 update(Name, Description, Tags, ActingUser) ->
210 rabbit_misc:execute_mnesia_transaction(
211 fun () ->
212 case mnesia:wread({rabbit_vhost, Name}) of
213 [] ->
214 {error, {no_such_vhost, Name}};
215 [VHost0] ->
216 VHost = vhost:merge_metadata(VHost0, #{description => Description, tags => Tags}),
217 rabbit_log:debug("Updating a virtual host record ~p", [VHost]),
218 ok = mnesia:write(rabbit_vhost, VHost, write),
219 rabbit_event:notify(vhost_updated, info(VHost)
220 ++ [{user_who_performed_action, ActingUser},
221 {description, Description},
222 {tags, Tags}]),
223 ok
224 end
225 end).
226
227
205228 -spec delete(vhost:name(), rabbit_types:username()) -> rabbit_types:ok_or_error(any()).
206229
207230 delete(VHost, ActingUser) ->
239262 "null" -> <<"">>;
240263 Other -> Other
241264 end,
265 ParsedTags = parse_tags(Tags),
266 rabbit_log:debug("Parsed tags ~p to ~p", [Tags, ParsedTags]),
242267 Result = case exists(Name) of
243 true -> ok;
268 true ->
269 update(Name, Description, ParsedTags, Username);
244270 false ->
245 ParsedTags = parse_tags(Tags),
246 rabbit_log:debug("Parsed tags ~p to ~p", [Tags, ParsedTags]),
247271 add(Name, Description, ParsedTags, Username),
248272 %% wait for up to 45 seconds for the vhost to initialise
249273 %% on all nodes
378402
379403 -spec all() -> [vhost:vhost()].
380404 all() -> mnesia:dirty_match_object(rabbit_vhost, vhost:pattern_match_all()).
405
406 -spec all_tagged_with(atom()) -> [vhost:vhost()].
407 all_tagged_with(TagName) ->
408 lists:filter(
409 fun(VHost) ->
410 Meta = vhost:get_metadata(VHost),
411 case Meta of
412 #{tags := Tags} ->
413 lists:member(rabbit_data_coercion:to_atom(TagName), Tags);
414 _ -> false
415 end
416 end, all()).
381417
382418 -spec count() -> non_neg_integer().
383419 count() ->
2626 get_tags/1,
2727 set_limits/2,
2828 set_metadata/2,
29 merge_metadata/2,
2930 is_tagged_with/2
3031 ]).
3132
172173 vhost_v1:set_limits(VHost, Value)
173174 end.
174175
176 -spec set_metadata(vhost(), metadata()) -> vhost().
175177 set_metadata(VHost, Value) ->
176178 case record_version_to_use() of
177179 ?record_version ->
181183 VHost
182184 end.
183185
186 -spec merge_metadata(vhost(), metadata()) -> vhost().
187 merge_metadata(VHost, Value) ->
188 case record_version_to_use() of
189 ?record_version ->
190 Meta0 = get_metadata(VHost),
191 NewMeta = maps:merge(Meta0, Value),
192 VHost#vhost{metadata = NewMeta};
193 _ ->
194 %% the field is not available, so this is a no-op
195 VHost
196 end.
197
184198 -spec is_tagged_with(vhost:vhost(), atom()) -> boolean().
185199 is_tagged_with(VHost, Tag) ->
186200 lists:member(Tag, get_tags(VHost)).
177177 deps = DEPS + RUNTIME_DEPS,
178178 )
179179
180 xref(tags = ["xref"])
180 xref(
181 additional_libs = [
182 "@ranch//:bazel_erlang_lib",
183 ],
184 tags = ["xref"],
185 )
181186
182187 plt(
183188 name = "base_plt",
224224 -define(SUPERVISOR_WAIT,
225225 rabbit_misc:get_env(rabbit, supervisor_shutdown_timeout, infinity)).
226226 -define(WORKER_WAIT,
227 rabbit_misc:get_env(rabbit, worker_shutdown_timeout, 30000)).
227 rabbit_misc:get_env(rabbit, worker_shutdown_timeout, 300000)).
228228 -define(MSG_STORE_WORKER_WAIT,
229229 rabbit_misc:get_env(rabbit, msg_store_shutdown_timeout, 600000)).
230 -define(CLASSIC_QUEUE_WORKER_WAIT,
231 rabbit_misc:get_env(rabbit, classic_queue_shutdown_timeout, 600000)).
230232
231233 -define(HIBERNATE_AFTER_MIN, 1000).
232234 -define(DESIRED_HIBERNATE, 10000).
1717 RMQ_ERLC_OPTS += -pa $(DEPS_DIR)/rabbitmq_cli/_build/dev/lib/rabbitmqctl/ebin
1818 endif
1919
20 RMQ_ERLC_OPTS += +deterministic
21
2022 # Push our compilation options to both the normal and test ERLC_OPTS.
2123 ERLC_OPTS += $(RMQ_ERLC_OPTS)
2224 TEST_ERLC_OPTS += $(RMQ_ERLC_OPTS)
1414 setopts/2, send/2, close/1, fast_close/1, sockname/1, peername/1,
1515 peercert/1, connection_string/2, socket_ends/2, is_loopback/1,
1616 tcp_host/1, unwrap_socket/1, maybe_get_proxy_socket/1,
17 hostname/0, getifaddrs/0]).
17 hostname/0, getifaddrs/0, proxy_ssl_info/2]).
1818
1919 %%---------------------------------------------------------------------------
2020
3333 % -type host_or_ip() :: binary() | inet:ip_address().
3434 -spec is_ssl(socket()) -> boolean().
3535 -spec ssl_info(socket()) -> 'nossl' | ok_val_or_error([{atom(), any()}]).
36 -spec proxy_ssl_info(socket(), ranch_proxy:proxy_socket()) -> 'nossl' | ok_val_or_error([{atom(), any()}]).
3637 -spec controlling_process(socket(), pid()) -> ok_or_any_error().
3738 -spec getstat(socket(), [stat_option()]) ->
3839 ok_val_or_error([{stat_option(), integer()}]).
9798 ssl_info(_Sock) ->
9899 nossl.
99100
101 proxy_ssl_info(Sock, {rabbit_proxy_socket, _, ProxyInfo}) ->
102 ConnInfo = ranch_proxy_header:to_connection_info(ProxyInfo),
103 case lists:keymember(protocol, 1, ConnInfo) andalso
104 lists:keymember(selected_cipher_suite, 1, ConnInfo) of
105 true ->
106 {ok, ConnInfo};
107 false ->
108 ssl_info(Sock)
109 end;
110 proxy_ssl_info(Sock, _) ->
111 ssl_info(Sock).
112
113
100114 controlling_process(Sock, Pid) when ?IS_SSL(Sock) ->
101115 ssl:controlling_process(Sock, Pid);
102116 controlling_process(Sock, Pid) when is_port(Sock) ->
792792 {error, _} -> ''
793793 end.
794794
795 ssl_info(F, #v1{sock = Sock}) ->
796 case rabbit_net:ssl_info(Sock) of
795 ssl_info(F, #v1{sock = Sock, proxy_socket = ProxySock}) ->
796 case rabbit_net:proxy_ssl_info(Sock, ProxySock) of
797797 nossl -> '';
798798 {error, _} -> '';
799799 {ok, Items} ->
3737 "//deps/rabbit_common:bazel_erlang_lib",
3838 "@credentials_obfuscation//:bazel_erlang_lib",
3939 "@jsx//:bazel_erlang_lib",
40 "@ranch//:bazel_erlang_lib",
4041 "@recon//:bazel_erlang_lib",
4142 ],
4243 tags = ["xref"],
4141 -define(METADATA_TOKEN_TTL_SECONDS, 60).
4242
4343 -define(METADATA_TOKEN, "X-aws-ec2-metadata-token").
44
45 -define(LINEAR_BACK_OFF_MILLIS, 500).
46 -define(MAX_RETRIES, 5).
4447
4548 -type access_key() :: nonempty_string().
4649 -type secret_access_key() :: nonempty_string().
8484 refresh_credentials(State) ->
8585 rabbit_log:debug("Refreshing AWS credentials..."),
8686 {_, NewState} = load_credentials(State),
87 rabbit_log:debug("AWS credentials have been refreshed."),
87 rabbit_log:debug("AWS credentials have been refreshed"),
8888 set_credentials(NewState).
8989
9090
333333 security_token = SecurityToken,
334334 imdsv2_token = undefined}};
335335 {error, Reason} ->
336 error_logger:error_msg("Could not load AWS credentials from environment variables, AWS_CONFIG_FILE, AWS_SHARED_CREDENTIALS_FILE or EC2 metadata endpoint: ~p. Will depend on config settings to be set.~n.", [Reason]),
336 error_logger:error_msg("Could not load AWS credentials from environment variables, AWS_CONFIG_FILE, AWS_SHARED_CREDENTIALS_FILE or EC2 metadata endpoint: ~p. Will depend on config settings to be set~n", [Reason]),
337337 {error, #state{region = Region,
338338 error = Reason,
339339 access_key = undefined,
496496 %% @doc Determine whether or not an Imdsv2Token has expired.
497497 %% @end
498498 expired_imdsv2_token(undefined) ->
499 rabbit_log:debug("EC2 IMDSv2 token has not yet been obtained."),
499 rabbit_log:debug("EC2 IMDSv2 token has not yet been obtained"),
500500 true;
501501 expired_imdsv2_token({_, _, undefined}) ->
502 rabbit_log:debug("EC2 IMDSv2 token is not available."),
502 rabbit_log:debug("EC2 IMDSv2 token is not available"),
503503 true;
504504 expired_imdsv2_token({_, _, Expiration}) ->
505505 Now = calendar:datetime_to_gregorian_seconds(local_time()),
526526 %% If the credentials are not available or have expired, then refresh them before performing the request.
527527 %% @end
528528 ensure_credentials_valid() ->
529 rabbit_log:debug("Making sure AWS credentials are available and still valid."),
529 rabbit_log:debug("Making sure AWS credentials are available and still valid"),
530530 {ok, State} = gen_server:call(rabbitmq_aws, get_state),
531531 case has_credentials(State) of
532532 true -> case expired_credentials(State#state.expiration) of
542542 %% @end
543543 api_get_request(Service, Path) ->
544544 rabbit_log:debug("Invoking AWS request {Service: ~p; Path: ~p}...", [Service, Path]),
545 api_get_request_with_retries(Service, Path, ?MAX_RETRIES, ?LINEAR_BACK_OFF_MILLIS).
546
547
548 -spec api_get_request_with_retries(string(), path(), integer(), integer()) -> result().
549 %% @doc Invoke an API call to an AWS service with retries.
550 %% @end
551 api_get_request_with_retries(_, _, 0, _) ->
552 rabbit_log:warning("Request to AWS service has failed after ~b retries", [?MAX_RETRIES]),
553 {error, "AWS service is unavailable"};
554 api_get_request_with_retries(Service, Path, Retries, WaitTimeBetweenRetries) ->
545555 ensure_credentials_valid(),
546556 case get(Service, Path) of
547557 {ok, {_Headers, Payload}} -> rabbit_log:debug("AWS request: ~s~nResponse: ~p", [Path, Payload]),
548558 {ok, Payload};
549559 {error, {credentials, _}} -> {error, credentials};
550 {error, Message, _} -> {error, Message}
560 {error, Message, _} -> rabbit_log:warning("Error occurred ~s~nWill retry AWS request, remaining retries: ~b", [Message, Retries]),
561 timer:sleep(WaitTimeBetweenRetries),
562 api_get_request_with_retries(Service, Path, Retries - 1, WaitTimeBetweenRetries)
551563 end.
00 PROJECT = rabbitmq_cli
1
2 dep_observer_cli = git https://github.com/zhongwencool/observer_cli 1.4.4
31
42 BUILD_DEPS = rabbit_common
53 DEPS = observer_cli
1210 MAX_CASES ?= 1
1311
1412 MIX_TEST_OPTS ?= ""
15 MIX_TEST = mix test --max-cases=$(MAX_CASES)
13 MIX_TEST = ERL_COMPILER_OPTIONS=deterministic mix test --max-cases=$(MAX_CASES)
1614
1715 ifneq ("",$(MIX_TEST_OPTS))
1816 MIX_TEST := $(MIX_TEST) $(MIX_TEST_OPTS)
105103 # ones).
106104 $(ACTUAL_ESCRIPTS): $(rabbitmqctl_srcs)
107105 $(gen_verbose) if test -d ../.hex; then \
108 echo y | mix make_all_in_src_archive; \
106 echo y | ERL_COMPILER_OPTIONS=deterministic mix make_all_in_src_archive; \
109107 else \
110 echo y | mix make_all; \
108 echo y | ERL_COMPILER_OPTIONS=deterministic mix make_all; \
111109 fi
112110
113111 $(LINKED_ESCRIPTS):
119119 {:json, "~> 1.4.1"},
120120 {:csv, "~> 2.4.0"},
121121 {:stdout_formatter, "~> 0.2.3"},
122 {:observer_cli, "~> 1.6.1"},
122 {:observer_cli, "~> 1.7.1"},
123123
124124 {:amqp, "~> 2.1.0", only: :test},
125125 {:dialyxir, "~> 0.5", only: :test, runtime: false},
00 %{
11 "csv": {:hex, :csv, "2.4.1", "50e32749953b6bf9818dbfed81cf1190e38cdf24f95891303108087486c5925e", [:mix], [{:parallel_stream, "~> 1.0.4", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "54508938ac67e27966b10ef49606e3ad5995d665d7fc2688efb3eab1307c9079"},
22 "json": {:hex, :json, "1.4.1", "8648f04a9439765ad449bc56a3ff7d8b11dd44ff08ffcdefc4329f7c93843dfa", [:mix], [], "hexpm", "9abf218dbe4ea4fcb875e087d5f904ef263d012ee5ed21d46e9dbca63f053d16"},
3 "observer_cli": {:hex, :observer_cli, "1.6.2", "016588e9a966247401bcbf02976d468f1e6f06891dde44f873c9259c6496cca1", [:mix, :rebar3], [{:recon, "~>2.5.1", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm", "c23db9e4cca0e849adc42b0a099affb9e6267c5f23a871fc6f144348b249341f"},
3 "observer_cli": {:hex, :observer_cli, "1.7.1", "c9ca1f623a3ef0158283a3c37cd7b7235bfe85927ad6e26396dd247e2057f5a1", [:mix, :rebar3], [{:recon, "~>2.5.1", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm", "4ccafaaa2ce01b85ddd14591f4d5f6731b4e13b610a70fb841f0701178478280"},
44 "parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm", "639b2e8749e11b87b9eb42f2ad325d161c170b39b288ac8d04c4f31f8f0823eb"},
55 "recon": {:hex, :recon, "2.5.2", "cba53fa8db83ad968c9a652e09c3ed7ddcc4da434f27c3eaa9ca47ffb2b1ff03", [:mix, :rebar3], [], "hexpm", "2c7523c8dee91dff41f6b3d63cba2bd49eb6d2fe5bf1eec0df7f87eb5e230e1c"},
66 "stdout_formatter": {:hex, :stdout_formatter, "0.2.4", "d9394ab3f5adcda97a4b8b1c0b798dcb286b07913c4020ea7f78c88723fb145e", [:mix, :rebar3], [], "hexpm", "51f1df921b0477275ea712763042155dbc74acc75d9648dbd54985c45c913b29"},
8484 fi
8585
8686 export DEPS_DIR={mix_deps_dir}
87 export ERL_COMPILER_OPTIONS=deterministic
8788 mix local.hex --force
8889 mix local.rebar --force
8990 mix make_all
7777 fi
7878
7979 export DEPS_DIR={mix_deps_dir}
80 export ERL_COMPILER_OPTIONS=deterministic
8081 mix local.hex --force
8182 mix local.rebar --force
8283 mix make_deps
9797 });
9898
9999 sammy.get('#/queues/:vhost/:name', function() {
100 var path = '/queues/' + esc(this.params['vhost']) + '/' + esc(this.params['name']);
101 render({'queue': {path: path,
102 options: {ranges:['lengths-q', 'msg-rates-q', 'data-rates-q']}},
103 'bindings': path + '/bindings'}, 'queue', '#/queues');
100 var vhost = this.params['vhost'];
101 var queue = this.params['name'];
102 var path = '/queues/' + esc(vhost) + '/' + esc(queue);
103 var requests = {'queue': {path: path,
104 options: {ranges:['lengths-q', 'msg-rates-q', 'data-rates-q']}},
105 'bindings': path + '/bindings'};
106 // we add extra requests that can be added by code plugged on the extension point
107 for (var i = 0; i < QUEUE_EXTRA_CONTENT_REQUESTS.length; i++) {
108 var extra = QUEUE_EXTRA_CONTENT_REQUESTS[i](vhost, queue);
109 for (key in extra) {
110 if(extra.hasOwnProperty(key)){
111 requests[key] = extra[key];
112 }
113 }
114 }
115 render(requests, 'queue', '#/queues');
104116 });
105117 sammy.put('#/queues', function() {
106118 if (sync_put(this, '/queues/:vhost/:name'))
167167
168168 var RENDER_CALLBACKS = {};
169169
170 const QUEUE_EXTRA_CONTENT = [];
171 const QUEUE_EXTRA_CONTENT_REQUESTS = [];
172
170173 // All help ? popups
171174 var HELP = {
172175 'delivery-limit':
479479 if(outstanding_reqs.length > 0){
480480 return false;
481481 }
482 with_reqs(apply_state(current_reqs), [], function(json) {
482 var model = [];
483 model['extra_content'] = []; // magic key for extension point
484 with_reqs(apply_state(current_reqs), model, function(json) {
483485 var html = format(current_template, json);
484486 fun(html);
485487 update_status('ok');
11151117 if (keys(reqs).length > 0) {
11161118 var key = keys(reqs)[0];
11171119 with_req('GET', reqs[key], null, function(resp) {
1118 acc[key] = JSON.parse(resp.responseText);
1120 if (key.startsWith("extra_")) {
1121 var extraContent = acc["extra_content"];
1122 extraContent[key] = JSON.parse(resp.responseText);
1123 acc["extra_content"] = extraContent;
1124 } else {
1125 acc[key] = JSON.parse(resp.responseText);
1126 }
11191127 var remainder = {};
11201128 for (var k in reqs) {
11211129 if (k != key) remainder[k] = reqs[k];
11491157 console.log("Stack: " + err['stack']);
11501158 debug(err['name'] + ": " + err['message'] + "\n" + err['stack'] + "\n");
11511159 }
1160 }
1161
1162 function maybe_format_extra_queue_content(queue, extraContent) {
1163 var content = '';
1164 for (var i = 0; i < QUEUE_EXTRA_CONTENT.length; i++) {
1165 content += QUEUE_EXTRA_CONTENT[i](queue, extraContent);
1166 }
1167 return content;
11521168 }
11531169
11541170 function update_status(status) {
7373 </div>
7474
7575 <div class="section">
76 <h2>Consumers</h2>
76 <h2 class="updatable" >Consumers (<%=(channel.consumer_details.length)%>) </h2>
7777 <div class="hider updatable">
7878 <%= format('consumers', {'mode': 'channel', 'consumers': channel.consumer_details}) %>
7979 </div>
7575 </div>
7676
7777 <div class="section">
78 <h2>Channels</h2>
78 <h2 class="updatable" >Channels (<%=(channels.length)%>) </h2>
7979 <div class="hider updatable">
8080 <%= format('channels-list', {'channels': channels, 'mode': 'connection'}) %>
8181 </div>
266266
267267 <% } %>
268268
269
270269 <% if(!disable_stats) { %>
271 <div class="section-hidden">
272 <h2>Consumers</h2>
270 <%= maybe_format_extra_queue_content(queue, extra_content) %>
271 <% } %>
272
273 <% if(!disable_stats) { %>
274 <div class="section-hidden">
275 <h2 class="updatable">Consumers (<%=(queue.consumer_details.length)%>) </h2>
273276 <div class="hider updatable">
274277 <%= format('consumers', {'mode': 'queue', 'consumers': queue.consumer_details}) %>
275278 </div>
277280 <% } %>
278281
279282 <div class="section-hidden">
280 <h2>Bindings</h2>
283 <h2 class="updatable">Bindings (<%=(bindings.length)%>) </h2>
281284 <div class="hider">
282285 <div class="bindings-wrapper">
283286 <%= format('bindings', {'mode': 'queue', 'bindings': bindings}) %>
104104 gc_process_and_entity(channel_exchange_stats_fine_stats, GbSet).
105105
106106 gc_nodes() ->
107 Nodes = rabbit_mnesia:cluster_nodes(all),
107 Nodes = rabbit_nodes:all(),
108108 GbSet = gb_sets:from_list(Nodes),
109109 gc_entity(node_stats, GbSet),
110110 gc_entity(node_coarse_stats, GbSet),
110110 PACKAGE,
111111 name = "cluster_SUITE",
112112 size = "large",
113 flaky = True,
113114 runtime_deps = [
114115 "@emqttc//:bazel_erlang_lib",
115116 ],
2323 {?ID_NAME, Node}.
2424
2525 all_node_ids() ->
26 [server_id(N) || N <- rabbit_mnesia:cluster_nodes(all),
26 [server_id(N) || N <- rabbit_nodes:all(),
2727 can_participate_in_clientid_tracking(N)].
2828
2929 start() ->
2727
2828 xref(
2929 additional_libs = [
30 "@ranch//:bazel_erlang_lib",
3031 "@systemd//:bazel_erlang_lib",
3132 ],
3233 tags = ["xref"],
8484 rabbitmqctl eval 'application:set_env(rabbitmq_prometheus, return_per_object_metrics, false).'
8585 ```
8686
87 ## Selective querying of per-object metrics
88
89 As mentioned in the previous section, returning a lot of per-object metrics is quite computationally expensive process. One of the reasons is that `/metrics/per-object` returns every possible metric for every possible object - even if having them makes no sense in the day-to-day monitoring activity.
90
91 That's why there is an additional endpoint that always return per-object metrics and allows one to explicitly query only the things that are relevant - `/metrics/detailed`. By default it doesn't return anything at all, but it's possible to specify required metric groups and virtual host filters in the GET-parameters. Scraping `/metrics/detailed?vhost=vhost-1&vhost=vhost-2&family=queue_coarse_metrics&family=queue_consumer_count`. will only return requested metrics (and not, for example, channel metrics that include erlang PID in labels).
92
93 This endpoint supports the following parameters:
94
95 * Zero or more `family` - only the requested metric families will be returned. The full list is documented in [metrics-detailed](metrics-detailed.md).
96 * Zero or more `vhost` - if it's given, queue related metrics (`queue_coarse_metrics`, `queue_consumer_count` and `queue_metrics`) will be returned only for given vhost(s).
97
98 The returned metrics use different prefix `rabbitmq_detailed_` (instead of plain `rabbitmq_` used by other endpoints), so that endpoint can be used simultaneously with `/metrics`, and existing dashboards won't be affected.
99
100 Here are the performance gains you can expect from using this endpoint. On a test system with 10k queues/10k consumer/10k producers, `/metrics/per-object` took a bit over 2 minutes. Querying `/metrics/detailed?family=queue_coarse_metrics&family=queue_consumer_count` provides just enough metrics to see how many messages sit in every queue and how much consumers each of these queues have. And it takes only 2 seconds, a significant improvement over indiscriminate `/metrics/per-object`.
87101
88102 ## Contributing
89103
0 ## Configurable RabbitMQ metric groups
1
2 Those are metrics than can be explicitly requested via `/metrics/detailed` endpoint.
3
4 ### Generic metrics
5
6 These are some generic metrics, which do not refer to any specific
7 queue/connection/etc.
8
9 #### Connection/channel/queue churn
10
11 Group `connection_churn_metrics`:
12
13 | Metric | Description |
14 |--------------------------------------------|--------------------------------------------------|
15 | rabbitmq_detailed_connections_opened_total | Total number of connections opened |
16 | rabbitmq_detailed_connections_closed_total | Total number of connections closed or terminated |
17 | rabbitmq_detailed_channels_opened_total | Total number of channels opened |
18 | rabbitmq_detailed_channels_closed_total | Total number of channels closed |
19 | rabbitmq_detailed_queues_declared_total | Total number of queues declared |
20 | rabbitmq_detailed_queues_created_total | Total number of queues created |
21 | rabbitmq_detailed_queues_deleted_total | Total number of queues deleted |
22
23
24 #### Erlang VM/Disk IO via RabbitMQ
25
26 Group `node_coarse_metrics`:
27
28 | Metric | Description |
29 |-----------------------------------------------------------|-----------------------------------------------------------------------|
30 | rabbitmq_detailed_process_open_fds | Open file descriptors |
31 | rabbitmq_detailed_process_open_tcp_sockets | Open TCP sockets |
32 | rabbitmq_detailed_process_resident_memory_bytes | Memory used in bytes |
33 | rabbitmq_detailed_disk_space_available_bytes | Disk space available in bytes |
34 | rabbitmq_detailed_erlang_processes_used | Erlang processes used |
35 | rabbitmq_detailed_erlang_gc_runs_total | Total number of Erlang garbage collector runs |
36 | rabbitmq_detailed_erlang_gc_reclaimed_bytes_total | Total number of bytes of memory reclaimed by Erlang garbage collector |
37 | rabbitmq_detailed_erlang_scheduler_context_switches_total | Total number of Erlang scheduler context switches |
38
39 Group `node_metrics`:
40
41 | Metric | Description |
42 |----------------------------------------------------|----------------------------------------|
43 | rabbitmq_detailed_process_max_fds | Open file descriptors limit |
44 | rabbitmq_detailed_process_max_tcp_sockets | Open TCP sockets limit |
45 | rabbitmq_detailed_resident_memory_limit_bytes | Memory high watermark in bytes |
46 | rabbitmq_detailed_disk_space_available_limit_bytes | Free disk space low watermark in bytes |
47 | rabbitmq_detailed_erlang_processes_limit | Erlang processes limit |
48 | rabbitmq_detailed_erlang_scheduler_run_queue | Erlang scheduler run queue |
49 | rabbitmq_detailed_erlang_net_ticktime_seconds | Inter-node heartbeat interval |
50 | rabbitmq_detailed_erlang_uptime_seconds | Node uptime |
51
52
53 Group `node_persister_metrics`:
54
55 | Metric | Description |
56 |-------------------------------------------------------|------------------------------------------------------|
57 | rabbitmq_detailed_io_read_ops_total | Total number of I/O read operations |
58 | rabbitmq_detailed_io_read_bytes_total | Total number of I/O bytes read |
59 | rabbitmq_detailed_io_write_ops_total | Total number of I/O write operations |
60 | rabbitmq_detailed_io_write_bytes_total | Total number of I/O bytes written |
61 | rabbitmq_detailed_io_sync_ops_total | Total number of I/O sync operations |
62 | rabbitmq_detailed_io_seek_ops_total | Total number of I/O seek operations |
63 | rabbitmq_detailed_io_open_attempt_ops_total | Total number of file open attempts |
64 | rabbitmq_detailed_io_reopen_ops_total | Total number of times files have been reopened |
65 | rabbitmq_detailed_schema_db_ram_tx_total | Total number of Schema DB memory transactions |
66 | rabbitmq_detailed_schema_db_disk_tx_total | Total number of Schema DB disk transactions |
67 | rabbitmq_detailed_msg_store_read_total | Total number of Message Store read operations |
68 | rabbitmq_detailed_msg_store_write_total | Total number of Message Store write operations |
69 | rabbitmq_detailed_queue_index_read_ops_total | Total number of Queue Index read operations |
70 | rabbitmq_detailed_queue_index_write_ops_total | Total number of Queue Index write operations |
71 | rabbitmq_detailed_queue_index_journal_write_ops_total | Total number of Queue Index Journal write operations |
72 | rabbitmq_detailed_io_read_time_seconds_total | Total I/O read time |
73 | rabbitmq_detailed_io_write_time_seconds_total | Total I/O write time |
74 | rabbitmq_detailed_io_sync_time_seconds_total | Total I/O sync time |
75 | rabbitmq_detailed_io_seek_time_seconds_total | Total I/O seek time |
76 | rabbitmq_detailed_io_open_attempt_time_seconds_total | Total file open attempts time |
77
78
79 #### Raft metrics
80
81 Group `ra_metrics`:
82
83 | Metric | Description |
84 |-----------------------------------------------------|--------------------------------------------|
85 | rabbitmq_detailed_raft_term_total | Current Raft term number |
86 | rabbitmq_detailed_raft_log_snapshot_index | Raft log snapshot index |
87 | rabbitmq_detailed_raft_log_last_applied_index | Raft log last applied index |
88 | rabbitmq_detailed_raft_log_commit_index | Raft log commit index |
89 | rabbitmq_detailed_raft_log_last_written_index | Raft log last written index |
90 | rabbitmq_detailed_raft_entry_commit_latency_seconds | Time taken for a log entry to be committed |
91
92 #### Auth metrics
93
94 Group `auth_attempt_metrics`:
95
96 | Metric | Description |
97 |-------------------------------------------------|----------------------------------------------------|
98 | rabbitmq_detailed_auth_attempts_total | Total number of authorization attempts |
99 | rabbitmq_detailed_auth_attempts_succeeded_total | Total number of successful authentication attempts |
100 | rabbitmq_detailed_auth_attempts_failed_total | Total number of failed authentication attempts |
101
102
103 Group `auth_attempt_detailed_metrics` (when aggregated, it produces the same numbers as `auth_attempt_metrics` - so it's mutually exclusive with it in the aggregation mode):
104
105 | Metric | Description |
106 |----------------------------------------------------------|--------------------------------------------------------------------|
107 | rabbitmq_detailed_auth_attempts_detailed_total | Total number of authorization attempts with source info |
108 | rabbitmq_detailed_auth_attempts_detailed_succeeded_total | Total number of successful authorization attempts with source info |
109 | rabbitmq_detailed_auth_attempts_detailed_failed_total | Total number of failed authorization attempts with source info |
110
111
112 ### Queue metrics
113
114 Each of metrics in this group refers to a single queue in its label. Amount of data and performance totally depends on the number of queues.
115
116 They are listed from least expensive to collect to the most expensive.
117
118 #### Queue coarse metrics
119
120 Group `queue_coarse_metrics`:
121
122 | Metric | Description |
123 |--------------------------------------------------|--------------------------------------------------------------|
124 | rabbitmq_detailed_queue_messages_ready | Messages ready to be delivered to consumers |
125 | rabbitmq_detailed_queue_messages_unacked | Messages delivered to consumers but not yet acknowledged |
126 | rabbitmq_detailed_queue_messages | Sum of ready and unacknowledged messages - total queue depth |
127 | rabbitmq_detailed_queue_process_reductions_total | Total number of queue process reductions |
128
129 #### Per-queue consumer count
130
131 Group `queue_consumer_count`. This is a strict subset of `queue_metrics` which contains only a single metric (if both `queue_consumer_count` and `queue_metrics` are requested, the former will be automatically skipped):
132
133 | Metric | Description |
134 |-----------------------------------|----------------------|
135 | rabbitmq_detailed_queue_consumers | Consumers on a queue |
136
137 This is one of the more telling metrics, and having it separately allows to skip some expensive operations for extracting/exposing the other metrics from the same datasource.
138
139 #### Detailed queue metrics
140
141 Group `queue_metrics` contains all the metrics for every queue, and can be relatively expensive to produce:
142
143 | Metric | Description |
144 |---------------------------------------------------|------------------------------------------------------------|
145 | rabbitmq_detailed_queue_consumers | Consumers on a queue |
146 | rabbitmq_detailed_queue_consumer_capacity | Consumer capacity |
147 | rabbitmq_detailed_queue_consumer_utilisation | Same as consumer capacity |
148 | rabbitmq_detailed_queue_process_memory_bytes | Memory in bytes used by the Erlang queue process |
149 | rabbitmq_detailed_queue_messages_ram | Ready and unacknowledged messages stored in memory |
150 | rabbitmq_detailed_queue_messages_ram_bytes | Size of ready and unacknowledged messages stored in memory |
151 | rabbitmq_detailed_queue_messages_ready_ram | Ready messages stored in memory |
152 | rabbitmq_detailed_queue_messages_unacked_ram | Unacknowledged messages stored in memory |
153 | rabbitmq_detailed_queue_messages_persistent | Persistent messages |
154 | rabbitmq_detailed_queue_messages_persistent_bytes | Size in bytes of persistent messages |
155 | rabbitmq_detailed_queue_messages_bytes | Size in bytes of ready and unacknowledged messages |
156 | rabbitmq_detailed_queue_messages_ready_bytes | Size in bytes of ready messages |
157 | rabbitmq_detailed_queue_messages_unacked_bytes | Size in bytes of all unacknowledged messages |
158 | rabbitmq_detailed_queue_messages_paged_out | Messages paged out to disk |
159 | rabbitmq_detailed_queue_messages_paged_out_bytes | Size in bytes of messages paged out to disk |
160 | rabbitmq_detailed_queue_disk_reads_total | Total number of times queue read messages from disk |
161 | rabbitmq_detailed_queue_disk_writes_total | Total number of times queue wrote messages to disk |
162
163 Tests show that performance difference between it and `queue_consumer_count` is approximately 8 times. E.g. on a test broker with 10k queues/producers/consumers, scrape time was ~8 second and ~1 respectively. So while it's expensive, it's not prohibitively so - especially compared to other metrics from per-connection/channel groups.
164
165 ### Connection/channel metrics
166
167 All of those include Erlang PID in their label, which is rarely useful when ingested into Prometheus. And they are most expensive to produce, the most resources are spent by `/metrics/per-object` on these.
168
169 #### Connection metrics
170
171 Group `connection_coarse_metrics`:
172
173 | Metric | Description |
174 |-------------------------------------------------------|------------------------------------------------|
175 | rabbitmq_detailed_connection_incoming_bytes_total | Total number of bytes received on a connection |
176 | rabbitmq_detailed_connection_outgoing_bytes_total | Total number of bytes sent on a connection |
177 | rabbitmq_detailed_connection_process_reductions_total | Total number of connection process reductions |
178
179 Group `connection_metrics`:
180
181 | Metric | Description |
182 |-----------------------------------------------------|------------------------------------------------------|
183 | rabbitmq_detailed_connection_incoming_packets_total | Total number of packets received on a connection |
184 | rabbitmq_detailed_connection_outgoing_packets_total | Total number of packets sent on a connection |
185 | rabbitmq_detailed_connection_pending_packets | Number of packets waiting to be sent on a connection |
186 | rabbitmq_detailed_connection_channels | Channels on a connection |
187
188 #### General channel metrics
189
190 Group `channel_metrics`:
191
192 | Metric | Description |
193 |------------------------------------------------|-----------------------------------------------------------------------|
194 | rabbitmq_detailed_channel_consumers | Consumers on a channel |
195 | rabbitmq_detailed_channel_messages_unacked | Delivered but not yet acknowledged messages |
196 | rabbitmq_detailed_channel_messages_unconfirmed | Published but not yet confirmed messages |
197 | rabbitmq_detailed_channel_messages_uncommitted | Messages received in a transaction but not yet committed |
198 | rabbitmq_detailed_channel_acks_uncommitted | Message acknowledgements in a transaction not yet committed |
199 | rabbitmq_detailed_consumer_prefetch | Limit of unacknowledged messages for each consumer |
200 | rabbitmq_detailed_channel_prefetch | Total limit of unacknowledged messages for all consumers on a channel |
201
202
203 Group `channel_process_metrics`:
204
205 | Metric | Description |
206 |----------------------------------------------------|--------------------------------------------|
207 | rabbitmq_detailed_channel_process_reductions_total | Total number of channel process reductions |
208
209
210 #### Channel metrics with queue/exchange breakdowns
211
212 Group `channel_exchange_metrics`:
213
214 | Metric | Description |
215 |--------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
216 | rabbitmq_detailed_channel_messages_published_total | Total number of messages published into an exchange on a channel |
217 | rabbitmq_detailed_channel_messages_confirmed_total | Total number of messages published into an exchange and confirmed on the channel |
218 | rabbitmq_detailed_channel_messages_unroutable_returned_total | Total number of messages published as mandatory into an exchange and returned to the publisher as unroutable |
219 | rabbitmq_detailed_channel_messages_unroutable_dropped_total | Total number of messages published as non-mandatory into an exchange and dropped as unroutable |
220
221 Group `channel_queue_metrics`:
222
223 | Metric | Description |
224 |--------------------------------------------------------|-----------------------------------------------------------------------------------|
225 | rabbitmq_detailed_channel_get_ack_total | Total number of messages fetched with basic.get in manual acknowledgement mode |
226 | rabbitmq_detailed_channel_get_total | Total number of messages fetched with basic.get in automatic acknowledgement mode |
227 | rabbitmq_detailed_channel_messages_delivered_ack_total | Total number of messages delivered to consumers in manual acknowledgement mode |
228 | rabbitmq_detailed_channel_messages_delivered_total | Total number of messages delivered to consumers in automatic acknowledgement mode |
229 | rabbitmq_detailed_channel_messages_redelivered_total | Total number of messages redelivered to consumers |
230 | rabbitmq_detailed_channel_messages_acked_total | Total number of messages acknowledged by consumers |
231 | rabbitmq_detailed_channel_get_empty_total | Total number of times basic.get operations fetched no message |
232
233 Group `channel_queue_exchange_metrics`:
234
235 | Metric | Description |
236 |--------------------------------------------------|----------------------------------------------|
237 | rabbitmq_detailed_queue_messages_published_total | Total number of messages published to queues |
1919 -include_lib("rabbit_common/include/rabbit.hrl").
2020
2121 -behaviour(prometheus_collector).
22
23 %% Because all metrics are from RabbitMQ's perspective,
24 %% cached for up to 5 seconds by default (configurable),
25 %% we prepend rabbitmq_ to all metrics emitted by this collector.
26 %% Some metrics are for Erlang (erlang_), Mnesia (schema_db_) or the System (io_),
22 %% We prepend either rabbitmq_ or rabbitmq_detailed_ to all metrics emitted by this collector.
23 %% As there are also some metrics for Erlang (erlang_), Mnesia (schema_db_) or the System (io_),
2724 %% as observed by RabbitMQ.
25
26 %% Used by `/metrics` and `/metrics/per-object`.
2827 -define(METRIC_NAME_PREFIX, "rabbitmq_").
28
29 %% Used by `/metrics/detailed` endpoint
30 -define(DETAILED_METRIC_NAME_PREFIX, "rabbitmq_detailed_").
2931
3032 %% ==The source of these metrics can be found in the rabbit_core_metrics module==
3133 %% The relevant files are:
5153 -define(MICROSECOND, 1000000).
5254
5355 -define(METRICS_RAW, [
54 {channel_metrics, [
55 {2, undefined, channel_consumers, gauge, "Consumers on a channel", consumer_count},
56 {2, undefined, channel_messages_unacked, gauge, "Delivered but not yet acknowledged messages", messages_unacknowledged},
57 {2, undefined, channel_messages_unconfirmed, gauge, "Published but not yet confirmed messages", messages_unconfirmed},
58 {2, undefined, channel_messages_uncommitted, gauge, "Messages received in a transaction but not yet committed", messages_uncommitted},
59 {2, undefined, channel_acks_uncommitted, gauge, "Message acknowledgements in a transaction not yet committed", acks_uncommitted},
60 {2, undefined, consumer_prefetch, gauge, "Limit of unacknowledged messages for each consumer", prefetch_count},
61 {2, undefined, channel_prefetch, gauge, "Total limit of unacknowledged messages for all consumers on a channel", global_prefetch_count}
62 ]},
63
64 {channel_exchange_metrics, [
65 {2, undefined, channel_messages_published_total, counter, "Total number of messages published into an exchange on a channel"},
66 {3, undefined, channel_messages_confirmed_total, counter, "Total number of messages published into an exchange and confirmed on the channel"},
67 {4, undefined, channel_messages_unroutable_returned_total, counter, "Total number of messages published as mandatory into an exchange and returned to the publisher as unroutable"},
68 {5, undefined, channel_messages_unroutable_dropped_total, counter, "Total number of messages published as non-mandatory into an exchange and dropped as unroutable"}
69 ]},
70
71 {channel_process_metrics, [
72 {2, undefined, channel_process_reductions_total, counter, "Total number of channel process reductions"}
73 ]},
74
75 {channel_queue_metrics, [
76 {2, undefined, channel_get_ack_total, counter, "Total number of messages fetched with basic.get in manual acknowledgement mode"},
77 {3, undefined, channel_get_total, counter, "Total number of messages fetched with basic.get in automatic acknowledgement mode"},
78 {4, undefined, channel_messages_delivered_ack_total, counter, "Total number of messages delivered to consumers in manual acknowledgement mode"},
79 {5, undefined, channel_messages_delivered_total, counter, "Total number of messages delivered to consumers in automatic acknowledgement mode"},
80 {6, undefined, channel_messages_redelivered_total, counter, "Total number of messages redelivered to consumers"},
81 {7, undefined, channel_messages_acked_total, counter, "Total number of messages acknowledged by consumers"},
82 {8, undefined, channel_get_empty_total, counter, "Total number of times basic.get operations fetched no message"}
83 ]},
84
56
57 %%% Those are global, i.e. they contain no reference to queue/vhost/channel
8558 {connection_churn_metrics, [
8659 {2, undefined, connections_opened_total, counter, "Total number of connections opened"},
8760 {3, undefined, connections_closed_total, counter, "Total number of connections closed or terminated"},
9164 {7, undefined, queues_created_total, counter, "Total number of queues created"},
9265 {8, undefined, queues_deleted_total, counter, "Total number of queues deleted"}
9366 ]},
94
95 {connection_coarse_metrics, [
96 {2, undefined, connection_incoming_bytes_total, counter, "Total number of bytes received on a connection"},
97 {3, undefined, connection_outgoing_bytes_total, counter, "Total number of bytes sent on a connection"},
98 {4, undefined, connection_process_reductions_total, counter, "Total number of connection process reductions"}
99 ]},
100
101 {connection_metrics, [
102 {2, undefined, connection_incoming_packets_total, counter, "Total number of packets received on a connection", recv_cnt},
103 {2, undefined, connection_outgoing_packets_total, counter, "Total number of packets sent on a connection", send_cnt},
104 {2, undefined, connection_pending_packets, gauge, "Number of packets waiting to be sent on a connection", send_pend},
105 {2, undefined, connection_channels, gauge, "Channels on a connection", channels}
106 ]},
107
108 {channel_queue_exchange_metrics, [
109 {2, undefined, queue_messages_published_total, counter, "Total number of messages published to queues"}
110 ]},
111
11267 {node_coarse_metrics, [
11368 {2, undefined, process_open_fds, gauge, "Open file descriptors", fd_used},
11469 {2, undefined, process_open_tcp_sockets, gauge, "Open TCP sockets", sockets_used},
11974 {2, undefined, erlang_gc_reclaimed_bytes_total, counter, "Total number of bytes of memory reclaimed by Erlang garbage collector", gc_bytes_reclaimed},
12075 {2, undefined, erlang_scheduler_context_switches_total, counter, "Total number of Erlang scheduler context switches", context_switches}
12176 ]},
122
12377 {node_metrics, [
12478 {2, undefined, process_max_fds, gauge, "Open file descriptors limit", fd_total},
12579 {2, undefined, process_max_tcp_sockets, gauge, "Open TCP sockets limit", sockets_total},
163117 {7, ?MILLISECOND, raft_entry_commit_latency_seconds, gauge, "Time taken for a log entry to be committed"}
164118 ]},
165119
120 {auth_attempt_metrics, [
121 {2, undefined, auth_attempts_total, counter, "Total number of authorization attempts"},
122 {3, undefined, auth_attempts_succeeded_total, counter, "Total number of successful authentication attempts"},
123 {4, undefined, auth_attempts_failed_total, counter, "Total number of failed authentication attempts"}
124 ]},
125
126 {auth_attempt_detailed_metrics, [
127 {2, undefined, auth_attempts_detailed_total, counter, "Total number of authorization attempts with source info"},
128 {3, undefined, auth_attempts_detailed_succeeded_total, counter, "Total number of successful authorization attempts with source info"},
129 {4, undefined, auth_attempts_detailed_failed_total, counter, "Total number of failed authorization attempts with source info"}
130 ]},
131
132 %%% Those metrics have reference only to a queue name. This is the only group where filtering (e.g. by vhost) makes sense.
166133 {queue_coarse_metrics, [
167134 {2, undefined, queue_messages_ready, gauge, "Messages ready to be delivered to consumers"},
168135 {3, undefined, queue_messages_unacked, gauge, "Messages delivered to consumers but not yet acknowledged"},
169136 {4, undefined, queue_messages, gauge, "Sum of ready and unacknowledged messages - total queue depth"},
170137 {5, undefined, queue_process_reductions_total, counter, "Total number of queue process reductions"}
138 ]},
139
140 {queue_consumer_count, [
141 {2, undefined, queue_consumers, gauge, "Consumers on a queue", consumers}
171142 ]},
172143
173144 {queue_metrics, [
190161 {2, undefined, queue_disk_writes_total, counter, "Total number of times queue wrote messages to disk", disk_writes}
191162 ]},
192163
193 {auth_attempt_metrics, [
194 {2, undefined, auth_attempts_total, counter, "Total number of authorization attempts"},
195 {3, undefined, auth_attempts_succeeded_total, counter, "Total number of successful authentication attempts"},
196 {4, undefined, auth_attempts_failed_total, counter, "Total number of failed authentication attempts"}
197 ]},
198
199 {auth_attempt_detailed_metrics, [
200 {2, undefined, auth_attempts_detailed_total, counter, "Total number of authorization attempts with source info"},
201 {3, undefined, auth_attempts_detailed_succeeded_total, counter, "Total number of successful authorization attempts with source info"},
202 {4, undefined, auth_attempts_detailed_failed_total, counter, "Total number of failed authorization attempts with source info"}
164 %%% Metrics that contain reference to a channel. Some of them also have
165 %%% a queue name, but in this case filtering on it doesn't make any
166 %%% sense, as the queue is not an object of interest here.
167 {channel_metrics, [
168 {2, undefined, channel_consumers, gauge, "Consumers on a channel", consumer_count},
169 {2, undefined, channel_messages_unacked, gauge, "Delivered but not yet acknowledged messages", messages_unacknowledged},
170 {2, undefined, channel_messages_unconfirmed, gauge, "Published but not yet confirmed messages", messages_unconfirmed},
171 {2, undefined, channel_messages_uncommitted, gauge, "Messages received in a transaction but not yet committed", messages_uncommitted},
172 {2, undefined, channel_acks_uncommitted, gauge, "Message acknowledgements in a transaction not yet committed", acks_uncommitted},
173 {2, undefined, consumer_prefetch, gauge, "Limit of unacknowledged messages for each consumer", prefetch_count},
174 {2, undefined, channel_prefetch, gauge, "Total limit of unacknowledged messages for all consumers on a channel", global_prefetch_count}
175 ]},
176
177 {channel_exchange_metrics, [
178 {2, undefined, channel_messages_published_total, counter, "Total number of messages published into an exchange on a channel"},
179 {3, undefined, channel_messages_confirmed_total, counter, "Total number of messages published into an exchange and confirmed on the channel"},
180 {4, undefined, channel_messages_unroutable_returned_total, counter, "Total number of messages published as mandatory into an exchange and returned to the publisher as unroutable"},
181 {5, undefined, channel_messages_unroutable_dropped_total, counter, "Total number of messages published as non-mandatory into an exchange and dropped as unroutable"}
182 ]},
183
184 {channel_process_metrics, [
185 {2, undefined, channel_process_reductions_total, counter, "Total number of channel process reductions"}
186 ]},
187
188 {channel_queue_metrics, [
189 {2, undefined, channel_get_ack_total, counter, "Total number of messages fetched with basic.get in manual acknowledgement mode"},
190 {3, undefined, channel_get_total, counter, "Total number of messages fetched with basic.get in automatic acknowledgement mode"},
191 {4, undefined, channel_messages_delivered_ack_total, counter, "Total number of messages delivered to consumers in manual acknowledgement mode"},
192 {5, undefined, channel_messages_delivered_total, counter, "Total number of messages delivered to consumers in automatic acknowledgement mode"},
193 {6, undefined, channel_messages_redelivered_total, counter, "Total number of messages redelivered to consumers"},
194 {7, undefined, channel_messages_acked_total, counter, "Total number of messages acknowledged by consumers"},
195 {8, undefined, channel_get_empty_total, counter, "Total number of times basic.get operations fetched no message"}
196 ]},
197
198 {connection_coarse_metrics, [
199 {2, undefined, connection_incoming_bytes_total, counter, "Total number of bytes received on a connection"},
200 {3, undefined, connection_outgoing_bytes_total, counter, "Total number of bytes sent on a connection"},
201 {4, undefined, connection_process_reductions_total, counter, "Total number of connection process reductions"}
202 ]},
203
204 {connection_metrics, [
205 {2, undefined, connection_incoming_packets_total, counter, "Total number of packets received on a connection", recv_cnt},
206 {2, undefined, connection_outgoing_packets_total, counter, "Total number of packets sent on a connection", send_cnt},
207 {2, undefined, connection_pending_packets, gauge, "Number of packets waiting to be sent on a connection", send_pend},
208 {2, undefined, connection_channels, gauge, "Channels on a connection", channels}
209 ]},
210
211 {channel_queue_exchange_metrics, [
212 {2, undefined, queue_messages_published_total, counter, "Total number of messages published to queues"}
203213 ]}
204
205214 ]).
206215
207216 -define(TOTALS, [
221230
222231 deregister_cleanup(_) -> ok.
223232
233 collect_mf('detailed', Callback) ->
234 collect(true, ?DETAILED_METRIC_NAME_PREFIX, vhosts_filter_from_pdict(), enabled_mfs_from_pdict(), Callback),
235 ok;
224236 collect_mf('per-object', Callback) ->
225 collect(true, Callback);
237 collect(true, ?METRIC_NAME_PREFIX, false, ?METRICS_RAW, Callback),
238 totals(Callback),
239 ok;
226240 collect_mf(_Registry, Callback) ->
227241 PerObjectMetrics = application:get_env(rabbitmq_prometheus, return_per_object_metrics, false),
228 collect(PerObjectMetrics, Callback).
229
230 collect(PerObjectMetrics, Callback) ->
242 collect(PerObjectMetrics, ?METRIC_NAME_PREFIX, false, ?METRICS_RAW, Callback),
243 totals(Callback),
244 ok.
245
246 collect(PerObjectMetrics, Prefix, VHostsFilter, IncludedMFs, Callback) ->
231247 [begin
232 Data = get_data(Table, PerObjectMetrics),
233 mf(Callback, Contents, Data)
234 end || {Table, Contents} <- ?METRICS_RAW, include_when_per_object_metrics(PerObjectMetrics, Table)],
248 Data = get_data(Table, PerObjectMetrics, VHostsFilter),
249 mf(Callback, Prefix, Contents, Data)
250 end || {Table, Contents} <- IncludedMFs, not mutually_exclusive_mf(PerObjectMetrics, Table, IncludedMFs)].
251
252 totals(Callback) ->
235253 [begin
236254 Size = ets:info(Table, size),
237255 mf_totals(Callback, Name, Type, Help, Size)
238256 end || {Table, Name, Type, Help} <- ?TOTALS],
239257 add_metric_family(build_info(), Callback),
240 add_metric_family(identity_info(), Callback),
241 ok.
242
243 include_when_per_object_metrics(false, auth_attempt_detailed_metrics) ->
244 false;
245 include_when_per_object_metrics(_, _) ->
246 true.
258 add_metric_family(identity_info(), Callback).
259
260 %% Aggregated `auth``_attempt_detailed_metrics` and
261 %% `auth_attempt_metrics` are the same numbers. The former is just
262 %% more computationally intensive.
263 mutually_exclusive_mf(false, auth_attempt_detailed_metrics, _) ->
264 true;
265 %% `queue_consumer_count` is a strict subset of queue metrics. They
266 %% read from the same table, but `queue_consumer_count` skips a lot of
267 %% `proplists:get_value/2` calls.
268 mutually_exclusive_mf(_, queue_consumer_count, MFs) ->
269 lists:keymember(queue_metrics, 1, MFs);
270 mutually_exclusive_mf(_, _, _) ->
271 false.
247272
248273 build_info() ->
249274 ProductInfo = rabbit:product_info(),
295320 add_metric_family({Name, Type, Help, Metrics}, Callback) ->
296321 Callback(create_mf(?METRIC_NAME(Name), Help, Type, Metrics)).
297322
298 mf(Callback, Contents, Data) ->
323 mf(Callback, Prefix, Contents, Data) ->
299324 [begin
300325 Fun = case Conversion of
301326 undefined ->
305330 end,
306331 Callback(
307332 create_mf(
308 ?METRIC_NAME(Name),
333 [Prefix, prometheus_model_helpers:metric_name(Name)],
309334 Help,
310335 catch_boolean(Type),
311336 ?MODULE,
322347 end,
323348 Callback(
324349 create_mf(
325 ?METRIC_NAME(Name),
350 [Prefix, prometheus_model_helpers:metric_name(Name)],
326351 Help,
327352 catch_boolean(Type),
328353 ?MODULE,
415440 gauge_metric(Labels, Value)
416441 end.
417442
418 get_data(connection_metrics = Table, false) ->
443 get_data(connection_metrics = Table, false, _) ->
419444 {Table, A1, A2, A3, A4} = ets:foldl(fun({_, Props}, {T, A1, A2, A3, A4}) ->
420445 {T,
421446 sum(proplists:get_value(recv_cnt, Props), A1),
424449 sum(proplists:get_value(channels, Props), A4)}
425450 end, empty(Table), Table),
426451 [{Table, [{recv_cnt, A1}, {send_cnt, A2}, {send_pend, A3}, {channels, A4}]}];
427 get_data(channel_metrics = Table, false) ->
452 get_data(channel_metrics = Table, false, _) ->
428453 {Table, A1, A2, A3, A4, A5, A6, A7} =
429454 ets:foldl(fun({_, Props}, {T, A1, A2, A3, A4, A5, A6, A7}) ->
430455 {T,
439464 [{Table, [{consumer_count, A1}, {messages_unacknowledged, A2}, {messages_unconfirmed, A3},
440465 {messages_uncommitted, A4}, {acks_uncommitted, A5}, {prefetch_count, A6},
441466 {global_prefetch_count, A7}]}];
442 get_data(queue_metrics = Table, false) ->
467 get_data(queue_consumer_count = MF, false, VHostsFilter) ->
468 Table = queue_metrics, %% Real table name
469 {_, A1} = ets:foldl(fun
470 ({#resource{kind = queue, virtual_host = VHost}, _, _}, Acc) when is_map(VHostsFilter), map_get(VHost, VHostsFilter) == false ->
471 Acc;
472 ({_, Props, _}, {T, A1}) ->
473 {T,
474 sum(proplists:get_value(consumers, Props), A1)
475 }
476 end, empty(MF), Table),
477 [{Table, [{consumers, A1}]}];
478 get_data(queue_metrics = Table, false, VHostsFilter) ->
443479 {Table, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16} =
444 ets:foldl(fun({_, Props, _}, {T, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10,
445 A11, A12, A13, A14, A15, A16}) ->
480 ets:foldl(fun
481 ({#resource{kind = queue, virtual_host = VHost}, _, _}, Acc) when is_map(VHostsFilter), map_get(VHost, VHostsFilter) == false ->
482 Acc;
483 ({_, Props, _}, {T, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10,
484 A11, A12, A13, A14, A15, A16}) ->
446485 {T,
447486 sum(proplists:get_value(consumers, Props), A1),
448487 sum(proplists:get_value(consumer_utilisation, Props), A2),
469508 {message_bytes_ready, A11}, {message_bytes_unacknowledged, A12},
470509 {messages_paged_out, A13}, {message_bytes_paged_out, A14},
471510 {disk_reads, A15}, {disk_writes, A16}]}];
472 get_data(Table, false) when Table == channel_exchange_metrics;
511 get_data(Table, false, VHostsFilter) when Table == channel_exchange_metrics;
473512 Table == queue_coarse_metrics;
474513 Table == channel_queue_metrics;
475514 Table == connection_coarse_metrics;
476515 Table == channel_queue_exchange_metrics;
477516 Table == ra_metrics;
478517 Table == channel_process_metrics ->
479 Result = ets:foldl(fun({_, V1}, {T, A1}) ->
518 Result = ets:foldl(fun
519 %% For queue_coarse_metrics
520 ({#resource{kind = queue, virtual_host = VHost}, _, _, _, _}, Acc) when is_map(VHostsFilter), map_get(VHost, VHostsFilter) == false ->
521 Acc;
522 ({_, V1}, {T, A1}) ->
480523 {T, V1 + A1};
481524 ({_, V1, _}, {T, A1}) ->
482525 {T, V1 + A1};
502545 _ ->
503546 [Result]
504547 end;
505 get_data(Table, _) ->
548 get_data(queue_coarse_metrics = Table, true, VHostsFilter) when is_map(VHostsFilter) ->
549 ets:foldl(fun
550 ({#resource{kind = queue, virtual_host = VHost}, _, _, _, _} = Row, Acc) when map_get(VHost, VHostsFilter) ->
551 [Row|Acc];
552 (_, Acc) ->
553 Acc
554 end, [], Table);
555 get_data(MF, true, VHostsFilter) when is_map(VHostsFilter), MF == queue_metrics orelse MF == queue_consumer_count ->
556 Table = queue_metrics,
557 ets:foldl(fun
558 ({#resource{kind = queue, virtual_host = VHost}, _, _} = Row, Acc) when map_get(VHost, VHostsFilter) ->
559 [Row|Acc];
560 (_, Acc) ->
561 Acc
562 end, [], Table);
563 get_data(queue_consumer_count, true, _) ->
564 ets:tab2list(queue_metrics);
565 get_data(Table, _, _) ->
506566 ets:tab2list(Table).
507567
508568 division(0, 0) ->
513573 accumulate_count_and_sum(Value, {Count, Sum}) ->
514574 {Count + 1, Sum + Value}.
515575
516 empty(T) when T == channel_queue_exchange_metrics; T == channel_process_metrics ->
576 empty(T) when T == channel_queue_exchange_metrics; T == channel_process_metrics; T == queue_consumer_count ->
517577 {T, 0};
518578 empty(T) when T == connection_coarse_metrics; T == auth_attempt_metrics; T == auth_attempt_detailed_metrics ->
519579 {T, 0, 0, 0};
532592 B;
533593 sum(A, B) ->
534594 A + B.
595
596 enabled_mfs_from_pdict() ->
597 case get(prometheus_mf_filter) of
598 undefined ->
599 [];
600 MFNames ->
601 MFNameSet = sets:from_list(MFNames),
602 [ MF || MF = {Table, _} <- ?METRICS_RAW, sets:is_element(Table, MFNameSet) ]
603 end.
604
605 vhosts_filter_from_pdict() ->
606 case get(prometheus_vhost_filter) of
607 undefined ->
608 false;
609 L ->
610 %% Having both excluded and included hosts in this map makes some guards easier (or even possible).
611 All = maps:from_list([ {VHost, false} || VHost <- rabbit_vhost:list()]),
612 Enabled = maps:from_list([ {VHost, true} || VHost <- L ]),
613 maps:merge(All, Enabled)
614 end.
2626 prometheus_rabbitmq_core_metrics_collector,
2727 prometheus_rabbitmq_global_metrics_collector
2828 ]),
29 prometheus_registry:register_collectors('detailed', [
30 prometheus_rabbitmq_core_metrics_collector
31 ]),
2932 rabbit_prometheus_handler:setup(),
3033 cowboy_router:compile([{'_', dispatcher()}]).
3134
3131
3232 setup() ->
3333 setup_metrics(telemetry_registry()),
34 setup_metrics('per-object').
34 setup_metrics('per-object'),
35 setup_metrics('detailed').
3536
3637 setup_metrics(Registry) ->
3738 ScrapeDuration = [{name, ?SCRAPE_DURATION},
6667 false ->
6768 cowboy_req:reply(404, #{}, <<"Unknown Registry">>, Request);
6869 Registry ->
70 put_filtering_options_into_process_dictionary(Request),
6971 gen_metrics_response(Registry, Request)
7072 end;
7173 gen_response(_, Request) ->
145147 encode_format_("gzip", Scrape) ->
146148 zlib:gzip(Scrape);
147149 encode_format_("identity", Scrape) ->
148 Scrape.
150 Scrape.
151
152 %% It's not easy to pass this information in a pure way (it'll require changing prometheus.erl)
153 put_filtering_options_into_process_dictionary(Request) ->
154 #{vhost := VHosts, family := Families} = cowboy_req:match_qs([{vhost, [], undefined}, {family, [], undefined}], Request),
155 case parse_vhosts(VHosts) of
156 Vs when is_list(Vs) ->
157 put(prometheus_vhost_filter, Vs);
158 _ -> ok
159 end,
160 case parse_metric_families(Families) of
161 Fs when is_list(Fs) ->
162 put(prometheus_mf_filter, Fs);
163 _ -> ok
164 end,
165 ok.
166
167 parse_vhosts(N) when is_binary(N) ->
168 parse_vhosts([N]);
169 parse_vhosts(L) when is_list(L) ->
170 [ VHostName || VHostName <- L, rabbit_vhost:exists(VHostName)];
171 parse_vhosts(_) ->
172 false.
173
174 parse_metric_families(N) when is_binary(N) ->
175 parse_metric_families([N]);
176 parse_metric_families([]) ->
177 [];
178 parse_metric_families([B|Bs]) ->
179 %% binary_to_existing_atom() should be enough, as it's used for filtering things out.
180 %% Getting a full list of supported metrics would be harder.
181 %% NB But on the other hand, it's nice to have validation. Implement it?
182 case catch erlang:binary_to_existing_atom(B) of
183 A when is_atom(A) ->
184 [A|parse_metric_families(Bs)];
185 _ ->
186 parse_metric_families(Bs)
187 end;
188 parse_metric_families(_) ->
189 false.
3535 with a custom exchange type that distributes messages by applying
3636 a hashing function to the routing key.
3737
38 ## Sharding and Queue Replication
39
40 Sharding performed by this plugin makes sense for **non-replicated classic queues** only.
41
42 Combining sharding with a replicated queue type, e.g. [quorum queues]() or
43 (**deprecated**) mirrored classic queues will lose most or all of the benefits offered
44 by this plugin.
45
46 Do not use this plugin with quorum queues. Avoid classic mirrored queues in general.
3847
3948 ## Messages Distribution Between Shards (Partitioning)
4049
1010 -export([start_link/0, init/1, adjust/2, stop_child/1, cleanup_specs/0]).
1111
1212 -import(rabbit_misc, [pget/2]).
13 -import(rabbit_data_coercion, [to_map/1, to_list/1]).
1314
1415 -include("rabbit_shovel.hrl").
1516 -include_lib("rabbit_common/include/rabbit.hrl").
4142 rabbit_log_shovel:debug("Starting a mirrored supervisor named '~s' in virtual host '~s'", [ShovelName, VHost]),
4243 Result = case mirrored_supervisor:start_child(
4344 ?SUPERVISOR,
44 {Name, {rabbit_shovel_dyn_worker_sup, start_link, [Name, Def]},
45 {Name, {rabbit_shovel_dyn_worker_sup, start_link, [Name, obfuscated_uris_parameters(Def)]},
4546 transient, ?WORKER_WAIT, worker, [rabbit_shovel_dyn_worker_sup]}) of
4647 {ok, _Pid} -> ok;
4748 {error, {already_started, _Pid}} -> ok
4950 %% release the lock if we managed to acquire one
5051 rabbit_shovel_locks:unlock(LockId),
5152 Result.
53
54 obfuscated_uris_parameters(Def) when is_map(Def) ->
55 to_map(rabbit_shovel_parameters:obfuscate_uris_in_definition(to_list(Def)));
56 obfuscated_uris_parameters(Def) when is_list(Def) ->
57 rabbit_shovel_parameters:obfuscate_uris_in_definition(Def).
5258
5359 child_exists(Name) ->
5460 lists:any(fun ({N, _, _, _}) -> N =:= Name end,
1212
1313 -export([validate/5, notify/5, notify_clear/4]).
1414 -export([register/0, unregister/0, parse/3]).
15
16 -import(rabbit_misc, [pget/2, pget/3]).
15 -export([obfuscate_uris_in_definition/1]).
16
17 -import(rabbit_misc, [pget/2, pget/3, pset/3]).
1718
1819 -rabbit_boot_step({?MODULE,
1920 [{description, "shovel parameters"},
8081 _ ->
8182 ok
8283 end].
84
85 obfuscate_uris_in_definition(Def) ->
86 SrcURIs = get_uris(<<"src-uri">>, Def),
87 ObfuscatedSrcURIsDef = pset(<<"src-uri">>, obfuscate_uris(SrcURIs), Def),
88 DestURIs = get_uris(<<"dest-uri">>, Def),
89 ObfuscatedDef = pset(<<"dest-uri">>, obfuscate_uris(DestURIs), ObfuscatedSrcURIsDef),
90 ObfuscatedDef.
91
92 obfuscate_uris(URIs) ->
93 [credentials_obfuscation:encrypt(URI) || URI <- URIs].
8394
8495 validate_amqp091_dest(Def) ->
8596 [case pget2(<<"dest-exchange">>, <<"dest-queue">>, Def) of
278289 end.
279290
280291 parse_amqp10_dest({_VHost, _Name}, _ClusterName, Def, SourceHeaders) ->
281 Uris = get_uris(<<"dest-uri">>, Def),
292 Uris = deobfuscated_uris(<<"dest-uri">>, Def),
282293 Address = pget(<<"dest-address">>, Def),
283294 Properties =
284295 rabbit_data_coercion:to_proplist(
304315 }.
305316
306317 parse_amqp091_dest({VHost, Name}, ClusterName, Def, SourceHeaders) ->
307 DestURIs = get_uris(<<"dest-uri">>, Def),
318 DestURIs = deobfuscated_uris(<<"dest-uri">>, Def),
308319 DestX = pget(<<"dest-exchange">>, Def, none),
309320 DestXKey = pget(<<"dest-exchange-key">>, Def, none),
310321 DestQ = pget(<<"dest-queue">>, Def, none),
372383 }, Details).
373384
374385 parse_amqp10_source(Def) ->
375 Uris = get_uris(<<"src-uri">>, Def),
386 Uris = deobfuscated_uris(<<"src-uri">>, Def),
376387 Address = pget(<<"src-address">>, Def),
377388 DeleteAfter = pget(<<"src-delete-after">>, Def, <<"never">>),
378389 PrefetchCount = pget(<<"src-prefetch-count">>, Def, 1000),
385396 consumer_args => []}, Headers}.
386397
387398 parse_amqp091_source(Def) ->
388 SrcURIs = get_uris(<<"src-uri">>, Def),
399 SrcURIs = deobfuscated_uris(<<"src-uri">>, Def),
389400 SrcX = pget(<<"src-exchange">>,Def, none),
390401 SrcXKey = pget(<<"src-exchange-key">>, Def, <<>>), %% [1]
391402 SrcQ = pget(<<"src-queue">>, Def, none),
429440 end,
430441 [binary_to_list(URI) || URI <- URIs].
431442
443 deobfuscated_uris(Key, Def) ->
444 ObfuscatedURIs = pget(Key, Def),
445 URIs = [credentials_obfuscation:decrypt(ObfuscatedURI) || ObfuscatedURI <- ObfuscatedURIs],
446 [binary_to_list(URI) || URI <- URIs].
447
432448 translate_ack_mode(<<"on-confirm">>) -> on_confirm;
433449 translate_ack_mode(<<"on-publish">>) -> on_publish;
434450 translate_ack_mode(<<"no-ack">>) -> no_ack.
8181 ),
8282 rabbitmq_integration_suite(
8383 PACKAGE,
84 name = "rabbit_stream_utils_SUITE",
85 ),
86 rabbitmq_integration_suite(
87 PACKAGE,
8488 name = "rabbit_stream_SUITE",
8589 deps = [
8690 "//deps/rabbit:bazel_erlang_lib",
369369 Key => uint16 // 10
370370 Version => uint16
371371 Reference => string // max 256 characters
372 SubscriptionId => uint8
372 Stream => string // the name of the stream
373373 Offset => uint64
374374 ```
375375
586586 RoutingKey => string
587587 SuperStream => string
588588
589 RouteResponse => Key Version CorrelationId Stream
589 RouteResponse => Key Version CorrelationId [Stream]
590590 Key => uint16 // 24
591591 Version => uint16
592592 CorrelationId => uint32
654654 from the server's suggestions.
655655 * Open: the client sends an `Open` frame to pick a virtual host to connect to. The server
656656 answers whether it accepts the access or not.
657
658 == Resources
659
660 - https://docs.google.com/presentation/d/1Hlv4qaWm2PRU04dVPmShP9wU7TEQEttXdsbV8P54Uvw/edit#slide=id.gdbeadf9676_0_37[RabbitMQ stream client] : a general guide line to write a stream client
661 - https://docs.google.com/presentation/d/1BFwf01LcicZ-SyxE1CycZv2gUQMPFGdtFkVuXhgkoTE/edit#slide=id.p1[RabbitMQ Streams Internals]: how the streams work internally
3333
3434 -include_lib("rabbit_common/include/rabbit.hrl").
3535 -include_lib("rabbitmq_stream_common/include/rabbit_stream.hrl").
36
3637 -include("rabbit_stream_metrics.hrl").
3738
3839 start(_Type, _Args) ->
3940 rabbit_stream_metrics:init(),
4041 rabbit_global_counters:init([{protocol, stream}], ?PROTOCOL_COUNTERS),
41 rabbit_global_counters:init([{protocol, stream}, {queue_type, ?STREAM_QUEUE_TYPE}]),
42 rabbit_global_counters:init([{protocol, stream},
43 {queue_type, ?STREAM_QUEUE_TYPE}]),
4244 rabbit_stream_sup:start_link().
4345
4446 host() ->
7474 gen_server:call(?MODULE, {topology, VirtualHost, Stream}).
7575
7676 -spec route(binary(), binary(), binary()) ->
77 {ok, binary() | no_route} | {error, stream_not_found}.
77 {ok, [binary()] | no_route} | {error, stream_not_found}.
7878 route(RoutingKey, VirtualHost, SuperStream) ->
7979 gen_server:call(?MODULE,
8080 {route, RoutingKey, VirtualHost, SuperStream}).
367367 case rabbit_exchange:route(Exchange, Delivery) of
368368 [] ->
369369 {ok, no_route};
370 [#resource{name = Stream}] ->
371 {ok, Stream};
372 [#resource{name = Stream} | _] ->
373 {ok, Stream}
370 Routes ->
371 {ok,
372 [Stream
373 || #resource{name = Stream} = R <- Routes,
374 is_resource_stream_queue(R)]}
374375 end
375376 catch
376377 exit:Error ->
383384 ExchangeName = rabbit_misc:r(VirtualHost, exchange, SuperStream),
384385 Res = try
385386 rabbit_exchange:lookup_or_die(ExchangeName),
386 %% FIXME make sure queue is a stream
387 %% TODO bindings could be sorted by partition number, by using a binding argument
388 %% this would make the spreading of messages stable
387 UnorderedBindings =
388 [Binding
389 || Binding = #binding{destination = D}
390 <- rabbit_binding:list_for_source(ExchangeName),
391 is_resource_stream_queue(D)],
392 OrderedBindings =
393 rabbit_stream_utils:sort_partitions(UnorderedBindings),
389394 {ok,
390395 lists:foldl(fun (#binding{destination =
391396 #resource{kind = queue, name = Q}},
394399 (_Binding, Acc) ->
395400 Acc
396401 end,
397 [], rabbit_binding:list_for_source(ExchangeName))}
402 [], OrderedBindings)}
398403 catch
399404 exit:Error ->
400405 rabbit_log:error("Error while looking up exchange ~p, ~p",
445450 _ ->
446451 false
447452 end.
453
454 is_resource_stream_queue(#resource{kind = queue} = Resource) ->
455 case rabbit_amqqueue:lookup(Resource) of
456 {ok, Q} ->
457 is_stream_queue(Q);
458 _ ->
459 false
460 end;
461 is_resource_stream_queue(_) ->
462 false.
00 %% The contents of this file are subject to the Mozilla Public License
1 %% Version 2.0 (the "License"); you may not use this file except in
2 %% compliance with the License. You may obtain a copy of the License
31 %% at https://www.mozilla.org/en-US/MPL/2.0/
42 %%
53 %% Software distributed under the License is distributed on an "AS IS"
1917
2018 -include_lib("rabbit_common/include/rabbit.hrl").
2119 -include_lib("rabbitmq_stream_common/include/rabbit_stream.hrl").
20
2221 -include("rabbit_stream_metrics.hrl").
2322
2423 -type stream() :: binary().
8180 resource_alarm :: boolean(),
8281 send_file_oct ::
8382 atomics:atomics_ref(), % number of bytes sent with send_file (for metrics)
84 transport :: tcp | ssl}).
83 transport :: tcp | ssl,
84 proxy_socket :: undefined | ranch_proxy:proxy_socket()}).
8585 -record(configuration,
8686 {initial_credits :: integer(),
8787 credits_required_for_unblocking :: integer(),
8888 frame_max :: integer(),
8989 heartbeat :: integer(),
9090 connection_negotiation_step_timeout :: integer()}).
91
9291 -record(statem_data,
9392 {transport :: module(),
9493 connection :: #stream_connection{},
151150 consumers_info/2,
152151 publishers_info/2,
153152 in_vhost/2]).
154
155153 -export([resource_alarm/3]).
156
157154 %% gen_statem callbacks
158155 -export([callback_mode/0,
159156 terminate/3,
160 %% not called by gen_statem since gen_statem:enter_loop/4 is used
161157 init/1,
162 %% states
163158 tcp_connected/3,
164159 peer_properties_exchanged/3,
165160 authenticating/3,
168163 open/3,
169164 close_sent/3]).
170165
166 %% not called by gen_statem since gen_statem:enter_loop/4 is used
167
168 %% states
169
171170 callback_mode() ->
172171 [state_functions, state_enter].
173172
174 terminate(Reason, State, _StatemData) ->
175 rabbit_log:debug("~p terminating in state '~s' with reason '~p'", [?MODULE, State, Reason]).
173 terminate(Reason, State,
174 #statem_data{transport = Transport,
175 connection = #stream_connection{socket = Socket},
176 connection_state = ConnectionState} =
177 StatemData) ->
178 close(Transport, Socket, ConnectionState),
179 rabbit_networking:unregister_non_amqp_connection(self()),
180 notify_connection_closed(StatemData),
181 rabbit_log:debug("~s terminating in state '~s' with reason '~W'",
182 [?MODULE, State, Reason, 10]).
176183
177184 start_link(KeepaliveSup, Transport, Ref, Opts) ->
178 {ok, proc_lib:spawn_link(?MODULE, init, [[KeepaliveSup, Transport, Ref, Opts]])}.
185 {ok,
186 proc_lib:spawn_link(?MODULE, init,
187 [[KeepaliveSup, Transport, Ref, Opts]])}.
179188
180189 init([KeepaliveSup,
181190 Transport,
187196 transport := ConnTransport}]) ->
188197 process_flag(trap_exit, true),
189198 {ok, Sock} =
190 rabbit_networking:handshake(Ref,
191 application:get_env(rabbitmq_stream,
192 proxy_protocol, false)),
199 rabbit_networking:handshake(Ref,
200 application:get_env(rabbitmq_stream,
201 proxy_protocol, false)),
193202 RealSocket = rabbit_net:unwrap_socket(Sock),
194203 case rabbit_net:connection_string(Sock, inbound) of
195204 {ok, ConnStr} ->
198207 atomics:put(SendFileOct, 1, 0),
199208 init_credit(Credits, InitialCredits),
200209 {PeerHost, PeerPort, Host, Port} =
201 socket_op(Sock,
202 fun(S) -> rabbit_net:socket_ends(S, inbound) end),
210 socket_op(Sock,
211 fun(S) -> rabbit_net:socket_ends(S, inbound) end),
203212 Connection =
204 #stream_connection{name =
205 rabbit_data_coercion:to_binary(ConnStr),
206 host = Host,
207 peer_host = PeerHost,
208 port = Port,
209 peer_port = PeerPort,
210 connected_at = os:system_time(milli_seconds),
211 auth_mechanism = none,
212 helper_sup = KeepaliveSup,
213 socket = RealSocket,
214 publishers = #{},
215 publisher_to_ids = #{},
216 stream_leaders = #{},
217 stream_subscriptions = #{},
218 credits = Credits,
219 authentication_state = none,
220 connection_step = tcp_connected,
221 frame_max = FrameMax,
222 resource_alarm = false,
223 send_file_oct = SendFileOct,
224 transport = ConnTransport},
213 #stream_connection{name =
214 rabbit_data_coercion:to_binary(ConnStr),
215 host = Host,
216 peer_host = PeerHost,
217 port = Port,
218 peer_port = PeerPort,
219 connected_at = os:system_time(milli_seconds),
220 auth_mechanism = none,
221 helper_sup = KeepaliveSup,
222 socket = RealSocket,
223 publishers = #{},
224 publisher_to_ids = #{},
225 stream_leaders = #{},
226 stream_subscriptions = #{},
227 credits = Credits,
228 authentication_state = none,
229 connection_step = tcp_connected,
230 frame_max = FrameMax,
231 resource_alarm = false,
232 send_file_oct = SendFileOct,
233 transport = ConnTransport,
234 proxy_socket =
235 rabbit_net:maybe_get_proxy_socket(Sock)},
225236 State =
226 #stream_connection_state{consumers = #{},
227 blocked = false,
228 data =
229 rabbit_stream_core:init(undefined)},
237 #stream_connection_state{consumers = #{},
238 blocked = false,
239 data =
240 rabbit_stream_core:init(undefined)},
230241 Transport:setopts(RealSocket, [{active, once}]),
231242 rabbit_alarm:register(self(), {?MODULE, resource_alarm, []}),
232 ConnectionNegotiationStepTimeout = application:get_env(
233 rabbitmq_stream,
234 connection_negotiation_step_timeout,
235 10_000),
243 ConnectionNegotiationStepTimeout =
244 application:get_env(rabbitmq_stream,
245 connection_negotiation_step_timeout,
246 10_000),
236247 % gen_statem process has its start_link call not return until the init function returns.
237248 % This is problematic, because we won't be able to call ranch:handshake/2
238249 % from the init callback as this would cause a deadlock to happen.
239250 % Therefore, we use the gen_statem:enter_loop/4 function.
240251 % See https://ninenines.eu/docs/en/ranch/2.0/guide/protocols/
241 gen_statem:enter_loop(?MODULE, [], tcp_connected,
242 #statem_data{
243 transport = Transport,
244 connection = Connection,
245 connection_state = State,
246 config = #configuration{
247 initial_credits = InitialCredits,
248 credits_required_for_unblocking = CreditsRequiredBeforeUnblocking,
249 frame_max = FrameMax,
250 heartbeat = Heartbeat,
251 connection_negotiation_step_timeout = ConnectionNegotiationStepTimeout}});
252 gen_statem:enter_loop(?MODULE,
253 [],
254 tcp_connected,
255 #statem_data{transport = Transport,
256 connection = Connection,
257 connection_state = State,
258 config =
259 #configuration{initial_credits
260 =
261 InitialCredits,
262 credits_required_for_unblocking
263 =
264 CreditsRequiredBeforeUnblocking,
265 frame_max =
266 FrameMax,
267 heartbeat =
268 Heartbeat,
269 connection_negotiation_step_timeout
270 =
271 ConnectionNegotiationStepTimeout}});
252272 {Error, Reason} ->
253273 rabbit_net:fast_close(RealSocket),
254274 rabbit_log_connection:warning("Closing connection because of ~p ~p",
255275 [Error, Reason])
256276 end.
257277
258 tcp_connected(enter, _OldState, #statem_data{
259 config = #configuration{
260 connection_negotiation_step_timeout = StateTimeout}}) ->
278 tcp_connected(enter, _OldState,
279 #statem_data{config =
280 #configuration{connection_negotiation_step_timeout
281 = StateTimeout}}) ->
261282 {keep_state_and_data, {state_timeout, StateTimeout, close}};
262 tcp_connected(state_timeout, close, #statem_data{
263 transport = Transport,
264 connection = #stream_connection{socket = Socket}
265 }) ->
283 tcp_connected(state_timeout, close,
284 #statem_data{transport = Transport,
285 connection = #stream_connection{socket = Socket}}) ->
266286 state_timeout(?FUNCTION_NAME, Transport, Socket);
267287 tcp_connected(info, Msg, StateData) ->
268288 handle_info(Msg, StateData,
269 fun (NextConnectionStep,
270 #statem_data{
271 transport = Transport,
272 connection = #stream_connection{socket = S}
273 } = StatemData,
274 NewConnection,
275 NewConnectionState) ->
276 if NextConnectionStep =:= peer_properties_exchanged ->
277 {next_state, peer_properties_exchanged, StatemData#statem_data{
278 connection = NewConnection,
279 connection_state = NewConnectionState
280 }};
281 true ->
282 invalid_transition(Transport, S, ?FUNCTION_NAME, NextConnectionStep)
283 end
289 fun(NextConnectionStep,
290 #statem_data{transport = Transport,
291 connection = #stream_connection{socket = S}} =
292 StatemData,
293 NewConnection,
294 NewConnectionState) ->
295 if NextConnectionStep =:= peer_properties_exchanged ->
296 {next_state, peer_properties_exchanged,
297 StatemData#statem_data{connection = NewConnection,
298 connection_state =
299 NewConnectionState}};
300 true ->
301 invalid_transition(Transport,
302 S,
303 ?FUNCTION_NAME,
304 NextConnectionStep)
305 end
284306 end).
285307
286 peer_properties_exchanged(enter, _OldState, #statem_data{
287 config = #configuration{
288 connection_negotiation_step_timeout = StateTimeout}}) ->
308 peer_properties_exchanged(enter, _OldState,
309 #statem_data{config =
310 #configuration{connection_negotiation_step_timeout
311 =
312 StateTimeout}}) ->
289313 {keep_state_and_data, {state_timeout, StateTimeout, close}};
290 peer_properties_exchanged(state_timeout, close, #statem_data{
291 transport = Transport,
292 connection = #stream_connection{socket = Socket}
293 }) ->
314 peer_properties_exchanged(state_timeout, close,
315 #statem_data{transport = Transport,
316 connection =
317 #stream_connection{socket =
318 Socket}}) ->
294319 state_timeout(?FUNCTION_NAME, Transport, Socket);
295320 peer_properties_exchanged(info, Msg, StateData) ->
296321 handle_info(Msg, StateData,
297 fun (NextConnectionStep,
298 #statem_data{
299 transport = Transport,
300 connection = #stream_connection{socket = S}
301 } = StatemData,
302 NewConnection,
303 NewConnectionState) ->
304 if NextConnectionStep =:= authenticating ->
305 {next_state, authenticating, StatemData#statem_data{
306 connection = NewConnection,
307 connection_state = NewConnectionState
308 }};
309 true ->
310 invalid_transition(Transport, S, ?FUNCTION_NAME, NextConnectionStep)
311 end
322 fun(NextConnectionStep,
323 #statem_data{transport = Transport,
324 connection = #stream_connection{socket = S}} =
325 StatemData,
326 NewConnection,
327 NewConnectionState) ->
328 if NextConnectionStep =:= authenticating ->
329 {next_state, authenticating,
330 StatemData#statem_data{connection = NewConnection,
331 connection_state =
332 NewConnectionState}};
333 true ->
334 invalid_transition(Transport,
335 S,
336 ?FUNCTION_NAME,
337 NextConnectionStep)
338 end
312339 end).
313340
314 authenticating(enter, _OldState, #statem_data{
315 config = #configuration{
316 connection_negotiation_step_timeout = StateTimeout}}) ->
341 authenticating(enter, _OldState,
342 #statem_data{config =
343 #configuration{connection_negotiation_step_timeout
344 = StateTimeout}}) ->
317345 {keep_state_and_data, {state_timeout, StateTimeout, close}};
318 authenticating(state_timeout, close, #statem_data{
319 transport = Transport,
320 connection = #stream_connection{socket = Socket}
321 }) ->
346 authenticating(state_timeout, close,
347 #statem_data{transport = Transport,
348 connection =
349 #stream_connection{socket = Socket}}) ->
322350 state_timeout(?FUNCTION_NAME, Transport, Socket);
323351 authenticating(info, Msg, StateData) ->
324352 handle_info(Msg, StateData,
325353 fun(NextConnectionStep,
326 #statem_data{
327 transport = Transport,
328 connection = #stream_connection{socket = S},
329 config = #configuration{
330 frame_max = FrameMax,
331 heartbeat = Heartbeat}
332 } = StatemData,
354 #statem_data{transport = Transport,
355 connection = #stream_connection{socket = S},
356 config =
357 #configuration{frame_max = FrameMax,
358 heartbeat = Heartbeat}} =
359 StatemData,
333360 NewConnection,
334361 NewConnectionState) ->
335 if NextConnectionStep =:= authenticated ->
336 Frame = rabbit_stream_core:frame({tune, FrameMax, Heartbeat}),
337 send(Transport, S, Frame),
338 {next_state, tuning, StatemData#statem_data{
339 connection = NewConnection#stream_connection{connection_step
340 =
341 tuning},
342 connection_state = NewConnectionState
343 }};
344 true ->
345 invalid_transition(Transport, S, ?FUNCTION_NAME, NextConnectionStep)
346 end
362 if NextConnectionStep =:= authenticated ->
363 Frame =
364 rabbit_stream_core:frame({tune, FrameMax,
365 Heartbeat}),
366 send(Transport, S, Frame),
367 {next_state, tuning,
368 StatemData#statem_data{connection =
369 NewConnection#stream_connection{connection_step
370 =
371 tuning},
372 connection_state =
373 NewConnectionState}};
374 true ->
375 invalid_transition(Transport,
376 S,
377 ?FUNCTION_NAME,
378 NextConnectionStep)
379 end
347380 end).
348381
349 tuning(enter, _OldState, #statem_data{
350 config = #configuration{
351 connection_negotiation_step_timeout = StateTimeout}}) ->
382 tuning(enter, _OldState,
383 #statem_data{config =
384 #configuration{connection_negotiation_step_timeout =
385 StateTimeout}}) ->
352386 {keep_state_and_data, {state_timeout, StateTimeout, close}};
353 tuning(state_timeout, close, #statem_data{
354 transport = Transport,
355 connection = #stream_connection{socket = Socket}
356 }) ->
387 tuning(state_timeout, close,
388 #statem_data{transport = Transport,
389 connection = #stream_connection{socket = Socket}}) ->
357390 state_timeout(?FUNCTION_NAME, Transport, Socket);
358391 tuning(info, Msg, StateData) ->
359392 handle_info(Msg, StateData,
360 fun (NextConnectionStep,
361 #statem_data{
362 transport = Transport,
363 connection = #stream_connection{socket = S},
364 config = Configuration
365 } = StatemData,
366 NewConnection,
367 NewConnectionState) ->
368 case NextConnectionStep of
369 tuned ->
370 {next_state, tuned, StatemData#statem_data{
371 connection = NewConnection,
372 connection_state = NewConnectionState
373 }};
374 opened ->
375 transition_to_opened(Transport, Configuration, NewConnection, NewConnectionState);
376 _ ->
377 invalid_transition(Transport, S, ?FUNCTION_NAME, NextConnectionStep)
378 end
393 fun(NextConnectionStep,
394 #statem_data{transport = Transport,
395 connection = #stream_connection{socket = S},
396 config = Configuration} =
397 StatemData,
398 NewConnection,
399 NewConnectionState) ->
400 case NextConnectionStep of
401 tuned ->
402 {next_state, tuned,
403 StatemData#statem_data{connection = NewConnection,
404 connection_state =
405 NewConnectionState}};
406 opened ->
407 transition_to_opened(Transport,
408 Configuration,
409 NewConnection,
410 NewConnectionState);
411 _ ->
412 invalid_transition(Transport,
413 S,
414 ?FUNCTION_NAME,
415 NextConnectionStep)
416 end
379417 end).
380418
381 tuned(enter, _OldState, #statem_data{
382 config = #configuration{
383 connection_negotiation_step_timeout = StateTimeout}}) ->
419 tuned(enter, _OldState,
420 #statem_data{config =
421 #configuration{connection_negotiation_step_timeout =
422 StateTimeout}}) ->
384423 {keep_state_and_data, {state_timeout, StateTimeout, close}};
385 tuned(state_timeout, close, #statem_data{
386 transport = Transport,
387 connection = #stream_connection{socket = Socket}
388 }) ->
424 tuned(state_timeout, close,
425 #statem_data{transport = Transport,
426 connection = #stream_connection{socket = Socket}}) ->
389427 state_timeout(?FUNCTION_NAME, Transport, Socket);
390428 tuned(info, Msg, StateData) ->
391429 handle_info(Msg, StateData,
392 fun (NextConnectionStep,
393 #statem_data{
394 transport = Transport,
395 connection = #stream_connection{socket = S},
396 config = Configuration},
397 NewConnection, NewConnectionState) ->
398 if NextConnectionStep =:= opened ->
399 transition_to_opened(Transport, Configuration, NewConnection, NewConnectionState);
400 true ->
401 invalid_transition(Transport, S, ?FUNCTION_NAME, NextConnectionStep)
402 end
430 fun(NextConnectionStep,
431 #statem_data{transport = Transport,
432 connection = #stream_connection{socket = S},
433 config = Configuration},
434 NewConnection,
435 NewConnectionState) ->
436 if NextConnectionStep =:= opened ->
437 transition_to_opened(Transport,
438 Configuration,
439 NewConnection,
440 NewConnectionState);
441 true ->
442 invalid_transition(Transport,
443 S,
444 ?FUNCTION_NAME,
445 NextConnectionStep)
446 end
403447 end).
404448
405449 state_timeout(State, Transport, Socket) ->
406 rabbit_log_connection:warning(
407 "Closing connection because of timeout in state '~s' likely due to lack of client action.",
408 [State]),
450 rabbit_log_connection:warning("Closing connection because of timeout in state "
451 "'~s' likely due to lack of client action.",
452 [State]),
409453 close_immediately(Transport, Socket),
410454 stop.
411455
412 handle_info(Msg, #statem_data{
413 transport = Transport,
414 connection = #stream_connection{socket = S, connection_step = PreviousConnectionStep} = Connection,
415 connection_state = State
416 } = StatemData, Transition) ->
456 handle_info(Msg,
457 #statem_data{transport = Transport,
458 connection =
459 #stream_connection{socket = S,
460 connection_step =
461 PreviousConnectionStep} =
462 Connection,
463 connection_state = State} =
464 StatemData,
465 Transition) ->
417466 {OK, Closed, Error, _Passive} = Transport:messages(),
418467 case Msg of
419468 {OK, S, Data} ->
420 {Connection1, State1} = handle_inbound_data_pre_auth(Transport, Connection, State, Data),
469 {Connection1, State1} =
470 handle_inbound_data_pre_auth(Transport,
471 Connection,
472 State,
473 Data),
421474 Transport:setopts(S, [{active, once}]),
422 #stream_connection{connection_step = NewConnectionStep} = Connection1,
423 rabbit_log_connection:debug("Transitioned from ~s to ~s", [PreviousConnectionStep, NewConnectionStep]),
475 #stream_connection{connection_step = NewConnectionStep} =
476 Connection1,
477 rabbit_log_connection:debug("Transitioned from ~s to ~s",
478 [PreviousConnectionStep,
479 NewConnectionStep]),
424480 Transition(NewConnectionStep, StatemData, Connection1, State1);
425481 {Closed, S} ->
426 rabbit_log_connection:warning("Stream protocol connection socket ~w closed", [S]),
482 rabbit_log_connection:warning("Stream protocol connection socket ~w closed",
483 [S]),
427484 stop;
428485 {Error, S, Reason} ->
429486 rabbit_log_connection:warning("Socket error ~p [~w]", [Reason, S]),
430487 stop;
431488 {resource_alarm, IsThereAlarm} ->
432 {keep_state, StatemData#statem_data{
433 connection = Connection#stream_connection{
434 resource_alarm = IsThereAlarm},
435 connection_state = State#stream_connection_state{
436 blocked = true}}};
489 {keep_state,
490 StatemData#statem_data{connection =
491 Connection#stream_connection{resource_alarm
492 =
493 IsThereAlarm},
494 connection_state =
495 State#stream_connection_state{blocked =
496 true}}};
437497 Unknown ->
438498 rabbit_log:warning("Received unknown message ~p", [Unknown]),
439499 close_immediately(Transport, S),
440500 stop
441501 end.
442502
443 transition_to_opened(Transport, Configuration, NewConnection, NewConnectionState) ->
503 transition_to_opened(Transport,
504 Configuration,
505 NewConnection,
506 NewConnectionState) ->
444507 % TODO remove registration to rabbit_stream_connections
445508 % just meant to be able to close the connection remotely
446509 % should be possible once the connections are available in ctl list_connections
447510 pg_local:join(rabbit_stream_connections, self()),
448511 Connection1 =
449 rabbit_event:init_stats_timer(NewConnection,
450 #stream_connection.stats_timer),
512 rabbit_event:init_stats_timer(NewConnection,
513 #stream_connection.stats_timer),
451514 Connection2 = ensure_stats_timer(Connection1),
452515 Infos =
453 augment_infos_with_user_provided_connection_name(infos(?CREATION_EVENT_KEYS,
454 Connection2,
455 NewConnectionState),
456 Connection2),
516 augment_infos_with_user_provided_connection_name(infos(?CREATION_EVENT_KEYS,
517 Connection2,
518 NewConnectionState),
519 Connection2),
457520 rabbit_core_metrics:connection_created(self(), Infos),
458521 rabbit_event:notify(connection_created, Infos),
459522 rabbit_networking:register_non_amqp_connection(self()),
460 {next_state, open, #statem_data{
461 transport = Transport,
462 connection = Connection2,
463 connection_state = NewConnectionState,
464 config = Configuration
465 }}.
523 {next_state, open,
524 #statem_data{transport = Transport,
525 connection = Connection2,
526 connection_state = NewConnectionState,
527 config = Configuration}}.
466528
467529 invalid_transition(Transport, Socket, From, To) ->
468 rabbit_log_connection:warning("Closing socket ~w. Invalid transition from ~s to ~s.",
530 rabbit_log_connection:warning("Closing socket ~w. Invalid transition from ~s "
531 "to ~s.",
469532 [Socket, From, To]),
470533 close_immediately(Transport, Socket),
471534 stop.
485548 {ok, Res} ->
486549 Res;
487550 {error, Reason} ->
488 rabbit_log_connection:warning("Error during socket operation ~p", [Reason]),
551 rabbit_log_connection:warning("Error during socket operation ~p",
552 [Reason]),
489553 rabbit_net:fast_close(RealSocket),
490554 exit(normal)
491555 end.
524588 atomics:get(CreditReference, 1) > CreditsRequiredForUnblocking.
525589
526590 increase_messages_consumed(Counters, Count) ->
527 rabbit_global_counters:messages_delivered(stream, ?STREAM_QUEUE_TYPE, Count),
591 rabbit_global_counters:messages_delivered(stream, ?STREAM_QUEUE_TYPE,
592 Count),
528593 atomics:add(Counters, 1, Count).
529594
530595 set_consumer_offset(Counters, Offset) ->
586651
587652 open(enter, _OldState, _StateData) ->
588653 keep_state_and_data;
589 open(info,
590 {resource_alarm, IsThereAlarm},
591 #statem_data{
592 transport = Transport,
593 connection = #stream_connection{
594 socket = S,
595 name = ConnectionName,
596 credits = Credits,
597 heartbeater = Heartbeater
598 } = Connection,
599 connection_state = #stream_connection_state{blocked = Blocked} = State,
600 config = #configuration{credits_required_for_unblocking = CreditsRequiredForUnblocking}
601 } = StatemData) ->
654 open(info, {resource_alarm, IsThereAlarm},
655 #statem_data{transport = Transport,
656 connection =
657 #stream_connection{socket = S,
658 name = ConnectionName,
659 credits = Credits,
660 heartbeater = Heartbeater} =
661 Connection,
662 connection_state =
663 #stream_connection_state{blocked = Blocked} = State,
664 config =
665 #configuration{credits_required_for_unblocking =
666 CreditsRequiredForUnblocking}} =
667 StatemData) ->
602668 rabbit_log_connection:debug("Connection ~p received resource alarm. Alarm "
603669 "on? ~p",
604670 [ConnectionName, IsThereAlarm]),
605671 EnoughCreditsToUnblock =
606 has_enough_credits_to_unblock(Credits,
607 CreditsRequiredForUnblocking),
672 has_enough_credits_to_unblock(Credits, CreditsRequiredForUnblocking),
608673 NewBlockedState =
609 case {IsThereAlarm, EnoughCreditsToUnblock} of
610 {true, _} ->
611 true;
612 {false, EnoughCredits} ->
613 not EnoughCredits
614 end,
674 case {IsThereAlarm, EnoughCreditsToUnblock} of
675 {true, _} ->
676 true;
677 {false, EnoughCredits} ->
678 not EnoughCredits
679 end,
615680 rabbit_log_connection:debug("Connection ~p had blocked status set to ~p, new "
616681 "blocked status is now ~p",
617682 [ConnectionName, Blocked, NewBlockedState]),
629694 ok
630695 end,
631696 {keep_state,
632 StatemData#statem_data{
633 connection = Connection#stream_connection{resource_alarm = IsThereAlarm},
634 connection_state = State#stream_connection_state{blocked = NewBlockedState}
635 }};
636 open(info,
637 {OK, S, Data},
638 #statem_data{
639 transport = Transport,
640 connection = #stream_connection{socket = S,
641 credits = Credits,
642 heartbeater = Heartbeater
643 } = Connection,
644 connection_state = #stream_connection_state{blocked = Blocked} = State,
645 config = Configuration
646 } = StatemData)
647 when OK =:= tcp; OK =:= ssl ->
648 {Connection1, State1} = handle_inbound_data_post_auth(Transport,
649 Connection,
650 State,
651 Data),
697 StatemData#statem_data{connection =
698 Connection#stream_connection{resource_alarm =
699 IsThereAlarm},
700 connection_state =
701 State#stream_connection_state{blocked =
702 NewBlockedState}}};
703 open(info, {OK, S, Data},
704 #statem_data{transport = Transport,
705 connection =
706 #stream_connection{socket = S,
707 credits = Credits,
708 heartbeater = Heartbeater} =
709 Connection,
710 connection_state =
711 #stream_connection_state{blocked = Blocked} = State,
712 config = Configuration} =
713 StatemData)
714 when OK =:= tcp; OK =:= ssl ->
715 {Connection1, State1} =
716 handle_inbound_data_post_auth(Transport, Connection, State, Data),
652717 #stream_connection{connection_step = Step} = Connection1,
653718 case Step of
654719 closing ->
655 close(Transport, S, State),
656 rabbit_networking:unregister_non_amqp_connection(self()),
657 notify_connection_closed(Connection1, State1),
658720 stop;
659721 close_sent ->
660722 rabbit_log_connection:debug("Transitioned to close_sent"),
661723 Transport:setopts(S, [{active, once}]),
662 {next_state, close_sent, StatemData#statem_data{
663 connection = Connection1,
664 connection_state = State1
665 }};
724 {next_state, close_sent,
725 StatemData#statem_data{connection = Connection1,
726 connection_state = State1}};
666727 _ ->
667728 State2 =
668 case Blocked of
669 true ->
670 case should_unblock(Connection, Configuration)
671 of
672 true ->
673 Transport:setopts(S, [{active, once}]),
674 ok =
675 rabbit_heartbeat:resume_monitor(Heartbeater),
676 State1#stream_connection_state{blocked =
677 false};
678 false ->
679 State1
680 end;
681 false ->
682 case has_credits(Credits) of
683 true ->
684 Transport:setopts(S, [{active, once}]),
685 State1;
686 false ->
687 ok =
688 rabbit_heartbeat:pause_monitor(Heartbeater),
689 State1#stream_connection_state{blocked =
690 true}
691 end
692 end,
693 {keep_state, StatemData#statem_data{
694 connection = Connection1,
695 connection_state = State2}}
729 case Blocked of
730 true ->
731 case should_unblock(Connection, Configuration) of
732 true ->
733 Transport:setopts(S, [{active, once}]),
734 ok =
735 rabbit_heartbeat:resume_monitor(Heartbeater),
736 State1#stream_connection_state{blocked = false};
737 false ->
738 State1
739 end;
740 false ->
741 case has_credits(Credits) of
742 true ->
743 Transport:setopts(S, [{active, once}]),
744 State1;
745 false ->
746 ok =
747 rabbit_heartbeat:pause_monitor(Heartbeater),
748 State1#stream_connection_state{blocked = true}
749 end
750 end,
751 {keep_state,
752 StatemData#statem_data{connection = Connection1,
753 connection_state = State2}}
696754 end;
697 open(info, {Closed, Socket}, #statem_data{
698 connection = Connection,
699 connection_state = State
700 })
701 when Closed =:= tcp_closed; Closed =:= ssl_closed ->
755 open(info, {Closed, Socket}, #statem_data{connection = Connection})
756 when Closed =:= tcp_closed; Closed =:= ssl_closed ->
702757 demonitor_all_streams(Connection),
703 rabbit_networking:unregister_non_amqp_connection(self()),
704 notify_connection_closed(Connection, State),
705 rabbit_log_connection:warning("Socket ~w closed [~w]", [Socket, self()]),
758 rabbit_log_connection:warning("Socket ~w closed [~w]",
759 [Socket, self()]),
706760 stop;
707 open(info, {Error, Socket, Reason}, #statem_data{
708 connection = Connection,
709 connection_state = State
710 })
711 when Error =:= tcp_error; Error =:= ssl_error ->
761 open(info, {Error, Socket, Reason},
762 #statem_data{connection = Connection})
763 when Error =:= tcp_error; Error =:= ssl_error ->
712764 demonitor_all_streams(Connection),
713 rabbit_networking:unregister_non_amqp_connection(self()),
714 notify_connection_closed(Connection, State),
715 rabbit_log_connection:error("Socket error ~p [~w] [~w]", [Reason, Socket, self()]),
765 rabbit_log_connection:error("Socket error ~p [~w] [~w]",
766 [Reason, Socket, self()]),
716767 stop;
717 open(info,
718 {'DOWN', MonitorRef, process, _OsirisPid, _Reason},
719 #statem_data{
720 transport = Transport,
721 connection = #stream_connection{socket = S,
722 monitors = Monitors
723 } = Connection,
724 connection_state = State
725 } = StatemData) ->
768 open(info, {'DOWN', MonitorRef, process, _OsirisPid, _Reason},
769 #statem_data{transport = Transport,
770 connection =
771 #stream_connection{socket = S, monitors = Monitors} =
772 Connection,
773 connection_state = State} =
774 StatemData) ->
726775 {Connection1, State1} =
727 case Monitors of
728 #{MonitorRef := Stream} ->
729 Monitors1 = maps:remove(MonitorRef, Monitors),
730 C = Connection#stream_connection{monitors = Monitors1},
731 case
732 clean_state_after_stream_deletion_or_failure(Stream,
733 C,
734 State)
735 of
736 {cleaned, NewConnection, NewState} ->
737 Command =
738 {metadata_update, Stream,
739 ?RESPONSE_CODE_STREAM_NOT_AVAILABLE},
740 Frame = rabbit_stream_core:frame(Command),
741 send(Transport, S, Frame),
742 rabbit_global_counters:increase_protocol_counter(stream, ?STREAM_NOT_AVAILABLE, 1),
743 {NewConnection, NewState};
744 {not_cleaned, SameConnection, SameState} ->
745 {SameConnection, SameState}
746 end;
747 _ ->
748 {Connection, State}
749 end,
750 {keep_state, StatemData#statem_data{
751 connection = Connection1,
752 connection_state = State1}};
753 open(info, heartbeat_send, #statem_data{
754 transport = Transport,
755 connection = #stream_connection{socket = S} = Connection,
756 connection_state = State }) ->
776 case Monitors of
777 #{MonitorRef := Stream} ->
778 Monitors1 = maps:remove(MonitorRef, Monitors),
779 C = Connection#stream_connection{monitors = Monitors1},
780 case clean_state_after_stream_deletion_or_failure(Stream, C,
781 State)
782 of
783 {cleaned, NewConnection, NewState} ->
784 Command =
785 {metadata_update, Stream,
786 ?RESPONSE_CODE_STREAM_NOT_AVAILABLE},
787 Frame = rabbit_stream_core:frame(Command),
788 send(Transport, S, Frame),
789 rabbit_global_counters:increase_protocol_counter(stream,
790 ?STREAM_NOT_AVAILABLE,
791 1),
792 {NewConnection, NewState};
793 {not_cleaned, SameConnection, SameState} ->
794 {SameConnection, SameState}
795 end;
796 _ ->
797 {Connection, State}
798 end,
799 {keep_state,
800 StatemData#statem_data{connection = Connection1,
801 connection_state = State1}};
802 open(info, heartbeat_send,
803 #statem_data{transport = Transport,
804 connection = #stream_connection{socket = S} = Connection}) ->
757805 Frame = rabbit_stream_core:frame(heartbeat),
758806 case catch send(Transport, S, Frame) of
759807 ok ->
761809 Unexpected ->
762810 rabbit_log_connection:info("Heartbeat send error ~p, closing connection",
763811 [Unexpected]),
764 C1 = demonitor_all_streams(Connection),
765 close(Transport, C1, State),
812 _C1 = demonitor_all_streams(Connection),
766813 stop
767814 end;
768 open(info, heartbeat_timeout, #statem_data{
769 transport = Transport,
770 connection = Connection,
771 connection_state = State }) ->
815 open(info, heartbeat_timeout,
816 #statem_data{connection = #stream_connection{} = Connection}) ->
772817 rabbit_log_connection:debug("Heartbeat timeout, closing connection"),
773 C1 = demonitor_all_streams(Connection),
774 close(Transport, C1, State),
818 _C1 = demonitor_all_streams(Connection),
775819 stop;
776 open(info, {infos, From}, #statem_data{
777 connection = #stream_connection{
778 client_properties = ClientProperties
779 }}) ->
820 open(info, {infos, From},
821 #statem_data{connection =
822 #stream_connection{client_properties =
823 ClientProperties}}) ->
780824 From ! {self(), ClientProperties},
781825 keep_state_and_data;
782 open(info, emit_stats, #statem_data{
783 connection = Connection,
784 connection_state = State} = StatemData) ->
826 open(info, emit_stats,
827 #statem_data{connection = Connection, connection_state = State} =
828 StatemData) ->
785829 Connection1 = emit_stats(Connection, State),
786830 {keep_state, StatemData#statem_data{connection = Connection1}};
787831 open(info, Unknown, _StatemData) ->
789833 [Unknown, ?FUNCTION_NAME]),
790834 %% FIXME send close
791835 keep_state_and_data;
792 open({call, From}, info, #statem_data{
793 connection = Connection,
794 connection_state = State}) ->
795 {keep_state_and_data, {reply, From, infos(?INFO_ITEMS, Connection, State)}};
796 open({call, From}, {info, Items}, #statem_data{
797 connection = Connection,
798 connection_state = State}) ->
836 open({call, From}, info,
837 #statem_data{connection = Connection, connection_state = State}) ->
838 {keep_state_and_data,
839 {reply, From, infos(?INFO_ITEMS, Connection, State)}};
840 open({call, From}, {info, Items},
841 #statem_data{connection = Connection, connection_state = State}) ->
799842 {keep_state_and_data, {reply, From, infos(Items, Connection, State)}};
800 open({call, From}, {consumers_info, Items}, #statem_data{connection_state = State}) ->
843 open({call, From}, {consumers_info, Items},
844 #statem_data{connection_state = State}) ->
801845 {keep_state_and_data, {reply, From, consumers_infos(Items, State)}};
802 open({call, From}, {publishers_info, Items}, #statem_data{connection = Connection}) ->
803 {keep_state_and_data, {reply, From, publishers_infos(Items, Connection)}};
804 open({call, From}, {shutdown, Explanation}, #statem_data{
805 transport = Transport,
806 connection = #stream_connection{socket = S}
807 = Connection,
808 connection_state = State}) ->
846 open({call, From}, {publishers_info, Items},
847 #statem_data{connection = Connection}) ->
848 {keep_state_and_data,
849 {reply, From, publishers_infos(Items, Connection)}};
850 open({call, From}, {shutdown, Explanation},
851 #statem_data{connection = Connection}) ->
809852 % likely closing call from the management plugin
810853 rabbit_log_connection:info("Forcing stream connection ~p closing: ~p",
811854 [self(), Explanation]),
812855 demonitor_all_streams(Connection),
813 rabbit_networking:unregister_non_amqp_connection(self()),
814 notify_connection_closed(Connection, State),
815 close(Transport, S, State),
816856 {stop_and_reply, normal, {reply, From, ok}};
817857 open(cast,
818858 {queue_event, _, {osiris_written, _, undefined, CorrelationList}},
819 #statem_data{
820 transport = Transport,
821 connection = #stream_connection{socket = S,
822 credits = Credits,
823 heartbeater = Heartbeater,
824 publishers = Publishers
825 } = Connection,
826 connection_state = #stream_connection_state{
827 blocked = Blocked
828 } = State,
829 config = Configuration
830 } = StatemData) ->
859 #statem_data{transport = Transport,
860 connection =
861 #stream_connection{socket = S,
862 credits = Credits,
863 heartbeater = Heartbeater,
864 publishers = Publishers} =
865 Connection,
866 connection_state =
867 #stream_connection_state{blocked = Blocked} = State,
868 config = Configuration} =
869 StatemData) ->
831870 ByPublisher =
832 lists:foldr(fun({PublisherId, PublishingId}, Acc) ->
833 case maps:is_key(PublisherId, Publishers) of
834 true ->
835 case maps:get(PublisherId, Acc, undefined) of
836 undefined ->
837 Acc#{PublisherId => [PublishingId]};
838 Ids ->
839 Acc#{PublisherId => [PublishingId | Ids]}
840 end;
841 false ->
842 Acc
843 end
844 end,
845 #{}, CorrelationList),
871 lists:foldr(fun({PublisherId, PublishingId}, Acc) ->
872 case maps:is_key(PublisherId, Publishers) of
873 true ->
874 case maps:get(PublisherId, Acc, undefined) of
875 undefined ->
876 Acc#{PublisherId => [PublishingId]};
877 Ids ->
878 Acc#{PublisherId => [PublishingId | Ids]}
879 end;
880 false -> Acc
881 end
882 end,
883 #{}, CorrelationList),
846884 _ = maps:map(fun(PublisherId, PublishingIds) ->
847 Command =
848 {publish_confirm, PublisherId, PublishingIds},
849 send(Transport, S,
850 rabbit_stream_core:frame(Command)),
851 #{PublisherId :=
852 #publisher{message_counters = Cnt}} =
853 Publishers,
854 increase_messages_confirmed(Cnt,
855 length(PublishingIds))
885 Command = {publish_confirm, PublisherId, PublishingIds},
886 send(Transport, S, rabbit_stream_core:frame(Command)),
887 #{PublisherId := #publisher{message_counters = Cnt}} =
888 Publishers,
889 increase_messages_confirmed(Cnt, length(PublishingIds))
856890 end,
857891 ByPublisher),
858892 CorrelationIdCount = length(CorrelationList),
859893 add_credits(Credits, CorrelationIdCount),
860 State1 = case Blocked of
861 true ->
862 case should_unblock(Connection, Configuration) of
863 true ->
864 Transport:setopts(S, [{active, once}]),
865 ok =
866 rabbit_heartbeat:resume_monitor(Heartbeater),
867 State#stream_connection_state{blocked = false};
868 false ->
869 State
870 end;
871 false ->
872 State
873 end,
894 State1 =
895 case Blocked of
896 true ->
897 case should_unblock(Connection, Configuration) of
898 true ->
899 Transport:setopts(S, [{active, once}]),
900 ok = rabbit_heartbeat:resume_monitor(Heartbeater),
901 State#stream_connection_state{blocked = false};
902 false ->
903 State
904 end;
905 false ->
906 State
907 end,
874908 {keep_state, StatemData#statem_data{connection_state = State1}};
875909 open(cast,
876910 {queue_event, _QueueResource,
877 {osiris_written, #resource{name = Stream}, PublisherReference, CorrelationList}},
878 #statem_data{
879 transport = Transport,
880 connection = #stream_connection{socket = S,
881 credits = Credits,
882 heartbeater = Heartbeater,
883 publishers = Publishers,
884 publisher_to_ids = PublisherRefToIds
885 } = Connection,
886 connection_state = #stream_connection_state{
887 blocked = Blocked
888 } = State,
889 config = Configuration
890 } = StatemData) ->
911 {osiris_written,
912 #resource{name = Stream},
913 PublisherReference,
914 CorrelationList}},
915 #statem_data{transport = Transport,
916 connection =
917 #stream_connection{socket = S,
918 credits = Credits,
919 heartbeater = Heartbeater,
920 publishers = Publishers,
921 publisher_to_ids = PublisherRefToIds} =
922 Connection,
923 connection_state =
924 #stream_connection_state{blocked = Blocked} = State,
925 config = Configuration} =
926 StatemData) ->
891927 PublishingIdCount = length(CorrelationList),
892928 case maps:get({Stream, PublisherReference}, PublisherRefToIds,
893 undefined) of
894 undefined ->
895 ok;
896 PublisherId ->
897 Command = {publish_confirm, PublisherId, CorrelationList},
898 send(Transport, S, rabbit_stream_core:frame(Command)),
899 #{PublisherId := #publisher{message_counters = Counters}} =
900 Publishers,
901 increase_messages_confirmed(Counters, PublishingIdCount)
929 undefined)
930 of
931 undefined ->
932 ok;
933 PublisherId ->
934 Command = {publish_confirm, PublisherId, CorrelationList},
935 send(Transport, S, rabbit_stream_core:frame(Command)),
936 #{PublisherId := #publisher{message_counters = Counters}} =
937 Publishers,
938 increase_messages_confirmed(Counters, PublishingIdCount)
902939 end,
903940 add_credits(Credits, PublishingIdCount),
904941 State1 =
905 case Blocked of
906 true ->
907 case should_unblock(Connection, Configuration) of
908 true ->
909 Transport:setopts(S, [{active, once}]),
910 ok =
911 rabbit_heartbeat:resume_monitor(Heartbeater),
912 State#stream_connection_state{blocked = false};
913 false ->
914 State
915 end;
916 false ->
917 State
918 end,
942 case Blocked of
943 true ->
944 case should_unblock(Connection, Configuration) of
945 true ->
946 Transport:setopts(S, [{active, once}]),
947 ok = rabbit_heartbeat:resume_monitor(Heartbeater),
948 State#stream_connection_state{blocked = false};
949 false ->
950 State
951 end;
952 false ->
953 State
954 end,
919955 {keep_state, StatemData#statem_data{connection_state = State1}};
920 open(cast, {queue_event, #resource{name = StreamName},
921 {osiris_offset, _QueueResource, -1}}, _StatemData) ->
922 rabbit_log:debug("Stream protocol connection received osiris offset event for ~p with offset ~p",
956 open(cast,
957 {queue_event, #resource{name = StreamName},
958 {osiris_offset, _QueueResource, -1}},
959 _StatemData) ->
960 rabbit_log:debug("Stream protocol connection received osiris offset "
961 "event for ~p with offset ~p",
923962 [StreamName, -1]),
924963 keep_state_and_data;
925964 open(cast,
926965 {queue_event, #resource{name = StreamName},
927966 {osiris_offset, _QueueResource, Offset}},
928 #statem_data{
929 transport = Transport,
930 connection = #stream_connection{
931 stream_subscriptions = StreamSubscriptions,
932 send_file_oct = SendFileOct
933 } = Connection,
934 connection_state = #stream_connection_state{consumers = Consumers} = State
935 } = StatemData)
936 when Offset > -1 ->
967 #statem_data{transport = Transport,
968 connection =
969 #stream_connection{stream_subscriptions =
970 StreamSubscriptions,
971 send_file_oct = SendFileOct} =
972 Connection,
973 connection_state =
974 #stream_connection_state{consumers = Consumers} = State} =
975 StatemData)
976 when Offset > -1 ->
937977 {Connection1, State1} =
938 case maps:get(StreamName, StreamSubscriptions, undefined) of
939 undefined ->
940 rabbit_log:debug("Stream protocol connection: osiris offset event for ~p, but no subscription (leftover messages after unsubscribe?)",
941 [StreamName]),
942 {Connection, State};
943 [] ->
944 rabbit_log:debug("Stream protocol connection: osiris offset event for ~p, but no registered consumers!",
945 [StreamName]),
946 {Connection#stream_connection{stream_subscriptions =
947 maps:remove(StreamName,
948 StreamSubscriptions)},
949 State};
950 CorrelationIds when is_list(CorrelationIds) ->
951 Consumers1 =
952 lists:foldl(fun(CorrelationId, ConsumersAcc) ->
953 #{CorrelationId := Consumer} =
954 ConsumersAcc,
955 #consumer{credit = Credit} =
956 Consumer,
957 Consumer1 =
958 case Credit of
959 0 -> Consumer;
960 _ ->
961 case
962 send_chunks(Transport,
963 Consumer,
964 SendFileOct)
965 of
966 {error, Reason} ->
967 rabbit_log_connection:info("Error while sending chunks: ~p",
968 [Reason]),
969 %% likely a connection problem
970 Consumer;
971 {{segment, Segment1},
972 {credit,
973 Credit1}} ->
974 Consumer#consumer{segment
975 =
976 Segment1,
977 credit
978 =
979 Credit1}
980 end
978 case maps:get(StreamName, StreamSubscriptions, undefined) of
979 undefined ->
980 rabbit_log:debug("Stream protocol connection: osiris offset event "
981 "for ~p, but no subscription (leftover messages "
982 "after unsubscribe?)",
983 [StreamName]),
984 {Connection, State};
985 [] ->
986 rabbit_log:debug("Stream protocol connection: osiris offset event "
987 "for ~p, but no registered consumers!",
988 [StreamName]),
989 {Connection#stream_connection{stream_subscriptions =
990 maps:remove(StreamName,
991 StreamSubscriptions)},
992 State};
993 CorrelationIds when is_list(CorrelationIds) ->
994 Consumers1 =
995 lists:foldl(fun(CorrelationId, ConsumersAcc) ->
996 #{CorrelationId := Consumer} = ConsumersAcc,
997 #consumer{credit = Credit} = Consumer,
998 Consumer1 =
999 case Credit of
1000 0 -> Consumer;
1001 _ ->
1002 case send_chunks(Transport,
1003 Consumer,
1004 SendFileOct)
1005 of
1006 {error, closed} ->
1007 rabbit_log_connection:info("Stream protocol connection has been closed by "
1008 "peer",
1009 []),
1010 throw({stop, normal});
1011 {error, Reason} ->
1012 rabbit_log_connection:info("Error while sending chunks: ~p",
1013 [Reason]),
1014 %% likely a connection problem
1015 Consumer;
1016 {{segment, Segment1},
1017 {credit, Credit1}} ->
1018 Consumer#consumer{segment
1019 =
1020 Segment1,
1021 credit
1022 =
1023 Credit1}
1024 end
1025 end,
1026 ConsumersAcc#{CorrelationId => Consumer1}
9811027 end,
982 ConsumersAcc#{CorrelationId =>
983 Consumer1}
984 end,
985 Consumers, CorrelationIds),
986 {Connection,
987 State#stream_connection_state{consumers = Consumers1}}
988 end,
989 {keep_state, StatemData#statem_data{
990 connection = Connection1,
991 connection_state = State1}};
992 open(cast, {force_event_refresh, Ref}, #statem_data{
993 connection = Connection,
994 connection_state = State} = StatemData) ->
1028 Consumers, CorrelationIds),
1029 {Connection,
1030 State#stream_connection_state{consumers = Consumers1}}
1031 end,
1032 {keep_state,
1033 StatemData#statem_data{connection = Connection1,
1034 connection_state = State1}};
1035 open(cast, {force_event_refresh, Ref},
1036 #statem_data{connection = Connection, connection_state = State} =
1037 StatemData) ->
9951038 Infos =
996 augment_infos_with_user_provided_connection_name(infos(?CREATION_EVENT_KEYS,
997 Connection,
998 State),
999 Connection),
1039 augment_infos_with_user_provided_connection_name(infos(?CREATION_EVENT_KEYS,
1040 Connection,
1041 State),
1042 Connection),
10001043 rabbit_event:notify(connection_created, Infos, Ref),
10011044 Connection1 =
1002 rabbit_event:init_stats_timer(Connection,
1003 #stream_connection.stats_timer),
1045 rabbit_event:init_stats_timer(Connection,
1046 #stream_connection.stats_timer),
10041047 Connection2 = ensure_stats_timer(Connection1),
10051048 {keep_state, StatemData#statem_data{connection = Connection2}}.
10061049
1007 close_sent(enter, _OldState, #statem_data{
1008 config = #configuration{
1009 connection_negotiation_step_timeout = StateTimeout}}) ->
1050 close_sent(enter, _OldState,
1051 #statem_data{config =
1052 #configuration{connection_negotiation_step_timeout =
1053 StateTimeout}}) ->
10101054 {keep_state_and_data, {state_timeout, StateTimeout, close}};
1011 close_sent(state_timeout, close, #statem_data{
1012 transport = Transport,
1013 connection = #stream_connection{socket = Socket} = Connection,
1014 connection_state = State
1015 }) ->
1016 rabbit_log_connection:warning(
1017 "Closing connection because of timeout in state '~s' likely due to lack of client action.",
1018 [?FUNCTION_NAME]),
1019 close(Transport, Socket, State),
1020 rabbit_networking:unregister_non_amqp_connection(self()),
1021 notify_connection_closed(Connection, State),
1055 close_sent(state_timeout, close, #statem_data{}) ->
1056 rabbit_log_connection:warning("Closing connection because of timeout in state "
1057 "'~s' likely due to lack of client action.",
1058 [?FUNCTION_NAME]),
10221059 stop;
1023 close_sent(info, {tcp, S, Data}, #statem_data{
1024 transport = Transport,
1025 connection = Connection,
1026 connection_state = State
1027 } = StatemData) when byte_size(Data) > 1 ->
1028 {Connection1, State1} = handle_inbound_data_post_close(Transport,
1029 Connection,
1030 State,
1031 Data),
1060 close_sent(info, {tcp, S, Data},
1061 #statem_data{transport = Transport,
1062 connection = Connection,
1063 connection_state = State} =
1064 StatemData)
1065 when byte_size(Data) > 1 ->
1066 {Connection1, State1} =
1067 handle_inbound_data_post_close(Transport, Connection, State, Data),
10321068 #stream_connection{connection_step = Step} = Connection1,
1033 rabbit_log_connection:debug("Stream reader has transitioned from ~s to ~s", [?FUNCTION_NAME, Step]),
1069 rabbit_log_connection:debug("Stream reader has transitioned from ~s to ~s",
1070 [?FUNCTION_NAME, Step]),
10341071 case Step of
10351072 closing_done ->
1036 close(Transport, S, State1),
1037 rabbit_networking:unregister_non_amqp_connection(self()),
1038 notify_connection_closed(Connection1, State1),
10391073 stop;
10401074 _ ->
10411075 Transport:setopts(S, [{active, once}]),
1042 {keep_state, StatemData#statem_data{
1043 connection = Connection1,
1044 connection_state = State1
1045 }}
1076 {keep_state,
1077 StatemData#statem_data{connection = Connection1,
1078 connection_state = State1}}
10461079 end;
1047 close_sent(info, {tcp_closed, S}, #statem_data{
1048 connection = Connection,
1049 connection_state = State
1050 }) ->
1051 rabbit_networking:unregister_non_amqp_connection(self()),
1052 notify_connection_closed(Connection, State),
1053 rabbit_log_connection:debug("Stream protocol connection socket ~w closed [~w]", [S, self()]),
1080 close_sent(info, {tcp_closed, S}, _StatemData) ->
1081 rabbit_log_connection:debug("Stream protocol connection socket ~w closed [~w]",
1082 [S, self()]),
10541083 stop;
1055 close_sent(info, {tcp_error, S, Reason}, #statem_data{
1056 transport = Transport,
1057 connection = Connection,
1058 connection_state = State
1059 }) ->
1060 rabbit_log_connection:error("Stream protocol connection socket error: ~p [~w] [~w]", [Reason, S, self()]),
1061 close(Transport, S, State),
1062 rabbit_networking:unregister_non_amqp_connection(self()),
1063 notify_connection_closed(Connection, State),
1084 close_sent(info, {tcp_error, S, Reason}, #statem_data{}) ->
1085 rabbit_log_connection:error("Stream protocol connection socket error: ~p [~w] "
1086 "[~w]",
1087 [Reason, S, self()]),
10641088 stop;
1065 close_sent(info,{resource_alarm, IsThereAlarm},
1089 close_sent(info, {resource_alarm, IsThereAlarm},
10661090 StatemData = #statem_data{connection = Connection}) ->
1067 rabbit_log:warning("Stream protocol connection ignored a resource alarm ~p in state ~s", [IsThereAlarm, ?FUNCTION_NAME]),
1068 {keep_state, StatemData#statem_data{
1069 connection = Connection#stream_connection{resource_alarm = IsThereAlarm}
1070 }};
1091 rabbit_log:warning("Stream protocol connection ignored a resource "
1092 "alarm ~p in state ~s",
1093 [IsThereAlarm, ?FUNCTION_NAME]),
1094 {keep_state,
1095 StatemData#statem_data{connection =
1096 Connection#stream_connection{resource_alarm =
1097 IsThereAlarm}}};
10711098 close_sent(info, Msg, _StatemData) ->
1072 rabbit_log_connection:warning("Ignored unknown message ~p in state ~s", [Msg, ?FUNCTION_NAME]),
1099 rabbit_log_connection:warning("Ignored unknown message ~p in state ~s",
1100 [Msg, ?FUNCTION_NAME]),
10731101 keep_state_and_data.
10741102
10751103 handle_inbound_data_pre_auth(Transport, Connection, State, Data) ->
11621190 ServerProperties}}),
11631191 send(Transport, S, Frame),
11641192 {Connection#stream_connection{client_properties = ClientProperties,
1165 authentication_state = peer_properties_exchanged,
1166 connection_step = peer_properties_exchanged
1167 }, State};
1193 authentication_state =
1194 peer_properties_exchanged,
1195 connection_step = peer_properties_exchanged},
1196 State};
11681197 handle_frame_pre_auth(Transport,
11691198 #stream_connection{socket = S} = Connection,
11701199 State,
11751204 {sasl_handshake, ?RESPONSE_CODE_OK,
11761205 Mechanisms}}),
11771206 send(Transport, S, Frame),
1178 {Connection#stream_connection{connection_step = authenticating}, State};
1207 {Connection#stream_connection{connection_step = authenticating},
1208 State};
11791209 handle_frame_pre_auth(Transport,
11801210 #stream_connection{socket = S,
11811211 authentication_state = AuthState0,
12591289 Username,
12601290 stream),
12611291 rabbit_log_connection:warning("User '~s' can only connect via localhost",
1262 [Username]),
1292 [Username]),
12631293 {C1#stream_connection{connection_step =
12641294 failure},
12651295 {sasl_authenticate,
12961326 Connection,
12971327 #stream_connection_state{blocked = Blocked} = State,
12981328 {tune, FrameMax, Heartbeat}) ->
1299 rabbit_log_connection:debug("Tuning response ~p ~p ", [FrameMax, Heartbeat]),
1329 rabbit_log_connection:debug("Tuning response ~p ~p ",
1330 [FrameMax, Heartbeat]),
13001331 Parent = self(),
13011332 %% sending a message to the main process so the heartbeat frame is sent from this main process
13021333 %% otherwise heartbeat frames can interleave with chunk delivery
13831414 {Connection, State};
13841415 handle_frame_pre_auth(_Transport, Connection, State, Command) ->
13851416 rabbit_log_connection:warning("unknown command ~w, closing connection.",
1386 [Command]),
1417 [Command]),
13871418 {Connection#stream_connection{connection_step = failure}, State}.
13881419
13891420 auth_fail(Username, Msg, Args, Connection, ConnectionState) ->
14271458 _WriterRef,
14281459 Stream}}) ->
14291460 rabbit_log_connection:info("Cannot create publisher ~p on stream ~p, connection "
1430 "is blocked because of resource alarm",
1431 [PublisherId, Stream]),
1461 "is blocked because of resource alarm",
1462 [PublisherId, Stream]),
14321463 response(Transport,
14331464 Connection0,
14341465 declare_publisher,
14351466 CorrelationId,
14361467 ?RESPONSE_CODE_PRECONDITION_FAILED),
1437 rabbit_global_counters:increase_protocol_counter(stream, ?PRECONDITION_FAILED, 1),
1468 rabbit_global_counters:increase_protocol_counter(stream,
1469 ?PRECONDITION_FAILED, 1),
14381470 {Connection0, State};
14391471 handle_frame_post_auth(Transport,
14401472 #stream_connection{user = User,
14611493 declare_publisher,
14621494 CorrelationId,
14631495 ?RESPONSE_CODE_STREAM_DOES_NOT_EXIST),
1464 rabbit_global_counters:increase_protocol_counter(stream, ?STREAM_DOES_NOT_EXIST, 1),
1496 rabbit_global_counters:increase_protocol_counter(stream,
1497 ?STREAM_DOES_NOT_EXIST,
1498 1),
14651499 {Connection0, State};
14661500 {ClusterLeader,
14671501 #stream_connection{publishers = Publishers0,
15081542 declare_publisher,
15091543 CorrelationId,
15101544 ?RESPONSE_CODE_PRECONDITION_FAILED),
1511 rabbit_global_counters:increase_protocol_counter(stream, ?PRECONDITION_FAILED, 1),
1545 rabbit_global_counters:increase_protocol_counter(stream,
1546 ?PRECONDITION_FAILED,
1547 1),
15121548 {Connection0, State}
15131549 end;
15141550 error ->
15171553 declare_publisher,
15181554 CorrelationId,
15191555 ?RESPONSE_CODE_ACCESS_REFUSED),
1520 rabbit_global_counters:increase_protocol_counter(stream, ?ACCESS_REFUSED, 1),
1556 rabbit_global_counters:increase_protocol_counter(stream,
1557 ?ACCESS_REFUSED,
1558 1),
15211559 {Connection0, State}
15221560 end;
15231561 handle_frame_post_auth(Transport,
15621600 PublishingIds},
15631601 Frame = rabbit_stream_core:frame(Command),
15641602 send(Transport, S, Frame),
1565 rabbit_global_counters:increase_protocol_counter(stream, ?ACCESS_REFUSED, 1),
1603 rabbit_global_counters:increase_protocol_counter(stream,
1604 ?ACCESS_REFUSED,
1605 1),
15661606 increase_messages_errored(Counters, MessageCount),
15671607 {Connection, State}
15681608 end;
15751615 PublishingIds},
15761616 Frame = rabbit_stream_core:frame(Command),
15771617 send(Transport, S, Frame),
1578 rabbit_global_counters:increase_protocol_counter(stream, ?PUBLISHER_DOES_NOT_EXIST, 1),
1618 rabbit_global_counters:increase_protocol_counter(stream,
1619 ?PUBLISHER_DOES_NOT_EXIST,
1620 1),
15791621 {Connection, State}
15801622 end;
15811623 handle_frame_post_auth(Transport,
15991641 Stream)
16001642 of
16011643 {error, not_found} ->
1602 rabbit_global_counters:increase_protocol_counter(stream, ?STREAM_DOES_NOT_EXIST, 1),
1644 rabbit_global_counters:increase_protocol_counter(stream,
1645 ?STREAM_DOES_NOT_EXIST,
1646 1),
16031647 {?RESPONSE_CODE_STREAM_DOES_NOT_EXIST, 0};
16041648 {ok, LocalMemberPid} ->
16051649 {?RESPONSE_CODE_OK,
16121656 end}
16131657 end;
16141658 error ->
1615 rabbit_global_counters:increase_protocol_counter(stream, ?ACCESS_REFUSED, 1),
1659 rabbit_global_counters:increase_protocol_counter(stream,
1660 ?ACCESS_REFUSED,
1661 1),
16161662 {?RESPONSE_CODE_ACCESS_REFUSED, 0}
16171663 end,
16181664 Frame =
16551701 delete_publisher,
16561702 CorrelationId,
16571703 ?RESPONSE_CODE_PUBLISHER_DOES_NOT_EXIST),
1658 rabbit_global_counters:increase_protocol_counter(stream, ?PUBLISHER_DOES_NOT_EXIST, 1),
1704 rabbit_global_counters:increase_protocol_counter(stream,
1705 ?PUBLISHER_DOES_NOT_EXIST,
1706 1),
16591707 {Connection0, State}
16601708 end;
16611709 handle_frame_post_auth(Transport,
16921740 subscribe,
16931741 CorrelationId,
16941742 ?RESPONSE_CODE_STREAM_NOT_AVAILABLE),
1695 rabbit_global_counters:increase_protocol_counter(stream, ?STREAM_NOT_AVAILABLE, 1),
1743 rabbit_global_counters:increase_protocol_counter(stream,
1744 ?STREAM_NOT_AVAILABLE,
1745 1),
16961746 {Connection, State};
16971747 {error, not_found} ->
16981748 response(Transport,
17001750 subscribe,
17011751 CorrelationId,
17021752 ?RESPONSE_CODE_STREAM_DOES_NOT_EXIST),
1703 rabbit_global_counters:increase_protocol_counter(stream, ?STREAM_DOES_NOT_EXIST, 1),
1753 rabbit_global_counters:increase_protocol_counter(stream,
1754 ?STREAM_DOES_NOT_EXIST,
1755 1),
17041756 {Connection, State};
17051757 {ok, LocalMemberPid} ->
17061758 case subscription_exists(StreamSubscriptions,
17121764 subscribe,
17131765 CorrelationId,
17141766 ?RESPONSE_CODE_SUBSCRIPTION_ID_ALREADY_EXISTS),
1715 rabbit_global_counters:increase_protocol_counter(stream, ?SUBSCRIPTION_ID_ALREADY_EXISTS, 1),
1767 rabbit_global_counters:increase_protocol_counter(stream,
1768 ?SUBSCRIPTION_ID_ALREADY_EXISTS,
1769 1),
17161770 {Connection, State};
17171771 false ->
17181772 rabbit_log:debug("Creating subscription ~p to ~p, with offset specificat"
17191773 "ion ~p, properties ~0p",
1720 [SubscriptionId,
1721 Stream,
1722 OffsetSpec,
1723 Properties]),
1774 [SubscriptionId,
1775 Stream,
1776 OffsetSpec,
1777 Properties]),
17241778 CounterSpec =
17251779 {{?MODULE,
17261780 QueueResource,
17371791 CounterSpec,
17381792 Options),
17391793 rabbit_log:debug("Next offset for subscription ~p is ~p",
1740 [SubscriptionId, osiris_log:next_offset(Segment)]),
1794 [SubscriptionId,
1795 osiris_log:next_offset(Segment)]),
17411796 ConsumerCounters =
17421797 atomics:new(2, [{signed, false}]),
17431798 ConsumerState =
17621817
17631818 rabbit_log:debug("Distributing existing messages to subscription ~p",
17641819 [SubscriptionId]),
1765 {{segment, Segment1}, {credit, Credit1}} =
1766 send_chunks(Transport, ConsumerState,
1767 SendFileOct),
1768 ConsumerState1 =
1769 ConsumerState#consumer{segment = Segment1,
1770 credit = Credit1},
1771 Consumers1 =
1772 Consumers#{SubscriptionId => ConsumerState1},
1773
1774 StreamSubscriptions1 =
1775 case StreamSubscriptions of
1776 #{Stream := SubscriptionIds} ->
1777 StreamSubscriptions#{Stream =>
1778 [SubscriptionId]
1779 ++ SubscriptionIds};
1780 _ ->
1781 StreamSubscriptions#{Stream =>
1782 [SubscriptionId]}
1783 end,
1784
1785 #consumer{counters = ConsumerCounters1} =
1786 ConsumerState1,
1787
1788 ConsumerOffset = osiris_log:next_offset(Segment1),
1789 ConsumerOffsetLag =
1790 consumer_i(offset_lag, ConsumerState1),
1791
1792 rabbit_log:debug("Subscription ~p is now at offset ~p with ~p message(s) "
1793 "distributed after subscription",
1794 [SubscriptionId, ConsumerOffset,
1795 messages_consumed(ConsumerCounters1)]),
1796
1797 rabbit_stream_metrics:consumer_created(self(),
1798 stream_r(Stream,
1799 Connection1),
1800 SubscriptionId,
1801 Credit1,
1802 messages_consumed(ConsumerCounters1),
1803 ConsumerOffset,
1804 ConsumerOffsetLag,
1805 Properties),
1806 {Connection1#stream_connection{stream_subscriptions
1807 =
1808 StreamSubscriptions1},
1809 State#stream_connection_state{consumers =
1810 Consumers1}}
1820
1821 case send_chunks(Transport, ConsumerState,
1822 SendFileOct)
1823 of
1824 {error, closed} ->
1825 rabbit_log_connection:info("Stream protocol connection has been closed by "
1826 "peer",
1827 []),
1828 throw({stop, normal});
1829 {{segment, Segment1}, {credit, Credit1}} ->
1830 ConsumerState1 =
1831 ConsumerState#consumer{segment =
1832 Segment1,
1833 credit =
1834 Credit1},
1835 Consumers1 =
1836 Consumers#{SubscriptionId =>
1837 ConsumerState1},
1838
1839 StreamSubscriptions1 =
1840 case StreamSubscriptions of
1841 #{Stream := SubscriptionIds} ->
1842 StreamSubscriptions#{Stream =>
1843 [SubscriptionId]
1844 ++ SubscriptionIds};
1845 _ ->
1846 StreamSubscriptions#{Stream =>
1847 [SubscriptionId]}
1848 end,
1849
1850 #consumer{counters = ConsumerCounters1} =
1851 ConsumerState1,
1852
1853 ConsumerOffset =
1854 osiris_log:next_offset(Segment1),
1855 ConsumerOffsetLag =
1856 consumer_i(offset_lag, ConsumerState1),
1857
1858 rabbit_log:debug("Subscription ~p is now at offset ~p with ~p message(s) "
1859 "distributed after subscription",
1860 [SubscriptionId,
1861 ConsumerOffset,
1862 messages_consumed(ConsumerCounters1)]),
1863
1864 rabbit_stream_metrics:consumer_created(self(),
1865 stream_r(Stream,
1866 Connection1),
1867 SubscriptionId,
1868 Credit1,
1869 messages_consumed(ConsumerCounters1),
1870 ConsumerOffset,
1871 ConsumerOffsetLag,
1872 Properties),
1873 {Connection1#stream_connection{stream_subscriptions
1874 =
1875 StreamSubscriptions1},
1876 State#stream_connection_state{consumers =
1877 Consumers1}}
1878 end
18111879 end
18121880 end;
18131881 error ->
18161884 subscribe,
18171885 CorrelationId,
18181886 ?RESPONSE_CODE_ACCESS_REFUSED),
1819 rabbit_global_counters:increase_protocol_counter(stream, ?ACCESS_REFUSED, 1),
1887 rabbit_global_counters:increase_protocol_counter(stream,
1888 ?ACCESS_REFUSED,
1889 1),
18201890 {Connection, State}
18211891 end;
18221892 handle_frame_post_auth(Transport,
18341904 SendFileOct)
18351905 of
18361906 {error, closed} ->
1837 rabbit_log:warning("Stream protocol connection for subscription ~p has been closed, removing "
1838 "subscription",
1839 [SubscriptionId]),
1840 {Connection1, State1} =
1841 remove_subscription(SubscriptionId, Connection, State),
1842
1843 Code = ?RESPONSE_CODE_SUBSCRIPTION_ID_DOES_NOT_EXIST,
1844 Frame =
1845 rabbit_stream_core:frame({response, 1,
1846 {credit, Code,
1847 SubscriptionId}}),
1848 send(Transport, S, Frame),
1849 rabbit_global_counters:increase_protocol_counter(stream, ?SUBSCRIPTION_ID_DOES_NOT_EXIST, 1),
1850 {Connection1, State1};
1907 rabbit_log_connection:info("Stream protocol connection has been closed by "
1908 "peer",
1909 []),
1910 throw({stop, normal});
18511911 {{segment, Segment1}, {credit, Credit1}} ->
18521912 Consumer1 =
18531913 Consumer#consumer{segment = Segment1, credit = Credit1},
18661926 rabbit_stream_core:frame({response, 1,
18671927 {credit, Code, SubscriptionId}}),
18681928 send(Transport, S, Frame),
1869 rabbit_global_counters:increase_protocol_counter(stream, ?SUBSCRIPTION_ID_DOES_NOT_EXIST, 1),
1929 rabbit_global_counters:increase_protocol_counter(stream,
1930 ?SUBSCRIPTION_ID_DOES_NOT_EXIST,
1931 1),
18701932 {Connection, State}
18711933 end;
18721934 handle_frame_post_auth(_Transport,
18951957 end;
18961958 error ->
18971959 %% FIXME store offset is fire-and-forget, so no response even if error, change this?
1898 rabbit_log:warning("Not authorized to store offset on stream ~p", [Stream]),
1960 rabbit_log:warning("Not authorized to store offset on stream ~p",
1961 [Stream]),
18991962 {Connection, State}
19001963 end;
19011964 handle_frame_post_auth(Transport,
19161979 ok ->
19171980 case lookup_leader(Stream, Connection0) of
19181981 cluster_not_found ->
1919 rabbit_global_counters:increase_protocol_counter(stream, ?STREAM_DOES_NOT_EXIST, 1),
1982 rabbit_global_counters:increase_protocol_counter(stream,
1983 ?STREAM_DOES_NOT_EXIST,
1984 1),
19201985 {?RESPONSE_CODE_STREAM_DOES_NOT_EXIST, 0, Connection0};
19211986 {LeaderPid, C} ->
19221987 {?RESPONSE_CODE_OK,
19291994 C}
19301995 end;
19311996 error ->
1932 rabbit_global_counters:increase_protocol_counter(stream, ?ACCESS_REFUSED, 1),
1997 rabbit_global_counters:increase_protocol_counter(stream,
1998 ?ACCESS_REFUSED,
1999 1),
19332000 {?RESPONSE_CODE_ACCESS_REFUSED, 0, Connection0}
19342001 end,
19352002 Frame =
19512018 unsubscribe,
19522019 CorrelationId,
19532020 ?RESPONSE_CODE_SUBSCRIPTION_ID_DOES_NOT_EXIST),
1954 rabbit_global_counters:increase_protocol_counter(stream, ?SUBSCRIPTION_ID_DOES_NOT_EXIST, 1),
2021 rabbit_global_counters:increase_protocol_counter(stream,
2022 ?SUBSCRIPTION_ID_DOES_NOT_EXIST,
2023 1),
19552024 {Connection, State};
19562025 true ->
19572026 {Connection1, State1} =
19882057 {ok,
19892058 #{leader_node := LeaderPid,
19902059 replica_nodes := ReturnedReplicas}} ->
1991 rabbit_log:debug("Created stream cluster with leader on ~p and replicas on ~p",
2060 rabbit_log:debug("Created stream cluster with leader on ~p and "
2061 "replicas on ~p",
19922062 [LeaderPid, ReturnedReplicas]),
19932063 response_ok(Transport,
19942064 Connection,
20012071 create_stream,
20022072 CorrelationId,
20032073 ?RESPONSE_CODE_PRECONDITION_FAILED),
2004 rabbit_global_counters:increase_protocol_counter(stream, ?PRECONDITION_FAILED, 1),
2074 rabbit_global_counters:increase_protocol_counter(stream,
2075 ?PRECONDITION_FAILED,
2076 1),
20052077 {Connection, State};
20062078 {error, reference_already_exists} ->
20072079 response(Transport,
20092081 create_stream,
20102082 CorrelationId,
20112083 ?RESPONSE_CODE_STREAM_ALREADY_EXISTS),
2012 rabbit_global_counters:increase_protocol_counter(stream, ?STREAM_ALREADY_EXISTS, 1),
2084 rabbit_global_counters:increase_protocol_counter(stream,
2085 ?STREAM_ALREADY_EXISTS,
2086 1),
20132087 {Connection, State};
20142088 {error, _} ->
20152089 response(Transport,
20172091 create_stream,
20182092 CorrelationId,
20192093 ?RESPONSE_CODE_INTERNAL_ERROR),
2020 rabbit_global_counters:increase_protocol_counter(stream, ?INTERNAL_ERROR, 1),
2094 rabbit_global_counters:increase_protocol_counter(stream,
2095 ?INTERNAL_ERROR,
2096 1),
20212097 {Connection, State}
20222098 end;
20232099 error ->
20262102 create_stream,
20272103 CorrelationId,
20282104 ?RESPONSE_CODE_ACCESS_REFUSED),
2029 rabbit_global_counters:increase_protocol_counter(stream, ?ACCESS_REFUSED, 1),
2105 rabbit_global_counters:increase_protocol_counter(stream,
2106 ?ACCESS_REFUSED,
2107 1),
20302108 {Connection, State}
20312109 end;
20322110 _ ->
20352113 create_stream,
20362114 CorrelationId,
20372115 ?RESPONSE_CODE_PRECONDITION_FAILED),
2038 rabbit_global_counters:increase_protocol_counter(stream, ?PRECONDITION_FAILED, 1),
2116 rabbit_global_counters:increase_protocol_counter(stream,
2117 ?PRECONDITION_FAILED,
2118 1),
20392119 {Connection, State}
20402120 end;
20412121 handle_frame_post_auth(Transport,
20732153 ?RESPONSE_CODE_STREAM_NOT_AVAILABLE},
20742154 Frame = rabbit_stream_core:frame(Command),
20752155 send(Transport, S, Frame),
2076 rabbit_global_counters:increase_protocol_counter(stream, ?STREAM_NOT_AVAILABLE, 1),
2156 rabbit_global_counters:increase_protocol_counter(stream,
2157 ?STREAM_NOT_AVAILABLE,
2158 1),
20772159 {NewConnection, NewState};
20782160 {not_cleaned, SameConnection, SameState} ->
20792161 {SameConnection, SameState}
20852167 delete_stream,
20862168 CorrelationId,
20872169 ?RESPONSE_CODE_STREAM_DOES_NOT_EXIST),
2088 rabbit_global_counters:increase_protocol_counter(stream, ?STREAM_DOES_NOT_EXIST, 1),
2170 rabbit_global_counters:increase_protocol_counter(stream,
2171 ?STREAM_DOES_NOT_EXIST,
2172 1),
20892173 {Connection, State}
20902174 end;
20912175 error ->
20942178 delete_stream,
20952179 CorrelationId,
20962180 ?RESPONSE_CODE_ACCESS_REFUSED),
2097 rabbit_global_counters:increase_protocol_counter(stream, ?ACCESS_REFUSED, 1),
2181 rabbit_global_counters:increase_protocol_counter(stream,
2182 ?ACCESS_REFUSED,
2183 1),
20982184 {Connection, State}
20992185 end;
21002186 handle_frame_post_auth(Transport,
22042290 case rabbit_stream_manager:route(RoutingKey, VirtualHost, SuperStream)
22052291 of
22062292 {ok, no_route} ->
2207 {?RESPONSE_CODE_OK, <<(-1):16>>};
2208 {ok, Stream} ->
2209 StreamSize = byte_size(Stream),
2210 {?RESPONSE_CODE_OK,
2211 <<StreamSize:16, Stream:StreamSize/binary>>};
2293 {?RESPONSE_CODE_OK, <<0:32>>};
2294 {ok, Streams} ->
2295 StreamCount = length(Streams),
2296 Bin = lists:foldl(fun(Stream, Acc) ->
2297 StreamSize = byte_size(Stream),
2298 <<Acc/binary, StreamSize:16,
2299 Stream:StreamSize/binary>>
2300 end,
2301 <<StreamCount:32>>, Streams),
2302 {?RESPONSE_CODE_OK, Bin};
22122303 {error, _} ->
2213 rabbit_global_counters:increase_protocol_counter(stream, ?STREAM_DOES_NOT_EXIST, 1),
2214 {?RESPONSE_CODE_STREAM_DOES_NOT_EXIST, <<(-1):16>>}
2304 rabbit_global_counters:increase_protocol_counter(stream,
2305 ?STREAM_DOES_NOT_EXIST,
2306 1),
2307 {?RESPONSE_CODE_STREAM_DOES_NOT_EXIST, <<0:32>>}
22152308 end,
22162309
22172310 Frame =
22432336 <<StreamCount:32>>, Streams),
22442337 {?RESPONSE_CODE_OK, Bin};
22452338 {error, _} ->
2246 rabbit_global_counters:increase_protocol_counter(stream, ?STREAM_DOES_NOT_EXIST, 1),
2339 rabbit_global_counters:increase_protocol_counter(stream,
2340 ?STREAM_DOES_NOT_EXIST,
2341 1),
22472342 {?RESPONSE_CODE_STREAM_DOES_NOT_EXIST, <<0:32>>}
22482343 end,
22492344
22612356 State,
22622357 {request, CorrelationId,
22632358 {close, ClosingCode, ClosingReason}}) ->
2264 rabbit_log:debug("Stream protocol reader received close command ~p ~p",
2359 rabbit_log:debug("Stream protocol reader received close command "
2360 "~p ~p",
22652361 [ClosingCode, ClosingReason]),
22662362 Frame =
22672363 rabbit_stream_core:frame({response, CorrelationId,
22842380 {close, ?RESPONSE_CODE_UNKNOWN_FRAME,
22852381 CloseReason}}),
22862382 send(Transport, S, Frame),
2287 rabbit_global_counters:increase_protocol_counter(stream, ?UNKNOWN_FRAME, 1),
2383 rabbit_global_counters:increase_protocol_counter(stream,
2384 ?UNKNOWN_FRAME, 1),
22882385 {Connection#stream_connection{connection_step = close_sent}, State}.
22892386
2290 notify_connection_closed(#stream_connection{name = Name,
2291 publishers = Publishers} =
2292 Connection,
2293 #stream_connection_state{consumers = Consumers} =
2294 ConnectionState) ->
2387 notify_connection_closed(#statem_data{connection =
2388 #stream_connection{name = Name,
2389 publishers =
2390 Publishers} =
2391 Connection,
2392 connection_state =
2393 #stream_connection_state{consumers =
2394 Consumers} =
2395 ConnectionState}) ->
22952396 rabbit_core_metrics:connection_closed(self()),
22962397 [rabbit_stream_metrics:consumer_cancelled(self(),
22972398 stream_r(S, Connection), SubId)
23212422 rabbit_log_connection:debug("Received heartbeat command post close"),
23222423 {Connection, State};
23232424 handle_frame_post_close(_Transport, Connection, State, Command) ->
2324 rabbit_log_connection:warning("ignored command on close ~p .", [Command]),
2425 rabbit_log_connection:warning("ignored command on close ~p .",
2426 [Command]),
23252427 {Connection, State}.
23262428
23272429 stream_r(Stream, #stream_connection{virtual_host = VHost}) ->
27982900 Host;
27992901 i(peer_host, #stream_connection{peer_host = PeerHost}, _) ->
28002902 PeerHost;
2801 i(ssl, #stream_connection{socket = Socket}, _) ->
2802 rabbit_net:is_ssl(Socket);
2903 i(ssl, #stream_connection{socket = Socket, proxy_socket = ProxySock},
2904 _) ->
2905 rabbit_net:proxy_ssl_info(Socket, ProxySock) /= nossl;
28032906 i(peer_cert_subject, S, _) ->
28042907 cert_info(fun rabbit_ssl:peer_cert_subject/1, S);
28052908 i(peer_cert_issuer, S, _) ->
28652968 list_to_binary(F(Cert))
28662969 end.
28672970
2868 ssl_info(F, #stream_connection{socket = Sock}) ->
2869 case rabbit_net:ssl_info(Sock) of
2971 ssl_info(F,
2972 #stream_connection{socket = Sock, proxy_socket = ProxySock}) ->
2973 case rabbit_net:proxy_ssl_info(Sock, ProxySock) of
28702974 nossl ->
28712975 '';
28722976 {error, _} ->
5151 end}
5252 end,
5353
54 Nodes = rabbit_mnesia:cluster_nodes(all),
54 Nodes = rabbit_nodes:all(),
5555 OsirisConf = #{nodes => Nodes},
5656
5757 ServerConfiguration =
2424 check_configure_permitted/3,
2525 check_write_permitted/3,
2626 check_read_permitted/3,
27 extract_stream_list/2]).
27 extract_stream_list/2,
28 sort_partitions/1]).
2829
2930 -define(MAX_PERMISSION_CACHE_SIZE, 12).
31
32 -include_lib("rabbit_common/include/rabbit.hrl").
3033
3134 enforce_correct_stream_name(Name) ->
3235 % from rabbit_channel
8083 osiris:write(ClusterLeader,
8184 undefined,
8285 {PublisherId, PublishingId},
83 {batch, MessageCount, CompressionType, UncompressedSize, Batch}),
86 {batch,
87 MessageCount,
88 CompressionType,
89 UncompressedSize,
90 Batch}),
8491 write_messages(ClusterLeader, undefined, PublisherId, Rest);
8592 write_messages(_ClusterLeader, _PublisherRef, _PublisherId, <<>>) ->
8693 ok;
112119 osiris:write(ClusterLeader,
113120 PublisherRef,
114121 PublishingId,
115 {batch, MessageCount, CompressionType, UncompressedSize, Batch}),
122 {batch,
123 MessageCount,
124 CompressionType,
125 UncompressedSize,
126 Batch}),
116127 write_messages(ClusterLeader, PublisherRef, PublisherId, Rest).
117128
118129 parse_map(<<>>, _Count) ->
204215 extract_stream_list(<<Length:16, Stream:Length/binary, Rest/binary>>,
205216 Streams) ->
206217 extract_stream_list(Rest, [Stream | Streams]).
218
219 -spec sort_partitions([#binding{}]) -> [#binding{}].
220 sort_partitions(Partitions) ->
221 lists:sort(fun(#binding{args = Args1}, #binding{args = Args2}) ->
222 Arg1 =
223 rabbit_misc:table_lookup(Args1,
224 <<"x-stream-partition-order">>),
225 Arg2 =
226 rabbit_misc:table_lookup(Args2,
227 <<"x-stream-partition-order">>),
228 case {Arg1, Arg2} of
229 {{_, Order1}, {_, Order2}} ->
230 rabbit_data_coercion:to_integer(Order1)
231 =< rabbit_data_coercion:to_integer(Order2);
232 {undefined, {_, _Order2}} -> false;
233 {{_, _Order1}, undefined} -> true;
234 _ -> true
235 end
236 end,
237 Partitions).
1010 'publishers': '/stream/connections/' + vhost + '/' + name + '/publishers'},
1111 'streamConnection', '#/stream/connections');
1212 });
13
13 // not exactly dispatcher stuff, but we have to make sure this is called before
14 // HTTP requests are made in case of refresh of the queue page
15 QUEUE_EXTRA_CONTENT_REQUESTS.push(function(vhost, queue) {
16 return {'extra_stream_publishers' : '/stream/publishers/' + esc(vhost) + '/' + esc(queue)};
17 });
18 QUEUE_EXTRA_CONTENT.push(function(queue, extraContent) {
19 if (is_stream(queue)) {
20 var publishers = extraContent['extra_stream_publishers'];
21 if (publishers !== undefined) {
22 return '<div class="section-hidden"><h2 class="updatable">Stream publishers (' + Object.keys(publishers).length +')</h2><div class="hider updatable">' +
23 format('streamPublishersList', {'publishers': publishers, 'mode': 'queue'}) +
24 '</div></div>';
25 } else {
26 return '';
27 }
28 } else {
29 return '';
30 }
31 });
1432 });
1533
1634 NAVIGATION['Stream'] = ['#/stream/connections', "monitoring"];
6078 }
6179 });
6280
63 CONSUMER_OWNER_FORMATTERS.sort(CONSUMER_OWNER_FORMATTERS_COMPARATOR);
81 CONSUMER_OWNER_FORMATTERS.sort(CONSUMER_OWNER_FORMATTERS_COMPARATOR);
82
115115 <% } %>
116116
117117 <div class="section">
118 <h2>Publishers</h2>
118 <h2 class="updatable">Publishers (<%=(publishers.length)%>) </h2>
119119 <div class="hider updatable">
120 <%= format('streamPublishersList', {'publishers': publishers}) %>
120 <%= format('streamPublishersList', {'publishers': publishers, 'mode' : 'connection'}) %>
121121 </div>
122122 </div>
123123
124124 <div class="section">
125 <h2>Consumers</h2>
125 <h2 class="updatable" >Consumers (<%=(consumers.length)%>)</h2>
126126 <div class="hider updatable">
127127 <%= format('streamConsumersList', {'consumers': consumers}) %>
128128 </div>
11 <table class="list">
22 <thead>
33 <tr>
4 <% if (mode == 'queue') { %>
5 <th>Connection</th>
6 <th>ID</th>
7 <th>Reference</th>
8 <% } else { %>
49 <th>ID</th>
510 <th>Reference</th>
611 <th>Queue</th>
12 <% } %>
713 <th>Messages Published</th>
814 <th>Messages Confirmed</th>
915 <th>Messages Errored</th>
1420 var publisher = publishers[i];
1521 %>
1622 <tr<%= alt_rows(i) %>>
23 <% if (mode == 'queue') { %>
24 <td><%= link_stream_conn(publisher.queue.vhost, publisher.connection_details.name) %></td>
25 <td><%= publisher.publisher_id %></td>
26 <td class="c"><%= fmt_string(publisher.reference) %></td>
27 <% } else { %>
1728 <td><%= publisher.publisher_id %></td>
1829 <td class="c"><%= fmt_string(publisher.reference) %></td>
1930 <td><%= link_queue(publisher.queue.vhost, publisher.queue.name) %></td>
31 <% } %>
2032 <td class="c"><%= publisher.published %></td>
2133 <td class="c"><%= publisher.confirmed %></td>
2234 <td class="c"><%= publisher.errored %></td>
1616 get_all_publishers/1]).
1717 -export([entity_data/4]).
1818 -export([get_connection_consumers/1,
19 get_connection_publishers/1]).
19 get_connection_publishers/1,
20 get_stream_publishers/1]).
2021
2122 get_all_consumers(VHosts) ->
2223 rabbit_mgmt_db:submit(fun(_Interval) -> consumers_stats(VHosts) end).
3233 get_connection_publishers(ConnectionPid) when is_pid(ConnectionPid) ->
3334 rabbit_mgmt_db:submit(fun(_Interval) ->
3435 connection_publishers_stats(ConnectionPid)
36 end).
37
38 get_stream_publishers(QueueResource) ->
39 rabbit_mgmt_db:submit(fun(_Interval) ->
40 stream_publishers_stats(QueueResource)
3541 end).
3642
3743 consumers_stats(VHost) ->
6470 entity_data,
6571 [ConnectionPid, ?ENTITY_PUBLISHER,
6672 fun publishers_by_connection/1]}),
73 [V || {_, V} <- maps:to_list(Data)].
74
75 stream_publishers_stats(Queue) ->
76 Data =
77 rabbit_mgmt_db:get_data_from_nodes({rabbit_stream_mgmt_db,
78 entity_data,
79 [Queue, ?ENTITY_PUBLISHER,
80 fun publishers_by_stream/1]}),
6781 [V || {_, V} <- maps:to_list(Data)].
6882
6983 entity_data(_Pid, Param, EntityType, QueryFun) ->
102116 publishers_by_connection(ConnectionPid) ->
103117 get_entity_stats(?TABLE_PUBLISHER, ConnectionPid).
104118
119 publishers_by_stream(QueueResource) ->
120 get_entity_stats_by_resource(?TABLE_PUBLISHER, QueueResource).
121
105122 get_entity_stats(Table, Id) ->
106123 ets:select(Table, match_entity_spec(Id)).
107124
108125 match_entity_spec(ConnectionId) ->
109126 [{{{'_', '$1', '_'}, '_'}, [{'==', ConnectionId, '$1'}], ['$_']}].
127
128 get_entity_stats_by_resource(Table, Resource) ->
129 ets:select(Table, match_entity_spec_by_resource(Resource)).
130
131 match_entity_spec_by_resource(#resource{virtual_host = VHost,
132 name = Name}) ->
133 [{{{#resource{virtual_host = '$1',
134 name = '$2',
135 _ = '_'},
136 '_', '_'},
137 '_'},
138 [{'andalso', {'==', '$1', VHost}, {'==', Name, '$2'}}], ['$_']}].
110139
111140 augment_connection_pid(Consumer) ->
112141 Pid = rabbit_misc:pget(connection, Consumer),
2121
2222 dispatcher() ->
2323 [{"/stream/publishers", ?MODULE, []},
24 {"/stream/publishers/:vhost", ?MODULE, []}].
24 {"/stream/publishers/:vhost", ?MODULE, []},
25 {"/stream/publishers/:vhost/:queue", ?MODULE, []}].
2526
2627 web_ui() ->
2728 [].
4142 none ->
4243 true; % none means `all`
4344 _ ->
44 true
45 case rabbit_mgmt_util:id(queue, ReqData) of
46 none ->
47 true;
48 _ ->
49 case rabbit_mgmt_wm_queue:queue(ReqData) of
50 not_found ->
51 false;
52 _ ->
53 true
54 end
55 end
4556 end,
4657 ReqData, Context}.
4758
4859 to_json(ReqData, Context = #context{user = User}) ->
4960 case rabbit_mgmt_util:disable_stats(ReqData) of
5061 false ->
51 Arg = case rabbit_mgmt_util:vhost(ReqData) of
52 none ->
53 all;
54 VHost ->
55 VHost
56 end,
62 VHost =
63 case rabbit_mgmt_util:vhost(ReqData) of
64 none ->
65 all;
66 V ->
67 V
68 end,
69 Queue = rabbit_mgmt_util:id(queue, ReqData),
5770 Publishers =
58 rabbit_mgmt_format:strip_pids(
59 rabbit_stream_mgmt_db:get_all_publishers(Arg)),
71 case {VHost, Queue} of
72 {VHost, none} ->
73 rabbit_mgmt_format:strip_pids(
74 rabbit_stream_mgmt_db:get_all_publishers(VHost));
75 {VHost, Q} ->
76 QueueResource =
77 #resource{virtual_host = VHost,
78 name = Q,
79 kind = queue},
80 rabbit_mgmt_format:strip_pids(
81 rabbit_stream_mgmt_db:get_stream_publishers(QueueResource))
82 end,
6083 rabbit_mgmt_util:reply_list(filter_user(Publishers, User),
6184 [],
6285 ReqData,
0 Copyright (c) 2011-2020, Loïc Hoguin <essen@ninenines.eu>
0 Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
11
22 Permission to use, copy, modify, and/or distribute this software for any
33 purpose with or without fee is hereby granted, provided that the above
11
22 PROJECT = ranch
33 PROJECT_DESCRIPTION = Socket acceptor pool for TCP protocols.
4 PROJECT_VERSION = 2.0.0
4 PROJECT_VERSION = 2.1.0
55 PROJECT_REGISTERED = ranch_server
66
77 # Options.
1717
1818 TEST_DEPS = $(if $(CI_ERLANG_MK),ci.erlang.mk) ct_helper stampede
1919 dep_ct_helper = git https://github.com/ninenines/ct_helper master
20 dep_stampede = git https://github.com/juhlig/stampede 0.5.0
20 dep_stampede = git https://github.com/juhlig/stampede 0.6.0
2121
2222 # Concuerror tests.
2323
2929 dep_ci.erlang.mk = git https://github.com/ninenines/ci.erlang.mk master
3030 DEP_EARLY_PLUGINS = ci.erlang.mk
3131
32 AUTO_CI_OTP ?= OTP-21+
32 AUTO_CI_OTP ?= OTP-22+
3333 AUTO_CI_HIPE ?= OTP-LATEST
3434 # AUTO_CI_ERLLVM ?= OTP-LATEST
35 AUTO_CI_WINDOWS ?= OTP-21+
35 AUTO_CI_WINDOWS ?= OTP-22+
36
37 # Hex configuration.
38
39 define HEX_TARBALL_EXTRA_METADATA
40 #{
41 licenses => [<<"ISC">>],
42 links => #{
43 <<"User guide">> => <<"https://ninenines.eu/docs/en/ranch/2.1/guide/">>,
44 <<"Function reference">> => <<"https://ninenines.eu/docs/en/ranch/2.1/manual/">>,
45 <<"GitHub">> => <<"https://github.com/ninenines/ranch">>,
46 <<"Sponsor">> => <<"https://github.com/sponsors/essen">>
47 }
48 }
49 endef
3650
3751 # Standard targets.
3852
6680 # Prepare for the release.
6781
6882 prepare_tag:
83 $(verbose) $(warning Hex metadata: $(HEX_TARBALL_EXTRA_METADATA))
84 $(verbose) echo
6985 $(verbose) echo -n "Most recent tag: "
70 $(verbose) git tag | tail -n1
71 $(verbose) git verify-tag `git tag | tail -n1`
86 $(verbose) git tag --sort=creatordate | tail -n1
87 $(verbose) git verify-tag `git tag --sort=creatordate | tail -n1`
7288 $(verbose) echo -n "MAKEFILE: "
7389 $(verbose) grep -m1 PROJECT_VERSION Makefile
7490 $(verbose) echo -n "APP: "
7591 $(verbose) grep -m1 vsn ebin/$(PROJECT).app | sed 's/ //g'
7692 $(verbose) echo -n "APPUP: "
7793 $(verbose) grep -m1 {\" src/$(PROJECT).appup
94 $(verbose) echo -n "GUIDE: "
95 $(verbose) grep -h dep_$(PROJECT)_commit doc/src/guide/*.asciidoc || true
96 $(verbose) echo
97 $(verbose) echo "Dependencies:"
98 $(verbose) grep ^DEPS Makefile || echo "DEPS ="
99 $(verbose) grep ^dep_ Makefile || true
78100 $(verbose) echo
79101 $(verbose) echo "Links in the README:"
80102 $(verbose) grep http.*:// README.asciidoc
1818
1919 == Online documentation
2020
21 * https://ninenines.eu/docs/en/ranch/2.0/guide[User guide]
22 * https://ninenines.eu/docs/en/ranch/2.0/manual[Function reference]
21 * https://ninenines.eu/docs/en/ranch/2.1/guide[User guide]
22 * https://ninenines.eu/docs/en/ranch/2.1/manual[Function reference]
2323
2424 == Offline documentation
2525
3232
3333 == Getting help
3434
35 * Official IRC Channel: #ninenines on irc.freenode.net
3635 * https://github.com/ninenines/ranch/issues[Issues tracker]
3736 * https://ninenines.eu/services[Commercial Support]
1616 ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST)))
1717 export ERLANG_MK_FILENAME
1818
19 ERLANG_MK_VERSION = 2020.03.05-18-g4ad50cd
19 ERLANG_MK_VERSION = d80984c
2020 ERLANG_MK_WITHOUT =
2121
2222 # Make 3.81 and 3.82 are deprecated.
954954
955955 PACKAGES += cuttlefish
956956 pkg_cuttlefish_name = cuttlefish
957 pkg_cuttlefish_description = never lose your childlike sense of wonder baby cuttlefish, promise me?
958 pkg_cuttlefish_homepage = https://github.com/basho/cuttlefish
957 pkg_cuttlefish_description = cuttlefish configuration abstraction
958 pkg_cuttlefish_homepage = https://github.com/Kyorai/cuttlefish
959959 pkg_cuttlefish_fetch = git
960 pkg_cuttlefish_repo = https://github.com/basho/cuttlefish
960 pkg_cuttlefish_repo = https://github.com/Kyorai/cuttlefish
961961 pkg_cuttlefish_commit = master
962962
963963 PACKAGES += damocles
23342334 pkg_jsx_homepage = https://github.com/talentdeficit/jsx
23352335 pkg_jsx_fetch = git
23362336 pkg_jsx_repo = https://github.com/talentdeficit/jsx
2337 pkg_jsx_commit = master
2337 pkg_jsx_commit = main
23382338
23392339 PACKAGES += kafka
23402340 pkg_kafka_name = kafka
28222822 pkg_mysql_homepage = https://github.com/mysql-otp/mysql-otp
28232823 pkg_mysql_fetch = git
28242824 pkg_mysql_repo = https://github.com/mysql-otp/mysql-otp
2825 pkg_mysql_commit = 1.5.1
2825 pkg_mysql_commit = 1.7.0
28262826
28272827 PACKAGES += n2o
28282828 pkg_n2o_name = n2o
46914691 case file:consult("$(call core_native_path,$(DEPS_DIR)/$1/rebar.lock)") of
46924692 {ok, Lock} ->
46934693 io:format("~p~n", [Lock]),
4694 case lists:keyfind("1.1.0", 1, Lock) of
4695 {_, LockPkgs} ->
4694 LockPkgs = case lists:keyfind("1.2.0", 1, Lock) of
4695 {_, LP} ->
4696 LP;
4697 _ ->
4698 case lists:keyfind("1.1.0", 1, Lock) of
4699 {_, LP} ->
4700 LP;
4701 _ ->
4702 false
4703 end
4704 end,
4705 if
4706 is_list(LockPkgs) ->
46964707 io:format("~p~n", [LockPkgs]),
46974708 case lists:keyfind(atom_to_binary(N, latin1), 1, LockPkgs) of
46984709 {_, {pkg, _, Vsn}, _} ->
47014712 _ ->
47024713 false
47034714 end;
4704 _ ->
4715 true ->
47054716 false
47064717 end;
47074718 _ ->
55665577 ERL_TEST_FILES = $(call core_find,$(TEST_DIR)/,*.erl)
55675578 $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build: $(ERL_TEST_FILES) $(MAKEFILE_LIST)
55685579 $(eval FILES_TO_COMPILE := $(if $(filter $(MAKEFILE_LIST),$?),$(filter $(ERL_TEST_FILES),$^),$?))
5569 $(if $(strip $(FILES_TO_COMPILE)),$(call compile_test_erl,$(FILES_TO_COMPILE)); touch $@)
5580 $(if $(strip $(FILES_TO_COMPILE)),$(call compile_test_erl,$(FILES_TO_COMPILE)) && touch $@)
55705581 endif
55715582
55725583 test-build:: IS_TEST=1
65646575
65656576 $(ERLANG_MK_TMP)/Concuerror/bin/concuerror: | $(ERLANG_MK_TMP)
65666577 $(verbose) git clone https://github.com/parapluu/Concuerror $(ERLANG_MK_TMP)/Concuerror
6567 $(verbose) make -C $(ERLANG_MK_TMP)/Concuerror
6578 $(verbose) $(MAKE) -C $(ERLANG_MK_TMP)/Concuerror
65686579
65696580 $(CONCUERROR_LOGS_DIR):
65706581 $(verbose) mkdir -p $(CONCUERROR_LOGS_DIR)
69856996 exit $$eunit_retcode
69866997 endif
69876998 endif
6999
7000 # Copyright (c) 2020, Loïc Hoguin <essen@ninenines.eu>
7001 # This file is part of erlang.mk and subject to the terms of the ISC License.
7002
7003 HEX_CORE_GIT ?= https://github.com/hexpm/hex_core
7004 HEX_CORE_COMMIT ?= v0.7.0
7005
7006 PACKAGES += hex_core
7007 pkg_hex_core_name = hex_core
7008 pkg_hex_core_description = Reference implementation of Hex specifications
7009 pkg_hex_core_homepage = $(HEX_CORE_GIT)
7010 pkg_hex_core_fetch = git
7011 pkg_hex_core_repo = $(HEX_CORE_GIT)
7012 pkg_hex_core_commit = $(HEX_CORE_COMMIT)
7013
7014 # We automatically depend on hex_core when the project isn't already.
7015 $(if $(filter hex_core,$(DEPS) $(BUILD_DEPS) $(DOC_DEPS) $(REL_DEPS) $(TEST_DEPS)),,\
7016 $(eval $(call dep_target,hex_core)))
7017
7018 hex-core: $(DEPS_DIR)/hex_core
7019 $(verbose) if [ ! -e $(DEPS_DIR)/hex_core/ebin/dep_built ]; then \
7020 $(MAKE) -C $(DEPS_DIR)/hex_core IS_DEP=1; \
7021 touch $(DEPS_DIR)/hex_core/ebin/dep_built; \
7022 fi
7023
7024 # @todo This must also apply to fetching.
7025 HEX_CONFIG ?=
7026
7027 define hex_config.erl
7028 begin
7029 Config0 = hex_core:default_config(),
7030 Config0$(HEX_CONFIG)
7031 end
7032 endef
7033
7034 define hex_user_create.erl
7035 {ok, _} = application:ensure_all_started(ssl),
7036 {ok, _} = application:ensure_all_started(inets),
7037 Config = $(hex_config.erl),
7038 case hex_api_user:create(Config, <<"$(strip $1)">>, <<"$(strip $2)">>, <<"$(strip $3)">>) of
7039 {ok, {201, _, #{<<"email">> := Email, <<"url">> := URL, <<"username">> := Username}}} ->
7040 io:format("User ~s (~s) created at ~s~n"
7041 "Please check your inbox for a confirmation email.~n"
7042 "You must confirm before you are allowed to publish packages.~n",
7043 [Username, Email, URL]),
7044 halt(0);
7045 {ok, {Status, _, Errors}} ->
7046 io:format("Error ~b: ~0p~n", [Status, Errors]),
7047 halt(80)
7048 end
7049 endef
7050
7051 # The $(info ) call inserts a new line after the password prompt.
7052 hex-user-create: hex-core
7053 $(if $(HEX_USERNAME),,$(eval HEX_USERNAME := $(shell read -p "Username: " username; echo $$username)))
7054 $(if $(HEX_PASSWORD),,$(eval HEX_PASSWORD := $(shell stty -echo; read -p "Password: " password; stty echo; echo $$password) $(info )))
7055 $(if $(HEX_EMAIL),,$(eval HEX_EMAIL := $(shell read -p "Email: " email; echo $$email)))
7056 $(gen_verbose) $(call erlang,$(call hex_user_create.erl,$(HEX_USERNAME),$(HEX_PASSWORD),$(HEX_EMAIL)))
7057
7058 define hex_key_add.erl
7059 {ok, _} = application:ensure_all_started(ssl),
7060 {ok, _} = application:ensure_all_started(inets),
7061 Config = $(hex_config.erl),
7062 ConfigF = Config#{api_key => iolist_to_binary([<<"Basic ">>, base64:encode(<<"$(strip $1):$(strip $2)">>)])},
7063 Permissions = [
7064 case string:split(P, <<":">>) of
7065 [D] -> #{domain => D};
7066 [D, R] -> #{domain => D, resource => R}
7067 end
7068 || P <- string:split(<<"$(strip $4)">>, <<",">>, all)],
7069 case hex_api_key:add(ConfigF, <<"$(strip $3)">>, Permissions) of
7070 {ok, {201, _, #{<<"secret">> := Secret}}} ->
7071 io:format("Key ~s created for user ~s~nSecret: ~s~n"
7072 "Please store the secret in a secure location, such as a password store.~n"
7073 "The secret will be requested for most Hex-related operations.~n",
7074 [<<"$(strip $3)">>, <<"$(strip $1)">>, Secret]),
7075 halt(0);
7076 {ok, {Status, _, Errors}} ->
7077 io:format("Error ~b: ~0p~n", [Status, Errors]),
7078 halt(81)
7079 end
7080 endef
7081
7082 hex-key-add: hex-core
7083 $(if $(HEX_USERNAME),,$(eval HEX_USERNAME := $(shell read -p "Username: " username; echo $$username)))
7084 $(if $(HEX_PASSWORD),,$(eval HEX_PASSWORD := $(shell stty -echo; read -p "Password: " password; stty echo; echo $$password) $(info )))
7085 $(gen_verbose) $(call erlang,$(call hex_key_add.erl,$(HEX_USERNAME),$(HEX_PASSWORD),\
7086 $(if $(name),$(name),$(shell hostname)-erlang-mk),\
7087 $(if $(perm),$(perm),api)))
7088
7089 HEX_TARBALL_EXTRA_METADATA ?=
7090
7091 # @todo Check that we can += files
7092 HEX_TARBALL_FILES ?= \
7093 $(wildcard early-plugins.mk) \
7094 $(wildcard ebin/$(PROJECT).app) \
7095 $(wildcard ebin/$(PROJECT).appup) \
7096 $(wildcard $(notdir $(ERLANG_MK_FILENAME))) \
7097 $(sort $(call core_find,include/,*.hrl)) \
7098 $(wildcard LICENSE*) \
7099 $(wildcard Makefile) \
7100 $(wildcard plugins.mk) \
7101 $(sort $(call core_find,priv/,*)) \
7102 $(wildcard README*) \
7103 $(wildcard rebar.config) \
7104 $(sort $(call core_find,src/,*))
7105
7106 HEX_TARBALL_OUTPUT_FILE ?= $(ERLANG_MK_TMP)/$(PROJECT).tar
7107
7108 # @todo Need to check for rebar.config and/or the absence of DEPS to know
7109 # whether a project will work with Rebar.
7110 #
7111 # @todo contributors licenses links in HEX_TARBALL_EXTRA_METADATA
7112
7113 # In order to build the requirements metadata we look into DEPS.
7114 # We do not require that the project use Hex dependencies, however
7115 # Hex.pm does require that the package name and version numbers
7116 # correspond to a real Hex package.
7117 define hex_tarball_create.erl
7118 Files0 = [$(call comma_list,$(patsubst %,"%",$(HEX_TARBALL_FILES)))],
7119 Requirements0 = #{
7120 $(foreach d,$(DEPS),
7121 <<"$(if $(subst hex,,$(call query_fetch_method,$d)),$d,$(if $(word 3,$(dep_$d)),$(word 3,$(dep_$d)),$d))">> => #{
7122 <<"app">> => <<"$d">>,
7123 <<"optional">> => false,
7124 <<"requirement">> => <<"$(call query_version,$d)">>
7125 },)
7126 $(if $(DEPS),dummy => dummy)
7127 },
7128 Requirements = maps:remove(dummy, Requirements0),
7129 Metadata0 = #{
7130 app => <<"$(strip $(PROJECT))">>,
7131 build_tools => [<<"make">>, <<"rebar3">>],
7132 description => <<"$(strip $(PROJECT_DESCRIPTION))">>,
7133 files => [unicode:characters_to_binary(F) || F <- Files0],
7134 name => <<"$(strip $(PROJECT))">>,
7135 requirements => Requirements,
7136 version => <<"$(strip $(PROJECT_VERSION))">>
7137 },
7138 Metadata = Metadata0$(HEX_TARBALL_EXTRA_METADATA),
7139 Files = [case file:read_file(F) of
7140 {ok, Bin} ->
7141 {F, Bin};
7142 {error, Reason} ->
7143 io:format("Error trying to open file ~0p: ~0p~n", [F, Reason]),
7144 halt(82)
7145 end || F <- Files0],
7146 case hex_tarball:create(Metadata, Files) of
7147 {ok, #{tarball := Tarball}} ->
7148 ok = file:write_file("$(strip $(HEX_TARBALL_OUTPUT_FILE))", Tarball),
7149 halt(0);
7150 {error, Reason} ->
7151 io:format("Error ~0p~n", [Reason]),
7152 halt(83)
7153 end
7154 endef
7155
7156 hex_tar_verbose_0 = @echo " TAR $(notdir $(ERLANG_MK_TMP))/$(@F)";
7157 hex_tar_verbose_2 = set -x;
7158 hex_tar_verbose = $(hex_tar_verbose_$(V))
7159
7160 $(HEX_TARBALL_OUTPUT_FILE): hex-core app
7161 $(hex_tar_verbose) $(call erlang,$(call hex_tarball_create.erl))
7162
7163 hex-tarball-create: $(HEX_TARBALL_OUTPUT_FILE)
7164
7165 define hex_release_publish_summary.erl
7166 {ok, Tarball} = erl_tar:open("$(strip $(HEX_TARBALL_OUTPUT_FILE))", [read]),
7167 ok = erl_tar:extract(Tarball, [{cwd, "$(ERLANG_MK_TMP)"}, {files, ["metadata.config"]}]),
7168 {ok, Metadata} = file:consult("$(ERLANG_MK_TMP)/metadata.config"),
7169 #{
7170 <<"name">> := Name,
7171 <<"version">> := Version,
7172 <<"files">> := Files,
7173 <<"requirements">> := Deps
7174 } = maps:from_list(Metadata),
7175 io:format("Publishing ~s ~s~n Dependencies:~n", [Name, Version]),
7176 case Deps of
7177 [] ->
7178 io:format(" (none)~n");
7179 _ ->
7180 [begin
7181 #{<<"app">> := DA, <<"requirement">> := DR} = maps:from_list(D),
7182 io:format(" ~s ~s~n", [DA, DR])
7183 end || {_, D} <- Deps]
7184 end,
7185 io:format(" Included files:~n"),
7186 [io:format(" ~s~n", [F]) || F <- Files],
7187 io:format("You may also review the contents of the tarball file.~n"
7188 "Please enter your secret key to proceed.~n"),
7189 halt(0)
7190 endef
7191
7192 define hex_release_publish.erl
7193 {ok, _} = application:ensure_all_started(ssl),
7194 {ok, _} = application:ensure_all_started(inets),
7195 Config = $(hex_config.erl),
7196 ConfigF = Config#{api_key => <<"$(strip $1)">>},
7197 {ok, Tarball} = file:read_file("$(strip $(HEX_TARBALL_OUTPUT_FILE))"),
7198 case hex_api_release:publish(ConfigF, Tarball, [{replace, $2}]) of
7199 {ok, {200, _, #{}}} ->
7200 io:format("Release replaced~n"),
7201 halt(0);
7202 {ok, {201, _, #{}}} ->
7203 io:format("Release published~n"),
7204 halt(0);
7205 {ok, {Status, _, Errors}} ->
7206 io:format("Error ~b: ~0p~n", [Status, Errors]),
7207 halt(84)
7208 end
7209 endef
7210
7211 hex-release-tarball: hex-core $(HEX_TARBALL_OUTPUT_FILE)
7212 $(verbose) $(call erlang,$(call hex_release_publish_summary.erl))
7213
7214 hex-release-publish: hex-core hex-release-tarball
7215 $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
7216 $(gen_verbose) $(call erlang,$(call hex_release_publish.erl,$(HEX_SECRET),false))
7217
7218 hex-release-replace: hex-core hex-release-tarball
7219 $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
7220 $(gen_verbose) $(call erlang,$(call hex_release_publish.erl,$(HEX_SECRET),true))
7221
7222 define hex_release_delete.erl
7223 {ok, _} = application:ensure_all_started(ssl),
7224 {ok, _} = application:ensure_all_started(inets),
7225 Config = $(hex_config.erl),
7226 ConfigF = Config#{api_key => <<"$(strip $1)">>},
7227 case hex_api_release:delete(ConfigF, <<"$(strip $(PROJECT))">>, <<"$(strip $(PROJECT_VERSION))">>) of
7228 {ok, {204, _, _}} ->
7229 io:format("Release $(strip $(PROJECT_VERSION)) deleted~n"),
7230 halt(0);
7231 {ok, {Status, _, Errors}} ->
7232 io:format("Error ~b: ~0p~n", [Status, Errors]),
7233 halt(85)
7234 end
7235 endef
7236
7237 hex-release-delete: hex-core
7238 $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
7239 $(gen_verbose) $(call erlang,$(call hex_release_delete.erl,$(HEX_SECRET)))
7240
7241 define hex_release_retire.erl
7242 {ok, _} = application:ensure_all_started(ssl),
7243 {ok, _} = application:ensure_all_started(inets),
7244 Config = $(hex_config.erl),
7245 ConfigF = Config#{api_key => <<"$(strip $1)">>},
7246 Params = #{<<"reason">> => <<"$(strip $3)">>, <<"message">> => <<"$(strip $4)">>},
7247 case hex_api_release:retire(ConfigF, <<"$(strip $(PROJECT))">>, <<"$(strip $2)">>, Params) of
7248 {ok, {204, _, _}} ->
7249 io:format("Release $(strip $2) has been retired~n"),
7250 halt(0);
7251 {ok, {Status, _, Errors}} ->
7252 io:format("Error ~b: ~0p~n", [Status, Errors]),
7253 halt(86)
7254 end
7255 endef
7256
7257 hex-release-retire: hex-core
7258 $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
7259 $(gen_verbose) $(call erlang,$(call hex_release_retire.erl,$(HEX_SECRET),\
7260 $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION)),\
7261 $(if $(HEX_REASON),$(HEX_REASON),invalid),\
7262 $(HEX_MESSAGE)))
7263
7264 define hex_release_unretire.erl
7265 {ok, _} = application:ensure_all_started(ssl),
7266 {ok, _} = application:ensure_all_started(inets),
7267 Config = $(hex_config.erl),
7268 ConfigF = Config#{api_key => <<"$(strip $1)">>},
7269 case hex_api_release:unretire(ConfigF, <<"$(strip $(PROJECT))">>, <<"$(strip $2)">>) of
7270 {ok, {204, _, _}} ->
7271 io:format("Release $(strip $2) is not retired anymore~n"),
7272 halt(0);
7273 {ok, {Status, _, Errors}} ->
7274 io:format("Error ~b: ~0p~n", [Status, Errors]),
7275 halt(87)
7276 end
7277 endef
7278
7279 hex-release-unretire: hex-core
7280 $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
7281 $(gen_verbose) $(call erlang,$(call hex_release_unretire.erl,$(HEX_SECRET),\
7282 $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION))))
7283
7284 HEX_DOCS_DOC_DIR ?= doc/
7285 HEX_DOCS_TARBALL_FILES ?= $(sort $(call core_find,$(HEX_DOCS_DOC_DIR),*))
7286 HEX_DOCS_TARBALL_OUTPUT_FILE ?= $(ERLANG_MK_TMP)/$(PROJECT)-docs.tar.gz
7287
7288 $(HEX_DOCS_TARBALL_OUTPUT_FILE): hex-core app docs
7289 $(hex_tar_verbose) tar czf $(HEX_DOCS_TARBALL_OUTPUT_FILE) -C $(HEX_DOCS_DOC_DIR) \
7290 $(HEX_DOCS_TARBALL_FILES:$(HEX_DOCS_DOC_DIR)%=%)
7291
7292 hex-docs-tarball-create: $(HEX_DOCS_TARBALL_OUTPUT_FILE)
7293
7294 define hex_docs_publish.erl
7295 {ok, _} = application:ensure_all_started(ssl),
7296 {ok, _} = application:ensure_all_started(inets),
7297 Config = $(hex_config.erl),
7298 ConfigF = Config#{api_key => <<"$(strip $1)">>},
7299 {ok, Tarball} = file:read_file("$(strip $(HEX_DOCS_TARBALL_OUTPUT_FILE))"),
7300 case hex_api:post(ConfigF,
7301 ["packages", "$(strip $(PROJECT))", "releases", "$(strip $(PROJECT_VERSION))", "docs"],
7302 {"application/octet-stream", Tarball}) of
7303 {ok, {Status, _, _}} when Status >= 200, Status < 300 ->
7304 io:format("Docs published~n"),
7305 halt(0);
7306 {ok, {Status, _, Errors}} ->
7307 io:format("Error ~b: ~0p~n", [Status, Errors]),
7308 halt(88)
7309 end
7310 endef
7311
7312 hex-docs-publish: hex-core hex-docs-tarball-create
7313 $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
7314 $(gen_verbose) $(call erlang,$(call hex_docs_publish.erl,$(HEX_SECRET)))
7315
7316 define hex_docs_delete.erl
7317 {ok, _} = application:ensure_all_started(ssl),
7318 {ok, _} = application:ensure_all_started(inets),
7319 Config = $(hex_config.erl),
7320 ConfigF = Config#{api_key => <<"$(strip $1)">>},
7321 case hex_api:delete(ConfigF,
7322 ["packages", "$(strip $(PROJECT))", "releases", "$(strip $2)", "docs"]) of
7323 {ok, {Status, _, _}} when Status >= 200, Status < 300 ->
7324 io:format("Docs removed~n"),
7325 halt(0);
7326 {ok, {Status, _, Errors}} ->
7327 io:format("Error ~b: ~0p~n", [Status, Errors]),
7328 halt(89)
7329 end
7330 endef
7331
7332 hex-docs-delete: hex-core
7333 $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
7334 $(gen_verbose) $(call erlang,$(call hex_docs_delete.erl,$(HEX_SECRET),\
7335 $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION))))
69887336
69897337 # Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>
69907338 # This file is part of erlang.mk and subject to the terms of the ISC License.
77308078 ifeq ($(IS_APP)$(IS_DEP),)
77318079 $(verbose) rm -f $(ERLANG_MK_RECURSIVE_TMP_LIST)
77328080 endif
8081 $(verbose) touch $(ERLANG_MK_RECURSIVE_TMP_LIST)
77338082 $(verbose) set -e; for dep in $^ ; do \
77348083 if ! grep -qs ^$$dep$$ $(ERLANG_MK_RECURSIVE_TMP_LIST); then \
77358084 echo $$dep >> $(ERLANG_MK_RECURSIVE_TMP_LIST); \
+0
-8
deps/ranch/src/ranch.app.src less more
0 {application,ranch,
1 [{description,"Socket acceptor pool for TCP protocols."},
2 {vsn,"2.0.0"},
3 {modules,[]},
4 {registered,[ranch_sup,ranch_server]},
5 {applications,[kernel,stdlib,ssl]},
6 {mod,{ranch_app,[]}},
7 {env,[]}]}.
88 %% module then call system_code_change; and when downgrading,
99 %% call system_code_change and then load the module.
1010
11 {"2.0.0",
11 {"2.1.0",
1212 [{<<"2\\.0\\.[0-9]+.*">>, [
1313 {apply, {ranch, stop_all_acceptors, []}},
1414 {load_module, ranch},
1515 {load_module, ranch_acceptor},
1616 {update, ranch_acceptors_sup, supervisor},
1717 {load_module, ranch_app},
18 {update, ranch_server, {advanced, []}},
19 {update, ranch_conns_sup_sup, supervisor},
1820 %% See comments at the top of the file about ranch_conns_sup.
1921 {update, ranch_conns_sup, {advanced, []}},
20 {update, ranch_conns_sup_sup, supervisor},
2122 {load_module, ranch_crc32c},
2223 {update, ranch_embedded_sup, supervisor},
2324 {update, ranch_listener_sup, supervisor},
2425 {load_module, ranch_protocol},
2526 {load_module, ranch_proxy_header},
26 {update, ranch_server, {advanced, []}},
2727 {update, ranch_server_proxy, {advanced, []}},
2828 {load_module, ranch_ssl},
2929 {update, ranch_sup, supervisor},
5454 {apply, {ranch, restart_all_acceptors, []}}
5555 ]}]
5656 }.
57
58 {"2.0.0",
59 [{<<"2\\.0\\.[0-9]+.*">>, [
60 {apply, {ranch, stop_all_acceptors, []}},
61 {load_module, ranch},
62 {load_module, ranch_acceptor},
63 {update, ranch_acceptors_sup, supervisor},
64 {load_module, ranch_app},
65 {update, ranch_server, {advanced, []}},
66 {update, ranch_conns_sup_sup, supervisor},
67 %% See comments at the top of the file about ranch_conns_sup.
68 {update, ranch_conns_sup, {advanced, []}},
69 {load_module, ranch_crc32c},
70 {update, ranch_embedded_sup, supervisor},
71 {update, ranch_listener_sup, supervisor},
72 {load_module, ranch_protocol},
73 {load_module, ranch_proxy_header},
74 {update, ranch_server_proxy, {advanced, []}},
75 {load_module, ranch_ssl},
76 {update, ranch_sup, supervisor},
77 {load_module, ranch_tcp},
78 {load_module, ranch_transport},
79 {apply, {ranch, restart_all_acceptors, []}}
80 ]}],
81 [{<<"2\\.0\\.[0-9]+.*">>, [
82 {apply, {ranch, stop_all_acceptors, []}},
83 {load_module, ranch},
84 {load_module, ranch_acceptor},
85 {update, ranch_acceptors_sup, supervisor},
86 {load_module, ranch_app},
87 %% See comments at the top of the file about ranch_conns_sup.
88 {update, ranch_conns_sup, {advanced, []}},
89 {update, ranch_conns_sup_sup, supervisor},
90 {load_module, ranch_crc32c},
91 {update, ranch_embedded_sup, supervisor},
92 {update, ranch_listener_sup, supervisor},
93 {load_module, ranch_protocol},
94 {load_module, ranch_proxy_header},
95 {update, ranch_server, {advanced, []}},
96 {update, ranch_server_proxy, {advanced, []}},
97 {load_module, ranch_ssl},
98 {update, ranch_sup, supervisor},
99 {load_module, ranch_tcp},
100 {load_module, ranch_transport},
101 {apply, {ranch, restart_all_acceptors, []}}
102 ]}]
103 }.
0 %% Copyright (c) 2011-2020, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020, Jan Uhlig <j.uhlig@mailingwork.de>
0 %% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
2 %% Copyright (c) 2021, Maria Scott <maria-12648430@hnc-agency.org>
23 %%
34 %% Permission to use, copy, modify, and/or distribute this software for any
45 %% purpose with or without fee is hereby granted, provided that the above
5455 -type opts() :: any() | transport_opts(any()).
5556 -export_type([opts/0]).
5657
58 -type alarm(Type, Callback) :: #{
59 type := Type,
60 callback := Callback,
61 treshold := non_neg_integer(),
62 cooldown => non_neg_integer()
63 }.
64
65 -type alarm_num_connections() :: alarm(num_connections, fun((ref(), term(), pid(), [pid()]) -> any())).
66
5767 -type transport_opts(SocketOpts) :: #{
68 alarms => #{term() => alarm_num_connections()},
5869 connection_type => worker | supervisor,
5970 handshake_timeout => timeout(),
71 logger => module(),
6072 max_connections => max_conns(),
61 logger => module(),
6273 num_acceptors => pos_integer(),
6374 num_conns_sups => pos_integer(),
6475 num_listen_sockets => pos_integer(),
76 post_listen_callback => fun((term()) -> ok | {error, term()}),
6577 shutdown => timeout() | brutal_kill,
6678 socket_opts => SocketOpts
6779 }.
121133 true;
122134 validate_transport_opt(max_connections, Value, _) ->
123135 is_integer(Value) andalso Value >= 0;
136 validate_transport_opt(alarms, Alarms, _) ->
137 maps:fold(
138 fun
139 (_, Opts, true) ->
140 validate_alarm(Opts);
141 (_, _, false) ->
142 false
143 end,
144 true,
145 Alarms);
124146 validate_transport_opt(logger, Value, _) ->
125147 is_atom(Value);
126148 validate_transport_opt(num_acceptors, Value, _) ->
130152 validate_transport_opt(num_listen_sockets, Value, Opts) ->
131153 is_integer(Value) andalso Value > 0
132154 andalso Value =< maps:get(num_acceptors, Opts, 10);
155 validate_transport_opt(post_listen_callback, Value, _) ->
156 is_function(Value, 1);
133157 validate_transport_opt(shutdown, brutal_kill, _) ->
134158 true;
135159 validate_transport_opt(shutdown, infinity, _) ->
139163 validate_transport_opt(socket_opts, _, _) ->
140164 true;
141165 validate_transport_opt(_, _, _) ->
166 false.
167
168 validate_alarm(Alarm = #{type := num_connections, treshold := Treshold,
169 callback := Callback}) ->
170 is_integer(Treshold) andalso Treshold >= 0
171 andalso is_function(Callback, 4)
172 andalso case Alarm of
173 #{cooldown := Cooldown} ->
174 is_integer(Cooldown) andalso Cooldown >= 0;
175 _ ->
176 true
177 end;
178 validate_alarm(_) ->
142179 false.
143180
144181 maybe_started({error, {{shutdown,
419456 transport => Transport,
420457 transport_options => TransOpts,
421458 protocol => Protocol,
422 protocol_options => ProtoOpts
459 protocol_options => ProtoOpts,
460 metrics => metrics(Ref)
423461 }.
424462
425463 -spec procs(ref(), acceptors | connections) -> [pid()].
445483 supervisor:which_children(SupSupPid)
446484 ),
447485 lists:flatten(Conns).
486
487 -spec metrics(ref()) -> #{}.
488 metrics(Ref) ->
489 Counters = ranch_server:get_stats_counters(Ref),
490 CounterInfo = counters:info(Counters),
491 NumCounters = maps:get(size, CounterInfo),
492 NumConnsSups = NumCounters div 2,
493 lists:foldl(
494 fun (Id, Acc) ->
495 Acc#{
496 {conns_sup, Id, accept} => counters:get(Counters, 2*Id-1),
497 {conns_sup, Id, terminate} => counters:get(Counters, 2*Id)
498 }
499 end,
500 #{},
501 lists:seq(1, NumConnsSups)
502 ).
448503
449504 -spec wait_for_connections
450505 (ref(), '>' | '>=' | '==' | '=<', non_neg_integer()) -> ok;
0 %% Copyright (c) 2011-2020, Loïc Hoguin <essen@ninenines.eu>
0 %% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
11 %%
22 %% Permission to use, copy, modify, and/or distribute this software for any
33 %% purpose with or without fee is hereby granted, provided that the above
0 %% Copyright (c) 2011-2020, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020, Jan Uhlig <j.uhlig@mailingwork.de>
0 %% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
22 %%
33 %% Permission to use, copy, modify, and/or distribute this software for any
44 %% purpose with or without fee is hereby granted, provided that the above
5959 [];
6060 {_, Port} ->
6161 SocketOpts = maps:get(socket_opts, TransOpts0, []),
62 SocketOpts1 = case lists:keyfind(port, 1, SocketOpts) of
63 {port, Port} ->
64 SocketOpts;
65 _ ->
66 [{port, Port}|lists:keydelete(port, 1, SocketOpts)]
67 end,
62 SocketOpts1 = lists:keystore(port, 1, SocketOpts, {port, Port}),
6863 TransOpts1 = TransOpts0#{socket_opts => SocketOpts1},
6964 [{N, start_listen_socket(Ref, Transport, TransOpts1, Logger)}
7065 || N <- lists:seq(2, NumListenSockets)]
7671 start_listen_socket(Ref, Transport, TransOpts, Logger) ->
7772 case Transport:listen(TransOpts) of
7873 {ok, Socket} ->
79 Socket;
74 PostListenCb = maps:get(post_listen_callback, TransOpts, fun (_) -> ok end),
75 case PostListenCb(Socket) of
76 ok ->
77 Socket;
78 {error, Reason} ->
79 listen_error(Ref, Transport, TransOpts, Reason, Logger)
80 end;
8081 {error, Reason} ->
8182 listen_error(Ref, Transport, TransOpts, Reason, Logger)
8283 end.
0 %% Copyright (c) 2011-2020, Loïc Hoguin <essen@ninenines.eu>
0 %% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
11 %%
22 %% Permission to use, copy, modify, and/or distribute this software for any
33 %% purpose with or without fee is hereby granted, provided that the above
0 %% Copyright (c) 2011-2020, Loïc Hoguin <essen@ninenines.eu>
0 %% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2021, Maria Scott <maria-12648430@hnc-agency.org>
12 %%
23 %% Permission to use, copy, modify, and/or distribute this software for any
34 %% purpose with or without fee is hereby granted, provided that the above
3334 -record(state, {
3435 parent = undefined :: pid(),
3536 ref :: ranch:ref(),
37 id :: pos_integer(),
3638 conn_type :: conn_type(),
3739 shutdown :: shutdown(),
3840 transport = undefined :: module(),
4042 opts :: any(),
4143 handshake_timeout :: timeout(),
4244 max_conns = undefined :: ranch:max_conns(),
45 stats_counters_ref :: counters:counters_ref(),
46 alarms = #{} :: #{term() => {map(), undefined | reference()}},
4347 logger = undefined :: module()
4448 }).
4549
103107 process_flag(trap_exit, true),
104108 ok = ranch_server:set_connections_sup(Ref, Id, self()),
105109 MaxConns = ranch_server:get_max_connections(Ref),
110 Alarms = get_alarms(TransOpts),
106111 ConnType = maps:get(connection_type, TransOpts, worker),
107112 Shutdown = maps:get(shutdown, TransOpts, 5000),
108113 HandshakeTimeout = maps:get(handshake_timeout, TransOpts, 5000),
109114 ProtoOpts = ranch_server:get_protocol_options(Ref),
115 StatsCounters = ranch_server:get_stats_counters(Ref),
110116 ok = proc_lib:init_ack(Parent, {ok, self()}),
111 loop(#state{parent=Parent, ref=Ref, conn_type=ConnType,
117 loop(#state{parent=Parent, ref=Ref, id=Id, conn_type=ConnType,
112118 shutdown=Shutdown, transport=Transport, protocol=Protocol,
113 opts=ProtoOpts, handshake_timeout=HandshakeTimeout,
114 max_conns=MaxConns, logger=Logger}, 0, 0, []).
115
116 loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType,
117 transport=Transport, protocol=Protocol, opts=Opts,
118 max_conns=MaxConns, logger=Logger}, CurConns, NbChildren, Sleepers) ->
119 opts=ProtoOpts, stats_counters_ref=StatsCounters,
120 handshake_timeout=HandshakeTimeout,
121 max_conns=MaxConns, alarms=Alarms,
122 logger=Logger}, 0, 0, []).
123
124 loop(State=#state{parent=Parent, ref=Ref, id=Id, conn_type=ConnType,
125 transport=Transport, protocol=Protocol, opts=Opts, stats_counters_ref=StatsCounters,
126 alarms=Alarms, max_conns=MaxConns, logger=Logger}, CurConns, NbChildren, Sleepers) ->
119127 receive
120128 {?MODULE, start_protocol, To, Socket} ->
121129 try Protocol:start_link(Ref, Transport, Opts) of
122130 {ok, Pid} ->
131 inc_accept(StatsCounters, Id, 1),
123132 handshake(State, CurConns, NbChildren, Sleepers, To, Socket, Pid, Pid);
124133 {ok, SupPid, ProtocolPid} when ConnType =:= supervisor ->
134 inc_accept(StatsCounters, Id, 1),
125135 handshake(State, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid);
126136 Ret ->
127137 To ! self(),
174184 {set_protocol_options, Opts2} ->
175185 loop(State#state{opts=Opts2},
176186 CurConns, NbChildren, Sleepers);
187 {timeout, _, {activate_alarm, AlarmName}} when is_map_key(AlarmName, Alarms) ->
188 {AlarmOpts, _} = maps:get(AlarmName, Alarms),
189 NewAlarm = trigger_alarm(Ref, AlarmName, {AlarmOpts, undefined}, CurConns),
190 loop(State#state{alarms=Alarms#{AlarmName => NewAlarm}}, CurConns, NbChildren, Sleepers);
191 {timeout, _, {activate_alarm, _}} ->
192 loop(State, CurConns, NbChildren, Sleepers);
177193 {'EXIT', Parent, Reason} ->
178194 terminate(State, Reason, NbChildren);
179195 {'EXIT', Pid, Reason} when Sleepers =:= [] ->
180196 case erase(Pid) of
181197 active ->
198 inc_terminate(StatsCounters, Id, 1),
182199 report_error(Logger, Ref, Protocol, Pid, Reason),
183200 loop(State, CurConns - 1, NbChildren - 1, Sleepers);
184201 removed ->
202 inc_terminate(StatsCounters, Id, 1),
185203 report_error(Logger, Ref, Protocol, Pid, Reason),
186204 loop(State, CurConns, NbChildren - 1, Sleepers);
187205 undefined ->
191209 {'EXIT', Pid, Reason} ->
192210 case erase(Pid) of
193211 active when CurConns > MaxConns ->
212 inc_terminate(StatsCounters, Id, 1),
194213 report_error(Logger, Ref, Protocol, Pid, Reason),
195214 loop(State, CurConns - 1, NbChildren - 1, Sleepers);
196215 active ->
216 inc_terminate(StatsCounters, Id, 1),
197217 report_error(Logger, Ref, Protocol, Pid, Reason),
198218 [To|Sleepers2] = Sleepers,
199219 To ! self(),
200220 loop(State, CurConns - 1, NbChildren - 1, Sleepers2);
201221 removed ->
222 inc_terminate(StatsCounters, Id, 1),
202223 report_error(Logger, Ref, Protocol, Pid, Reason),
203224 loop(State, CurConns, NbChildren - 1, Sleepers);
204225 undefined ->
233254 end.
234255
235256 handshake(State=#state{ref=Ref, transport=Transport, handshake_timeout=HandshakeTimeout,
236 max_conns=MaxConns}, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid) ->
257 max_conns=MaxConns, alarms=Alarms0}, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid) ->
237258 case Transport:controlling_process(Socket, ProtocolPid) of
238259 ok ->
239260 ProtocolPid ! {handshake, Ref, Transport, Socket, HandshakeTimeout},
240261 put(SupPid, active),
241262 CurConns2 = CurConns + 1,
242 if CurConns2 < MaxConns ->
263 Sleepers2 = if CurConns2 < MaxConns ->
243264 To ! self(),
244 loop(State, CurConns2, NbChildren + 1, Sleepers);
265 Sleepers;
245266 true ->
246 loop(State, CurConns2, NbChildren + 1, [To|Sleepers])
247 end;
267 [To|Sleepers]
268 end,
269 Alarms1 = trigger_alarms(Ref, Alarms0, CurConns2),
270 loop(State#state{alarms=Alarms1}, CurConns2, NbChildren + 1, Sleepers2);
248271 {error, _} ->
249272 Transport:close(Socket),
250273 %% Only kill the supervised pid, because the connection's pid,
253276 To ! self(),
254277 loop(State, CurConns, NbChildren, Sleepers)
255278 end.
279
280 trigger_alarms(Ref, Alarms, CurConns) ->
281 maps:map(
282 fun
283 (AlarmName, Alarm) ->
284 trigger_alarm(Ref, AlarmName, Alarm, CurConns)
285 end,
286 Alarms
287 ).
288
289 trigger_alarm(Ref, AlarmName, {Opts=#{treshold := Treshold, callback := Callback}, undefined}, CurConns) when CurConns >= Treshold ->
290 ActiveConns = [Pid || {Pid, active} <- get()],
291 case Callback of
292 {Mod, Fun} ->
293 spawn(Mod, Fun, [Ref, AlarmName, self(), ActiveConns]);
294 _ ->
295 Self = self(),
296 spawn(fun () -> Callback(Ref, AlarmName, Self, ActiveConns) end)
297 end,
298 {Opts, schedule_activate_alarm(AlarmName, Opts)};
299 trigger_alarm(_, _, Alarm, _) ->
300 Alarm.
301
302 schedule_activate_alarm(AlarmName, #{cooldown := Cooldown}) when Cooldown > 0 ->
303 erlang:start_timer(Cooldown, self(), {activate_alarm, AlarmName});
304 schedule_activate_alarm(_, _) ->
305 undefined.
306
307 get_alarms(#{alarms := Alarms}) when is_map(Alarms) ->
308 maps:fold(
309 fun
310 (Name, Opts = #{type := num_connections, cooldown := _}, Acc) ->
311 Acc#{Name => {Opts, undefined}};
312 (Name, Opts = #{type := num_connections}, Acc) ->
313 Acc#{Name => {Opts#{cooldown => 5000}, undefined}};
314 (_, _, Acc) -> Acc
315 end,
316 #{},
317 Alarms
318 );
319 get_alarms(_) ->
320 #{}.
256321
257322 set_transport_options(State=#state{max_conns=MaxConns0}, CurConns, NbChildren, Sleepers0, TransOpts) ->
258323 MaxConns1 = maps:get(max_connections, TransOpts, 1024),
265330 false ->
266331 Sleepers0
267332 end,
268 loop(State#state{max_conns=MaxConns1, handshake_timeout=HandshakeTimeout, shutdown=Shutdown},
333 State1=set_alarm_option(State, TransOpts, CurConns),
334 loop(State1#state{max_conns=MaxConns1, handshake_timeout=HandshakeTimeout, shutdown=Shutdown},
269335 CurConns, NbChildren, Sleepers1).
270336
337 set_alarm_option(State=#state{ref=Ref, alarms=OldAlarms}, TransOpts, CurConns) ->
338 NewAlarms0 = get_alarms(TransOpts),
339 NewAlarms1 = merge_alarms(OldAlarms, NewAlarms0),
340 NewAlarms2 = trigger_alarms(Ref, NewAlarms1, CurConns),
341 State#state{alarms=NewAlarms2}.
342
343 merge_alarms(Old, New) ->
344 OldList = lists:sort(maps:to_list(Old)),
345 NewList = lists:sort(maps:to_list(New)),
346 Merged = merge_alarms(OldList, NewList, []),
347 maps:from_list(Merged).
348
349 merge_alarms([], News, Acc) ->
350 News ++ Acc;
351 merge_alarms([{_, {_, undefined}}|Olds], [], Acc) ->
352 merge_alarms(Olds, [], Acc);
353 merge_alarms([{_, {_, Timer}}|Olds], [], Acc) ->
354 _ = cancel_alarm_reactivation_timer(Timer),
355 merge_alarms(Olds, [], Acc);
356 merge_alarms([{Name, {OldOpts, Timer}}|Olds], [{Name, {NewOpts, _}}|News], Acc) ->
357 merge_alarms(Olds, News, [{Name, {NewOpts, adapt_alarm_timer(Name, Timer, OldOpts, NewOpts)}}|Acc]);
358 merge_alarms([{OldName, {_, Timer}}|Olds], News=[{NewName, _}|_], Acc) when OldName < NewName ->
359 _ = cancel_alarm_reactivation_timer(Timer),
360 merge_alarms(Olds, News, Acc);
361 merge_alarms(Olds, [New|News], Acc) ->
362 merge_alarms(Olds, News, [New|Acc]).
363
364 %% Not in cooldown.
365 adapt_alarm_timer(_, undefined, _, _) ->
366 undefined;
367 %% Cooldown unchanged.
368 adapt_alarm_timer(_, Timer, #{cooldown := Cooldown}, #{cooldown := Cooldown}) ->
369 Timer;
370 %% Cooldown changed to no cooldown, cancel cooldown timer.
371 adapt_alarm_timer(_, Timer, _, #{cooldown := 0}) ->
372 _ = cancel_alarm_reactivation_timer(Timer),
373 undefined;
374 %% Cooldown changed, cancel current and start new timer taking the already elapsed time into account.
375 adapt_alarm_timer(Name, Timer, #{cooldown := OldCooldown}, #{cooldown := NewCooldown}) ->
376 OldTimeLeft = cancel_alarm_reactivation_timer(Timer),
377 case NewCooldown-OldCooldown+OldTimeLeft of
378 NewTimeLeft when NewTimeLeft>0 ->
379 erlang:start_timer(NewTimeLeft, self(), {activate_alarm, Name});
380 _ ->
381 undefined
382 end.
383
384 cancel_alarm_reactivation_timer(Timer) ->
385 case erlang:cancel_timer(Timer) of
386 %% Timer had already expired when we tried to cancel it, so we flush the
387 %% reactivation message it sent and return 0 as remaining time.
388 false ->
389 ok = receive {timeout, Timer, {activate_alarm, _}} -> ok after 0 -> ok end,
390 0;
391 %% Timer has not yet expired, we return the amount of time that was remaining.
392 TimeLeft ->
393 TimeLeft
394 end.
395
271396 -spec terminate(#state{}, any(), non_neg_integer()) -> no_return().
272 terminate(#state{shutdown=brutal_kill}, Reason, _) ->
397 terminate(#state{shutdown=brutal_kill, id=Id,
398 stats_counters_ref=StatsCounters}, Reason, NbChildren) ->
273399 kill_children(get_keys(active)),
274400 kill_children(get_keys(removed)),
401 inc_terminate(StatsCounters, Id, NbChildren),
275402 exit(Reason);
276403 %% Attempt to gracefully shutdown all children.
277 terminate(#state{shutdown=Shutdown}, Reason, NbChildren) ->
404 terminate(#state{shutdown=Shutdown, id=Id,
405 stats_counters_ref=StatsCounters}, Reason, NbChildren) ->
278406 shutdown_children(get_keys(active)),
279407 shutdown_children(get_keys(removed)),
280408 _ = if
284412 erlang:send_after(Shutdown, self(), kill)
285413 end,
286414 wait_children(NbChildren),
415 inc_terminate(StatsCounters, Id, NbChildren),
287416 exit(Reason).
417
418 inc_accept(StatsCounters, Id, N) ->
419 %% Accepts are counted in the odd indexes.
420 counters:add(StatsCounters, 2*Id-1, N).
421
422 inc_terminate(StatsCounters, Id, N) ->
423 %% Terminates are counted in the even indexes.
424 counters:add(StatsCounters, 2*Id, N).
288425
289426 %% Kill all children and then exit. We unlink first to avoid
290427 %% getting a message for each child getting killed.
333470 terminate(State, Reason, NbChildren).
334471
335472 -spec system_code_change(any(), _, _, _) -> {ok, any()}.
473 system_code_change({#state{parent=Parent, ref=Ref, conn_type=ConnType,
474 shutdown=Shutdown, transport=Transport, protocol=Protocol,
475 opts=Opts, handshake_timeout=HandshakeTimeout,
476 max_conns=MaxConns, logger=Logger}, CurConns, NbChildren,
477 Sleepers}, _, {down, _}, _) ->
478 {ok, {{state, Parent, Ref, ConnType, Shutdown, Transport, Protocol,
479 Opts, HandshakeTimeout, MaxConns, Logger}, CurConns, NbChildren,
480 Sleepers}};
481 system_code_change({{state, Parent, Ref, ConnType, Shutdown, Transport, Protocol,
482 Opts, HandshakeTimeout, MaxConns, Logger}, CurConns, NbChildren,
483 Sleepers}, _, _, _) ->
484 Self = self(),
485 [Id] = [Id || {Id, Pid} <- ranch_server:get_connections_sups(Ref), Pid=:=Self],
486 StatsCounters = ranch_server:get_stats_counters(Ref),
487 {ok, {#state{parent=Parent, ref=Ref, id=Id, conn_type=ConnType, shutdown=Shutdown,
488 transport=Transport, protocol=Protocol, opts=Opts,
489 handshake_timeout=HandshakeTimeout, max_conns=MaxConns,
490 stats_counters_ref=StatsCounters,
491 logger=Logger}, CurConns, NbChildren, Sleepers}};
336492 system_code_change(Misc, _, _, _) ->
337493 {ok, Misc}.
338494
0 %% Copyright (c) 2019-2020, Jan Uhlig <ju@mailingwork.de>
0 %% Copyright (c) 2019-2021, Jan Uhlig <juhlig@hnc-agency.org>
11 %%
22 %% Permission to use, copy, modify, and/or distribute this software for any
33 %% purpose with or without fee is hereby granted, provided that the above
3131 TransOpts = ranch_server:get_transport_options(Ref),
3232 NumAcceptors = maps:get(num_acceptors, TransOpts, 10),
3333 NumConnsSups = maps:get(num_conns_sups, TransOpts, NumAcceptors),
34 StatsCounters = counters:new(2*NumConnsSups, []),
35 ok = ranch_server:set_stats_counters(Ref, StatsCounters),
3436 ChildSpecs = [#{
3537 id => {ranch_conns_sup, N},
3638 start => {ranch_conns_sup, start_link, [Ref, N, Transport, TransOpts, Protocol, Logger]},
0 %% Copyright (c) 2018-2020, Loïc Hoguin <essen@ninenines.eu>
0 %% Copyright (c) 2018-2021, Loïc Hoguin <essen@ninenines.eu>
11 %%
22 %% Permission to use, copy, modify, and/or distribute this software for any
33 %% purpose with or without fee is hereby granted, provided that the above
0 %% Copyright (c) 2019-2020, Jan Uhlig <ju@mailingwork.de>
0 %% Copyright (c) 2019-2021, Jan Uhlig <juhlig@hnc-agency.org>
11 %%
22 %% Permission to use, copy, modify, and/or distribute this software for any
33 %% purpose with or without fee is hereby granted, provided that the above
0 %% Copyright (c) 2011-2020, Loïc Hoguin <essen@ninenines.eu>
0 %% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
11 %%
22 %% Permission to use, copy, modify, and/or distribute this software for any
33 %% purpose with or without fee is hereby granted, provided that the above
0 %% Copyright (c) 2012-2020, Loïc Hoguin <essen@ninenines.eu>
0 %% Copyright (c) 2012-2021, Loïc Hoguin <essen@ninenines.eu>
11 %%
22 %% Permission to use, copy, modify, and/or distribute this software for any
33 %% purpose with or without fee is hereby granted, provided that the above
0 %% Copyright (c) 2018-2020, Loïc Hoguin <essen@ninenines.eu>
0 %% Copyright (c) 2018-2021, Loïc Hoguin <essen@ninenines.eu>
11 %%
22 %% Permission to use, copy, modify, and/or distribute this software for any
33 %% purpose with or without fee is hereby granted, provided that the above
1616 -export([parse/1]).
1717 -export([header/1]).
1818 -export([header/2]).
19 -export([to_connection_info/1]).
1920
2021 -type proxy_info() :: #{
2122 %% Mandatory part.
829830 Test4 = Common#{ssl => #{
830831 client => [ssl, cert_conn, cert_sess],
831832 verified => true,
832 version => <<"TLSv1.3">>, %% Note that I'm not sure this example value is correct.
833 version => <<"TLSv1.3">>,
833834 cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>,
834835 sig_alg => <<"SHA256">>,
835836 key_alg => <<"RSA2048">>,
877878 {ok, Test, <<>>} = parse(iolist_to_binary(header(Test, #{padding => 123}))),
878879 ok.
879880 -endif.
881
882 %% Helper to convert proxy_info() to ssl:connection_info().
883 %%
884 %% Because there isn't a lot of fields common to both types
885 %% this only ends up returning the keys protocol, selected_cipher_suite
886 %% and sni_hostname *at most*.
887
888 -spec to_connection_info(proxy_info()) -> ssl:connection_info().
889 to_connection_info(ProxyInfo=#{ssl := SSL}) ->
890 ConnInfo0 = case ProxyInfo of
891 #{authority := Authority} ->
892 [{sni_hostname, Authority}];
893 _ ->
894 []
895 end,
896 ConnInfo = case SSL of
897 #{cipher := Cipher} ->
898 case ssl:str_to_suite(binary_to_list(Cipher)) of
899 {error, {not_recognized, _}} ->
900 ConnInfo0;
901 CipherInfo ->
902 [{selected_cipher_suite, CipherInfo}|ConnInfo0]
903 end;
904 _ ->
905 ConnInfo0
906 end,
907 %% https://www.openssl.org/docs/man1.1.1/man3/SSL_get_version.html
908 case SSL of
909 #{version := <<"TLSv1.3">>} -> [{protocol, 'tlsv1.3'}|ConnInfo];
910 #{version := <<"TLSv1.2">>} -> [{protocol, 'tlsv1.2'}|ConnInfo];
911 #{version := <<"TLSv1.1">>} -> [{protocol, 'tlsv1.1'}|ConnInfo];
912 #{version := <<"TLSv1">>} -> [{protocol, tlsv1}|ConnInfo];
913 #{version := <<"SSLv3">>} -> [{protocol, sslv3}|ConnInfo];
914 #{version := <<"SSLv2">>} -> [{protocol, sslv2}|ConnInfo];
915 %% <<"unknown">>, unsupported or missing version.
916 _ -> ConnInfo
917 end;
918 %% No SSL/TLS information available.
919 to_connection_info(_) ->
920 [].
921
922 -ifdef(TEST).
923 to_connection_info_test() ->
924 Common = #{
925 version => 2,
926 command => proxy,
927 transport_family => ipv4,
928 transport_protocol => stream,
929 src_address => {127, 0, 0, 1},
930 src_port => 1234,
931 dest_address => {10, 11, 12, 13},
932 dest_port => 23456
933 },
934 %% Version 1.
935 [] = to_connection_info(#{
936 version => 1,
937 command => proxy,
938 transport_family => undefined,
939 transport_protocol => undefined
940 }),
941 [] = to_connection_info(Common#{version => 1}),
942 %% Version 2, no ssl data.
943 [] = to_connection_info(#{
944 version => 2,
945 command => local
946 }),
947 [] = to_connection_info(#{
948 version => 2,
949 command => proxy,
950 transport_family => undefined,
951 transport_protocol => undefined
952 }),
953 [] = to_connection_info(Common),
954 [] = to_connection_info(#{
955 version => 2,
956 command => proxy,
957 transport_family => unix,
958 transport_protocol => dgram,
959 src_address => <<"/run/source.sock">>,
960 dest_address => <<"/run/destination.sock">>
961 }),
962 [] = to_connection_info(Common#{netns => <<"/var/run/netns/example">>}),
963 [] = to_connection_info(Common#{raw_tlvs => [
964 {16#ff, <<1, 2, 3, 4, 5, 6, 7, 8, 9, 0>>}
965 ]}),
966 %% Version 2, with ssl-related data.
967 [] = to_connection_info(Common#{alpn => <<"h2">>}),
968 %% The authority alone is not enough to deduce that this is SNI.
969 [] = to_connection_info(Common#{authority => <<"internal.example.org">>}),
970 [
971 {protocol, 'tlsv1.3'},
972 {selected_cipher_suite, #{
973 cipher := aes_128_gcm,
974 key_exchange := ecdhe_rsa,
975 mac := aead,
976 prf := sha256
977 }}
978 ] = to_connection_info(Common#{ssl => #{
979 client => [ssl, cert_conn, cert_sess],
980 verified => true,
981 version => <<"TLSv1.3">>,
982 cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>,
983 sig_alg => <<"SHA256">>,
984 key_alg => <<"RSA2048">>,
985 cn => <<"example.com">>
986 }}),
987 [
988 {protocol, 'tlsv1.3'},
989 {selected_cipher_suite, #{
990 cipher := aes_128_gcm,
991 key_exchange := ecdhe_rsa,
992 mac := aead,
993 prf := sha256
994 }},
995 {sni_hostname, <<"internal.example.org">>}
996 ] = to_connection_info(Common#{authority => <<"internal.example.org">>, ssl => #{
997 client => [ssl, cert_conn, cert_sess],
998 verified => true,
999 version => <<"TLSv1.3">>,
1000 cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>,
1001 sig_alg => <<"SHA256">>,
1002 key_alg => <<"RSA2048">>,
1003 cn => <<"example.com">>
1004 }}),
1005 ok.
1006 -endif.
0 %% Copyright (c) 2012-2020, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020, Jan Uhlig <j.uhlig@mailingwork.de>
0 %% Copyright (c) 2012-2021, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
22 %%
33 %% Permission to use, copy, modify, and/or distribute this software for any
44 %% purpose with or without fee is hereby granted, provided that the above
3131 -export([get_addr/1]).
3232 -export([set_max_connections/2]).
3333 -export([get_max_connections/1]).
34 -export([set_stats_counters/2]).
35 -export([get_stats_counters/1]).
3436 -export([set_transport_options/2]).
3537 -export([get_transport_options/1]).
3638 -export([set_protocol_options/2]).
7678 %% cases when calling stop_listener followed by get_connections_sup,
7779 %% we could end up with the pid still being returned, when we
7880 %% expected a crash (because the listener was stopped).
79 %% Deleting it explictly here removes any possible confusion.
81 %% Deleting it explicitly here removes any possible confusion.
8082 _ = ets:match_delete(?TAB, {{conns_sup, Ref, '_'}, '_'}),
83 _ = ets:delete(?TAB, {stats_counters, Ref}),
8184 %% Ditto for the listener supervisor.
8285 _ = ets:delete(?TAB, {listener_sup, Ref}),
8386 ok.
8588 -spec cleanup_connections_sups(ranch:ref()) -> ok.
8689 cleanup_connections_sups(Ref) ->
8790 _ = ets:match_delete(?TAB, {{conns_sup, Ref, '_'}, '_'}),
91 _ = ets:delete(?TAB, {stats_counters, Ref}),
8892 ok.
8993
9094 -spec set_connections_sup(ranch:ref(), non_neg_integer(), pid()) -> ok.
137141 -spec get_max_connections(ranch:ref()) -> ranch:max_conns().
138142 get_max_connections(Ref) ->
139143 ets:lookup_element(?TAB, {max_conns, Ref}, 2).
144
145 -spec set_stats_counters(ranch:ref(), counters:counters_ref()) -> ok.
146 set_stats_counters(Ref, Counters) ->
147 gen_server:call(?MODULE, {set_stats_counters, Ref, Counters}).
148
149 -spec get_stats_counters(ranch:ref()) -> counters:counters_ref().
150 get_stats_counters(Ref) ->
151 ets:lookup_element(?TAB, {stats_counters, Ref}, 2).
140152
141153 -spec set_transport_options(ranch:ref(), any()) -> ok.
142154 set_transport_options(Ref, TransOpts) ->
196208 handle_call({set_max_conns, Ref, MaxConns}, _, State) ->
197209 ets:insert(?TAB, {{max_conns, Ref}, MaxConns}),
198210 _ = [ConnsSup ! {set_max_conns, MaxConns} || {_, ConnsSup} <- get_connections_sups(Ref)],
211 {reply, ok, State};
212 handle_call({set_stats_counters, Ref, Counters}, _, State) ->
213 ets:insert(?TAB, {{stats_counters, Ref}, Counters}),
199214 {reply, ok, State};
200215 handle_call({set_trans_opts, Ref, Opts}, _, State) ->
201216 ets:insert(?TAB, {{trans_opts, Ref}, Opts}),
236251 ok.
237252
238253 -spec code_change(term() | {down, term()}, #state{}, term()) -> {ok, term()}.
254 code_change({down, _}, State, _Extra) ->
255 true = ets:match_delete(?TAB, {{stats_counters, '_'}, '_'}),
256 {ok, State};
239257 code_change(_OldVsn, State, _Extra) ->
240258 {ok, State}.
241259
0 %% Copyright (c) 2019-2020, Jan Uhlig <ju@mailingwork.de>
0 %% Copyright (c) 2019-2021, Jan Uhlig <juhlig@hnc-agency.org>
11 %%
22 %% Permission to use, copy, modify, and/or distribute this software for any
33 %% purpose with or without fee is hereby granted, provided that the above
0 %% Copyright (c) 2011-2020, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020, Jan Uhlig <j.uhlig@mailingwork.de>
0 %% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
2 %% Copyright (c) 2021, Maria Scott <maria-12648430@hnc-agency.org>
23 %%
34 %% Permission to use, copy, modify, and/or distribute this software for any
45 %% purpose with or without fee is hereby granted, provided that the above
116117 case lists:keymember(cert, 1, SocketOpts)
117118 orelse lists:keymember(certfile, 1, SocketOpts)
118119 orelse lists:keymember(sni_fun, 1, SocketOpts)
119 orelse lists:keymember(sni_hosts, 1, SocketOpts) of
120 orelse lists:keymember(sni_hosts, 1, SocketOpts)
121 orelse lists:keymember(user_lookup_fun, 1, SocketOpts) of
120122 true ->
121123 Logger = maps:get(logger, TransOpts, logger),
122124 do_listen(SocketOpts, Logger);
129131 SocketOpts2 = ranch:set_option_default(SocketOpts1, nodelay, true),
130132 SocketOpts3 = ranch:set_option_default(SocketOpts2, send_timeout, 30000),
131133 SocketOpts = ranch:set_option_default(SocketOpts3, send_timeout_close, true),
134 DisallowedOpts0 = disallowed_listen_options(),
135 DisallowedOpts = unsupported_tls_options(SocketOpts) ++ DisallowedOpts0,
132136 %% We set the port to 0 because it is given in the Opts directly.
133137 %% The port in the options takes precedence over the one in the
134138 %% first argument.
135 ssl:listen(0, ranch:filter_options(SocketOpts, disallowed_listen_options(),
139 ssl:listen(0, ranch:filter_options(SocketOpts, DisallowedOpts,
136140 [binary, {active, false}, {packet, raw}, {reuseaddr, true}], Logger)).
137141
138142 %% 'binary' and 'list' are disallowed but they are handled
142146 [alpn_advertised_protocols, client_preferred_next_protocols,
143147 fallback, server_name_indication, srp_identity
144148 |ranch_tcp:disallowed_listen_options()].
149
150 unsupported_tls_options(SocketOpts) ->
151 unsupported_tls_version_options(lists:usort(get_tls_versions(SocketOpts))).
152
153 unsupported_tls_version_options([tlsv1|_]) ->
154 [];
155 unsupported_tls_version_options(['tlsv1.1'|_]) ->
156 [beast_mitigation, padding_check];
157 unsupported_tls_version_options(['tlsv1.2'|_]) ->
158 [beast_mitigation, padding_check];
159 unsupported_tls_version_options(['tlsv1.3'|_]) ->
160 [beast_mitigation, client_renegotiation, next_protocols_advertised,
161 padding_check, psk_identity, reuse_session, reuse_sessions,
162 secure_renegotiate, user_lookup_fun];
163 unsupported_tls_version_options(_) ->
164 [].
145165
146166 -spec accept(ssl:sslsocket(), timeout())
147167 -> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}.
295315 end;
296316 cleanup(_) ->
297317 ok.
318
319 get_tls_versions(SocketOpts) ->
320 %% Socket options need to be reversed for keyfind because later options
321 %% take precedence when contained multiple times, but keyfind will return
322 %% the earliest occurence.
323 case lists:keyfind(versions, 1, lists:reverse(SocketOpts)) of
324 {versions, Versions} ->
325 Versions;
326 false ->
327 get_tls_versions_env()
328 end.
329
330 get_tls_versions_env() ->
331 case application:get_env(ssl, protocol_version) of
332 {ok, Versions} ->
333 Versions;
334 undefined ->
335 get_tls_versions_app()
336 end.
337
338 get_tls_versions_app() ->
339 {supported, Versions} = lists:keyfind(supported, 1, ssl:versions()),
340 Versions.
0 %% Copyright (c) 2011-2020, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020, Jan Uhlig <j.uhlig@mailingwork.de>
0 %% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
22 %%
33 %% Permission to use, copy, modify, and/or distribute this software for any
44 %% purpose with or without fee is hereby granted, provided that the above
0 %% Copyright (c) 2011-2020, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020, Jan Uhlig <j.uhlig@mailingwork.de>
0 %% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
22 %%
33 %% Permission to use, copy, modify, and/or distribute this software for any
44 %% purpose with or without fee is hereby granted, provided that the above
8989 listen(TransOpts) ->
9090 ok = cleanup(TransOpts),
9191 Logger = maps:get(logger, TransOpts, logger),
92 SocketOpts0 = maps:get(socket_opts, TransOpts, []),
92 SocketOpts = maps:get(socket_opts, TransOpts, []),
93 %% We set the port to 0 because it is given in the Opts directly.
94 %% The port in the options takes precedence over the one in the
95 %% first argument.
96 gen_tcp:listen(0, prepare_socket_opts(SocketOpts, Logger)).
97
98 prepare_socket_opts([Backend = {inet_backend, _}|SocketOpts], Logger) ->
99 %% In OTP/23, the inet_backend option may be used to activate the
100 %% experimental socket backend for inet/gen_tcp. If present, it must
101 %% be the first option in the list.
102 [Backend|prepare_socket_opts(SocketOpts, Logger)];
103 prepare_socket_opts(SocketOpts0, Logger) ->
93104 SocketOpts1 = ranch:set_option_default(SocketOpts0, backlog, 1024),
94105 SocketOpts2 = ranch:set_option_default(SocketOpts1, nodelay, true),
95106 SocketOpts3 = ranch:set_option_default(SocketOpts2, send_timeout, 30000),
96107 SocketOpts4 = ranch:set_option_default(SocketOpts3, send_timeout_close, true),
97 %% We set the port to 0 because it is given in the Opts directly.
98 %% The port in the options takes precedence over the one in the
99 %% first argument.
100 gen_tcp:listen(0, ranch:filter_options(SocketOpts4, disallowed_listen_options(),
101 [binary, {active, false}, {packet, raw}, {reuseaddr, true}], Logger)).
108 ranch:filter_options(SocketOpts4, disallowed_listen_options(),
109 [binary, {active, false}, {packet, raw}, {reuseaddr, true}], Logger).
102110
103111 %% 'binary' and 'list' are disallowed but they are handled
104112 %% specifically as they do not have 2-tuple equivalents.
0 %% Copyright (c) 2012-2020, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020, Jan Uhlig <j.uhlig@mailingwork.de>
0 %% Copyright (c) 2012-2021, Loïc Hoguin <essen@ninenines.eu>
1 %% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org>
22 %%
33 %% Permission to use, copy, modify, and/or distribute this software for any
44 %% purpose with or without fee is hereby granted, provided that the above
0 RabbitMQ Server 3.9.4
1 rabbitmq_server_release 5b3b08af16858b74d7bc28cad98200313749d6d0
2 accept 5b3b08af16858b74d7bc28cad98200313749d6d0
3 amqp10_client 5b3b08af16858b74d7bc28cad98200313749d6d0
4 amqp10_common 5b3b08af16858b74d7bc28cad98200313749d6d0
5 amqp_client 5b3b08af16858b74d7bc28cad98200313749d6d0
6 aten 5b3b08af16858b74d7bc28cad98200313749d6d0
7 base64url 5b3b08af16858b74d7bc28cad98200313749d6d0
8 cowboy 5b3b08af16858b74d7bc28cad98200313749d6d0
9 cowlib 5b3b08af16858b74d7bc28cad98200313749d6d0
10 credentials_obfuscation 5b3b08af16858b74d7bc28cad98200313749d6d0
11 cuttlefish 5b3b08af16858b74d7bc28cad98200313749d6d0
12 eetcd 5b3b08af16858b74d7bc28cad98200313749d6d0
0 RabbitMQ Server 3.9.7
1 rabbitmq_server_release 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
2 accept 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
3 amqp10_client 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
4 amqp10_common 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
5 amqp_client 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
6 aten 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
7 base64url 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
8 cowboy 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
9 cowlib 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
10 credentials_obfuscation 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
11 cuttlefish 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
12 eetcd 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
1313 elvis_mk ac5ad774c0e317c8bfd0f5cca4153dd291a40ed1 master
14 enough 5b3b08af16858b74d7bc28cad98200313749d6d0
15 gen_batch_server 5b3b08af16858b74d7bc28cad98200313749d6d0
16 getopt 5b3b08af16858b74d7bc28cad98200313749d6d0
17 gun 5b3b08af16858b74d7bc28cad98200313749d6d0
14 enough 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
15 gen_batch_server 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
16 getopt 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
17 gun 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
1818 jose 2b1d66b5f4fbe33cb198149a8cb23895a2c877ea
19 jsx 5b3b08af16858b74d7bc28cad98200313749d6d0
20 observer_cli 5b3b08af16858b74d7bc28cad98200313749d6d0
21 osiris c940a5113537c92f874fd3f6135c7df38e85292c v1.1.0
22 prometheus 5b3b08af16858b74d7bc28cad98200313749d6d0
23 quantile_estimator 5b3b08af16858b74d7bc28cad98200313749d6d0
24 ra 5b3b08af16858b74d7bc28cad98200313749d6d0
25 rabbit 5b3b08af16858b74d7bc28cad98200313749d6d0
26 rabbitmq_prelaunch 5b3b08af16858b74d7bc28cad98200313749d6d0
27 rabbit_common 5b3b08af16858b74d7bc28cad98200313749d6d0
28 rabbitmq_amqp1_0 5b3b08af16858b74d7bc28cad98200313749d6d0
29 rabbitmq_auth_backend_cache 5b3b08af16858b74d7bc28cad98200313749d6d0
30 rabbitmq_auth_backend_http 5b3b08af16858b74d7bc28cad98200313749d6d0
31 rabbitmq_auth_backend_ldap 5b3b08af16858b74d7bc28cad98200313749d6d0
32 rabbitmq_auth_backend_oauth2 5b3b08af16858b74d7bc28cad98200313749d6d0
33 rabbitmq_auth_mechanism_ssl 5b3b08af16858b74d7bc28cad98200313749d6d0
34 rabbitmq_aws 5b3b08af16858b74d7bc28cad98200313749d6d0
35 rabbitmq_cli 5b3b08af16858b74d7bc28cad98200313749d6d0
36 rabbitmq_codegen 5b3b08af16858b74d7bc28cad98200313749d6d0
37 rabbitmq_consistent_hash_exchange 5b3b08af16858b74d7bc28cad98200313749d6d0
38 rabbitmq_event_exchange 5b3b08af16858b74d7bc28cad98200313749d6d0
39 rabbitmq_federation 5b3b08af16858b74d7bc28cad98200313749d6d0
40 rabbitmq_federation_management 5b3b08af16858b74d7bc28cad98200313749d6d0
41 rabbitmq_jms_topic_exchange 5b3b08af16858b74d7bc28cad98200313749d6d0
42 rabbitmq_management 5b3b08af16858b74d7bc28cad98200313749d6d0
43 rabbitmq_management_agent 5b3b08af16858b74d7bc28cad98200313749d6d0
44 rabbitmq_mqtt 5b3b08af16858b74d7bc28cad98200313749d6d0
45 rabbitmq_peer_discovery_aws 5b3b08af16858b74d7bc28cad98200313749d6d0
46 rabbitmq_peer_discovery_common 5b3b08af16858b74d7bc28cad98200313749d6d0
47 rabbitmq_peer_discovery_consul 5b3b08af16858b74d7bc28cad98200313749d6d0
48 rabbitmq_peer_discovery_etcd 5b3b08af16858b74d7bc28cad98200313749d6d0
49 rabbitmq_peer_discovery_k8s 5b3b08af16858b74d7bc28cad98200313749d6d0
50 rabbitmq_prometheus 5b3b08af16858b74d7bc28cad98200313749d6d0
51 rabbitmq_random_exchange 5b3b08af16858b74d7bc28cad98200313749d6d0
52 rabbitmq_recent_history_exchange 5b3b08af16858b74d7bc28cad98200313749d6d0
53 rabbitmq_sharding 5b3b08af16858b74d7bc28cad98200313749d6d0
54 rabbitmq_shovel 5b3b08af16858b74d7bc28cad98200313749d6d0
55 rabbitmq_shovel_management 5b3b08af16858b74d7bc28cad98200313749d6d0
56 rabbitmq_stomp 5b3b08af16858b74d7bc28cad98200313749d6d0
57 rabbitmq_stream 5b3b08af16858b74d7bc28cad98200313749d6d0
58 rabbitmq_stream_common 5b3b08af16858b74d7bc28cad98200313749d6d0
59 rabbitmq_stream_management 5b3b08af16858b74d7bc28cad98200313749d6d0
60 rabbitmq_top 5b3b08af16858b74d7bc28cad98200313749d6d0
61 rabbitmq_tracing 5b3b08af16858b74d7bc28cad98200313749d6d0
62 rabbitmq_trust_store 5b3b08af16858b74d7bc28cad98200313749d6d0
63 rabbitmq_web_dispatch 5b3b08af16858b74d7bc28cad98200313749d6d0
64 rabbitmq_web_mqtt 5b3b08af16858b74d7bc28cad98200313749d6d0
65 rabbitmq_web_mqtt_examples 5b3b08af16858b74d7bc28cad98200313749d6d0
66 rabbitmq_web_stomp 5b3b08af16858b74d7bc28cad98200313749d6d0
67 rabbitmq_web_stomp_examples 5b3b08af16858b74d7bc28cad98200313749d6d0
68 ranch 5b3b08af16858b74d7bc28cad98200313749d6d0
69 recon 5b3b08af16858b74d7bc28cad98200313749d6d0
19 jsx 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
20 observer_cli 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
21 osiris 654f5dc22a62f243da16839b921d6bf33173078f v1.2.0
22 prometheus 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
23 quantile_estimator 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
24 ra 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
25 rabbit 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
26 rabbitmq_prelaunch 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
27 rabbit_common 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
28 rabbitmq_amqp1_0 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
29 rabbitmq_auth_backend_cache 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
30 rabbitmq_auth_backend_http 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
31 rabbitmq_auth_backend_ldap 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
32 rabbitmq_auth_backend_oauth2 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
33 rabbitmq_auth_mechanism_ssl 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
34 rabbitmq_aws 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
35 rabbitmq_cli 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
36 rabbitmq_codegen 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
37 rabbitmq_consistent_hash_exchange 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
38 rabbitmq_event_exchange 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
39 rabbitmq_federation 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
40 rabbitmq_federation_management 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
41 rabbitmq_jms_topic_exchange 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
42 rabbitmq_management 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
43 rabbitmq_management_agent 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
44 rabbitmq_mqtt 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
45 rabbitmq_peer_discovery_aws 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
46 rabbitmq_peer_discovery_common 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
47 rabbitmq_peer_discovery_consul 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
48 rabbitmq_peer_discovery_etcd 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
49 rabbitmq_peer_discovery_k8s 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
50 rabbitmq_prometheus 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
51 rabbitmq_random_exchange 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
52 rabbitmq_recent_history_exchange 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
53 rabbitmq_sharding 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
54 rabbitmq_shovel 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
55 rabbitmq_shovel_management 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
56 rabbitmq_stomp 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
57 rabbitmq_stream 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
58 rabbitmq_stream_common 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
59 rabbitmq_stream_management 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
60 rabbitmq_top 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
61 rabbitmq_tracing 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
62 rabbitmq_trust_store 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
63 rabbitmq_web_dispatch 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
64 rabbitmq_web_mqtt 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
65 rabbitmq_web_mqtt_examples 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
66 rabbitmq_web_stomp 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
67 rabbitmq_web_stomp_examples 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
68 ranch 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
69 recon 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
7070 seshat 220335c03bd0fd761be276cbfa69f3ec5c3b6b56 0.1.0
71 stdout_formatter 5b3b08af16858b74d7bc28cad98200313749d6d0
71 stdout_formatter 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
7272 syslog 67704aec9e7ee2ef9c69d20152b09825d7e6a236 4.0.0
73 sysmon_handler 5b3b08af16858b74d7bc28cad98200313749d6d0
74 systemd 5b3b08af16858b74d7bc28cad98200313749d6d0
73 sysmon_handler 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
74 systemd 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2
116116 dep_looking_glass = git https://github.com/rabbitmq/looking_glass master
117117 dep_prometheus = hex 4.8.1
118118 dep_ra = hex 2.0.0
119 dep_ranch = hex 2.0.0
119 dep_ranch = hex 2.1.0
120120 dep_recon = hex 2.5.1
121 dep_observer_cli = hex 1.6.2
121 dep_observer_cli = hex 1.7.1
122122 dep_stdout_formatter = hex 0.2.4
123123 dep_sysmon_handler = hex 1.3.0
124124
168168 "RABBITMQCTL": "$TEST_SRCDIR/$TEST_WORKSPACE/{}/broker-for-tests-home/sbin/rabbitmqctl".format(package),
169169 "RABBITMQ_PLUGINS": "$TEST_SRCDIR/$TEST_WORKSPACE/{}/broker-for-tests-home/sbin/rabbitmq-plugins".format(package),
170170 "RABBITMQ_QUEUES": "$TEST_SRCDIR/$TEST_WORKSPACE/{}/broker-for-tests-home/sbin/rabbitmq-queues".format(package),
171 "RABBITMQ_RUN_SECONDARY": "$TEST_SRCDIR/rabbitmq-server-generic-unix-3.8.18/rabbitmq-run",
171 "RABBITMQ_RUN_SECONDARY": "$TEST_SRCDIR/rabbitmq-server-generic-unix-3.8.22/rabbitmq-run",
172172 }.items() + test_env.items()),
173173 tools = [
174174 ":rabbitmq-for-tests-run",
175 "@rabbitmq-server-generic-unix-3.8.18//:rabbitmq-run",
175 "@rabbitmq-server-generic-unix-3.8.22//:rabbitmq-run",
176176 ] + tools,
177177 runtime_deps = [
178178 "//deps/rabbitmq_cli:elixir_as_bazel_erlang_lib",
22 RabbitmqHomeInfo = provider(
33 doc = "An assembled RABBITMQ_HOME dir",
44 fields = {
5 "sbin": "Files making up the sbin dir",
6 "escript": "Files making up the escript dir",
7 "plugins": "Files making up the plugins dir",
5 "rabbitmqctl": "rabbitmqctl script from the sbin directory",
86 },
97 )
108
111109
112110 plugins = _flatten([_plugins_dir_links(ctx, plugin) for plugin in plugins])
113111
112 rabbitmqctl = None
113 for script in scripts:
114 if script.basename == "rabbitmqctl":
115 rabbitmqctl = script
116 if rabbitmqctl == None:
117 fail("could not find rabbitmqct among", scripts)
118
114119 return [
115120 RabbitmqHomeInfo(
116 sbin = scripts,
117 escript = escripts,
118 plugins = plugins,
121 rabbitmqctl = rabbitmqctl,
119122 ),
120123 DefaultInfo(
121124 files = depset(scripts + escripts + plugins),
141144 "plugins": attr.label_list(),
142145 },
143146 )
147
148 def _dirname(p):
149 return p.rpartition("/")[0]
150
151 def rabbitmq_home_short_path(rabbitmq_home):
152 short_path = rabbitmq_home[RabbitmqHomeInfo].rabbitmqctl.short_path
153 if rabbitmq_home.label.workspace_root != "":
154 short_path = path_join(rabbitmq_home.label.workspace_root, short_path)
155 return _dirname(_dirname(short_path))
00 load("@//:rabbitmq_home.bzl", "RabbitmqHomeInfo")
11
22 def _impl(ctx):
3 scripts = ctx.files.sbin
4 escripts = ctx.files.escript
5 plugins = ctx.files.plugins
6
73 return [
84 RabbitmqHomeInfo(
9 sbin = scripts,
10 escript = escripts,
11 plugins = plugins,
5 rabbitmqctl = ctx.file.rabbitmqctl,
126 ),
137 DefaultInfo(
14 files = depset(scripts + escripts + plugins),
8 files = depset(ctx.files.rabbitmqctl + ctx.files.additional_files),
159 ),
1610 ]
1711
1812 rabbitmq_package_generic_unix = rule(
1913 implementation = _impl,
2014 attrs = {
21 "sbin": attr.label_list(allow_files = True),
22 "escript": attr.label_list(allow_files = True),
23 "plugins": attr.label_list(allow_files = True),
15 "rabbitmqctl": attr.label(allow_single_file = True),
16 "additional_files": attr.label_list(allow_files = True),
2417 },
2518 )
00 load("@bazel-erlang//:erlang_home.bzl", "ErlangHomeProvider", "ErlangVersionProvider")
11 load("@bazel-erlang//:bazel_erlang_lib.bzl", "path_join")
22 load("@bazel-erlang//:ct.bzl", "sanitize_sname")
3 load(":rabbitmq_home.bzl", "RabbitmqHomeInfo")
4
5 def _dirname(p):
6 return p.rpartition("/")[0]
7
8 def _rabbitmq_home_info_root_short_path(rabbitmq_home):
9 return _dirname(_dirname(rabbitmq_home.sbin[0].short_path))
3 load(":rabbitmq_home.bzl", "RabbitmqHomeInfo", "rabbitmq_home_short_path")
104
115 def _impl(ctx):
12 rabbitmq_home = ctx.attr.home[RabbitmqHomeInfo]
6 rabbitmq_home_path = rabbitmq_home_short_path(ctx.attr.home)
137
14 root = _rabbitmq_home_info_root_short_path(rabbitmq_home)
15
16 erl_libs = [path_join(root, "plugins")]
8 # the rabbitmq-run.sh template only allows a single erl_libs currently
9 erl_libs = [path_join(rabbitmq_home_path, "plugins")]
1710
1811 ctx.actions.expand_template(
1912 template = ctx.file._template,
2013 output = ctx.outputs.executable,
2114 substitutions = {
22 "{RABBITMQ_HOME}": root,
15 "{RABBITMQ_HOME}": rabbitmq_home_path,
2316 "{ERL_LIBS}": ":".join(erl_libs),
2417 "{ERLANG_HOME}": ctx.attr._erlang_home[ErlangHomeProvider].path,
2518 "{SNAME}": sanitize_sname("sbb-" + ctx.attr.name),
00 load("@bazel-erlang//:erlang_home.bzl", "ErlangVersionProvider")
1 load(":rabbitmq_home.bzl", "RabbitmqHomeInfo")
1 load(":rabbitmq_home.bzl", "RabbitmqHomeInfo", "rabbitmq_home_short_path")
22
33 def _impl(ctx):
44 erlang_version = ctx.attr._erlang_version[ErlangVersionProvider].version
55
6 rabbitmq_home = ctx.attr.home[RabbitmqHomeInfo]
6 rabbitmq_home_path = rabbitmq_home_short_path(ctx.attr.home)
77
88 script = """
99 exec ./{home}/sbin/{cmd} $@
1010 """.format(
11 home = ctx.attr.home.label.name,
11 home = rabbitmq_home_path,
1212 cmd = ctx.label.name,
1313 )
1414
0 RabbitMQ `3.9.5` is a maintenance release in the `3.9.x` release series.
1
2 Please refer to the **Upgrading to 3.9** section from [v3.9.0 release notes](https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.9.0) if upgrading from a version prior to 3.9.0.
3
4 This release requires at least Erlang 23.2, and supports the latest Erlang 24 version, 24.0.5 at the time of release. [RabbitMQ and Erlang/OTP Compatibility Matrix](https://www.rabbitmq.com/which-erlang.html) has more details on Erlang version requirements for RabbitMQ.
5
6
7 ## Changes Worth Mentioning
8
9 Release notes are kept under [rabbitmq-server/release-notes](https://github.com/rabbitmq/rabbitmq-server/tree/v3.9.x/release-notes).
10 Contributors are encouraged to update them together with their changes. This helps with release automation and more
11 consistent release schedule.
12
13 ### Core Server
14
15 #### Bug Fixes
16
17 * Virtual host metadata (description, tags) was not imported from definitions.
18
19 GitHub issue: [#3333](https://github.com/rabbitmq/rabbitmq-server/pull/3333)
20
21 * Reduced unnecessary debug logging from streams.
22
23 GitHub issue: [#3279](https://github.com/rabbitmq/rabbitmq-server/pull/3279)
24
25
26 ### AWS Peer Discovery Plugin
27
28 #### Enhancements
29
30 * AWS API calls are now retried multiple times.
31
32 Contributed by AWS.
33
34 GitHub issue: [#3329](https://github.com/rabbitmq/rabbitmq-server/pull/3329)
35
36
37 ### Management Plugin
38
39 #### Enhancements
40
41 * `PUT /api/vhosts/{name}` now can update metadata (tags and descriptions) for existing
42 virtual hosts.
43
44 GitHub issue: [#3319](https://github.com/rabbitmq/rabbitmq-server/pull/3319)
45
46
47
48 ## Dependency Upgrades
49
50 No dependency changes in this release.
51
52
53 ## Source Code Archives
54
55 To obtain source code of the entire distribution, please download the archive named `rabbitmq-server-3.9.5.tar.xz` instead of the source tarball produced by GitHub.
0 RabbitMQ `3.9.6` is a maintenance release in the `3.9.x` release series.
1
2 Please refer to the **Upgrading to 3.9** section from [v3.9.0 release notes](https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.9.0) if upgrading from a version prior to 3.9.0.
3
4 This release requires at least Erlang 23.2, and supports the latest Erlang 24 version, 24.0.5 at the time of release. [RabbitMQ and Erlang/OTP Compatibility Matrix](https://www.rabbitmq.com/which-erlang.html) has more details on Erlang version requirements for RabbitMQ.
5
6
7
8 ## Changes Worth Mentioning
9
10 Release notes are kept under [rabbitmq-server/release-notes](https://github.com/rabbitmq/rabbitmq-server/tree/v3.9.x/release-notes).
11 Contributors are encouraged to update them together with their changes. This helps with release automation and a more consistent release schedule.
12
13
14 ### Core Server
15
16 #### Bug Fixes
17
18 * TLS information delivered in [Proxy protocol](https://www.rabbitmq.com/networking.html#proxy-protocol) header is now attached to connection metrics as if it was provided by a non-proxying client.
19
20 GitHub issues: [#3175](https://github.com/rabbitmq/rabbitmq-server/pull/3175), [#3371](https://github.com/rabbitmq/rabbitmq-server/pull/3371) contributed by @prefiks, sponsored by CloudAMQP
21
22 * `max_message_size` had a one-off error in the validator.
23
24 GitHub issue: [#3398](https://github.com/rabbitmq/rabbitmq-server/pull/3398)
25
26 * `mirroring_sync_batch_size` was incorrectly validated as if it represented batch size in bytes.
27 It represents batch size in number of messages, so the new default hard cap is now 1M (a very high number that's impractical)
28
29 GitHub issue: [#3398](https://github.com/rabbitmq/rabbitmq-server/pull/3398)
30
31 ### Stream Plugin
32
33 #### Bug Fixes
34
35 * Offset parameters were not stored correctly in some cases.
36
37 GitHub issue: [#3360](https://github.com/rabbitmq/rabbitmq-server/pull/3360), contributed by @korsmakolnikov
38
39 * Partitions list order is now stable.
40
41 GitHub issue: [#3423](https://github.com/rabbitmq/rabbitmq-server/pull/3423)
42
43 * When stream clients close connections abruptly, publisher and consumer metrics get cleaned up correctly.
44
45 GitHub issue: [#3340](https://github.com/rabbitmq/rabbitmq-server/pull/3340)
46
47 ### Management Plugin
48
49 #### Enhancements
50
51 * Stream publishers are now listed on the individual stream page.
52
53 GitHub issue: [#3389](https://github.com/rabbitmq/rabbitmq-server/issues/3389)
54
55 * Counters have been added to the tiles of several sections on detail pages.
56
57 GitHub issue: [#3422](https://github.com/rabbitmq/rabbitmq-server/pull/3422)
58
59
60 ## Dependency Upgrades
61
62 * Osiris was [upgraded to `1.2.0`](https://github.com/rabbitmq/osiris/compare/v1.1.0...v1.2.0)
63 * Ranch was [upgraded to `2.1.0`](https://github.com/ninenines/ranch/compare/2.0.0...2.1.0)
64
65
66 ## Source Code Archives
67
68 To obtain source code of the entire distribution, please download the archive named `rabbitmq-server-3.9.6.tar.xz` instead of the source tarball produced by GitHub.
0 RabbitMQ `3.9.7` is a maintenance release in the `3.9.x` release series.
1
2 Please refer to the **Upgrading to 3.9** section from [v3.9.0 release notes](https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.9.0) if upgrading from a version prior to 3.9.0.
3
4 This release requires at least Erlang 23.2, and supports the latest Erlang 24 version, 24.0.5 at the time of release. [RabbitMQ and Erlang/OTP Compatibility Matrix](https://www.rabbitmq.com/which-erlang.html) has more details on Erlang version requirements for RabbitMQ.
5
6
7
8 ## Changes Worth Mentioning
9
10 Release notes are kept under [rabbitmq-server/release-notes](https://github.com/rabbitmq/rabbitmq-server/tree/v3.9.x/release-notes).
11 Contributors are encouraged to update them together with their changes. This helps with release automation and a more consistent release schedule.
12
13 ### All Components
14
15 * All bytecode is now compiled using the `+deterministic` compiler flag. This should eliminate the capture of some irrelevant build environment attributes in produced artifacts, improve consistency between builds, and reduce the file level diff between release artifacts.
16
17 GitHub issue: [#3442](https://github.com/rabbitmq/rabbitmq-server/pull/3442)
18
19
20 ### Core Server
21
22 #### Enhancements
23
24 * Classic queue shutdown now uses a much higher timeout (up to 10 minutes instead of 30 seconds).
25
26 In environments with many queues (especially mirrored queues) and many consumers this means that
27 the chance of queue indices rebuilding after node restart is now substantially lower.
28
29 GitHub issue: [#3409](https://github.com/rabbitmq/rabbitmq-server/pull/3409)
30
31
32 ### Prometheus Plugin
33
34 #### Enhancements
35
36 * More configurability for metrics exposed via the Prometheus endpoint.
37
38 GitHub issue: [#3421](https://github.com/rabbitmq/rabbitmq-server/pull/3421)
39
40
41 ### Shovel Plugin
42
43 #### Bug Fixes
44
45 * Shovel URIs could be logged with credentials in some scenarios.
46
47 GitHub issue: [#3476](https://github.com/rabbitmq/rabbitmq-server/pull/3476), contributed by @thuandb (AWS)
48
49
50 ## Dependency Upgrades
51
52 * observer_cli has been upgraded from `1.6.2` to `1.7.1`
53
54
55 ## Source Code Archives
56
57 To obtain source code of the entire distribution, please download the archive named `rabbitmq-server-3.9.7.tar.xz` instead of the source tarball produced by GitHub.
00 #!/usr/bin/env bash
11 set -euo pipefail
2
3 rmq_realpath() {
4 local path=$1
5
6 if [ -d "$path" ]; then
7 cd "$path" && pwd
8 elif [ -f "$path" ]; then
9 cd "$(dirname "$path")" && echo $(pwd)/$(basename "$path")
10 else
11 echo "$path"
12 fi
13 }
214
315 if [ -z ${TEST_SRCDIR+x} ]; then
416 BASE_DIR=$PWD
4052 fi
4153
4254 TEST_TMPDIR=${TEST_TMPDIR:=${TMPDIR}/rabbitmq-test-instances}
43 RABBITMQ_SCRIPTS_DIR=${BASE_DIR}/{RABBITMQ_HOME}/sbin
55 RABBITMQ_SCRIPTS_DIR="$(rmq_realpath ${BASE_DIR}/{RABBITMQ_HOME}/sbin)"
4456 RABBITMQ_PLUGINS=${RABBITMQ_SCRIPTS_DIR}/rabbitmq-plugins
4557 RABBITMQ_SERVER=${RABBITMQ_SCRIPTS_DIR}/rabbitmq-server
46 RABBITMQCTL=${RABBITMQ_SCRIPTS_DIR}/rabbitmqzctl
58 RABBITMQCTL=${RABBITMQ_SCRIPTS_DIR}/rabbitmqctl
4759
4860 export RABBITMQ_SCRIPTS_DIR RABBITMQCTL RABBITMQ_PLUGINS RABBITMQ_SERVER
4961
66 # adding "--spawn_strategy=local" to the invocation is a workaround
77 build --spawn_strategy=local
88
9 # run one test at a time on the local machine
10 build --test_strategy=exclusive
11
129 # don't re-run flakes automatically on the local machine
1310 build --flaky_test_attempts=1
1411
163163
164164 hex_pm_bazel_erlang_lib(
165165 name = "observer_cli",
166 version = "1.6.1",
167 sha256 = "3418e319764b9dff1f469e43cbdffd7fd54ea47cbf765027c557abd146a19fb3",
166 version = "1.7.1",
167 sha256 = "4ccafaaa2ce01b85ddd14591f4d5f6731b4e13b610a70fb841f0701178478280",
168168 )
169169
170170 github_bazel_erlang_lib(
227227
228228 hex_archive(
229229 name = "ranch",
230 version = "2.0.0",
231 sha256 = "c20a4840c7d6623c19812d3a7c828b2f1bd153ef0f124cb69c54fe51d8a42ae0",
230 version = "2.1.0",
231 sha256 = "244ee3fa2a6175270d8e1fc59024fd9dbc76294a321057de8f803b1479e76916",
232232 build_file = rabbitmq_workspace + "//:BUILD.ranch",
233233 )
234234