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}