Coverage Report - kg.apc.jmeter.reporters.FlexibleFileWriter
 
Classes in this File Line Coverage Branch Coverage Complexity
FlexibleFileWriter
90%
154/170
79%
53/67
3
 
 1  
 // TODO: add startTimeSec - integer epoch seconds only - why startTime does not apply?
 2  
 // TODO: buffer file writes to bigger chunks?
 3  
 package kg.apc.jmeter.reporters;
 4  
 
 5  
 import java.io.FileNotFoundException;
 6  
 import java.io.FileOutputStream;
 7  
 import java.io.IOException;
 8  
 import java.io.Serializable;
 9  
 import java.nio.ByteBuffer;
 10  
 import java.nio.channels.FileChannel;
 11  
 import java.nio.channels.FileLock;
 12  
 import java.nio.charset.Charset;
 13  
 import java.util.ArrayList;
 14  
 import java.util.Arrays;
 15  
 
 16  
 import kg.apc.jmeter.JMeterPluginsUtils;
 17  
 import org.apache.jmeter.engine.util.NoThreadClone;
 18  
 import org.apache.jmeter.reporters.AbstractListenerElement;
 19  
 import org.apache.jmeter.reporters.ResultCollector;
 20  
 import org.apache.jmeter.samplers.Remoteable;
 21  
 import org.apache.jmeter.samplers.SampleEvent;
 22  
 import org.apache.jmeter.samplers.SampleListener;
 23  
 import org.apache.jmeter.samplers.SampleResult;
 24  
 import org.apache.jmeter.testelement.TestStateListener;
 25  
 import org.apache.jmeter.util.JMeterUtils;
 26  
 import org.apache.jorphan.logging.LoggingManager;
 27  
 import org.apache.log.Logger;
 28  
 
 29  
 /**
 30  
  * @see ResultCollector
 31  
  */
 32  
 public class FlexibleFileWriter
 33  
         extends AbstractListenerElement
 34  
         implements SampleListener, Serializable,
 35  
         TestStateListener, Remoteable, NoThreadClone {
 36  
 
 37  
     public static final String AVAILABLE_FIELDS = "isSuccsessful "
 38  
             + "startTime endTime "
 39  
             + "sentBytes receivedBytes "
 40  
             + "responseTime latency "
 41  
             + "responseCode responseMessage "
 42  
             + "isFailed " // surrogates
 43  
             + "threadName sampleLabel "
 44  
             + "startTimeMillis endTimeMillis "
 45  
             + "responseTimeMicros latencyMicros "
 46  
             + "requestData responseData responseHeaders "
 47  
             + "threadsCount requestHeaders connectTime";
 48  1
     private static final Logger log = LoggingManager.getLoggerForClass();
 49  
     private static final String OVERWRITE = "overwrite";
 50  
     private static final String FILENAME = "filename";
 51  
     private static final String COLUMNS = "columns";
 52  
     private static final String HEADER = "header";
 53  
     private static final String FOOTER = "footer";
 54  
     private static final String VAR_PREFIX = "variable#";
 55  
     private static final String WRITE_BUFFER_LEN_PROPERTY = "kg.apc.jmeter.reporters.FFWBufferSize";
 56  26
     private final int writeBufferSize = JMeterUtils.getPropDefault(WRITE_BUFFER_LEN_PROPERTY, 1024 * 10);
 57  
     protected volatile FileChannel fileChannel;
 58  
     private int[] compiledVars;
 59  
     private int[] compiledFields;
 60  
     private ByteBuffer[] compiledConsts;
 61  26
     private ArrayList<String> availableFieldNames = new ArrayList<String>(Arrays.asList(AVAILABLE_FIELDS.trim().split(" ")));
 62  1
     private static final byte[] b1 = "1".getBytes();
 63  1
     private static final byte[] b0 = "0".getBytes();
 64  
 
 65  1
     protected static final String ENCODING = JMeterUtils.getPropDefault("sampleresult.default.encoding", SampleResult.DEFAULT_HTTP_ENCODING);
 66  1
     private static final Charset CHARSET = Charset.forName(ENCODING);
 67  
 
 68  
     public FlexibleFileWriter() {
 69  26
         super();
 70  26
     }
 71  
 
 72  
     @Override
 73  
     public void sampleStarted(SampleEvent e) {
 74  1
     }
 75  
 
 76  
     @Override
 77  
     public void sampleStopped(SampleEvent e) {
 78  1
     }
 79  
 
 80  
     @Override
 81  
     public void testStarted() {
 82  10
         compileColumns();
 83  
         try {
 84  10
             openFile();
 85  2
         } catch (FileNotFoundException ex) {
 86  2
             log.error("Cannot open file " + getFilename(), ex);
 87  0
         } catch (IOException ex) {
 88  0
             log.error("Cannot write file header " + getFilename(), ex);
 89  10
         }
 90  10
     }
 91  
 
 92  
     @Override
 93  
     public void testStarted(String host) {
 94  2
         testStarted();
 95  2
     }
 96  
 
 97  
     @Override
 98  
     public void testEnded() {
 99  8
         closeFile();
 100  8
     }
 101  
 
 102  
     @Override
 103  
     public void testEnded(String host) {
 104  1
         testEnded();
 105  1
     }
 106  
 
 107  
     public void setFilename(String name) {
 108  12
         setProperty(FILENAME, name);
 109  12
     }
 110  
 
 111  
     public String getFilename() {
 112  15
         return getPropertyAsString(FILENAME);
 113  
     }
 114  
 
 115  
     public void setColumns(String cols) {
 116  8
         setProperty(COLUMNS, cols);
 117  8
     }
 118  
 
 119  
     public String getColumns() {
 120  22
         return getPropertyAsString(COLUMNS);
 121  
     }
 122  
 
 123  
     public boolean isOverwrite() {
 124  13
         return getPropertyAsBoolean(OVERWRITE, false);
 125  
     }
 126  
 
 127  
     public void setOverwrite(boolean ov) {
 128  3
         setProperty(OVERWRITE, ov);
 129  3
     }
 130  
 
 131  
     public void setFileHeader(String str) {
 132  3
         setProperty(HEADER, str);
 133  3
     }
 134  
 
 135  
     public String getFileHeader() {
 136  11
         return getPropertyAsString(HEADER);
 137  
     }
 138  
 
 139  
     public void setFileFooter(String str) {
 140  3
         setProperty(FOOTER, str);
 141  3
     }
 142  
 
 143  
     public String getFileFooter() {
 144  10
         return getPropertyAsString(FOOTER);
 145  
     }
 146  
 
 147  
     /**
 148  
      * making this once to be efficient and avoid manipulating strings in switch
 149  
      * operators
 150  
      */
 151  
     private void compileColumns() {
 152  10
         log.debug("Compiling columns string: " + getColumns());
 153  10
         String[] chunks = JMeterPluginsUtils.replaceRNT(getColumns()).split("\\|");
 154  10
         log.debug("Chunks " + chunks.length);
 155  10
         compiledFields = new int[chunks.length];
 156  10
         compiledVars = new int[chunks.length];
 157  10
         compiledConsts = new ByteBuffer[chunks.length];
 158  66
         for (int n = 0; n < chunks.length; n++) {
 159  56
             int fieldID = availableFieldNames.indexOf(chunks[n]);
 160  56
             if (fieldID >= 0) {
 161  
                 //log.debug(chunks[n] + " field id: " + fieldID);
 162  34
                 compiledFields[n] = fieldID;
 163  
             } else {
 164  22
                 compiledFields[n] = -1;
 165  22
                 compiledVars[n] = -1;
 166  22
                 if (chunks[n].contains(VAR_PREFIX)) {
 167  3
                     log.debug(chunks[n] + " is sample variable");
 168  3
                     String varN = chunks[n].substring(VAR_PREFIX.length());
 169  
                     try {
 170  3
                         compiledVars[n] = Integer.parseInt(varN);
 171  2
                     } catch (NumberFormatException e) {
 172  2
                         log.error("Seems it is not variable spec: " + chunks[n]);
 173  2
                         compiledConsts[n] = ByteBuffer.wrap(chunks[n].getBytes());
 174  1
                     }
 175  3
                 } else {
 176  19
                     log.debug(chunks[n] + " is const");
 177  19
                     if (chunks[n].length() == 0) {
 178  
                         //log.debug("Empty const, treated as |");
 179  6
                         chunks[n] = "|";
 180  
                     }
 181  
 
 182  19
                     compiledConsts[n] = ByteBuffer.wrap(chunks[n].getBytes());
 183  
                 }
 184  
             }
 185  
         }
 186  10
     }
 187  
 
 188  
     protected void openFile() throws IOException {
 189  11
         String filename = getFilename();
 190  11
         FileOutputStream fos = new FileOutputStream(filename, !isOverwrite());
 191  9
         fileChannel = fos.getChannel();
 192  
 
 193  9
         String header = JMeterPluginsUtils.replaceRNT(getFileHeader());
 194  9
         if (!header.isEmpty()) {
 195  1
             syncWrite(ByteBuffer.wrap(header.getBytes()));
 196  
         }
 197  9
     }
 198  
 
 199  
     private synchronized void closeFile() {
 200  8
         if (fileChannel != null && fileChannel.isOpen()) {
 201  
             try {
 202  8
                 String footer = JMeterPluginsUtils.replaceRNT(getFileFooter());
 203  8
                 if (!footer.isEmpty()) {
 204  0
                     syncWrite(ByteBuffer.wrap(footer.getBytes()));
 205  
                 }
 206  
 
 207  8
                 fileChannel.force(false);
 208  8
                 fileChannel.close();
 209  0
             } catch (IOException ex) {
 210  0
                 log.error("Failed to close file: " + getFilename(), ex);
 211  8
             }
 212  
         }
 213  8
     }
 214  
 
 215  
     @Override
 216  
     public void sampleOccurred(SampleEvent evt) {
 217  32
         if (fileChannel == null || !fileChannel.isOpen()) {
 218  0
             if (log.isWarnEnabled()) {
 219  0
                 log.warn("File writer is closed! Maybe test has already been stopped");
 220  
             }
 221  0
             return;
 222  
         }
 223  
 
 224  32
         ByteBuffer buf = ByteBuffer.allocateDirect(writeBufferSize);
 225  371
         for (int n = 0; n < compiledConsts.length; n++) {
 226  339
             if (compiledConsts[n] != null) {
 227  79
                 synchronized (compiledConsts) {
 228  79
                     buf.put(compiledConsts[n].duplicate());
 229  79
                 }
 230  
             } else {
 231  260
                 if (!appendSampleResultField(buf, evt.getResult(), compiledFields[n])) {
 232  10
                     appendSampleVariable(buf, evt, compiledVars[n]);
 233  
                 }
 234  
             }
 235  
         }
 236  
 
 237  32
         buf.flip();
 238  
 
 239  
         try {
 240  32
             syncWrite(buf);
 241  0
         } catch (IOException ex) {
 242  0
             log.error("Problems writing to file", ex);
 243  32
         }
 244  32
     }
 245  
 
 246  
     private synchronized void syncWrite(ByteBuffer buf) throws IOException {
 247  33
         FileLock lock = fileChannel.lock();
 248  
         try {
 249  33
             fileChannel.write(buf);
 250  
         } finally {
 251  33
             lock.release();
 252  33
         }
 253  33
     }
 254  
 
 255  
     /*
 256  
      * we work with timestamps, so we assume number > 1000 to avoid tests
 257  
      * to be faster
 258  
      */
 259  
     private String getShiftDecimal(long number, int shift) {
 260  21
         StringBuilder builder = new StringBuilder();
 261  21
         builder.append(number);
 262  
 
 263  21
         int index = builder.length() - shift;
 264  21
         builder.insert(index, ".");
 265  
 
 266  21
         return builder.toString();
 267  
     }
 268  
 
 269  
     private void appendSampleVariable(ByteBuffer buf, SampleEvent evt, int varID) {
 270  10
         if (SampleEvent.getVarCount() < varID + 1) {
 271  0
             buf.put(("UNDEFINED_variable#" + varID).getBytes());
 272  0
             log.warn("variable#" + varID + " does not exist!");
 273  
         } else {
 274  10
             if (evt.getVarValue(varID) != null) {
 275  10
                 buf.put(evt.getVarValue(varID).getBytes());
 276  
             }
 277  
         }
 278  10
     }
 279  
 
 280  
     /**
 281  
      * @return boolean true if existing field found, false instead
 282  
      */
 283  
     private boolean appendSampleResultField(ByteBuffer buf, SampleResult result, int fieldID) {
 284  
         // IMPORTANT: keep this as fast as possible
 285  260
         switch (fieldID) {
 286  
             case 0:
 287  21
                 buf.put(result.isSuccessful() ? b1 : b0);
 288  21
                 break;
 289  
 
 290  
             case 1:
 291  10
                 buf.put(String.valueOf(result.getStartTime()).getBytes(CHARSET));
 292  10
                 break;
 293  
 
 294  
             case 2:
 295  10
                 buf.put(String.valueOf(result.getEndTime()).getBytes(CHARSET));
 296  10
                 break;
 297  
 
 298  
             case 3:
 299  11
                 if (result.getSamplerData() != null) {
 300  0
                     buf.put(String.valueOf(result.getSamplerData().length()).getBytes(CHARSET));
 301  
                 } else {
 302  11
                     buf.put(b0);
 303  
                 }
 304  11
                 break;
 305  
 
 306  
             case 4:
 307  11
                 if (result.getResponseData() != null) {
 308  11
                     buf.put(String.valueOf(result.getResponseData().length).getBytes(CHARSET));
 309  
                 } else {
 310  0
                     buf.put(b0);
 311  
                 }
 312  0
                 break;
 313  
 
 314  
             case 5:
 315  10
                 buf.put(String.valueOf(result.getTime()).getBytes(CHARSET));
 316  10
                 break;
 317  
 
 318  
             case 6:
 319  20
                 buf.put(String.valueOf(result.getLatency()).getBytes(CHARSET));
 320  20
                 break;
 321  
 
 322  
             case 7:
 323  11
                 buf.put(result.getResponseCode().getBytes(CHARSET));
 324  11
                 break;
 325  
 
 326  
             case 8:
 327  10
                 buf.put(result.getResponseMessage().getBytes(CHARSET));
 328  10
                 break;
 329  
 
 330  
             case 9:
 331  10
                 buf.put(!result.isSuccessful() ? b1 : b0);
 332  10
                 break;
 333  
 
 334  
             case 10:
 335  11
                 buf.put(result.getThreadName().getBytes(CHARSET));
 336  11
                 break;
 337  
 
 338  
             case 11:
 339  11
                 buf.put(result.getSampleLabel().getBytes(CHARSET));
 340  11
                 break;
 341  
 
 342  
             case 12:
 343  10
                 buf.put(getShiftDecimal(result.getStartTime(), 3).getBytes(CHARSET));
 344  10
                 break;
 345  
 
 346  
             case 13:
 347  11
                 buf.put(getShiftDecimal(result.getEndTime(), 3).getBytes(CHARSET));
 348  11
                 break;
 349  
 
 350  
             case 14:
 351  11
                 buf.put(String.valueOf(result.getTime() * 1000).getBytes(CHARSET));
 352  11
                 break;
 353  
 
 354  
             case 15:
 355  11
                 buf.put(String.valueOf(result.getLatency() * 1000).getBytes(CHARSET));
 356  11
                 break;
 357  
 
 358  
             case 16:
 359  10
                 if (result.getSamplerData() != null) {
 360  0
                     buf.put(result.getSamplerData().getBytes(CHARSET));
 361  
                 } else {
 362  10
                     buf.put(b0);
 363  
                 }
 364  10
                 break;
 365  
 
 366  
             case 17:
 367  10
                 buf.put(result.getResponseData());
 368  10
                 break;
 369  
 
 370  
             case 18:
 371  10
                 buf.put(result.getResponseHeaders().getBytes(CHARSET));
 372  10
                 break;
 373  
 
 374  
             case 19:
 375  10
                 buf.put(String.valueOf(result.getAllThreads()).getBytes(CHARSET));
 376  10
                 break;
 377  
 
 378  
             case 20:
 379  10
                 buf.put(String.valueOf(result.getRequestHeaders()).getBytes(CHARSET));
 380  10
                 break;
 381  
 
 382  
             case 21:
 383  11
                 buf.put(String.valueOf(result.getConnectTime()).getBytes(CHARSET));
 384  11
                 break;
 385  
 
 386  
             default:
 387  10
                 return false;
 388  
         }
 389  250
         return true;
 390  
     }
 391  
 }