ustate-client 0.0.1

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.
@@ -0,0 +1,116 @@
1
+ module UState
2
+ grammar QueryString
3
+ # (state =~ "foo" or state == "bar") and service == "foo"
4
+ # Binding order proceeds from loosest to tightest.
5
+
6
+ rule or
7
+ first:and rest:(space 'or' space and)* {
8
+ def sql
9
+ rest.elements.map { |x| x.and }.
10
+ inject(first.sql) do |a, sub|
11
+ a | sub.sql
12
+ end
13
+ end
14
+ }
15
+ end
16
+
17
+ rule and
18
+ first:primary rest:(space 'and' space primary)* {
19
+ def sql
20
+ rest.elements.map { |x| x.primary }.
21
+ inject(first.sql) do |a, sub|
22
+ a & sub.sql
23
+ end
24
+ end
25
+ }
26
+ end
27
+
28
+ rule primary
29
+ '(' space? x:or space? ')' {
30
+ def sql
31
+ x.sql
32
+ end
33
+ }
34
+ /
35
+ predicate
36
+ end
37
+
38
+ rule predicate
39
+ equals / not_equals / approximately
40
+ end
41
+
42
+ rule approximately
43
+ field space? '=~' space? string {
44
+ def sql
45
+ Sequel::SQL::StringExpression.like field.sql, string.sql
46
+ end
47
+ }
48
+ end
49
+
50
+ rule not_equals
51
+ field space? '!=' space? value {
52
+ def sql
53
+ Sequel::SQL::BooleanExpression.from_value_pairs({field.sql => value.sql}, :AND, true)
54
+ end
55
+ }
56
+ end
57
+
58
+ rule equals
59
+ field space? ('=' / '==') space? value {
60
+ def sql
61
+ Sequel::SQL::BooleanExpression.from_value_pairs field.sql => value.sql
62
+ end
63
+ }
64
+ end
65
+
66
+ rule value
67
+ double_quoted_string / float / integer
68
+ end
69
+
70
+ rule integer
71
+ '-'? [0-9]+ {
72
+ def ruby_value
73
+ Integer(text_value)
74
+ end
75
+ alias sql ruby_value
76
+ }
77
+ end
78
+
79
+ rule float
80
+ '-'? [0-9]+ '.' [0-9]+ {
81
+ def ruby_value
82
+ Float(text_value)
83
+ end
84
+
85
+ alias sql ruby_value
86
+ }
87
+ end
88
+
89
+ rule field
90
+ # [a-zA-Z] [a-zA-Z_0-9]*
91
+ ("state" / "host" / "service" / "description" / "metric_f") {
92
+ def sql
93
+ text_value.to_sym
94
+ end
95
+ }
96
+ end
97
+
98
+ rule string
99
+ double_quoted_string
100
+ end
101
+
102
+ # This doesn't work! Taken from the docs... :(
103
+ rule double_quoted_string
104
+ '"' (!'"' . / '\"')* '"' {
105
+ def ruby_value
106
+ text_value[1..-2].gsub('\"', '"')
107
+ end
108
+ alias sql ruby_value
109
+ }
110
+ end
111
+
112
+ rule space
113
+ [\s]+
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,103 @@
1
+ # Largely stolen from Thin
2
+
3
+ class UState::Server::Backends::Base
4
+ TIMEOUT = 5
5
+ MAXIMUM_CONNECTIONS = 1024
6
+ MAX_CONNECTIONS = 512
7
+
8
+ attr_accessor :server
9
+ attr_accessor :timeout
10
+ attr_accessor :maximum_connections
11
+ attr_writer :ssl, :ssl_options
12
+ def ssl?; @ssl; end
13
+
14
+ def initialize(opts = {})
15
+ @connections = []
16
+ @timeout = opts[:timeout] || TIMEOUT
17
+ @maximum_connections = opts[:maximum_connections] || MAXIMUM_CONNECTIONS
18
+ end
19
+
20
+ def start
21
+ @stopping = false
22
+ starter = proc do
23
+ connect
24
+ @running = true
25
+ end
26
+
27
+ # Allow for early run-up of eventmachine
28
+ if EventMachine.reactor_running?
29
+ starter.call
30
+ else
31
+ EventMachine.run &starter
32
+ end
33
+ end
34
+
35
+ # Graceful stop
36
+ def stop
37
+ @running = false
38
+ @stopping = true
39
+
40
+ # Stop accepting connections
41
+ disconnect
42
+ stop! if @connections.empty?
43
+ end
44
+
45
+ # Force stop
46
+ def stop!
47
+ @running = false
48
+ @stopping = false
49
+
50
+ EventMachine.stop if EventMachine.reactor_running?
51
+ @connections.each { |connection| connection.close_connection }
52
+ close
53
+ end
54
+
55
+ # Configure backend
56
+ def config
57
+ EventMachine.epoll
58
+
59
+ @maximum_connections =
60
+ EventMachine.set_descriptor_table_size(@maximum_connections)
61
+ end
62
+
63
+ # Free up resources used by the backend
64
+ def close
65
+ end
66
+
67
+ def running?
68
+ @running
69
+ end
70
+
71
+ # Called by a connection when it's unbound
72
+ def connection_finished(connection)
73
+ @connections.delete connection
74
+
75
+ # Finalize graceful stop if there's no more active connections.
76
+ stop! if @stopping and @connections.empty?
77
+ end
78
+
79
+ # No connections?
80
+ def empty?
81
+ @connections.empty?
82
+ end
83
+
84
+ # No of connections
85
+ def size
86
+ @connections.size
87
+ end
88
+
89
+ protected
90
+
91
+ # Initialize a new connection to a client
92
+ def initialize_connection(connection)
93
+ connection.backend = self
94
+ connection.index = @server.index
95
+ connection.comm_inactivity_timeout = @timeout
96
+
97
+ # if @ssl
98
+ # connection.start_tls(@ssl_options
99
+ # end
100
+
101
+ @connections << connection
102
+ end
103
+ end
@@ -0,0 +1,33 @@
1
+ # Stolen from Thin
2
+
3
+ class UState::Server
4
+ class Backends::TCP < Backends::Base
5
+ require 'socket'
6
+
7
+ attr_accessor :host, :port
8
+
9
+ HOST = '127.0.0.1'
10
+ PORT = 55956
11
+
12
+ def initialize(opts = {})
13
+ @host = opts[:host] || HOST
14
+ @port = opts[:port] || PORT
15
+ super opts
16
+ end
17
+
18
+ # Connect the server
19
+ def connect
20
+ puts "Listening on #{@host}:#{@port}"
21
+ @signature = EventMachine.start_server(@host, @port, Connection, &method(:initialize_connection))
22
+ end
23
+
24
+ # Stops server
25
+ def disconnect
26
+ EventMachine.stop_server @signature
27
+ end
28
+
29
+ def to_s
30
+ "#{@host}:#{@port}"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,4 @@
1
+ module UState::Server::Backends
2
+ require 'ustate/server/backends/base'
3
+ require 'ustate/server/backends/tcp'
4
+ end
@@ -0,0 +1,82 @@
1
+ # Instantiated by EventMachine for each new connection
2
+ # Mostly from Thin.
3
+ class UState::Server::Connection < EventMachine::Connection
4
+ attr_accessor :backend
5
+ attr_accessor :index
6
+
7
+ # Called to prepare the connection for a request
8
+ def post_init
9
+ @state = :length
10
+ @buffer = ""
11
+ end
12
+
13
+ # Called when data is received
14
+ def receive_data(data = '')
15
+ @buffer << data
16
+
17
+ case @state
18
+ when :length
19
+ # Length header
20
+ if @buffer.bytesize >= 4
21
+ @length = @buffer.slice!(0,4).unpack('N').first
22
+ @state = :data
23
+ receive_data unless @buffer.empty?
24
+ end
25
+ when :data
26
+ # Data
27
+ if @buffer.bytesize >= @length
28
+ receive_message @buffer.slice!(0, @length)
29
+ @state = :length
30
+ receive_data unless @buffer.empty?
31
+ end
32
+ end
33
+ end
34
+
35
+ # Called with a message type and data.
36
+ def receive_message(data)
37
+ begin
38
+ message = UState::Message.decode data
39
+ if states = message.states
40
+ # State update
41
+ states.each do |state|
42
+ @index << state
43
+ end
44
+ send UState::Message.new(ok: true)
45
+ elsif q = message.query
46
+ res = @index.query(q)
47
+ send UState::Message.new(ok: true, states: res)
48
+ else
49
+ send UState::Message.new(ok: false, error: "unknown message type")
50
+ end
51
+ rescue Exception => e
52
+ puts e
53
+ puts e.backtrace.join("\n")
54
+ m = UState::Message.new(ok: false, error: e.message)
55
+ send m
56
+ end
57
+ end
58
+
59
+ def send(message)
60
+ send_data message.encode_with_length
61
+ end
62
+
63
+ # Called when the connection is unbound from the socket and can no longer be
64
+ # used to process requests.
65
+ def unbind
66
+ @backend.connection_finished self
67
+ end
68
+
69
+ def remote_address
70
+ socket_address
71
+ end
72
+
73
+ def terminate_request
74
+ close_connection_after_writing rescue nil
75
+ end
76
+
77
+ protected
78
+
79
+ def socket_address
80
+ Socket.unpack_sockaddr_in(get_peername)[1]
81
+ end
82
+ end
@@ -0,0 +1,11 @@
1
+ class UState
2
+ class Server::Graphite
3
+ def initialize(opts)
4
+
5
+ end
6
+
7
+ def on_state
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,134 @@
1
+ module UState
2
+ class Server::Index
3
+ # Combines state messages. Responds to queries for particular states.
4
+ # Forwards messages to various receivers.
5
+ # Inserts are NOT threadsafe.
6
+
7
+ class ParseFailed < Server::Error; end
8
+
9
+ require 'sequel'
10
+
11
+ THREADS = 1000
12
+ BUFFER_SIZE = 10
13
+
14
+ attr_reader :db, :queue
15
+
16
+ def initialize(opts = {})
17
+ @db = Sequel.sqlite
18
+
19
+ @threads = opts[:threads] || THREADS
20
+ @pool = []
21
+
22
+ setup_db
23
+ end
24
+
25
+ def clear
26
+ setup_db
27
+ end
28
+
29
+ def <<(s)
30
+ process s
31
+ end
32
+
33
+ def thread(s)
34
+ Thread.new do
35
+ process s
36
+ @pooltex.synchronize do
37
+ @pool.delete Thread.current
38
+ end
39
+ end
40
+ end
41
+
42
+ def on_state_change(old, new)
43
+ end
44
+
45
+ def on_state(state)
46
+ end
47
+
48
+ def process(s)
49
+ if current = @db[:states][host: s.host, service: s.service]
50
+ # Update
51
+ if current[:time] <= s.time
52
+ if current[:state] != s.state
53
+ on_state_change current, s
54
+ end
55
+
56
+ # Update
57
+ @db[:states].filter(host: s.host, service: s.service).update(
58
+ state: s.state,
59
+ time: s.time,
60
+ description: s.description,
61
+ metric_f: s.metric_f
62
+ )
63
+ end
64
+ else
65
+ # Insert
66
+ @db[:states].insert(
67
+ host: s.host,
68
+ service: s.service,
69
+ state: s.state,
70
+ time: s.time,
71
+ description: s.description,
72
+ metric_f: s.metric_f
73
+ )
74
+ end
75
+
76
+ on_state s
77
+ end
78
+
79
+ # Returns an array of States matching Query.
80
+ def query(q)
81
+ parser = QueryStringParser.new
82
+ if q.string
83
+ unless expression = parser.parse(q.string)
84
+ raise ParseFailed, "error parsing #{q.string.inspect} at line #{parser.failure_line}:#{parser.failure_column}: #{parser.failure_reason}"
85
+ end
86
+ filter = expression.sql
87
+ else
88
+ # No string? All states.
89
+ filter = true
90
+ end
91
+
92
+ ds = @db[:states].filter filter
93
+ ds.all.map do |row|
94
+ row_to_state row
95
+ end
96
+ end
97
+
98
+ # Converts a row to a State
99
+ def row_to_state(row)
100
+ State.new(row)
101
+ end
102
+
103
+ def setup_db
104
+ @db.drop_table :states rescue nil
105
+ @db.create_table :states do
106
+ String :host
107
+ String :service
108
+ String :state
109
+ String :description, :text => true
110
+ Integer :time
111
+ Float :metric_f
112
+ primary_key [:host, :service]
113
+ end
114
+ end
115
+
116
+ def start
117
+ stop!
118
+ @pool = []
119
+ end
120
+
121
+ # Finish up
122
+ def stop
123
+ @pool.each do |thread|
124
+ thread.join
125
+ end
126
+ end
127
+
128
+ def stop!
129
+ @pool.each do |thread|
130
+ thread.kill
131
+ end
132
+ end
133
+ end
134
+ end