/* Copyright 2014 MITRE Corporation
*
* 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 org.mitre.provenance.dag;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.mitre.provenance.Metadata;
import org.mitre.provenance.PLUSException;
import org.mitre.provenance.PropertyCapable;
import org.mitre.provenance.PropertySet;
import org.mitre.provenance.StopWatch;
import org.mitre.provenance.plusobject.PLUSActor;
import org.mitre.provenance.plusobject.PLUSEdge;
import org.mitre.provenance.plusobject.PLUSObject;
import org.mitre.provenance.plusobject.ProvenanceCollection;
import org.mitre.provenance.tools.PLUSUtils;
/**
* A FingerPrint is a statistical profile of a LineageDAG that stores basic information
* about the nature of the information in the DAG. This is a first-class object which can be
* written, loaded, and so on.
* @see LineageDAG#getFingerPrint()
* @author moxious
*/
public class FingerPrint extends DAGWatcher implements PropertyCapable {
private static final Logger log = Logger.getLogger(FingerPrint.class.getName());
protected String id = null;
protected boolean finished = false;
protected boolean written = false;
protected int nodes = 0;
protected int edges = 0;
protected int owners = 0;
protected int invocations = 0;
protected int data = 0;
protected String dagId = null;
protected String startId = null;
protected long created;
protected long runTime = 0;
protected long minNodeCreated = Long.MAX_VALUE-1;
protected long maxNodeCreated = 0;
protected HashMap<String,Boolean> seenOwners = new HashMap<String,Boolean>();
protected HashMap<String, StopWatch> timers = new HashMap<String,StopWatch>();
protected HashMap<String,NodeProfile> nodeProfiles = new HashMap<String,NodeProfile> ();
protected PathCollection pathCollection = null;
/** Data/invocation ratio */
public static final String DI_RATIO = "DIRatio";
/** Data/total ratio */
public static final String DATA_RATIO = "DataRatio";
/** Invocation/total ratio */
public static final String INVOCATION_RATIO = "InvRatio";
/** Maximum amount of inbound edges to any node */
public static final String MAX_IN_EDGES = "MaxInEdges";
/** Maximum amount of outbound edges to any node */
public static final String MAX_OUT_EDGES = "MaxOutEdges";
/** Node/Edge Ratio */
public static final String NODE_EDGE_RATIO = "NodeEdgeRatio";
/** Average total number of edges per node */
public static final String AVG_TOTAL_EDGES = "AverageTotalEdges";
/** Run time of entire graph (newest node creation time minus oldest node creation time) */
public static final String RUN_TIME = "RunTime";
protected long init = 0;
protected long end = 0;
public FingerPrint() {
id = PLUSUtils.generateID();
init = System.currentTimeMillis();
}
public String getId() { return id; }
public void setStartId(String startId) { this.startId = startId; }
protected void setCreated(long creationTime) { created = creationTime; }
/**
* Sets the object's created timestamp to this moment in milliseconds since the epoch, UTC
*/
public void setCreated() {
Calendar calInitial = Calendar.getInstance();
int offsetInitial = calInitial.get(Calendar.ZONE_OFFSET)
+ calInitial.get(Calendar.DST_OFFSET);
long current = System.currentTimeMillis();
// Check right time
created = current - offsetInitial;
}
public String toString() {
StringBuffer b = new StringBuffer("");
Metadata m = asMetadata();
for(String k : m.keySet()) {
b.append(k + " = " + m.get(k) + "\n");
}
return b.toString();
}
public long getCreated() { return created; }
public void setDagId(String dagId) { this.dagId = dagId; }
public String getDagId() { return dagId; }
/**
* Return the FingerPrint object as a metadata table
* @return a table mapping field names (such as INVOCATION_RATIO) to values
*/
public Metadata asMetadata() {
Metadata m = new Metadata();
m.put("TotalTime", (end-init));
m.put("DataItems", ""+data);
m.put("Invocations", ""+invocations);
m.put("Nodes", ""+nodes);
m.put("Edges", ""+edges);
m.put(RUN_TIME, ""+(maxNodeCreated-minNodeCreated));
m.put(NODE_EDGE_RATIO, ""+(edges <= 0 ? 0 : ((double)nodes/(double)edges)));
m.put("Owners", ""+owners);
for(String key : timers.keySet())
m.put("timer:"+key, timers.get(key).toString());
int nps = 0;
int inMax = 0;
int outMax = 0;
int totTot = 0;
for(String key : nodeProfiles.keySet()) {
nps++;
NodeProfile p = nodeProfiles.get(key);
if(p.getEdgesIn() > inMax) inMax = p.getEdgesIn();
if(p.getEdgesOut() > outMax) outMax = p.getEdgesOut();
totTot += p.getTotalEdges();
} // End for
m.put(MAX_IN_EDGES, ""+inMax);
m.put(MAX_OUT_EDGES, ""+outMax);
m.put(INVOCATION_RATIO, ""+(invocations <= 0 ? 0 : ((double)invocations/(double)nodes)));
m.put(DATA_RATIO, ""+(data <= 0 ? 0 : ((double)data/(double)nodes)));
m.put(DI_RATIO, ""+(invocations <= 0 ? 0 : ((double)data/(double)invocations)));
if(nps > 0) m.put(AVG_TOTAL_EDGES, ""+((double)totTot/(double)nps));
else m.put(AVG_TOTAL_EDGES, "0");
return m;
} // End asMetadata
public void edgeRemoved(PLUSEdge edge) {
if(finished) log.severe("Removing edge " + edge + " after graph was finished!");
edges--;
} // End edgeRemoved
public void nodeRemoved(PLUSObject obj) {
if(finished) log.severe("Removing node " + obj.getName() + " after graph was finished!");
nodes--;
nodeProfiles.remove(obj.getId());
}
public void edgeAdded(PLUSEdge edge) {
if(finished) log.severe("Adding edge " + edge + " after graph was finished!");
edges++;
String from = edge.getFrom().getId();
String to = edge.getTo().getId();
NodeProfile fp = getProfile(from);
NodeProfile tp = getProfile(to);
fp.edgeOut();
tp.edgeIn();
} // End edgeAdded
protected NodeProfile getProfile(String nodeId) {
if(nodeId == null) nodeId = "null";
if(!nodeProfiles.containsKey(nodeId))
nodeProfiles.put(nodeId, new NodeProfile());
return nodeProfiles.get(nodeId);
} // End getProfile
/**
* Indicate that the dag in question is finished, and will not be subsequently changed.
* This permits any fingerprinting that requires presence of the entire DAG.
* <p><b>As a side-effect of this call, after it has been called, certain other functions will
* issue error messages</b>. Examples would include adding nodes and edges after the graph was
* finished.
* @param dag the DAG that has been finished.
*/
public void finished(LineageDAG dag) {
end = System.currentTimeMillis();
nodes = dag.countNodes();
edges = dag.countEdges();
data = 0;
invocations = 0;
for(PLUSObject o : dag.getNodes()) {
if(o.isDataItem()) data++;
if(o.isInvocation()) invocations++;
}
finished = true;
}
public void nodeAdded(PLUSObject node) {
if(finished) log.severe("Adding node " + node.getName() + " after graph was finished!");
nodes++;
if(startId == null) setStartId(node.getId());
long c = node.getCreated();
if(c > maxNodeCreated) maxNodeCreated = c;
if(c < minNodeCreated) minNodeCreated = c;
if(node.isDataItem()) data++;
if(node.isInvocation()) invocations++;
try {
PLUSActor a = node.getOwner();
String name = "unknown";
if(a != null) name = a.getName();
if(!seenOwners.containsKey(name)) {
owners++;
seenOwners.put(name, true);
} // End if
} catch(Exception e) {
log.severe("FingerPrint#nodeAdded: " + e);
} // End catch
} // End nodeAdded
public void startTimer(String timerName) {
StopWatch t = timers.get(timerName);
if(t == null) { t = new StopWatch(); timers.put(timerName, t); }
t.start();
} // End startTimer
public void stopTimer(String timerName) {
StopWatch t = timers.get(timerName);
if(t == null) {
log.severe("Stopping non-existant timer " + timerName + "!");
t = new StopWatch(); timers.put(timerName, t);
}
if(!t.isStarted()) {
log.warning("Stopping not-started timer " + timerName);
}
t.stop();
} // End stopTimer
public Map<String, Object> getStorableProperties() {
Metadata m = asMetadata();
m.put("dagId", dagId);
m.put("startId", startId);
m.put("created", created);
m.put(RUN_TIME, Long.parseLong(""+m.get(RUN_TIME)));
m.put("nodes", nodes);
m.put("edges", edges);
return m;
}
public PLUSObject setProperties(PropertySet props, ProvenanceCollection contextCollection) throws PLUSException {
// TODO Auto-generated method stub
throw new PLUSException("Implement me");
}
/***************************************************************************************/
private class NodeProfile {
int in = 0;
int out = 0;
public NodeProfile() { in=0; out=0; }
public void edgeIn() { in++; }
public void edgeOut() { out++; }
public int getEdgesIn() { return in; }
public int getEdgesOut() { return out; }
public int getTotalEdges() { return in + out; }
} // end class NodeProfile
} // End FingerPrint