/* # Licensed Materials - Property of IBM # Copyright IBM Corp. 2015 */ package com.ibm.streamsx.topology.generator.spl; import static com.ibm.streamsx.topology.builder.BVirtualMarker.END_LOW_LATENCY; import static com.ibm.streamsx.topology.builder.BVirtualMarker.ISOLATE; import static com.ibm.streamsx.topology.builder.BVirtualMarker.LOW_LATENCY; import static com.ibm.streamsx.topology.generator.operator.OpProperties.CONFIG; import static com.ibm.streamsx.topology.generator.operator.OpProperties.PLACEMENT; import static com.ibm.streamsx.topology.generator.operator.OpProperties.PLACEMENT_COLOCATE_KEY; import static com.ibm.streamsx.topology.generator.operator.OpProperties.PLACEMENT_EXPLICIT_COLOCATE_ID; import static com.ibm.streamsx.topology.generator.operator.OpProperties.PLACEMENT_ISOLATE_REGION_ID; import static com.ibm.streamsx.topology.generator.operator.OpProperties.PLACEMENT_LOW_LATENCY_REGION_ID; import static com.ibm.streamsx.topology.generator.spl.GraphUtilities.findOperatorByKind; import static com.ibm.streamsx.topology.generator.spl.GraphUtilities.getDownstream; import static com.ibm.streamsx.topology.generator.spl.GraphUtilities.getUpstream; import static com.ibm.streamsx.topology.generator.spl.GraphUtilities.operators; import static com.ibm.streamsx.topology.internal.graph.GraphKeys.CFG_COLOCATE_TAG_MAPPING; import static com.ibm.streamsx.topology.internal.graph.GraphKeys.CFG_HAS_ISOLATE; import static com.ibm.streamsx.topology.internal.graph.GraphKeys.CFG_HAS_LOW_LATENCY; import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.jstring; import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.object; import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.objectCreate; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.gson.JsonObject; import com.ibm.streamsx.topology.builder.BVirtualMarker; import com.ibm.streamsx.topology.function.Consumer; import com.ibm.streamsx.topology.generator.operator.OpProperties; import com.ibm.streamsx.topology.internal.graph.GraphKeys; import com.ibm.streamsx.topology.internal.gson.GsonUtilities; class PEPlacement { private final SPLGenerator generator; private final JsonObject graph; private int isolateRegionCount; private int lowLatencyRegionCount; PEPlacement(SPLGenerator generator, JsonObject graph) { this.generator = generator; this.graph = graph; } private void setIsolateRegionId(JsonObject op, String isolationRegionId) { JsonObject placement = objectCreate(op, CONFIG, PLACEMENT); // As we create regions up and downstream of isolate // operators the same region can be seen twice, // e.g x -> isolate1 -> y -> isolate2 -> z // y is seen twice, once from isolate1 and once from isolate2 if (jstring(placement, PLACEMENT_ISOLATE_REGION_ID) != null) return; placement.addProperty(PLACEMENT_ISOLATE_REGION_ID, isolationRegionId); } /** * Assign a region isolate identifier to all operators * in an isolate region. From the starts (which are immediately * up/downstream of an isolate set the isolate region id for * all reachable operators until another isolate or the edge is hit. * * @param starts Set of operators upstream or downstream of an isolate marker. */ private void assignIsolateRegionIds(Set<JsonObject> starts) { final String isolationRegionId = newIsolateRegionId(); Set<BVirtualMarker> boundaries = EnumSet.of(BVirtualMarker.ISOLATE); GraphUtilities.visitOnce(starts, boundaries, graph, op -> setIsolateRegionId(op, isolationRegionId)); } /** * Determine whether any isolated region is ever joined with its parent. * I.E: * * <pre> * <code> * |---$Isolate---| * ----| |---- * |--------------| * </code> * </pre> * * @param isolate * An $Isolate$ operator in the graph * @return a boolean which is false if the the Isolated region is later * merged with its parent. */ @SuppressWarnings("serial") private void checkValidColocationRegion(JsonObject isolate) { final Set<JsonObject> isolateChildren = GraphUtilities.getDownstream( isolate, graph); Set<JsonObject> isoParents = GraphUtilities.getUpstream(isolate, graph); assertNotIsolated(isoParents); Set<BVirtualMarker> boundaries = EnumSet.of(BVirtualMarker.ISOLATE); GraphUtilities.visitOnce(isoParents, boundaries, graph, new Consumer<JsonObject>() { @Override public void accept(JsonObject op) { if (isolateChildren.contains(op)) { throw new IllegalStateException( "Invalid isolation " + "configuration. An isolated region is joined with a non-" + "isolated region."); } } }); } void tagIsolationRegions() { // Check whether graph is valid for colocations Set<JsonObject> isolateOperators = findOperatorByKind(ISOLATE, graph); if (!isolateOperators.isEmpty()) graph.getAsJsonObject("config").addProperty(CFG_HAS_ISOLATE, true); for (JsonObject jso : isolateOperators) { checkValidColocationRegion(jso); } // Assign isolation regions their partition colocations // by working upstream from the the isolate marker // and then downstream to separate the regions with // different isolate region identifiers. for (JsonObject isolate : isolateOperators) { assignIsolateRegionIds(getUpstream(isolate, graph)); assignIsolateRegionIds(getDownstream(isolate, graph)); } // For 4.2 and later we do not force colocation // on every operator, instead we allow submission // time fusion to figure out the best plan. if (!generator.versionAtLeast(4, 2)) tagIslandIsolatedRegions(); GraphUtilities.removeOperators(isolateOperators, graph); } /** * Tag any "island" regions with their own isolated region id. * This can occur when there are there sub-graphs that are * not connected to a region already processed with an isolate. * So two cases: * a) No isolates exist at all in the graph * b) Isolates exist in the whole graph but a disconnected * sub-graph has no isolates. * @param graph */ private void tagIslandIsolatedRegions(){ Set<JsonObject> starts = GraphUtilities.findStarts(graph); for(JsonObject start : starts){ final String colocationTag = newIsolateRegionId(); JsonObject placement = objectCreate(start, CONFIG, PLACEMENT); String regionTag = jstring(placement, PLACEMENT_ISOLATE_REGION_ID); if (regionTag != null && !regionTag.isEmpty()) { continue; } Set<JsonObject> startList = Collections.singleton(start); Set<BVirtualMarker> boundaries = EnumSet.of(BVirtualMarker.ISOLATE); GraphUtilities.visitOnce(startList, boundaries, graph, op -> setIsolateRegionId(op, colocationTag)); } } private String newIsolateRegionId() { return "__spl_isolateRegionId" + isolateRegionCount++; } private static void assertNotIsolated(Collection<JsonObject> jsos) { for (JsonObject jso : jsos) { if (BVirtualMarker.ISOLATE.isThis(jstring(jso, "kind"))) { throw new IllegalStateException( "Cannot put \"isolate\" regions immediately" + " adjacent to each other. E.g -- .isolate().isolate()"); } } } void tagLowLatencyRegions() { Set<JsonObject> lowLatencyStartOperators = GraphUtilities .findOperatorByKind(LOW_LATENCY, graph); if (lowLatencyStartOperators.isEmpty()) return; graph.getAsJsonObject("config").addProperty(CFG_HAS_LOW_LATENCY, true); // Assign isolation regions their lowLatency tag for (JsonObject llStart : lowLatencyStartOperators) { assignLowLatency(llStart); } // Remove all the markers lowLatencyStartOperators.addAll(findOperatorByKind(END_LOW_LATENCY, graph)); GraphUtilities.removeOperators(lowLatencyStartOperators, graph); } @SuppressWarnings("serial") private void assignLowLatency(JsonObject llStart) { final String lowLatencyTag = "__spl_lowLatencyRegionId" + lowLatencyRegionCount++; Set<JsonObject> llStartChildren = getDownstream(llStart, graph); Set<BVirtualMarker> boundaries = EnumSet.of(LOW_LATENCY, END_LOW_LATENCY); GraphUtilities.visitOnce(llStartChildren, boundaries, graph, new Consumer<JsonObject>() { @Override public void accept(JsonObject op) { // Add a manual threading annotation // to ensure low latency by not allowing // any scheduled ports to be added //JsonObject threading = new JsonObject(); //threading.addProperty("model", "manual"); // op.add("threading", threading); // If the region has already been assigned a // lowLatency tag, simply return. JsonObject placement = objectCreate(op, CONFIG, PLACEMENT); assert jstring(placement, PLACEMENT_LOW_LATENCY_REGION_ID) == null || jstring(placement, PLACEMENT_LOW_LATENCY_REGION_ID).equals(lowLatencyTag); placement.addProperty(PLACEMENT_LOW_LATENCY_REGION_ID, lowLatencyTag); } }); } /** * Goes through the graph and looks to merge all colocation tags to a * single value for the set of colocated operators. * This resolves the mutliple potential colocate tags * through explicit colocation, isolation and low-latency. * Each operator with a colocate directive is left with * a single key to a map of tags in the graph config. * The value in the map is the actual tag used in SPL * placement config for the operator. */ void resolveColocationTags() { JsonObject tagMaps = new JsonObject(); operators(graph, op -> { JsonObject placement = object(op, CONFIG, PLACEMENT); if (placement == null) return; // Three types of co-locate. String explicit = jstring(placement, PLACEMENT_EXPLICIT_COLOCATE_ID); String lowLatency = jstring(placement, PLACEMENT_LOW_LATENCY_REGION_ID); String isolate = jstring(placement, PLACEMENT_ISOLATE_REGION_ID); Set<String> sameTags = new HashSet<>(); if (explicit != null) sameTags.add(explicit); if (lowLatency != null) sameTags.add(lowLatency); if (isolate != null) sameTags.add(isolate); if (sameTags.isEmpty()) return; // Find if any of these tags are already mapped. Set<String> existingTags = new HashSet<>(); for (String tag : sameTags) { if (tagMaps.has(tag)) { existingTags.add(jstring(tagMaps, tag)); } } final String singleTag; if (existingTags.isEmpty()) { // pick any one of the existing ones // none of which have been seen yet. singleTag = sameTags.iterator().next(); } else if (existingTags.size() == 1) { // single existing tag, use it singleTag = existingTags.iterator().next(); } else { // Need to merge tags // e.g. A->A, B->B were separate but now we need to merge A,B // pick one tag as single singleTag = existingTags.iterator().next(); sameTags.addAll(existingTags); } for (String tag : sameTags) tagMaps.addProperty(tag, singleTag); // finally add the single tag key to the operator placement.addProperty(PLACEMENT_COLOCATE_KEY, singleTag); }); object(graph, CONFIG).add(CFG_COLOCATE_TAG_MAPPING, tagMaps); } }