/** ------------------------------------------------------------
   In improved profiling using the external C-interface and the
   rdtsc assembler call to read the CPU clock ticks.

   There is a usage example at the bottom of this file. Use this
   with care. It is _much_ better than the profile/1 provided by
   ECLiPSe, but it also takes quite a long time, so if you use
   nested profiling instructions, you may get warped results. It
   is hence best for profiling key predicates one at a time -- or
   at least not nested.

   The output gives the percentage of all CPU ticks spent in the
   named predicates, and the corresponding amount of CPU time,
   computed as a percentage of the overall time.

   Make sure to compile external.c first. You can try the
   provided Makefile which so far covers i386 and x86_i64
   architectures, and has been tested using gcc 4.0.3. I used
   this with ECLiPSe version 5.10 on both named architectures.

   Please feel free to email me with questions or comments.
  
   -------------------------------------------------------------
   @author: Christian Fritz <fritz@cs.toronto.edu>

   @date: 6.5.2008
   -------------------------------------------------------------

   This program is free software: you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation, either version 3 of
   the License, or (at your option) any later version.
   
   This program is distributed in the hope that it will be
   useful, but WITHOUT ANY WARRANTY; without even the implied
   warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
   PURPOSE. See the GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public
   License along with this program. If not, see
   <http://www.gnu.org/licenses/>.
   
   -----------------------------------------------------------*/

   
/** ---------------------------------------------------------
  External Tools (in external.c/-.so)
---------------------------------------------------------- */

:- load("external.so").
:- external(ext_clock/2, p_clock). %% hi, lo

/** clock ticks since system start */
external_rdtsc(Ticks) :-
	ext_clock(Hi, Lo),
	( Hi < 0 -> Hi2 is Hi + 2^32 ; Hi2 = Hi),
	( Lo < 0 -> Lo2 is Lo + 2^32 ; Lo2 = Lo),
	Ticks is Hi2*(2^32) + Lo2.

/* ------------------------------------------------------- */

/** ---------------------------------------------------------
  time tools
---------------------------------------------------------- */

/** get a time -Stamp */
time_Stamp(Stamp) :- statistics(times, Stamp).
time_Diff([CPUT1, SYST1, RealT1], [CPUTD, SYSTD, RealTD]) :-
	statistics(times, [CPUT2, SYST2, RealT2]),
	CPUTD is CPUT2 - CPUT1,
	SYSTD is SYST2 - SYST1,
	RealTD is RealT2 - RealT1.
time_printDiff([CPUT1, SYST1, RealT1]) :-
	statistics(times, [CPUT2, SYST2, RealT2]),
	CPUTD is CPUT2 - CPUT1,
	SYSTD is SYST2 - SYST1,
	RealTD is RealT2 - RealT1,
	printf("%w\n", [[CPUTD, SYSTD, RealTD]]).
/* ------------------------------------------------------- */


/** ---------------------------------------------------------
  profiling using external clock function
  ---------------------------------------------------------- */

/** preprocessing */

/** uncomment the following line to control whether profiling is done
or not by setting the CFPROFILE environment variable (for
instance with 'env CFPROFILE= eclipse -b myprogram.pl -e run') */
%:- inline( cf_profile/2, trans_cf_profile/2).

trans_cf_profile(cf_profile(_Token, Goal), Goal) :-
	%% remove all profiling requests if not enabled
	not(getenv('CFPROFILE', _)).

:- local store(cf_profiling).
cf_profile_init :-
	external_rdtsc(Start),
	setval(cf_profiling_start, Start),
	time_Stamp(StartTime),
	setval(cf_profiling_start_time, StartTime).

cf_profile(Name, X) :-
	store_contains(cf_profiling, Name), !,
	store_get(cf_profiling, Name, N),
	cf_profile_aux(X, N1),
	N2 is N + N1,
	store_set(cf_profiling, Name, N2).
cf_profile(Name, X) :-
	cf_profile_aux(X, N),
	store_set(cf_profiling, Name, N).

/* rdtsc version */
cf_profile_aux(X, N) :-
	external_rdtsc(Before),
	call(X),
	external_rdtsc(After),
	N is After-Before.

%%            -------------------------------

cf_print_profile :-
	getval(cf_profiling_start, Start),
	external_rdtsc(End),
	TotalTicks is End - Start,
	getval(cf_profiling_start_time, StartTime),
	time_Diff(StartTime, [Time, _S, _R]),
	stored_keys_and_values(cf_profiling, AssocList),
	sort(AssocList, AssocListSorted),
	printf("[cf_profiling] %20s  TICKS %%\tTIME\n", ["NAME"]),
	printf("--------------------------------------------------------\n", []),
	cf_print_profile_aux(TotalTicks, Time, AssocListSorted, 0).

cf_print_profile_aux(Total, Time, [], Sum) :-
	Ratio is Sum / Total,
	RTime is Ratio * Time,
	printf("--------------------------------------------------------\n", []),
	printf("[cf_profiling] %20s: %4f\t%4f\n", ["Total", Ratio, RTime]).
cf_print_profile_aux(Total, Time, [HKey-HVal|T], Sum) :-
	Ratio is HVal / Total,
	RTime is Ratio * Time,
	Sum2 is Sum + HVal,
	printf("[cf_profiling] %20s: %4f\t%4f\n", [HKey, Ratio, RTime]),
	cf_print_profile_aux(Total, Time, T, Sum2).

/* ------------------------------------------------------- */

/* a silly small predicate for testing */
cf_profile_test_predA(0, []) :- !.
cf_profile_test_predA(N, [Nsq|T]) :-
	Nsq is N*N,
	N2 is N-1,
	cf_profile_test_predA(N2, T).

cf_profile_test1 :-
	cf_profile_init,
	cf_profile( predA_with_10000,
		    cf_profile_test_predA(10000,_)),
	cf_profile( predA_with_100000,
		    cf_profile_test_predA(100000,_)),
	cf_print_profile.
