## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## Copyright (c) 2008 Andreas Kupries.
#
# This software is licensed as described in the file LICENSE, which
# you should have received as part of this distribution.
#
# This software consists of voluntary contributions made by many
# individuals.  For exact contribution history, see the revision
# history and logs, available at http://fossil-scm.hwaci.com/fossil
# # ## ### ##### ######## ############# #####################

## Utilities for memory tracking

# # ## ### ##### ######## ############# #####################
## Requirements

package require Tcl 8.4      ; # Required runtime
package require struct::list ; # List assignment

# # ## ### ##### ######## ############# #####################
##

namespace eval ::vc::tools::mem {
    # # ## ### ##### ######## #############
    ## Public API, Methods

    if {[llength [info commands memory]]} {
	proc minfo {} {
	    # memory info reduced to the set of relevant numbers in the output
	    struct::list assign [split [memory info] \n] tm tf cpa cba mpa mba
	    struct::list assign $tm  _ _   tm
	    struct::list assign $tf  _ _   tf
	    struct::list assign $cpa _ _ _ cpa
	    struct::list assign $cba _ _ _ cba
	    struct::list assign $mpa _ _ _ mpa
	    struct::list assign $mba _ _ _ mba
	    return [list $tm $tf $cpa $cba $mpa $mba]
	}
    } else {
	proc minfo {} {return {0 0 0 0 0 0}}
    }

    proc mlog {} {
	variable track ; if {!$track} { return {} }

	variable lcba
	variable lmba
	variable mid

	struct::list assign [minfo] _ _ _ cba _ mba

	set dc [expr $cba - $lcba] ; set lcba $cba	
	set dm [expr $mba - $lmba] ; set lmba $mba	

	# projection: 1          2 3          4 5         6 7          6 8         10
	return "[F [incr mid]] | [F $cba] | [F $dc] | [F $mba] | [F $dm] |=| "
    }

    proc mark {} {
	variable track ; if {!$track} return
	variable mid
	variable lcba
	variable lmark
	set dm [expr {$lcba - $lmark}]
	puts  "[F $mid] | [F $lcba] | [F $dm] | [X %] | [X %] |@| [X %]"
	set lmark $lcba
	return
    }

    proc F {n} { format %10d $n }
    proc X {c} { string repeat $c 10 }

    proc mlimit {} {
	variable track ; if {!$track} return ; # No checks if there is no memory tracking
	variable limit ; if {!$limit} return ; # No checks if there is no memory limit set

	struct::list assign [minfo] _ _ _ cba _ _

	# Nothing to do if we are still under the limit
	if {$cba <= $limit} return

	# Notify user and kill the importer
	puts ""
	puts "\tMemory limit breached: $cba > $limit"
	puts ""
	exit 1
    }

    proc setlimit {thelimit} {
	# Activate memory tracking, and set the limit. The specified
	# limit is taken relative to the amount of memory allocated at
	# the time of the call.

	variable limit
	struct::list assign [minfo] _ _ _ cba _ _
	set limit [expr $cba + $thelimit]

	track
	return
    }

    proc notrack {} {
	variable track 0
	return
    }

    proc track {} {
	variable track 1
	return
    }

    # # ## ### ##### ######## #############

    variable track 0 ; # Boolean flag. If set this module will track
		       # memory, inserting the relevant information
		       # whenever the application logs something.
    variable limit 0 ; # The maximum amount of memory allowed to the
		       # application. This module will abort when
		       # 'current bytes allocated' goes over this
		       # value.

    variable lcba 0 ; # Last 'current bytes allocated' (cba)
    variable lmba 0 ; # Last 'maximum bytes allocated' (mba)
    variable mid  0 ; # Memory id, abstract time
    variable lmark 0 ; #

    # # ## ### ##### ######## #############
}

namespace eval ::vc::tools::mem {
    namespace export minfo mlog track notrack mlimit setlimit mark
}

# -----------------------------------------------------------------------------
# Ready

package provide vc::tools::mem 1.0
return
