From 55af53a77eb9635c1255168ac32607e30be4724e Mon Sep 17 00:00:00 2001 From: Jason Frey Date: Tue, 10 Dec 2019 17:13:54 -0500 Subject: [PATCH] Add tool to visualize Jobs in graphviz dot format --- app/models/job/state_machine.rb | 47 +++++++++++++++++++++++++++++++++ tools/job_to_svg.rb | 21 +++++++++++++++ 2 files changed, 68 insertions(+) create mode 100755 tools/job_to_svg.rb diff --git a/app/models/job/state_machine.rb b/app/models/job/state_machine.rb index ea16621f364c..4b0a8f91b520 100644 --- a/app/models/job/state_machine.rb +++ b/app/models/job/state_machine.rb @@ -1,6 +1,21 @@ module Job::StateMachine extend ActiveSupport::Concern + module ClassMethods + # + # Helper methods for display of transitions + # + + def to_dot(*args) + new.to_dot(*args) + end + + def to_svg(*args) + new.to_svg(*args) + end + end + + def transitions @transitions ||= load_transitions end @@ -51,4 +66,36 @@ def queue_signal(*args, priority: MiqQueue::NORMAL_PRIORITY, role: nil, deliver_ :server_guid => server_guid ) end + + # + # Helper methods for display of transitions + # + + def to_dot(include_starred_states = false) + all_states = transitions.values.map(&:to_a).flatten.uniq.reject { |t| t == "*" } + + "".tap do |s| + s << "digraph #{self.class.name.inspect} {\n" + transitions.each do |signal, signal_transitions| + signal_transitions.each do |from, to| + next if !include_starred_states && (from == "*" || to == "*") + + from = from == "*" ? all_states : [from] + to = to == "*" ? all_states : [to] + from.product(to).each { |f, t| s << " #{f} -> #{t} [ label=\"#{signal}\" ]\n" } + end + end + s << "}\n" + end + end + + def to_svg(include_starred_states = false) + dot = to_dot(include_starred_states) + + require "open3" + out, err, _status = Open3.capture3("dot -Tsvg", :stdin_data => dot) + + raise "Error from graphviz:\n#{err}" unless err.blank? + out + end end diff --git a/tools/job_to_svg.rb b/tools/job_to_svg.rb new file mode 100755 index 000000000000..59450c148d07 --- /dev/null +++ b/tools/job_to_svg.rb @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby + +if ARGV.empty? + puts "USAGE: #{__FILE__} job_class [outfile]" + exit 1 +end + +job_class, outfile = ARGV + +require File.expand_path("../config/environment", __dir__) + +job_class = job_class.constantize rescue NilClass +unless job_class < Job + puts "ERROR: job_class is not a subclass of Job.\n\nValid job_class values are:" + puts Job.descendants.map(&:name).sort.join("\n").indent(2) + exit 1 +end + +outfile ||= "#{job_class.name.underscore.gsub("/", "-")}.svg" +File.write(outfile, job_class.to_svg) +puts "\nWritten to #{outfile}"