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    (   catch(thread_stats(Thread, Stacks, Time), _, fail)
  249    ->  Stats = thread{id:Id,
  250                       status:Status,
  251                       time:Time,
  252                       stacks:Stacks}
  253    ;   Stats = thread{id:Thread,
  254                       status:Status}
  255    ).
  256
  257human_thread_id(Thread, Id) :-
  258    atom(Thread),
  259    !,
  260    Id = Thread.
  261human_thread_id(Thread, Id) :-
  262    thread_property(Thread, id(Id)).
  263
  264thread_stats(Thread, Stacks,
  265             time{cpu:CpuTime,
  266                  inferences:Inferences,
  267                  epoch:Epoch
  268                 }) :-
  269    thread_statistics(Thread, cputime, CpuTime),
  270    thread_statistics(Thread, inferences, Inferences),
  271    thread_statistics(Thread, epoch, Epoch),
  272    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.
  289time(Goal) :-
  290    time_state(State0),
  291    (   call_cleanup(catch(Goal, E, (report(State0,10), throw(E))),
  292                     Det = true),
  293        time_true(State0),
  294        (   Det == true
  295        ->  !
  296        ;   true
  297        )
  298    ;   report(State0, 11),
  299        fail
  300    ).
  301
  302report(t(OldWall, OldTime, OldInferences), Sub) :-
  303    time_state(t(NewWall, NewTime, NewInferences)),
  304    UsedTime is NewTime - OldTime,
  305    UsedInf  is NewInferences - OldInferences - Sub,
  306    Wall     is NewWall - OldWall,
  307    (   UsedTime =:= 0
  308    ->  Lips = 'Infinite'
  309    ;   Lips is integer(UsedInf / UsedTime)
  310    ),
  311    print_message(information, time(UsedInf, UsedTime, Wall, Lips)).
  312
  313time_state(t(Wall, Time, Inferences)) :-
  314    get_time(Wall),
  315    statistics(cputime, Time),
  316    statistics(inferences, Inferences).
  317
  318time_true(State0) :-
  319    report(State0, 12).             % leave choice-point
  320time_true(State) :-
  321    get_time(Wall),
  322    statistics(cputime, Time),
  323    statistics(inferences, Inferences0),
  324    plus(Inferences0, -3, Inferences),
  325    nb_setarg(1, State, Wall),
  326    nb_setarg(2, State, Time),
  327    nb_setarg(3, State, Inferences),
  328    fail.
  329
  330
  331                 /*******************************
  332                 *     EXECUTION PROFILING      *
  333                 *******************************/
  334
  335/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  336This module provides a simple backward compatibility frontend on the new
  337(in version 5.1.10) execution profiler  with  a   hook  to  the  new GUI
  338visualiser for profiling results defined in library('swi/pce_profile').
  339
  340Later we will add a proper textual report-generator.
  341- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  342
  343:- multifile
  344    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.
  359profile(Goal) :-
  360    profile(Goal, []).
  361
  362profile(Goal0, Options) :-
  363    option(time(Which), Options, cpu),
  364    time_name(Which, How),
  365    expand_goal(Goal0, Goal),
  366    call_cleanup('$profile'(Goal, How),
  367                 prolog_statistics:show_profile(Options)).
  368
  369time_name(cpu,      cputime)  :- !.
  370time_name(wall,     walltime) :- !.
  371time_name(cputime,  cputime)  :- !.
  372time_name(walltime, walltime) :- !.
  373time_name(Time, _) :-
  374    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.
  386show_profile(N) :-
  387    integer(N),
  388    !,
  389    show_profile([top(N)]).
  390show_profile(Options) :-
  391    profiler(Old, false),
  392    show_profile_(Options),
  393    profiler(_, Old).
  394
  395show_profile_(Options) :-
  396    prolog:show_profile_hook(Options),
  397    !.
  398show_profile_(Options) :-
  399    prof_statistics(Stat),
  400    sort_on(Options, SortKey),
  401    findall(Node, profile_procedure_data(_:_, Node), Nodes),
  402    sort_prof_nodes(SortKey, Nodes, Sorted),
  403    format('~`=t~69|~n'),
  404    format('Total time: ~3f seconds~n', [Stat.time]),
  405    format('~`=t~69|~n'),
  406    format('~w~t~w =~45|~t~w~60|~t~w~69|~n',
  407           [ 'Predicate', 'Box Entries', 'Calls+Redos', 'Time'
  408           ]),
  409    format('~`=t~69|~n'),
  410    option(top(N), Options, 25),
  411    show_plain(Sorted, N, Stat, SortKey).
  412
  413sort_on(Options, ticks_self) :-
  414    option(cumulative(false), Options, false),
  415    !.
  416sort_on(_, ticks).
  417
  418sort_prof_nodes(ticks, Nodes, Sorted) :-
  419    !,
  420    map_list_to_pairs(key_ticks, Nodes, Keyed),
  421    sort(1, >=, Keyed, KeySorted),
  422    pairs_values(KeySorted, Sorted).
  423sort_prof_nodes(Key, Nodes, Sorted) :-
  424    sort(Key, >=, Nodes, Sorted).
  425
  426key_ticks(Node, Ticks) :-
  427    Ticks is Node.ticks_self + Node.ticks_siblings.
  428
  429show_plain([], _, _, _).
  430show_plain(_, 0, _, _) :- !.
  431show_plain([H|T], N, Stat, Key) :-
  432    show_plain(H, Stat, Key),
  433    N2 is N - 1,
  434    show_plain(T, N2, Stat, Key).
  435
  436show_plain(Node, Stat, Key) :-
  437    value(label,                       Node, Pred),
  438    value(call,                        Node, Call),
  439    value(redo,                        Node, Redo),
  440    value(time(Key, percentage, Stat), Node, Percent),
  441    IntPercent is round(Percent*10),
  442    Entry is Call + Redo,
  443    format('~w~t~D =~45|~t~D+~55|~D ~t~1d%~69|~n',
  444           [Pred, Entry, Call, Redo, IntPercent]).
  445
  446
  447                 /*******************************
  448                 *         DATA GATHERING       *
  449                 *******************************/
 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)
  486profile_data(Data) :-
  487    setup_call_cleanup(
  488        profiler(Old, false),
  489        profile_data_(Data),
  490        profiler(_, Old)).
  491
  492profile_data_(profile{summary:Summary, nodes:Nodes}) :-
  493    prof_statistics(Summary),
  494    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)
  502prof_statistics(summary{samples:Samples, ticks:Ticks,
  503                        accounting:Account, time:Time, nodes:Nodes}) :-
  504    '$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.
  512profile_procedure_data(Pred, Node) :-
  513    Node = node{predicate:Pred,
  514                ticks_self:TicksSelf, ticks_siblings:TicksSiblings,
  515                call:Call, redo:Redo, exit:Exit,
  516                callers:Parents, callees:Siblings},
  517    (   specified(Pred)
  518    ->  true
  519    ;   profiled_predicates(Preds),
  520        member(Pred, Preds)
  521    ),
  522    '$prof_procedure_data'(Pred,
  523                           TicksSelf, TicksSiblings,
  524                           Call, Redo, Exit,
  525                           Parents, Siblings).
  526
  527specified(Module:Head) :-
  528    atom(Module),
  529    callable(Head).
  530
  531profiled_predicates(Preds) :-
  532    setof(Pred, prof_impl(Pred), Preds).
  533
  534prof_impl(Pred) :-
  535    prof_node_id(Node),
  536    node_id_pred(Node, Pred).
  537
  538prof_node_id(N) :-
  539    prof_node_id_below(N, -).
  540
  541prof_node_id_below(N, Root) :-
  542    '$prof_sibling_of'(N0, Root),
  543    (   N = N0
  544    ;   prof_node_id_below(N, N0)
  545    ).
  546
  547node_id_pred(Node, Pred) :-
  548    '$prof_node'(Node, Pred, _Calls, _Redos, _Exits, _Recur,
  549                 _Ticks, _SiblingTicks).
 value(+Key, +NodeData, -Value)
