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