View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  1999-2019, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(prolog_statistics,
   38          [ statistics/0,
   39            statistics/1,               % -Stats
   40            thread_statistics/2,        % ?Thread, -Stats
   41            time/1,                     % :Goal
   42            profile/1,                  % :Goal
   43            profile/2,                  % :Goal, +Options
   44            show_profile/1,             % +Options
   45            profile_data/1,             % -Dict
   46            profile_procedure_data/2    % :PI, -Data
   47          ]).   48:- autoload(library(error),[must_be/2]).   49:- autoload(library(lists),[append/3,member/2]).   50:- autoload(library(option),[option/3]).   51:- autoload(library(pairs),[map_list_to_pairs/3,pairs_values/2]).   52:- autoload(library(prolog_code),
   53	    [predicate_sort_key/2,predicate_label/2]).   54
   55
   56:- set_prolog_flag(generate_debug_info, false).   57
   58:- meta_predicate
   59    time(0),
   60    profile(0),
   61    profile(0, +),
   62    profile_procedure_data(:, -).

Get information about resource usage

This library provides predicates to obtain information about resource usage by your program. The predicates of this library are for human use at the toplevel: information is printed. All predicates obtain their information using public low-level primitives. These primitives can be use to obtain selective statistics during execution. */

 statistics is det
Print information about resource usage using print_message/2.
See also
- All statistics printed are obtained through statistics/2.
   79statistics :-
   80    phrase(collect_stats, Stats),
   81    print_message(information, statistics(Stats)).
 statistics(-Stats:dict) is det
