tupelo 0.18 → 0.19

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 60516b564c0329a1544a913f4ec1680bd8308de8
4
- data.tar.gz: 6aadbcc4c96e57d762d072e266ff921972f58578
3
+ metadata.gz: a8a67711763af58aa46979622519450ba198e603
4
+ data.tar.gz: fc399bd00b0b69ec5938a0d3ffae68155c186ae7
5
5
  SHA512:
6
- metadata.gz: 75b24e537a78c831be55b389457a41fd527bf3fc88cb7e4dd358d03ecc492d439157ce28a3191ae86946c2df12a5ee5f74b5b388aebd8a9da57e0e1cc0af8386
7
- data.tar.gz: 53a0848f5b3c30e091fc0bd0d58fced4703b77561099866b032e1d9f7c312a85da01d2a4066c2979f2632caa31a6c97466b21cf4319acca99308f4beddaa25d5
6
+ metadata.gz: 0f3570788a47744267fc361e25bd50b56bf327177764fb428317cb51ed1870c7e86acc8df53ef28d60278f9c902057b2a5275f86eba29189ea80f28259ddfc9f
7
+ data.tar.gz: 85a3be5a56f49d9518c2ed1409d992654e3f36f80847b441d7f4d84bf29afba541c11164c4052f97e208916e9a2a4cbf57aeef04736207160d4c6b4cd2d69e25
data/example/lease.rb CHANGED
@@ -52,9 +52,8 @@ Tupelo.application do
52
52
  # one worker lives. This demonstrates how to recover from worker failure
53
53
  # and prevent "lost tuples".
54
54
  child passive: true do
55
- require 'tupelo/client/atdo'
56
-
57
55
  scheduler = make_scheduler
56
+
58
57
  alive_until = Hash.new(0)
59
58
 
60
59
  loop do
data/example/observer.rb CHANGED
@@ -32,6 +32,9 @@ Tupelo.application do
32
32
  counter = t.read count: Numeric
33
33
  log "entering new state: #{counter}"
34
34
  t.wait
35
+ # If you prefer to check for failure periodically, rather than
36
+ # blocking with #wait, then simply call t.failed? You can call
37
+ # t.missing to see which tuples caused the failure.
35
38
  rescue Tupelo::Client::TransactionFailure => ex
36
39
  log "leaving old state: #{counter}"
37
40
  end
