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}