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     }