/*
 * Copyright 2023, ttcoder
 * All rights reserved. Distributed under the terms of the MIT license.
 */


// this
#include "LeakChecker.h"



/***********/
const int LeakChecker::debug = 1;
	// 1: report "consumed RAM" (total)
	// 2: also list potential leaks
	// 3: also trace all malloc/free calls as they occur



/****************/
static LeakChecker * _leak_checker = nullptr;
#define COLORCODE "\033[33m"
#define RESETCOL "\033[0m"


void LeakChecker::InitSingleton( Genode::Env & env, Genode::Allocator & alloc )
{
	if( _leak_checker )
		Genode::error( "LeakChecker::InitSingleton: the singleton has already been instantiated" );
	
	_leak_checker = new(alloc/*harmless to use it for ourself?*/) LeakChecker( env, alloc );
}

LeakChecker * LeakChecker::Singleton()
{
	if( nullptr == _leak_checker )
		Genode::error( "_leak_checker is NULL" );
	
	return _leak_checker;
}



//#pragma mark -


LeakChecker::LeakChecker(
			Genode::Env & env,
			Genode::Allocator & instrumented_alloc
			)
: 	dictHeap( env.ram(), env.rm() )
	,instrumentedAlloc( instrumented_alloc )
	,sizeByAddy()
	//,cyclicDisp( 0 )
{
}

LeakChecker::~LeakChecker()
{
	Clear();
}


void LeakChecker::Clear()
{
	if( debug >= 2 )
		Genode::log( "  leakchk: ", COLORCODE, "Clear !", RESETCOL );
	
	auto destroy_fn = [&] (BufProperties &b) { destroy(dictHeap, &b); };
	
	while( sizeByAddy.with_any_element(destroy_fn) )
	{
	}
}


void LeakChecker::track_malloc(
			const void * alloc_addy,
			const size_t size,
			const void * caller_address
			)
{
	if( debug >= 3 )
		Genode::log( "  leakchk: malloc( ", size, " -> ", alloc_addy, " ) via ", caller_address, " <?>, ", __builtin_return_address(0), " <malloc>  total ", ConsumedBytes(), " -> ", ConsumedBytes() +size );
	
	// insert entry
	new( dictHeap ) BufProperties( sizeByAddy, alloc_addy, size, caller_address );
}

void LeakChecker::track_free(
			const void * alloc_addy,
			const size_t size,
			const void * caller_address
			)
{
	if( debug >= 3 )
		Genode::log( "  leakchk: free( ", size, ", ", alloc_addy, " ) via ", caller_address, " <?>, ", __builtin_return_address(0), " <free>  total ", ConsumedBytes(), " -> ", ConsumedBytes() -size );
	
	// remove entry
	sizeByAddy.with_element(
		alloc_addy,
		[&] (BufProperties & e) {
			destroy( dictHeap, &e );  // "match" case
			},
		[&] () {
			Genode::error( "free() called on unknown buffer addy: ", alloc_addy );  // "no match" case
			}
		);
	
	// display
	/*
	if( cyclicDisp++ % 10 == 0 )
		if( debug >= 2 )
			DisplayStats();
	*/
}


size_t LeakChecker::ConsumedBytes() const
{
	//return instrumentedAlloc.consumed();
	// nah -- consumed() is NOT a reliable indicator of (leaked) memory usage,
	// as it can go down even as memory allocations go up (!), due to slab/bloc mechanics.
	// Do the math by hand instead, to be 100% down-to-the-byte accurate:
	
	size_t s = 0;
	
	sizeByAddy.for_each(
		[&] (const BufProperties & e) {
			s += e.bufSize;
			}
		);
	
	return s;
}


void LeakChecker::DisplayStats()
{
	Genode::log( "    === leaks checkpoint === consumed: [ ", COLORCODE, ConsumedBytes(), RESETCOL, " ] --- (with overhead: ", instrumentedAlloc.consumed(), ") ", (debug >= 2) ? "potentially leaked chunks:" : "" );
	
	if( debug >= 2 )
	{
		sizeByAddy.for_each(
			[] (const BufProperties & e) {
					Genode::log( "     leak: addy=", e.bufAddy, " size=", e.bufSize, " caller=", e.callerAddy );
				}
			);
		
		Genode::log( "      === --- --- ===" );
	}
}

