sidetiq 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/.travis.yml +9 -0
- data/Gemfile +17 -0
- data/LICENSE +20 -0
- data/README.md +146 -0
- data/Rakefile +11 -0
- data/examples/simple.rb +18 -0
- data/ext/sidetiq_ext/extconf.rb +4 -0
- data/ext/sidetiq_ext/sidetiq_ext.c +97 -0
- data/ext/sidetiq_ext/sidetiq_ext.h +17 -0
- data/lib/sidetiq/clock.rb +105 -0
- data/lib/sidetiq/config.rb +14 -0
- data/lib/sidetiq/middleware.rb +20 -0
- data/lib/sidetiq/schedulable.rb +17 -0
- data/lib/sidetiq/schedule.rb +22 -0
- data/lib/sidetiq/version.rb +10 -0
- data/lib/sidetiq/views/sidetiq.slim +23 -0
- data/lib/sidetiq/views/sidetiq_details.slim +40 -0
- data/lib/sidetiq/web.rb +47 -0
- data/lib/sidetiq.rb +20 -0
- data/sidetiq.gemspec +24 -0
- data/test/helper.rb +26 -0
- data/test/test_clock.rb +65 -0
- data/test/test_config.rb +21 -0
- data/test/test_errors.rb +7 -0
- data/test/test_middleware.rb +18 -0
- data/test/test_schedule.rb +25 -0
- data/test/test_version.rb +21 -0
- data/test/test_web.rb +58 -0
- metadata +117 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
group :development do
|
4
|
+
gem 'rake'
|
5
|
+
gem 'rake-compiler'
|
6
|
+
gem 'sinatra', require: false
|
7
|
+
gem 'slim', require: false
|
8
|
+
end
|
9
|
+
|
10
|
+
group :test do
|
11
|
+
gem 'simplecov'
|
12
|
+
gem 'mocha'
|
13
|
+
gem 'rack-test'
|
14
|
+
end
|
15
|
+
|
16
|
+
gemspec
|
17
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (C) 2012 by Tobias Svensson <tobias@musicglue.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
data/README.md
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
Sidetiq
|
2
|
+
=======
|
3
|
+
|
4
|
+
[![Build Status](https://travis-ci.org/tobiassvn/sidetiq.png)](https://travis-ci.org/tobiassvn/sidetiq)
|
5
|
+
|
6
|
+
Recurring jobs for [Sidekiq](http://mperham.github.com/sidekiq/).
|
7
|
+
|
8
|
+
## DESCRIPTION
|
9
|
+
|
10
|
+
Sidetiq provides a simple API for defining recurring workers for Sidekiq.
|
11
|
+
|
12
|
+
- Flexible DSL based on [ice_cube](http://seejohnrun.github.com/ice_cube/)
|
13
|
+
|
14
|
+
- High-resolution timer using `clock_gettime(3)` (or `mach_absolute_time()` on
|
15
|
+
Apple Mac OS X), allowing for accurate sub-second clock ticks.
|
16
|
+
|
17
|
+
- Sidetiq uses a locking mechanism (based on `setnx` and `pexpire`) internally
|
18
|
+
so Sidetiq clocks can run in each Sidekiq process without interfering with
|
19
|
+
each other (tested with sub-second polling of scheduled jobs by Sidekiq and
|
20
|
+
Sidetiq clock rates above 100hz).
|
21
|
+
|
22
|
+
## DEPENDENCIES
|
23
|
+
|
24
|
+
- [Sidekiq](http://mperham.github.com/sidekiq/)
|
25
|
+
- [ice_cube](http://seejohnrun.github.com/ice_cube/)
|
26
|
+
|
27
|
+
## INSTALLATION
|
28
|
+
|
29
|
+
The best way to install Sidetiq is with RubyGems:
|
30
|
+
|
31
|
+
$ [sudo] gem install sidetiq
|
32
|
+
|
33
|
+
If you're installing from source, you can use [Bundler](http://gembundler.com/)
|
34
|
+
to pick up all the gems ([more info](http://gembundler.com/bundle_install.html)):
|
35
|
+
|
36
|
+
$ bundle install
|
37
|
+
|
38
|
+
## GETTING STARTED
|
39
|
+
|
40
|
+
Defining recurring jobs is simple:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
class MyWorker
|
44
|
+
include Sidekiq::Worker
|
45
|
+
include Sidetiq::Schedulable
|
46
|
+
|
47
|
+
# Daily at midnight
|
48
|
+
tiq { daily }
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
It also is possible to define multiple scheduling rules for a worker:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class MyWorker
|
56
|
+
include Sidekiq::Worker
|
57
|
+
include Sidetiq::Schedulable
|
58
|
+
|
59
|
+
tiq do
|
60
|
+
# Every third year in March
|
61
|
+
yearly(3).month_of_year(:march)
|
62
|
+
|
63
|
+
# Every second year in February
|
64
|
+
yearly(2).month_of_year(:february)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
Or complex schedules:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class MyWorker
|
73
|
+
include Sidekiq::Worker
|
74
|
+
include Sidetiq::Schedulable
|
75
|
+
|
76
|
+
# Every other month on the first monday and last tuesday at 12 o'clock.
|
77
|
+
tiq { monthly(2).day_of_week(1 => [1], 2 => [-1]).hour_of_day(12) }
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
To start Sidetiq, simply call `Sidetiq::Clock.start!` in a server specific
|
82
|
+
configuration block:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
Sidekiq.configure_server do |config|
|
86
|
+
Sidetiq::Clock.start!
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
Additionally, Sidetiq includes a middleware that will check if the clock
|
91
|
+
thread is still alive and restart it if necessary.
|
92
|
+
|
93
|
+
## CONFIGURATION
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
Sidetiq.configure do |config|
|
97
|
+
# Thread priority of the clock thread (default: Thread.main.priority as
|
98
|
+
# defined when Sidetiq is loaded).
|
99
|
+
config.priority = 2
|
100
|
+
|
101
|
+
# Clock tick resolution in seconds (default: 1).
|
102
|
+
config.resolution = 0.5
|
103
|
+
|
104
|
+
# Clock locking key expiration in ms (default: 1000).
|
105
|
+
config.lock_expire = 100
|
106
|
+
|
107
|
+
# When `true` uses UTC instead of local times (default: false)
|
108
|
+
config.utc = false
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
## WEB EXTENSION
|
113
|
+
|
114
|
+
Sidetiq includes an extension for Sidekiq's web interface. It will not be
|
115
|
+
loaded by default, so it will have to be required manually:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
require 'sidetiq/web'
|
119
|
+
```
|
120
|
+
|
121
|
+
### SCREENSHOT
|
122
|
+
|
123
|
+
![Screenshot](http://f.cl.ly/items/1P2u1v091F3V1n381g2I/Screen%20Shot%202013-02-01%20at%2012.16.17.png)
|
124
|
+
|
125
|
+
## CONTRIBUTE
|
126
|
+
|
127
|
+
If you'd like to contribute to Sidetiq, start by forking my repo on GitHub:
|
128
|
+
|
129
|
+
[http://github.com/tobiassvn/sidetiq](http://github.com/tobiassvn/sidetiq)
|
130
|
+
|
131
|
+
To get all of the dependencies, install the gem first. The best way to get
|
132
|
+
your changes merged back into core is as follows:
|
133
|
+
|
134
|
+
1. Clone down your fork
|
135
|
+
1. Create a thoughtfully named topic branch to contain your change
|
136
|
+
1. Write some code
|
137
|
+
1. Add tests and make sure everything still passes by running `rake`
|
138
|
+
1. If you are adding new functionality, document it in the README
|
139
|
+
1. Do not change the version number, I will do that on my end
|
140
|
+
1. If necessary, rebase your commits into logical chunks, without errors
|
141
|
+
1. Push the branch up to GitHub
|
142
|
+
1. Send a pull request to the tobiassvn/sidetiq project.
|
143
|
+
|
144
|
+
## LICENSE
|
145
|
+
|
146
|
+
Sidetiq is released under the MIT License. See LICENSE for further details.
|
data/Rakefile
ADDED
data/examples/simple.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Run with `sidekiq -r /path/to/simple.rb`
|
2
|
+
|
3
|
+
require 'sidekiq'
|
4
|
+
require 'sidetiq'
|
5
|
+
|
6
|
+
Sidekiq.options[:poll_interval] = 1
|
7
|
+
|
8
|
+
class MyWorker
|
9
|
+
include Sidekiq::Worker
|
10
|
+
include Sidetiq::Schedulable
|
11
|
+
|
12
|
+
tiq { secondly }
|
13
|
+
|
14
|
+
def perform(*args)
|
15
|
+
Sidekiq.logger.info "#perform"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <assert.h>
|
3
|
+
#include "sidetiq_ext.h"
|
4
|
+
|
5
|
+
#ifdef __APPLE__
|
6
|
+
#include <sys/time.h>
|
7
|
+
#include <sys/resource.h>
|
8
|
+
#include <mach/mach.h>
|
9
|
+
#include <mach/clock.h>
|
10
|
+
#include <mach/mach_time.h>
|
11
|
+
#include <errno.h>
|
12
|
+
#include <unistd.h>
|
13
|
+
#include <sched.h>
|
14
|
+
#else
|
15
|
+
#include <time.h>
|
16
|
+
#endif
|
17
|
+
|
18
|
+
VALUE msidetiq;
|
19
|
+
VALUE esidetiq_error;
|
20
|
+
VALUE csidetiq_clock;
|
21
|
+
|
22
|
+
#ifdef __APPLE__
|
23
|
+
static mach_timebase_info_data_t clock_gettime_inf;
|
24
|
+
|
25
|
+
typedef enum {
|
26
|
+
CLOCK_REALTIME,
|
27
|
+
CLOCK_MONOTONIC,
|
28
|
+
CLOCK_PROCESS_CPUTIME_ID,
|
29
|
+
CLOCK_THREAD_CPUTIME_ID
|
30
|
+
} clockid_t;
|
31
|
+
|
32
|
+
int clock_gettime(clockid_t clk_id, struct timespec *tp)
|
33
|
+
{
|
34
|
+
kern_return_t ret;
|
35
|
+
clock_serv_t clk;
|
36
|
+
clock_id_t clk_serv_id;
|
37
|
+
mach_timespec_t tm;
|
38
|
+
uint64_t start, end, delta, nano;
|
39
|
+
int retval = -1;
|
40
|
+
|
41
|
+
switch (clk_id) {
|
42
|
+
case CLOCK_REALTIME:
|
43
|
+
case CLOCK_MONOTONIC:
|
44
|
+
clk_serv_id = clk_id == CLOCK_REALTIME ? CALENDAR_CLOCK : SYSTEM_CLOCK;
|
45
|
+
if (KERN_SUCCESS == (ret = host_get_clock_service(mach_host_self(), clk_serv_id, &clk))) {
|
46
|
+
if (KERN_SUCCESS == (ret = clock_get_time(clk, &tm))) {
|
47
|
+
tp->tv_sec = tm.tv_sec;
|
48
|
+
tp->tv_nsec = tm.tv_nsec;
|
49
|
+
retval = 0;
|
50
|
+
}
|
51
|
+
}
|
52
|
+
if (KERN_SUCCESS != ret) {
|
53
|
+
errno = EINVAL;
|
54
|
+
retval = -1;
|
55
|
+
}
|
56
|
+
break;
|
57
|
+
case CLOCK_PROCESS_CPUTIME_ID:
|
58
|
+
case CLOCK_THREAD_CPUTIME_ID:
|
59
|
+
start = mach_absolute_time();
|
60
|
+
if (clk_id == CLOCK_PROCESS_CPUTIME_ID) {
|
61
|
+
getpid();
|
62
|
+
} else {
|
63
|
+
sched_yield();
|
64
|
+
}
|
65
|
+
end = mach_absolute_time();
|
66
|
+
delta = end - start;
|
67
|
+
if (0 == clock_gettime_inf.denom) {
|
68
|
+
mach_timebase_info(&clock_gettime_inf);
|
69
|
+
}
|
70
|
+
nano = delta * clock_gettime_inf.numer / clock_gettime_inf.denom;
|
71
|
+
tp->tv_sec = nano * 1e-9;
|
72
|
+
tp->tv_nsec = nano - (tp->tv_sec * 1e9);
|
73
|
+
retval = 0;
|
74
|
+
break;
|
75
|
+
default:
|
76
|
+
errno = EINVAL;
|
77
|
+
retval = -1;
|
78
|
+
}
|
79
|
+
return retval;
|
80
|
+
}
|
81
|
+
#endif
|
82
|
+
|
83
|
+
static VALUE sidetiq_gettime(VALUE self)
|
84
|
+
{
|
85
|
+
struct timespec time;
|
86
|
+
assert(clock_gettime(CLOCK_REALTIME, &time) == 0);
|
87
|
+
return rb_time_nano_new(time.tv_sec, time.tv_nsec);
|
88
|
+
}
|
89
|
+
|
90
|
+
void Init_sidetiq_ext()
|
91
|
+
{
|
92
|
+
msidetiq = rb_define_module("Sidetiq");
|
93
|
+
esidetiq_error = rb_define_class_under(msidetiq, "Error", rb_eStandardError);
|
94
|
+
csidetiq_clock = rb_define_class_under(msidetiq, "Clock", rb_cObject);
|
95
|
+
rb_define_private_method(csidetiq_clock, "clock_gettime", sidetiq_gettime, 0);
|
96
|
+
}
|
97
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#ifndef __SIDETIQ_EXT_H__
|
2
|
+
#define __SIDETIQ_EXT_H__
|
3
|
+
#include <ruby.h>
|
4
|
+
|
5
|
+
void Init_sidetiq_ext();
|
6
|
+
static VALUE sidetiq_gettime(VALUE self);
|
7
|
+
|
8
|
+
/* module Sidetiq */
|
9
|
+
extern VALUE msidetiq;
|
10
|
+
|
11
|
+
/* class Sidetiq::Error < StandardError */
|
12
|
+
extern VALUE esidetiq_error;
|
13
|
+
|
14
|
+
/* class Sidetiq::Clock */
|
15
|
+
extern VALUE csidetiq_clock;
|
16
|
+
|
17
|
+
#endif
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Sidetiq
|
2
|
+
configure do |config|
|
3
|
+
config.priority = Thread.main.priority
|
4
|
+
config.resolution = 1
|
5
|
+
config.lock_expire = 1000
|
6
|
+
config.utc = false
|
7
|
+
end
|
8
|
+
|
9
|
+
class Clock
|
10
|
+
include Singleton
|
11
|
+
include MonitorMixin
|
12
|
+
|
13
|
+
START_TIME = Sidetiq.config.utc ? Time.utc(2010, 1, 1) : Time.local(2010, 1, 1)
|
14
|
+
|
15
|
+
attr_reader :schedules, :thread
|
16
|
+
|
17
|
+
def self.method_missing(meth, *args, &block)
|
18
|
+
instance.__send__(meth, *args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
super
|
23
|
+
@schedules = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def schedule_for(worker)
|
27
|
+
schedules[worker] ||= Sidetiq::Schedule.new(START_TIME)
|
28
|
+
end
|
29
|
+
|
30
|
+
def tick
|
31
|
+
@tick = gettime
|
32
|
+
synchronize do
|
33
|
+
schedules.each do |worker, schedule|
|
34
|
+
if schedule.schedule_next?(@tick)
|
35
|
+
enqueue(worker, schedule.next_occurrence(@tick))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def gettime
|
42
|
+
Sidetiq.config.utc ? clock_gettime.utc : clock_gettime
|
43
|
+
end
|
44
|
+
|
45
|
+
def start!
|
46
|
+
return if ticking?
|
47
|
+
|
48
|
+
Sidekiq.logger.info "Sidetiq::Clock start"
|
49
|
+
@thread = Thread.start { clock { tick } }
|
50
|
+
@thread.abort_on_exception = true
|
51
|
+
@thread.priority = Sidetiq.config.resolution
|
52
|
+
end
|
53
|
+
|
54
|
+
def stop!
|
55
|
+
if ticking?
|
56
|
+
@thread.kill
|
57
|
+
Sidekiq.logger.info "Sidetiq::Clock stop"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def ticking?
|
62
|
+
@thread && @thread.alive?
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def enqueue(worker, time)
|
68
|
+
key = "sidetiq:#{worker.name}"
|
69
|
+
|
70
|
+
synchronize_clockworks("#{key}:lock") do |redis|
|
71
|
+
status = redis.get(key)
|
72
|
+
|
73
|
+
if status.nil? || status.to_f < time.to_f
|
74
|
+
time_f = time.to_f
|
75
|
+
Sidekiq.logger.info "Sidetiq::Clock enqueue #{worker.name} (at: #{time_f})"
|
76
|
+
redis.set(key, time_f)
|
77
|
+
worker.perform_at(time)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def synchronize_clockworks(lock)
|
83
|
+
Sidekiq.redis do |redis|
|
84
|
+
if redis.setnx(lock, 1)
|
85
|
+
Sidekiq.logger.debug "Sidetiq::Clock lock #{lock} #{Thread.current.inspect}"
|
86
|
+
|
87
|
+
redis.pexpire(lock, Sidetiq.config.lock_expire)
|
88
|
+
yield redis
|
89
|
+
redis.del(lock)
|
90
|
+
|
91
|
+
Sidekiq.logger.debug "Sidetiq::Clock unlock #{lock} #{Thread.current.inspect}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def clock
|
97
|
+
loop do
|
98
|
+
yield
|
99
|
+
Thread.pass
|
100
|
+
sleep Sidetiq.config.resolution
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Sidetiq
|
2
|
+
class Middleware
|
3
|
+
def initialize
|
4
|
+
@clock = Sidetiq::Clock.instance
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(*args)
|
8
|
+
# Restart the clock if the thread died.
|
9
|
+
@clock.start! if !@clock.ticking?
|
10
|
+
yield
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Sidekiq.configure_server do |config|
|
16
|
+
config.server_middleware do |chain|
|
17
|
+
chain.add Sidetiq::Middleware
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Sidetiq
|
2
|
+
module Schedulable
|
3
|
+
module ClassMethods
|
4
|
+
def tiq(&block)
|
5
|
+
clock = Sidetiq::Clock.instance
|
6
|
+
clock.synchronize do
|
7
|
+
clock.schedule_for(self).instance_eval(&block)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(klass)
|
13
|
+
klass.extend(Sidetiq::Schedulable::ClassMethods)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Sidetiq
|
2
|
+
class Schedule < IceCube::Schedule
|
3
|
+
def method_missing(meth, *args, &block)
|
4
|
+
if IceCube::Rule.respond_to?(meth)
|
5
|
+
rule = IceCube::Rule.send(meth, *args, &block)
|
6
|
+
add_recurrence_rule(rule)
|
7
|
+
rule
|
8
|
+
else
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def schedule_next?(time)
|
14
|
+
if @last_scheduled != (no = next_occurrence(time))
|
15
|
+
@last_scheduled = no
|
16
|
+
return true
|
17
|
+
end
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
header.row
|
2
|
+
.span5
|
3
|
+
h3 Recurring Jobs
|
4
|
+
|
5
|
+
- if @schedules.length > 0
|
6
|
+
table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;"
|
7
|
+
thead
|
8
|
+
th style="width: 50%" Worker
|
9
|
+
th style="width: 20%" Queue
|
10
|
+
th style="width: 20%" Next Run
|
11
|
+
th style="width: 10%" Actions
|
12
|
+
- @schedules.each do |worker, schedule|
|
13
|
+
tr
|
14
|
+
td
|
15
|
+
= worker.name
|
16
|
+
td= worker.get_sidekiq_options['queue']
|
17
|
+
td
|
18
|
+
== relative_time(schedule.next_occurrence(@time))
|
19
|
+
td
|
20
|
+
a href="#{root_path}sidetiq/#{worker.name}" Details
|
21
|
+
- else
|
22
|
+
.alert.alert-success No recurring jobs found.
|
23
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
header.row
|
2
|
+
.span5
|
3
|
+
h3
|
4
|
+
' Recurring Job:
|
5
|
+
= @worker.name
|
6
|
+
|
7
|
+
- if (recurrences = @schedule.recurrence_rules).length > 0
|
8
|
+
table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;"
|
9
|
+
thead
|
10
|
+
th Recurrences
|
11
|
+
- recurrences.each do |rule|
|
12
|
+
tr
|
13
|
+
td
|
14
|
+
= rule.to_s
|
15
|
+
|
16
|
+
br
|
17
|
+
|
18
|
+
- if (exceptions = @schedule.exception_rules).length > 0
|
19
|
+
table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;"
|
20
|
+
thead
|
21
|
+
th Exceptions
|
22
|
+
- exceptions.each do |rule|
|
23
|
+
tr
|
24
|
+
td
|
25
|
+
= rule.to_s
|
26
|
+
|
27
|
+
br
|
28
|
+
|
29
|
+
table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;"
|
30
|
+
thead
|
31
|
+
th style="width: 25%" Next 10 runs
|
32
|
+
th style="width: 75%"
|
33
|
+
- @schedule.next_occurrences(10, @time).each do |time|
|
34
|
+
tr
|
35
|
+
td
|
36
|
+
time= time.getutc
|
37
|
+
td
|
38
|
+
== relative_time(time)
|
39
|
+
|
40
|
+
br
|
data/lib/sidetiq/web.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'sidekiq/web'
|
2
|
+
|
3
|
+
module Sidetiq
|
4
|
+
module Web
|
5
|
+
|
6
|
+
def self.registered(app)
|
7
|
+
app.helpers do
|
8
|
+
def find_template(view, *args, &block)
|
9
|
+
path = File.expand_path(File.join('..', 'views'), __FILE__)
|
10
|
+
super(path, *args, &block)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
app.get "/sidetiq" do
|
16
|
+
clock = Sidetiq::Clock.instance
|
17
|
+
@schedules = clock.schedules
|
18
|
+
@time = clock.gettime
|
19
|
+
slim :sidetiq
|
20
|
+
end
|
21
|
+
|
22
|
+
app.get "/sidetiq/:name" do
|
23
|
+
halt 404 unless (name = params[:name])
|
24
|
+
|
25
|
+
clock = Sidetiq::Clock.instance
|
26
|
+
schedules = clock.schedules
|
27
|
+
|
28
|
+
@time = clock.gettime
|
29
|
+
|
30
|
+
@worker, @schedule = schedules.select do |worker, schedule|
|
31
|
+
worker.name == name
|
32
|
+
end.flatten
|
33
|
+
|
34
|
+
slim :sidetiq_details
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Sidekiq::Web.register(Sidetiq::Web)
|
41
|
+
|
42
|
+
if Sidekiq::Web.tabs.is_a?(Array)
|
43
|
+
Sidekiq::Web.tabs << "sidetiq"
|
44
|
+
else
|
45
|
+
Sidekiq::Web.tabs["Sidetiq"] = "sidetiq"
|
46
|
+
end
|
47
|
+
|
data/lib/sidetiq.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# stdlib
|
2
|
+
require 'monitor'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
# gems
|
7
|
+
require 'ice_cube'
|
8
|
+
require 'sidekiq'
|
9
|
+
|
10
|
+
# c extensions
|
11
|
+
require 'sidetiq_ext'
|
12
|
+
|
13
|
+
# internal
|
14
|
+
require 'sidetiq/config'
|
15
|
+
require 'sidetiq/clock'
|
16
|
+
require 'sidetiq/middleware'
|
17
|
+
require 'sidetiq/schedule'
|
18
|
+
require 'sidetiq/schedulable'
|
19
|
+
require 'sidetiq/version'
|
20
|
+
|
data/sidetiq.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path(File.join('..', 'lib'), __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sidetiq/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "sidetiq"
|
8
|
+
gem.version = Sidetiq::VERSION::STRING
|
9
|
+
gem.authors = ["Tobias Svensson"]
|
10
|
+
gem.email = ["tob@tobiassvensson.co.uk"]
|
11
|
+
gem.description = "High-resolution job scheduler for Sidekiq"
|
12
|
+
gem.summary = gem.description
|
13
|
+
gem.homepage = "http://github.com/tobiassvn/sidetiq"
|
14
|
+
gem.license = "MIT"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
gem.extensions = ['ext/sidetiq_ext/extconf.rb']
|
21
|
+
|
22
|
+
gem.add_dependency 'sidekiq'
|
23
|
+
gem.add_dependency 'ice_cube'
|
24
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start { add_filter "/test/" }
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'mocha/setup'
|
6
|
+
require 'rack/test'
|
7
|
+
|
8
|
+
require 'sidekiq'
|
9
|
+
require 'sidekiq/testing'
|
10
|
+
|
11
|
+
require 'sidetiq'
|
12
|
+
require 'sidetiq/web'
|
13
|
+
|
14
|
+
# Keep the test output clean
|
15
|
+
Sidekiq.logger = Logger.new(nil)
|
16
|
+
|
17
|
+
class Sidetiq::TestCase < MiniTest::Unit::TestCase
|
18
|
+
def setup
|
19
|
+
Sidekiq.redis { |r| r.flushall }
|
20
|
+
end
|
21
|
+
|
22
|
+
def clock
|
23
|
+
@clock ||= Sidetiq::Clock.instance
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
data/test/test_clock.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestClock < Sidetiq::TestCase
|
4
|
+
class FakeWorker;
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_delegates_to_instance
|
8
|
+
Sidetiq::Clock.instance.expects(:foo).once
|
9
|
+
Sidetiq::Clock.foo
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_start_stop
|
13
|
+
refute clock.ticking?
|
14
|
+
assert_nil clock.thread
|
15
|
+
|
16
|
+
clock.start!
|
17
|
+
Thread.pass
|
18
|
+
sleep 0.01
|
19
|
+
|
20
|
+
assert clock.ticking?
|
21
|
+
assert_kind_of Thread, clock.thread
|
22
|
+
|
23
|
+
clock.stop!
|
24
|
+
Thread.pass
|
25
|
+
sleep 0.01
|
26
|
+
|
27
|
+
refute clock.ticking?
|
28
|
+
refute clock.thread.alive?
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_gettime_seconds
|
32
|
+
assert_equal clock.gettime.tv_sec, Time.now.tv_sec
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_gettime_nsec
|
36
|
+
refute_nil clock.gettime.tv_nsec
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_gettime_utc
|
40
|
+
refute clock.gettime.utc?
|
41
|
+
Sidetiq.config.utc = true
|
42
|
+
assert clock.gettime.utc?
|
43
|
+
Sidetiq.config.utc = false
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_enqueues_jobs_by_schedule
|
47
|
+
schedule = Sidetiq::Schedule.new(Sidetiq::Clock::START_TIME)
|
48
|
+
schedule.daily
|
49
|
+
|
50
|
+
clock.stubs(:schedules).returns(FakeWorker => schedule)
|
51
|
+
|
52
|
+
FakeWorker.expects(:perform_at).times(10)
|
53
|
+
|
54
|
+
10.times do |i|
|
55
|
+
clock.stubs(:gettime).returns(Time.local(2011, 1, i + 1, 1))
|
56
|
+
clock.tick
|
57
|
+
end
|
58
|
+
|
59
|
+
clock.stubs(:gettime).returns(Time.local(2011, 1, 10, 2))
|
60
|
+
clock.tick
|
61
|
+
clock.tick
|
62
|
+
clock.tick
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
data/test/test_config.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestConfig < Sidetiq::TestCase
|
4
|
+
def setup
|
5
|
+
@saved = Sidetiq.config
|
6
|
+
Sidetiq.config = OpenStruct.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
Sidetiq.config = @saved
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_configure
|
14
|
+
Sidetiq.configure do |config|
|
15
|
+
config.test = 42
|
16
|
+
end
|
17
|
+
|
18
|
+
assert_equal 42, Sidetiq.config.test
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
data/test/test_errors.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestMiddleware < Sidetiq::TestCase
|
4
|
+
def middleware
|
5
|
+
Sidetiq::Middleware.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_restarts_clock
|
9
|
+
clock.stubs(:ticking?).returns(false)
|
10
|
+
clock.expects(:start!).once
|
11
|
+
middleware.call {}
|
12
|
+
|
13
|
+
clock.stubs(:ticking?).returns(true)
|
14
|
+
clock.expects(:start!).never
|
15
|
+
middleware.call {}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestSchedule < Sidetiq::TestCase
|
4
|
+
def test_super
|
5
|
+
assert_equal IceCube::Schedule, Sidetiq::Schedule.superclass
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_method_missing
|
9
|
+
sched = Sidetiq::Schedule.new
|
10
|
+
sched.daily
|
11
|
+
assert_equal "Daily", sched.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_schedule_next?
|
15
|
+
sched = Sidetiq::Schedule.new
|
16
|
+
|
17
|
+
sched.daily
|
18
|
+
|
19
|
+
assert sched.schedule_next?(Time.now + (24 * 60 * 60))
|
20
|
+
refute sched.schedule_next?(Time.now + (24 * 60 * 60))
|
21
|
+
assert sched.schedule_next?(Time.now + (2 * 24 * 60 * 60))
|
22
|
+
refute sched.schedule_next?(Time.now + (2 * 24 * 60 * 60))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestVersion < Sidetiq::TestCase
|
4
|
+
def test_major
|
5
|
+
assert_instance_of Fixnum, Sidetiq::VERSION::MAJOR
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_minor
|
9
|
+
assert_instance_of Fixnum, Sidetiq::VERSION::MINOR
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_patch
|
13
|
+
assert_instance_of Fixnum, Sidetiq::VERSION::PATCH
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_string
|
17
|
+
assert_equal Sidetiq::VERSION::STRING, [Sidetiq::VERSION::MAJOR,
|
18
|
+
Sidetiq::VERSION::MINOR, Sidetiq::VERSION::PATCH].compact.join('.')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
data/test/test_web.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestWeb < Sidetiq::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
class Worker
|
7
|
+
include Sidekiq::Worker
|
8
|
+
include Sidetiq::Schedulable
|
9
|
+
|
10
|
+
tiq do
|
11
|
+
daily(1)
|
12
|
+
yearly(2)
|
13
|
+
monthly(3)
|
14
|
+
|
15
|
+
add_exception_rule yearly.month_of_year(:february)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def app
|
20
|
+
Sidekiq::Web
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_home_tab
|
24
|
+
get '/'
|
25
|
+
assert_equal 200, last_response.status
|
26
|
+
assert_match last_response.body, /Sidekiq/
|
27
|
+
assert_match last_response.body, /Sidetiq/
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_sidetiq_page
|
31
|
+
get '/sidetiq'
|
32
|
+
assert_equal 200, last_response.status
|
33
|
+
|
34
|
+
clock.schedules.each do |worker, schedule|
|
35
|
+
assert_match last_response.body, /#{worker.name}/
|
36
|
+
assert_match last_response.body, /#{worker.get_sidekiq_options['queue']}/
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_details_page
|
41
|
+
get "/sidetiq/#{Worker.name}"
|
42
|
+
assert_equal 200, last_response.status
|
43
|
+
schedule = clock.schedules[Worker]
|
44
|
+
|
45
|
+
schedule.recurrence_rules.each do |rule|
|
46
|
+
assert_match last_response.body, /#{rule.to_s}/
|
47
|
+
end
|
48
|
+
|
49
|
+
schedule.exception_rules.each do |rule|
|
50
|
+
assert_match last_response.body, /#{rule.to_s}/
|
51
|
+
end
|
52
|
+
|
53
|
+
schedule.next_occurrences(10).each do |time|
|
54
|
+
assert_match last_response.body, /#{time.getutc.to_s}/
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sidetiq
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.2
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tobias Svensson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
version_requirements: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
none: false
|
21
|
+
name: sidekiq
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
requirement: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ! '>='
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
none: false
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
version_requirements: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ! '>='
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
none: false
|
37
|
+
name: ice_cube
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
requirement: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
none: false
|
46
|
+
description: High-resolution job scheduler for Sidekiq
|
47
|
+
email:
|
48
|
+
- tob@tobiassvensson.co.uk
|
49
|
+
executables: []
|
50
|
+
extensions:
|
51
|
+
- ext/sidetiq_ext/extconf.rb
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- .travis.yml
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- examples/simple.rb
|
61
|
+
- ext/sidetiq_ext/extconf.rb
|
62
|
+
- ext/sidetiq_ext/sidetiq_ext.c
|
63
|
+
- ext/sidetiq_ext/sidetiq_ext.h
|
64
|
+
- lib/sidetiq.rb
|
65
|
+
- lib/sidetiq/clock.rb
|
66
|
+
- lib/sidetiq/config.rb
|
67
|
+
- lib/sidetiq/middleware.rb
|
68
|
+
- lib/sidetiq/schedulable.rb
|
69
|
+
- lib/sidetiq/schedule.rb
|
70
|
+
- lib/sidetiq/version.rb
|
71
|
+
- lib/sidetiq/views/sidetiq.slim
|
72
|
+
- lib/sidetiq/views/sidetiq_details.slim
|
73
|
+
- lib/sidetiq/web.rb
|
74
|
+
- sidetiq.gemspec
|
75
|
+
- test/helper.rb
|
76
|
+
- test/test_clock.rb
|
77
|
+
- test/test_config.rb
|
78
|
+
- test/test_errors.rb
|
79
|
+
- test/test_middleware.rb
|
80
|
+
- test/test_schedule.rb
|
81
|
+
- test/test_version.rb
|
82
|
+
- test/test_web.rb
|
83
|
+
homepage: http://github.com/tobiassvn/sidetiq
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
none: false
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
none: false
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.8.23
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: High-resolution job scheduler for Sidekiq
|
108
|
+
test_files:
|
109
|
+
- test/helper.rb
|
110
|
+
- test/test_clock.rb
|
111
|
+
- test/test_config.rb
|
112
|
+
- test/test_errors.rb
|
113
|
+
- test/test_middleware.rb
|
114
|
+
- test/test_schedule.rb
|
115
|
+
- test/test_version.rb
|
116
|
+
- test/test_web.rb
|
117
|
+
has_rdoc:
|