1 cananian 1.1.2.1 // ProfileParser.java, created Fri Nov 9 21:30:05 2001 by cananian 2 cananian 1.1.2.1 // Copyright (C) 2000 C. Scott Ananian <cananian@alumni.princeton.edu> 3 cananian 1.1.2.1 // Licensed under the terms of the GNU GPL; see COPYING for details. 4 cananian 1.1.2.1 package harpoon.Analysis.SizeOpt; 5 cananian 1.1.2.1 6 cananian 1.1.2.1 import harpoon.ClassFile.HClass; 7 cananian 1.1.2.1 import harpoon.ClassFile.HField; 8 cananian 1.1.2.1 import harpoon.ClassFile.Linker; 9 cananian 1.1.2.1 import harpoon.ClassFile.Loader; 10 cananian 1.1.2.1 import harpoon.ClassFile.NoSuchClassException; 11 cananian 1.3 import net.cscott.jutil.AggregateMapFactory; 12 cananian 1.3 import net.cscott.jutil.Factories; 13 cananian 1.3 import net.cscott.jutil.GenericMultiMap; 14 cananian 1.3 import net.cscott.jutil.MapSet; 15 cananian 1.3 import net.cscott.jutil.MultiMap; 16 cananian 1.3 import net.cscott.jutil.SetFactory; 17 cananian 1.3 import net.cscott.jutil.Default; 18 cananian 1.1.2.1 import harpoon.Util.ParseUtil; 19 cananian 1.1.2.1 import harpoon.Util.ParseUtil.BadLineException; 20 cananian 1.1.2.1 21 cananian 1.1.2.1 import java.io.IOException; 22 cananian 1.1.2.1 import java.util.Collections; 23 cananian 1.1.2.5 import java.util.HashMap; 24 cananian 1.1.2.1 import java.util.HashSet; 25 cananian 1.1.2.5 import java.util.Iterator; 26 cananian 1.1.2.1 import java.util.LinkedList; 27 cananian 1.1.2.2 import java.util.Map; 28 cananian 1.1.2.5 import java.util.Set; 29 cananian 1.1.2.1 /** 30 cananian 1.1.2.1 * The <code>ProfileParser</code> class parses the output produced 31 cananian 1.1.2.1 * by <code>SizeCounters</code>. 32 cananian 1.1.2.1 * 33 cananian 1.1.2.1 * @author C. Scott Ananian <cananian@alumni.princeton.edu> 34 cananian 1.4 * @version $Id: ProfileParser.java,v 1.4 2004/02/08 03:20:22 cananian Exp $ 35 cananian 1.1.2.1 */ 36 cananian 1.1.2.1 class ProfileParser { 37 cananian 1.1.2.5 // lines are in the format: 'mzf_savedbytes_<classname>: <number>', 38 cananian 1.1.2.5 // 'mzf_alloc_<classname>: <number>', or 'mzf_nonzero_<classname>: <num>' 39 cananian 1.1.2.1 // the line 'MZF START CONSTANT <number>' indicates a new 'mostly-N' 40 cananian 1.1.2.1 // section. 41 cananian 1.1.2.1 42 cananian 1.1.2.1 43 cananian 1.1.2.1 /** Creates a <code>ProfileParser</code>. */ 44 cananian 1.1.2.5 ProfileParser(Linker linker, String resourcePath) { 45 cananian 1.1.2.2 try { 46 cananian 1.1.2.2 ParseUtil.readResource(resourcePath, new ResourceParser(linker)); 47 cananian 1.1.2.2 } catch (IOException ioex) { 48 cananian 1.1.2.3 throw new RuntimeException(ioex.toString()); 49 cananian 1.1.2.2 } 50 cananian 1.1.2.2 } 51 cananian 1.1.2.2 /** Returns a map from 'mostly values' (Integers) to how many 52 cananian 1.1.2.2 * bytes can be saved (Longs). */ 53 cananian 1.1.2.5 Map savedBytesMap(HField hf) { 54 cananian 1.1.2.2 return Collections.unmodifiableMap 55 cananian 1.1.2.5 (((MapSet)savedBytesMap.getValues(hf)).asMap()); 56 cananian 1.1.2.5 } 57 cananian 1.1.2.5 /** Returns a map from 'mostly values' (Integers) to how many 58 cananian 1.1.2.5 * times this field was allocated. */ 59 cananian 1.1.2.5 Map allocMap(HField hf) { 60 cananian 1.1.2.5 return Collections.unmodifiableMap 61 cananian 1.1.2.5 (((MapSet)allocMap.getValues(hf)).asMap()); 62 cananian 1.1.2.5 } 63 cananian 1.1.2.5 /** Returns a map from 'mostly values' (Integers) to how many 64 cananian 1.1.2.5 * times this field was *not* that value. */ 65 cananian 1.1.2.5 Map notMostlyMap(HField hf) { 66 cananian 1.1.2.5 return Collections.unmodifiableMap 67 cananian 1.1.2.5 (((MapSet)notMostlyMap.getValues(hf)).asMap()); 68 cananian 1.1.2.5 } 69 cananian 1.1.2.5 /** Returns <code>true</code> iff there is enough profiling data to 70 cananian 1.1.2.5 * return a valid percentage of allocated fields <code>hf</code> 71 cananian 1.1.2.5 * with the unchanged value <code>mostly</code>. 72 cananian 1.1.2.5 */ 73 cananian 1.1.2.5 boolean isPercentValid(HField hf, int mostly) { 74 cananian 1.1.2.5 Integer MI = new Integer(mostly); 75 cananian 1.1.2.5 Number alloc = (Number) allocMap(hf).get(MI); 76 cananian 1.1.2.5 Number notmostly = (Number) notMostlyMap(hf).get(MI); 77 cananian 1.1.2.5 if (alloc==null || notmostly==null) return false; 78 cananian 1.1.2.5 if (alloc.intValue()==0) return false; 79 cananian 1.1.2.5 return true; 80 cananian 1.1.2.5 } 81 cananian 1.1.2.5 /** For the specified field, returns the percentage of allocations 82 cananian 1.1.2.5 * of the field whose values are *always* the given 'mostly' value. */ 83 cananian 1.1.2.5 double percentIsMostly(HField hf, int mostly) { 84 cananian 1.1.2.5 Integer MI = new Integer(mostly); 85 cananian 1.1.2.5 long alloc = ((Number)allocMap(hf).get(MI)).longValue(); 86 cananian 1.1.2.5 long notmostly = ((Number)notMostlyMap(hf).get(MI)).longValue(); 87 cananian 1.1.2.5 return 100.0-((100.0*notmostly)/(double)alloc); 88 cananian 1.1.2.5 } 89 cananian 1.1.2.5 /** Return a set of <field, mostly val> pairs where the percentage 90 cananian 1.1.2.5 * of allocated fields with mostly val exceeds 'thresholdPercent'. */ 91 cananian 1.1.2.5 Set fieldsAboveThresh(double thresholdPercent) { 92 cananian 1.1.2.5 Set result = new HashSet(); 93 cananian 1.4 for (Object hfO : allocMap.keySet()) { 94 cananian 1.4 HField hf = (HField) hfO; 95 cananian 1.4 for (Object mostlyO : allocMap(hf).keySet()) { 96 cananian 1.4 Number mostly = (Number) mostlyO; 97 cananian 1.1.2.5 if (isPercentValid(hf, mostly.intValue()) && 98 cananian 1.1.2.5 percentIsMostly(hf, mostly.intValue()) > thresholdPercent) 99 cananian 1.1.2.5 result.add(Default.pair(hf, mostly)); 100 cananian 1.1.2.5 } 101 cananian 1.1.2.5 } 102 cananian 1.1.2.5 return Collections.unmodifiableSet(result); 103 cananian 1.1.2.5 } 104 cananian 1.1.2.5 final MultiMap savedBytesMap, allocMap, notMostlyMap; 105 cananian 1.1.2.5 { 106 cananian 1.1.2.5 SetFactory sf = Factories.mapSetFactory(new AggregateMapFactory()); 107 cananian 1.1.2.5 savedBytesMap = new GenericMultiMap(sf); 108 cananian 1.1.2.5 allocMap = new GenericMultiMap(sf); 109 cananian 1.1.2.5 notMostlyMap = new GenericMultiMap(sf); 110 cananian 1.1.2.5 } 111 cananian 1.1.2.5 public String toString() { 112 cananian 1.1.2.5 Map m = new HashMap(); 113 cananian 1.1.2.5 m.put("savedbytes", savedBytesMap); 114 cananian 1.1.2.5 m.put("alloc", allocMap); 115 cananian 1.1.2.5 m.put("nonzero", notMostlyMap); 116 cananian 1.1.2.5 return m.toString(); 117 cananian 1.1.2.1 } 118 cananian 1.1.2.1 class ResourceParser implements ParseUtil.StringParser { 119 cananian 1.1.2.1 final Linker linker; 120 cananian 1.1.2.1 ResourceParser(Linker linker) { this.linker = linker; } 121 cananian 1.1.2.1 int constant = 0; // mutable as we parse. 122 cananian 1.1.2.1 public void parseString(String s) throws BadLineException { 123 cananian 1.1.2.1 // 'MZF START CONSTANT <number>' lines indicate new section. 124 cananian 1.1.2.1 if (s.startsWith("MZF START CONSTANT ")) { 125 cananian 1.1.2.1 String number = 126 cananian 1.1.2.1 s.substring("MZF START CONSTANT ".length()).trim(); 127 cananian 1.1.2.1 try { 128 cananian 1.1.2.1 constant = Integer.parseInt(number); 129 cananian 1.1.2.1 } catch (NumberFormatException e) { 130 cananian 1.1.2.1 throw new BadLineException("Not a number: "+number); 131 cananian 1.1.2.1 } 132 cananian 1.1.2.1 return; 133 cananian 1.1.2.1 } 134 cananian 1.1.2.5 // we accept lines which start with 'mzf_savedbytes_', 135 cananian 1.1.2.5 // 'mzf_alloc_' or 'mzf_nonzero_'. 136 cananian 1.1.2.5 String which_prefix; 137 cananian 1.1.2.5 MultiMap which_map; 138 cananian 1.1.2.5 if (s.startsWith("mzf_savedbytes_")) { 139 cananian 1.1.2.5 which_prefix="mzf_savedbytes_"; which_map = savedBytesMap; 140 cananian 1.1.2.5 } else if (s.startsWith("mzf_alloc_")) { 141 cananian 1.1.2.5 which_prefix="mzf_alloc_"; which_map = allocMap; 142 cananian 1.1.2.5 } else if (s.startsWith("mzf_nonzero_")) { 143 cananian 1.1.2.5 which_prefix="mzf_nonzero_"; which_map = notMostlyMap; 144 cananian 1.1.2.5 } else return; // wrong prefix; ignore this line. 145 cananian 1.1.2.1 int colon = s.indexOf(':'); 146 cananian 1.1.2.1 if (colon<0) return; // ignore lines with no colon. 147 cananian 1.1.2.1 if (!(colon+1<s.length())) 148 cananian 1.1.2.1 throw new BadLineException("No number part."); 149 cananian 1.1.2.5 String fieldname = s.substring(which_prefix.length(), colon); 150 cananian 1.1.2.1 String number = s.substring(colon+1).trim(); 151 cananian 1.1.2.1 long val; 152 cananian 1.1.2.1 try { 153 cananian 1.1.2.1 val = Long.parseLong(number); 154 cananian 1.1.2.1 } catch (NumberFormatException e) { 155 cananian 1.1.2.1 throw new BadLineException("Not a number: "+number); 156 cananian 1.1.2.1 } 157 cananian 1.1.2.1 int under = fieldname.lastIndexOf('_'); 158 cananian 1.1.2.1 if (under<0) 159 cananian 1.1.2.1 throw new BadLineException("No field part: "+fieldname); 160 cananian 1.1.2.4 try { 161 cananian 1.1.2.4 HField hf = parseField(fieldname); 162 cananian 1.1.2.5 which_map.add(hf, Default.entry 163 cananian 1.1.2.4 (new Integer(constant), new Long(val))); 164 cananian 1.1.2.5 // in perl terms: $which_map{$hf}{$constant}=$val; 165 cananian 1.1.2.4 } catch (BadLineException ble) { 166 cananian 1.1.2.4 /* assume that this field has been removed. */ 167 cananian 1.1.2.4 System.err.println("WARNING: can't find "+fieldname); 168 cananian 1.1.2.4 } 169 cananian 1.1.2.1 } 170 cananian 1.1.2.1 // any thing will do here. just something to initialize with. 171 cananian 1.1.2.1 private String last_success = "java.lang.String.offset"; 172 cananian 1.1.2.1 HField parseField(String fieldname) throws BadLineException { 173 cananian 1.1.2.1 // start by assuming that all '_'s should be '.'s. 174 cananian 1.1.2.1 fieldname = fieldname.replace('_', '.'); 175 cananian 1.1.2.1 // now we're going to convert the .'s back to underscores 176 cananian 1.1.2.1 // one-by-one to try to come up with a working fieldname. 177 cananian 1.1.2.1 HashSet tried = new HashSet(); 178 cananian 1.1.2.1 LinkedList ll = new LinkedList(Collections.singleton(fieldname)); 179 cananian 1.1.2.1 // efficiency hack: if normalized fieldname starts the same way 180 cananian 1.1.2.1 // as last_success, seed this search with the matching part 181 cananian 1.1.2.1 // of last_success. (try class, package, etc) 182 cananian 1.1.2.1 for (int dot=last_success.lastIndexOf('.'); dot>0; 183 cananian 1.1.2.1 dot=last_success.lastIndexOf('.' , dot-1)) { 184 cananian 1.1.2.1 String piece = last_success.substring(0, dot); 185 cananian 1.1.2.1 if (fieldname.startsWith(piece.replace('_','.'))) { 186 cananian 1.1.2.1 // this is the seed we'll start with. 187 cananian 1.1.2.1 String seed = piece + fieldname.substring(piece.length()); 188 cananian 1.1.2.1 ll.addFirst(seed); 189 cananian 1.1.2.1 break; 190 cananian 1.1.2.1 } 191 cananian 1.1.2.1 } 192 cananian 1.1.2.1 // okay, first in first out queue. 193 cananian 1.1.2.1 while (!ll.isEmpty()) { 194 cananian 1.1.2.1 String candidate = (String) ll.removeFirst(); 195 cananian 1.1.2.1 // try it. 196 cananian 1.1.2.1 HField hf = tryProper(candidate); 197 cananian 1.1.2.1 if (hf!=null) { 198 cananian 1.1.2.1 // it's good! cache this success. 199 cananian 1.1.2.1 last_success = 200 cananian 1.1.2.1 hf.getDeclaringClass().getName()+"."+hf.getName(); 201 cananian 1.1.2.1 return hf; // we're done! 202 cananian 1.1.2.1 } 203 cananian 1.1.2.1 // nope. change one of the dots to a '_' and push 204 cananian 1.1.2.1 // back on the list. 205 cananian 1.1.2.1 StringBuffer sb = new StringBuffer(candidate); 206 cananian 1.1.2.1 int dot=candidate.indexOf('.'); 207 cananian 1.1.2.1 while (dot>=0) { 208 cananian 1.1.2.1 // change this dot to an underscore. 209 cananian 1.1.2.1 sb.setCharAt(dot, '_'); 210 cananian 1.1.2.1 String s = sb.toString(); 211 cananian 1.1.2.1 // check whether this has been tried already. 212 cananian 1.1.2.1 if (tried.add(s)) 213 cananian 1.1.2.1 // hasn't been done. add it to *end of* list. 214 cananian 1.1.2.1 ll.addLast(s); 215 cananian 1.1.2.1 // okay, reset to an dot. 216 cananian 1.1.2.1 sb.setCharAt(dot, '.'); 217 cananian 1.1.2.1 // advance to the next dot. 218 cananian 1.1.2.1 if (!(dot+1 < candidate.length())) break; 219 cananian 1.1.2.1 dot=candidate.indexOf('.', dot+1); 220 cananian 1.1.2.1 } 221 cananian 1.1.2.1 } 222 cananian 1.1.2.1 // oh, no! we ran out of candidates. this must be a bad field. 223 cananian 1.1.2.1 fieldname = fieldname.replace('.', '_'); // convert back to _ 224 cananian 1.1.2.1 throw new BadLineException("Field not found: "+fieldname); 225 cananian 1.1.2.1 } 226 cananian 1.1.2.1 HField tryProper(String properField) { 227 cananian 1.1.2.1 int dot = properField.lastIndexOf('.'); 228 cananian 1.1.2.1 if (dot<0 || !(dot+1<properField.length())) return null; 229 cananian 1.1.2.1 String fieldPart = properField.substring(dot+1); 230 cananian 1.1.2.1 String classPart = properField.substring(0, dot); 231 cananian 1.1.2.1 try { 232 cananian 1.1.2.1 HClass hc = linker.forName(classPart); 233 cananian 1.1.2.1 HField hf = hc.getDeclaredField(fieldPart); 234 cananian 1.1.2.1 return hf; // success! 235 cananian 1.1.2.1 } catch (NoSuchFieldError ex) { 236 cananian 1.1.2.1 return null; // not a valid field. 237 cananian 1.1.2.1 } catch (NoSuchClassException ex) { 238 cananian 1.1.2.1 return null; // not a valid class. 239 cananian 1.1.2.1 } 240 cananian 1.1.2.1 } 241 cananian 1.1.2.1 } 242 cananian 1.1.2.1 //////////////////////////////////////////////////// 243 cananian 1.1.2.1 // tester. 244 cananian 1.1.2.1 public static void main(String[] args) throws IOException { 245 cananian 1.1.2.1 System.out.println(new ProfileParser(Loader.systemLinker, args[0])); 246 cananian 1.1.2.1 } 247 cananian 1.2 }