diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleLogger.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleLogger.java new file mode 100644 index 000000000..57a1cc6fd --- /dev/null +++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleLogger.java @@ -0,0 +1,229 @@ +package cz.cuni.mff.d3s.deeco.task; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.HashMap; +import java.util.Map; + +import cz.cuni.mff.d3s.deeco.knowledge.ReadOnlyKnowledgeManager; +import cz.cuni.mff.d3s.deeco.logging.Log; +import cz.cuni.mff.d3s.deeco.model.runtime.api.EnsembleController; +import cz.cuni.mff.d3s.deeco.scheduler.Scheduler; + +/** + * Main usage of this class: logging of the changes in ensemble membership + * @see {@link EnsembleLogger#logEvent(EnsembleController, ReadOnlyKnowledgeManager, Scheduler, boolean)} + * @author Tomas Filipek + */ +class EnsembleLogger { + + /** + * Encapsulates three pieces of data: ensemble name, coordinator ID and member ID. + * It is used to index the information whether the membership condition holds for the + * given triplet. + * @author Tomas Filipek + */ + private static class MembershipRecord { + + /** + * Identification of the ensemble + */ + private final String ensembleName; + + /** + * Identification of the coordinator + */ + private final String coordinatorID; + + /** + * Identification of the member + */ + private final String memberID; + + /** + * @param ensembleName Identification of the ensemble + * @param coordinatorID Identification of the coordinator + * @param memberID Identification of the member + */ + public MembershipRecord(String ensembleName, String coordinatorID, String memberID) { + this.ensembleName = ensembleName; + this.coordinatorID = coordinatorID; + this.memberID = memberID; + } + + /** + * The produced hash code depends on precisely the following fields: + * {@link MembershipRecord#ensembleName} , + * {@link MembershipRecord#coordinatorID} , + * {@link MembershipRecord#memberID} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime + * result + + ((coordinatorID == null) ? 0 : coordinatorID + .hashCode()); + result = prime + * result + + ((ensembleName == null) ? 0 : ensembleName.hashCode()); + result = prime * result + + ((memberID == null) ? 0 : memberID.hashCode()); + return result; + } + + /** + * The equality holds if and only if the following fields are equal amid the two instances: + * {@link MembershipRecord#ensembleName} , + * {@link MembershipRecord#coordinatorID} , + * {@link MembershipRecord#memberID} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof EnsembleLogger.MembershipRecord)) { + return false; + } + EnsembleLogger.MembershipRecord other = (EnsembleLogger.MembershipRecord) obj; + if (coordinatorID == null) { + if (other.coordinatorID != null) { + return false; + } + } else if (!coordinatorID.equals(other.coordinatorID)) { + return false; + } + if (ensembleName == null) { + if (other.ensembleName != null) { + return false; + } + } else if (!ensembleName.equals(other.ensembleName)) { + return false; + } + if (memberID == null) { + if (other.memberID != null) { + return false; + } + } else if (!memberID.equals(other.memberID)) { + return false; + } + return true; + } + } + + /** + * The event log will be saved to this file. + */ + private final File ensembleEventLog = new File("logs/ensembles.xml"); + + /** + * An instance of {@link Writer} that writes into {@link EnsembleLogger#ensembleEventLog}. + * @see {@link EnsembleLogger#ensembleEventLog} + */ + private final OutputStreamWriter out; + + /** + *

Used to remember the last encountered value of the membership condition, computed on the + * input objects which are given as an instance of {@link MembershipRecord}.

+ *

Example:
+ * key = triplet of (ensemble E, coordinator C, member M)
+ * value = true (meaning that when last checked, M was in ensemble E, where C was the coordinator) + *

+ */ + private final Map membershipRecords = new HashMap<>(); + + /** + * Initializes the output streams + */ + private EnsembleLogger() { + OutputStreamWriter osw; + try { + FileOutputStream fos = new FileOutputStream(ensembleEventLog); + osw = new OutputStreamWriter(fos); + } catch (Exception ex) { + osw = null; + } + out = osw; + } + + /** + * The singleton instance of this class + */ + private static final EnsembleLogger INSTANCE = new EnsembleLogger(); + + /** + * @return The singleton instance of this class + * @see {@link EnsembleLogger#INSTANCE} + */ + public static EnsembleLogger getInstance() { + return INSTANCE; + } + + /** + * Logs the information about ensemble membership to the output file. It remembers the + * previous value, so the output is produced only if it has changed from the last time. + * @param ensembleController Controller of the {@link EnsembleTask} where this method is called. + * @param shadowKnowledgeManager Knowledge of the other component + * @param scheduler Used to get the current time + * @param membership The current value of the membership condition + */ + public void logEvent(EnsembleController ensembleController, ReadOnlyKnowledgeManager shadowKnowledgeManager, + Scheduler scheduler, boolean membership){ + long timeSeconds = scheduler.getCurrentMilliseconds() / 1000L; + String ensembleName = ensembleController.getEnsembleDefinition().getName(); + String coordinatorID = ensembleController.getComponentInstance().getKnowledgeManager().getId(); + String memberID = shadowKnowledgeManager.getId(); + EnsembleLogger.MembershipRecord mr = new MembershipRecord(ensembleName, coordinatorID, memberID); + boolean oldMembership = membershipRecords.get(mr) == null ? false : membershipRecords.get(mr); + if (oldMembership != membership){ + membershipRecords.put(mr, Boolean.valueOf(membership)); + String[] attrNames = new String[] {"coordinator", "member", "membership", "ensemble", "time"}; + String[] attrValues = new String[] {coordinatorID, memberID, Boolean.toString(membership), ensembleName, Long.toString(timeSeconds)}; + String elementText = getElementText("event", attrNames, attrValues); + try { + out.append(elementText); + out.flush(); + } catch (IOException | NullPointerException ex) { + Log.e("Could not log an ensemble event to " + ensembleEventLog.getAbsolutePath().toString()); + } + } + } + + /** + * Builds a textual representation of an XML element defined by the parameters. + * A newline is added to the end of the element. + * @param name Name of the element + * @param attributeNames Names of the attributes, in the order as they will appear + * @param attributeValues Values of the attributes, in the same order as the names + * @return A textual representation of an XML element defined by the parameters + */ + private String getElementText(String name, String[] attributeNames, String[] attributeValues){ + if ((name == null) || name.isEmpty() || (attributeNames == null) || + (attributeValues == null) || (attributeNames.length != attributeValues.length)){ + return null; + } + StringBuilder sb = new StringBuilder(); + sb.append("<"); + sb.append(name); + sb.append(" "); + for (int i = 0; i < attributeNames.length; i++){ + String attrName = attributeNames[i]; + String attrValue = attributeValues[i]; + sb.append(attrName); + sb.append("=\""); + sb.append(attrValue); + sb.append("\" "); + } + sb.append("/>"); + sb.append("\n"); + return sb.toString(); + } +} diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleTask.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleTask.java index 610f91986..1bc1a2d48 100644 --- a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleTask.java +++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleTask.java @@ -9,10 +9,10 @@ import cz.cuni.mff.d3s.deeco.knowledge.ChangeSet; import cz.cuni.mff.d3s.deeco.knowledge.KnowledgeManager; -import cz.cuni.mff.d3s.deeco.knowledge.KnowledgeUpdateException; -import cz.cuni.mff.d3s.deeco.knowledge.ShadowKnowledgeManagerRegistry; import cz.cuni.mff.d3s.deeco.knowledge.KnowledgeNotFoundException; +import cz.cuni.mff.d3s.deeco.knowledge.KnowledgeUpdateException; import cz.cuni.mff.d3s.deeco.knowledge.ReadOnlyKnowledgeManager; +import cz.cuni.mff.d3s.deeco.knowledge.ShadowKnowledgeManagerRegistry; import cz.cuni.mff.d3s.deeco.knowledge.ShadowsTriggerListener; import cz.cuni.mff.d3s.deeco.knowledge.TriggerListener; import cz.cuni.mff.d3s.deeco.knowledge.ValueSet; @@ -25,8 +25,8 @@ import cz.cuni.mff.d3s.deeco.model.runtime.api.ParameterDirection; import cz.cuni.mff.d3s.deeco.model.runtime.api.TimeTrigger; import cz.cuni.mff.d3s.deeco.model.runtime.api.Trigger; -import cz.cuni.mff.d3s.deeco.model.runtime.meta.RuntimeMetadataFactory; import cz.cuni.mff.d3s.deeco.model.runtime.impl.TriggerImpl; +import cz.cuni.mff.d3s.deeco.model.runtime.meta.RuntimeMetadataFactory; import cz.cuni.mff.d3s.deeco.runtime.ArchitectureObserver; import cz.cuni.mff.d3s.deeco.scheduler.Scheduler; import cz.cuni.mff.d3s.deeco.task.KnowledgePathHelper.KnowledgePathAndRoot; @@ -495,14 +495,20 @@ public void invoke(Trigger trigger) throws TaskInvocationException { } } } - + private void evaluateMembershipAndPerformExchange(ReadOnlyKnowledgeManager shadowKnowledgeManager) throws TaskInvocationException { // Invoke the membership condition and if the membership condition returned true, invoke the knowledge exchange + boolean membership; if (checkMembership(PathRoot.COORDINATOR, shadowKnowledgeManager)) { architectureObserver.ensembleFormed(ensembleController.getEnsembleDefinition(), ensembleController.getComponentInstance(), ensembleController.getComponentInstance().getKnowledgeManager().getId(),shadowKnowledgeManager.getId()); performExchange(PathRoot.COORDINATOR, shadowKnowledgeManager); + membership = true; + } else { + membership = false; } + EnsembleLogger.getInstance().logEvent(ensembleController, shadowKnowledgeManager, scheduler, membership); + // Do the same with the roles exchanged if (checkMembership(PathRoot.MEMBER, shadowKnowledgeManager)) { architectureObserver.ensembleFormed(ensembleController.getEnsembleDefinition(), ensembleController.getComponentInstance(),