ffwd 0.1.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.
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