Stats is a dict representing the same information as statistics/0. This convience function is primarily intended to pass statistical information to e.g., a web client. Time critical code that wishes to collect statistics typically only need a small subset and should use statistics/2 to obtain exactly the data they need.
   92statistics(Stats) :-
   93    phrase(collect_stats, [CoreStats|StatList]),
   94    dict_pairs(CoreStats, _, CorePairs),
   95    map_list_to_pairs(dict_key, StatList, ExtraPairs),
   96    append(CorePairs, ExtraPairs, Pairs),
   97    dict_pairs(Stats, statistics, Pairs).
   98
   99dict_key(Dict, Key) :-
  100    gc{type:atom} :< Dict,
  101    !,
  102    Key = agc.
  103dict_key(Dict, Key) :-
  104    gc{type:clause} :< Dict,
  105    !,
  106    Key = cgc.
  107dict_key(Dict, Key) :-
  108    is_dict(Dict, Key).
  109
  110collect_stats -->
  111    core_statistics,
  112    gc_statistics,
  113    agc_statistics,
  114    cgc_statistics,
  115    shift_statistics,
  116    thread_counts,
  117    engine_counts.
  118
  119core_statistics -->
  120    { statistics(process_cputime, Cputime),
  121      statistics(process_epoch, Epoch),
  122      statistics(inferences, Inferences),
  123      statistics(atoms, Atoms),
  124      statistics(functors, Functors),
  125      statistics(predicates, Predicates),
  126      statistics(modules, Modules),
  127      statistics(codes, Codes),
  128      thread_self(Me),
  129      thread_stack_statistics(Me, Stacks)
  130    },
  131    [ core{ time:time{cpu:Cputime, inferences:Inferences, epoch:Epoch},
  132            data:counts{atoms:Atoms, functors:Functors,
  133                        predicates:Predicates, modules:Modules,
  134                        vm_codes:Codes},
  135            stacks:Stacks
  136          }
  137    ].
  138
  139:- if(\+current_predicate(thread_statistics/3)).  140thread_statistics(_Thread, Key, Value) :-       % single threaded version
  141    statistics(Key, Value).
  142:- endif.  143
  144thread_stack_statistics(Thread,
  145                  stacks{local:stack{name:local,
  146                                     allocated:Local,
  147                                     usage:LocalUsed},
  148                         global:stack{name:global,
  149                                      allocated:Global,
  150                                      usage:GlobalUsed},
  151                         trail:stack{name:trail,
  152                                     allocated:Trail,
  153                                     usage:TrailUsed},
  154                         total:stack{name:stacks,
  155                                     limit:StackLimit,
  156                                     allocated:StackAllocated,
  157                                     usage:StackUsed}
  158                        }) :-
  159    thread_statistics(Thread, trail,       Trail),
  160    thread_statistics(Thread, trailused,   TrailUsed),
  161    thread_statistics(Thread, local,       Local),
  162    thread_statistics(Thread, localused,   LocalUsed),
  163    thread_statistics(Thread, global,      Global),
  164    thread_statistics(Thread, globalused,  GlobalUsed),
  165    thread_statistics(Thread, stack_limit, StackLimit), %
  166    StackUsed is LocalUsed+GlobalUsed+TrailUsed,
  167    StackAllocated is Local+Global+Trail.
  168
  169gc_statistics -->
  170    { statistics(collections, Collections),
  171      Collections > 0,
  172      !,
  173      statistics(collected, Collected),
  174      statistics(gctime, GcTime)
  175    },
  176    [ gc{type:stack, unit:byte,
  177         count:Collections, time:GcTime, gained:Collected } ].
  178gc_statistics --> [].
  179
  180agc_statistics -->
  181    { catch(statistics(agc, Agc), _, fail),
  182      Agc > 0,
  183      !,
  184      statistics(agc_gained, Gained),
  185      statistics(agc_time, Time)
  186    },
  187    [ gc{type:atom, unit:atom,
  188         count:Agc, time:Time, gained:Gained} ].
  189agc_statistics --> [].
  190
  191cgc_statistics -->
  192    { catch(statistics(cgc, Cgc), _, fail),
  193      Cgc > 0,
  194      !,
  195      statistics(cgc_gained, Gained),
  196      statistics(cgc_time, Time)
  197    },
  198    [ gc{type:clause, unit:clause,
  199         count:Cgc, time:Time, gained:Gained} ].
  200cgc_statistics --> [].
  201
  202shift_statistics -->
  203    { statistics(local_shifts, LS),
  204      statistics(global_shifts, GS),
  205      statistics(trail_shifts, TS),
  206      (   LS > 0
  207      ;   GS > 0
  208      ;   TS > 0
  209      ),
  210      !,
  211      statistics(shift_time, Time)
  212    },
  213    [ shift{local:LS, global:GS, trail:TS, time:Time} ].
  214shift_statistics --> [].
  215
  216thread_counts -->
  217    { current_prolog_flag(threads, true),
  218      statistics(threads, Active),
  219      statistics(threads_created, Created),
  220      Created > 1,
  221      !,
  222      statistics(thread_cputime, CpuTime),
  223      Finished is Created - Active
  224    },
  225    [ thread{count:Active, finished:Finished, time:CpuTime} ].
  226thread_counts --> [].
  227
  228engine_counts -->
  229    { current_prolog_flag(threads, true),
  230      statistics(engines, Active),
  231      statistics(engines_created, Created),
  232      Created > 0,
  233      !,
  234      Finished is Created - Active
  235    },
  236    [ engine{count:Active, finished:Finished} ].
  237engine_counts --> [].
 thread_statistics(?Thread, -Stats:dict) is nondet
Obtain statistical information about a single thread. Fails silently of the Thread is no longer alive.
Arguments:
Stats- is a dict containing status, time and stack-size information about Thread.
  248thread_statistics(Thread, Stats) :-
  249    thread_property(Thread, status(Status)),
  250    human_thread_id(Thread, Id),
  251    Error = error(_,_),
  252    (   catch(thread_stats(Thread, Stacks, Time), Error, fail)
  253    ->  Stats = thread{id:Id,
  254                       status:Status,
  255                       time:Time,
  256                       stacks:Stacks}
  257    ;   Stats = thread{id:Thread,
  258                       status:Status}
  259    ).
  260
  261human_thread_id(Thread, Id) :-
  262    atom(Thread),
  263    !,
  264    Id = Thread.
  265human_thread_id(Thread, Id) :-
  266    thread_property(Thread, id(Id)).
  267
  268thread_stats(Thread, Stacks,
  269             time{cpu:CpuTime,
  270                  inferences:Inferences,
  271                  epoch:Epoch
  272                 }) :-
  273    thread_statistics(Thread, cputime, CpuTime),
  274    thread_statistics(Thread, inferences, Inferences),
  275    thread_statistics(Thread, epoch, Epoch),
  276    thread_stack_statistics(Thread, Stacks).
 time(:Goal) is nondet