@@ -0,0 +1,60 @@
1
+ class Tupelo::Client
2
+ def define_event_subspace
3
+ define_subspace("event", {
4
+ # field type description
5
+ # (from http://riemann.io/concepts.html)
6
+
7
+ host: String, # A hostname, e.g. "api1", "foo.com"
8
+
9
+ service: String, # e.g. "API port 8000 reqs/sec"
10
+
11
+ state: String, # Any string less than 255 bytes, e.g. "ok",
12
+ # "warning", "critical"
13
+
14
+ time: Numeric, # The time of the event, in unix epoch seconds
15
+
16
+ description: String, # Freeform text
17
+
18
+ tags: Array, # Freeform list of strings,
19
+ # e.g. ["rate", "fooproduct", "transient"]
20
+
21
+ metric: Numeric, # A number associated with this event,
22
+ # e.g. the number of reqs/sec.
23
+
24
+ ttl: Numeric, # A floating-point time, in seconds, that this
25
+ # event is considered valid for. Expired states
26
+ # may be removed from the index.
27
+
28
+ custom: nil # Any data
29
+ # (not quite same as riemann's custom event attrs,
30
+ # which are just arbitrary key-value pairs;
31
+ # tupelo does not permit wildcards in keys)
32
+ })
33
+ end
34
+
35
+ # This could be a subspace of the event subspace, but for now, we can just
36
+ # use it as a template to select critical events out of the event subspace.
37
+ CRITICAL_EVENT = {
38
+ host: nil,
39
+ service: nil,
40
+ state: /\A(?:critical|fatal)\z/i,
41
+ time: nil,
42
+ description: nil,
43
+ tags: nil,
44
+ metric: nil,
45
+ ttl: nil,
46
+ custom: nil
47
+ }.freeze
48
+
49
+ EXPIRED_EVENT = {
50
+ host: nil,
51
+ service: nil,
52
+ state: /\Aexpired\z/i,
53
+ time: nil,
54
+ description: nil,
55
+ tags: nil,
56
+ metric: nil,
57
+ ttl: nil,
58
+ custom: nil
59
+ }.freeze
60
+ end
@@ -0,0 +1,25 @@
1
+ class Tupelo::Client
2
+ # Expire old events.
3
+ def run_expirer_v1
4
+ scheduler = make_scheduler
5
+
6
+ read subspace("event") do |event|
7
+ next if event["state"] == "expired"
8
+
9
+ event_exp = event["time"] + event["ttl"]
10
+ scheduler.at event_exp do
11
+ transaction do
12
+ take event
13
+ pulse event.merge("state" => "expired")
14
+ # Not sure if this is riemann semantics. Using #pulse rather
15
+ # that #write means that the expired event exists in the
16
+ # space only while the transaction is executing, but that is
17
+ # enough to trigger any client that is waiting on a template
18
+ # that matches the event. Use the --debug-expiration switch
19
+ # to see this happening (and use -v to make log messages
20
+ # verbose, showing timestamps).
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ class Tupelo::Client
2
+ # Generate some events.
3
+ def run_producer i
4
+ event = {
5
+ host: `hostname`.chomp,
6
+ service: "service #{i}",
7
+ state: "",
8
+ time: 0,
9
+ description: "",
10
+ tags: [],
11
+ metric: 0,
12
+ ttl: 0,
13
+ custom: nil
14
+ }.freeze
15
+
16
+ e_ok = event.merge(
17
+ state: "ok",
18
+ time: Time.now.to_f,
19
+ ttl: 0.2
20
+ )
21
+
22
+ if e_ok[:ttl] == 0.0
23
+ pulse e_ok # no need to bother with expiration
24
+ else
25
+ write e_ok
26
+ end
27
+
28
+ log "created event #{e_ok}"
29
+
30
+ sleep 0.5 # Let it expire
31
+ end
32
+ end
@@ -0,0 +1,61 @@
1
+ # A toy implementation of Riemann (http://riemann.io).
2
+ #
3
+ # Version 1 uses the default tuplespace for all subspaces, which is inefficient
4
+ # for searching.
5
+
6
+ require 'tupelo/app'
7
+ require_relative 'event-subspace'
8
+ require_relative 'producer'
9
+ require_relative 'expirer-v1'
10
+
11
+ N_PRODUCERS = 3
12
+ N_CONSUMERS = 2
13
+
14
+ Tupelo.application do
15
+
16
+ local do
17
+ use_subspaces!
18
+ define_event_subspace
19
+ end
20
+
21
+ N_PRODUCERS.times do |i|
22
+ child subscribe: [] do # N.b., no subscriptions
23
+ log.progname = "producer #{i}"
24
+ run_producer i
25
+ end
26
+ end
27
+
28
+ N_CONSUMERS.times do |i|
29
+ # stores events
30
+ child subscribe: "event", passive: true do
31
+ log.progname = "consumer #{i}"
32
+ read subspace("event") do |event|
33
+ log event ### need filtering, actions, etc.
34
+ end
35
+ end
36
+ end
37
+
38
+ # critical event alerter
39
+ child subscribe: "event", passive: true do
40
+ log.progname = "alerter"
41
+ read Tupelo::Client::CRITICAL_EVENT do |event|
42
+ log.error event
43
+ end
44
+ end
45
+
46
+ if argv.include?("--debug-expiration")
47
+ # expired event debugger
48
+ child subscribe: "event", passive: true do
49
+ log.progname = "expiration debugger"
50
+ read Tupelo::Client::EXPIRED_EVENT do |event|
51
+ log event
52
+ end
53
+ end
54
+ end
55
+
56
+ # expirer: stores current events and looks for events that can be expired.
57
+ child subscribe: "event", passive: true do
58
+ log.progname = "expirer"
59
+ run_expirer_v1
60
+ end
61
+ end
@@ -0,0 +1,72 @@
1
+ # A toy implementation of Riemann (http://riemann.io).
2
+ #
3
+ # Version 2 stores the event subspace using different data
4
+ # structures in different clients, depending on needs:
5
+ #
6
+ # * generic consumers need to index by host and service
7
+ #
8
+ # * the expiration manager need to sort by expiration time
9
+ #
10
+ # * the critical event alerter doesn't need to sort at all.
11
+
12
+ abort "work in progress"
13
+
14
+ require 'tupelo/app'
15
+ require_relative 'event-subspace'
16
+ require_relative 'producer'
17
+ require_relative 'expirer-v2'
18
+
19
+ N_PRODUCERS = 3
20
+ N_CONSUMERS = 2
21
+
22
+ Tupelo.application do
23
+
24
+ local do
25
+ use_subspaces!
26
+ define_event_subspace
27
+ end
28
+
29
+ N_PRODUCERS.times do |i|
30
+ child subscribe: [] do # N.b., no subscriptions
31
+ log.progname = "producer #{i}"
32
+ run_producer i ### V2: manual tagging
33
+ end
34
+ end
35
+
36
+ N_CONSUMERS.times do |i|
37
+ # stores events indexed by host, service
38
+ child subscribe: "event", passive: true do ### tuplespace: sqlite
39
+ log.progname = "consumer #{i}"
40
+ read subspace("event") do |event|
41
+ log event ### need filtering, actions, etc.
42
+ end
43
+ end
44
+ end
45
+
46
+ # critical event alerter
47
+ child subscribe: "event", passive: true do ### tuplespace: bag?
48
+ log.progname = "alerter"
49
+ read Tupelo::Client::CRITICAL_EVENT do |event|
50
+ log.error event
51
+ end
52
+ end
53
+
54
+ if argv.include?("--debug-expiration")
55
+ # expired event debugger
56
+ child subscribe: "event", passive: true do
57
+ log.progname = "expiration debugger"
58
+ read Tupelo::Client::EXPIRED_EVENT do |event|
59
+ log event
60
+ end
61
+ end
62
+ end
63
+
64
+ # expirer: stores current events and looks for events that can be expired.
65
+ child subscribe: "event", passive: true do
66
+ log.progname = "expirer"
67
+ run_expirer_v2
68
+ ### use rbtree
69
+ end
70
+
71
+ ### Add sinatra app.
72
+ end
@@ -0,0 +1,56 @@
1
+ require 'atdo'
2
+
3
+ module Tupelo
4
+ class Client
5
+ class Scheduler < AtDo
6
+ DEFAULT_STORAGE =
7
+ begin
8
+ require 'rbtree'
9
+ MultiRBTree
10
+ rescue LoadError
11
+ Array
12
+ end
13
+
14
+ # Instead of calling this method, call Client#make_scheduler.
15
+ def initialize client, **opts
16
+ @client = client
17
+ super **opts
18
+ end
19
+
20
+ # Accepts numeric +time+ or Time instance. Logs errors that occur
21
+ # in +action+. Otherwise, same as AtDo#at from the atdo gem.
22
+ def at time, &action
23
+ time = Time.at(time) if time.kind_of? Numeric
24
+ super time do
25
+ begin
26
+ action.call
27
+ rescue => ex
28
+ @client.log.error "error in action scheduled for #{time}:" +
29
+ " #{ex.class}: #{ex}\n #{ex.backtrace.join("\n ")}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # Returns a scheduler, which has a method that you can call to schedule
36
+ # an action at a time:
37
+ #
38
+ # s = make_scheduler # or client.make_scheduler
39
+ # s.at t do ... end
40
+ #
41
+ # where t can be either a Time or Numeric seconds.
42
+ #
43
+ # The scheduler runs in its own thread. It uses a red-black tree to store
44
+ # the actions, if the rbtree gem is installed and you do not specify
45
+ # 'storage: Array'. Otherwise it uses a sorted Array, which is fine for
46
+ # small schedules.
47
+ #
48
+ # Scheduler is used internally by Worker to manage transaction timeouts,
49
+ # but client code may create its own scheduler -- see example/lease.rb.
50
+ #
51
+ def make_scheduler **opts
52
+ opts[:storage] ||= Scheduler::DEFAULT_STORAGE
53
+ Scheduler.new self, **opts
54
+ end
55
+ end
56
+ end
@@ -1,8 +1,8 @@
1
1
  require 'thread'
