teplohod 0.0.1.alpha1
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/.gitignore +9 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +5 -0
- data/Rakefile +21 -0
- data/bin/teplohod +14 -0
- data/example/main.rb +50 -0
- data/lib/teplohod/cli.rb +63 -0
- data/lib/teplohod/initializers/banner.rb +31 -0
- data/lib/teplohod/initializers/boot.rb +15 -0
- data/lib/teplohod/initializers/daemonizer.rb +51 -0
- data/lib/teplohod/initializers/logger.rb +14 -0
- data/lib/teplohod/initializers/options.rb +57 -0
- data/lib/teplohod/initializers/pid.rb +16 -0
- data/lib/teplohod/initializers/redis.rb +14 -0
- data/lib/teplohod/initializers/web.rb +13 -0
- data/lib/teplohod/initializers.rb +25 -0
- data/lib/teplohod/launcher.rb +28 -0
- data/lib/teplohod/logging.rb +87 -0
- data/lib/teplohod/manager.rb +19 -0
- data/lib/teplohod/pipeline/base.rb +89 -0
- data/lib/teplohod/processor/base.rb +9 -0
- data/lib/teplohod/processor/equality.rb +11 -0
- data/lib/teplohod/provider/base.rb +48 -0
- data/lib/teplohod/queue/base.rb +25 -0
- data/lib/teplohod/queue/memory/base.rb +33 -0
- data/lib/teplohod/signal_handler.rb +39 -0
- data/lib/teplohod/stats.rb +36 -0
- data/lib/teplohod/utils.rb +31 -0
- data/lib/teplohod/version.rb +3 -0
- data/lib/teplohod/web.rb +19 -0
- data/lib/teplohod.rb +57 -0
- data/teplohod.gemspec +38 -0
- data/web/assets/stylesheets/turret.css +7821 -0
- data/web/views/index.erb +48 -0
- data/web/views/layout.erb +18 -0
- metadata +291 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 0d50cedb72cdebc67d5e1f53545099061783de7d
|
|
4
|
+
data.tar.gz: 5af88cd316675cc4f24419de22a01b776500ff92
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 06ff005b1d8c27575757b9cf6e48aed4b0137ec2caf0959e2b8b13b8b2e9c9fec4e70bd5a10d821de694929f906d310d07f02a677b9511b7a7b0a58ea9382b22
|
|
7
|
+
data.tar.gz: e07d47c681221a83639ba9b7a0d0738e7644e1e3fbbee1230611b3ab73ce9320fc47aaf33951cb377d1e532c3402a95a3dcf3411427ff79a78a2fafd7571a669
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'bundler/gem_tasks'
|
|
2
|
+
require 'rake/testtask'
|
|
3
|
+
require 'rubocop/rake_task'
|
|
4
|
+
require 'yard'
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << 'test'
|
|
8
|
+
t.libs << 'lib'
|
|
9
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
RuboCop::RakeTask.new do |task|
|
|
13
|
+
task.fail_on_error = false
|
|
14
|
+
task.formatters = %w(simple)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
YARD::Rake::YardocTask.new do |t|
|
|
18
|
+
t.files = ['lib/**/*.rb']
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
task default: %i(rubocop test yard)
|
data/bin/teplohod
ADDED
data/example/main.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'teplohod'
|
|
2
|
+
require 'teplohod/queue/memory/base'
|
|
3
|
+
|
|
4
|
+
class StartQueue < Teplohod::Queue::Memory::Base
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class ResultQueue < Teplohod::Queue::Memory::Base
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class InitialPipeline < Teplohod::Pipeline::Base
|
|
11
|
+
self.queue = StartQueue.new
|
|
12
|
+
|
|
13
|
+
def name
|
|
14
|
+
"Initial Pipeline"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def desription
|
|
18
|
+
"Initial data there"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def on_empty_load
|
|
22
|
+
10.times do
|
|
23
|
+
queue.push rand(100)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def on_finish
|
|
28
|
+
on_empty_load
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class ResultPipeline < Teplohod::Pipeline::Base
|
|
33
|
+
self.parent = InitialPipeline.new
|
|
34
|
+
self.queue = ResultQueue.new
|
|
35
|
+
|
|
36
|
+
def name
|
|
37
|
+
"Result Pipeline"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def result
|
|
41
|
+
"Result data there"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class DemoProvider < Teplohod::Provider::Base
|
|
46
|
+
self.tail = ResultPipeline.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Teplohod::Manager.reload_providers!
|
|
50
|
+
Teplohod::Manager.register_provider DemoProvider.new
|
data/lib/teplohod/cli.rb
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
$stdout.sync = true
|
|
2
|
+
|
|
3
|
+
require 'singleton'
|
|
4
|
+
require 'optparse'
|
|
5
|
+
require 'logger'
|
|
6
|
+
|
|
7
|
+
require 'teplohod/utils'
|
|
8
|
+
require 'teplohod/initializers'
|
|
9
|
+
require 'teplohod/signal_handler'
|
|
10
|
+
require 'teplohod'
|
|
11
|
+
|
|
12
|
+
module Teplohod
|
|
13
|
+
class CLI
|
|
14
|
+
include Singleton
|
|
15
|
+
include Utils
|
|
16
|
+
|
|
17
|
+
def prepare(_args = ARGV)
|
|
18
|
+
Teplohod::Initializers::Options.run!
|
|
19
|
+
Teplohod::Initializers::Redis.run!
|
|
20
|
+
Teplohod::Initializers::Logger.run! unless options[:daemonize]
|
|
21
|
+
Teplohod::Initializers::Daemonizer.run! if options[:daemonize]
|
|
22
|
+
Teplohod::Initializers::Pid.run!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def run
|
|
26
|
+
Teplohod::Initializers::Boot.run!
|
|
27
|
+
Teplohod::Initializers::Web.run! if options[:web]
|
|
28
|
+
Teplohod::Initializers::Banner.run!
|
|
29
|
+
|
|
30
|
+
self_read, self_write = IO.pipe
|
|
31
|
+
|
|
32
|
+
%w(INT TERM USR1 USR2 TTIN).each do |sig|
|
|
33
|
+
begin
|
|
34
|
+
trap sig do
|
|
35
|
+
self_write.puts(sig)
|
|
36
|
+
end
|
|
37
|
+
rescue ArgumentError
|
|
38
|
+
puts "Signal #{sig} not supported"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
require 'teplohod/launcher'
|
|
43
|
+
launcher = Teplohod::Launcher.new
|
|
44
|
+
|
|
45
|
+
begin
|
|
46
|
+
launcher.run
|
|
47
|
+
|
|
48
|
+
while readable_io = IO.select([self_read])
|
|
49
|
+
signal = readable_io.first[0].gets.strip
|
|
50
|
+
Teplohod::SignalHandler.handle(signal)
|
|
51
|
+
end
|
|
52
|
+
rescue Interrupt
|
|
53
|
+
logger.tagged('system') { logger.info 'Shutting down' }
|
|
54
|
+
launcher.stop
|
|
55
|
+
logger.tagged('system') { logger.info 'Bye!' }
|
|
56
|
+
exit 0
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def handle_signal(sig)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Teplohod
|
|
2
|
+
module Initializers
|
|
3
|
+
class Banner
|
|
4
|
+
include Initializable
|
|
5
|
+
|
|
6
|
+
def run
|
|
7
|
+
puts "\e[32m"
|
|
8
|
+
puts banner
|
|
9
|
+
puts "\e[0m"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
# TODO: Add teplohod
|
|
15
|
+
def banner
|
|
16
|
+
%(
|
|
17
|
+
Welcome to #{Teplohod::NAME}!
|
|
18
|
+
Current version: #{Teplohod::VERSION}
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
Environment: #{environment}
|
|
22
|
+
Mount Point: #{options[:require]}
|
|
23
|
+
Log Level: #{logger.level}
|
|
24
|
+
Pid File Path: #{options[:pidfile]}
|
|
25
|
+
Daemonize: #{options[:daemonize]}
|
|
26
|
+
Web: #{options[:web]}
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Teplohod
|
|
2
|
+
class NoRequiredFile < ArgumentError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
module Initializers
|
|
6
|
+
class Boot
|
|
7
|
+
include Initializable
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
raise NoRequiredFile, "#{options[:require]} does not exist" unless File.exist?(options[:require])
|
|
11
|
+
require options[:require]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require 'teplohod/initializers/logger'
|
|
2
|
+
|
|
3
|
+
module Teplohod
|
|
4
|
+
class NoLogfileError < ArgumentError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module Initializers
|
|
8
|
+
class Daemonizer
|
|
9
|
+
include Initializable
|
|
10
|
+
|
|
11
|
+
def run
|
|
12
|
+
validate!
|
|
13
|
+
|
|
14
|
+
files_to_reopen = []
|
|
15
|
+
ObjectSpace.each_object(File) do |file|
|
|
16
|
+
files_to_reopen << file unless file.closed?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
::Process.daemon(true, true)
|
|
20
|
+
|
|
21
|
+
files_to_reopen.each do |file|
|
|
22
|
+
begin
|
|
23
|
+
file.reopen file.path, 'a+'
|
|
24
|
+
file.sync = true
|
|
25
|
+
rescue ::Exception
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
[$stdout, $stderr].each do |io|
|
|
30
|
+
File.open(options[:logfile], 'ab') do |f|
|
|
31
|
+
io.reopen(f)
|
|
32
|
+
end
|
|
33
|
+
io.sync = true
|
|
34
|
+
end
|
|
35
|
+
$stdin.reopen('/dev/null')
|
|
36
|
+
|
|
37
|
+
Teplohod::Initializers::Logger.run!
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def logfile?
|
|
43
|
+
options[:logfile]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def validate!
|
|
47
|
+
raise NoLogfileError, "You should specify a logfile if you're going to daemonize" unless logfile?
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'teplohod/logging'
|
|
2
|
+
|
|
3
|
+
module Teplohod
|
|
4
|
+
module Initializers
|
|
5
|
+
class Logger
|
|
6
|
+
include Initializable
|
|
7
|
+
|
|
8
|
+
def run
|
|
9
|
+
Teplohod::Logging.initialize_logger(options[:logfile]) if options[:logfile]
|
|
10
|
+
Teplohod.logger.level = ::Logger::DEBUG if options[:verbose]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'optparse'
|
|
2
|
+
|
|
3
|
+
require 'teplohod'
|
|
4
|
+
|
|
5
|
+
module Teplohod
|
|
6
|
+
module Initializers
|
|
7
|
+
class Options
|
|
8
|
+
include Initializable
|
|
9
|
+
|
|
10
|
+
def self.run!
|
|
11
|
+
new.run
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
cli_options = parse
|
|
16
|
+
Teplohod.options = options.merge cli_options
|
|
17
|
+
puts "Start #{Teplohod::NAME} with #{options}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def parse
|
|
23
|
+
opts = {}
|
|
24
|
+
parser = ::OptionParser.new do |o|
|
|
25
|
+
o.on '-d', '--daemon', 'Daemonize process' do |arg|
|
|
26
|
+
opts[:daemonize] = arg
|
|
27
|
+
end
|
|
28
|
+
o.on '-r', '--require [PATH|DIR]', 'Location to require' do |arg|
|
|
29
|
+
opts[:require] = arg
|
|
30
|
+
end
|
|
31
|
+
o.on '-e', '--environment ENV', 'Application environment' do |arg|
|
|
32
|
+
opts[:environment] = arg
|
|
33
|
+
end
|
|
34
|
+
o.on '-v', '--verbose', 'Print more verbose output' do |arg|
|
|
35
|
+
opts[:verbose] = arg
|
|
36
|
+
end
|
|
37
|
+
o.on '-L', '--logfile PATH', 'path to writable logfile' do |arg|
|
|
38
|
+
opts[:logfile] = arg
|
|
39
|
+
end
|
|
40
|
+
o.on '-P', '--pidfile PATH', 'path to pidfile' do |arg|
|
|
41
|
+
opts[:pidfile] = arg
|
|
42
|
+
end
|
|
43
|
+
o.on '-w', '--web', 'run web dashboard' do |arg|
|
|
44
|
+
opts[:web] = arg
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
parser.banner = 'teplohod [options]'
|
|
48
|
+
parser.on_tail '-h', '--help', 'Show help' do
|
|
49
|
+
puts parser.to_s
|
|
50
|
+
exit 1
|
|
51
|
+
end
|
|
52
|
+
parser.parse! ARGV
|
|
53
|
+
opts
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Teplohod
|
|
2
|
+
module Initializers
|
|
3
|
+
class Pid
|
|
4
|
+
include Initializable
|
|
5
|
+
|
|
6
|
+
def run
|
|
7
|
+
if path = options[:pidfile]
|
|
8
|
+
pidfile = File.expand_path(path)
|
|
9
|
+
File.open(pidfile, 'w') do |f|
|
|
10
|
+
f.puts ::Process.pid
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
|
|
3
|
+
require 'teplohod/utils'
|
|
4
|
+
|
|
5
|
+
module Teplohod
|
|
6
|
+
module Initializable
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
include Utils
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def run!
|
|
12
|
+
new.run
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
require 'teplohod/initializers/options'
|
|
19
|
+
require 'teplohod/initializers/banner'
|
|
20
|
+
require 'teplohod/initializers/logger'
|
|
21
|
+
require 'teplohod/initializers/daemonizer'
|
|
22
|
+
require 'teplohod/initializers/pid'
|
|
23
|
+
require 'teplohod/initializers/redis'
|
|
24
|
+
require 'teplohod/initializers/boot'
|
|
25
|
+
require 'teplohod/initializers/web'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'teplohod/utils'
|
|
2
|
+
|
|
3
|
+
module Teplohod
|
|
4
|
+
class Launcher
|
|
5
|
+
include Utils
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@providers = Teplohod::Manager.providers
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def run
|
|
12
|
+
log { logger.info { 'Running..' } }
|
|
13
|
+
@providers.each(&:run)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def stop
|
|
17
|
+
log { logger.info { 'Stopping..' } }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def log
|
|
23
|
+
logger.tagged 'system' do
|
|
24
|
+
yield
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'time'
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'fcntl'
|
|
5
|
+
require 'active_support/tagged_logging'
|
|
6
|
+
|
|
7
|
+
module Teplohod
|
|
8
|
+
module Logging
|
|
9
|
+
class Formatter
|
|
10
|
+
include ActiveSupport::TaggedLogging::Formatter
|
|
11
|
+
|
|
12
|
+
def call(severity, time, _program_name, message)
|
|
13
|
+
tags = current_tags.join('/')
|
|
14
|
+
"#{time.utc.iso8601(3)} #{::Process.pid} TID-#{Thread.current.object_id.to_s(36)} #{severity}: [#{tags}] #{message}\n"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.initialize_logger(log_target = STDOUT)
|
|
19
|
+
oldlogger = defined?(@logger) ? @logger : nil
|
|
20
|
+
@logger = ActiveSupport::TaggedLogging.new(Logger.new(log_target))
|
|
21
|
+
@logger.formatter = Formatter.new
|
|
22
|
+
@logger.level = Logger::INFO
|
|
23
|
+
oldlogger.close if oldlogger
|
|
24
|
+
@logger
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.logger
|
|
28
|
+
defined?(@logger) ? @logger : initialize_logger
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.logger=(log)
|
|
32
|
+
@logger = (log ? log : Logger.new(File::NULL))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# This reopens ALL logfiles in the process that have been rotated
|
|
36
|
+
# using logrotate(8) (without copytruncate) or similar tools.
|
|
37
|
+
# A +File+ object is considered for reopening if it is:
|
|
38
|
+
# 1) opened with the O_APPEND and O_WRONLY flags
|
|
39
|
+
# 2) the current open file handle does not match its original open path
|
|
40
|
+
# 3) unbuffered (as far as userspace buffering goes, not O_SYNC)
|
|
41
|
+
# Returns the number of files reopened
|
|
42
|
+
def self.reopen_logs
|
|
43
|
+
to_reopen = []
|
|
44
|
+
append_flags = File::WRONLY | File::APPEND
|
|
45
|
+
|
|
46
|
+
ObjectSpace.each_object(File) do |fp|
|
|
47
|
+
begin
|
|
48
|
+
if !fp.closed? && fp.stat.file? && fp.sync && (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
|
|
49
|
+
to_reopen << fp
|
|
50
|
+
end
|
|
51
|
+
rescue IOError, Errno::EBADF
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
nr = 0
|
|
56
|
+
to_reopen.each do |fp|
|
|
57
|
+
orig_st = begin
|
|
58
|
+
fp.stat
|
|
59
|
+
rescue IOError, Errno::EBADF
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
begin
|
|
64
|
+
b = File.stat(fp.path)
|
|
65
|
+
next if orig_st.ino == b.ino && orig_st.dev == b.dev
|
|
66
|
+
rescue Errno::ENOENT
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
begin
|
|
70
|
+
File.open(fp.path, 'a') { |tmpfp| fp.reopen(tmpfp) }
|
|
71
|
+
fp.sync = true
|
|
72
|
+
nr += 1
|
|
73
|
+
rescue IOError, Errno::EBADF
|
|
74
|
+
# not much we can do...
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
nr
|
|
78
|
+
rescue RuntimeError => ex
|
|
79
|
+
# RuntimeError: ObjectSpace is disabled; each_object will only work with Class, pass -X+O to enable
|
|
80
|
+
puts "Unable to reopen logs: #{ex.message}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def logger
|
|
84
|
+
Teplohod::Logging.logger
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'teplohod/stats'
|
|
2
|
+
|
|
3
|
+
module Teplohod
|
|
4
|
+
class Manager
|
|
5
|
+
class << self
|
|
6
|
+
def providers
|
|
7
|
+
@providers ||= []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def register_provider(provider)
|
|
11
|
+
providers << provider
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def reload_providers!
|
|
15
|
+
@providers = []
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require 'teplohod/utils'
|
|
2
|
+
require 'teplohod/processor/equality'
|
|
3
|
+
|
|
4
|
+
module Teplohod
|
|
5
|
+
module Pipeline
|
|
6
|
+
class Base
|
|
7
|
+
include Utils
|
|
8
|
+
attr_reader :key, :timeout
|
|
9
|
+
attr_accessor :provider
|
|
10
|
+
|
|
11
|
+
def initialize(key: self.class.to_s.underscore, timeout: 5, loop: true)
|
|
12
|
+
@key = key
|
|
13
|
+
@timeout = timeout
|
|
14
|
+
@loop = loop
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parent
|
|
18
|
+
@parent ||= self.class.parent
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def queue
|
|
22
|
+
@queue ||= self.class.queue
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def processor
|
|
26
|
+
@processor = @processor || self.class.processor || Teplohod::Processor::Equality.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def run
|
|
30
|
+
on_empty_load if queue.empty?
|
|
31
|
+
|
|
32
|
+
return if parent.nil?
|
|
33
|
+
threads thread_name do
|
|
34
|
+
log { logger.info { 'loop started' } }
|
|
35
|
+
loop do
|
|
36
|
+
while element = parent.queue.pop
|
|
37
|
+
queue.push process(element)
|
|
38
|
+
Teplohod::Stats.increment(:processed_jobs, self)
|
|
39
|
+
end
|
|
40
|
+
log { logger.warn { "parent queue is clear, sleep #{timeout} sec"} }
|
|
41
|
+
parent.on_finish
|
|
42
|
+
break unless @loop
|
|
43
|
+
sleep timeout
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def name
|
|
49
|
+
"#{self.class}#name is not implemented"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def description
|
|
53
|
+
"#{self.class}#description is not implemented"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
protected
|
|
57
|
+
|
|
58
|
+
def process(data)
|
|
59
|
+
result = processor.process data
|
|
60
|
+
log { logger.info { "processed #{data}" } }
|
|
61
|
+
result
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def on_finish
|
|
65
|
+
log { logger.warn { '#on_finish does nothing' } }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def on_empty_load
|
|
69
|
+
log { logger.warn { '#on_empty_load does nothing' } }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def thread_name
|
|
75
|
+
[provider&.key, key].join('/')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class << self
|
|
79
|
+
attr_accessor :parent, :queue, :processor
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def log
|
|
83
|
+
logger.tagged provider&.key, key do
|
|
84
|
+
yield
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|