zeusd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +10 -0
  5. data/Guardfile +7 -0
  6. data/LICENSE.md +13 -0
  7. data/README.md +48 -0
  8. data/Rakefile +7 -0
  9. data/bin/zeusd +42 -0
  10. data/lib/zeusd.rb +8 -0
  11. data/lib/zeusd/daemon.rb +104 -0
  12. data/lib/zeusd/process.rb +141 -0
  13. data/lib/zeusd/state_interpreter.rb +89 -0
  14. data/lib/zeusd/version.rb +3 -0
  15. data/spec/dummy/.gitignore +16 -0
  16. data/spec/dummy/Gemfile +6 -0
  17. data/spec/dummy/README.rdoc +261 -0
  18. data/spec/dummy/Rakefile +7 -0
  19. data/spec/dummy/app/assets/images/rails.png +0 -0
  20. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  21. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  22. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  23. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  24. data/spec/dummy/app/mailers/.gitkeep +0 -0
  25. data/spec/dummy/app/models/.gitkeep +0 -0
  26. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  27. data/spec/dummy/config.ru +4 -0
  28. data/spec/dummy/config/application.rb +62 -0
  29. data/spec/dummy/config/boot.rb +6 -0
  30. data/spec/dummy/config/database.yml +25 -0
  31. data/spec/dummy/config/environment.rb +5 -0
  32. data/spec/dummy/config/environments/development.rb +37 -0
  33. data/spec/dummy/config/environments/production.rb +67 -0
  34. data/spec/dummy/config/environments/test.rb +37 -0
  35. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  36. data/spec/dummy/config/initializers/inflections.rb +15 -0
  37. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  38. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  39. data/spec/dummy/config/initializers/session_store.rb +8 -0
  40. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  41. data/spec/dummy/config/locales/en.yml +5 -0
  42. data/spec/dummy/config/routes.rb +58 -0
  43. data/spec/dummy/custom_plan.rb +11 -0
  44. data/spec/dummy/db/seeds.rb +7 -0
  45. data/spec/dummy/lib/assets/.gitkeep +0 -0
  46. data/spec/dummy/lib/tasks/.gitkeep +0 -0
  47. data/spec/dummy/log/.gitkeep +0 -0
  48. data/spec/dummy/public/404.html +26 -0
  49. data/spec/dummy/public/422.html +26 -0
  50. data/spec/dummy/public/500.html +25 -0
  51. data/spec/dummy/public/favicon.ico +0 -0
  52. data/spec/dummy/public/index.html +241 -0
  53. data/spec/dummy/public/robots.txt +5 -0
  54. data/spec/dummy/script/rails +6 -0
  55. data/spec/dummy/test/fixtures/.gitkeep +0 -0
  56. data/spec/dummy/test/functional/.gitkeep +0 -0
  57. data/spec/dummy/test/integration/.gitkeep +0 -0
  58. data/spec/dummy/test/performance/browsing_test.rb +12 -0
  59. data/spec/dummy/test/test_helper.rb +13 -0
  60. data/spec/dummy/test/unit/.gitkeep +0 -0
  61. data/spec/dummy/vendor/assets/javascripts/.gitkeep +0 -0
  62. data/spec/dummy/vendor/assets/stylesheets/.gitkeep +0 -0
  63. data/spec/dummy/vendor/plugins/.gitkeep +0 -0
  64. data/spec/dummy/zeus.json +22 -0
  65. data/spec/spec_helper.rb +8 -0
  66. data/spec/zeusd/daemon_spec.rb +25 -0
  67. data/spec/zeusd/process_spec.rb +84 -0
  68. data/zeusd.gemspec +28 -0
  69. metadata +270 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ before_install: bundle install --gemfile=spec/dummy/Gemfile
