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
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
|
2
|
+
require 'teplohod/utils'
|
|
3
|
+
|
|
4
|
+
module Teplohod
|
|
5
|
+
module Provider
|
|
6
|
+
class Base
|
|
7
|
+
include Utils
|
|
8
|
+
|
|
9
|
+
attr_reader :key
|
|
10
|
+
|
|
11
|
+
def initialize(key: self.class.to_s.underscore)
|
|
12
|
+
@key = key
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def tail
|
|
16
|
+
@tail ||= self.class.tail
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def pipelines
|
|
20
|
+
@pipelines ||= []
|
|
21
|
+
if @pipelines.empty?
|
|
22
|
+
pipeline = tail
|
|
23
|
+
while pipeline
|
|
24
|
+
@pipelines << pipeline
|
|
25
|
+
pipeline = pipeline.parent
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
@pipelines
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def run
|
|
32
|
+
logger.tagged key do
|
|
33
|
+
logger.info { 'starting...' }
|
|
34
|
+
end
|
|
35
|
+
pipeline = tail
|
|
36
|
+
while pipeline
|
|
37
|
+
pipeline.provider = self
|
|
38
|
+
pipeline.run
|
|
39
|
+
pipeline = pipeline.parent
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class << self
|
|
44
|
+
attr_accessor :tail
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Teplohod
|
|
2
|
+
module Queue
|
|
3
|
+
class Base
|
|
4
|
+
def pop
|
|
5
|
+
raise NotImplementedError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def push(_data)
|
|
9
|
+
raise NotImplementedError
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def length
|
|
13
|
+
raise NotImplementedError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def empty?
|
|
17
|
+
length.zero?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def last(count)
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'teplohod/queue/base'
|
|
2
|
+
|
|
3
|
+
module Teplohod
|
|
4
|
+
module Queue
|
|
5
|
+
module Memory
|
|
6
|
+
class Base < ::Teplohod::Queue::Base
|
|
7
|
+
def initialize
|
|
8
|
+
@data = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def all
|
|
12
|
+
@data
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def push(value)
|
|
16
|
+
@data.push value
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def pop
|
|
20
|
+
@data.pop
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def length
|
|
24
|
+
@data.length
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def last(count)
|
|
28
|
+
@data.take(count)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'teplohod/utils'
|
|
2
|
+
|
|
3
|
+
module Teplohod
|
|
4
|
+
module SignalHandler
|
|
5
|
+
class << self
|
|
6
|
+
include Utils
|
|
7
|
+
|
|
8
|
+
def handle(signal)
|
|
9
|
+
logger.debug "Got #{signal} signal"
|
|
10
|
+
case signal
|
|
11
|
+
when 'INT'
|
|
12
|
+
# Handle Ctrl-C in JRuby like MRI
|
|
13
|
+
# http://jira.codehaus.org/browse/JRUBY-4637
|
|
14
|
+
raise Interrupt
|
|
15
|
+
when 'TERM'
|
|
16
|
+
# Heroku sends TERM and then waits 10 seconds for process to exit.
|
|
17
|
+
raise Interrupt
|
|
18
|
+
when 'USR1'
|
|
19
|
+
logger.info 'Received USR1, no longer accepting new work'
|
|
20
|
+
launcher.quiet
|
|
21
|
+
when 'USR2'
|
|
22
|
+
if options[:logfile]
|
|
23
|
+
logger.info 'Received USR2, reopening log file'
|
|
24
|
+
Teplohod::Logging.reopen_logs
|
|
25
|
+
end
|
|
26
|
+
when 'TTIN'
|
|
27
|
+
Thread.list.each do |thread|
|
|
28
|
+
logger.warn "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
|
|
29
|
+
if thread.backtrace
|
|
30
|
+
logger.warn thread.backtrace.join("\n")
|
|
31
|
+
else
|
|
32
|
+
logger.warn '<no backtrace available>'
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'teplohod/utils'
|
|
2
|
+
|
|
3
|
+
# TODO: use statsd or something more flexible
|
|
4
|
+
module Teplohod
|
|
5
|
+
module Stats
|
|
6
|
+
include Utils
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
PROVIDERS = 'providers'.freeze
|
|
10
|
+
|
|
11
|
+
def all(target)
|
|
12
|
+
key = target.key
|
|
13
|
+
processed = redis.with { |conn| conn.get "#{key}:processed" } || 0
|
|
14
|
+
{
|
|
15
|
+
processed: processed
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def increment(type, target)
|
|
20
|
+
send type, target.key
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def processed_jobs(key)
|
|
26
|
+
redis.with do |conn|
|
|
27
|
+
conn.incr "#{key}:processed"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def redis
|
|
32
|
+
@redis ||= Teplohod.redis
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Teplohod
|
|
2
|
+
module Utils
|
|
3
|
+
def threads(_name, count: 1)
|
|
4
|
+
threads = []
|
|
5
|
+
count.times do
|
|
6
|
+
thread = Thread.new do
|
|
7
|
+
yield
|
|
8
|
+
end
|
|
9
|
+
thread.abort_on_exception = true
|
|
10
|
+
threads << thread
|
|
11
|
+
end
|
|
12
|
+
threads
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def environment
|
|
16
|
+
Teplohod.environment
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def logger
|
|
20
|
+
Teplohod.logger
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def options
|
|
24
|
+
Teplohod.options
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def redis
|
|
28
|
+
Teplohod.redis
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/teplohod/web.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'sinatra'
|
|
2
|
+
require 'erb'
|
|
3
|
+
|
|
4
|
+
require 'teplohod'
|
|
5
|
+
|
|
6
|
+
module Teplohod
|
|
7
|
+
class Web < Sinatra::Base
|
|
8
|
+
set :views, File.expand_path("#{File.dirname(__FILE__)}/../../web/views")
|
|
9
|
+
set :public_folder, File.expand_path("#{File.dirname(__FILE__)}/../../web/assets")
|
|
10
|
+
|
|
11
|
+
set :server, :thin
|
|
12
|
+
set :logging, false
|
|
13
|
+
|
|
14
|
+
get '/' do
|
|
15
|
+
providers = Teplohod::Manager.providers
|
|
16
|
+
erb :index, locals: { providers: providers }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/teplohod.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
|
2
|
+
require 'redis'
|
|
3
|
+
require 'connection_pool'
|
|
4
|
+
require 'redis-namespace'
|
|
5
|
+
|
|
6
|
+
require 'teplohod/version'
|
|
7
|
+
require 'teplohod/utils'
|
|
8
|
+
require 'teplohod/logging'
|
|
9
|
+
|
|
10
|
+
require 'teplohod/manager'
|
|
11
|
+
|
|
12
|
+
require 'teplohod/queue/base'
|
|
13
|
+
require 'teplohod/provider/base'
|
|
14
|
+
require 'teplohod/pipeline/base'
|
|
15
|
+
require 'teplohod/processor/base'
|
|
16
|
+
|
|
17
|
+
module Teplohod
|
|
18
|
+
NAME = 'Teplohod'.freeze
|
|
19
|
+
REDIS_NAMESPACE = 'teplohod'.freeze
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
DEFAULTS = {
|
|
23
|
+
environment: (ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'),
|
|
24
|
+
require: './parser.rb',
|
|
25
|
+
verbose: false,
|
|
26
|
+
daemonize: false
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
def options
|
|
30
|
+
@options ||= DEFAULTS.dup
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
attr_writer :options
|
|
34
|
+
attr_reader :redis
|
|
35
|
+
|
|
36
|
+
def reload_options!
|
|
37
|
+
@options = DEFAULTS.dup
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def logger
|
|
41
|
+
Teplohod::Logging.logger
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def logger=(log)
|
|
45
|
+
Teplohod::Logging.logger = log
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# TODO add configuartion from options
|
|
49
|
+
def redis=(redis)
|
|
50
|
+
@redis ||= ConnectionPool.new(options) { Redis::Namespace.new(REDIS_NAMESPACE, redis: redis) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def environment
|
|
54
|
+
options[:environment]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/teplohod.gemspec
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'teplohod/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = 'teplohod'
|
|
8
|
+
spec.version = Teplohod::VERSION
|
|
9
|
+
spec.authors = ['Sergey Kuchmistov']
|
|
10
|
+
spec.email = ['sergey.kuchmistov@gmail.com']
|
|
11
|
+
|
|
12
|
+
spec.summary = 'Framework for persisted ongoing data processing'
|
|
13
|
+
spec.description = 'Framework for persisted ongoing data processing like scraping or analyzing.'
|
|
14
|
+
spec.homepage = 'http://raisintech.com/teplohod'
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
17
|
+
f.match(%r{^(test)/})
|
|
18
|
+
end
|
|
19
|
+
spec.executables = ['teplohod']
|
|
20
|
+
spec.require_paths = ['lib']
|
|
21
|
+
|
|
22
|
+
spec.add_dependency 'sinatra', '~> 2.0.0.beta2'
|
|
23
|
+
spec.add_dependency 'activesupport'
|
|
24
|
+
spec.add_dependency 'redis'
|
|
25
|
+
spec.add_dependency 'connection_pool'
|
|
26
|
+
spec.add_dependency 'redis-namespace'
|
|
27
|
+
spec.add_dependency 'thin'
|
|
28
|
+
|
|
29
|
+
spec.add_development_dependency 'bundler', '~> 1.13'
|
|
30
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
|
31
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
|
32
|
+
spec.add_development_dependency 'simplecov'
|
|
33
|
+
spec.add_development_dependency 'simplecov-console'
|
|
34
|
+
spec.add_development_dependency 'minitest-reporters'
|
|
35
|
+
spec.add_development_dependency 'fakeredis'
|
|
36
|
+
spec.add_development_dependency 'rubocop'
|
|
37
|
+
spec.add_development_dependency 'yard'
|
|
38
|
+
end
|