Execute Goal, reporting statistics to the user. If Goal succeeds non-deterministically, retrying reports the statistics for providing the next answer.

Statistics are retrieved using thread_statistics/3 on the calling thread. Note that not all systems support thread-specific CPU time. Notable, this is lacking on MacOS X.

See also
- statistics/2 for obtaining statistics in your program and understanding the reported values.
bug
- Inference statistics are often a few off.
  293time(Goal) :-
  294    time_state(State0),
  295    (   call_cleanup(catch(Goal, E, (report(State0,10), throw(E))),
  296                     Det = true),
  297        time_true(State0),
  298        (   Det == true
  299        ->  !
  300        ;   true
  301        )
  302    ;   report(State0, 11),
  303        fail
  304    ).
  305
  306report(t(OldWall, OldTime, OldInferences), Sub) :-
  307    time_state(t(NewWall, NewTime, NewInferences)),
  308    UsedTime is NewTime - OldTime,
  309    UsedInf  is NewInferences - OldInferences - Sub,
  310    Wall     is NewWall - OldWall,
  311    (   UsedTime =:= 0
  312    ->  Lips = 'Infinite'
  313    ;   Lips is integer(UsedInf / UsedTime)
  314    ),
  315    print_message(information, time(UsedInf, UsedTime, Wall, Lips)).
  316
  317time_state(t(Wall, Time, Inferences)) :-
  318    get_time(Wall),
  319    statistics(cputime, Time),
  320    statistics(inferences, Inferences).
  321
  322time_true(State0) :-
  323    report(State0, 12).             % leave choice-point
  324time_true(State) :-
  325    get_time(Wall),
  326    statistics(cputime, Time),
  327    statistics(inferences, Inferences0),
  328    plus(Inferences0, -3, Inferences),
  329    nb_setarg(1, State, Wall),
  330    nb_setarg(2, State, Time),
  331    nb_setarg(3, State, Inferences),
  332    fail.
  333
  334
  335                 /*******************************
  336                 *     EXECUTION PROFILING      *
  337                 *******************************/
  338
  339/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  340This module provides a simple backward compatibility frontend on the new
  341(in version 5.1.10) execution profiler  with  a   hook  to  the  new GUI
  342visualiser for profiling results defined in library('swi/pce_profile').
  343
  344Later we will add a proper textual report-generator.
  345- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  346
  347:- multifile
  348    prolog:show_profile_hook/1.
 profile(:Goal)
 profile(:Goal, +Options)
Run Goal under the execution profiler. Defined options are:
time(Which)
Profile cpu or wall time. The default is CPU time.
top(N)
When generating a textual report, show the top N predicates.
cumulative(Bool)
If true (default false), show cumulative output in a textual report.
  363profile(Goal) :-
  364    profile(Goal, []).
  365
  366profile(Goal0, Options) :-
  367    option(time(Which), Options, cpu),
  368    time_name(Which, How),
  369    expand_goal(Goal0, Goal),
  370    call_cleanup('$profile'(Goal, How),
  371                 prolog_statistics:show_profile(Options)).
  372
  373time_name(cpu,      cputime)  :- !.
  374time_name(wall,     walltime) :- !.
  375time_name(cputime,  cputime)  :- !.
  376time_name(walltime, walltime) :- !.
  377time_name(Time, _) :-
  378    must_be(oneof([cpu,wall]), Time).
 show_profile(+Options)
