ustate-client 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -83,6 +83,7 @@ Email
83
83
  -----
84
84
 
85
85
  config.rb:
86
+
86
87
  # Email comes from this address (required):
87
88
  emailer.from = 'ustate@your.net'
88
89
 
@@ -93,10 +94,56 @@ config.rb:
93
94
  emailer.tell 'you@gmail.com', 'state = "error" or state = "critical"'
94
95
  emailer.tell 'you@gmail.com', 'service =~ "mysql%"'
95
96
 
97
+ Aggregating states
98
+ ---
99
+
100
+ UState can fold together states matching some criteria to provide a more
101
+ general overview of a complex system. Folds are executed in a separate thread
102
+ and polled from the index every aggregator.interval seconds.
103
+
104
+ config.rb:
105
+
106
+ # Add together the metrics for all feed mergers on any host.
107
+ # The resulting state has service name 'feed merger', but no host.
108
+ aggregator.sum 'service = "feed merger" and host != null', host: nil
109
+
110
+ # Average latencies
111
+ aggregator.average 'service = "api latency"'
112
+
113
+ # You can also pass any block to aggregator.fold. The block will be called
114
+ # with an array of states matching your query.
115
+ aggregator.fold 'service = "custom"' do |states|
116
+ UState::State.new(
117
+ service: 'some crazy result',
118
+ metric_f: states.map(&:metric_f).max
119
+ )
120
+ end
121
+
122
+ Graphite
123
+ ---
124
+
125
+ UState can forward metrics to Graphite. Just specify a query matching states
126
+ you'd like to forward. Forwarding is performed in a separate thread, and polled from the index every graphite.interval seconds.
127
+
128
+ config.rb:
129
+
130
+ graphite.host = 'foo'
131
+ graphite.port = 12345
132
+
133
+ # Submit states every 5 seconds
134
+ graphite.interval = 5
135
+
136
+ # Send everything without a host
137
+ graphite.graph 'host = null'
138
+
139
+ # And also include the disk use on all nodes
140
+ graphite.graph 'service = "disk"'
141
+
96
142
  Custom hooks
97
143
  ------------
98
144
 
99
145
  config.rb:
146
+
100
147
  # Log all states received to console.
101
148
  index.on_state do |s|
102
149
  p s
@@ -0,0 +1,69 @@
1
+ module UState
2
+ class Aggregator
3
+ # Combines states periodically.
4
+
5
+ INTERVAL = 1
6
+
7
+ attr_accessor :interval
8
+ attr_accessor :folds
9
+ def initialize(index, opts = {})
10
+ @index = index
11
+
12
+ @folds = {}
13
+ @interval = opts[:interval] || INTERVAL
14
+
15
+ start
16
+ end
17
+
18
+ # Combines states matching query with State.average
19
+ def average(query, init = State.new)
20
+ fold query do |states|
21
+ State.average states, init
22
+ end
23
+ end
24
+
25
+ # Combines states matching query with the given block. The block
26
+ # receives an array of states which presently match.
27
+ #
28
+ # Example:
29
+ # fold 'service = "api % reqs/sec"' do |states|
30
+ # states.inject(State.new(service: 'api reqs/sec')) do |combined, state|
31
+ # combined.metric_f += state.metric_f
32
+ # combined
33
+ # end
34
+ # end
35
+ def fold(query, &block)
36
+ @folds[block] = if existing = @folds[block]
37
+ "(#{existing}) or (#{q})"
38
+ else
39
+ query
40
+ end
41
+ end
42
+
43
+ # Polls index for states matching each fold, applies fold, and inserts into
44
+ # index.
45
+ def start
46
+ @runner = Thread.new do
47
+ 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
54
+ end
55
+ end
56
+ sleep interval
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ # Combines states matching query with State.sum
63
+ def sum(query, init = State.new)
64
+ fold query do |states|
65
+ State.sum states, init
66
+ end
67
+ end
68
+ end
69
+ end
@@ -89,7 +89,7 @@ class UState::Client
89
89
  @locket.synchronize do
90
90
  begin
91
91
  tries += 1
92
- yield (@socket or connect)
92
+ yield (@socket || connect)
93
93
  rescue IOError => e
94
94
  raise if tries > 3
95
95
  connect and retry
@@ -102,6 +102,9 @@ class UState::Client
102
102
  rescue Errno::ECONNRESET => e
103
103
  raise if tries > 3
104
104
  connect and retry
105
+ rescue InvalidResponse => e
106
+ raise if tries > 3
107
+ connect and retry
105
108
  end
