001// DtraceNonceFixer.java
002
003package daikon.tools;
004
005import java.io.BufferedReader;
006import java.io.IOException;
007import java.io.PrintWriter;
008import java.io.UncheckedIOException;
009import java.util.StringTokenizer;
010import org.plumelib.util.FilesPlume;
011import org.plumelib.util.StringsPlume;
012
013/**
014 * This tool fixes a Dtrace file whose invocation nonces became inaccurate as a result of a {@code
015 * cat} command combining multiple dtrace files. Every dtrace file besides the first will have the
016 * invocation nonces increased by the "correct" amount, determined in the following way:
017 *
018 * <p>Keep track of all the nonces you see and maintain a record of the highest nonce observed. The
019 * next time you see a '0' valued nonce that is not part of an EXIT program point, then you know you
020 * have reached the beginning of the next dtrace file. Use that as the number to add to the
021 * remaining nonces and repeat. This should only require one pass through the file.
022 */
023public class DtraceNonceFixer {
024
025  /** The system-specific line separator. */
026  private static final String lineSep = System.lineSeparator();
027
028  /** The usage message for this program. */
029  private static String usage =
030      StringsPlume.joinLines(
031          "Usage: DtraceNonceFixer FILENAME",
032          "Modifies dtrace file FILENAME so that the invocation nonces are consistent.",
033          "The output file will be FILENAME_fixed and another output included",
034          "nonces for OBJECT and CLASS invocations called FILENAME_all_fixed");
035
036  public static void main(String[] args) {
037    try {
038      mainHelper(args);
039    } catch (daikon.Daikon.DaikonTerminationException e) {
040      daikon.Daikon.handleDaikonTerminationException(e);
041    }
042  }
043
044  /**
045   * This does the work of {@link #main(String[])}, but it never calls System.exit, so it is
046   * appropriate to be called progrmmatically.
047   *
048   * @param args command-line arguments, like those of {@link #main}
049   */
050  public static void mainHelper(final String[] args) {
051    if (args.length != 1) {
052      throw new daikon.Daikon.UserError(usage);
053    }
054
055    String outputFilename =
056        args[0].endsWith(".gz") ? (args[0] + "_fixed.gz") : (args[0] + "_fixed");
057
058    try (BufferedReader br1 = FilesPlume.newBufferedFileReader(args[0]);
059        PrintWriter out1 = new PrintWriter(FilesPlume.newBufferedFileWriter(outputFilename))) {
060
061      // maxNonce - the biggest nonce ever found in the file
062      // correctionFactor - the amount to add to each observed nonce
063      int maxNonce = 0;
064      int correctionFactor = 0;
065      boolean first = true;
066      while (br1.ready()) {
067        String nextInvo = grabNextInvocation(br1);
068        int non = peekNonce(nextInvo);
069        // The first legit 0 nonce will have an ENTER and EXIT
070        // seeing a 0 means we have reached the next file
071        if (non == 0 && nextInvo.indexOf("EXIT") == -1) {
072          if (first) {
073            // on the first file, keep the first nonce as 0
074            first = false;
075          } else {
076            correctionFactor = maxNonce + 1;
077          }
078        }
079        int newNonce = non + correctionFactor;
080        maxNonce = Math.max(maxNonce, newNonce);
081        if (non != -1) {
082          out1.println(spawnWithNewNonce(nextInvo, newNonce));
083        } else {
084          out1.println(nextInvo);
085        }
086      }
087      out1.flush();
088
089      // now go back and add the OBJECT and CLASS invocations
090      String allFixedFilename =
091          outputFilename.endsWith(".gz") ? (args[0] + "_all_fixed.gz") : (args[0] + "_all_fixed");
092
093      try (BufferedReader br2 = FilesPlume.newBufferedFileReader(outputFilename);
094          PrintWriter out2 = new PrintWriter(FilesPlume.newBufferedFileWriter(allFixedFilename))) {
095
096        while (br2.ready()) {
097          String nextInvo = grabNextInvocation(br2);
098          int non = peekNonce(nextInvo);
099          // if there is no nonce at this point it must be an OBJECT
100          // or a CLASS invocation
101          if (non == -1) {
102            out2.println(spawnWithNewNonce(nextInvo, ++maxNonce));
103          } else {
104            out2.println(nextInvo);
105          }
106        }
107
108        out2.flush();
109      }
110    } catch (IOException e) {
111      throw new UncheckedIOException(e);
112    }
113  }
114
115  /**
116   * Returns a String representing an invocation with the line directly under
117   * 'this_invocation_nonce' changed to 'newNone'. If the String 'this_invocation_nonce' is not
118   * found, then creates a line 'this_invocation_nonce' directly below the program point name and a
119   * line containing newNonce directly under that.
120   */
121  private static String spawnWithNewNonce(String invo, int newNonce) {
122
123    //    System.out.println (invo);
124
125    StringBuilder sb = new StringBuilder();
126    StringTokenizer st = new StringTokenizer(invo, lineSep);
127
128    if (!st.hasMoreTokens()) {
129      return sb.toString();
130    }
131
132    // First line is the program point name
133    sb.append(st.nextToken()).append(lineSep);
134
135    // There is a chance that this is not really an invocation
136    // but a EOF shutdown hook instead.
137    if (!st.hasMoreTokens()) {
138      return sb.toString();
139    }
140
141    // See if the second line is the nonce
142    String line = st.nextToken();
143    if (line.equals("this_invocation_nonce")) {
144      // modify the next line to include the new nonce
145      sb.append(line).append(lineSep).append(newNonce).append(lineSep);
146      // throw out the next token, because it will be the old nonce
147      st.nextToken();
148    } else {
149      // otherwise create the required this_invocation_nonce line
150      sb.append("this_invocation_nonce" + lineSep).append(newNonce).append(lineSep);
151    }
152
153    while (st.hasMoreTokens()) {
154      sb.append(st.nextToken()).append(lineSep);
155    }
156
157    return sb.toString();
158  }
159
160  /**
161   * Returns the nonce of the invocation 'invo', or -1 if the String 'this_invocation_nonce' is not
162   * found in {@code invo}.
163   */
164  private static int peekNonce(String invo) {
165    StringTokenizer st = new StringTokenizer(invo, lineSep);
166    while (st.hasMoreTokens()) {
167      String line = st.nextToken();
168      if (line.equals("this_invocation_nonce")) {
169        return Integer.parseInt(st.nextToken());
170      }
171    }
172    return -1;
173  }
174
175  /**
176   * Grabs the next invocation out of the dtrace buffer and returns a String with endline characters
177   * preserved. This method will return a single blank line if the original dtrace file contained
178   * consecutive blank lines.
179   */
180  private static String grabNextInvocation(BufferedReader br) throws IOException {
181    StringBuilder sb = new StringBuilder();
182    while (br.ready()) {
183      String line = br.readLine();
184      assert line != null; // because br.ready() = true
185      line = line.trim();
186      if (line.equals("")) {
187        break;
188      }
189      sb.append(line).append(lineSep);
190    }
191    return sb.toString();
192  }
193}