Ulf Wiger 14 лет назад
Родитель
Сommit
b1589f6a2b
7 измененных файлов с 234 добавлено и 6 удалено
  1. 1 0
      README.md
  2. 1 0
      doc/README.md
  3. 2 1
      doc/edoc-info
  4. 18 2
      doc/gproc.md
  5. 40 0
      doc/gproc_info.md
  6. 9 3
      src/gproc.erl
  7. 163 0
      src/gproc_info.erl

+ 1 - 0
README.md

@@ -133,6 +133,7 @@ Freiburg 2007 ([Paper available here](http://github.com/esl/gproc/blob/master/do
 <tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc.md" class="module">gproc</a></td></tr>
 <tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc_app.md" class="module">gproc_app</a></td></tr>
 <tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc_dist.md" class="module">gproc_dist</a></td></tr>
+<tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc_info.md" class="module">gproc_info</a></td></tr>
 <tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc_init.md" class="module">gproc_init</a></td></tr>
 <tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc_lib.md" class="module">gproc_lib</a></td></tr>
 <tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc_sup.md" class="module">gproc_sup</a></td></tr></table>

+ 1 - 0
doc/README.md

@@ -133,6 +133,7 @@ Freiburg 2007 ([Paper available here](erlang07-wiger.pdf)).
 <tr><td><a href="gproc.md" class="module">gproc</a></td></tr>
 <tr><td><a href="gproc_app.md" class="module">gproc_app</a></td></tr>
 <tr><td><a href="gproc_dist.md" class="module">gproc_dist</a></td></tr>
+<tr><td><a href="gproc_info.md" class="module">gproc_info</a></td></tr>
 <tr><td><a href="gproc_init.md" class="module">gproc_init</a></td></tr>
 <tr><td><a href="gproc_lib.md" class="module">gproc_lib</a></td></tr>
 <tr><td><a href="gproc_sup.md" class="module">gproc_sup</a></td></tr></table>

+ 2 - 1
doc/edoc-info

@@ -1,3 +1,4 @@
 {application,gproc}.
 {packages,[]}.
-{modules,[gproc,gproc_app,gproc_dist,gproc_init,gproc_lib,gproc_sup]}.
+{modules,[gproc,gproc_app,gproc_dist,gproc_info,gproc_init,gproc_lib,
+          gproc_sup]}.

+ 18 - 2
doc/gproc.md

@@ -181,7 +181,8 @@ a = aggregate_counter
 
 
 <table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#add_global_aggr_counter-1">add_global_aggr_counter/1</a></td><td>Registers a global (unique) aggregated counter.</td></tr><tr><td valign="top"><a href="#add_global_counter-2">add_global_counter/2</a></td><td>Registers a global (non-unique) counter.</td></tr><tr><td valign="top"><a href="#add_global_name-1">add_global_name/1</a></td><td>Registers a global (unique) name.</td></tr><tr><td valign="top"><a href="#add_global_property-2">add_global_property/2</a></td><td>Registers a global (non-unique) property.</td></tr><tr><td valign="top"><a href="#add_local_aggr_counter-1">add_local_aggr_counter/1</a></td><td>Registers a local (unique) aggregated counter.</td></tr><tr><td valign="top"><a href="#add_local_counter-2">add_local_counter/2</a></td><td>Registers a local (non-unique) counter.</td></tr><tr><td valign="top"><a href="#add_local_name-1">add_local_name/1</a></td><td>Registers a local (unique) name.</td></tr><tr><td valign="top"><a href="#add_local_property-2">add_local_property/2</a></td><td>Registers a local (non-unique) property.</td></tr><tr><td valign="top"><a href="#audit_process-1">audit_process/1</a></td><td></td></tr><tr><td valign="top"><a href="#await-1">await/1</a></td><td>Equivalent to <a href="#await-2"><tt>await(Key, infinity)</tt></a>.</td></tr><tr><td valign="top"><a href="#await-2">await/2</a></td><td>Wait for a local name to be registered.</td></tr><tr><td valign="top"><a href="#cancel_wait-2">cancel_wait/2</a></td><td></td></tr><tr><td valign="top"><a href="#default-1">default/1</a></td><td></td></tr><tr><td valign="top"><a href="#first-1">first/1</a></td><td>Behaves as ets:first(Tab) for a given type of registration object.</td></tr><tr><td valign="top"><a href="#get_env-3">get_env/3</a></td><td>Equivalent to <a href="#get_env-4"><tt>get_env(Scope, App, Key, [app_env])</tt></a>.</td></tr><tr><td valign="top"><a href="#get_env-4">get_env/4</a></td><td>Read an environment value, potentially cached as a <code>gproc_env</code> property.</td></tr><tr><td valign="top"><a href="#get_set_env-3">get_set_env/3</a></td><td>Equivalent to <a href="#get_set_env-4"><tt>get_set_env(Scope, App, Key, [app_env])</tt></a>.</td></tr><tr><td valign="top"><a href="#get_set_env-4">get_set_env/4</a></td><td>Fetch and cache an environment value, if not already cached.</td></tr><tr><td valign="top"><a href="#get_value-1">get_value/1</a></td><td>Read the value stored with a key registered to the current process.</td></tr><tr><td valign="top"><a href="#give_away-2">give_away/2</a></td><td>Atomically transfers the key <code>From</code> to the process identified by <code>To</code>.</td></tr><tr><td valign="top"><a href="#goodbye-0">goodbye/0</a></td><td>Unregister all items of the calling process and inform gproc  
-to forget about the calling process.</td></tr><tr><td valign="top"><a href="#info-1">info/1</a></td><td>Similar to <code>process_info(Pid)</code> but with additional gproc info.</td></tr><tr><td valign="top"><a href="#info-2">info/2</a></td><td>Similar to process_info(Pid, Item), but with additional gproc info.</td></tr><tr><td valign="top"><a href="#last-1">last/1</a></td><td>Behaves as ets:last(Tab) for a given type of registration object.</td></tr><tr><td valign="top"><a href="#lookup_global_aggr_counter-1">lookup_global_aggr_counter/1</a></td><td>Lookup a global (unique) aggregated counter and returns its value.</td></tr><tr><td valign="top"><a href="#lookup_global_counters-1">lookup_global_counters/1</a></td><td>Look up all global (non-unique) instances of a given Counter.</td></tr><tr><td valign="top"><a href="#lookup_global_name-1">lookup_global_name/1</a></td><td>Lookup a global unique name.</td></tr><tr><td valign="top"><a href="#lookup_global_properties-1">lookup_global_properties/1</a></td><td>Look up all global (non-unique) instances of a given Property.</td></tr><tr><td valign="top"><a href="#lookup_local_aggr_counter-1">lookup_local_aggr_counter/1</a></td><td>Lookup a local (unique) aggregated counter and returns its value.</td></tr><tr><td valign="top"><a href="#lookup_local_counters-1">lookup_local_counters/1</a></td><td>Look up all local (non-unique) instances of a given Counter.</td></tr><tr><td valign="top"><a href="#lookup_local_name-1">lookup_local_name/1</a></td><td>Lookup a local unique name.</td></tr><tr><td valign="top"><a href="#lookup_local_properties-1">lookup_local_properties/1</a></td><td>Look up all local (non-unique) instances of a given Property.</td></tr><tr><td valign="top"><a href="#lookup_pid-1">lookup_pid/1</a></td><td>Lookup the Pid stored with a key.</td></tr><tr><td valign="top"><a href="#lookup_pids-1">lookup_pids/1</a></td><td>Returns a list of pids with the published key Key.</td></tr><tr><td valign="top"><a href="#lookup_value-1">lookup_value/1</a></td><td>Lookup the value stored with a key.</td></tr><tr><td valign="top"><a href="#lookup_values-1">lookup_values/1</a></td><td>Retrieve the <code>{Pid,Value}</code> pairs corresponding to Key.</td></tr><tr><td valign="top"><a href="#mreg-3">mreg/3</a></td><td>Register multiple {Key,Value} pairs of a given type and scope.</td></tr><tr><td valign="top"><a href="#munreg-3">munreg/3</a></td><td>Unregister multiple Key items of a given type and scope.</td></tr><tr><td valign="top"><a href="#nb_wait-1">nb_wait/1</a></td><td>Wait for a local name to be registered.</td></tr><tr><td valign="top"><a href="#next-2">next/2</a></td><td>Behaves as ets:next(Tab,Key) for a given type of registration object.</td></tr><tr><td valign="top"><a href="#prev-2">prev/2</a></td><td>Behaves as ets:prev(Tab,Key) for a given type of registration object.</td></tr><tr><td valign="top"><a href="#reg-1">reg/1</a></td><td>Equivalent to <a href="#reg-2"><tt>reg(Key, default(Key))</tt></a>.</td></tr><tr><td valign="top"><a href="#reg-2">reg/2</a></td><td>Register a name or property for the current process.</td></tr><tr><td valign="top"><a href="#select-1">select/1</a></td><td>Equivalent to <a href="#select-2"><tt>select(all, Pat)</tt></a>.</td></tr><tr><td valign="top"><a href="#select-2">select/2</a></td><td>Perform a select operation on the process registry.</td></tr><tr><td valign="top"><a href="#select-3">select/3</a></td><td>Like <a href="#select-2"><code>select/2</code></a> but returns Limit objects at a time.</td></tr><tr><td valign="top"><a href="#select_count-1">select_count/1</a></td><td>Equivalent to <a href="#select_count-2"><tt>select_count(all, Pat)</tt></a>.</td></tr><tr><td valign="top"><a href="#select_count-2">select_count/2</a></td><td>Perform a select_count operation on the process registry.</td></tr><tr><td valign="top"><a href="#send-2">send/2</a></td><td>Sends a message to the process, or processes, corresponding to Key.</td></tr><tr><td valign="top"><a href="#set_env-5">set_env/5</a></td><td>Updates the cached value as well as underlying environment.</td></tr><tr><td valign="top"><a href="#set_value-2">set_value/2</a></td><td>Sets the value of the registeration entry given by Key.</td></tr><tr><td valign="top"><a href="#start_link-0">start_link/0</a></td><td>Starts the gproc server.</td></tr><tr><td valign="top"><a href="#table-0">table/0</a></td><td>Equivalent to <a href="#table-1"><tt>table({all, all})</tt></a>.</td></tr><tr><td valign="top"><a href="#table-1">table/1</a></td><td>Equivalent to <a href="#table-2"><tt>table(Context, [])</tt></a>.</td></tr><tr><td valign="top"><a href="#table-2">table/2</a></td><td>QLC table generator for the gproc registry.</td></tr><tr><td valign="top"><a href="#unreg-1">unreg/1</a></td><td>Unregister a name or property.</td></tr><tr><td valign="top"><a href="#unregister_name-1">unregister_name/1</a></td><td>Equivalent to <tt>unreg / 1</tt>.</td></tr><tr><td valign="top"><a href="#update_counter-2">update_counter/2</a></td><td>Updates the counter registered as Key for the current process.</td></tr><tr><td valign="top"><a href="#where-1">where/1</a></td><td>Returns the pid registered as Key.</td></tr><tr><td valign="top"><a href="#whereis_name-1">whereis_name/1</a></td><td>Equivalent to <tt>where / 1</tt>.</td></tr></table>
+to forget about the calling process.</td></tr><tr><td valign="top"><a href="#i-0">i/0</a></td><td>Similar to the built-in shell command <code>i()</code> but inserts information
+about names and properties registered in Gproc, where applicable.</td></tr><tr><td valign="top"><a href="#info-1">info/1</a></td><td>Similar to <code>process_info(Pid)</code> but with additional gproc info.</td></tr><tr><td valign="top"><a href="#info-2">info/2</a></td><td>Similar to process_info(Pid, Item), but with additional gproc info.</td></tr><tr><td valign="top"><a href="#last-1">last/1</a></td><td>Behaves as ets:last(Tab) for a given type of registration object.</td></tr><tr><td valign="top"><a href="#lookup_global_aggr_counter-1">lookup_global_aggr_counter/1</a></td><td>Lookup a global (unique) aggregated counter and returns its value.</td></tr><tr><td valign="top"><a href="#lookup_global_counters-1">lookup_global_counters/1</a></td><td>Look up all global (non-unique) instances of a given Counter.</td></tr><tr><td valign="top"><a href="#lookup_global_name-1">lookup_global_name/1</a></td><td>Lookup a global unique name.</td></tr><tr><td valign="top"><a href="#lookup_global_properties-1">lookup_global_properties/1</a></td><td>Look up all global (non-unique) instances of a given Property.</td></tr><tr><td valign="top"><a href="#lookup_local_aggr_counter-1">lookup_local_aggr_counter/1</a></td><td>Lookup a local (unique) aggregated counter and returns its value.</td></tr><tr><td valign="top"><a href="#lookup_local_counters-1">lookup_local_counters/1</a></td><td>Look up all local (non-unique) instances of a given Counter.</td></tr><tr><td valign="top"><a href="#lookup_local_name-1">lookup_local_name/1</a></td><td>Lookup a local unique name.</td></tr><tr><td valign="top"><a href="#lookup_local_properties-1">lookup_local_properties/1</a></td><td>Look up all local (non-unique) instances of a given Property.</td></tr><tr><td valign="top"><a href="#lookup_pid-1">lookup_pid/1</a></td><td>Lookup the Pid stored with a key.</td></tr><tr><td valign="top"><a href="#lookup_pids-1">lookup_pids/1</a></td><td>Returns a list of pids with the published key Key.</td></tr><tr><td valign="top"><a href="#lookup_value-1">lookup_value/1</a></td><td>Lookup the value stored with a key.</td></tr><tr><td valign="top"><a href="#lookup_values-1">lookup_values/1</a></td><td>Retrieve the <code>{Pid,Value}</code> pairs corresponding to Key.</td></tr><tr><td valign="top"><a href="#mreg-3">mreg/3</a></td><td>Register multiple {Key,Value} pairs of a given type and scope.</td></tr><tr><td valign="top"><a href="#munreg-3">munreg/3</a></td><td>Unregister multiple Key items of a given type and scope.</td></tr><tr><td valign="top"><a href="#nb_wait-1">nb_wait/1</a></td><td>Wait for a local name to be registered.</td></tr><tr><td valign="top"><a href="#next-2">next/2</a></td><td>Behaves as ets:next(Tab,Key) for a given type of registration object.</td></tr><tr><td valign="top"><a href="#prev-2">prev/2</a></td><td>Behaves as ets:prev(Tab,Key) for a given type of registration object.</td></tr><tr><td valign="top"><a href="#reg-1">reg/1</a></td><td>Equivalent to <a href="#reg-2"><tt>reg(Key, default(Key))</tt></a>.</td></tr><tr><td valign="top"><a href="#reg-2">reg/2</a></td><td>Register a name or property for the current process.</td></tr><tr><td valign="top"><a href="#select-1">select/1</a></td><td>Equivalent to <a href="#select-2"><tt>select(all, Pat)</tt></a>.</td></tr><tr><td valign="top"><a href="#select-2">select/2</a></td><td>Perform a select operation on the process registry.</td></tr><tr><td valign="top"><a href="#select-3">select/3</a></td><td>Like <a href="#select-2"><code>select/2</code></a> but returns Limit objects at a time.</td></tr><tr><td valign="top"><a href="#select_count-1">select_count/1</a></td><td>Equivalent to <a href="#select_count-2"><tt>select_count(all, Pat)</tt></a>.</td></tr><tr><td valign="top"><a href="#select_count-2">select_count/2</a></td><td>Perform a select_count operation on the process registry.</td></tr><tr><td valign="top"><a href="#send-2">send/2</a></td><td>Sends a message to the process, or processes, corresponding to Key.</td></tr><tr><td valign="top"><a href="#set_env-5">set_env/5</a></td><td>Updates the cached value as well as underlying environment.</td></tr><tr><td valign="top"><a href="#set_value-2">set_value/2</a></td><td>Sets the value of the registeration entry given by Key.</td></tr><tr><td valign="top"><a href="#start_link-0">start_link/0</a></td><td>Starts the gproc server.</td></tr><tr><td valign="top"><a href="#table-0">table/0</a></td><td>Equivalent to <a href="#table-1"><tt>table({all, all})</tt></a>.</td></tr><tr><td valign="top"><a href="#table-1">table/1</a></td><td>Equivalent to <a href="#table-2"><tt>table(Context, [])</tt></a>.</td></tr><tr><td valign="top"><a href="#table-2">table/2</a></td><td>QLC table generator for the gproc registry.</td></tr><tr><td valign="top"><a href="#unreg-1">unreg/1</a></td><td>Unregister a name or property.</td></tr><tr><td valign="top"><a href="#unregister_name-1">unregister_name/1</a></td><td>Equivalent to <tt>unreg / 1</tt>.</td></tr><tr><td valign="top"><a href="#update_counter-2">update_counter/2</a></td><td>Updates the counter registered as Key for the current process.</td></tr><tr><td valign="top"><a href="#where-1">where/1</a></td><td>Returns the pid registered as Key.</td></tr><tr><td valign="top"><a href="#whereis_name-1">whereis_name/1</a></td><td>Equivalent to <tt>where / 1</tt>.</td></tr></table>
 
 
 
@@ -559,7 +560,22 @@ Unregister all items of the calling process and inform gproc
 to forget about the calling process.
 
 This function is more efficient than letting gproc perform these
-cleanup operations.<a name="info-1"></a>
+cleanup operations.<a name="i-0"></a>
+
+<h3>i/0</h3>
+
+
+
+
+
+<pre>i() -> ok</pre>
+<br></br>
+
+
+
+
+Similar to the built-in shell command `i()` but inserts information
+about names and properties registered in Gproc, where applicable.<a name="info-1"></a>
 
 <h3>info/1</h3>
 

+ 40 - 0
doc/gproc_info.md

@@ -0,0 +1,40 @@
+Module gproc_info
+=================
+
+
+<h1>Module gproc_info</h1>
+
+* [Function Index](#index)
+* [Function Details](#functions)
+
+
+
+
+
+
+__Authors:__ Ulf Wiger ([`ulf.wiger@erlang-solutions.com`](mailto:ulf.wiger@erlang-solutions.com)).
+
+<h2><a name="index">Function Index</a></h2>
+
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#i-0">i/0</a></td><td></td></tr></table>
+
+
+
+
+<h2><a name="functions">Function Details</a></h2>
+
+
+<a name="i-0"></a>
+
+<h3>i/0</h3>
+
+
+
+
+
+<pre>i() -> ok</pre>
+<br></br>
+
+

+ 9 - 3
src/gproc.erl

@@ -81,6 +81,7 @@
          goodbye/0,
          send/2,
          info/1, info/2,
+	 i/0,
          select/1, select/2, select/3,
          select_count/1, select_count/2,
          first/1,
@@ -1153,7 +1154,7 @@ info(Pid, ?MODULE) ->
     {?MODULE, lists:zf(
                 fun(K) ->
                         try V = get_value(K, Pid),
-                            {true, {K,V}}
+			      {true, {K,V}}
                         catch
                             error:_ ->
                                 false
@@ -1162,8 +1163,13 @@ info(Pid, ?MODULE) ->
 info(Pid, I) ->
     process_info(Pid, I).
 
-
-
+%% @spec () -> ok
+%%
+%% @doc Similar to the built-in shell command `i()' but inserts information
+%% about names and properties registered in Gproc, where applicable.
+%% @end
+i() ->
+    gproc_info:i().
 
 %%% ==========================================================
 

+ 163 - 0
src/gproc_info.erl

@@ -0,0 +1,163 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% @author Ulf Wiger <ulf.wiger@erlang-solutions.com>
+%%
+-module(gproc_info).
+
+-export([i/0]).
+
+-import(lists, [foldl/3]).
+-import(io, [format/2]).
+
+%% c:i() extended with gproc info
+-spec i() -> 'ok'.
+
+i() -> i(processes()).
+
+-spec i([pid()]) -> 'ok'.
+
+i(Ps) ->
+    i(Ps, length(Ps)).
+
+-spec i([pid()], non_neg_integer()) -> 'ok'.
+
+i(Ps, N) when N =< 100 ->
+    iformat("Pid", "Initial Call", "Heap", "Reds",
+            "Msgs"),
+    iformat("Registered", "Current Function", "Stack", "",
+            ""),
+    {R,M,H,S} = foldl(fun(Pid, {R0,M0,H0,S0}) ->
+                              {A,B,C,D} = display_info(Pid),
+                              {R0+A,M0+B,H0+C,S0+D}
+                      end, {0,0,0,0}, Ps),
+    iformat("Total", "", w(H), w(R), w(M)),
+    iformat("", "", w(S), "", "");
+i(Ps, N) ->
+    iformat("Pid", "Initial Call", "Heap", "Reds",
+            "Msgs"),
+    iformat("Registered", "Current Function", "Stack", "",
+            ""),
+    paged_i(Ps, {0,0,0,0}, N, 50).
+
+paged_i([], {R,M,H,S}, _, _) ->
+    iformat("Total", "", w(H), w(R), w(M)),
+    iformat("", "", w(S), "", "");
+paged_i(Ps, Acc, N, Page) ->
+    {Pids, Rest, N1} =
+        if N > Page ->
+                {L1,L2} = lists:split(Page, Ps),
+                {L1,L2,N-Page};
+           true ->
+                {Ps, [], 0}
+        end,
+    NewAcc = foldl(fun(Pid, {R,M,H,S}) ->
+                           {A,B,C,D} = display_info(Pid),
+                           {R+A,M+B,H+C,S+D}
+                   end, Acc, Pids),
+    case Rest of
+        [_|_] ->
+            choice(fun() -> paged_i(Rest, NewAcc, N1, Page) end);
+        [] ->
+            paged_i([], NewAcc, 0, Page)
+    end.
+
+choice(F) ->
+    case get_line('(c)ontinue (q)uit -->', "c\n") of
+        "c\n" ->
+            F();
+        "q\n" ->
+            quit;
+        _ ->
+            choice(F)
+    end.
+
+
+iformat(A1, A2, A3, A4, A5) ->
+    format("~-21s ~-33s ~8s ~8s ~4s~n", [A1,A2,A3,A4,A5]).
+
+get_line(P, Default) ->
+    case io:get_line(P) of
+        "\n" ->
+            Default;
+        L ->
+            L
+    end.
+
+w(X) ->
+    io_lib:write(X).
+
+w(X, L) ->
+    S = lists:flatten(io_lib:format("~w", [X])),
+    case length(S) of
+	Len when Len > L ->
+	    lists:sublist(S, 1, L-3) ++ "...";
+	_ ->
+	    S
+    end.
+
+display_info(Pid) ->
+    Res = c:display_info(Pid),
+    {gproc, GI} = gproc:info(Pid, gproc),
+    case GI of
+	[] ->
+	    skip;
+	_ ->
+	    display_gproc_info("Gproc: ", [{n, N,S,V} || {{n,S,N},V} <- GI]),
+	    display_gproc_info("       ", [{p, N,S,V} || {{p,S,N},V} <- GI])
+    end,
+    Res.
+
+display_gproc_info(_, []) ->
+    skip;
+display_gproc_info(Hdr, [H|T] = Entries) ->
+    L = 3 + length(Hdr),
+    Max = 78 - L - 8,
+    {Ck, Cv} = info_cols(Entries, Max),
+    Fmt = fun(I, {Type, K, Sc, V}) ->
+		  if Cv == 0 ->
+			  io_lib:format(
+			    I ++ "~w,~w: ~-" ++ i2l(Ck)
+			    ++ "s ~n", [Type, Sc, w(K, Ck)]);
+		     true ->
+			  io_lib:format(
+			    I ++ "~w,~w: ~-" ++ i2l(Ck)
+			    ++ "s | ~-" ++ i2l(Cv) ++ "s~n",
+			    [Type, Sc, w(K, Ck), w(V, Cv)])
+		  end
+	  end,
+    format("   ~s~s", [Hdr, Fmt("", H)]),
+    Indent = lists:duplicate(L, $\s),
+    lists:foreach(
+      fun(X) ->
+	      io:format(Fmt(Indent, X))
+      end, T).
+
+info_cols(L, Max) ->
+    case [V || {_,_,_,V} <- L, V =/= undefined] of
+	[] ->
+	    {Max, 0};
+	_ ->
+	    KMax = lists:max([w_length(K) || {_,K,_,_} <- L]),
+	    Ck = erlang:min(KMax, round(Max*2/3)),
+	    {Ck, Max - Ck}
+    end.
+
+w_length(Term) ->
+    lists:flatlength(io_lib:format("~w", [Term])).
+
+
+i2l(I) ->
+    integer_to_list(I).