My first Hudson PlugIn
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.
Explore posts in the same categories: en, Hudson
14. September 2009 um 19:17
Sounds like exactly what I’m just looking for.
Has there been any progress lately on the plugin?
14. September 2009 um 20:08
Sorry, no news. I hadnt any time to work on that, but it’s still on my todo list.
When finished it will be available via update center and in the Hudson svn repository.
14. September 2009 um 20:29
Okay.
BTW: have you seen Hudson.getInstance().getDependencyGraph() ?
Looks like that could be useful for your plugin.
15. September 2009 um 20:55
No I havent, but I dont have investigated much effort in that. Thanks for the hint – I’ll read the JavaDocs.
20. September 2009 um 19:57
I was also thinking of implementing such a plugin. Please let me know of your progress.
Another feature I had in mind was to also analyze the dependencies from the below plugin to have the full picture.
http://wiki.hudson-ci.org//display/HUDSON/Join+Plugin
28. September 2009 um 22:59
I just found out that there is already an experimental dependency graph implementation integrated into Hudson.
Just add „/dependencyGraph/graph“ to your base Hudson URL (eg. http://localhost:8080/dependencyGraph/graph) and it shows up.
29. August 2011 um 12:36
[…] https://janmaterne.wordpress.com/2009/06/24/my-first-hudson-plugin/ […]