ffwd 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/bin/ffwd +9 -0
  3. data/bin/fwc +15 -0
  4. data/lib/em/all.rb +68 -0
  5. data/lib/ffwd.rb +250 -0
  6. data/lib/ffwd/channel.rb +62 -0
  7. data/lib/ffwd/circular_buffer.rb +78 -0
  8. data/lib/ffwd/connection.rb +40 -0
  9. data/lib/ffwd/core.rb +173 -0
  10. data/lib/ffwd/core/emitter.rb +38 -0
  11. data/lib/ffwd/core/interface.rb +47 -0
  12. data/lib/ffwd/core/processor.rb +92 -0
  13. data/lib/ffwd/core/reporter.rb +32 -0
  14. data/lib/ffwd/debug.rb +76 -0
  15. data/lib/ffwd/debug/connection.rb +48 -0
  16. data/lib/ffwd/debug/monitor_session.rb +71 -0
  17. data/lib/ffwd/debug/tcp.rb +82 -0
  18. data/lib/ffwd/event.rb +65 -0
  19. data/lib/ffwd/event_emitter.rb +57 -0
  20. data/lib/ffwd/handler.rb +43 -0
  21. data/lib/ffwd/lifecycle.rb +92 -0
  22. data/lib/ffwd/logging.rb +139 -0
  23. data/lib/ffwd/metric.rb +55 -0
  24. data/lib/ffwd/metric_emitter.rb +50 -0
  25. data/lib/ffwd/plugin.rb +149 -0
  26. data/lib/ffwd/plugin/json_line.rb +47 -0
  27. data/lib/ffwd/plugin/json_line/connection.rb +118 -0
  28. data/lib/ffwd/plugin/log.rb +35 -0
  29. data/lib/ffwd/plugin/log/writer.rb +42 -0
  30. data/lib/ffwd/plugin_channel.rb +64 -0
  31. data/lib/ffwd/plugin_loader.rb +121 -0
  32. data/lib/ffwd/processor.rb +96 -0
  33. data/lib/ffwd/processor/count.rb +109 -0
  34. data/lib/ffwd/processor/histogram.rb +200 -0
  35. data/lib/ffwd/processor/rate.rb +116 -0
  36. data/lib/ffwd/producing_client.rb +181 -0
  37. data/lib/ffwd/protocol.rb +28 -0
  38. data/lib/ffwd/protocol/tcp.rb +126 -0
  39. data/lib/ffwd/protocol/tcp/bind.rb +64 -0
  40. data/lib/ffwd/protocol/tcp/connection.rb +107 -0
  41. data/lib/ffwd/protocol/tcp/flushing_connect.rb +135 -0
  42. data/lib/ffwd/protocol/tcp/plain_connect.rb +74 -0
  43. data/lib/ffwd/protocol/udp.rb +48 -0
  44. data/lib/ffwd/protocol/udp/bind.rb +64 -0
  45. data/lib/ffwd/protocol/udp/connect.rb +110 -0
  46. data/lib/ffwd/reporter.rb +65 -0
  47. data/lib/ffwd/retrier.rb +72 -0
  48. data/lib/ffwd/schema.rb +92 -0
  49. data/lib/ffwd/schema/default.rb +36 -0
  50. data/lib/ffwd/schema/spotify100.rb +58 -0
  51. data/lib/ffwd/statistics.rb +29 -0
  52. data/lib/ffwd/statistics/collector.rb +99 -0
  53. data/lib/ffwd/statistics/system_statistics.rb +255 -0
  54. data/lib/ffwd/tunnel.rb +27 -0
  55. data/lib/ffwd/tunnel/plugin.rb +47 -0
  56. data/lib/ffwd/tunnel/tcp.rb +60 -0
  57. data/lib/ffwd/tunnel/udp.rb +61 -0
  58. data/lib/ffwd/utils.rb +46 -0
  59. data/lib/ffwd/version.rb +18 -0
  60. data/lib/fwc.rb +206 -0
  61. metadata +163 -0
