package kg.apc.jmeter.reporters;
import kg.apc.jmeter.JMeterPluginsUtils;
import org.apache.jmeter.reporters.ResultCollector;
import org.apache.jmeter.samplers.SampleEvent;
import org.apache.jmeter.samplers.SampleSaveConfiguration;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;
import org.loadosophia.jmeter.LoadosophiaAPIClient;
import org.loadosophia.jmeter.LoadosophiaUploadResults;
import org.loadosophia.jmeter.StatusNotifierCallback;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class LoadosophiaUploader extends ResultCollector implements StatusNotifierCallback, Runnable, TestStateListener {
private static final Logger log = LoggingManager.getLoggerForClass();
public static final String TITLE = "title";
public static final String COLOR = "color";
public static final String UPLOAD_TOKEN = "uploadToken";
public static final String PROJECT = "project";
public static final String STORE_DIR = "storeDir";
public static final String USE_ONLINE = "useOnline";
private String fileName;
private static final Object LOCK = new Object();
private boolean isSaving;
private LoadosophiaUploadingNotifier perfMonNotifier = LoadosophiaUploadingNotifier.getInstance();
private String address;
private boolean isOnlineInitiated = false;
private LoadosophiaAPIClient apiClient;
private BlockingQueue<SampleEvent> processingQueue;
private Thread processorThread;
private LoadosophiaAggregator aggregator;
public LoadosophiaUploader() {
super();
address = JMeterUtils.getPropDefault("loadosophia.address", "https://loadosophia.org/");
}
@Override
public void testStarted(String host) {
synchronized (LOCK) {
this.apiClient = getAPIClient();
try {
if (!isSaving) {
isSaving = true;
setupSaving();
}
} catch (IOException ex) {
log.error("Error setting up saving", ex);
}
initiateOnline();
}
super.testStarted(host);
perfMonNotifier.startCollecting();
}
@Override
public void testEnded(String host) {
super.testEnded(host);
synchronized (LOCK) {
// FIXME: trying to handle safe upgrade, needs to be removed in the future
// @see https://issues.apache.org/bugzilla/show_bug.cgi?id=56807
try {
Class<ResultCollector> c = ResultCollector.class;
Method m = c.getDeclaredMethod("flushFile");
m.invoke(this);
log.info("Successfully flushed results file");
} catch (NoSuchMethodException ex) {
log.warn("Cannot flush results file since you are using old version of JMeter, consider upgrading to latest. Currently the results may be incomplete.");
} catch (InvocationTargetException e) {
log.error("Failed to flush file", e);
} catch (IllegalAccessException e) {
log.error("Failed to flush file", e);
}
if (isOnlineInitiated) {
finishOnline();
}
try {
if (fileName == null) {
throw new IOException("File for upload was not set, search for errors above in log");
}
isSaving = false;
LoadosophiaUploadResults uploadResult = this.apiClient.sendFiles(new File(fileName), perfMonNotifier.getFiles());
informUser("Uploaded successfully, go to results: " + uploadResult.getRedirectLink());
} catch (IOException ex) {
informUser("Failed to upload results to Loadosophia.org, see log for detais");
log.error("Failed to upload results to loadosophia", ex);
}
}
clearData();
perfMonNotifier.endCollecting();
}
private void setupSaving() throws IOException {
String dir = getStoreDir();
if (!dir.endsWith(File.separator)) {
dir += File.separator;
}
File tmpFile;
try {
tmpFile = File.createTempFile("Loadosophia_", ".jtl", new File(dir));
} catch (IOException ex) {
informUser("Unable to create temp file: " + ex.getMessage());
informUser("Try to set another directory in the above field.");
throw ex;
}
fileName = tmpFile.getAbsolutePath();
tmpFile.delete(); // IMPORTANT! this is required to have CSV headers
informUser("Storing results for upload to Loadosophia.org: " + fileName);
setFilename(fileName);
// OMG, I spent 2 days finding that setting properties in testStarted
// marks them temporary, though they cleared in some places.
// So we do dirty(?) hack here...
clearTemporary(getProperty(FILENAME));
SampleSaveConfiguration conf = getSaveConfig();
JMeterPluginsUtils.doBestCSVSetup(conf);
setSaveConfig(conf);
}
public void setProject(String proj) {
setProperty(PROJECT, proj);
}
public String getProject() {
return getPropertyAsString(PROJECT);
}
public void setUploadToken(String token) {
setProperty(UPLOAD_TOKEN, token);
}
public String getUploadToken() {
return getPropertyAsString(UPLOAD_TOKEN);
}
public void setTitle(String prefix) {
setProperty(TITLE, prefix);
}
public String getTitle() {
return getPropertyAsString(TITLE);
}
private void informUser(String string) {
log.info(string);
if (getVisualizer() != null && getVisualizer() instanceof LoadosophiaUploaderGui) {
((LoadosophiaUploaderGui) getVisualizer()).inform(string);
} else {
log.info(string);
}
}
public String getStoreDir() {
return getPropertyAsString(STORE_DIR);
}
public void setStoreDir(String prefix) {
setProperty(STORE_DIR, prefix);
}
public void setColorFlag(String color) {
setProperty(COLOR, color);
}
public String getColorFlag() {
return getPropertyAsString(COLOR);
}
protected LoadosophiaAPIClient getAPIClient() {
return new LoadosophiaAPIClient(this, address, getUploadToken(), getProject(), getColorFlag(), getTitle());
}
@Override
public void notifyAbout(String info) {
informUser(info);
}
public boolean isUseOnline() {
return getPropertyAsBoolean(USE_ONLINE);
}
public void setUseOnline(boolean selected) {
setProperty(USE_ONLINE, selected);
}
@Override
public void sampleOccurred(SampleEvent event) {
super.sampleOccurred(event);
if (isOnlineInitiated) {
try {
if (!processingQueue.offer(event, 1, TimeUnit.SECONDS)) {
log.warn("Failed first dequeue insert try, retrying");
if (!processingQueue.offer(event, 1, TimeUnit.SECONDS)) {
log.error("Failed second try to inser into deque, dropped sample");
}
}
} catch (InterruptedException ex) {
log.info("Interrupted while putting sample event into deque", ex);
}
}
}
@Override
public void run() {
while (isOnlineInitiated) {
try {
SampleEvent event = processingQueue.poll(1, TimeUnit.SECONDS);
if (event != null) {
aggregator.addSample(event.getResult());
}
if (aggregator.haveDataToSend()) {
try {
apiClient.sendOnlineData(aggregator.getDataToSend());
} catch (IOException ex) {
log.warn("Failed to send active test data", ex);
}
}
} catch (InterruptedException ex) {
log.debug("Interrupted while taking sample event from deque", ex);
break;
}
}
}
private void initiateOnline() {
if (isUseOnline()) {
try {
log.info("Starting Loadosophia online test");
informUser("Started active test: " + apiClient.startOnline());
aggregator = new LoadosophiaAggregator();
processingQueue = new LinkedBlockingQueue<SampleEvent>();
processorThread = new Thread(this);
processorThread.setDaemon(true);
isOnlineInitiated = true;
processorThread.start();
} catch (IOException ex) {
informUser("Failed to start active test");
log.warn("Failed to initiate active test", ex);
this.isOnlineInitiated = false;
}
}
}
private void finishOnline() {
isOnlineInitiated = false;
processorThread.interrupt();
while (processorThread.isAlive() && !processorThread.isInterrupted()) {
log.info("Waiting for aggregator thread to stop...");
try {
Thread.sleep(50);
processorThread.interrupt();
} catch (InterruptedException ex) {
log.warn("Interrupted sleep", ex);
}
}
log.info("Ending Loadosophia online test");
try {
apiClient.endOnline();
} catch (IOException ex) {
log.warn("Failed to finalize active test", ex);
}
}
}