/*
* 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.reporting;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.TreeMultimap;
import com.google.common.io.Resources;
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.util.PerfAlyzerFile;
import com.mgmtp.perfload.perfalyzer.util.TestMetadata;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.text.StrTokenizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.io.Files.newReader;
import static com.google.common.io.Files.newWriter;
import static com.mgmtp.perfload.perfalyzer.constants.PerfAlyzerConstants.DELIMITER;
import static com.mgmtp.perfload.perfalyzer.util.PerfAlyzerUtils.extractFileNameParts;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.io.FileUtils.copyFile;
import static org.apache.commons.io.FilenameUtils.getExtension;
import static org.apache.commons.io.FilenameUtils.removeExtension;
import static org.apache.commons.lang3.StringUtils.split;
import static org.apache.commons.lang3.StringUtils.substringBefore;
/**
* @author rnaegele
*/
@Singleton
public class ReportCreator {
private final Logger log = LoggerFactory.getLogger(getClass());
private final TestMetadata testMetadata;
private final File soureDir;
private final File destDir;
private final Map<String, List<Pattern>> reportContentsConfigMap;
private final StrTokenizer tokenizer = StrTokenizer.getCSVInstance();
private final ResourceBundle resourceBundle;
private final Locale locale;
private final List<String> tabNames;
@Inject
public ReportCreator(final TestMetadata testMetadata, @ReportPreparationDir final File soureDir,
@ReportDir final File destDir, final Map<String, List<Pattern>> reportContentsConfigMap,
final ResourceBundle resourceBundle, final Locale locale, @ReportTabNames final List<String> tabNames) {
this.testMetadata = testMetadata;
this.soureDir = soureDir;
this.destDir = destDir;
this.reportContentsConfigMap = reportContentsConfigMap;
this.resourceBundle = resourceBundle;
this.locale = locale;
this.tabNames = tabNames;
tokenizer.setDelimiterChar(DELIMITER);
}
public void createReport(final List<PerfAlyzerFile> files) throws IOException {
Function<PerfAlyzerFile, String> classifier = perfAlyzerFile -> {
String marker = perfAlyzerFile.getMarker();
return marker == null ? "Overall" : marker;
};
Supplier<Map<String, List<PerfAlyzerFile>>> mapFactory = () -> new TreeMap<>(Ordering.explicit(tabNames));
Map<String, List<PerfAlyzerFile>> filesByMarker = files.stream().collect(Collectors.groupingBy(classifier, mapFactory, toList()));
Map<String, SortedSetMultimap<String, PerfAlyzerFile>> contentItemFiles = new LinkedHashMap<>();
for (Entry<String, List<PerfAlyzerFile>> entry : filesByMarker.entrySet()) {
SortedSetMultimap<String, PerfAlyzerFile> contentItemFilesByMarker =
contentItemFiles.computeIfAbsent(entry.getKey(), s -> TreeMultimap.create(
new ItemComparator(reportContentsConfigMap.get("priorities")),
Ordering.natural()));
for (PerfAlyzerFile perfAlyzerFile : entry.getValue()) {
File file = perfAlyzerFile.getFile();
String groupKey = removeExtension(file.getPath());
boolean excluded = false;
for (Pattern pattern : reportContentsConfigMap.get("exclusions")) {
Matcher matcher = pattern.matcher(groupKey);
if (matcher.matches()) {
excluded = true;
log.debug("Excluded from report: {}", groupKey);
break;
}
}
if (!excluded) {
contentItemFilesByMarker.put(groupKey, perfAlyzerFile);
}
}
}
// explicitly copy it because it is otherwise filtered from the report in order to only show in the overview
String loadProfilePlot = new File("console", "[loadprofile].png").getPath();
copyFile(new File(soureDir, loadProfilePlot), new File(destDir, loadProfilePlot));
Map<String, List<ContentItem>> tabItems = new LinkedHashMap<>();
Map<String, QuickJump> quickJumps = new HashMap<>();
Set<String> tabNames = contentItemFiles.keySet();
for (Entry<String, SortedSetMultimap<String, PerfAlyzerFile>> tabEntry : contentItemFiles.entrySet()) {
String tab = tabEntry.getKey();
SortedSetMultimap<String, PerfAlyzerFile> filesForTab = tabEntry.getValue();
List<ContentItem> contentItems = tabItems.computeIfAbsent(tab, list -> new ArrayList<>());
Map<String, String> quickJumpMap = new LinkedHashMap<>();
quickJumps.put(tab, new QuickJump(tab, quickJumpMap));
int itemIndex = 0;
for (Entry<String, Collection<PerfAlyzerFile>> itemEntry : filesForTab.asMap().entrySet()) {
String title = itemEntry.getKey();
Collection<PerfAlyzerFile> itemFiles = itemEntry.getValue();
TableData tableData = null;
String plotSrc = null;
for (PerfAlyzerFile file : itemFiles) {
if ("png".equals(getExtension(file.getFile().getName()))) {
plotSrc = file.getFile().getPath();
copyFile(new File(soureDir, plotSrc), new File(destDir, plotSrc));
} else {
tableData = createTableData(file.getFile());
}
}
// strip off potential marker
title = substringBefore(title, "{");
String[] titleParts = split(title, SystemUtils.FILE_SEPARATOR);
StringBuilder sb = new StringBuilder(50);
String separator = " - ";
sb.append(resourceBundle.getString(titleParts[0]));
sb.append(separator);
sb.append(resourceBundle.getString(titleParts[1]));
List<String> fileNameParts = extractFileNameParts(titleParts[1], true);
if (titleParts[1].contains("[distribution]")) {
String operation = fileNameParts.get(1);
sb.append(separator);
sb.append(operation);
} else if ("comparison".equals(titleParts[0])) {
String operation = fileNameParts.get(1);
sb.append(separator);
sb.append(operation);
} else if (titleParts[1].contains("[gclog]")) {
if (fileNameParts.size() > 1) {
sb.append(separator);
sb.append(fileNameParts.get(1));
}
}
title = sb.toString();
ContentItem item = new ContentItem(tab, itemIndex, title, tableData, plotSrc,
resourceBundle.getString("report.topLink"));
contentItems.add(item);
quickJumpMap.put(tab + "_" + itemIndex, title);
itemIndex++;
}
}
NavBar navBar = new NavBar(tabNames, quickJumps);
String testName = removeExtension(testMetadata.getTestPlanFile());
OverviewItem overviewItem = new OverviewItem(testMetadata, resourceBundle, locale);
Content content = new Content(tabItems);
String perfAlyzerVersion;
try {
perfAlyzerVersion = Resources.toString(Resources.getResource("perfAlyzer.version"), Charsets.UTF_8);
} catch (IOException ex) {
log.error("Could not read perfAlyzer version from classpath resource 'perfAlyzer.version'", ex);
perfAlyzerVersion = "";
}
String dateTimeString = DateTimeFormatter.ISO_OFFSET_DATE_TIME.withLocale(locale).format(ZonedDateTime.now());
String createdString = String.format(resourceBundle.getString("footer.created"), perfAlyzerVersion, dateTimeString);
HtmlSkeleton html = new HtmlSkeleton(testName, createdString, navBar, overviewItem, content);
writeReport(html);
}
private void writeReport(final HtmlSkeleton html) throws IOException {
try (Writer wr = newWriter(new File(destDir, "report.html"), Charsets.UTF_8)) {
html.write(wr);
}
}
private TableData createTableData(final File file) throws IOException {
try (BufferedReader br = newReader(new File(soureDir, file.getPath()), Charsets.UTF_8)) {
List<String> headers = null;
List<List<String>> rows = newArrayList();
int valueColumnsCount = 0;
String fileName = file.getName();
for (String line; (line = br.readLine()) != null; ) {
tokenizer.reset(line);
List<String> tokenList = tokenizer.getTokenList();
if (headers == null) {
headers = Lists.transform(tokenList, resourceBundle::getString);
valueColumnsCount = tokenList.size() - 1;
// TODO make that nicer
if (file.getPath().startsWith("global") && !fileName.startsWith("[measuring][executions]") &&
!fileName.contains("[distribution]")) {
valueColumnsCount--;
} else if (fileName.contains("[distribution]")) {
valueColumnsCount -= 2;
}
} else {
rows.add(tokenList);
}
}
boolean imageInNewRow = fileName.contains("[distribution]") || fileName.contains("[executions]") || fileName.contains("[gclog]");
return new TableData(headers, rows, valueColumnsCount, imageInNewRow);
}
}
static class ItemComparator implements Comparator<String> {
private final Logger log = LoggerFactory.getLogger(getClass());
List<Pattern> priorityPatterns;
int size;
public ItemComparator(final List<Pattern> priorityPatterns) {
this.priorityPatterns = priorityPatterns;
this.size = priorityPatterns.size();
}
@Override
public int compare(final String o1, final String o2) {
int priority1 = getPriority(o1);
int priority2 = getPriority(o2);
int result = priority1 - priority2;
if (result == 0) {
result = o1.compareTo(o2);
}
log.debug("compareTo({}, {}): {}", priority1, priority2, result);
return result;
}
int getPriority(final String s) {
int result = Integer.MIN_VALUE + size - 1;
// we must iterate in reverse order because the last element has the highest priority
for (int i = size - 1; i >= 0; --i) {
Pattern pattern = priorityPatterns.get(i);
Matcher matcher = pattern.matcher(s);
if (matcher.matches()) {
result = result - i;
log.debug("Priority match: [s={},pattern={},priority={}", s, pattern, result);
return result; // negate, so it is sorted up in the set
}
}
return 0;
}
}
}