106
109
  end
107
110
  end
@@ -101,11 +101,17 @@ module UState
101
101
  # names have common prefixes removed.
102
102
  # hosts: an array of hosts for rows. Default is every host present in
103
103
  # states, sorted.
104
+ # transpose: Hosts go across, services go down. Enables :global_maxima.
105
+ # global_maximum: Compute default maxima for services globally,
106
+ # instead of a different maximum for each service.
104
107
  def state_chart(states, opts = {})
105
108
  o = {
106
109
  :maxima => {},
107
110
  :service_names => {}
108
111
  }.merge opts
112
+ if o[:transpose] and not o.include?(:global_maximum)
113
+ o[:global_maximum] = true
114
+ end
109
115
 
110
116
  # Get all services
111
117
  services = states.map { |s| s.service }.compact.uniq.sort
@@ -118,10 +124,18 @@ module UState
118
124
  end.merge o[:service_names]
119
125
 
120
126
  # Compute maximum for each service
121
- maxima = states.inject(Hash.new(0)) do |m, s|
122
- m[s.service] = [s.metric, m[s.service]].max
123
- m
124
- end.merge o[:maxima]
127
+ maxima = if o[:global_maximum]
128
+ max = states.map(&:metric).max
129
+ services.inject({}) do |m, s|
130
+ m[s] = max
131
+ m
132
+ end.merge o[:maxima]
133
+ else
134
+ states.inject(Hash.new(0)) do |m, s|
135
+ m[s.service] = [s.metric, m[s.service]].max
136
+ m
137
+ end.merge o[:maxima]
138
+ end
125
139
 
126
140
  # Compute union of all hosts for these states, if no
127
141
  # list of hosts explicitly given.
@@ -139,27 +153,51 @@ module UState
139
153
  # Title
140
154
  title = o[:title] || prefix.capitalize rescue 'Unknown'
141
155
 
142
- h2(title) +
143
- table(
144
- tr(
145
- th,
156
+ if o[:transpose]
157
+ h2(title) +
158
+ table(
159
+ tr(
160
+ th,
161
+ *hosts.map do |host|
162
+ th host
163
+ end
164
+ ),
146
165
  *services.map do |service|
147
- th service_names[service]
148
- end
149
- ),
150
- *hosts.map do |host|
166
+ tr(
167
+ th(service_names[service]),
168
+ *hosts.map do |host|
169
+ s = by[[host, service]]
170
+ td(
171
+ s ? state_bar(s, max: maxima[service]) : nil
172
+ )
173
+ end
174
+ )
175
+ end,
176
+ :class => 'chart'
177
+ )
178
+ else
179
+ h2(title) +
180
+ table(
151
181
  tr(
152
- th(host),
182
+ th,
153
183
  *services.map do |service|
154
- s = by[[host, service]]
155
- td(
156
- s ? state_bar(s, max: maxima[service]) : nil
157
- )
184
+ th service_names[service]
158
185
  end
159
- )
160
- end,
161
- :class => 'chart'
162
- )
186
+ ),
187
+ *hosts.map do |host|
188
+ tr(
189
+ th(host),
190
+ *services.map do |service|
191
+ s = by[[host, service]]
192
+ td(
193
+ s ? state_bar(s, max: maxima[service]) : nil
194
+ )
195
+ end
196
+ )
197
+ end,
198
+ :class => 'chart'
199
+ )
200
+ end
163
201
  end
164
202
 
165
203
  # Renders a state as a short tag.
@@ -1,3 +1,5 @@
1
1
  <div class="box"><%= state_list query 'state != "ok"' %></div>
2
2
 
3
3
  <div class="box"><%= state_chart query 'service = "cpu" or service = "memory" or service =~ "disk%" or service = "load"' %></div>
4
+
5
+ <div class="box"><%= state_chart query('host = "host0"'), transpose: true %></div>
@@ -1,11 +1,16 @@
1
1
  <html>
2
2
  <head>
3
3
  <title>Dashboard</title>
4
- <meta http-equiv="refresh" content="1" />
5
4
  <link rel="stylesheet" type="text/css" href="/css" />
5
+
6
+ <script type="text/javascript">
7
+ setTimeout(function() {
8
+ location.reload(true);
9
+ }, 10000);
10
+ </script>
6
11
  </head>
7
12
 
8
- <body>
13
+ <body onload="refresh();">
9
14
  <div>
10
15
  <h1>Dashboard</h1>
