ffwd 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -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
|