2
2
  require 'tupelo/client/reader'
3
3
  require 'tupelo/client/transaction'
4
+ require 'tupelo/client/scheduler'
4
5
  require 'object-template'
5
- require 'atdo'
6
6
 
7
7
  class Tupelo::Client
8
8
  class Worker
@@ -70,6 +70,7 @@ class Tupelo::Client
70
70
  @seq = nil
71
71
  @arc = nil
72
72
  @log = client.log
73
+ @scheduler = nil
73
74
 
74
75
  @client_id = nil
75
76
  @global_tick = nil
@@ -142,19 +143,19 @@ class Tupelo::Client
142
143
  cmd_queue << :stop
143
144
  worker_thread.join if worker_thread ## join(limit)?
144
145
  msg_reader_thread.kill if msg_reader_thread
145
- @atdo.stop if @atdo
146
+ @scheduler.stop if @scheduler
146
147
  end
147
148
 
148
149
  # stop without any remote handshaking
149
150
  def stop!
150
151
  @msg_reader_thread.kill if msg_reader_thread
151
152
  @worker_thread.kill if worker_thread
152
- @atdo.stop if @atdo
153
+ @scheduler.stop if @scheduler
153
154
  end
154
155
 
155
156
  def at time, &action
156
- @atdo ||= AtDo.new
157
- @atdo.at time do
157
+ @scheduler ||= client.make_scheduler
158
+ @scheduler.at time do
158
159
  cmd_queue << action