11
16
  <span style="position: absolute; top: 4px; right: 4px;">(as of <%= time Time.now.to_i %>)</span>
@@ -1,5 +1,8 @@
1
1
  module UState
2
2
  class Emailer
3
+ # Sends emails when the index receives state transitions matching specific
4
+ # queries.
5
+
3
6
  require 'net/smtp'
4
7
 
5
8
  attr_accessor :from
@@ -32,7 +35,7 @@ module UState
32
35
  raise ArgumentError, "no from address" unless @from
33
36
 
34
37
  # Subject
35
- subject = "#{s.host} #{s.service} #{s.state}"
38
+ subject = "#{s.host} #{s.service}"
36
39
  if s.once
37
40
  subject << " transient "
38
41
  else
@@ -59,11 +62,10 @@ EOF
59
62
 
60
63
  # Dispatch emails to each address which is interested in this state
61
64
  def receive(*states)
62
- state = states.last
63
65
  Thread.new do
64
66
  @tell.each do |address, q|
65
- if q === state
66
- email address, state
67
+ if states.any? { |state| p state; q === state }
68
+ email address, states.last
67
69
  end
68
70
  end
69
71
  end
@@ -0,0 +1,101 @@
1
+ module UState
2
+ class Graphite
3
+ # Forwards states to Graphite.
4
+ HOST = '127.0.0.1'
5
+ PORT = 2003
6
+ INTERVAL = 5
7
+
8
+ attr_accessor :query
9
+ attr_accessor :host
10
+ attr_accessor :port
11
+ attr_accessor :interval
12
+
13
+ def initialize(index, opts = {})
14
+ @index = index
15
+ @query = opts[:query]
16
+ @host = opts[:host] || HOST
17
+ @port = opts[:port] || PORT
18
+ @interval = opts[:interval] || INTERVAL
19
+ @locket = Mutex.new
20
+
21
+ start
22
+ end
23
+
24
+ def connect
25
+ @socket = TCPSocket.new(@host, @port)
26
+ end
27
+
28
+ def graph(q)
29
+ if @query
30
+ @query = "(#{@query}) or (#{q})"
31
+ else
32
+ @query = q
33
+ end
34
+ end
35
+
36
+ def forward(state)
37
+ # Figure out what time to use.
38
+ present = Time.now.to_i
39
+ if (present - state.time) >= @interval
40
+ time = present
41
+ else
42
+ time = state.time
43
+ end
44
+
45
+ # Construct message
46
+ string = "#{path(state)} #{state.metric} #{time}"
47
+
48
+ # Validate string
49
+ if string["\n"]
50
+ raise ArgumentError, "#{string} has a newline"
51
+ end
52
+
53
+ with_connection do |s|
54
+ s.puts string
55
+ end
56
+ end
57
+
58
+ # Formats a state into a Graphite metric path.
59
+ def path(state)
60
+ if state.host
61
+ host = state.host.split('.').reverse.join('.')
62
+ "#{host}.#{state.service.gsub(' ', '.')}"
63
+ else
64
+ state.service.gsub(' ', '.')
65
+ end
66
+ end
67
+
68
+ def start
69
+ @runner = Thread.new do
70
+ loop do
71
+ @index.query(Query.new(string: @query)).each do |state|
72
+ forward state
73
+ end
74
+ sleep @interval
75
+ end
76
+ end
77
+ end
78
+
79
+ def with_connection
80
+ tries = 0
81
+ @locket.synchronize do
82
+ begin
83
+ tries += 1
84
+ yield (@socket or connect)
85
+ rescue IOError => e
86
+ raise if tries > 3
87
+ connect and retry
88
+ rescue Errno::EPIPE => e
89
+ raise if tries > 3
90
+ connect and retry
91
+ rescue Errno::ECONNREFUSED => e
92
+ raise if tries > 3
93
+ connect and retry
94
+ rescue Errno::ECONNRESET => e
95
+ raise if tries > 3
96
+ connect and retry
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,68 @@
1
+ class UState::MetricThread
2
+ # A metric thread is simple: it wraps some metric object which responds to <<,
3
+ # and every interval seconds, calls #flush which replaces the object and calls
4
+ # a user specified function.
5
+
6
+ INTERVAL = 10
7
+
8
+ attr_accessor :interval
9
+ attr_accessor :metric
10
+
11
+ # client = UState::Client.new
12
+ # m = MetricThread.new Mtrc::Rate do |rate|
13
+ # client << rate
14
+ # end
15
+ #
16
+ # loop do
17
+ # sleep rand
18
+ # m << rand
19
+ # end
20
+ def initialize(klass, *klass_args, &f)
21
+ @klass = klass
22
+ @klass_args = klass_args
23
+ @f = f
24
+ @interval = INTERVAL
25
+
26
+ @metric = new_metric
27
+
28
+ start
29
+ end
30
+
31
+ def <<(*a)
32
+ @metric.<<(*a)
33
+ end
34
+
35
+ def new_metric
36
+ @klass.new *@klass_args
37
+ end
38
+
39
+ def flush
40
+ old, @metric = @metric, new_metric
41
+ @f[old]
42
+ end
43
+
44
+ def start
45
+ raise RuntimeError, "already running" if @runner
46
+
47
+ @running = true
48
+ @runner = Thread.new do
49
+ while @running
50
+ sleep @interval
51
+ begin
52
+ flush
53
+ rescue Exception => e
54
+ end
55
+ end
56
+ @runner = nil
57
+ end
58
+ end
59
+
60
+ def stop
61
+ stop!
62
+ @runner.join
63
+ end
64
+
65
+ def stop!
66
+ @running = false
67
+ end
68
+ end
@@ -3,5 +3,22 @@ module UState
3
3
  include Beefcake::Message
