All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mgmtp.perfload.perfalyzer.PerfAlyzerModule Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2013-2015 mgm technology partners GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mgmtp.perfload.perfalyzer;

import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.spi.Message;
import com.google.inject.util.Providers;
import com.mgmtp.perfload.perfalyzer.annotations.BinnedDir;
import com.mgmtp.perfload.perfalyzer.annotations.DoBinning;
import com.mgmtp.perfload.perfalyzer.annotations.DoNormalization;
import com.mgmtp.perfload.perfalyzer.annotations.DoReportPreparation;
import com.mgmtp.perfload.perfalyzer.annotations.EmailFrom;
import com.mgmtp.perfload.perfalyzer.annotations.EmailTo;
import com.mgmtp.perfload.perfalyzer.annotations.FloatFormat;
import com.mgmtp.perfload.perfalyzer.annotations.IntFormat;
import com.mgmtp.perfload.perfalyzer.annotations.MaxEmailHistoryItems;
import com.mgmtp.perfload.perfalyzer.annotations.MaxHistoryItems;
import com.mgmtp.perfload.perfalyzer.annotations.NormalizedDir;
import com.mgmtp.perfload.perfalyzer.annotations.RelativeDestDir;
import com.mgmtp.perfload.perfalyzer.annotations.ReportDir;
import com.mgmtp.perfload.perfalyzer.annotations.ReportPreparationDir;
import com.mgmtp.perfload.perfalyzer.annotations.ReportTabNames;
import com.mgmtp.perfload.perfalyzer.annotations.ReportsBaseUrl;
import com.mgmtp.perfload.perfalyzer.annotations.SmtpProps;
import com.mgmtp.perfload.perfalyzer.annotations.SubjectProps;
import com.mgmtp.perfload.perfalyzer.annotations.UnzippedDir;
import com.mgmtp.perfload.perfalyzer.annotations.WarmUpSeconds;
import com.mgmtp.perfload.perfalyzer.reporting.ReportCreator;
import com.mgmtp.perfload.perfalyzer.reporting.email.EmailReporter;
import com.mgmtp.perfload.perfalyzer.reportpreparation.DisplayData;
import com.mgmtp.perfload.perfalyzer.reportpreparation.PlotCreator;
import com.mgmtp.perfload.perfalyzer.util.ArchiveExtracter;
import com.mgmtp.perfload.perfalyzer.util.Marker;
import com.mgmtp.perfload.perfalyzer.util.MarkersReader;
import com.mgmtp.perfload.perfalyzer.util.MemoryFormat;
import com.mgmtp.perfload.perfalyzer.util.ResourceBundleProvider;
import com.mgmtp.perfload.perfalyzer.util.ResourceBundleProvider.Utf8Control;
import com.mgmtp.perfload.perfalyzer.util.TestMetadata;
import com.mgmtp.perfload.perfalyzer.util.TimestampNormalizer;
import com.mgmtp.perfload.perfalyzer.workflow.GcLogWorkflow;
import com.mgmtp.perfload.perfalyzer.workflow.LoadProfileWorkflow;
import com.mgmtp.perfload.perfalyzer.workflow.MeasuringWorkflow;
import com.mgmtp.perfload.perfalyzer.workflow.PerfMonWorkflow;
import com.mgmtp.perfload.perfalyzer.workflow.Workflow;
import com.mgmtp.perfload.perfalyzer.workflow.WorkflowExecutor;
import groovy.util.ConfigObject;
import groovy.util.ConfigSlurper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Singleton;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import java.io.File;
import java.io.IOException;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.ResourceBundle.Control;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.beust.jcommander.internal.Lists.newArrayList;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Lists.newArrayListWithCapacity;
import static com.mgmtp.perfload.perfalyzer.util.PropertiesUtils.loadIntoProperties;
import static com.mgmtp.perfload.perfalyzer.util.PropertiesUtils.loadProperties;
import static com.mgmtp.perfload.perfalyzer.util.PropertiesUtils.saveProperties;
import static com.mgmtp.perfload.perfalyzer.util.PropertiesUtils.setIfNonNull;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.io.FileUtils.deleteDirectory;
import static org.apache.commons.io.FileUtils.listFiles;
import static org.apache.commons.io.filefilter.FileFilterUtils.suffixFileFilter;

