Vor zwei Wochen hat Joel Wiesmann hier im Blog sein Tool Workflow Visio(n) zur Visualisierung von Workflows mittels Visio vorgestellt.

Als ich von diesem Tool zum ersten Mal gehört habe, war ich sofort begeistert davon. Joel hat es mir persönlich vorgeführt und mich sofort davon überzeugt.

Mir ist dann auch sofort ein Use-Case in den Kopf gekommen: Die Visualisierung des Critical Path.
Ich hatte schon im Dezember 2014 im früheren AutomicBlog einen Artikel darüber geschrieben, wie man mittels SQL den kritischen Pfad eines Workflows herausfinden kann.

Hier finden Sie nochmal eine kurze Zusammenfassung, wie das geht, und wie man den kritischen Pfad mit Joels Visio-Tool visualisieren kann.

Der kritische Pfad eines Workflows

Ein Workflow hat normalerweise mehrere Abfolgen von Tasks. Im Screenshot sehen Sie zum Beispiel einen einfachen Workflow mit drei Pfaden.

Die Pfade sind:

  • 1 -> 2 -> 5 > 6
  • 1 -> 3 -> 6
  • 1 -> 4 -> 6

Als kritischen Pfad bezeichnet man meistens den mit der längsten Laufzeit. Für ein Workflow-Objekt bedeutet das: Der Pfad, dessen Tasks in Summe die höchste Estimated Runtime haben.

Welcher Pfad im Beispiel ist jetzt aber der kritische Pfad?

Um das herauszufinden, brauchen Sie zunächst eine rekursive Datenbankabfrage. Diese ermittelt die Pfade eines Workflows und enthält bereits für jeden Pfad die Summe der Estimated Runtime aller Tasks.

Achtung beim Kopieren des Codes: Sie müssen in den Zeilen 15 und 16 Ihre Mandantennummer und den Namen Ihres Workflows einfügen.

WITH wpath (Client, Wflowname, Idnr, Tasknr, path, ertsum) as (
  --Anchor -> tasks without predecessor
  select OH_Client, OH_Name, OH_Idnr, JPP_Lnr, 
  cast (JPP_Lnr as VARCHAR2(200)),  --T-SQL: CAST(JPP_LNR as VARCHAR(MAX))
  case
    when JPP_Otype = ''
     then 0
    else
      coalesce (
        (select b.OH_ERT from OH b where a.OH_CLIENT=b.OH_Client and b.OH_NAme = JPP_Object), 0
      )
  end
  from OH a
  inner join JPP on OH_Idnr = JPP_OH_IDNr
  where OH_CLIENT = 1000    -- !! YOUR CLIENT !! 
  and OH_NAME = 'JOBP.002'  -- !! YOUR WFLOW NAME !!
  --only tasks without predecessor
  and not exists (
    select 1
    from JPPA where jppa_oh_idnr = OH_Idnr and JPP_Lnr = jppa_jpp_lnr
  )
  UNION ALL
  select Client, Wflowname, Idnr, JPP_Lnr, 
  path || '.' || JPP_Lnr,
  ertsum + coalesce (
        (select b.OH_ERT from OH b where Client=b.OH_Client and b.OH_NAme = JPP_Object), 0)
  from wpath
  inner join JPPA on IDNR = JPPA_OH_Idnr and JPPA_PreLnr = Tasknr
  inner join JPP on IDNR = JPP_OH_IDNR and JPPA_JPP_Lnr = JPP_Lnr
)
select Client, Wflowname, Path, Ertsum 
from wpath a
--eliminate sub-paths 
where not exists ( 
  select 1
  from wpath b where b.path like a.path || '.%'
)

Ich werde das Script hier nicht erläutern, wenn Sie Fragen dazu haben, schreiben Sie mir doch einfach einen Kommentar. Jedenfalls gibt es am Ende eine Tabelle mit allen Pfaden des Workflows aus. Es listet den Mandanten, den Namen des Workflows, den Pfad und die aufaddierte Estimated Runtime.
Für das Beispiel im Screenshot oben sieht die Tabelle so aus:

Client Wflowname Path Ertsum
1000 JOBP.002 1.3.6 1
1000 JOBP.002 1.4.6 1
1000 JOBP.002 1.2.5.6 120

Der kritische Pfad ist also ganz klar 1→2→5→6.

Komplexe Workflows sind unübersichtlich