4
4
 
5
5
  optional :string, :string, 1
6
+
7
+ # Converts a Query or string to a query AST.
8
+ def self.query(q)
9
+ case q
10
+ when String
11
+ parser = QueryStringParser.new
12
+ q = parser.parse(q)
13
+ unless q
14
+ raise ArgumentError, "error parsing #{query_string.inspect} at line #{parser.failure_line}:#{parser.failure_column}: #{parser.failure_reason}"
15
+ end
16
+ q.query
17
+ when Query
18
+ query q.string
19
+ else
20
+ raise ArgumentError, "don't know what to do with #{q.class} #{q.inspect}"
21
+ end
22
+ end
6
23
  end
7
24
  end
@@ -635,8 +635,13 @@ module UState
635
635
  if r3
636
636
  r0 = r3
637
637
  else
638
- @index = i0
639
- r0 = nil
638
+ r4 = _nt_null
639
+ if r4
640
+ r0 = r4
641
+ else
642
+ @index = i0
643
+ r0 = nil
644
+ end
640
645
  end
641
646
  end
642
647
  end
@@ -1048,6 +1053,57 @@ module UState
1048
1053
  r0
1049
1054
  end
1050
1055
 
1056
+ module Null0
1057
+ def ruby_value
1058
+ nil
1059
+ end
1060
+ alias sql ruby_value
1061
+ end
1062
+
1063
+ def _nt_null
1064
+ start_index = index
1065
+ if node_cache[:null].has_key?(index)
1066
+ cached = node_cache[:null][index]
1067
+ if cached
1068
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
1069
+ @index = cached.interval.end
1070
+ end
1071
+ return cached
1072
+ end
1073
+
1074
+ i0 = index
1075
+ if has_terminal?('null', false, index)
1076
+ r1 = instantiate_node(SyntaxNode,input, index...(index + 4))
1077
+ @index += 4
1078
+ else
1079
+ terminal_parse_failure('null')
1080
+ r1 = nil
1081
+ end
1082
+ if r1
1083
+ r0 = r1
1084
+ r0.extend(Null0)
1085
+ else
1086
+ if has_terminal?('nil', false, index)
1087
+ r2 = instantiate_node(SyntaxNode,input, index...(index + 3))
1088
+ @index += 3
1089
+ else
1090
+ terminal_parse_failure('nil')
1091
+ r2 = nil
1092
+ end
1093
+ if r2
1094
+ r0 = r2
1095
+ r0.extend(Null0)
1096
+ else
1097
+ @index = i0
1098
+ r0 = nil
1099
+ end
1100
+ end
1101
+
1102
+ node_cache[:null][start_index] = r0
1103
+
1104
+ r0
1105
+ end
1106
+
1051
1107
  def _nt_space
1052
1108
  start_index = index
1053
1109
  if node_cache[:space].has_key?(index)
@@ -92,7 +92,7 @@ module UState
92
92
  end
93
93
 
94
94
  rule value
95
- double_quoted_string / float / integer
95
+ double_quoted_string / float / integer / null
96
96
  end
97
97
 
98
98
  rule integer
@@ -137,6 +137,15 @@ module UState
137
137
  }
138
138
  end
139
139
 
140
+ rule null
141
+ ('null' / 'nil') {
142
+ def ruby_value
143
+ nil
144
+ end
145
+ alias sql ruby_value
146
+ }
147
+ end
148
+
140
149
  rule space
