fluq 0.7.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 (68) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +6 -0
  3. data/Gemfile +6 -0
  4. data/Gemfile.lock +39 -0
  5. data/MIT-LICENCE +19 -0
  6. data/README.md +10 -0
  7. data/Rakefile +11 -0
  8. data/benchmark/logging.rb +37 -0
  9. data/benchmark/socket.rb +52 -0
  10. data/bin/fluq-rb +8 -0
  11. data/examples/common.rb +3 -0
  12. data/examples/simple.rb +5 -0
  13. data/fluq.gemspec +33 -0
  14. data/lib/fluq.rb +50 -0
  15. data/lib/fluq/buffer.rb +6 -0
  16. data/lib/fluq/buffer/base.rb +51 -0
  17. data/lib/fluq/buffer/file.rb +68 -0
  18. data/lib/fluq/cli.rb +142 -0
  19. data/lib/fluq/dsl.rb +49 -0
  20. data/lib/fluq/dsl/options.rb +27 -0
  21. data/lib/fluq/error.rb +2 -0
  22. data/lib/fluq/event.rb +55 -0
  23. data/lib/fluq/feed.rb +6 -0
  24. data/lib/fluq/feed/base.rb +18 -0
  25. data/lib/fluq/feed/json.rb +28 -0
  26. data/lib/fluq/feed/msgpack.rb +27 -0
  27. data/lib/fluq/feed/tsv.rb +30 -0
  28. data/lib/fluq/handler.rb +6 -0
  29. data/lib/fluq/handler/base.rb +80 -0
  30. data/lib/fluq/handler/log.rb +67 -0
  31. data/lib/fluq/handler/null.rb +4 -0
  32. data/lib/fluq/input.rb +6 -0
  33. data/lib/fluq/input/base.rb +59 -0
  34. data/lib/fluq/input/socket.rb +50 -0
  35. data/lib/fluq/input/socket/connection.rb +41 -0
  36. data/lib/fluq/mixins.rb +6 -0
  37. data/lib/fluq/mixins/loggable.rb +7 -0
  38. data/lib/fluq/mixins/logger.rb +26 -0
  39. data/lib/fluq/reactor.rb +76 -0
  40. data/lib/fluq/testing.rb +26 -0
  41. data/lib/fluq/url.rb +16 -0
  42. data/lib/fluq/version.rb +3 -0
  43. data/spec/fluq/buffer/base_spec.rb +21 -0
  44. data/spec/fluq/buffer/file_spec.rb +47 -0
  45. data/spec/fluq/dsl/options_spec.rb +24 -0
  46. data/spec/fluq/dsl_spec.rb +43 -0
  47. data/spec/fluq/event_spec.rb +25 -0
  48. data/spec/fluq/feed/base_spec.rb +15 -0
  49. data/spec/fluq/feed/json_spec.rb +27 -0
  50. data/spec/fluq/feed/msgpack_spec.rb +27 -0
  51. data/spec/fluq/feed/tsv_spec.rb +27 -0
  52. data/spec/fluq/handler/base_spec.rb +70 -0
  53. data/spec/fluq/handler/log_spec.rb +68 -0
  54. data/spec/fluq/handler/null_spec.rb +11 -0
  55. data/spec/fluq/input/base_spec.rb +29 -0
  56. data/spec/fluq/input/socket/connection_spec.rb +35 -0
  57. data/spec/fluq/input/socket_spec.rb +45 -0
  58. data/spec/fluq/mixins/loggable_spec.rb +10 -0
  59. data/spec/fluq/mixins/logger_spec.rb +25 -0
  60. data/spec/fluq/reactor_spec.rb +58 -0
  61. data/spec/fluq/url_spec.rb +16 -0
  62. data/spec/fluq_spec.rb +11 -0
  63. data/spec/scenario/config/nested/common.rb +3 -0
  64. data/spec/scenario/config/test.rb +3 -0
  65. data/spec/scenario/lib/fluq/handler/custom/test_handler.rb +4 -0
  66. data/spec/spec_helper.rb +12 -0
  67. data/spec/support/configuration.rb +25 -0
  68. metadata +242 -0
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ log/
2
+ tmp/
3
+ *.gem
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ gemfile:
6
+ - Gemfile
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+ gemspec
3
+
4
+ group :development do
5
+ gem "oj"
6
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fluq (0.7.0)
5
+ eventmachine-le
6
+ msgpack (~> 0.5.0)
7
+ oj (>= 2.0.10)
8
+ timed_lru
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ diff-lcs (1.2.4)
14
+ eventmachine-le (1.1.5)
15
+ msgpack (0.5.5)
16
+ oj (2.1.3)
17
+ rake (10.1.0)
18
+ rspec (2.13.0)
19
+ rspec-core (~> 2.13.0)
20
+ rspec-expectations (~> 2.13.0)
21
+ rspec-mocks (~> 2.13.0)
22
+ rspec-core (2.13.1)
23
+ rspec-expectations (2.13.0)
24
+ diff-lcs (>= 1.1.3, < 2.0)
25
+ rspec-mocks (2.13.1)
26
+ timed_lru (0.3.1)
27
+ yard (0.8.6.2)
28
+
29
+ PLATFORMS
30
+ java
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bundler
35
+ fluq!
36
+ oj
37
+ rake
38
+ rspec
39
+ yard
data/MIT-LICENCE ADDED
@@ -0,0 +1,19 @@
1
+ Permission is hereby granted, free of charge, to any person obtaining
2
+ a copy of this software and associated documentation files (the
3
+ "Software"), to deal in the Software without restriction, including
4
+ without limitation the rights to use, copy, modify, merge, publish,
5
+ distribute, sublicense, and/or sell copies of the Software, and to
6
+ permit persons to whom the Software is furnished to do so, subject to
7
+ the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be
10
+ included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
data/README.md ADDED
@@ -0,0 +1,10 @@
1
+ FluQ
2
+ ====
3
+
4
+ Description coming soon ...
5
+
6
+ Licence
7
+ -------
8
+
9
+ Copyright 2013 Black Square Media Ltd.
10
+ It is free software, and may be redistributed under the terms specified in the MIT-LICENCE file.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'rake'
2
+
3
+ require 'rspec/mocks/version'
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ require 'yard'
8
+ YARD::Rake::YardocTask.new
9
+
10
+ desc 'Default: run specs.'
11
+ task :default => :spec
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.expand_path('../../lib', __FILE__))
4
+
5
+ require 'bundler/setup'
6
+ require 'fluq'
7
+ require 'benchmark'
8
+
9
+ ITER = 100_000
10
+
11
+ MultiJson.use :oj
12
+ FileUtils.rm_rf FluQ.root.join("log/benchmark")
13
+
14
+ events = (1..ITER).map do
15
+ FluQ::Event.new "a.b#{rand(4)}.c#{rand(100)}.d#{rand(100)}", Time.now.to_i, "k1" => "value", "k2" => "value", "k3" => "value"
16
+ end
17
+
18
+ handler = FluQ::Handler::Log.new \
19
+ path: "log/benchmark/%Y%m/%d/%H/%t.log",
20
+ rewrite: lambda {|t| t.split(".")[1] }
21
+
22
+ puts "--> Started benchmark"
23
+ processed = Benchmark.realtime do
24
+ num = 0
25
+ events.each_slice(1_000) do |slice|
26
+ handler.on_events(slice)
27
+ num += slice.size
28
+ if (num % 10_000).zero?
29
+ puts "--> Processed : #{num}"
30
+ end
31
+ end
32
+ end
33
+
34
+ puts "--> Processed : #{events.size} in #{processed.round(1)}s"
35
+ files = Dir[FluQ.root.join("log/benchmark/**/*.log").to_s]
36
+ lines = `cat #{files.join(' ')} | wc -l`.strip
37
+ puts "--> Written : #{lines} lines"
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.expand_path('../../lib', __FILE__))
4
+
5
+ require 'bundler/setup'
6
+ require 'fluq'
7
+
8
+ BATCH_SIZE = 100_000
9
+ BATCHES = 50
10
+ ROOT = FluQ.root.join("log/benchmark")
11
+ EVENT = FluQ::Event.new("a.b.c.d", Time.now.to_i, "k1" => "value", "k2" => "value", "k3" => "value").to_msgpack
12
+
13
+ FileUtils.rm_rf ROOT.to_s
14
+ FileUtils.mkdir_p ROOT.to_s
15
+
16
+ class FluQ::Handler::Counter < FluQ::Handler::Base
17
+ attr_reader :count
18
+ def initialize(*)
19
+ super
20
+ @count = 0
21
+ end
22
+ def on_events(events)
23
+ @count += events.size
24
+ EM.stop if @count >= BATCHES * BATCH_SIZE
25
+ end
26
+ end
27
+
28
+ puts "--> Preparing"
29
+ BATCHES.times do |i|
30
+ ROOT.join("batch.#{i}").open("wb:ASCII-8BIT") do |file|
31
+ BATCH_SIZE.times { file.write(EVENT) }
32
+ end
33
+ end
34
+
35
+ processed = 0
36
+ handler = nil
37
+ start = Time.now
38
+ FluQ::Reactor.run do |reactor|
39
+ reactor.listen FluQ::Input::Socket, bind: "tcp://127.0.0.1:8765"
40
+ handler = reactor.register FluQ::Handler::Counter
41
+
42
+ sleep(0.1)
43
+ start = Time.now
44
+ puts "--> Started benchmark"
45
+ BATCHES.times do |i|
46
+ file = ROOT.join("batch.#{i}")
47
+ spawn("nc 127.0.0.1 8765 < #{file}")
48
+ end
49
+ end
50
+
51
+ puts "--> Accepted : #{BATCHES * BATCH_SIZE} in #{(Time.now - start).round(1)}s"
52
+ puts "--> Processed : #{handler.count} events"
data/bin/fluq-rb ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if File.exists?(File.expand_path('../../lib/fluq.rb', __FILE__))
4
+ $:.unshift(File.expand_path('../../lib', __FILE__))
5
+ end
6
+ require 'fluq/cli'
7
+
8
+ FluQ::CLI.run
@@ -0,0 +1,3 @@
1
+ input :socket do
2
+ bind "tcp://127.0.0.1:6790"
3
+ end
@@ -0,0 +1,5 @@
1
+ input :socket do
2
+ bind "tcp://127.0.0.1:6789"
3
+ end
4
+
5
+ handler :log
data/fluq.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+ require "fluq/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.required_ruby_version = '>= 1.9.1'
8
+ s.required_rubygems_version = ">= 1.8.0"
9
+
10
+ s.name = File.basename(__FILE__, '.gemspec')
11
+ s.summary = "FluQ"
12
+ s.description = "The minimalistic stream processor"
13
+ s.version = FluQ::VERSION.dup
14
+
15
+ s.authors = ["Black Square Media"]
16
+ s.email = "info@blacksquaremedia.com"
17
+ s.homepage = "https://github.com/bsm/fluq"
18
+
19
+ s.require_path = 'lib'
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+
24
+ s.add_dependency "msgpack", "~> 0.5.0"
25
+ s.add_dependency "eventmachine-le"
26
+ s.add_dependency "oj", ">= 2.0.10"
27
+ s.add_dependency "timed_lru"
28
+
29
+ s.add_development_dependency "rake"
30
+ s.add_development_dependency "bundler"
31
+ s.add_development_dependency "rspec"
32
+ s.add_development_dependency "yard"
33
+ end
data/lib/fluq.rb ADDED
@@ -0,0 +1,50 @@
1
+ require 'pathname'
2
+ require 'uri'
3
+ require 'fileutils'
4
+ require 'securerandom'
5
+ require 'forwardable'
6
+ require 'logger'
7
+ require 'eventmachine'
8
+ require 'msgpack'
9
+ require 'oj'
10
+ require 'timed_lru'
11
+
12
+ module FluQ
13
+ %w'version error mixins'.each do |name|
14
+ require "fluq/#{name}"
15
+ end
16
+
17
+ class << self
18
+
19
+ # @attr_reader [String] env runtime environemnt
20
+ # @attr_reader [Pathname] root project root
21
+ # @attr_reader [Logger] logger the main logger
22
+ attr_reader :env, :root, :logger
23
+
24
+ # @param [Logger] instance the thread-safe logger instance
25
+ def logger=(instance)
26
+ instance.extend(FluQ::Mixins::Logger)
27
+ @logger = instance
28
+ end
29
+
30
+ def init!
31
+ # Detect environment
32
+ @env = ENV['FLUQ_ENV'] || "development"
33
+
34
+ # Set root path
35
+ @root = Pathname.new(ENV['FLUQ_ROOT'] || ".")
36
+
37
+ # Setup logger
38
+ self.logger = ::Logger.new(STDOUT)
39
+ logger.level = ::Logger::INFO if env == "production"
40
+ end
41
+ protected :init!
42
+
43
+ end
44
+
45
+ init!
46
+ end
47
+
48
+ %w'url event reactor handler input buffer feed dsl'.each do |name|
49
+ require "fluq/#{name}"
50
+ end
@@ -0,0 +1,6 @@
1
+ module FluQ::Buffer
2
+ end
3
+
4
+ %w'base file'.each do |name|
5
+ require "fluq/buffer/#{name}"
6
+ end
@@ -0,0 +1,51 @@
1
+ class FluQ::Buffer::Base
2
+ MAX_SIZE = 256 * 1024 * 1024 # 256M
3
+
4
+ # @attr_reader [Hash] config
5
+ attr_reader :config
6
+
7
+ # @param [Hash] options various configuration options
8
+ def initialize(options = {})
9
+ super()
10
+ @config = defaults.merge(options)
11
+ end
12
+
13
+ # @return [String] name identifier
14
+ def name
15
+ @name ||= self.class.name.split("::").last.downcase
16
+ end
17
+
18
+ # @abstract
19
+ # @yield over io object
20
+ # @yieldparam [IO] io
21
+ def drain
22
+ yield StringIO.new
23
+ end
24
+
25
+ # @abstract
26
+ # @return [Integer] the size
27
+ def size
28
+ 0
29
+ end
30
+
31
+ # @return [Boolean] true if size exceeds limit
32
+ def full?
33
+ size >= config[:max_size]
34
+ end
35
+
36
+ # @abstract data writer
37
+ # @param [String] data binary string
38
+ def write(data)
39
+ end
40
+
41
+ # @abstract callback, close buffer
42
+ def close
43
+ end
44
+
45
+ protected
46
+
47
+ def defaults
48
+ { max_size: MAX_SIZE }
49
+ end
50
+
51
+ end
@@ -0,0 +1,68 @@
1
+ class FluQ::Buffer::File < FluQ::Buffer::Base
2
+
3
+ # @attr_reader [File] file instance
4
+ attr_reader :file
5
+
6
+ # @see FluQ::Buffer::Base#initialize
7
+ def initialize(*)
8
+ super
9
+ @file = new_file
10
+ @size = 0
11
+ end
12
+
13
+ # @see FluQ::Buffer::Base#name
14
+ def name
15
+ @name ||= [super, File.basename(file.path)].join("-")
16
+ end
17
+
18
+ # @see FluQ::Buffer::Base#write
19
+ def write(data)
20
+ file.write(data)
21
+ end
22
+
23
+ # @see FluQ::Buffer::Base#size
24
+ def size
25
+ file.size
26
+ end
27
+
28
+ # @see FluQ::Buffer::Base#close
29
+ def close
30
+ file.close unless file.closed?
31
+ File.unlink(file.path) if File.exists?(file.path)
32
+ end
33
+
34
+ # @see FluQ::Buffer::Base#drain
35
+ def drain
36
+ file.close unless file.closed?
37
+ io = File.open(file.path, 'rb', encoding: Encoding::BINARY)
38
+ yield(io)
39
+ ensure
40
+ io.close if io
41
+ end
42
+
43
+ protected
44
+
45
+ def defaults
46
+ super.merge(path: "tmp/buffers")
47
+ end
48
+
49
+ def new_file
50
+ path = nil
51
+ incr = 0
52
+ path = root.join(generate_name(incr+=1)) until path && !path.exist?
53
+ file = path.open("wb", encoding: Encoding::BINARY)
54
+ file.sync = true
55
+ file
56
+ end
57
+
58
+ def root
59
+ @root ||= FluQ.root.join(config[:path]).tap do |full_path|
60
+ FileUtils.mkdir_p full_path.to_s
61
+ end
62
+ end
63
+
64
+ def generate_name(index)
65
+ "fb-#{(Time.now.utc.to_f * 1000).round}.#{index}"
66
+ end
67
+
68
+ end
data/lib/fluq/cli.rb ADDED
@@ -0,0 +1,142 @@
1
+ require "optparse"
2
+ require "fileutils"
3
+ require "socket"
4
+
5
+ module FluQ
6
+ class CLI
7
+ SIGNALS = [ :QUIT, :INT, :TERM, :HUP ]
8
+
9
+ # attr_reader [Hash] options
10
+ attr_reader :options
11
+
12
+ # Runs the CLI
13
+ def self.run
14
+ if BasicSocket.respond_to?(:do_not_reverse_lookup=)
15
+ BasicSocket.do_not_reverse_lookup = true
16
+ end
17
+ new.run
18
+ end
19
+
20
+ # Constructor
21
+ def initialize
22
+ super
23
+
24
+ # Parse options
25
+ @options = {}
26
+ parser.parse!(ARGV)
27
+ end
28
+
29
+ def run
30
+ # Exit if not configured correctly
31
+ unless configured?
32
+ puts parser
33
+ exit
34
+ end
35
+
36
+ # Set the environment
37
+ if options[:env]
38
+ ENV["FLUQ_ENV"] = options[:env]
39
+ end
40
+
41
+ # Boot and add project's lib/ dir to load path
42
+ require 'fluq'
43
+ $LOAD_PATH.unshift FluQ.root.join('lib')
44
+ procline "(starting)"
45
+
46
+ # Setup logger
47
+ if options[:log]
48
+ FileUtils.mkdir_p(File.dirname(options[:log]))
49
+ FluQ.logger = ::Logger.new(options[:log])
50
+ end
51
+ if options[:verbose]
52
+ FluQ.logger.level = ::Logger::DEBUG
53
+ end
54
+
55
+ # Write PID file
56
+ @pidfile = options[:pidfile] || FluQ.root.join("tmp", "pids", "fluq.pid")
57
+ FileUtils.mkdir_p(File.dirname(@pidfile))
58
+ File.open(@pidfile, "w") {|f| f.write Process.pid }
59
+
60
+ # Trap signals
61
+ SIGNALS.each do |signal|
62
+ trap(signal) {|*| shutdown! }
63
+ end
64
+
65
+ # Start
66
+ log "Starting FluQ #{FluQ::VERSION} (#{FluQ.env})"
67
+ FluQ::Reactor.run do |reactor|
68
+ FluQ::DSL.new(reactor, options[:config]).run
69
+ procline
70
+ end
71
+ end
72
+
73
+ # @return [Boolean] true if configured
74
+ def configured?
75
+ options[:config] && File.file?(options[:config])
76
+ end
77
+
78
+ private
79
+
80
+ # Shut down
81
+ def shutdown!
82
+ FluQ.logger.info "Shutting down FluQ" if defined?(FluQ)
83
+ FileUtils.rm_f(@pidfile) if @pidfile
84
+ exit
85
+ end
86
+
87
+ def log(message)
88
+ FluQ.logger.info(message)
89
+ end
90
+
91
+ def procline(message = nil)
92
+ $0 = ["fluq-rb", FluQ::VERSION, message].compact.join(" ")
93
+ end
94
+
95
+ def parser
96
+ @parser ||= OptionParser.new do |o|
97
+ o.banner = "Usage: #{File.basename($0)} [OPTIONS]"
98
+
99
+ o.separator ""
100
+ o.separator "Required:"
101
+
102
+ o.on "-C", "--config FILE", "Use this config file" do |val|
103
+ @options[:config] = val
104
+ end
105
+
106
+ o.separator ""
107
+ o.separator "Optional:"
108
+
109
+ o.on("-e", "--environment ENV", "Runtime environment (default: development)") do |val|
110
+ @options[:env] = val
111
+ end
112
+
113
+ o.on("-l", "--log FILE", "File to log to (default: STDOUT)") do |val|
114
+ @options[:log] = val
115
+ end
116
+
117
+ o.on("-v", "--verbose", "Use verbose output") do |val|
118
+ @options[:verbose] = true
119
+ end
120
+
121
+ o.separator ""
122
+
123
+ o.on "--pidfile FILE", "Path to pidfile (default: tmp/pids/fluq.pid)" do |val|
124
+ @options[:pidfile] = val
125
+ end
126
+
127
+ o.separator ""
128
+
129
+ o.on("-h", "--help", "Show this message") do
130
+ puts o
131
+ exit
132
+ end
133
+
134
+ o.on("-V", "--version", "Show version") do
135
+ puts FluQ::VERSION
136
+ exit
137
+ end
138
+ end
139
+ end
140
+
141
+ end
142
+ end