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,139 @@
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
+ # Logging functionality.
17
+ #
18
+ # Defines FFWD::Logging which when included in either a class or module makes
19
+ # the 'log' field available both as a class and instance field.
20
+ #
21
+ # 'log' in turn is an object with the following fields available.
22
+ #
23
+ # 'debug' - Log a message of level DEBUG..
24
+ # 'info' - Log a message of level INFO.
25
+ # 'warning' - Log a message of level WARNING.
26
+ # 'error' - Log a message of level ERROR.
27
+ #
28
+ # Every function takes the message to log as the only parameter except 'error'
29
+ # which can take an exception as a secondary argument.
30
+ #
31
+ # If an exception is provided, a stacktrace will be printed to log.
32
+ require 'logger'
33
+
34
+ module FFWD
35
+ def self.log
36
+ @log ||= setup_log
37
+ end
38
+
39
+ def self.log_reload
40
+ @log = setup_log
41
+ end
42
+
43
+ def self.setup_log
44
+ if log_config[:file]
45
+ file = log_config[:file]
46
+ shift_age = log_config[:shift_age]
47
+
48
+ return ::Logger.new(file, shift_age=shift_age).tap do |l|
49
+ l.level = log_config[:level]
50
+ l.progname = log_config[:progname]
51
+ end
52
+ end
53
+
54
+ if log_config[:stream]
55
+ return ::Logger.new(log_config[:stream]).tap do |l|
56
+ l.level = log_config[:level]
57
+ l.progname = log_config[:progname]
58
+ end
59
+ end
60
+
61
+ raise "cannot setup loggin with options: #{log_config}"
62
+ end
63
+
64
+ def self.log_config
65
+ return @log_config unless @log_config.nil?
66
+
67
+ @log_config = {
68
+ :file => nil,
69
+ :shift_age => 1,
70
+ :level => Logger::INFO,
71
+ :stream => STDOUT,
72
+ :progname => 'FFWD',
73
+ }
74
+ end
75
+
76
+ def self.log_disable
77
+ @log_disable = true
78
+ end
79
+
80
+ def self.log_disabled?
81
+ @log_disable || false
82
+ end
83
+
84
+ class ClassLogger
85
+ def initialize klass
86
+ @progname = klass.name
87
+ end
88
+
89
+ def debug message
90
+ FFWD.log.debug(@progname){message}
91
+ end
92
+
93
+ def info message
94
+ FFWD.log.info(@progname){message}
95
+ end
96
+
97
+ def warning message
98
+ FFWD.log.warn(@progname){message}
99
+ end
100
+
101
+ def error message, e=nil
102
+ FFWD.log.error(@progname){message}
103
+
104
+ return unless e
105
+
106
+ FFWD.log.error(@progname){"Caused by #{e.class}: #{e}"}
107
+ e.backtrace.each do |b|
108
+ FFWD.log.error(@progname){" #{b}"}
109
+ end
110
+ end
111
+ end
112
+
113
+ class FakeLogger
114
+ def debug message; end
115
+ def info message; end
116
+ def warning message; end
117
+ def error message, e=nil; end
118
+ end
119
+
120
+ module Logging
121
+ module ClassMethods
122
+ attr_accessor :log
123
+ end
124
+
125
+ def log
126
+ self.class.log
127
+ end
128
+
129
+ def self.included klass
130
+ klass.extend ClassMethods
131
+
132
+ if FFWD.log_disabled?
133
+ klass.log = FakeLogger.new
134
+ else
135
+ klass.log = ClassLogger.new klass
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,55 @@
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
+ # Struct used to define all fields related to a metric.
18
+ MetricStruct = Struct.new(
19
+ # The time at which the metric was collected.
20
+ :time,
21
+ # The unique key of the metric.
22
+ :key,
23
+ # A numeric value associated with the metric.
24
+ :value,
25
+ # The host from which the metric originated.
26
+ :host,
27
+ # The source metric this metric was derived from (if any).
28
+ :source,
29
+ # Tags associated to the metric.
30
+ :tags,
31
+ # Attributes (extra fields) associated to the metric.
32
+ :attributes
33
+ )
34
+
35
+ # A convenience class for each individual metric.
36
+ class Metric < MetricStruct
37
+ def self.make opts={}
38
+ new(opts[:time], opts[:key], opts[:value], opts[:host], opts[:source],
39
+ opts[:tags], opts[:attributes])
40
+ end
41
+
42
+ # Convert metric to a sparse hash.
43
+ def to_h
44
+ d = {}
45
+ d[:time] = time.to_i if time
46
+ d[:key] = key if key
47
+ d[:value] = value if value
48
+ d[:host] = host if host
49
+ d[:source] = source if source
50
+ d[:tags] = tags.to_a if tags
51
+ d[:attributes] = attributes if attributes
52
+ d
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,50 @@
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 'utils'
17
+ require_relative 'metric'
18
+
19
+ module FFWD
20
+ # Used to emit metrics to an 'output' channel
21
+ #
22
+ # Can take two parts of a configuration 'base' and 'opts' to decide which
23
+ # metadata emitted metrics should be decorated with.
24
+ class MetricEmitter
25
+ def self.build output, base, opts
26
+ host = opts[:host] || base[:host] || FFWD.current_host
27
+ tags = FFWD.merge_sets base[:tags], opts[:tags]
28
+ attributes = FFWD.merge_hashes base[:attributes], opts[:attributes]
29
+ new output, host, tags, attributes
30
+ end
31
+
32
+ def initialize output, host, tags, attributes
33
+ @output = output
34
+ @host = host
35
+ @tags = tags
36
+ @attributes = attributes
37
+ end
38
+
39
+ def emit m
40
+ m[:time] ||= Time.now
41
+ m[:host] ||= @host if @host
42
+ m[:tags] = FFWD.merge_sets @tags, m[:tags]
43
+ m[:attributes] = FFWD.merge_hashes @attributes, m[:attributes]
44
+
45
+ @output.metric Metric.make(m)
46
+ rescue => e
47
+ log.error "Failed to emit metric", e
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,149 @@
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
19
+ module Plugin
20
+ class Loaded
21
+ attr_reader :source, :name
22
+
23
+ def initialize source, name, options
24
+ @source = source
25
+ @name = name
26
+ @mod = options[:mod]
27
+ @setup_input_method = load_method @mod, options[:setup_input_method_name]
28
+ @setup_output_method = load_method @mod, options[:setup_output_method_name]
29
+ @setup_tunnel_method = load_method @mod, options[:setup_tunnel_method_name]
30
+ end
31
+
32
+ def capabilities
33
+ capabilities = []
34
+
35
+ if not @setup_input_method.nil?
36
+ capabilities << "input"
37
+ end
38
+
39
+ if not @setup_output_method.nil?
40
+ capabilities << "output"
41
+ end
42
+
43
+ if not @setup_tunnel_method.nil?
44
+ capabilities << "tunnel"
45
+ end
46
+
47
+ return capabilities
48
+ end
49
+
50
+ def can?(kind)
51
+ not get(kind).nil?
52
+ end
53
+
54
+ def get(kind)
55
+ return @setup_input_method if kind == :input
56
+ return @setup_output_method if kind == :output
57
+ return @setup_tunnel_method if kind == :tunnel
58
+ return nil
59
+ end
60
+
61
+ private
62
+
63
+ def load_method mod, method_name
64
+ return nil unless mod.respond_to? method_name
65
+ return mod.method method_name
66
+ end
67
+ end
68
+
69
+ class Setup
70
+ attr_reader :name, :config
71
+
72
+ def initialize name, setup, config
73
+ @name = name
74
+ @setup = setup
75
+ @config = config
76
+ end
77
+
78
+ def setup *args
79
+ @setup.call @config, *args
80
+ end
81
+ end
82
+
83
+ def self.discovered
84
+ @@discovered ||= {}
85
+ end
86
+
87
+ def self.loaded
88
+ @@loaded ||= {}
89
+ end
90
+
91
+ module ClassMethods
92
+ def register_plugin(name, opts={})
93
+ options = {:mod => self}
94
+
95
+ options[:setup_input_method_name] = (opts[:setup_input_method] || :setup_input)
96
+ options[:setup_output_method_name] = (opts[:setup_output_method] || :setup_output)
97
+ options[:setup_tunnel_method_name] = (opts[:setup_tunnel_method] || :setup_tunnel)
98
+
99
+ FFWD::Plugin.discovered[name] = options
100
+ end
101
+ end
102
+
103
+ def self.included mod
104
+ mod.extend ClassMethods
105
+ end
106
+
107
+ def self.category
108
+ 'plugin'
109
+ end
110
+
111
+ def self.load_discovered source
112
+ FFWD::Plugin.discovered.each do |name, options|
113
+ FFWD::Plugin.loaded[name] = Loaded.new source, name, options
114
+ end
115
+
116
+ FFWD::Plugin.discovered.clear
117
+ end
118
+
119
+ def self.load_plugins log, kind_name, config, kind
120
+ result = []
121
+
122
+ if config.nil?
123
+ return result
124
+ end
125
+
126
+ config.each_with_index do |plugin_config, index|
127
+ d = "#{kind_name} plugin ##{index}"
128
+
129
+ if (name = plugin_config[:type]).nil?
130
+ log.error "#{d}: Missing :type attribute for '#{kind_name}'"
131
+ end
132
+
133
+ if (plugin = FFWD::Plugin.loaded[name]).nil?
134
+ log.error "#{d}: Not an available plugin '#{name}'"
135
+ next
136
+ end
137
+
138
+ unless plugin.can?(kind)
139
+ log.error "#{d}: Not an #{kind_name} plugin '#{name}'"
140
+ next
141
+ end
142
+
143
+ result << Setup.new(name, plugin.get(kind), plugin_config)
144
+ end
145
+
146
+ return result
147
+ end
148
+ end
149
+ 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
+ require 'eventmachine'
17
+
18
+ require 'ffwd/protocol'
19
+ require 'ffwd/plugin'
20
+ require 'ffwd/logging'
21
+
22
+ require_relative 'json_line/connection'
23
+
24
+ module FFWD::Plugin::JsonLine
25
+ include FFWD::Plugin
26
+ include FFWD::Logging
27
+
28
+ register_plugin "json_line"
29
+
30
+ DEFAULT_HOST = "localhost"
31
+ DEFAULT_PORT = 19000
32
+
33
+ def self.setup_input opts, core
34
+ opts[:host] ||= DEFAULT_HOST
35
+ opts[:port] ||= DEFAULT_PORT
36
+ buffer_limit = opts["buffer_limit"] || 1000
37
+ protocol = FFWD.parse_protocol(opts[:protocol] || "tcp")
38
+ protocol.bind opts, core, log, Connection, buffer_limit
39
+ end
40
+
41
+ def self.setup_tunnel opts, core, tunnel
42
+ opts[:port] ||= DEFAULT_PORT
43
+ buffer_limit = opts["buffer_limit"] || 1000
44
+ protocol = FFWD.parse_protocol(opts[:protocol] || "tcp")
45
+ protocol.tunnel opts, core, tunnel, log, Connection, buffer_limit
46
+ end
47
+ end
@@ -0,0 +1,118 @@
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 'eventmachine'
17
+
18
+ require 'ffwd/logging'
19
+ require 'ffwd/connection'
20
+
21
+ module FFWD::Plugin::JsonLine
22
+ class Connection < FFWD::Connection
23
+ include FFWD::Logging
24
+ include EM::Protocols::LineText2
25
+
26
+ EVENT_FIELDS = [
27
+ ["key", :key],
28
+ ["value", :value],
29
+ ["host", :host],
30
+ ["state", :state],
31
+ ["description", :description],
32
+ ["ttl", :ttl],
33
+ ["tags", :tags],
34
+ ["attributes", :attributes],
35
+ ]
36
+
37
+ METRIC_FIELDS = [
38
+ ["proc", :proc],
39
+ ["key", :key],
40
+ ["value", :value],
41
+ ["tags", :tags],
42
+ ["attributes", :attributes]
43
+ ]
44
+
45
+ def self.plugin_type
46
+ "json_line_in"
47
+ end
48
+
49
+ def initialize bind, core, buffer_limit
50
+ @bind = bind
51
+ @core = core
52
+ @buffer_limit = buffer_limit
53
+ end
54
+
55
+ def receive_line data
56
+ data = JSON.load(data)
57
+
58
+ unless type = data["type"]
59
+ log.error "Field 'type' missing from received line"
60
+ return
61
+ end
62
+
63
+ if type == "metric"
64
+ @core.input.metric read_metric(data)
65
+ @bind.increment :received_metric
66
+ return
67
+ end
68
+
69
+ if type == "event"
70
+ @core.input.event read_event(data)
71
+ @bind.increment :received_event
72
+ return
73
+ end
74
+
75
+ log.error "No such type: #{type}"
76
+ rescue => e
77
+ log.error "Failed to receive line", e
78
+ end
79
+
80
+ def read_tags d, source
81
+ return if (tags = d["tags"]).nil?
82
+ d[:tags] = tags.to_set
83
+ end
84
+
85
+ def read_time d, source
86
+ return if (time = d["time"]).nil?
87
+ d[:time] = Time.at time
88
+ end
89
+
90
+ def read_metric data
91
+ d = {}
92
+
93
+ read_tags d, data["tags"]
94
+ read_time d, data["time"]
95
+
96
+ METRIC_FIELDS.each do |from, to|
97
+ next if (v = data[from]).nil?
98
+ d[to] = v
99
+ end
100
+
101
+ d
102
+ end
103
+
104
+ def read_event data
105
+ d = {}
106
+
107
+ read_tags d, data["tags"]
108
+ read_time d, data["time"]
109
+
110
+ EVENT_FIELDS.each do |from, to|
111
+ next if (v = data[from]).nil?
112
+ d[to] = v
113
+ end
114
+
115
+ d
116
+ end
117
+ end
118
+ end