141
150
  [\s]+
142
151
  end
@@ -14,9 +14,14 @@ module UState
14
14
  require 'treetop'
15
15
  require 'ustate/query_string'
16
16
  require 'ustate/query/ast'
17
+ require 'ustate/metric_thread'
18
+ require 'mtrc'
17
19
 
18
20
  attr_accessor :backends
19
21
  attr_accessor :index
22
+ attr_writer :aggregator
23
+ attr_writer :emailer
24
+ attr_writer :graphite
20
25
 
21
26
  def initialize(opts = {})
22
27
  # Backends
@@ -30,11 +35,21 @@ module UState
30
35
  setup_signals
31
36
  end
32
37
 
38
+ def aggregator(opts = {})
39
+ require 'ustate/aggregator'
40
+ @aggregator ||= UState::Aggregator.new(@index, opts)
41
+ end
42
+
33
43
  def emailer(opts = {})
34
44
  require 'ustate/emailer'
35
45
  @emailer ||= UState::Emailer.new(@index, opts)
36
46
  end
37
47
 
48
+ def graphite(opts = {})
49
+ require 'ustate/graphite'
50
+ @graphite ||= UState::Graphite.new(@index, opts)
51
+ end
52
+
38
53
  def start
39
54
  @index.start
40
55
 
@@ -10,8 +10,17 @@ module UState
10
10
 
11
11
  THREADS = 1000
12
12
  BUFFER_SIZE = 10
13
+ # Forget about states after
14
+ EXPIRY = 1000
15
+
16
+ # Update metrics every
17
+ INSERT_RATE_INTERVAL = 5
18
+ INSERT_TIMES_INTERVAL = 5
13
19
 
14
- attr_reader :db, :queue
20
+ attr_reader :db, :queue
21
+ attr_accessor :expiry
22
+ attr_accessor :insert_rate_interval
23
+ attr_accessor :insert_times_interval
15
24
 
16
25
  def initialize(opts = {})
17
26
  @db = Sequel.sqlite
@@ -23,6 +32,10 @@ module UState
23
32
  @on_state_once = []
24
33
  @on_state = []
25
34
 
35
+ @expiry = opts[:expiry] || EXPIRY
36
+
37
+ @insert_rate_interval = opts[:insert_rate_interval] || INSERT_RATE_INTERVAL
38
+ @insert_times_interval = opts[:insert_times_interval] || INSERT_TIMES_INTERVAL
26
39
  setup_db
27
40
  end
28
41
 
@@ -31,7 +44,11 @@ module UState
31
44
  end
32
45
 
33
46
  def <<(s)
47
+ t0 = Time.now
34
48
  process s
49
+ dt = Time.now - t0
50
+ @insert_times << dt
51
+ @insert_rate << 1
35
52
  end
36
53
 
37
54
  def thread(s)
@@ -83,7 +100,7 @@ module UState
83
100
  # Update
84
101
  if current[:time] <= s.time
85
102
  if current[:state] != s.state
86
- on_state_change current, s
103
+ on_state_change row_to_state(current), s
87
104
  end
88
105
 
89
106
  # Update
@@ -128,6 +145,36 @@ module UState
128
145
  end
129
146
  end
130
147
 
148
+ # Remove states older than @expiry
149
+ def reap
150
+ @db[:states].filter { |s|
151
+ s.time < (Time.now - @expiry).to_i
152
+ }.each do |row|
153
+ on_state_change(
154
+ row_to_state(row),
155
+ row_to_state(
156
+ row.merge(
157
+ state: 'unknown',
158
+ description: "ustate has not heard from this service since #{Time.at(row[:time])}",
159
+ metric_f: nil,
160
+ time: Time.now.to_i
161
+ )
162
+ )
163
+ )
164
+ @db[:states].filter(host: row[:host], state: row[:state]).delete
165
+ end
166
+ end
167
+
168
+ # Periodically expire old states.
169
+ def reaper
170
+ Thread.new do
171
+ loop do
172
+ sleep 1
173
+ reap
174
+ end
175
+ end
176
+ end
177
+
131
178
  # Converts a row to a State
132
179
  def row_to_state(row)
133
180
  State.new(row)
@@ -149,13 +196,49 @@ module UState
149
196
  def start
150
197
  stop!
151
198
  @pool = []