Display last collected profiling data. Options are
top(N)
When generating a textual report, show the top N predicates.
cumulative(Bool)
If true (default false), show cumulative output in a textual report.
  390show_profile(N) :-
  391    integer(N),
  392    !,
  393    show_profile([top(N)]).
  394show_profile(Options) :-
  395    profiler(Old, false),
  396    show_profile_(Options),
  397    profiler(_, Old).
  398
  399show_profile_(Options) :-
  400    prolog:show_profile_hook(Options),
  401    !.
  402show_profile_(Options) :-
  403    prof_statistics(Stat),
  404    sort_on(Options, SortKey),
  405    findall(Node, profile_procedure_data(_:_, Node), Nodes),
  406    sort_prof_nodes(SortKey, Nodes, Sorted),
  407    format('~`=t~69|~n'),
  408    format('Total time: ~3f seconds~n', [Stat.time]),
  409    format('~`=t~69|~n'),
  410    format('~w~t~w =~45|~t~w~60|~t~w~69|~n',
  411           [ 'Predicate', 'Box Entries', 'Calls+Redos', 'Time'
  412           ]),
  413    format('~`=t~69|~n'),
  414    option(top(N), Options, 25),
  415    show_plain(Sorted, N, Stat, SortKey).
  416
  417sort_on(Options, ticks_self) :-
  418    option(cumulative(false), Options, false),
  419    !.
  420sort_on(_, ticks).
  421
  422sort_prof_nodes(ticks, Nodes, Sorted) :-
  423    !,
  424    map_list_to_pairs(key_ticks, Nodes, Keyed),
  425    sort(1, >=, Keyed, KeySorted),
  426    pairs_values(KeySorted, Sorted).
  427sort_prof_nodes(Key, Nodes, Sorted) :-
  428    sort(Key, >=, Nodes, Sorted).
  429
  430key_ticks(Node, Ticks) :-
  431    Ticks is Node.ticks_self + Node.ticks_siblings.
  432
  433show_plain([], _, _, _).
  434show_plain(_, 0, _, _) :- !.
  435show_plain([H|T], N, Stat, Key) :-
  436    show_plain(H, Stat, Key),
  437    N2 is N - 1,
  438    show_plain(T, N2, Stat, Key).
  439
  440show_plain(Node, Stat, Key) :-
  441    value(label,                       Node, Pred),
  442    value(call,                        Node, Call),
  443    value(redo,                        Node, Redo),
  444    value(time(Key, percentage, Stat), Node, Percent),
  445    IntPercent is round(Percent*10),
  446    Entry is Call + Redo,
  447    format('~w~t~D =~45|~t~D+~55|~D ~t~1d%~69|~n',
  448           [Pred, Entry, Call, Redo, IntPercent]).
  449
  450
  451                 /*******************************
  452                 *         DATA GATHERING       *
  453                 *******************************/
 profile_data(-Data) is det
Gather all relevant data from profiler. This predicate may be called while profiling is active in which case it is suspended while collecting the data. Data is a dict providing the following fields:
summary:Dict
Overall statistics providing
  • samples:Count: Times the statistical profiler was called
  • ticks:Count Virtual ticks during profiling
  • accounting:Count Tick spent on accounting
  • time:Seconds Total time sampled
  • nodes:Count Nodes in the call graph.
nodes
List of nodes. Each node provides:
  • predicate:PredicateIndicator
  • ticks_self:Count
  • ticks_siblings:Count
  • call:Count
  • redo:Count
  • exit:Count
  • callers:list_of(Relative)
  • callees:list_of(Relative)

Relative is a term of the shape below that represents a caller or callee. Future versions are likely to use a dict instead.

node(PredicateIndicator, CycleID, Ticks, TicksSiblings,
     Calls, Redos, Exits)
  490profile_data(Data) :-
  491    setup_call_cleanup(
  492        profiler(Old, false),
  493        profile_data_(Data),
  494        profiler(_, Old)).
  495
  496profile_data_(profile{summary:Summary, nodes:Nodes}) :-
  497    prof_statistics(Summary),
  498    findall(Node, profile_procedure_data(_:_, Node), Nodes).
 prof_statistics(-Node) is det
Get overall statistics
Arguments:
Node- term of the format prof(Ticks, Account, Time, Nodes)
  506prof_statistics(summary{samples:Samples, ticks:Ticks,
  507                        accounting:Account, time:Time, nodes:Nodes}) :-
  508    '$prof_statistics'(Samples, Ticks, Account, Time, Nodes).
 profile_procedure_data(?Pred, -Data:dict) is nondet