Es lohnt sich für so kleine Workflows wie den oben kaum, den kritischen Pfad gesondert zu visualisieren. Es gibt nicht gerade viele Pfade und alles ist sehr übersichtlich.

Bei komplexeren Workflows sieht das schon anders aus.

Dieser Workflow ist auch noch ziemlich harmlos, hat aber schon 43 verschiedene Pfade. Wer will, kann gerne nachzählen.

Mit dem Script von oben kann man den kritischen Pfad herausfinden. Es ist der Pfad 1.6.7.8.10.11.16.17.22 .

Na, finden Sie ihn auf den ersten Blick? Hier ist eine Visualisierung schon deutlich sinnvoller und verhindert, dass man jedes Mal aufs Neue suchen muss.

Darstellung im Visio-Tool

Joel hat es uns ja alles schon genau erklärt. Sein Tool erwartet als Input zwei Dateien.

  • csvRelDataFile
    Eine CSV-Datei mit einem Eintrag pro Abhängigkeit zwischen zwei Tasks. Workflow Visio(n) liefert SQL-Scripts zur Erstellung dieser Datei für ein paar typische Anwendungsfälle.
  • csvTaskDataFile
    Eine CSV-Datei mit einem Eintrag pro Workflow-Tasks.
    Diese Datei muss für jeden Task mindestens die lnr, den Typ, die X und die Y Koordinate enthalten. Weitere Felder sind optional möglich, z.B. name, description oder highlight.

Ausführliche Informationen dazu finden Sie in Joels Gastartikel.

Das csvRelDataFile für mein Beispiel habe ich einfach mit dem enthaltenen Script oracle_nonrecursive_oh-rel.sql erstellt. Die Tabelle hat 32 Zeilen, für jede Abhängigkeit zwischen zwei Tasks eine. Die Datei habe ich criticalpath_rel.csv genannt.

Für das csvTaskDataFile zur Visualisierung des kritischen Pfades gibt es noch keine Vorlage und ich durfte es selbst schreiben. Joels Beispiel Heat-Data (oracle_nonrecursive_oh-task_default_or_heatmap.sql) enthält schon viele nützliche Felder und ist ein guter Ausgangspunkt. Es enthält die folgenden Felder:

LNR: Die JPP_LNR des Tasks innerhalb des Workflows

OBJECTNAME: Name des Objektes

NAME: Aliasname des Objektes, falls einer definiert ist. Ansonsten der Objektname

TYPE: Objekttyp

Y: Y-Koordinate für die Position im Workflow

X: X-Koordinate für die Position im Workflow

ACTIVE: Ist der Task im Workflow aktiv?

DESCRIPTION: Der Objekttitel als Beschreibung

ERT: Die „Estimated Runtime“ des Objektes in Sekunden (OH_ERT)

AGENT: Agent, auf dem der Job läuft (bzw. Src und Dst Agent für Filetransfers)

HEATDATA: Die „Estimated Runtime“ des Objektes im Format „HH:MM:SS“

HEAT: Ein aus HEATDATA berechneter Prozentwert für die Heatmap-Darstellung mit Ampelfarben. Je niedriger der Wert, umso grüner die Taskdarstellung. Je höher der Wert, umso roter die Taskdarstellung.

Das ist alles schon sehr nützlich aber für uns fehlt die wichtigste Information: Gehört der Task zum kritischen Pfad? Deshalb habe ich das Script angepasst und meine Abfrage von oben mit eingebaut. Die Ausgabe hat jetzt ein weiteres Feld:

HIGHLIGHT: Mögliche Werte: 0 oder 1. Tasks mit „1“ werden hervorgehoben, also rot umrandet, Tasks mit „0“ nicht.

Highlight bekommt dann den Wert 1, wenn sich der Task im Critical Path befindet.

Hier ist mein angepasstes Script (Nur für Oracle. Sorry!).

