thrift_server 0.1.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +2 -0
- data/Makefile +27 -14
- data/README.md +142 -43
- data/circle.yml +14 -0
- data/echo_client.rb +22 -3
- data/echo_server.rb +23 -16
- data/echo_service.thrift +11 -0
- data/lib/thrift_server.rb +112 -36
- data/lib/thrift_server/instrumentation_middleware.rb +33 -0
- data/lib/thrift_server/log_subscriber.rb +60 -0
- data/lib/thrift_server/publisher.rb +25 -0
- data/lib/thrift_server/rpc_metrics_subscriber.rb +25 -0
- data/lib/thrift_server/server_metrics_subscriber.rb +32 -0
- data/lib/thrift_server/thread_pool_server.rb +115 -0
- data/lib/thrift_server/threaded_server.rb +87 -0
- data/lib/thrift_server/validation_middleware.rb +14 -0
- data/lib/thrift_server/version.rb +2 -2
- data/script/circleci/cache-image +10 -0
- data/test/log_subscriber_test.rb +86 -0
- data/test/processor_test.rb +189 -0
- data/test/rpc_metrics_subscriber_test.rb +44 -0
- data/test/server_metrics_subscriber_test.rb +51 -0
- data/test/support/log_yielder.rb +32 -0
- data/test/support/server_tests.rb +87 -0
- data/test/test_helper.rb +32 -23
- data/test/thread_pool_server_test.rb +101 -0
- data/test/threaded_server_test.rb +56 -0
- data/test/validation_middleware_test.rb +71 -0
- data/thrift_server.gemspec +2 -2
- metadata +34 -22
- data/lib/thrift_server/error_tracking_middleware.rb +0 -12
- data/lib/thrift_server/honeybadger_error_tracker.rb +0 -11
- data/lib/thrift_server/logging_middleware.rb +0 -13
- data/lib/thrift_server/metrics_middleware.rb +0 -16
- data/script/buildbox/ci +0 -5
- data/script/buildbox/step_failed +0 -5
- data/test/acceptance_test.rb +0 -225
- data/test/error_tracking_middleware_test.rb +0 -25
- data/test/honeybadger_error_tracking_test.rb +0 -12
- data/test/logging_middleware_test.rb +0 -42
- data/test/metrics_middleware_test.rb +0 -70
@@ -0,0 +1,33 @@
|
|
1
|
+
module ThriftServer
|
2
|
+
class InstrumentationMiddleware
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators :publisher, :publish
|
6
|
+
|
7
|
+
include Concord.new(:app, :publisher)
|
8
|
+
|
9
|
+
def call(rpc)
|
10
|
+
start_time = Time.now
|
11
|
+
|
12
|
+
publish :rpc_incoming, rpc
|
13
|
+
|
14
|
+
app.call(rpc).tap do |response|
|
15
|
+
latency = (Time.now - start_time) * 1000
|
16
|
+
|
17
|
+
publish :rpc_ok, rpc, response, {
|
18
|
+
latency: latency
|
19
|
+
}
|
20
|
+
end
|
21
|
+
rescue => ex
|
22
|
+
latency = (Time.now - start_time) * 1000
|
23
|
+
|
24
|
+
if rpc.protocol_exception? ex
|
25
|
+
publish :rpc_exception, rpc, ex, latency: latency
|
26
|
+
else
|
27
|
+
publish :rpc_error, rpc, ex, latency: latency
|
28
|
+
end
|
29
|
+
|
30
|
+
raise ex
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ThriftServer
|
2
|
+
class LogSubscriber
|
3
|
+
include Concord.new(:logger)
|
4
|
+
|
5
|
+
def thread_pool_server_pool_change(meta)
|
6
|
+
logger.debug :server do
|
7
|
+
"Thread pool change: %+d" % [ meta.fetch(:delta) ]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def server_connection_opened(addr)
|
12
|
+
logger.debug :server do
|
13
|
+
"%s:%d connected" % [
|
14
|
+
addr.ip_address,
|
15
|
+
addr.ip_port
|
16
|
+
]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def server_connection_closed(addr)
|
21
|
+
logger.debug :server do
|
22
|
+
"%s:%d disconnected" % [
|
23
|
+
addr.ip_address,
|
24
|
+
addr.ip_port
|
25
|
+
]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def rpc_ok(rpc, response, meta)
|
30
|
+
logger.info :processor do
|
31
|
+
"%s => OK (%.2fms)" % [
|
32
|
+
rpc.name,
|
33
|
+
meta.fetch(:latency)
|
34
|
+
]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def rpc_error(rpc, ex, meta)
|
39
|
+
logger.info :processor do
|
40
|
+
"%s => Error! %s (%.2fms)" % [
|
41
|
+
rpc.name,
|
42
|
+
ex.class.name,
|
43
|
+
meta.fetch(:latency)
|
44
|
+
]
|
45
|
+
end
|
46
|
+
|
47
|
+
logger.error ex
|
48
|
+
end
|
49
|
+
|
50
|
+
def rpc_exception(rpc, ex, meta)
|
51
|
+
logger.info :processor do
|
52
|
+
"%s => %s (%.2fms)" % [
|
53
|
+
rpc.name,
|
54
|
+
rpc.exception_name(ex),
|
55
|
+
meta.fetch(:latency)
|
56
|
+
]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ThriftServer
|
2
|
+
class Publisher
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :listeners, :each
|
8
|
+
|
9
|
+
attr_reader :listeners
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@listeners = [ ]
|
13
|
+
end
|
14
|
+
|
15
|
+
def subscribe(object)
|
16
|
+
listeners << object
|
17
|
+
end
|
18
|
+
|
19
|
+
def publish(event, *args)
|
20
|
+
listeners.each do |listener|
|
21
|
+
listener.send(event, *args) if listener.respond_to? event
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ThriftServer
|
2
|
+
class RpcMetricsSubscriber
|
3
|
+
include Concord.new(:statsd)
|
4
|
+
|
5
|
+
def rpc_incoming(rpc)
|
6
|
+
statsd.increment "rpc.#{rpc}.incoming"
|
7
|
+
end
|
8
|
+
|
9
|
+
def rpc_ok(rpc, response, meta)
|
10
|
+
statsd.increment "rpc.#{rpc}.success"
|
11
|
+
statsd.timing "rpc.#{rpc}.latency", meta.fetch(:latency)
|
12
|
+
end
|
13
|
+
|
14
|
+
def rpc_exception(rpc, ex, meta)
|
15
|
+
statsd.increment "rpc.#{rpc}.exception"
|
16
|
+
statsd.increment "rpc.#{rpc}.exception.#{rpc.exception_name(ex)}"
|
17
|
+
statsd.timing "rpc.#{rpc}.latency", meta.fetch(:latency)
|
18
|
+
end
|
19
|
+
|
20
|
+
def rpc_error(rpc, ex, meta)
|
21
|
+
statsd.increment "rpc.#{rpc}.error"
|
22
|
+
statsd.timing "rpc.#{rpc}.latency", meta.fetch(:latency)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ThriftServer
|
2
|
+
class ServerMetricsSubscriber
|
3
|
+
include Concord.new(:statsd)
|
4
|
+
|
5
|
+
def server_connection_opened(*)
|
6
|
+
statsd.gauge 'server.connection.active', '+1'
|
7
|
+
end
|
8
|
+
|
9
|
+
def server_connection_closed(*)
|
10
|
+
statsd.gauge 'server.connection.active', '-1'
|
11
|
+
end
|
12
|
+
|
13
|
+
def rpc_incoming(rpc)
|
14
|
+
statsd.increment 'rpc.incoming'
|
15
|
+
end
|
16
|
+
|
17
|
+
def rpc_ok(rpc, response, meta)
|
18
|
+
statsd.increment 'rpc.success'
|
19
|
+
statsd.timing 'rpc.latency', meta.fetch(:latency)
|
20
|
+
end
|
21
|
+
|
22
|
+
def rpc_exception(rpc, ex, meta)
|
23
|
+
statsd.increment 'rpc.exception'
|
24
|
+
statsd.timing 'rpc.latency', meta.fetch(:latency)
|
25
|
+
end
|
26
|
+
|
27
|
+
def rpc_error(rpc, ex, meta)
|
28
|
+
statsd.increment 'rpc.error'
|
29
|
+
statsd.timing 'rpc.latency', meta.fetch(:latency)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module ThriftServer
|
2
|
+
class ThreadPoolServer < Thrift::ThreadPoolServer
|
3
|
+
class LogSubscriber
|
4
|
+
include Concord.new(:logger)
|
5
|
+
|
6
|
+
def server_start(server)
|
7
|
+
logger.info :server do
|
8
|
+
"Started on port %d" % [ server.port ]
|
9
|
+
end
|
10
|
+
|
11
|
+
logger.info :server do
|
12
|
+
"-> Threads: %d" % [ server.threads ]
|
13
|
+
end
|
14
|
+
|
15
|
+
logger.info :server do
|
16
|
+
"-> Transport: %s" % [ server.transport ]
|
17
|
+
end
|
18
|
+
|
19
|
+
logger.info :server do
|
20
|
+
"-> Protocol: %s" % [ server.protocol ]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class MetricsSubscriber
|
26
|
+
include Concord.new(:statsd)
|
27
|
+
|
28
|
+
def thread_pool_server_pool_change(meta)
|
29
|
+
statsd.gauge('server.pool.size', '%+d' % [ meta.fetch(:delta) ])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
extend Forwardable
|
34
|
+
|
35
|
+
def_delegators :@processor, :use
|
36
|
+
def_delegators :@processor, :publisher, :publish, :subscribe
|
37
|
+
|
38
|
+
attr_accessor :port
|
39
|
+
|
40
|
+
def log(logger)
|
41
|
+
subscribe LogSubscriber.new(logger)
|
42
|
+
subscribe ThriftServer::LogSubscriber.new(logger)
|
43
|
+
end
|
44
|
+
|
45
|
+
def metrics(statsd)
|
46
|
+
subscribe MetricsSubscriber.new(statsd)
|
47
|
+
subscribe ServerMetricsSubscriber.new(statsd)
|
48
|
+
subscribe RpcMetricsSubscriber.new(statsd)
|
49
|
+
end
|
50
|
+
|
51
|
+
def threads
|
52
|
+
@thread_q.max
|
53
|
+
end
|
54
|
+
|
55
|
+
def protocol
|
56
|
+
@protocol_factory
|
57
|
+
end
|
58
|
+
|
59
|
+
def transport
|
60
|
+
@transport_factory
|
61
|
+
end
|
62
|
+
|
63
|
+
def server_transport
|
64
|
+
@server_transport
|
65
|
+
end
|
66
|
+
|
67
|
+
def start(dry_run: false)
|
68
|
+
publish :server_start, self
|
69
|
+
|
70
|
+
serve unless dry_run
|
71
|
+
end
|
72
|
+
|
73
|
+
# NOTE: this a direct copy of the upstream code with instrumentation added.
|
74
|
+
def serve
|
75
|
+
@server_transport.listen
|
76
|
+
|
77
|
+
begin
|
78
|
+
loop do
|
79
|
+
@thread_q.push(:token)
|
80
|
+
publish :thread_pool_server_pool_change, delta: 1
|
81
|
+
|
82
|
+
Thread.new do
|
83
|
+
begin
|
84
|
+
loop do
|
85
|
+
client = @server_transport.accept
|
86
|
+
remote_address = client.handle.remote_address
|
87
|
+
|
88
|
+
publish :server_connection_opened, remote_address
|
89
|
+
|
90
|
+
trans = @transport_factory.get_transport(client)
|
91
|
+
prot = @protocol_factory.get_protocol(trans)
|
92
|
+
begin
|
93
|
+
loop do
|
94
|
+
@processor.process(prot, prot)
|
95
|
+
end
|
96
|
+
rescue Thrift::TransportException, Thrift::ProtocolException => e
|
97
|
+
publish :server_connection_closed, remote_address
|
98
|
+
ensure
|
99
|
+
trans.close
|
100
|
+
end
|
101
|
+
end
|
102
|
+
rescue => e
|
103
|
+
@exception_q.push(e)
|
104
|
+
ensure
|
105
|
+
publish :thread_pool_server_pool_change, delta: -1
|
106
|
+
@thread_q.pop # thread died!
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
ensure
|
111
|
+
@server_transport.close
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module ThriftServer
|
2
|
+
class ThreadedServer < Thrift::ThreadedServer
|
3
|
+
class LogSubscriber
|
4
|
+
include Concord.new(:logger)
|
5
|
+
|
6
|
+
def server_start(server)
|
7
|
+
logger.info :server do
|
8
|
+
"Started on port %d" % [ server.port ]
|
9
|
+
end
|
10
|
+
|
11
|
+
logger.info :server do
|
12
|
+
"-> Transport: %s" % [ server.transport ]
|
13
|
+
end
|
14
|
+
|
15
|
+
logger.info :server do
|
16
|
+
"-> Protocol: %s" % [ server.protocol ]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
extend Forwardable
|
22
|
+
|
23
|
+
def_delegators :@processor, :use
|
24
|
+
def_delegators :@processor, :publisher, :publish, :subscribe
|
25
|
+
|
26
|
+
attr_accessor :port
|
27
|
+
|
28
|
+
def log(logger)
|
29
|
+
subscribe LogSubscriber.new(logger)
|
30
|
+
subscribe ThriftServer::LogSubscriber.new(logger)
|
31
|
+
end
|
32
|
+
|
33
|
+
def metrics(statsd)
|
34
|
+
subscribe ServerMetricsSubscriber.new(statsd)
|
35
|
+
subscribe RpcMetricsSubscriber.new(statsd)
|
36
|
+
end
|
37
|
+
|
38
|
+
def protocol
|
39
|
+
@protocol_factory
|
40
|
+
end
|
41
|
+
|
42
|
+
def transport
|
43
|
+
@transport_factory
|
44
|
+
end
|
45
|
+
|
46
|
+
def server_transport
|
47
|
+
@server_transport
|
48
|
+
end
|
49
|
+
|
50
|
+
def start(dry_run: false)
|
51
|
+
publish :server_start, self
|
52
|
+
|
53
|
+
serve unless dry_run
|
54
|
+
end
|
55
|
+
|
56
|
+
# NOTE: this is a copy of the upstream code with instrumentation added.
|
57
|
+
def serve
|
58
|
+
begin
|
59
|
+
@server_transport.listen
|
60
|
+
loop do
|
61
|
+
client = @server_transport.accept
|
62
|
+
|
63
|
+
remote_address = client.handle.remote_address
|
64
|
+
publish :server_connection_opened, remote_address
|
65
|
+
|
66
|
+
trans = @transport_factory.get_transport(client)
|
67
|
+
prot = @protocol_factory.get_protocol(trans)
|
68
|
+
|
69
|
+
Thread.new(prot, trans) do |p, t|
|
70
|
+
begin
|
71
|
+
loop do
|
72
|
+
@processor.process(p, p)
|
73
|
+
end
|
74
|
+
rescue Thrift::TransportException, Thrift::ProtocolException
|
75
|
+
ensure
|
76
|
+
publish :server_connection_closed, remote_address
|
77
|
+
|
78
|
+
t.close
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
ensure
|
83
|
+
@server_transport.close
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ThriftServer
|
2
|
+
class ValidationMiddleware
|
3
|
+
include Concord.new(:app)
|
4
|
+
|
5
|
+
def call(rpc)
|
6
|
+
app.call(rpc).tap do |response|
|
7
|
+
case response
|
8
|
+
when Thrift::Struct, Thrift::Struct_Union
|
9
|
+
Thrift::Validator.new.validate response
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "0.
|
1
|
+
module ThriftServer
|
2
|
+
VERSION = "1.0.0"
|
3
3
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class LogSubscriberTest < MiniTest::Unit::TestCase
|
4
|
+
attr_reader :logger, :subscriber, :rpc
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@logger = mock
|
8
|
+
@subscriber = ThriftServer::LogSubscriber.new LogYielder.new(logger)
|
9
|
+
|
10
|
+
@rpc = ThriftServer::RPC.new :foo, :bar
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_thread_pool_server_pool_change_with_positive_delta
|
14
|
+
logger.expects(:debug).with do |line|
|
15
|
+
line =~ /\+1/
|
16
|
+
end
|
17
|
+
|
18
|
+
subscriber.thread_pool_server_pool_change delta: 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_thread_pool_server_pool_change_with_negative_delta
|
22
|
+
logger.expects(:debug).with do |line|
|
23
|
+
line =~ /-1/
|
24
|
+
end
|
25
|
+
|
26
|
+
subscriber.thread_pool_server_pool_change delta: -1
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_server_connection_opened
|
30
|
+
addr = stub ip_address: 'stub_ip', ip_port: 823
|
31
|
+
|
32
|
+
logger.expects(:debug).with do |line|
|
33
|
+
line =~ /stub_ip/ && line =~ /823/
|
34
|
+
end
|
35
|
+
|
36
|
+
subscriber.server_connection_opened addr
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_server_connection_closed
|
40
|
+
addr = stub ip_address: 'stub_ip', ip_port: 823
|
41
|
+
|
42
|
+
logger.expects(:debug).with do |line|
|
43
|
+
line =~ /stub_ip/ && line =~ /823/
|
44
|
+
end
|
45
|
+
|
46
|
+
subscriber.server_connection_closed addr
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_rpc_ok_logs_result_to_info
|
50
|
+
logger.expects(:info).with do |line|
|
51
|
+
assert_match /foo/, line, 'RPC name not printed'
|
52
|
+
assert_match /OK/, line
|
53
|
+
assert_match /5.*ms/, line, 'Timing not printed'
|
54
|
+
end
|
55
|
+
|
56
|
+
subscriber.rpc_ok rpc, :response, latency: 5
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_rpc_error_prints_result_and_trace
|
60
|
+
error = StandardError.new
|
61
|
+
|
62
|
+
logger.expects(:info).with do |line|
|
63
|
+
assert_match /foo/, line, 'RPC name not printed'
|
64
|
+
refute_match /OK/, line
|
65
|
+
assert_match /StandardError/, line
|
66
|
+
assert_match /5.*ms/, line, 'Timing not printed'
|
67
|
+
end
|
68
|
+
|
69
|
+
logger.expects(:error).with(error)
|
70
|
+
|
71
|
+
subscriber.rpc_error rpc, error, latency: 5
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_rpc_exception_prints_protocol_name
|
75
|
+
rpc.exceptions = { exName: TestException }
|
76
|
+
|
77
|
+
logger.expects(:info).with do |line|
|
78
|
+
assert_match /foo/, line, 'RPC name not printed'
|
79
|
+
refute_match /OK/, line
|
80
|
+
assert_match /exName/, line
|
81
|
+
assert_match /5.*ms/, line, 'Timing not printed'
|
82
|
+
end
|
83
|
+
|
84
|
+
subscriber.rpc_exception rpc, TestException.new(:message), latency: 5
|
85
|
+
end
|
86
|
+
end
|