3
+ bundler_args: --without tool
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1.0
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in zeusd.gemspec
4
+ gemspec
5
+
6
+ group :tool do
7
+ gem "guard-rspec"
8
+ gem "pry"
9
+ gem "pry-nav"
10
+ end
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ guard :rspec, :failed_mode => :none do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch(%r{^lib/zeusd/(.+)\.rb$}) { |m| "spec/lib/zeusd/#{m[1]}_spec.rb" }
5
+ watch('spec/spec_helper.rb') { "spec" }
6
+ end
7
+
data/LICENSE.md ADDED
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 1999 - 2014, Daniel Doezema
2
+
3
+ ## All rights reserved.
4
+
5
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8
+
9
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10
+
11
+ The names of the contributors and/or copyright holder may not be used to endorse or promote products derived from this software without specific prior written permission.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DANIEL DOEZEMA BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # Zeusd
2
+
3
+ Run the Zeud Gem as a daemon
4
+
5
+ [![Build Status](https://travis-ci.org/veloper/zeusd.png?branch=master)](https://travis-ci.org/veloper/zeusd) [![Code Climate](https://codeclimate.com/github/veloper/zeusd.png)](https://codeclimate.com/github/veloper/zeusd)
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'zeusd'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install zeusd
21
+
22
+ ## Usage
23
+
24
+ ### Commands
25
+
26
+ ```
27
+ zeusd start [--block]
28
+ zeusd stop
29
+ zeusd restart
30
+ ```
31
+
32
+ ### Global Flags
33
+ * `--cwd=current/work/directory/of/rails/app` or alias `-d`
34
+ * `--verbose` or `-v`
35
+
36
+
37
+ ## Contributing
38
+
39
+ 1. Fork it
40
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
41
+ 3. Write and test your changes
42
+ 3. Commit your changes and specs (`git commit -am 'Add some feature'`)
43
+ 4. Push to the branch (`git push origin my-new-feature`)
44
+ 5. Create new Pull Request
45
+
46
+ ## License
47
+
48
+ * Zeusd is released under the New BSD license. http://dan.doezema.com/licenses/new-bsd/
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ # If you want to make this the default task
7
+ task :default => :spec
data/bin/zeusd ADDED
@@ -0,0 +1,42 @@
1
+ require "thor"
2
+ require "zeusd"
3
+
4
+ class ZeusdCLI < Thor
5
+ class_option :verbose, :type => :boolean, :aliases => :v
6
+ class_option :block, :type => :boolean, :aliases => :b
7
+ class_option :cwd, :type => :string, :aliases => :d
8
+ class_option :log, :type => :string, :aliases => :l
9
+
10
+ desc "start", "."
11
+ def start
12
+ daemon.start! :block => options[:block]
13
+ end
14
+
15
+ desc "restart", "."
16
+ def restart
17
+ stop
18
+ start
19
+ end
20
+
21
+ desc "stop", "."
22
+ def stop
23
+ daemon.stop!
24
+ end
25
+
26
+ desc "status", "."
27
+ def status
28
+ # Zeusd::Daemon.new(options).stop!
29
+ end
30
+
31
+ protected
32
+
33
+ def daemon
34
+ Zeusd::Daemon.new(
35
+ :verbose => options[:verbose],
36
+ :cwd => options[:cwd]
37
+ )
38
+ end
39
+
40
+ end
41
+
42
+ ZeusdCLI.start(ARGV)
data/lib/zeusd.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'zeusd/version'
2
+ require 'zeusd/process'
3
+ require 'zeusd/state_interpreter'
4
+ require 'zeusd/daemon'
5
+
6
+ module Zeusd
7
+
8
+ end
@@ -0,0 +1,104 @@
1
+ require 'thread'
2
+ require 'childprocess'
3
+ require 'pathname'
4
+
5
+ module Zeusd
6
+ class DaemonException < StandardError; end
7
+
8
+ class Daemon
9
+ attr_reader :cwd, :verbose
10
+ attr_reader :queue
11
+ attr_reader :state
12
+ attr_reader :child_process, :reader, :writer
13
+
14
+ def initialize(options = {})
15
+ @cwd = Pathname.new(options.fetch(:cwd, Dir.pwd)).realpath
16
+ @verbose = options.fetch(:verbose, false)
17
+ @queue = Queue.new
18
+ @state = StateInterpreter.new
19
+ on_update(&method(:puts)) if verbose
20
+ end
21
+
22
+ def stop!
23
+ processes = process ? Array([process.descendants, process]).flatten : []
24
+ if processes.any?
25
+ Zeusd::Process.kill!(processes.map(&:pid))
26
+ end
27
+ (socket_file.delete rescue nil) if socket_file.exist?
28
+ if (alive_processes = processes).all?(&:alive?)
29
+ raise DaemonException, "Unable to KILL processes: " + alive_processes.join(', ')
30
+ else
31
+ @process = nil
32
+ true
33
+ end
34
+ end
35
+
36
+ def start!(options = {})
37
+ @process = Zeusd::Process.find(start_child_process!.pid)
38
+
39
+ if options.fetch(:block, false)
40
+ loop do
41
+ if loaded?
42
+ puts state.last_status
43
+ break
44
+ end
45
+ sleep(0.1)
46
+ end
47
+ end
48
+
49
+ process
50
+ end
51
+
52
+ def process
53
+ @process ||= Process.all.find do |p|
54
+ !!p.command[/zeus.*start$/] && p.cwd == cwd
55
+ end
56
+ end
57
+
58
+ def loaded?
59
+ process.descendants.all?(&:asleep?)
60
+ end
61
+
62
+ def on_update(&block)
63
+ @on_update = block if block_given?
64
+ @on_update
65
+ end
66
+
67
+ def socket_file
68
+ cwd.join('.zeus.sock')
69
+ end
70
+
71
+ protected
72
+
73
+ def start_child_process!
74
+ @reader, @writer = IO.pipe
75
+ @child_process = ChildProcess.build("zeus", "start")
76
+ @child_process.environment["BUNDLE_GEMFILE"] = cwd.join("Gemfile").to_path
77
+ @child_process.io.stdout = @child_process.io.stderr = @writer
78
+ @child_process.cwd = cwd.to_path
79
+ @child_process.detach = true
80
+ @child_process.start
81
+ @writer.close
82
+
83
+ Thread.new do
84
+ while (buffer = (reader.readpartial(10000) rescue nil)) do
85
+ state << buffer
86
+ queue << buffer
87
+ end
88
+ end
89
+
90
+ if on_update.is_a?(Proc)
91
+ Thread.new do
92
+ while output = queue.pop
93
+ on_update.call(output)
94
+ end
95
+ end
96
+ end
97
+
98
+ sleep(0.1) until state.commands.any?
99
+
100
+ @child_process
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,141 @@
1
+ module Zeusd
2
+ class Process
3
+ CASTINGS = {
4
+ "pid" => ->(x){x.to_i},
5
+ "ppid" => ->(x){x.to_i},
6
+ "pgid" => ->(x){x.to_i},
7
+ "stat" => ->(x){x.to_s},
8
+ "command" => ->(x){x.to_s}
9
+ }
10
+ attr_accessor :attributes
11
+ attr_accessor :children
12
+
13
+ def initialize(attributes = {})
14
+ self.attributes = attributes
15
+ end
16
+
17
+ def self.ps(options = {})
18
+ keywords = Array(options[:keywords]) | %w[pid ppid pgid stat command]
19
+ command = ["ps"].tap do |ps|
20
+ ps << "-o #{keywords.join(',')}"
21
+ ps << "-p #{options[:pid].to_i}" if options[:pid]
22
+ end.join(" ")
23
+ header, *rows = `#{command}`.split("\n")
24
+ keys = header.downcase.split
25
+ glob_columns = 0...(keys.length-1)
26
+ cmd_columns = (keys.length-1)..-1
27
+ Array(rows.map(&:split)).map do |parts|
28
+ Hash[keys.zip(parts[glob_columns] << parts[cmd_columns].join(" "))] # Attributes
29
+ end
30
+ end
31
+
32
+ def self.all(options = {})
33
+ ps(options).map do |attributes|
34
+ self.new(attributes)
35
+ end
36
+ end
37
+
38
+ # Note: Non-chinable, AND joined, Proc allowed for value
39
+ # {"attr" => value}
40
+ def self.where(criteria, options = {})
41
+ all(options).select do |process|
42
+ criteria.all? do |key, value|
43
+ case value
44
+ when Array then value.include?(process.send(key))
45
+ when Proc then !!value.call(process)
46
+ else
47
+ process.send(key) == value
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ def self.find(pid)
54
+ if attributes = ps(:pid => pid).first
55
+ self.new(attributes)
56
+ end
57
+ end
58
+
59
+ def self.kill!(pids, options = {})
60
+ signal = options.fetch(:signal, "INT")
61
+ pids = Array(pids).map(&:to_i).select{|x| x > 0}
62
+ if pids.any?
63
+ system("kill -#{signal} #{pids.join(' ')}")
64
+ $?.success?
65
+ else
66
+ false
67
+ end
68
+ end
69
+
70
+ def reload!
71
+ self.attributes = self.class.ps(:pid => pid).first || {}
72
+ @children = nil
73
+ !attributes.empty?
74
+ end
75
+
76
+ def cwd
77
+ @cwd ||= (path = `lsof -p #{pid}`.split("\n").find{|x| x[" cwd "]}.split.last.strip) ? Pathname.new(path).realpath : nil
78
+ end
79
+
80
+ def pid
81
+ attributes["pid"]
82
+ end
83
+
84
+ def ppid
85
+ attributes["ppid"]
86
+ end
87
+
88
+ def pgid
89
+ attributes["pgid"]
90
+ end
91
+
92
+ def state
93
+ reload!
94
+ attributes["stat"]
95
+ end
96
+
97
+ def command
98
+ attributes["command"]
99
+ end
100
+
101
+ def asleep?
102
+ !!state.to_s["S"]
103
+ end
104
+
105
+ def alive?
106
+ reload!
107
+ !attributes.empty?
108
+ end
109
+
110
+ def dead?
111
+ !alive?
112
+ end
113
+
114
+ def kill!(options = {})
115
+ self.class.kill!(pid, options)
116
+ reload!
117
+ end
118
+
119
+ def descendants(options = {})
120
+ children(options).map do |child_process|
121
+ [child_process].concat(Array(child_process.descendants))
122
+ end.flatten.compact
123
+ end
124
+
125
+ def children(options = {})
126
+ @children = self.class.where("ppid" => pid).tap do |processes|
127
+ if options.fetch(:recursive, false)
128
+ processes.each{|p| p.children(options)}
129
+ end
130
+ end
131
+ end
132
+
133
+ def attributes=(hash)
134
+ @attributes = hash.reduce({}) do |seed, (key, value)|
135
+ value = CASTINGS[key] ? CASTINGS[key].call(value) : value
136
+ seed.merge(key => value)
137
+ end
138
+ end
139
+
140
+ end
141
+ end
@@ -0,0 +1,89 @@
1
+ require 'stringio'
2
+ module Zeusd
3
+ class StateInterpreter
4
+ STATES = %w[ready crashed running connecting waiting]
5
+
6
+ attr_reader :lines
7
+ attr_reader :state_colors, :commands, :errors
8
+
9
+ def initialize(*args)
10
+ @state_colors = Hash[STATES.zip([nil]*STATES.length)]
11
+ @commands = {}
12
+ @errors = []
13
+ @lines = []
14
+ super(*args)
15
+ end
16
+
17
+ def <<(input)
18
+ input.split("\n").map{|x| Line.new(x) }.each do |line|
19
+ # State Colors
20
+ if @state_colors.values.any?(&:nil?) && line.legend?
21
+ STATES.each do |state|
22
+ state_colors[state] = line.color_of(state)
23
+ end
24
+ end
25
+
26
+ # Errors
27
+ @errors << line if line.color == state_colors["crashed"]
28
+
29
+ # Commands
30
+ @commands[line.command[:name]] = state_colors.invert[line.command[:color]] if line.command?
31
+
32
+ # Add Line
33
+ @lines << line
34
+ end
35
+ end
36
+
37
+ def is_complete?
38
+ return false if errors.any?
39
+ return true if commands.all? {|command, status| %[crashed running].include?(status)}
40
+ false
41
+ end
42
+
43
+ def last_status
44
+ @lines[@lines.rindex(&:update?)..-1].join("\n").to_s
45
+ end
46
+
47
+ class Line < String
48
+
49
+ def update?
50
+ self =~ /\=\=\=\=$/
51
+ end
52
+
53
+ def command?
54
+ !!command
55
+ end
56
+
57
+ def command
58
+ if match = self.match(/^(\e.*?)zeus\s(.*?)(\s|\e)/)
59
+ { :name => match[2], :color => match[1] }
60
+ end
61
+ end
62
+
63
+ def legend?
64
+ STATES.all?{|state| !!self[state]}
65
+ end
66
+
67
+ def color_of(substring)
68
+ if stop_point = index(substring) + substring.length
69
+ if color_start = rindex(/\e/, stop_point)
70
+ color_end = index('m', color_start)
71
+ self[color_start..color_end]
72
+ end
73
+ end
74
+ end
75
+
76
+ def color
77
+ if self[0] == "\e" && !self.index('m').nil?
78
+ self[0..self.index('m')]
79
+ end
80
+ end
81
+
82
+ def color?
83
+ !!color
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+ end