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.
- checksums.yaml +7 -0
- data/bin/ffwd +9 -0
- data/bin/fwc +15 -0
- data/lib/em/all.rb +68 -0
- data/lib/ffwd.rb +250 -0
- data/lib/ffwd/channel.rb +62 -0
- data/lib/ffwd/circular_buffer.rb +78 -0
- data/lib/ffwd/connection.rb +40 -0
- data/lib/ffwd/core.rb +173 -0
- data/lib/ffwd/core/emitter.rb +38 -0
- data/lib/ffwd/core/interface.rb +47 -0
- data/lib/ffwd/core/processor.rb +92 -0
- data/lib/ffwd/core/reporter.rb +32 -0
- data/lib/ffwd/debug.rb +76 -0
- data/lib/ffwd/debug/connection.rb +48 -0
- data/lib/ffwd/debug/monitor_session.rb +71 -0
- data/lib/ffwd/debug/tcp.rb +82 -0
- data/lib/ffwd/event.rb +65 -0
- data/lib/ffwd/event_emitter.rb +57 -0
- data/lib/ffwd/handler.rb +43 -0
- data/lib/ffwd/lifecycle.rb +92 -0
- data/lib/ffwd/logging.rb +139 -0
- data/lib/ffwd/metric.rb +55 -0
- data/lib/ffwd/metric_emitter.rb +50 -0
- data/lib/ffwd/plugin.rb +149 -0
- data/lib/ffwd/plugin/json_line.rb +47 -0
- data/lib/ffwd/plugin/json_line/connection.rb +118 -0
- data/lib/ffwd/plugin/log.rb +35 -0
- data/lib/ffwd/plugin/log/writer.rb +42 -0
- data/lib/ffwd/plugin_channel.rb +64 -0
- data/lib/ffwd/plugin_loader.rb +121 -0
- data/lib/ffwd/processor.rb +96 -0
- data/lib/ffwd/processor/count.rb +109 -0
- data/lib/ffwd/processor/histogram.rb +200 -0
- data/lib/ffwd/processor/rate.rb +116 -0
- data/lib/ffwd/producing_client.rb +181 -0
- data/lib/ffwd/protocol.rb +28 -0
- data/lib/ffwd/protocol/tcp.rb +126 -0
- data/lib/ffwd/protocol/tcp/bind.rb +64 -0
- data/lib/ffwd/protocol/tcp/connection.rb +107 -0
- data/lib/ffwd/protocol/tcp/flushing_connect.rb +135 -0
- data/lib/ffwd/protocol/tcp/plain_connect.rb +74 -0
- data/lib/ffwd/protocol/udp.rb +48 -0
- data/lib/ffwd/protocol/udp/bind.rb +64 -0
- data/lib/ffwd/protocol/udp/connect.rb +110 -0
- data/lib/ffwd/reporter.rb +65 -0
- data/lib/ffwd/retrier.rb +72 -0
- data/lib/ffwd/schema.rb +92 -0
- data/lib/ffwd/schema/default.rb +36 -0
- data/lib/ffwd/schema/spotify100.rb +58 -0
- data/lib/ffwd/statistics.rb +29 -0
- data/lib/ffwd/statistics/collector.rb +99 -0
- data/lib/ffwd/statistics/system_statistics.rb +255 -0
- data/lib/ffwd/tunnel.rb +27 -0
- data/lib/ffwd/tunnel/plugin.rb +47 -0
- data/lib/ffwd/tunnel/tcp.rb +60 -0
- data/lib/ffwd/tunnel/udp.rb +61 -0
- data/lib/ffwd/utils.rb +46 -0
- data/lib/ffwd/version.rb +18 -0
- data/lib/fwc.rb +206 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1d0b671545d89979b849e559f5a5e5c6c0a86d8a
|
4
|
+
data.tar.gz: 3470a5c756d427bac159f4ea1c2c5a45099f87ef
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 84bfcbee1c667878c8760b3c1722c8d3b3a822d8d1f23d4c0e25e0bde744d815c4d74f7d3f6baf4a52be058b2d8c18a5506117f51e29e8e7e6c0236dd3b2ab11
|
7
|
+
data.tar.gz: 0e97592bd405068ae86f0d6057cc9d8d0989b310858b7e070a53dfc0c583c71de538edf12086c60658f768191423f2be5b02d38ff4ec98cf5ca078fcdfd20ef1
|
data/bin/ffwd
ADDED
data/bin/fwc
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
if __FILE__ == $0
|
4
|
+
lib = File.expand_path File.join('..', '..', 'lib'), $0
|
5
|
+
$:.insert(0, lib) if File.file? File.join(lib, 'ffwd.rb')
|
6
|
+
end
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'fwc'
|
10
|
+
rescue LoadError
|
11
|
+
require 'rubygems'
|
12
|
+
require 'fwc'
|
13
|
+
end
|
14
|
+
|
15
|
+
exit FWC::main(ARGV || [])
|
data/lib/em/all.rb
ADDED
@@ -0,0 +1,68 @@
|
|
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
|
+
# EventMachine extension to create a deferrable that correctly manages multiple
|
17
|
+
# deferred objects in parallel.
|
18
|
+
|
19
|
+
module EM
|
20
|
+
class All
|
21
|
+
include EM::Deferrable
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@defs = []
|
25
|
+
@errors = []
|
26
|
+
@results = []
|
27
|
+
@all_ok = true
|
28
|
+
@any_ok = false
|
29
|
+
@done = 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def <<(d)
|
33
|
+
@defs << d
|
34
|
+
|
35
|
+
index = @results.size
|
36
|
+
|
37
|
+
@results << nil
|
38
|
+
@errors << nil
|
39
|
+
|
40
|
+
d.callback do |*args|
|
41
|
+
@results[index] = args
|
42
|
+
@any_ok = true
|
43
|
+
check_results
|
44
|
+
end
|
45
|
+
|
46
|
+
d.errback do |*args|
|
47
|
+
@errors[index] = args
|
48
|
+
@all_ok = false
|
49
|
+
check_results
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def check_results
|
56
|
+
@done += 1
|
57
|
+
|
58
|
+
return unless @defs.size == @done
|
59
|
+
|
60
|
+
unless @all_ok
|
61
|
+
fail(@errors)
|
62
|
+
return
|
63
|
+
end
|
64
|
+
|
65
|
+
succeed(@results)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/ffwd.rb
ADDED
@@ -0,0 +1,250 @@
|
|
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 'yaml'
|
17
|
+
require 'optparse'
|
18
|
+
|
19
|
+
require_relative 'ffwd/core'
|
20
|
+
require_relative 'ffwd/logging'
|
21
|
+
require_relative 'ffwd/plugin'
|
22
|
+
require_relative 'ffwd/plugin_loader'
|
23
|
+
require_relative 'ffwd/processor'
|
24
|
+
require_relative 'ffwd/schema'
|
25
|
+
|
26
|
+
module FFWD
|
27
|
+
DEFAULT_PLUGIN_DIRECTORIES = [
|
28
|
+
'./plugins'
|
29
|
+
]
|
30
|
+
|
31
|
+
def self.load_yaml path
|
32
|
+
return YAML.load_file path
|
33
|
+
rescue => e
|
34
|
+
log.error "Failed to load config: #{path} (#{e})"
|
35
|
+
return nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.merge_configurations target, source
|
39
|
+
if target.is_a? Hash
|
40
|
+
raise "source not a Hash: #{source}" unless source.is_a? Hash
|
41
|
+
|
42
|
+
source.each do |key, value|
|
43
|
+
target[key] = merge_configurations target[key], source[key]
|
44
|
+
end
|
45
|
+
|
46
|
+
return target
|
47
|
+
end
|
48
|
+
|
49
|
+
if target.is_a? Array
|
50
|
+
raise "source not an Array: #{source}" unless source.is_a? Array
|
51
|
+
return target + source
|
52
|
+
end
|
53
|
+
|
54
|
+
# override source
|
55
|
+
return source
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.load_config_dir dir, config
|
59
|
+
Dir.entries(dir).sort.each do |entry|
|
60
|
+
entry_path = File.join dir, entry
|
61
|
+
|
62
|
+
next unless File.file? entry_path
|
63
|
+
|
64
|
+
if entry.start_with? "."
|
65
|
+
log.debug "Ignoring: #{entry_path} (hidden file)"
|
66
|
+
next
|
67
|
+
end
|
68
|
+
|
69
|
+
c = load_yaml entry_path
|
70
|
+
|
71
|
+
if c.nil?
|
72
|
+
log.warn "Ignoring: #{entry_path} (invalid yaml)"
|
73
|
+
next
|
74
|
+
end
|
75
|
+
|
76
|
+
yield c
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.opts
|
81
|
+
@@opts ||= {:debug => false, :config => nil, :config_dir => nil,
|
82
|
+
:list_plugins => false, :list_schemas => false,
|
83
|
+
:dump_config => false,
|
84
|
+
:plugin_directories => DEFAULT_PLUGIN_DIRECTORIES}
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.parser
|
88
|
+
@@parser ||= OptionParser.new do |o|
|
89
|
+
o.banner = "Usage: ffwd [options]"
|
90
|
+
|
91
|
+
o.on "-d", "--[no-]debug" do |d|
|
92
|
+
opts[:debug] = d
|
93
|
+
end
|
94
|
+
|
95
|
+
o.on "-c", "--config <path>" do |path|
|
96
|
+
opts[:config_path] = path
|
97
|
+
end
|
98
|
+
|
99
|
+
o.on "-d", "--config-dir <path>" do |path|
|
100
|
+
opts[:config_dir] = path
|
101
|
+
end
|
102
|
+
|
103
|
+
o.on "--list-plugins", "Print available plugins." do
|
104
|
+
opts[:list_plugins] = true
|
105
|
+
end
|
106
|
+
|
107
|
+
o.on "--list-schemas", "Print available schemas." do
|
108
|
+
opts[:list_schemas] = true
|
109
|
+
end
|
110
|
+
|
111
|
+
o.on "--dump-config", "Dump the configuration that has been loaded." do
|
112
|
+
opts[:dump_config] = true
|
113
|
+
end
|
114
|
+
|
115
|
+
o.on "--plugin-directory <dir>", "Load plugins from the specified directory." do |dir|
|
116
|
+
opts[:plugin_directories] << dir
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.parse_options args
|
122
|
+
parser.parse args
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.setup_plugins config
|
126
|
+
plugins = {}
|
127
|
+
|
128
|
+
plugins[:tunnel] = FFWD::Plugin.load_plugins(
|
129
|
+
log, "Tunnel", config[:tunnel], :tunnel)
|
130
|
+
|
131
|
+
plugins[:input] = FFWD::Plugin.load_plugins(
|
132
|
+
log, "Input", config[:input], :input)
|
133
|
+
|
134
|
+
plugins[:output] = FFWD::Plugin.load_plugins(
|
135
|
+
log, "Output", config[:output], :output)
|
136
|
+
|
137
|
+
plugins
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.main args
|
141
|
+
parse_options args
|
142
|
+
|
143
|
+
FFWD.log_config[:level] = opts[:debug] ? Logger::DEBUG : Logger::INFO
|
144
|
+
|
145
|
+
config = {:debug => nil}
|
146
|
+
|
147
|
+
if config_path = opts[:config_path]
|
148
|
+
unless File.file? config_path
|
149
|
+
puts "Configuration path does not exist: #{config_path}"
|
150
|
+
puts ""
|
151
|
+
puts parser.help
|
152
|
+
return 1
|
153
|
+
end
|
154
|
+
|
155
|
+
unless source = load_yaml(config_path)
|
156
|
+
return 0
|
157
|
+
end
|
158
|
+
|
159
|
+
merge_configurations config, source
|
160
|
+
end
|
161
|
+
|
162
|
+
if config_dir = opts[:config_dir]
|
163
|
+
unless File.directory? config_dir
|
164
|
+
puts "Configuration directory does not exist: #{path}"
|
165
|
+
puts ""
|
166
|
+
puts parser.help
|
167
|
+
return 1
|
168
|
+
end
|
169
|
+
|
170
|
+
load_config_dir(config_dir, config) do |c|
|
171
|
+
merge_configurations config, c
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
if config[:logging]
|
176
|
+
config[:logging].each do |key, value|
|
177
|
+
FFWD.log_config[key] = value
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
FFWD.log_reload
|
182
|
+
|
183
|
+
if FFWD.log_config[:file]
|
184
|
+
puts "Logging to file: #{FFWD.log_config[:file]}"
|
185
|
+
end
|
186
|
+
|
187
|
+
blacklist = config[:blacklist] || {}
|
188
|
+
|
189
|
+
directories = ((config[:plugin_directories] || []) +
|
190
|
+
(opts[:plugin_directories] || []))
|
191
|
+
|
192
|
+
PluginLoader.plugin_directories = directories
|
193
|
+
|
194
|
+
PluginLoader.load FFWD::Plugin, blacklist[:plugins] || []
|
195
|
+
PluginLoader.load FFWD::Processor, blacklist[:processors] || []
|
196
|
+
PluginLoader.load FFWD::Schema, blacklist[:schemas] || []
|
197
|
+
|
198
|
+
stop_early = false
|
199
|
+
stop_early ||= opts[:list_plugins]
|
200
|
+
stop_early ||= opts[:list_schemas]
|
201
|
+
stop_early ||= opts[:dump_config]
|
202
|
+
|
203
|
+
plugins = setup_plugins config
|
204
|
+
|
205
|
+
if opts[:list_plugins]
|
206
|
+
puts "Available Plugins:"
|
207
|
+
|
208
|
+
FFWD::Plugin.loaded.each do |name, plugin|
|
209
|
+
puts "Plugin '#{name}' (#{plugin.source})"
|
210
|
+
puts " supports: #{plugin.capabilities.join(' ')}"
|
211
|
+
end
|
212
|
+
|
213
|
+
puts "Activated Plugins:"
|
214
|
+
|
215
|
+
plugins.each do |kind, kind_plugins|
|
216
|
+
puts "#{kind}:"
|
217
|
+
|
218
|
+
if kind_plugins.empty?
|
219
|
+
puts " (no active)"
|
220
|
+
end
|
221
|
+
|
222
|
+
kind_plugins.each do |p|
|
223
|
+
puts " #{p.name}: #{p.config}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
if opts[:list_schemas]
|
229
|
+
puts "Available Schemas:"
|
230
|
+
|
231
|
+
FFWD::Schema.loaded.each do |key, schema|
|
232
|
+
name, content_type = key
|
233
|
+
puts "Schema '#{name}' #{content_type} (#{schema.source})"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
if opts[:dump_config]
|
238
|
+
puts "Dumping Configuration:"
|
239
|
+
puts config
|
240
|
+
end
|
241
|
+
|
242
|
+
if stop_early
|
243
|
+
return 0
|
244
|
+
end
|
245
|
+
|
246
|
+
core = FFWD::Core.new plugins, config
|
247
|
+
core.run
|
248
|
+
return 0
|
249
|
+
end
|
250
|
+
end
|
data/lib/ffwd/channel.rb
ADDED
@@ -0,0 +1,62 @@
|
|
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 'set'
|
17
|
+
|
18
|
+
# Defines containers which are limited in size.
|
19
|
+
module FFWD
|
20
|
+
class Channel
|
21
|
+
class Sub
|
22
|
+
attr_reader :channel, :block
|
23
|
+
|
24
|
+
def initialize channel, block
|
25
|
+
@channel = channel
|
26
|
+
@block = block
|
27
|
+
end
|
28
|
+
|
29
|
+
def unsubscribe
|
30
|
+
@channel.unsubscribe self
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :subs
|
35
|
+
|
36
|
+
def initialize log, name
|
37
|
+
@log = log
|
38
|
+
@name = name
|
39
|
+
@subs = Set.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def <<(item)
|
43
|
+
@subs.each do |sub|
|
44
|
+
begin
|
45
|
+
sub.block.call item
|
46
|
+
rescue => e
|
47
|
+
@log.error "#{@name}: Subscription failed", e
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def subscribe(&block)
|
53
|
+
s = Sub.new(self, block)
|
54
|
+
@subs << s
|
55
|
+
return s
|
56
|
+
end
|
57
|
+
|
58
|
+
def unsubscribe sub
|
59
|
+
@subs.delete sub
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,78 @@
|
|
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
|
+
Node = Struct.new(:value, :next)
|
17
|
+
|
18
|
+
module FFWD
|
19
|
+
class CircularBuffer
|
20
|
+
attr_reader :buffer, :capacity, :size
|
21
|
+
|
22
|
+
def initialize capacity
|
23
|
+
@capacity = capacity
|
24
|
+
@first = nil
|
25
|
+
@last = nil
|
26
|
+
@size = 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear
|
30
|
+
@first = nil
|
31
|
+
@last = nil
|
32
|
+
@size = 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def empty?
|
36
|
+
size == 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def peek
|
40
|
+
return nil if @first.nil?
|
41
|
+
@first.value
|
42
|
+
end
|
43
|
+
|
44
|
+
def shift
|
45
|
+
return nil if @first.nil?
|
46
|
+
value = @first.value
|
47
|
+
@first = @first.next
|
48
|
+
@last = nil if @first.nil?
|
49
|
+
@size -= 1
|
50
|
+
value
|
51
|
+
end
|
52
|
+
|
53
|
+
def << item
|
54
|
+
node = Node.new(item, nil)
|
55
|
+
|
56
|
+
if @last.nil?
|
57
|
+
@first = @last = node
|
58
|
+
else
|
59
|
+
@last = @last.next = node
|
60
|
+
end
|
61
|
+
|
62
|
+
if @size >= @capacity
|
63
|
+
@first = @first.next
|
64
|
+
else
|
65
|
+
@size += 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def each
|
70
|
+
current = @first
|
71
|
+
|
72
|
+
while not current.nil?
|
73
|
+
yield current.value
|
74
|
+
current = current.next
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|