sysperfana/perfinvestigator/com.nokia.carbide.cpp.pi.call/src/com/nokia/carbide/cpp/pi/call/GfcTrace.java
author Toni Pulkkinen <ext-toni.p.pulkkinen@nokia.com>
Wed, 23 Jun 2010 15:05:09 +0300
changeset 12 ae255c9aa552
parent 5 844b047e260d
permissions -rw-r--r--
Performance Investigator Carbide extension 2.4.0

/*
 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of the License "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Nokia Corporation - initial contribution.
 *
 * Contributors:
 *
 * Description: 
 *
 */

package com.nokia.carbide.cpp.pi.call;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import com.nokia.carbide.cpp.internal.pi.analyser.NpiInstanceRepository;
import com.nokia.carbide.cpp.internal.pi.model.Function;
import com.nokia.carbide.cpp.internal.pi.model.FunctionResolver;
import com.nokia.carbide.cpp.internal.pi.model.GenericSample;
import com.nokia.carbide.cpp.internal.pi.model.GenericSampledTraceWithFunctions;
import com.nokia.carbide.cpp.internal.pi.utils.QuickSortImpl;
import com.nokia.carbide.cpp.internal.pi.utils.Sortable;
import com.nokia.carbide.cpp.pi.visual.IGenericTraceGraph;


public class GfcTrace extends GenericSampledTraceWithFunctions
{
	private static final long serialVersionUID = -8144591614894549185L;

	private long previousSample;
	  
	private Hashtable parsedGfcTrace;
	private Vector    completeGfcTrace;
	private int firstSample;
	private int lastSample;
	
	private transient CallVisualiser callVisualiser;

	private transient boolean completeTrace   = true;
	private transient boolean completeSamples = true;
	private transient int samplingInterval;
	
	public static final int SORT_BY_TOTAL_LOAD = 1;
	public static final int SORT_BY_CALLED_LOAD = 2;
	public static final int SORT_BY_RECURSIVE_LOAD = 3;

	public GfcTrace(int size)
	{	
	    this.completeGfcTrace = new Vector(size);
	    this.samples = new Vector(size);
	    previousSample = 0;  	
	}
	
	public void addSample(GfcSample sample, Long[] element)
	{
	    this.samples.add(sample);
	  	  
	    long sampleNumber   = sample.sampleSynchTime;
	      
	    if (previousSample != 0)
	    {
	    	if (sampleNumber != previousSample + samplingInterval)
	    		System.out.println(Messages.getString("GfcTrace.missingSample1") + previousSample + Messages.getString("GfcTrace.missingSample2") + sample); //$NON-NLS-1$ //$NON-NLS-2$
	    }
	    previousSample = sampleNumber;
	
	    if (this.firstSample == -1)
	    	this.firstSample = (int)sampleNumber;
	    else
	    	this.lastSample  = (int)sampleNumber;
	    /*
	    System.out.print("\n\nSample: "+sample+" pc: "+programCounter+" lr: "+linkRegister);
	    System.out.print("\nSample: "+sample+
	                       "\npc: "+symbolParser.getFunctionForAddress(programCounter).functionName+
	                       "\nlr: "+symbolParser.getFunctionForAddress(linkRegister).functionName);
	    */
	    this.completeGfcTrace.add(element);
	}
  
