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,36 @@
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 '../schema'
17
+
18
+ require 'json'
19
+
20
+ module FFWD::Schema
21
+ module Default
22
+ include FFWD::Schema
23
+
24
+ module ApplicationJSON
25
+ def self.dump_metric m
26
+ JSON.dump m.to_h
27
+ end
28
+
29
+ def self.dump_event e
30
+ JSON.dump e.to_h
31
+ end
32
+ end
33
+
34
+ register_schema 'default', 'application/json', ApplicationJSON
35
+ end
36
+ end
@@ -0,0 +1,58 @@
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 '../schema'
17
+
18
+ require 'json'
19
+
20
+ module FFWD::Schema
21
+ # Spotify's metric schema.
22
+ module Spotify100
23
+ include FFWD::Schema
24
+
25
+ VERSION = "1.0.0"
26
+
27
+ module ApplicationJSON
28
+ def self.dump_metric m
29
+ d = {}
30
+ d[:version] = VERSION
31
+ d[:time] = (m.time.to_f * 1000).to_i if m.time
32
+ d[:key] = m.key if m.key
33
+ d[:value] = m.value if m.value
34
+ d[:host] = m.host if m.host
35
+ d[:tags] = m.tags.to_a if m.tags
36
+ d[:attributes] = m.attributes if m.attributes
37
+ JSON.dump d
38
+ end
39
+
40
+ def self.dump_event e
41
+ d = {}
42
+ d[:version] = VERSION
43
+ d[:time] = (e.time.to_f * 1000).to_i if e.time
44
+ d[:key] = e.key if e.key
45
+ d[:value] = e.value if e.value
46
+ d[:host] = e.host if e.host
47
+ d[:state] = e.state if e.state
48
+ d[:description] = e.description if e.description
49
+ d[:ttl] = e.ttl if e.ttl
50
+ d[:tags] = e.tags.to_a if e.tags
51
+ d[:attributes] = e.attributes if e.attributes
52
+ JSON.dump d
53
+ end
54
+ end
55
+
56
+ register_schema 'spotify 1.0.0', 'application/json', ApplicationJSON
57
+ end
58
+ end
@@ -0,0 +1,29 @@
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 'logging'
18
+
19
+ require_relative 'statistics/collector'
20
+
21
+ module FFWD
22
+ module Statistics
23
+ include FFWD::Logging
24
+
25
+ def self.setup emitter, channel, opts={}
26
+ Collector.new log, emitter, channel, opts
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,99 @@
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
+ require_relative 'system_statistics'
19
+
20
+ module FFWD::Statistics
21
+ class Collector
22
+ include FFWD::Lifecycle
23
+
24
+ DEFAULT_PERIOD = 10
25
+
26
+ # Initialize the statistics collector.
27
+ #
28
+ # emitter - The emitter used to dispatch metrics for all reporters and
29
+ # statistics collectors.
30
+ # channel - A side-channel used by the SystemStatistics component
31
+ # to report information about the system. Messages sent on this channel
32
+ # help Core decide if it should seppuku.
33
+ def initialize log, emitter, channel, opts={}
34
+ @emitter = emitter
35
+ @period = opts[:period] || DEFAULT_PERIOD
36
+ @tags = opts[:tags] || []
37
+ @attributes = opts[:attributes] || {}
38
+ @reporters = {}
39
+ @channel = channel
40
+ @timer = nil
41
+
42
+ system = SystemStatistics.new(opts[:system] || {})
43
+
44
+ if system.check
45
+ @system = system
46
+ else
47
+ @system = nil
48
+ end
49
+
50
+ starting do
51
+ log.info "Started statistics collection"
52
+
53
+ @last = Time.now
54
+
55
+ @timer = EM::PeriodicTimer.new @period do
56
+ now = Time.now
57
+ generate! @last, now
58
+ @last = now
59
+ end
60
+ end
61
+
62
+ stopping do
63
+ log.info "Stopped statistics collection"
64
+
65
+ if @timer
66
+ @timer.cancel
67
+ @timer = nil
68
+ end
69
+ end
70
+ end
71
+
72
+ def generate! last, now
73
+ if @system
74
+ @system.collect @channel do |key, value|
75
+ @emitter.metric.emit(
76
+ :key => key, :value => value,
77
+ :tags => @tags, :attributes => @attributes)
78
+ end
79
+ end
80
+
81
+ @reporters.each do |id, reporter|
82
+ reporter.report! do |d|
83
+ attributes = FFWD.merge_hashes @attributes, d[:meta]
84
+ @emitter.metric.emit(
85
+ :key => d[:key], :value => d[:value],
86
+ :tags => @tags, :attributes => attributes)
87
+ end
88
+ end
89
+ end
90
+
91
+ def register id, reporter
92
+ @reporters[id] = reporter
93
+ end
94
+
95
+ def unregister id
96
+ @reporters.delete id
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,255 @@
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
+
18
+ module FFWD::Statistics
19
+ # Example SMAP
20
+ #(mapping header)
21
+ #Size: 4 kB
22
+ #Rss: 0 kB
23
+ #Pss: 0 kB
24
+ #Shared_Clean: 0 kB
25
+ #Shared_Dirty: 0 kB
26
+ #Private_Clean: 0 kB
27
+ #Private_Dirty: 0 kB
28
+ #Referenced: 0 kB
29
+ #Anonymous: 0 kB
30
+ #AnonHugePages: 0 kB
31
+ #Swap: 0 kB
32
+ #KernelPageSize: 4 kB
33
+ #MMUPageSize: 4 kB
34
+ #Locked: 0 kB
35
+ #VmFlags: rd ex
36
+
37
+ class SMAP
38
+ attr_reader :size
39
+ attr_reader :rss
40
+ attr_reader :pss
41
+ attr_reader :shared_clean
42
+ attr_reader :shared_dirty
43
+ attr_reader :private_clean
44
+ attr_reader :private_dirty
45
+ attr_reader :referenced
46
+ attr_reader :anonymous
47
+ attr_reader :anon_huge_pages
48
+ attr_reader :swap
49
+ attr_reader :kernel_page_size
50
+ attr_reader :mmu_page_size
51
+ attr_reader :locked
52
+ attr_reader :vmflags
53
+
54
+ KEY_MAPPING = {
55
+ "Size" => :size,
56
+ "Rss" => :rss,
57
+ "Pss" => :pss,
58
+ "Shared_Clean" => :shared_clean,
59
+ "Shared_Dirty" => :shared_dirty,
60
+ "Private_Clean" => :private_clean,
61
+ "Private_Dirty" => :private_dirty,
62
+ "Referenced" => :referenced,
63
+ "Anonymous" => :anonymous,
64
+ "AnonHugePages" => :anon_huge_pages,
65
+ "Swap" => :swap,
66
+ "KernelPageSize" => :kernel_page_size,
67
+ "MMUPageSize" => :mmu_page_size,
68
+ "Locked" => :locked,
69
+ "VmFlags" => :vm_flags,
70
+ }
71
+
72
+ TYPE_MAP = {
73
+ "VmFlags" => lambda{|s| s}
74
+ }
75
+
76
+ DEFAULT_TYPE = lambda{|s| s[0, s.size - 3].to_i * 1024}
77
+
78
+ def initialize values
79
+ values.each do |key, value|
80
+ unless target = KEY_MAPPING[key]
81
+ raise "unexpected key: #{key}"
82
+ end
83
+
84
+ instance_variable_set "@#{target}", value
85
+ end
86
+ end
87
+ end
88
+
89
+ class SystemStatistics
90
+ include FFWD::Logging
91
+
92
+ PID_SMAPS_FILE = '/proc/self/smaps'
93
+ PID_STAT_FILE = '/proc/self/stat'
94
+ STAT_FILE = '/proc/stat'
95
+ MEMINFO_FILE = '/proc/meminfo'
96
+
97
+ def initialize opts={}
98
+ @cpu_prev = nil
99
+ end
100
+
101
+ def collect channel
102
+ memory_use = memory_usage
103
+ cpu_use = cpu_usage
104
+
105
+ cpu_use.each do |key, value|
106
+ yield "statistics-cpu/#{key}", value
107
+ end
108
+
109
+ memory_use.each do |key, value|
110
+ yield "statistics-memory/#{key}", value
111
+ end
112
+
113
+ channel << {
114
+ :cpu => cpu_use,
115
+ :memory => memory_use,
116
+ }
117
+ end
118
+
119
+ def check
120
+ if not File.file? PID_SMAPS_FILE
121
+ log.error "File does not exist: #{PID_SMAPS_FILE} (is this a linux system?)"
122
+ return false
123
+ end
124
+
125
+ if not File.file? PID_STAT_FILE
126
+ log.error "File does not exist: #{PID_STAT_FILE} (is this a linux system?)"
127
+ return false
128
+ end
129
+
130
+ if not File.file? STAT_FILE
131
+ log.error "File does not exist: #{STAT_FILE} (is this a linux system?)"
132
+ return false
133
+ end
134
+
135
+ if not File.file? MEMINFO_FILE
136
+ log.error "File does not exist: #{MEMINFO_FILE} (is this a linux system?)"
137
+ return false
138
+ end
139
+
140
+ return true
141
+ end
142
+
143
+ def memory_usage
144
+ result = {:resident => 0, :total => read_total_memory}
145
+
146
+ read_smaps do |smap|
147
+ result[:resident] += smap.rss
148
+ end
149
+
150
+ result
151
+ end
152
+
153
+ def cpu_usage
154
+ stat = read_pid_stat
155
+
156
+ current = {
157
+ :system => stat[:stime],
158
+ :user => stat[:utime],
159
+ :total => read_stat_total
160
+ }
161
+
162
+ prev = @cpu_prev
163
+
164
+ if @cpu_prev.nil?
165
+ @cpu_prev = prev = current
166
+ else
167
+ @cpu_prev = current
168
+ end
169
+
170
+ return {
171
+ :system => current[:system] - prev[:system],
172
+ :user => current[:user] - prev[:user],
173
+ :total => current[:total] - prev[:total],
174
+ }
175
+ end
176
+
177
+ private
178
+
179
+ def read_pid_stat
180
+ File.open(PID_STAT_FILE) do |f|
181
+ stat = f.readline.split(' ').map(&:strip)
182
+ return {:utime => stat[13].to_i, :stime => stat[14].to_i}
183
+ end
184
+ end
185
+
186
+ def read_stat_total
187
+ File.open(STAT_FILE) do |f|
188
+ f.each do |line|
189
+ next unless line.start_with? "cpu "
190
+ stat = line.split(' ').map(&:strip).map(&:to_i)
191
+ return stat.reduce(&:+)
192
+ end
193
+ end
194
+ end
195
+
196
+ def read_total_memory
197
+ File.open(MEMINFO_FILE) do |f|
198
+ f.each do |line|
199
+ next unless line.start_with? "MemTotal:"
200
+ total = line.split(' ').map(&:strip)
201
+ return total[1].to_i * 1000
202
+ end
203
+ end
204
+ end
205
+
206
+ def read_smaps
207
+ File.open(PID_SMAPS_FILE) do |f|
208
+ smap = {}
209
+
210
+ loop do
211
+ break if f.eof?
212
+
213
+ unless smap.empty?
214
+ yield SMAP.new(smap)
215
+ smap = {}
216
+ end
217
+
218
+ loop do
219
+ break if f.eof?
220
+
221
+ line = f.readline.strip
222
+
223
+ case line
224
+ when /^[0-9a-f]+-[0-9a-f]+ /
225
+ break
226
+ else
227
+ key, value = line.split(':', 2)
228
+ next unless SMAP::KEY_MAPPING[key]
229
+ smap[key] = (SMAP::TYPE_MAP[key] || SMAP::DEFAULT_TYPE).call(value.strip)
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ def smaps_read_entry f
237
+ result = {}
238
+
239
+ loop do
240
+ break if f.eof?
241
+
242
+ line = f.readline.strip
243
+
244
+ case line
245
+ when /^[0-9a-f]+-[0-9a-f]+ /
246
+ break
247
+ else
248
+ result[key] = (SMAP::TYPE_MAP[key] || SMAP::DEFAULT_TYPE).call(value.strip)
249
+ end
250
+ end
251
+
252
+ result
253
+ end
254
+ end
255
+ end