scout 5.3.5 → 5.4.4.alpha
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.
- data/.gitignore +6 -0
- data/CHANGELOG +0 -12
- data/Gemfile +4 -0
- data/README +8 -0
- data/Rakefile +6 -108
- data/bin/scout +1 -0
- data/lib/scout.rb +5 -4
- data/lib/scout/command.rb +11 -12
- data/lib/scout/command/install.rb +1 -1
- data/lib/scout/command/run.rb +13 -1
- data/lib/scout/command/sign.rb +2 -8
- data/lib/scout/command/stream.rb +50 -0
- data/lib/scout/command/test.rb +1 -1
- data/lib/scout/daemon_spawn.rb +215 -0
- data/lib/scout/plugin.rb +20 -1
- data/lib/scout/server.rb +16 -111
- data/lib/scout/server_base.rb +100 -0
- data/lib/scout/streamer.rb +162 -0
- data/lib/scout/streamer_control.rb +43 -0
- data/lib/scout/version.rb +3 -0
- data/scout.gemspec +27 -0
- data/test/plugins/disk_usage.rb +86 -0
- data/test/scout_test.rb +598 -0
- data/vendor/pusher-gem/Gemfile +2 -0
- data/vendor/pusher-gem/LICENSE +20 -0
- data/vendor/pusher-gem/README.md +80 -0
- data/vendor/pusher-gem/Rakefile +11 -0
- data/vendor/pusher-gem/examples/async_message.rb +28 -0
- data/vendor/pusher-gem/lib/pusher.rb +107 -0
- data/vendor/pusher-gem/lib/pusher/channel.rb +154 -0
- data/vendor/pusher-gem/lib/pusher/request.rb +107 -0
- data/vendor/pusher-gem/pusher.gemspec +28 -0
- data/vendor/pusher-gem/spec/channel_spec.rb +274 -0
- data/vendor/pusher-gem/spec/pusher_spec.rb +87 -0
- data/vendor/pusher-gem/spec/spec_helper.rb +13 -0
- data/vendor/ruby-hmac/History.txt +15 -0
- data/vendor/ruby-hmac/Manifest.txt +11 -0
- data/vendor/ruby-hmac/README.md +41 -0
- data/vendor/ruby-hmac/Rakefile +23 -0
- data/vendor/ruby-hmac/lib/hmac-md5.rb +11 -0
- data/vendor/ruby-hmac/lib/hmac-rmd160.rb +11 -0
- data/vendor/ruby-hmac/lib/hmac-sha1.rb +11 -0
- data/vendor/ruby-hmac/lib/hmac-sha2.rb +25 -0
- data/vendor/ruby-hmac/lib/hmac.rb +118 -0
- data/vendor/ruby-hmac/lib/ruby_hmac.rb +2 -0
- data/vendor/ruby-hmac/ruby-hmac.gemspec +33 -0
- data/vendor/ruby-hmac/test/test_hmac.rb +89 -0
- data/vendor/signature/.document +5 -0
- data/vendor/signature/.gitignore +21 -0
- data/vendor/signature/Gemfile +3 -0
- data/vendor/signature/Gemfile.lock +29 -0
- data/vendor/signature/LICENSE +20 -0
- data/vendor/signature/README.md +55 -0
- data/vendor/signature/Rakefile +2 -0
- data/vendor/signature/VERSION +1 -0
- data/vendor/signature/lib/signature.rb +142 -0
- data/vendor/signature/lib/signature/version.rb +3 -0
- data/vendor/signature/signature.gemspec +22 -0
- data/vendor/signature/spec/signature_spec.rb +176 -0
- data/vendor/signature/spec/spec_helper.rb +10 -0
- data/vendor/util/lib/core_extensions.rb +60 -0
- metadata +120 -84
- data/AUTHORS +0 -4
- data/COPYING +0 -340
- data/INSTALL +0 -18
- data/TODO +0 -6
data/.gitignore
ADDED
data/CHANGELOG
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
== Master
|
|
2
|
-
|
|
3
|
-
== 5.3.5
|
|
4
|
-
|
|
5
|
-
* Moved proxy support to explicit command line flags --http_proxy and https_proxy
|
|
6
|
-
* fixed two unused variables that were causing warnings under 1.9.3
|
|
7
|
-
|
|
8
|
-
== 5.3.4
|
|
9
|
-
|
|
10
|
-
* Incorporating sleep interval into Server#time_to_checkin?
|
|
11
|
-
* Added proxy support command line flags
|
|
12
|
-
|
|
13
1
|
== 5.3.3
|
|
14
2
|
|
|
15
3
|
* Sending embedded options to server for local & plugin overrides.
|
data/Gemfile
ADDED
data/README
CHANGED
|
@@ -64,3 +64,11 @@ It's often helpful to log the output to a file. To do so:
|
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
For additional help, please visit http://scoutapp.com
|
|
67
|
+
|
|
68
|
+
== Credits / Contact
|
|
69
|
+
|
|
70
|
+
Contact support@scoutapp.com with questions.
|
|
71
|
+
|
|
72
|
+
Primary maintainers: Andre Lewis (andre@scoutapp.com) and Derek Haynes (derek@scoutapp.com)
|
|
73
|
+
|
|
74
|
+
Many thanks to James Edward Gray II, Charles Brian Quinn, and Matt Todd for early work on the Scout agent!
|
data/Rakefile
CHANGED
|
@@ -1,109 +1,7 @@
|
|
|
1
|
-
require "
|
|
2
|
-
require "rake/testtask"
|
|
3
|
-
require "rake/gempackagetask"
|
|
4
|
-
require "rake/contrib/rubyforgepublisher"
|
|
5
|
-
require "net/ssh"
|
|
1
|
+
require "bundler/gem_tasks"
|
|
6
2
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
version = File.read(lib)[/^\s*VERSION\s*=\s*(['"])(\d\.\d\.\d)\1/, 2]
|
|
13
|
-
history = File.read("CHANGELOG").split(/^(===.*)/)
|
|
14
|
-
changes ||= history[0..2].join.strip
|
|
15
|
-
|
|
16
|
-
need_tar = true
|
|
17
|
-
need_zip = true
|
|
18
|
-
|
|
19
|
-
task :default => [:test]
|
|
20
|
-
|
|
21
|
-
Rake::TestTask.new do |test|
|
|
22
|
-
test.libs << "test"
|
|
23
|
-
test.test_files = [ "test/scout_test.rb" ]
|
|
24
|
-
test.verbose = true
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
Rake::RDocTask.new do |rdoc|
|
|
28
|
-
rdoc.main = "README"
|
|
29
|
-
rdoc.rdoc_dir = "doc/html"
|
|
30
|
-
rdoc.title = "Scout Client Documentation"
|
|
31
|
-
rdoc.rdoc_files.include( "README", "INSTALL",
|
|
32
|
-
"TODO", "CHANGELOG",
|
|
33
|
-
"AUTHORS", "COPYING",
|
|
34
|
-
"LICENSE", "lib/" )
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
spec = Gem::Specification.new do |spec|
|
|
38
|
-
spec.name = "scout"
|
|
39
|
-
spec.version = version
|
|
40
|
-
|
|
41
|
-
spec.platform = Gem::Platform::RUBY
|
|
42
|
-
spec.summary = "Scout makes monitoring and reporting on your web applications as flexible and simple as possible."
|
|
43
|
-
|
|
44
|
-
# TODO: test suite
|
|
45
|
-
# spec.test_suite_file = "test/ts_all.rb"
|
|
46
|
-
spec.files = Dir.glob("{bin,lib}/**/*.rb") +
|
|
47
|
-
Dir.glob("{data,vendor}/**/*") +
|
|
48
|
-
%w[Rakefile]
|
|
49
|
-
spec.executables = ["scout"]
|
|
50
|
-
|
|
51
|
-
spec.has_rdoc = true
|
|
52
|
-
spec.extra_rdoc_files = %w[ AUTHORS COPYING README INSTALL TODO CHANGELOG
|
|
53
|
-
LICENSE ]
|
|
54
|
-
spec.rdoc_options << "--title" << "Scout Client Documentation" <<
|
|
55
|
-
"--main" << "README"
|
|
56
|
-
|
|
57
|
-
spec.require_path = "lib"
|
|
58
|
-
|
|
59
|
-
spec.add_dependency "elif"
|
|
60
|
-
|
|
61
|
-
spec.author = "Scout Monitoring"
|
|
62
|
-
spec.email = "support@scoutapp.com"
|
|
63
|
-
spec.rubyforge_project = "scout"
|
|
64
|
-
spec.homepage = "http://scoutapp.com"
|
|
65
|
-
spec.description = <<END_DESC
|
|
66
|
-
Scout makes monitoring and reporting on your web applications as flexible and simple as possible.
|
|
67
|
-
END_DESC
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
|
71
|
-
pkg.need_zip = need_tar
|
|
72
|
-
pkg.need_tar = need_zip
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
desc "Publishes to Scout Gem Server and Rubyforge"
|
|
76
|
-
task :publish => [:package, :publish_rubyforge]
|
|
77
|
-
|
|
78
|
-
desc "Publishes Gem to Rubyforge"
|
|
79
|
-
task :publish_rubyforge => [:package] do
|
|
80
|
-
puts "Publishing on RubyForge"
|
|
81
|
-
forge = RubyForge.new
|
|
82
|
-
forge.configure
|
|
83
|
-
puts "Logging in"
|
|
84
|
-
forge.login
|
|
85
|
-
|
|
86
|
-
release = forge.userconfig
|
|
87
|
-
release["release_changes"] = File.read(File.join(dir, "CHANGELOG"))
|
|
88
|
-
release["preformatted"] = true
|
|
89
|
-
|
|
90
|
-
package = "pkg/#{spec.name}-#{version}"
|
|
91
|
-
files = %W[#{package}.tgz #{package}.zip #{package}.gem].compact
|
|
92
|
-
|
|
93
|
-
puts "Releasing #{spec.name}-#{version}"
|
|
94
|
-
forge.add_release(spec.rubyforge_project, spec.name, version, *files)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
desc "Upload current documentation to Scout Gem Server and RubyForge"
|
|
98
|
-
task :upload_docs => [:rdoc] do
|
|
99
|
-
sh "scp -r doc/html/* " +
|
|
100
|
-
"deploy@gems.scoutapp.com:/var/www/gems/docs"
|
|
101
|
-
|
|
102
|
-
config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
|
|
103
|
-
host = "#{config["username"]}@rubyforge.org"
|
|
104
|
-
|
|
105
|
-
remote_dir = "/var/www/gforge-projects/#{spec.rubyforge_project}"
|
|
106
|
-
local_dir = 'doc/html'
|
|
107
|
-
|
|
108
|
-
sh %{rsync -av --delete #{local_dir}/ #{host}:#{remote_dir}}
|
|
109
|
-
end
|
|
3
|
+
# Provides the Following tasks:
|
|
4
|
+
#
|
|
5
|
+
# rake build # Builds gem into the pkg directory
|
|
6
|
+
# rake install # Build and install the current version into system gems
|
|
7
|
+
# rake release # Create, tag, build, and publish the current version to rubygems.org
|
data/bin/scout
CHANGED
data/lib/scout.rb
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env ruby -wKU
|
|
2
|
-
|
|
3
|
-
module Scout
|
|
4
|
-
VERSION = "5.3.5".freeze
|
|
5
|
-
end
|
|
2
|
+
require "scout/version"
|
|
6
3
|
|
|
7
4
|
require "scout/command"
|
|
8
5
|
require "scout/plugin"
|
|
9
6
|
require "scout/plugin_options"
|
|
10
7
|
require "scout/scout_logger"
|
|
8
|
+
require "scout/server_base"
|
|
11
9
|
require "scout/server"
|
|
10
|
+
require "scout/streamer"
|
|
11
|
+
require "scout/daemon_spawn"
|
|
12
|
+
require "scout/streamer_control"
|
data/lib/scout/command.rb
CHANGED
|
@@ -72,16 +72,6 @@ module Scout
|
|
|
72
72
|
options[:server_name] = server_name
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
opts.on("--http-proxy URL", String,
|
|
76
|
-
"Optional http proxy for non-SSL traffic." ) do |http_proxy|
|
|
77
|
-
options[:http_proxy] = http_proxy
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
opts.on("--https-proxy URL", String,
|
|
81
|
-
"Optional https proxy for SSL traffic." ) do |https_proxy|
|
|
82
|
-
options[:https_proxy] = https_proxy
|
|
83
|
-
end
|
|
84
|
-
|
|
85
75
|
opts.separator " "
|
|
86
76
|
opts.separator "Common Options:"
|
|
87
77
|
opts.separator "--------------------------------------------------------------------------"
|
|
@@ -105,6 +95,16 @@ module Scout
|
|
|
105
95
|
options[:force] = bool
|
|
106
96
|
end
|
|
107
97
|
|
|
98
|
+
opts.on( "-p", "--plugin_ids PLUGINS", String,
|
|
99
|
+
"A subset of plugin ids, for streaming mode only - not generally used manually.") do |plugins|
|
|
100
|
+
options[:plugin_ids] = plugins.split(",").map(&:to_i)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
opts.on( "-k", "--streaming_key KEY", String,
|
|
104
|
+
"for streaming mode only - not generally used manually.") do |streaming_key|
|
|
105
|
+
options[:streaming_key] = streaming_key
|
|
106
|
+
end
|
|
107
|
+
|
|
108
108
|
opts.separator " "
|
|
109
109
|
opts.separator "Troubleshooting Options:"
|
|
110
110
|
opts.separator "--------------------------------------------------------------------------"
|
|
@@ -164,9 +164,8 @@ module Scout
|
|
|
164
164
|
@level = options[:level] || "info"
|
|
165
165
|
@force = options[:force] || false
|
|
166
166
|
@server_name = options[:server_name]
|
|
167
|
-
@http_proxy = options[:http_proxy] || ""
|
|
168
|
-
@https_proxy = options[:https_proxy] || ""
|
|
169
167
|
|
|
168
|
+
@options = options
|
|
170
169
|
@args = args
|
|
171
170
|
|
|
172
171
|
# create config dir if necessary
|
|
@@ -22,7 +22,7 @@ module Scout
|
|
|
22
22
|
|
|
23
23
|
puts "\nAttempting to contact the server..."
|
|
24
24
|
begin
|
|
25
|
-
Scout::Server.new(server, key, history, log
|
|
25
|
+
Scout::Server.new(server, key, history, log) do |scout|
|
|
26
26
|
scout.fetch_plan
|
|
27
27
|
scout.run_plugins_by_plan
|
|
28
28
|
end
|
data/lib/scout/command/run.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Scout
|
|
|
9
9
|
configuration_directory = config_dir
|
|
10
10
|
log.debug("Configuration directory is #{configuration_directory} ") if log
|
|
11
11
|
# TODO: too much external logic of command doing things TO server. This should be moved into the server class.
|
|
12
|
-
@scout = Scout::Server.new(server, key, history, log, server_name
|
|
12
|
+
@scout = Scout::Server.new(server, key, history, log, server_name)
|
|
13
13
|
@scout.load_history
|
|
14
14
|
|
|
15
15
|
unless $stdin.tty?
|
|
@@ -19,6 +19,18 @@ module Scout
|
|
|
19
19
|
|
|
20
20
|
@scout.fetch_plan
|
|
21
21
|
|
|
22
|
+
log.info "streamer command=#{@scout.streamer_command}"
|
|
23
|
+
# Spawn streamer if directed to, or stop it. @scout.streamer_command should only be [start|stop]
|
|
24
|
+
if @scout.streamer_command.is_a?(String) && @scout.streamer_command.start_with?("start") || @scout.streamer_command == "stop"
|
|
25
|
+
tokens = @scout.streamer_command.split(",")
|
|
26
|
+
tokens.shift # gets rid of the "start"
|
|
27
|
+
streaming_key=tokens.shift
|
|
28
|
+
plugin_ids = tokens.map(&:to_i)
|
|
29
|
+
stream=Scout::Command::Stream.new(@options.merge(:streaming_key=>streaming_key,:plugin_ids=>plugin_ids), [key, @scout.streamer_command])
|
|
30
|
+
stream.run
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Check in if appropriate
|
|
22
34
|
if @scout.new_plan || @scout.time_to_checkin? || @force
|
|
23
35
|
if @scout.new_plan
|
|
24
36
|
log.info("Now checking in with new plugin plan") if log
|
data/lib/scout/command/sign.rb
CHANGED
|
@@ -11,7 +11,7 @@ module Scout
|
|
|
11
11
|
VERIFY_MODE = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
|
12
12
|
|
|
13
13
|
def run
|
|
14
|
-
url = @args
|
|
14
|
+
url, *provided_options = @args
|
|
15
15
|
# read the plugin_code from the file specified
|
|
16
16
|
if url.nil? or url == ''
|
|
17
17
|
puts "Please specify the path to the plugin (scout sign /path/to/plugin.rb)"
|
|
@@ -35,13 +35,7 @@ module Scout
|
|
|
35
35
|
|
|
36
36
|
puts "Posting Signature..."
|
|
37
37
|
uri = URI.parse(url)
|
|
38
|
-
|
|
39
|
-
# take care of http/https proxy, if specified in command line options
|
|
40
|
-
# Given a blank string, the proxy_uri URI instance's host/port/user/pass will be nil
|
|
41
|
-
# Net::HTTP::Proxy returns a regular Net::HTTP class if the first argument (host) is nil
|
|
42
|
-
proxy_uri = URI.parse(uri.is_a?(URI::HTTPS) ? @https_proxy : @http_proxy)
|
|
43
|
-
http=Net::HTTP::Proxy(proxy_uri.host,proxy_uri.port,proxy_uri.user,proxy_uri.port).new(uri.host, uri.port)
|
|
44
|
-
|
|
38
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
45
39
|
if uri.is_a?(URI::HTTPS)
|
|
46
40
|
http.use_ssl = true
|
|
47
41
|
http.ca_file = CA_FILE
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env ruby -wKU
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
5
|
+
module Scout
|
|
6
|
+
class Command
|
|
7
|
+
class Stream < Command
|
|
8
|
+
PID_FILENAME="streamer.pid"
|
|
9
|
+
|
|
10
|
+
def run
|
|
11
|
+
@key = @args[0]
|
|
12
|
+
daemon_command = @args[1]
|
|
13
|
+
@plugin_ids = @options[:plugin_ids]
|
|
14
|
+
|
|
15
|
+
if !@key
|
|
16
|
+
puts "usage: scout stream [your_scout_key] [start|stop]"
|
|
17
|
+
exit(1)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# server and history methods are inherited from Scout::Command base class
|
|
21
|
+
streamer_log_file=File.join(File.dirname(history),"scout_streamer.log")
|
|
22
|
+
streamer_pid_file=File.join(File.dirname(history),"scout_streamer.pid")
|
|
23
|
+
|
|
24
|
+
streamer_control_options = {:log_file => streamer_log_file,
|
|
25
|
+
:pid_file => streamer_pid_file,
|
|
26
|
+
:sync_log => true,
|
|
27
|
+
:working_dir => File.dirname(history)}
|
|
28
|
+
|
|
29
|
+
# we use STDOUT for the logger because daemon_spawn directs STDOUT to a log file
|
|
30
|
+
streamer_control_args = [server, @key, history, @plugin_ids, @options[:streaming_key],Logger.new(STDOUT)]
|
|
31
|
+
|
|
32
|
+
if daemon_command.include? "start" # can be 'start' or 'restart'
|
|
33
|
+
if File.exists?(streamer_pid_file)
|
|
34
|
+
puts "PID file existed. Restarting ..."
|
|
35
|
+
Scout::StreamerControl.restart(streamer_control_options,streamer_control_args)
|
|
36
|
+
else
|
|
37
|
+
puts "Starting ... "
|
|
38
|
+
Scout::StreamerControl.start(streamer_control_options,streamer_control_args)
|
|
39
|
+
end
|
|
40
|
+
elsif daemon_command == "stop"
|
|
41
|
+
puts "Stopping ..."
|
|
42
|
+
Scout::StreamerControl.stop(streamer_control_options,[])
|
|
43
|
+
else
|
|
44
|
+
puts "usage: scout stream [your_scout_key] [start|stop]"
|
|
45
|
+
exit(1)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/scout/command/test.rb
CHANGED
|
@@ -45,7 +45,7 @@ module Scout
|
|
|
45
45
|
puts "== You haven't provided any options for running this plugin."
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
Scout::Server.new(nil, nil, history, log
|
|
48
|
+
Scout::Server.new(nil, nil, history, log) do |scout|
|
|
49
49
|
scout.prepare_checkin
|
|
50
50
|
scout.process_plugin( 'interval' => 0,
|
|
51
51
|
'plugin_id' => 1,
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
# Taken unchanged from https://github.com/alexvollmer/daemon-spawn
|
|
4
|
+
#
|
|
5
|
+
# Large portions of this were liberally stolen from the
|
|
6
|
+
# 'simple-daemon' project at http://simple-daemon.rubyforge.org/
|
|
7
|
+
module DaemonSpawn
|
|
8
|
+
VERSION = '0.3.0'
|
|
9
|
+
|
|
10
|
+
def self.usage(msg=nil) #:nodoc:
|
|
11
|
+
print "#{msg}, " if msg
|
|
12
|
+
puts "usage: #{$0} <command> [options]"
|
|
13
|
+
puts "Where <command> is one of start, stop, restart or status"
|
|
14
|
+
puts "[options] are additional options passed to the underlying process"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.alive?(pid)
|
|
18
|
+
Process.kill 0, pid
|
|
19
|
+
rescue Errno::ESRCH
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.start(daemon, args) #:nodoc:
|
|
24
|
+
if !File.writable?(File.dirname(daemon.log_file))
|
|
25
|
+
STDERR.puts "Unable to write log file to #{daemon.log_file}"
|
|
26
|
+
exit 1
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if !File.writable?(File.dirname(daemon.pid_file))
|
|
30
|
+
STDERR.puts "Unable to write PID file to #{daemon.pid_file}"
|
|
31
|
+
exit 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if daemon.alive? && daemon.singleton
|
|
35
|
+
STDERR.puts "An instance of #{daemon.app_name} is already " +
|
|
36
|
+
"running (PID #{daemon.pid})"
|
|
37
|
+
exit 0
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
fork do
|
|
41
|
+
Process.setsid
|
|
42
|
+
exit if fork
|
|
43
|
+
open(daemon.pid_file, 'w') { |f| f << Process.pid }
|
|
44
|
+
Dir.chdir daemon.working_dir
|
|
45
|
+
old_umask = File.umask 0000
|
|
46
|
+
log = File.new(daemon.log_file, "a")
|
|
47
|
+
File.umask old_umask
|
|
48
|
+
log.sync = daemon.sync_log
|
|
49
|
+
STDIN.reopen "/dev/null"
|
|
50
|
+
STDOUT.reopen log
|
|
51
|
+
STDERR.reopen STDOUT
|
|
52
|
+
trap("TERM") {daemon.stop; exit}
|
|
53
|
+
daemon.start(args)
|
|
54
|
+
end
|
|
55
|
+
puts "#{daemon.app_name} started."
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.stop(daemon) #:nodoc:
|
|
59
|
+
if pid = daemon.pid
|
|
60
|
+
FileUtils.rm(daemon.pid_file)
|
|
61
|
+
Process.kill(daemon.signal, pid)
|
|
62
|
+
begin
|
|
63
|
+
Process.wait(pid)
|
|
64
|
+
rescue Errno::ECHILD
|
|
65
|
+
end
|
|
66
|
+
if ticks = daemon.timeout
|
|
67
|
+
while ticks > 0 and alive?(pid) do
|
|
68
|
+
puts "Process is still alive. #{ticks} seconds until I kill -9 it..."
|
|
69
|
+
sleep 1
|
|
70
|
+
ticks -= 1
|
|
71
|
+
end
|
|
72
|
+
if alive?(pid)
|
|
73
|
+
puts "Process didn't quit after timeout of #{daemon.timeout} seconds. Killing..."
|
|
74
|
+
Process.kill 9, pid
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
puts "PID file not found. Is the daemon started?"
|
|
79
|
+
end
|
|
80
|
+
rescue Errno::ESRCH
|
|
81
|
+
puts "PID file found, but process was not running. The daemon may have died."
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.status(daemon) #:nodoc:
|
|
85
|
+
puts "#{daemon.app_name} is #{daemon.alive? ? "" : "NOT "}running (PID #{daemon.pid})"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class Base
|
|
89
|
+
attr_accessor :log_file, :pid_file, :sync_log, :working_dir, :app_name, :singleton, :index, :signal, :timeout
|
|
90
|
+
|
|
91
|
+
def initialize(opts = {})
|
|
92
|
+
raise 'You must specify a :working_dir' unless opts[:working_dir]
|
|
93
|
+
self.working_dir = opts[:working_dir]
|
|
94
|
+
self.app_name = opts[:application] || classname
|
|
95
|
+
self.pid_file = opts[:pid_file] || File.join(working_dir, 'tmp', 'pids', app_name + '.pid')
|
|
96
|
+
self.log_file = opts[:log_file] || File.join(working_dir, 'logs', app_name + '.log')
|
|
97
|
+
self.signal = opts[:signal] || 'TERM'
|
|
98
|
+
self.timeout = opts[:timeout]
|
|
99
|
+
self.index = opts[:index] || 0
|
|
100
|
+
if self.index > 0
|
|
101
|
+
self.pid_file += ".#{self.index}"
|
|
102
|
+
self.log_file += ".#{self.index}"
|
|
103
|
+
end
|
|
104
|
+
self.sync_log = opts[:sync_log]
|
|
105
|
+
self.singleton = opts[:singleton] || false
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def classname #:nodoc:
|
|
109
|
+
self.class.to_s.split('::').last
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Provide your implementation. These are provided as a reminder
|
|
113
|
+
# only and will raise an error if invoked. When started, this
|
|
114
|
+
# method will be invoked with the remaining command-line arguments.
|
|
115
|
+
def start(args)
|
|
116
|
+
raise "You must implement a 'start' method in your class!"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Provide your implementation. These are provided as a reminder
|
|
120
|
+
# only and will raise an error if invoked.
|
|
121
|
+
def stop
|
|
122
|
+
raise "You must implement a 'stop' method in your class!"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def alive? #:nodoc:
|
|
126
|
+
if File.file?(pid_file)
|
|
127
|
+
DaemonSpawn.alive? pid
|
|
128
|
+
else
|
|
129
|
+
false
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def pid #:nodoc:
|
|
134
|
+
IO.read(self.pid_file).to_i rescue nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def self.build(options)
|
|
138
|
+
count = options.delete(:processes) || 1
|
|
139
|
+
daemons = []
|
|
140
|
+
count.times do |index|
|
|
141
|
+
daemons << new(options.merge(:index => index))
|
|
142
|
+
end
|
|
143
|
+
daemons
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def self.find(options)
|
|
147
|
+
pid_file = new(options).pid_file
|
|
148
|
+
basename = File.basename(pid_file).split('.').first
|
|
149
|
+
pid_files = Dir.glob(File.join(File.dirname(pid_file), "#{basename}.*pid*"))
|
|
150
|
+
pid_files.map { |f| new(options.merge(:pid_file => f)) }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Invoke this method to process command-line args and dispatch
|
|
154
|
+
# appropriately. Valid options include the following _symbols_:
|
|
155
|
+
# - <tt>:working_dir</tt> -- the working directory (required)
|
|
156
|
+
# - <tt>:log_file</tt> -- path to the log file
|
|
157
|
+
# - <tt>:pid_file</tt> -- path to the pid file
|
|
158
|
+
# - <tt>:sync_log</tt> -- indicate whether or not to sync log IO
|
|
159
|
+
# - <tt>:singleton</tt> -- If set to true, only one instance is
|
|
160
|
+
# allowed to start
|
|
161
|
+
# args must begin with 'start', 'stop', 'status', or 'restart'.
|
|
162
|
+
# The first token will be removed and any remaining arguments
|
|
163
|
+
# passed to the daemon's start method.
|
|
164
|
+
def self.spawn!(opts = {}, args = ARGV)
|
|
165
|
+
case args.any? and command = args.shift
|
|
166
|
+
when 'start', 'stop', 'status', 'restart'
|
|
167
|
+
send(command, opts, args)
|
|
168
|
+
when '-h', '--help', 'help'
|
|
169
|
+
DaemonSpawn.usage
|
|
170
|
+
exit
|
|
171
|
+
else
|
|
172
|
+
DaemonSpawn.usage "Invalid command"
|
|
173
|
+
exit 1
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def self.start(opts, args)
|
|
178
|
+
living_daemons = find(opts).select { |d| d.alive? }
|
|
179
|
+
if living_daemons.any?
|
|
180
|
+
puts "Daemons already started! PIDS: #{living_daemons.map {|d| d.pid}.join(', ')}"
|
|
181
|
+
exit 1
|
|
182
|
+
else
|
|
183
|
+
build(opts).map { |d| DaemonSpawn.start(d, args) }
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def self.stop(opts, args)
|
|
188
|
+
daemons = find(opts)
|
|
189
|
+
if daemons.empty?
|
|
190
|
+
puts "No PID files found. Is the daemon started?"
|
|
191
|
+
exit 1
|
|
192
|
+
else
|
|
193
|
+
daemons.each { |d| DaemonSpawn.stop(d) }
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def self.status(opts, args)
|
|
198
|
+
daemons = find(opts)
|
|
199
|
+
if daemons.empty?
|
|
200
|
+
puts 'No PIDs found'
|
|
201
|
+
else
|
|
202
|
+
daemons.each { |d| DaemonSpawn.status(d) }
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def self.restart(opts, args)
|
|
207
|
+
daemons = build(opts)
|
|
208
|
+
daemons.map do |daemon|
|
|
209
|
+
DaemonSpawn.stop(daemon)
|
|
210
|
+
sleep 0.1
|
|
211
|
+
DaemonSpawn.start(daemon, args)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|