	public Hashtable parseEntries(int startSample, int endSample)
	{
	  	// code to avoid stack overflow in saving analysis,
	  	// with parameters -20,-20 , the parsedGfcTrace field is cleared,
	  	// and it is restored with values -10,-10
	    if (startSample == -20 && endSample == -20)
	    {
	    	if (this.parsedGfcTrace == null)
	    	{
	    		return null;
	    	}
	    	else
	    	{
	    		this.parsedGfcTrace = null;
	    		return new Hashtable();
	    	}
	    }
	    else if (startSample == -10 && endSample == -10)
	    {
	    	this.parseEntries(this.firstSample, this.lastSample);
	    	return null;
	    }
	
		int samplingInterval = (Integer) NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.samplingInterval"); //$NON-NLS-1$

    	if (startSample < 1)
    		startSample = 0;

    	this.firstSample = startSample / samplingInterval;
	    this.lastSample  = endSample / samplingInterval;
	    
	    this.parsedGfcTrace = new Hashtable();
	    
    	if (this.completeTrace) {
	    	// adjust the sample search
	    	if (startSample > this.completeGfcTrace.size())
	    		startSample = this.completeGfcTrace.size();
	    	if (endSample > this.completeGfcTrace.size())
	    		endSample = this.completeGfcTrace.size();
	    	
	    	for (int i = startSample; i < endSample; i++) {
	    		Long[] data = (Long[])this.completeGfcTrace.elementAt(i);
	    		this.parseOneEntry(data[0], data[1], data[2]);
	    	}
	    } else if (endSample >= samplingInterval) {
	    	// sample is incomplete, so find the sample nearest but after the start
	    	int nearest = 0;
	    	if (startSample >= samplingInterval) {
	    		int last = 0;
	    		int half = this.completeGfcTrace.size() / 2;
	    		nearest = this.completeGfcTrace.size() - 1;
	    		long time = ((Long[]) this.completeGfcTrace.get(nearest))[0];
	    		
	    		while (time != startSample && half > 0) {
	    			if (time < startSample)
	    				nearest = nearest + half;
	    			else
	    				nearest = nearest - half;
	    			half /= 2;
	    			time = ((Long[]) this.completeGfcTrace.get(nearest))[0];
	    		}
	    		
	    		while (nearest > 0 && time > startSample) {
	    			nearest--;
	    			time = ((Long[]) this.completeGfcTrace.get(nearest))[0];
	    		}
	    		
	    		while (nearest < this.completeGfcTrace.size() - 1 && time + samplingInterval < startSample) {
	    			nearest++;
	    			time = ((Long[]) this.completeGfcTrace.get(nearest))[0];
	    		}
	    	}
	    	
	    	// we're starting at the right spot, so collect data (don't worry that we get the first one twice - we only parse it once
	    	Long[] data = (Long[])this.completeGfcTrace.get(nearest);
		    for ( ; data[0].longValue() <= endSample && nearest < this.completeGfcTrace.size(); nearest++) {
		        data = (Long[])this.completeGfcTrace.get(nearest);
		        this.parseOneEntry(data[0], data[1], data[2]);
		    }
	    }

	    countRecursiveLoad();
    
	    return this.parsedGfcTrace;
	}
	
	private class CallerCallee {
		String  functionName;
		Long    startAdress;
		String  binaryName;
		boolean symbolParsed;
	}
  
