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(:, -).
63
64/** <module> Get information about resource usage
65
66This library provides predicates to   obtain  information about resource
67usage by your program. The predicates of  this library are for human use
68at the toplevel: information is _printed_.   All predicates obtain their
69information using public low-level primitives.   These primitives can be
70use to obtain selective statistics during execution.
71*/
72
73%!  statistics is det.
74%
75%   Print information about resource usage using print_message/2.
76%
77%   @see    All statistics printed are obtained through statistics/2.
78
79statistics :-
80    phrase(collect_stats, Stats),
81    print_message(information, statistics(Stats)).
82
83%!  statistics(-Stats:dict) is det.
84%
85%   Stats  is  a  dict   representing    the   same  information  as
86%   statistics/0. This convience function is   primarily intended to
87%   pass  statistical  information  to  e.g.,  a  web  client.  Time
88%   critical code that wishes to   collect statistics typically only
89%   need a small subset  and  should   use  statistics/2  to  obtain
90%   exactly the data they need.
91
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 --> [].
238
239
240%!  thread_statistics(?Thread, -Stats:dict) is nondet.
241%
242%   Obtain statistical information about a single thread.  Fails
243%   silently of the Thread is no longer alive.
244%
245%   @arg    Stats is a dict containing status, time and stack-size
246%           information about Thread.
247
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).
277
278
279%!  time(:Goal) is nondet.
280%
281%   Execute Goal, reporting statistics to the user. If Goal succeeds
282%   non-deterministically,  retrying  reports  the   statistics  for
283%   providing the next answer.
284%
285%   Statistics  are  retrieved  using   thread_statistics/3  on  the
286%   calling   thread.   Note   that   not    all   systems   support
287%   thread-specific CPU time. Notable, this is lacking on MacOS X.
288%
289%   @bug Inference statistics are often a few off.
290%   @see statistics/2 for obtaining statistics in your program and
291%        understanding the reported values.
292
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.
349
350%!  profile(:Goal).
351%!  profile(:Goal, +Options).
352%
353%   Run Goal under the execution profiler.  Defined options are:
354%
355%     * time(Which)
356%     Profile =cpu= or =wall= time.  The default is CPU time.
357%     * top(N)
358%     When generating a textual report, show the top N predicates.
359%     * cumulative(Bool)
360%     If =true= (default =false=), show cumulative output in
361%     a textual report.
362
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).
379
380%!  show_profile(+Options)
381%
382%   Display last collected profiling data.  Options are
383%
384%     * top(N)
385%     When generating a textual report, show the top N predicates.
386%     * cumulative(Bool)
387%     If =true= (default =false=), show cumulative output in
388%     a textual report.
389
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                 *******************************/
454
455%!  profile_data(-Data) is det.
456%
457%   Gather all relevant data from profiler. This predicate may be called
458%   while profiling is active  in  which   case  it  is  suspended while
459%   collecting the data. Data is a dict providing the following fields:
460%
461%     - summary:Dict
462%       Overall statistics providing
463%       - samples:Count:
464%         Times the statistical profiler was called
465%       - ticks:Count
466%         Virtual ticks during profiling
467%       - accounting:Count
468%         Tick spent on accounting
469%       - time:Seconds
470%         Total time sampled
471%       - nodes:Count
472%         Nodes in the call graph.
473%     - nodes
474%       List of nodes.  Each node provides:
475%       - predicate:PredicateIndicator
476%       - ticks_self:Count
477%       - ticks_siblings:Count
478%       - call:Count
479%       - redo:Count
480%       - exit:Count
481%       - callers:list_of(Relative)
482%       - callees:list_of(Relative)
483%
484%    _Relative_ is a term of the shape below that represents a caller or
485%    callee. Future versions are likely to use a dict instead.
486%
487%        node(PredicateIndicator, CycleID, Ticks, TicksSiblings,
488%             Calls, Redos, Exits)
489
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).
499
500%!  prof_statistics(-Node) is det.
501%
502%   Get overall statistics
503%
504%   @param Node     term of the format prof(Ticks, Account, Time, Nodes)
505
506prof_statistics(summary{samples:Samples, ticks:Ticks,
507                        accounting:Account, time:Time, nodes:Nodes}) :-
508    '$prof_statistics'(Samples, Ticks, Account, Time, Nodes).
509
510%!  profile_procedure_data(?Pred, -Data:dict) is nondet.
511%
512%   Collect data for Pred. If Pred is   unbound  data for each predicate
513%   that has profile data available is   returned.  Data is described in
514%   profile_data/1 as an element of the `nodes` key.
515
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).
554
555%!  value(+Key, +NodeData, -Value)
556%
557%   Obtain possible computed attributes from NodeData.
558
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).
735