hot_potato 0.12.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE +19 -0
- data/Rakefile +25 -0
- data/bin/hotpotato +14 -0
- data/hot_potato.gemspec +28 -0
- data/lib/hot_potato/admin/public/admin.css +30 -0
- data/lib/hot_potato/admin/views/index.erb +58 -0
- data/lib/hot_potato/admin.rb +67 -0
- data/lib/hot_potato/app_task.rb +42 -0
- data/lib/hot_potato/app_task_info.rb +80 -0
- data/lib/hot_potato/app_task_server.rb +92 -0
- data/lib/hot_potato/cache.rb +45 -0
- data/lib/hot_potato/core.rb +62 -0
- data/lib/hot_potato/dsl.rb +172 -0
- data/lib/hot_potato/faucet.rb +41 -0
- data/lib/hot_potato/generate.rb +55 -0
- data/lib/hot_potato/generate_app_task.rb +41 -0
- data/lib/hot_potato/queue_logger.rb +33 -0
- data/lib/hot_potato/sink.rb +33 -0
- data/lib/hot_potato/supervisor_info.rb +64 -0
- data/lib/hot_potato/supervisor_server.rb +225 -0
- data/lib/hot_potato/templates/Gemfile +6 -0
- data/lib/hot_potato/templates/Rakefile +9 -0
- data/lib/hot_potato/templates/admin +4 -0
- data/lib/hot_potato/templates/app_task +4 -0
- data/lib/hot_potato/templates/boot.rb +21 -0
- data/lib/hot_potato/templates/config.yml +11 -0
- data/lib/hot_potato/templates/development.rb +0 -0
- data/lib/hot_potato/templates/generate +4 -0
- data/lib/hot_potato/templates/production.rb +0 -0
- data/lib/hot_potato/templates/routes.rb +3 -0
- data/lib/hot_potato/templates/supervisor +4 -0
- data/lib/hot_potato/templates/template_faucet.rb +8 -0
- data/lib/hot_potato/templates/template_sink.rb +7 -0
- data/lib/hot_potato/templates/template_worker.rb +8 -0
- data/lib/hot_potato/templates/test.rb +0 -0
- data/lib/hot_potato/utils.rb +43 -0
- data/lib/hot_potato/version.rb +3 -0
- data/lib/hot_potato/worker.rb +40 -0
- data/lib/hot_potato.rb +20 -0
- data/readme.md +219 -0
- data/test/helper.rb +7 -0
- data/test/version_test.rb +9 -0
- metadata +166 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010-2011 Darian Shimy
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'rake/rdoctask'
|
7
|
+
|
8
|
+
desc 'Default: run unit tests.'
|
9
|
+
task :default => :test
|
10
|
+
|
11
|
+
desc 'Run the Hot Potato tests.'
|
12
|
+
Rake::TestTask.new(:test) do |t|
|
13
|
+
t.libs << 'lib' << 'test'
|
14
|
+
t.pattern = 'test/*_test.rb'
|
15
|
+
t.verbose = true
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Generate documentation for Hot Potato.'
|
19
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'rdoc'
|
21
|
+
rdoc.title = 'Hot Potato'
|
22
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
23
|
+
rdoc.rdoc_files.include('README')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
data/bin/hotpotato
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hot_potato/version'
|
5
|
+
require 'hot_potato/generate'
|
6
|
+
|
7
|
+
app_path = ARGV.first
|
8
|
+
unless app_path
|
9
|
+
puts "Usage: hotpotato [-v|--version] app_name"
|
10
|
+
exit(1)
|
11
|
+
end
|
12
|
+
|
13
|
+
puts "Hot Potato (v #{HotPotato::VERSION})"
|
14
|
+
HotPotato::Generate.new app_path
|
data/hot_potato.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "hot_potato/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "hot_potato"
|
7
|
+
s.version = HotPotato::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Darian Shimy"]
|
10
|
+
s.email = ["dshimy@gmail.com"]
|
11
|
+
s.homepage = "http://github.com/dshimy/hotpotato"
|
12
|
+
s.summary = %q{A Real-time Processing Framework}
|
13
|
+
s.description = %q{Hot Potato is an open source real-time processing framework written in Ruby. Originally designed to process the Twitter firehose at 3,000+ tweets per second, it has been extended to support any type of streaming data as input or output to the framework. The framework excels with applications such as, social media analysis, log processing, fraud prevention, spam detection, instant messaging, and many others that include the processing of streaming data.}
|
14
|
+
|
15
|
+
s.add_dependency 'json'
|
16
|
+
s.add_dependency 'redis'
|
17
|
+
s.add_dependency 'bunny'
|
18
|
+
s.add_dependency 'sinatra'
|
19
|
+
s.add_dependency "vegas"
|
20
|
+
s.add_dependency "snappy"
|
21
|
+
|
22
|
+
s.rubyforge_project = "hot_potato"
|
23
|
+
|
24
|
+
s.files = `git ls-files`.split("\n")
|
25
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
26
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
27
|
+
s.require_paths = ["lib"]
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
body {
|
2
|
+
font-family: helvetica, sans-serif;
|
3
|
+
font-size: 12px;
|
4
|
+
}
|
5
|
+
h1 {
|
6
|
+
margin: 0 0 20px 0;
|
7
|
+
font-size: 24px;
|
8
|
+
}
|
9
|
+
h2 {
|
10
|
+
margin: 0 0 5px 0;
|
11
|
+
font-size: 20px;
|
12
|
+
}
|
13
|
+
table {
|
14
|
+
width: 900px;
|
15
|
+
border: 1px solid #ccc;
|
16
|
+
border-collapse: collapse;
|
17
|
+
margin-bottom: 20px;
|
18
|
+
}
|
19
|
+
table tr th {
|
20
|
+
border: 1px solid #ccc;
|
21
|
+
background: #EC7800;
|
22
|
+
color: #fff;
|
23
|
+
padding: 4px;
|
24
|
+
|
25
|
+
}
|
26
|
+
table tr td {
|
27
|
+
border: 1px solid #ccc;
|
28
|
+
padding: 2px 8px;
|
29
|
+
text-align: center;
|
30
|
+
}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
5
|
+
<title>Hot Potato Admin Server</title>
|
6
|
+
<link rel='stylesheet' href='/admin.css' type='text/css' media="screen">
|
7
|
+
<meta http-equiv="refresh" content="30">
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<h1>Hot Potato Admin Server</h1>
|
11
|
+
<h2>Machines</h2>
|
12
|
+
<table>
|
13
|
+
<tr>
|
14
|
+
<th>Hostname</th>
|
15
|
+
<th>Process ID</th>
|
16
|
+
<th>Started</th>
|
17
|
+
<th>Last Heartbeat</th>
|
18
|
+
<th>AppTasks</th>
|
19
|
+
</tr>
|
20
|
+
<% @machines.each do |m| %>
|
21
|
+
<tr>
|
22
|
+
<td><%= m.hostname %></td>
|
23
|
+
<td><%= m.pid %></td>
|
24
|
+
<td><%= format_date(m.started_at) %></td>
|
25
|
+
<td><%= format_date(m.updated_at) %></td>
|
26
|
+
<td><%= m.app_tasks %></td>
|
27
|
+
</tr>
|
28
|
+
<% end %>
|
29
|
+
</table>
|
30
|
+
|
31
|
+
<h2>App Tasks</h2>
|
32
|
+
<table>
|
33
|
+
<tr>
|
34
|
+
<th>Hostname</th>
|
35
|
+
<th>Class</th>
|
36
|
+
<th>Type</th>
|
37
|
+
<th>Process ID</th>
|
38
|
+
<th>Started</th>
|
39
|
+
<th>Last Heartbeat</th>
|
40
|
+
<th>Requests In</th>
|
41
|
+
<th>Requests Out</th>
|
42
|
+
</tr>
|
43
|
+
<% @apptasks.each do |a| %>
|
44
|
+
<tr>
|
45
|
+
<td><%= a.hostname %></td>
|
46
|
+
<td><%= a.classname %></td>
|
47
|
+
<td><%= a.type %></td>
|
48
|
+
<td><%= a.pid %></td>
|
49
|
+
<td><%= format_date(a.started_at) %></td>
|
50
|
+
<td><%= format_date(a.updated_at) %></td>
|
51
|
+
<td><%= a.requests_in %></td>
|
52
|
+
<td><%= a.requests_out %></td>
|
53
|
+
</tr>
|
54
|
+
<% end %>
|
55
|
+
</table>
|
56
|
+
|
57
|
+
</body>
|
58
|
+
</html>
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'redis'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module HotPotato
|
6
|
+
|
7
|
+
# The admin server is a Sinatra-based application to display statistical and diagnostic information.
|
8
|
+
#
|
9
|
+
# The admin server can be managed from the command line:
|
10
|
+
#
|
11
|
+
# $ bin/admin --help
|
12
|
+
#
|
13
|
+
# Usage: ./admin [options]
|
14
|
+
#
|
15
|
+
# Vegas options:
|
16
|
+
# -K, --kill kill the running process and exit
|
17
|
+
# -S, --status display the current running PID and URL then quit
|
18
|
+
# -s, --server SERVER serve using SERVER (thin/mongrel/webrick)
|
19
|
+
# -o, --host HOST listen on HOST (default: 0.0.0.0)
|
20
|
+
# -p, --port PORT use PORT (default: 5678)
|
21
|
+
# -x, --no-proxy ignore env proxy settings (e.g. http_proxy)
|
22
|
+
# -e, --env ENVIRONMENT use ENVIRONMENT for defaults (default: development)
|
23
|
+
# -F, --foreground don't daemonize, run in the foreground
|
24
|
+
# -L, --no-launch don't launch the browser
|
25
|
+
# -d, --debug raise the log level to :debug (default: :info)
|
26
|
+
# --app-dir APP_DIR set the app dir where files are stored (default: ~/.vegas/Hot_Potato_Admin_Server)/)
|
27
|
+
# -P, --pid-file PID_FILE set the path to the pid file (default: app_dir/Hot_Potato_Admin_Server.pid)
|
28
|
+
# --log-file LOG_FILE set the path to the log file (default: app_dir/Hot_Potato_Admin_Server.log)
|
29
|
+
# --url-file URL_FILE set the path to the URL file (default: app_dir/Hot_Potato_Admin_Server.url)
|
30
|
+
#
|
31
|
+
# Common options:
|
32
|
+
# -h, --help Show this message
|
33
|
+
# --version Show version
|
34
|
+
#
|
35
|
+
# The page can be accessed at http://localhost:5678
|
36
|
+
class Admin < Sinatra::Base
|
37
|
+
|
38
|
+
include HotPotato::Core
|
39
|
+
|
40
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
41
|
+
|
42
|
+
set :views, "#{dir}/admin/views"
|
43
|
+
set :public, "#{dir}/admin/public"
|
44
|
+
set :static, true
|
45
|
+
|
46
|
+
helpers do
|
47
|
+
def format_date(d)
|
48
|
+
return "" unless d
|
49
|
+
d.strftime("%b %d, %Y %H:%M")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
get "/" do
|
54
|
+
@machines = []
|
55
|
+
stat.keys('hotpotato.supervisor.*').each do |key|
|
56
|
+
@machines << JSON.parse(stat.get(key))
|
57
|
+
end
|
58
|
+
@apptasks = []
|
59
|
+
stat.keys('hotpotato.apptask.*').each do |key|
|
60
|
+
@apptasks << JSON.parse(stat.get(key))
|
61
|
+
end
|
62
|
+
erb :index
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
module HotPotato
|
4
|
+
|
5
|
+
# AppTasks are the controllers in the framework. The Supervisor (See below) is responsible for
|
6
|
+
# starting AppTasks. There are three types: Faucets, Workers, and Sinks.
|
7
|
+
module AppTask
|
8
|
+
|
9
|
+
HEARTBEAT_INTERVAL = 20
|
10
|
+
|
11
|
+
include HotPotato::Core
|
12
|
+
|
13
|
+
# Used to keep AppTask statistics when a message is received.
|
14
|
+
def count_message_in
|
15
|
+
stat.incr "hotpotato.counter.apptask.#{Socket.gethostname}.#{self.class.name}.#{Process.pid}.messages_in"
|
16
|
+
stat.expire "hotpotato.counter.apptask.#{Socket.gethostname}.#{self.class.name}.#{Process.pid}.messages_in", 600
|
17
|
+
end
|
18
|
+
|
19
|
+
# Used to keep AppTask statistics when a message is sent.
|
20
|
+
def count_message_out
|
21
|
+
stat.incr "hotpotato.counter.apptask.#{Socket.gethostname}.#{self.class.name}.#{Process.pid}.messages_out"
|
22
|
+
stat.expire "hotpotato.counter.apptask.#{Socket.gethostname}.#{self.class.name}.#{Process.pid}.messages_out", 600
|
23
|
+
end
|
24
|
+
|
25
|
+
# Starts the HeartBeat service to maintain the process id table.
|
26
|
+
def start_heartbeat_service
|
27
|
+
ati = AppTaskInfo.new(:classname => self.class.name)
|
28
|
+
stat.set ati.key, ati.to_json, 120
|
29
|
+
|
30
|
+
Thread.new do
|
31
|
+
log.info "Thread created for AppTask [Heartbeat]"
|
32
|
+
loop do
|
33
|
+
ati.touch
|
34
|
+
stat.set ati.key, ati.to_json, 120
|
35
|
+
sleep HEARTBEAT_INTERVAL
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
class AppTaskInfo
|
4
|
+
|
5
|
+
include HotPotato::Core
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@data = {}
|
9
|
+
@data[:started_at] = options[:started_at] || Time.now
|
10
|
+
@data[:updated_at] = options[:updated_at] || @data[:started_at]
|
11
|
+
@data[:hostname] = options[:hostname] || Socket.gethostname
|
12
|
+
@data[:classname] = options[:classname] || "AppTask"
|
13
|
+
@data[:pid] = options[:pid] || Process.pid
|
14
|
+
end
|
15
|
+
|
16
|
+
def key
|
17
|
+
"hotpotato.apptask.#{@data[:hostname]}.#{@data[:classname]}.#{@data[:pid]}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
@data.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def type
|
25
|
+
return "Faucet" if Kernel.const_get(@data[:classname]).ancestors.include?(HotPotato::Faucet)
|
26
|
+
return "Worker" if Kernel.const_get(@data[:classname]).ancestors.include?(HotPotato::Worker)
|
27
|
+
return "Sink" if Kernel.const_get(@data[:classname]).ancestors.include?(HotPotato::Sink)
|
28
|
+
end
|
29
|
+
|
30
|
+
def classname
|
31
|
+
@data[:classname]
|
32
|
+
end
|
33
|
+
|
34
|
+
def hostname
|
35
|
+
@data[:hostname]
|
36
|
+
end
|
37
|
+
|
38
|
+
def requests_in
|
39
|
+
stat.get("hotpotato.counter.apptask.#{@data[:hostname]}.#{@data[:classname]}.#{@data[:pid]}.messages_in") || 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def requests_out
|
43
|
+
stat.get("hotpotato.counter.apptask.#{@data[:hostname]}.#{@data[:classname]}.#{@data[:pid]}.messages_out") || 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def pid
|
47
|
+
@data[:pid].to_i
|
48
|
+
end
|
49
|
+
|
50
|
+
def started_at
|
51
|
+
return nil unless @data[:started_at]
|
52
|
+
DateTime.parse(@data[:started_at])
|
53
|
+
end
|
54
|
+
|
55
|
+
def updated_at
|
56
|
+
return nil unless @data[:updated_at]
|
57
|
+
DateTime.parse(@data[:updated_at])
|
58
|
+
end
|
59
|
+
|
60
|
+
def touch
|
61
|
+
@data[:updated_at] = Time.now
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_json(*a)
|
65
|
+
result = @data
|
66
|
+
result["json_class"] = self.class.name
|
67
|
+
result.to_json(*a)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.json_create(o)
|
71
|
+
options = {}
|
72
|
+
options[:started_at] = o["started_at"]
|
73
|
+
options[:updated_at] = o["updated_at"]
|
74
|
+
options[:pid] = o["pid"]
|
75
|
+
options[:classname] = o["classname"]
|
76
|
+
options[:hostname] = o["hostname"]
|
77
|
+
new options
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'optparse'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module HotPotato
|
6
|
+
|
7
|
+
# This is used internally.
|
8
|
+
class AppTaskServer
|
9
|
+
|
10
|
+
include HotPotato::Core
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@options = load_options
|
14
|
+
@options.app_task_name, @options.mode = parse_options
|
15
|
+
trap("INT") { shutdown }
|
16
|
+
self.send(@options.mode)
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
set_logger(:queue_logger, :classname => classify(app_task_name))
|
21
|
+
Process.daemon
|
22
|
+
STDIN.reopen '/dev/null'
|
23
|
+
STDOUT.sync = true
|
24
|
+
STDERR.sync = true
|
25
|
+
run
|
26
|
+
end
|
27
|
+
|
28
|
+
def run
|
29
|
+
$0 = "Hot Potato AppTask [#{classify(@options.app_task_name)}]"
|
30
|
+
log.info "Starting Hot Potato AppTask #{HotPotato::VERSION} #{classify(@options.app_task_name)}"
|
31
|
+
app_task = @options.routes.find @options.app_task_name
|
32
|
+
if app_task
|
33
|
+
obj = Kernel.const_get(classify(app_task.classname)).new
|
34
|
+
begin
|
35
|
+
if app_task.source
|
36
|
+
obj.start app_task.source
|
37
|
+
else
|
38
|
+
obj.start
|
39
|
+
end
|
40
|
+
rescue Exception
|
41
|
+
log.fatal $!
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
else
|
45
|
+
puts "Could not find #{@options.app_task_name} in the route definition"
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_options
|
51
|
+
mode = :run
|
52
|
+
app_task_name = ""
|
53
|
+
op = OptionParser.new do |opts|
|
54
|
+
opts.banner = "Usage: #{$0} app_task_name [run|start]"
|
55
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
56
|
+
puts op
|
57
|
+
exit!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
begin
|
61
|
+
op.parse!
|
62
|
+
app_task_name = ARGV.shift
|
63
|
+
mode = (ARGV.shift || "run").to_sym
|
64
|
+
if ![:start, :run].include?(mode)
|
65
|
+
puts op
|
66
|
+
exit!
|
67
|
+
end
|
68
|
+
rescue
|
69
|
+
puts op
|
70
|
+
exit!
|
71
|
+
end
|
72
|
+
return app_task_name, mode
|
73
|
+
end
|
74
|
+
|
75
|
+
def shutdown
|
76
|
+
log.info "Stopping AppTask..."
|
77
|
+
exit!
|
78
|
+
end
|
79
|
+
|
80
|
+
def load_options
|
81
|
+
options = OpenStruct.new
|
82
|
+
options.mode = :run
|
83
|
+
options.hostname = Socket.gethostname
|
84
|
+
options.routes = HotPotato::Route.routes
|
85
|
+
options.app_task_name = ""
|
86
|
+
|
87
|
+
return options
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module HotPotato
|
2
|
+
|
3
|
+
class Cache
|
4
|
+
|
5
|
+
include HotPotato::Core
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
log.info "Initializing connection to Redis (#{config['redis_hostname']}:#{config['redis_port']})"
|
9
|
+
@@redis ||= Redis.new :host => config['redis_hostname'], :port => config['redis_port']
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(k)
|
13
|
+
@@redis.get(k)
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(k, v, ttl = nil)
|
17
|
+
@@redis.multi do
|
18
|
+
@@redis.set(k, v)
|
19
|
+
@@redis.expire(k, ttl) if ttl
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def getset(k, v)
|
24
|
+
@@redis.getset(k, v)
|
25
|
+
end
|
26
|
+
|
27
|
+
def keys(k)
|
28
|
+
@@redis.keys(k)
|
29
|
+
end
|
30
|
+
|
31
|
+
def del(k)
|
32
|
+
@@redis.del(k)
|
33
|
+
end
|
34
|
+
|
35
|
+
def expire(k, ttl)
|
36
|
+
@@redis.expire(k, ttl)
|
37
|
+
end
|
38
|
+
|
39
|
+
def incr(k)
|
40
|
+
@@redis.incr(k)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'snappy'
|
3
|
+
|
4
|
+
module HotPotato
|
5
|
+
|
6
|
+
module Core
|
7
|
+
|
8
|
+
CONFIG_PATH = "#{APP_PATH}/config/config.yml"
|
9
|
+
|
10
|
+
def set_logger(provider, options = {})
|
11
|
+
if provider == :queue_logger
|
12
|
+
@@log ||= QueueLogger.new(options)
|
13
|
+
else
|
14
|
+
@@log ||= Logger.new(STDOUT)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def log
|
19
|
+
@@log ||= Logger.new(STDOUT)
|
20
|
+
@@log.level = Logger::INFO
|
21
|
+
return @@log
|
22
|
+
end
|
23
|
+
|
24
|
+
def config
|
25
|
+
@@config ||= YAML.load_file(CONFIG_PATH)[RACK_ENV]
|
26
|
+
end
|
27
|
+
|
28
|
+
def stat
|
29
|
+
@@cache ||= Cache.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def queue_inject(name, message)
|
33
|
+
@@queue ||= Redis.new :host => config['redis_hostname'], :port => config['redis_port']
|
34
|
+
@@queue.publish name.to_sym, Snappy.deflate(message.to_json)
|
35
|
+
end
|
36
|
+
|
37
|
+
def queue_subscribe(name, &block)
|
38
|
+
queue ||= Redis.new :host => config['redis_hostname'], :port => config['redis_port']
|
39
|
+
queue.subscribe(*name) do |on|
|
40
|
+
on.message do |channel, message|
|
41
|
+
yield JSON.parse(Snappy.inflate(message))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def acquire_lock(key, duration = 10)
|
47
|
+
if !stat.getset("hotpotato.lock.#{key.to_s}", "1")
|
48
|
+
stat.expire "hotpotato.lock.#{key.to_s}", duration
|
49
|
+
return true
|
50
|
+
else
|
51
|
+
stat.expire "hotpotato.lock.#{key.to_s}", duration
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def release_lock(key)
|
57
|
+
stat.del("hotpotato.lock.#{key.to_s}")
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|