sidekiq-hierarchy 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +6 -0
  7. data/CHANGELOG.md +14 -0
  8. data/CONTRIBUTING.md +57 -0
  9. data/Gemfile +7 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +396 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +7 -0
  15. data/img/dashboard.png +0 -0
  16. data/img/failed_workflow.png +0 -0
  17. data/img/in_progress_workflow.png +0 -0
  18. data/img/job.png +0 -0
  19. data/img/workflow_set.png +0 -0
  20. data/lib/sidekiq-hierarchy.rb +1 -0
  21. data/lib/sidekiq/hierarchy.rb +105 -0
  22. data/lib/sidekiq/hierarchy/callback_registry.rb +33 -0
  23. data/lib/sidekiq/hierarchy/client/middleware.rb +23 -0
  24. data/lib/sidekiq/hierarchy/faraday/middleware.rb +16 -0
  25. data/lib/sidekiq/hierarchy/http.rb +8 -0
  26. data/lib/sidekiq/hierarchy/job.rb +290 -0
  27. data/lib/sidekiq/hierarchy/notifications.rb +8 -0
  28. data/lib/sidekiq/hierarchy/observers.rb +9 -0
  29. data/lib/sidekiq/hierarchy/observers/job_update.rb +15 -0
  30. data/lib/sidekiq/hierarchy/observers/workflow_update.rb +18 -0
  31. data/lib/sidekiq/hierarchy/rack/middleware.rb +27 -0
  32. data/lib/sidekiq/hierarchy/server/middleware.rb +62 -0
  33. data/lib/sidekiq/hierarchy/version.rb +5 -0
  34. data/lib/sidekiq/hierarchy/web.rb +149 -0
  35. data/lib/sidekiq/hierarchy/workflow.rb +130 -0
  36. data/lib/sidekiq/hierarchy/workflow_set.rb +134 -0
  37. data/sidekiq-hierarchy.gemspec +33 -0
  38. data/web/views/_job_progress_bar.erb +28 -0
  39. data/web/views/_job_table.erb +37 -0
  40. data/web/views/_job_timings.erb +10 -0
  41. data/web/views/_progress_bar.erb +8 -0
  42. data/web/views/_search_bar.erb +17 -0
  43. data/web/views/_summary_bar.erb +30 -0
  44. data/web/views/_workflow_progress_bar.erb +24 -0
  45. data/web/views/_workflow_set_clear.erb +7 -0
  46. data/web/views/_workflow_table.erb +33 -0
  47. data/web/views/_workflow_timings.erb +14 -0
  48. data/web/views/_workflow_tree.erb +82 -0
  49. data/web/views/_workflow_tree_node.erb +18 -0
  50. data/web/views/job.erb +12 -0
  51. data/web/views/not_found.erb +1 -0
  52. data/web/views/status.erb +120 -0
  53. data/web/views/workflow.erb +45 -0
  54. data/web/views/workflow_set.erb +3 -0
  55. metadata +225 -0
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sidekiq/hierarchy/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sidekiq-hierarchy"
8
+ spec.version = Sidekiq::Hierarchy::VERSION
9
+ spec.authors = ["Anuj Das"]
10
+ spec.email = ["anujdas@gmail.com"]
11
+
12
+ spec.summary = %q{A set of sidekiq middlewares to track workflows consisting of multiple levels of sidekiq jobs}
13
+ spec.description = %q{A set of sidekiq middlewares to track workflows consisting of multiple levels of sidekiq jobs}
14
+ spec.homepage = 'https://www.github.com/anujdas/sidekiq-hierarchy'
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency 'sidekiq', '~> 3.3'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.10'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.0'
27
+ spec.add_development_dependency 'rspec-its'
28
+ spec.add_development_dependency 'fakeredis'
29
+ spec.add_development_dependency 'rspec-sidekiq'
30
+
31
+ spec.add_development_dependency 'faraday'
32
+ spec.add_development_dependency 'rack'
33
+ end
@@ -0,0 +1,28 @@
1
+ <% status = job.status %>
2
+ <% enqueued_at = job.enqueued_at %>
3
+ <% run_at = job.run_at %>
4
+
5
+ <% if status == :enqueued %>
6
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, 1.0]]} %>
7
+
8
+ <% elsif status == :requeued %>
9
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:requeued, 1.0]]} %>
10
+
11
+ <% elsif status == :running %>
12
+ <% runtime = Time.now - enqueued_at %>
13
+ <% enqueued_pct = (run_at - enqueued_at) / runtime %>
14
+ <% run_pct = 1.0 - enqueued_pct %>
15
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, enqueued_pct], [:running, run_pct]], active: true} %>
16
+
17
+ <% elsif status == :complete %>
18
+ <% runtime = job.complete_at - enqueued_at %>
19
+ <% enqueued_pct = (run_at - enqueued_at) / runtime %>
20
+ <% run_pct = 1.0 - enqueued_pct %>
21
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, enqueued_pct], [:complete, run_pct]]} %>
22
+
23
+ <% elsif status == :failed %>
24
+ <% runtime = job.failed_at - enqueued_at %>
25
+ <% enqueued_pct = (run_at - enqueued_at) / runtime %>
26
+ <% failed_pct = 1.0 - enqueued_pct %>
27
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, enqueued_pct], [:failed, failed_pct]]} %>
28
+ <% end %>
@@ -0,0 +1,37 @@
1
+ <table class="table table-hover table-bordered table-striped table-white">
2
+ <thead>
3
+ <tr>
4
+ <th>JID</th>
5
+ <th>Class</th>
6
+ <th>Queue</th>
7
+ <th>Status</th>
8
+ <th>Enqueued at</th>
9
+ <th>Run at</th>
10
+ <th>Completed at</th>
11
+ <th>Failed at</th>
12
+ </tr>
13
+ </thead>
14
+
15
+ <tbody>
16
+ <% jobs.each do |job| %>
17
+ <tr>
18
+ <td>
19
+ <a href="<%= job_url(job) %>">
20
+ <%= job.jid %>
21
+ </a>
22
+ </td>
23
+ <td><%= job.info['class'] %></td>
24
+ <td><%= job.info['queue'] %></td>
25
+ <td>
26
+ <span class="label label-<%= bootstrap_status(job.status) %>">
27
+ <%= job.status %>
28
+ </span>
29
+ </td>
30
+ <td><%= job.enqueued_at %></td>
31
+ <td><%= job.run_at %></td>
32
+ <td><%= job.complete_at %></td>
33
+ <td><%= job.failed_at %></td>
34
+ </tr>
35
+ <% end %>
36
+ </tbody>
37
+ </table>
@@ -0,0 +1,10 @@
1
+ <% if run_at = job.run_at %>
2
+ <dl class="dl-horizontal">
3
+ <dt>Enqueued for</dt>
4
+ <dd><%= run_at - job.enqueued_at %> seconds</dd>
5
+ <% if run_at && finished_at = (job.complete_at || job.failed_at) %>
6
+ <dt>Ran for</dt>
7
+ <dd><%= finished_at - run_at %> seconds</dd>
8
+ <% end %>
9
+ </dl>
10
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <%# input: local var 'bars', in the format [[status, width], ...]; local var 'active', true == moving bar %>
2
+ <div class="progress<%= ' progress-striped active' if !!locals[:active] %>">
3
+ <% bars.each do |status, width| %>
4
+ <div class="progress-bar progress-bar-<%= bootstrap_status(status) %>" style="width: <%= width * 100 %>%">
5
+ <%= status.to_s.capitalize %>
6
+ </div>
7
+ <% end %>
8
+ </div>
@@ -0,0 +1,17 @@
1
+ <div class="row col-sm-12" style="margin: 20px 0">
2
+ <form class="form-inline col-sm-6" action="<%= workflow_url %>" method="get">
3
+ <div class="form-group">
4
+ <label for="workflow_jid" class="sr-only">Workflow ID</label>
5
+ <input type="search" class="form-control" name="workflow_jid" placeholder="Enter Workflow ID" size="24">
6
+ </div>
7
+ <button type="submit" class="btn btn-default">Search workflows</button>
8
+ </form>
9
+
10
+ <form class="form-inline col-sm-6" action="<%= job_url %>" method="get">
11
+ <div class="form-group">
12
+ <label for="jid" class="sr-only">Job ID</label>
13
+ <input type="search" class="form-control" name="jid" placeholder="Enter Job ID" size="24">
14
+ </div>
15
+ <button type="submit" class="btn btn-default">Search jobs</button>
16
+ </form>
17
+ </div>
@@ -0,0 +1,30 @@
1
+ <div class="col-sm-12 summary_bar">
2
+ <ul class="list-unstyled summary row">
3
+ <li class="busy col-sm-4" style="width: 33%">
4
+ <a href="<%= workflow_set_url(:running) %>">
5
+ <span class="count">
6
+ <%= Sidekiq::Hierarchy::RunningSet.new.size %>
7
+ </span>
8
+ <span class="desc">Running</span>
9
+ </a>
10
+ </li>
11
+
12
+ <li class="processed col-sm-4" style="width: 33%">
13
+ <a href="<%= workflow_set_url(:complete) %>">
14
+ <span class="count">
15
+ <%= Sidekiq::Hierarchy::CompleteSet.new.size %>
16
+ </span>
17
+ <span class="desc">Complete</span>
18
+ </a>
19
+ </li>
20
+
21
+ <li class="dead col-sm-4" style="width: 33%">
22
+ <a href="<%= workflow_set_url(:failed) %>">
23
+ <span class="count">
24
+ <%= Sidekiq::Hierarchy::FailedSet.new.size %>
25
+ </span>
26
+ <span class="desc">Failed</span>
27
+ </a>
28
+ </li>
29
+ </ul>
30
+ </div>
@@ -0,0 +1,24 @@
1
+ <% enqueued_at = workflow.enqueued_at %>
2
+ <% run_at = workflow.run_at %>
3
+
4
+ <% if workflow.root.enqueued? %>
5
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, 1.0]]} %>
6
+
7
+ <% elsif workflow.running? %>
8
+ <% runtime = Time.now - enqueued_at %>
9
+ <% enqueued_pct = (run_at - enqueued_at) / runtime %>
10
+ <% run_pct = 1.0 - enqueued_pct %>
11
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, enqueued_pct], [:running, run_pct]], active: true} %>
12
+
13
+ <% elsif complete_at = workflow.complete_at %>
14
+ <% runtime = complete_at - enqueued_at %>
15
+ <% enqueued_pct = (run_at - enqueued_at) / runtime %>
16
+ <% run_pct = 1.0 - enqueued_pct %>
17
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, enqueued_pct], [:complete, run_pct]]} %>
18
+
19
+ <% elsif failed_at = workflow.failed_at %>
20
+ <% runtime = failed_at - enqueued_at %>
21
+ <% enqueued_pct = (run_at - enqueued_at) / runtime %>
22
+ <% failed_pct = 1.0 - enqueued_pct %>
23
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, enqueued_pct], [:failed, failed_pct]]} %>
24
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <form action="<%= workflow_set_url(status) %>" method="post">
2
+ <div style="margin:0;padding:0">
3
+ <input name="_method" type="hidden" value="delete" />
4
+ <input name="authenticity_token" type="hidden" value="<%= session[:csrf] %>">
5
+ </div>
6
+ <button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure? This is irreversible and potentially very expensive')">Clear Set</button>
7
+ </form>
@@ -0,0 +1,33 @@
1
+ <div class="table_container">
2
+ <table class="table table-hover table-bordered table-striped table-white">
3
+ <thead>
4
+ <tr>
5
+ <th>Workflow ID</th>
6
+ <th>Status</th>
7
+ <th>Enqueued at</th>
8
+ <th>Run at</th>
9
+ <th>Completed at</th>
10
+ <th>Failed at</th>
11
+ <th>Jobs</th>
12
+ </tr>
13
+ </thead>
14
+
15
+ <tbody>
16
+ <% workflows.each do |workflow| %>
17
+ <tr>
18
+ <td>
19
+ <a href="<%= workflow_url(workflow) %>">
20
+ <%= workflow.jid %>
21
+ </a>
22
+ </td>
23
+ <td><%= workflow.status %></td>
24
+ <td><%= workflow.enqueued_at %></td>
25
+ <td><%= workflow.run_at %></td>
26
+ <td><%= workflow.complete_at %></td>
27
+ <td><%= workflow.failed_at %></td>
28
+ <td><%= workflow.jobs.count %></td>
29
+ </tr>
30
+ <% end%>
31
+ </tbody>
32
+ </table>
33
+ </div>
@@ -0,0 +1,14 @@
1
+ <% if run_at = workflow.run_at %>
2
+ <dl class="dl-horizontal">
3
+ <% if workflow.complete? %>
4
+ <dt>Ran for</dt>
5
+ <dd><%= workflow.complete_at - run_at %> seconds</dd>
6
+ <% elsif workflow.failed? %>
7
+ <dt>Failed in</dt>
8
+ <dd><%= workflow.failed_at - run_at %> seconds</dd>
9
+ <% else %>
10
+ <dt>Running for</dt>
11
+ <dd><%= Time.now - run_at %> seconds</dd>
12
+ <% end %>
13
+ </dl>
14
+ <% end %>
@@ -0,0 +1,82 @@
1
+ <style>
2
+ /* Lifted from http://www.cssscript.com/demo/minimalist-tree-view-in-pure-css */
3
+ /* Alternative styling: http://www.goocode.net/css/153-pure-css-to-create-family-tree.html */
4
+
5
+ ul.workflow_tree {
6
+ padding-left: 0px;
7
+ }
8
+
9
+ .workflow_tree li {
10
+ list-style-type: none;
11
+ margin: 10px;
12
+ position: relative;
13
+ }
14
+
15
+ .workflow_tree li::before {
16
+ content: "";
17
+ position: absolute;
18
+ top: -7px;
19
+ left: -20px;
20
+ border-left: 1px solid #ccc;
21
+ border-bottom: 1px solid #ccc;
22
+ border-radius: 0 0 0 0px;
23
+ width: 20px;
24
+ height: 15px;
25
+ }
26
+
27
+ .workflow_tree li::after {
28
+ position: absolute;
29
+ content: "";
30
+ top: 8px;
31
+ left: -20px;
32
+ border-left: 1px solid #ccc;
33
+ border-top: 1px solid #ccc;
34
+ border-radius: 0px 0 0 0;
35
+ width: 20px;
36
+ height: 100%;
37
+ }
38
+
39
+ .workflow_tree li:last-child::after {
40
+ display: none;
41
+ }
42
+
43
+ .workflow_tree li:last-child:before{
44
+ border-radius: 0 0 0 5px;
45
+ }
46
+
47
+ ul.workflow_tree > li:first-child::before {
48
+ display: none;
49
+ }
50
+
51
+ ul.workflow_tree > li:first-child::after {
52
+ border-radius: 5px 0 0 0;
53
+ }
54
+
55
+ .workflow_tree li a {
56
+ border-radius: 5px;
57
+ padding: 2px 5px;
58
+ }
59
+
60
+ .workflow_tree li a:hover, .workflow_tree li a:focus {
61
+ background: #ccc;
62
+ color: #000;
63
+ text-decoration: none;
64
+ }
65
+
66
+ .workflow_tree li a:hover+ul li a, .workflow_tree li a:focus+ul li a {
67
+ color: #000;
68
+ }
69
+
70
+ .workflow_tree li a:hover+ul li::after, .workflow_tree li a:focus+ul li::after,
71
+ .workflow_tree li a:hover+ul li::before, .workflow_tree li a:focus+ul li::before
72
+ .workflow_tree li a:hover+ul::before, .workflow_tree li a:focus+ul::before
73
+ .workflow_tree li a:hover+ul ul::before, .workflow_tree li a:focus+ul ul::before{
74
+ border-color: #000;
75
+ }
76
+ </style>
77
+
78
+ <ul class="workflow_tree">
79
+ <% unless root.root? %><ul><% end %>
80
+ <%= erb sidekiq_hierarchy_template(:_workflow_tree_node), locals: {job: root} %>
81
+ <% unless root.root? %></ul><% end %>
82
+ </ul>
@@ -0,0 +1,18 @@
1
+ <li>
2
+ <a href="<%= root_path %>hierarchy/jobs/<%= job.jid %>">
3
+ <span title="<%= job.jid %>">
4
+ <%= job.info['class'] %>
5
+ </span>
6
+ (<%= job.info['queue'] %>)
7
+ <span class="label label-<%= bootstrap_status(job.status) %>">
8
+ <%= job.status %> <%= relative_time(status_updated_at(job)) %>
9
+ </span>
10
+ </a>
11
+ <% unless job.leaf? %>
12
+ <ul>
13
+ <% job.children.each do |child| %>
14
+ <%= erb sidekiq_hierarchy_template(:_workflow_tree_node), locals: {job: child} %>
15
+ <% end %>
16
+ </ul>
17
+ <% end %>
18
+ </li>
data/web/views/job.erb ADDED
@@ -0,0 +1,12 @@
1
+ <a href="<%= workflow_url(@workflow) %>">
2
+ <h3>Workflow <%= @workflow.jid %></h3>
3
+ </a>
4
+ <h4>Job <%= @job.jid %></h4>
5
+
6
+ <%= erb sidekiq_hierarchy_template(:_job_progress_bar), locals: {job: @job} %>
7
+
8
+ <%= erb sidekiq_hierarchy_template(:_job_table), locals: {jobs: [@job]} %>
9
+ <%= erb sidekiq_hierarchy_template(:_job_timings), locals: {job: @job} %>
10
+
11
+ <h4>Job Tree</h4>
12
+ <%= erb sidekiq_hierarchy_template(:_workflow_tree), locals: {root: @job} %>
@@ -0,0 +1 @@
1
+ <h2>404 NOT FOUND</h2>
@@ -0,0 +1,120 @@
1
+ <h3>Sidekiq Hierarchy</h3>
2
+
3
+ <%= erb sidekiq_hierarchy_template(:_summary_bar) %>
4
+ <%= erb sidekiq_hierarchy_template(:_search_bar) %>
5
+
6
+ <div class="panel panel-info">
7
+ <div class="panel-heading">
8
+ <a href="<%= workflow_set_url(:running) %>">
9
+ <h4>Running Workflows</h4>
10
+ </a>
11
+ </div>
12
+
13
+ <div class="panel-body">
14
+ <div class="table_container">
15
+ <table class="table table-hover table-bordered table-striped table-white">
16
+ <thead>
17
+ <tr>
18
+ <th>Workflow ID</th>
19
+ <th>Enqueued at</th>
20
+ <th>Run at</th>
21
+ </tr>
22
+ </thead>
23
+
24
+ <tbody>
25
+ <% @running.each do |workflow| %>
26
+ <tr>
27
+ <td>
28
+ <a href="<%= workflow_url(workflow) %>">
29
+ <%= workflow.jid %>
30
+ </a>
31
+ </td>
32
+ <td><%= relative_time(workflow.enqueued_at) %></td>
33
+ <td><%= relative_time(workflow.run_at) if workflow.run_at %></td>
34
+ </tr>
35
+ <% end%>
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="panel panel-success">
43
+ <div class="panel-heading">
44
+ <a href="<%= workflow_set_url(:complete) %>">
45
+ <h4>Complete Workflows</h4>
46
+ </a>
47
+ </div>
48
+
49
+ <div class="panel-body">
50
+ <div class="table_container">
51
+ <table class="table table-hover table-bordered table-striped table-white">
52
+ <thead>
53
+ <tr>
54
+ <th>Workflow ID</th>
55
+ <th>Enqueued at</th>
56
+ <th>Run at</th>
57
+ <th>Completed at</th>
58
+ </tr>
59
+ </thead>
60
+
61
+ <tbody>
62
+ <% @complete.each do |workflow| %>
63
+ <tr>
64
+ <td>
65
+ <a href="<%= workflow_url(workflow) %>">
66
+ <%= workflow.jid %>
67
+ </a>
68
+ </td>
69
+ <td><%= relative_time(workflow.enqueued_at) %></td>
70
+ <td><%= relative_time(workflow.run_at) %></td>
71
+ <td><%= relative_time(workflow.complete_at) %></td>
72
+ </tr>
73
+ <% end%>
74
+ </tbody>
75
+ </table>
76
+ </div>
77
+
78
+ <%= erb sidekiq_hierarchy_template(:_workflow_set_clear), locals: {status: :complete} %>
79
+ </div>
80
+ </div>
81
+
82
+ <div class="panel panel-danger">
83
+ <div class="panel-heading">
84
+ <a href="<%= workflow_set_url(:failed) %>">
85
+ <h4>Failed Workflows</h4>
86
+ </a>
87
+ </div>
88
+
89
+ <div class="panel-body">
90
+ <div class="table_container">
91
+ <table class="table table-hover table-bordered table-striped table-white">
92
+ <thead>
93
+ <tr>
94
+ <th>Workflow ID</th>
95
+ <th>Enqueued at</th>
96
+ <th>Run at</th>
97
+ <th>Failed at</th>
98
+ </tr>
99
+ </thead>
100
+
101
+ <tbody>
102
+ <% @failed.each do |workflow| %>
103
+ <tr>
104
+ <td>
105
+ <a href="<%= workflow_url(workflow) %>">
106
+ <%= workflow.jid %>
107
+ </a>
108
+ </td>
109
+ <td><%= relative_time(workflow.enqueued_at) %></td>
110
+ <td><%= relative_time(workflow.run_at) %></td>
111
+ <td><%= relative_time(workflow.failed_at) %></td>
112
+ </tr>
113
+ <% end %>
114
+ </tbody>
115
+ </table>
116
+ </div>
117
+
118
+ <%= erb sidekiq_hierarchy_template(:_workflow_set_clear), locals: {status: :failed} %>
119
+ </div>
120
+ </div>