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,27 @@
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 'tunnel/tcp'
17
+ require_relative 'tunnel/udp'
18
+
19
+ module FFWD
20
+ FAMILIES = {:tcp => Tunnel::TCP, :udp => Tunnel::UDP}
21
+
22
+ def self.tunnel family, port, core, plugin, log, connection, args
23
+ impl = FAMILIES[family]
24
+ raise "Unsupported family: #{family}" if impl.nil?
25
+ return impl.new port, core, plugin, log, connection, args
26
+ end
27
+ 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
+ # describes the protocol that has to be implemented by a tunnel.
17
+
18
+ module FFWD::Tunnel
19
+ class Plugin
20
+ # Object type that should be returned by 'tcp'.
21
+ class Handle
22
+ def close &block
23
+ raise "Not implemented: close"
24
+ end
25
+
26
+ def data &block
27
+ raise "Not implemented: data"
28
+ end
29
+
30
+ def send_data data
31
+ raise "Not implemented: send_data"
32
+ end
33
+ end
34
+
35
+ def tcp port, &block
36
+ raise "Not implemented: tcp"
37
+ end
38
+
39
+ def udp port, &block
40
+ raise "Not implemented: udp"
41
+ end
42
+
43
+ def send_data addr, data
44
+ raise "Not implemented: send_data"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,60 @@
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
+ require_relative '../reporter'
18
+
19
+ module FFWD::Tunnel
20
+ class TCP
21
+ include FFWD::Lifecycle
22
+ include FFWD::Reporter
23
+
24
+ setup_reporter :keys => [
25
+ :received_events, :received_metrics, :failed_events, :failed_metrics]
26
+
27
+ attr_reader :log
28
+
29
+ def initialize port, core, plugin, log, connection, args
30
+ @port = port
31
+ @core = core
32
+ @plugin = plugin
33
+ @log = log
34
+ @connection = connection
35
+ @args = args
36
+
37
+ starting do
38
+ @plugin.tcp @port do |handle|
39
+ log.debug "Open tcp/#{@port}"
40
+
41
+ instance = @connection.new(nil, self, @core, *@args)
42
+ instance.datasink = handle
43
+
44
+ handle.data do |data|
45
+ instance.receive_data data
46
+ end
47
+
48
+ handle.close do
49
+ log.debug "Close tcp/#{@port}"
50
+ instance.unbind
51
+ end
52
+ end
53
+ end
54
+
55
+ stopping do
56
+ log.info "Stopped tunneling tcp/#{@port}"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,61 @@
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
+ require_relative '../reporter'
18
+
19
+ module FFWD::Tunnel
20
+ class UDP
21
+ include FFWD::Lifecycle
22
+ include FFWD::Reporter
23
+
24
+ setup_reporter :keys => [
25
+ :received_events, :received_metrics, :failed_events, :failed_metrics]
26
+
27
+ attr_reader :log
28
+
29
+ def initialize port, core, plugin, log, connection, args
30
+ @port = port
31
+ @core = core
32
+ @plugin = plugin
33
+ @log = log
34
+ @connection = connection
35
+ @args = args
36
+
37
+ @instance = nil
38
+
39
+ starting do
40
+ @instance = @connection.new(nil, self, @core, *@args)
41
+
42
+ @plugin.udp @port do |handle, data|
43
+ @instance.datasink = handle
44
+ @instance.receive_data data
45
+ @instance.datasink = nil
46
+ end
47
+
48
+ @log.info "Tunneling udp/#{@port}"
49
+ end
50
+
51
+ stopping do
52
+ if @instance
53
+ @instance.unbind
54
+ @instance = nil
55
+ end
56
+
57
+ @log.info "Stopped tunnelling udp/#{@port}"
58
+ end
59
+ end
60
+ end
61
+ end
data/lib/ffwd/utils.rb ADDED
@@ -0,0 +1,46 @@
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 'socket'
17
+ require 'set'
18
+
19
+ module FFWD
20
+ # Merge two sets (arrays actually)
21
+ def self.merge_sets(a, b)
22
+ return Set.new(a + b).to_a if a and b
23
+ a || b || []
24
+ end
25
+
26
+ # Merge two hashes.
27
+ def self.merge_hashes(a, b)
28
+ return a.merge(b) if a and b
29
+ b || a || {}
30
+ end
31
+
32
+ def self.is_reporter? var
33
+ var.respond_to? :report!
34
+ end
35
+
36
+ def self.current_host
37
+ Socket.gethostname
38
+ end
39
+
40
+ def self.timing &block
41
+ start = Time.now
42
+ block.call
43
+ stop = Time.now
44
+ ((stop - start) * 1000).round(3)
45
+ end
46
+ end
@@ -0,0 +1,18 @@
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
+ VERSION = "0.1.0"
18
+ end
data/lib/fwc.rb ADDED
@@ -0,0 +1,206 @@
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 'optparse'
17
+ require 'eventmachine'
18
+ require 'json'
19
+ require 'set'
20
+ require 'logger'
21
+
22
+ require 'ffwd/debug'
23
+
24
+ module FWC
25
+ def self.log
26
+ @logger ||= ::Logger.new(STDOUT).tap do |l|
27
+ l.level = Logger::INFO
28
+ l.formatter = proc do |severity, datetime, progname, msg|
29
+ t = datetime.strftime("%H:%M:%S")
30
+ "#{t} #{severity}: #{msg}\n"
31
+ end
32
+ end
33
+ end
34
+
35
+ class TraceConnection < EM::Connection
36
+ include EM::Protocols::LineText2
37
+
38
+ def initialize instances
39
+ @instances = instances
40
+ end
41
+
42
+ def unbind
43
+ FWC.log.error "Connection lost"
44
+ EM.stop
45
+ end
46
+
47
+ def receive_line line
48
+ data = JSON.load line
49
+
50
+ @instances.each do |instance|
51
+ instance.receive data
52
+ end
53
+ end
54
+ end
55
+
56
+ def self.opts
57
+ @@opts ||= {
58
+ :debug => false,
59
+ :host => "localhost",
60
+ :port => FFWD::Debug::DEFAULT_PORT,
61
+ :summary => false,
62
+ :raw => false,
63
+ :raw_threshold => 100,
64
+ :report_interval => 10
65
+ }
66
+ end
67
+
68
+ def self.parser
69
+ @@parser ||= OptionParser.new do |o|
70
+ o.banner = "Usage: fwc [options]"
71
+
72
+ o.on "-d", "--[no-]debug" do |d|
73
+ opts[:debug] = d
74
+ end
75
+
76
+ o.on "-s", "--summary", "Show a periodic summary of everything seen" do
77
+ opts[:summary] = true
78
+ end
79
+
80
+ o.on "-r", "--raw", "Display raw metrics and events, as soon as they are seen" do
81
+ opts[:raw] = true
82
+ end
83
+
84
+ o.on "--raw-threshold <rate>", "Limit of how many messages/s is allowed before disabling output" do |d|
85
+ opts[:raw_threshold] = d.to_i
86
+ end
87
+
88
+ o.on "-i", "--report-interval", "Interval in seconds to generate report" do |d|
89
+ opts[:report_interval] = d.to_i
90
+ end
91
+ end
92
+ end
93
+
94
+ def self.parse_options(args)
95
+ parser.parse args
96
+ end
97
+
98
+ class Summary
99
+ def initialize
100
+ @groups = {}
101
+ end
102
+
103
+ def report!
104
+ return if @groups.empty?
105
+
106
+ FWC.log.info "Summary Report:"
107
+
108
+ @groups.sort.each do |id, group|
109
+ items = group[:items].to_a
110
+
111
+ FWC.log.info " #{group[:id]} (#{group[:type]})"
112
+ items.sort.each do |key, count|
113
+ key = "<nil>" if key.nil?
114
+ FWC.log.info " #{key} #{count}"
115
+ end
116
+ end
117
+
118
+ @groups = {}
119
+ end
120
+
121
+ def receive data
122
+ id = [data["id"], data["type"]]
123
+ group = (@groups[id] ||= {:id => data["id"], :type => data["type"],
124
+ :items => {}})
125
+
126
+ key = data["data"]["key"]
127
+ items = group[:items]
128
+
129
+ if v = items[key]
130
+ items[key] = v + 1
131
+ else
132
+ items[key] = 1
133
+ end
134
+ end
135
+ end
136
+
137
+ class Raw
138
+ def initialize threshold
139
+ @threshold = threshold
140
+ @count = 0
141
+ @rate = 0
142
+ @disabled = false
143
+ @first = Time.now
144
+ end
145
+
146
+ def report!
147
+ return if @count == 0
148
+
149
+ FWC.log.info "Raw Report:"
150
+ FWC.log.info " count: #{@count}"
151
+
152
+ @first = Time.now
153
+ @count = 0
154
+ @disabled = false
155
+ end
156
+
157
+ def receive data
158
+ if not @disabled and @count > 100
159
+ diff = (Time.now - @first)
160
+ rate = (@count / diff)
161
+
162
+ if rate > @threshold
163
+ r = rate.to_i
164
+ desc = "#{r}/s > #{@threshold}/s"
165
+ FWC.log.info "Raw: disabled - rate too high (#{desc})"
166
+ @disabled = true
167
+ end
168
+ end
169
+
170
+ unless @disabled
171
+ FWC.log.info "#{@count}: #{data}"
172
+ end
173
+
174
+ @count += 1
175
+ end
176
+ end
177
+
178
+ def self.main(args)
179
+ parse_options args
180
+
181
+ handlers = []
182
+
183
+ if opts[:summary]
184
+ handlers << Summary.new
185
+ end
186
+
187
+ if opts[:raw]
188
+ handlers << Raw.new(opts[:raw_threshold])
189
+ end
190
+
191
+ if handlers.empty?
192
+ puts "No methods specified"
193
+ return 1
194
+ end
195
+
196
+ EM.run do
197
+ EM.connect(opts[:host], opts[:port], FWC::TraceConnection, handlers)
198
+
199
+ EM::PeriodicTimer.new(opts[:report_interval]) do
200
+ handlers.each(&:report!)
201
+ end
202
+ end
203
+
204
+ return 0
205
+ end
206
+ end