ustate-client 0.0.3 → 0.0.4

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.
@@ -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