/**
 * @author rnaegele
 */
public class PerfAlyzerModule extends AbstractModule {
	public static final Pattern INPUT_DIR_PATTERN = Pattern.compile("(\\d{8}-\\d{4})_(.*)");

	private final Logger log = LoggerFactory.getLogger(getClass());

	private final PerfAlyzerArgs args;

	public PerfAlyzerModule(final PerfAlyzerArgs args) {
		checkState(!args.unzip || args.inputDir.isDirectory(), "'inputDir' does not exist or is not a directory: %s", args.inputDir);
		this.args = args;
	}

	@SuppressWarnings("unchecked")
	private static  T get(final ConfigObject configObject, final String key) {
		return (T) configObject.get(key);
	}

	@Override
	protected void configure() {
		binder().requireExplicitBindings();

		bindConstant().annotatedWith(DoNormalization.class).to(args.normalization);
		bindConstant().annotatedWith(DoBinning.class).to(args.binning);
		bindConstant().annotatedWith(DoReportPreparation.class).to(args.reportPreparation);

		String inputDirName = args.inputDir.getName();
		Matcher matcher = INPUT_DIR_PATTERN.matcher(inputDirName);
		checkState(matcher.matches(), "Input dir did not match pattern %s", INPUT_DIR_PATTERN);

		String timestamp = matcher.group(1);
		String testName = matcher.group(2);
		File destDir = new File(new File(args.outputDir, testName), timestamp);

		File unzippedDir = new File(destDir, "01_unzipped");

		bind(File.class).annotatedWith(RelativeDestDir.class).toInstance(new File(testName, timestamp));
		bind(File.class).annotatedWith(UnzippedDir.class).toInstance(unzippedDir);
		bind(File.class).annotatedWith(NormalizedDir.class).toInstance(new File(destDir, "02_normalized"));
		bind(File.class).annotatedWith(BinnedDir.class).toInstance(new File(destDir, "03_binned"));
		bind(File.class).annotatedWith(ReportPreparationDir.class).toInstance(new File(destDir, "04_reportpreparation"));
		bind(File.class).annotatedWith(ReportDir.class).toInstance(new File(destDir, "05_report"));

		if (args.unzip) {
			try {
				if (unzippedDir.isDirectory()) {
					log.info("Directory '{}' already exists. Deleting it...", unzippedDir);
					deleteDirectory(unzippedDir);
				}
				log.info("Extracting result archives...");
				ArchiveExtracter archiveExtracter = new ArchiveExtracter(args.inputDir, unzippedDir);
				archiveExtracter.extract();
			} catch (IOException ex) {
				Throwables.propagate(ex);
			}
		} else {
			checkState(unzippedDir.isDirectory(),
					"Unzipping was turned off, but directory with unzipped files does not exist or is not a directory: %s",
					unzippedDir);
		}

		createBindingsFromConfigFile(args.outputDir);
		bindTestMetadata(unzippedDir);

		Multibinder workflowBinder = Multibinder.newSetBinder(binder(), Workflow.class);
		workflowBinder.addBinding().to(PerfMonWorkflow.class);
		workflowBinder.addBinding().to(MeasuringWorkflow.class);
		workflowBinder.addBinding().to(GcLogWorkflow.class);
		workflowBinder.addBinding().to(LoadProfileWorkflow.class);

		bind(WorkflowExecutor.class);
		bind(PerfAlyzer.class);
		bind(ReportCreator.class);
		bind(ResourceBundle.class).toProvider(ResourceBundleProvider.class);
		bind(PlotCreator.class);
		bind(MemoryFormat.class);
	}

