I set up a Hudson instance in my company. But after adding the 20th job I lost the overview: which job depends on which other job? I need a visualization.
I need a plugin which reads the job dependencies and creates a dependency graph.
This problem consists of two parts: getting te dependencies and creating the graph. And I need to solve both for getting the plugin work …
I start with creating the graph. Shouldn’t be a problem – I am sure there are some libraries I could use as dependency graphs are an old and common ‘problem’. And searching for “java create dependency graph” gave me a nice hit: yFiles.
But this lib has an enourmous drawback: it is commercial and the plugin should be published as OSS.
But I remembered another tool: Graphiz. And via its homepage I found a Java layer on top of it: Linguine Maps.
First played a little bit:
String dotExeFileName = "C:/temp/ec-dep/Job Dependency Visualizer/lib/graphiz/dot.exe";
// create the board for the graph
Graph graph = GraphFactory.newGraph();
// create the nodes
GraphNode child = graph.addNode();
GraphNode me = graph.addNode();
GraphNode wife = graph.addNode();
GraphNode father = graph.addNode();
GraphNode mother = graph.addNode();
GraphNode grandma = graph.addNode();
GraphNode grandpa = graph.addNode();
GraphNode friend = graph.addNode();
// connect the nodes according to the dependencies
graph.addEdge(me, child);
graph.addEdge(wife, child);
graph.addEdge(father, me);
graph.addEdge(mother, me);
graph.addEdge(grandma, father);
graph.addEdge(grandpa, father);
// nodes have a caption
child.getInfo().setCaption("Child");
me.getInfo().setCaption("Me");
wife.getInfo().setCaption("Wife");
father.getInfo().setCaption("Father");
mother.getInfo().setCaption("Mother");
grandma.getInfo().setCaption("Grandma");
grandpa.getInfo().setCaption("Grandpa");
friend.getInfo().setCaption("Friend");
// highlight some nodes
me.getInfo().setFillColor("red");
grandma.getInfo().setModeDotted();
grandpa.getInfo().setModeDotted();
// save as gif
File dotFileName = File.createTempFile("tmp.graph.", ".dot");
GRAPHtoDOTtoGIF.transform(
graph,
dotFileName.getAbsolutePath(),
"C:/temp/graph.demo-old.gif",
dotExeFileName
);

Oh, this is the library I want to use. So I wrote a class tayloring the graph creation (Java 1.4) to dependency graphs with use of Java >=5 and a fluent interface.
DependencyGraph
.create()
.addDependency("Child", "Me", "Wife")
.addDependency("Me", "Father", "Mother")
.addDependency("Father", "Grandma", "Grandpa")
.addDependency("Friend")
.nodeBorder("Grandma", BorderStyle.BORDER_DOTTED)
.nodeBorder("Grandpa", BorderStyle.BORDER_DOTTED)
.highlight("Me", Style.FILL_COLOR, "red")
.toGif("C:/temp/graph.demo.gif");
My base concepts are:
- a string could have dependency on other strings (me–>father,mother; wife:<none>)
- the string is the caption of the graph node
- the string is the unique node name
- two strings can only be connected once
So I could store the dependencies as a map: Map<String,Set<String>>
public class DependencyGraph {
/** Path to the Graphiz executable. */
private String dotExeFileName = "C:/temp/ec-dep/Job Dependency Visualizer/lib/graphiz/dot.exe";
/** The graph to create. */
Graph graph = GraphFactory.newGraph();
/** All already created GraphNodes. */
Map<String, GraphNode> nodes = new Hashtable<String, GraphNode>();
/** Already created dependency lines. */
Map<String, Set<String>> dependencies = new Hashtable<String, Set<String>>();
public static DependencyGraph create() {
return new DependencyGraph();
}
/**
* Adds a node and its dependencies to the graph. If the node exists
* already, it is not created a second time.
*/
public DependencyGraph addDependency(String node, String... parents) {
Set<String> list = new HashSet<String>();
Collections.addAll(list, parents);
addDependency(node, list);
return this;
}
public DependencyGraph addDependency(String node, Set<String> parents) {
createGraphNode(node);
for (String parent : parents) {
createGraphNode(parent);
if (!dependencies.get(node).contains(parent)) {
// create the link
graph.addEdge(nodes.get(parent), nodes.get(node));
// register the dependency
dependencies.get(node).add(parent);
}
}
return this;
}
/**
* Helper method for creating graph nodes and store them in the cache if the
* don't exist before.
*/
private void createGraphNode(String nodeName) {
if (!nodes.containsKey(nodeName)) {
GraphNode node = graph.addNode();
node.getInfo().setCaption(mask(nodeName));
nodes.put(nodeName, node);
dependencies.put(nodeName, new HashSet<String>());
}
}
/**
* Modify the shape style of a node.
*
* @param node the name of the node to style
* @param style the new style
* @return self
*/
public DependencyGraph nodeStyle(String node, ShapeStyle style) {
nodes.get(node).getInfo().setShape(style.value);
return this;
}
/**
* Stores the graph as GIF image.
* @param file the file to new image file
* @return self
* @throws IOException while file access
*/
public DependencyGraph toGif(File file) throws IOException {
File dotFileName = File.createTempFile("tmp.graph.", ".dot");
GRAPHtoDOTtoGIF.transform(
graph, dotFileName.getAbsolutePath(),
file.getAbsolutePath(), dotExeFileName);
dotFileName.delete();
return this;
}
}
public enum ShapeStyle {
RECORD(0),
BOX(1),
ELLIPSE(2),
CIRCLE(3),
DIAMOND(4),
TRIANGLE(5),
INVTRIANGLE(6),
HEXAGON(7),
OCTAGON(8),
PARALLELOGRAM(9);
/** the LinguineMaps style value */
int value;
private ShapeStyle(int value) {
this.value = value;
}
}
After solving the second part of my problem I have to solve the first part too. I need information from Hudson:
- List<Job> getAllJobs()
- List<Job> getParentJobs(Job)
For creating a graph per view I also need
- List<View> getAllViews()
- List<Job> getJobs(View)
And for keeping the graphs up to date I need to be informed about configuration changes.
- void onJobConfigChange(Job)
I have to search for these…
I am working on the plugin part now – so I’ll write more if I have finished that.