New upstream release.
Debian Janitor
2 years ago
0 | 0 | build --incompatible_strict_action_env |
1 | build --local_test_jobs=1 | |
1 | 2 | |
2 | 3 | build:buildbuddy --bes_results_url=https://app.buildbuddy.io/invocation/ |
3 | 4 | build:buildbuddy --bes_backend=grpcs://cloud.buildbuddy.io |
26 | 26 | exec_properties = { |
27 | 27 | "OSFamily": "Linux", |
28 | 28 | # linux-erlang-23.3 |
29 | "container-image": "docker://pivotalrabbitmq/rabbitmq-server-buildenv@sha256:16439a9c4a519d39acc098fdc4d4002f60d6f19762423e71eac24af85f5fddb7", | |
29 | "container-image": "docker://pivotalrabbitmq/rabbitmq-server-buildenv@sha256:5de95518e8d5f3724839ad46e450b80d89cb0e7e546872a63b7ce4fd482a696e", | |
30 | 30 | }, |
31 | 31 | ) |
32 | 32 |
0 | 0 | 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") | |
2 | 3 | |
3 | 4 | rabbitmq_package_generic_unix( |
4 | 5 | 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", | |
11 | 18 | ) |
12 | 19 | |
13 | 20 | rabbitmq_run( |
15 | 22 | home = ":broker-home", |
16 | 23 | visibility = ["//visibility:public"], |
17 | 24 | ) |
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 | ) |
31 | 31 | app_file( |
32 | 32 | name = "app_file", |
33 | 33 | app_name = "ranch", |
34 | app_src = ["src/ranch.app.src"], | |
34 | app_version = "2.1.0", | |
35 | 35 | modules = [":first_beam_files", ":beam_files"], |
36 | 36 | ) |
37 | 37 |
30 | 30 | |
31 | 31 | http_archive( |
32 | 32 | 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"], | |
36 | 36 | ) |
37 | 37 | |
38 | 38 | load("@bazel-erlang//:bazel_erlang.bzl", "bazel_erlang_deps") |
45 | 45 | |
46 | 46 | git_repository( |
47 | 47 | name = "rabbitmq_ct_helpers", |
48 | branch = "v3.9.x", | |
48 | commit = "f709a6f6a2345d3ab5076469cb8d8e42e89b2b59", | |
49 | 49 | remote = "https://github.com/rabbitmq/rabbitmq-ct-helpers.git", |
50 | 50 | repo_mapping = { |
51 | 51 | "@rabbitmq-server": "@", |
54 | 54 | |
55 | 55 | git_repository( |
56 | 56 | name = "rabbitmq_ct_client_helpers", |
57 | branch = "v3.9.x", | |
57 | commit = "51ed1e59d725816cd82a3bd6211c043d385f180e", | |
58 | 58 | remote = "https://github.com/rabbitmq/rabbitmq-ct-client-helpers.git", |
59 | 59 | repo_mapping = { |
60 | 60 | "@rabbitmq-server": "@", |
89 | 89 | """ |
90 | 90 | |
91 | 91 | http_archive( |
92 | name = "rabbitmq-server-generic-unix-3.8.18", | |
92 | name = "rabbitmq-server-generic-unix-3.8.22", | |
93 | 93 | build_file = "@//:BUILD.package_generic_unix", |
94 | 94 | 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"], | |
97 | 97 | ) |
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 | ||
0 | 6 | rabbitmq-server (3.9.4-1.2) unstable; urgency=medium |
1 | 7 | |
2 | 8 | * Non-maintainer upload. |
0 | 0 | [{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}]. |
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
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.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',[]}}]}. |
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
0 | ~> 1.0⏎ |
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
deps/.mix/archives/hex-0.21.3/hex-0.21.3/ebin/Elixir.Hex.Version.InvalidRequirementError.beam
less
more
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
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',[]}}]}. |
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
39 | 39 | deps = DEPS, |
40 | 40 | ) |
41 | 41 | |
42 | xref(tags = ["xref"]) | |
42 | xref( | |
43 | additional_libs = [ | |
44 | "@ranch//:bazel_erlang_lib", | |
45 | ], | |
46 | tags = ["xref"], | |
47 | ) | |
43 | 48 | |
44 | 49 | plt( |
45 | 50 | name = "base_plt", |
194 | 194 | |
195 | 195 | maybe_ssl_info(Sock) -> |
196 | 196 | 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) | |
200 | 200 | end. |
201 | 201 | |
202 | ssl_info(Sock) -> | |
202 | ssl_info(Info) -> | |
203 | 203 | {Protocol, KeyExchange, Cipher, Hash} = |
204 | case rabbit_net:ssl_info(Sock) of | |
204 | case Info of | |
205 | 205 | {ok, Infos} -> |
206 | 206 | {_, P} = lists:keyfind(protocol, 1, Infos), |
207 | 207 | #{cipher := C, |
23 | 23 | %% rebar.config |
24 | 24 | {deps, [observer_cli]} |
25 | 25 | %% erlang.mk |
26 | dep_observer_cli = hex 1.6.2 | |
26 | dep_observer_cli = hex 1.7.1 | |
27 | 27 | ``` |
28 | 28 | **Elixir** |
29 | 29 | ```elixir |
30 | 30 | # mix.exs |
31 | 31 | def deps do |
32 | [{:observer_cli, "~> 1.6"}] | |
32 | [{:observer_cli, "~> 1.7"}] | |
33 | 33 | end |
34 | 34 | ``` |
35 | 35 | ------------------ |
87 | 87 | |
88 | 88 | {plugins, |
89 | 89 | [ |
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} | |
94 | 94 | ] |
95 | 95 | } |
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 | ``` | |
100 | 103 | 2. Write observer_cli_plugin behaviour. |
101 | 104 | observer_cli_plugin has 3 callbacks. |
102 | ||
105 | ||
103 | 106 | 2.1 attributes. |
104 | 107 | ```erlang |
105 | 108 | -callback atributes(PrevState) -> {[Rows], NewState} when |
109 | 112 | for example: |
110 | 113 | ```erlang |
111 | 114 | attributes(PrevState) -> |
112 | Attrs = | |
115 | Attrs = [ | |
113 | 116 | [ |
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} | |
138 | 123 | ], |
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 | ``` | |
143 | 151 | |
144 | 152 | ```erlang |
145 | 153 | -callback sheet_header() -> [SheetHeader] when |
148 | 156 | for example: |
149 | 157 | ```erlang |
150 | 158 | 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) | | |
158 | 169 | ``` |
159 | 170 | |
160 | 171 | ```erlang |
166 | 177 | ``` |
167 | 178 | |
168 | 179 | for example: |
169 | ||
170 | 180 | ```erlang |
171 | 181 | 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 | ``` | |
187 | 219 | 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> | |
192 | 220 | |
193 | 221 | [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. |
194 | 222 | |
195 | 223 | ---------------- |
196 | 224 | ### 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. | |
197 | 234 | - 1.6.2 |
198 | 235 | - fixed crash when ps command not found on windows. |
199 | 236 | - 1.6.1 |
13 | 13 | interval = ?DEFAULT_INTERVAL :: pos_integer(), |
14 | 14 | scheduler_usage = application:get_env(observer_cli, scheduler_usage, ?DISABLE) :: |
15 | 15 | ?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() | |
16 | 22 | }). |
17 | 23 | |
18 | 24 | -record(ets, { |
47 | 53 | home = #home{} :: home(), |
48 | 54 | ets = #ets{} :: ets(), |
49 | 55 | sys = #system{} :: system(), |
56 | app = #app{} :: app(), | |
50 | 57 | db = #db{} :: db(), |
51 | 58 | help = #help{} :: help(), |
52 | 59 | inet = #inet{} :: inet(), |
60 | 67 | |
61 | 68 | -type view_opts() :: #view_opts{}. |
62 | 69 | -type home() :: #home{}. |
70 | -type app() :: #app{}. | |
63 | 71 | -type system() :: #system{}. |
64 | 72 | -type ets() :: #ets{}. |
65 | 73 | -type db() :: #db{}. |
87 | 95 | -define(NEW_LINE, "\e[0m\n|"). |
88 | 96 | -define(I, <<" | ">>). |
89 | 97 | -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_}). | |
93 | 101 | |
94 | 102 | -define(SELECT(Text), observer_cli_lib:select(Text)). |
95 | 103 | -define(UNSELECT(Text), observer_cli_lib:unselect(Text)). |
3 | 3 | def project do |
4 | 4 | [ |
5 | 5 | app: :observer_cli, |
6 | version: "1.6.2", | |
6 | version: "1.7.1", | |
7 | 7 | language: :erlang, |
8 | 8 | description: "observer in shell", |
9 | 9 | deps: [ |
0 | 0 | {application,observer_cli, |
1 | 1 | [{description,"Visualize Erlang Nodes On The Command Line"}, |
2 | {vsn,"1.6.2"}, | |
2 | {vsn,"1.7.1"}, | |
3 | 3 | {modules,[]}, |
4 | 4 | {registered,[]}, |
5 | 5 | {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}. |
6 | 6 | -export([start/1]). |
7 | 7 | -export([clean/1]). |
8 | 8 | |
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 | ). | |
10 | 13 | |
11 | 14 | %% @doc List application info |
12 | 15 | |
13 | 16 | -spec start(ViewOpts) -> no_return when ViewOpts :: view_opts(). |
14 | start(#view_opts{} = ViewOpts) -> | |
17 | start(#view_opts{app = App, auto_row = AutoRow} = ViewOpts) -> | |
15 | 18 | Pid = spawn_link(fun() -> |
16 | 19 | ?output(?CLEAR), |
17 | render_worker(?INTERVAL) | |
20 | render_worker(App, AutoRow) | |
18 | 21 | end), |
19 | 22 | manager(Pid, ViewOpts). |
20 | 23 | |
24 | 27 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
25 | 28 | %%% Private |
26 | 29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
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), | |
34 | 67 | Text = "Interval: " ++ integer_to_list(Interval) ++ "ms", |
35 | 68 | 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), | |
38 | 72 | ?output([?CURSOR_TOP, Menu, Info, LastLine]), |
39 | 73 | erlang:send_after(Interval, self(), redraw), |
40 | 74 | receive |
41 | 75 | 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 | ], | |
46 | 93 | [ |
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}), | |
63 | 99 | Title = ?render([ |
64 | 100 | ?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) | |
76 | 116 | ]), |
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 -> | |
86 | 154 | [ |
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) -> | |
119 | 178 | 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. |
234 | 234 | |
235 | 235 | tidy_format_args([], _NeedLine, FAcc, AAcc) -> |
236 | 236 | {FAcc, AAcc}; |
237 | tidy_format_args([{extend, A, W} | Rest], true, FAcc, AAcc) -> | |
237 | tidy_format_args([{width, A, W} | Rest], true, FAcc, AAcc) -> | |
238 | 238 | WBin = erlang:integer_to_binary(W), |
239 | 239 | F = <<"~-", WBin/binary, ".", WBin/binary, "ts">>, |
240 | 240 | 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) -> | |
242 | 242 | WBin = erlang:integer_to_binary(W), |
243 | 243 | F = <<"~-", WBin/binary, ".", WBin/binary, "ts", ?I/binary>>, |
244 | 244 | 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) -> | |
246 | 246 | WBin = erlang:integer_to_binary(W), |
247 | 247 | F = <<C/binary, "~-", WBin/binary, ".", WBin/binary, "ts">>, |
248 | 248 | 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) -> | |
250 | 250 | WBin = erlang:integer_to_binary(W), |
251 | 251 | F = <<C/binary, "~-", WBin/binary, ".", WBin/binary, "ts", ?I2/binary>>, |
252 | 252 | 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) -> | |
254 | 254 | WBin = erlang:integer_to_binary(W), |
255 | 255 | F = <<C/binary, "~-", WBin/binary, ".", WBin/binary, "ts">>, |
256 | 256 | 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) -> | |
258 | 258 | WBin = erlang:integer_to_binary(W), |
259 | 259 | F = <<C/binary, "~-", WBin/binary, ".", WBin/binary, "ts", ?RESET/binary, ?I2/binary>>, |
260 | 260 | tidy_format_args(Rest, false, [F | FAcc], [to_str(A) | AAcc]); |
361 | 361 | hide; |
362 | 362 | "`\n" -> |
363 | 363 | scheduler_usage; |
364 | %% {error, estale}|{error, terminated} | |
365 | {error, _Reason} -> | |
366 | quit; | |
364 | 367 | Number -> |
365 | 368 | parse_integer(Number) |
366 | 369 | end. |
443 | 446 | after 100 -> ok |
444 | 447 | end. |
445 | 448 | |
446 | -spec sublist(list(), integer(), integer()) -> list(). | |
449 | -spec sublist(list(), integer(), integer()) -> {integer(), list()}. | |
447 | 450 | sublist(AllEts, Rows, CurPage) -> |
448 | 451 | SortEts = recon_lib:sublist_top_n_attrs(AllEts, Rows * CurPage), |
449 | 452 | Start = Rows * (CurPage - 1) + 1, |
450 | 453 | case erlang:length(SortEts) >= Start of |
451 | 454 | true -> |
452 | lists:sublist(SortEts, Start, Rows); | |
455 | {Start, lists:sublist(SortEts, Start, Rows)}; | |
453 | 456 | false -> |
454 | [] | |
457 | {Start, []} | |
455 | 458 | end. |
456 | 459 | |
457 | 460 | -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)). |
29 | 29 | -spec start(ViewOpts) -> no_return() when ViewOpts :: view_opts(). |
30 | 30 | start(#view_opts{plug = Plugs, auto_row = AutoRow} = ViewOpts) -> |
31 | 31 | NewPlugs = init_config(Plugs), |
32 | SheetCache = ets:new(?MODULE, [set, public]), | |
32 | 33 | Pid = spawn_link(fun() -> |
33 | 34 | ?output(?CLEAR), |
34 | render_worker(?INIT_TIME_REF, NewPlugs, AutoRow, undefined, undefined) | |
35 | render_worker(?INIT_TIME_REF, NewPlugs, AutoRow, SheetCache, undefined, undefined) | |
35 | 36 | end), |
36 | manager(Pid, ViewOpts#view_opts{plug = NewPlugs}). | |
37 | manager(Pid, SheetCache, ViewOpts#view_opts{plug = NewPlugs}). | |
37 | 38 | |
38 | 39 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
39 | 40 | %%% Private |
48 | 49 | Config = maps:merge( |
49 | 50 | #{ |
50 | 51 | cur_page => 1, |
52 | cur_row => 1, | |
51 | 53 | sort_column => 2, |
52 | 54 | interval => 1500, |
53 | 55 | sheet_width => SheetWidth |
63 | 65 | init_config(Plugs) -> |
64 | 66 | Plugs. |
65 | 67 | |
66 | manager(ChildPid, ViewOpts) -> | |
68 | manager(ChildPid, SheetCache, ViewOpts) -> | |
67 | 69 | #view_opts{plug = PlugOpts = #plug{cur_index = CurIndex, plugs = Plugs}} = ViewOpts, |
68 | 70 | case parse_cmd() of |
69 | 71 | quit -> |
72 | ets:delete(SheetCache), | |
70 | 73 | erlang:send(ChildPid, quit); |
71 | 74 | go_home -> |
72 | 75 | observer_cli_lib:exit_processes([ChildPid]), |
76 | ets:delete(SheetCache), | |
73 | 77 | observer_cli:start(ViewOpts); |
74 | 78 | {new_interval, NewMs} -> |
75 | 79 | observer_cli_lib:exit_processes([ChildPid]), |
89 | 93 | NewPlugs = update_plugins(CurIndex, Plugs, #{cur_page => NewPage}), |
90 | 94 | observer_cli_lib:exit_processes([ChildPid]), |
91 | 95 | 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; | |
92 | 130 | {input_str, Cmd} -> |
93 | 131 | case maybe_shortcut(Cmd, ViewOpts) of |
94 | 132 | {ok, menu, Index} -> |
99 | 137 | observer_cli_lib:exit_processes([ChildPid]), |
100 | 138 | start(ViewOpts#view_opts{plug = PlugOpts#plug{plugs = NewPlugs}}); |
101 | 139 | {error, _} -> |
102 | manager(ChildPid, ViewOpts) | |
140 | manager(ChildPid, SheetCache, ViewOpts) | |
103 | 141 | end; |
104 | 142 | _ -> |
105 | manager(ChildPid, ViewOpts) | |
143 | manager(ChildPid, SheetCache, ViewOpts) | |
106 | 144 | end. |
107 | 145 | |
108 | 146 | update_plugins(CurIndex, Lists, UpdateItems) -> |
148 | 186 | LastTimeRef, |
149 | 187 | #plug{cur_index = CurIndex, plugs = Plugs} = PlugInfo, |
150 | 188 | AutoRow, |
189 | SheetCache, | |
151 | 190 | PrevAttrs, |
152 | 191 | PrevSheet |
153 | 192 | ) -> |
159 | 198 | {SheetLine, NewSheet} = render_sheet( |
160 | 199 | erlang:max(0, TerminalRow - LabelLine - 4), |
161 | 200 | CurPlug, |
201 | SheetCache, | |
162 | 202 | PrevSheet |
163 | 203 | ), |
164 | 204 | 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)]), | |
166 | 206 | ?output([?CURSOR_TOP, Menu, Labels, SheetLine, LastLine]), |
167 | 207 | NextTimeRef = observer_cli_lib:next_redraw(LastTimeRef, Interval), |
168 | 208 | receive |
169 | 209 | quit -> quit; |
170 | _ -> render_worker(NextTimeRef, PlugInfo, AutoRow, NewAttrs, NewSheet) | |
210 | _ -> render_worker(NextTimeRef, PlugInfo, AutoRow, SheetCache, NewAttrs, NewSheet) | |
171 | 211 | end; |
172 | 212 | error -> |
173 | 213 | Menu = ?render([ |
188 | 228 | %% forward |
189 | 229 | "F\n" -> page_down_top_n; |
190 | 230 | "q\n" -> quit; |
231 | "\n" -> jump; | |
232 | %% {error, estale}|{error, terminated} | |
233 | {error, _Reason} -> quit; | |
191 | 234 | Number -> observer_cli_lib:parse_integer(Number) |
192 | 235 | end. |
193 | 236 | |
203 | 246 | "|", |
204 | 247 | Title |
205 | 248 | ], |
206 | SheetWidth + Num * 21 + 1 | |
249 | SheetWidth + Num * 21 + 5 | |
207 | 250 | ), |
208 | 251 | Time |
209 | 252 | ]). |
231 | 274 | L = [ |
232 | 275 | begin |
233 | 276 | #{content := Content, width := Width} = Item, |
277 | Value = | |
278 | case Content of | |
279 | {percent, Float} -> observer_cli_lib:to_percent(Float); | |
280 | _ -> Content | |
281 | end, | |
234 | 282 | 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) | |
237 | 285 | end |
238 | 286 | end |
239 | 287 | || Item <- Label |
248 | 296 | {[], 0, PrevAttrs} |
249 | 297 | end. |
250 | 298 | |
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, | |
252 | 306 | try |
253 | 307 | {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 | ), | |
255 | 318 | {[Headers | Body], NewSheet} |
256 | 319 | catch |
257 | 320 | error:undef -> |
258 | [] | |
321 | {[], []} | |
259 | 322 | end. |
260 | 323 | |
261 | 324 | render_sheet_header(Module, SortRow) -> |
270 | 333 | "" -> Header; |
271 | 334 | Shortcut -> Header ++ "(" ++ Shortcut ++ ")" |
272 | 335 | 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} | |
287 | 342 | end, |
288 | 343 | {[], [], length(SheetHeader)}, |
289 | 344 | lists:reverse(SheetHeader) |
290 | 345 | ), |
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) -> | |
294 | 349 | {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 | |
298 | 360 | end, |
299 | Diff | |
361 | {[], StartAt}, | |
362 | SortData | |
300 | 363 | ), |
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}. | |
310 | 365 | |
311 | 366 | mix_content_width([], _, Acc) -> |
312 | 367 | lists:reverse(Acc); |
313 | 368 | %% first |
314 | 369 | mix_content_width([I | IRest], [W | WRest], []) -> |
315 | 370 | 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)]); | |
317 | 372 | %% last |
318 | 373 | mix_content_width([I], [W], Acc) -> |
319 | 374 | 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). |
1 | 1 | |
2 | 2 | -include("observer_cli.hrl"). |
3 | 3 | |
4 | -export([start/2]). | |
4 | -export([start/3]). | |
5 | 5 | |
6 | 6 | %% lists:foldl(fun(_X, Acc) -> queue:in('NaN', Acc) end, queue:new(), lists:seq(1, 5)) |
7 | 7 | -define(INIT_QUEUE, {['NaN', 'NaN', 'NaN', 'NaN'], ['NaN']}). |
8 | 8 | |
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) -> | |
11 | 11 | #view_opts{process = #process{interval = RefreshMs}} = Opts, |
12 | 12 | RenderPid = spawn_link(fun() -> |
13 | 13 | ?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) | |
15 | 15 | end), |
16 | manager(RenderPid, Opts). | |
16 | manager(RenderPid, Type, Opts). | |
17 | 17 | |
18 | 18 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
19 | 19 | %%% Private |
20 | 20 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
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 | |
23 | 23 | quit -> |
24 | 24 | erlang:send(RenderPid, quit); |
25 | 25 | {new_interval, NewInterval} -> |
26 | 26 | 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); | |
28 | 38 | ViewAction -> |
29 | 39 | erlang:send(RenderPid, ViewAction), |
30 | manager(RenderPid, Opts) | |
40 | manager(RenderPid, Type, Opts) | |
31 | 41 | end. |
32 | 42 | |
33 | render_worker(info, Interval, Pid, TimeRef, RedQ, MemQ) -> | |
43 | render_worker(info, Type, Interval, Pid, TimeRef, RedQ, MemQ) -> | |
34 | 44 | ProcessInfo = recon:info(Pid), |
35 | 45 | Meta = proplists:get_value(meta, ProcessInfo), |
36 | 46 | case Meta of |
37 | 47 | 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); | |
40 | 50 | _ -> |
41 | 51 | RegisteredName = proplists:get_value(registered_name, Meta), |
42 | 52 | GroupLeader = proplists:get_value(group_leader, Meta), |
61 | 71 | Work = proplists:get_value(work, ProcessInfo), |
62 | 72 | Reductions = proplists:get_value(reductions, Work), |
63 | 73 | |
64 | Menu = render_menu(info, Interval), | |
74 | Menu = render_menu(info, Type, Interval), | |
65 | 75 | |
66 | 76 | Line1 = render_process_info( |
67 | 77 | Pid, |
83 | 93 | LastLine = render_last_line(), |
84 | 94 | |
85 | 95 | ?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) | |
87 | 97 | end; |
88 | render_worker(message, Interval, Pid, TimeRef, RedQ, MemQ) -> | |
98 | render_worker(message, Type, Interval, Pid, TimeRef, RedQ, MemQ) -> | |
89 | 99 | case erlang:process_info(Pid, message_queue_len) of |
90 | 100 | {message_queue_len, Len} -> |
91 | 101 | Line = |
101 | 111 | truncate_str(Messages) |
102 | 112 | ] |
103 | 113 | end, |
104 | Menu = render_menu(message, Interval), | |
114 | Menu = render_menu(message, Type, Interval), | |
105 | 115 | LastLine = render_last_line(), |
106 | 116 | ?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); | |
108 | 118 | 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) | |
110 | 120 | end; |
111 | render_worker(dict, Interval, Pid, TimeRef, RedQ, MemQ) -> | |
121 | render_worker(dict, Type, Interval, Pid, TimeRef, RedQ, MemQ) -> | |
112 | 122 | case erlang:process_info(Pid, dictionary) of |
113 | 123 | {dictionary, List} -> |
114 | 124 | Len = erlang:length(List), |
121 | 131 | 0 -> "\e[32;1mNo dictionary was found\e[0m\n"; |
122 | 132 | _ -> truncate_str(List) |
123 | 133 | end, |
124 | Menu = render_menu(dict, Interval), | |
134 | Menu = render_menu(dict, Type, Interval), | |
125 | 135 | LastLine = render_last_line(), |
126 | 136 | ?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); | |
128 | 138 | 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) | |
130 | 140 | end; |
131 | render_worker(stack, Interval, Pid, TimeRef, RedQ, MemQ) -> | |
141 | render_worker(stack, Type, Interval, Pid, TimeRef, RedQ, MemQ) -> | |
132 | 142 | case erlang:process_info(Pid, current_stacktrace) of |
133 | 143 | {current_stacktrace, StackTrace} -> |
134 | Menu = render_menu(stack, Interval), | |
144 | Menu = render_menu(stack, Type, Interval), | |
135 | 145 | Prompt = io_lib:format("erlang:process_info(~p, current_stacktrace). ~n", [Pid]), |
136 | 146 | LastLine = render_last_line(), |
137 | 147 | {_, Line} = |
150 | 160 | lists:sublist(StackTrace, 30) |
151 | 161 | ), |
152 | 162 | ?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); | |
154 | 164 | 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) | |
156 | 166 | 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) | |
161 | 171 | end. |
162 | 172 | |
163 | next_draw_view(Status, TimeRef, Interval, Pid, NewRedQ, NewMemQ) -> | |
173 | next_draw_view(Status, Type, TimeRef, Interval, Pid, NewRedQ, NewMemQ) -> | |
164 | 174 | 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) -> | |
168 | 178 | receive |
169 | 179 | quit -> |
170 | 180 | quit; |
171 | 181 | {new_interval, NewInterval} -> |
172 | 182 | ?output(?CLEAR), |
173 | render_worker(Status, NewInterval, Pid, TimeRef, NewRedQ, NewMemQ); | |
183 | render_worker(Status, Type, NewInterval, Pid, TimeRef, NewRedQ, NewMemQ); | |
174 | 184 | info_view -> |
175 | 185 | ?output(?CLEAR), |
176 | render_worker(info, Interval, Pid, TimeRef, NewRedQ, NewMemQ); | |
186 | render_worker(info, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ); | |
177 | 187 | message_view -> |
178 | 188 | ?output(?CLEAR), |
179 | render_worker(message, Interval, Pid, TimeRef, NewRedQ, NewMemQ); | |
189 | render_worker(message, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ); | |
180 | 190 | dict_view -> |
181 | 191 | ?output(?CLEAR), |
182 | render_worker(dict, Interval, Pid, TimeRef, NewRedQ, NewMemQ); | |
192 | render_worker(dict, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ); | |
183 | 193 | stack_view -> |
184 | 194 | ?output(?CLEAR), |
185 | render_worker(stack, Interval, Pid, TimeRef, NewRedQ, NewMemQ); | |
195 | render_worker(stack, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ); | |
186 | 196 | state_view -> |
187 | 197 | ?output(?CLEAR), |
188 | render_worker(state, Interval, Pid, TimeRef, NewRedQ, NewMemQ); | |
198 | render_worker(state, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ); | |
189 | 199 | _Msg -> |
190 | render_worker(Status, Interval, Pid, TimeRef, NewRedQ, NewMemQ) | |
200 | render_worker(Status, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ) | |
191 | 201 | end. |
192 | 202 | |
193 | 203 | render_process_info( |
333 | 343 | chart_format([R1, R2 | RestRed], Lines) when R1 < R2 -> |
334 | 344 | chart_format([R2 | RestRed], Lines ++ observer_cli_lib:to_list(R1) ++ "->"). |
335 | 345 | |
336 | render_menu(Type, Interval) -> | |
346 | render_menu(Type, Menu, Interval) -> | |
337 | 347 | Text = "Interval: " ++ integer_to_list(Interval) ++ "ms", |
338 | Title = get_menu_title(Type), | |
348 | Title = get_menu_title(Type, Menu), | |
339 | 349 | UpTime = observer_cli_lib:uptime(), |
340 | 350 | TitleWidth = ?COLUMN + 104 - erlang:length(UpTime), |
341 | 351 | ?render([?W([Title | Text], TitleWidth) | UpTime]). |
342 | 352 | |
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), | |
345 | 360 | [Home, "|", Process, "|", Messages, "|", Dict, "|", Stack, "|", State, "|"]. |
346 | 361 | |
347 | get_menu_title2(info) -> | |
348 | [ | |
349 | ?UNSELECT("Home(H)"), | |
362 | get_menu_title2(info, Menu) -> | |
363 | [ | |
364 | ?UNSELECT(Menu), | |
350 | 365 | ?SELECT("Process Info(P)"), |
351 | 366 | ?UNSELECT("Messages(M)"), |
352 | 367 | ?UNSELECT("Dictionary(D)"), |
353 | 368 | ?UNSELECT("Current Stack(C)"), |
354 | 369 | ?UNSELECT("State(S)") |
355 | 370 | ]; |
356 | get_menu_title2(message) -> | |
357 | [ | |
358 | ?UNSELECT("Home(H)"), | |
371 | get_menu_title2(message, Menu) -> | |
372 | [ | |
373 | ?UNSELECT(Menu), | |
359 | 374 | ?UNSELECT("Process Info(P)"), |
360 | 375 | ?SELECT("Messages(M)"), |
361 | 376 | ?UNSELECT("Dictionary(D)"), |
362 | 377 | ?UNSELECT("Current Stack(C)"), |
363 | 378 | ?UNSELECT("State(S)") |
364 | 379 | ]; |
365 | get_menu_title2(dict) -> | |
366 | [ | |
367 | ?UNSELECT("Home(H)"), | |
380 | get_menu_title2(dict, Menu) -> | |
381 | [ | |
382 | ?UNSELECT(Menu), | |
368 | 383 | ?UNSELECT("Process Info(P)"), |
369 | 384 | ?UNSELECT("Messages(M)"), |
370 | 385 | ?SELECT("Dictionary(D)"), |
371 | 386 | ?UNSELECT("Current Stack(C)"), |
372 | 387 | ?UNSELECT("State(S)") |
373 | 388 | ]; |
374 | get_menu_title2(stack) -> | |
375 | [ | |
376 | ?UNSELECT("Home(H)"), | |
389 | get_menu_title2(stack, Menu) -> | |
390 | [ | |
391 | ?UNSELECT(Menu), | |
377 | 392 | ?UNSELECT("Process Info(P)"), |
378 | 393 | ?UNSELECT("Messages(M)"), |
379 | 394 | ?UNSELECT("Dictionary(D)"), |
380 | 395 | ?SELECT("Current Stack(C)"), |
381 | 396 | ?UNSELECT("State(S)") |
382 | 397 | ]; |
383 | get_menu_title2(state) -> | |
384 | [ | |
385 | ?UNSELECT("Home(H)"), | |
398 | get_menu_title2(state, Menu) -> | |
399 | [ | |
400 | ?UNSELECT(Menu), | |
386 | 401 | ?UNSELECT("Process Info(P)"), |
387 | 402 | ?UNSELECT("Messages(M)"), |
388 | 403 | ?UNSELECT("Dictionary(D)"), |
390 | 405 | ?SELECT("State(S)") |
391 | 406 | ]. |
392 | 407 | |
393 | parse_cmd(ViewOpts, Pid) -> | |
408 | parse_cmd() -> | |
394 | 409 | case observer_cli_lib:to_list(io:get_line("")) of |
395 | 410 | "q\n" -> |
396 | 411 | quit; |
407 | 422 | "S\n" -> |
408 | 423 | state_view; |
409 | 424 | "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; | |
412 | 431 | Number -> |
413 | 432 | observer_cli_lib:parse_integer(Number) |
414 | 433 | end. |
415 | 434 | |
416 | render_state(Pid, Interval) -> | |
417 | Menu = render_menu(state, Interval), | |
435 | render_state(Pid, Type, Interval) -> | |
436 | Menu = render_menu(state, Type, Interval), | |
418 | 437 | PromptRes = io_lib:format("recon:get_state(~p, 2500). ~n", [Pid]), |
419 | 438 | PromptBefore = io_lib:format("\e[32;1mWaiting recon:get_state(~p, 2500) return...\e[0m~n", [Pid]), |
420 | 439 | LastLine = render_last_line(), |
432 | 451 | error |
433 | 452 | end. |
434 | 453 | |
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), | |
437 | 456 | Line = io_lib:format("\e[31mProcess(~p) has already die.\e[0m~n", [Pid]), |
438 | 457 | LastLine = render_last_line(), |
439 | 458 | ?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}. |
385 | 385 | timestamp :: non_neg_integer(), |
386 | 386 | epoch :: epoch(), |
387 | 387 | 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() | |
389 | 393 | }). |
390 | 394 | -record(seg_info, |
391 | 395 | {file :: file:filename(), |
794 | 798 | empty | {offset(), offset()}}} | |
795 | 799 | {error, |
796 | 800 | {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) -> | |
799 | 802 | SegInfos = build_log_overview(Dir), |
800 | 803 | Range = offset_range_from_segment_infos(SegInfos), |
801 | 804 | ?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]), | |
804 | 807 | %% Invariant: there is always at least one segment left on disk |
805 | 808 | 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 -> | |
820 | 812 | {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 -> | |
825 | 814 | {error, {offset_out_of_range, Range}}; |
826 | _ -> | |
815 | _ when PrevEOT == empty -> | |
827 | 816 | %% this assumes the offset is in range |
828 | 817 | %% 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 | |
895 | 832 | end |
896 | 833 | end. |
897 | 834 | |
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), | |
905 | 858 | 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), | |
908 | 861 | #?MODULE{cfg = |
909 | 862 | #cfg{directory = Dir, |
910 | 863 | counter = Cnt, |
911 | 864 | name = Name, |
912 | readers_counter_fun = CounterFun, | |
865 | readers_counter_fun = CountersFun, | |
913 | 866 | first_offset_fun = fun (_) -> ok end}, |
914 | 867 | mode = |
915 | 868 | #read{type = data, |
916 | 869 | offset_ref = maps:get(offset_ref, Config, undefined), |
917 | next_offset = NextOffs, | |
870 | next_offset = ChunkId, | |
918 | 871 | chunk_selector = all, |
919 | 872 | transport = maps:get(transport, Config, tcp)}, |
920 | 873 | 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). | |
921 | 886 | |
922 | 887 | %% @doc Initialise a new offset reader |
923 | 888 | %% @param OffsetSpec specifies where in the log to attach the reader |
939 | 904 | {ok, state()} | |
940 | 905 | {error, |
941 | 906 | {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) -> | |
945 | 918 | Range = offset_range_from_segment_infos(build_log_overview(Dir)), |
946 | 919 | case Range of |
947 | 920 | empty -> |
950 | 923 | {error, {offset_out_of_range, Range}}; |
951 | 924 | _ -> |
952 | 925 | %% it is in range, convert to standard offset |
953 | init_offset_reader(Offs, Conf) | |
926 | init_offset_reader0(Offs, Conf) | |
954 | 927 | end; |
955 | init_offset_reader({timestamp, Ts}, #{dir := Dir} = Conf) -> | |
928 | init_offset_reader0({timestamp, Ts}, #{dir := Dir} = Conf) -> | |
956 | 929 | case build_log_overview(Dir) of |
957 | 930 | [] -> |
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}} | _] | |
960 | 936 | when is_integer(Fst) andalso Fst > Ts -> |
961 | 937 | %% timestamp is lower than the first timestamp available |
962 | init_offset_reader(first, Conf); | |
938 | open_offset_reader_at(SegmentFile, ChunkId, FilePos, Conf); | |
963 | 939 | SegInfos -> |
964 | 940 | case lists:search(fun (#seg_info{first = #chunk_info{timestamp = F}, |
965 | 941 | last = #chunk_info{timestamp = L}}) |
971 | 947 | end, |
972 | 948 | SegInfos) |
973 | 949 | of |
974 | {value, Info} -> | |
950 | {value, #seg_info{file = SegmentFile} = Info} -> | |
975 | 951 | %% segment was found, now we need to scan index to |
976 | 952 | %% 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); | |
979 | 955 | false -> |
980 | 956 | %% 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) | |
982 | 960 | end |
983 | 961 | 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) -> | |
991 | 1010 | SegInfos = build_log_overview(Dir), |
992 | 1011 | ChunkRange = chunk_range_from_segment_infos(SegInfos), |
993 | 1012 | 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 ", | |
996 | 1014 | [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} = | |
1027 | 1038 | case scan_idx(StartOffset, SegmentInfo) of |
1028 | 1039 | eof -> |
1029 | {StartOffset, 0}; | |
1040 | exit(offset_out_of_range); | |
1030 | 1041 | enoent -> |
1031 | 1042 | %% 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); | |
1035 | 1047 | IdxResult when is_tuple(IdxResult) -> |
1036 | 1048 | IdxResult |
1037 | 1049 | 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}}. | |
1063 | 1087 | |
1064 | 1088 | %% 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) -> | |
1066 | 1090 | {Time, Result} = timer:tc( |
1067 | 1091 | fun() -> |
1068 | 1092 | last_user_chunk_id0(lists:reverse(SegInfos)) |
1072 | 1096 | |
1073 | 1097 | last_user_chunk_id0([]) -> |
1074 | 1098 | %% 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]) -> | |
1077 | 1101 | try |
1078 | 1102 | %% Do not read-ahead since we read the index file backwards chunk by chunk. |
1079 | 1103 | {ok, IdxFd} = open(IdxFile, [read, raw, binary]), |
1080 | 1104 | file:position(IdxFd, eof), |
1081 | 1105 | Last = last_user_chunk_id_in_index(IdxFd), |
1082 | file:close(IdxFd), | |
1106 | _ = file:close(IdxFd), | |
1083 | 1107 | case Last of |
1084 | {ok, Id} -> | |
1085 | Id; | |
1108 | {ok, Id, Pos} -> | |
1109 | {Id, Pos, Info}; | |
1086 | 1110 | {error, Reason} -> |
1087 | 1111 | ?DEBUG("Could not find user chunk in index file ~s (~p)", [IdxFile, Reason]), |
1088 | 1112 | last_user_chunk_id0(Rest) |
1104 | 1128 | <<Offset:64/unsigned, |
1105 | 1129 | _Timestamp:64/signed, |
1106 | 1130 | _Epoch:64/unsigned, |
1107 | _FileOffset:32/unsigned, | |
1131 | FilePos:32/unsigned, | |
1108 | 1132 | ?CHNK_USER:8/unsigned>>} -> |
1109 | {ok, Offset}; | |
1133 | {ok, Offset, FilePos}; | |
1110 | 1134 | {ok, |
1111 | 1135 | <<_Offset:64/unsigned, |
1112 | 1136 | _Timestamp:64/signed, |
1113 | 1137 | _Epoch:64/unsigned, |
1114 | _FileOffset:32/unsigned, | |
1138 | _FilePos:32/unsigned, | |
1115 | 1139 | _ChType:8/unsigned>>} -> |
1116 | 1140 | last_user_chunk_id_in_index(IdxFd); |
1117 | 1141 | {error, _} = Error -> |
1423 | 1447 | fun() -> |
1424 | 1448 | try |
1425 | 1449 | IdxFiles = |
1426 | lists:sort( | |
1427 | filelib:wildcard( | |
1428 | filename:join(Dir, "*.index"))), | |
1450 | lists:sort( | |
1451 | filelib:wildcard( | |
1452 | filename:join(Dir, "*.index"))), | |
1429 | 1453 | build_log_overview0(IdxFiles, []) |
1430 | 1454 | catch |
1431 | 1455 | missing_file -> |
1490 | 1514 | FirstTs:64/signed, |
1491 | 1515 | FirstEpoch:64/unsigned, |
1492 | 1516 | FirstChId:64/unsigned, |
1517 | _FirstCrc:32/integer, | |
1518 | FirstSize:32/unsigned, | |
1519 | FirstTSize:32/unsigned, | |
1493 | 1520 | _/binary>>} -> |
1494 | 1521 | {ok, LastChunkPos} = file:position(Fd, LastChunkPos), |
1495 | 1522 | {ok, |
1520 | 1547 | timestamp = FirstTs, |
1521 | 1548 | id = FirstChId, |
1522 | 1549 | num = FirstNumRecords, |
1523 | type = FirstChType}, | |
1550 | type = FirstChType, | |
1551 | size = FirstSize + FirstTSize, | |
1552 | pos = ?LOG_HEADER_SIZE}, | |
1524 | 1553 | last = |
1525 | 1554 | #chunk_info{epoch = LastEpoch, |
1526 | 1555 | timestamp = LastTs, |
1527 | 1556 | id = LastChId, |
1528 | 1557 | num = LastNumRecords, |
1529 | type = LastChType}} | |
1558 | type = LastChType, | |
1559 | size = LastSize + LastTSize, | |
1560 | pos = LastChunkPos}} | |
1530 | 1561 | | Acc0] |
1531 | 1562 | end |
1532 | 1563 | catch |
1911 | 1942 | _ = file:read(Fd, ?IDX_HEADER_SIZE), |
1912 | 1943 | Fd. |
1913 | 1944 | |
1914 | scan_idx(Offset, SegmentInfo = #seg_info{index = IndexFile, last = LastChunkInSegment}) -> | |
1945 | scan_idx(Offset, #seg_info{index = IndexFile, | |
1946 | last = LastChunkInSegment} = SegmentInfo) -> | |
1915 | 1947 | {Time, Result} = timer:tc( |
1916 | 1948 | fun() -> |
1917 | 1949 | case offset_range_from_segment_infos([SegmentInfo]) of |
1918 | 1950 | 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; | |
1925 | 1957 | false -> |
1926 | 1958 | 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), | |
1929 | 1962 | Result |
1930 | 1963 | end |
1931 | 1964 | end |
1932 | 1965 | 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]), | |
1934 | 1968 | Result. |
1935 | 1969 | |
1936 | scan_idx(Fd, Offset, #chunk_info{id = LastChunkInSegmentId, num = LastChunkInSegmentNum}) -> | |
1970 | scan_idx(Fd, Offset, #chunk_info{id = LastChunkInSegmentId, | |
1971 | num = LastChunkInSegmentNum}) -> | |
1937 | 1972 | 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 | |
1945 | 1980 | true -> |
1946 | %% offset is lower than the first chunk in this segment | |
1947 | 1981 | %% shouldn't really happen as we check the range above |
1948 | 1982 | offset_out_of_range; |
1949 | 1983 | 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}) | |
1958 | 1986 | end; |
1959 | 1987 | eof -> |
1960 | 1988 | %% this should never happen - offset is in the range and we are reading the first record |
1961 | 1989 | eof; |
1962 | 1990 | {error, Posix} -> |
1963 | 1991 | Posix |
1964 | end; | |
1965 | scan_idx(Fd, Offset, PreviousChunk) -> | |
1992 | end. | |
1993 | ||
1994 | scan_idx0(Fd, Offset, PreviousChunk) -> | |
1966 | 1995 | case file:read(Fd, ?INDEX_RECORD_SIZE_B) of |
1967 | 1996 | {ok, |
1968 | 1997 | <<ChunkId:64/unsigned, |
1969 | 1998 | _Timestamp:64/signed, |
1970 | _Epoch:64/unsigned, | |
1999 | Epoch:64/unsigned, | |
1971 | 2000 | FilePos:32/unsigned, |
1972 | 2001 | _ChType:8/unsigned>>} -> |
1973 | 2002 | case Offset < ChunkId of |
1974 | 2003 | 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 | |
1977 | 2008 | PreviousChunk; |
1978 | 2009 | false -> |
1979 | scan_idx(Fd, Offset, {ChunkId, FilePos}) | |
2010 | scan_idx0(Fd, Offset, {ChunkId, Epoch, FilePos}) | |
1980 | 2011 | end; |
1981 | 2012 | 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 | |
1984 | 2017 | end. |
1985 | 2018 | |
1986 | 2019 | throw_missing({error, enoent}) -> |
1991 | 2024 | open(SegFile, Options) -> |
1992 | 2025 | throw_missing(file:open(SegFile, Options)). |
1993 | 2026 | |
1994 | chunk_id_for_timestamp(#seg_info{index = Idx}, Ts) -> | |
2027 | chunk_location_for_timestamp(#seg_info{index = Idx}, Ts) -> | |
1995 | 2028 | Fd = open_index_read(Idx), |
1996 | 2029 | %% 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}. | |
1999 | 2032 | |
2000 | 2033 | timestamp_idx_scan(Fd, Ts) -> |
2001 | 2034 | case file:read(Fd, ?INDEX_RECORD_SIZE_B) of |
2226 | 2259 | end), |
2227 | 2260 | State. |
2228 | 2261 | |
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 | ||
2229 | 2270 | -ifdef(TEST). |
2230 | 2271 | |
2231 | 2272 | % -include_lib("eunit/include/eunit.hrl"). |
131 | 131 | case rpc:call(Node, osiris_writer, overview, [LeaderPid]) of |
132 | 132 | {error, _} = Err -> |
133 | 133 | {stop, Err}; |
134 | {badrpc, Reason} -> | |
135 | {error, Reason}; | |
134 | 136 | {ok, {LeaderRange, LeaderEpochOffs}} -> |
135 | 137 | {ok, {Min, Max}} = application:get_env(port_range), |
136 | 138 | RecBuf = application:get_env(osiris, replica_recbuf, ?DEF_REC_BUF), |
436 | 436 | size = "large", |
437 | 437 | flaky = True, |
438 | 438 | 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, | |
439 | 452 | # The enabling_* tests chmod files and then expect writes to be blocked. |
440 | 453 | # This probably doesn't work because we are root in the remote docker image. |
441 | 454 | tags = ["exclusive"], |
141 | 141 | PLT_APPS += mnesia |
142 | 142 | |
143 | 143 | 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 | |
145 | 145 | dep_systemd = hex 0.6.1 |
146 | 146 | dep_seshat = git https://github.com/rabbitmq/seshat 0.1.0 |
147 | 147 |
27 | 27 | |
28 | 28 | xref( |
29 | 29 | additional_libs = [ |
30 | "@ranch//:bazel_erlang_lib", | |
30 | 31 | "@systemd//:bazel_erlang_lib", |
31 | 32 | ], |
32 | 33 | tags = ["xref"], |
742 | 742 | |
743 | 743 | |
744 | 744 | {mapping, "max_message_size", "rabbit.max_message_size", |
745 | [{datatype, integer}, {validators, ["less_then_512MB"]}]}. | |
745 | [{datatype, integer}, {validators, ["max_message_size"]}]}. | |
746 | 746 | |
747 | 747 | %% Customising Socket Options. |
748 | 748 | %% |
1018 | 1018 | %% {mirroring_sync_batch_size, 4096}, |
1019 | 1019 | |
1020 | 1020 | {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"]}]}. | |
1022 | 1022 | |
1023 | 1023 | %% Peer discovery backend used by cluster formation. |
1024 | 1024 | %% |
2049 | 2049 | % Validators |
2050 | 2050 | % =============================== |
2051 | 2051 | |
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", | |
2053 | 2053 | 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", | |
2058 | 2058 | fun(Size) when is_integer(Size) -> |
2059 | Size > 0 andalso Size < 536870912 | |
2059 | Size > 0 andalso Size =< 536870912 | |
2060 | 2060 | end}. |
2061 | 2061 | |
2062 | 2062 | {validator, "less_than_1", "Float is not between 0 and 1", |
1778 | 1778 | node_permits_offline_promotion(Node) -> |
1779 | 1779 | case node() of |
1780 | 1780 | Node -> not rabbit:is_running(); %% [1] |
1781 | _ -> All = rabbit_mnesia:cluster_nodes(all), | |
1781 | _ -> All = rabbit_nodes:all(), | |
1782 | 1782 | Running = rabbit_nodes:all_running(), |
1783 | 1783 | lists:member(Node, All) andalso |
1784 | 1784 | not lists:member(Node, Running) %% [2] |
23 | 23 | Marker = spawn_link(fun() -> receive stop -> ok end end), |
24 | 24 | ChildSpec = {rabbit_amqqueue, |
25 | 25 | {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]}, | |
28 | 28 | {ok, SupPid} = supervisor2:start_link(?MODULE, []), |
29 | 29 | {ok, QPid} = supervisor2:start_child(SupPid, ChildSpec), |
30 | 30 | unlink(Marker), |
143 | 143 | end. |
144 | 144 | |
145 | 145 | leader() -> |
146 | [Leader | _] = lists:usort(rabbit_mnesia:cluster_nodes(all)), | |
146 | [Leader | _] = lists:usort(rabbit_nodes:all()), | |
147 | 147 | Leader. |
148 | 148 | |
149 | 149 | %% This is the winner receiving its last notification that a node has |
409 | 409 | %% only know which nodes we have been partitioned from, not which |
410 | 410 | %% nodes are partitioned from each other. |
411 | 411 | check_other_nodes(LocalPartitions) -> |
412 | Nodes = rabbit_mnesia:cluster_nodes(all), | |
412 | Nodes = rabbit_nodes:all(), | |
413 | 413 | {Results, Bad} = rabbit_node_monitor:status(Nodes -- [node()]), |
414 | 414 | RemotePartitions = [{Node, proplists:get_value(partitions, Res)} |
415 | 415 | || {Node, Res} <- Results], |
178 | 178 | lists:foldl( |
179 | 179 | fun (Node, Acc) -> |
180 | 180 | 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 | |
182 | 193 | end, [], rabbit_nodes:all_running()). |
183 | 194 | |
184 | 195 | -spec list_of_user(rabbit_types:username()) -> [rabbit_types:tracked_channel()]. |
350 | 350 | lists:foldl( |
351 | 351 | fun (Node, Acc) -> |
352 | 352 | 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 | |
354 | 365 | end, [], rabbit_nodes:all_running()). |
355 | 366 | |
356 | 367 | -spec count() -> non_neg_integer(). |
359 | 370 | lists:foldl( |
360 | 371 | fun (Node, Acc) -> |
361 | 372 | 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. | |
362 | 376 | Acc + mnesia:table_info(Tab, size) |
363 | 377 | end, 0, rabbit_nodes:all_running()). |
364 | 378 |
101 | 101 | gc_process_and_entity(channel_exchange_metrics, GbSet). |
102 | 102 | |
103 | 103 | gc_nodes() -> |
104 | Nodes = rabbit_mnesia:cluster_nodes(all), | |
104 | Nodes = rabbit_nodes:all(), | |
105 | 105 | GbSet = gb_sets:from_list(Nodes), |
106 | 106 | gc_entity(node_node_metrics, GbSet). |
107 | 107 |
480 | 480 | -spec add_vhost(map(), rabbit_types:username()) -> ok. |
481 | 481 | |
482 | 482 | 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). | |
488 | 490 | |
489 | 491 | add_permission(Permission, ActingUser) -> |
490 | 492 | rabbit_auth_backend_internal:set_permissions(maps:get(user, Permission, undefined), |
339 | 339 | {State, Reply, Effects} |
340 | 340 | end |
341 | 341 | 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); | |
346 | 349 | apply(Meta, #checkout{spec = Spec, meta = ConsumerMeta, |
347 | 350 | consumer_id = {_, Pid} = ConsumerId}, |
348 | 351 | State0) -> |
371 | 374 | {State, _, Effects} = evaluate_limit(Index, false, State0, |
372 | 375 | State1, Effects0), |
373 | 376 | 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}]); | |
376 | 379 | apply(#{system_time := Ts} = Meta, {down, Pid, noconnection}, |
377 | 380 | #?MODULE{consumers = Cons0, |
378 | 381 | cfg = #cfg{consumer_strategy = single_active}, |
505 | 508 | checkout(Meta, State0, State, Effects); |
506 | 509 | apply(_, {nodedown, _Node}, State) -> |
507 | 510 | {State, ok}; |
508 | apply(Meta, #purge_nodes{nodes = Nodes}, State0) -> | |
511 | apply(#{index := Idx} = Meta, #purge_nodes{nodes = Nodes}, State0) -> | |
509 | 512 | {State, Effects} = lists:foldl(fun(Node, {S, E}) -> |
510 | 513 | purge_node(Meta, Node, S, E) |
511 | 514 | 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); | |
515 | 519 | apply(_Meta, {machine_version, 0, 1}, V0State) -> |
516 | 520 | State = convert_v0_to_v1(V0State), |
517 | 521 | {State, ok, []}; |
1749 | 1753 | messages = Messages0, |
1750 | 1754 | consumers = Cons0} = InitState) -> |
1751 | 1755 | case priority_queue:out(SQ0) of |
1752 | {{value, ConsumerId}, SQ1} -> | |
1756 | {{value, ConsumerId}, SQ1} | |
1757 | when is_map_key(ConsumerId, Cons0) -> | |
1753 | 1758 | case take_next_msg(InitState) of |
1754 | 1759 | {ConsumerMsg, State0} -> |
1755 | 1760 | %% there are consumers waiting to be serviced |
1756 | 1761 | %% 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} -> | |
1759 | 1764 | %% no credit but was still on queue |
1760 | 1765 | %% can happen when draining |
1761 | 1766 | %% recurse without consumer on queue |
1762 | 1767 | checkout_one(Meta, InitState#?MODULE{service_queue = SQ1}); |
1763 | {ok, #consumer{status = cancelled}} -> | |
1768 | #consumer{status = cancelled} -> | |
1764 | 1769 | checkout_one(Meta, InitState#?MODULE{service_queue = SQ1}); |
1765 | {ok, #consumer{status = suspected_down}} -> | |
1770 | #consumer{status = suspected_down} -> | |
1766 | 1771 | 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 -> | |
1771 | 1776 | Checked = maps:put(Next, ConsumerMsg, Checked0), |
1772 | 1777 | Con = Con0#consumer{checked_out = Checked, |
1773 | 1778 | next_msg_id = Next + 1, |
1794 | 1799 | add_bytes_checkout(Header, State1)), |
1795 | 1800 | M} |
1796 | 1801 | 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} | |
1801 | 1803 | end; |
1802 | 1804 | empty -> |
1803 | 1805 | {nochange, InitState} |
1804 | 1806 | end; |
1807 | {{value, _ConsumerId}, SQ1} -> | |
1808 | %% consumer did not exist but was queued, recurse | |
1809 | checkout_one(Meta, InitState#?MODULE{service_queue = SQ1}); | |
1805 | 1810 | {empty, _} -> |
1806 | 1811 | case lqueue:len(Messages0) of |
1807 | 1812 | 0 -> {nochange, InitState}; |
99 | 99 | |
100 | 100 | %% Check that we are in the cluster, all old nodes are in the |
101 | 101 | %% cluster, and no new nodes are. |
102 | Nodes = rabbit_mnesia:cluster_nodes(all), | |
102 | Nodes = rabbit_nodes:all(), | |
103 | 103 | case {FromNodes -- Nodes, ToNodes -- (ToNodes -- Nodes), |
104 | 104 | lists:member(Node, Nodes ++ ToNodes)} of |
105 | 105 | {[], [], true} -> ok; |
231 | 231 | Term. |
232 | 232 | |
233 | 233 | rename_in_running_mnesia(FromNode, ToNode) -> |
234 | All = rabbit_mnesia:cluster_nodes(all), | |
234 | All = rabbit_nodes:all(), | |
235 | 235 | Running = rabbit_nodes:all_running(), |
236 | 236 | case {lists:member(FromNode, Running), lists:member(ToNode, All)} of |
237 | 237 | {false, true} -> ok; |
165 | 165 | -spec notify_joined_cluster() -> 'ok'. |
166 | 166 | |
167 | 167 | notify_joined_cluster() -> |
168 | Nodes = rabbit_nodes:all_running() -- [node()], | |
168 | NewMember = node(), | |
169 | Nodes = rabbit_nodes:all_running() -- [NewMember], | |
169 | 170 | gen_server:abcast(Nodes, ?SERVER, |
170 | 171 | {joined_cluster, node(), rabbit_mnesia:node_type()}), |
172 | ||
171 | 173 | ok. |
172 | 174 | |
173 | 175 | -spec notify_left_cluster(node()) -> 'ok'. |
535 | 537 | ram -> DiscNodes |
536 | 538 | end, |
537 | 539 | RunningNodes}), |
540 | rabbit_log:debug("Node '~p' has joined the cluster", [Node]), | |
541 | rabbit_event:notify(node_added, [{node, Node}]), | |
538 | 542 | {noreply, State}; |
539 | 543 | |
540 | 544 | handle_cast({left_cluster, Node}, State) -> |
871 | 875 | majority([]). |
872 | 876 | |
873 | 877 | majority(NodesDown) -> |
874 | Nodes = rabbit_mnesia:cluster_nodes(all), | |
878 | Nodes = rabbit_nodes:all(), | |
875 | 879 | AliveNodes = alive_nodes(Nodes) -- NodesDown, |
876 | 880 | length(AliveNodes) / length(Nodes) > 0.5. |
877 | 881 | |
884 | 888 | in_preferred_partition(PreferredNodes, []). |
885 | 889 | |
886 | 890 | in_preferred_partition(PreferredNodes, NodesDown) -> |
887 | Nodes = rabbit_mnesia:cluster_nodes(all), | |
891 | Nodes = rabbit_nodes:all(), | |
888 | 892 | RealPreferredNodes = [N || N <- PreferredNodes, lists:member(N, Nodes)], |
889 | 893 | AliveNodes = alive_nodes(RealPreferredNodes) -- NodesDown, |
890 | 894 | RealPreferredNodes =:= [] orelse AliveNodes =/= []. |
891 | 895 | |
892 | 896 | all_nodes_up() -> |
893 | Nodes = rabbit_mnesia:cluster_nodes(all), | |
897 | Nodes = rabbit_nodes:all(), | |
894 | 898 | length(alive_nodes(Nodes)) =:= length(Nodes). |
895 | 899 | |
896 | 900 | -spec all_rabbit_nodes_up() -> boolean(). |
897 | 901 | |
898 | 902 | all_rabbit_nodes_up() -> |
899 | Nodes = rabbit_mnesia:cluster_nodes(all), | |
903 | Nodes = rabbit_nodes:all(), | |
900 | 904 | length(alive_rabbit_nodes(Nodes)) =:= length(Nodes). |
901 | 905 | |
902 | alive_nodes() -> alive_nodes(rabbit_mnesia:cluster_nodes(all)). | |
906 | alive_nodes() -> alive_nodes(rabbit_nodes:all()). | |
903 | 907 | |
904 | 908 | -spec alive_nodes([node()]) -> [node()]. |
905 | 909 | |
906 | 910 | alive_nodes(Nodes) -> [N || N <- Nodes, lists:member(N, [node()|nodes()])]. |
907 | 911 | |
908 | alive_rabbit_nodes() -> alive_rabbit_nodes(rabbit_mnesia:cluster_nodes(all)). | |
912 | alive_rabbit_nodes() -> alive_rabbit_nodes(rabbit_nodes:all()). | |
909 | 913 | |
910 | 914 | -spec alive_rabbit_nodes([node()]) -> [node()]. |
911 | 915 | |
917 | 921 | -spec ping_all() -> 'ok'. |
918 | 922 | |
919 | 923 | ping_all() -> |
920 | [net_adm:ping(N) || N <- rabbit_mnesia:cluster_nodes(all)], | |
924 | [net_adm:ping(N) || N <- rabbit_nodes:all()], | |
921 | 925 | ok. |
922 | 926 | |
923 | 927 | possibly_partitioned_nodes() -> |
13 | 13 | await_running_count/2, is_single_node_cluster/0, |
14 | 14 | boot/0]). |
15 | 15 | -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]). | |
17 | 17 | -export([lock_id/1, lock_retries/0]). |
18 | 18 | |
19 | 19 | -include_lib("kernel/include/inet.hrl"). |
136 | 136 | ensure_epmd() -> |
137 | 137 | rabbit_nodes_common:ensure_epmd(). |
138 | 138 | |
139 | -spec all() -> [node()]. | |
140 | all() -> rabbit_mnesia:cluster_nodes(all). | |
141 | ||
139 | 142 | -spec all_running() -> [node()]. |
140 | 143 | all_running() -> rabbit_mnesia:cluster_nodes(running). |
141 | 144 | |
143 | 146 | running_count() -> length(all_running()). |
144 | 147 | |
145 | 148 | -spec total_count() -> integer(). |
146 | total_count() -> length(rabbit_mnesia:cluster_nodes(all)). | |
149 | total_count() -> length(rabbit_nodes:all()). | |
147 | 150 | |
148 | 151 | -spec is_single_node_cluster() -> boolean(). |
149 | 152 | is_single_node_cluster() -> |
37 | 37 | -export([validate/5, notify/5, notify_clear/4]). |
38 | 38 | -export([parse_set/7, set/7, delete/3, lookup/2, list/0, list/1, |
39 | 39 | 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, | |
41 | 41 | list_formatted_op/1, list_formatted_op/3, |
42 | 42 | match_all/2, match_as_map/1, match_op_as_map/1, definition_keys/1, |
43 | 43 | list_in/1, list_in/2, list_as_maps/0, list_as_maps/1, list_op_as_maps/1 |
44 | 44 | ]). |
45 | -export([sort_by_priority/1]). | |
45 | 46 | |
46 | 47 | -rabbit_boot_step({?MODULE, |
47 | 48 | [{description, "policy parameters"}, |
134 | 135 | |
135 | 136 | list_op(VHost) -> |
136 | 137 | list0_op(VHost, fun ident/1). |
138 | ||
139 | list_op(VHost, DefinitionKeys) -> | |
140 | [P || P <- list_op(VHost), keys_overlap(definition_keys(P), DefinitionKeys)]. | |
137 | 141 | |
138 | 142 | list_formatted_op(VHost) -> |
139 | 143 | sort_by_priority(list0_op(VHost, fun rabbit_json:encode/1)). |
175 | 175 | rabbit_data_coercion:to_atom(ra:new_uid(N)) |
176 | 176 | end, |
177 | 177 | Id = {RaName, node()}, |
178 | Nodes = select_quorum_nodes(QuorumSize, rabbit_mnesia:cluster_nodes(all)), | |
178 | Nodes = select_quorum_nodes(QuorumSize, rabbit_nodes:all()), | |
179 | 179 | NewQ0 = amqqueue:set_pid(Q, Id), |
180 | 180 | NewQ1 = amqqueue:set_type_state(NewQ0, #{nodes => Nodes}), |
181 | 181 | |
439 | 439 | {messages_unacknowledged, MU}, |
440 | 440 | {reductions, R}]), |
441 | 441 | ok = repair_leader_record(QName, Self), |
442 | ExpectedNodes = rabbit_mnesia:cluster_nodes(all), | |
442 | ExpectedNodes = rabbit_nodes:all(), | |
443 | 443 | case Nodes -- ExpectedNodes of |
444 | 444 | [] -> |
445 | 445 | ok; |
1505 | 1505 | SockStat =:= send_pend -> |
1506 | 1506 | socket_info(fun (Sock) -> rabbit_net:getstat(Sock, [SockStat]) end, |
1507 | 1507 | 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; | |
1509 | 1510 | i(ssl_protocol, S) -> ssl_info(fun ({P, _}) -> P end, S); |
1510 | 1511 | i(ssl_key_exchange, S) -> ssl_info(fun ({_, {K, _, _}}) -> K end, S); |
1511 | 1512 | i(ssl_cipher, S) -> ssl_info(fun ({_, {_, C, _}}) -> C end, S); |
1575 | 1576 | {error, _} -> 0 |
1576 | 1577 | end. |
1577 | 1578 | |
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 | |
1580 | 1581 | nossl -> ''; |
1581 | 1582 | {error, _} -> ''; |
1582 | 1583 | {ok, Items} -> |
254 | 254 | process_command([Server | Servers], Cmd) -> |
255 | 255 | case ra:process_command(Server, Cmd, ?CMD_TIMEOUT) of |
256 | 256 | {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]), | |
259 | 259 | process_command(Servers, Cmd); |
260 | 260 | {error, noproc} -> |
261 | 261 | process_command(Servers, Cmd); |
469 | 469 | [{aux, maybe_resize_coordinator_cluster}]. |
470 | 470 | |
471 | 471 | maybe_resize_coordinator_cluster() -> |
472 | spawn_link(fun() -> | |
472 | spawn(fun() -> | |
473 | 473 | case ra:members({?MODULE, node()}) of |
474 | 474 | {_, Members, _} -> |
475 | 475 | MemberNodes = [Node || {_, Node} <- Members], |
476 | 476 | Running = rabbit_mnesia:cluster_nodes(running), |
477 | All = rabbit_mnesia:cluster_nodes(all), | |
477 | All = rabbit_nodes:all(), | |
478 | 478 | case Running -- MemberNodes of |
479 | 479 | [] -> |
480 | 480 | ok; |
510 | 510 | add_members(Members, Nodes) |
511 | 511 | end; |
512 | 512 | 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]), | |
515 | 515 | add_members(Members, Nodes) |
516 | 516 | end. |
517 | 517 | |
528 | 528 | -record(aux, {actions = #{} :: |
529 | 529 | #{pid() := {stream_id(), #{node := node(), |
530 | 530 | index := non_neg_integer(), |
531 | epoch := osiris:epoch()}}}, | |
531 | epoch := osiris:epoch()}}}, | |
532 | 532 | resizer :: undefined | pid()}). |
533 | 533 | |
534 | 534 | init_aux(_Name) -> |
535 | 535 | #aux{}. |
536 | % {#{}, undefined}. | |
537 | 536 | |
538 | 537 | %% TODO ensure the dead writer is restarted as a replica at some point in time, increasing timeout? |
539 | 538 | handle_aux(leader, _, maybe_resize_coordinator_cluster, |
629 | 628 | run_action(Action, StreamId, #{node := _Node, |
630 | 629 | epoch := _Epoch} = Args, |
631 | 630 | 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}], | |
634 | 637 | Actions = Actions0#{Pid => {StreamId, Action, Args}}, |
635 | 638 | {no_reply, Aux#aux{actions = Actions}, Log, Effects}. |
636 | 639 | |
642 | 645 | fun() -> |
643 | 646 | try osiris_replica:start(Node, Conf0) of |
644 | 647 | {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]), | |
647 | 650 | send_self_command({member_started, StreamId, |
648 | 651 | Args#{pid => Pid}}); |
649 | 652 | {error, already_present} -> |
658 | 661 | send_self_command({member_started, StreamId, |
659 | 662 | Args#{pid => Pid}}); |
660 | 663 | {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]), | |
661 | 666 | maybe_sleep(Reason), |
662 | rabbit_log:warning("~s: Error while starting replica for ~s : ~W", | |
663 | [?MODULE, maps:get(name, Conf0), Reason, 10]), | |
664 | 667 | 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), | |
669 | 672 | send_action_failed(StreamId, starting, Args) |
670 | 673 | end |
671 | 674 | end. |
686 | 689 | _ -> |
687 | 690 | send_action_failed(StreamId, deleting, Arg) |
688 | 691 | 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]), | |
691 | 694 | maybe_sleep(E), |
692 | 695 | send_action_failed(StreamId, deleting, Arg) |
693 | 696 | end |
706 | 709 | [?MODULE, StreamId, Node, Epoch, Tail]), |
707 | 710 | send_self_command({member_stopped, StreamId, Arg}); |
708 | 711 | 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), | |
712 | 715 | send_action_failed(StreamId, stopping, Arg0) |
713 | 716 | 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), | |
717 | 720 | send_action_failed(StreamId, stopping, Arg0) |
718 | 721 | end; |
719 | 722 | 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), | |
723 | 727 | send_action_failed(StreamId, stopping, Arg0) |
724 | 728 | 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) | |
730 | 733 | end |
731 | 734 | end. |
732 | 735 | |
736 | 739 | try osiris_writer:start(Conf) of |
737 | 740 | {ok, Pid} -> |
738 | 741 | 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]), | |
741 | 744 | send_self_command({member_started, StreamId, Args}); |
742 | 745 | 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]), | |
747 | 750 | send_action_failed(StreamId, starting, Args0) |
748 | 751 | 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]), | |
752 | 754 | send_action_failed(StreamId, starting, Args0) |
753 | 755 | end |
754 | 756 | end. |
759 | 761 | try osiris:update_retention(Pid, Retention) of |
760 | 762 | ok -> |
761 | 763 | 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), | |
766 | 768 | send_action_failed(StreamId, update_retention, Args) |
767 | 769 | 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", | |
770 | 771 | [?MODULE, StreamId, node(Pid), Err]), |
771 | 772 | maybe_sleep(Err), |
772 | 773 | send_action_failed(StreamId, update_retention, Args) |
777 | 778 | case rpc:call(Node, ?MODULE, log_overview, [Conf]) of |
778 | 779 | {badrpc, nodedown} -> |
779 | 780 | {error, nodedown}; |
781 | {error, _} = Err -> | |
782 | Err; | |
780 | 783 | {_Range, Offsets} -> |
781 | 784 | {ok, select_highest_offset(Offsets)} |
782 | 785 | end. |
787 | 790 | lists:last(Offsets). |
788 | 791 | |
789 | 792 | 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. | |
792 | 800 | |
793 | 801 | |
794 | 802 | replay(L) when is_list(L) -> |
859 | 867 | case maps:size(Members) =< 1 of |
860 | 868 | true -> |
861 | 869 | 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", | |
863 | 871 | [?MODULE, Node, StreamId]), |
864 | 872 | {error, last_stream_member}; |
865 | 873 | false -> |
874 | 882 | catch |
875 | 883 | _:E:Stacktrace -> |
876 | 884 | 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]), | |
879 | 887 | Stream |
880 | 888 | end. |
881 | 889 | |
1501 | 1509 | Node. |
1502 | 1510 | |
1503 | 1511 | maybe_sleep({{nodedown, _}, _}) -> |
1512 | timer:sleep(10000); | |
1513 | maybe_sleep({noproc, _}) -> | |
1504 | 1514 | timer:sleep(5000); |
1505 | maybe_sleep({noproc, _}) -> | |
1515 | maybe_sleep({error, nodedown}) -> | |
1516 | timer:sleep(5000); | |
1517 | maybe_sleep({error, _}) -> | |
1506 | 1518 | timer:sleep(5000); |
1507 | 1519 | maybe_sleep(_) -> |
1508 | 1520 | ok. |
0 | 0 | |
1 | 1 | -define(STREAM_COORDINATOR_STARTUP, {stream_coordinator_startup, self()}). |
2 | -define(TICK_TIMEOUT, 1000). | |
2 | -define(TICK_TIMEOUT, 30000). | |
3 | 3 | -define(RESTART_TIMEOUT, 1000). |
4 | 4 | -define(PHASE_RETRY_TIMEOUT, 10000). |
5 | 5 | -define(CMD_TIMEOUT, 30000). |
749 | 749 | InitialClusterSize = initial_cluster_size( |
750 | 750 | args_policy_lookup(<<"initial-cluster-size">>, |
751 | 751 | fun policy_precedence/2, Q)), |
752 | Replicas0 = rabbit_mnesia:cluster_nodes(all) -- [Node], | |
752 | Replicas0 = rabbit_nodes:all() -- [Node], | |
753 | 753 | %% TODO: try to avoid nodes that are not connected |
754 | 754 | Replicas = select_stream_nodes(InitialClusterSize - 1, Replicas0), |
755 | 755 | Formatter = {?MODULE, format_osiris_event, [QName]}, |
106 | 106 | ok -> |
107 | 107 | ok; |
108 | 108 | {timeout, BadTabs} -> |
109 | AllNodes = rabbit_mnesia:cluster_nodes(all), | |
109 | AllNodes = rabbit_nodes:all(), | |
110 | 110 | {error, {timeout_waiting_for_tables, AllNodes, BadTabs}}; |
111 | 111 | {error, Reason} -> |
112 | AllNodes = rabbit_mnesia:cluster_nodes(all), | |
112 | AllNodes = rabbit_nodes:all(), | |
113 | 113 | {error, {failed_waiting_for_tables, AllNodes, Reason}} |
114 | 114 | end, |
115 | 115 | case {Retries, Result} of |
110 | 110 | -spec maybe_upgrade_mnesia() -> 'ok'. |
111 | 111 | |
112 | 112 | maybe_upgrade_mnesia() -> |
113 | AllNodes = rabbit_mnesia:cluster_nodes(all), | |
113 | AllNodes = rabbit_nodes:all(), | |
114 | 114 | ok = rabbit_mnesia_rename:maybe_finish(AllNodes), |
115 | 115 | %% Mnesia upgrade is the first upgrade scope, |
116 | 116 | %% so we should create a backup here if there are any upgrades |
12 | 12 | -export([recover/0, recover/1, read_config/1]). |
13 | 13 | -export([add/2, add/4, delete/2, exists/1, with/2, with_user_and_vhost/3, assert/1, update/2, |
14 | 14 | 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]). | |
16 | 16 | -export([parse_tags/1, update_metadata/2, tag_with/2, untag_from/2, update_tags/2, update_tags/3]). |
17 | 17 | -export([lookup/1]). |
18 | 18 | -export([info/1, info/2, info_all/0, info_all/1, info_all/2, info_all/3]). |
128 | 128 | case hd(Val) of |
129 | 129 | Bin when is_binary(Bin) -> |
130 | 130 | %% 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 | |
131 | 134 | [trim_tag(Tag) || Tag <- Val]; |
132 | 135 | Int when is_integer(Int) -> |
133 | 136 | %% this is a string/charlist |
202 | 205 | {error, Msg} |
203 | 206 | end. |
204 | 207 | |
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 | ||
205 | 228 | -spec delete(vhost:name(), rabbit_types:username()) -> rabbit_types:ok_or_error(any()). |
206 | 229 | |
207 | 230 | delete(VHost, ActingUser) -> |
239 | 262 | "null" -> <<"">>; |
240 | 263 | Other -> Other |
241 | 264 | end, |
265 | ParsedTags = parse_tags(Tags), | |
266 | rabbit_log:debug("Parsed tags ~p to ~p", [Tags, ParsedTags]), | |
242 | 267 | Result = case exists(Name) of |
243 | true -> ok; | |
268 | true -> | |
269 | update(Name, Description, ParsedTags, Username); | |
244 | 270 | false -> |
245 | ParsedTags = parse_tags(Tags), | |
246 | rabbit_log:debug("Parsed tags ~p to ~p", [Tags, ParsedTags]), | |
247 | 271 | add(Name, Description, ParsedTags, Username), |
248 | 272 | %% wait for up to 45 seconds for the vhost to initialise |
249 | 273 | %% on all nodes |
378 | 402 | |
379 | 403 | -spec all() -> [vhost:vhost()]. |
380 | 404 | 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()). | |
381 | 417 | |
382 | 418 | -spec count() -> non_neg_integer(). |
383 | 419 | count() -> |
26 | 26 | get_tags/1, |
27 | 27 | set_limits/2, |
28 | 28 | set_metadata/2, |
29 | merge_metadata/2, | |
29 | 30 | is_tagged_with/2 |
30 | 31 | ]). |
31 | 32 | |
172 | 173 | vhost_v1:set_limits(VHost, Value) |
173 | 174 | end. |
174 | 175 | |
176 | -spec set_metadata(vhost(), metadata()) -> vhost(). | |
175 | 177 | set_metadata(VHost, Value) -> |
176 | 178 | case record_version_to_use() of |
177 | 179 | ?record_version -> |
181 | 183 | VHost |
182 | 184 | end. |
183 | 185 | |
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 | ||
184 | 198 | -spec is_tagged_with(vhost:vhost(), atom()) -> boolean(). |
185 | 199 | is_tagged_with(VHost, Tag) -> |
186 | 200 | lists:member(Tag, get_tags(VHost)). |
177 | 177 | deps = DEPS + RUNTIME_DEPS, |
178 | 178 | ) |
179 | 179 | |
180 | xref(tags = ["xref"]) | |
180 | xref( | |
181 | additional_libs = [ | |
182 | "@ranch//:bazel_erlang_lib", | |
183 | ], | |
184 | tags = ["xref"], | |
185 | ) | |
181 | 186 | |
182 | 187 | plt( |
183 | 188 | name = "base_plt", |
224 | 224 | -define(SUPERVISOR_WAIT, |
225 | 225 | rabbit_misc:get_env(rabbit, supervisor_shutdown_timeout, infinity)). |
226 | 226 | -define(WORKER_WAIT, |
227 | rabbit_misc:get_env(rabbit, worker_shutdown_timeout, 30000)). | |
227 | rabbit_misc:get_env(rabbit, worker_shutdown_timeout, 300000)). | |
228 | 228 | -define(MSG_STORE_WORKER_WAIT, |
229 | 229 | 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)). | |
230 | 232 | |
231 | 233 | -define(HIBERNATE_AFTER_MIN, 1000). |
232 | 234 | -define(DESIRED_HIBERNATE, 10000). |
17 | 17 | RMQ_ERLC_OPTS += -pa $(DEPS_DIR)/rabbitmq_cli/_build/dev/lib/rabbitmqctl/ebin |
18 | 18 | endif |
19 | 19 | |
20 | RMQ_ERLC_OPTS += +deterministic | |
21 | ||
20 | 22 | # Push our compilation options to both the normal and test ERLC_OPTS. |
21 | 23 | ERLC_OPTS += $(RMQ_ERLC_OPTS) |
22 | 24 | TEST_ERLC_OPTS += $(RMQ_ERLC_OPTS) |
14 | 14 | setopts/2, send/2, close/1, fast_close/1, sockname/1, peername/1, |
15 | 15 | peercert/1, connection_string/2, socket_ends/2, is_loopback/1, |
16 | 16 | 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]). | |
18 | 18 | |
19 | 19 | %%--------------------------------------------------------------------------- |
20 | 20 | |
33 | 33 | % -type host_or_ip() :: binary() | inet:ip_address(). |
34 | 34 | -spec is_ssl(socket()) -> boolean(). |
35 | 35 | -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()}]). | |
36 | 37 | -spec controlling_process(socket(), pid()) -> ok_or_any_error(). |
37 | 38 | -spec getstat(socket(), [stat_option()]) -> |
38 | 39 | ok_val_or_error([{stat_option(), integer()}]). |
97 | 98 | ssl_info(_Sock) -> |
98 | 99 | nossl. |
99 | 100 | |
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 | ||
100 | 114 | controlling_process(Sock, Pid) when ?IS_SSL(Sock) -> |
101 | 115 | ssl:controlling_process(Sock, Pid); |
102 | 116 | controlling_process(Sock, Pid) when is_port(Sock) -> |
792 | 792 | {error, _} -> '' |
793 | 793 | end. |
794 | 794 | |
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 | |
797 | 797 | nossl -> ''; |
798 | 798 | {error, _} -> ''; |
799 | 799 | {ok, Items} -> |
37 | 37 | "//deps/rabbit_common:bazel_erlang_lib", |
38 | 38 | "@credentials_obfuscation//:bazel_erlang_lib", |
39 | 39 | "@jsx//:bazel_erlang_lib", |
40 | "@ranch//:bazel_erlang_lib", | |
40 | 41 | "@recon//:bazel_erlang_lib", |
41 | 42 | ], |
42 | 43 | tags = ["xref"], |
41 | 41 | -define(METADATA_TOKEN_TTL_SECONDS, 60). |
42 | 42 | |
43 | 43 | -define(METADATA_TOKEN, "X-aws-ec2-metadata-token"). |
44 | ||
45 | -define(LINEAR_BACK_OFF_MILLIS, 500). | |
46 | -define(MAX_RETRIES, 5). | |
44 | 47 | |
45 | 48 | -type access_key() :: nonempty_string(). |
46 | 49 | -type secret_access_key() :: nonempty_string(). |
84 | 84 | refresh_credentials(State) -> |
85 | 85 | rabbit_log:debug("Refreshing AWS credentials..."), |
86 | 86 | {_, NewState} = load_credentials(State), |
87 | rabbit_log:debug("AWS credentials have been refreshed."), | |
87 | rabbit_log:debug("AWS credentials have been refreshed"), | |
88 | 88 | set_credentials(NewState). |
89 | 89 | |
90 | 90 | |
333 | 333 | security_token = SecurityToken, |
334 | 334 | imdsv2_token = undefined}}; |
335 | 335 | {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]), | |
337 | 337 | {error, #state{region = Region, |
338 | 338 | error = Reason, |
339 | 339 | access_key = undefined, |
496 | 496 | %% @doc Determine whether or not an Imdsv2Token has expired. |
497 | 497 | %% @end |
498 | 498 | 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"), | |
500 | 500 | true; |
501 | 501 | expired_imdsv2_token({_, _, undefined}) -> |
502 | rabbit_log:debug("EC2 IMDSv2 token is not available."), | |
502 | rabbit_log:debug("EC2 IMDSv2 token is not available"), | |
503 | 503 | true; |
504 | 504 | expired_imdsv2_token({_, _, Expiration}) -> |
505 | 505 | Now = calendar:datetime_to_gregorian_seconds(local_time()), |
526 | 526 | %% If the credentials are not available or have expired, then refresh them before performing the request. |
527 | 527 | %% @end |
528 | 528 | 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"), | |
530 | 530 | {ok, State} = gen_server:call(rabbitmq_aws, get_state), |
531 | 531 | case has_credentials(State) of |
532 | 532 | true -> case expired_credentials(State#state.expiration) of |
542 | 542 | %% @end |
543 | 543 | api_get_request(Service, Path) -> |
544 | 544 | 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) -> | |
545 | 555 | ensure_credentials_valid(), |
546 | 556 | case get(Service, Path) of |
547 | 557 | {ok, {_Headers, Payload}} -> rabbit_log:debug("AWS request: ~s~nResponse: ~p", [Path, Payload]), |
548 | 558 | {ok, Payload}; |
549 | 559 | {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) | |
551 | 563 | end. |
0 | 0 | PROJECT = rabbitmq_cli |
1 | ||
2 | dep_observer_cli = git https://github.com/zhongwencool/observer_cli 1.4.4 | |
3 | 1 | |
4 | 2 | BUILD_DEPS = rabbit_common |
5 | 3 | DEPS = observer_cli |
12 | 10 | MAX_CASES ?= 1 |
13 | 11 | |
14 | 12 | 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) | |
16 | 14 | |
17 | 15 | ifneq ("",$(MIX_TEST_OPTS)) |
18 | 16 | MIX_TEST := $(MIX_TEST) $(MIX_TEST_OPTS) |
105 | 103 | # ones). |
106 | 104 | $(ACTUAL_ESCRIPTS): $(rabbitmqctl_srcs) |
107 | 105 | $(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; \ | |
109 | 107 | else \ |
110 | echo y | mix make_all; \ | |
108 | echo y | ERL_COMPILER_OPTIONS=deterministic mix make_all; \ | |
111 | 109 | fi |
112 | 110 | |
113 | 111 | $(LINKED_ESCRIPTS): |
119 | 119 | {:json, "~> 1.4.1"}, |
120 | 120 | {:csv, "~> 2.4.0"}, |
121 | 121 | {:stdout_formatter, "~> 0.2.3"}, |
122 | {:observer_cli, "~> 1.6.1"}, | |
122 | {:observer_cli, "~> 1.7.1"}, | |
123 | 123 | |
124 | 124 | {:amqp, "~> 2.1.0", only: :test}, |
125 | 125 | {:dialyxir, "~> 0.5", only: :test, runtime: false}, |
0 | 0 | %{ |
1 | 1 | "csv": {:hex, :csv, "2.4.1", "50e32749953b6bf9818dbfed81cf1190e38cdf24f95891303108087486c5925e", [:mix], [{:parallel_stream, "~> 1.0.4", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "54508938ac67e27966b10ef49606e3ad5995d665d7fc2688efb3eab1307c9079"}, |
2 | 2 | "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"}, | |
4 | 4 | "parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm", "639b2e8749e11b87b9eb42f2ad325d161c170b39b288ac8d04c4f31f8f0823eb"}, |
5 | 5 | "recon": {:hex, :recon, "2.5.2", "cba53fa8db83ad968c9a652e09c3ed7ddcc4da434f27c3eaa9ca47ffb2b1ff03", [:mix, :rebar3], [], "hexpm", "2c7523c8dee91dff41f6b3d63cba2bd49eb6d2fe5bf1eec0df7f87eb5e230e1c"}, |
6 | 6 | "stdout_formatter": {:hex, :stdout_formatter, "0.2.4", "d9394ab3f5adcda97a4b8b1c0b798dcb286b07913c4020ea7f78c88723fb145e", [:mix, :rebar3], [], "hexpm", "51f1df921b0477275ea712763042155dbc74acc75d9648dbd54985c45c913b29"}, |
84 | 84 | fi |
85 | 85 | |
86 | 86 | export DEPS_DIR={mix_deps_dir} |
87 | export ERL_COMPILER_OPTIONS=deterministic | |
87 | 88 | mix local.hex --force |
88 | 89 | mix local.rebar --force |
89 | 90 | mix make_all |
77 | 77 | fi |
78 | 78 | |
79 | 79 | export DEPS_DIR={mix_deps_dir} |
80 | export ERL_COMPILER_OPTIONS=deterministic | |
80 | 81 | mix local.hex --force |
81 | 82 | mix local.rebar --force |
82 | 83 | mix make_deps |
97 | 97 | }); |
98 | 98 | |
99 | 99 | 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'); | |
104 | 116 | }); |
105 | 117 | sammy.put('#/queues', function() { |
106 | 118 | if (sync_put(this, '/queues/:vhost/:name')) |
167 | 167 | |
168 | 168 | var RENDER_CALLBACKS = {}; |
169 | 169 | |
170 | const QUEUE_EXTRA_CONTENT = []; | |
171 | const QUEUE_EXTRA_CONTENT_REQUESTS = []; | |
172 | ||
170 | 173 | // All help ? popups |
171 | 174 | var HELP = { |
172 | 175 | 'delivery-limit': |
479 | 479 | if(outstanding_reqs.length > 0){ |
480 | 480 | return false; |
481 | 481 | } |
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) { | |
483 | 485 | var html = format(current_template, json); |
484 | 486 | fun(html); |
485 | 487 | update_status('ok'); |
1115 | 1117 | if (keys(reqs).length > 0) { |
1116 | 1118 | var key = keys(reqs)[0]; |
1117 | 1119 | 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 | } | |
1119 | 1127 | var remainder = {}; |
1120 | 1128 | for (var k in reqs) { |
1121 | 1129 | if (k != key) remainder[k] = reqs[k]; |
1149 | 1157 | console.log("Stack: " + err['stack']); |
1150 | 1158 | debug(err['name'] + ": " + err['message'] + "\n" + err['stack'] + "\n"); |
1151 | 1159 | } |
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; | |
1152 | 1168 | } |
1153 | 1169 | |
1154 | 1170 | function update_status(status) { |
73 | 73 | </div> |
74 | 74 | |
75 | 75 | <div class="section"> |
76 | <h2>Consumers</h2> | |
76 | <h2 class="updatable" >Consumers (<%=(channel.consumer_details.length)%>) </h2> | |
77 | 77 | <div class="hider updatable"> |
78 | 78 | <%= format('consumers', {'mode': 'channel', 'consumers': channel.consumer_details}) %> |
79 | 79 | </div> |
75 | 75 | </div> |
76 | 76 | |
77 | 77 | <div class="section"> |
78 | <h2>Channels</h2> | |
78 | <h2 class="updatable" >Channels (<%=(channels.length)%>) </h2> | |
79 | 79 | <div class="hider updatable"> |
80 | 80 | <%= format('channels-list', {'channels': channels, 'mode': 'connection'}) %> |
81 | 81 | </div> |
266 | 266 | |
267 | 267 | <% } %> |
268 | 268 | |
269 | ||
270 | 269 | <% 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> | |
273 | 276 | <div class="hider updatable"> |
274 | 277 | <%= format('consumers', {'mode': 'queue', 'consumers': queue.consumer_details}) %> |
275 | 278 | </div> |
277 | 280 | <% } %> |
278 | 281 | |
279 | 282 | <div class="section-hidden"> |
280 | <h2>Bindings</h2> | |
283 | <h2 class="updatable">Bindings (<%=(bindings.length)%>) </h2> | |
281 | 284 | <div class="hider"> |
282 | 285 | <div class="bindings-wrapper"> |
283 | 286 | <%= format('bindings', {'mode': 'queue', 'bindings': bindings}) %> |
104 | 104 | gc_process_and_entity(channel_exchange_stats_fine_stats, GbSet). |
105 | 105 | |
106 | 106 | gc_nodes() -> |
107 | Nodes = rabbit_mnesia:cluster_nodes(all), | |
107 | Nodes = rabbit_nodes:all(), | |
108 | 108 | GbSet = gb_sets:from_list(Nodes), |
109 | 109 | gc_entity(node_stats, GbSet), |
110 | 110 | gc_entity(node_coarse_stats, GbSet), |
110 | 110 | PACKAGE, |
111 | 111 | name = "cluster_SUITE", |
112 | 112 | size = "large", |
113 | flaky = True, | |
113 | 114 | runtime_deps = [ |
114 | 115 | "@emqttc//:bazel_erlang_lib", |
115 | 116 | ], |
23 | 23 | {?ID_NAME, Node}. |
24 | 24 | |
25 | 25 | all_node_ids() -> |
26 | [server_id(N) || N <- rabbit_mnesia:cluster_nodes(all), | |
26 | [server_id(N) || N <- rabbit_nodes:all(), | |
27 | 27 | can_participate_in_clientid_tracking(N)]. |
28 | 28 | |
29 | 29 | start() -> |
27 | 27 | |
28 | 28 | xref( |
29 | 29 | additional_libs = [ |
30 | "@ranch//:bazel_erlang_lib", | |
30 | 31 | "@systemd//:bazel_erlang_lib", |
31 | 32 | ], |
32 | 33 | tags = ["xref"], |
84 | 84 | rabbitmqctl eval 'application:set_env(rabbitmq_prometheus, return_per_object_metrics, false).' |
85 | 85 | ``` |
86 | 86 | |
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`. | |
87 | 101 | |
88 | 102 | ## Contributing |
89 | 103 |
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 | |
+172
-92
19 | 19 | -include_lib("rabbit_common/include/rabbit.hrl"). |
20 | 20 | |
21 | 21 | -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_), | |
27 | 24 | %% as observed by RabbitMQ. |
25 | ||
26 | %% Used by `/metrics` and `/metrics/per-object`. | |
28 | 27 | -define(METRIC_NAME_PREFIX, "rabbitmq_"). |
28 | ||
29 | %% Used by `/metrics/detailed` endpoint | |
30 | -define(DETAILED_METRIC_NAME_PREFIX, "rabbitmq_detailed_"). | |
29 | 31 | |
30 | 32 | %% ==The source of these metrics can be found in the rabbit_core_metrics module== |
31 | 33 | %% The relevant files are: |
51 | 53 | -define(MICROSECOND, 1000000). |
52 | 54 | |
53 | 55 | -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 | |
85 | 58 | {connection_churn_metrics, [ |
86 | 59 | {2, undefined, connections_opened_total, counter, "Total number of connections opened"}, |
87 | 60 | {3, undefined, connections_closed_total, counter, "Total number of connections closed or terminated"}, |
91 | 64 | {7, undefined, queues_created_total, counter, "Total number of queues created"}, |
92 | 65 | {8, undefined, queues_deleted_total, counter, "Total number of queues deleted"} |
93 | 66 | ]}, |
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 | ||
112 | 67 | {node_coarse_metrics, [ |
113 | 68 | {2, undefined, process_open_fds, gauge, "Open file descriptors", fd_used}, |
114 | 69 | {2, undefined, process_open_tcp_sockets, gauge, "Open TCP sockets", sockets_used}, |
119 | 74 | {2, undefined, erlang_gc_reclaimed_bytes_total, counter, "Total number of bytes of memory reclaimed by Erlang garbage collector", gc_bytes_reclaimed}, |
120 | 75 | {2, undefined, erlang_scheduler_context_switches_total, counter, "Total number of Erlang scheduler context switches", context_switches} |
121 | 76 | ]}, |
122 | ||
123 | 77 | {node_metrics, [ |
124 | 78 | {2, undefined, process_max_fds, gauge, "Open file descriptors limit", fd_total}, |
125 | 79 | {2, undefined, process_max_tcp_sockets, gauge, "Open TCP sockets limit", sockets_total}, |
163 | 117 | {7, ?MILLISECOND, raft_entry_commit_latency_seconds, gauge, "Time taken for a log entry to be committed"} |
164 | 118 | ]}, |
165 | 119 | |
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. | |
166 | 133 | {queue_coarse_metrics, [ |
167 | 134 | {2, undefined, queue_messages_ready, gauge, "Messages ready to be delivered to consumers"}, |
168 | 135 | {3, undefined, queue_messages_unacked, gauge, "Messages delivered to consumers but not yet acknowledged"}, |
169 | 136 | {4, undefined, queue_messages, gauge, "Sum of ready and unacknowledged messages - total queue depth"}, |
170 | 137 | {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} | |
171 | 142 | ]}, |
172 | 143 | |
173 | 144 | {queue_metrics, [ |
190 | 161 | {2, undefined, queue_disk_writes_total, counter, "Total number of times queue wrote messages to disk", disk_writes} |
191 | 162 | ]}, |
192 | 163 | |
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"} | |
203 | 213 | ]} |
204 | ||
205 | 214 | ]). |
206 | 215 | |
207 | 216 | -define(TOTALS, [ |
221 | 230 | |
222 | 231 | deregister_cleanup(_) -> ok. |
223 | 232 | |
233 | collect_mf('detailed', Callback) -> | |
234 | collect(true, ?DETAILED_METRIC_NAME_PREFIX, vhosts_filter_from_pdict(), enabled_mfs_from_pdict(), Callback), | |
235 | ok; | |
224 | 236 | collect_mf('per-object', Callback) -> |
225 | collect(true, Callback); | |
237 | collect(true, ?METRIC_NAME_PREFIX, false, ?METRICS_RAW, Callback), | |
238 | totals(Callback), | |
239 | ok; | |
226 | 240 | collect_mf(_Registry, Callback) -> |
227 | 241 | 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) -> | |
231 | 247 | [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) -> | |
235 | 253 | [begin |
236 | 254 | Size = ets:info(Table, size), |
237 | 255 | mf_totals(Callback, Name, Type, Help, Size) |
238 | 256 | end || {Table, Name, Type, Help} <- ?TOTALS], |
239 | 257 | 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. | |
247 | 272 | |
248 | 273 | build_info() -> |
249 | 274 | ProductInfo = rabbit:product_info(), |
295 | 320 | add_metric_family({Name, Type, Help, Metrics}, Callback) -> |
296 | 321 | Callback(create_mf(?METRIC_NAME(Name), Help, Type, Metrics)). |
297 | 322 | |
298 | mf(Callback, Contents, Data) -> | |
323 | mf(Callback, Prefix, Contents, Data) -> | |
299 | 324 | [begin |
300 | 325 | Fun = case Conversion of |
301 | 326 | undefined -> |
305 | 330 | end, |
306 | 331 | Callback( |
307 | 332 | create_mf( |
308 | ?METRIC_NAME(Name), | |
333 | [Prefix, prometheus_model_helpers:metric_name(Name)], | |
309 | 334 | Help, |
310 | 335 | catch_boolean(Type), |
311 | 336 | ?MODULE, |
322 | 347 | end, |
323 | 348 | Callback( |
324 | 349 | create_mf( |
325 | ?METRIC_NAME(Name), | |
350 | [Prefix, prometheus_model_helpers:metric_name(Name)], | |
326 | 351 | Help, |
327 | 352 | catch_boolean(Type), |
328 | 353 | ?MODULE, |
415 | 440 | gauge_metric(Labels, Value) |
416 | 441 | end. |
417 | 442 | |
418 | get_data(connection_metrics = Table, false) -> | |
443 | get_data(connection_metrics = Table, false, _) -> | |
419 | 444 | {Table, A1, A2, A3, A4} = ets:foldl(fun({_, Props}, {T, A1, A2, A3, A4}) -> |
420 | 445 | {T, |
421 | 446 | sum(proplists:get_value(recv_cnt, Props), A1), |
424 | 449 | sum(proplists:get_value(channels, Props), A4)} |
425 | 450 | end, empty(Table), Table), |
426 | 451 | [{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, _) -> | |
428 | 453 | {Table, A1, A2, A3, A4, A5, A6, A7} = |
429 | 454 | ets:foldl(fun({_, Props}, {T, A1, A2, A3, A4, A5, A6, A7}) -> |
430 | 455 | {T, |
439 | 464 | [{Table, [{consumer_count, A1}, {messages_unacknowledged, A2}, {messages_unconfirmed, A3}, |
440 | 465 | {messages_uncommitted, A4}, {acks_uncommitted, A5}, {prefetch_count, A6}, |
441 | 466 | {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) -> | |
443 | 479 | {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}) -> | |
446 | 485 | {T, |
447 | 486 | sum(proplists:get_value(consumers, Props), A1), |
448 | 487 | sum(proplists:get_value(consumer_utilisation, Props), A2), |
469 | 508 | {message_bytes_ready, A11}, {message_bytes_unacknowledged, A12}, |
470 | 509 | {messages_paged_out, A13}, {message_bytes_paged_out, A14}, |
471 | 510 | {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; | |
473 | 512 | Table == queue_coarse_metrics; |
474 | 513 | Table == channel_queue_metrics; |
475 | 514 | Table == connection_coarse_metrics; |
476 | 515 | Table == channel_queue_exchange_metrics; |
477 | 516 | Table == ra_metrics; |
478 | 517 | 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}) -> | |
480 | 523 | {T, V1 + A1}; |
481 | 524 | ({_, V1, _}, {T, A1}) -> |
482 | 525 | {T, V1 + A1}; |
502 | 545 | _ -> |
503 | 546 | [Result] |
504 | 547 | 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, _, _) -> | |
506 | 566 | ets:tab2list(Table). |
507 | 567 | |
508 | 568 | division(0, 0) -> |
513 | 573 | accumulate_count_and_sum(Value, {Count, Sum}) -> |
514 | 574 | {Count + 1, Sum + Value}. |
515 | 575 | |
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 -> | |
517 | 577 | {T, 0}; |
518 | 578 | empty(T) when T == connection_coarse_metrics; T == auth_attempt_metrics; T == auth_attempt_detailed_metrics -> |
519 | 579 | {T, 0, 0, 0}; |
532 | 592 | B; |
533 | 593 | sum(A, B) -> |
534 | 594 | 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. |
26 | 26 | prometheus_rabbitmq_core_metrics_collector, |
27 | 27 | prometheus_rabbitmq_global_metrics_collector |
28 | 28 | ]), |
29 | prometheus_registry:register_collectors('detailed', [ | |
30 | prometheus_rabbitmq_core_metrics_collector | |
31 | ]), | |
29 | 32 | rabbit_prometheus_handler:setup(), |
30 | 33 | cowboy_router:compile([{'_', dispatcher()}]). |
31 | 34 |
31 | 31 | |
32 | 32 | setup() -> |
33 | 33 | setup_metrics(telemetry_registry()), |
34 | setup_metrics('per-object'). | |
34 | setup_metrics('per-object'), | |
35 | setup_metrics('detailed'). | |
35 | 36 | |
36 | 37 | setup_metrics(Registry) -> |
37 | 38 | ScrapeDuration = [{name, ?SCRAPE_DURATION}, |
66 | 67 | false -> |
67 | 68 | cowboy_req:reply(404, #{}, <<"Unknown Registry">>, Request); |
68 | 69 | Registry -> |
70 | put_filtering_options_into_process_dictionary(Request), | |
69 | 71 | gen_metrics_response(Registry, Request) |
70 | 72 | end; |
71 | 73 | gen_response(_, Request) -> |
145 | 147 | encode_format_("gzip", Scrape) -> |
146 | 148 | zlib:gzip(Scrape); |
147 | 149 | 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. |
35 | 35 | with a custom exchange type that distributes messages by applying |
36 | 36 | a hashing function to the routing key. |
37 | 37 | |
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. | |
38 | 47 | |
39 | 48 | ## Messages Distribution Between Shards (Partitioning) |
40 | 49 |
10 | 10 | -export([start_link/0, init/1, adjust/2, stop_child/1, cleanup_specs/0]). |
11 | 11 | |
12 | 12 | -import(rabbit_misc, [pget/2]). |
13 | -import(rabbit_data_coercion, [to_map/1, to_list/1]). | |
13 | 14 | |
14 | 15 | -include("rabbit_shovel.hrl"). |
15 | 16 | -include_lib("rabbit_common/include/rabbit.hrl"). |
41 | 42 | rabbit_log_shovel:debug("Starting a mirrored supervisor named '~s' in virtual host '~s'", [ShovelName, VHost]), |
42 | 43 | Result = case mirrored_supervisor:start_child( |
43 | 44 | ?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)]}, | |
45 | 46 | transient, ?WORKER_WAIT, worker, [rabbit_shovel_dyn_worker_sup]}) of |
46 | 47 | {ok, _Pid} -> ok; |
47 | 48 | {error, {already_started, _Pid}} -> ok |
49 | 50 | %% release the lock if we managed to acquire one |
50 | 51 | rabbit_shovel_locks:unlock(LockId), |
51 | 52 | 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). | |
52 | 58 | |
53 | 59 | child_exists(Name) -> |
54 | 60 | lists:any(fun ({N, _, _, _}) -> N =:= Name end, |
12 | 12 | |
13 | 13 | -export([validate/5, notify/5, notify_clear/4]). |
14 | 14 | -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]). | |
17 | 18 | |
18 | 19 | -rabbit_boot_step({?MODULE, |
19 | 20 | [{description, "shovel parameters"}, |
80 | 81 | _ -> |
81 | 82 | ok |
82 | 83 | 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]. | |
83 | 94 | |
84 | 95 | validate_amqp091_dest(Def) -> |
85 | 96 | [case pget2(<<"dest-exchange">>, <<"dest-queue">>, Def) of |
278 | 289 | end. |
279 | 290 | |
280 | 291 | parse_amqp10_dest({_VHost, _Name}, _ClusterName, Def, SourceHeaders) -> |
281 | Uris = get_uris(<<"dest-uri">>, Def), | |
292 | Uris = deobfuscated_uris(<<"dest-uri">>, Def), | |
282 | 293 | Address = pget(<<"dest-address">>, Def), |
283 | 294 | Properties = |
284 | 295 | rabbit_data_coercion:to_proplist( |
304 | 315 | }. |
305 | 316 | |
306 | 317 | parse_amqp091_dest({VHost, Name}, ClusterName, Def, SourceHeaders) -> |
307 | DestURIs = get_uris(<<"dest-uri">>, Def), | |
318 | DestURIs = deobfuscated_uris(<<"dest-uri">>, Def), | |
308 | 319 | DestX = pget(<<"dest-exchange">>, Def, none), |
309 | 320 | DestXKey = pget(<<"dest-exchange-key">>, Def, none), |
310 | 321 | DestQ = pget(<<"dest-queue">>, Def, none), |
372 | 383 | }, Details). |
373 | 384 | |
374 | 385 | parse_amqp10_source(Def) -> |
375 | Uris = get_uris(<<"src-uri">>, Def), | |
386 | Uris = deobfuscated_uris(<<"src-uri">>, Def), | |
376 | 387 | Address = pget(<<"src-address">>, Def), |
377 | 388 | DeleteAfter = pget(<<"src-delete-after">>, Def, <<"never">>), |
378 | 389 | PrefetchCount = pget(<<"src-prefetch-count">>, Def, 1000), |
385 | 396 | consumer_args => []}, Headers}. |
386 | 397 | |
387 | 398 | parse_amqp091_source(Def) -> |
388 | SrcURIs = get_uris(<<"src-uri">>, Def), | |
399 | SrcURIs = deobfuscated_uris(<<"src-uri">>, Def), | |
389 | 400 | SrcX = pget(<<"src-exchange">>,Def, none), |
390 | 401 | SrcXKey = pget(<<"src-exchange-key">>, Def, <<>>), %% [1] |
391 | 402 | SrcQ = pget(<<"src-queue">>, Def, none), |
429 | 440 | end, |
430 | 441 | [binary_to_list(URI) || URI <- URIs]. |
431 | 442 | |
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 | ||
432 | 448 | translate_ack_mode(<<"on-confirm">>) -> on_confirm; |
433 | 449 | translate_ack_mode(<<"on-publish">>) -> on_publish; |
434 | 450 | translate_ack_mode(<<"no-ack">>) -> no_ack. |
81 | 81 | ), |
82 | 82 | rabbitmq_integration_suite( |
83 | 83 | PACKAGE, |
84 | name = "rabbit_stream_utils_SUITE", | |
85 | ), | |
86 | rabbitmq_integration_suite( | |
87 | PACKAGE, | |
84 | 88 | name = "rabbit_stream_SUITE", |
85 | 89 | deps = [ |
86 | 90 | "//deps/rabbit:bazel_erlang_lib", |
369 | 369 | Key => uint16 // 10 |
370 | 370 | Version => uint16 |
371 | 371 | Reference => string // max 256 characters |
372 | SubscriptionId => uint8 | |
372 | Stream => string // the name of the stream | |
373 | 373 | Offset => uint64 |
374 | 374 | ``` |
375 | 375 | |
586 | 586 | RoutingKey => string |
587 | 587 | SuperStream => string |
588 | 588 | |
589 | RouteResponse => Key Version CorrelationId Stream | |
589 | RouteResponse => Key Version CorrelationId [Stream] | |
590 | 590 | Key => uint16 // 24 |
591 | 591 | Version => uint16 |
592 | 592 | CorrelationId => uint32 |
654 | 654 | from the server's suggestions. |
655 | 655 | * Open: the client sends an `Open` frame to pick a virtual host to connect to. The server |
656 | 656 | 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 |
33 | 33 | |
34 | 34 | -include_lib("rabbit_common/include/rabbit.hrl"). |
35 | 35 | -include_lib("rabbitmq_stream_common/include/rabbit_stream.hrl"). |
36 | ||
36 | 37 | -include("rabbit_stream_metrics.hrl"). |
37 | 38 | |
38 | 39 | start(_Type, _Args) -> |
39 | 40 | rabbit_stream_metrics:init(), |
40 | 41 | 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}]), | |
42 | 44 | rabbit_stream_sup:start_link(). |
43 | 45 | |
44 | 46 | host() -> |
74 | 74 | gen_server:call(?MODULE, {topology, VirtualHost, Stream}). |
75 | 75 | |
76 | 76 | -spec route(binary(), binary(), binary()) -> |
77 | {ok, binary() | no_route} | {error, stream_not_found}. | |
77 | {ok, [binary()] | no_route} | {error, stream_not_found}. | |
78 | 78 | route(RoutingKey, VirtualHost, SuperStream) -> |
79 | 79 | gen_server:call(?MODULE, |
80 | 80 | {route, RoutingKey, VirtualHost, SuperStream}). |
367 | 367 | case rabbit_exchange:route(Exchange, Delivery) of |
368 | 368 | [] -> |
369 | 369 | {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)]} | |
374 | 375 | end |
375 | 376 | catch |
376 | 377 | exit:Error -> |
383 | 384 | ExchangeName = rabbit_misc:r(VirtualHost, exchange, SuperStream), |
384 | 385 | Res = try |
385 | 386 | 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), | |
389 | 394 | {ok, |
390 | 395 | lists:foldl(fun (#binding{destination = |
391 | 396 | #resource{kind = queue, name = Q}}, |
394 | 399 | (_Binding, Acc) -> |
395 | 400 | Acc |
396 | 401 | end, |
397 | [], rabbit_binding:list_for_source(ExchangeName))} | |
402 | [], OrderedBindings)} | |
398 | 403 | catch |
399 | 404 | exit:Error -> |
400 | 405 | rabbit_log:error("Error while looking up exchange ~p, ~p", |
445 | 450 | _ -> |
446 | 451 | false |
447 | 452 | 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. |
0 | 0 | %% 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 | |
3 | 1 | %% at https://www.mozilla.org/en-US/MPL/2.0/ |
4 | 2 | %% |
5 | 3 | %% Software distributed under the License is distributed on an "AS IS" |
19 | 17 | |
20 | 18 | -include_lib("rabbit_common/include/rabbit.hrl"). |
21 | 19 | -include_lib("rabbitmq_stream_common/include/rabbit_stream.hrl"). |
20 | ||
22 | 21 | -include("rabbit_stream_metrics.hrl"). |
23 | 22 | |
24 | 23 | -type stream() :: binary(). |
81 | 80 | resource_alarm :: boolean(), |
82 | 81 | send_file_oct :: |
83 | 82 | 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()}). | |
85 | 85 | -record(configuration, |
86 | 86 | {initial_credits :: integer(), |
87 | 87 | credits_required_for_unblocking :: integer(), |
88 | 88 | frame_max :: integer(), |
89 | 89 | heartbeat :: integer(), |
90 | 90 | connection_negotiation_step_timeout :: integer()}). |
91 | ||
92 | 91 | -record(statem_data, |
93 | 92 | {transport :: module(), |
94 | 93 | connection :: #stream_connection{}, |
151 | 150 | consumers_info/2, |
152 | 151 | publishers_info/2, |
153 | 152 | in_vhost/2]). |
154 | ||
155 | 153 | -export([resource_alarm/3]). |
156 | ||
157 | 154 | %% gen_statem callbacks |
158 | 155 | -export([callback_mode/0, |
159 | 156 | terminate/3, |
160 | %% not called by gen_statem since gen_statem:enter_loop/4 is used | |
161 | 157 | init/1, |
162 | %% states | |
163 | 158 | tcp_connected/3, |
164 | 159 | peer_properties_exchanged/3, |
165 | 160 | authenticating/3, |
168 | 163 | open/3, |
169 | 164 | close_sent/3]). |
170 | 165 | |
166 | %% not called by gen_statem since gen_statem:enter_loop/4 is used | |
167 | ||
168 | %% states | |
169 | ||
171 | 170 | callback_mode() -> |
172 | 171 | [state_functions, state_enter]. |
173 | 172 | |
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]). | |
176 | 183 | |
177 | 184 | 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]])}. | |
179 | 188 | |
180 | 189 | init([KeepaliveSup, |
181 | 190 | Transport, |
187 | 196 | transport := ConnTransport}]) -> |
188 | 197 | process_flag(trap_exit, true), |
189 | 198 | {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)), | |
193 | 202 | RealSocket = rabbit_net:unwrap_socket(Sock), |
194 | 203 | case rabbit_net:connection_string(Sock, inbound) of |
195 | 204 | {ok, ConnStr} -> |
198 | 207 | atomics:put(SendFileOct, 1, 0), |
199 | 208 | init_credit(Credits, InitialCredits), |
200 | 209 | {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), | |
203 | 212 | 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)}, | |
225 | 236 | 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)}, | |
230 | 241 | Transport:setopts(RealSocket, [{active, once}]), |
231 | 242 | 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), | |
236 | 247 | % gen_statem process has its start_link call not return until the init function returns. |
237 | 248 | % This is problematic, because we won't be able to call ranch:handshake/2 |
238 | 249 | % from the init callback as this would cause a deadlock to happen. |
239 | 250 | % Therefore, we use the gen_statem:enter_loop/4 function. |
240 | 251 | % 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}}); | |
252 | 272 | {Error, Reason} -> |
253 | 273 | rabbit_net:fast_close(RealSocket), |
254 | 274 | rabbit_log_connection:warning("Closing connection because of ~p ~p", |
255 | 275 | [Error, Reason]) |
256 | 276 | end. |
257 | 277 | |
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}}) -> | |
261 | 282 | {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}}) -> | |
266 | 286 | state_timeout(?FUNCTION_NAME, Transport, Socket); |
267 | 287 | tcp_connected(info, Msg, StateData) -> |
268 | 288 | 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 | |
284 | 306 | end). |
285 | 307 | |
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}}) -> | |
289 | 313 | {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}}) -> | |
294 | 319 | state_timeout(?FUNCTION_NAME, Transport, Socket); |
295 | 320 | peer_properties_exchanged(info, Msg, StateData) -> |
296 | 321 | 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 | |
312 | 339 | end). |
313 | 340 | |
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}}) -> | |
317 | 345 | {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}}) -> | |
322 | 350 | state_timeout(?FUNCTION_NAME, Transport, Socket); |
323 | 351 | authenticating(info, Msg, StateData) -> |
324 | 352 | handle_info(Msg, StateData, |
325 | 353 | 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, | |
333 | 360 | NewConnection, |
334 | 361 | 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 | |
347 | 380 | end). |
348 | 381 | |
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}}) -> | |
352 | 386 | {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}}) -> | |
357 | 390 | state_timeout(?FUNCTION_NAME, Transport, Socket); |
358 | 391 | tuning(info, Msg, StateData) -> |
359 | 392 | 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 | |
379 | 417 | end). |
380 | 418 | |
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}}) -> | |
384 | 423 | {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}}) -> | |
389 | 427 | state_timeout(?FUNCTION_NAME, Transport, Socket); |
390 | 428 | tuned(info, Msg, StateData) -> |
391 | 429 | 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 | |
403 | 447 | end). |
404 | 448 | |
405 | 449 | 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]), | |
409 | 453 | close_immediately(Transport, Socket), |
410 | 454 | stop. |
411 | 455 | |
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) -> | |
417 | 466 | {OK, Closed, Error, _Passive} = Transport:messages(), |
418 | 467 | case Msg of |
419 | 468 | {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), | |
421 | 474 | 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]), | |
424 | 480 | Transition(NewConnectionStep, StatemData, Connection1, State1); |
425 | 481 | {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]), | |
427 | 484 | stop; |
428 | 485 | {Error, S, Reason} -> |
429 | 486 | rabbit_log_connection:warning("Socket error ~p [~w]", [Reason, S]), |
430 | 487 | stop; |
431 | 488 | {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}}}; | |
437 | 497 | Unknown -> |
438 | 498 | rabbit_log:warning("Received unknown message ~p", [Unknown]), |
439 | 499 | close_immediately(Transport, S), |
440 | 500 | stop |
441 | 501 | end. |
442 | 502 | |
443 | transition_to_opened(Transport, Configuration, NewConnection, NewConnectionState) -> | |
503 | transition_to_opened(Transport, | |
504 | Configuration, | |
505 | NewConnection, | |
506 | NewConnectionState) -> | |
444 | 507 | % TODO remove registration to rabbit_stream_connections |
445 | 508 | % just meant to be able to close the connection remotely |
446 | 509 | % should be possible once the connections are available in ctl list_connections |
447 | 510 | pg_local:join(rabbit_stream_connections, self()), |
448 | 511 | 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), | |
451 | 514 | Connection2 = ensure_stats_timer(Connection1), |
452 | 515 | 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), | |
457 | 520 | rabbit_core_metrics:connection_created(self(), Infos), |
458 | 521 | rabbit_event:notify(connection_created, Infos), |
459 | 522 | 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}}. | |
466 | 528 | |
467 | 529 | 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.", | |
469 | 532 | [Socket, From, To]), |
470 | 533 | close_immediately(Transport, Socket), |
471 | 534 | stop. |
485 | 548 | {ok, Res} -> |
486 | 549 | Res; |
487 | 550 | {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]), | |
489 | 553 | rabbit_net:fast_close(RealSocket), |
490 | 554 | exit(normal) |
491 | 555 | end. |
524 | 588 | atomics:get(CreditReference, 1) > CreditsRequiredForUnblocking. |
525 | 589 | |
526 | 590 | 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), | |
528 | 593 | atomics:add(Counters, 1, Count). |
529 | 594 | |
530 | 595 | set_consumer_offset(Counters, Offset) -> |
586 | 651 | |
587 | 652 | open(enter, _OldState, _StateData) -> |
588 | 653 | 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) -> | |
602 | 668 | rabbit_log_connection:debug("Connection ~p received resource alarm. Alarm " |
603 | 669 | "on? ~p", |
604 | 670 | [ConnectionName, IsThereAlarm]), |
605 | 671 | EnoughCreditsToUnblock = |
606 | has_enough_credits_to_unblock(Credits, | |
607 | CreditsRequiredForUnblocking), | |
672 | has_enough_credits_to_unblock(Credits, CreditsRequiredForUnblocking), | |
608 | 673 | 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, | |
615 | 680 | rabbit_log_connection:debug("Connection ~p had blocked status set to ~p, new " |
616 | 681 | "blocked status is now ~p", |
617 | 682 | [ConnectionName, Blocked, NewBlockedState]), |
629 | 694 | ok |
630 | 695 | end, |
631 | 696 | {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), | |
652 | 717 | #stream_connection{connection_step = Step} = Connection1, |
653 | 718 | case Step of |
654 | 719 | closing -> |
655 | close(Transport, S, State), | |
656 | rabbit_networking:unregister_non_amqp_connection(self()), | |
657 | notify_connection_closed(Connection1, State1), | |
658 | 720 | stop; |
659 | 721 | close_sent -> |
660 | 722 | rabbit_log_connection:debug("Transitioned to close_sent"), |
661 | 723 | 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}}; | |
666 | 727 | _ -> |
667 | 728 | 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}} | |
696 | 754 | 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 -> | |
702 | 757 | 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()]), | |
706 | 760 | 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 -> | |
712 | 764 | 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()]), | |
716 | 767 | 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) -> | |
726 | 775 | {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}) -> | |
757 | 805 | Frame = rabbit_stream_core:frame(heartbeat), |
758 | 806 | case catch send(Transport, S, Frame) of |
759 | 807 | ok -> |
761 | 809 | Unexpected -> |
762 | 810 | rabbit_log_connection:info("Heartbeat send error ~p, closing connection", |
763 | 811 | [Unexpected]), |
764 | C1 = demonitor_all_streams(Connection), | |
765 | close(Transport, C1, State), | |
812 | _C1 = demonitor_all_streams(Connection), | |
766 | 813 | stop |
767 | 814 | 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}) -> | |
772 | 817 | 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), | |
775 | 819 | 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}}) -> | |
780 | 824 | From ! {self(), ClientProperties}, |
781 | 825 | 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) -> | |
785 | 829 | Connection1 = emit_stats(Connection, State), |
786 | 830 | {keep_state, StatemData#statem_data{connection = Connection1}}; |
787 | 831 | open(info, Unknown, _StatemData) -> |
789 | 833 | [Unknown, ?FUNCTION_NAME]), |
790 | 834 | %% FIXME send close |
791 | 835 | 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}) -> | |
799 | 842 | {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}) -> | |
801 | 845 | {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}) -> | |
809 | 852 | % likely closing call from the management plugin |
810 | 853 | rabbit_log_connection:info("Forcing stream connection ~p closing: ~p", |
811 | 854 | [self(), Explanation]), |
812 | 855 | demonitor_all_streams(Connection), |
813 | rabbit_networking:unregister_non_amqp_connection(self()), | |
814 | notify_connection_closed(Connection, State), | |
815 | close(Transport, S, State), | |
816 | 856 | {stop_and_reply, normal, {reply, From, ok}}; |
817 | 857 | open(cast, |
818 | 858 | {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) -> | |
831 | 870 | 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), | |
846 | 884 | _ = 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)) | |
856 | 890 | end, |
857 | 891 | ByPublisher), |
858 | 892 | CorrelationIdCount = length(CorrelationList), |
859 | 893 | 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, | |
874 | 908 | {keep_state, StatemData#statem_data{connection_state = State1}}; |
875 | 909 | open(cast, |
876 | 910 | {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) -> | |
891 | 927 | PublishingIdCount = length(CorrelationList), |
892 | 928 | 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) | |
902 | 939 | end, |
903 | 940 | add_credits(Credits, PublishingIdCount), |
904 | 941 | 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, | |
919 | 955 | {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", | |
923 | 962 | [StreamName, -1]), |
924 | 963 | keep_state_and_data; |
925 | 964 | open(cast, |
926 | 965 | {queue_event, #resource{name = StreamName}, |
927 | 966 | {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 -> | |
937 | 977 | {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} | |
981 | 1027 | 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) -> | |
995 | 1038 | 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), | |
1000 | 1043 | rabbit_event:notify(connection_created, Infos, Ref), |
1001 | 1044 | 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), | |
1004 | 1047 | Connection2 = ensure_stats_timer(Connection1), |
1005 | 1048 | {keep_state, StatemData#statem_data{connection = Connection2}}. |
1006 | 1049 | |
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}}) -> | |
1010 | 1054 | {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]), | |
1022 | 1059 | 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), | |
1032 | 1068 | #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]), | |
1034 | 1071 | case Step of |
1035 | 1072 | closing_done -> |
1036 | close(Transport, S, State1), | |
1037 | rabbit_networking:unregister_non_amqp_connection(self()), | |
1038 | notify_connection_closed(Connection1, State1), | |
1039 | 1073 | stop; |
1040 | 1074 | _ -> |
1041 | 1075 | 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}} | |
1046 | 1079 | 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()]), | |
1054 | 1083 | 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()]), | |
1064 | 1088 | stop; |
1065 | close_sent(info,{resource_alarm, IsThereAlarm}, | |
1089 | close_sent(info, {resource_alarm, IsThereAlarm}, | |
1066 | 1090 | 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}}}; | |
1071 | 1098 | 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]), | |
1073 | 1101 | keep_state_and_data. |
1074 | 1102 | |
1075 | 1103 | handle_inbound_data_pre_auth(Transport, Connection, State, Data) -> |
1162 | 1190 | ServerProperties}}), |
1163 | 1191 | send(Transport, S, Frame), |
1164 | 1192 | {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}; | |
1168 | 1197 | handle_frame_pre_auth(Transport, |
1169 | 1198 | #stream_connection{socket = S} = Connection, |
1170 | 1199 | State, |
1175 | 1204 | {sasl_handshake, ?RESPONSE_CODE_OK, |
1176 | 1205 | Mechanisms}}), |
1177 | 1206 | send(Transport, S, Frame), |
1178 | {Connection#stream_connection{connection_step = authenticating}, State}; | |
1207 | {Connection#stream_connection{connection_step = authenticating}, | |
1208 | State}; | |
1179 | 1209 | handle_frame_pre_auth(Transport, |
1180 | 1210 | #stream_connection{socket = S, |
1181 | 1211 | authentication_state = AuthState0, |
1259 | 1289 | Username, |
1260 | 1290 | stream), |
1261 | 1291 | rabbit_log_connection:warning("User '~s' can only connect via localhost", |
1262 | [Username]), | |
1292 | [Username]), | |
1263 | 1293 | {C1#stream_connection{connection_step = |
1264 | 1294 | failure}, |
1265 | 1295 | {sasl_authenticate, |
1296 | 1326 | Connection, |
1297 | 1327 | #stream_connection_state{blocked = Blocked} = State, |
1298 | 1328 | {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]), | |
1300 | 1331 | Parent = self(), |
1301 | 1332 | %% sending a message to the main process so the heartbeat frame is sent from this main process |
1302 | 1333 | %% otherwise heartbeat frames can interleave with chunk delivery |
1383 | 1414 | {Connection, State}; |
1384 | 1415 | handle_frame_pre_auth(_Transport, Connection, State, Command) -> |
1385 | 1416 | rabbit_log_connection:warning("unknown command ~w, closing connection.", |
1386 | [Command]), | |
1417 | [Command]), | |
1387 | 1418 | {Connection#stream_connection{connection_step = failure}, State}. |
1388 | 1419 | |
1389 | 1420 | auth_fail(Username, Msg, Args, Connection, ConnectionState) -> |
1427 | 1458 | _WriterRef, |
1428 | 1459 | Stream}}) -> |
1429 | 1460 | 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]), | |
1432 | 1463 | response(Transport, |
1433 | 1464 | Connection0, |
1434 | 1465 | declare_publisher, |
1435 | 1466 | CorrelationId, |
1436 | 1467 | ?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), | |
1438 | 1470 | {Connection0, State}; |
1439 | 1471 | handle_frame_post_auth(Transport, |
1440 | 1472 | #stream_connection{user = User, |
1461 | 1493 | declare_publisher, |
1462 | 1494 | CorrelationId, |
1463 | 1495 | ?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), | |
1465 | 1499 | {Connection0, State}; |
1466 | 1500 | {ClusterLeader, |
1467 | 1501 | #stream_connection{publishers = Publishers0, |
1508 | 1542 | declare_publisher, |
1509 | 1543 | CorrelationId, |
1510 | 1544 | ?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), | |
1512 | 1548 | {Connection0, State} |
1513 | 1549 | end; |
1514 | 1550 | error -> |
1517 | 1553 | declare_publisher, |
1518 | 1554 | CorrelationId, |
1519 | 1555 | ?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), | |
1521 | 1559 | {Connection0, State} |
1522 | 1560 | end; |
1523 | 1561 | handle_frame_post_auth(Transport, |
1562 | 1600 | PublishingIds}, |
1563 | 1601 | Frame = rabbit_stream_core:frame(Command), |
1564 | 1602 | 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), | |
1566 | 1606 | increase_messages_errored(Counters, MessageCount), |
1567 | 1607 | {Connection, State} |
1568 | 1608 | end; |
1575 | 1615 | PublishingIds}, |
1576 | 1616 | Frame = rabbit_stream_core:frame(Command), |
1577 | 1617 | 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), | |
1579 | 1621 | {Connection, State} |
1580 | 1622 | end; |
1581 | 1623 | handle_frame_post_auth(Transport, |
1599 | 1641 | Stream) |
1600 | 1642 | of |
1601 | 1643 | {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), | |
1603 | 1647 | {?RESPONSE_CODE_STREAM_DOES_NOT_EXIST, 0}; |
1604 | 1648 | {ok, LocalMemberPid} -> |
1605 | 1649 | {?RESPONSE_CODE_OK, |
1612 | 1656 | end} |
1613 | 1657 | end; |
1614 | 1658 | 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), | |
1616 | 1662 | {?RESPONSE_CODE_ACCESS_REFUSED, 0} |
1617 | 1663 | end, |
1618 | 1664 | Frame = |
1655 | 1701 | delete_publisher, |
1656 | 1702 | CorrelationId, |
1657 | 1703 | ?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), | |
1659 | 1707 | {Connection0, State} |
1660 | 1708 | end; |
1661 | 1709 | handle_frame_post_auth(Transport, |
1692 | 1740 | subscribe, |
1693 | 1741 | CorrelationId, |
1694 | 1742 | ?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), | |
1696 | 1746 | {Connection, State}; |
1697 | 1747 | {error, not_found} -> |
1698 | 1748 | response(Transport, |
1700 | 1750 | subscribe, |
1701 | 1751 | CorrelationId, |
1702 | 1752 | ?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), | |
1704 | 1756 | {Connection, State}; |
1705 | 1757 | {ok, LocalMemberPid} -> |
1706 | 1758 | case subscription_exists(StreamSubscriptions, |
1712 | 1764 | subscribe, |
1713 | 1765 | CorrelationId, |
1714 | 1766 | ?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), | |
1716 | 1770 | {Connection, State}; |
1717 | 1771 | false -> |
1718 | 1772 | rabbit_log:debug("Creating subscription ~p to ~p, with offset specificat" |
1719 | 1773 | "ion ~p, properties ~0p", |
1720 | [SubscriptionId, | |
1721 | Stream, | |
1722 | OffsetSpec, | |
1723 | Properties]), | |
1774 | [SubscriptionId, | |
1775 | Stream, | |
1776 | OffsetSpec, | |
1777 | Properties]), | |
1724 | 1778 | CounterSpec = |
1725 | 1779 | {{?MODULE, |
1726 | 1780 | QueueResource, |
1737 | 1791 | CounterSpec, |
1738 | 1792 | Options), |
1739 | 1793 | 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)]), | |
1741 | 1796 | ConsumerCounters = |
1742 | 1797 | atomics:new(2, [{signed, false}]), |
1743 | 1798 | ConsumerState = |
1762 | 1817 | |
1763 | 1818 | rabbit_log:debug("Distributing existing messages to subscription ~p", |
1764 | 1819 | [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 | |
1811 | 1879 | end |
1812 | 1880 | end; |
1813 | 1881 | error -> |
1816 | 1884 | subscribe, |
1817 | 1885 | CorrelationId, |
1818 | 1886 | ?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), | |
1820 | 1890 | {Connection, State} |
1821 | 1891 | end; |
1822 | 1892 | handle_frame_post_auth(Transport, |
1834 | 1904 | SendFileOct) |
1835 | 1905 | of |
1836 | 1906 | {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}); | |
1851 | 1911 | {{segment, Segment1}, {credit, Credit1}} -> |
1852 | 1912 | Consumer1 = |
1853 | 1913 | Consumer#consumer{segment = Segment1, credit = Credit1}, |
1866 | 1926 | rabbit_stream_core:frame({response, 1, |
1867 | 1927 | {credit, Code, SubscriptionId}}), |
1868 | 1928 | 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), | |
1870 | 1932 | {Connection, State} |
1871 | 1933 | end; |
1872 | 1934 | handle_frame_post_auth(_Transport, |
1895 | 1957 | end; |
1896 | 1958 | error -> |
1897 | 1959 | %% 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]), | |
1899 | 1962 | {Connection, State} |
1900 | 1963 | end; |
1901 | 1964 | handle_frame_post_auth(Transport, |
1916 | 1979 | ok -> |
1917 | 1980 | case lookup_leader(Stream, Connection0) of |
1918 | 1981 | 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), | |
1920 | 1985 | {?RESPONSE_CODE_STREAM_DOES_NOT_EXIST, 0, Connection0}; |
1921 | 1986 | {LeaderPid, C} -> |
1922 | 1987 | {?RESPONSE_CODE_OK, |
1929 | 1994 | C} |
1930 | 1995 | end; |
1931 | 1996 | 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), | |
1933 | 2000 | {?RESPONSE_CODE_ACCESS_REFUSED, 0, Connection0} |
1934 | 2001 | end, |
1935 | 2002 | Frame = |
1951 | 2018 | unsubscribe, |
1952 | 2019 | CorrelationId, |
1953 | 2020 | ?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), | |
1955 | 2024 | {Connection, State}; |
1956 | 2025 | true -> |
1957 | 2026 | {Connection1, State1} = |
1988 | 2057 | {ok, |
1989 | 2058 | #{leader_node := LeaderPid, |
1990 | 2059 | 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", | |
1992 | 2062 | [LeaderPid, ReturnedReplicas]), |
1993 | 2063 | response_ok(Transport, |
1994 | 2064 | Connection, |
2001 | 2071 | create_stream, |
2002 | 2072 | CorrelationId, |
2003 | 2073 | ?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), | |
2005 | 2077 | {Connection, State}; |
2006 | 2078 | {error, reference_already_exists} -> |
2007 | 2079 | response(Transport, |
2009 | 2081 | create_stream, |
2010 | 2082 | CorrelationId, |
2011 | 2083 | ?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), | |
2013 | 2087 | {Connection, State}; |
2014 | 2088 | {error, _} -> |
2015 | 2089 | response(Transport, |
2017 | 2091 | create_stream, |
2018 | 2092 | CorrelationId, |
2019 | 2093 | ?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), | |
2021 | 2097 | {Connection, State} |
2022 | 2098 | end; |
2023 | 2099 | error -> |
2026 | 2102 | create_stream, |
2027 | 2103 | CorrelationId, |
2028 | 2104 | ?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), | |
2030 | 2108 | {Connection, State} |
2031 | 2109 | end; |
2032 | 2110 | _ -> |
2035 | 2113 | create_stream, |
2036 | 2114 | CorrelationId, |
2037 | 2115 | ?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), | |
2039 | 2119 | {Connection, State} |
2040 | 2120 | end; |
2041 | 2121 | handle_frame_post_auth(Transport, |
2073 | 2153 | ?RESPONSE_CODE_STREAM_NOT_AVAILABLE}, |
2074 | 2154 | Frame = rabbit_stream_core:frame(Command), |
2075 | 2155 | 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), | |
2077 | 2159 | {NewConnection, NewState}; |
2078 | 2160 | {not_cleaned, SameConnection, SameState} -> |
2079 | 2161 | {SameConnection, SameState} |
2085 | 2167 | delete_stream, |
2086 | 2168 | CorrelationId, |
2087 | 2169 | ?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), | |
2089 | 2173 | {Connection, State} |
2090 | 2174 | end; |
2091 | 2175 | error -> |
2094 | 2178 | delete_stream, |
2095 | 2179 | CorrelationId, |
2096 | 2180 | ?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), | |
2098 | 2184 | {Connection, State} |
2099 | 2185 | end; |
2100 | 2186 | handle_frame_post_auth(Transport, |
2204 | 2290 | case rabbit_stream_manager:route(RoutingKey, VirtualHost, SuperStream) |
2205 | 2291 | of |
2206 | 2292 | {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}; | |
2212 | 2303 | {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>>} | |
2215 | 2308 | end, |
2216 | 2309 | |
2217 | 2310 | Frame = |
2243 | 2336 | <<StreamCount:32>>, Streams), |
2244 | 2337 | {?RESPONSE_CODE_OK, Bin}; |
2245 | 2338 | {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), | |
2247 | 2342 | {?RESPONSE_CODE_STREAM_DOES_NOT_EXIST, <<0:32>>} |
2248 | 2343 | end, |
2249 | 2344 | |
2261 | 2356 | State, |
2262 | 2357 | {request, CorrelationId, |
2263 | 2358 | {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", | |
2265 | 2361 | [ClosingCode, ClosingReason]), |
2266 | 2362 | Frame = |
2267 | 2363 | rabbit_stream_core:frame({response, CorrelationId, |
2284 | 2380 | {close, ?RESPONSE_CODE_UNKNOWN_FRAME, |
2285 | 2381 | CloseReason}}), |
2286 | 2382 | 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), | |
2288 | 2385 | {Connection#stream_connection{connection_step = close_sent}, State}. |
2289 | 2386 | |
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}) -> | |
2295 | 2396 | rabbit_core_metrics:connection_closed(self()), |
2296 | 2397 | [rabbit_stream_metrics:consumer_cancelled(self(), |
2297 | 2398 | stream_r(S, Connection), SubId) |
2321 | 2422 | rabbit_log_connection:debug("Received heartbeat command post close"), |
2322 | 2423 | {Connection, State}; |
2323 | 2424 | 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]), | |
2325 | 2427 | {Connection, State}. |
2326 | 2428 | |
2327 | 2429 | stream_r(Stream, #stream_connection{virtual_host = VHost}) -> |
2798 | 2900 | Host; |
2799 | 2901 | i(peer_host, #stream_connection{peer_host = PeerHost}, _) -> |
2800 | 2902 | 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; | |
2803 | 2906 | i(peer_cert_subject, S, _) -> |
2804 | 2907 | cert_info(fun rabbit_ssl:peer_cert_subject/1, S); |
2805 | 2908 | i(peer_cert_issuer, S, _) -> |
2865 | 2968 | list_to_binary(F(Cert)) |
2866 | 2969 | end. |
2867 | 2970 | |
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 | |
2870 | 2974 | nossl -> |
2871 | 2975 | ''; |
2872 | 2976 | {error, _} -> |
51 | 51 | end} |
52 | 52 | end, |
53 | 53 | |
54 | Nodes = rabbit_mnesia:cluster_nodes(all), | |
54 | Nodes = rabbit_nodes:all(), | |
55 | 55 | OsirisConf = #{nodes => Nodes}, |
56 | 56 | |
57 | 57 | ServerConfiguration = |
24 | 24 | check_configure_permitted/3, |
25 | 25 | check_write_permitted/3, |
26 | 26 | check_read_permitted/3, |
27 | extract_stream_list/2]). | |
27 | extract_stream_list/2, | |
28 | sort_partitions/1]). | |
28 | 29 | |
29 | 30 | -define(MAX_PERMISSION_CACHE_SIZE, 12). |
31 | ||
32 | -include_lib("rabbit_common/include/rabbit.hrl"). | |
30 | 33 | |
31 | 34 | enforce_correct_stream_name(Name) -> |
32 | 35 | % from rabbit_channel |
80 | 83 | osiris:write(ClusterLeader, |
81 | 84 | undefined, |
82 | 85 | {PublisherId, PublishingId}, |
83 | {batch, MessageCount, CompressionType, UncompressedSize, Batch}), | |
86 | {batch, | |
87 | MessageCount, | |
88 | CompressionType, | |
89 | UncompressedSize, | |
90 | Batch}), | |
84 | 91 | write_messages(ClusterLeader, undefined, PublisherId, Rest); |
85 | 92 | write_messages(_ClusterLeader, _PublisherRef, _PublisherId, <<>>) -> |
86 | 93 | ok; |
112 | 119 | osiris:write(ClusterLeader, |
113 | 120 | PublisherRef, |
114 | 121 | PublishingId, |
115 | {batch, MessageCount, CompressionType, UncompressedSize, Batch}), | |
122 | {batch, | |
123 | MessageCount, | |
124 | CompressionType, | |
125 | UncompressedSize, | |
126 | Batch}), | |
116 | 127 | write_messages(ClusterLeader, PublisherRef, PublisherId, Rest). |
117 | 128 | |
118 | 129 | parse_map(<<>>, _Count) -> |
204 | 215 | extract_stream_list(<<Length:16, Stream:Length/binary, Rest/binary>>, |
205 | 216 | Streams) -> |
206 | 217 | 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). |
10 | 10 | 'publishers': '/stream/connections/' + vhost + '/' + name + '/publishers'}, |
11 | 11 | 'streamConnection', '#/stream/connections'); |
12 | 12 | }); |
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 | }); | |
14 | 32 | }); |
15 | 33 | |
16 | 34 | NAVIGATION['Stream'] = ['#/stream/connections', "monitoring"]; |
60 | 78 | } |
61 | 79 | }); |
62 | 80 | |
63 | CONSUMER_OWNER_FORMATTERS.sort(CONSUMER_OWNER_FORMATTERS_COMPARATOR);⏎ | |
81 | CONSUMER_OWNER_FORMATTERS.sort(CONSUMER_OWNER_FORMATTERS_COMPARATOR); | |
82 |
115 | 115 | <% } %> |
116 | 116 | |
117 | 117 | <div class="section"> |
118 | <h2>Publishers</h2> | |
118 | <h2 class="updatable">Publishers (<%=(publishers.length)%>) </h2> | |
119 | 119 | <div class="hider updatable"> |
120 | <%= format('streamPublishersList', {'publishers': publishers}) %> | |
120 | <%= format('streamPublishersList', {'publishers': publishers, 'mode' : 'connection'}) %> | |
121 | 121 | </div> |
122 | 122 | </div> |
123 | 123 | |
124 | 124 | <div class="section"> |
125 | <h2>Consumers</h2> | |
125 | <h2 class="updatable" >Consumers (<%=(consumers.length)%>)</h2> | |
126 | 126 | <div class="hider updatable"> |
127 | 127 | <%= format('streamConsumersList', {'consumers': consumers}) %> |
128 | 128 | </div> |
1 | 1 | <table class="list"> |
2 | 2 | <thead> |
3 | 3 | <tr> |
4 | <% if (mode == 'queue') { %> | |
5 | <th>Connection</th> | |
6 | <th>ID</th> | |
7 | <th>Reference</th> | |
8 | <% } else { %> | |
4 | 9 | <th>ID</th> |
5 | 10 | <th>Reference</th> |
6 | 11 | <th>Queue</th> |
12 | <% } %> | |
7 | 13 | <th>Messages Published</th> |
8 | 14 | <th>Messages Confirmed</th> |
9 | 15 | <th>Messages Errored</th> |
14 | 20 | var publisher = publishers[i]; |
15 | 21 | %> |
16 | 22 | <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 { %> | |
17 | 28 | <td><%= publisher.publisher_id %></td> |
18 | 29 | <td class="c"><%= fmt_string(publisher.reference) %></td> |
19 | 30 | <td><%= link_queue(publisher.queue.vhost, publisher.queue.name) %></td> |
31 | <% } %> | |
20 | 32 | <td class="c"><%= publisher.published %></td> |
21 | 33 | <td class="c"><%= publisher.confirmed %></td> |
22 | 34 | <td class="c"><%= publisher.errored %></td> |
16 | 16 | get_all_publishers/1]). |
17 | 17 | -export([entity_data/4]). |
18 | 18 | -export([get_connection_consumers/1, |
19 | get_connection_publishers/1]). | |
19 | get_connection_publishers/1, | |
20 | get_stream_publishers/1]). | |
20 | 21 | |
21 | 22 | get_all_consumers(VHosts) -> |
22 | 23 | rabbit_mgmt_db:submit(fun(_Interval) -> consumers_stats(VHosts) end). |
32 | 33 | get_connection_publishers(ConnectionPid) when is_pid(ConnectionPid) -> |
33 | 34 | rabbit_mgmt_db:submit(fun(_Interval) -> |
34 | 35 | connection_publishers_stats(ConnectionPid) |
36 | end). | |
37 | ||
38 | get_stream_publishers(QueueResource) -> | |
39 | rabbit_mgmt_db:submit(fun(_Interval) -> | |
40 | stream_publishers_stats(QueueResource) | |
35 | 41 | end). |
36 | 42 | |
37 | 43 | consumers_stats(VHost) -> |
64 | 70 | entity_data, |
65 | 71 | [ConnectionPid, ?ENTITY_PUBLISHER, |
66 | 72 | 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]}), | |
67 | 81 | [V || {_, V} <- maps:to_list(Data)]. |
68 | 82 | |
69 | 83 | entity_data(_Pid, Param, EntityType, QueryFun) -> |
102 | 116 | publishers_by_connection(ConnectionPid) -> |
103 | 117 | get_entity_stats(?TABLE_PUBLISHER, ConnectionPid). |
104 | 118 | |
119 | publishers_by_stream(QueueResource) -> | |
120 | get_entity_stats_by_resource(?TABLE_PUBLISHER, QueueResource). | |
121 | ||
105 | 122 | get_entity_stats(Table, Id) -> |
106 | 123 | ets:select(Table, match_entity_spec(Id)). |
107 | 124 | |
108 | 125 | match_entity_spec(ConnectionId) -> |
109 | 126 | [{{{'_', '$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'}}], ['$_']}]. | |
110 | 139 | |
111 | 140 | augment_connection_pid(Consumer) -> |
112 | 141 | Pid = rabbit_misc:pget(connection, Consumer), |
21 | 21 | |
22 | 22 | dispatcher() -> |
23 | 23 | [{"/stream/publishers", ?MODULE, []}, |
24 | {"/stream/publishers/:vhost", ?MODULE, []}]. | |
24 | {"/stream/publishers/:vhost", ?MODULE, []}, | |
25 | {"/stream/publishers/:vhost/:queue", ?MODULE, []}]. | |
25 | 26 | |
26 | 27 | web_ui() -> |
27 | 28 | []. |
41 | 42 | none -> |
42 | 43 | true; % none means `all` |
43 | 44 | _ -> |
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 | |
45 | 56 | end, |
46 | 57 | ReqData, Context}. |
47 | 58 | |
48 | 59 | to_json(ReqData, Context = #context{user = User}) -> |
49 | 60 | case rabbit_mgmt_util:disable_stats(ReqData) of |
50 | 61 | 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), | |
57 | 70 | 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, | |
60 | 83 | rabbit_mgmt_util:reply_list(filter_user(Publishers, User), |
61 | 84 | [], |
62 | 85 | ReqData, |
0 | Copyright (c) 2011-2020, Loïc Hoguin <essen@ninenines.eu> | |
0 | Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu> | |
1 | 1 | |
2 | 2 | Permission to use, copy, modify, and/or distribute this software for any |
3 | 3 | purpose with or without fee is hereby granted, provided that the above |
1 | 1 | |
2 | 2 | PROJECT = ranch |
3 | 3 | PROJECT_DESCRIPTION = Socket acceptor pool for TCP protocols. |
4 | PROJECT_VERSION = 2.0.0 | |
4 | PROJECT_VERSION = 2.1.0 | |
5 | 5 | PROJECT_REGISTERED = ranch_server |
6 | 6 | |
7 | 7 | # Options. |
17 | 17 | |
18 | 18 | TEST_DEPS = $(if $(CI_ERLANG_MK),ci.erlang.mk) ct_helper stampede |
19 | 19 | 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 | |
21 | 21 | |
22 | 22 | # Concuerror tests. |
23 | 23 | |
29 | 29 | dep_ci.erlang.mk = git https://github.com/ninenines/ci.erlang.mk master |
30 | 30 | DEP_EARLY_PLUGINS = ci.erlang.mk |
31 | 31 | |
32 | AUTO_CI_OTP ?= OTP-21+ | |
32 | AUTO_CI_OTP ?= OTP-22+ | |
33 | 33 | AUTO_CI_HIPE ?= OTP-LATEST |
34 | 34 | # 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 | |
36 | 50 | |
37 | 51 | # Standard targets. |
38 | 52 | |
66 | 80 | # Prepare for the release. |
67 | 81 | |
68 | 82 | prepare_tag: |
83 | $(verbose) $(warning Hex metadata: $(HEX_TARBALL_EXTRA_METADATA)) | |
84 | $(verbose) echo | |
69 | 85 | $(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` | |
72 | 88 | $(verbose) echo -n "MAKEFILE: " |
73 | 89 | $(verbose) grep -m1 PROJECT_VERSION Makefile |
74 | 90 | $(verbose) echo -n "APP: " |
75 | 91 | $(verbose) grep -m1 vsn ebin/$(PROJECT).app | sed 's/ //g' |
76 | 92 | $(verbose) echo -n "APPUP: " |
77 | 93 | $(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 | |
78 | 100 | $(verbose) echo |
79 | 101 | $(verbose) echo "Links in the README:" |
80 | 102 | $(verbose) grep http.*:// README.asciidoc |
18 | 18 | |
19 | 19 | == Online documentation |
20 | 20 | |
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] | |
23 | 23 | |
24 | 24 | == Offline documentation |
25 | 25 | |
32 | 32 | |
33 | 33 | == Getting help |
34 | 34 | |
35 | * Official IRC Channel: #ninenines on irc.freenode.net | |
36 | 35 | * https://github.com/ninenines/ranch/issues[Issues tracker] |
37 | 36 | * https://ninenines.eu/services[Commercial Support] |
16 | 16 | ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST))) |
17 | 17 | export ERLANG_MK_FILENAME |
18 | 18 | |
19 | ERLANG_MK_VERSION = 2020.03.05-18-g4ad50cd | |
19 | ERLANG_MK_VERSION = d80984c | |
20 | 20 | ERLANG_MK_WITHOUT = |
21 | 21 | |
22 | 22 | # Make 3.81 and 3.82 are deprecated. |
954 | 954 | |
955 | 955 | PACKAGES += cuttlefish |
956 | 956 | 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 | |
959 | 959 | pkg_cuttlefish_fetch = git |
960 | pkg_cuttlefish_repo = https://github.com/basho/cuttlefish | |
960 | pkg_cuttlefish_repo = https://github.com/Kyorai/cuttlefish | |
961 | 961 | pkg_cuttlefish_commit = master |
962 | 962 | |
963 | 963 | PACKAGES += damocles |
2334 | 2334 | pkg_jsx_homepage = https://github.com/talentdeficit/jsx |
2335 | 2335 | pkg_jsx_fetch = git |
2336 | 2336 | pkg_jsx_repo = https://github.com/talentdeficit/jsx |
2337 | pkg_jsx_commit = master | |
2337 | pkg_jsx_commit = main | |
2338 | 2338 | |
2339 | 2339 | PACKAGES += kafka |
2340 | 2340 | pkg_kafka_name = kafka |
2822 | 2822 | pkg_mysql_homepage = https://github.com/mysql-otp/mysql-otp |
2823 | 2823 | pkg_mysql_fetch = git |
2824 | 2824 | pkg_mysql_repo = https://github.com/mysql-otp/mysql-otp |
2825 | pkg_mysql_commit = 1.5.1 | |
2825 | pkg_mysql_commit = 1.7.0 | |
2826 | 2826 | |
2827 | 2827 | PACKAGES += n2o |
2828 | 2828 | pkg_n2o_name = n2o |
4691 | 4691 | case file:consult("$(call core_native_path,$(DEPS_DIR)/$1/rebar.lock)") of |
4692 | 4692 | {ok, Lock} -> |
4693 | 4693 | 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) -> | |
4696 | 4707 | io:format("~p~n", [LockPkgs]), |
4697 | 4708 | case lists:keyfind(atom_to_binary(N, latin1), 1, LockPkgs) of |
4698 | 4709 | {_, {pkg, _, Vsn}, _} -> |
4701 | 4712 | _ -> |
4702 | 4713 | false |
4703 | 4714 | end; |
4704 | _ -> | |
4715 | true -> | |
4705 | 4716 | false |
4706 | 4717 | end; |
4707 | 4718 | _ -> |
5566 | 5577 | ERL_TEST_FILES = $(call core_find,$(TEST_DIR)/,*.erl) |
5567 | 5578 | $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build: $(ERL_TEST_FILES) $(MAKEFILE_LIST) |
5568 | 5579 | $(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 $@) | |
5570 | 5581 | endif |
5571 | 5582 | |
5572 | 5583 | test-build:: IS_TEST=1 |
6564 | 6575 | |
6565 | 6576 | $(ERLANG_MK_TMP)/Concuerror/bin/concuerror: | $(ERLANG_MK_TMP) |
6566 | 6577 | $(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 | |
6568 | 6579 | |
6569 | 6580 | $(CONCUERROR_LOGS_DIR): |
6570 | 6581 | $(verbose) mkdir -p $(CONCUERROR_LOGS_DIR) |
6985 | 6996 | exit $$eunit_retcode |
6986 | 6997 | endif |
6987 | 6998 | 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)))) | |
6988 | 7336 | |
6989 | 7337 | # Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu> |
6990 | 7338 | # This file is part of erlang.mk and subject to the terms of the ISC License. |
7730 | 8078 | ifeq ($(IS_APP)$(IS_DEP),) |
7731 | 8079 | $(verbose) rm -f $(ERLANG_MK_RECURSIVE_TMP_LIST) |
7732 | 8080 | endif |
8081 | $(verbose) touch $(ERLANG_MK_RECURSIVE_TMP_LIST) | |
7733 | 8082 | $(verbose) set -e; for dep in $^ ; do \ |
7734 | 8083 | if ! grep -qs ^$$dep$$ $(ERLANG_MK_RECURSIVE_TMP_LIST); then \ |
7735 | 8084 | echo $$dep >> $(ERLANG_MK_RECURSIVE_TMP_LIST); \ |
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,[]}]}. |
8 | 8 | %% module then call system_code_change; and when downgrading, |
9 | 9 | %% call system_code_change and then load the module. |
10 | 10 | |
11 | {"2.0.0", | |
11 | {"2.1.0", | |
12 | 12 | [{<<"2\\.0\\.[0-9]+.*">>, [ |
13 | 13 | {apply, {ranch, stop_all_acceptors, []}}, |
14 | 14 | {load_module, ranch}, |
15 | 15 | {load_module, ranch_acceptor}, |
16 | 16 | {update, ranch_acceptors_sup, supervisor}, |
17 | 17 | {load_module, ranch_app}, |
18 | {update, ranch_server, {advanced, []}}, | |
19 | {update, ranch_conns_sup_sup, supervisor}, | |
18 | 20 | %% See comments at the top of the file about ranch_conns_sup. |
19 | 21 | {update, ranch_conns_sup, {advanced, []}}, |
20 | {update, ranch_conns_sup_sup, supervisor}, | |
21 | 22 | {load_module, ranch_crc32c}, |
22 | 23 | {update, ranch_embedded_sup, supervisor}, |
23 | 24 | {update, ranch_listener_sup, supervisor}, |
24 | 25 | {load_module, ranch_protocol}, |
25 | 26 | {load_module, ranch_proxy_header}, |
26 | {update, ranch_server, {advanced, []}}, | |
27 | 27 | {update, ranch_server_proxy, {advanced, []}}, |
28 | 28 | {load_module, ranch_ssl}, |
29 | 29 | {update, ranch_sup, supervisor}, |
54 | 54 | {apply, {ranch, restart_all_acceptors, []}} |
55 | 55 | ]}] |
56 | 56 | }. |
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> | |
2 | 3 | %% |
3 | 4 | %% Permission to use, copy, modify, and/or distribute this software for any |
4 | 5 | %% purpose with or without fee is hereby granted, provided that the above |
54 | 55 | -type opts() :: any() | transport_opts(any()). |
55 | 56 | -export_type([opts/0]). |
56 | 57 | |
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 | ||
57 | 67 | -type transport_opts(SocketOpts) :: #{ |
68 | alarms => #{term() => alarm_num_connections()}, | |
58 | 69 | connection_type => worker | supervisor, |
59 | 70 | handshake_timeout => timeout(), |
71 | logger => module(), | |
60 | 72 | max_connections => max_conns(), |
61 | logger => module(), | |
62 | 73 | num_acceptors => pos_integer(), |
63 | 74 | num_conns_sups => pos_integer(), |
64 | 75 | num_listen_sockets => pos_integer(), |
76 | post_listen_callback => fun((term()) -> ok | {error, term()}), | |
65 | 77 | shutdown => timeout() | brutal_kill, |
66 | 78 | socket_opts => SocketOpts |
67 | 79 | }. |
121 | 133 | true; |
122 | 134 | validate_transport_opt(max_connections, Value, _) -> |
123 | 135 | 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); | |
124 | 146 | validate_transport_opt(logger, Value, _) -> |
125 | 147 | is_atom(Value); |
126 | 148 | validate_transport_opt(num_acceptors, Value, _) -> |
130 | 152 | validate_transport_opt(num_listen_sockets, Value, Opts) -> |
131 | 153 | is_integer(Value) andalso Value > 0 |
132 | 154 | andalso Value =< maps:get(num_acceptors, Opts, 10); |
155 | validate_transport_opt(post_listen_callback, Value, _) -> | |
156 | is_function(Value, 1); | |
133 | 157 | validate_transport_opt(shutdown, brutal_kill, _) -> |
134 | 158 | true; |
135 | 159 | validate_transport_opt(shutdown, infinity, _) -> |
139 | 163 | validate_transport_opt(socket_opts, _, _) -> |
140 | 164 | true; |
141 | 165 | 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(_) -> | |
142 | 179 | false. |
143 | 180 | |
144 | 181 | maybe_started({error, {{shutdown, |
419 | 456 | transport => Transport, |
420 | 457 | transport_options => TransOpts, |
421 | 458 | protocol => Protocol, |
422 | protocol_options => ProtoOpts | |
459 | protocol_options => ProtoOpts, | |
460 | metrics => metrics(Ref) | |
423 | 461 | }. |
424 | 462 | |
425 | 463 | -spec procs(ref(), acceptors | connections) -> [pid()]. |
445 | 483 | supervisor:which_children(SupSupPid) |
446 | 484 | ), |
447 | 485 | 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 | ). | |
448 | 503 | |
449 | 504 | -spec wait_for_connections |
450 | 505 | (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> | |
1 | 1 | %% |
2 | 2 | %% Permission to use, copy, modify, and/or distribute this software for any |
3 | 3 | %% 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 | 2 | %% |
3 | 3 | %% Permission to use, copy, modify, and/or distribute this software for any |
4 | 4 | %% purpose with or without fee is hereby granted, provided that the above |
59 | 59 | []; |
60 | 60 | {_, Port} -> |
61 | 61 | 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}), | |
68 | 63 | TransOpts1 = TransOpts0#{socket_opts => SocketOpts1}, |
69 | 64 | [{N, start_listen_socket(Ref, Transport, TransOpts1, Logger)} |
70 | 65 | || N <- lists:seq(2, NumListenSockets)] |
76 | 71 | start_listen_socket(Ref, Transport, TransOpts, Logger) -> |
77 | 72 | case Transport:listen(TransOpts) of |
78 | 73 | {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; | |
80 | 81 | {error, Reason} -> |
81 | 82 | listen_error(Ref, Transport, TransOpts, Reason, Logger) |
82 | 83 | end. |
0 | %% Copyright (c) 2011-2020, Loïc Hoguin <essen@ninenines.eu> | |
0 | %% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu> | |
1 | 1 | %% |
2 | 2 | %% Permission to use, copy, modify, and/or distribute this software for any |
3 | 3 | %% 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> | |
1 | 2 | %% |
2 | 3 | %% Permission to use, copy, modify, and/or distribute this software for any |
3 | 4 | %% purpose with or without fee is hereby granted, provided that the above |
33 | 34 | -record(state, { |
34 | 35 | parent = undefined :: pid(), |
35 | 36 | ref :: ranch:ref(), |
37 | id :: pos_integer(), | |
36 | 38 | conn_type :: conn_type(), |
37 | 39 | shutdown :: shutdown(), |
38 | 40 | transport = undefined :: module(), |
40 | 42 | opts :: any(), |
41 | 43 | handshake_timeout :: timeout(), |
42 | 44 | max_conns = undefined :: ranch:max_conns(), |
45 | stats_counters_ref :: counters:counters_ref(), | |
46 | alarms = #{} :: #{term() => {map(), undefined | reference()}}, | |
43 | 47 | logger = undefined :: module() |
44 | 48 | }). |
45 | 49 | |
103 | 107 | process_flag(trap_exit, true), |
104 | 108 | ok = ranch_server:set_connections_sup(Ref, Id, self()), |
105 | 109 | MaxConns = ranch_server:get_max_connections(Ref), |
110 | Alarms = get_alarms(TransOpts), | |
106 | 111 | ConnType = maps:get(connection_type, TransOpts, worker), |
107 | 112 | Shutdown = maps:get(shutdown, TransOpts, 5000), |
108 | 113 | HandshakeTimeout = maps:get(handshake_timeout, TransOpts, 5000), |
109 | 114 | ProtoOpts = ranch_server:get_protocol_options(Ref), |
115 | StatsCounters = ranch_server:get_stats_counters(Ref), | |
110 | 116 | 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, | |
112 | 118 | 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) -> | |
119 | 127 | receive |
120 | 128 | {?MODULE, start_protocol, To, Socket} -> |
121 | 129 | try Protocol:start_link(Ref, Transport, Opts) of |
122 | 130 | {ok, Pid} -> |
131 | inc_accept(StatsCounters, Id, 1), | |
123 | 132 | handshake(State, CurConns, NbChildren, Sleepers, To, Socket, Pid, Pid); |
124 | 133 | {ok, SupPid, ProtocolPid} when ConnType =:= supervisor -> |
134 | inc_accept(StatsCounters, Id, 1), | |
125 | 135 | handshake(State, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid); |
126 | 136 | Ret -> |
127 | 137 | To ! self(), |
174 | 184 | {set_protocol_options, Opts2} -> |
175 | 185 | loop(State#state{opts=Opts2}, |
176 | 186 | 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); | |
177 | 193 | {'EXIT', Parent, Reason} -> |
178 | 194 | terminate(State, Reason, NbChildren); |
179 | 195 | {'EXIT', Pid, Reason} when Sleepers =:= [] -> |
180 | 196 | case erase(Pid) of |
181 | 197 | active -> |
198 | inc_terminate(StatsCounters, Id, 1), | |
182 | 199 | report_error(Logger, Ref, Protocol, Pid, Reason), |
183 | 200 | loop(State, CurConns - 1, NbChildren - 1, Sleepers); |
184 | 201 | removed -> |
202 | inc_terminate(StatsCounters, Id, 1), | |
185 | 203 | report_error(Logger, Ref, Protocol, Pid, Reason), |
186 | 204 | loop(State, CurConns, NbChildren - 1, Sleepers); |
187 | 205 | undefined -> |
191 | 209 | {'EXIT', Pid, Reason} -> |
192 | 210 | case erase(Pid) of |
193 | 211 | active when CurConns > MaxConns -> |
212 | inc_terminate(StatsCounters, Id, 1), | |
194 | 213 | report_error(Logger, Ref, Protocol, Pid, Reason), |
195 | 214 | loop(State, CurConns - 1, NbChildren - 1, Sleepers); |
196 | 215 | active -> |
216 | inc_terminate(StatsCounters, Id, 1), | |
197 | 217 | report_error(Logger, Ref, Protocol, Pid, Reason), |
198 | 218 | [To|Sleepers2] = Sleepers, |
199 | 219 | To ! self(), |
200 | 220 | loop(State, CurConns - 1, NbChildren - 1, Sleepers2); |
201 | 221 | removed -> |
222 | inc_terminate(StatsCounters, Id, 1), | |
202 | 223 | report_error(Logger, Ref, Protocol, Pid, Reason), |
203 | 224 | loop(State, CurConns, NbChildren - 1, Sleepers); |
204 | 225 | undefined -> |
233 | 254 | end. |
234 | 255 | |
235 | 256 | 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) -> | |
237 | 258 | case Transport:controlling_process(Socket, ProtocolPid) of |
238 | 259 | ok -> |
239 | 260 | ProtocolPid ! {handshake, Ref, Transport, Socket, HandshakeTimeout}, |
240 | 261 | put(SupPid, active), |
241 | 262 | CurConns2 = CurConns + 1, |
242 | if CurConns2 < MaxConns -> | |
263 | Sleepers2 = if CurConns2 < MaxConns -> | |
243 | 264 | To ! self(), |
244 | loop(State, CurConns2, NbChildren + 1, Sleepers); | |
265 | Sleepers; | |
245 | 266 | 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); | |
248 | 271 | {error, _} -> |
249 | 272 | Transport:close(Socket), |
250 | 273 | %% Only kill the supervised pid, because the connection's pid, |
253 | 276 | To ! self(), |
254 | 277 | loop(State, CurConns, NbChildren, Sleepers) |
255 | 278 | 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 | #{}. | |
256 | 321 | |
257 | 322 | set_transport_options(State=#state{max_conns=MaxConns0}, CurConns, NbChildren, Sleepers0, TransOpts) -> |
258 | 323 | MaxConns1 = maps:get(max_connections, TransOpts, 1024), |
265 | 330 | false -> |
266 | 331 | Sleepers0 |
267 | 332 | 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}, | |
269 | 335 | CurConns, NbChildren, Sleepers1). |
270 | 336 | |
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 | ||
271 | 396 | -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) -> | |
273 | 399 | kill_children(get_keys(active)), |
274 | 400 | kill_children(get_keys(removed)), |
401 | inc_terminate(StatsCounters, Id, NbChildren), | |
275 | 402 | exit(Reason); |
276 | 403 | %% 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) -> | |
278 | 406 | shutdown_children(get_keys(active)), |
279 | 407 | shutdown_children(get_keys(removed)), |
280 | 408 | _ = if |
284 | 412 | erlang:send_after(Shutdown, self(), kill) |
285 | 413 | end, |
286 | 414 | wait_children(NbChildren), |
415 | inc_terminate(StatsCounters, Id, NbChildren), | |
287 | 416 | 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). | |
288 | 425 | |
289 | 426 | %% Kill all children and then exit. We unlink first to avoid |
290 | 427 | %% getting a message for each child getting killed. |
333 | 470 | terminate(State, Reason, NbChildren). |
334 | 471 | |
335 | 472 | -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}}; | |
336 | 492 | system_code_change(Misc, _, _, _) -> |
337 | 493 | {ok, Misc}. |
338 | 494 |
0 | %% Copyright (c) 2019-2020, Jan Uhlig <ju@mailingwork.de> | |
0 | %% Copyright (c) 2019-2021, Jan Uhlig <juhlig@hnc-agency.org> | |
1 | 1 | %% |
2 | 2 | %% Permission to use, copy, modify, and/or distribute this software for any |
3 | 3 | %% purpose with or without fee is hereby granted, provided that the above |
31 | 31 | TransOpts = ranch_server:get_transport_options(Ref), |
32 | 32 | NumAcceptors = maps:get(num_acceptors, TransOpts, 10), |
33 | 33 | NumConnsSups = maps:get(num_conns_sups, TransOpts, NumAcceptors), |
34 | StatsCounters = counters:new(2*NumConnsSups, []), | |
35 | ok = ranch_server:set_stats_counters(Ref, StatsCounters), | |
34 | 36 | ChildSpecs = [#{ |
35 | 37 | id => {ranch_conns_sup, N}, |
36 | 38 | 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> | |
1 | 1 | %% |
2 | 2 | %% Permission to use, copy, modify, and/or distribute this software for any |
3 | 3 | %% 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> | |
1 | 1 | %% |
2 | 2 | %% Permission to use, copy, modify, and/or distribute this software for any |
3 | 3 | %% 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 | 1 | %% |
2 | 2 | %% Permission to use, copy, modify, and/or distribute this software for any |
3 | 3 | %% 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> | |
1 | 1 | %% |
2 | 2 | %% Permission to use, copy, modify, and/or distribute this software for any |
3 | 3 | %% 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> | |
1 | 1 | %% |
2 | 2 | %% Permission to use, copy, modify, and/or distribute this software for any |
3 | 3 | %% purpose with or without fee is hereby granted, provided that the above |
16 | 16 | -export([parse/1]). |
17 | 17 | -export([header/1]). |
18 | 18 | -export([header/2]). |
19 | -export([to_connection_info/1]). | |
19 | 20 | |
20 | 21 | -type proxy_info() :: #{ |
21 | 22 | %% Mandatory part. |
829 | 830 | Test4 = Common#{ssl => #{ |
830 | 831 | client => [ssl, cert_conn, cert_sess], |
831 | 832 | verified => true, |
832 | version => <<"TLSv1.3">>, %% Note that I'm not sure this example value is correct. | |
833 | version => <<"TLSv1.3">>, | |
833 | 834 | cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>, |
834 | 835 | sig_alg => <<"SHA256">>, |
835 | 836 | key_alg => <<"RSA2048">>, |
877 | 878 | {ok, Test, <<>>} = parse(iolist_to_binary(header(Test, #{padding => 123}))), |
878 | 879 | ok. |
879 | 880 | -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> | |
2 | 2 | %% |
3 | 3 | %% Permission to use, copy, modify, and/or distribute this software for any |
4 | 4 | %% purpose with or without fee is hereby granted, provided that the above |
31 | 31 | -export([get_addr/1]). |
32 | 32 | -export([set_max_connections/2]). |
33 | 33 | -export([get_max_connections/1]). |
34 | -export([set_stats_counters/2]). | |
35 | -export([get_stats_counters/1]). | |
34 | 36 | -export([set_transport_options/2]). |
35 | 37 | -export([get_transport_options/1]). |
36 | 38 | -export([set_protocol_options/2]). |
76 | 78 | %% cases when calling stop_listener followed by get_connections_sup, |
77 | 79 | %% we could end up with the pid still being returned, when we |
78 | 80 | %% 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. | |
80 | 82 | _ = ets:match_delete(?TAB, {{conns_sup, Ref, '_'}, '_'}), |
83 | _ = ets:delete(?TAB, {stats_counters, Ref}), | |
81 | 84 | %% Ditto for the listener supervisor. |
82 | 85 | _ = ets:delete(?TAB, {listener_sup, Ref}), |
83 | 86 | ok. |
85 | 88 | -spec cleanup_connections_sups(ranch:ref()) -> ok. |
86 | 89 | cleanup_connections_sups(Ref) -> |
87 | 90 | _ = ets:match_delete(?TAB, {{conns_sup, Ref, '_'}, '_'}), |
91 | _ = ets:delete(?TAB, {stats_counters, Ref}), | |
88 | 92 | ok. |
89 | 93 | |
90 | 94 | -spec set_connections_sup(ranch:ref(), non_neg_integer(), pid()) -> ok. |
137 | 141 | -spec get_max_connections(ranch:ref()) -> ranch:max_conns(). |
138 | 142 | get_max_connections(Ref) -> |
139 | 143 | 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). | |
140 | 152 | |
141 | 153 | -spec set_transport_options(ranch:ref(), any()) -> ok. |
142 | 154 | set_transport_options(Ref, TransOpts) -> |
196 | 208 | handle_call({set_max_conns, Ref, MaxConns}, _, State) -> |
197 | 209 | ets:insert(?TAB, {{max_conns, Ref}, MaxConns}), |
198 | 210 | _ = [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}), | |
199 | 214 | {reply, ok, State}; |
200 | 215 | handle_call({set_trans_opts, Ref, Opts}, _, State) -> |
201 | 216 | ets:insert(?TAB, {{trans_opts, Ref}, Opts}), |
236 | 251 | ok. |
237 | 252 | |
238 | 253 | -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}; | |
239 | 257 | code_change(_OldVsn, State, _Extra) -> |
240 | 258 | {ok, State}. |
241 | 259 |
0 | %% Copyright (c) 2019-2020, Jan Uhlig <ju@mailingwork.de> | |
0 | %% Copyright (c) 2019-2021, Jan Uhlig <juhlig@hnc-agency.org> | |
1 | 1 | %% |
2 | 2 | %% Permission to use, copy, modify, and/or distribute this software for any |
3 | 3 | %% 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> | |
2 | 3 | %% |
3 | 4 | %% Permission to use, copy, modify, and/or distribute this software for any |
4 | 5 | %% purpose with or without fee is hereby granted, provided that the above |
116 | 117 | case lists:keymember(cert, 1, SocketOpts) |
117 | 118 | orelse lists:keymember(certfile, 1, SocketOpts) |
118 | 119 | 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 | |
120 | 122 | true -> |
121 | 123 | Logger = maps:get(logger, TransOpts, logger), |
122 | 124 | do_listen(SocketOpts, Logger); |
129 | 131 | SocketOpts2 = ranch:set_option_default(SocketOpts1, nodelay, true), |
130 | 132 | SocketOpts3 = ranch:set_option_default(SocketOpts2, send_timeout, 30000), |
131 | 133 | SocketOpts = ranch:set_option_default(SocketOpts3, send_timeout_close, true), |
134 | DisallowedOpts0 = disallowed_listen_options(), | |
135 | DisallowedOpts = unsupported_tls_options(SocketOpts) ++ DisallowedOpts0, | |
132 | 136 | %% We set the port to 0 because it is given in the Opts directly. |
133 | 137 | %% The port in the options takes precedence over the one in the |
134 | 138 | %% first argument. |
135 | ssl:listen(0, ranch:filter_options(SocketOpts, disallowed_listen_options(), | |
139 | ssl:listen(0, ranch:filter_options(SocketOpts, DisallowedOpts, | |
136 | 140 | [binary, {active, false}, {packet, raw}, {reuseaddr, true}], Logger)). |
137 | 141 | |
138 | 142 | %% 'binary' and 'list' are disallowed but they are handled |
142 | 146 | [alpn_advertised_protocols, client_preferred_next_protocols, |
143 | 147 | fallback, server_name_indication, srp_identity |
144 | 148 | |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 | []. | |
145 | 165 | |
146 | 166 | -spec accept(ssl:sslsocket(), timeout()) |
147 | 167 | -> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}. |
295 | 315 | end; |
296 | 316 | cleanup(_) -> |
297 | 317 | 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> | |
2 | 2 | %% |
3 | 3 | %% Permission to use, copy, modify, and/or distribute this software for any |
4 | 4 | %% 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 | 2 | %% |
3 | 3 | %% Permission to use, copy, modify, and/or distribute this software for any |
4 | 4 | %% purpose with or without fee is hereby granted, provided that the above |
89 | 89 | listen(TransOpts) -> |
90 | 90 | ok = cleanup(TransOpts), |
91 | 91 | 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) -> | |
93 | 104 | SocketOpts1 = ranch:set_option_default(SocketOpts0, backlog, 1024), |
94 | 105 | SocketOpts2 = ranch:set_option_default(SocketOpts1, nodelay, true), |
95 | 106 | SocketOpts3 = ranch:set_option_default(SocketOpts2, send_timeout, 30000), |
96 | 107 | 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). | |
102 | 110 | |
103 | 111 | %% 'binary' and 'list' are disallowed but they are handled |
104 | 112 | %% 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> | |
2 | 2 | %% |
3 | 3 | %% Permission to use, copy, modify, and/or distribute this software for any |
4 | 4 | %% 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 | |
13 | 13 | 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 | |
18 | 18 | 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 | |
70 | 70 | seshat 220335c03bd0fd761be276cbfa69f3ec5c3b6b56 0.1.0 |
71 | stdout_formatter 5b3b08af16858b74d7bc28cad98200313749d6d0 | |
71 | stdout_formatter 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2 | |
72 | 72 | syslog 67704aec9e7ee2ef9c69d20152b09825d7e6a236 4.0.0 |
73 | sysmon_handler 5b3b08af16858b74d7bc28cad98200313749d6d0 | |
74 | systemd 5b3b08af16858b74d7bc28cad98200313749d6d0 | |
73 | sysmon_handler 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2 | |
74 | systemd 9bc77d25f11d66147d3c6fcf1c894ff4e84fc6d2 |
116 | 116 | dep_looking_glass = git https://github.com/rabbitmq/looking_glass master |
117 | 117 | dep_prometheus = hex 4.8.1 |
118 | 118 | dep_ra = hex 2.0.0 |
119 | dep_ranch = hex 2.0.0 | |
119 | dep_ranch = hex 2.1.0 | |
120 | 120 | dep_recon = hex 2.5.1 |
121 | dep_observer_cli = hex 1.6.2 | |
121 | dep_observer_cli = hex 1.7.1 | |
122 | 122 | dep_stdout_formatter = hex 0.2.4 |
123 | 123 | dep_sysmon_handler = hex 1.3.0 |
124 | 124 |
168 | 168 | "RABBITMQCTL": "$TEST_SRCDIR/$TEST_WORKSPACE/{}/broker-for-tests-home/sbin/rabbitmqctl".format(package), |
169 | 169 | "RABBITMQ_PLUGINS": "$TEST_SRCDIR/$TEST_WORKSPACE/{}/broker-for-tests-home/sbin/rabbitmq-plugins".format(package), |
170 | 170 | "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", | |
172 | 172 | }.items() + test_env.items()), |
173 | 173 | tools = [ |
174 | 174 | ":rabbitmq-for-tests-run", |
175 | "@rabbitmq-server-generic-unix-3.8.18//:rabbitmq-run", | |
175 | "@rabbitmq-server-generic-unix-3.8.22//:rabbitmq-run", | |
176 | 176 | ] + tools, |
177 | 177 | runtime_deps = [ |
178 | 178 | "//deps/rabbitmq_cli:elixir_as_bazel_erlang_lib", |
2 | 2 | RabbitmqHomeInfo = provider( |
3 | 3 | doc = "An assembled RABBITMQ_HOME dir", |
4 | 4 | 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", | |
8 | 6 | }, |
9 | 7 | ) |
10 | 8 | |
111 | 109 | |
112 | 110 | plugins = _flatten([_plugins_dir_links(ctx, plugin) for plugin in plugins]) |
113 | 111 | |
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 | ||
114 | 119 | return [ |
115 | 120 | RabbitmqHomeInfo( |
116 | sbin = scripts, | |
117 | escript = escripts, | |
118 | plugins = plugins, | |
121 | rabbitmqctl = rabbitmqctl, | |
119 | 122 | ), |
120 | 123 | DefaultInfo( |
121 | 124 | files = depset(scripts + escripts + plugins), |
141 | 144 | "plugins": attr.label_list(), |
142 | 145 | }, |
143 | 146 | ) |
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)) |
0 | 0 | load("@//:rabbitmq_home.bzl", "RabbitmqHomeInfo") |
1 | 1 | |
2 | 2 | def _impl(ctx): |
3 | scripts = ctx.files.sbin | |
4 | escripts = ctx.files.escript | |
5 | plugins = ctx.files.plugins | |
6 | ||
7 | 3 | return [ |
8 | 4 | RabbitmqHomeInfo( |
9 | sbin = scripts, | |
10 | escript = escripts, | |
11 | plugins = plugins, | |
5 | rabbitmqctl = ctx.file.rabbitmqctl, | |
12 | 6 | ), |
13 | 7 | DefaultInfo( |
14 | files = depset(scripts + escripts + plugins), | |
8 | files = depset(ctx.files.rabbitmqctl + ctx.files.additional_files), | |
15 | 9 | ), |
16 | 10 | ] |
17 | 11 | |
18 | 12 | rabbitmq_package_generic_unix = rule( |
19 | 13 | implementation = _impl, |
20 | 14 | 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), | |
24 | 17 | }, |
25 | 18 | ) |
0 | 0 | load("@bazel-erlang//:erlang_home.bzl", "ErlangHomeProvider", "ErlangVersionProvider") |
1 | 1 | load("@bazel-erlang//:bazel_erlang_lib.bzl", "path_join") |
2 | 2 | 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") | |
10 | 4 | |
11 | 5 | def _impl(ctx): |
12 | rabbitmq_home = ctx.attr.home[RabbitmqHomeInfo] | |
6 | rabbitmq_home_path = rabbitmq_home_short_path(ctx.attr.home) | |
13 | 7 | |
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")] | |
17 | 10 | |
18 | 11 | ctx.actions.expand_template( |
19 | 12 | template = ctx.file._template, |
20 | 13 | output = ctx.outputs.executable, |
21 | 14 | substitutions = { |
22 | "{RABBITMQ_HOME}": root, | |
15 | "{RABBITMQ_HOME}": rabbitmq_home_path, | |
23 | 16 | "{ERL_LIBS}": ":".join(erl_libs), |
24 | 17 | "{ERLANG_HOME}": ctx.attr._erlang_home[ErlangHomeProvider].path, |
25 | 18 | "{SNAME}": sanitize_sname("sbb-" + ctx.attr.name), |
0 | 0 | load("@bazel-erlang//:erlang_home.bzl", "ErlangVersionProvider") |
1 | load(":rabbitmq_home.bzl", "RabbitmqHomeInfo") | |
1 | load(":rabbitmq_home.bzl", "RabbitmqHomeInfo", "rabbitmq_home_short_path") | |
2 | 2 | |
3 | 3 | def _impl(ctx): |
4 | 4 | erlang_version = ctx.attr._erlang_version[ErlangVersionProvider].version |
5 | 5 | |
6 | rabbitmq_home = ctx.attr.home[RabbitmqHomeInfo] | |
6 | rabbitmq_home_path = rabbitmq_home_short_path(ctx.attr.home) | |
7 | 7 | |
8 | 8 | script = """ |
9 | 9 | exec ./{home}/sbin/{cmd} $@ |
10 | 10 | """.format( |
11 | home = ctx.attr.home.label.name, | |
11 | home = rabbitmq_home_path, | |
12 | 12 | cmd = ctx.label.name, |
13 | 13 | ) |
14 | 14 |
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. |
0 | 0 | #!/usr/bin/env bash |
1 | 1 | 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 | } | |
2 | 14 | |
3 | 15 | if [ -z ${TEST_SRCDIR+x} ]; then |
4 | 16 | BASE_DIR=$PWD |
40 | 52 | fi |
41 | 53 | |
42 | 54 | 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)" | |
44 | 56 | RABBITMQ_PLUGINS=${RABBITMQ_SCRIPTS_DIR}/rabbitmq-plugins |
45 | 57 | RABBITMQ_SERVER=${RABBITMQ_SCRIPTS_DIR}/rabbitmq-server |
46 | RABBITMQCTL=${RABBITMQ_SCRIPTS_DIR}/rabbitmqzctl | |
58 | RABBITMQCTL=${RABBITMQ_SCRIPTS_DIR}/rabbitmqctl | |
47 | 59 | |
48 | 60 | export RABBITMQ_SCRIPTS_DIR RABBITMQCTL RABBITMQ_PLUGINS RABBITMQ_SERVER |
49 | 61 |
6 | 6 | # adding "--spawn_strategy=local" to the invocation is a workaround |
7 | 7 | build --spawn_strategy=local |
8 | 8 | |
9 | # run one test at a time on the local machine | |
10 | build --test_strategy=exclusive | |
11 | ||
12 | 9 | # don't re-run flakes automatically on the local machine |
13 | 10 | build --flaky_test_attempts=1 |
14 | 11 |
163 | 163 | |
164 | 164 | hex_pm_bazel_erlang_lib( |
165 | 165 | name = "observer_cli", |
166 | version = "1.6.1", | |
167 | sha256 = "3418e319764b9dff1f469e43cbdffd7fd54ea47cbf765027c557abd146a19fb3", | |
166 | version = "1.7.1", | |
167 | sha256 = "4ccafaaa2ce01b85ddd14591f4d5f6731b4e13b610a70fb841f0701178478280", | |
168 | 168 | ) |
169 | 169 | |
170 | 170 | github_bazel_erlang_lib( |
227 | 227 | |
228 | 228 | hex_archive( |
229 | 229 | name = "ranch", |
230 | version = "2.0.0", | |
231 | sha256 = "c20a4840c7d6623c19812d3a7c828b2f1bd153ef0f124cb69c54fe51d8a42ae0", | |
230 | version = "2.1.0", | |
231 | sha256 = "244ee3fa2a6175270d8e1fc59024fd9dbc76294a321057de8f803b1479e76916", | |
232 | 232 | build_file = rabbitmq_workspace + "//:BUILD.ranch", |
233 | 233 | ) |
234 | 234 |