ustate-client 0.0.4 → 0.0.5

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.
@@ -187,6 +187,16 @@ Then:
187
187
  c.query.states # => [UState::State(state: 'ok', service: 'My service')]
188
188
  c.query('state != "ok"').states # => []
189
189
 
190
+ Client state management
191
+ -----------------------
192
+
193
+ UState provides some classes to make managing state updates easier.
194
+
195
+ UState::MetricThread starts a thread to poll a metric periodically, which can
196
+ be used to flush an accumulated value to ustate at regular intervals.
197
+
198
+ UState::AutoState bundles a state and a client together. Any changes to the AutoState automatically send the new state to the client.
199
+
190
200
  The Dashboard
191
201
  =============
192
202
 
@@ -11,14 +11,15 @@ module UState
11
11
 
12
12
  @folds = {}
13
13
  @interval = opts[:interval] || INTERVAL
14
+ @server = opts[:server]
14
15
 
15
16
  start
16
17
  end
17
18
 
18
19
  # Combines states matching query with State.average
19
- def average(query, init = State.new)
20
+ def average(query, *a)
20
21
  fold query do |states|
21
- State.average states, init
22
+ State.average states, *a
22
23
  end
23
24
  end
24
25
 
@@ -45,24 +46,29 @@ module UState
45
46
  def start
46
47
  @runner = Thread.new do
47
48
  loop do
48
- interval = (@interval.to_f / @folds.size) rescue @interval
49
- @folds.each do |f, query|
50
- matching = @index.query(Query.new(string: query))
51
- unless matching.empty?
52
- if combined = f[matching]
53
- @index << combined
49
+ begin
50
+ interval = (@interval.to_f / @folds.size) rescue @interval
51
+ @folds.each do |f, query|
52
+ matching = @index.query(Query.new(string: query))
53
+ unless matching.empty?
54
+ if combined = f[matching]
55
+ @index << combined
56
+ end
54
57
  end
58
+ sleep interval
55
59
  end
56
- sleep interval
60
+ rescue Exception => e
61
+ @server.log.error e
62
+ sleep 1
57
63
  end
58
64
  end
59
65
  end
60
66
  end
61
67
 
62
68
  # Combines states matching query with State.sum
63
- def sum(query, init = State.new)
69
+ def sum(query, *a)
64
70
  fold query do |states|
65
- State.sum states, init
71
+ State.sum states, *a
66
72
  end
67
73
  end
68
74
  end
@@ -0,0 +1,125 @@
1
+ module UState
2
+ class AutoState
3
+ # Binds together a State and a Client. Any change made here
4
+ # sends the state to the client. Useful when updates to a state are made
5
+ # decoherently, e.g. across many methods. Combine with MetricThread (or
6
+ # just Thread.new { loop { autostate.flush; sleep n } }) to ensure regular
7
+ # updates.
8
+ #
9
+ # example:
10
+ #
11
+ # class Job
12
+ # def initialize
13
+ # @state = AutoState.new
14
+ # @state.service = 'job'
15
+ # @state.state = 'starting up'
16
+ #
17
+ # run
18
+ # end
19
+ #
20
+ # def run
21
+ # loop do
22
+ # begin
23
+ # a
24
+ # b
25
+ # rescue Exception => e
26
+ # @state.once(
27
+ # state: 'error',
28
+ # description: e.to_s
29
+ # )
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ # def a
35
+ # @state.state = 'heavy lifting a'
36
+ # ...
37
+ # end
38
+ #
39
+ # def b
40
+ # @state.state = 'heavy lifting b'
41
+ # ...
42
+ # end
43
+
44
+ def initialize(client = Client.new, state = State.new)
45
+ @client = client
46
+ @state = case state
47
+ when State
48
+ state
49
+ else
50
+ State.new state
51
+ end
52
+ end
53
+
54
+ def description=(description)
55
+ @state.description = description
56
+ flush
57
+ end
58
+
59
+ def description
60
+ @state.description
61
+ end
62
+
63
+ # Send state to client
64
+ def flush
65
+ @client << @state
66
+ end
67
+
68
+ def host=(host)
69
+ @state.host = host
70
+ flush
71
+ end
72
+
73
+ def host
74
+ @state.host
75
+ end
76
+
77
+ def metric_f=(metric_f)
78
+ @state.metric_f = metric_f
79
+ flush
80
+ end
81
+
82
+ def metric_f
83
+ @state.metric_f
84
+ end
85
+
86
+ # Performs multiple updates, followed by flush.
87
+ # Example: merge state: critical, metric_f: 10235.3
88
+ def merge(opts)
89
+ opts.each do |k, v|
90
+ @state.send "#{k}=", v
91
+ end
92
+ flush
93
+ end
94
+
95
+ # Issues an immediate update of the state with the :once option
96
+ # set, but does not update the local state. Useful for transient errors.
97
+ # Opts are merged with the state.
98
+ def once(opts)
99
+ o = @state.dup
100
+ opts.each do |k, v|
101
+ o.send "#{k}=", v
102
+ end
103
+ o.once = true
104
+ @client << o
105
+ end
106
+
107
+ def state=(state)
108
+ @state.state = state
109
+ flush
110
+ end
111
+
112
+ def state
113
+ @state.state
114
+ end
115
+
116
+ def service=(service)
117
+ @state.service = service
118
+ flush
119
+ end
120
+
121
+ def service
122
+ @state.service
123
+ end
124
+ end
125
+ end
@@ -23,9 +23,15 @@ class UState::Client
23
23
  # Send a state
