├── .gitignore ├── ChangeLog ├── LICENSE ├── Makefile ├── README.md ├── entop ├── rebar.config ├── rebar3 └── src ├── entop.app.src ├── entop.erl ├── entop_collector.erl ├── entop_format.erl ├── entop_net.erl └── entop_view.erl /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | deps 3 | ebin 4 | _build 5 | .rebar3 6 | rebar.lock 7 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | * Wed Oct 24 2018 Anthony Molinaro (djnym) 0.4.1 2 | - update cecho dependency 3 | 4 | * Tue Jul 10 2018 Anthony Molinaro (djnym) 0.4.0 5 | - Support column resize on window resize (thanks Vagabond) 6 | - Support nodenames of the form app@127.0.0.1 (thanks Vagabond) 7 | - Cleanup of state records 8 | - Sorting based on numbers 1-9 switched to 0-9 as we have 10 columns now 9 | - ChangeLog added and version in .app.src derived from ChangeLog 10 | - Added convenience Makefile 11 | 12 | * Tue May 08 2018 Anthony Molinaro (djnym) 0.3.1 13 | - Update version of cecho 14 | 15 | * Sun Aug 13 2017 Mazen Harake (mazenharake) 0.3.0 16 | - Update to use rebar3 17 | 18 | * Sat Jun 11 2017 Mazen Harake (mazenharake) 0.2.0 19 | - License changed to BSD 20 | 21 | * Mon Aug 16 2010 Mazen Harake (mazenharake) 0.0.1 22 | First release 23 | - Shows most common information which is needed when monitoring a remote 24 | Erlang node 25 | - Sorting can be done on any column and sorting can be reversed 26 | - Easy to extend by specifiying own callback modules 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Mazen Harake 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @rebar3 compile 3 | 4 | check: 5 | @rebar3 as test do dialyzer,eunit,cover 6 | 7 | clean: 8 | @rebar3 clean 9 | 10 | maintainer-clean: clean 11 | rm -rf _build 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # entop 2 | A top-like Erlang node monitoring tool 3 | 4 | 5 | ## Introduction 6 | Entop is a tool which shows information about a remote Erlang node in a way which is similar to unix 'top'. 7 | 8 | For entop to run it needs `cecho 0.5.1` or higher 9 | (http://www.github.com/mazenharake/cecho). 10 | 11 | ## Compile 12 | To clean/compile run 13 | 14 | ./rebar3 clean 15 | ./rebar3 compile 16 | 17 | You can now ensure that cecho and entop are installed in your erlang libs dir, 18 | then you can copy the entop script to a bin directory 19 | 20 | ## Usage 21 | To run entop make sure you have Erlang installed, cecho and entop libraries installed, and that the script is in your path. 22 | 23 | Usage: ./entop [] 24 | 25 | ### An example of how you run entop: 26 | 27 | > ./entop foo@11.0.1.2 secret 28 | 29 | ### User Interface: 30 | entop's interface can be customized so this section only applies for the "built-in" interface. 31 | 32 | #### Headers 33 | ##### First row 34 | Shows information about the node which is more or less static such as the node name the operating system, erl flags and erlang version it is running. 35 | ##### Second row 36 | Shows information on what the local time is (according to the node), how long it has been up for (Days:Hours:Minutes:Seconds) and how much latency there is to the node I.e. how long a net_adm:ping() takes. 37 | ##### Third row 38 | Shows information about the processes of the system; the total number of processes, the run queue (number of processes scheduled to run by the scheduler(s)), the reductions per interval (RpI) which shows how many reductions the system has made since it last called the node. 39 | ##### Fourth row 40 | Shows how much process memory is being used and the total amount. 41 | ##### Fifth row 42 | Shows how much system memory, atom memory (currently used/total allocated), binary memory, code memory and ets memory. 43 | ##### Sixth row 44 | Is left blank and is reserved for now. 45 | ##### Seventh row 46 | Shows information about the rows in the list such as the interval in which the information is fetched, what the list is sorted on and how long it took to retrieve the information. 47 | 48 | ### Commands when running entop: 49 | 50 | [0-9]: 51 | Sort on column number 0 through 9. Starts with first column (0) 52 | and up to the last column (9). 53 | 54 | r: 55 | Toggles the sorting order from ascending to descending and vice versa. 56 | 57 | q: 58 | Quits entop and return to the shell. 59 | 60 | Ctrl-C: 61 | Same as 'q'. 62 | 63 | '<' and '>': 64 | Moves the sorting column to the left or right respectively 65 | (these are the lower/greater-than-tags; not arrow keys). 66 | 67 | ### GProc Support 68 | 69 | When entop starts, it checks if the ````gproc```` module is loaded on the target. If so, for processes with no ````registered_name````, entop checks for a gproc name, typically set using ````gen_server:start({via, gproc, ...} ...````. 70 | 71 | Retrieving the gproc name for all processes at once is expensive, so names are dynamically fetched as rows are rendered. 72 | The resulting pid/name results are cached in a local-to-entop ets table. The number of name lookups made is indicated in the table header. 73 | 74 | GProc names are prefixed with ````l```` or ````g```` to indicate local or global registration. 75 | 76 | Contribute 77 | ---------- 78 | Should you find yourself using entop and have issues, comments or feedback please [create an issue!] [1] 79 | 80 | [1]: http://github.com/mazenharake/entop/issues "entop issues" 81 | -------------------------------------------------------------------------------- /entop: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ##============================================================================== 3 | ## Copyright (c) 2017, Mazen Harake 4 | ## All rights reserved. 5 | ## 6 | ## Redistribution and use in source and binary forms, with or without 7 | ## modification, are permitted provided that the following conditions are met: 8 | ## 9 | ## * Redistributions of source code must retain the above copyright notice, 10 | ## this list of conditions and the following disclaimer. 11 | ## * Redistributions in binary form must reproduce the above copyright 12 | ## notice, this list of conditions and the following disclaimer in the 13 | ## documentation and/or other materials provided with the distribution. 14 | ## 15 | ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | ## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | ## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | ## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | ## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | ## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | ## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | ## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | ## POSSIBILITY OF SUCH DAMAGE. 26 | ##============================================================================== 27 | 28 | ## Shell script for running entop. See the function usage() for instructions. 29 | 30 | function usage() { 31 | echo -e "Usage: ./entop []" 32 | } 33 | 34 | if [[ $# -lt 1 ]]; then 35 | usage 36 | exit 1 37 | fi 38 | 39 | TARGETNODE=$1 40 | shift 41 | 42 | COOKIE=${1-undefined} 43 | shift 44 | 45 | if [[ -d _build ]] ; then 46 | PATHS="-pa _build/default/lib/entop/ebin -pa _build/default/lib/cecho/ebin" 47 | else 48 | PATHS="" 49 | fi 50 | 51 | erl -noinput -hidden $PATHS +A 20 +Bc \ 52 | -eval "entop:main(['${TARGETNODE}','${COOKIE}'])." "$@" 53 | 54 | CODE=$? 55 | if [[ $CODE -eq 101 ]]; then 56 | echo "Unable to connect to '${TARGETNODE}', check nodename, cookie and network." 57 | exit 2 58 | elif [[ $CODE -gt 0 ]]; then 59 | echo "Something wrong. Code: ${CODE}" 60 | exit $CODE 61 | fi 62 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [fail_on_warning, 2 | debug_info, 3 | {platform_define, "^(R14|R15|R16B|17)", 'random_module_available'} 4 | ]}. 5 | 6 | {escript_emu_args, "%%! -hidden -noinput +A 20 +Bc"}. 7 | 8 | { deps, [ 9 | {cecho, ".*", 10 | {git, "https://github.com/mazenharake/cecho.git", {tag, "0.5.3"}} 11 | } 12 | ] 13 | }. 14 | -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mazenharake/entop/789f1ee95a30ac08ee11d16fd1b2eff8df2db773/rebar3 -------------------------------------------------------------------------------- /src/entop.app.src: -------------------------------------------------------------------------------- 1 | {application, entop, 2 | [{description, "A top-like tool for monitoring an erlang node"}, 3 | {vsn, {cmd, "/bin/bash -c 'awk \"match(\\$0, /[0-9]+\\.[0-9]+(\\.[0-9]+)+/){print substr(\\$0, RSTART,RLENGTH); exit}\" ChangeLog'"}}, 4 | {registered, []}, 5 | {applications, 6 | [kernel, 7 | stdlib, 8 | cecho 9 | ]}, 10 | {env,[]}, 11 | {modules, [entop, entop_collector, entop_format, entop_view, entop_net]}, 12 | {maintainers, []}, 13 | {licenses, ["BSD"]}, 14 | {links, []} 15 | ]}. 16 | -------------------------------------------------------------------------------- /src/entop.erl: -------------------------------------------------------------------------------- 1 | %%============================================================================== 2 | %% Copyright (c) 2017, Mazen Harake 3 | %% All rights reserved. 4 | %% 5 | %% Redistribution and use in source and binary forms, with or without 6 | %% modification, are permitted provided that the following conditions are met: 7 | %% 8 | %% * Redistributions of source code must retain the above copyright notice, 9 | %% this list of conditions and the following disclaimer. 10 | %% * Redistributions in binary form must reproduce the above copyright 11 | %% notice, this list of conditions and the following disclaimer in the 12 | %% documentation and/or other materials provided with the distribution. 13 | %% 14 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | %% POSSIBILITY OF SUCH DAMAGE. 25 | %%============================================================================== 26 | 27 | -module(entop). 28 | 29 | -include_lib("cecho/include/cecho.hrl"). 30 | 31 | %% escript bits 32 | -export([main/1]). 33 | 34 | %% Application API 35 | -export([start/1]). 36 | 37 | -ifdef(random_module_available). 38 | rand_seed() -> 39 | random:seed(os:timestamp()). 40 | rand_uniform(N) -> 41 | random:uniform(N). 42 | -else. 43 | rand_seed() -> 44 | ok. 45 | rand_uniform(N) -> 46 | rand:uniform(N). 47 | -endif. 48 | 49 | main([]) -> 50 | io:format("Usage: ./entop []~n", []), 51 | halt(1); 52 | main([Node]) -> 53 | main([Node,undefined]); 54 | main([NodeIn,CookieIn]) -> 55 | % node and cookie should both be atoms 56 | Node = normalize_to_atom(NodeIn), 57 | Cookie = normalize_to_atom(CookieIn), 58 | case name_type(Node) of 59 | { error, improper_node_name } -> 60 | io:format("Nodename ~p is malformed, use form 'node@host'~n", [Node]), 61 | halt(101); 62 | {ok, NameType} -> 63 | case net_kernel:start(NameType) of 64 | {ok, _} -> 65 | case maybe_set_cookie(Node,Cookie) of 66 | ok -> 67 | case start(Node) of 68 | {error, cant_connect} -> 69 | io:format("Unable to connect to '~p', check nodename, cookie and network~n",[Node]), 70 | halt(101); 71 | Started -> 72 | Started 73 | end; 74 | CE -> 75 | io:format("Cookie ~p is malformed, got error ~p~n", [Cookie,CE]), 76 | halt(101) 77 | end; 78 | NetError -> 79 | io:format("Couldn't start network with ~p, got error ~p~n", 80 | [NameType, NetError]), 81 | halt(101) 82 | end 83 | end. 84 | 85 | normalize_to_atom(L) when is_list(L) -> 86 | list_to_atom(L); 87 | normalize_to_atom(A) when is_atom(A) -> 88 | A. 89 | 90 | maybe_set_cookie(_, undefined) -> ok; 91 | maybe_set_cookie(Node, Cookie) -> 92 | case erlang:set_cookie(Node,Cookie) of 93 | true -> ok; 94 | E -> E 95 | end. 96 | 97 | name_type (Node) when is_atom(Node) -> 98 | name_type(atom_to_list(Node)); 99 | name_type (Node) when is_list(Node) -> 100 | case string:tokens(Node,"@") of 101 | [_] -> {error, improper_node_name}; 102 | [N,H] -> 103 | T = 104 | case lists:member($.,H) of 105 | true -> longnames; 106 | false -> shortnames 107 | end, 108 | rand_seed(), 109 | R = rand_uniform(1000000), 110 | EntopName = list_to_atom("entop-" ++ integer_to_list(R) ++ "-" ++ N ++ "@" ++ H), 111 | {ok,[EntopName,T]} 112 | end. 113 | 114 | %% ============================================================================= 115 | %% Application API 116 | %% ============================================================================= 117 | start(Node) -> 118 | case entop_view:start(Node) of 119 | {ok, ViewPid} -> control(ViewPid); 120 | E -> E 121 | end. 122 | 123 | control(ViewPid) -> 124 | P = cecho:getch(), 125 | case P of 126 | N when N >= 48 andalso N =< 57 -> 127 | % allow 0-9 to sort based on those columns 128 | ViewPid ! {sort, N - 48}, control(ViewPid); 129 | $> -> ViewPid ! {sort, next}, control(ViewPid); 130 | $< -> ViewPid ! {sort, prev}, control(ViewPid); 131 | $r -> ViewPid ! reverse_sort, control(ViewPid); 132 | $q -> do_exit(ViewPid); 133 | 3 -> do_exit(ViewPid); %Ctrl-C 134 | _ -> ViewPid ! force_update, control(ViewPid) 135 | end. 136 | 137 | do_exit(ViewPid) -> 138 | exit(ViewPid, normal), 139 | application:stop(cecho), 140 | halt(). 141 | 142 | -------------------------------------------------------------------------------- /src/entop_collector.erl: -------------------------------------------------------------------------------- 1 | %%============================================================================== 2 | %% Copyright (c) 2017, Mazen Harake 3 | %% All rights reserved. 4 | %% 5 | %% Redistribution and use in source and binary forms, with or without 6 | %% modification, are permitted provided that the following conditions are met: 7 | %% 8 | %% * Redistributions of source code must retain the above copyright notice, 9 | %% this list of conditions and the following disclaimer. 10 | %% * Redistributions in binary form must reproduce the above copyright 11 | %% notice, this list of conditions and the following disclaimer in the 12 | %% documentation and/or other materials provided with the distribution. 13 | %% 14 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | %% POSSIBILITY OF SUCH DAMAGE. 25 | %%============================================================================== 26 | 27 | -module(entop_collector). 28 | 29 | %% Module API 30 | -export([get_data/0]). 31 | -export([lookup_name/1]). 32 | 33 | %% ============================================================================= 34 | %% Module API 35 | %% ============================================================================= 36 | get_data() -> 37 | HeaderProplist = [{uptime, erlang:statistics(wall_clock)}, 38 | {local_time, calendar:local_time()}, 39 | {process_count, erlang:system_info(process_count)}, 40 | {run_queue, erlang:statistics(run_queue)}, 41 | {reduction_count, erlang:statistics(reductions)}, 42 | {process_memory_used, erlang:memory(processes_used)}, 43 | {process_memory_total, erlang:memory(processes)}, 44 | {memory, erlang:memory([system, atom, atom_used, binary, code, ets])} 45 | ], 46 | HeaderProplist1 = HeaderProplist 47 | ++ case os_mon_started() of 48 | true -> 49 | [{cpu, [{avg1, cpu_sup:avg1() / 256}, 50 | {avg5, cpu_sup:avg5() / 256}, 51 | {avg15, cpu_sup:avg15() / 256}]}]; 52 | false -> 53 | [] 54 | end, 55 | Self = self(), 56 | ProcessesProplist = 57 | [ [ {pid,erlang:pid_to_list(P)}, {realpid, P} | process_info_items(P) ] 58 | || P <- erlang:processes(), P /= Self ], 59 | 60 | {ok, HeaderProplist1, ProcessesProplist}. 61 | 62 | %% ============================================================================= 63 | %% Internal Functions 64 | %% ============================================================================= 65 | process_info_items(P) -> 66 | erlang:process_info(P, [registered_name, 67 | reductions, 68 | message_queue_len, 69 | heap_size, 70 | stack_size, 71 | total_heap_size, 72 | memory, 73 | dictionary, 74 | initial_call, 75 | current_function, 76 | status]). 77 | 78 | os_mon_started() -> 79 | [App || {os_mon, _, _} = App <- application:which_applications()] /= []. 80 | 81 | lookup_name(Pid) when is_pid(Pid) -> 82 | case whereis(gproc) of 83 | undefined -> undefined; 84 | _ -> 85 | {gproc, Props} = gproc:info(Pid, gproc), 86 | case [ E || {E,_} <- Props, element(1,E)==n ] of 87 | [] -> undefined; 88 | [Ret] -> Ret 89 | end 90 | end. 91 | -------------------------------------------------------------------------------- /src/entop_format.erl: -------------------------------------------------------------------------------- 1 | %%============================================================================== 2 | %% Copyright (c) 2017, Mazen Harake 3 | %% All rights reserved. 4 | %% 5 | %% Redistribution and use in source and binary forms, with or without 6 | %% modification, are permitted provided that the following conditions are met: 7 | %% 8 | %% * Redistributions of source code must retain the above copyright notice, 9 | %% this list of conditions and the following disclaimer. 10 | %% * Redistributions in binary form must reproduce the above copyright 11 | %% notice, this list of conditions and the following disclaimer in the 12 | %% documentation and/or other materials provided with the distribution. 13 | %% 14 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | %% POSSIBILITY OF SUCH DAMAGE. 25 | %%============================================================================== 26 | 27 | -module(entop_format). 28 | 29 | -include_lib("cecho/include/cecho.hrl"). 30 | 31 | %% Module API 32 | -export([init/1, resize/2, header/2, row/3, row_reductions/1]). 33 | 34 | %% Records 35 | -record(state, { node = undefined, cache = [], default_columns = [] }). 36 | 37 | %% Defines 38 | -define(KIB,(1024)). 39 | -define(MIB,(?KIB*1024)). 40 | -define(GIB,(?MIB*1024)). 41 | -define(SECONDS_PER_MIN, 60). 42 | -define(SECONDS_PER_HOUR, (?SECONDS_PER_MIN*60)). 43 | -define(SECONDS_PER_DAY, (?SECONDS_PER_HOUR*24)). 44 | -define(R(V,N), string:right(integer_to_list(V),N,$0)). 45 | 46 | %% ============================================================================= 47 | %% Module API 48 | %% ============================================================================= 49 | init(Node) -> 50 | ets:new(piddb, [set,named_table]), 51 | Columns = [ 52 | {"Pid", 16, [{align, left}]}, 53 | {"Process", 30, [{align, left}]}, 54 | {"Current Function", 30, [{align, left}]}, 55 | {"Status", 8, [{align, left}]}, 56 | {"Reductions", 13, [{align, right}]}, 57 | {"Reductions+", 13, [{align, right}]}, 58 | {"Message Queue", 14, [{align, right}]}, 59 | {"Memory", 15, [{align, right}]}, 60 | {"Stack Size", 11, [{align, right}]}, 61 | {"Heap Size", 12, [{align, right}]} 62 | ], 63 | {ok, {Columns, 5}, #state{ node = Node, default_columns=Columns }}. 64 | 65 | resize(Width, State) -> 66 | Columns = State#state.default_columns, 67 | TotalWidth = lists:sum([ X || {_, X, _} <- Columns]), 68 | ExpandedColumns = case (Width - (TotalWidth + length(Columns))) of 69 | ExtraCols when (ExtraCols - length(Columns)) > 0 -> 70 | ExtraPerColumn = ExtraCols div length(Columns), 71 | [ {Title, ColWidth + ExtraPerColumn, Style} || {Title, ColWidth, Style} <- Columns]; 72 | _ -> 73 | Columns 74 | end, 75 | {ok, ExpandedColumns}. 76 | 77 | %% Header Callback 78 | header(SystemInfo, State) -> 79 | Uptime = millis2uptimestr(element(1, proplists:get_value(uptime, SystemInfo, 0))), 80 | LocalTime = local2str(element(2, proplists:get_value(local_time, SystemInfo))), 81 | PingTime = element(1,timer:tc(net_adm, ping, [State#state.node])) div 1000, 82 | 83 | Row1Essentials = io_lib:format("Time: ~s, up for ~s, ~pms latency", [LocalTime, Uptime, PingTime]), 84 | Row1 = Row1Essentials 85 | ++ case proplists:get_value(cpu, SystemInfo, []) of 86 | [] -> 87 | []; 88 | CPUInfo -> 89 | CPUAvg1 = proplists:get_value(avg1, CPUInfo, 0.0), 90 | CPUAvg5 = proplists:get_value(avg5, CPUInfo, 0.0), 91 | CPUAvg15 = proplists:get_value(avg15, CPUInfo, 0.0), 92 | io_lib:format(", load average: ~.2f, ~.2f, ~.2f", 93 | [CPUAvg1, CPUAvg5, CPUAvg15]) 94 | end, 95 | 96 | PTotal = proplists:get_value(process_count, SystemInfo), 97 | RQueue = proplists:get_value(run_queue, SystemInfo), 98 | RedTotal = element(2,proplists:get_value(reduction_count, SystemInfo)), 99 | Row2 = io_lib:format("Processes: ~6w, Run Queue: ~5w, Reductions: ~9w namerpcs: ~B~n", 100 | [PTotal, RQueue, RedTotal, ets:info(piddb,size)]), 101 | 102 | PMemUsed = mem2str(proplists:get_value(process_memory_used, SystemInfo)), 103 | PMemTotal = mem2str(proplists:get_value(process_memory_total, SystemInfo)), 104 | MemInfo = proplists:get_value(memory, SystemInfo), 105 | SystemMem = mem2str(proplists:get_value(system, MemInfo)), 106 | AtomMem = mem2str(proplists:get_value(atom, MemInfo)), 107 | BinMem = mem2str(proplists:get_value(binary, MemInfo)), 108 | CodeMem = mem2str(proplists:get_value(code, MemInfo)), 109 | EtsMem = mem2str(proplists:get_value(ets, MemInfo)), 110 | Row3 = io_lib:format("Process Memory: ~s total (~s used)", [PMemTotal, PMemUsed]), 111 | Row4 = io_lib:format("System Memory: ~s (Atom: ~s, Binary: ~s, Code: ~s, ETS: ~s)", 112 | [SystemMem, AtomMem, BinMem, CodeMem, EtsMem]), 113 | Row5 = "", 114 | {ok, [ lists:flatten(Row) || Row <- [Row1, Row2, Row3, Row4, Row5] ], State}. 115 | 116 | lookup_name(Props, State=#state{}) when is_list(Props) -> 117 | case proplists:get_value(registered_name, Props) of 118 | [] -> 119 | Pid = proplists:get_value(realpid, Props), 120 | case ets:lookup(piddb, Pid) of 121 | [{Pid,Val}] -> Val; 122 | [] -> 123 | Val = entop_net:lookup_name(State#state.node, entop_collector, Pid), 124 | ets:insert(piddb, {Pid, Val}), 125 | Val 126 | end; 127 | N -> 128 | N 129 | end. 130 | 131 | format_name(Name) -> 132 | case Name of 133 | [] -> []; 134 | undefined -> []; 135 | Name when is_atom(Name) -> 136 | atom_to_list(Name); 137 | {n,g,T} -> lists:flatten(io_lib:format("g:~p",[T])); 138 | {n,l,T} -> lists:flatten(io_lib:format("l:~p",[T])); 139 | Name -> lists:flatten(io_lib:format("~p",[Name])) 140 | end. 141 | 142 | %% Column Specific Callbacks 143 | row([{pid,_},{realpid,_}|undefined], _LastReductions, State) -> 144 | {ok, skip, State}; 145 | row(ProcessInfo, LastReductions, State) -> 146 | Pid = proplists:get_value(pid, ProcessInfo), 147 | %% defer, only called if this row is rendered: 148 | ProcessName = fun() -> proc_name(ProcessInfo, State) end, 149 | CurrentFunction = proplists:get_value(current_function, ProcessInfo), 150 | Reductions = proplists:get_value(reductions, ProcessInfo, 0), 151 | ReductionsDiff = Reductions - LastReductions, 152 | Queue = proplists:get_value(message_queue_len, ProcessInfo, 0), 153 | Memory = proplists:get_value(memory, ProcessInfo, 0), 154 | StackSize = proplists:get_value(stack_size, ProcessInfo, 0), 155 | TotalHeapSize = proplists:get_value(total_heap_size, ProcessInfo, 0), 156 | Status = proplists:get_value(status, ProcessInfo), 157 | {ok, {Pid, ProcessName, CurrentFunction, Status, Reductions, ReductionsDiff, Queue, Memory, StackSize, TotalHeapSize}, State}. 158 | 159 | row_reductions({_Pid, _, _, _, Reductions, _, _, _, _, _} = _Row) -> 160 | Reductions. 161 | 162 | proc_name(ProcessInfo, State) -> 163 | case format_name(lookup_name(ProcessInfo, State)) of 164 | [] -> initial_call(ProcessInfo); 165 | Name -> Name 166 | end. 167 | 168 | initial_call(ProcessInfo) -> 169 | ProcessDict = proplists:get_value('dictionary', ProcessInfo, []), 170 | case proplists:get_value('$initial_call', ProcessDict, []) of 171 | [] -> proplists:get_value(initial_call, ProcessInfo); 172 | Call -> Call 173 | end. 174 | 175 | mem2str(Mem) -> 176 | if Mem > ?GIB -> io_lib:format("~.1fG",[Mem/?GIB]); 177 | Mem > ?MIB -> io_lib:format("~.1fM",[Mem/?MIB]); 178 | Mem > ?KIB -> io_lib:format("~.1fK",[Mem/?KIB]); 179 | Mem >= 0 -> io_lib:format("~.1fb",[Mem/1.0]) 180 | end. 181 | 182 | millis2uptimestr(Millis) -> 183 | SecTime = Millis div 1000, 184 | Days = ?R(SecTime div ?SECONDS_PER_DAY,3), 185 | Hours = ?R((SecTime rem ?SECONDS_PER_DAY) div ?SECONDS_PER_HOUR,2), 186 | Minutes = ?R(((SecTime rem ?SECONDS_PER_DAY) rem ?SECONDS_PER_HOUR) div ?SECONDS_PER_MIN, 2), 187 | Seconds = ?R(((SecTime rem ?SECONDS_PER_DAY) rem ?SECONDS_PER_HOUR) rem ?SECONDS_PER_MIN, 2), 188 | io_lib:format("~s:~s:~s:~s",[Days,Hours,Minutes,Seconds]). 189 | 190 | local2str({Hours,Minutes,Seconds}) -> 191 | io_lib:format("~s:~s:~s",[?R(Hours,2),?R(Minutes,2),?R(Seconds,2)]). 192 | -------------------------------------------------------------------------------- /src/entop_net.erl: -------------------------------------------------------------------------------- 1 | %%============================================================================== 2 | %% Copyright (c) 2017, Mazen Harake 3 | %% All rights reserved. 4 | %% 5 | %% Redistribution and use in source and binary forms, with or without 6 | %% modification, are permitted provided that the following conditions are met: 7 | %% 8 | %% * Redistributions of source code must retain the above copyright notice, 9 | %% this list of conditions and the following disclaimer. 10 | %% * Redistributions in binary form must reproduce the above copyright 11 | %% notice, this list of conditions and the following disclaimer in the 12 | %% documentation and/or other materials provided with the distribution. 13 | %% 14 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | %% POSSIBILITY OF SUCH DAMAGE. 25 | %%============================================================================== 26 | 27 | -module(entop_net). 28 | 29 | %% Module API 30 | -export([fetch_data/2, reconnect/2, lookup_name/3]). 31 | 32 | %% ============================================================================= 33 | %% Module API 34 | %% ============================================================================= 35 | fetch_data(Node, Module) -> 36 | timer:tc(rpc, call, [Node, Module, get_data, []]). 37 | 38 | lookup_name(Node, Module, Pid) when is_pid(Pid) -> 39 | rpc:call(Node, Module, lookup_name, [Pid]). 40 | 41 | reconnect(Parent, Node) -> 42 | case net_kernel:connect(Node) of 43 | true -> 44 | Parent ! {nodeup, Node}; 45 | false -> 46 | catch ets:delete_all_objects(piddb), 47 | timer:sleep(1000), 48 | reconnect(Parent, Node) 49 | end. 50 | -------------------------------------------------------------------------------- /src/entop_view.erl: -------------------------------------------------------------------------------- 1 | %%============================================================================== 2 | %% Copyright (c) 2017, Mazen Harake 3 | %% All rights reserved. 4 | %% 5 | %% Redistribution and use in source and binary forms, with or without 6 | %% modification, are permitted provided that the following conditions are met: 7 | %% 8 | %% * Redistributions of source code must retain the above copyright notice, 9 | %% this list of conditions and the following disclaimer. 10 | %% * Redistributions in binary form must reproduce the above copyright 11 | %% notice, this list of conditions and the following disclaimer in the 12 | %% documentation and/or other materials provided with the distribution. 13 | %% 14 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | %% POSSIBILITY OF SUCH DAMAGE. 25 | %%============================================================================== 26 | 27 | -module(entop_view). 28 | 29 | -include_lib("cecho/include/cecho.hrl"). 30 | 31 | %% Module API 32 | -export([start/1, reload/1]). 33 | 34 | %% Defines 35 | -define(MAX_HLINE, 300). 36 | 37 | %% Records 38 | -record(state, { callback = entop_format, 39 | remote_module = entop_collector, 40 | maxyx, 41 | columns, 42 | cbstate, 43 | node, 44 | otp_version, 45 | erts_version, 46 | os_fam, 47 | os, 48 | os_version, 49 | node_flags, 50 | interval = 2000, 51 | reverse_sort = true, 52 | sort = 0, 53 | connected = false, 54 | last_reductions = dict:new() }). 55 | 56 | %% ============================================================================= 57 | %% Module API 58 | %% ============================================================================= 59 | start(Node) -> 60 | Parent = self(), 61 | case net_kernel:connect_node(Node) of 62 | true -> 63 | State = #state { node = Node, connected = true }, 64 | NState = load_remote_static_data(State), 65 | remote_load_code(NState#state.remote_module, State#state.node), 66 | ViewPid = erlang:spawn(fun() -> init(Parent, NState) end), 67 | receive continue -> ok end, 68 | {ok, ViewPid}; 69 | false -> 70 | {error, cant_connect} 71 | end. 72 | 73 | reload(ViewPid) -> 74 | ViewPid ! reload. 75 | 76 | %% ============================================================================= 77 | %% Internal Functions 78 | %% ============================================================================= 79 | load_remote_static_data(State) -> 80 | RPC = fun(M, F, A) -> rpc:call(State#state.node, M, F, A) end, 81 | Otp = RPC(erlang, system_info, [otp_release]), 82 | Erts = RPC(erlang, system_info, [version]), 83 | {Os1, Os2} = RPC(os, type, []), 84 | OsVers = RPC(os, version, []), 85 | Flags = [{cpus, RPC(erlang, system_info, [logical_processors])}, 86 | {smp, RPC(erlang, system_info, [smp_support])}, 87 | {a_threads, RPC(erlang, system_info, [thread_pool_size])}, 88 | {kpoll, RPC(erlang, system_info, [kernel_poll])}], 89 | State#state{ otp_version = Otp, erts_version = Erts, 90 | os_fam = Os1, os = Os2, os_version = OsVers, 91 | node_flags = Flags }. 92 | 93 | remote_load_code(Module, Node) -> 94 | {_, Binary, Filename} = code:get_object_code(Module), 95 | rpc:call(Node, code, load_binary, [Module, Filename, Binary]). 96 | 97 | init(Parent, State0) -> 98 | process_flag(trap_exit, true), 99 | application:start(cecho), 100 | ok = cecho:cbreak(), 101 | ok = cecho:noecho(), 102 | ok = cecho:curs_set(?ceCURS_INVISIBLE), 103 | ok = cecho:keypad(?ceSTDSCR, true), 104 | % keep track of the max screen size so we can resize if necessary 105 | MaxYX = cecho:getmaxyx(), 106 | State1 = State0#state { maxyx = MaxYX }, 107 | 108 | State2 = init_callback(State1), 109 | print_nodeinfo(State2), 110 | Parent ! continue, 111 | self() ! time_update, 112 | loop(Parent, State2). 113 | 114 | init_callback(State) -> 115 | case (State#state.callback):init(State#state.node) of 116 | {ok, {Columns, DefaultSort}, CBState} 117 | when DefaultSort >= 0 andalso DefaultSort < length(Columns) -> 118 | % sorting is based on 0 indices so all columns can be sorted on 119 | State#state{ columns = Columns, cbstate = CBState, sort = DefaultSort }; 120 | {ok, {Columns, _}, CBState} -> 121 | State#state{ columns = Columns, cbstate = CBState, sort = 0 } 122 | end. 123 | 124 | print_nodeinfo(State) -> 125 | cecho:move(0, 0), 126 | cecho:hline($ , ?MAX_HLINE), 127 | {Mj, Md, Mi} = State#state.os_version, 128 | OsVers = lists:concat([Mj,".",Md,".",Mi]), 129 | cecho:mvaddstr(0, 0, io_lib:format("Node: ~p ",[State#state.node])), 130 | case State#state.connected of 131 | false -> cecho:addstr("(Disconnected)"); 132 | true -> cecho:addstr("(Connected)") 133 | end, 134 | Head = io_lib:format(" (~s/~s) ~p (~p ~s)~s", 135 | [State#state.otp_version, 136 | State#state.erts_version, State#state.os_fam, 137 | State#state.os, OsVers, flags2str(State#state.node_flags)]), 138 | ok = cecho:addstr(lists:flatten(Head)). 139 | 140 | flags2str([]) -> []; 141 | flags2str([{cpus, N}|Rest]) -> 142 | [" CPU:"++integer_to_list(N)|flags2str(Rest)]; 143 | flags2str([{smp, true}|Rest]) -> 144 | [" SMP"|flags2str(Rest)]; 145 | flags2str([{a_threads, N}|Rest]) -> 146 | [" +A:"++integer_to_list(N)|flags2str(Rest)]; 147 | flags2str([{kpoll, true}|Rest]) -> 148 | [" +K"|flags2str(Rest)]; 149 | flags2str([_|Rest]) -> 150 | flags2str(Rest). 151 | 152 | loop(Parent, #state{ connected = false } = State) -> 153 | receive 154 | {nodeup, Node} when Node == State#state.node -> 155 | remote_load_code(State#state.remote_module, State#state.node), 156 | loop(Parent, fetch_and_update(State#state{ connected = true }, false)); 157 | _ -> 158 | loop(Parent, State) 159 | end; 160 | loop(Parent, State) -> 161 | receive 162 | time_update -> 163 | loop(Parent, fetch_and_update(State, false)); 164 | force_update -> 165 | loop(Parent, fetch_and_update(State, true)); 166 | {sort, N} when is_integer(N) -> 167 | State2 = update_sort_screen(State, N), 168 | loop(Parent, State2); 169 | {sort, Direction} -> 170 | State2 = 171 | case Direction of 172 | next -> update_sort_screen(State, State#state.sort + 1); 173 | prev -> update_sort_screen(State, State#state.sort - 1) 174 | end, 175 | loop(Parent, State2); 176 | reverse_sort -> 177 | State2 = fetch_and_update(State#state{ reverse_sort = (not State#state.reverse_sort) }, true), 178 | loop(Parent, State2); 179 | {'EXIT', Parent, _} -> 180 | ok 181 | end. 182 | 183 | fetch_and_update(State, IsForced) -> 184 | case entop_net:fetch_data(State#state.node, State#state.remote_module) of 185 | {_Time, {badrpc, nodedown}} -> 186 | NState = State#state{ connected = false }, 187 | print_nodeinfo(NState), 188 | cecho:refresh(), 189 | erlang:spawn_link(entop_net, reconnect, [self(), State#state.node]), 190 | NState; 191 | {_Time, {badrpc, {'EXIT', {undef, _}}}}-> 192 | remote_load_code(State#state.remote_module, State#state.node), 193 | fetch_and_update(State, IsForced); 194 | {Time, {ok, HeaderData, RowDataList}} -> 195 | State2 = update_screen(Time, HeaderData, RowDataList, State), 196 | if not IsForced -> erlang:send_after(State2#state.interval, self(), time_update); 197 | true -> ok 198 | end, 199 | State2 200 | end. 201 | 202 | update_sort_screen(State, N) -> 203 | case N >= 0 andalso N < length(State#state.columns) of 204 | true -> fetch_and_update(State#state{ sort = N }, true); 205 | false -> State 206 | end. 207 | 208 | update_screen(Time, HeaderData, RowDataList, State0) -> 209 | % check to see if the screen has changed and if it has resize it 210 | NewMaxYX = {MaxY, MaxX} = cecho:getmaxyx(), 211 | State1 = 212 | case NewMaxYX =:= State0#state.maxyx of 213 | true -> State0; 214 | false -> 215 | % then resize the columns if it has 216 | {ok, Columns} = 217 | (State0#state.callback):resize(MaxX, State0#state.cbstate), 218 | State0#state{columns = Columns} 219 | end, 220 | 221 | print_nodeinfo(State1), 222 | draw_title_bar(State1), 223 | print_showinfo(State1, Time), 224 | {Headers, State2} = process_header_data(HeaderData, State1), 225 | lists:foldl(fun(Header, Y) -> 226 | cecho:hline($ , ?MAX_HLINE), 227 | cecho:mvaddstr(Y, 0, Header), Y + 1 228 | end, 1, Headers), 229 | {RowList, State3} = process_row_data(RowDataList, State2), 230 | SortedRowList = sort(RowList, State3), 231 | StartY = (MaxY-(MaxY-8)), 232 | lists:foreach(fun(N) -> cecho:move(N, 0), cecho:hline($ , ?MAX_HLINE) end, lists:seq(StartY, MaxY)), 233 | update_rows(SortedRowList, State3#state.columns, StartY, MaxY), 234 | cecho:refresh(), 235 | State3. 236 | 237 | draw_title_bar(State) -> 238 | cecho:move(7, 0), 239 | cecho:attron(?ceA_REVERSE), 240 | cecho:hline($ , ?MAX_HLINE), 241 | draw_title_bar(State#state.columns, 0), 242 | cecho:attroff(?ceA_REVERSE). 243 | 244 | draw_title_bar([], _) -> ok; 245 | draw_title_bar([{Title, Width, Options}|Rest], Offset) -> 246 | Align = proplists:get_value(align, Options, left), 247 | cecho:mvaddstr(7, Offset, string:Align(Title, Width)++" "), 248 | draw_title_bar(Rest, Offset + Width + 1). 249 | 250 | print_showinfo(State, RoundTripTime) -> 251 | cecho:move(6, 0), 252 | cecho:hline($ , ?MAX_HLINE), 253 | ColName = element(1,lists:nth(State#state.sort+1, State#state.columns)), 254 | SortName = if State#state.reverse_sort -> "Descending"; true -> "Ascending" end, 255 | Showing = io_lib:format("Interval ~pms, Sorting on ~p (~s), Retrieved in ~pms", 256 | [State#state.interval, ColName, SortName, RoundTripTime div 1000]), 257 | cecho:mvaddstr(6, 0, lists:flatten(Showing)). 258 | 259 | process_header_data(HeaderData, State) -> 260 | {ok, Headers, NCBState} = (State#state.callback):header(HeaderData, State#state.cbstate), 261 | {Headers, State#state{ cbstate = NCBState }}. 262 | 263 | process_row_data(RowDataList, State) -> 264 | prd(RowDataList, State, {[], dict:new()}). 265 | 266 | prd([], State, {Acc, NLRD}) -> 267 | {Acc, State#state{last_reductions = NLRD}}; 268 | prd([RowData|Rest], #state{last_reductions = LRD} = State, FullAcc = {Acc, LRDAcc}) -> 269 | Pid = proplists:get_value(pid, RowData), 270 | LastReductions = case dict:find(Pid, LRD) of 271 | error -> 0; 272 | {ok, N} -> N 273 | end, 274 | case (State#state.callback):row(RowData, LastReductions, State#state.cbstate) of 275 | {ok, skip, NCBState} -> 276 | prd(Rest, State#state{ cbstate = NCBState }, FullAcc); 277 | {ok, Row, NCBState} -> 278 | NReds = (State#state.callback):row_reductions(Row), 279 | prd(Rest, State#state{ cbstate = NCBState }, {[Row|Acc], dict:store(Pid, NReds, LRDAcc)}) 280 | end. 281 | 282 | sort(ProcList, State) -> 283 | Sorted = lists:keysort(State#state.sort+1, ProcList), 284 | case State#state.reverse_sort of 285 | true -> 286 | lists:reverse(Sorted); 287 | false -> 288 | Sorted 289 | end. 290 | 291 | update_rows(ProcValuesList, _, LineNumber, Max) 292 | when LineNumber == Max orelse ProcValuesList == [] -> 293 | ok; 294 | update_rows([RowValues|Rest], Columns, LineNumber, Max) -> 295 | update_row(tuple_to_list(RowValues), Columns, LineNumber, 0), 296 | update_rows(Rest, Columns, LineNumber + 1, Max). 297 | 298 | update_row(R, C, _, _) 299 | when R == [] orelse C == [] -> 300 | ok; 301 | update_row([RowColValue|Rest], ColOpts, LineNumber, Offset) 302 | when is_function(RowColValue) -> 303 | update_row([RowColValue()|Rest], ColOpts, LineNumber, Offset); 304 | update_row([RowColValue|Rest], [{_,Width,Options}|RestColumns], 305 | LineNumber, Offset) -> 306 | StrColVal = if is_list(RowColValue) -> RowColValue; 307 | true -> lists:flatten(io_lib:format("~1000p",[RowColValue])) 308 | end, 309 | Aligned = case proplists:get_value(align, Options) of 310 | right -> 311 | string:right(StrColVal, Width); 312 | _ -> 313 | string:left(StrColVal, Width) 314 | end, 315 | cecho:mvaddstr(LineNumber, Offset, Aligned), 316 | update_row(Rest, RestColumns, LineNumber, Offset+Width+1). 317 | --------------------------------------------------------------------------------