| 1 | |
|
| 2 | |
|
| 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 | |
|
| 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 " |
| 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 | |
|
| 149 | |
|
| 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 | |
|
| 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 | |
|
| 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 | |
|
| 257 | |
|
| 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 | |
|
| 282 | |
|
| 283 | |
private boolean appendSampleResultField(ByteBuffer buf, SampleResult result, int fieldID) { |
| 284 | |
|
| 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 | |
} |