24
24
  def <<(state_opts)
25
25
  # Create state
26
- state = UState::State.new(state_opts)
27
- state.time ||= Time.now.utc.to_i
28
- state.host ||= Socket.gethostname
26
+ case state_opts
27
+ when UState::State
28
+ state = state_opts
29
+ else
30
+ unless state_opts.include? :host
31
+ state_opts[:host] = Socket.gethostname
32
+ end
33
+ state = UState::State.new(state_opts)
34
+ end
29
35
 
30
36
  message = UState::Message.new :states => [state]
31
37
 
@@ -125,14 +125,16 @@ module UState
125
125
 
126
126
  # Compute maximum for each service
127
127
  maxima = if o[:global_maximum]
128
- max = states.map(&:metric).max
128
+ max = states.map(&:metric).compact.max
129
129
  services.inject({}) do |m, s|
130
130
  m[s] = max
131
131
  m
132
132
  end.merge o[:maxima]
133
133
  else
134
134
  states.inject(Hash.new(0)) do |m, s|
135
- m[s.service] = [s.metric, m[s.service]].max
135
+ if s.metric
136
+ m[s.service] = [s.metric, m[s.service]].max
137
+ end
136
138
  m
137
139
  end.merge o[:maxima]
138
140
  end
@@ -141,8 +143,16 @@ module UState
141
143
  # list of hosts explicitly given.
142
144
  hosts = o[:hosts] || states.map do |state|
143
145
  state.host
144
- end.compact
145
- hosts = hosts.uniq.sort
146
+ end
147
+ hosts = hosts.uniq.sort { |a, b|
148
+ if !a
149
+ -1
150
+ elsif !b
151
+ 1
152
+ else
153
+ a <=> b
154
+ end
155
+ }
146
156
 
147
157
  # Construct index
148
158
  by = states.inject({}) do |index, s|
@@ -23,6 +23,7 @@ module UState
23
23
  @from = opts[:from]
24
24
  @name = opts[:name]
25
25
  @host = opts[:host]
26
+ @server = opts[:server]
26
27
 
27
28
  @tell = {}
28
29
 
@@ -63,10 +64,14 @@ EOF
63
64
  # Dispatch emails to each address which is interested in this state
64
65
  def receive(*states)
65
66
  Thread.new do
66
- @tell.each do |address, q|
67
- if states.any? { |state| p state; q === state }
68
- email address, states.last
67
+ begin
68
+ @tell.each do |address, q|
69
+ if states.any? { |state| p state; q === state }
70
+ email address, states.last
71
+ end
69
72
  end
73
+ rescue Exception => e
74
+ @server.log.error e
70
75
  end
71
76
  end
72
77
  end
@@ -15,6 +15,7 @@ module UState
15
15
  @query = opts[:query]
16
16
  @host = opts[:host] || HOST
17
17
  @port = opts[:port] || PORT
18
+ @server = opts[:server]
18
19
  @interval = opts[:interval] || INTERVAL
19
20
  @locket = Mutex.new
20
21
 
@@ -68,10 +69,15 @@ module UState
68
69
  def start
69
70
  @runner = Thread.new do
70
71
  loop do
71
- @index.query(Query.new(string: @query)).each do |state|
72
- forward state
72
+ begin
73
+ @index.query(Query.new(string: @query)).each do |state|
74
+ forward state
75
+ end
76
+ sleep @interval
77
+ rescue Exception => e
78
+ @server.log.warn e
79
+ sleep 1
73
80
  end
74
- sleep @interval
75
81
  end
76
82
  end
77
83
  end
@@ -15,6 +15,7 @@ module UState
15
15
  require 'ustate/query_string'
16
16
  require 'ustate/query/ast'
17
17
  require 'ustate/metric_thread'
18
+ require 'logger'
18
19
  require 'mtrc'
19
20
 
20
21
  attr_accessor :backends
@@ -22,32 +23,37 @@ module UState
22
23
  attr_writer :aggregator
23
24
  attr_writer :emailer
24
25
  attr_writer :graphite
26
+
27
+ attr_accessor :log
25
28
 
26
29
  def initialize(opts = {})
27
30
  # Backends
28
31
  @backends = []
29
- b = Backends::TCP.new opts
32
+ b = Backends::TCP.new opts.merge(server: self)
30
33
  b.server = self
31
34
  @backends << b
32
35
 
33
- @index = Index.new
36
+ @index = Index.new :server => self
37
+
38
+ @log = Logger.new('ustate.log', 4, 134217728)
39
+ @log.level = Logger::INFO
34
40
 