  	private void parseOneEntry(long sample, long programCounter, long linkRegister)
  	{
	  	GfcSample gSam = null;
 
	  	if (this.completeSamples)
  	    {
	  		// find it directly
	  		if (sample <= this.getLastSampleNumber())
	  			gSam = (GfcSample)this.samples.elementAt((int) (sample - 1));
  	    } else {
  	    	// find it the slow way
  		  	if (this.getLastSampleNumber() <= sample + 1 || this.getFirstSampleNumber() > sample)
  		  		return;

  		  	GenericSample[] samples = this.getSamplesForTime(sample);
	  	    if ((samples != null) && (samples.length != 0))
	  	    	gSam = (GfcSample)samples[0];
  	    }
  	    
  	    if (gSam == null) {
  	    	System.out.println(Messages.getString("GfcTrace.sampleNotFound") + sample);  	    	 //$NON-NLS-1$
  	    	return;
  	    }
  	    
  	    CallerCallee caller = new CallerCallee();
  	    CallerCallee callee = new CallerCallee();

		if (  (gSam.getCallerFunctionSym() == null || gSam.getCallerFunctionSym().getFunctionName().endsWith(Messages.getString("GfcTrace.functionNotFound"))) //$NON-NLS-1$
			&& gSam.getCallerFunctionItt() != null) //$NON-NLS-1$
		{
			caller.functionName = gSam.getCallerFunctionItt().getFunctionName();
    		caller.startAdress  = gSam.getCallerFunctionItt().getStartAddress();
			caller.binaryName   = gSam.getCallerFunctionItt().getFunctionBinary().getBinaryName();
			caller.symbolParsed = Boolean.FALSE;
		}
		else
		{
			caller.functionName = gSam.getCallerFunctionSym().getFunctionName();
			caller.startAdress  = gSam.getCallerFunctionSym().getStartAddress();
			caller.binaryName   = gSam.getCallerFunctionSym().getFunctionBinary().getBinaryName();
			caller.symbolParsed = Boolean.TRUE;
		}
		
		if (   (gSam.getCurrentFunctionSym() == null || gSam.getCurrentFunctionSym().getFunctionName().endsWith(Messages.getString("GfcTrace.functionNotFound"))) //$NON-NLS-1$
			&& gSam.getCurrentFunctionItt() != null) //$NON-NLS-1$
		{
			callee.functionName = gSam.getCurrentFunctionItt().getFunctionName();
    		callee.startAdress  = gSam.getCurrentFunctionItt().getStartAddress();
			callee.binaryName   = gSam.getCurrentFunctionItt().getFunctionBinary().getBinaryName();
			callee.symbolParsed = Boolean.FALSE;
		}
		else
		{
			callee.functionName = gSam.getCurrentFunctionSym().getFunctionName();
    		callee.startAdress  = gSam.getCurrentFunctionSym().getStartAddress();
			callee.binaryName   = gSam.getCurrentFunctionSym().getFunctionBinary().getBinaryName();
			callee.symbolParsed = Boolean.TRUE;
		}

		this.updateEntryLists(sample, programCounter, linkRegister, caller, callee);
  	}

  	private void updateEntryLists(long sample, long programCounter, long linkRegister, CallerCallee callerObj, CallerCallee calleeObj)
	{
	    String callerName          = callerObj.functionName;
	    Long callerStart           = callerObj.startAdress;
	    String callerDllName       = callerObj.binaryName;
	    boolean callerSymbolParsed = callerObj.symbolParsed;
	
	    String calleeName           = calleeObj.functionName;
	    Long calleeStart            = calleeObj.startAdress;
	    String calleeDllName        = calleeObj.binaryName;
	    boolean calleeSymbolParsed  = calleeObj.symbolParsed;
	    
	    GfcFunctionItem caller;
	    GfcFunctionItem callee;
	
	    // is the callee in the list
	    if (parsedGfcTrace.containsKey(calleeStart))
	    {
		    // the callee is in the list
		    callee = (GfcFunctionItem)this.parsedGfcTrace.get(calleeStart);
	    }
	    else
	    {
		    // the callee is not in the list
		    callee = new GfcFunctionItem(calleeName, calleeStart.longValue(), calleeDllName, calleeSymbolParsed);
		    this.parsedGfcTrace.put(calleeStart, callee);
	    }

	    // is the caller in the list
	    if (this.parsedGfcTrace.containsKey(callerStart))
	    {
	    	// the caller is in the list
	    	caller = (GfcFunctionItem)this.parsedGfcTrace.get(callerStart);
	    }
	    else
	    {
	    	// no, we have to add the caller to the list as well
	    	caller = new GfcFunctionItem(callerName, callerStart.longValue(), callerDllName, callerSymbolParsed);
	    	this.parsedGfcTrace.put(callerStart, caller);
	    }

	    if (!caller.equals(callee))
	    {
	    	callee.addCaller(caller, Integer.valueOf((int)sample));
	    	caller.addCallee(callee, Integer.valueOf((int)sample));
	    }
	}
  
