001// TraceSelect.java 002package daikon.tools; 003 004import java.io.File; 005import java.io.IOException; 006import java.io.PrintWriter; 007import java.util.ArrayList; 008import java.util.Comparator; 009import java.util.Iterator; 010import java.util.List; 011import java.util.Locale; 012import java.util.Random; 013import java.util.StringTokenizer; 014import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 015import org.checkerframework.checker.nullness.qual.RequiresNonNull; 016import org.checkerframework.dataflow.qual.Pure; 017import org.plumelib.util.FilesPlume; 018import org.plumelib.util.MultiRandSelector; 019import org.plumelib.util.StringsPlume; 020 021/** 022 * The TraceSelect tool creates several small subsets of the data by randomly selecting parts of the 023 * original trace file. 024 */ 025public class TraceSelect { 026 027 public static boolean CLEAN = true; 028 public static boolean INCLUDE_UNRETURNED = false; 029 public static boolean DO_DIFFS = false; 030 031 private static int num_reps; 032 033 private static @MonotonicNonNull String fileName = null; 034 035 // Just a quick command line cache 036 // ... but I think it would it be better to pass args to invokeDaikon 037 // rather than introducing this variable. 038 private static String @MonotonicNonNull [] argles; 039 // // stores the invocations in Strings 040 // private static ArrayList invokeBuffer; 041 042 private static int numPerSample; 043 044 // always set to non-null by mainHelper 045 private static @MonotonicNonNull Random randObj; 046 047 private static int daikonArgStart = 0; 048 049 // This allows us to simply call MultiDiff 050 // with the same files we just created. 051 // Always set to non-null by mainHelper 052 private static String @MonotonicNonNull [] sampleNames; 053 054 /** The usage message for this program. */ 055 private static final String usage = 056 StringsPlume.joinLines( 057 "USAGE: TraceSelect num_reps sample_size [options] [Daikon-args]...", 058 "Example: java TraceSelect 20 10 -NOCLEAN -INCLUDE_UNRETURNED-SEED 1000 foo.dtrace" 059 + " foo2.dtrace foo.decls RatPoly.decls foo3.dtrace"); 060 061 /** 062 * The entry point of TraceSelect 063 * 064 * @param args command-line arguments 065 */ 066 public static void main(String[] args) { 067 try { 068 mainHelper(args); 069 } catch (daikon.Daikon.DaikonTerminationException e) { 070 daikon.Daikon.handleDaikonTerminationException(e); 071 } 072 } 073 074 /** 075 * This does the work of {@link #main(String[])}, but it never calls System.exit, so it is 076 * appropriate to be called progrmmatically. 077 * 078 * @param args command-line arguments, like those of {@link #main} 079 */ 080 public static void mainHelper(final String[] args) { 081 argles = args; 082 if (args.length == 0) { 083 throw new daikon.Daikon.UserError("No arguments found." + daikon.Daikon.lineSep + usage); 084 } 085 086 num_reps = Integer.parseInt(args[0]); 087 numPerSample = Integer.parseInt(args[1]); 088 089 // process optional switches 090 // also deduce index of arg for Daikon 091 boolean knowArgStart = false; 092 for (int i = 2; i < args.length; i++) { 093 // allows seed setting 094 if (args[i].toUpperCase(Locale.ENGLISH).equals("-SEED")) { 095 if (i + 1 >= args.length) { 096 throw new daikon.Daikon.UserError("-SEED options requires argument"); 097 } 098 randObj = new Random(Long.parseLong(args[++i])); 099 daikonArgStart = i + 1; 100 } 101 102 // NOCLEAN argument will leave the trace samples even after 103 // the invariants from these samples have been generated 104 else if (args[i].toUpperCase(Locale.ENGLISH).equals("-NOCLEAN")) { 105 CLEAN = false; 106 daikonArgStart = i + 1; 107 } 108 109 // INCLUDE_UNRETURNED option will allow selecting method invocations 110 // that entered the method successfully but did not exit normally; 111 // either from a thrown Exception or abnormal termination. 112 else if (args[i].toUpperCase(Locale.ENGLISH).equals("-INCLUDE_UNRETURNED")) { 113 INCLUDE_UNRETURNED = true; 114 daikonArgStart = i + 1; 115 } 116 117 // DO_DIFFS will create an spinfo file for generating 118 // conditional invariants and implications by running 119 // daikon.diff.Diff over each of the samples and finding 120 // properties that appear in some but not all of the 121 // samples. 122 else if (args[i].toUpperCase(Locale.ENGLISH).equals("-DO_DIFFS")) { 123 DO_DIFFS = true; 124 daikonArgStart = i + 1; 125 } 126 127 // TODO: The current implementation assumes that a decls 128 // or dtrace file will be the first of the Daikon arguments, 129 // marking the end of the TraceSelect arguments. That is 130 // not necessarily true, especially in cases when someone 131 // uses a Daikon argument such as "--noheirarchy" or "--format java" 132 // and the manual examples place the arguments before any dtrace 133 // or decls arguments. 134 135 // For now, only the first dtrace file will be sampled 136 else if (args[i].endsWith(".dtrace")) { 137 if (fileName == null) { 138 fileName = args[i]; 139 } else { 140 throw new daikon.Daikon.UserError("Only 1 dtrace file for input allowed"); 141 } 142 143 if (!knowArgStart) { 144 daikonArgStart = i; 145 knowArgStart = true; 146 } 147 } else if (args[i].endsWith(".decls")) { 148 if (!knowArgStart) { 149 daikonArgStart = i; 150 knowArgStart = true; 151 } 152 } 153 } 154 155 // if no seed provided, use default Random() constructor 156 if (randObj == null) { 157 randObj = new Random(); 158 } 159 160 sampleNames = new String[num_reps + 1]; 161 sampleNames[0] = "-p"; 162 163 if (fileName == null) { 164 throw new daikon.Daikon.UserError("No .dtrace file name specified"); 165 } 166 167 try { 168 169 // invokeBuffer = new ArrayList(); 170 // fileName = args[1]; 171 172 System.out.println("*******Processing********"); 173 174 // Have to call the DtraceNonceDoctor 175 // to avoid the broken Dtrace from 176 // using a command-line 'cat' that 177 // results in repeat nonces 178 /* 179 String[] doctorArgs = new String[1]; 180 doctorArgs[0] = fileName; 181 DtraceNonceDoctor.main (doctorArgs ); 182 Runtime.getRuntime().exec ("mv " + doctorArgs[0] + "_fixed " + 183 doctorArgs[0]); 184 */ 185 186 while (num_reps > 0) { 187 188 List<String> al = new ArrayList<>(); 189 try (DtracePartitioner dec = new DtracePartitioner(fileName)) { 190 MultiRandSelector<String> mrs = new MultiRandSelector<>(numPerSample, dec); 191 192 while (dec.hasNext()) { 193 mrs.accept(dec.next()); 194 } 195 196 for (Iterator<String> i = mrs.valuesIter(); i.hasNext(); ) { 197 al.add(i.next()); 198 } 199 200 List<String> al_tmp = dec.patchValues(al, INCLUDE_UNRETURNED); 201 al = al_tmp; 202 } 203 204 String filePrefix = calcOut(fileName); 205 206 // gotta do num_reps - 1 because of "off by one" 207 // but now add a '-p' in the front so it's all good 208 sampleNames[num_reps] = filePrefix + ".inv"; 209 210 try (PrintWriter pwOut = new PrintWriter(FilesPlume.newBufferedFileWriter(filePrefix))) { 211 for (String toPrint : al) { 212 pwOut.println(toPrint); 213 } 214 pwOut.flush(); 215 } 216 217 invokeDaikon(filePrefix); 218 219 // cleanup the mess 220 if (CLEAN) { 221 Runtime.getRuntime().exec(new String[] {"rm", filePrefix}); 222 } 223 224 num_reps--; 225 } 226 227 if (DO_DIFFS) { 228 // histograms 229 // daikon.diff.Diff.main (sampleNames); 230 231 // spinfo format 232 daikon.diff.MultiDiff.main(sampleNames); 233 } 234 235 // cleanup the mess! 236 for (int j = 0; j < sampleNames.length; j++) { 237 if (CLEAN) { 238 Runtime.getRuntime().exec(new String[] {"rm", sampleNames[j]}); 239 } 240 } 241 242 } catch (Exception e) { 243 throw new Error(e); 244 } 245 } 246 247 @RequiresNonNull("argles") 248 private static void invokeDaikon(String dtraceName) throws IOException { 249 250 System.out.println("Created file: " + dtraceName); 251 252 // this part adds on the rest of the decls files 253 ArrayList<String> daikonArgsList = new ArrayList<>(); 254 daikonArgsList.add(dtraceName); 255 daikonArgsList.add("-o"); 256 daikonArgsList.add(dtraceName + ".inv"); 257 258 // find all the Daikon args except for the original 259 // single dtrace file. 260 for (int i = daikonArgStart; i < argles.length; i++) { 261 if (argles[i].endsWith(".dtrace")) { 262 continue; 263 } 264 daikonArgsList.add(argles[i]); 265 } 266 267 // create an array to store the Strings in daikonArgsList 268 String[] daikonArgs = daikonArgsList.toArray(new String[0]); 269 270 // initializes daikon again or else an exception is thrown 271 reinitializeDaikon(); 272 daikon.Daikon.main(daikonArgs); 273 // Run: java daikon.PrintInvariants dtraceName.inv > dtraceName.txt 274 ProcessBuilder pb = new ProcessBuilder("java", "daikon.PrintInvariants", dtraceName + ".inv"); 275 pb.redirectOutput(new File(dtraceName + ".txt")); 276 Process p = pb.start(); 277 try { 278 p.waitFor(); 279 } catch (InterruptedException e) { 280 // do nothing 281 } 282 } 283 284 private static void reinitializeDaikon() { 285 daikon.Daikon.inv_file = null; 286 } 287 288 private static String calcOut(String strFileName) { 289 StringBuilder product = new StringBuilder(); 290 int index = strFileName.indexOf('.'); 291 if (index >= 0) { 292 product.append(strFileName.substring(0, index)); 293 product.append(num_reps); 294 if (index != strFileName.length()) { 295 product.append(strFileName.substring(index)); 296 } 297 } else { 298 product.append(strFileName).append("2"); 299 } 300 return product.toString(); 301 } 302} 303 304// I don't think any of this is used anymore... 305// Now all of the random selection comes from the 306// classes in plume. 307 308class InvocationComparator implements Comparator<String> { 309 /** Requires: s1 and s2 are String representations of invocations from a tracefile. */ 310 @Pure 311 @Override 312 public int compare(String s1, String s2) { 313 if (s1 == s2) { 314 return 0; 315 } 316 317 // sorts first by program point 318 int pptCompare = 319 s1.substring(0, s1.indexOf(":::")).compareTo(s2.substring(0, s2.indexOf(":::"))); 320 if (pptCompare != 0) { 321 return pptCompare; 322 } 323 324 // next sorts based on the other stuff 325 int nonce1 = getNonce(s1); 326 int nonce2 = getNonce(s2); 327 int type1 = getType(s1); 328 int type2 = getType(s2); 329 // This makes sure nounce takes priority, ties are broken 330 // so that ENTER comes before EXIT for the same program point 331 return 3 * (nonce1 - nonce2) + (type1 - type2); 332 } 333 334 private int getNonce(String s1) { 335 if (s1.indexOf("OBJECT") != -1 || s1.indexOf("CLASS") != -1) { 336 // it's ok, no chance of overflow wrapa round 337 return Integer.MAX_VALUE; 338 } 339 StringTokenizer st = new StringTokenizer(s1); 340 st.nextToken(); 341 st.nextToken(); 342 return Integer.parseInt(st.nextToken()); 343 } 344 345 private int getType(String s1) { 346 // we want ENTER to come before EXIT 347 if (s1.indexOf("CLASS") != -1) { 348 return -1; 349 } 350 if (s1.indexOf("OBJECT") != -1) { 351 return 0; 352 } 353 if (s1.indexOf("ENTER") != -1) { 354 return 1; 355 } 356 if (s1.indexOf("EXIT") != -1) { 357 return 2; 358 } 359 System.out.println("ERROR" + s1); 360 return 0; 361 } 362}