thrift_server 0.1.1 → 1.0.0
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.
- 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
|