199
+ @reaper = reaper
200
+
201
+ @insert_rate = MetricThread.new(Mtrc::Rate) do |r|
202
+ self << State.new(
203
+ service: "ustate insert rate",
204
+ state: "ok",
205
+ host: Socket.gethostname,
206
+ time: Time.now.to_i,
207
+ metric_f: r.rate.to_f,
208
+ )
209
+ end
210
+ @insert_rate.interval = @insert_rate_interval
211
+
212
+ @insert_times = MetricThread.new(Mtrc::SortedSamples) do |r|
213
+ self << State.new(
214
+ service: "ustate insert 50",
215
+ state: "ok",
216
+ host: Socket.gethostname,
217
+ time: Time.now.to_i,
218
+ metric_f: r % 50,
219
+ )
220
+ self << State.new(
221
+ service: "ustate insert 95",
222
+ state: "ok",
223
+ host: Socket.gethostname,
224
+ time: Time.now.to_i,
225
+ metric_f: r % 95,
226
+ )
227
+ self << State.new(
228
+ service: "ustate insert 99",
229
+ state: "ok",
230
+ host: Socket.gethostname,
231
+ time: Time.now.to_i,
232
+ metric_f: r % 99,
233
+ )
234
+ end
235
+ @insert_times.interval = @insert_times_interval
152
236
  end
153
237
 
154
238
  # Finish up
155
239
  def stop
156
- @pool.each do |thread|
157
- thread.join
158
- end
240
+ @insert_rate.stop rescue nil
241
+ @insert_times.stop rescue nil
159
242
  end
160
243
 
161
244
  def stop!
@@ -1,19 +1,156 @@
1
- class UState::State
2
- include Beefcake::Message
3
-
4
- optional :time, :int64, 1
5
- optional :state, :string, 2
6
- optional :service, :string, 3
7
- optional :host, :string, 4
8
- optional :description, :string, 5
9
- optional :once, :bool, 6
10
- optional :metric_f, :float, 15
11
-
12
- def metric
13
- @metric || metric_f
14
- end
1
+ module UState
2
+ class State
3
+ include Beefcake::Message
4
+
5
+ optional :time, :int64, 1
6
+ optional :state, :string, 2
7
+ optional :service, :string, 3
8
+ optional :host, :string, 4
9
+ optional :description, :string, 5
10
+ optional :once, :bool, 6
11
+ optional :metric_f, :float, 15
12
+
13
+ # Average a set of states together. Chooses the mean metric, the mode
14
+ # state, mode service, and the mean time. If init is provided, its values
15
+ # override (where present) the computed ones.
16
+ def self.average(states, init = State.new)
17
+ init = case init
18
+ when State
19
+ init.dup
20
+ else
21
+ State.new init
22
+ end
23
+
24
+ # Metric
25
+ init.metric_f ||= states.inject(0.0) { |a, state|
26
+ a + (state.metric || 0)
27
+ } / states.size
28
+ if init.metric_f.nan?
29
+ init.metric_f = 0.0
30
+ end
31
+
32
+ # State
33
+ init.state ||= mode states.map(&:state)
34
+ init.service ||= mode states.map(&:service)
35
+
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
42
+ end
43
+ init.time ||= Time.now.to_i
44
+
45
+ init
46
+ end
47
+
48
+ # Sum a set of states together. Adds metrics, takes the mode state, mode
49
+ # service and the mean time. If init is provided, its values override
50
+ # (where present) the computed ones.
51
+ def self.sum(states, init = State.new)
52
+ init = case init
53
+ when State
54
+ init.dup
55
+ else
56
+ State.new init
57
+ end
58
+
59
+ # Metric
60
+ init.metric_f ||= states.inject(0.0) { |a, state|
61
+ a + (state.metric || 0)
62
+ }
63
+ if init.metric_f.nan?
64
+ init.metric_f = 0.0
65
+ end
66
+
67
+ # State
68
+ init.state ||= mode states.map(&:state)
69
+ init.service ||= mode states.map(&:service)
70
+
71
+ # Time
72
+ init.time ||= begin
73
+ (states.inject(0) do |a, state|
74
+ a + state.time.to_f
75
+ end / states.size).to_i
76
+ rescue
77
+ end
78
+ init.time ||= Time.now.to_i
79
+
80
+ init
81
+ end
82
+
83
+ # Finds the maximum of a set of states. Metric is the maximum. State is the
84
+ # highest, as defined by Dash.config.state_order. Time is the mean.
85
+ def self.max(states, init = State.new)
86
+ init = case init
87
+ when State
88
+ init.dup
89
+ else
90
+ State.new init
91
+ end
92
+
93
+ # Metric
94
+ init.metric_f ||= states.inject(0.0) { |a, state|
95
+ a + (state.metric || 0)
96
+ }
97
+ if init.metric_f.nan?
98
+ init.metric_f = 0.0
99
+ end
100
+
101
+ # State
102
+ init.state ||= states.inject(nil) do |max, state|
103
+ state.state if Dash.config[:state_order][state.state] > Dash.config[:state_order][max]
104
+ end
105
+
106
+ # Time
107
+ init.time ||= begin
108
+ (states.inject(0) { |a, state|
109
+ a + state.time.to_f
110
+ } / states.size).to_i
111
+ rescue
112
+ end
113
+ init.time ||= Time.now.to_i
114
+
115
+ init
116
+ end
117
+
118
+ def self.mode(array)
119
+ array.inject(Hash.new(0)) do |counts, e|
120
+ counts[e] += 1
121
+ counts
122
+ end.sort_by { |e, count| count }.last.first rescue nil
123
+ end
124
+
125
+ # Partition a list of states by a field
126
+ # Returns a hash of field_value => state
127
+ def self.partition(states, field)
128
+ states.inject(Hash.new { [] }) do |p, state|
129
+ p[state.send(field)] << state
130
+ end
131
+ end
132
+
133
+ # Sorts states by a field. nil values first.
134
+ def self.sort(states, field)
135
+ states.sort do |a, b|
136
+ a = a.send field
137
+ b = b.send field
138
+ if a.nil?
139
+ -1
140
+ elsif b.nil?
141
+ 1
142
+ else
143
+ a <=> b
144
+ end
145
+ end
146
+ end
147
+
148
+ def metric
149
+ @metric || metric_f
150
+ end
15
151
 
