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:- use_module(library(lists)).   49:- use_module(library(pairs)).   50:- use_module(library(option)).   51:- use_module(library(error)).   52:- use_module(library(prolog_code)).   53:- set_prolog_flag(generate_debug_info, false).   54
   55:- meta_predicate
   56    time(0),
   57    profile(0),
   58    profile(0, +),
   59    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.
   76statistics :-
   77    phrase(collect_stats, Stats),
   78    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.
   89statistics(Stats) :-
   90    phrase(collect_stats, [CoreStats|StatList]),
   91    dict_pairs(CoreStats, _, CorePairs),
   92    map_list_to_pairs(dict_key, StatList, ExtraPairs),
   93    append(CorePairs, ExtraPairs, Pairs),
   94    dict_pairs(Stats, statistics, Pairs).
   95
   96dict_key(Dict, Key) :-
   97    gc{type:atom} :< Dict,
   98    !,
   99    Key = agc.
  100dict_key(Dict, Key) :-
  101    gc{type:clause} :< Dict,
  102    !,
  103    Key = cgc.
  104dict_key(Dict, Key) :-
  105    is_dict(Dict, Key).
  106
  107collect_stats -->
  108    core_statistics,
  109    gc_statistics,
  110    agc_statistics,
  111    cgc_statistics,
  112    shift_statistics,
  113    thread_counts,
  114    engine_counts.
  115
  116core_statistics -->
  117    { statistics(process_cputime, Cputime),
  118      statistics(process_epoch, Epoch),
  119      statistics(inferences, Inferences),
  120      statistics(atoms, Atoms),
  121      statistics(functors, Functors),
  122      statistics(predicates, Predicates),
  123      statistics(modules, Modules),
  124      statistics(codes, Codes),
  125      thread_self(Me),
  126      thread_stack_statistics(Me, Stacks)
  127    },
  128    [ core{ time:time{cpu:Cputime, inferences:Inferences, epoch:Epoch},
  129            data:counts{atoms:Atoms, functors:Functors,
  130                        predicates:Predicates, modules:Modules,
  131                        vm_codes:Codes},
  132            stacks:Stacks
  133          }
  134    ].
  135
  136:- if(\+current_predicate(thread_statistics/3)).  137thread_statistics(_Thread, Key, Value) :-       % single threaded version
  138    statistics(Key, Value).
  139:- endif.  140
  141thread_stack_statistics(Thread,
  142                  stacks{local:stack{name:local,
  143                                     allocated:Local,
  144                                     usage:LocalUsed},
  145                         global:stack{name:global,
  146                                      allocated:Global,
  147                                      usage:GlobalUsed},
  148                         trail:stack{name:trail,
  149                                     allocated:Trail,
  150                                     usage:TrailUsed},
  151                         total:stack{name:stacks,
  152                                     limit:StackLimit,
  153                                     allocated:StackAllocated,
  154                                     usage:StackUsed}
  155                        }) :-
  156    thread_statistics(Thread, trail,       Trail),
  157    thread_statistics(Thread, trailused,   TrailUsed),
  158    thread_statistics(Thread, local,       Local),
  159    thread_statistics(Thread, localused,   LocalUsed),
  160    thread_statistics(Thread, global,      Global),
  161    thread_statistics(Thread, globalused,  GlobalUsed),
  162    thread_statistics(Thread, stack_limit, StackLimit), %
  163    StackUsed is LocalUsed+GlobalUsed+TrailUsed,
  164    StackAllocated is Local+Global+Trail.
  165
  166gc_statistics -->
  167    { statistics(collections, Collections),
  168      Collections > 0,
  169      !,
  170      statistics(collected, Collected),
  171      statistics(gctime, GcTime)
  172    },
  173    [ gc{type:stack, unit:byte,
  174         count:Collections, time:GcTime, gained:Collected } ].
  175gc_statistics --> [].
  176
  177agc_statistics -->
  178    { catch(statistics(agc, Agc), _, fail),
  179      Agc > 0,
  180      !,
  181      statistics(agc_gained, Gained),
  182      statistics(agc_time, Time)
  183    },
  184    [ gc{type:atom, unit:atom,
  185         count:Agc, time:Time, gained:Gained} ].
  186agc_statistics --> [].
  187
  188cgc_statistics -->
  189    { catch(statistics(cgc, Cgc), _, fail),
  190      Cgc > 0,
  191      !,
  192      statistics(cgc_gained, Gained),
  193      statistics(cgc_time, Time)
  194    },
  195    [ gc{type:clause, unit:clause,
  196         count:Cgc, time:Time, gained:Gained} ].
  197cgc_statistics --> [].
  198
  199shift_statistics -->
  200    { statistics(local_shifts, LS),
  201      statistics(global_shifts, GS),
  202      statistics(trail_shifts, TS),
  203      (   LS > 0
  204      ;   GS > 0
  205      ;   TS > 0
  206      ),
  207      !,
  208      statistics(shift_time, Time)
  209    },
  210    [ shift{local:LS, global:GS, trail:TS, time:Time} ].
  211shift_statistics --> [].
  212
  213thread_counts -->
  214    { current_prolog_flag(threads, true),
  215      statistics(threads, Active),
  216      statistics(threads_created, Created),
  217      Created > 1,
  218      !,
  219      statistics(thread_cputime, CpuTime),
  220      Finished is Created - Active
  221    },
  222    [ thread{count:Active, finished:Finished, time:CpuTime} ].
  223thread_counts --> [].
  224
  225engine_counts -->
  226    { current_prolog_flag(threads, true),
  227      statistics(engines, Active),
  228      statistics(engines_created, Created),
  229      Created > 0,
  230      !,
  231      Finished is Created - Active
  232    },
  233    [ engine{count:Active, finished:Finished} ].
  234engine_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.
  245thread_statistics(Thread, Stats) :-
  246    thread_property(Thread, status(Status)),
  247    human_thread_id(Thread, Id),
  248    Error = error(_,_),
  249    (   catch(thread_stats(Thread, Stacks, Time), Error, fail)
  250    ->  Stats = thread{id:Id,
  251                       status:Status,
  252                       time:Time,
  253                       stacks:Stacks}
  254    ;   Stats = thread{id:Thread,
  255                       status:Status}
  256    ).
  257
  258human_thread_id(Thread, Id) :-
  259    atom(Thread),
  260    !,
  261    Id = Thread.
  262human_thread_id(Thread, Id) :-
  263    thread_property(Thread, id(Id)).
  264
  265thread_stats(Thread, Stacks,
  266             time{cpu:CpuTime,
  267                  inferences:Inferences,
  268                  epoch:Epoch
  269                 }) :-
  270    thread_statistics(Thread, cputime, CpuTime),
  271    thread_statistics(Thread, inferences, Inferences),
  272    thread_statistics(Thread, epoch, Epoch),
  273    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.
  290time(Goal) :-
  291    time_state(State0),
  292    (   call_cleanup(catch(Goal, E, (report(State0,10), throw(E))),
  293                     Det = true),
  294        time_true(State0),
  295        (   Det == true
  296        ->  !
  297        ;   true
  298        )
  299    ;   report(State0, 11),
  300        fail
  301    ).
  302
  303report(t(OldWall, OldTime, OldInferences), Sub) :-
  304    time_state(t(NewWall, NewTime, NewInferences)),
  305    UsedTime is NewTime - OldTime,
  306    UsedInf  is NewInferences - OldInferences - Sub,
  307    Wall     is NewWall - OldWall,
  308    (   UsedTime =:= 0
  309    ->  Lips = 'Infinite'
  310    ;   Lips is integer(UsedInf / UsedTime)
  311    ),
  312    print_message(information, time(UsedInf, UsedTime, Wall, Lips)).
  313
  314time_state(t(Wall, Time, Inferences)) :-
  315    get_time(Wall),
  316    statistics(cputime, Time),
  317    statistics(inferences, Inferences).
  318
  319time_true(State0) :-
  320    report(State0, 12).             % leave choice-point
  321time_true(State) :-
  322    get_time(Wall),
  323    statistics(cputime, Time),
  324    statistics(inferences, Inferences0),
  325    plus(Inferences0, -3, Inferences),
  326    nb_setarg(1, State, Wall),
  327    nb_setarg(2, State, Time),
  328    nb_setarg(3, State, Inferences),
  329    fail.
  330
  331
  332                 /*******************************
  333                 *     EXECUTION PROFILING      *
  334                 *******************************/
  335
  336/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  337This module provides a simple backward compatibility frontend on the new
  338(in version 5.1.10) execution profiler  with  a   hook  to  the  new GUI
  339visualiser for profiling results defined in library('swi/pce_profile').
  340
  341Later we will add a proper textual report-generator.
  342- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  343
  344:- multifile
  345    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.
  360profile(Goal) :-
  361    profile(Goal, []).
  362
  363profile(Goal0, Options) :-
  364    option(time(Which), Options, cpu),
  365    time_name(Which, How),
  366    expand_goal(Goal0, Goal),
  367    call_cleanup('$profile'(Goal, How),
  368                 prolog_statistics:show_profile(Options)).
  369
  370time_name(cpu,      cputime)  :- !.
  371time_name(wall,     walltime) :- !.
  372time_name(cputime,  cputime)  :- !.
  373time_name(walltime, walltime) :- !.
  374time_name(Time, _) :-
  375    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.
  387show_profile(N) :-
  388    integer(N),
  389    !,
  390    show_profile([top(N)]).
  391show_profile(Options) :-
  392    profiler(Old, false),
  393    show_profile_(Options),
  394    profiler(_, Old).
  395
  396show_profile_(Options) :-
  397    prolog:show_profile_hook(Options),
  398    !.
  399show_profile_(Options) :-
  400    prof_statistics(Stat),
  401    sort_on(Options, SortKey),
  402    findall(Node, profile_procedure_data(_:_, Node), Nodes),
  403    sort_prof_nodes(SortKey, Nodes, Sorted),
  404    format('~`=t~69|~n'),
  405    format('Total time: ~3f seconds~n', [Stat.time]),
  406    format('~`=t~69|~n'),
  407    format('~w~t~w =~45|~t~w~60|~t~w~69|~n',
  408           [ 'Predicate', 'Box Entries', 'Calls+Redos', 'Time'
  409           ]),
  410    format('~`=t~69|~n'),
  411    option(top(N), Options, 25),
  412    show_plain(Sorted, N, Stat, SortKey).
  413
  414sort_on(Options, ticks_self) :-
  415    option(cumulative(false), Options, false),
  416    !.
  417sort_on(_, ticks).
  418
  419sort_prof_nodes(ticks, Nodes, Sorted) :-
  420    !,
  421    map_list_to_pairs(key_ticks, Nodes, Keyed),
  422    sort(1, >=, Keyed, KeySorted),
  423    pairs_values(KeySorted, Sorted).
  424sort_prof_nodes(Key, Nodes, Sorted) :-
  425    sort(Key, >=, Nodes, Sorted).
  426
  427key_ticks(Node, Ticks) :-
  428    Ticks is Node.ticks_self + Node.ticks_siblings.
  429
  430show_plain([], _, _, _).
  431show_plain(_, 0, _, _) :- !.
  432show_plain([H|T], N, Stat, Key) :-
  433    show_plain(H, Stat, Key),
  434    N2 is N - 1,
  435    show_plain(T, N2, Stat, Key).
  436
  437show_plain(Node, Stat, Key) :-
  438    value(label,                       Node, Pred),
  439    value(call,                        Node, Call),
  440    value(redo,                        Node, Redo),
  441    value(time(Key, percentage, Stat), Node, Percent),
  442    IntPercent is round(Percent*10),
  443    Entry is Call + Redo,
  444    format('~w~t~D =~45|~t~D+~55|~D ~t~1d%~69|~n',
  445           [Pred, Entry, Call, Redo, IntPercent]).
  446
  447
  448                 /*******************************
  449                 *         DATA GATHERING       *
  450                 *******************************/
 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)
  487profile_data(Data) :-
  488    setup_call_cleanup(
  489        profiler(Old, false),
  490        profile_data_(Data),
  491        profiler(_, Old)).
  492
  493profile_data_(profile{summary:Summary, nodes:Nodes}) :-
  494    prof_statistics(Summary),
  495    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)
  503prof_statistics(summary{samples:Samples, ticks:Ticks,
  504                        accounting:Account, time:Time, nodes:Nodes}) :-
  505    '$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.
  513profile_procedure_data(Pred, Node) :-
  514    Node = node{predicate:Pred,
  515                ticks_self:TicksSelf, ticks_siblings:TicksSiblings,
  516                call:Call, redo:Redo, exit:Exit,
  517                callers:Parents, callees:Siblings},
  518    (   specified(Pred)
  519    ->  true
  520    ;   profiled_predicates(Preds),
  521        member(Pred, Preds)
  522    ),
  523    '$prof_procedure_data'(Pred,
  524                           TicksSelf, TicksSiblings,
  525                           Call, Redo, Exit,
  526                           Parents, Siblings).
  527
  528specified(Module:Head) :-
  529    atom(Module),
  530    callable(Head).
  531
  532profiled_predicates(Preds) :-
  533    setof(Pred, prof_impl(Pred), Preds).
  534
  535prof_impl(Pred) :-
  536    prof_node_id(Node),
  537    node_id_pred(Node, Pred).
  538
  539prof_node_id(N) :-
  540    prof_node_id_below(N, -).
  541
  542prof_node_id_below(N, Root) :-
  543    '$prof_sibling_of'(N0, Root),
  544    (   N = N0
  545    ;   prof_node_id_below(N, N0)
  546    ).
  547
  548node_id_pred(Node, Pred) :-
  549    '$prof_node'(Node, Pred, _Calls, _Redos, _Exits, _Recur,
  550                 _Ticks, _SiblingTicks).
 value(+Key, +NodeData, -Value)