Collect data for Pred. If Pred is unbound data for each predicate that has profile data available is returned. Data is described in profile_data/1 as an element of the nodes key.
  516profile_procedure_data(Pred, Node) :-
  517    Node = node{predicate:Pred,
  518                ticks_self:TicksSelf, ticks_siblings:TicksSiblings,
  519                call:Call, redo:Redo, exit:Exit,
  520                callers:Parents, callees:Siblings},
  521    (   specified(Pred)
  522    ->  true
  523    ;   profiled_predicates(Preds),
  524        member(Pred, Preds)
  525    ),
  526    '$prof_procedure_data'(Pred,
  527                           TicksSelf, TicksSiblings,
  528                           Call, Redo, Exit,
  529                           Parents, Siblings).
  530
  531specified(Module:Head) :-
  532    atom(Module),
  533    callable(Head).
  534
  535profiled_predicates(Preds) :-
  536    setof(Pred, prof_impl(Pred), Preds).
  537
  538prof_impl(Pred) :-
  539    prof_node_id(Node),
  540    node_id_pred(Node, Pred).
  541
  542prof_node_id(N) :-
  543    prof_node_id_below(N, -).
  544
  545prof_node_id_below(N, Root) :-
  546    '$prof_sibling_of'(N0, Root),
  547    (   N = N0
  548    ;   prof_node_id_below(N, N0)
  549    ).
  550
  551node_id_pred(Node, Pred) :-
  552    '$prof_node'(Node, Pred, _Calls, _Redos, _Exits, _Recur,
  553                 _Ticks, _SiblingTicks).
 value(+Key, +NodeData, -Value)
