ustate-client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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