	public void countRecursiveLoad()
	{
		//if (exclude == null)
		ArrayList exclude = new ArrayList();

		GfcFunctionItem[] sorted = this.getEntriesSorted(SORT_BY_CALLED_LOAD);
		
		if (sorted.length == 0)
			return;
		
		int index = sorted.length - 1;
		int lastExclude = 0;

		// add the load of this function to the accumulated load
		for (int i = 0; i < sorted.length; i++)
		{
			GfcFunctionItem gfi = sorted[i];
			gfi.addRecursiveLoad(gfi.countSamplesThatCallThisFunction());
			gfi.setRecursiveLoad(0);
		}

		while (true)
		{
		    GfcFunctionItem item = sorted[index];
		    item.storePercents(-1, -1, -1, this.firstSample, this.lastSample);
		
		    index--;
		    if (index == -1)
		    {
		        index = sorted.length - 1;
		        if (lastExclude == exclude.size())
		        {
		        	break;
		        }
		        else
		        {
		        	lastExclude = exclude.size();
		        }
		    }
		
		    if (!exclude.contains(item))
		    {
		        double load = item.countSamplesThatCallThisFunction();
		
		        GfcFunctionItem[] callers = item.getCallerList();
		        Double[] percentages = item.getCallerPercentages();
		
		        for (int i = 0; i < callers.length; i++)
		        {
			        //System.out.println(item.name+" load:"+load+" Adding "+load*(percentages[i].doubleValue()/100)+" to"+callers[i].name);
			        callers[i].addRecursiveLoad(load*(percentages[i].doubleValue()/100));
			
			        // there is some accumulated recursive load to distribute
			        // between the callers
			        if (item.getRecursiveLoad() > 0)
			        {
			            double recursiveLoadToShare = item.getRecursiveLoad()*(percentages[i].doubleValue()/100);
			
			            // add recursive load only if the caller is not the function itself
			            if (!callers[i].equals(item))
			            {
			            	callers[i].addRecursiveLoad(recursiveLoadToShare);
			            }
			        }
		        }
		        // clear the recursive load
		        item.setRecursiveLoad(0);
		    }
	
		    // this function does not call any other functions so it can be added
		    // to the excluded functions list
		    if (item.getCalleeList().length == 0)
		    {
		        if (!exclude.contains(item))
		        	exclude.add(item);
		        //sorted.remove(item);
		    }
		    else
		    {
		        GfcFunctionItem[] callees = item.getCalleeList();
		        int i;
		        for (i = 0; i < callees.length; i++)
		        {
			        // break if any item is found that is not excluded
			        if (!exclude.contains(callees[i]))
			        	break;
		        }
		
		        // there are only excluded items in this function's call list,
		        // thus it can also be excluded
		        if (i == callees.length)
		        {
			        if (!exclude.contains(item))
			        	exclude.add(item);
			        //sorted.remove(item);
		        }
		    }
		}
	}
  
