/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.node;

import freenet.support.Logger;
import freenet.support.SizeUtil;
import freenet.support.Ticker;
import freenet.support.Logger.LogLevel;
import freenet.support.math.RunningAverage;
import freenet.support.math.SimpleRunningAverage;

public class MemoryChecker implements Runnable {
	private volatile boolean goon = false;
	private final Ticker ps;
	private int aggressiveGCModificator;
	private RunningAverage avgFreeMemory;
	
	public MemoryChecker(Ticker ps, int modificator){
		this.ps = ps;
		this.aggressiveGCModificator = modificator;
	}

	protected synchronized void terminate() {
		goon = false;
		Logger.normal(this, "Terminating Memory Checker!");
	}
	
	public boolean isRunning() {
		return goon;
	}
	
	public synchronized void start() {
		goon = true;
		Logger.normal(this, "Starting Memory Checker!");
		run();
	}

	@Override
	public void run() {
		freenet.support.Logger.OSThread.logPID(this);
		if(!goon){
			Logger.normal(this, "Goon is false ; killing MemoryChecker");
			return;
		}
		
		Runtime r = Runtime.getRuntime();
		
		long totalMemory = r.totalMemory();
		long freeMemory = r.freeMemory();
		long maxMemory = r.maxMemory();
		
		Logger.normal(this, "Memory in use: "+SizeUtil.formatSize((totalMemory-freeMemory)));
		
		if (totalMemory == maxMemory || maxMemory == Long.MAX_VALUE) {
			// jvm have allocated maximum memory
			// totalMemory never decrease, so check it only for once
			if (avgFreeMemory == null)
				avgFreeMemory = new SimpleRunningAverage(3, freeMemory);
			else
				avgFreeMemory.report(freeMemory);
			
			if (avgFreeMemory.countReports() >= 3 && avgFreeMemory.currentValue() < 4 * 1024 * 1024) {//  average free memory < 4 MB
				Logger.normal(this, "Reached threshold, checking for low memory ...");
				System.gc();
				System.runFinalization();

				try {
					Thread.sleep(10); // Force a context switch, finalization need a CS to complete
				} catch (InterruptedException e) {
				}

				freeMemory = r.freeMemory();
				avgFreeMemory.report(freeMemory);
			}
		}
		
		int sleeptime = aggressiveGCModificator;
		if(sleeptime <= 0) { // We are done
			ps.queueTimedJob(this, 120 * 250); // 30 sec
			return;
		} else
			ps.queueTimedJob(this, 120L * sleeptime);
		
		// FIXME
		// Do not remove until all known memory issues fixed,
		// Especially #66
		// This probably reduces performance, but it makes
		// memory usage *more predictable*. This will make
		// tracking down the sort of nasty unpredictable OOMs
		// we are getting much easier. 
		if(aggressiveGCModificator > 0) {
			boolean logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
			long beforeGCUsedMemory = (r.totalMemory() - r.freeMemory());
			if(logMINOR) Logger.minor(this, "Memory in use before GC: "+beforeGCUsedMemory);
			long beforeGCTime = System.currentTimeMillis();
			System.gc();
			System.runFinalization();
			long afterGCTime = System.currentTimeMillis();
			long afterGCUsedMemory = (r.totalMemory() - r.freeMemory());
			if(logMINOR) {
				Logger.minor(this, "Memory in use after GC: "+afterGCUsedMemory);
				Logger.minor(this, "GC completed after "+(afterGCTime - beforeGCTime)+"ms and \"recovered\" "+(beforeGCUsedMemory - afterGCUsedMemory)+" bytes, leaving "+afterGCUsedMemory+" bytes used");
			}
		}

	}
}