159
160
  end
160
161
  end
@@ -1,3 +1,3 @@
1
1
  module Tupelo
2
- VERSION = "0.18"
2
+ VERSION = "0.19"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tupelo
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.18'
4
+ version: '0.19'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel VanderWerf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-25 00:00:00.000000000 Z
11
+ date: 2014-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: atdo
@@ -145,6 +145,11 @@ files:
145
145
  - example/pulse.rb
146
146
  - example/read-in-trans.rb
147
147
  - example/remote.rb
148
+ - example/riemann/event-subspace.rb
149
+ - example/riemann/expirer-v1.rb
150
+ - example/riemann/producer.rb
151
+ - example/riemann/riemann-v1.rb
152
+ - example/riemann/riemann-v2.rb
148
153
  - example/small-simplified.rb
149
154
  - example/small.rb
150
155
  - example/socket-broker.rb
@@ -153,7 +158,6 @@ files:
153
158
  - example/subspaces/addr-book.rb
154
159
  - example/subspaces/pubsub.rb
155
160
  - example/subspaces/ramp.rb
156
- - example/subspaces/riemann.rb
157
161
  - example/subspaces/shop/shop-v1.rb
158
162
  - example/subspaces/shop/shop-v2.rb
159
163
  - example/subspaces/simple.rb
@@ -186,9 +190,9 @@ files:
186
190
  - lib/tupelo/archiver/tuplespace.rb
187
191
  - lib/tupelo/archiver/worker.rb
188
192
  - lib/tupelo/client.rb
189
- - lib/tupelo/client/atdo.rb
190
193
  - lib/tupelo/client/common.rb
191
194
  - lib/tupelo/client/reader.rb
195
+ - lib/tupelo/client/scheduler.rb
192
196
  - lib/tupelo/client/subspace.rb
193
197
  - lib/tupelo/client/transaction.rb
194
198
  - lib/tupelo/client/tuplespace.rb
