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