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