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}