package alma.acs.manager.logparser; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import java.util.regex.Pattern; import org.apache.commons.lang.builder.CompareToBuilder; import alma.acs.util.IsoDateFormat; import alma.acs.util.StopWatch; /** * Parses logs like * 2011-09-30T21:34:20.084 INFO [Manager] 'Python Client' requested component 'curl:///CONTROL/DV15/Mount'. * 2011-09-30T21:34:20.085 INFO [Manager] Component 'curl:///CONTROL/DV15/Mount' provided to 'Python Client'. * @author hsommer */ public class ManagerStdoutParser { private final Logger logger; private final Pattern msgSplitSpace = Pattern.compile(" "); private final Pattern msgSplitQuotes = Pattern.compile("'"); /** * @param logger * @param stdoutLogFile * throws IllegalArgumentException if <code>stdoutLogFile</code> is missing or cannot be read. */ public ManagerStdoutParser(Logger logger) { this.logger = logger; } public List<ComponentRequest> parse(File stdoutLogFile) throws IOException, ParseException { if (stdoutLogFile == null) { throw new IllegalArgumentException("No log file specified (null)."); } else if (!stdoutLogFile.exists() || !stdoutLogFile.canRead()) { throw new IllegalArgumentException("Log file " + stdoutLogFile.getAbsolutePath() + " does not exist or cannot be read."); } StopWatch sw = new StopWatch(logger); Map<ComponentRequestKey, List<ComponentRequest>> pendingRequests = new HashMap<ComponentRequestKey, List<ComponentRequest>>(); List<ComponentRequest> finishedRequests = new ArrayList<ComponentRequest>(); BufferedReader reader = new BufferedReader(new FileReader(stdoutLogFile)); String line = null; int lineCount = 0; while ((line = reader.readLine()) != null) { lineCount++; parseLine(line, pendingRequests, finishedRequests); } reader.close(); logger.info("Parsed file " + stdoutLogFile.getAbsolutePath() + " in " + sw.getLapTimeMillis() + " ms, found " + lineCount + " lines total, and " + finishedRequests.size() + " component activations."); // Check for unfinished requests (may be pending, or may have failed which we don't parse out yet) StringBuilder b = new StringBuilder(); for (ComponentRequestKey key : pendingRequests.keySet()) { b.append(key.toString() + ": " + pendingRequests.get(key).size() + "\n"); } logger.info("Unfinished or failed component requests: " + ( b.length() > 0 ? "\n" + b.toString() : "none")); return finishedRequests; } /** * @param line * @param pendingRequests List of pending requests, for efficiency implemented as a map where the client/CURL fields are used as key. * @param finishedRequests * @throws ParseException */ protected void parseLine(String line, Map<ComponentRequestKey, List<ComponentRequest>> pendingRequests, List<ComponentRequest> finishedRequests) throws ParseException { // 2011-09-30T21:34:20.084 INFO [Manager] 'Python Client' requested component 'curl:///CONTROL/DV15/Mount'. // 2011-09-30T21:34:20.085 INFO [Manager] Component 'curl:///CONTROL/DV15/Mount' provided to 'Python Client'. // 2011-09-30T21:38:20.008 FINE [Manager] 'CCLSimpleClient' requested non-sticky component 'curl:///CONTROL/DA43/WVR'. // 2011-09-30T21:38:20.008 FINE [Manager] Non-sticky component 'curl:///CONTROL/DA43/WVR' provided to 'CCLSimpleClient'. String[] splitByQuotes = msgSplitQuotes.split(line); if (splitByQuotes.length == 5) { String action = splitByQuotes[2].trim(); String[] words = msgSplitSpace.split(splitByQuotes[0]); if (words[2].equals("[Manager]")) { long time = IsoDateFormat.parseIsoTimestamp(words[0]).getTime(); if (action.equals("requested component") || action.equals("requested non-sticky component")) { String clientName = splitByQuotes[1]; String curl = splitByQuotes[3]; ComponentRequest thisRequest = new ComponentRequest(time, clientName, curl); // Add this request to pendingRequests List<ComponentRequest> compRequests = pendingRequests.get(thisRequest.asComponentRequestKey()); if (compRequests == null) { compRequests = new ArrayList<ComponentRequest>(); pendingRequests.put(thisRequest.asComponentRequestKey(), compRequests); } else { // logger.info("Overlapping identical request " + thisRequest); } compRequests.add(thisRequest); } else if (action.equals("provided to")) { String clientName = splitByQuotes[3]; String curl = splitByQuotes[1]; List<ComponentRequest> compRequests = pendingRequests.get(new ComponentRequestKey(clientName, curl)); if (compRequests == null || compRequests.isEmpty()) { logger.warning("Unmatched 'provided to' found at " + toISO(time) + "; clientName=" + clientName + ", CURL=" + curl); } else { ComponentRequest match = compRequests.remove(0); if (!compRequests.isEmpty()) { logger.warning("Ambiguous 'provided to' at " + toISO(time) + ", clientName=" + clientName + ", CURL=" + curl + ": Will match it with the first of " + (compRequests.size()+1) + " possibly matching requests, from " + toISO(match.timeRequested)); } else { pendingRequests.remove(match.asComponentRequestKey()); } match.timeProvided = time; finishedRequests.add(match); } } } } } private String toISO(long time) { return IsoDateFormat.formatDate(new Date(time)); } public static class ComponentRequestKey implements Comparable<ComponentRequestKey>{ protected final String clientName; protected final String curl; public ComponentRequestKey(String clientName, String curl) { this.clientName = clientName; this.curl = curl; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof ComponentRequestKey)) return false; ComponentRequestKey other = (ComponentRequestKey) obj; return ( clientName.equals(other.clientName) && curl.equals(other.curl) ); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + clientName.hashCode(); result = prime * result + curl.hashCode(); return result; } @Override public String toString() { return ("curl=" + curl + ", clientName=" + clientName ); } @Override public int compareTo(ComponentRequestKey other) { return new CompareToBuilder() .append(this.clientName, other.clientName) .append(this.curl, other.curl) .toComparison(); } } public static class ComponentRequest implements Comparable<ComponentRequest> { protected final ComponentRequestKey key; protected final long timeRequested; protected long timeProvided; public ComponentRequest(long timeRequested, String clientName, String curl) { this.key = new ComponentRequestKey(clientName, curl); this.timeRequested = timeRequested; } public ComponentRequestKey asComponentRequestKey() { return key; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof ComponentRequest)) return false; ComponentRequest other = (ComponentRequest) obj; return (key.equals(other.key) && timeRequested == other.timeRequested && timeProvided == other.timeProvided); } @Override public int hashCode() { return key.hashCode(); } @Override public String toString() { return ("timeRequested=" + IsoDateFormat.formatDate(new Date(timeRequested)) + ", timeProvided=" + ( timeProvided > 0 ? IsoDateFormat.formatDate(new Date(timeProvided)) : "--" ) + ", curl=" + key.curl + ", clientName=" + key.clientName); } @Override public int compareTo(ComponentRequest other) { if (this.timeRequested < other.timeRequested) return -1; if (this.timeRequested > other.timeRequested) return 1; return this.key.compareTo(other.key); } } }