Obtain possible computed attributes from NodeData.
  559value(name, Data, Name) :-
  560    !,
  561    predicate_sort_key(Data.predicate, Name).
  562value(label, Data, Label) :-
  563    !,
  564    predicate_label(Data.predicate, Label).
  565value(ticks, Data, Ticks) :-
  566    !,
  567    Ticks is Data.ticks_self + Data.ticks_siblings.
  568value(time(Key, percentage, Stat), Data, Percent) :-
  569    !,
  570    value(Key, Data, Ticks),
  571    Total = Stat.ticks,
  572    Account = Stat.accounting,
  573    (   Total-Account > 0
  574    ->  Percent is 100 * (Ticks/(Total-Account))
  575    ;   Percent is 0.0
  576    ).
  577value(Name, Data, Value) :-
  578    Value = Data.Name.
  579
  580
  581                 /*******************************
  582                 *            MESSAGES          *
  583                 *******************************/
  584
  585:- multifile
  586    prolog:message/3.  587
  588% NOTE: The code below uses get_dict/3 rather than the functional
  589% notation to make this code work with `swipl --traditional`
  590
  591prolog:message(time(UsedInf, UsedTime, Wall, Lips)) -->
  592    [ '~D inferences, ~3f CPU in ~3f seconds (~w% CPU, ~w Lips)'-
  593      [UsedInf, UsedTime, Wall, Perc, Lips] ],
  594    {   Wall > 0
  595    ->  Perc is round(100*UsedTime/Wall)
  596    ;   Perc = ?
  597    }.
  598prolog:message(statistics(List)) -->
  599    msg_statistics(List).
  600
  601msg_statistics([]) --> [].
  602msg_statistics([H|T]) -->
  603    { is_dict(H, Tag) },
  604    msg_statistics(Tag, H),
  605    (   { T == [] }
  606    ->  []
  607    ;   [nl], msg_statistics(T)
  608    ).
  609
  610msg_statistics(core, S) -->
  611    { get_dict(time, S, Time),
  612      get_dict(data, S, Data),
  613      get_dict(stacks, S, Stacks)
  614    },
  615    time_stats(Time), [nl],
  616    data_stats(Data), [nl,nl],
  617    stacks_stats(Stacks).
  618msg_statistics(gc, S) -->
  619    {   (   get_dict(type, S, stack)
  620        ->  Label = ''
  621        ;   get_dict(type, S, Type),
  622            string_concat(Type, " ", Label)
  623        ),
  624        get_dict(count, S, Count),
  625        get_dict(gained, S, Gained),
  626        get_dict(unit, S, Unit),
  627        get_dict(time, S, Time)
  628    },
  629    [ '~D ~wgarbage collections gained ~D ~ws in ~3f seconds.'-
  630      [ Count, Label, Gained, Unit, Time]
  631    ].
  632msg_statistics(shift, S) -->
  633    { get_dict(local, S, Local),
  634      get_dict(global, S, Global),
  635      get_dict(trail, S, Trail),
  636      get_dict(time, S, Time)
  637    },
  638    [ 'Stack shifts: ~D local, ~D global, ~D trail in ~3f seconds'-
  639      [ Local, Global, Trail, Time ]
  640    ].
  641msg_statistics(thread, S) -->
  642    { get_dict(count, S, Count),
  643      get_dict(finished, S, Finished),
  644      get_dict(time, S, Time)
  645    },
  646    [ '~D threads, ~D finished threads used ~3f seconds'-
  647      [Count, Finished, Time]
  648    ].
  649msg_statistics(engine, S) -->
  650    { get_dict(count, S, Count),
  651      get_dict(finished, S, Finished)
  652    },
  653    [ '~D engines, ~D finished engines'-
  654      [Count, Finished]
  655    ].
  656
  657time_stats(T) -->
  658    { get_dict(epoch, T, Epoch),
  659      format_time(string(EpochS), '%+', Epoch),
  660      get_dict(cpu, T, CPU),
  661      get_dict(inferences, T, Inferences)
  662    },
  663    [ 'Started at ~s'-[EpochS], nl,
  664      '~3f seconds cpu time for ~D inferences'-
  665      [ CPU, Inferences ]
  666    ].
  667data_stats(C) -->
  668    { get_dict(atoms, C, Atoms),
  669      get_dict(functors, C, Functors),
  670      get_dict(predicates, C, Predicates),
  671      get_dict(modules, C, Modules),
  672      get_dict(vm_codes, C, VMCodes)
  673    },
  674    [ '~D atoms, ~D functors, ~D predicates, ~D modules, ~D VM-codes'-
  675      [ Atoms, Functors, Predicates, Modules, VMCodes]
  676    ].
  677stacks_stats(S) -->
  678    { get_dict(local, S, Local),
  679      get_dict(global, S, Global),
  680      get_dict(trail, S, Trail),
  681      get_dict(total, S, Total)
  682    },
  683    [ '~|~tLimit~25+~tAllocated~12+~tIn use~12+'-[], nl ],
  684    stack_stats('Local',  Local),  [nl],
  685    stack_stats('Global', Global), [nl],
  686    stack_stats('Trail',  Trail),  [nl],
  687    stack_stats('Total',  Total),  [nl].
  688
  689stack_stats('Total', S) -->
  690    { dict_human_bytes(limit,     S, Limit),
  691      dict_human_bytes(allocated, S, Allocated),
  692      dict_human_bytes(usage,     S, Usage)
  693    },
  694    !,
  695    [ '~|~tTotal:~13+~t~s~12+ ~t~s~12+ ~t~s~12+'-
  696      [Limit, Allocated, Usage]
  697    ].
  698stack_stats(Stack, S) -->
  699    { dict_human_bytes(allocated, S, Allocated),
  700      dict_human_bytes(usage,     S, Usage)
  701    },
  702    [ '~|~w ~tstack:~13+~t~w~12+ ~t~s~12+ ~t~s~12+'-
  703      [Stack, -, Allocated, Usage]
  704    ].
  705
  706dict_human_bytes(Key, Dict, String) :-
  707    get_dict(Key, Dict, Bytes),
  708    human_bytes(Bytes, String).
  709
  710human_bytes(Bytes, String) :-
  711    Bytes < 20_000,
  712    !,
  713    format(string(String), '~D  b', [Bytes]).
  714human_bytes(Bytes, String) :-
  715    Bytes < 20_000_000,
  716    !,
  717    Kb is (Bytes+512) // 1024,
  718    format(string(String), '~D Kb', [Kb]).
  719human_bytes(Bytes, String) :-
  720    Bytes < 20_000_000_000,
  721    !,
  722    Mb is (Bytes+512*1024) // (1024*1024),
  723    format(string(String), '~D Mb', [Mb]).
  724human_bytes(Bytes, String) :-
  725    Gb is (Bytes+512*1024*1024) // (1024*1024*1024),
  726    format(string(String), '~D Gb', [Gb]).
  727
  728
  729:- multifile sandbox:safe_primitive/1.  730
  731sandbox:safe_primitive(prolog_statistics:statistics(_)).
  732sandbox:safe_primitive(prolog_statistics:statistics).
  733sandbox:safe_meta_predicate(prolog_statistics:profile/1).
  734sandbox:safe_meta_predicate(prolog_statistics:profile/2)