Obtain possible computed attributes from NodeData.
  555value(name, Data, Name) :-
  556    !,
  557    predicate_sort_key(Data.predicate, Name).
  558value(label, Data, Label) :-
  559    !,
  560    predicate_label(Data.predicate, Label).
  561value(ticks, Data, Ticks) :-
  562    !,
  563    Ticks is Data.ticks_self + Data.ticks_siblings.
  564value(time(Key, percentage, Stat), Data, Percent) :-
  565    !,
  566    value(Key, Data, Ticks),
  567    Total = Stat.ticks,
  568    Account = Stat.accounting,
  569    (   Total-Account > 0
  570    ->  Percent is 100 * (Ticks/(Total-Account))
  571    ;   Percent is 0.0
  572    ).
  573value(Name, Data, Value) :-
  574    Value = Data.Name.
  575
  576
  577                 /*******************************
  578                 *            MESSAGES          *
  579                 *******************************/
  580
  581:- multifile
  582    prolog:message/3.  583
  584% NOTE: The code below uses get_dict/3 rather than the functional
  585% notation to make this code work with `swipl --traditional`
  586
  587prolog:message(time(UsedInf, UsedTime, Wall, Lips)) -->
  588    [ '~D inferences, ~3f CPU in ~3f seconds (~w% CPU, ~w Lips)'-
  589      [UsedInf, UsedTime, Wall, Perc, Lips] ],
  590    {   Wall > 0
  591    ->  Perc is round(100*UsedTime/Wall)
  592    ;   Perc = ?
  593    }.
  594prolog:message(statistics(List)) -->
  595    msg_statistics(List).
  596
  597msg_statistics([]) --> [].
  598msg_statistics([H|T]) -->
  599    { is_dict(H, Tag) },
  600    msg_statistics(Tag, H),
  601    (   { T == [] }
  602    ->  []
  603    ;   [nl], msg_statistics(T)
  604    ).
  605
  606msg_statistics(core, S) -->
  607    { get_dict(time, S, Time),
  608      get_dict(data, S, Data),
  609      get_dict(stacks, S, Stacks)
  610    },
  611    time_stats(Time), [nl],
  612    data_stats(Data), [nl,nl],
  613    stacks_stats(Stacks).
  614msg_statistics(gc, S) -->
  615    {   (   get_dict(type, S, stack)
  616        ->  Label = ''
  617        ;   get_dict(type, S, Type),
  618            string_concat(Type, " ", Label)
  619        ),
  620        get_dict(count, S, Count),
  621        get_dict(gained, S, Gained),
  622        get_dict(unit, S, Unit),
  623        get_dict(time, S, Time)
  624    },
  625    [ '~D ~wgarbage collections gained ~D ~ws in ~3f seconds.'-
  626      [ Count, Label, Gained, Unit, Time]
  627    ].
  628msg_statistics(shift, S) -->
  629    { get_dict(local, S, Local),
  630      get_dict(global, S, Global),
  631      get_dict(trail, S, Trail),
  632      get_dict(time, S, Time)
  633    },
  634    [ 'Stack shifts: ~D local, ~D global, ~D trail in ~3f seconds'-
  635      [ Local, Global, Trail, Time ]
  636    ].
  637msg_statistics(thread, S) -->
  638    { get_dict(count, S, Count),
  639      get_dict(finished, S, Finished),
  640      get_dict(time, S, Time)
  641    },
  642    [ '~D threads, ~D finished threads used ~3f seconds'-
  643      [Count, Finished, Time]
  644    ].
  645msg_statistics(engine, S) -->
  646    { get_dict(count, S, Count),
  647      get_dict(finished, S, Finished)
  648    },
  649    [ '~D engines, ~D finished engines'-
  650      [Count, Finished]
  651    ].
  652
  653time_stats(T) -->
  654    { get_dict(epoch, T, Epoch),
  655      format_time(string(EpochS), '%+', Epoch),
  656      get_dict(cpu, T, CPU),
  657      get_dict(inferences, T, Inferences)
  658    },
  659    [ 'Started at ~s'-[EpochS], nl,
  660      '~3f seconds cpu time for ~D inferences'-
  661      [ CPU, Inferences ]
  662    ].
  663data_stats(C) -->
  664    { get_dict(atoms, C, Atoms),
  665      get_dict(functors, C, Functors),
  666      get_dict(predicates, C, Predicates),
  667      get_dict(modules, C, Modules),
  668      get_dict(vm_codes, C, VMCodes)
  669    },
  670    [ '~D atoms, ~D functors, ~D predicates, ~D modules, ~D VM-codes'-
  671      [ Atoms, Functors, Predicates, Modules, VMCodes]
  672    ].
  673stacks_stats(S) -->
  674    { get_dict(local, S, Local),
  675      get_dict(global, S, Global),
  676      get_dict(trail, S, Trail),
  677      get_dict(total, S, Total)
  678    },
  679    [ '~|~tLimit~25+~tAllocated~12+~tIn use~12+'-[], nl ],
  680    stack_stats('Local',  Local),  [nl],
  681    stack_stats('Global', Global), [nl],
  682    stack_stats('Trail',  Trail),  [nl],
  683    stack_stats('Total',  Total),  [nl].
  684
  685stack_stats('Total', S) -->
  686    { dict_human_bytes(limit,     S, Limit),
  687      dict_human_bytes(allocated, S, Allocated),
  688      dict_human_bytes(usage,     S, Usage)
  689    },
  690    !,
  691    [ '~|~tTotal:~13+~t~s~12+ ~t~s~12+ ~t~s~12+'-
  692      [Limit, Allocated, Usage]
  693    ].
  694stack_stats(Stack, S) -->
  695    { dict_human_bytes(allocated, S, Allocated),
  696      dict_human_bytes(usage,     S, Usage)
  697    },
  698    [ '~|~w ~tstack:~13+~t~w~12+ ~t~s~12+ ~t~s~12+'-
  699      [Stack, -, Allocated, Usage]
  700    ].
  701
  702dict_human_bytes(Key, Dict, String) :-
  703    get_dict(Key, Dict, Bytes),
  704    human_bytes(Bytes, String).
  705
  706human_bytes(Bytes, String) :-
  707    Bytes < 20_000,
  708    !,
  709    format(string(String), '~D  b', [Bytes]).
  710human_bytes(Bytes, String) :-
  711    Bytes < 20_000_000,
  712    !,
  713    Kb is (Bytes+512) // 1024,
  714    format(string(String), '~D Kb', [Kb]).
  715human_bytes(Bytes, String) :-
  716    Bytes < 20_000_000_000,
  717    !,
  718    Mb is (Bytes+512*1024) // (1024*1024),
  719    format(string(String), '~D Mb', [Mb]).
  720human_bytes(Bytes, String) :-
  721    Gb is (Bytes+512*1024*1024) // (1024*1024*1024),
  722    format(string(String), '~D Gb', [Gb]).
  723
  724
  725:- multifile sandbox:safe_primitive/1.  726
  727sandbox:safe_primitive(prolog_statistics:statistics(_)).
  728sandbox:safe_primitive(prolog_statistics:statistics).
  729sandbox:safe_meta_predicate(prolog_statistics:profile/1).
  730sandbox:safe_meta_predicate(prolog_statistics:profile/2)