ensembl-hive  2.8.1
visualize_jobs.pl
Go to the documentation of this file.
1 #!/usr/bin/env perl
2 
3 use strict;
4 use warnings;
5 
6  # Finding out own path in order to reference own components (including own modules):
7 use Cwd ();
8 use File::Basename ();
9 BEGIN {
10  $ENV{'EHIVE_ROOT_DIR'} ||= File::Basename::dirname( File::Basename::dirname( Cwd::realpath($0) ) );
11  unshift @INC, $ENV{'EHIVE_ROOT_DIR'}.'/modules';
12 }
13 
14 use Getopt::Long;
15 use Pod::Usage;
16 
19 use Bio::EnsEMBL::Hive::Utils ('destringify');
22 
24 
25 my $self = {};
26 my ($main_pipeline, $start_analysis, $stop_analysis);
27 my %analysis_name_2_pipeline;
28 my %semaphore_url_hash = ();
29 
30 main();
31 
32 
33 sub main {
34 
35  GetOptions(
36  # connection parameters
37  'url=s' => \$self->{'url'},
38  'reg_conf|reg_file=s' => \$self->{'reg_conf'},
39  'reg_type=s' => \$self->{'reg_type'},
40  'reg_alias|reg_name=s' => \$self->{'reg_alias'},
41  'nosqlvc' => \$self->{'nosqlvc'}, # using "nosqlvc" instead of "sqlvc!" for consistency with scripts where it is a propagated option
42 
43  'job_id=s@' => \$self->{'job_ids'}, # Jobs to start from
44  'start_analysis_name=s' => \$self->{'start_analysis_name'}, # if given, first trace the graph up to the given analysis or the seed_jobs, and then start visualisation
45  'stop_analysis_name=s' => \$self->{'stop_analysis_name'}, # if given, the visualisation is aborted at that analysis and doesn't go any further
46 
47  'include!' => \$self->{'include'}, # if set, include other pipeline rectangles inside the main one
48  'suppress_funnel_parent_link!' => \$self->{'suppress'}, # if set, do not show the link to the parent of a funnel job (potentially less clutter)
49 
50  'accu_keys|accus!' => \$self->{'show_accu_keys'}, # show accu keys, but not necessarily values
51  'accu_values|values!' => \$self->{'show_accu_values'}, # show accu keys & values (implies -accu_keys)
52  'accu_pointers|accu_ptrs!' => \$self->{'show_accu_pointers'}, # (attempt to) show which accu values come from which Jobs
53 
54  'o|out|output=s' => \$self->{'output'}, # output file name
55  'f|format=s' => \$self->{'format'}, # output format (if not guessable from -output)
56 
57  'h|help' => \$self->{'help'},
58  );
59 
60  if($self->{'help'}) {
61  pod2usage({-exitvalue => 0, -verbose => 2});
62  }
63 
64  $self->{'show_accu_keys'} = 1 if($self->{'show_accu_values'}); # -accu_values implies -accu_keys
65 
66  if($self->{'url'} or $self->{'reg_alias'}) {
67  $main_pipeline = Bio::EnsEMBL::Hive::HivePipeline->new(
68  -url => $self->{'url'},
69  -reg_conf => $self->{'reg_conf'},
70  -reg_type => $self->{'reg_type'},
71  -reg_alias => $self->{'reg_alias'},
72  -no_sql_schema_version_check => $self->{'nosqlvc'},
73  );
74 
75  } else {
76  die "\nERROR : Connection parameters (url or reg_conf+reg_alias) need to be specified\n\n";
77  }
78 
79  if($self->{'output'}) {
80 
81  if(!$self->{'format'}) {
82  if($self->{'output'}=~/\.(\w+)$/) {
83  $self->{'format'} = $1;
84  } else {
85  die "Format was not set and could not guess from ".$self->{'output'}.". Please use either way to select it.\n";
86  }
87  }
88 
89  $self->{'graph'} = Bio::EnsEMBL::Hive::Utils::GraphViz->new(
90  'name' => 'JobDependencyGraph',
91  'pad' => 0,
92  'ranksep' => '1.4',
93  'remincross' => 'true',
94  );
95 
96  # Defined on its own because it should not be in the dot output but
97  # Bio::EnsEMBL::Hive::Utils::GraphViz->new passes all its parameters to dot
98  $self->{'graph'}->{'SORT'} = 1;
99 
100  $self->{'graph'}->cluster_2_nodes( {} );
101 
102  # preload all participating pipeline databases into TheApiary:
103  precache_participating_pipelines( $main_pipeline );
104 
105  my $job_adaptor = $main_pipeline->hive_dba->get_AnalysisJobAdaptor;
106  my $anchor_jobs = $self->{'job_ids'} && $job_adaptor->fetch_all( 'job_id IN ('.join(',', @{$self->{'job_ids'}} ).')' );
107  $start_analysis = $self->{'start_analysis_name'} && $main_pipeline->find_by_query( {'object_type' => 'Analysis', 'logic_name' => $self->{'start_analysis_name'} } );
108  $stop_analysis = $self->{'stop_analysis_name'} && $main_pipeline->find_by_query( {'object_type' => 'Analysis', 'logic_name' => $self->{'stop_analysis_name'} } );
109 
110  my $start_jobs = $anchor_jobs
111  ? find_the_top( $anchor_jobs ) # scan from $anchor_jobs to start_analysis//top
112  : $start_analysis
113  ? $job_adaptor->fetch_all_by_analysis_id( $start_analysis->dbID ) # take all jobs of the top analysis
114  : find_the_top( $job_adaptor->fetch_all_by_prev_job_id( undef ) ); # scan from seed_jobs to start_analysis//top
115 
116  foreach my $start_job ( @$start_jobs ) {
117  my $job_node_name = add_job_node( $start_job );
118  }
119 
120 if(0) {
121  for (1..2) { # a hacky way to get relative independence on sorting order (we don't know the ideal sorting order)
122  foreach my $semaphore_url ( keys %semaphore_url_hash ) {
123  foreach my $remote_semaphore ( @{ Bio::EnsEMBL::Hive::TheApiary->fetch_remote_semaphores_controlling_this_one( $semaphore_url, $main_pipeline ) } ) {
124 
125  foreach my $start_job ( @{ find_the_top( $remote_semaphore->fetch_my_local_controlling_jobs ) } ) {
126  my $job_node_name = add_job_node( $start_job );
127  }
128  }
129  }
130  }
131 }
132 
133  foreach my $analysis_name (keys %analysis_name_2_pipeline) {
134  my $this_pipeline = $analysis_name_2_pipeline{$analysis_name};
135  push @{ $self->{'graph'}->cluster_2_nodes->{ $this_pipeline->hive_pipeline_name } }, $analysis_name;
136  }
137 
138  my $mcluster_name = $main_pipeline->hive_pipeline_name;
139  $self->{'graph'}->cluster_2_attributes->{ $mcluster_name }{ 'cluster_label' } = $mcluster_name;
140  $self->{'graph'}->cluster_2_attributes->{ $mcluster_name }{ 'style' } = 'bold,filled';
141  $self->{'graph'}->cluster_2_attributes->{ $mcluster_name }{ 'fill_colour_pair' } = ['pastel19', 3];
142  my @other_pipeline_colour_pairs = ( ['pastel19', 8], ['pastel19', 5], ['pastel19', 6], ['pastel19', 1] );
143 
144  # now rotate through the list of the non-reference pipelines:
145  foreach my $other_pipeline ( @{ Bio::EnsEMBL::Hive::TheApiary->pipelines_except($main_pipeline) } ) {
146 
147  my $ocluster_name = $other_pipeline->hive_pipeline_name;
148 
149  my $colour_pair = shift @other_pipeline_colour_pairs;
150  $self->{'graph'}->cluster_2_attributes->{ $ocluster_name }{ 'cluster_label' } = $ocluster_name;
151  $self->{'graph'}->cluster_2_attributes->{ $ocluster_name }{ 'style' } = 'bold,filled';
152  $self->{'graph'}->cluster_2_attributes->{ $ocluster_name }{ 'fill_colour_pair' } = $colour_pair;
153  push @other_pipeline_colour_pairs, $colour_pair;
154 
155  if($self->{'include'}) {
156  push @{ $self->{'graph'}->cluster_2_nodes->{ $mcluster_name } }, $ocluster_name;
157  }
158  }
159 
160  if( $self->{'format'} eq 'dot' ) { # If you need to take a look at the intermediate dot file
161  $self->{'graph'}->dot_input_filename( $self->{'output'} );
162  $self->{'graph'}->as_canon( '/dev/null' );
163 
164  } else {
165  my $call = 'as_'.$self->{'format'};
166  $self->{'graph'}->$call($self->{'output'});
167  }
168 
169  } else {
170  die "\nERROR : -output filename has to be defined\n\n";
171  }
172 }
173 
174 
175 ##################### tracing: ##############################################################################
176 
177  # preload all participating pipeline databases into TheApiary:
179  my @pipelines_to_check = @_;
180 
181  my %scanned_pipeline_urls = ();
182 
183  while( my $current_pipeline = shift @pipelines_to_check ) {
184  my $current_pipeline_url = $current_pipeline->hive_dba->dbc->url;
185  unless( $scanned_pipeline_urls{ $current_pipeline_url }++ ) {
186  foreach my $df_target ( $current_pipeline->collection_of('DataflowTarget')->list ) {
187  # touching it for the side-effect of loading it to TheApiary:
188  my $target_object_pipeline = $df_target->to_analysis->hive_pipeline;
189  my $target_pipeline_url = $target_object_pipeline->hive_dba->dbc->url;
190  unless(exists $scanned_pipeline_urls{$target_pipeline_url}) {
191  push @pipelines_to_check, $target_object_pipeline;
192  }
193  }
194  }
195  }
196 }
197 
198 
199 
200 sub find_the_top {
201  my ($anchor_jobs) = @_;
202 
203  my @starters = ();
204 
205  # first try to find the start_analysis on the way up:
206  foreach my $anchor_job ( @$anchor_jobs ) {
207 
208  push @starters, trace_job_up($anchor_job);
209 
210  my $children = $anchor_job->adaptor->fetch_all_by_prev_job_id( $anchor_job->dbID );
211  foreach my $child ( @$children ) {
212  if( my $semaphore = $child->fetch_local_blocking_semaphore ) {
213  push @starters, trace_semaphore_up( $semaphore );
214  }
215  }
216  }
217 
218  return \@starters;
219 }
220 
221 
222 sub trace_job_up {
223  my $job = shift @_;
224 
225  my @buffer = ();
226 
227  if(my $local_blocking_semaphore = $job->fetch_local_blocking_semaphore) {
228  push @buffer, trace_semaphore_up( $local_blocking_semaphore );
229  }
230 
231  if(my $local_parent_job = $job->prev_job) {
232  push @buffer, trace_job_up( $local_parent_job );
233  } else {
234  push @buffer, $job;
235  }
236 
237  return @buffer;
238 }
239 
240 
241 sub trace_semaphore_up {
242  my $semaphore = shift @_;
243 
244  my @buffer = ();
245 
246  foreach my $local_controlling_job ( @{ $semaphore->fetch_my_local_controlling_jobs } ) {
247  push @buffer, trace_job_up( $local_controlling_job );
248  }
249 
250  foreach my $remote_semaphore ( @{ Bio::EnsEMBL::Hive::TheApiary->fetch_remote_semaphores_controlling_this_one( $semaphore, $main_pipeline ) } ) {
251  push @buffer, trace_semaphore_up( $remote_semaphore );
252  }
253 
254  return @buffer;
255 }
256 
257 
258 ##################### drawing: ##############################################################################
259 
260 sub draw_job_node {
261  my ($job, $job_node_name) = @_;
262 
263  my $job_shape = 'box3d';
264  my $job_status = $job->status;
265  my $job_status_colour = {'DONE' => 'DeepSkyBlue', 'READY' => 'green', 'SEMAPHORED' => 'grey', 'FAILED' => 'red'}->{$job_status} // 'yellow';
266  my $analysis_status_colour = {
267  "EMPTY" => "white",
268  "BLOCKED" => "grey",
269  "LOADING" => "green",
270  "ALL_CLAIMED" => "grey",
271  "SYNCHING" => "green",
272  "READY" => "green",
273  "WORKING" => "yellow",
274  "DONE" => "DeepSkyBlue",
275  "FAILED" => "red",
276  };
277 
278  my $job_id = $job->dbID;
279  my $job_params = destringify($job->input_id);
280 
281  my $job_label = qq{<<table border="0" cellborder="0" cellspacing="0" cellpadding="1">}
282  .qq{<tr><td><u><i>job_id:</i></u></td><td><i>$job_id</i></td></tr>};
283 
284  if(my $param_id_stack = $job->param_id_stack) {
285  $job_label .= qq{<tr><td><u><i>params from:</i></u></td><td><i>$param_id_stack</i></td></tr>};
286  }
287 
288  foreach my $param_key (sort keys %$job_params) {
289  my $param_value = $job_params->{$param_key};
290  $job_label .= "<tr><td>$param_key:</td><td> $param_value</td></tr>";
291  }
292 
293  $job_label .= "</table>>";
294 
295 
296  $self->{'graph'}->add_node( $job_node_name,
297  shape => $job_shape,
298  style => 'filled',
299  fillcolor => $job_status_colour,
300  label => $job_label,
301  );
302 
303  # adding the job to the corresponding analysis' cluster:
304  my $analysis_name = $job->analysis->relative_display_name($main_pipeline);
305  my $cluster_label;
306 
307  if($analysis_name =~ m{^(\w+)/(\w+)$}) {
308  $analysis_name = $1 . '___' . $2;
309  $cluster_label = $2;
310  } else {
311  $cluster_label = $analysis_name;
312  }
313 
314  my $analysis_status = $job->analysis->status;
315  push @{$self->{'graph'}->cluster_2_nodes->{ $analysis_name }}, $job_node_name;
316  $self->{'graph'}->cluster_2_attributes->{ $analysis_name }{ 'cluster_label' } = $cluster_label;
317  $self->{'graph'}->cluster_2_attributes->{ $analysis_name }{ 'style' } = 'rounded,filled';
318  $self->{'graph'}->cluster_2_attributes->{ $analysis_name }{ 'fill_colour_pair' } = [ $analysis_status_colour->{$analysis_status} ];
319  $analysis_name_2_pipeline{ $analysis_name } = $job->hive_pipeline;
320 }
321 
322 
323 my %job_node_hash = ();
324 
325 sub add_job_node {
326  my $job = shift @_;
327 
328  my $job_id = $job->dbID;
329  my $job_pipeline_name = $job->hive_pipeline->hive_pipeline_name;
330  my $job_node_name = 'job_'.$job_id.'__'.$job_pipeline_name;
331 
332  unless($job_node_hash{$job_node_name}++) {
333 
334  draw_job_node( $job, $job_node_name);
335 
336  # recursion via child jobs:
337  if( !$stop_analysis or ($job->analysis != $stop_analysis) ) {
338 
339 
340  my $children = $job->adaptor->fetch_all_by_prev_job_id( $job_id );
341  foreach my $child_job ( @$children ) {
342  my $child_node_name = add_job_node( $child_job );
343 
344  my $child_can_be_controlled = $child_job->fetch_local_blocking_semaphore;
345 
346  unless( $self->{'suppress'} and $child_can_be_controlled ) {
347  $self->{'graph'}->add_edge( $job_node_name => $child_node_name,
348  color => 'blue',
349  );
350  }
351  }
352 
353  # a local semaphore potentially blocked by this job:
354  if(my $controlled_semaphore = $job->controlled_semaphore) {
355  my $semaphore_node_name = add_semaphore_node( $controlled_semaphore );
356 
357  my $job_status = $job->status;
358  my $parent_is_blocking = ($job_status eq 'DONE' or $job_status eq 'PASSED_ON') ? 0 : 1;
359  my $parent_controlling_colour = $parent_is_blocking ? 'red' : 'darkgreen';
360  my $blocking_arrow = $parent_is_blocking ? 'tee' : 'none';
361 
362  $self->{'graph'}->add_edge( $job_node_name => $semaphore_node_name,
363  color => $parent_controlling_colour,
364  style => 'dashed',
365  arrowhead => $blocking_arrow,
366  );
367  }
368  }
369  }
370 
371  return $job_node_name;
372 }
373 
374 
376  my ($semaphore, $dependent_node_name) = @_;
377 
378  my $semaphore_id = $semaphore->dbID;
379  my $semaphore_pipeline_name = $semaphore->hive_pipeline->hive_pipeline_name;
380  my $semaphore_node_name = 'semaphore_'.$semaphore_id.'__'.$semaphore_pipeline_name;
381 
382  my $semaphore_blockers = $semaphore->local_jobs_counter + $semaphore->remote_jobs_counter;
383  my $semaphore_is_blocked = $semaphore_blockers > 0;
384  my $meta_shape = $self->{'show_accu_keys'}
385  ? ['house', 'invhouse' ] # house shape hints that accu data will be shown if present
386  : ['triangle', 'invtriangle']; # triangle shape hints that no accu data will be shown even if present
387  my $columns_in_table = $self->{'show_accu_values'} ? 3 : 2;
388 
389  my ($semaphore_shape, $semaphore_bgcolour, $semaphore_fgcolour, $dependent_blocking_arrow_colour, $dependent_blocking_arrow_shape ) = $semaphore_is_blocked
390  ? ($meta_shape->[0], 'grey', 'brown', 'red', 'tee')
391  : ($meta_shape->[1], 'darkgreen', 'white', 'darkgreen', 'none');
392 
393  my @semaphore_label_parts = ();
394  if($semaphore_is_blocked) {
395  if(my $local=$semaphore->local_jobs_counter) { push @semaphore_label_parts, "local: $local" }
396  if(my $remote=$semaphore->remote_jobs_counter) { push @semaphore_label_parts, "remote: $remote" }
397  } else {
398  push @semaphore_label_parts, "open";
399  }
400  my $semaphore_label = join(', ', @semaphore_label_parts);
401 
402  my $accusem_label = qq{<<table border="0" cellborder="0" cellspacing="0" cellpadding="1">};
403  $accusem_label .= qq{<tr><td colspan="$columns_in_table"><font color="$semaphore_fgcolour"><b><i>$semaphore_label</i></b></font></td></tr>};
404 
405  my %accu_ptrs = ();
406 
407  if($self->{'show_accu_keys'}) {
408  my $raw_accu_data = $semaphore->fetch_my_raw_accu_data;
409 
410  if(@$raw_accu_data) {
411  $accusem_label .= qq{<tr><td colspan="$columns_in_table">&nbsp;</td></tr>}; # skip one table row between semaphore attributes and accu data
412 
413  my %struct_name_2_key_signature_and_value = ();
414  foreach my $accu_rowhash (@$raw_accu_data) {
415  push @{ $struct_name_2_key_signature_and_value{ $accu_rowhash->{'struct_name'} } },
416  [ $accu_rowhash->{'key_signature'}, $accu_rowhash->{'value'}, $accu_rowhash->{'sending_job_id'} ];
417  }
418 
419  my $sending_job_pipeline_name = $semaphore->hive_pipeline->hive_pipeline_name; # assuming cross-database links are currently not stored
420 
421  foreach my $struct_name (sort keys %struct_name_2_key_signature_and_value) {
422  $accusem_label .= $self->{'show_accu_values'}
423  ? qq{<tr><td></td><td><b><u>$struct_name</u></b></td><td></td></tr>}
424  : qq{<tr> <td><b><u>$struct_name</u></b></td><td></td></tr>};
425 
426  my @sorted_values = sort {(($a->[2]//0) <=> ($b->[2]//0)) || ($a->[0] cmp $b->[0]) || ($a->[1] cmp $b->[1])} @{ $struct_name_2_key_signature_and_value{$struct_name} };
427  foreach my $accu_vector ( @sorted_values ) {
428  my ($key_signature, $value, $sending_job_id) = @$accu_vector;
429  $sending_job_id //= 0;
430 
431  my $protected_value = $self->{'graph'}->protect_string_for_display($value);
432  my $port_label = "${semaphore_node_name}_${struct_name}_${sending_job_id}";
433  my $port_attribute = $sending_job_id ? qq{port="$port_label"} : '';
434 
435  if(my $sending_job_node_name = 'job_'.$sending_job_id.'__'.$sending_job_pipeline_name) {
436  push @{ $accu_ptrs{$sending_job_node_name} }, $port_label;
437  }
438 
439  $accusem_label .= $self->{'show_accu_values'}
440  ? qq{<tr><td $port_attribute>$key_signature</td><td>&nbsp;<b>--&gt;</b>&nbsp;</td><td>$protected_value</td></tr>}
441  : qq{<tr><td $port_attribute></td><td>$key_signature</td></tr>};
442  }
443  }
444  }
445  }
446 
447  $accusem_label .= "</table>>";
448 
449  $self->{'graph'}->add_node( $semaphore_node_name,
450  shape => $semaphore_shape, # 'note',
451  margin => '0,0',
452  style => 'filled',
453  fillcolor => $semaphore_bgcolour,
454  label => $accusem_label,
455  );
456 
457  if($dependent_node_name) {
458  $self->{'graph'}->add_edge( $semaphore_node_name => $dependent_node_name,
459  color => $dependent_blocking_arrow_colour,
460  style => 'dashed',
461  arrowhead => $dependent_blocking_arrow_shape,
462  tailport => 's',
463  headport => 'n',
464  );
465  }
466 
467  if($self->{'show_accu_pointers'}) {
468  foreach my $sending_job_node_name (keys %accu_ptrs) {
469  foreach my $receiving_port (@{ $accu_ptrs{$sending_job_node_name} }) {
470 
471  $self->{'graph'}->add_edge( $sending_job_node_name => $semaphore_node_name,
472  headport => $receiving_port.':w',
473  color => 'black',
474  style => 'dotted',
475  );
476  }
477  }
478  }
479 
480  return $semaphore_node_name;
481 }
482 
483 
484 sub add_semaphore_node {
485  my $semaphore = shift @_;
486 
487  my $semaphore_url = $semaphore->relative_url( 0 ); # request for an absolute URL
488  my $semaphore_id = $semaphore->dbID;
489  my $semaphore_pipeline_name = $semaphore->hive_pipeline->hive_pipeline_name;
490  my $semaphore_node_name = 'semaphore_'.$semaphore_id.'__'.$semaphore_pipeline_name;
491 
492  unless($semaphore_url_hash{$semaphore_url}++) {
493 
494  my ($accu_node_name, $target_cluster_name);
495 
496  if(my $dependent_job = $semaphore->dependent_job) {
497  my $dependent_job_node_name = add_job_node( $dependent_job );
498 
499  $accu_node_name = draw_semaphore_and_accu($semaphore, $dependent_job_node_name);
500 
501  $target_cluster_name = $dependent_job->analysis->relative_display_name($main_pipeline);
502 
503  if($dependent_job->analysis->hive_pipeline->hive_pipeline_name eq $main_pipeline->hive_pipeline_name) {
504  $target_cluster_name =~s{^.*/}{}; # workaround since we may have added $main_pipeline into TheApiary
505  } else {
506  $target_cluster_name =~s{/}{___};
507  }
508 
509  } elsif(my $dependent_semaphore = $semaphore->dependent_semaphore) {
510 
511  my $dependent_semaphore_node_name = add_semaphore_node( $dependent_semaphore );
512 
513  $accu_node_name = draw_semaphore_and_accu($semaphore, $dependent_semaphore_node_name);
514 
515  $target_cluster_name = $semaphore->hive_pipeline->hive_pipeline_name;
516 
517  # can we trace the local blocking jobs up to their roots?
518  foreach my $start_job ( @{ find_the_top( $dependent_semaphore->fetch_my_local_controlling_jobs ) } ) {
519  my $job_node_name = add_job_node( $start_job );
520  }
521 
522  } else { # The semaphore is not blocking anything, possibly the end of execution.
523 
524  $accu_node_name = draw_semaphore_and_accu($semaphore, undef);
525 
526  $target_cluster_name = $semaphore_pipeline_name;
527  }
528 
529  # adding the semaphore node to the cluster of the dependent job's analysis:
530  push @{$self->{'graph'}->cluster_2_nodes->{ $target_cluster_name }}, $semaphore_node_name;
531  }
532 
533  return $semaphore_node_name;
534 }
535 
536 
537 __DATA__
538 
539 =pod
540 
541 =head1 NAME
542 
543 visualize_jobs.pl
544 
545 =head1 SYNOPSIS
546 
547  visualize_jobs.pl -help
548 
549  visualize_jobs.pl [ -url mysql://user:pass@server:port/dbname | -reg_conf <reg_conf_filename> -reg_alias <reg_alias> ] -output <output_image_filename
550 
551 =head1 DESCRIPTION
552 
553 This program generates a visualisation of a subset of interrelated Jobs, Semaphores and Accumulators from a given pipeline database.
554 
555 Jobs are represented by 3D-rectangles which contain parameters and are colour-coded (reflecting the Job's status).
556 Semaphores are represented by triangles (red upward-pointing = closed, green downward-pointing = open) which contain the counter.
557 Accumulators are represented by rectangles with key-paths and may contain data (configurable).
558 
559 Blue solid arrows show Jobs' parent-child relationships (parents point at their children).
560 Dashed red lines show Jobs blocking downstream Semaphores.
561 Dashed green lines show Jobs no longer blocking downstream Semaphores (when the Jobs have finished successfully).
562 Dashed red/green lines (with colour matching Semaphore's) also link the Semaphores to their Accumulators and further to the controlled Job.
563 
564 =head1 OPTIONS
565 
566 =over
567 
568 =item --url <url>
569 
570 URL defining where eHive database is located
571 
572 =item --reg_conf <path>
573 
574 path to a Registry configuration file
575 
576 =item --reg_alias <name>
577 
578 species/alias name for the eHive DBAdaptor
579 
580 =item --nosqlvc
581 
582 "No SQL Version Check" - set if you want to force working with a database created by a potentially schema-incompatible API
583 
584 =item --job_id <int>
585 
586 Start with this job(s) and reach as far as possible using parent-child relationships.
587 
588 =item --start_analysis_name <logic_name>
589 
590 Trace up to this Analysis and start displaying from this Analysis.
591 
592 =item --stop_analysis_name <logic_name>
593 
594 Make this Analysis to be the last one to be displayed.
595 As the result, the graph may not contain the initial job_id(s).
596 
597 =item --include
598 
599 If set, in multi-pipeline contexts include other pipeline rectangles inside the "main" one.
600 Off by default.
601 
602 =item --suppress_funnel_parent_link
603 
604 If set, do not show the link to the parent of a funnel Job (potentially less clutter).
605 Off by default.
606 
607 =item --accu_keys
608 
609 If set, show accu keys in Semaphore nodes.
610 Off by default.
611 
612 =item --accu_values
613 
614 If set, show accu keys & values in Semaphore nodes.
615 Off by default.
616 
617 =item --accu_pointers
618 
619 If set, show an extra link between an item in the accu and the local Job that generated it.
620 Off by default.
621 
622 =item --output <path>
623 
624 Location of the file to write to.
625 The file extension (.png , .jpeg , .dot , .gif , .ps) will define the output format.
626 
627 =item --help
628 
629 Print this help message
630 
631 =back
632 
633 =head1 EXTERNAL DEPENDENCIES
634 
635 =over
636 
637 =item GraphViz
638 
639 =back
640 
641 =head1 LICENSE
642 
643  See the NOTICE file distributed with this work for additional information
644  regarding copyright ownership.
645 
646  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
647  You may obtain a copy of the License at
648 
649  http://www.apache.org/licenses/LICENSE-2.0
650 
651  Unless required by applicable law or agreed to in writing, software distributed under the License
652  is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
653  See the License for the specific language governing permissions and limitations under the License.
654 
655 =head1 CONTACT
656 
657 Please subscribe to the eHive mailing list: http://listserver.ebi.ac.uk/mailman/listinfo/ehive-users to discuss eHive-related questions or to be notified of our updates
658 
659 =cut
660 
Bio::EnsEMBL::Hive::Utils
Definition: Collection.pm:4
Bio::EnsEMBL::Hive::Utils::URL::hide_url_password
public Void hide_url_password()
Bio::EnsEMBL::Hive::Utils::URL
Definition: URL.pm:11
add_semaphore_node
public add_semaphore_node()
Bio::EnsEMBL::Hive::TheApiary::fetch_remote_semaphores_controlling_this_one
public fetch_remote_semaphores_controlling_this_one()
trace_job_up
public trace_job_up()
find_the_top
public find_the_top()
main
public main()
precache_participating_pipelines
public precache_participating_pipelines()
Bio::EnsEMBL::Hive::HivePipeline
Definition: HivePipeline.pm:13
Bio::EnsEMBL::Hive::Utils::GraphViz
Definition: GraphViz.pm:16
draw_semaphore_and_accu
public draw_semaphore_and_accu()
BEGIN
public BEGIN()
Bio::EnsEMBL::Hive::TheApiary
Definition: TheApiary.pm:16
draw_job_node
public draw_job_node()
Bio::EnsEMBL::Hive::TheApiary::pipelines_except
public pipelines_except()
add_job_node
public add_job_node()
trace_semaphore_up
public trace_semaphore_up()