with tasksErt (WflowIdnr, Client, WflowName, Lnr, objectName, Name, Type, Y, X, Active, Description, Ert, Agent) as ( 
  select
    wflow.OH_Idnr, wflow.OH_Client, wflow.OH_Name,
    JPP_Lnr, JPP_Object, coalesce(JPP_Alias, JPP_Object), JPP_OType,
    JPP_Row, JPP_Col, JPP_Active, tasks.OH_Title, tasks.OH_Ert as Ert,
    CASE 
      -- You can add the LoginDst / LoginSrc as well
      WHEN tasks.OH_OTYPE = 'JOBS' THEN (select JBA_HostDst from JBA where JBA_OH_Idnr = tasks.OH_idnr)
      WHEN tasks.OH_OTYPE = 'JOBF' THEN (select JFA_HostSrc || '=>' || JFA_HostDst from JFA where JFA_OH_Idnr = tasks.OH_idnr)
    -- EVNT is missing here
    ELSE 'n/a'
    END 
  from jpp
  inner join OH wflow on JPP_OH_Idnr = wflow.OH_Idnr
  left join OH tasks on tasks.OH_Name = JPP_Object and tasks.OH_Client = wflow.OH_Client and tasks.OH_Otype = JPP_Otype --ignore 
  where wflow.OH_Client = 1000
  and wflow.OH_Name = 'JOBP_CRITICALPATH_DEMO'
),
taskswithheat as (
  select 
    tasksErt.*,
    CASE WHEN ERT IS NOT NULL THEN TO_CHAR(TRUNC(ert/3600),'FM9900') || ':' || TO_CHAR(TRUNC(MOD(ert,3600)/60),'FM00') || ':' || TO_CHAR(MOD(ert,60),'FM00') END as heatData,
    round((ert - (select min(Ert) from tasksErt)) / (select max(Ert) from tasksErt) * 100, 0) as heat
  from tasksErt
),
wpath (Client, Wflowname, Idnr, Tasknr, path, ertsum) as (
  select Client, Wflowname, Wflowidnr, Lnr, 
  cast (Lnr as VARCHAR2(200)),  --T-SQL: CAST(JPP_LNR as VARCHAR(MAX))
  case
    when Type = ''
     then 0
    else
      coalesce (ERT, 0)
  end
  from taskswithheat
  --only tasks without predecessor
  where not exists (
    select 1
    from JPPA where jppa_oh_idnr = WFLowIdnr and Lnr = jppa_jpp_lnr
  )
  UNION ALL
  select Client, Wflowname, Idnr, JPP_Lnr, 
  path || '.' || JPP_Lnr,
  ertsum + coalesce (
        (select b.OH_ERT from OH b where Client=b.OH_Client and b.OH_NAme = JPP_Object), 0)
  from wpath
  inner join JPPA on IDNR = JPPA_OH_Idnr and JPPA_PreLnr = Tasknr
  inner join JPP on IDNR = JPP_OH_IDNR and JPPA_JPP_Lnr = JPP_Lnr
), 
maxpath as (
  select path, ertsum 
  from wpath a
  --eliminate sub-paths 
  where not exists ( 
    select 1
    from wpath b where b.path like a.path || '.%'
  )
  and ertsum = (
      select max(b.ertsum)
      from wpath b
    )
)
select LNR, OBJECTNAME, NAME, TYPE, Y, X, ACTIVE, DESCRIPTION, ERT, AGENT, HEATDATA, HEAT, 
case
  when exists (
    select 1 from maxpath where regexp_like('.' || path || '.', '\.' || lnr || '\.')
  ) then 1
  else 0
  end as HIGHLIGHT
from taskswithheat;

Das Ergebnis speichere ich in der Datei criticalpath_task.csv ab. Damit sind alle Dateien bereit und ich kann WorkflowVision(n) aufrufen:

powershell.exe -ExecutionPolicy Bypass .\workflowVision.ps1 -csvTaskDataFile .\criticalpath_task.csv -csvRelDataFile .\criticalpath_rel.csv -outputFile .\criticalpath.svg

Wie Sie sehen, habe ich als Ausgabeformat SVG angegeben.

https:http://philippelmer.com/wp-content/uploads/2016/07/31180844/screen3.jpg

Die Heatmap färbt Tasks ein, je nach geschätzter Laufzeit. Je „roter“ der Task, umso länger läuft er.

Durch meine Anpassung des Scripts werden alle Tasks im Critical Path rot umrandet. Nützlich, oder?

Nutzen Sie schon Workflow Visio(n)?

Ich finde Joels Tool einfach nur cool. Und es ist doch großartig, dass er den Aufwand betrieben hat, es zu veröffentlichen und kostenlos zur Verfügung zu stellen. Vielen Dank, Joel!

Haben Sie Workflow Visio(n) schon benutzt oder eine tolle Idee, was man damit machen kann? Schreiben Sie mir doch eine Mail oder einen Kommentar. Ich stelle Ihre Idee dann auch gerne hier im Blog vor.