35
41
  setup_signals
36
42
  end
37
43
 
38
44
  def aggregator(opts = {})
39
45
  require 'ustate/aggregator'
40
- @aggregator ||= UState::Aggregator.new(@index, opts)
46
+ @aggregator ||= UState::Aggregator.new(@index, opts.merge(server: self))
41
47
  end
42
48
 
43
49
  def emailer(opts = {})
44
50
  require 'ustate/emailer'
45
- @emailer ||= UState::Emailer.new(@index, opts)
51
+ @emailer ||= UState::Emailer.new(@index, opts.merge(server: self))
46
52
  end
47
53
 
48
54
  def graphite(opts = {})
49
55
  require 'ustate/graphite'
50
- @graphite ||= UState::Graphite.new(@index, opts)
56
+ @graphite ||= UState::Graphite.new(@index, opts.merge(server: self))
51
57
  end
52
58
 
53
59
  def start
@@ -13,6 +13,7 @@ class UState::Server::Backends::Base
13
13
 
14
14
  def initialize(opts = {})
15
15
  @connections = []
16
+ @server = opts[:server]
16
17
  @timeout = opts[:timeout] || TIMEOUT
17
18
  @maximum_connections = opts[:maximum_connections] || MAXIMUM_CONNECTIONS
18
19
  end
@@ -17,7 +17,7 @@ class UState::Server
17
17
 
18
18
  # Connect the server
19
19
  def connect
20
- puts "Listening on #{@host}:#{@port}"
20
+ @server.log.info "Listening on #{@host}:#{@port}"
21
21
  @signature = EventMachine.start_server(@host, @port, Connection, &method(:initialize_connection))
22
22
  end
23
23
 
@@ -25,6 +25,7 @@ module UState
25
25
  def initialize(opts = {})
26
26
  @db = Sequel.sqlite
27
27
 
28
+ @server = opts[:server]
28
29
  @threads = opts[:threads] || THREADS
29
30
  @pool = []
30
31
 
@@ -34,11 +34,10 @@ module UState
34
34
  init.service ||= mode states.map(&:service)
35
35
 
36
36
  # Time
37
- init.time ||= begin
38
- (states.inject(0) do |a, state|
39
- a + state.time.to_f
40
- end / states.size).to_i
41
- rescue
37
+ init.time = begin
38
+ times = states.map(&:time).compact
39
+ (times.inject(:+) / times.size).to_i
40
+ rescue
42
41
  end
43
42
  init.time ||= Time.now.to_i
44
43
 
@@ -69,10 +68,9 @@ module UState
69
68
  init.service ||= mode states.map(&:service)
70
69
 
71
70
  # Time
72
- init.time ||= begin
73
- (states.inject(0) do |a, state|
74
- a + state.time.to_f
75
- end / states.size).to_i
71
+ init.time = begin
72
+ times = states.map(&:time).compact
73
+ (times.inject(:+) / times.size).to_i
76
74
  rescue
77
75
  end
78
76
  init.time ||= Time.now.to_i
@@ -104,11 +102,10 @@ module UState
104
102
  end
105
103
 
106
104
  # Time
107
- init.time ||= begin
108
- (states.inject(0) { |a, state|
109
- a + state.time.to_f
110
- } / states.size).to_i
111
- rescue
105
+ init.time = begin
106
+ times = states.map(&:time).compact
107
+ (times.inject(:+) / times.size).to_i
108
+ rescue
112
109
  end
113
110
  init.time ||= Time.now.to_i
114
111
 
@@ -145,6 +142,12 @@ module UState
145
142
  end
146
143
  end
147
144
 
145
+ def initialize(*a)
146
+ super *a
147
+
148
+ @time ||= Time.now.to_i
149
+ end
150
+
148
151
  def metric
149
152
  @metric || metric_f
150
153
  end
@@ -1,3 +1,3 @@
1
1
  module UState
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.5'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ustate-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-09-13 00:00:00.000000000Z
12
+ date: 2011-09-17 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: beefcake
16
- requirement: &69429590 !ruby/object:Gem::Requirement
16
+ requirement: &73107330 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.3.5
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *69429590
24
+ version_requirements: *73107330
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: trollop
27
- requirement: &69428990 !ruby/object:Gem::Requirement
27
+ requirement: &73106920 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 1.16.2
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *69428990
35
+ version_requirements: *73106920
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: mtrc
38
- requirement: &69428610 !ruby/object:Gem::Requirement
38
+ requirement: &73106530 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: 0.0.4
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *69428610
46
+ version_requirements: *73106530
47
47
  description:
48
48
  email: aphyr@aphyr.com
49
49
  executables: []
@@ -54,6 +54,7 @@ files:
54
54
  - lib/ustate/graphite.rb
55
55
  - lib/ustate/query_string.treetop
56
56
  - lib/ustate/message.rb
57
+ - lib/ustate/auto_state.rb
57
58
  - lib/ustate/metric_thread.rb
58
59
  - lib/ustate/aggregator.rb
59
60
  - lib/ustate/version.rb