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,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