blob: 3aa2c2ba5a9e3d802aea89e1562431942c307410 [file] [log] [blame]
/*
* 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.impl.plan;
import java.io.PrintStream;
import java.util.LinkedList;
import java.util.Collection;
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<E extends Operator, P extends OperatorPlan<E>,
N extends Operator, S extends OperatorPlan<N>>
extends PlanDumper<E, P, S> {
protected Set<Operator> mSubgraphs;
protected Set<Operator> mMultiInputSubgraphs;
protected Set<Operator> mMultiOutputSubgraphs;
private boolean isSubGraph = false;
public DotPlanDumper(P plan, PrintStream ps) {
this(plan, ps, false, new HashSet<Operator>(), new HashSet<Operator>(),
new HashSet<Operator>());
}
protected DotPlanDumper(P 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(E op, MultiMap<E, S> plans) {
dumpInvisibleOutput(op);
ps.print("subgraph ");
ps.print(getClusterID(op));
ps.println(" {");
join("; ", getAttributes(op));
ps.println("labelloc=b;");
mMultiInputSubgraphs.add(op);
for (E o: plans.keySet()) {
ps.print("subgraph ");
ps.print(getClusterID(op, o));
ps.println(" {");
ps.println("label=\"\";");
dumpInvisibleInput(op, o);
for (S plan : plans.get(o)) {
PlanDumper dumper = makeDumper(plan, ps);
dumper.dump();
connectInvisibleInput(op, o, plan);
}
ps.println("};");
}
ps.println("};");
for (E o: plans.keySet()) {
for (S plan: plans.get(o)) {
connectInvisibleOutput(op, plan);
}
}
}
@Override
protected void dumpMultiOutputNestedOperator(E op, Collection<S> plans) {
super.dumpMultiOutputNestedOperator(op, plans);
mMultiOutputSubgraphs.add(op);
dumpInvisibleOutput(op);
for (S plan: plans) {
connectInvisibleOutput(op, plan);
}
}
@Override
protected void dumpNestedOperator(E op, Collection<S> plans) {
dumpInvisibleOperators(op);
ps.print("subgraph ");
ps.print(getClusterID(op));
ps.println(" {");
join("; ", getAttributes(op));
ps.println("labelloc=b;");
mSubgraphs.add(op);
for (S plan: plans) {
PlanDumper dumper = makeDumper(plan, ps);
dumper.dump();
connectInvisibleInput(op, plan);
}
ps.println("};");
for (S plan: plans) {
connectInvisibleOutput(op, plan);
}
}
@Override
protected void dumpOperator(E 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);
}
ps.print(in);
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)+"]";
}
ps.print(" -> ");
ps.print(out);
ps.println(attributes);
}
@SuppressWarnings("unchecked")
@Override
protected PlanDumper makeDumper(S 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(E op) {
return op.name();
}
/**
* Used to generate the the attributes of a node
* @param op operator
*/
protected String[] getAttributes(E op) {
String[] attributes = new String[1];
attributes[0] = "label=\""+getName(op)+"\"";
return attributes;
}
private void connectInvisibleInput(E op1, E op2, S plan) {
String in = getSubgraphID(op1, op2, true);
for (N l: plan.getRoots()) {
dumpInvisibleEdge(in, getID(l));
}
}
private void connectInvisibleInput(E op, S plan) {
String in = getSubgraphID(op, true);
for (N l: plan.getRoots()) {
String out;
if (mSubgraphs.contains(l) || mMultiInputSubgraphs.contains(l)) {
out = getSubgraphID(l, true);
} else {
out = getID(l);
}
dumpInvisibleEdge(in, out);
}
}
private void connectInvisibleOutput(E op,
OperatorPlan<? extends Operator> plan) {
String out = getSubgraphID(op, false);
for (Operator l: plan.getLeaves()) {
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(E op, S plan) {
connectInvisibleInput(op, plan);
connectInvisibleOutput(op, plan);
}
private void dumpInvisibleInput(E op1, E op2) {
ps.print(getSubgraphID(op1, op2, true));
ps.print(" ");
ps.print(getInvisibleAttributes(op1));
ps.println(";");
}
private void dumpInvisibleInput(E op) {
ps.print(getSubgraphID(op, true));
ps.print(" ");
ps.print(getInvisibleAttributes(op));
ps.println(";");
}
private void dumpInvisibleOutput(E op) {
ps.print(getSubgraphID(op, false));
ps.print(" ");
ps.print(getInvisibleAttributes(op));
ps.println(";");
}
protected void dumpInvisibleOperators(E 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];");
}
}