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,40 @@
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
+ module FFWD
19
+ # Connections are used by input plugins in the protocol stack.
20
+ #
21
+ # The sole purpose this exists is to incorporate a datasink functionality
22
+ # in the EM::Connection.
23
+ #
24
+ # The datasink is used by tunnels to 'hook into' outgoing data.
25
+ class Connection < EM::Connection
26
+ def datasink= sink
27
+ @datasink = sink
28
+ end
29
+
30
+ # send_data indirection.
31
+ def send_data data
32
+ if @datasink
33
+ @datasink.send_data data
34
+ return
35
+ end
36
+
37
+ super data
38
+ end
39
+ end
40
+ end
data/lib/ffwd/core.rb ADDED
@@ -0,0 +1,173 @@
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 'json'
17
+ require 'eventmachine'
18
+
19
+ require_relative 'channel'
20
+ require_relative 'debug'
21
+ require_relative 'lifecycle'
22
+ require_relative 'logging'
23
+ require_relative 'plugin_channel'
24
+ require_relative 'processor'
25
+ require_relative 'protocol'
26
+ require_relative 'statistics'
27
+ require_relative 'utils'
28
+
29
+ require_relative 'core/emitter'
30
+ require_relative 'core/interface'
31
+ require_relative 'core/processor'
32
+ require_relative 'core/reporter'
33
+
34
+ module FFWD
35
+ class Core
36
+ include FFWD::Lifecycle
37
+ include FFWD::Logging
38
+
39
+ def initialize plugins, opts={}
40
+ @tunnel_plugins = plugins[:tunnel] || []
41
+ @input_plugins = plugins[:input] || []
42
+ @output_plugins = plugins[:output] || []
43
+
44
+ @statistics_opts = opts[:statistics]
45
+ @debug_opts = opts[:debug]
46
+ @core_opts = opts[:core] || {}
47
+ @processors = FFWD::Processor.load_processors(opts[:processor] || {})
48
+
49
+ @output_channel = FFWD::PluginChannel.build 'output'
50
+ @input_channel = FFWD::PluginChannel.build 'input'
51
+
52
+ @system_channel = Channel.new log, "system_channel"
53
+
54
+ memory_config = (@core_opts[:memory] || {})
55
+ @memory_limit = (memory_config[:limit] || 1000).to_f.round(3)
56
+ @memory_limit95 = @memory_limit * 0.95
57
+
58
+ if @memory_limit < 0
59
+ raise "memory limit must be non-negative number"
60
+ end
61
+
62
+ @emitter = Core::Emitter.build @output_channel, @core_opts
63
+ @processor = Core::Processor.build @input_channel, @emitter, @processors
64
+
65
+ @debug = nil
66
+
67
+ if @debug_opts
68
+ @debug = FFWD::Debug.setup @debug_opts
69
+ @debug.monitor "core.input", @input_channel, FFWD::Debug::Input
70
+ @debug.monitor "core.output", @output_channel, FFWD::Debug::Output
71
+ @debug.depend_on self
72
+ end
73
+
74
+ # Configuration for statistics module.
75
+ @statistics = nil
76
+
77
+ if config = @statistics_opts
78
+ @statistics = FFWD::Statistics.setup @emitter, @system_channel, config
79
+ @statistics.depend_on self
80
+ end
81
+
82
+ @interface = Core::Interface.new(
83
+ @input_channel, @output_channel,
84
+ @tunnel_plugins, @statistics, @debug, @processors, @core_opts
85
+ )
86
+
87
+ @interface.depend_on self
88
+
89
+ @input_instances = @input_plugins.map do |plugin|
90
+ plugin.setup @interface
91
+ end
92
+
93
+ @output_instances = @output_plugins.map do |plugin|
94
+ plugin.setup @interface
95
+ end
96
+
97
+ unless @statistics.nil?
98
+ reporters = [@input_channel, @output_channel, @processor]
99
+ reporters += @input_instances.select{|i| FFWD.is_reporter?(i)}
100
+ reporters += @output_instances.select{|i| FFWD.is_reporter?(i)}
101
+ @statistics.register "core", Core::Reporter.new(reporters)
102
+ end
103
+
104
+ # Make the core-related channels depend on core.
105
+ # They will then be orchestrated with core when it's being
106
+ # started/stopped.
107
+ @input_channel.depend_on self
108
+ @output_channel.depend_on self
109
+ end
110
+
111
+ # Main entry point.
112
+ #
113
+ # Since all components are governed by the lifecycle of core, it should
114
+ # mostly be a matter of calling 'start'.
115
+ def run
116
+ # What to do when we receive a shutdown signal?
117
+ shutdown_handler = proc do
118
+ # Hack to get out of trap context and into EM land.
119
+ EM.add_timer(0) do
120
+ log.info "Shutting down"
121
+ stop
122
+ EM.stop
123
+ end
124
+ end
125
+
126
+ EM.run do
127
+ Signal.trap("INT", &shutdown_handler)
128
+ Signal.trap("TERM", &shutdown_handler)
129
+
130
+ start
131
+ setup_memory_monitor
132
+ end
133
+
134
+ stopping do
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ # Sets up a memory monitor based of :core -> :memory -> :limit.
141
+ # Will warn at least once before shutting down.
142
+ def setup_memory_monitor
143
+ if @memory_limit == 0
144
+ log.warning "WARNING!!! YOU ARE RUNNING FFWD WITHOUT A MEMORY LIMIT, THIS COULD DAMAGE YOUR SYSTEM"
145
+ log.warning "To configure it, set the (:core -> :memory -> :limit) option to a non-zero number!"
146
+ return
147
+ end
148
+
149
+ log.info "Memory limited to #{@memory_limit} MB (:core -> :memory -> :limit)"
150
+
151
+ memory_one_warning = false
152
+
153
+ @system_channel.subscribe do |system|
154
+ memory = system[:memory]
155
+
156
+ mb = (memory[:resident].to_f / 1000000).round(3)
157
+
158
+ if memory_one_warning and mb > @memory_limit
159
+ log.error "Memory limit exceeded (#{mb}/#{@memory_limit} MB): SHUTTING DOWN"
160
+ EM.stop
161
+ next
162
+ end
163
+
164
+ if mb > @memory_limit95
165
+ log.warning "Memory limit almost reached (#{mb}/#{@memory_limit} MB)"
166
+ memory_one_warning = true
167
+ else
168
+ memory_one_warning = false
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,38 @@
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 '../logging'
17
+ require_relative '../utils'
18
+ require_relative '../metric_emitter'
19
+ require_relative '../event_emitter'
20
+
21
+ module FFWD
22
+ class Core; end
23
+
24
+ class Core::Emitter
25
+ attr_reader :event, :metric
26
+
27
+ def self.build output, opts={}
28
+ event = EventEmitter.build output, opts, opts[:event] || {}
29
+ metric = MetricEmitter.build output, opts, opts[:metric] || {}
30
+ new(event, metric)
31
+ end
32
+
33
+ def initialize event, metric
34
+ @event = event
35
+ @metric = metric
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,47 @@
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 '../lifecycle'
17
+
18
+ module FFWD
19
+ class Core; end
20
+
21
+ class Core::Interface
22
+ include FFWD::Lifecycle
23
+
24
+ attr_reader :input, :output
25
+ attr_reader :tunnel_plugins, :statistics, :debug, :processors
26
+ attr_reader :tags, :attributes
27
+
28
+ def initialize(input, output, tunnel_plugins, statistics, debug,
29
+ processors, opts)
30
+ @input = input
31
+ @output = output
32
+ @tunnel_plugins = tunnel_plugins
33
+ @statistics = statistics
34
+ @debug = debug
35
+ @processors = processors
36
+ @opts = opts
37
+ @tags = opts[:tags] || []
38
+ @attributes = opts[:attributes] || {}
39
+ end
40
+
41
+ def reconnect input
42
+ self.class.new(
43
+ input, @output, @tunnel_plugins, @statistics, @debug, @processors,
44
+ @opts)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,92 @@
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 '../event_emitter'
17
+ require_relative '../lifecycle'
18
+
19
+ module FFWD
20
+ class Core; end
21
+
22
+ # Component responsible for receiving and internally route metrics and
23
+ # events.
24
+ #
25
+ # The term 'processor' is used because depending on the set of provided
26
+ # processors it might be determined that the received metric should be
27
+ # provided to one of them instead.
28
+ #
29
+ # If no processor matches, it is just passed straight through.
30
+ class Core::Processor
31
+ def self.build input, emitter, processors
32
+ processors = Hash[processors.map{|p| [p.name, p.setup(emitter)]}]
33
+ reporters = processors.select{|k, p| FFWD.is_reporter?(p)}.map{|k, p| p}
34
+ new(input, emitter, processors, reporters)
35
+ end
36
+
37
+ def initialize input, emitter, processors, reporters
38
+ @emitter = emitter
39
+ @processors = processors
40
+ @reporters = reporters
41
+
42
+ subs = []
43
+
44
+ @processors.each do |name, p|
45
+ p.depend_on input
46
+ end
47
+
48
+ input.starting do
49
+ subs << input.metric_subscribe do |m|
50
+ process_metric m
51
+ end
52
+
53
+ subs << input.event_subscribe do |e|
54
+ process_event e
55
+ end
56
+ end
57
+
58
+ input.stopping do
59
+ subs.each(&:unsubscribe).clear
60
+ end
61
+ end
62
+
63
+ def report!
64
+ @reporters.each do |reporter|
65
+ reporter.report! do |d|
66
+ yield d
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def process_metric m
74
+ m[:time] ||= Time.now
75
+
76
+ unless p = m[:proc]
77
+ return @emitter.metric.emit m
78
+ end
79
+
80
+ unless p = @processors[p]
81
+ return @emitter.metric.emit m
82
+ end
83
+
84
+ p.process m
85
+ end
86
+
87
+ def process_event e
88
+ e[:time] ||= Time.now
89
+ @emitter.event.emit e
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,32 @@
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
17
+ class Core; end
18
+
19
+ class Core::Reporter
20
+ def initialize reporters
21
+ @reporters = reporters
22
+ end
23
+
24
+ def report!
25
+ @reporters.each do |reporter|
26
+ reporter.report! do |d|
27
+ yield d
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
data/lib/ffwd/debug.rb ADDED
@@ -0,0 +1,76 @@
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 'json'
17
+ require 'eventmachine'
18
+
19
+ require_relative 'logging'
20
+ require_relative 'event'
21
+ require_relative 'metric'
22
+ require_relative 'retrier'
23
+ require_relative 'lifecycle'
24
+
25
+ require_relative 'debug/tcp'
26
+
27
+ module FFWD::Debug
28
+ module Input
29
+ def self.serialize_event event
30
+ event = Hash[event]
31
+
32
+ if tags = event[:tags]
33
+ event[:tags] = tags.to_a
34
+ end
35
+
36
+ event
37
+ end
38
+
39
+ def self.serialize_metric metric
40
+ metric = Hash[metric]
41
+
42
+ if tags = metric[:tags]
43
+ metric[:tags] = tags.to_a
44
+ end
45
+
46
+ metric
47
+ end
48
+ end
49
+
50
+ module Output
51
+ def self.serialize_event event
52
+ event.to_h
53
+ end
54
+
55
+ def self.serialize_metric metric
56
+ metric.to_h
57
+ end
58
+ end
59
+
60
+ DEFAULT_REBIND_TIMEOUT = 10
61
+ DEFAULT_HOST = "localhost"
62
+ DEFAULT_PORT = 19001
63
+
64
+ def self.setup opts={}
65
+ host = opts[:host] || DEFAULT_HOST
66
+ port = opts[:port] || DEFAULT_PORT
67
+ rebind_timeout = opts[:rebind_timeout] || DEFAULT_REBIND_TIMEOUT
68
+ proto = FFWD.parse_protocol(opts[:protocol] || "tcp")
69
+
70
+ if proto == FFWD::TCP
71
+ return TCP.new host, port, rebind_timeout
72
+ end
73
+
74
+ throw Exception.new("Unsupported protocol '#{proto}'")
75
+ end
76
+ end