symphony-metronome 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/History.md +27 -0
- data/README.md +224 -0
- data/lib/symphony/metronome/intervalexpression.rb +2 -2
- data/lib/symphony/metronome/scheduledevent.rb +8 -11
- data/lib/symphony/metronome/scheduler.rb +12 -14
- data/lib/symphony/metronome.rb +5 -8
- data.tar.gz.sig +1 -0
- metadata +131 -54
- metadata.gz.sig +0 -0
- data/README.rdoc +0 -207
- data/bin/metronome-exp +0 -33
- data/lib/symphony/metronome/intervalexpression.rl +0 -599
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7924d613c9657d90bf4214bb9408d1f23bb9ecb677fdad75e47517fb8f30846d
|
|
4
|
+
data.tar.gz: e1ee7ba0db1f90c3d0e71ab05209493590d00f00d560b743938f986e869a872c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a4146c565cf102bf1adf6979a4f57ffd5c2f8baedc8f6570da3654b0d65fd5183ae53778ee17d489de735c2f235875951d03347a84fe6143cf7fae86656ce95f
|
|
7
|
+
data.tar.gz: d9e6dd409f5206a1d98c8899626c2921465b013c2c160c84ffc43add998c3e32a672f5e75dbaa2f0c68f082dc6329f8b6cbaa9ac1def09847add88f7ca7549a2
|
checksums.yaml.gz.sig
ADDED
|
Binary file
|
data/History.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Release History for Symphony-Metronome
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## v0.3.0 [2023-03-20] Mahlon E. Smith <mahlon@martini.nu>
|
|
6
|
+
|
|
7
|
+
- Updates for Ruby 3.
|
|
8
|
+
- Dependency updates.
|
|
9
|
+
- Conversion to more modern ruby development tooling.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## v0.2.1 [2015-07-08] Mahlon E. Smith <mahlon@martini.nu>
|
|
13
|
+
|
|
14
|
+
Enhancements:
|
|
15
|
+
|
|
16
|
+
- Add "last run" timestamps for recurring events.
|
|
17
|
+
|
|
18
|
+
Fixes:
|
|
19
|
+
|
|
20
|
+
- Repair exact start times for recurring events, ie
|
|
21
|
+
'starting at 2015-01-01 09:00:00 run every other minute for 2 days'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## v0.1.0 [2014-04-22] Mahlon E. Smith <mahlon@martini.nu>
|
|
25
|
+
|
|
26
|
+
Initial release.
|
|
27
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Metronome
|
|
2
|
+
|
|
3
|
+
home
|
|
4
|
+
: https://code.martini.nu/fossil/symphony-metronome
|
|
5
|
+
|
|
6
|
+
docs
|
|
7
|
+
: https://martini.nu/docs/symphony-metronome
|
|
8
|
+
|
|
9
|
+
github_mirror
|
|
10
|
+
: https://github.com/mahlonsmith/symphony-metronome
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Description
|
|
15
|
+
|
|
16
|
+
Metronome is an interval scheduler and task runner. It can be used locally as a
|
|
17
|
+
cron replacement, or as a network-wide job executor. It is intended to be run
|
|
18
|
+
alongside Symphony, a Ruby AMQP event consumer.
|
|
19
|
+
|
|
20
|
+
Events are stored via simple database rows, and optionally themselves managed
|
|
21
|
+
via AMQP events. Interval/time values are expressed with intuitive English
|
|
22
|
+
phrases, ie.: 'at 2pm', or 'Starting in 20 minutes, run every 10 seconds and
|
|
23
|
+
then finish in 2 days', or 'execute 12 times during the next minute'.
|
|
24
|
+
|
|
25
|
+
## Synopsis
|
|
26
|
+
|
|
27
|
+
Here's an example of a simple cron clone:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
#!ruby
|
|
31
|
+
|
|
32
|
+
require 'symphony/metronome'
|
|
33
|
+
|
|
34
|
+
Symphony.load_config
|
|
35
|
+
|
|
36
|
+
Symphony::Metronome.run do |opts, id|
|
|
37
|
+
Thread.new do
|
|
38
|
+
pid = fork { exec opts.delete('command') }
|
|
39
|
+
Process.waitpid( pid )
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
And here's a simplistic timed AMQP message broadcaster, using existing Symphony
|
|
46
|
+
connection information:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
#!ruby
|
|
50
|
+
|
|
51
|
+
require 'symphony/metronome'
|
|
52
|
+
|
|
53
|
+
Symphony.load_config
|
|
54
|
+
|
|
55
|
+
Symphony::Metronome.run do |opts, id|
|
|
56
|
+
key = opts.delete( 'routing_key' ) or next
|
|
57
|
+
exchange = Symphony::Queue.amqp_exchange
|
|
58
|
+
exchange.publish( 'hi from Metronome!', routing_key: key )
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Adding Actions
|
|
63
|
+
|
|
64
|
+
There are two primary components to Metronome -- getting actions into
|
|
65
|
+
its database, and performing some task with those actions when the time
|
|
66
|
+
is appropriate.
|
|
67
|
+
|
|
68
|
+
By default, Metronome will start up an AMQP listener, attached to your
|
|
69
|
+
Symphony exchange, and wait for new scheduling messages. There are two
|
|
70
|
+
events it will take action on:
|
|
71
|
+
|
|
72
|
+
metronome.create:
|
|
73
|
+
|
|
74
|
+
Create a new scheduled event. The payload should be a hash. An
|
|
75
|
+
'expression' key is required, that provides the interval description.
|
|
76
|
+
Anything additional is serialized to 'options', that are passed to the
|
|
77
|
+
block when the interval fires. You can populate it with anything
|
|
78
|
+
your task requires to execute.
|
|
79
|
+
|
|
80
|
+
metronome.delete:
|
|
81
|
+
|
|
82
|
+
The payload is the row ID of the action. Metronome removes it from
|
|
83
|
+
the database.
|
|
84
|
+
|
|
85
|
+
If you'd prefer not to use the AMQP listener, you can put actions into
|
|
86
|
+
Metronome using any database methodology you please. When the daemon
|
|
87
|
+
starts up or receives a HUP signal, it will re-read and schedule out
|
|
88
|
+
upcoming work.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## Options
|
|
92
|
+
|
|
93
|
+
Metronome uses
|
|
94
|
+
[Configurability](https://rubygems.org/gems/configurability) to determine
|
|
95
|
+
behavior. The configuration is a [YAML](http://www.yaml.org/) file. It
|
|
96
|
+
shares AMQP configuration with Symphony, and adds metronome specific
|
|
97
|
+
controls in the 'metronome' key.
|
|
98
|
+
|
|
99
|
+
metronome:
|
|
100
|
+
splay: 0
|
|
101
|
+
listen: true
|
|
102
|
+
db: sqlite:///tmp/metronome.db
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
### splay
|
|
106
|
+
|
|
107
|
+
Randomize all start times for actions by this many seconds on either
|
|
108
|
+
side of the original execution time. Defaults to none.
|
|
109
|
+
|
|
110
|
+
### listen
|
|
111
|
+
|
|
112
|
+
Start up an AMQP listener using Symphony configuration, for remote
|
|
113
|
+
administration of schedule events. Defaults to true.
|
|
114
|
+
|
|
115
|
+
### db
|
|
116
|
+
|
|
117
|
+
A [Sequel](https://rubygems.org/gems/sequel) connection URI. Currently,
|
|
118
|
+
Metronome is tested under SQLite and PostgreSQL. Defaults to a SQLite
|
|
119
|
+
file at /tmp/metronome.db.
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
## Scheduling Examples
|
|
123
|
+
|
|
124
|
+
Note that Metronome is designed as an interval scheduler, not a
|
|
125
|
+
calendaring app. It doesn't have any concepts around phrases like "next
|
|
126
|
+
tuesday", or "the 3rd sunday after christmas". If that's what you're
|
|
127
|
+
after, check out the [chronic](http://rubygems.org/gems/chronic)
|
|
128
|
+
library instead.
|
|
129
|
+
|
|
130
|
+
Here are a small set of example expressions. Feel free to use the
|
|
131
|
+
*metronome-exp* utility to get a feel for what Metronome anticipates.
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
in 30.5 minutes
|
|
135
|
+
once an hour
|
|
136
|
+
every 15 minutes for 2 days
|
|
137
|
+
at 2014-05-01
|
|
138
|
+
at 2014-04-01 14:00:25
|
|
139
|
+
at 2pm
|
|
140
|
+
starting at 2pm once a day
|
|
141
|
+
start in 1 hour from now run every 5 seconds end at 11:15pm
|
|
142
|
+
every other hour
|
|
143
|
+
run every 7th minute for a day
|
|
144
|
+
once a day ending in 1 week
|
|
145
|
+
run once a minute for an hour starting in 6 days
|
|
146
|
+
10 times a minute for 2 days
|
|
147
|
+
run 45 times every hour
|
|
148
|
+
30 times per day
|
|
149
|
+
start at 2010-01-02 run 12 times and end on 2010-01-03
|
|
150
|
+
starting in an hour from now run 6 times a minute for 2 hours
|
|
151
|
+
beginning a day from now, run 30 times per minute and finish in 2 weeks
|
|
152
|
+
execute 12 times during the next 2 minutes
|
|
153
|
+
once a minute beginning in 5 minutes
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
In general, you can use reasonably intuitive phrasings. Capitalization,
|
|
157
|
+
whitespace, and punctuation doesn't matter. When describing numbers,
|
|
158
|
+
use digit/integer form instead of words, ie: '1 hour' instead of 'one
|
|
159
|
+
hour'.
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
## Installation
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
gem install symphony-metronome
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Contributing
|
|
169
|
+
|
|
170
|
+
You can check out the source via Fossil from the following uri:
|
|
171
|
+
|
|
172
|
+
% fossil clone https://code.martini.nu/fossil/symphony-metronome
|
|
173
|
+
|
|
174
|
+
or via its GitHub mirror at:
|
|
175
|
+
|
|
176
|
+
% git clone https://github.com/mahlonsmith/Symphony-Metronome
|
|
177
|
+
|
|
178
|
+
After checking out the source, run:
|
|
179
|
+
|
|
180
|
+
$ gem install -Ng
|
|
181
|
+
$ rake setup
|
|
182
|
+
|
|
183
|
+
This will install dependencies, and do any other necessary setup for
|
|
184
|
+
development.
|
|
185
|
+
|
|
186
|
+
Please report any issues
|
|
187
|
+
[here](https://code.martini.nu/fossil/symphony-metronome/tktnew).
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
## Authors
|
|
191
|
+
|
|
192
|
+
- Mahlon E. Smith <mahlon@martini.nu>
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
Copyright (c) 2014-2023 Mahlon E. Smith
|
|
198
|
+
All rights reserved.
|
|
199
|
+
|
|
200
|
+
Redistribution and use in source and binary forms, with or without
|
|
201
|
+
modification, are permitted provided that the following conditions are met:
|
|
202
|
+
|
|
203
|
+
* Redistributions of source code must retain the above copyright notice,
|
|
204
|
+
this list of conditions and the following disclaimer.
|
|
205
|
+
|
|
206
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
207
|
+
this list of conditions and the following disclaimer in the documentation
|
|
208
|
+
and/or other materials provided with the distribution.
|
|
209
|
+
|
|
210
|
+
* Neither the name of the author/s, nor the names of the project's
|
|
211
|
+
contributors may be used to endorse or promote products derived from this
|
|
212
|
+
software without specific prior written permission.
|
|
213
|
+
|
|
214
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
215
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
216
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
217
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
218
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
219
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
220
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
221
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
222
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
223
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
224
|
+
|
|
@@ -1916,7 +1916,7 @@ self.interval_expression_en_main = 1;
|
|
|
1916
1916
|
### an expression was generated, you can 'reconstitute' an interval
|
|
1917
1917
|
### object this way.
|
|
1918
1918
|
###
|
|
1919
|
-
def self::parse( exp, time=
|
|
1919
|
+
def self::parse( exp, time=Time.now )
|
|
1920
1920
|
|
|
1921
1921
|
# Normalize the expression before parsing
|
|
1922
1922
|
#
|
|
@@ -1927,7 +1927,7 @@ self.interval_expression_en_main = 1;
|
|
|
1927
1927
|
gsub( /([:\-])+/, '\1' ). # collapse multiple - or : chars
|
|
1928
1928
|
gsub( /\.+$/, '' ) # trailing periods
|
|
1929
1929
|
|
|
1930
|
-
event = new( exp, time
|
|
1930
|
+
event = new( exp, time )
|
|
1931
1931
|
data = event.instance_variable_get( :@data )
|
|
1932
1932
|
|
|
1933
1933
|
# Ragel interface variables
|
|
@@ -21,19 +21,17 @@ class Symphony::Metronome::ScheduledEvent
|
|
|
21
21
|
config_key :metronome
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
class << self
|
|
24
|
+
### Configurability API.
|
|
25
|
+
###
|
|
26
|
+
configurability do
|
|
27
|
+
|
|
28
|
+
##
|
|
32
29
|
# A Sequel-style DB connection URI.
|
|
33
|
-
|
|
30
|
+
setting :db, default: 'sqlite:///tmp/metronome.db'
|
|
34
31
|
|
|
32
|
+
##
|
|
35
33
|
# Adjust recurring intervals by a random window.
|
|
36
|
-
|
|
34
|
+
setting :splay, default: 0
|
|
37
35
|
end
|
|
38
36
|
|
|
39
37
|
|
|
@@ -62,7 +60,6 @@ class Symphony::Metronome::ScheduledEvent
|
|
|
62
60
|
### Delete any rows that are invalid expressions.
|
|
63
61
|
###
|
|
64
62
|
def self::load
|
|
65
|
-
now = Time.now
|
|
66
63
|
events = SortedSet.new
|
|
67
64
|
|
|
68
65
|
# Force reset the DB handle.
|
|
@@ -17,22 +17,15 @@ class Symphony::Metronome::Scheduler
|
|
|
17
17
|
# Signals the daemon responds to.
|
|
18
18
|
SIGNALS = [ :HUP, :INT, :TERM ]
|
|
19
19
|
|
|
20
|
-
CONFIG_DEFAULTS = {
|
|
21
|
-
:listen => false
|
|
22
|
-
}
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
### Configurability API.
|
|
22
|
+
###
|
|
23
|
+
configurability do
|
|
24
|
+
|
|
25
25
|
# Should Metronome register and schedule events via AMQP?
|
|
26
26
|
# If +false+, you'll need a separate way to add event actions
|
|
27
27
|
# to the database, and manually HUP the daemon.
|
|
28
|
-
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
### Configurability API
|
|
32
|
-
###
|
|
33
|
-
def self::configure( config=nil )
|
|
34
|
-
config = self.defaults.merge( config || {} )
|
|
35
|
-
@listen = config.delete( :listen )
|
|
28
|
+
setting :listen, default: false
|
|
36
29
|
end
|
|
37
30
|
|
|
38
31
|
|
|
@@ -131,17 +124,19 @@ class Symphony::Metronome::Scheduler
|
|
|
131
124
|
def process_events
|
|
132
125
|
now = Time.now
|
|
133
126
|
|
|
127
|
+
events_to_delete = []
|
|
128
|
+
events_to_add = []
|
|
134
129
|
self.queue.each do |ev|
|
|
135
130
|
next unless now >= ev.runtime
|
|
136
131
|
|
|
137
|
-
|
|
132
|
+
events_to_delete << ev
|
|
138
133
|
rv = ev.fire( &@proc )
|
|
139
134
|
|
|
140
135
|
# Reschedule the event and place it back on the queue.
|
|
141
136
|
#
|
|
142
137
|
if ev.event.recurring
|
|
143
138
|
ev.reset_runtime
|
|
144
|
-
|
|
139
|
+
events_to_add << ev unless rv.nil?
|
|
145
140
|
|
|
146
141
|
# It was a single run event, torch it!
|
|
147
142
|
#
|
|
@@ -150,6 +145,9 @@ class Symphony::Metronome::Scheduler
|
|
|
150
145
|
|
|
151
146
|
end
|
|
152
147
|
end
|
|
148
|
+
|
|
149
|
+
events_to_delete.map{|e| self.queue.delete( e ) }
|
|
150
|
+
events_to_add.map{|e| self.queue.add( e ) }
|
|
153
151
|
end
|
|
154
152
|
|
|
155
153
|
end # Symphony::Metronome::Scheduler
|
data/lib/symphony/metronome.rb
CHANGED
|
@@ -9,10 +9,7 @@ module Symphony::Metronome
|
|
|
9
9
|
Configurability
|
|
10
10
|
|
|
11
11
|
# Library version constant
|
|
12
|
-
VERSION = '0.
|
|
13
|
-
|
|
14
|
-
# Version-control revision constant
|
|
15
|
-
REVISION = %q$Revision: 87e45dca63b3 $
|
|
12
|
+
VERSION = '0.3.0'
|
|
16
13
|
|
|
17
14
|
# The name of the environment variable to check for config file overrides
|
|
18
15
|
CONFIG_ENV = 'METRONOME_CONFIG'
|
|
@@ -23,11 +20,11 @@ module Symphony::Metronome
|
|
|
23
20
|
# The data directory that contains migration files.
|
|
24
21
|
#
|
|
25
22
|
DATADIR = if ENV['METRONOME_DATADIR']
|
|
26
|
-
Pathname
|
|
27
|
-
elsif Gem.
|
|
28
|
-
Pathname
|
|
23
|
+
Pathname( ENV['METRONOME_DATADIR'] )
|
|
24
|
+
elsif Gem.loaded_specs[ 'symphony-metronome' ] && File.exist?( Gem.loaded_specs['symphony-metronome'].datadir )
|
|
25
|
+
Pathname( Gem.loaded_specs['symphony-metronome'].datadir )
|
|
29
26
|
else
|
|
30
|
-
Pathname
|
|
27
|
+
Pathname( __FILE__ ).dirname.parent.parent + 'data/symphony-metronome'
|
|
31
28
|
end
|
|
32
29
|
|
|
33
30
|
|
data.tar.gz.sig
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
���)�����;�Dҫ~7���^ە��9�b���e�1?�J��"LX��2/�'ΒV����Ie5�Q)E��y�&bӧ�Zm�,��eaE�'>�e'��a^�j1Lֈy+��w�ʭI�M�d�u�l�:I�K��E%�fgl@�#H���x���>_Λ��p���os��l2� �E�{�D���sIN�>�h@��Z���GtLt�\rh�B�H)�9Z����#���}@�+�ϥ�L;z"ϔT�QGb���&��.g�v�0?�Q��@�y����ԞkA���A�������As(��=k�Ƥ���͢��ew����I�ݷ*?Y��՚_hN{�p* �<����`���#4"�R����b�#b��#ڶvj��
|