@@ -1,103 +0,0 @@
1
- # A toy implementation of Riemann (http://riemann.io)
2
- #
3
- # Also, this is an example of storing a subspace using different data
4
- # structures in different clients, depending on needs: some clients (generic
5
- # consumers) need to index by host and service, and others (expiration manager)
6
- # need to sort by expiration time.
7
-
8
- require 'tupelo/app'
9
-
10
- N_PRODUCERS = 3
11
- N_CONSUMERS = 2
12
-
13
- Tupelo.application do
14
-
15
- local do
16
- use_subspaces!
17
-
18
- define_subspace("event", {
19
- # field type description
20
- # (from http://riemann.io/concepts.html)
21
-
22
- host: String, # A hostname, e.g. "api1", "foo.com"
23
-
24
- service: String, # e.g. "API port 8000 reqs/sec"
25
-
26
- state: String, # Any string less than 255 bytes, e.g. "ok",
27
- # "warning", "critical"
28
-
29
- time: Numeric, # The time of the event, in unix epoch seconds
30
-
31
- description: String, # Freeform text
32
-
33
- tags: Array, # Freeform list of strings,
34
- # e.g. ["rate", "fooproduct", "transient"]
35
-
36
- metric: Numeric, # A number associated with this event,
37
- # e.g. the number of reqs/sec.
38
-
39
- ttl: Numeric # A floating-point time, in seconds, that this
40
- # event is considered valid for. Expired states
41
- # may be removed from the index.
42
- })
43
- end
44
-
45
- N_PRODUCERS.times do
46
- child subscribe: [] do # N.b., no subscriptions
47
- event = {
48
- host: `hostname`.chomp,
49
- service: "service #{client_id}", # placeholder
50
- state: "",
51
- time: 0,
52
- description: "",
53
- tags: [],
54
- metric: 0,
55
- ttl: 0
56
- }.freeze
57
-
58
- e_ok = event.merge(
59
- state: "ok",
60
- time: Time.now.to_f,
61
- ttl: 1.0
62
- )
63
-
64
- if e_ok[:ttl] == 0.0
65
- pulse e_ok # no need to bother with expiration
66
- else
67
- write e_ok
68
- end
69
- end
70
- end
71
-
72
- N_CONSUMERS.times do
73
- # stores events indexed by host, service
74
- child subscribe: "event", passive: true do ### tuplespace: sqlite
75
- read subspace("event") do |event|
76
- log event ### need filtering, actions, etc.
77
- end
78
- end
79
- end
80
-
81
- # This could be a subspace of the event subspace.
82
- critical_event = {
83
- host: nil,
84
- service: nil,
85
- state: /critical|fatal/i,
86
- time: nil,
87
- description: nil,
88
- tags: nil,
89
- metric: nil,
90
- ttl: nil
91
- }
92
-
93
- child subscribe: "event", passive: true do
94
- read critical_event do |event|
95
- log.error event
96
- end
97
- end
98
-
99
- # expirer: stores current events in expiration order
100
- child subscribe: "event", passive: true do
101
- ### use rbtree
102
- end
103
- end
@@ -1,31 +0,0 @@
1
- require 'tupelo/client'
2
- require 'atdo'
3
-
4
- module Tupelo
5
- class Client
6
- class AtDo < ::AtDo
7
- def initialize client, **opts
8
- @client = client
9
- super **opts
10
- end
11
-
12
- # Accepts numeric +time+. Logs errors in +action+. Otherwise, same
13
- # as ::AtDo.
14
- def at time, &action
15
- time = Time.at(time) if time.kind_of? Numeric
16
- super time do
17
- begin
18
- action.call
19
- rescue => ex
20
- @client.log.error "error in action scheduled for #{time}:" +
21
- " #{ex.class}: #{ex}\n #{ex.backtrace.join("\n ")}"
22
- end
23
- end
24
- end
25
- end
26
-
27
- def make_scheduler **opts
28
- AtDo.new self, **opts
29
- end
30
- end
31
- end