Obtain possible computed attributes from NodeData.
  556value(name, Data, Name) :-
  557    !,
  558    predicate_sort_key(Data.predicate, Name).
  559value(label, Data, Label) :-
  560    !,
  561    predicate_label(Data.predicate, Label).
  562value(ticks, Data, Ticks) :-
  563    !,
  564    Ticks is Data.ticks_self + Data.ticks_siblings.
  565value(time(Key, percentage, Stat), Data, Percent) :-
  566    !,
  567    value(Key, Data, Ticks),
  568    Total = Stat.ticks,
  569    Account = Stat.accounting,
  570    (   Total-Account > 0
  571    ->  Percent is 100 * (Ticks/(Total-Account))
  572    ;   Percent is 0.0
  573    ).
  574value(Name, Data, Value) :-
  575    Value = Data.Name.
  576
  577
  578                 /*******************************
  579                 *            MESSAGES          *
  580                 *******************************/
  581
  582:- multifile
  583    prolog:message/3.  584
  585% NOTE: The code below uses get_dict/3 rather than the functional
  586% notation to make this code work with `swipl --traditional`
  587
  588prolog:message(time(UsedInf, UsedTime, Wall, Lips)) -->
  589    [ '~D inferences, ~3f CPU in ~3f seconds (~w% CPU, ~w Lips)'-
  590      [UsedInf, UsedTime, Wall, Perc, Lips] ],
  591    {   Wall > 0
  592    ->  Perc is round(100*UsedTime/Wall)
  593    ;   Perc = ?
  594    }.
  595prolog:message(statistics(List)) -->
  596    msg_statistics(List).
  597
  598msg_statistics([]) --> [].
  599msg_statistics([H|T]) -->
  600    { is_dict(H, Tag) },
  601    msg_statistics(Tag, H),
  602    (   { T == [] }
  603    ->  []
  604    ;   [nl], msg_statistics(T)
  605    ).
  606
  607msg_statistics(core, S) -->
  608    { get_dict(time, S, Time),
  609      get_dict(data, S, Data),
  610      get_dict(stacks, S, Stacks)
  611    },
  612    time_stats(Time), [nl],
  613    data_stats(Data), [nl,nl],
  614    stacks_stats(Stacks).
  615msg_statistics(gc, S) -->
  616    {   (   get_dict(type, S, stack)
  617        ->  Label = ''
  618        ;   get_dict(type, S, Type),
  619            string_concat(Type, " ", Label)
  620        ),
  621        get_dict(count, S, Count),
  622        get_dict(gained, S, Gained),
  623        get_dict(unit, S, Unit),
  624        get_dict(time, S, Time)
  625    },
  626    [ '~D ~wgarbage collections gained ~D ~ws in ~3f seconds.'-
  627      [ Count, Label, Gained, Unit, Time]
  628    ].
  629msg_statistics(shift, S) -->
  630    { get_dict(local, S, Local),
  631      get_dict(global, S, Global),
  632      get_dict(trail, S, Trail),
  633      get_dict(time, S, Time)
  634    },
  635    [ 'Stack shifts: ~D local, ~D global, ~D trail in ~3f seconds'-
  636      [ Local, Global, Trail, Time ]
  637    ].
  638msg_statistics(thread, S) -->
  639    { get_dict(count, S, Count),
  640      get_dict(finished, S, Finished),
  641      get_dict(time, S, Time)
  642    },
  643    [ '~D threads, ~D finished threads used ~3f seconds'-
  644      [Count, Finished, Time]
  645    ].
  646msg_statistics(engine, S) -->
  647    { get_dict(count, S, Count),
  648      get_dict(finished, S, Finished)
  649    },
  650    [ '~D engines, ~D finished engines'-
  651      [Count, Finished]
  652    ].
  653
  654time_stats(T) -->
  655    { get_dict(epoch, T, Epoch),
  656      format_time(string(EpochS), '%+', Epoch),
  657      get_dict(cpu, T, CPU),
  658      get_dict(inferences, T, Inferences)
  659    },
  660    [ 'Started at ~s'-[EpochS], nl,
  661      '~3f seconds cpu time for ~D inferences'-
  662      [ CPU, Inferences ]
  663    ].
  664data_stats(C) -->
  665    { get_dict(atoms, C, Atoms),
  666      get_dict(functors, C, Functors),
  667      get_dict(predicates, C, Predicates),
  668      get_dict(modules, C, Modules),
  669      get_dict(vm_codes, C, VMCodes)
  670    },
  671    [ '~D atoms, ~D functors, ~D predicates, ~D modules, ~D VM-codes'-
  672      [ Atoms, Functors, Predicates, Modules, VMCodes]
  673    ].
  674stacks_stats(S) -->
  675    { get_dict(local, S, Local),
  676      get_dict(global, S, Global),
  677      get_dict(trail, S, Trail),
  678      get_dict(total, S, Total)
  679    },
  680    [ '~|~tLimit~25+~tAllocated~12+~tIn use~12+'-[], nl ],
  681    stack_stats('Local',  Local),  [nl],
  682    stack_stats('Global', Global), [nl],
  683    stack_stats('Trail',  Trail),  [nl],
  684    stack_stats('Total',  Total),  [nl].
  685
  686stack_stats('Total', S) -->
  687    { dict_human_bytes(limit,     S, Limit),
  688      dict_human_bytes(allocated, S, Allocated),
  689      dict_human_bytes(usage,     S, Usage)
  690    },
  691    !,
  692    [ '~|~tTotal:~13+~t~s~12+ ~t~s~12+ ~t~s~12+'-
  693      [Limit, Allocated, Usage]
  694    ].
  695stack_stats(Stack, S) -->
  696    { dict_human_bytes(allocated, S, Allocated),
  697      dict_human_bytes(usage,     S, Usage)
  698    },
  699    [ '~|~w ~tstack:~13+~t~w~12+ ~t~s~12+ ~t~s~12+'-
  700      [Stack, -, Allocated, Usage]
  701    ].
  702
  703dict_human_bytes(Key, Dict, String) :-
  704    get_dict(Key, Dict, Bytes),
  705    human_bytes(Bytes, String).
  706
  707human_bytes(Bytes, String) :-
  708    Bytes < 20_000,
  709    !,
  710    format(string(String), '~D  b', [Bytes]).
  711human_bytes(Bytes, String) :-
  712    Bytes < 20_000_000,
  713    !,
  714    Kb is (Bytes+512) // 1024,
  715    format(string(String), '~D Kb', [Kb]).
  716human_bytes(Bytes, String) :-
  717    Bytes < 20_000_000_000,
  718    !,
  719    Mb is (Bytes+512*1024) // (1024*1024),
  720    format(string(String), '~D Mb', [Mb]).
  721human_bytes(Bytes, String) :-
  722    Gb is (Bytes+512*1024*1024) // (1024*1024*1024),
  723    format(string(String), '~D Gb', [Gb]).
  724
  725
  726:- multifile sandbox:safe_primitive/1.  727
  728sandbox:safe_primitive(prolog_statistics:statistics(_)).
  729sandbox:safe_primitive(prolog_statistics:statistics).
  730sandbox:safe_meta_predicate(prolog_statistics:profile/1).
  731sandbox:safe_meta_predicate(prolog_statistics:profile/2)