teplohod 0.0.1.alpha1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Teplohod
2
+ VERSION = '0.0.1.alpha1'.freeze
3
+ end
@@ -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