	private void bindTestMetadata(final File unzippedDir) {
		File metaPropsFile = new File(unzippedDir, "console/console-logs/perfload.meta.utf8.props");
		try {
			Properties perfLoadMetaProps;
			if (metaPropsFile.exists()) {
				log.info("Loading test meta properties...");
				perfLoadMetaProps = loadProperties(metaPropsFile);
			} else {
				log.warn("Testplan properties file does not exist: {}", metaPropsFile);
				perfLoadMetaProps = new Properties();
			}
			// args override
			setIfNonNull(perfLoadMetaProps, "test.start", args.testStartDate);
			setIfNonNull(perfLoadMetaProps, "test.finish", args.testFinishDate);
			setIfNonNull(perfLoadMetaProps, "test.comment", args.testComment);
			setIfNonNull(perfLoadMetaProps, "operations", args.operations);

			TestMetadata testMetadata = TestMetadata.create(args.inputDir.getName(), perfLoadMetaProps);
			bind(TestMetadata.class).toInstance(testMetadata);
		} catch (IOException ex) {
			log.error("Error reading test meta properties: " + metaPropsFile, ex);
			Throwables.propagate(ex);
		}
	}

	private void createBindingsFromConfigFile(final File destDir) {
		File configFile = new File("config", "PerfAlyzerConfig.groovy");
		if (!configFile.exists()) {
			log.info("Config file '{}' does not exist. Using default config file.", configFile);
			configFile = new File("config", "PerfAlyzerConfig_Default.groovy");
		}
		try {
			log.info("Loading perfAlyzer config file...");
			ConfigSlurper slurper = new ConfigSlurper();
			ConfigObject configObject = slurper.parse(configFile.toURI().toURL());

			// wrapping into a provider caters for null
			String url = get(configObject, "reportsBaseUrl");
			bind(String.class).annotatedWith(ReportsBaseUrl.class).toProvider(Providers.of(url));

			Integer warmUpSeconds = get(configObject, "warmUpSeconds");
			bindConstant().annotatedWith(WarmUpSeconds.class).to(warmUpSeconds);

			Integer maxHistoryItems = get(configObject, "maxHistoryItems");
			bindConstant().annotatedWith(MaxHistoryItems.class).to(maxHistoryItems);

			/***** email *****/
			ConfigObject emailConfig = get(configObject, "email");
			Boolean flag = get(emailConfig, "enabled");
			if (flag) {
				bindConstant().annotatedWith(EmailFrom.class).to((String) emailConfig.get("from"));

				List toList = get(emailConfig, "to");
				bind(new TypeLiteral>() {
					//
				}).annotatedWith(EmailTo.class).toInstance(toList);

				ConfigObject smtpConfig = get(emailConfig, "smtp");

				Boolean auth = (Boolean) smtpConfig.get("auth");
				if (auth != null && auth) {
					final String username = (String) smtpConfig.get("username");
					final String password = (String) smtpConfig.get("password");
					bind(Authenticator.class).toInstance(new Authenticator() {
						@Override
						protected PasswordAuthentication getPasswordAuthentication() {
							return new PasswordAuthentication(username, password);
						}
					});
				}
				Boolean ssl = (Boolean) smtpConfig.remove("ssl");
				String prefix;
				String protocol;
				if (ssl != null && ssl) {
					protocol = "smtps";
					prefix = "mail.smtps";
				} else {
					protocol = "smtp";
					prefix = "mail.smtp";
				}

				Properties smtpProps = smtpConfig.toProperties(prefix);
				smtpProps.setProperty("mail.transport.protocol", protocol);
				bind(Properties.class).annotatedWith(SmtpProps.class).toInstance(smtpProps);

				ConfigObject subjectConfig = get(emailConfig, "subjects");
				Properties subjectProps = subjectConfig != null ? subjectConfig.toProperties() : new Properties();
				bind(Properties.class).annotatedWith(SubjectProps.class).toInstance(subjectProps);

				Integer maxEmailHistoryItems = get(emailConfig, "maxHistoryItems");
				if (maxEmailHistoryItems == null) {
					maxEmailHistoryItems = maxHistoryItems;
				} else {
					checkState(maxEmailHistoryItems <= maxHistoryItems,
							"Max. history items in e-mail cannot be greater than global max history items");
				}
				bindConstant().annotatedWith(MaxEmailHistoryItems.class).to(maxEmailHistoryItems);

				bind(EmailReporter.class);
			} else {
				bind(EmailReporter.class).toProvider(Providers.of(null));
			}

			/***** thread count *****/
			Integer threadCount = get(configObject, "threadCount");
			bind(ExecutorService.class).toInstance(Executors.newFixedThreadPool(threadCount));

			/***** locale *****/
			String localeString = get(configObject, "locale");
			File localPropsFile = new File(destDir, ".config");
			Properties localProps = new Properties();
			if (localPropsFile.exists()) {
				loadIntoProperties(localPropsFile, localProps);
				String originalLocaleString = localProps.getProperty("locale");
				if (!originalLocaleString.equals(localeString)) {
					log.warn(
							"Configured locale ({}) has changed but is ignored for compatibility reasons with comparison data. Locale used: {}",
							localeString, originalLocaleString);
				}
				localeString = originalLocaleString;
			} else {
				localProps.setProperty("locale", localeString);
				saveProperties(localPropsFile, localProps);
			}
			final Locale locale = new Locale(localeString);
			bind(Locale.class).toInstance(locale);

			/***** reports contents configuration *****/
			Map> reportContentsConfigMap = get(configObject, "reportContents");
			bind(new TypeLiteral>>() {
				//
			}).toInstance(reportContentsConfigMap);

			/***** display data *****/
			Map> displayDataMap = get(configObject, "formats");

			List displayDataList = newArrayListWithCapacity(displayDataMap.size());
			for (Map map : displayDataMap.values()) {
				Pattern pattern = (Pattern) map.get("pattern");
				String unitX = (String) map.get("unitX");
				@SuppressWarnings("unchecked")
				List unitYList = (List) map.get("unitY");
				DisplayData dd = new DisplayData(pattern, unitX, unitYList);
				displayDataList.add(dd);
			}

			bind(new TypeLiteral>() {
				//
			}).toInstance(displayDataList);
		} catch (IOException ex) {
			addError(new Message(ImmutableList.of(configFile), "Error reading config file: " + configFile, ex));
		}
	}