16
- def metric=(m)
17
- @metric = m
152
+ def metric=(m)
153
+ @metric = m
154
+ end
18
155
  end
19
156
  end
@@ -1,3 +1,3 @@
1
1
  module UState
2
- VERSION = '0.0.3'
2
+ VERSION = '0.0.4'
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.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,12 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-09-06 00:00:00.000000000 -07:00
13
- default_executable:
12
+ date: 2011-09-13 00:00:00.000000000Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: beefcake
17
- requirement: &15107700 !ruby/object:Gem::Requirement
16
+ requirement: &69429590 !ruby/object:Gem::Requirement
18
17
  none: false
19
18
  requirements:
20
19
  - - ! '>='
@@ -22,10 +21,10 @@ dependencies:
22
21
  version: 0.3.5
23
22
  type: :runtime
24
23
  prerelease: false
25
- version_requirements: *15107700
24
+ version_requirements: *69429590
26
25
  - !ruby/object:Gem::Dependency
27
26
  name: trollop
28
- requirement: &15107220 !ruby/object:Gem::Requirement
27
+ requirement: &69428990 !ruby/object:Gem::Requirement
29
28
  none: false
30
29
  requirements:
31
30
  - - ! '>='
@@ -33,7 +32,18 @@ dependencies:
33
32
  version: 1.16.2
34
33
  type: :runtime
35
34
  prerelease: false
36
- version_requirements: *15107220
35
+ version_requirements: *69428990
36
+ - !ruby/object:Gem::Dependency
37
+ name: mtrc
38
+ requirement: &69428610 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 0.0.4
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *69428610
37
47
  description:
38
48
  email: aphyr@aphyr.com
39
49
  executables: []
@@ -41,41 +51,42 @@ extensions: []
41
51
  extra_rdoc_files: []
42
52
  files:
43
53
  - lib/ustate.rb
44
- - lib/ustate/query/not_equals.rb
45
- - lib/ustate/query/equals.rb
46
- - lib/ustate/query/or.rb
47
- - lib/ustate/query/approximately.rb
48
- - lib/ustate/query/and.rb
49
- - lib/ustate/query/ast.rb
50
- - lib/ustate/query/not.rb
54
+ - lib/ustate/graphite.rb
51
55
  - lib/ustate/query_string.treetop
52
- - lib/ustate/server.rb
53
- - lib/ustate/emailer.rb
54
- - lib/ustate/query.rb
56
+ - lib/ustate/message.rb
57
+ - lib/ustate/metric_thread.rb
58
+ - lib/ustate/aggregator.rb
55
59
  - lib/ustate/version.rb
56
- - lib/ustate/state.rb
57
- - lib/ustate/client/query.rb
58
- - lib/ustate/query_string.rb
59
- - lib/ustate/server/index.rb
60
+ - lib/ustate/server/graphite.rb
60
61
  - lib/ustate/server/backends.rb
