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