@@ -0,0 +1,28 @@
1
+ # $LICENSE
2
+ # Copyright 2013-2014 Spotify AB. All rights reserved.
3
+ #
4
+ # The contents of this file are licensed under the Apache License, Version 2.0
5
+ # (the "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ require_relative 'protocol/udp'
17
+ require_relative 'protocol/tcp'
18
+
19
+ module FFWD
20
+ def self.parse_protocol(original)
21
+ string = original.downcase
22
+
23
+ return UDP if string == "udp"
24
+ return TCP if string == "tcp"
25
+
26
+ throw "Unknown protocol '#{original}'"
27
+ end
28
+ end
@@ -0,0 +1,126 @@
1
+ # $LICENSE
2
+ # Copyright 2013-2014 Spotify AB. All rights reserved.
3
+ #
4
+ # The contents of this file are licensed under the Apache License, Version 2.0
5
+ # (the "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ require 'eventmachine'
17
+
18
+ require_relative '../tunnel'
19
+
20
+ require_relative 'tcp/bind'
21
+ require_relative 'tcp/plain_connect'
22
+ require_relative 'tcp/flushing_connect'
23
+ require_relative 'tcp/connection'
24
+
25
+ module FFWD::TCP
26
+ def self.family
27
+ :tcp
28
+ end
29
+
30
+ # default amount of bytes that the outbound connection will allow in its
31
+ # application-level buffer.
32
+ DEFAULT_OUTBOUND_LIMIT = 2 ** 20
33
+ # default flush period, if non-zero will cause the connection to be buffered.
34
+ DEFAULT_FLUSH_PERIOD = 10
35
+ # defaults for buffered connections.
36
+ # maximum amount of events to buffer up.
37
+ DEFAULT_EVENT_LIMIT = 1000
38
+ # maximum amount of metrics to buffer up.
39
+ DEFAULT_METRIC_LIMIT = 10000
40
+ # percent of maximum events/metrics which will cause a flush.
41
+ DEFAULT_FLUSH_LIMIT = 0.8
42
+ # Default initial timeout when binding fails.
43
+ DEFAULT_REBIND_TIMEOUT = 10
44
+
45
+ # Establish an outbound tcp connection.
46
+ #
47
+ # opts - Option hash.
48
+ # Expects the following keys.
49
+ # :host - The host to connect to.
50
+ # :port - The port to connect to.
51
+ # :outbound_limit - The amount of bytes that are allowed to be pending in
52
+ # the application level buffer for the connection to be considered
53
+ # 'writable'.
54
+ # :flush_period - The period in which outgoing data is buffered. If this
55
+ # is 0 no buffering will occur.
56
+ # Reads the following keys if the connection is buffered.
57
+ # :event_limit - The maximum amount of events the connection is allowed
58
+ # to buffer up.
59
+ # :metric_limimt - The maximum amount of metrics the connection is
60
+ # allowed to buffer up.
61
+ # :flush_limit - A percentage (0.0 - 1.0) indicating 'the percentage of
62
+ # events/metrics' that is required for a flush to be forced.
63
+ # If this percentage is reached, the connection will attempt to forcibly
64
+ # flush all buffered events and metrics prior to the end of the flushing
65
+ # period.
66
+ # core - The core interface associated with this connection.
67
+ # log - The logger to use for this connection.
68
+ # handler - An implementation of FFWD::Handler containing the connection
69
+ # logic.
70
+ # args - Arguments passed to the handler when a new instance is created.
71
+ def self.connect opts, core, log, handler, *args
72
+ raise "Missing required option :host" if (host = opts[:host]).nil?
73
+ raise "Missing required option :port" if (port = opts[:port]).nil?
74
+
75
+ outbound_limit = opts[:outbound_limit] || DEFAULT_OUTBOUND_LIMIT
76
+ flush_period = opts[:flush_period] || DEFAULT_FLUSH_PERIOD
77
+
78
+ connection = Connection.new log, host, port, handler, args, outbound_limit
79
+
80
+ if flush_period == 0
81
+ PlainConnect.new core, log, connection
82
+ else
83
+ event_limit = opts[:event_limit] || DEFAULT_EVENT_LIMIT
84
+ metric_limit = opts[:metric_limit] || DEFAULT_METRIC_LIMIT
85
+ flush_limit = opts[:flush_limit] || DEFAULT_FLUSH_LIMIT
86
+
87
+ FlushingConnect.new(
88
+ core, log, connection,
89
+ flush_period, event_limit, metric_limit, flush_limit
90
+ )
91
+ end
92
+ end
93
+
94
+ # Bind and listen for a TCP connection.
95
+ #
96
+ # opts - Option hash.
97
+ # :host - The host to bind to.
98
+ # :port - The port to bind to.
99
+ # :rebind_timeout - The initial timeout to use when rebinding the
100
+ # connection.
101
+ # core - The core interface associated with this connection.
102
+ # log - The logger to use for this connection.
103
+ # connection - An implementation of FFWD::Connection containing the
104
+ # connection logic.
105
+ # args - Arguments passed to the connection when a new instance is created.
106
+ def self.bind opts, core, log, connection, *args
107
+ raise "Missing required option :host" if (host = opts[:host]).nil?
108
+ raise "Missing required option :port" if (port = opts[:port]).nil?
109
+ rebind_timeout = opts[:rebind_timeout] || DEFAULT_REBIND_TIMEOUT
110
+ Bind.new core, log, host, port, connection, args, rebind_timeout
111
+ end
112
+
113
+ # Set up a TCP tunnel.
114
+ #
115
+ # opts - Option hash.
116
+ # :port - The port to bind to on the remote side.
117
+ # core - The core interface associated with this connection.
118
+ # log - The logger to use for this connection.
119
+ # connection - An implementation of FFWD::Connection containing the
120
+ # connection logic.
121
+ # args - Arguments passed to the connection when a new instance is created.
122
+ def self.tunnel opts, core, plugin, log, connection, *args
123
+ raise "Missing required option :port" if (port = opts[:port]).nil?
124
+ FFWD.tunnel self.family, port, core, plugin, log, connection, args
125
+ end
126
+ end
@@ -0,0 +1,64 @@
1
+ # $LICENSE
2
+ # Copyright 2013-2014 Spotify AB. All rights reserved.
3
+ #
4
+ # The contents of this file are licensed under the Apache License, Version 2.0
5
+ # (the "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ require 'eventmachine'
17
+
18
+ require_relative '../../reporter'
19
+ require_relative '../../retrier'
20
+
21
+ module FFWD::TCP
22
+ class Bind
23
+ include FFWD::Reporter
24
+
25
+ setup_reporter :keys => [
26
+ :received_events, :received_metrics,
27
+ :failed_events, :failed_metrics
28
+ ]
29
+
30
+ attr_reader :reporter_meta
31
+
32
+ def initialize core, log, host, port, connection, args, rebind_timeout
33
+ @peer = "#{host}:#{port}"
34
+ @reporter_meta = {
35
+ :type => connection.plugin_type,
36
+ :listen => @peer, :family => 'tcp'
37
+ }
38
+
39
+ @sig = nil
40
+
41
+ info = "tcp://#{@peer}"
42
+
43
+ r = FFWD.retry :timeout => rebind_timeout do |a|
44
+ @sig = EM.start_server host, port, connection, self, core, *args
45
+ log.info "Bind on #{info} (attempt #{a})"
46
+ end
47
+
48
+ r.error do |a, t, e|
49
+ log.error "Failed to bind #{info} (attempt #{a}), retry in #{t}s", e
50
+ end
51
+
52
+ r.depend_on core
53
+
54
+ core.stopping do
55
+ log.info "Unbinding #{info}"
56
+
57
+ if @sig
58
+ EM.stop_server @sig
59
+ @sig = nil
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,107 @@
1
+ # $LICENSE
2
+ # Copyright 2013-2014 Spotify AB. All rights reserved.
3
+ #
4
+ # The contents of this file are licensed under the Apache License, Version 2.0
5
+ # (the "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ module FFWD::TCP
17
+ class Connection
18
+ INITIAL_TIMEOUT = 2
19
+
20
+ attr_reader :log, :peer, :reporter_meta
21
+
22
+ def initialize log, host, port, handler, args, outbound_limit
23
+ @log = log
24
+ @host = host
25
+ @port = port
26
+ @handler = handler
27
+ @args = args
28
+ @outbound_limit = outbound_limit
29
+
30
+ @peer = "#{host}:#{port}"
31
+ @closing = false
32
+ @reconnect_timeout = INITIAL_TIMEOUT
33
+ @reporter_meta = {:type => @handler.plugin_type, :peer => peer}
34
+
35
+ @timer = nil
36
+ @c = nil
37
+ @open = false
38
+ end
39
+
40
+ # Start attempting to connect.
41
+ def connect
42
+ log.info "Connecting to tcp://#{@host}:#{@port}"
43
+ @c = EM.connect @host, @port, @handler, self, *@args
44
+ end
45
+
46
+ # Explicitly disconnect and discard any reconnect attempts..
47
+ def disconnect
48
+ log.info "Disconnecting from tcp://#{@host}:#{@port}"
49
+ @closing = true
50
+
51
+ @c.close_connection if @c
52
+ @timer.cancel if @timer
53
+ @c = nil
54
+ @timer = nil
55
+ end
56
+
57
+ def send_event event
58
+ @c.send_event event
59
+ end
60
+
61
+ def send_metric metric
62
+ @c.send_metric metric
63
+ end
64
+
65
+ def send_all events, metrics
66
+ @c.send_all events, metrics
67
+ end
68
+
69
+ def connection_completed
70
+ @open = true
71
+ @log.info "Connected tcp://#{peer}"
72
+ @reconnect_timeout = INITIAL_TIMEOUT
73
+
74
+ unless @timer.nil?
75
+ @timer.cancel
76
+ @timer = nil
77
+ end
78
+ end
79
+
80
+ def unbind
81
+ @open = false
82
+ @c = nil
83
+
84
+ if @closing
85
+ return
86
+ end
87
+
88
+ @log.info "Disconnected from tcp://#{peer}, reconnecting in #{@reconnect_timeout}s"
89
+
90
+ unless @timer.nil?
91
+ @timer.cancel
92
+ @timer = nil
93
+ end
94
+
95
+ @timer = EM::Timer.new(@reconnect_timeout) do
96
+ @reconnect_timeout *= 2
97
+ @timer = nil
98
+ @c = EM.connect @host, @port, @handler, self, *@args
99
+ end
100
+ end
101
+
102
+ # Check if a connection is writable or not.
103
+ def writable?
104
+ not @c.nil? and @open and @c.get_outbound_data_size < @outbound_limit
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,135 @@
1
+ # $LICENSE
2
+ # Copyright 2013-2014 Spotify AB. All rights reserved.
3
+ #
4
+ # The contents of this file are licensed under the Apache License, Version 2.0
5
+ # (the "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ require_relative '../../reporter'
17
+
18
+ module FFWD::TCP
19
+ # A TCP connection implementation that buffers events and metrics in batches
20
+ # over a time window and calls 'send_all' on the connection.
21
+ class FlushingConnect
22
+ include FFWD::Reporter
23
+
24
+ setup_reporter :keys => [
25
+ :dropped_events, :dropped_metrics,
26
+ :sent_events, :sent_metrics,
27
+ :failed_events, :failed_metrics,
28
+ :forced_flush
29
+ ]
30
+
31
+ attr_reader :log
32
+
33
+ def reporter_meta
34
+ @c.reporter_meta
35
+ end
36
+
37
+ def initialize(
38
+ core, log, connection,
39
+ flush_period, event_limit, metric_limit, flush_limit
40
+ )
41
+ @log = log
42
+ @c = connection
43
+
44
+ @flush_period = flush_period
45
+ @event_limit = event_limit
46
+ @event_flush_limit = flush_limit * event_limit
47
+ @metric_limit = metric_limit
48
+ @metric_flush_limit = flush_limit * metric_limit
49
+
50
+ @event_buffer = []
51
+ @metric_buffer = []
52
+ @timer = nil
53
+
54
+ subs = []
55
+
56
+ core.starting do
57
+ log.info "Flushing every #{@flush_period}s"
58
+ @timer = EM::PeriodicTimer.new(@flush_period){flush!}
59
+
60
+ event_consumer = setup_consumer(
61
+ @event_buffer, @event_limit, @event_flush_limit, :dropped_events)
62
+ metric_consumer = setup_consumer(
63
+ @metric_buffer, @metric_limit, @metric_flush_limit, :dropped_metrics)
64
+
65
+ subs << core.output.event_subscribe(&event_consumer)
66
+ subs << core.output.metric_subscribe(&metric_consumer)
67
+
68
+ @c.connect
69
+ end
70
+
71
+ core.stopping do
72
+ subs.each(&:unsubscribe).clear
73
+
74
+ if @timer
75
+ @timer.cancel
76
+ @timer = nil
77
+ end
78
+
79
+ @c.disconnect
80
+ end
81
+ end
82
+
83
+ def flush!
84
+ if @event_buffer.empty? and @metric_buffer.empty?
85
+ return
86
+ end
87
+
88
+ unless @c.writable?
89
+ increment :dropped_events, @event_buffer.size
90
+ increment :dropped_metrics, @metric_buffer.size
91
+ return
92
+ end
93
+
94
+ @c.send_all @event_buffer, @metric_buffer
95
+ increment :sent_events, @event_buffer.size
96
+ increment :sent_metrics, @metric_buffer.size
97
+ rescue => e
98
+ log.error "Failed to flush buffers", e
99
+
100
+ log.error "The following data could not be flushed:"
101
+
102
+ @event_buffer.each_with_index do |event, i|
103
+ log.error "##{i}: #{event.to_h}"
104
+ end
105
+
106
+ @metric_buffer.each_with_index do |metric, i|
107
+ log.error "##{i}: #{metric.to_h}"
108
+ end
109
+
110
+ increment :failed_events, @event_buffer.size
111
+ increment :failed_metrics, @metric_buffer.size
112
+ ensure
113
+ @event_buffer.clear
114
+ @metric_buffer.clear
115
+ end
116
+
117
+ private
118
+
119
+ def setup_consumer buffer, buffer_limit, flush_limit, statistics_key
120
+ proc do |e|
121
+ if buffer.size >= buffer_limit
122
+ increment statistics_key, 1
123
+ next
124
+ end
125
+
126
+ buffer << e
127
+
128
+ if buffer.size >= flush_limit
129
+ increment :forced_flush, 1
130
+ flush!
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end