61
- - lib/ustate/server/backends/tcp.rb
62
62
  - lib/ustate/server/backends/base.rb
63
- - lib/ustate/server/graphite.rb
63
+ - lib/ustate/server/backends/tcp.rb
64
+ - lib/ustate/server/index.rb
64
65
  - lib/ustate/server/connection.rb
66
+ - lib/ustate/server.rb
67
+ - lib/ustate/dash.rb
68
+ - lib/ustate/state.rb
69
+ - lib/ustate/query.rb
70
+ - lib/ustate/client/query.rb
65
71
  - lib/ustate/client.rb
66
- - lib/ustate/message.rb
67
- - lib/ustate/dash/helper/renderer.rb
68
- - lib/ustate/dash/views/css.scss
69
72
  - lib/ustate/dash/views/index.erubis
70
73
  - lib/ustate/dash/views/layout.erubis
71
- - lib/ustate/dash/state.rb
74
+ - lib/ustate/dash/views/css.scss
72
75
  - lib/ustate/dash/controller/index.rb
73
76
  - lib/ustate/dash/controller/css.rb
74
77
  - lib/ustate/dash/config.rb
75
- - lib/ustate/dash.rb
78
+ - lib/ustate/dash/helper/renderer.rb
79
+ - lib/ustate/query/or.rb
80
+ - lib/ustate/query/not_equals.rb
81
+ - lib/ustate/query/ast.rb
82
+ - lib/ustate/query/equals.rb
83
+ - lib/ustate/query/approximately.rb
84
+ - lib/ustate/query/not.rb
85
+ - lib/ustate/query/and.rb
86
+ - lib/ustate/query_string.rb
87
+ - lib/ustate/emailer.rb
76
88
  - LICENSE
77
89
  - README.markdown
78
- has_rdoc: true
79
90
  homepage: https://github.com/aphyr/ustate
80
91
  licenses: []
81
92
  post_install_message:
@@ -96,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
107
  version: '0'
97
108
  requirements: []
98
109
  rubyforge_project: ustate-client
99
- rubygems_version: 1.6.2
110
+ rubygems_version: 1.8.10
100
111
  signing_key:
101
112
  specification_version: 3
102
113
  summary: Client for the distributed state server ustate.
@@ -1,75 +0,0 @@
1
- class UState::State
2
- # Average a set of states together. Chooses the meane metric, the mode
3
- # state, and the mean time. If init is provided, its values override (where present) the
4
- # computed ones.
5
- def average(states, init = State.new)
6
- # Metric
7
- init.metric ||= begin
8
- states.inject(0) { |a, state|
9
- a + (state.metric || 0)
10
- } / states.size
11
- rescue
12
- nil
13
- end
14
-
15
- # State
16
- init.state ||= states.inject(Hash.new(0)) do |counts, s|
17
- counts[s.state] += 1
18
- counts
19
- end.sort_by { |state, count| count }.first.first rescue nil
20
-
21
- init.time ||= begin
22
- states.inject(0) do |a, state|
23
- a + state.time.to_f
24
- end / states.size
25
- rescue
26
- end
27
-
28
- init
29
- end
30
-
31
- # Finds the maximum of a set of states. Metric is the maximum. State is the highest,
32
- # as defined by Dash.config.state_order. Time is the mean.
33
- def max(states, init = {})
34
- init.metric ||= states.inject(0) { |a, state|
35
- a + (state.metric || 0)
36
- }
37
-
38
- init.state] ||= states.inject(nil) do |max, state|
39
- state.state if Dash.config[:state_order][state.state] > Dash.config[:state_order][max]
40
- end
41
-
42
- init.time ||= begin
43
- states.inject(0) { |a, state|
44
- a + state.time.to_f
45
- } / states.size
46
- rescue
47
- nil
48
- end
49
-
50
- init
51
- end
52
-
53
- # Partition a list of states by a field
54
- # Returns a hash of field_value => state
55
- def partition(states, field)
56
- states.inject(Hash.new { [] }) do |p, state|
57
- p[state.send(field)] << state
58
- end
59
- end
60
-
61
- # Sorts states by a field. nil values first.
62
- def sort(states, field)
63
- states.sort do |a, b|
64
- a = a.send field
65
- b = b.send field
66
- if a.nil?
67
- -1
68
- elsif b.nil?
69
- 1
70
- else
71
- a <=> b
72
- end
73
- end
74
- end
75
- end