	public GfcFunctionItem[] getEntriesSorted(final int sortMethod)
	{
	  	class ItemSorter implements Sortable
	  	{
	  		GfcFunctionItem item;
	  		
	  		double itemTotal;
	  		double itemCaller;
	  		double itemRecursive;
	        
	        public ItemSorter(GfcFunctionItem item)
	        {
	        	this.item = item;
	            // calculate and store the percentages for the items for the selected period
	            itemTotal     = item.getAbsoluteTotalPercentage(firstSample, lastSample);
	            itemCaller    = item.getAbsoluteCallerPercentage(firstSample, lastSample);
	            itemRecursive = item.getRecursiveCallerPercentage(firstSample, lastSample);
	            
	            if (itemTotal == -1 || itemCaller == -1 || itemRecursive == -1)
	            {
	            	itemTotal     = getAbsoluteTraditionalPercentageFor(item);
	            	itemCaller    = getAbsoluteCallerPercentageFor(item);
	            	itemRecursive = getRecursiveCallerPrecentageFor(item);
	            
	            	item.storePercents(itemTotal, itemCaller, itemRecursive,
	                               		firstSample, lastSample);
	            }
	        }
        
	  		public long valueOf()
	  		{
	  	        if (sortMethod == GfcTrace.SORT_BY_TOTAL_LOAD)
	  	        {
	  	        	return (long)(1000 * itemTotal);
	  	        }
	  	        else if (sortMethod == GfcTrace.SORT_BY_CALLED_LOAD)
	  	        {
	  	        	return (long)(1000 * itemCaller);
	  	        }
	  	        else if (sortMethod == GfcTrace.SORT_BY_RECURSIVE_LOAD)
	  	        {
	  	        	return (long)(1000 * itemRecursive);
	  	        }
	  	        else
	  	        {
	  	        	return 0;
	  	        }
	  		}
	  	}
  	
		Enumeration elements = this.parsedGfcTrace.elements();
	  	
		Vector s = new Vector();
		while (elements.hasMoreElements())
		{
	  		ItemSorter is = new ItemSorter((GfcFunctionItem)elements.nextElement());
	  		s.add(is);
	  	}
	  	
	  	QuickSortImpl.sortReversed(s);
  	
	  	elements = s.elements();
	  	this.parsedGfcTrace.clear();
	  	
	  	GfcFunctionItem[] f = new GfcFunctionItem[s.size()];
	  	int i = 0;
	  	while (elements.hasMoreElements())
	  	{
	  		ItemSorter is = (ItemSorter)elements.nextElement();
	  		f[i++] = is.item;
	  		this.parsedGfcTrace.put(Long.valueOf(is.item.address), is.item);
	  	}
  	
	  	return f;
	}
  
  	public double getAbsoluteTraditionalPercentageFor(GfcFunctionItem gfi)
	{
	    double samples = gfi.countSamplesThatCallThisFunction();
	    double total = this.lastSample - this.firstSample;
	    return 100 * (samples / total);
	}

  	public double getAbsoluteCallerPercentageFor(GfcFunctionItem gfi)
  	{
  		double samples = gfi.countSamplesWhereThisFunctionIsTheCaller();
  		double total = this.lastSample - this.firstSample;
  		return 100 * (samples / total);
  	}

  	public double getRecursiveCallerPrecentageFor(GfcFunctionItem gfi)
  	{
  		double samples = gfi.getAccumulatedLoad();
  		double total = this.lastSample - this.firstSample;
  		return 100 * (samples / total);
  	}

  	/*
  	 * Check if the function call sample set and trace are complete
  	 * (first sample is at time 1, sample N is at time N)
  	 */
  	public void setComplete()
  	{
  		Object intervalObject = NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.samplingInterval"); //$NON-NLS-1$
		int samplingInterval = intervalObject != null ? (Integer) intervalObject : -1; 
  		int size;
  		
  		this.completeTrace = true;
  		
  		size = this.completeGfcTrace.size();
  		for (int i = 0; i < size; i++) {
  			Long[] element = (Long[])this.completeGfcTrace.elementAt(i);
  			if (element[0].longValue() != (i + 1)*samplingInterval) {
  				this.completeTrace = false;
  				break;
  			}
  		}

  		this.completeSamples = true;
  		
  		size = this.samples.size();
  		for (int i = 0; i < size; i++) {
  			GfcSample element = (GfcSample)this.samples.elementAt(i);
  			if (element.sampleSynchTime != (i + 1)*samplingInterval) {
  				this.completeSamples = false;
  				break;
  			}
  		}
  	}
  		
	public CallVisualiser getCallVisualiser()
	{
		return this.callVisualiser;
	}
	
	public void setCallVisualiser(CallVisualiser callVisualiser)
	{
		this.callVisualiser = callVisualiser;
	}
	
	public void setSamplingInterval(int samplingInterval) {
		this.samplingInterval = samplingInterval;
	}
	
	public void refineTrace(FunctionResolver resolver)
	{
		GfcSample.setAddressTraceSamples();
		super.refineTrace(resolver);
	}
	
	public Vector getCompleteGfcTrace()
	{
		return this.completeGfcTrace;
	}

}