| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.pig.newplan; |
| |
| import java.io.PrintStream; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.HashSet; |
| |
| import org.apache.pig.impl.util.MultiMap; |
| |
| /** |
| * This class puts everything that is needed to dump a plan in a |
| * format readable by graphviz's dot algorithm. Out of the box it does |
| * not print any nested plans. |
| */ |
| public class DotPlanDumper extends PlanDumper { |
| |
| protected Set<Operator> mSubgraphs; |
| protected Set<Operator> mMultiInputSubgraphs; |
| protected Set<Operator> mMultiOutputSubgraphs; |
| private boolean isSubGraph = false; |
| |
| public DotPlanDumper(BaseOperatorPlan plan, PrintStream ps) { |
| this(plan, ps, false, new HashSet<Operator>(), new HashSet<Operator>(), |
| new HashSet<Operator>()); |
| } |
| |
| protected DotPlanDumper(BaseOperatorPlan plan, PrintStream ps, boolean isSubGraph, |
| Set<Operator> mSubgraphs, |
| Set<Operator> mMultiInputSubgraphs, |
| Set<Operator> mMultiOutputSubgraphs) { |
| super(plan, ps); |
| this.isSubGraph = isSubGraph; |
| this.mSubgraphs = mSubgraphs; |
| this.mMultiInputSubgraphs = mMultiInputSubgraphs; |
| this.mMultiOutputSubgraphs = mMultiOutputSubgraphs; |
| } |
| |
| @Override |
| public void dump() { |
| if (!isSubGraph) { |
| ps.println("digraph plan {"); |
| ps.println("compound=true;"); |
| ps.println("node [shape=rect];"); |
| } |
| super.dump(); |
| if (!isSubGraph) { |
| ps.println("}"); |
| } |
| } |
| |
| @Override |
| protected void dumpMultiInputNestedOperator(Operator op, MultiMap<Operator, BaseOperatorPlan> plans) { |
| dumpInvisibleOutput(op); |
| |
| ps.print("subgraph "); |
| ps.print(getClusterID(op)); |
| ps.println(" {"); |
| join("; ", getAttributes(op)); |
| ps.println("labelloc=b;"); |
| |
| mMultiInputSubgraphs.add(op); |
| |
| for (Operator o: plans.keySet()) { |
| ps.print("subgraph "); |
| ps.print(getClusterID(op, o)); |
| ps.println(" {"); |
| ps.println("label=\"\";"); |
| dumpInvisibleInput(op, o); |
| for (BaseOperatorPlan plan : plans.get(o)) { |
| PlanDumper dumper = makeDumper(plan, ps); |
| dumper.dump(); |
| connectInvisibleInput(op, o, plan); |
| } |
| ps.println("};"); |
| } |
| ps.println("};"); |
| |
| for (Operator o: plans.keySet()) { |
| for (BaseOperatorPlan plan: plans.get(o)) { |
| connectInvisibleOutput(op, plan); |
| } |
| } |
| } |
| |
| @Override |
| protected void dumpMultiOutputNestedOperator(Operator op, Collection<BaseOperatorPlan> plans) { |
| super.dumpMultiOutputNestedOperator(op, plans); |
| |
| mMultiOutputSubgraphs.add(op); |
| |
| dumpInvisibleOutput(op); |
| for (BaseOperatorPlan plan: plans) { |
| connectInvisibleOutput(op, plan); |
| } |
| } |
| |
| @Override |
| protected void dumpNestedOperator(Operator op, Collection<BaseOperatorPlan> plans) { |
| dumpInvisibleOperators(op); |
| ps.print("subgraph "); |
| ps.print(getClusterID(op)); |
| ps.println(" {"); |
| join("; ", getAttributes(op)); |
| ps.println("labelloc=b;"); |
| |
| mSubgraphs.add(op); |
| |
| for (BaseOperatorPlan plan: plans) { |
| PlanDumper dumper = makeDumper(plan, ps); |
| dumper.dump(); |
| connectInvisibleInput(op, plan); |
| } |
| ps.println("};"); |
| |
| for (BaseOperatorPlan plan: plans) { |
| connectInvisibleOutput(op, plan); |
| } |
| } |
| |
| @Override |
| protected void dumpOperator(Operator op) { |
| ps.print(getID(op)); |
| ps.print(" ["); |
| join(", ", getAttributes(op)); |
| ps.println("];"); |
| } |
| |
| @Override |
| protected void dumpEdge(Operator op, Operator suc) { |
| String in = getID(op); |
| String out = getID(suc); |
| String attributes = ""; |
| |
| if (mMultiInputSubgraphs.contains(op) |
| || mSubgraphs.contains(op) |
| || mMultiOutputSubgraphs.contains(op)) { |
| in = getSubgraphID(op, false); |
| } |
| |
| if (mMultiInputSubgraphs.contains(suc)) { |
| out = getSubgraphID(suc, op, true); |
| attributes = " [lhead="+getClusterID(suc,op)+"]"; |
| } |
| |
| if (mSubgraphs.contains(suc)) { |
| out = getSubgraphID(suc, true); |
| attributes = " [lhead="+getClusterID(suc)+"]"; |
| } |
| |
| if (reverse(plan)) { |
| ps.print(out); |
| ps.print(" -> "); |
| ps.print(in); |
| } else { |
| ps.print(in); |
| ps.print(" -> "); |
| ps.print(out); |
| } |
| ps.println(attributes); |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| protected PlanDumper makeDumper(BaseOperatorPlan plan, PrintStream ps) { |
| return new DotPlanDumper(plan, ps, true, |
| mSubgraphs, mMultiInputSubgraphs, |
| mMultiOutputSubgraphs); |
| } |
| |
| /** |
| * Used to generate the label for an operator. |
| * @param op operator to dump |
| */ |
| protected String getName(Operator op) { |
| return op.getName(); |
| } |
| |
| /** |
| * Used to generate the the attributes of a node |
| * @param op operator |
| */ |
| protected String[] getAttributes(Operator op) { |
| String[] attributes = new String[1]; |
| attributes[0] = "label=\""+getName(op)+"\""; |
| return attributes; |
| } |
| |
| |
| private void connectInvisibleInput(Operator op1, Operator op2, BaseOperatorPlan plan) { |
| String in = getSubgraphID(op1, op2, true); |
| |
| List<Operator> sources; |
| if (reverse(plan)) |
| sources = plan.getSinks(); |
| else |
| sources = plan.getSources(); |
| |
| for (Operator l: sources) { |
| dumpInvisibleEdge(in, getID(l)); |
| } |
| } |
| |
| private void connectInvisibleInput(Operator op, BaseOperatorPlan plan) { |
| String in = getSubgraphID(op, true); |
| |
| List<Operator> sources; |
| if (reverse(plan)) |
| sources = plan.getSinks(); |
| else |
| sources = plan.getSources(); |
| |
| for (Operator l: sources) { |
| String out; |
| if (mSubgraphs.contains(l) || mMultiInputSubgraphs.contains(l)) { |
| out = getSubgraphID(l, true); |
| } else { |
| out = getID(l); |
| } |
| |
| dumpInvisibleEdge(in, out); |
| } |
| } |
| |
| private void connectInvisibleOutput(Operator op, |
| BaseOperatorPlan plan) { |
| String out = getSubgraphID(op, false); |
| |
| List<Operator> sinks; |
| if (reverse(plan)) |
| sinks = plan.getSources(); |
| else |
| sinks = plan.getSinks(); |
| |
| for (Operator l: sinks) { |
| String in; |
| if (mSubgraphs.contains(l) |
| || mMultiInputSubgraphs.contains(l) |
| || mMultiOutputSubgraphs.contains(l)) { |
| in = getSubgraphID(l, false); |
| } else { |
| in = getID(l); |
| } |
| |
| dumpInvisibleEdge(in, out); |
| } |
| } |
| |
| private void connectInvisible(Operator op, BaseOperatorPlan plan) { |
| connectInvisibleInput(op, plan); |
| connectInvisibleOutput(op, plan); |
| } |
| |
| private void dumpInvisibleInput(Operator op1, Operator op2) { |
| ps.print(getSubgraphID(op1, op2, true)); |
| ps.print(" "); |
| ps.print(getInvisibleAttributes(op1)); |
| ps.println(";"); |
| } |
| |
| private void dumpInvisibleInput(Operator op) { |
| ps.print(getSubgraphID(op, true)); |
| ps.print(" "); |
| ps.print(getInvisibleAttributes(op)); |
| ps.println(";"); |
| } |
| |
| private void dumpInvisibleOutput(Operator op) { |
| ps.print(getSubgraphID(op, false)); |
| ps.print(" "); |
| ps.print(getInvisibleAttributes(op)); |
| ps.println(";"); |
| } |
| |
| protected void dumpInvisibleOperators(Operator op) { |
| dumpInvisibleInput(op); |
| dumpInvisibleOutput(op); |
| } |
| |
| private String getClusterID(Operator op1, Operator op2) { |
| return getClusterID(op1)+"_"+getID(op2); |
| } |
| |
| private String getClusterID(Operator op) { |
| return "cluster_"+getID(op); |
| } |
| |
| private String getSubgraphID(Operator op1, Operator op2, boolean in) { |
| String id = "s"+getID(op1)+"_"+getID(op2); |
| if (in) { |
| id += "_in"; |
| } |
| else { |
| id += "_out"; |
| } |
| return id; |
| } |
| |
| private String getSubgraphID(Operator op, boolean in) { |
| String id = "s"+getID(op); |
| if (in) { |
| id += "_in"; |
| } |
| else { |
| id += "_out"; |
| } |
| return id; |
| } |
| |
| private String getID(Operator op) { |
| return ""+Math.abs(op.hashCode()); |
| } |
| |
| private String getInvisibleAttributes(Operator op) { |
| return "[label=\"\", style=invis, height=0, width=0]"; |
| } |
| |
| private void dumpInvisibleEdge(String op, String suc) { |
| ps.print(op); |
| ps.print(" -> "); |
| ps.print(suc); |
| ps.println(" [style=invis];"); |
| } |
| |
| protected boolean reverse(BaseOperatorPlan plan) { |
| return false; |
| } |
| } |