// // Copyright (C) 2006 United States Government as represented by the // Administrator of the National Aeronautics and Space Administration // (NASA). All Rights Reserved. // // This software is distributed under the NASA Open Source Agreement // (NOSA), version 1.3. The NOSA has been approved by the Open Source // Initiative. See the file NOSA-1.3-JPF at the top of the distribution // directory tree for the complete NOSA document. // // THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY // KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT // LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO // SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR // A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT // THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT // DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. // package gov.nasa.jpf.listener; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.Stack; import gov.nasa.jpf.Config; import gov.nasa.jpf.JPF; import gov.nasa.jpf.PropertyListenerAdapter; import gov.nasa.jpf.report.ConsolePublisher; import gov.nasa.jpf.report.Publisher; import gov.nasa.jpf.search.Search; import gov.nasa.jpf.util.DynamicObjectArray; import gov.nasa.jpf.util.Misc; import gov.nasa.jpf.util.SourceRef; import gov.nasa.jpf.util.StringSetMatcher; import gov.nasa.jpf.vm.ClassInfo; import gov.nasa.jpf.vm.ElementInfo; import gov.nasa.jpf.vm.Heap; import gov.nasa.jpf.vm.MethodInfo; import gov.nasa.jpf.vm.ThreadInfo; import gov.nasa.jpf.vm.VM; /** * HeapTracker - property-listener class to check heap utilization along all * execution paths (e.g. to verify heap bounds) */ public class HeapTracker extends PropertyListenerAdapter { static class PathStat implements Cloneable { int nNew = 0; int nReleased = 0; int heapSize = 0; // in bytes public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { return null; } } } static class TypeStat { String typeName; int nAlloc; int nReleased; TypeStat (String typeName){ this.typeName = typeName; } } PathStat stat = new PathStat(); Stack<PathStat> pathStats = new Stack<PathStat>(); DynamicObjectArray<SourceRef> loc = new DynamicObjectArray<SourceRef>(); HashMap<String,TypeStat> typeStat = new HashMap<String,TypeStat>(); int maxState; int nForward; int nBacktrack; int nElemTotal; int nGcTotal; int nSharedTotal; int nImmutableTotal; int nElemMax = Integer.MIN_VALUE; int nElemMin = Integer.MAX_VALUE; int nElemAv; int pElemSharedMax = Integer.MIN_VALUE; int pElemSharedMin = Integer.MAX_VALUE; int pElemSharedAv; int pElemImmutableMax = Integer.MIN_VALUE; int pElemImmutableMin = Integer.MAX_VALUE; int pElemImmutableAv; int nReleased; int nReleasedTotal; int nReleasedAv; int nReleasedMax = Integer.MIN_VALUE; int nReleasedMin = Integer.MAX_VALUE; int maxPathHeap = Integer.MIN_VALUE; int maxPathNew = Integer.MIN_VALUE; int maxPathReleased = Integer.MIN_VALUE; int maxPathAlive = Integer.MIN_VALUE; int initHeap = 0; int initNew = 0; int initReleased = 0; int initAlive = 0; boolean showTypeStats; int maxTypesShown; // used as a property check int maxHeapSizeLimit; int maxLiveLimit; boolean throwOutOfMemory = false; StringSetMatcher includes, excludes; void updateMaxPathValues() { if (stat.heapSize > maxPathHeap) { maxPathHeap = stat.heapSize; } if (stat.nNew > maxPathNew) { maxPathNew = stat.nNew; } if (stat.nReleased > maxPathReleased) { maxPathReleased = stat.nReleased; } int nAlive = stat.nNew - stat.nReleased; if (nAlive > maxPathAlive) { maxPathAlive = nAlive; } } void allocTypeStats (ElementInfo ei) { String typeName = ei.getClassInfo().getName(); TypeStat ts = typeStat.get(typeName); if (ts == null) { ts = new TypeStat(typeName); typeStat.put(typeName, ts); } ts.nAlloc++; } void releaseTypeStats (ElementInfo ei) { String typeName = ei.getClassInfo().getName(); TypeStat ts = typeStat.get(typeName); if (ts != null) { ts.nReleased++; } } public HeapTracker (Config config, JPF jpf) { maxHeapSizeLimit = config.getInt("heap.size_limit", -1); maxLiveLimit = config.getInt("heap.live_limit", -1); throwOutOfMemory = config.getBoolean("heap.throw_exception"); showTypeStats = config.getBoolean("heap.show_types"); maxTypesShown = config.getInt("heap.max_types", 20); includes = StringSetMatcher.getNonEmpty(config.getStringArray("heap.include")); excludes = StringSetMatcher.getNonEmpty(config.getStringArray("heap.exclude")); jpf.addPublisherExtension(ConsolePublisher.class, this); } /******************************************* abstract Property *****/ /** * return 'false' if property is violated */ @Override public boolean check (Search search, VM vm) { if (throwOutOfMemory) { // in this case we don't want to stop the program, but see if it // behaves gracefully - don't report a property violation return true; } else { if ((maxHeapSizeLimit >= 0) && (stat.heapSize > maxHeapSizeLimit)) { return false; } if ((maxLiveLimit >=0) && ((stat.nNew - stat.nReleased) > maxLiveLimit)) { return false; } return true; } } public String getErrorMessage () { return "heap limit exceeded: " + stat.heapSize + " > " + maxHeapSizeLimit; } /******************************************* SearchListener interface *****/ @Override public void searchStarted(Search search) { super.searchStarted(search); updateMaxPathValues(); pathStats.push(stat); initHeap = stat.heapSize; initNew = stat.nNew; initReleased = stat.nReleased; initAlive = initNew - initReleased; stat = (PathStat)stat.clone(); } @Override public void stateAdvanced(Search search) { if (search.isNewState()) { int id = search.getStateId(); if (id > maxState) maxState = id; updateMaxPathValues(); pathStats.push(stat); stat = (PathStat)stat.clone(); nForward++; } } @Override public void stateBacktracked(Search search) { nBacktrack++; if (!pathStats.isEmpty()){ stat = pathStats.pop(); } } /******************************************* PublisherExtension interface ****/ @Override public void publishFinished (Publisher publisher) { PrintWriter pw = publisher.getOut(); publisher.publishTopicStart("heap statistics"); pw.println("heap statistics:"); pw.println(" states: " + maxState); pw.println(" forwards: " + nForward); pw.println(" backtrack: " + nBacktrack); pw.println(); pw.println(" gc cycles: " + nGcTotal); pw.println(); pw.println(" max Objects: " + nElemMax); pw.println(" min Objects: " + nElemMin); pw.println(" avg Objects: " + nElemAv); pw.println(); pw.println(" max% shared: " + pElemSharedMax); pw.println(" min% shared: " + pElemSharedMin); pw.println(" avg% shared: " + pElemSharedAv); pw.println(); pw.println(" max% immutable: " + pElemImmutableMax); pw.println(" min% immutable: " + pElemImmutableMin); pw.println(" avg% immutable: " + pElemImmutableAv); pw.println(); pw.println(" max released: " + nReleasedMax); pw.println(" min released: " + nReleasedMin); pw.println(" avg released: " + nReleasedAv); pw.println(); pw.print( " max path heap (B): " + maxPathHeap); pw.println(" / " + (maxPathHeap - initHeap)); pw.print( " max path alive: " + maxPathAlive); pw.println(" / " + (maxPathAlive - initAlive)); pw.print( " max path new: " + maxPathNew); pw.println(" / " + (maxPathNew - initNew)); pw.print( " max path released: " + maxPathReleased); pw.println(" / " + (maxPathReleased - initReleased)); if (showTypeStats) { pw.println(); pw.println(" type allocation statistics:"); ArrayList<Map.Entry<String,TypeStat>> list = Misc.createSortedEntryList(typeStat, new Comparator<Map.Entry<String,TypeStat>>() { public int compare (Map.Entry<String,TypeStat> e1, Map.Entry<String,TypeStat> e2) { return Integer.signum(e1.getValue().nAlloc - e2.getValue().nAlloc); }}); int i=0; for (Map.Entry<String,TypeStat> e : list) { TypeStat ts = e.getValue(); pw.print(" "); pw.print(String.format("%1$9d : ", ts.nAlloc)); pw.println(ts.typeName); if (i++ > maxTypesShown) { pw.println(" ..."); break; } } } } /******************************************* VMListener interface *********/ @Override public void gcBegin(VM vm) { /** System.out.println(); System.out.println( "----- gc cycle: " + vm.getDynamicArea().getGcNumber() + ", state: " + vm.getStateId()); **/ } @Override public void gcEnd(VM vm) { Heap heap = vm.getHeap(); int n = 0; int nShared = 0; int nImmutable = 0; for (ElementInfo ei : heap.liveObjects()) { n++; if (ei.isShared()) nShared++; if (ei.isImmutable()) nImmutable++; //printElementInfo(ei); } nElemTotal += n; nGcTotal++; if (n > nElemMax) nElemMax = n; if (n < nElemMin) nElemMin = n; int pShared = (nShared * 100) / n; int pImmutable = (nImmutable * 100) / n; if (pShared > pElemSharedMax) pElemSharedMax = pShared; if (pShared < pElemSharedMin) pElemSharedMin = pShared; nSharedTotal += nShared; nImmutableTotal += nImmutable; pElemSharedAv = (nSharedTotal * 100) / nElemTotal; pElemImmutableAv = (nImmutableTotal * 100) / nElemTotal; if (pImmutable > pElemImmutableMax) pElemImmutableMax = pImmutable; if (pImmutable < pElemImmutableMin) pElemImmutableMin = pImmutable; nElemAv = nElemTotal / nGcTotal; nReleasedAv = nReleasedTotal / nGcTotal; if (nReleased > nReleasedMax) nReleasedMax = nReleased; if (nReleased < nReleasedMin) nReleasedMin = nReleased; nReleased = 0; } boolean isRelevantType (ElementInfo ei) { String clsName = ei.getClassInfo().getName(); return StringSetMatcher.isMatch(clsName, includes, excludes); } @Override public void objectCreated(VM vm, ThreadInfo ti, ElementInfo ei) { int idx = ei.getObjectRef(); int line = ti.getLine(); MethodInfo mi = ti.getTopFrameMethodInfo(); SourceRef sr = null; if (!isRelevantType(ei)) { return; } if (mi != null) { ClassInfo mci = mi.getClassInfo(); if (mci != null) { String file = mci.getSourceFileName(); if (file != null) { sr = new SourceRef(file, line); } else { sr = new SourceRef(mci.getName(), line); } } } // means references with null loc are from synthetic methods loc.set(idx, sr); stat.nNew++; stat.heapSize += ei.getHeapSize(); // update the type statistics if (showTypeStats) { allocTypeStats(ei); } // check if we should simulate an OutOfMemoryError if (throwOutOfMemory) { if (((maxHeapSizeLimit >=0) && (stat.heapSize > maxHeapSizeLimit)) || ((maxLiveLimit >=0) && ((stat.nNew - stat.nReleased) > maxLiveLimit))){ vm.getHeap().setOutOfMemory(true); } } } @Override public void objectReleased(VM vm, ThreadInfo ti, ElementInfo ei) { if (!isRelevantType(ei)) { return; } nReleasedTotal++; nReleased++; if (showTypeStats) { releaseTypeStats(ei); } stat.nReleased++; stat.heapSize -= ei.getHeapSize(); } /****************************************** private stuff ******/ protected void printElementInfo(ElementInfo ei) { boolean first = false; System.out.print( ei.getObjectRef()); System.out.print( ": "); System.out.print( ei.getClassInfo().getName()); System.out.print( " ["); if (ei.isShared()) { System.out.print( "shared"); first = false; } if (ei.isImmutable()) { if (!first) System.out.print(' '); System.out.print( "immutable"); } System.out.print( "] "); SourceRef sr = loc.get(ei.getObjectRef()); if (sr != null) { System.out.println(sr); } else { System.out.println("?"); } } static void printUsage () { System.out.println("HeapTracker - a JPF listener tool to report and check heap utilization"); System.out.println("usage: java gov.nasa.jpf.tools.HeapTracker <jpf-options> <heapTracker-options> <class>"); System.out.println(" +heap.size_limit=<num> : report property violation if heap exceeds <num> bytes"); System.out.println(" +heap.live_limit=<num> : report property violation if more than <num> live objects"); System.out.println(" +heap.classes=<regEx> : only report instances of classes matching <regEx>"); System.out.println(" +heap.throw_exception=<bool>: throw a OutOfMemoryError instead of reporting property violation"); } }