//
// Cforall Version 1.0.0 Copyright (C) 2018 University of Waterloo
//
// The contents of this file are covered under the licence agreement in the
// file "LICENCE" distributed with Cforall.
//
// Heap.cc --
//
// Author           : Thierry Delisle
// Created On       : Thu May  3 16:16:10 2018
// Last Modified By : Peter A. Buhr
// Last Modified On : Fri May  4 17:27:31 2018
// Update Count     : 28
//

#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstring>
#include <iomanip>
#include <iostream>

//#define WITH_HEAP_STATISTICS

namespace HeapStats {
#if !defined( WITH_HEAP_STATISTICS )
	void newPass( const char * const ) {}

	void printStats() {}
#else
	struct StatBlock {
		const char * name  = nullptr;	///< Name of this pass
		size_t mallocs     = 0;			///< Allocations in this pass
		size_t frees       = 0;			///< Frees in this pass
		size_t n_allocs    = 0;			///< Current number of live allocations
		size_t peak_allocs = 0;			///< Peak number of live allocations this pass
	};

	StatBlock    passes[100] = {{ "Pre-Parse", 0, 0, 0, 0 }};
	const size_t passes_size = sizeof(passes) / sizeof(passes[0]);
	size_t       passes_cnt = 1;

	void newPass( const char * const name ) {
		passes[passes_cnt].name    = name;
		passes[passes_cnt].mallocs = 0;
		passes[passes_cnt].frees   = 0;
		passes[passes_cnt].n_allocs 
			= passes[passes_cnt].peak_allocs 
			= passes[passes_cnt-1].n_allocs;
		passes_cnt++;

		assertf(passes_cnt < passes_size, "Too many passes for HeapStats, increase the size of the array in Heap.h");
	}

	void print(size_t value, size_t total) {
		std::cerr << std::setw(12) << value;
		std::cerr << "(" << std::setw(3);
		std::cerr << (value == 0 ? 0 : value * 100 / total);
		std::cerr << "%) | ";
	}

	void print(const StatBlock& stat, size_t nc, size_t total_mallocs, size_t total_frees, size_t overall_peak) {
		std::cerr << std::setw(nc) << stat.name;
		std::cerr << " | ";

		print(stat.mallocs,     total_mallocs);
		print(stat.frees,       total_frees  );
		print(stat.peak_allocs, overall_peak );
		std::cerr << "\n";
	}

	void print(char c, size_t nc) {
		for(size_t i = 0; i < nc; i++) {
			std::cerr << c;
		}
		std::cerr << '\n';
	}

	void printStats() {
		size_t nc = 0;
		size_t total_mallocs = 0;
		size_t total_frees   = 0;
		size_t overall_peak  = 0;
		for(size_t i = 0; i < passes_cnt; i++) {
			nc = std::max(nc, std::strlen(passes[i].name));
			total_mallocs += passes[i].mallocs;
			total_frees   += passes[i].frees;
			overall_peak = std::max(overall_peak, passes[i].peak_allocs);
		}
		size_t nct = nc + 65;

		const char * const title = "Heap Usage Statistic";
		print('=', nct);
		for(size_t i = 0; i < (nct - std::strlen(title)) / 2; i++) std::cerr << ' ';
		std::cerr << title << std::endl;
		print('-', nct);
		std::cerr << std::setw(nc) << "Pass";
		std::cerr << " |       Malloc Count |         Free Count |        Peak Allocs |" << std::endl;

		print('-', nct);
		for(size_t i = 0; i < passes_cnt; i++) {
			print(passes[i], nc, total_mallocs, total_frees, overall_peak);
		}
		print('-', nct);
		print({"Sum", total_mallocs, total_frees, 0, overall_peak}, 
			nc, total_mallocs, total_frees, overall_peak);

	}

#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
	extern "C" {
#include <dlfcn.h>
#include <execinfo.h>
	}

//=============================================================================================
// Interposing helpers
//=============================================================================================

	typedef void (* generic_fptr_t)(void);
	generic_fptr_t interpose_symbol( const char * symbol, const char * version ) {
		const char * error;

		static void * library;
		if ( ! library ) {
#if defined( RTLD_NEXT )
			library = RTLD_NEXT;
#else
			// missing RTLD_NEXT => must hard-code library name, assuming libstdc++
			library = dlopen( "libc.so.6", RTLD_LAZY );
			error = dlerror();
			if ( error ) {
				std::cerr << "interpose_symbol : failed to open libc, " << error << std::endl;
				abort();
			}
#endif // RTLD_NEXT
		} // if

		generic_fptr_t fptr;

#if defined( _GNU_SOURCE )
		if ( version ) {
			fptr = (generic_fptr_t)dlvsym( library, symbol, version );
		} else {
			fptr = (generic_fptr_t)dlsym( library, symbol );
		}
#else
		fptr = (generic_fptr_t)dlsym( library, symbol );
#endif // _GNU_SOURCE

		error = dlerror();
		if ( error ) {
			std::cerr << "interpose_symbol : internal error, " << error << std::endl;
			abort();
		}

		return fptr;
	}

	extern "C" {
		void * malloc( size_t size ) __attribute__((malloc));
		void * malloc( size_t size ) {
			static auto __malloc = reinterpret_cast<void * (*)(size_t)>(interpose_symbol( "malloc", nullptr ));
			if( passes_cnt > 0 ) {
				passes[passes_cnt - 1].mallocs++;
				passes[passes_cnt - 1].n_allocs++;
				passes[passes_cnt - 1].peak_allocs 
					= std::max(passes[passes_cnt - 1].peak_allocs, passes[passes_cnt - 1].n_allocs);
			}
			return __malloc( size );
		}

		void free( void * ptr ) {
			static auto __free = reinterpret_cast<void   (*)(void *)>(interpose_symbol( "free", nullptr ));
			if( passes_cnt > 0 ) {
				passes[passes_cnt - 1].frees++;
				passes[passes_cnt - 1].n_allocs--;
			}
			return __free( ptr );
		}

		void * calloc( size_t nelem, size_t size ) {
			static auto __calloc = reinterpret_cast<void * (*)(size_t, size_t)>(interpose_symbol( "calloc", nullptr ));
			if( passes_cnt > 0 ) {
				passes[passes_cnt - 1].mallocs++;
				passes[passes_cnt - 1].n_allocs++;
				passes[passes_cnt - 1].peak_allocs 
					= std::max(passes[passes_cnt - 1].peak_allocs, passes[passes_cnt - 1].n_allocs);
			}
			return __calloc( nelem, size );
		}

		void * realloc( void * ptr, size_t size ) {
			static auto __realloc = reinterpret_cast<void * (*)(void *, size_t)>(interpose_symbol( "realloc", nullptr ));
			void * s = __realloc( ptr, size );
			if ( s != ptr && passes_cnt > 0 ) {			// did realloc get new storage ?
				passes[passes_cnt - 1].mallocs++;
				passes[passes_cnt - 1].frees++;
			} // if
			return s;
		}
	}
#endif
}
