SavedLog.java
package handist.collections.util;
import static apgas.Constructs.*;
import static handist.collections.util.StringUtilities.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import apgas.Constructs;
import apgas.Place;
import handist.collections.dist.DistLog;
import handist.collections.dist.DistLog.LogItem;
import handist.collections.dist.DistLog.LogKey;
import handist.collections.dist.DistMultiMap;
/**
* Class used as a substitute to {@link DistLog} to save the events recorded
* into a DistLog to a file and to restore it later.
* <p>
* This class is used so that distributed logs can be accessed for processing
* and analysis post-mortem in single-threaded environments, something not
* possible when working directly with a {@link DistLog}.
*
* @author Patrick Finnerty
*
*/
public class SavedLog {
/**
* Class used as a substitute to {@link LogKey} in which the use of the APGAS
* {@link Place} class has been replaced by a {@code int}.
*
* @author Patrick Finnerty
*
*/
public static class Key implements Serializable {
/** Serial Version UID */
private static final long serialVersionUID = 6245573767313436511L;
/** Phase during which the events were recorded */
public final long phase;
/** Place on which the events occurred */
public final int place;
/** Tag under which the events are kept */
public final String tag;
/**
* Constructor
*
* @param p place number ({@link Place#id}) on which the events occurred
* @param t tag under which the events are gathered
* @param f the phase during which the events occurred
*/
private Key(int p, String t, long f) {
place = p;
tag = t;
phase = f;
}
/**
* Two {@link Key}s are equal iff their respective {@link #place}, {@link #tag}
* and {@link #phase} match. Note that {@link Key} can be compared to a
* {@link LogKey}.
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
} else if (obj instanceof LogKey) {
final LogKey logKey = (LogKey) obj;
return place == logKey.place.id && nullSafeEquals(tag, logKey.tag) && (phase == logKey.phase);
} else if (obj instanceof Key) {
final Key key2 = (Key) obj;
return place == key2.place && nullSafeEquals(tag, key2.tag) && (phase == key2.phase);
}
return false;
}
@Override
public int hashCode() {
return place + (tag.hashCode() << 2) + (int) (phase << 4 + phase >> 16);
}
@Override
public String toString() {
return "Log@Place(" + place + "), tag: " + tag + ", phase: " + phase;
}
}
/**
* Comparator which sorts the keys of a saved log according to their:
* <ol>
* <li>place
* <li>tag
* <li>phase
* </ol>
*/
public static final Comparator<? super Key> sortPlaceTagPhase = (o1, o2) -> {
int result = Integer.compareUnsigned(o1.place, o2.place);
if (result == 0) {
result = o1.tag.compareTo(o2.tag);
}
if (result == 0) {
result = Long.compare(o1.phase, o2.phase);
}
return result;
};
/**
* Main which prints the contents of a saved log, with the keys and the number
* of entries for said keys on std output, and the entire contents of the saved
* log on the error output.
*
* @param args one argument: the name of the file into which the log will be
* stored
*/
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("1 arguments required:");
System.err.println("\t<file name> \tfile to which a distributed log was saved");
return;
}
SavedLog savedLog;
try {
savedLog = new SavedLog(new File(args[0]));
} catch (ClassNotFoundException | IOException e) {
System.err.println("Trouble when parsing file ");
e.printStackTrace();
return;
}
savedLog.printKeys(System.out);
savedLog.printAll(System.err);
}
/**
* Map into which the logged entries of the {@link DistLog} are converted
*/
public final HashMap<Key, Collection<LogItem>> loggedEntries;
/**
* Number of hosts in the original distributed log recording
*/
public final int numberOfHosts;
/**
* Constructor
*
* @param log the distributed log instance into which events that occurred
* during a GLB execution were recorded
*/
public SavedLog(DistLog log) {
numberOfHosts = Constructs.places().size();
log.globalGather();
final DistMultiMap<LogKey, LogItem> distLogMap = log.getDistMultiMap();
loggedEntries = new HashMap<>(distLogMap.size());
// Initialize member loggedEntries by substituting the keys used to log the
// various entries
log.getDistMultiMap().forEach((key, entries) -> {
final Key substituteKey = new Key(key.place.id, key.tag, key.phase);
loggedEntries.put(substituteKey, entries);
});
}
/**
* Constructor
* <p>
* This constructor
*
* @param file the file to which an instance of this class was saved
* @throws IOException if thrown during the retrieval of information
* from the specified file
* @throws ClassNotFoundException if thrown when reading objects from the
* specified file
*/
@SuppressWarnings("unchecked")
public SavedLog(File file) throws IOException, ClassNotFoundException {
final ObjectInputStream inStream = new ObjectInputStream(new FileInputStream(file));
numberOfHosts = inStream.readInt();
loggedEntries = (HashMap<Key, Collection<LogItem>>) inStream.readObject();
inStream.close();
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o instanceof SavedLog) {
return equalsGlbLog((SavedLog) o);
} else if (o instanceof DistLog) {
return equalsDistLog((DistLog) o);
} else {
return false;
}
}
private boolean equalsDistLog(DistLog log) {
final Map<LogKey, Collection<LogItem>> logMap = log.getDistMultiMap();
if (logMap.size() != loggedEntries.size()) {
return false;
}
for (final Key k : loggedEntries.keySet()) {
final Collection<LogItem> otherItems = log.getLog(place(k.place), k.tag, k.phase);
final Collection<LogItem> myItems = loggedEntries.get(k);
if (otherItems != null && otherItems.size() == myItems.size()) {
// Check that every item in 'myItems' is also in 'otherItems'
if (!otherItems.containsAll(myItems)) {
return false;
}
} else {
return false;
}
// 2-way comparison
}
return true;
}
/**
* Checks if the provided GlbLog contains the same
*
* @param log
* @return
*/
private boolean equalsGlbLog(SavedLog log) {
final Map<Key, Collection<LogItem>> logMap = log.loggedEntries;
if (logMap.size() != loggedEntries.size()) {
return false;
}
for (final Key k : loggedEntries.keySet()) {
final Collection<LogItem> otherItems = log.getLog(k.place, k.tag, k.phase);
final Collection<LogItem> myItems = loggedEntries.get(k);
if (otherItems != null && otherItems.size() == myItems.size()) {
// Check that every item in 'myItems' is also in 'otherItems'
if (!otherItems.containsAll(myItems)) {
return false;
}
} else {
return false;
}
// 2-way comparison
}
return true;
}
/**
* Obtain the logged entries for the specified place, tag, and phase tuple.
*
* @param place the number id of the place from which events should be retrieved
* @param tag the tag under which the logged items were gathered
* @param phase the phase during which the events were logged
* @return a collection containing the {@link LogItem} that were recorded under
* the specified tuple, {@code null} if there are no such
*/
public Collection<LogItem> getLog(int place, String tag, long phase) {
return getLog(new Key(place, tag, phase));
}
/**
* Obtain the logged entries for the specified key
*
* @param k the key for which logged elements should be retrieved
* @return collection of {@link LogItem} matching the key, or {@code null} if
* there are no such elements
*/
public Collection<LogItem> getLog(Key k) {
return loggedEntries.get(k);
}
/**
* Obtain the logged entries for the specified log key. The provided key is
* converted from {@link LogKey} to {@link Key} to retrieve the logged elements
* from the {@link SavedLog} object.
*
* @param k key from a {@link DistLog} instance
* @return collection of logged items that
*/
public Collection<LogItem> getLog(LogKey k) {
return getLog(k.place.id, k.tag, k.phase);
}
/**
* Returns the number of hosts that took part in the execution this
* {@link SavedLog} is the
*
* @return the number of processes involved in this computation
*/
public int placeCount() {
return numberOfHosts;
}
/**
* Dumps the entire contents of the saved log on the provided
* {@link PrintStream}.
*
* @param out the output stream on which the entire contents of the saved log
* need to be dumped
*/
public void printAll(PrintStream out) {
// Custom map sorted by place first and tag second
final TreeMap<Key, Collection<LogItem>> sorted = new TreeMap<>(sortPlaceTagPhase);
// Insert all logs into the map so that they get sorted
for (final Entry<Key, Collection<LogItem>> entry : loggedEntries.entrySet()) {
sorted.put(entry.getKey(), entry.getValue());
}
// Traverse the sorted map and print each log on a dedicated line
sorted.forEach((Key key, Collection<LogItem> items) -> {
out.println("LogKey: " + key);
for (final LogItem item : items) {
out.println("\t" + item);
}
});
}
/**
* Prints the keys and the number of entries for each key on the provided
* {@link PrintStream}.
*
* @param out the output stream on which the keys contained by the saved log
* need to be printed
*/
public void printKeys(PrintStream out) {
final TreeMap<Key, Collection<LogItem>> sorted = new TreeMap<>(sortPlaceTagPhase);
// Insert all logs into the map so that they get sorted
for (final Entry<Key, Collection<LogItem>> entry : loggedEntries.entrySet()) {
sorted.put(entry.getKey(), entry.getValue());
}
// Traverse the sorted map and print each log on a dedicated line
sorted.forEach((Key key, Collection<LogItem> items) -> {
out.println("LogKey: " + key + "\titemCount: " + items.size());
});
}
/**
* Records this instance to a file for later retrieval
*
* @param file the file to which this instance needs to be saved to
* @throws IOException if thrown during the process of saving this class to the
* specified file
*/
public void saveToFile(File file) throws IOException {
final ObjectOutputStream outStream = new ObjectOutputStream(new FileOutputStream(file));
outStream.writeInt(numberOfHosts);
outStream.writeObject(loggedEntries);
outStream.flush();
outStream.close();
}
}