package hudson.plugins.performance.build; import com.google.common.base.Throwables; import hudson.EnvVars; import hudson.Extension; import hudson.FilePath; import hudson.Functions; import hudson.Launcher; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.Result; import hudson.model.Run; import hudson.model.TaskListener; import hudson.plugins.performance.Messages; import hudson.plugins.performance.PerformancePublisher; import hudson.plugins.performance.actions.PerformanceProjectAction; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Builder; import jenkins.tasks.SimpleBuildStep; import org.apache.commons.io.output.NullOutputStream; import org.jenkinsci.Symbol; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import javax.annotation.Nonnull; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * "Build step" for running performance test */ public class PerformanceTestBuild extends Builder implements SimpleBuildStep { protected final static String PERFORMANCE_TEST_COMMAND = "bzt"; protected final static String VIRTUALENV_COMMAND = "virtualenv"; protected final static String HELP_OPTION = "--help"; protected final static String VIRTUALENV_PATH_UNIX = "taurus-venv/bin/"; protected final static String VIRTUALENV_PATH_WINDOWS = "\\taurus-venv\\Scripts\\"; protected final static String[] CHECK_BZT_COMMAND = new String[]{PERFORMANCE_TEST_COMMAND, HELP_OPTION}; protected final static String[] CHECK_VIRTUALENV_COMMAND = new String[]{VIRTUALENV_COMMAND, HELP_OPTION}; protected final static String[] CREATE_LOCAL_PYTHON_COMMAND_WITH_SYSTEM_PACKAGES_OPTION = new String[]{VIRTUALENV_COMMAND, "--clear", "--system-site-packages", "taurus-venv"}; protected final static String[] CREATE_LOCAL_PYTHON_COMMAND = new String[]{VIRTUALENV_COMMAND, "--clear", "taurus-venv"}; protected final static String DEFAULT_CONFIG_FILE = "jenkins-report.yml"; @Symbol("performanceTest") @Extension public static class Descriptor extends BuildStepDescriptor<Builder> { @Override public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; } @Override public String getDisplayName() { return Messages.PerformanceTest_Name(); } } private String params; private boolean printDebugOutput; private boolean useSystemSitePackages; private boolean generatePerformanceTrend; private boolean useBztExitCode; @DataBoundConstructor public PerformanceTestBuild(String params, boolean generatePerformanceTrend, boolean printDebugOutput, boolean useSystemSitePackages, boolean useBztExitCode) throws IOException { this.params = params; this.generatePerformanceTrend = generatePerformanceTrend; this.printDebugOutput = printDebugOutput; this.useSystemSitePackages = useSystemSitePackages; this.useBztExitCode = useBztExitCode; } @Override public Action getProjectAction(AbstractProject<?, ?> project) { return generatePerformanceTrend ? new PerformanceProjectAction(project) : null; } @Override public void perform(@Nonnull Run<?, ?> run, @Nonnull FilePath workspace, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException { PrintStream logger = listener.getLogger(); EnvVars envVars = run.getEnvironment(listener); boolean isVirtualenvInstallation = false; if (isGlobalBztInstalled(workspace, logger, launcher, envVars) || (isVirtualenvInstallation = installBztAndCheck(workspace, logger, launcher, envVars))) { int testExitCode = runPerformanceTest(workspace, logger, launcher, envVars, isVirtualenvInstallation); run.setResult(useBztExitCode ? getBztJobResult(testExitCode) : getJobResult(testExitCode) ); if (generatePerformanceTrend && run.getResult().isBetterThan(Result.FAILURE)) { generatePerformanceTrend(run, workspace, launcher, listener); } return; } run.setResult(Result.FAILURE); } protected void generatePerformanceTrend(Run<?, ?> run, FilePath workspace, Launcher launcher, TaskListener listener) throws IOException, InterruptedException { new PerformancePublisher("aggregate-results.xml", -1, -1, "", 0, 0, 0, 0, 0, false, "", false, false, false, false, null). perform(run, workspace, launcher, listener); } private boolean installBztAndCheck(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException { return installBzt(workspace, logger, launcher, envVars) && isVirtualenvBztInstalled(workspace, logger, launcher, envVars); } private boolean installBzt(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException { return isVirtualenvInstalled(workspace, logger, launcher, envVars) && createVirtualenvAndInstallBzt(workspace, logger, launcher, envVars); } private boolean createVirtualenvAndInstallBzt(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException { return createIsolatedPython(workspace, logger, launcher, envVars) && installBztInVirtualenv(workspace, logger, launcher, envVars); } // Step 1.1: Check bzt using "bzt --help". private boolean isGlobalBztInstalled(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException { logger.println("Performance test: Checking global bzt installation..."); boolean result = isSuccessCode(runCmd(CHECK_BZT_COMMAND, workspace, new NullOutputStream(), launcher, envVars)); logger.println(result ? "Performance test: Found global bzt installation." : "Performance test: You don't have global bzt installed on this Jenkins host. Installing it globally will speed up job. Run 'sudo pip install bzt' to install it." ); return result; } // Step 1.2: If bzt not installed check virtualenv using "virtualenv --help". private boolean isVirtualenvInstalled(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException { logger.println("Performance test: Checking virtualenv tool availability..."); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); boolean result = isSuccessCode(runCmd(CHECK_VIRTUALENV_COMMAND, workspace, outputStream, launcher, envVars)); logger.println(result ? "Performance test: Found virtualenv tool." : "Performance test: No virtualenv found on this Jenkins host. Install it with 'sudo pip install virtualenv'." ); if (!result || printDebugOutput) { logger.write(outputStream.toByteArray()); } return result; } // Step 1.3: Create local python using "virtualenv --clear taurus-venv". private boolean createIsolatedPython(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException { logger.println("Performance test: Creating virtualev at 'taurus-venv'..."); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); boolean result = isSuccessCode(runCmd(useSystemSitePackages ? CREATE_LOCAL_PYTHON_COMMAND_WITH_SYSTEM_PACKAGES_OPTION : CREATE_LOCAL_PYTHON_COMMAND, workspace, outputStream, launcher, envVars)); logger.println(result ? "Performance test: Done creating virtualenv." : "Performance test: Failed to create virtualenv at 'taurus-venv'" ); if (!result || printDebugOutput) { logger.write(outputStream.toByteArray()); } return result; } // Step 1.4: Install bzt in virtualenv using "taurus-venv/bin/pip install bzt". private boolean installBztInVirtualenv(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException { logger.println("Performance test: Installing bzt into 'taurus-venv'"); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); boolean result = isSuccessCode(runCmd(getBztInstallCommand(workspace), workspace, outputStream, launcher, envVars)); logger.println(result ? "Performance test: bzt installed successfully." : "Performance test: Failed to install bzt into 'taurus-venv'" ); if (!result || printDebugOutput) { logger.write(outputStream.toByteArray()); } return result; } // Step 1.5: Check bzt using "taurus-venv/bin/bzt --help" private boolean isVirtualenvBztInstalled(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException { logger.println("Performance test: Checking installed bzt..."); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); boolean result = isSuccessCode(runCmd(getBztCheckCommand(workspace), workspace, outputStream, launcher, envVars)); logger.println(result ? "Performance test: bzt is operational." : "Performance test: Failed to run bzt inside virtualenv." ); if (!result || printDebugOutput) { logger.write(outputStream.toByteArray()); } return result; } // Step 2: Run performance test. private int runPerformanceTest(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars, boolean isVirtualenvInstallation) throws InterruptedException, IOException { String[] params = this.params.split(" "); final List<String> testCommand = new ArrayList<String>(params.length + 2); testCommand.add((isVirtualenvInstallation ? getVirtualenvPath(workspace) : "") + PERFORMANCE_TEST_COMMAND); for (String param : params) { if (!param.isEmpty()) { testCommand.add(param); } } if (generatePerformanceTrend) { testCommand.add(extractDefaultReportToWorkspace(workspace)); } logger.println("Performance test: run " + Arrays.toString(testCommand.toArray())); return runCmd(testCommand.toArray(new String[testCommand.size()]), workspace, logger, launcher, envVars); } public boolean isSuccessCode(int code) { return code == 0; } public Result getJobResult(int code) { if (code == 0) { return Result.SUCCESS; } else { return Result.FAILURE; } } public Result getBztJobResult(int code) { if (code == 0) { return Result.SUCCESS; } else if (code == 1) { return Result.FAILURE; } else { return Result.UNSTABLE; } } private String getVirtualenvPath(FilePath workspace) { return Functions.isWindows() ? workspace.getRemote() + VIRTUALENV_PATH_WINDOWS : VIRTUALENV_PATH_UNIX; } // return bzt install command private String[] getBztInstallCommand(FilePath workspace) { return new String[]{getVirtualenvPath(workspace) + "pip", "install", PERFORMANCE_TEST_COMMAND}; } // return bzt check command private String[] getBztCheckCommand(FilePath workspace) { return new String[]{getVirtualenvPath(workspace) + "bzt", HELP_OPTION}; } public int runCmd(String[] commands, FilePath workspace, OutputStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException { try { return launcher.launch().cmds(commands).envs(envVars).stdout(logger).stderr(logger).pwd(workspace).start().join(); } catch (IOException ex) { logger.write(ex.getMessage().getBytes()); if (printDebugOutput) { logger.write(Throwables.getStackTraceAsString(ex).getBytes()); } return 1; } } protected String extractDefaultReportToWorkspace(FilePath workspace) throws IOException, InterruptedException { FilePath defaultConfig = workspace.child(DEFAULT_CONFIG_FILE); defaultConfig.copyFrom(getClass().getResourceAsStream(DEFAULT_CONFIG_FILE)); return defaultConfig.getRemote(); } public String getParams() { return params; } @DataBoundSetter public void setParams(String params) { this.params = params; } public boolean isPrintDebugOutput() { return printDebugOutput; } @DataBoundSetter public void setPrintDebugOutput(boolean printDebugOutput) { this.printDebugOutput = printDebugOutput; } public boolean isUseSystemSitePackages() { return useSystemSitePackages; } @DataBoundSetter public void setUseSystemSitePackages(boolean useSystemSitePackages) { this.useSystemSitePackages = useSystemSitePackages; } public boolean isGeneratePerformanceTrend() { return generatePerformanceTrend; } @DataBoundSetter public void setGeneratePerformanceTrend(boolean generatePerformanceTrend) { this.generatePerformanceTrend = generatePerformanceTrend; } public boolean isUseBztExitCode() { return useBztExitCode; } @DataBoundSetter public void setUseBztExitCode(boolean useBztExitCode) { this.useBztExitCode = useBztExitCode; } }