	@Provides
	@IntFormat
	NumberFormat provideIntFormat(final Locale locale) {
		NumberFormat nf = NumberFormat.getIntegerInstance(locale);
		nf.setGroupingUsed(false);
		nf.setRoundingMode(RoundingMode.HALF_UP);
		return nf;
	}

	@Provides
	@FloatFormat
	NumberFormat provideFloatFormat(final Locale locale) {
		DecimalFormatSymbols dfs = new DecimalFormatSymbols(locale);
		NumberFormat nf = new DecimalFormat("0.00", dfs);
		nf.setGroupingUsed(false);
		nf.setRoundingMode(RoundingMode.HALF_UP);
		return nf;
	}

	@Provides
	@Singleton
	Control provideResourceBundleControl() {
		return new Utf8Control(new File("strings"));
	}

	@Provides
	@Singleton
	TimestampNormalizer provideTimestampNormalizer(final TestMetadata testMetadata, @WarmUpSeconds final int warmUpSeconds) {
		ZonedDateTime testStartDate = testMetadata.getTestStart();
		ZonedDateTime testEndDate = testMetadata.getTestEnd();
		return new TimestampNormalizer(testStartDate, testEndDate, warmUpSeconds);
	}

	@Provides
	@Singleton
	List provideMarkers(@UnzippedDir final File unzippedDir, final TestMetadata testMetadata) throws IOException {
		log.info("Loading markers from load profile...");
		File loadProfileFile = getOnlyElement(listFiles(new File(unzippedDir, "console/console-logs"), suffixFileFilter(".perfload"), null));
		MarkersReader markerReader = new MarkersReader(loadProfileFile, testMetadata.getTestStart());
		return markerReader.readMarkers();
	}

	@Provides
	@Singleton
	@ReportTabNames
	List provideReportTabNames(final List markers) {
		List result = newArrayList("Overall");
		result.addAll(markers.stream().map(Marker::getName).collect(toList()));
		return result;
	}
}