package com.anjlab.ping.services;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import com.anjlab.ping.entities.Job;
import com.anjlab.ping.entities.JobResult;
public class JobResultsAnalyzer {
public static class JobResultsInterval {
private Date startTime;
private Date endTime;
private int resultCode;
private int resultsCount;
public JobResultsInterval(JobResult jobResult) {
this.resultCode = jobResult.getPingResult();
this.startTime = jobResult.getTimestamp();
this.endTime = startTime;
this.resultsCount = 1;
}
public boolean append(JobResult jobResult) {
this.endTime = jobResult.getTimestamp();
boolean appended = jobResult.getPingResult() == resultCode;
if (appended) {
resultsCount++;
}
return appended;
}
public Date getStartTime() {
return startTime;
}
public Date getEndTime() {
return endTime;
}
public int getResultCode() {
return resultCode;
}
public int getResultsCount() {
return resultsCount;
}
public String getTimeDiffInWords() {
return Utils.formatMillisecondsToWordsUpToMinutes(getMilliseconds());
}
public long getMilliseconds() {
return endTime.getTime() - startTime.getTime();
}
}
private List<JobResultsInterval> intervals;
private Map<Integer, Long> resultCodeCounters;
private Map<Integer, Long> resultsCountCounters;
private long totalDurationMillis;
private int totalResultsCount;
public JobResultsAnalyzer(List<JobResult> jobResults, boolean resultsSorted) {
totalResultsCount = jobResults.size();
if (!resultsSorted) {
sort(jobResults);
}
analyze(jobResults);
}
public List<JobResultsInterval> getIntervals() {
return intervals;
}
public Map<Integer, Long> getResultCodeCounters() {
return resultCodeCounters;
}
private void sort(List<JobResult> jobResults) {
Collections.sort(jobResults, new Comparator<JobResult>() {
@Override
public int compare(JobResult o1, JobResult o2) {
return o1.getTimestamp().compareTo(o2.getTimestamp());
}
});
}
private void analyze(List<JobResult> jobResults) {
intervals = new ArrayList<JobResultsInterval>();
resultCodeCounters = new HashMap<Integer, Long>();
resultsCountCounters = new HashMap<Integer, Long>();
if (jobResults.size() == 0) {
return;
}
JobResultsInterval interval = new JobResultsInterval(jobResults.get(0));
intervals.add(interval);
for (int i = 1; i < jobResults.size(); i++) {
JobResult jobResult = jobResults.get(i);
if (!interval.append(jobResult)) {
countInterval(interval);
interval = new JobResultsInterval(jobResult);
intervals.add(interval);
}
}
countInterval(interval);
totalDurationMillis = 0;
for (Long duration : resultCodeCounters.values()) {
totalDurationMillis += duration;
}
}
private void countInterval(JobResultsInterval interval) {
incrementCounter(resultCodeCounters, interval.getMilliseconds(), interval);
incrementCounter(resultsCountCounters, interval.getResultsCount(), interval);
}
private void incrementCounter(Map<Integer, Long> counters, long value, JobResultsInterval interval) {
Long count = counters.get(interval.getResultCode());
if (count == null) {
count = value;
} else {
count += value;
}
counters.put(interval.getResultCode(), count);
}
private static final String spaces = buildString(50);
private static String buildString(int n) {
char[] a = new char[n];
Arrays.fill(a, ' ');
return new String(a);
}
public StringBuilder buildHtmlReport(TimeZone timeZone) {
StringBuilder sb = new StringBuilder();
sb.append("<p>Totals:</p>\n");
sb.append("<table>");
for (Integer resultCode : getSortedResultCodes()) {
String status = Job.buildPingResultSummary(resultCode);
Long durationMillis = resultCodeCounters.get(resultCode);
String duration = Utils.formatMillisecondsToWordsUpToMinutes(durationMillis);
sb.append("<tr><td style='text-align: right;'>");
sb.append(status);
sb.append(" :</td><td style='padding-left: 10px;'>");
buildResultCodeLine(sb, resultCode, durationMillis, duration);
sb.append("</td></tr>\n");
}
sb.append("</table>");
sb.append("\n<p>Detailed report:</p>\n");
DateFormat dateFormat = (DateFormat) Application.DATETIME_FORMAT.clone();
if (timeZone != null) {
dateFormat.setTimeZone(timeZone);
}
sb.append("<table class='detailed-output-table'>");
for (JobResultsInterval interval : intervals) {
sb.append("<tr><td>");
sb.append(dateFormat.format(interval.getStartTime()));
sb.append(" – ");
sb.append(dateFormat.format(interval.getEndTime()));
sb.append("</td><td style='padding-left: 10px;'>");
String resultSummary = Job.buildPingResultSummary(interval.getResultCode());
sb.append(resultSummary);
sb.append("</td><td style='text-align: right; padding-left: 10px;'>");
String timeDiffInWords = interval.getTimeDiffInWords();
sb.append(timeDiffInWords);
sb.append("</td><td style='text-align: right; padding-left: 10px;'>");
String resultsCount = String.valueOf(interval.getResultsCount());
sb.append(resultsCount);
sb.append(" ping(s)");
sb.append("</td></tr>\n");
}
sb.append("</table>");
return sb;
}
private Integer[] getSortedResultCodes() {
Integer[] result = new Integer[resultCodeCounters.size()];
resultCodeCounters.keySet().toArray(result);
Arrays.sort(result);
return result;
}
public String getAvailabilitySummary()
{
StringBuilder builder = new StringBuilder();
int resultCode = Job.PING_RESULT_OK;
Long durationMillis = resultCodeCounters.get(resultCode);
if (durationMillis == null)
{
durationMillis = 0L;
}
builder.append("availability ");
builder.append(String.format(Locale.ENGLISH, "%.1f", 100d * durationMillis / totalDurationMillis));
builder.append("%");
if (durationMillis != totalDurationMillis) {
builder.append(" (downtime ");
builder.append(Utils.formatMillisecondsToWordsUpToMinutes(totalDurationMillis - durationMillis));
builder.append(" / ");
Long count = resultsCountCounters.get(resultCode);
if (count == null) {
count = 0L;
}
builder.append(totalResultsCount - count);
builder.append(" ping(s))");
}
return builder.toString();
}
private void buildResultCodeLine(StringBuilder sb, Integer resultCode, Long durationMillis, String duration) {
sb.append(String.format(Locale.ENGLISH, "%.5f", 100d * durationMillis / totalDurationMillis));
sb.append(" % (");
sb.append(duration);
sb.append(") verified by ");
Long count = resultsCountCounters.get(resultCode);
if (count == null) {
count = 0L;
}
String resultsCount = count.toString();
sb.append(resultsCount);
sb.append(" ping(s)");
}
public StringBuilder buildPlainTextReport(TimeZone timeZone) {
StringBuilder sb = new StringBuilder();
int maxStatusLength = Job.buildPingResultSummary(Job.PING_RESULT_HTTP_ERROR
| Job.PING_RESULT_REGEXP_VALIDATION_FAILED).length();
int maxTimeInWords = "10 years 12 months 30 days 60 minutes".length();
int maxResultsCount = "99999".length();
sb.append("Totals:\n");
for (Integer resultCode : getSortedResultCodes()) {
String status = Job.buildPingResultSummary(resultCode);
Long durationMillis = resultCodeCounters.get(resultCode);
String duration = Utils.formatMillisecondsToWordsUpToMinutes(durationMillis);
sb.append(spaces.substring(0, maxStatusLength - status.length()));
sb.append(status);
sb.append(" : ");
buildResultCodeLine(sb, resultCode, durationMillis, duration);
sb.append('\n');
}
sb.append("\nDetailed report:\n\n");
DateFormat dateFormat = (DateFormat) Application.DATETIME_FORMAT.clone();
if (timeZone != null) {
dateFormat.setTimeZone(timeZone);
}
for (JobResultsInterval interval : intervals) {
sb.append(dateFormat.format(interval.getStartTime()));
sb.append(" - ");
sb.append(dateFormat.format(interval.getEndTime()));
sb.append(" ");
String resultSummary = Job.buildPingResultSummary(interval.getResultCode());
sb.append(resultSummary);
sb.append(spaces.substring(0, maxStatusLength - resultSummary.length()));
String timeDiffInWords = interval.getTimeDiffInWords();
sb.append(spaces.substring(0, maxTimeInWords - timeDiffInWords.length()));
sb.append(timeDiffInWords);
sb.append(" ");
String resultsCount = String.valueOf(interval.getResultsCount());
sb.append(spaces.substring(0, maxResultsCount - resultsCount.length()));
sb.append(resultsCount);
sb.append(" ping(s)\n");
}
return sb;
}
}