├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── CHANGES.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── eye ├── leye └── loader_eye ├── examples ├── custom_check.eye ├── custom_trigger.eye ├── delayed_job.eye ├── dependency.eye ├── leye_example │ └── Eyefile ├── notify.eye ├── plugin │ ├── README.md │ ├── main.eye │ └── plugin.rb ├── process_thin.rb ├── processes │ ├── em.rb │ ├── forking.rb │ ├── sample.rb │ └── thin.ru ├── puma.eye ├── rbenv.eye ├── sidekiq.eye ├── stress_test.eye ├── syslog.eye ├── test.eye ├── thin-farm.eye ├── triggers.eye └── unicorn.eye ├── eye.gemspec ├── lib ├── eye.rb └── eye │ ├── application.rb │ ├── checker.rb │ ├── checker │ ├── children_count.rb │ ├── children_memory.rb │ ├── cpu.rb │ ├── cputime.rb │ ├── file_ctime.rb │ ├── file_size.rb │ ├── file_touched.rb │ ├── http.rb │ ├── memory.rb │ ├── nop.rb │ ├── runtime.rb │ ├── socket.rb │ └── ssl_socket.rb │ ├── child_process.rb │ ├── cli.rb │ ├── cli │ ├── commands.rb │ ├── render.rb │ └── server.rb │ ├── client.rb │ ├── config.rb │ ├── control.rb │ ├── controller.rb │ ├── controller │ ├── apply.rb │ ├── commands.rb │ ├── helpers.rb │ ├── load.rb │ ├── options.rb │ └── status.rb │ ├── dsl.rb │ ├── dsl │ ├── application_opts.rb │ ├── chain.rb │ ├── child_process_opts.rb │ ├── config_opts.rb │ ├── group_opts.rb │ ├── helpers.rb │ ├── main.rb │ ├── opts.rb │ ├── process_opts.rb │ ├── pure_opts.rb │ └── validation.rb │ ├── group.rb │ ├── group │ ├── call.rb │ ├── chain.rb │ └── data.rb │ ├── loader.rb │ ├── local.rb │ ├── logger.rb │ ├── notify.rb │ ├── notify │ ├── jabber.rb │ ├── mail.rb │ └── slack.rb │ ├── process.rb │ ├── process │ ├── children.rb │ ├── commands.rb │ ├── config.rb │ ├── controller.rb │ ├── data.rb │ ├── monitor.rb │ ├── notify.rb │ ├── scheduler.rb │ ├── states.rb │ ├── states_history.rb │ ├── system.rb │ ├── trigger.rb │ ├── validate.rb │ └── watchers.rb │ ├── server.rb │ ├── sigar.rb │ ├── system.rb │ ├── system_resources.rb │ ├── trigger.rb │ ├── trigger │ ├── check_dependency.rb │ ├── flapping.rb │ ├── starting_guard.rb │ ├── stop_children.rb │ ├── transition.rb │ └── wait_dependency.rb │ ├── utils.rb │ └── utils │ ├── alive_array.rb │ ├── mini_active_support.rb │ ├── pmap.rb │ └── tail.rb └── spec ├── checker ├── cpu_spec.rb ├── cputime_spec.rb ├── file_ctime_spec.rb ├── file_size_spec.rb ├── file_touched_spec.rb ├── http_spec.rb ├── memory_spec.rb ├── runtime_spec.rb └── socket_spec.rb ├── checker_spec.rb ├── child_process └── child_process_spec.rb ├── cli └── render_spec.rb ├── client_server_spec.rb ├── controller ├── chain_spec.rb ├── commands_spec.rb ├── controller_spec.rb ├── data_spec.rb ├── delete_spec.rb ├── find_objects_spec.rb ├── group_spec.rb ├── intergration_spec.rb ├── load_spec.rb ├── races_spec.rb ├── restart_spec.rb ├── stop_on_delete_spec.rb └── user_command_spec.rb ├── dsl ├── chain_spec.rb ├── checks_spec.rb ├── config_spec.rb ├── dsl_spec.rb ├── getter_spec.rb ├── integration_spec.rb ├── load_spec.rb ├── monitor_children_spec.rb ├── notify_spec.rb ├── process_spec.rb ├── sub_procs_spec.rb ├── transform_spec.rb └── with_server_spec.rb ├── example ├── em.rb ├── forking.rb ├── leaf_child.sh ├── sample.rb └── thin.ru ├── fixtures └── dsl │ ├── 0.rb │ ├── 0a.rb │ ├── 0c.rb │ ├── 1.rb │ ├── Eyefile │ ├── bad.eye │ ├── configs │ ├── 1.eye │ ├── 2.eye │ ├── 3.eye │ ├── 4.eye │ └── 5.eye │ ├── contact1.eye │ ├── contact2.eye │ ├── default1.eye │ ├── default2.eye │ ├── default3.eye │ ├── default4.eye │ ├── empty.eye │ ├── env1 │ ├── include_test.eye │ ├── include_test │ ├── 1.rb │ └── ha.rb │ ├── include_test2.eye │ ├── integration.erb │ ├── integration2.erb │ ├── integration_locks.eye │ ├── integration_sor.erb │ ├── integration_sor2.erb │ ├── integration_sor3.erb │ ├── just_sleep.eye │ ├── load.eye │ ├── load2.eye │ ├── load2_dup2.eye │ ├── load2_dup_pid.eye │ ├── load3.eye │ ├── load4.eye │ ├── load5.eye │ ├── load6.eye │ ├── load_dup_ex_names.eye │ ├── load_dup_ex_names2.eye │ ├── load_dup_ex_names3.eye │ ├── load_dup_ex_names4.eye │ ├── load_dupls.eye │ ├── load_dupls2.eye │ ├── load_dupls3.eye │ ├── load_dupls5.eye │ ├── load_error.eye │ ├── load_error_folder │ ├── load3.eye │ └── load4.eye │ ├── load_folder │ ├── load3.eye │ └── load4.eye │ ├── load_int.eye │ ├── load_int2.eye │ ├── load_logger.eye │ ├── load_logger2.eye │ ├── long_load.eye │ ├── multiple_checks.eye │ ├── subfolder1 │ └── proc1.rb │ ├── subfolder2.eye │ ├── subfolder2 │ ├── common.rb │ ├── proc2.rb │ └── sub │ │ └── proc3.rb │ ├── subfolder3.eye │ ├── subfolder3 │ ├── common.rb │ ├── proc4.rb │ └── sub │ │ └── proc5.rb │ ├── subfolder4.eye │ └── subfolder4 │ ├── a.rb │ ├── b.rb │ └── c.rb ├── local_spec.rb ├── logger_spec.rb ├── mock_spec.rb ├── notify ├── jabber_spec.rb ├── mail_spec.rb └── slack_spec.rb ├── notify_spec.rb ├── process ├── behaviour_spec.rb ├── checks │ ├── child_checks_spec.rb │ ├── children_count_spec.rb │ ├── children_memory_spec.rb │ ├── cpu_spec.rb │ ├── ctime_spec.rb │ ├── custom_spec.rb │ ├── fsize_spec.rb │ ├── http_spec.rb │ ├── intergration_spec.rb │ ├── memory_spec.rb │ └── multiple_spec.rb ├── child_process_spec.rb ├── config_spec.rb ├── controller_spec.rb ├── data_spec.rb ├── dependency_multi_spec.rb ├── dependency_spec.rb ├── double_restart_spec.rb ├── monitoring_spec.rb ├── notify_spec.rb ├── pid_identity_spec.rb ├── pid_managment_emulate_spec.rb ├── pid_managment_spec.rb ├── restart_emulate_spec.rb ├── restart_spec.rb ├── scheduler_spec.rb ├── start_spec.rb ├── states_history_spec.rb ├── stop_spec.rb ├── system_spec.rb ├── triggers │ ├── custom_spec.rb │ ├── flapping_retry_spec.rb │ ├── flapping_spec.rb │ ├── starting_guard_spec.rb │ ├── stop_children_spec.rb │ └── transition_spec.rb ├── update_config_spec.rb └── use_leaf_child_spec.rb ├── spec_helper.rb ├── support ├── load_result.rb ├── rr_celluloid.rb └── spec_support.rb ├── system_resources_spec.rb ├── system_spec.rb ├── utils ├── alive_array_spec.rb ├── matchers_spec.rb ├── signals_spec.rb └── tail_spec.rb ├── utils_spec.rb └── weights.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .ruby-version 6 | .ruby-gemset 7 | .yardoc 8 | Gemfile.lock 9 | InstalledFiles 10 | _yardoc 11 | coverage 12 | doc/ 13 | lib/bundler/man 14 | pkg 15 | rdoc 16 | spec/reports 17 | test/tmp 18 | test/version_tmp 19 | tmp 20 | *.pid 21 | *.log 22 | *.swp 23 | *~ 24 | TODO 25 | .todo 26 | *.png 27 | *.lock 28 | experiments 29 | .git2 30 | *.stop 31 | *sublime* 32 | examples/work*.eye 33 | script 34 | [0-9].rb 35 | *.cache 36 | *.tmp 37 | /vendor/ 38 | *.gz 39 | .eye 40 | .DS_Store 41 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - "1.9.3" 4 | - "2.0.0" 5 | - "2.1.7" 6 | - "2.2.3" 7 | - "2.3.0" 8 | script: 9 | - bundle exec rake N=15 10 | - bundle exec rubocop 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | 4 | gem 'fakeweb', git: 'https://github.com/chrisk/fakeweb.git' 5 | gem 'json' 6 | gem 'rack', '1.6.4' 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2016 'Konstantin Makarchev' 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | 3 | require 'bundler/gem_tasks' 4 | require 'coveralls/rake/task' 5 | 6 | Coveralls::RakeTask.new 7 | 8 | task default: :split_test 9 | 10 | desc 'run parallel tests' 11 | task :pspec do 12 | dirname = File.expand_path(File.dirname(__FILE__)) 13 | cmd = "bundle exec parallel_rspec -n #{ENV['N'] || 10} --runtime-log '#{dirname}/spec/weights.txt' #{dirname}/spec" 14 | abort unless system(cmd) 15 | end 16 | 17 | desc 'run parallel split tests' 18 | task :split_test do 19 | dirname = File.expand_path(File.dirname(__FILE__)) 20 | ENV['PARALLEL_SPLIT_TEST_PROCESSES'] = (ENV['N'] || 10).to_s 21 | cmd = "bundle exec parallel_split_test #{dirname}/spec" 22 | abort unless system(cmd) 23 | end 24 | 25 | task :remove_coverage do 26 | require 'fileutils' 27 | FileUtils.rm_rf(File.expand_path(File.join(File.dirname(__FILE__), %w[coverage]))) 28 | end 29 | 30 | task :env do 31 | require 'bundler/setup' 32 | require 'eye' 33 | Eye::Controller 34 | Eye::Process 35 | end 36 | 37 | desc 'graph' 38 | task graph: :env do 39 | StateMachine::Machine.draw('Eye::Process') 40 | end 41 | -------------------------------------------------------------------------------- /bin/eye: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.unshift File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib])) 3 | require 'eye' 4 | 5 | Eye::Cli.start 6 | -------------------------------------------------------------------------------- /bin/leye: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.unshift File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib])) 3 | require 'eye' 4 | 5 | # Local version of eye 6 | # which looking for Eyefile 7 | # like foreman 8 | 9 | loop do 10 | if ARGV[0] == '--eyefile' 11 | ARGV.shift 12 | ENV['EYE_FILE'] = File.expand_path(ARGV.shift.to_s) 13 | elsif ARGV[0] == '--eyehome' 14 | ARGV.shift 15 | ENV['EYE_HOME'] = File.expand_path(ARGV.shift.to_s) 16 | else 17 | break 18 | end 19 | end 20 | 21 | if ENV['EYE_HOME'] && !File.directory?(File.expand_path(ENV['EYE_HOME'])) 22 | puts "\033[31mEYE_HOME is not directory (#{File.expand_path(ENV['EYE_HOME'])})\033[0m" 23 | exit 1 24 | end 25 | 26 | unless Eye::Local.eyefile || ENV['EYE_HOME'] 27 | puts "\033[31mNot found Eyefile (in #{File.expand_path(ENV['EYE_HOME'] || '.')})\033[0m" 28 | exit 1 29 | end 30 | 31 | ENV['EYE_HOME'] = File.dirname(Eye::Local.eyefile) unless ENV.key?('EYE_HOME') 32 | 33 | Eye::Local.local_runner = true 34 | Eye::Cli.start 35 | -------------------------------------------------------------------------------- /bin/loader_eye: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.unshift File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib])) 3 | require 'eye/loader' 4 | require 'optparse' 5 | require 'eye' 6 | 7 | options = { debug: false } 8 | 9 | OptionParser.new do |opts| 10 | opts.on('-h', '--help', 'Display this screen') do 11 | puts opts 12 | exit 13 | end 14 | 15 | opts.on('-c', '--config CONFIG', 'load with config') do |config_path| 16 | options[:config] = config_path 17 | end 18 | 19 | opts.on('-s', '--socket SOCKET', 'start listen on socket') do |socket_path| 20 | options[:socket_path] = socket_path 21 | end 22 | 23 | opts.on('-l', '--logger LOGGER', 'custom logger') do |logger| 24 | options[:logger] = logger 25 | end 26 | 27 | opts.on('-d', '--dir DIR', 'Dir for local runner') do |dir| 28 | Eye::Local.dir = dir 29 | Eye::Local.local_runner = true 30 | end 31 | 32 | opts.on('-a', '--stop_all', 'Stop all on exit') do 33 | options[:stop_all] = true 34 | end 35 | 36 | opts.on('-g', '--debug', 'debug info to logger') do 37 | options[:debug] = true 38 | end 39 | end.parse! 40 | 41 | Eye::Local.ensure_eye_dir 42 | 43 | socket_path = options[:socket_path] || Eye::Local.socket_path 44 | server = Eye::Server.new(socket_path) 45 | 46 | Eye::Logger.log_level = options[:debug] ? Logger::DEBUG : Logger::INFO 47 | Eye::Logger.link_logger(options[:logger]) if options[:logger] 48 | 49 | config = options[:config] 50 | config = File.expand_path(config) if config && !config.empty? 51 | 52 | Eye::Control # preload 53 | 54 | if config 55 | res = Eye::Control.command('load', config) 56 | exit(1) if res.values.any? { |r| r[:error] } 57 | end 58 | 59 | Eye::Control.set_proc_line 60 | 61 | server.async.run 62 | 63 | trap('USR1') { Eye::Logger.reopen } 64 | trap('USR2') { GC.start } 65 | 66 | at_exit { Eye::Control.command(:stop_all) } if options[:stop_all] 67 | 68 | begin 69 | sleep 70 | rescue Interrupt 71 | end 72 | -------------------------------------------------------------------------------- /examples/custom_check.eye: -------------------------------------------------------------------------------- 1 | # This example shows how to write custom checks: 2 | # We check process procline every 1.second, and if it matches `haha` 3 | # send TERM signal 4 | 5 | class MyCheck < Eye::Checker::Custom 6 | 7 | def get_value 8 | Eye::SystemResources.args(@pid) 9 | end 10 | 11 | def good?(value) 12 | value !~ /haha/ 13 | end 14 | 15 | end 16 | 17 | Eye.app :bla do 18 | process :a do 19 | start_command "ruby -e 'sleep 10; $0 = %q{HAHA}.downcase; sleep'" 20 | daemonize true 21 | pid_file '/tmp/1.pid' 22 | check :my_check, every: 1.second, fires: -> { send_signal(:TERM) } 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /examples/custom_trigger.eye: -------------------------------------------------------------------------------- 1 | # send notify when many times crashed process, finally resolved 2 | 3 | class Eye::Trigger::FixCrash < Eye::Trigger::Custom 4 | 5 | param :times, Integer, nil, 1 6 | param_default :to, :up 7 | 8 | def check(*) 9 | # process states here like this: [..., :starting, :down, :starting, :down, :starting, :up] 10 | states = process.states_history.states 11 | 12 | # states to compare with 13 | compare = [:starting, :down] * times + [:starting, :up] 14 | 15 | process.notify(:info, 'yahho, process up') if states[-compare.length..-1] == compare 16 | end 17 | 18 | end 19 | 20 | Eye.app :custom_trigger do 21 | trigger :fix_crash 22 | 23 | process :some do 24 | pid_file '/tmp/custom_trigger_some.pid' 25 | start_command "ruby -e 's = `cat /tmp/bla`; exit(1) unless s =~ /bla/; loop { sleep 1 } '" 26 | daemonize! 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /examples/delayed_job.eye: -------------------------------------------------------------------------------- 1 | cwd = File.expand_path(File.join(File.dirname(__FILE__), %w[../ ../])) 2 | 3 | config_path = File.join(cwd, %w[config dj.yml]) 4 | 5 | workers_count = if File.exist?(config_path) 6 | YAML.load_file(config_path).try(:[], :workers) || 5 7 | else 8 | 5 9 | end 10 | 11 | Eye.application 'delayed_job' do 12 | working_dir cwd 13 | stop_on_delete true 14 | 15 | group 'dj' do 16 | chain grace: 5.seconds 17 | 18 | (1..workers_count).each do |i| 19 | process "dj-#{i}" do 20 | pid_file "tmp/pids/delayed_job.#{i}.pid" 21 | start_command 'rake jobs:work' 22 | daemonize true 23 | stop_signals [:INT, 30.seconds, :TERM, 10.seconds, :KILL] 24 | stdall "log/dj-#{i}.log" 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /examples/dependency.eye: -------------------------------------------------------------------------------- 1 | # process dependencies example 2 | 3 | Eye.app :dependency do 4 | process(:a) do 5 | start_command 'sleep 100' 6 | daemonize true 7 | pid_file '/tmp/test_process_a.pid' 8 | end 9 | 10 | process(:b) do 11 | start_command 'sleep 100' 12 | daemonize true 13 | pid_file '/tmp/test_process_b.pid' 14 | depend_on :a 15 | end 16 | 17 | process(:c) do 18 | start_command 'sleep 100' 19 | daemonize true 20 | pid_file '/tmp/test_process_c.pid' 21 | depend_on :a 22 | end 23 | 24 | process(:d) do 25 | start_command 'sleep 100' 26 | daemonize true 27 | pid_file '/tmp/test_process_d.pid' 28 | depend_on :b 29 | end 30 | 31 | process(:e) do 32 | start_command 'sleep 100' 33 | daemonize true 34 | pid_file '/tmp/test_process_e.pid' 35 | depend_on [:d, :c] 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /examples/leye_example/Eyefile: -------------------------------------------------------------------------------- 1 | 2 | Eye.application :test_leye do 3 | 5.times do |i| 4 | process "test-#{i}" do 5 | start_command "sleep 200" 6 | daemonize! 7 | pid_file "#{i}.pid" 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /examples/notify.eye: -------------------------------------------------------------------------------- 1 | # Notify example 2 | 3 | Eye.config do 4 | mail host: 'mx.some.host', port: 25, domain: 'some.host' 5 | contact :errors, :mail, 'error@some.host' 6 | contact :dev, :mail, 'dev@some.host' 7 | end 8 | 9 | Eye.application :some do 10 | notify :errors 11 | 12 | process :some_process do 13 | notify :dev, :info 14 | pid_file '1.pid' 15 | 16 | # ... 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /examples/plugin/README.md: -------------------------------------------------------------------------------- 1 | Eye Plugin Example 2 | ------------------ 3 | 4 | This plugin adds reactor which try to reads command from file "/tmp/cmd.txt" every 1.second (then execute it and delete file). Also plugin add trigger to save every process state transition into "/tmp/saver.log". 5 | 6 | To test it: 7 | 8 | bundle exec eye l examples/plugin/main.eye 9 | tail -f /tmp/eye.log 10 | tail -f /tmp/saver.log 11 | echo 'restart' > /tmp/cmd.txt 12 | 13 | Also, here http example of gem: 14 | 15 | https://github.com/kostya/eye-http 16 | -------------------------------------------------------------------------------- /examples/plugin/main.eye: -------------------------------------------------------------------------------- 1 | Eye.load('./plugin.rb') 2 | 3 | Eye.config do 4 | logger '/tmp/eye.log' 5 | enable_reactor(1.second, '/tmp/cmd.txt') 6 | enable_saver('/tmp/saver.log') 7 | end 8 | 9 | Eye.app :app do 10 | process :process do 11 | pid_file '/tmp/p.pid' 12 | start_command 'sleep 10' 13 | daemonize true 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /examples/plugin/plugin.rb: -------------------------------------------------------------------------------- 1 | class Reactor 2 | include Celluloid 3 | 4 | def initialize(interval, filename) 5 | @interval = interval 6 | @filename = filename 7 | every(@interval) do 8 | info "check file #{@filename}" 9 | if cmd = read_file 10 | execute_command cmd 11 | end 12 | end 13 | end 14 | 15 | def read_file 16 | if File.exist?(@filename) 17 | cmd = File.read(@filename).chop 18 | File.delete(@filename) rescue nil 19 | cmd 20 | end 21 | end 22 | 23 | def execute_command(cmd) 24 | Eye::Control.command(cmd, 'all') if %w[restart start stop].include?(cmd) 25 | end 26 | 27 | end 28 | 29 | class Saver < Eye::Trigger::Custom 30 | 31 | param :log_name, String, true 32 | 33 | def check(trans) 34 | tlogger.info "#{process.full_name} transition from #{trans.from_name} to #{trans.to_name}" 35 | end 36 | 37 | def tlogger 38 | @tlogger ||= Logger.new(log_name) 39 | end 40 | 41 | end 42 | 43 | def reactor 44 | Celluloid::Actor[:reactor] 45 | end 46 | 47 | # Extend config options, add enable_reactor 48 | class Eye::Dsl::ConfigOpts 49 | 50 | def enable_reactor(*args) 51 | @config[:reactor] = args 52 | end 53 | 54 | def enable_saver(save_log) 55 | Eye.application '__default__' do 56 | trigger :saver, log_name: save_log 57 | end 58 | end 59 | 60 | end 61 | 62 | # extend controller to execute method, and config loads 63 | class Eye::Controller 64 | 65 | def set_opt_reactor(args) 66 | reactor.terminate if reactor 67 | Celluloid::Actor[:reactor] = Reactor.supervise(*args) 68 | end 69 | 70 | end 71 | -------------------------------------------------------------------------------- /examples/process_thin.rb: -------------------------------------------------------------------------------- 1 | # part of thin-farm.eye config 2 | 3 | def thin(proxy, port) 4 | name = "thin-#{port}" 5 | 6 | opts = [ 7 | '-l thins.log', 8 | "-p #{port}", 9 | "-P #{name}.pid", 10 | '-d', 11 | '-R thin.ru', 12 | "--tag #{proxy.app.name}.#{proxy.name}", 13 | '-t 60', 14 | "-e #{proxy.env['RAILS_ENV']}", 15 | "-c #{proxy.working_dir}", 16 | '-a 127.0.0.1' 17 | ] 18 | 19 | proxy.process(name) do 20 | pid_file "#{name}.pid" 21 | 22 | start_command "#{BUNDLE} exec thin start #{opts * ' '}" 23 | stop_signals [:QUIT, 2.seconds, :TERM, 1.seconds, :KILL] 24 | 25 | stdall 'thin.stdall.log' 26 | 27 | check :http, url: "http://127.0.0.1:#{port}/hello", pattern: /World/, 28 | every: 5.seconds, times: [2, 3], timeout: 1.second 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /examples/processes/em.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'eventmachine' 3 | 4 | def answer(data) 5 | case data 6 | when 'ping' then "pong\n" 7 | when 'bad' then "what\n" 8 | when 'timeout' then 9 | sleep 5 10 | "ok\n" 11 | when 'exception' then raise 'haha' 12 | when 'quit' then EM.stop 13 | when 'big' then 'a' * 10_000_000 14 | end 15 | end 16 | 17 | class Echo < EM::Connection 18 | 19 | def post_init 20 | puts '-- someone connected to the echo server!' 21 | end 22 | 23 | def receive_data(data) 24 | puts "receive #{data.inspect} " 25 | send_data(answer(data)) 26 | end 27 | 28 | def unbind 29 | puts '-- someone disconnected from the echo server!' 30 | end 31 | 32 | end 33 | 34 | class EchoObj < EM::Connection 35 | 36 | include EM::P::ObjectProtocol 37 | 38 | def post_init 39 | puts '-- someone connected to the echo server!' 40 | end 41 | 42 | # {:command => 'ping'} 43 | def receive_object(obj) 44 | puts "receive #{obj.inspect}" 45 | send_object(answer(obj[:command]).chop) 46 | end 47 | 48 | def unbind 49 | puts '-- someone disconnected from the echo server!' 50 | end 51 | 52 | end 53 | 54 | trap 'QUIT' do 55 | puts 'quit signal, stopping' 56 | EM.stop 57 | end 58 | 59 | EM.run do 60 | EM.start_server '127.0.0.1', 33_221, Echo 61 | EM.start_server '127.0.0.1', 33_222, EchoObj 62 | EM.start_server '/tmp/em_test_sock', nil, Echo 63 | puts 'started' 64 | end 65 | -------------------------------------------------------------------------------- /examples/processes/forking.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'forking' 3 | 4 | root = File.expand_path(File.dirname(__FILE__)) 5 | cnt = (ENV['FORKING_COUNT'] || 3).to_i 6 | 7 | f = Forking.new(name: 'forking', working_dir: root, 8 | log_file: "#{root}/forking.log", 9 | pid_file: "#{root}/forking.pid", sync_log: true) 10 | 11 | cnt.times do |i| 12 | f.spawn(log_file: "#{root}/child#{i}.log", sync_log: true) do 13 | $0 = 'forking child' 14 | t = 0 15 | loop do 16 | p "#{Time.now} - #{Time.now.to_f} - #{i} - tick" 17 | sleep 0.1 18 | t += 0.1 19 | exit if t > 300 20 | end 21 | end 22 | end 23 | 24 | f.run! 25 | -------------------------------------------------------------------------------- /examples/processes/thin.ru: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'sinatra' 3 | 4 | class Test < Sinatra::Base 5 | 6 | get '/hello' do 7 | sleep 0.5 8 | 'Hello World!' 9 | end 10 | 11 | end 12 | 13 | run Test.new 14 | -------------------------------------------------------------------------------- /examples/puma.eye: -------------------------------------------------------------------------------- 1 | BUNDLE = 'bundle' 2 | RAILS_ENV = 'production' 3 | ROOT = File.expand_path(File.join(File.dirname(__FILE__), %w[processes])) 4 | 5 | Eye.config do 6 | logger "#{ROOT}/eye.log" 7 | end 8 | 9 | Eye.application :puma do 10 | env 'RAILS_ENV' => RAILS_ENV 11 | working_dir ROOT 12 | trigger :flapping, times: 10, within: 1.minute 13 | 14 | process :puma do 15 | daemonize true 16 | pid_file 'puma.pid' 17 | stdall 'puma.log' 18 | 19 | start_command "#{BUNDLE} exec puma --port 33280 --environment #{RAILS_ENV} thin.ru" 20 | stop_signals [:TERM, 5.seconds, :KILL] 21 | restart_command 'kill -USR2 {PID}' 22 | 23 | # just sleep this until process get up status 24 | # (maybe enought to puma soft restart) 25 | restart_grace 10.seconds 26 | 27 | check :cpu, every: 30, below: 80, times: 3 28 | check :memory, every: 30, below: 70.megabytes, times: [3, 5] 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /examples/rbenv.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'rbenv_example' do 2 | env 'RBENV_ROOT' => '/usr/local/rbenv', 'PATH' => "/usr/local/rbenv/shims:/usr/local/rbenv/bin:#{ENV['PATH']}" 3 | working_dir File.expand_path(File.join(File.dirname(__FILE__), %w[processes])) 4 | 5 | process 'some_process' do 6 | pid_file 'some.pid' 7 | start_command 'ruby some.rb' 8 | daemonize true 9 | stdall 'some.log' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /examples/sidekiq.eye: -------------------------------------------------------------------------------- 1 | # Example: how to run sidekiq daemon 2 | 3 | def sidekiq_process(proxy, name) 4 | rails_env = proxy.env['RAILS_ENV'] 5 | 6 | proxy.process(name) do 7 | start_command "bin/sidekiq -e #{rails_env} -C ./config/sidekiq.#{rails_env}.yml" 8 | pid_file "tmp/pids/#{name}.pid" 9 | stdall "log/#{name}.log" 10 | daemonize true 11 | stop_signals [:USR1, 0, :TERM, 10.seconds, :KILL] 12 | 13 | check :cpu, every: 30, below: 100, times: 5 14 | check :memory, every: 30, below: 300.megabytes, times: 5 15 | end 16 | end 17 | 18 | Eye.application :sidekiq_test do 19 | working_dir File.expand_path(File.join(File.dirname(__FILE__), %w[processes])) 20 | env 'RAILS_ENV' => 'production' 21 | 22 | sidekiq_process self, :sidekiq 23 | end 24 | -------------------------------------------------------------------------------- /examples/stress_test.eye: -------------------------------------------------------------------------------- 1 | # this is not example, just config for eye stress test 2 | 3 | PREFIX = ENV['PRE'] || ENV['EYE_V'] || 1 4 | 5 | Eye.app :stress_test do 6 | working_dir '/tmp' 7 | 8 | 100.times do |i| 9 | process "sleep-#{i}" do 10 | pid_file "sleep-#{PREFIX}-#{i}.pid" 11 | start_command 'sleep 120' 12 | daemonize true 13 | 14 | checks :cpu, every: 5.seconds, below: 10, times: 5 15 | checks :memory, every: 6.seconds, below: 50.megabytes, times: 5 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /examples/syslog.eye: -------------------------------------------------------------------------------- 1 | # output eye logger to syslog, and process stdout to syslog too 2 | # experimental feature, in some cases may be unstable 3 | 4 | Eye.config do 5 | logger syslog 6 | end 7 | 8 | Eye.app :syslog_test do 9 | process :some do 10 | pid_file '/tmp/syslog_test.pid' 11 | start_command "ruby -e 'loop { p Time.now; sleep 1 }'" 12 | daemonize! 13 | stdall syslog 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /examples/thin-farm.eye: -------------------------------------------------------------------------------- 1 | RUBY = 'ruby' 2 | BUNDLE = 'bundle' 3 | 4 | Eye.load('process_thin.rb') 5 | 6 | Eye.config do 7 | logger '/tmp/eye.log' 8 | end 9 | 10 | Eye.app 'thin-farm' do 11 | working_dir File.expand_path(File.join(File.dirname(__FILE__), %w[processes])) 12 | env 'RAILS_ENV' => 'production' 13 | 14 | # more about stop_on_delete: https://github.com/kostya/eye/wiki/About-stop_on_delete-=-true 15 | stop_on_delete true 16 | 17 | trigger :flapping, times: 10, within: 1.minute 18 | check :memory, below: 60.megabytes, every: 30.seconds, times: 5 19 | start_timeout 30.seconds 20 | 21 | group :web do 22 | chain action: :restart, grace: 5.seconds 23 | chain action: :start, grace: 0.2.seconds 24 | 25 | (5555..5560).each do |port| 26 | thin self, port 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /examples/triggers.eye: -------------------------------------------------------------------------------- 1 | # Triggers example: 2 | # 3 | # to execute commands inside trigger need to use 2 methods: 4 | # process.execute_async(cmd, opts), and 5 | # process.execute_sync(cmd, opts) 6 | 7 | Eye.config do 8 | logger '/tmp/eye.log' 9 | end 10 | 11 | Eye.app :triggers do 12 | # Execute shell command before process start 13 | process :a do 14 | pid_file '/tmp/a.pid' 15 | start_command 'sleep 100' 16 | daemonize true 17 | 18 | # send message async which sendxmpp, before process start 19 | trigger :transition, to: :starting, do: -> { 20 | process.execute_async "sendxmpp -s 'hhahahaa' someone@jabber.org" 21 | } 22 | end 23 | 24 | # Touch some file before process start, remove file after process die 25 | process :b do 26 | pid_file '/tmp/b.pid' 27 | start_command 'sleep 100' 28 | daemonize true 29 | 30 | # before process starting, touch some file 31 | trigger :transition1, to: :starting, do: -> { 32 | process.execute_sync 'touch /tmp/bla.file' 33 | } 34 | 35 | # after process, crashed, or stopped, remove that file 36 | trigger :transition2, to: :down, do: -> { 37 | process.execute_sync 'rm /tmp/bla.file' 38 | } 39 | end 40 | 41 | # With restart :c process, send restart to process :a 42 | process :c do 43 | pid_file '/tmp/c.pid' 44 | start_command 'sleep 100' 45 | daemonize true 46 | 47 | app_name = app.name 48 | trigger :transition, event: :restarting, do: -> { 49 | info 'send restarting to :a' 50 | Eye::Control.command('restart', "#{app_name}:a") 51 | } 52 | end 53 | 54 | # process d cant start, until file /tmp/bla contains string 'bla' 55 | process :d do 56 | pid_file '/tmp/d.pid' 57 | start_command 'sleep 100' 58 | daemonize true 59 | 60 | trigger :starting_guard, every: 5.seconds, should: -> { `cat /tmp/bla` =~ /bla/ } 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /examples/unicorn.eye: -------------------------------------------------------------------------------- 1 | # Example: how to run unicorn, and monitor its child processes 2 | 3 | RUBY = '/usr/local/ruby/1.9.3/bin/ruby' # ruby on the server 4 | RAILS_ENV = 'production' 5 | 6 | Eye.application 'rails_unicorn' do 7 | env 'RAILS_ENV' => RAILS_ENV 8 | 9 | # unicorn requires to be `ruby` in path (for soft restart) 10 | env 'PATH' => "#{File.dirname(RUBY)}:#{ENV['PATH']}" 11 | 12 | working_dir File.expand_path(File.join(File.dirname(__FILE__), %w[processes])) 13 | 14 | process('unicorn') do 15 | pid_file 'tmp/pids/unicorn.pid' 16 | start_command "#{RUBY} ./bin/unicorn -Dc ./config/unicorn.rb -E #{RAILS_ENV}" 17 | stdall 'log/unicorn.log' 18 | 19 | # stop signals: 20 | # http://unicorn.bogomips.org/SIGNALS.html 21 | stop_signals [:TERM, 10.seconds] 22 | 23 | # soft restart 24 | restart_command 'kill -USR2 {PID}' 25 | 26 | check :cpu, every: 30, below: 80, times: 3 27 | check :memory, every: 30, below: 150.megabytes, times: [3, 5] 28 | 29 | start_timeout 100.seconds 30 | restart_grace 30.seconds 31 | 32 | monitor_children do 33 | stop_command 'kill -QUIT {PID}' 34 | check :cpu, every: 30, below: 80, times: 3 35 | check :memory, every: 30, below: 150.megabytes, times: [3, 5] 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /eye.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path('../lib/eye', __FILE__) 2 | 3 | Gem::Specification.new do |gem| 4 | gem.authors = 'Konstantin Makarchev' 5 | gem.email = 'eye-rb@googlegroups.com' 6 | 7 | gem.description = gem.summary = \ 8 | 'Process monitoring tool. Inspired from Bluepill and God. Requires Ruby(MRI) >= 1.9.3-p194. Uses Celluloid and Celluloid::IO.' 9 | gem.homepage = 'https://github.com/kostya/eye' 10 | 11 | gem.files = `git ls-files`.split($\).reject { |n| n =~ %r[png|gif\z] }.reject { |n| n =~ %r[^(test|spec|features)/] } 12 | gem.executables = gem.files.grep(%r[^bin/]).map { |f| File.basename(f) } 13 | # gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 14 | gem.name = 'eye' 15 | gem.require_paths = ['lib'] 16 | gem.version = Eye::VERSION 17 | gem.license = 'MIT' 18 | 19 | gem.required_ruby_version = '>= 1.9.2' 20 | gem.required_rubygems_version = '>= 1.3.6' 21 | 22 | gem.add_dependency 'celluloid', '~> 0.17.3' 23 | gem.add_dependency 'celluloid-io', '~> 0.17.0' 24 | gem.add_dependency 'state_machines' 25 | gem.add_dependency 'thor' 26 | gem.add_dependency 'kostya-sigar', '~> 2.0.0' 27 | 28 | gem.add_development_dependency 'rake' 29 | gem.add_development_dependency 'rspec', '< 2.14' 30 | gem.add_development_dependency 'rr', '1.1.2' 31 | gem.add_development_dependency 'ruby-graphviz' 32 | gem.add_development_dependency 'forking' 33 | gem.add_development_dependency 'fakeweb' 34 | gem.add_development_dependency 'eventmachine', '>= 1.0.3' 35 | gem.add_development_dependency 'sinatra' 36 | gem.add_development_dependency 'thin' 37 | gem.add_development_dependency 'xmpp4r' 38 | gem.add_development_dependency 'slack-notifier' 39 | gem.add_development_dependency 'coveralls' 40 | gem.add_development_dependency 'tins', '1.6.0' # for coveralls 41 | gem.add_development_dependency 'simplecov', '>= 0.8.1' 42 | gem.add_development_dependency 'parallel_tests', '<= 1.3.1' 43 | gem.add_development_dependency 'parallel_split_test' 44 | gem.add_development_dependency 'rubocop' 45 | end 46 | -------------------------------------------------------------------------------- /lib/eye.rb: -------------------------------------------------------------------------------- 1 | module Eye 2 | VERSION = '0.10.1.pre' 3 | ABOUT = "Eye v#{VERSION} (c) 2012-2018 @kostya" 4 | PROCLINE = "eye monitoring v#{VERSION}" 5 | 6 | autoload :Process, 'eye/process' 7 | autoload :ChildProcess, 'eye/child_process' 8 | autoload :Server, 'eye/server' 9 | autoload :Logger, 'eye/logger' 10 | autoload :System, 'eye/system' 11 | autoload :SystemResources, 'eye/system_resources' 12 | autoload :Checker, 'eye/checker' 13 | autoload :Trigger, 'eye/trigger' 14 | autoload :Group, 'eye/group' 15 | autoload :Dsl, 'eye/dsl' 16 | autoload :Application, 'eye/application' 17 | autoload :Local, 'eye/local' 18 | autoload :Client, 'eye/client' 19 | autoload :Utils, 'eye/utils' 20 | autoload :Notify, 'eye/notify' 21 | autoload :Config, 'eye/config' 22 | autoload :Sigar, 'eye/sigar' 23 | autoload :Controller, 'eye/controller' 24 | autoload :Control, 'eye/control' 25 | autoload :Cli, 'eye/cli' 26 | end 27 | -------------------------------------------------------------------------------- /lib/eye/application.rb: -------------------------------------------------------------------------------- 1 | class Eye::Application 2 | 3 | attr_reader :groups, :name, :config 4 | 5 | def initialize(name, config = {}) 6 | @groups = Eye::Utils::AliveArray.new 7 | @name = name 8 | @config = config 9 | debug { 'created' } 10 | end 11 | 12 | def logger_tag 13 | full_name 14 | end 15 | 16 | def full_name 17 | @name 18 | end 19 | 20 | def add_group(group) 21 | @groups << group 22 | end 23 | 24 | def resort_groups 25 | @groups.sort! # used group method <=> to compare 26 | end 27 | 28 | def status_data(opts = {}) 29 | h = { name: @name, type: :application, subtree: @groups.map { |gr| gr.status_data(opts) } } 30 | h[:debug] = debug_data if debug 31 | h 32 | end 33 | 34 | def status_data_short 35 | { name: @name, type: :application, subtree: @groups.map(&:status_data_short) } 36 | end 37 | 38 | def debug_data; end 39 | 40 | def send_call(call) 41 | info "call: #{call}" 42 | @groups.each { |group| group.send_call(call) } 43 | end 44 | 45 | def alive? 46 | true # emulate celluloid actor method 47 | end 48 | 49 | def sub_object?(obj) 50 | res = @groups.include?(obj) 51 | res ||= @groups.any? { |gr| gr.sub_object?(obj) } 52 | res 53 | end 54 | 55 | def processes 56 | out = [] 57 | @groups.each { |gr| out += gr.processes.to_a } 58 | Eye::Utils::AliveArray.new(out) 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /lib/eye/checker/children_count.rb: -------------------------------------------------------------------------------- 1 | class Eye::Checker::ChildrenCount < Eye::Checker::Measure 2 | 3 | # check :children_count, :every => 30.seconds, :below => 10, :strategy => :kill_old 4 | # monitor_children should be enabled 5 | 6 | param :strategy, Symbol, nil, :restart, [:restart, :kill_old, :kill_new] 7 | 8 | def get_value 9 | process.children.size 10 | end 11 | 12 | def fire 13 | if strategy == :restart 14 | super 15 | else 16 | pids = ordered_by_date_children_pids 17 | pids = strategy == :kill_old ? pids[0...-below] : pids[below..-1] 18 | kill_pids(pids) 19 | end 20 | end 21 | 22 | private 23 | 24 | def kill_pids(pids) 25 | info "killing pids: #{pids.inspect} for strategy: #{strategy}" 26 | pids.each do |pid| 27 | if child = process.children[pid] 28 | child.schedule command: :stop, reason: "bounded #{check_name}" 29 | end 30 | end 31 | end 32 | 33 | def ordered_by_date_children_pids 34 | children = process.children.values 35 | children.sort_by { |ch| [Eye::SystemResources.start_time(ch.pid).to_i, ch.pid] }.map(&:pid) 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /lib/eye/checker/children_memory.rb: -------------------------------------------------------------------------------- 1 | class Eye::Checker::ChildrenMemory < Eye::Checker::Measure 2 | 3 | # check :children_memory, :every => 30.seconds, :below => 400.megabytes 4 | # monitor_children should be enabled 5 | 6 | def check_name 7 | @check_name ||= "children_memory(#{measure_str})" 8 | end 9 | 10 | def get_value 11 | process.children.values.inject(0) do |sum, ch| 12 | sum + Eye::SystemResources.memory(ch.pid).to_i 13 | end 14 | end 15 | 16 | def human_value(value) 17 | "#{value.to_i / 1024 / 1024}Mb" 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/eye/checker/cpu.rb: -------------------------------------------------------------------------------- 1 | class Eye::Checker::Cpu < Eye::Checker::Measure 2 | 3 | # check :cpu, :every => 3.seconds, :below => 80, :times => [3,5] 4 | 5 | def check_name 6 | @check_name ||= "cpu(#{measure_str})" 7 | end 8 | 9 | def get_value 10 | Eye::SystemResources.cpu(@pid).to_i # nil => 0 11 | end 12 | 13 | def human_value(value) 14 | "#{value}%" 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lib/eye/checker/cputime.rb: -------------------------------------------------------------------------------- 1 | class Eye::Checker::Cputime < Eye::Checker::Measure 2 | 3 | # check :cputime, :every => 1.minute, :below => 120.minutes 4 | 5 | def get_value 6 | Eye::SystemResources.cputime(@pid).to_f 7 | end 8 | 9 | def human_value(value) 10 | "#{value / 60}m" 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /lib/eye/checker/file_ctime.rb: -------------------------------------------------------------------------------- 1 | class Eye::Checker::FileCTime < Eye::Checker 2 | 3 | # Check that file changes (log for example) 4 | # check :ctime, :every => 5.seconds, :file => "/tmp/1.log", :times => [3,5] 5 | 6 | param :file, [String], true 7 | 8 | def initialize(*args) 9 | super 10 | self.file = process.expand_path(file) if process && file 11 | end 12 | 13 | def get_value 14 | File.ctime(file) rescue nil 15 | end 16 | 17 | def human_value(value) 18 | if value.nil? 19 | 'Err' 20 | else 21 | value.strftime('%H:%M') 22 | end 23 | end 24 | 25 | def good?(value) 26 | value.to_i > previous_value.to_i 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /lib/eye/checker/file_size.rb: -------------------------------------------------------------------------------- 1 | class Eye::Checker::FileSize < Eye::Checker::Measure 2 | 3 | # Check that file size changed (log for example) 4 | # check :fsize, :every => 5.seconds, :file => "/tmp/1.log", :times => [3,5], 5 | # :below => 30.kilobytes, :above => 10.kilobytes 6 | 7 | param :file, [String], true 8 | 9 | def initialize(*args) 10 | super 11 | self.file = process.expand_path(file) if process && file 12 | end 13 | 14 | def check_name 15 | @check_name ||= "fsize(#{measure_str})" 16 | end 17 | 18 | def get_value 19 | File.size(file) rescue nil 20 | end 21 | 22 | def human_value(value) 23 | "#{value.to_i / 1024}Kb" 24 | end 25 | 26 | def good?(value) 27 | return true unless previous_value 28 | 29 | diff = value.to_i - previous_value.to_i 30 | 31 | return true if diff < 0 # case when logger nulled 32 | 33 | return false unless super(diff) 34 | return false if diff == 0 35 | 36 | true 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /lib/eye/checker/file_touched.rb: -------------------------------------------------------------------------------- 1 | class Eye::Checker::FileTouched < Eye::Checker 2 | 3 | param :file, [String], true 4 | param :delete, [TrueClass, FalseClass] 5 | 6 | def initialize(*args) 7 | super 8 | self.file = process.expand_path(file) if process && file 9 | end 10 | 11 | def get_value 12 | File.exist?(file) 13 | end 14 | 15 | def good?(value) 16 | File.delete(file) if value && delete 17 | !value 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/eye/checker/memory.rb: -------------------------------------------------------------------------------- 1 | class Eye::Checker::Memory < Eye::Checker::Measure 2 | 3 | # check :memory, :every => 3.seconds, :below => 80.megabytes, :times => [3,5] 4 | 5 | def check_name 6 | @check_name ||= "memory(#{measure_str})" 7 | end 8 | 9 | def get_value 10 | Eye::SystemResources.memory(@pid).to_i 11 | end 12 | 13 | def human_value(value) 14 | "#{value.to_i / 1024 / 1024}Mb" 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lib/eye/checker/nop.rb: -------------------------------------------------------------------------------- 1 | class Eye::Checker::Nop < Eye::Checker 2 | 3 | # check :nop, :every => 10.hours # means restart every 10 hours 4 | 5 | def get_value; end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /lib/eye/checker/runtime.rb: -------------------------------------------------------------------------------- 1 | class Eye::Checker::Runtime < Eye::Checker::Measure 2 | 3 | # check :runtime, :every => 1.minute, :below => 120.minutes 4 | 5 | def get_value 6 | st = Eye::SystemResources.start_time(@pid) 7 | if st 8 | Time.now.to_i - st.to_i 9 | else 10 | 0 11 | end 12 | end 13 | 14 | def human_value(value) 15 | "#{value / 60}m" 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /lib/eye/checker/ssl_socket.rb: -------------------------------------------------------------------------------- 1 | require 'openssl' 2 | 3 | class Eye::Checker::SslSocket < Eye::Checker::Socket 4 | 5 | # ctx params from http://ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html 6 | # 7 | # examples: 8 | # 9 | # check :ssl_socket, :addr => "tcp://127.0.0.1:443", :every => 5.seconds, :times => 1, :timeout => 1.second, 10 | # :ctx => {ssl_version: :SSLv23, verify_mode: OpenSSL::SSL::VERIFY_NONE} 11 | # 12 | 13 | param :ctx, Hash, nil, ssl_version: :SSLv23, verify_mode: OpenSSL::SSL::VERIFY_NONE 14 | 15 | private 16 | 17 | def open_socket 18 | OpenSSL::SSL::SSLSocket.new(super, ctx_params).tap do |socket| 19 | socket.sync_close = true 20 | socket.connect 21 | end 22 | end 23 | 24 | def ctx_params 25 | @ctx_params ||= OpenSSL::SSL::SSLContext.new.tap { |c| c.set_params(ctx) } 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /lib/eye/child_process.rb: -------------------------------------------------------------------------------- 1 | require 'celluloid' 2 | 3 | class Eye::ChildProcess 4 | 5 | include Celluloid 6 | 7 | # needs: kill_process 8 | include Eye::Process::Commands 9 | 10 | # easy config + defaults: prepare_config, c, [] 11 | include Eye::Process::Config 12 | 13 | # conditional watchers: start_checkers 14 | include Eye::Process::Watchers 15 | 16 | # system methods: send_signal 17 | include Eye::Process::System 18 | 19 | # self_status_data 20 | include Eye::Process::Data 21 | 22 | # manage notify methods 23 | include Eye::Process::Notify 24 | 25 | # scheduler 26 | include Eye::Process::Scheduler 27 | 28 | attr_reader :pid, :name, :full_name, :config, :watchers 29 | 30 | def initialize(pid, config = {}, logger_prefix = nil, parent = nil) 31 | raise 'Empty pid' unless pid 32 | 33 | @pid = pid 34 | @config = prepare_config(config) 35 | @name = "child-#{pid}" 36 | @full_name = [logger_prefix, @name].join(':') 37 | 38 | @watchers = {} 39 | 40 | @scheduler_history = parent.scheduler_history 41 | @parent_pid = parent.pid 42 | 43 | debug { "start monitoring CHILD config: #{@config.inspect}" } 44 | 45 | start_checkers 46 | end 47 | 48 | def logger_tag 49 | full_name 50 | end 51 | 52 | def state 53 | :up 54 | end 55 | 56 | def up? 57 | state == :up 58 | end 59 | 60 | def start; end 61 | 62 | def stop 63 | kill_process 64 | end 65 | 66 | def restart 67 | if self[:restart_command] 68 | execute_restart_command 69 | else 70 | stop 71 | end 72 | end 73 | 74 | def monitor; end 75 | 76 | def unmonitor; end 77 | 78 | def delete; end 79 | 80 | def destroy 81 | remove_watchers 82 | terminate 83 | end 84 | 85 | def signal(sig) 86 | send_signal(sig) if pid 87 | end 88 | 89 | def status_data(opts = {}) 90 | self_status_data(opts) 91 | end 92 | 93 | # override 94 | def prepare_command(command) 95 | super.gsub('{PARENT_PID}', @parent_pid.to_s) 96 | end 97 | 98 | end 99 | -------------------------------------------------------------------------------- /lib/eye/cli/commands.rb: -------------------------------------------------------------------------------- 1 | module Eye::Cli::Commands 2 | 3 | private 4 | 5 | def client 6 | @client ||= Eye::Client.new(Eye::Local.socket_path) 7 | end 8 | 9 | def _cmd(cmd, *args) 10 | client.execute(command: cmd, args: args) 11 | rescue Errno::ECONNREFUSED, Errno::ENOENT 12 | :not_started 13 | end 14 | 15 | def cmd(cmd, *args) 16 | res = _cmd(cmd, *args) 17 | 18 | if res == :not_started 19 | error! "socket(#{Eye::Local.socket_path}) not found, did you run `eye load`?" 20 | elsif res == :timeouted 21 | error! 'eye timed out without responding...' 22 | end 23 | 24 | res 25 | end 26 | 27 | def say_load_result(res = {}, opts = {}) 28 | error!(res) unless res.is_a?(Hash) 29 | say_filename = (res.size > 1) 30 | error = false 31 | res.each do |filename, res2| 32 | say "#{filename}: ", nil, true if say_filename 33 | show_load_message(res2, opts) 34 | error = true if res2[:error] 35 | end 36 | 37 | exit(1) if error 38 | end 39 | 40 | def show_load_message(res, opts = {}) 41 | if res[:error] 42 | say res[:message], :red 43 | res[:backtrace].to_a.each { |line| say line, :red } 44 | else 45 | unless res[:empty] 46 | say(opts[:syntax] ? 'Config ok!' : 'Config loaded!', :green) 47 | end 48 | 49 | if opts[:print_config] 50 | require 'pp' 51 | PP.pp res[:config], STDOUT, 150 52 | end 53 | end 54 | end 55 | 56 | def send_command(command, *args) 57 | res = cmd(command, *args) 58 | if res == :unknown_command 59 | error! "unknown command :#{command}" 60 | elsif res == :corrupted_data 61 | error! 'something crazy wrong, check eye logs!' 62 | elsif res.is_a?(Hash) 63 | if res[:error] 64 | error! "Error: #{res[:error]}" 65 | elsif res = res[:result] 66 | if res == [] 67 | error! "command :#{command}, objects not found!" 68 | else 69 | say "command :#{command} sent to [#{res * ', '}]" 70 | end 71 | end 72 | else 73 | error! "unknown result #{res.inspect}" 74 | end 75 | end 76 | 77 | end 78 | -------------------------------------------------------------------------------- /lib/eye/cli/server.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Eye::Cli::Server 4 | 5 | private 6 | 7 | def server_started? 8 | _cmd(:ping) == :pong 9 | end 10 | 11 | def loader_path 12 | filename = File.expand_path(File.join(File.dirname(__FILE__), %w[.. .. .. bin loader_eye])) 13 | File.exist?(filename) ? filename : nil 14 | end 15 | 16 | def ruby_path 17 | RbConfig.ruby 18 | end 19 | 20 | def ensure_loader_path 21 | unless loader_path 22 | error! "start monitoring needs to run under ruby with installed gem 'eye'" 23 | end 24 | end 25 | 26 | def server_start_foreground(conf = nil) 27 | ensure_loader_path 28 | Eye::Local.ensure_eye_dir 29 | 30 | args = [] 31 | args += ['--config', conf] if conf 32 | args += ['--logger', 'stdout'] 33 | args += ['--stop_all'] 34 | if Eye::Local.local_runner 35 | args += ['--dir', Eye::Local.dir] 36 | if !conf && Eye::Local.eyefile 37 | args += ['--config', Eye::Local.eyefile] 38 | end 39 | end 40 | 41 | Process.exec(ruby_path, loader_path, *args) 42 | end 43 | 44 | def server_start(configs) 45 | ensure_loader_path 46 | Eye::Local.ensure_eye_dir 47 | 48 | ensure_stop_previous_server 49 | 50 | args = [] 51 | args += ['--dir', Eye::Local.dir] if Eye::Local.local_runner 52 | 53 | chdir = if Eye::Local.local_runner 54 | Eye::Local.home 55 | else 56 | '/' 57 | end 58 | 59 | opts = { out: '/dev/null', err: '/dev/null', in: '/dev/null', 60 | chdir: chdir, pgroup: true } 61 | 62 | pid = Process.spawn(ruby_path, loader_path, *args, opts) 63 | Process.detach(pid) 64 | File.open(Eye::Local.pid_path, 'w') { |f| f.write(pid) } 65 | 66 | unless wait_server 67 | error! 'server has not started in 15 seconds, something is very wrong' 68 | end 69 | 70 | configs.unshift(Eye::Local.global_eyeconfig) if File.exist?(Eye::Local.global_eyeconfig) 71 | configs.unshift(Eye::Local.eyeconfig) if File.exist?(Eye::Local.eyeconfig) 72 | configs << Eye::Local.eyefile if Eye::Local.local_runner && Eye::Local.eyefile 73 | 74 | say "Eye started! ㋡ (#{Eye::Local.home})", :green 75 | 76 | if configs.any? 77 | say_load_result cmd(:load, *configs) 78 | end 79 | end 80 | 81 | def ensure_stop_previous_server 82 | Eye::Local.ensure_eye_dir 83 | pid = File.read(Eye::Local.pid_path).to_i rescue nil 84 | Process.kill(9, pid) rescue nil if pid 85 | File.delete(Eye::Local.pid_path) rescue nil 86 | true 87 | end 88 | 89 | def wait_server(timeout = 15) 90 | Timeout.timeout(timeout) do 91 | sleep 0.3 until server_started? 92 | end 93 | true 94 | rescue Timeout::Error 95 | false 96 | end 97 | 98 | end 99 | -------------------------------------------------------------------------------- /lib/eye/client.rb: -------------------------------------------------------------------------------- 1 | require 'socket' 2 | require 'timeout' 3 | 4 | class Eye::Client 5 | 6 | attr_reader :socket_path 7 | 8 | def initialize(socket_path, type = :old) 9 | @socket_path = socket_path 10 | @type = type 11 | end 12 | 13 | SIGN = 123_566_983 14 | 15 | def execute(h = {}) 16 | payload = if @type == :old 17 | Marshal.dump([h[:command], *h[:args]]) # TODO: remove in 1.0 18 | else 19 | payload = Marshal.dump(h) 20 | [SIGN, payload.length].pack('N*') + payload 21 | end 22 | timeout = h[:timeout] || Eye::Local.client_timeout 23 | attempt_command(payload, timeout) 24 | end 25 | 26 | private 27 | 28 | def attempt_command(payload, timeout) 29 | Timeout.timeout(timeout) { send_request(payload) } 30 | rescue Timeout::Error, EOFError 31 | :timeouted 32 | end 33 | 34 | def send_request(pack) 35 | UNIXSocket.open(@socket_path) do |socket| 36 | socket.write(pack) 37 | data = socket.read 38 | Marshal.load(data) rescue :corrupted_data 39 | end 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /lib/eye/control.rb: -------------------------------------------------------------------------------- 1 | # controller global singlton 2 | Eye::Control = Eye::Controller.new 3 | -------------------------------------------------------------------------------- /lib/eye/controller.rb: -------------------------------------------------------------------------------- 1 | require 'celluloid/current' 2 | require 'yaml' 3 | 4 | require_relative 'utils/pmap' 5 | require_relative 'utils/mini_active_support' 6 | 7 | # Extend all objects with logger 8 | Object.send(:include, Eye::Logger::ObjectExt) 9 | 10 | # needs to preload 11 | Eye::Sigar 12 | Eye::SystemResources 13 | 14 | class Eye::Controller 15 | 16 | include Celluloid 17 | 18 | autoload :Load, 'eye/controller/load' 19 | autoload :Helpers, 'eye/controller/helpers' 20 | autoload :Commands, 'eye/controller/commands' 21 | autoload :Status, 'eye/controller/status' 22 | autoload :Apply, 'eye/controller/apply' 23 | autoload :Options, 'eye/controller/options' 24 | 25 | include Eye::Controller::Load 26 | include Eye::Controller::Helpers 27 | include Eye::Controller::Commands 28 | include Eye::Controller::Status 29 | include Eye::Controller::Apply 30 | include Eye::Controller::Options 31 | 32 | attr_reader :applications, :current_config 33 | 34 | def initialize 35 | @applications = [] 36 | @current_config = Eye::Config.new 37 | 38 | Celluloid.logger = Eye::Logger.new('celluloid') 39 | 40 | info "starting #{Eye::ABOUT} <#{$$}>" 41 | end 42 | 43 | def settings 44 | current_config.settings 45 | end 46 | 47 | def logger_tag 48 | 'Eye' 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /lib/eye/controller/commands.rb: -------------------------------------------------------------------------------- 1 | module Eye::Controller::Commands 2 | 3 | NOT_IMPORTANT_COMMANDS = [:info_data, :short_data, :debug_data, :history_data, :ping, 4 | :logger_dev, :match, :explain, :check].freeze 5 | 6 | # Main method, answer for the client command 7 | def command(cmd, *args) 8 | opts = args.extract_options! 9 | msg = "command: #{cmd} #{args * ', '}" 10 | 11 | log_str = "=> #{msg}" 12 | NOT_IMPORTANT_COMMANDS.include?(cmd) ? debug(log_str) : info(log_str) 13 | 14 | start_at = Time.now 15 | cmd = cmd.to_sym 16 | 17 | res = case cmd 18 | 19 | # scheduled command 20 | when :start, :stop, :restart, :unmonitor, :monitor, :break_chain 21 | apply(args, command: cmd, signal: opts[:signal]) 22 | when :delete 23 | exclusive { apply(args, command: cmd, signal: opts[:signal]) } 24 | when :signal, :user_command 25 | apply(args[1..-1], command: cmd, args: args[0...1], signal: opts[:signal]) 26 | 27 | # inline command 28 | when :load 29 | exclusive { load(*args) } 30 | when :quit 31 | quit 32 | when :stop_all 33 | stop_all(*args) 34 | when :check 35 | check(*args) 36 | when :explain 37 | explain(*args) 38 | when :match 39 | match(*args) 40 | when :ping 41 | :pong 42 | when :logger_dev 43 | Eye::Logger.dev.to_s 44 | 45 | # object commands, for api 46 | when :info_data 47 | info_data(*args) 48 | when :short_data 49 | short_data(*args) 50 | when :debug_data 51 | debug_data(*args) 52 | when :history_data 53 | history_data(*args) 54 | 55 | else 56 | :unknown_command 57 | end 58 | 59 | GC.start 60 | 61 | log_str = "<= #{msg} (#{Time.now - start_at}s)" 62 | NOT_IMPORTANT_COMMANDS.include?(cmd) ? debug(log_str) : info(log_str) 63 | 64 | res 65 | end 66 | 67 | private 68 | 69 | def quit 70 | info 'Quit!' 71 | Eye::System.send_signal($$, :TERM) 72 | sleep 1 73 | Eye::System.send_signal($$, :KILL) 74 | end 75 | 76 | # stop all processes and wait 77 | def stop_all(timeout = nil) 78 | # TODO: rewrite with signal 79 | exclusive do 80 | apply(%w[all], command: :break_chain) 81 | apply(%w[all], command: :stop, freeze: true) 82 | end 83 | 84 | # wait until all processes goes to unmonitored 85 | timeout ||= 100 86 | 87 | all_processes.pmap do |p| 88 | p.wait_for_condition(timeout, 0.3) do 89 | p.state_name == :unmonitored 90 | end 91 | end 92 | end 93 | 94 | end 95 | -------------------------------------------------------------------------------- /lib/eye/controller/helpers.rb: -------------------------------------------------------------------------------- 1 | module Eye::Controller::Helpers 2 | 3 | def set_proc_line 4 | str = Eye::PROCLINE 5 | str = 'l' + str if Eye::Local.local_runner 6 | str += " [#{@applications.map(&:name) * ', '}]" if @applications.present? 7 | str += " (v #{ENV['EYE_V']})" if ENV['EYE_V'] 8 | str += " (in #{Eye::Local.home})" 9 | $0 = str 10 | end 11 | 12 | def save_cache 13 | File.open(Eye::Local.cache_path, 'w') { |f| f.write(cache_str) } 14 | rescue => ex 15 | log_ex(ex) 16 | end 17 | 18 | def cache_str 19 | all_processes.map { |p| "#{p.full_name}=#{p.state}" } * "\n" 20 | end 21 | 22 | def process_by_name(name) 23 | name = name.to_s 24 | all_processes.detect { |c| c.name == name } 25 | end 26 | 27 | def process_by_full_name(name) 28 | name = name.to_s 29 | all_processes.detect { |c| c.full_name == name } 30 | end 31 | 32 | def find_nearest_process(name, group_name = nil, app_name = nil) 33 | return process_by_full_name(name) if name.include?(':') 34 | 35 | if app_name 36 | if app = application_by_name(app_name) 37 | app.groups.each do |gr| 38 | p = gr.processes.detect { |c| c.name == name } 39 | return p if p 40 | end 41 | end 42 | end 43 | 44 | if group_name 45 | if gr = group_by_name(group_name) 46 | p = gr.processes.detect { |c| c.name == name } 47 | return p if p 48 | end 49 | end 50 | 51 | process_by_name(name) 52 | end 53 | 54 | def group_by_name(name) 55 | name = name.to_s 56 | all_groups.detect { |c| c.name == name } 57 | end 58 | 59 | def application_by_name(name) 60 | name = name.to_s 61 | @applications.detect { |c| c.name == name } 62 | end 63 | 64 | def all_processes 65 | processes = [] 66 | all_groups.each do |gr| 67 | processes += gr.processes.to_a 68 | end 69 | 70 | processes 71 | end 72 | 73 | def all_groups 74 | groups = [] 75 | @applications.each do |app| 76 | groups += app.groups.to_a 77 | end 78 | 79 | groups 80 | end 81 | 82 | # {'app_name' => {'group_name' => {'process_name' => 'pid_file'}}} 83 | def short_tree 84 | res = {} 85 | @applications.each do |app| 86 | res2 = {} 87 | 88 | app.groups.each do |group| 89 | res3 = {} 90 | 91 | group.processes.each do |process| 92 | res3[process.name] = process[:pid_file_ex] 93 | end 94 | 95 | res2[group.name] = res3 96 | end 97 | 98 | res[app.name] = res2 99 | end 100 | 101 | res 102 | end 103 | 104 | end 105 | -------------------------------------------------------------------------------- /lib/eye/controller/options.rb: -------------------------------------------------------------------------------- 1 | module Eye::Controller::Options 2 | 3 | def set_opt_logger(logger_args) 4 | # do not apply logger, if in stdout state 5 | unless %w[stdout stderr].include?(Eye::Logger.dev) 6 | Eye::Logger.link_logger(*logger_args) 7 | end 8 | end 9 | 10 | def set_opt_logger_level(level) 11 | Eye::Logger.log_level = level 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/eye/controller/status.rb: -------------------------------------------------------------------------------- 1 | module Eye::Controller::Status 2 | 3 | def debug_data(*args) 4 | h = args.extract_options! 5 | actors = Celluloid::Actor.all.map { |actor| actor.wrapped_object.class.to_s }.group_by { |a| a } 6 | actors = actors.map { |k, v| [k, v.size] }.sort_by { |a| a[1] }.reverse 7 | 8 | res = { 9 | about: Eye::ABOUT, 10 | resources: Eye::SystemResources.resources($$), 11 | ruby: RUBY_DESCRIPTION, 12 | gems: %w[Celluloid Celluloid::IO StateMachines NIO Timers Sigar].map { |c| gem_version(c) }, 13 | logger: Eye::Logger.args.present? ? [Eye::Logger.dev.to_s, *Eye::Logger.args] : Eye::Logger.dev.to_s, 14 | home: Eye::Local.home, 15 | dir: Eye::Local.dir, 16 | pid_path: Eye::Local.pid_path, 17 | sock_path: Eye::Local.socket_path, 18 | actors: actors 19 | } 20 | 21 | res[:config_yaml] = YAML.dump(current_config.to_h) if h[:config].present? 22 | 23 | res 24 | end 25 | 26 | def info_data(*args) 27 | h = args.extract_options! 28 | { subtree: info_objects(*args).map { |a| a.status_data(h) } } 29 | end 30 | 31 | def short_data(*args) 32 | { subtree: info_objects(*args).select { |o| o.class == Eye::Application }.map(&:status_data_short) } 33 | end 34 | 35 | def history_data(*args) 36 | res = {} 37 | history_objects(*args).each do |process| 38 | res[process.full_name] = process.scheduler_history.reject { |c| c[:state] == :check_crash } 39 | end 40 | res 41 | end 42 | 43 | private 44 | 45 | def info_objects(*args) 46 | res = [] 47 | return @applications if args.empty? 48 | matched_objects(*args) { |obj| res << obj } 49 | res 50 | end 51 | 52 | def gem_version(klass) 53 | v = nil 54 | begin 55 | v = eval("#{klass}::VERSION::STRING") 56 | rescue 57 | v = eval("#{klass}::VERSION") rescue '' 58 | end 59 | "#{klass}=#{v}" 60 | end 61 | 62 | def history_objects(*args) 63 | args = ['*'] if args.empty? 64 | res = [] 65 | matched_objects(*args) do |obj| 66 | if obj.is_a?(Eye::Process) 67 | res << obj 68 | elsif obj.is_a?(Eye::ChildProcess) 69 | else 70 | res += obj.processes.to_a 71 | end 72 | end 73 | Eye::Utils::AliveArray.new(res) 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /lib/eye/dsl.rb: -------------------------------------------------------------------------------- 1 | require_relative 'dsl/helpers' 2 | 3 | Eye::BINDING = binding 4 | 5 | class Eye::Dsl 6 | 7 | autoload :Main, 'eye/dsl/main' 8 | autoload :ApplicationOpts, 'eye/dsl/application_opts' 9 | autoload :GroupOpts, 'eye/dsl/group_opts' 10 | autoload :ProcessOpts, 'eye/dsl/process_opts' 11 | autoload :ChildProcessOpts, 'eye/dsl/child_process_opts' 12 | autoload :Opts, 'eye/dsl/opts' 13 | autoload :PureOpts, 'eye/dsl/pure_opts' 14 | autoload :Chain, 'eye/dsl/chain' 15 | autoload :ConfigOpts, 'eye/dsl/config_opts' 16 | autoload :Validation, 'eye/dsl/validation' 17 | 18 | class Error < RuntimeError; end 19 | 20 | class << self 21 | 22 | attr_accessor :verbose 23 | 24 | def debug(msg = '') 25 | puts msg if verbose 26 | end 27 | 28 | def parse(content = nil, filename = nil) 29 | Eye.parsed_config = Eye::Config.new 30 | Eye.parsed_filename = filename 31 | 32 | content = File.read(filename) if content.blank? 33 | 34 | silence_warnings do 35 | Kernel.eval(content, Eye::BINDING, filename.to_s) 36 | end 37 | 38 | Eye.parsed_config.transform! 39 | Eye.parsed_config.validate! 40 | parsed_config = Eye.parsed_config 41 | Eye.parsed_config = nil # remove object for better GC 42 | parsed_config 43 | end 44 | 45 | def parse_apps(*args) 46 | parse(*args).applications 47 | end 48 | 49 | def check_name(name) 50 | raise Error, "':' is not allowed in name '#{name}'" if name.to_s.include?(':') 51 | end 52 | 53 | end 54 | 55 | end 56 | 57 | # extend here global module 58 | Eye.send(:extend, Eye::Dsl::Main) 59 | -------------------------------------------------------------------------------- /lib/eye/dsl/application_opts.rb: -------------------------------------------------------------------------------- 1 | class Eye::Dsl::ApplicationOpts < Eye::Dsl::Opts 2 | 3 | include Eye::Dsl::Chain 4 | 5 | def disallow_options 6 | [:pid_file, :start_command, :daemonize] 7 | end 8 | 9 | def not_seed_options 10 | [:groups] 11 | end 12 | 13 | def group(name, &block) 14 | Eye::Dsl.check_name(name) 15 | Eye::Dsl.debug { "=> group #{name}" } 16 | 17 | opts = Eye::Dsl::GroupOpts.new(name, self) 18 | opts.instance_eval(&block) 19 | 20 | @config[:groups] ||= {} 21 | @config[:groups][name.to_s] ||= {} 22 | 23 | if cfg = opts.config 24 | Eye::Utils.deep_merge!(@config[:groups][name.to_s], cfg) 25 | end 26 | 27 | Eye::Dsl.debug { "<= group #{name}" } 28 | opts 29 | end 30 | 31 | def process(name, &block) 32 | res = nil 33 | group('__default__') { res = process(name.to_s, &block) } 34 | res 35 | end 36 | 37 | alias xgroup nop 38 | alias xprocess nop 39 | 40 | end 41 | -------------------------------------------------------------------------------- /lib/eye/dsl/chain.rb: -------------------------------------------------------------------------------- 1 | module Eye::Dsl::Chain 2 | 3 | def chain(opts = {}) 4 | acts = Array(opts[:action] || opts[:actions] || [:start, :restart]) 5 | 6 | acts.each do |act| 7 | @config[:chain] ||= {} 8 | @config[:chain][act] = opts.merge(action: act) 9 | end 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /lib/eye/dsl/child_process_opts.rb: -------------------------------------------------------------------------------- 1 | class Eye::Dsl::ChildProcessOpts < Eye::Dsl::Opts 2 | 3 | def allow_options 4 | [:stop_command, :restart_command, :children_update_period, 5 | :stop_signals, :stop_grace, :stop_timeout, :restart_timeout] 6 | end 7 | 8 | def triggers(*_args) 9 | raise Eye::Dsl::Error, 'triggers not allowed in monitor_children' 10 | end 11 | alias trigger triggers 12 | 13 | end 14 | -------------------------------------------------------------------------------- /lib/eye/dsl/config_opts.rb: -------------------------------------------------------------------------------- 1 | class Eye::Dsl::ConfigOpts < Eye::Dsl::PureOpts 2 | 3 | create_options_methods([:logger_level], Integer) 4 | create_options_methods([:http], Hash) 5 | 6 | def logger(*args) 7 | if args.empty? 8 | @config[:logger] 9 | else 10 | @config[:logger] = args 11 | end 12 | end 13 | alias logger= logger 14 | 15 | def syslog(name = 'eye', *args) 16 | require 'syslog/logger' 17 | Syslog::Logger.new(name, *args) 18 | rescue LoadError 19 | raise Eye::Dsl::Error, 'logger syslog requires Ruby >= 2.0' 20 | end 21 | 22 | # ==== contact options ============================== 23 | def self.add_notify(type) 24 | create_options_methods([type], Hash) 25 | 26 | define_method("set_#{type}") do |value| 27 | value = value.merge(type: type) 28 | super(value) 29 | Eye::Notify.validate!(value) 30 | end 31 | end 32 | 33 | Eye::Notify::TYPES.each_key { |name| add_notify(name) } 34 | 35 | def contact(contact_name, contact_type, contact, contact_opts = {}) 36 | raise Eye::Dsl::Error, "unknown contact_type #{contact_type}" unless Eye::Notify::TYPES[contact_type] 37 | raise Eye::Dsl::Error, 'contact should be a String' unless contact.is_a?(String) 38 | 39 | notify_hash = @config[contact_type] || (@parent && @parent.config[contact_type]) || Eye.parsed_config.settings[contact_type] || {} 40 | validate_hash = notify_hash.merge(contact_opts).merge(type: contact_type) 41 | 42 | Eye::Notify.validate!(validate_hash) 43 | 44 | @config[:contacts] ||= {} 45 | @config[:contacts][contact_name.to_s] = { name: contact_name.to_s, type: contact_type, 46 | contact: contact, opts: contact_opts } 47 | end 48 | 49 | def contact_group(contact_group_name, &block) 50 | c = Eye::Dsl::ConfigOpts.new nil, self, false 51 | c.instance_eval(&block) 52 | cfg = c.config 53 | @config[:contacts] ||= {} 54 | if cfg[:contacts].present? 55 | @config[:contacts][contact_group_name.to_s] = cfg[:contacts].values 56 | @config[:contacts].merge!(cfg[:contacts]) 57 | end 58 | end 59 | 60 | end 61 | -------------------------------------------------------------------------------- /lib/eye/dsl/group_opts.rb: -------------------------------------------------------------------------------- 1 | class Eye::Dsl::GroupOpts < Eye::Dsl::Opts 2 | 3 | include Eye::Dsl::Chain 4 | 5 | def disallow_options 6 | [:pid_file, :start_command, :daemonize] 7 | end 8 | 9 | def not_seed_options 10 | [:processes, :chain] 11 | end 12 | 13 | def process(name, &block) 14 | Eye::Dsl.check_name(name) 15 | 16 | Eye::Dsl.debug { "=> process #{name}" } 17 | 18 | opts = Eye::Dsl::ProcessOpts.new(name, self) 19 | opts.instance_eval(&block) 20 | @config[:processes] ||= {} 21 | @config[:processes][name.to_s] ||= {} 22 | Eye::Utils.deep_merge!(@config[:processes][name.to_s], opts.config) if opts.config 23 | 24 | Eye::Dsl.debug { "<= process #{name}" } 25 | opts 26 | end 27 | 28 | alias xprocess nop 29 | alias application parent 30 | alias app application 31 | 32 | end 33 | -------------------------------------------------------------------------------- /lib/eye/dsl/helpers.rb: -------------------------------------------------------------------------------- 1 | 2 | # Dsl Helpers 3 | 4 | # current eye parsed config path 5 | def current_config_path 6 | Eye.parsed_filename && File.symlink?(Eye.parsed_filename) ? File.readlink(Eye.parsed_filename) : Eye.parsed_filename 7 | end 8 | 9 | # host name 10 | def hostname 11 | Eye::Local.host 12 | end 13 | 14 | def example_process(proxy, name) 15 | proxy.process(name) do 16 | pid_file "/tmp/#{name}.pid" 17 | start_command 'sleep 100' 18 | daemonize true 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/eye/dsl/main.rb: -------------------------------------------------------------------------------- 1 | module Eye::Dsl::Main 2 | 3 | attr_accessor :parsed_config, :parsed_filename, :parsed_default_app 4 | 5 | def application(name, &block) 6 | Eye::Dsl.check_name(name) 7 | name = name.to_s 8 | 9 | Eye::Dsl.debug { "=> app: #{name}" } 10 | 11 | if name == '__default__' 12 | @parsed_default_app ||= Eye::Dsl::ApplicationOpts.new(name) 13 | @parsed_default_app.instance_eval(&block) 14 | else 15 | opts = Eye::Dsl::ApplicationOpts.new(name, @parsed_default_app) 16 | opts.instance_eval(&block) 17 | @parsed_config.applications[name] = opts.config if opts.config 18 | end 19 | 20 | Eye::Dsl.debug { "<= app: #{name}" } 21 | end 22 | 23 | alias project application 24 | alias app application 25 | 26 | def load(glob = '') 27 | return if glob.blank? 28 | 29 | loaded = false 30 | Eye::Dsl::Opts.with_parsed_file(glob) do |mask| 31 | Dir[mask].each do |path| 32 | loaded = true 33 | Eye::Dsl.debug { "=> load #{path}" } 34 | Eye.parsed_filename = path 35 | res = Kernel.load(path) 36 | Eye.info "load: subload #{path} (#{res})" 37 | Eye::Dsl.debug { "<= load #{path}" } 38 | end 39 | end 40 | 41 | unless loaded 42 | puts "Warning! Eye.load not found: '#{glob}'" 43 | warn "not found: '#{glob}'" 44 | end 45 | end 46 | 47 | def config(&block) 48 | Eye::Dsl.debug { '=> config' } 49 | 50 | opts = Eye::Dsl::ConfigOpts.new 51 | opts.instance_eval(&block) 52 | Eye::Utils.deep_merge!(@parsed_config.settings, opts.config) 53 | 54 | Eye::Dsl.debug { '<= config' } 55 | end 56 | 57 | alias settings config 58 | 59 | def shared 60 | require 'ostruct' 61 | @shared_object ||= OpenStruct.new 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /lib/eye/dsl/process_opts.rb: -------------------------------------------------------------------------------- 1 | class Eye::Dsl::ProcessOpts < Eye::Dsl::Opts 2 | 3 | def monitor_children(&block) 4 | opts = Eye::Dsl::ChildProcessOpts.new 5 | opts.instance_eval(&block) if block 6 | @config[:monitor_children] ||= {} 7 | Eye::Utils.deep_merge!(@config[:monitor_children], opts.config) 8 | end 9 | 10 | alias xmonitor_children nop 11 | 12 | def application 13 | parent.try(:parent) 14 | end 15 | alias app application 16 | alias group parent 17 | 18 | def depend_on(names, opts = {}) 19 | names = Array(names).map(&:to_s) 20 | trigger("wait_dependency_#{unique_num}", { names: names }.merge(opts)) 21 | nm = @config[:name] 22 | names.each do |name| 23 | parent.process(name) do 24 | trigger("check_dependency_#{unique_num}", names: [nm]) 25 | end 26 | end 27 | 28 | skip_group_action(:restart, [:up, :down, :starting, :stopping, :restarting]) 29 | end 30 | 31 | private 32 | 33 | def unique_num 34 | self.class.unique_num ||= 0 35 | self.class.unique_num += 1 36 | end 37 | 38 | class << self 39 | 40 | attr_accessor :unique_num 41 | 42 | end 43 | 44 | end 45 | -------------------------------------------------------------------------------- /lib/eye/group.rb: -------------------------------------------------------------------------------- 1 | require 'celluloid' 2 | 3 | class Eye::Group 4 | 5 | include Celluloid 6 | 7 | autoload :Call, 'eye/group/call' 8 | autoload :Chain, 'eye/group/chain' 9 | autoload :Data, 'eye/group/data' 10 | 11 | include Eye::Process::Scheduler 12 | include Eye::Group::Call 13 | include Eye::Group::Chain 14 | include Eye::Group::Data 15 | 16 | attr_reader :processes, :name, :hidden, :config 17 | 18 | def initialize(name, config) 19 | @name = name 20 | @config = config 21 | @processes = Eye::Utils::AliveArray.new 22 | @hidden = (name == '__default__') 23 | debug { 'created' } 24 | end 25 | 26 | def logger_tag 27 | full_name 28 | end 29 | 30 | def app_name 31 | @config[:application] 32 | end 33 | 34 | def full_name 35 | @full_name ||= "#{app_name}:#{@name}" 36 | end 37 | 38 | def add_process(process) 39 | @processes << process 40 | end 41 | 42 | # sort processes in name order 43 | def resort_processes 44 | @processes = @processes.sort_by(&:name) 45 | end 46 | 47 | def clear 48 | @processes = Eye::Utils::AliveArray.new 49 | end 50 | 51 | def sub_object?(obj) 52 | @processes.include?(obj) 53 | end 54 | 55 | # to sort groups 56 | def <=>(other) 57 | if hidden 58 | 1 59 | elsif other.hidden 60 | -1 61 | else 62 | name <=> other.name 63 | end 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /lib/eye/group/call.rb: -------------------------------------------------------------------------------- 1 | module Eye::Group::Call 2 | 3 | # :update_config, :start, :stop, :restart, :unmonitor, :monitor, :break_chain, :delete, :signal, :user_command 4 | def send_call(call) 5 | info "call: #{call[:method]}" 6 | 7 | case call[:command] 8 | when :delete 9 | delete 10 | when :break_chain 11 | break_chain 12 | else 13 | user_schedule(call) 14 | end 15 | end 16 | 17 | def update_config(cfg) 18 | @config = cfg 19 | @full_name = nil 20 | end 21 | 22 | def start 23 | chained_call command: :start 24 | end 25 | 26 | def stop 27 | fast_call command: :stop 28 | end 29 | 30 | def restart 31 | chained_call command: :restart 32 | end 33 | 34 | def delete 35 | fast_call command: :delete 36 | terminate 37 | end 38 | 39 | def monitor 40 | chained_call command: :monitor 41 | end 42 | 43 | def unmonitor 44 | fast_call command: :unmonitor 45 | end 46 | 47 | def signal(sig) 48 | fast_call command: :signal, args: [sig] 49 | end 50 | 51 | def user_command(cmd) 52 | fast_call command: :user_command, args: [cmd] 53 | end 54 | 55 | def break_chain 56 | info 'break chain' 57 | scheduler_clear_pending_list 58 | @chain_breaker = true 59 | end 60 | 61 | private 62 | 63 | def fast_call(call) 64 | command = call[:command] 65 | args = call[:args] 66 | info "send to all processes #{command} #{args.present? ? args * ',' : nil}" 67 | 68 | @processes.each do |process| 69 | process.send_call(call) unless process.skip_group_action?(command) 70 | end 71 | end 72 | 73 | end 74 | -------------------------------------------------------------------------------- /lib/eye/group/chain.rb: -------------------------------------------------------------------------------- 1 | module Eye::Group::Chain 2 | 3 | private 4 | 5 | def chained_call(call) 6 | type, grace = chain_options(call[:command]) 7 | chain_schedule(type, grace, call) 8 | end 9 | 10 | def chain_schedule(type, grace, call) 11 | command = call[:command] 12 | args = call[:args] 13 | info "starting #{type} with #{grace}s chain #{command} #{args}" 14 | 15 | @chain_processes_count = @processes.size 16 | @chain_processes_current = 0 17 | @chain_breaker = false 18 | 19 | started_at = Time.now 20 | 21 | @processes.each do |process| 22 | if process.skip_group_action?(command) 23 | @chain_processes_current = @chain_processes_current.to_i + 1 24 | next 25 | end 26 | 27 | chain_schedule_process(process, type, call) 28 | 29 | @chain_processes_current = @chain_processes_current.to_i + 1 30 | 31 | # to skip last sleep 32 | break if @chain_processes_current.to_i == @chain_processes_count.to_i 33 | break if @chain_breaker 34 | 35 | # wait next process 36 | sleep grace.to_f 37 | 38 | break if @chain_breaker 39 | end 40 | 41 | debug { "chain finished #{Time.now - started_at}s" } 42 | 43 | @chain_processes_count = nil 44 | @chain_processes_current = nil 45 | end 46 | 47 | def chain_schedule_process(process, type, call) 48 | debug { "chain_schedule_process #{process.name} #{type} #{call[:command]}" } 49 | 50 | if type == :sync 51 | # sync command, with waiting 52 | Eye::Utils.wait_signal(call[:signal_timeout]) do |signal| 53 | process.send_call(call.merge(signal: signal)) 54 | end 55 | 56 | else 57 | # async command 58 | process.send_call(call) 59 | end 60 | end 61 | 62 | def chain_status 63 | if @config[:chain] 64 | [:start, :restart].map { |c| @config[:chain][c].try(:[], :grace) } 65 | end 66 | end 67 | 68 | # with such delay will chained processes by default 69 | DEFAULT_CHAIN = 0.2 70 | 71 | def chain_options(command) 72 | command = :start if command == :monitor # HACK: for monitor command, work as start 73 | 74 | if @config[:chain] && @config[:chain][command] 75 | type = @config[:chain][command].try :[], :type 76 | type = [:async, :sync].include?(type) ? type : :async 77 | 78 | grace = @config[:chain][command].try :[], :grace 79 | grace = (grace || DEFAULT_CHAIN).to_f rescue DEFAULT_CHAIN 80 | 81 | [type, grace] 82 | else 83 | # default chain case 84 | [:async, DEFAULT_CHAIN] 85 | end 86 | end 87 | 88 | end 89 | -------------------------------------------------------------------------------- /lib/eye/group/data.rb: -------------------------------------------------------------------------------- 1 | module Eye::Group::Data 2 | 3 | def status_data(opts = {}) 4 | plist = @processes.map { |p| p.status_data(opts) } 5 | 6 | h = { name: name, type: :group, subtree: plist } 7 | 8 | h[:debug] = debug_data if opts[:debug] 9 | 10 | # show current chain 11 | if scheduled_call = @scheduled_call 12 | h[:current_command] = scheduled_call[:command] 13 | 14 | if (chain_commands = scheduler_commands_list) && chain_commands.present? 15 | h[:chain_commands] = chain_commands 16 | end 17 | 18 | if @chain_processes_current && @chain_processes_count 19 | h[:chain_progress] = [@chain_processes_current, @chain_processes_count] 20 | end 21 | end 22 | 23 | h 24 | end 25 | 26 | def status_data_short 27 | h = {} 28 | @processes.each do |p| 29 | state = p.state 30 | h[state] ||= 0 31 | h[state] += 1 32 | end 33 | { name: (@name == '__default__' ? 'default' : @name), type: :group, states: h } 34 | end 35 | 36 | def debug_data 37 | { queue: scheduler_commands_list, chain: chain_status } 38 | end 39 | 40 | end 41 | -------------------------------------------------------------------------------- /lib/eye/loader.rb: -------------------------------------------------------------------------------- 1 | # add gems to $: by `gem` method 2 | # this is only way when install eye as system wide 3 | 4 | gem 'celluloid', '~> 0.17.3' 5 | gem 'celluloid-io', '~> 0.17.0' 6 | gem 'nio4r' 7 | gem 'timers' 8 | 9 | gem 'state_machines' 10 | gem 'kostya-sigar', '~> 2.0.0' 11 | -------------------------------------------------------------------------------- /lib/eye/local.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | module Eye::Local 4 | 5 | class << self 6 | 7 | def dir 8 | @dir ||= begin 9 | if root? 10 | if (_home = ENV['EYE_HOME']) && !_home.empty? 11 | File.expand_path(File.join(_home, '.eye')) 12 | else 13 | '/var/run/eye' 14 | end 15 | else 16 | File.expand_path(File.join(home, '.eye')) 17 | end 18 | end 19 | end 20 | 21 | attr_writer :dir, :client_timeout, :host 22 | 23 | def global_eyeconfig 24 | '/etc/eye.conf' 25 | end 26 | 27 | def eyeconfig 28 | File.expand_path(File.join(home, '.eyeconfig')) 29 | end 30 | 31 | def root? 32 | Process::UID.eid == 0 33 | end 34 | 35 | def home 36 | h = ENV['EYE_HOME'] || ENV['HOME'] 37 | raise 'HOME undefined, should be HOME or EYE_HOME environment' unless h 38 | h 39 | end 40 | 41 | def path(path) 42 | File.expand_path(path, dir) 43 | end 44 | 45 | def ensure_eye_dir 46 | FileUtils.mkdir_p(dir) unless ENV['EYE_SOCK'] && ENV['EYE_PID'] 47 | end 48 | 49 | def socket_path 50 | path(ENV['EYE_SOCK'] || "sock#{ENV['EYE_V']}") 51 | end 52 | 53 | def pid_path 54 | path(ENV['EYE_PID'] || "pid#{ENV['EYE_V']}") 55 | end 56 | 57 | def cache_path 58 | path("processes#{ENV['EYE_V']}.cache") 59 | end 60 | 61 | def default_client_timeout 62 | (ENV['EYE_CLIENT_TIMEOUT'] || 5).to_i 63 | end 64 | 65 | def client_timeout 66 | @client_timeout ||= default_client_timeout 67 | end 68 | 69 | def supported_setsid? 70 | RUBY_VERSION >= '2.0' 71 | end 72 | 73 | def host 74 | @host ||= begin 75 | require 'socket' 76 | Socket.gethostname 77 | end 78 | end 79 | 80 | def eyefile 81 | @eyefile ||= find_eyefile(ENV['EYE_HOME'] || '.') 82 | end 83 | 84 | def find_eyefile(start_from_dir) 85 | fromenv = ENV['EYE_FILE'] 86 | return fromenv if fromenv && !fromenv.empty? && File.file?(fromenv) 87 | 88 | previous = nil 89 | current = File.expand_path(start_from_dir) 90 | 91 | until !File.directory?(current) || current == previous 92 | filename = File.join(current, 'Eyefile') 93 | return filename if File.file?(filename) 94 | previous = current 95 | current = File.expand_path('..', current) 96 | end 97 | end 98 | 99 | attr_accessor :local_runner 100 | 101 | end 102 | 103 | end 104 | -------------------------------------------------------------------------------- /lib/eye/logger.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | 3 | class Eye::Logger 4 | 5 | attr_accessor :prefix, :subprefix 6 | 7 | class InnerLogger < Logger 8 | 9 | FORMAT = '%Y.%m.%d %H:%M:%S'.freeze 10 | 11 | def initialize(*args) 12 | super 13 | 14 | self.formatter = proc do |s, d, _p, m| 15 | "#{d.strftime(FORMAT)} #{s.ljust(5)} -- #{m}\n" 16 | end 17 | end 18 | 19 | end 20 | 21 | module ObjectExt 22 | 23 | def logger_tag 24 | [Class, Module].include?(self.class) ? to_s : "<#{self.class}>" 25 | end 26 | 27 | def logger_sub_tag; end 28 | 29 | def logger 30 | @logger ||= Eye::Logger.new(logger_tag, logger_sub_tag) 31 | end 32 | 33 | Logger::Severity.constants.each do |level| 34 | method_name = level.to_s.downcase 35 | define_method method_name do |msg = nil, &block| 36 | logger.send(method_name, msg, &block) 37 | end 38 | end 39 | 40 | def log_ex(ex) 41 | error "Exception: #{ex.message} #{ex.backtrace}" 42 | # notify here? 43 | end 44 | 45 | end 46 | 47 | Logger::Severity.constants.each do |level| 48 | method_name = level.to_s.downcase 49 | define_method method_name do |msg = nil, &block| 50 | if block 51 | self.class.inner_logger.send(method_name) { "#{prefix_str}#{block.call}" } 52 | else 53 | self.class.inner_logger.send(method_name, "#{prefix_str}#{msg}") 54 | end 55 | end 56 | end 57 | 58 | def initialize(prefix = nil, subprefix = nil) 59 | @prefix = prefix 60 | @subprefix = subprefix 61 | end 62 | 63 | class << self 64 | 65 | attr_reader :dev, :log_level, :args 66 | 67 | def link_logger(dev, *args) 68 | old_dev = @dev 69 | @dev = @dev_fd = dev 70 | @args = args 71 | 72 | if dev.nil? 73 | @inner_logger = InnerLogger.new(nil) 74 | elsif dev.is_a?(String) 75 | @dev_fd = STDOUT if @dev.to_s.casecmp('stdout') == 0 76 | @dev_fd = STDERR if @dev.to_s.casecmp('stderr') == 0 77 | @inner_logger = InnerLogger.new(@dev_fd, *args) 78 | else 79 | @inner_logger = dev 80 | end 81 | 82 | @inner_logger.level = log_level || Logger::INFO 83 | 84 | rescue Exception 85 | @inner_logger = nil 86 | @dev = old_dev 87 | raise 88 | end 89 | 90 | def reopen 91 | link_logger(dev, *args) 92 | end 93 | 94 | def log_level=(level) 95 | @log_level = level 96 | @inner_logger.level = log_level if @inner_logger 97 | end 98 | 99 | def inner_logger 100 | @inner_logger ||= InnerLogger.new(nil) 101 | end 102 | 103 | end 104 | 105 | private 106 | 107 | def prefix_str 108 | @pref_string ||= begin 109 | pref_string = '' 110 | 111 | if @prefix 112 | pref_string = "[#{@prefix}] " 113 | pref_string += "#{@subprefix} " if @subprefix 114 | end 115 | 116 | pref_string 117 | end 118 | end 119 | 120 | end 121 | -------------------------------------------------------------------------------- /lib/eye/notify.rb: -------------------------------------------------------------------------------- 1 | require 'celluloid' 2 | 3 | class Eye::Notify 4 | 5 | include Celluloid 6 | include Eye::Dsl::Validation 7 | 8 | autoload :Mail, 'eye/notify/mail' 9 | autoload :Jabber, 'eye/notify/jabber' 10 | autoload :Slack, 'eye/notify/slack' 11 | 12 | TYPES = { mail: 'Mail', jabber: 'Jabber', slack: 'Slack' } 13 | 14 | def self.get_class(type) 15 | klass = eval("Eye::Notify::#{TYPES[type]}") rescue nil 16 | raise "unknown notifier :#{type}" unless klass 17 | if deps = klass.requires 18 | Array(deps).each { |d| require d } 19 | end 20 | klass 21 | end 22 | 23 | def self.validate!(options) 24 | get_class(options[:type]).validate(options) 25 | end 26 | 27 | def self.notify(contact, message_h) 28 | contact = contact.to_s 29 | settings = Eye::Control.settings 30 | needed_hash = (settings[:contacts] || {})[contact] 31 | 32 | if needed_hash.blank? 33 | error "contact #{contact} not found; check your configuration" 34 | return 35 | end 36 | 37 | create_proc = lambda do |nh| 38 | type = nh[:type] 39 | config = (settings[type] || {}).merge(nh[:opts] || {}).merge(contact: nh[:contact]) 40 | klass = get_class(type) 41 | notify = klass.new(config, message_h) 42 | notify.async_notify if notify 43 | end 44 | 45 | if needed_hash.is_a?(Array) 46 | needed_hash.each { |nh| create_proc[nh] } 47 | else 48 | create_proc[needed_hash] 49 | end 50 | rescue Object => ex 51 | log_ex(ex) 52 | end 53 | 54 | TIMEOUT = 1 * 60 55 | 56 | def initialize(options = {}, message_h = {}) 57 | @message_h = message_h 58 | @options = options 59 | 60 | debug { "created notifier #{options}" } 61 | end 62 | 63 | def logger_sub_tag 64 | @options[:contact] 65 | end 66 | 67 | def async_notify 68 | async.notify 69 | after(TIMEOUT) { terminate } 70 | end 71 | 72 | def notify 73 | debug { "start notify #{@message_h}" } 74 | execute 75 | debug { "end notify #{@message_h}" } 76 | terminate 77 | end 78 | 79 | def execute 80 | raise NotImplementedError 81 | end 82 | 83 | param :contact, [String] 84 | 85 | def message_subject 86 | "[#{msg_host}] [#{msg_full_name}] #{msg_message}" 87 | end 88 | 89 | def message_body 90 | "#{message_subject} at #{Eye::Utils.human_time2(msg_at)}" 91 | end 92 | 93 | def self.register(base) 94 | name = base.to_s.gsub('Eye::Notify::', '') 95 | type = name.underscore.to_sym 96 | Eye::Notify::TYPES[type] = name 97 | Eye::Notify.const_set(name, base) 98 | Eye::Dsl::ConfigOpts.add_notify(type) 99 | end 100 | 101 | def self.requires; end 102 | 103 | class Custom < Eye::Notify 104 | 105 | def self.inherited(base) 106 | super 107 | register(base) 108 | end 109 | 110 | end 111 | 112 | %w[at host message name full_name pid level].each do |name| 113 | define_method("msg_#{name}") do 114 | @message_h[name.to_sym] 115 | end 116 | end 117 | 118 | end 119 | -------------------------------------------------------------------------------- /lib/eye/notify/jabber.rb: -------------------------------------------------------------------------------- 1 | require 'xmpp4r' 2 | 3 | class Eye::Notify::Jabber < Eye::Notify 4 | 5 | # Eye.config do 6 | # jabber :host => "some.host", :port => 12345, :user => "eye@some.host", :password => "123456" 7 | # contact :vasya, :jabber, "vasya@some.host" 8 | # end 9 | 10 | param :host, String, true 11 | param :port, [String, Integer], true 12 | param :user, String, true 13 | param :password, String 14 | 15 | def execute 16 | debug { "send jabber #{[host, port, user, password]} - #{[contact, message_body]}" } 17 | 18 | mes = ::Jabber::Message.new(contact, message_body) 19 | mes.set_type(:normal) 20 | mes.set_id('1') 21 | mes.set_subject(message_subject) 22 | 23 | client = ::Jabber::Client.new(::Jabber::JID.new("#{user}/Eye")) 24 | client.connect(host, port) 25 | client.auth(password) 26 | client.send(mes) 27 | client.close 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /lib/eye/notify/mail.rb: -------------------------------------------------------------------------------- 1 | require 'net/smtp' 2 | 3 | class Eye::Notify::Mail < Eye::Notify 4 | 5 | # Eye.config do 6 | # mail :host => "some.host", :port => 12345, :user => "eye@some.host", :password => "123456", :domain => "some.host" 7 | # contact :vasya, :mail, "vasya@some.host" 8 | # end 9 | 10 | param :host, String, true 11 | param :port, [String, Integer], true 12 | 13 | param :domain, String 14 | param :user, String 15 | param :password, String 16 | param :auth, Symbol, nil, nil, [:plain, :login, :cram_md5] 17 | 18 | param :starttls, [TrueClass, FalseClass] 19 | 20 | param :from_mail, String 21 | param :from_name, String, nil, 'eye' 22 | 23 | def execute 24 | smtp 25 | end 26 | 27 | def smtp 28 | args = [host, port, domain, user, password, auth] 29 | debug { "called smtp with #{args}" } 30 | smtp = Net::SMTP.new host, port 31 | smtp.enable_starttls if starttls 32 | 33 | smtp.start(domain, user, password, auth) do |s| 34 | s.send_message(message, from_mail || user, contact) 35 | end 36 | end 37 | 38 | def message 39 | h = [] 40 | h << "From: #{from_name} <#{from_mail || user}>" if from_mail || user 41 | h << "To: <#{contact}>" 42 | h << "Subject: #{message_subject}" 43 | h << "Date: #{msg_at.httpdate}" 44 | h << "Message-Id: <#{rand(1_000_000_000).to_s(36)}.#{$$}.#{contact}>" 45 | "#{h * "\n"}\n#{message_body}" 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /lib/eye/notify/slack.rb: -------------------------------------------------------------------------------- 1 | require 'slack-notifier' 2 | 3 | class Eye::Notify::Slack < Eye::Notify 4 | 5 | # Eye.config do 6 | # slack :webhook_url => "http://...", :channel => "#default", :username => "eye" 7 | # contact :channel, :slack, "@channel" 8 | # end 9 | 10 | param :webhook_url, String, true 11 | param :channel, String, nil, '#default' 12 | param :username, String, nil, 'eye' 13 | 14 | param :icon, String 15 | 16 | def execute 17 | debug { "send slack #{[channel, username]} - #{[contact, message_body]}" } 18 | 19 | options = { 20 | channel: channel, 21 | username: username 22 | } 23 | 24 | options[:icon_emoji] = icon if icon && icon.start_with?(':') 25 | options[:icon_url] = icon if icon && icon.start_with?('http') 26 | 27 | notifier = ::Slack::Notifier.new webhook_url, options 28 | 29 | notifier.ping message_body 30 | end 31 | 32 | def message_body 33 | payload = '' 34 | payload << "#{contact}: *#{msg_host}* _#{msg_full_name}_ at #{Eye::Utils.human_time2(msg_at)}\n" 35 | payload << "> #{msg_message}" 36 | payload 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /lib/eye/process.rb: -------------------------------------------------------------------------------- 1 | require 'celluloid' 2 | 3 | class Eye::Process 4 | 5 | include Celluloid 6 | 7 | autoload :Config, 'eye/process/config' 8 | autoload :Commands, 'eye/process/commands' 9 | autoload :Data, 'eye/process/data' 10 | autoload :Watchers, 'eye/process/watchers' 11 | autoload :Monitor, 'eye/process/monitor' 12 | autoload :System, 'eye/process/system' 13 | autoload :Controller, 'eye/process/controller' 14 | autoload :StatesHistory, 'eye/process/states_history' 15 | autoload :Children, 'eye/process/children' 16 | autoload :Trigger, 'eye/process/trigger' 17 | autoload :Notify, 'eye/process/notify' 18 | autoload :Scheduler, 'eye/process/scheduler' 19 | autoload :Validate, 'eye/process/validate' 20 | 21 | attr_accessor :pid, :parent_pid, 22 | :watchers, :config, :states_history, 23 | :children, :triggers, :name, :state_reason 24 | 25 | def initialize(config) 26 | raise 'you must supply a pid_file location' unless config[:pid_file] 27 | 28 | @config = prepare_config(config) 29 | 30 | @watchers = {} 31 | @children = {} 32 | @triggers = [] 33 | @name = @config[:name] 34 | 35 | @states_history = Eye::Process::StatesHistory.new(100) 36 | @states_history << :unmonitored 37 | 38 | @state_call = {} 39 | 40 | debug { "creating with config: #{@config.inspect}" } 41 | 42 | add_triggers 43 | 44 | super() # for statemachine 45 | end 46 | 47 | # c(), self[] 48 | include Eye::Process::Config 49 | 50 | # full_name, status_data 51 | include Eye::Process::Data 52 | 53 | # commands: 54 | # start_process, stop_process, restart_process 55 | include Eye::Process::Commands 56 | 57 | # start, stop, restart, monitor, unmonit, delete 58 | include Eye::Process::Controller 59 | 60 | # add_watchers, remove_watchers: 61 | include Eye::Process::Watchers 62 | 63 | # check alive, crash methods: 64 | include Eye::Process::Monitor 65 | 66 | # system methods: 67 | include Eye::Process::System 68 | 69 | # manage child methods 70 | include Eye::Process::Children 71 | 72 | # manage triggers methods 73 | include Eye::Process::Trigger 74 | 75 | # manage notify methods 76 | include Eye::Process::Notify 77 | 78 | # scheduler 79 | include Eye::Process::Scheduler 80 | 81 | # validate 82 | extend Eye::Process::Validate 83 | 84 | end 85 | 86 | # include state_machine states 87 | require_relative 'process/states' 88 | -------------------------------------------------------------------------------- /lib/eye/process/children.rb: -------------------------------------------------------------------------------- 1 | module Eye::Process::Children 2 | 3 | def add_children 4 | add_or_update_children 5 | end 6 | 7 | def add_or_update_children 8 | return unless self[:monitor_children] 9 | return unless self.up? 10 | return if @updating_children 11 | @updating_children = true 12 | 13 | unless self.pid 14 | warn "can't add children; pid not set" 15 | return 16 | end 17 | 18 | now_children = Eye::SystemResources.children(self.pid) 19 | new_children = [] 20 | exist_children = [] 21 | 22 | now_children.each do |child_pid| 23 | if self.children[child_pid] 24 | exist_children << child_pid 25 | else 26 | new_children << child_pid 27 | end 28 | end 29 | 30 | removed_children = self.children.keys - now_children 31 | 32 | if new_children.present? 33 | new_children.each do |child_pid| 34 | cfg = self[:monitor_children].try :update, notify: self[:notify] 35 | self.children[child_pid] = Eye::ChildProcess.new(child_pid, cfg, logger.prefix, current_actor) 36 | end 37 | end 38 | 39 | if removed_children.present? 40 | removed_children.each { |child_pid| remove_child(child_pid) } 41 | end 42 | 43 | h = { new: new_children.size, removed: removed_children.size, exists: exist_children.size } 44 | debug { "children info: #{h.inspect}" } 45 | 46 | @updating_children = false 47 | h 48 | end 49 | 50 | def remove_children 51 | # here should .keys (not each_key), as it copy array of keys 52 | children.keys.each { |child_pid| clear_child(child_pid) } 53 | end 54 | 55 | def remove_child(child_pid) 56 | clear_child(child_pid) 57 | end 58 | 59 | def clear_child(child_pid) 60 | child = self.children.delete(child_pid) 61 | child.destroy if child && child.alive? 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /lib/eye/process/config.rb: -------------------------------------------------------------------------------- 1 | module Eye::Process::Config 2 | 3 | DEFAULTS = { 4 | keep_alive: true, # restart when crashed 5 | check_alive_period: 5.seconds, 6 | 7 | check_identity: true, 8 | check_identity_period: 60.seconds, 9 | check_identity_grace: 60.seconds, 10 | 11 | start_timeout: 15.seconds, 12 | stop_timeout: 10.seconds, 13 | restart_timeout: 10.seconds, 14 | 15 | start_grace: 2.5.seconds, 16 | stop_grace: 0.5.seconds, 17 | restart_grace: 1.second, 18 | 19 | daemonize: false, 20 | auto_start: true, # auto start on monitor action 21 | 22 | children_update_period: 30.seconds, 23 | clear_pid: true, # by default clear pid on stop 24 | 25 | auto_update_pidfile_grace: 30.seconds, 26 | revert_fuckup_pidfile_grace: 120.seconds 27 | }.freeze 28 | 29 | def prepare_config(new_config) 30 | h = DEFAULTS.merge(new_config) 31 | h[:pid_file_ex] = Eye::System.normalized_file(h[:pid_file], h[:working_dir]) if h[:pid_file] 32 | h[:checks] = {} if h[:checks].blank? 33 | h[:triggers] = {} if h[:triggers].blank? 34 | if upd = h.try(:[], :monitor_children).try(:[], :children_update_period) 35 | h[:children_update_period] = upd 36 | end 37 | 38 | # check speedy flapping by default 39 | if h[:triggers].blank? || !h[:triggers][:flapping] 40 | h[:triggers] ||= {} 41 | h[:triggers][:flapping] = { type: :flapping, times: 10, within: 10.seconds } 42 | end 43 | 44 | h[:stdout] = Eye::System.normalized_file(h[:stdout], h[:working_dir]) if h[:stdout] 45 | h[:stderr] = Eye::System.normalized_file(h[:stderr], h[:working_dir]) if h[:stderr] 46 | h[:stdall] = Eye::System.normalized_file(h[:stdall], h[:working_dir]) if h[:stdall] 47 | 48 | h[:environment] = Eye::System.prepare_env(h) 49 | h[:stop_signals] = [:TERM, 0.5, :KILL] unless h[:stop_command] || h[:stop_signals] 50 | 51 | h 52 | end 53 | 54 | def c(name) 55 | @config[name] 56 | end 57 | 58 | def [](name) 59 | @config[name] 60 | end 61 | 62 | def update_config(new_config = {}) 63 | new_config = prepare_config(new_config) 64 | @config = new_config 65 | @full_name = nil 66 | @logger = nil 67 | 68 | debug { "updating config to: #{@config.inspect}" } 69 | 70 | remove_triggers 71 | add_triggers 72 | 73 | if up? 74 | # rebuild checks for this process 75 | remove_watchers 76 | remove_children 77 | 78 | add_watchers 79 | add_children 80 | end 81 | end 82 | 83 | # is pid_file under Eye::Process control, or not 84 | def control_pid? 85 | !!self[:daemonize] 86 | end 87 | 88 | def skip_group_action?(action) 89 | if sga = self[:skip_group_actions] 90 | res = sga[action] 91 | if res == true 92 | res 93 | elsif res.is_a?(Array) 94 | res.include?(self.state_name) 95 | end 96 | end 97 | end 98 | 99 | end 100 | -------------------------------------------------------------------------------- /lib/eye/process/controller.rb: -------------------------------------------------------------------------------- 1 | module Eye::Process::Controller 2 | 3 | # scheduled actions 4 | # :update_config, :start, :stop, :restart, :unmonitor, :monitor, :break_chain, :delete, :signal, :user_command 5 | 6 | def start 7 | if load_external_pid_file == :ok 8 | switch :already_running 9 | :ok 10 | else 11 | start_process 12 | end 13 | end 14 | 15 | def stop 16 | stop_process 17 | switch :unmonitoring 18 | end 19 | 20 | def restart 21 | load_external_pid_file unless pid # unmonitored case 22 | restart_process 23 | end 24 | 25 | def monitor 26 | if self[:auto_start] 27 | start 28 | elsif load_external_pid_file == :ok 29 | switch :already_running 30 | else 31 | schedule command: :unmonitor, reason: 'not found' 32 | end 33 | end 34 | 35 | def unmonitor 36 | switch :unmonitoring 37 | end 38 | 39 | def delete 40 | if self[:stop_on_delete] 41 | info 'process has stop_on_delete option, so sync-stop it first' 42 | stop 43 | end 44 | 45 | remove_watchers 46 | remove_children 47 | remove_triggers 48 | 49 | terminate 50 | end 51 | 52 | def signal(sig = 0) 53 | send_signal(sig) if self.pid 54 | end 55 | 56 | def user_command(name) 57 | if self[:user_commands] && c = self[:user_commands][name.to_sym] 58 | execute_user_command(name, c) 59 | end 60 | end 61 | 62 | end 63 | -------------------------------------------------------------------------------- /lib/eye/process/data.rb: -------------------------------------------------------------------------------- 1 | module Eye::Process::Data 2 | 3 | def logger_tag 4 | full_name 5 | end 6 | 7 | def app_name 8 | self[:application] 9 | end 10 | 11 | def group_name 12 | self[:group] == '__default__' ? nil : self[:group] 13 | end 14 | 15 | def group_name_pure 16 | self[:group] 17 | end 18 | 19 | def full_name 20 | @full_name ||= [app_name, group_name, self[:name]].compact.join(':') 21 | end 22 | 23 | def status_data(opts = {}) 24 | p_st = self_status_data(opts) 25 | 26 | if children.present? 27 | p_st.merge(subtree: Eye::Utils::AliveArray.new(children.values).map { |c| c.status_data(opts) }) 28 | elsif self[:monitor_children] && self.up? 29 | p_st.merge(subtree: [{ name: '=loading children=' }]) 30 | else 31 | # common state 32 | p_st 33 | end 34 | end 35 | 36 | def self_status_data(opts) 37 | h = { name: name, 38 | state: state, 39 | type: (self.class == Eye::ChildProcess ? :child_process : :process), 40 | resources: Eye::SystemResources.resources(pid) } 41 | 42 | if @states_history 43 | h[:state_changed_at] = @states_history.last_state_changed_at.to_i 44 | h[:state_reason] = @states_history.last_reason.to_s 45 | end 46 | 47 | h[:debug] = debug_data if opts[:debug] 48 | h[:procline] = Eye::SystemResources.args(self.pid) if opts[:procline] 49 | h[:current_command] = scheduler_current_command if scheduler_current_command 50 | 51 | h 52 | end 53 | 54 | def debug_data 55 | { queue: scheduler_actions_list, watchers: @watchers.keys, timers: timers_data } 56 | end 57 | 58 | def timers_data 59 | if actor = Thread.current[:celluloid_actor] 60 | actor.timers.timers.map(&:interval) 61 | end 62 | rescue 63 | [] 64 | end 65 | 66 | def sub_object?(obj) 67 | return false if self.class == Eye::ChildProcess 68 | self.children.each { |_, child| return true if child == obj } 69 | false 70 | end 71 | 72 | def environment_string 73 | s = [] 74 | @config[:environment].each { |k, v| s << "#{k}=#{v}" } 75 | s * ' ' 76 | end 77 | 78 | def shell_string(dir = true) 79 | str = '' 80 | str += "cd #{self[:working_dir]} && " if dir 81 | str += environment_string 82 | str += ' ' 83 | str += self[:start_command] 84 | str += ' &' if self[:daemonize] 85 | str 86 | end 87 | 88 | end 89 | -------------------------------------------------------------------------------- /lib/eye/process/notify.rb: -------------------------------------------------------------------------------- 1 | module Eye::Process::Notify 2 | 3 | # notify to user: 4 | # 1) process crashed by itself, and we restart it [:info] 5 | # 2) checker bounded to restart process [:warn] 6 | # 3) flapping + switch to unmonitored [:error] 7 | 8 | LEVELS = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 }.freeze 9 | 10 | def notify(level, msg) 11 | # logging it 12 | error "NOTIFY: #{msg}" if ilevel(level) > ilevel(:info) 13 | 14 | return if self[:notify].blank? 15 | 16 | # send notifies 17 | message = { message: msg, name: name, 18 | full_name: full_name, pid: pid, host: Eye::Local.host, level: level, 19 | at: Time.now } 20 | 21 | self[:notify].each do |contact, not_level| 22 | Eye::Notify.notify(contact, message) if ilevel(level) >= ilevel(not_level) 23 | end 24 | end 25 | 26 | private 27 | 28 | def ilevel(level) 29 | LEVELS[level].to_i 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /lib/eye/process/states.rb: -------------------------------------------------------------------------------- 1 | require 'state_machines' 2 | require 'state_machines/version' 3 | 4 | class Eye::Process 5 | 6 | class StateError < RuntimeError; end 7 | 8 | # do transition 9 | def switch(name, call = {}) 10 | @state_call = @last_scheduled_call ? @last_scheduled_call.merge(call) : call 11 | self.send("#{name}!") 12 | end 13 | 14 | state_machine :state, :initial => :unmonitored do 15 | state :unmonitored, :up, :down 16 | state :starting, :stopping, :restarting 17 | 18 | event :starting do 19 | transition [:unmonitored, :down] => :starting 20 | end 21 | 22 | event :already_running do 23 | transition [:unmonitored, :down, :up] => :up 24 | end 25 | 26 | event :started do 27 | transition :starting => :up 28 | end 29 | 30 | event :crashed do 31 | transition [:starting, :restarting, :stopping, :up] => :down 32 | end 33 | 34 | event :stopping do 35 | transition [:up, :restarting] => :stopping 36 | end 37 | 38 | event :stopped do 39 | transition :stopping => :down 40 | end 41 | 42 | event :cant_kill do 43 | transition :stopping => :up 44 | end 45 | 46 | event :restarting do 47 | transition [:unmonitored, :up, :down] => :restarting 48 | end 49 | 50 | event :restarted do 51 | transition :restarting => :up 52 | end 53 | 54 | event :unmonitoring do 55 | transition any => :unmonitored 56 | end 57 | 58 | after_transition any => any, :do => :log_transition 59 | after_transition any => any, :do => :check_triggers 60 | 61 | after_transition any => :unmonitored, :do => :on_unmonitored 62 | 63 | after_transition any - :up => :up, :do => :add_watchers 64 | after_transition :up => any - :up, :do => :remove_watchers 65 | 66 | after_transition any - :up => :up, :do => :add_children 67 | after_transition any => [:unmonitored, :down], :do => :remove_children 68 | 69 | after_transition :on => :crashed, :do => :on_crashed 70 | end 71 | 72 | def on_crashed 73 | self.pid = nil 74 | schedule command: :check_crash, reason: :crashed 75 | end 76 | 77 | def on_unmonitored 78 | self.pid = nil 79 | end 80 | 81 | def log_transition(transition) 82 | if transition.to_name != transition.from_name || @state_call[:by] == :user 83 | reason_str = reason_from_call(@state_call) 84 | @states_history.push transition.to_name, reason_str 85 | info "switch :#{transition.event} [:#{transition.from_name} => :#{transition.to_name}] #{reason_str}" 86 | end 87 | end 88 | 89 | end 90 | -------------------------------------------------------------------------------- /lib/eye/process/states_history.rb: -------------------------------------------------------------------------------- 1 | class Eye::Process::StatesHistory < Eye::Utils::Tail 2 | 3 | def push(state, reason = nil, tm = Time.now) 4 | super(state: state, at: tm.to_i, reason: reason) 5 | end 6 | 7 | def states 8 | self.map { |c| c[:state] } 9 | end 10 | 11 | def states_for_period(period, from_time = nil, &block) 12 | tm = Time.now - period 13 | tm = [tm, from_time].max if from_time 14 | tm = tm.to_f 15 | if block 16 | self.each { |s| yield(s) if s[:at] >= tm } 17 | else 18 | self.select { |s| s[:at] >= tm }.map { |c| c[:state] } 19 | end 20 | end 21 | 22 | def last_state 23 | last[:state] 24 | end 25 | 26 | def last_reason 27 | last[:reason] rescue nil 28 | end 29 | 30 | def last_state_changed_at 31 | Time.at(last[:at]) 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /lib/eye/process/trigger.rb: -------------------------------------------------------------------------------- 1 | module Eye::Process::Trigger 2 | 3 | def add_triggers 4 | (self[:triggers] || {}).each_value { |cfg| add_trigger(cfg) } 5 | end 6 | 7 | def remove_triggers 8 | self.triggers = [] 9 | end 10 | 11 | def check_triggers(transition) 12 | self.triggers.each { |trigger| trigger.notify(transition, @state_call) } 13 | end 14 | 15 | # conditional start, used in triggers, to start only from unmonitored state, and only if special reason 16 | def conditional_start 17 | unless unmonitored? 18 | warn "skip, because in state #{state_name}" 19 | return 20 | end 21 | 22 | state_by = @state_call.try(:[], :by) 23 | current_by = @scheduled_call.try(:[], :by) 24 | if state_by && current_by && state_by != current_by 25 | warn "skip, state_by(#{state_by}) != current_by(#{current_by})" 26 | return 27 | end 28 | 29 | start 30 | end 31 | 32 | private 33 | 34 | def add_trigger(cfg = {}) 35 | trigger = Eye::Trigger.create(current_actor, cfg) 36 | self.triggers << trigger if trigger 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /lib/eye/process/validate.rb: -------------------------------------------------------------------------------- 1 | require 'shellwords' 2 | require 'etc' 3 | 4 | module Eye::Process::Validate 5 | 6 | class Error < RuntimeError; end 7 | 8 | def validate(config, localize = true) 9 | if (str = config[:start_command]) 10 | # it should parse with Shellwords and not raise 11 | spl = Shellwords.shellwords(str) * '#' 12 | 13 | if config[:daemonize] && !config[:use_leaf_child] 14 | if spl =~ %r[sh#\-c|#&&#|;#] 15 | raise Error, "#{config[:name]}, daemonize does not support concats like '&&' in start_command" 16 | end 17 | end 18 | end 19 | 20 | Shellwords.shellwords(config[:stop_command]) if config[:stop_command] 21 | Shellwords.shellwords(config[:restart_command]) if config[:restart_command] 22 | 23 | if localize 24 | Etc.getpwnam(config[:uid]) if config[:uid] 25 | Etc.getgrnam(config[:gid]) if config[:gid] 26 | 27 | if config[:working_dir] 28 | raise Error, "working_dir '#{config[:working_dir]}' is invalid" unless File.directory?(config[:working_dir]) 29 | end 30 | end 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /lib/eye/process/watchers.rb: -------------------------------------------------------------------------------- 1 | module Eye::Process::Watchers 2 | 3 | def add_watchers(force = false) 4 | return unless self.up? 5 | 6 | remove_watchers if force 7 | 8 | if @watchers.blank? 9 | # default watcher :check_alive 10 | add_watcher(:check_alive, self[:check_alive_period]) do 11 | check_alive 12 | end 13 | 14 | if self[:check_identity] 15 | add_watcher(:check_identity, self[:check_identity_period]) do 16 | check_identity 17 | end 18 | end 19 | 20 | # monitor children pids 21 | if self[:monitor_children] 22 | add_watcher(:check_children, self[:children_update_period]) do 23 | add_or_update_children 24 | end 25 | end 26 | 27 | # monitor conditional watchers 28 | start_checkers 29 | else 30 | warn 'add_watchers failed, watchers are already present' 31 | end 32 | end 33 | 34 | def remove_watchers 35 | @watchers.each_value { |h| h[:timer].cancel } 36 | @watchers = {} 37 | end 38 | 39 | private 40 | 41 | def add_watcher(type, period = 2, subject = nil, &block) 42 | return if @watchers[type] 43 | 44 | debug { "adding watcher: #{type}(#{period})" } 45 | 46 | timer = every(period.to_f) do 47 | debug { "check #{type}" } 48 | block.call(subject) 49 | end 50 | 51 | @watchers[type] ||= { timer: timer, subject: subject } 52 | end 53 | 54 | def start_checkers 55 | self[:checks].each { |name, cfg| start_checker(name, cfg) } 56 | end 57 | 58 | def start_checker(name, cfg) 59 | # cfg: {:type => :memory, :every => 5.seconds, :below => 100.megabytes, :times => [3, 5]} 60 | subject = Eye::Checker.create(pid, cfg, current_actor) 61 | add_watcher("check_#{name}".to_sym, subject.every, subject, &method(:watcher_tick).to_proc) if subject 62 | end 63 | 64 | def watcher_tick(subject) 65 | # double up? test needed because state can changed while subject.check 66 | subject.fire if up? && !subject.check && up? 67 | end 68 | 69 | end 70 | -------------------------------------------------------------------------------- /lib/eye/server.rb: -------------------------------------------------------------------------------- 1 | require 'celluloid/current' 2 | require 'celluloid/io' 3 | 4 | class Eye::Server 5 | 6 | include Celluloid::IO 7 | 8 | attr_reader :socket_path, :server 9 | 10 | def initialize(socket_path) 11 | @socket_path = socket_path 12 | @server = begin 13 | UNIXServer.open(socket_path) 14 | rescue Errno::EADDRINUSE 15 | unlink_socket_file 16 | UNIXServer.open(socket_path) 17 | end 18 | end 19 | 20 | def run 21 | loop { async.handle_connection @server.accept } 22 | end 23 | 24 | def handle_connection(socket) 25 | text = socket.read 26 | 27 | begin 28 | # TODO, remove in 1.0 29 | 30 | payload = Marshal.load(text) 31 | raise "unknown payload #{payload.inspect}" unless payload.is_a?(Array) 32 | cmd, *args = payload 33 | 34 | rescue 35 | # new format 36 | begin 37 | sign, msg_size = text[0...8].unpack('N*') 38 | raise "unknown protocol #{sign}" unless sign == Eye::Client::SIGN 39 | content = text[8..-1] 40 | content << socket.read(msg_size - content.length) while content.length < msg_size 41 | payload = Marshal.load(content) 42 | cmd = payload[:command] 43 | args = payload[:args] 44 | 45 | rescue => ex 46 | error "Failed to read from socket: #{ex.message}" 47 | return 48 | end 49 | end 50 | 51 | response = Eye::Control.command(cmd, *args, {}) 52 | socket.write(Marshal.dump(response)) 53 | 54 | rescue Errno::EPIPE 55 | # client timeouted 56 | # do nothing 57 | 58 | ensure 59 | socket.close 60 | end 61 | 62 | def unlink_socket_file 63 | File.delete(@socket_path) if @socket_path 64 | rescue 65 | end 66 | 67 | finalizer :close_socket 68 | 69 | def close_socket 70 | @server.close if @server 71 | unlink_socket_file 72 | end 73 | 74 | end 75 | -------------------------------------------------------------------------------- /lib/eye/sigar.rb: -------------------------------------------------------------------------------- 1 | require 'sigar' 2 | Eye::Sigar = ::Sigar.new 3 | -------------------------------------------------------------------------------- /lib/eye/system_resources.rb: -------------------------------------------------------------------------------- 1 | require 'celluloid/current' 2 | 3 | class Eye::SystemResources 4 | 5 | # cached system resources 6 | class << self 7 | 8 | def memory(pid) 9 | if mem = cache.proc_mem(pid) 10 | mem.resident 11 | end 12 | end 13 | 14 | def cpu(pid) 15 | if cpu = cache.proc_cpu(pid) 16 | cpu.percent * 100 17 | end 18 | end 19 | 20 | def children(parent_pid) 21 | cache.children(parent_pid) 22 | end 23 | 24 | # unixtime 25 | def start_time(pid) 26 | if cpu = cache.proc_cpu(pid) 27 | cpu.start_time.to_i / 1000 28 | end 29 | end 30 | 31 | # total cpu usage in seconds 32 | def cputime(pid) 33 | if cpu = cache.proc_cpu(pid) 34 | cpu.total.to_f / 1000 35 | end 36 | end 37 | 38 | # last child in a children tree 39 | def leaf_child(pid) 40 | if dc = deep_children(pid) 41 | dc.detect do |child| 42 | args = Eye::Sigar.proc_args(child)[0] rescue '' 43 | !args.start_with?('logger') && child != pid 44 | end 45 | end 46 | end 47 | 48 | def deep_children(pid) 49 | Array(pid_or_children(pid)).flatten.sort_by(&:-@) 50 | end 51 | 52 | def pid_or_children(pid) 53 | c = children(pid) 54 | if !c || c.empty? 55 | pid 56 | else 57 | c.map { |ppid| pid_or_children(ppid) } 58 | end 59 | end 60 | 61 | def args(pid) 62 | Eye::Sigar.proc_args(pid).join(' ').strip rescue '-' 63 | end 64 | 65 | def resources(pid) 66 | { memory: memory(pid), 67 | cpu: cpu(pid), 68 | start_time: start_time(pid), 69 | pid: pid } 70 | end 71 | 72 | def cache 73 | Celluloid::Actor[:system_resources_cache] 74 | end 75 | 76 | end 77 | 78 | class Cache 79 | 80 | include Celluloid 81 | 82 | attr_reader :expire 83 | 84 | def initialize 85 | clear 86 | setup_expire 87 | end 88 | 89 | def setup_expire(expire = 5) 90 | @expire = expire 91 | @timer.cancel if @timer 92 | @timer = every(@expire) { clear } 93 | end 94 | 95 | def clear 96 | @memory = {} 97 | @cpu = {} 98 | @ppids = {} 99 | end 100 | 101 | def proc_mem(pid) 102 | @memory[pid] ||= Eye::Sigar.proc_mem(pid) if pid 103 | 104 | rescue ArgumentError 105 | # when incorrect PID, just skip 106 | end 107 | 108 | def proc_cpu(pid) 109 | @cpu[pid] ||= Eye::Sigar.proc_cpu(pid) if pid 110 | 111 | rescue ArgumentError 112 | # when incorrect PID, just skip 113 | end 114 | 115 | def children(pid) 116 | if pid 117 | @ppids[pid] ||= Eye::Sigar.proc_list("State.Ppid.eq=#{pid}") 118 | else 119 | [] 120 | end 121 | end 122 | 123 | end 124 | 125 | # Setup global sigar singleton here 126 | Cache.supervise(as: :system_resources_cache) 127 | 128 | end 129 | -------------------------------------------------------------------------------- /lib/eye/trigger/check_dependency.rb: -------------------------------------------------------------------------------- 1 | class Eye::Trigger::CheckDependency < Eye::Trigger 2 | 3 | param :names, [Array], true, 5 4 | 5 | def check(transition) 6 | check_dependency(transition.to_name) if transition.from_name == :up 7 | end 8 | 9 | private 10 | 11 | def check_dependency(to) 12 | processes = names.map do |name| 13 | Eye::Control.find_nearest_process(name, process.group_name_pure, process.app_name) 14 | end 15 | 16 | processes = processes.compact.reject { |p| p.state_name == :unmonitored } 17 | return if processes.empty? 18 | processes = Eye::Utils::AliveArray.new(processes) 19 | 20 | act = case to 21 | when :down, :restarting then :restart 22 | when :stopping then :stop 23 | when :unmonitored then :unmonitor 24 | end 25 | 26 | if act 27 | processes.each do |p| 28 | p.schedule command: act, reason: "#{act} dependecies" 29 | end 30 | end 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /lib/eye/trigger/flapping.rb: -------------------------------------------------------------------------------- 1 | class Eye::Trigger::Flapping < Eye::Trigger 2 | 3 | # trigger :flapping, :times => 10, :within => 1.minute, 4 | # :retry_in => 10.minutes, :retry_times => 15 5 | 6 | param :times, [Integer], true, 5 7 | param :within, [Float, Integer], true 8 | param :retry_in, [Float, Integer] 9 | param :retry_times, [Integer] 10 | param :reretry_in, [Float, Integer] 11 | param :reretry_times, [Integer] 12 | 13 | def initialize(*args) 14 | super 15 | clear_counters 16 | end 17 | 18 | def check(transition) 19 | on_flapping if transition.event == :crashed && !good? 20 | end 21 | 22 | private 23 | 24 | def clear_counters 25 | @retry_times = 0 26 | @reretry_times = 0 27 | end 28 | 29 | def good? 30 | down_count = 0 31 | process.states_history.states_for_period(within, @last_at) do |s| 32 | down_count += 1 if s[:state] == :down 33 | end 34 | 35 | if down_count >= times 36 | @last_at = process.states_history.last_state_changed_at 37 | false 38 | else 39 | true 40 | end 41 | end 42 | 43 | def on_flapping 44 | debug { 'flapping recognized!!!' } 45 | 46 | process.notify :error, 'flapping!' 47 | process.schedule command: :unmonitor, by: :flapping 48 | 49 | return unless retry_in 50 | if !retry_times || (retry_times && @retry_times < retry_times) 51 | @retry_times += 1 52 | process.schedule(in: retry_in.to_f, command: :conditional_start, 53 | by: :flapping, reason: 'retry start after flapping') 54 | elsif reretry_in && !reretry_times || (reretry_times && @reretry_times < reretry_times) 55 | @retry_times = 0 56 | @reretry_times += 1 57 | process.schedule(in: reretry_in.to_f, command: :conditional_start, 58 | by: :flapping, reason: 'reretry start after flapping') 59 | end 60 | end 61 | 62 | end 63 | -------------------------------------------------------------------------------- /lib/eye/trigger/starting_guard.rb: -------------------------------------------------------------------------------- 1 | class Eye::Trigger::StartingGuard < Eye::Trigger 2 | 3 | # check that process ready to start or not 4 | # by custom user condition 5 | # if not, process switched to :unmonitored, and then retry to start after :every interval 6 | # 7 | # trigger :starting_guard, every: 10.seconds, should: ->{ `cat /tmp/bla` == "bla" } 8 | 9 | param :every, [Float, Integer], false, 10 10 | param :times, [Integer] 11 | param :retry_in, [Float, Integer] 12 | param :retry_times, [Integer] 13 | param :should, [Proc, Symbol] 14 | 15 | def initialize(*args) 16 | super 17 | 18 | @retry_count = 0 19 | @reretry_count = 0 20 | end 21 | 22 | def check(transition) 23 | check_start if transition.to_name == :starting 24 | end 25 | 26 | def check_start 27 | @retry_count += 1 28 | condition = defer { exec_proc(:should) } 29 | 30 | if condition 31 | info "ok, process ready to start #{condition.inspect}" 32 | @retry_count = 0 33 | @reretry_count = 0 34 | return 35 | else 36 | info 'false executed condition' 37 | end 38 | 39 | new_time = nil 40 | if every 41 | if times 42 | if @retry_count < times 43 | new_time = Time.now + every 44 | process.schedule(in: every, command: :conditional_start, 45 | by: :starting_guard, reason: 'starting_guard, retry start') 46 | else 47 | @retry_count = 0 48 | @reretry_count += 1 49 | if retry_in && (!retry_times || (@reretry_count < retry_times)) 50 | new_time = Time.now + retry_in 51 | process.schedule(in: retry_in, command: :conditional_start, 52 | by: :starting_guard, reason: 'restarting_guard, retry start') 53 | end 54 | end 55 | else 56 | new_time = Time.now + every 57 | process.schedule(in: every, command: :conditional_start, 58 | by: :starting_guard, reason: 'starting_guard, retry start') 59 | end 60 | end 61 | 62 | retry_msg = new_time ? ", retry at '#{Eye::Utils.human_time2(new_time.to_i)}'" : '' 63 | process.switch :unmonitoring, by: :starting_guard, reason: "failed condition#{retry_msg}" 64 | 65 | raise Eye::Process::StateError, 'starting_guard, refused to start' 66 | end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /lib/eye/trigger/stop_children.rb: -------------------------------------------------------------------------------- 1 | class Eye::Trigger::StopChildren < Eye::Trigger 2 | 3 | # Kill process children when parent process crashed, or stopped: 4 | # 5 | # trigger :stop_children 6 | 7 | param :timeout, [Integer, Float], nil, 60 8 | 9 | # default on stopped, crashed 10 | param_default :event, [:stopped, :crashed] 11 | 12 | def check(_trans) 13 | debug { 'stopping children' } 14 | process.children.values.pmap(&:stop) 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lib/eye/trigger/transition.rb: -------------------------------------------------------------------------------- 1 | class Eye::Trigger::Transition < Eye::Trigger 2 | 3 | # trigger :transition, :to => :up, :from => :starting, :do => ->{ ... } 4 | 5 | param :do, [Proc, Symbol] 6 | 7 | def check(_trans) 8 | exec_proc :do 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /lib/eye/trigger/wait_dependency.rb: -------------------------------------------------------------------------------- 1 | class Eye::Trigger::WaitDependency < Eye::Trigger 2 | 3 | param :names, [Array], true 4 | param :wait_timeout, [Numeric], nil, 15.seconds 5 | param :retry_after, [Numeric], nil, 1.minute 6 | param :should_start, [TrueClass, FalseClass] 7 | 8 | def check(transition) 9 | wait_dependency if transition.to_name == :starting 10 | end 11 | 12 | private 13 | 14 | def wait_dependency 15 | processes = names.map do |name| 16 | Eye::Control.find_nearest_process(name, process.group_name_pure, process.app_name) 17 | end.compact 18 | return if processes.empty? 19 | processes = Eye::Utils::AliveArray.new(processes) 20 | 21 | processes.each do |p| 22 | if p.state_name != :up && (should_start != false) 23 | p.schedule command: :start, reason: 'start_dependency' 24 | end 25 | end 26 | 27 | res = true 28 | 29 | processes.pmap do |p| 30 | name = p.name 31 | 32 | res &= process.wait_for_condition(wait_timeout, 0.5) do 33 | info "wait for #{name} until it :up" 34 | p.state_name == :up 35 | end 36 | end 37 | 38 | unless res 39 | warn "not waited for #{names} to be up" 40 | process.switch :unmonitoring 41 | 42 | if retry_after 43 | process.schedule in: retry_after, command: :start, reason: 'wait_dependency' 44 | end 45 | 46 | raise Eye::Process::StateError, 'stop transition because dependency is not up' 47 | end 48 | end 49 | 50 | end 51 | -------------------------------------------------------------------------------- /lib/eye/utils.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | 3 | module Eye::Utils 4 | 5 | autoload :Tail, 'eye/utils/tail' 6 | autoload :AliveArray, 'eye/utils/alive_array' 7 | 8 | def self.deep_clone(value) 9 | if value.is_a?(Array) 10 | value.map { |v| deep_clone(v) } 11 | elsif value.is_a?(Hash) 12 | value.each_with_object({}) { |(k, v), r| r[deep_clone(k)] = deep_clone(v) } 13 | else 14 | value 15 | end 16 | end 17 | 18 | # deep merging b into a (a deeply changed) 19 | def self.deep_merge!(a, b, allowed_keys = nil) 20 | b.each do |k, v| 21 | next if allowed_keys && !allowed_keys.include?(k) 22 | if a[k].is_a?(Hash) && v.is_a?(Hash) 23 | deep_merge!(a[k], v) 24 | else 25 | a[k] = v 26 | end 27 | end 28 | a 29 | end 30 | 31 | D1 = '%H:%M'.freeze 32 | D2 = '%b%d'.freeze 33 | 34 | def self.human_time(unix_time) 35 | time = Time.at(unix_time.to_i) 36 | d1 = time.to_date 37 | d2 = Time.now.to_date 38 | time.strftime(d1 == d2 ? D1 : D2) 39 | end 40 | 41 | DF = '%d %b %H:%M'.freeze 42 | 43 | def self.human_time2(unix_time) 44 | Time.at(unix_time.to_i).strftime(DF) 45 | end 46 | 47 | def self.load_env(filename) 48 | content = File.read(filename) 49 | env_vars = content.split("\n") 50 | h = {} 51 | env_vars.each do |e| 52 | e = e.gsub(%r[#.+$], '').strip 53 | next unless e.include?('=') 54 | k, v = e.split('=', 2) 55 | h[k] = v.gsub(%r/^["']+(.*)["']+$/, '\1') 56 | end 57 | h 58 | end 59 | 60 | def self.wait_signal(timeout = nil, &block) 61 | signal = Celluloid::Condition.new 62 | block.call(signal) 63 | signal.wait((timeout || 600).to_f) 64 | :ok 65 | rescue Celluloid::ConditionError 66 | :timeouted 67 | end 68 | 69 | end 70 | -------------------------------------------------------------------------------- /lib/eye/utils/alive_array.rb: -------------------------------------------------------------------------------- 1 | class Eye::Utils::AliveArray 2 | 3 | extend Forwardable 4 | include Enumerable 5 | 6 | def_delegators :@arr, :[], :<<, :clear, :delete, :size, :empty?, :push, 7 | :flatten, :present?, :uniq!, :select! 8 | 9 | def initialize(arr = []) 10 | @arr = arr 11 | end 12 | 13 | def each(&block) 14 | @arr.each { |elem| elem && elem.alive? && block[elem] } 15 | end 16 | 17 | def to_a 18 | map { |x| x } 19 | end 20 | 21 | def full_size 22 | @arr.size 23 | end 24 | 25 | def pure 26 | @arr 27 | end 28 | 29 | def sort_by(&block) 30 | self.class.new super 31 | end 32 | 33 | def sort(&block) 34 | self.class.new super 35 | end 36 | 37 | def sort! 38 | @arr.sort! 39 | end 40 | 41 | def +(other) 42 | if other.is_a?(Eye::Utils::AliveArray) 43 | @arr += other.pure 44 | elsif other.is_a?(Array) 45 | @arr += other 46 | else 47 | raise "Unexpected + #{other}" 48 | end 49 | self 50 | end 51 | 52 | def ==(other) 53 | if other.is_a?(Eye::Utils::AliveArray) 54 | @arr == other.pure 55 | elsif other.is_a?(Array) 56 | @arr == other 57 | else 58 | raise "Unexpected == #{other}" 59 | end 60 | end 61 | 62 | end 63 | -------------------------------------------------------------------------------- /lib/eye/utils/mini_active_support.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | 3 | def silence_warnings 4 | old_verbose, $VERBOSE = $VERBOSE, nil 5 | yield 6 | ensure 7 | $VERBOSE = old_verbose 8 | end 9 | 10 | class Object 11 | def blank? 12 | respond_to?(:empty?) ? empty? : !self 13 | end 14 | 15 | def present? 16 | !blank? 17 | end 18 | 19 | def try(m, *args) 20 | send(m, *args) if respond_to?(m) 21 | end 22 | end 23 | 24 | class NilClass 25 | def try(*args) 26 | end 27 | end 28 | 29 | class String 30 | def underscore 31 | word = self.dup 32 | word.gsub!('::', '/') 33 | word.gsub!(/(?:([A-Za-z\d])|^)((?=a)b)(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } 34 | word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2') 35 | word.gsub!(/([a-z\d])([A-Z])/, '\1_\2') 36 | word.tr!('-', '_') 37 | word.downcase! 38 | word 39 | end 40 | end 41 | 42 | class Array 43 | def extract_options! 44 | self[-1].is_a?(Hash) ? self.pop : {} 45 | end 46 | end 47 | 48 | class Numeric 49 | def percents 50 | self 51 | end 52 | alias_method :percent, :percents 53 | 54 | def seconds 55 | self 56 | end 57 | alias_method :second, :seconds 58 | 59 | def minutes 60 | self * 60 61 | end 62 | alias_method :minute, :minutes 63 | 64 | def hours 65 | self * 3600 66 | end 67 | alias_method :hour, :hours 68 | 69 | def days 70 | self * 86_400 71 | end 72 | alias_method :day, :days 73 | 74 | def weeks 75 | self * 86_400 * 7 76 | end 77 | alias_method :week, :weeks 78 | 79 | def ago 80 | ::Time.now - self 81 | end 82 | 83 | def bytes 84 | self 85 | end 86 | alias_method :byte, :bytes 87 | 88 | def kilobytes 89 | self * 1024 90 | end 91 | alias_method :kilobyte, :kilobytes 92 | 93 | def megabytes 94 | self * 1024 * 1024 95 | end 96 | alias_method :megabyte, :megabytes 97 | 98 | def gigabytes 99 | self * 1024 * 1024 * 1024 100 | end 101 | alias_method :gigabyte, :gigabytes 102 | 103 | def terabytes 104 | self * 1024 * 1024 * 1024 * 1024 105 | end 106 | alias_method :terabyte, :terabytes 107 | end 108 | -------------------------------------------------------------------------------- /lib/eye/utils/pmap.rb: -------------------------------------------------------------------------------- 1 | module Enumerable 2 | 3 | # Simple parallel map using Celluloid::Futures 4 | def pmap(&block) 5 | map { |elem| Celluloid::Future.new(elem, &block) }.map(&:value) 6 | end 7 | 8 | end 9 | -------------------------------------------------------------------------------- /lib/eye/utils/tail.rb: -------------------------------------------------------------------------------- 1 | class Eye::Utils::Tail < Array 2 | 3 | # limited array 4 | 5 | def initialize(max_size = 100) 6 | @max_size = max_size 7 | super() 8 | end 9 | 10 | def push(el) 11 | super(el) 12 | shift if length > @max_size 13 | self 14 | end 15 | 16 | def <<(el) 17 | push(el) 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /spec/checker/cpu_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | def chcpu(cfg = {}) 4 | Eye::Checker.create(123, {:type => :cpu, :every => 5.seconds, 5 | :times => 1}.merge(cfg)) 6 | end 7 | 8 | describe "Eye::Checker::Cpu" do 9 | 10 | describe "without below" do 11 | subject{ chcpu } 12 | 13 | it "get_value" do 14 | mock(Eye::SystemResources).cpu(123){ 65 } 15 | subject.get_value.should == 65 16 | end 17 | 18 | it "without below always true" do 19 | stub(subject).get_value{ 15 } 20 | subject.check.should == true 21 | 22 | stub(subject).get_value{ 20 } 23 | subject.check.should == true 24 | end 25 | end 26 | 27 | describe "with below" do 28 | subject{ chcpu(:below => 30) } 29 | 30 | it "good" do 31 | stub(subject).get_value{ 20 } 32 | subject.check.should == true 33 | 34 | stub(subject).get_value{ 25 } 35 | subject.check.should == true 36 | end 37 | 38 | it "good" do 39 | stub(subject).get_value{ 25 } 40 | subject.check.should == true 41 | 42 | stub(subject).get_value{ 35 } 43 | subject.check.should == false 44 | end 45 | 46 | end 47 | 48 | describe "validates" do 49 | it "ok" do 50 | Eye::Checker.validate!({:type => :cpu, :every => 5.seconds, :times => 1, :below => 100}) 51 | end 52 | 53 | it "bad param below" do 54 | expect{ Eye::Checker.validate!({:type => :cpu, :every => 5.seconds, :times => 1, :below => {1 => 2}}) }.to raise_error(Eye::Dsl::Validation::Error) 55 | end 56 | end 57 | 58 | end -------------------------------------------------------------------------------- /spec/checker/cputime_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::Checker::Cputime" do 4 | 5 | subject do 6 | Eye::Checker.create(123, {:type => :cputime, :every => 5.seconds, :times => 1, :below => 10.minutes}) 7 | end 8 | 9 | it "get_value" do 10 | mock(Eye::SystemResources).cputime(123){ 65 } 11 | subject.get_value.should == 65 12 | end 13 | 14 | it "good" do 15 | stub(subject).get_value{ 5.minutes } 16 | subject.check.should == true 17 | 18 | stub(subject).get_value{ 20.minutes } 19 | subject.check.should == false 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/checker/file_ctime_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | def chctime(cfg = {}) 4 | Eye::Checker.create(nil, {:type => :ctime, :every => 5.seconds, 5 | :file => $logger_path, :times => 1}.merge(cfg)) 6 | end 7 | 8 | describe "Eye::Checker::FileCTime" do 9 | 10 | describe "" do 11 | subject{ chctime } 12 | 13 | it "get_value" do 14 | subject.get_value.should == File.ctime($logger_path) 15 | end 16 | 17 | it "not good if size equal prevous" do 18 | stub(subject).get_value{ Time.parse('00:00:01') } 19 | subject.check.should == true 20 | 21 | stub(subject).get_value{ Time.parse('00:00:01') } 22 | subject.check.should == false 23 | end 24 | 25 | it "good when little different with previous" do 26 | stub(subject).get_value{ Time.parse('00:00:01') } 27 | subject.check.should == true 28 | 29 | stub(subject).get_value{ Time.parse('00:00:02') } 30 | subject.check.should == true 31 | end 32 | end 33 | 34 | end -------------------------------------------------------------------------------- /spec/checker/file_size_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | def chfsize(cfg = {}) 4 | Eye::Checker.create(nil, {:type => :fsize, :every => 5.seconds, 5 | :file => $logger_path, :times => 1}.merge(cfg)) 6 | end 7 | 8 | describe "Eye::Checker::FileSize" do 9 | 10 | describe "" do 11 | subject{ chfsize } 12 | 13 | it "get_value" do 14 | subject.get_value.should be_within(10).of(File.size($logger_path)) 15 | end 16 | 17 | it "not good if size equal prevous" do 18 | stub(subject).get_value{1001} 19 | subject.check.should == true 20 | 21 | stub(subject).get_value{1001} 22 | subject.check.should == false 23 | end 24 | 25 | it "good when little different with previous" do 26 | stub(subject).get_value{1001} 27 | subject.check.should == true 28 | 29 | stub(subject).get_value{1002} 30 | subject.check.should == true 31 | end 32 | end 33 | 34 | describe "below" do 35 | subject{ chfsize(:below => 10) } 36 | 37 | it "good" do 38 | stub(subject).get_value{1001} 39 | subject.check.should == true 40 | 41 | stub(subject).get_value{1005} 42 | subject.check.should == true 43 | end 44 | 45 | it "bad" do 46 | stub(subject).get_value{1001} 47 | subject.check.should == true 48 | 49 | stub(subject).get_value{1015} 50 | subject.check.should == false 51 | end 52 | 53 | end 54 | 55 | describe "above" do 56 | subject{ chfsize(:above => 10) } 57 | 58 | it "good" do 59 | stub(subject).get_value{1001} 60 | subject.check.should == true 61 | 62 | stub(subject).get_value{1005} 63 | subject.check.should == false 64 | end 65 | 66 | it "bad" do 67 | stub(subject).get_value{1001} 68 | subject.check.should == true 69 | 70 | stub(subject).get_value{1015} 71 | subject.check.should == true 72 | end 73 | 74 | end 75 | 76 | 77 | describe "above and below" do 78 | subject{ chfsize(:above => 10, :below => 30) } 79 | 80 | it "bad" do 81 | stub(subject).get_value{1001} 82 | subject.check.should == true 83 | 84 | stub(subject).get_value{1005} 85 | subject.check.should == false 86 | end 87 | 88 | it "good" do 89 | stub(subject).get_value{1001} 90 | subject.check.should == true 91 | 92 | stub(subject).get_value{1021} 93 | subject.check.should == true 94 | end 95 | 96 | it "bad" do 97 | stub(subject).get_value{1001} 98 | subject.check.should == true 99 | 100 | stub(subject).get_value{1045} 101 | subject.check.should == false 102 | end 103 | 104 | end 105 | 106 | 107 | end -------------------------------------------------------------------------------- /spec/checker/file_touched_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::Checker::FileTouched" do 4 | 5 | subject do 6 | Eye::Checker.create(123, {:type => :file_touched, :every => 5.seconds, :times => 1, :file => "1"}) 7 | end 8 | 9 | it "get_value" do 10 | mock(File).exist?("1"){ true } 11 | subject.get_value.should == true 12 | 13 | mock(File).exist?("1"){ false } 14 | subject.get_value.should == false 15 | end 16 | 17 | it "good" do 18 | mock(File).exist?("1"){ true } 19 | subject.check.should == false 20 | 21 | mock(File).exist?("1"){ false } 22 | subject.check.should == true 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/checker/runtime_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::Checker::Runtime" do 4 | 5 | subject do 6 | Eye::Checker.create(123, {:type => :runtime, :every => 5.seconds, :times => 1, :below => 10.minutes}) 7 | end 8 | 9 | it "get_value" do 10 | stub(Eye::SystemResources).start_time(123) { 65 } 11 | subject.get_value.should == Time.now.to_i - 65 12 | end 13 | 14 | it "good" do 15 | stub(subject).get_value{ 5.minutes } 16 | subject.check.should == true 17 | 18 | stub(subject).get_value{ 20.minutes } 19 | subject.check.should == false 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/child_process/child_process_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::ChildProcess" do 4 | 5 | before :each do 6 | @pid = Eye::System.daemonize(C.p1[:start_command], C.p1)[:pid] 7 | Eye::System.pid_alive?(@pid).should == true 8 | sleep 0.5 9 | @parent = OpenStruct.new(:pid => @parent, :scheduler_history => Eye::Process::StatesHistory.new(50)) 10 | end 11 | 12 | it "some process was declared by my child" do 13 | @process = Eye::ChildProcess.new(@pid, {}, nil, @parent) 14 | @process.pid.should == @pid 15 | 16 | @process.watchers.keys.should == [] 17 | end 18 | 19 | describe "restart" do 20 | 21 | it "kill by default command" do 22 | @process = Eye::ChildProcess.new(@pid, {}, nil, @parent) 23 | @process.schedule :restart 24 | 25 | sleep 0.5 26 | Eye::System.pid_alive?(@pid).should == false 27 | end 28 | 29 | it "kill by stop command" do 30 | @process = Eye::ChildProcess.new(@pid, {:stop_command => "kill -9 {PID}"}, nil, @parent) 31 | @process.schedule :restart 32 | 33 | sleep 0.5 34 | Eye::System.pid_alive?(@pid).should == false 35 | end 36 | 37 | it "kill by stop command with PARENT_PID" do 38 | # emulate with self pid as parent_pid 39 | @process = Eye::ChildProcess.new(@pid, {:stop_command => "kill -9 {PARENT_PID}"}, nil, OpenStruct.new(:pid => @pid)) 40 | @process.schedule :restart 41 | 42 | sleep 0.5 43 | Eye::System.pid_alive?(@pid).should == false 44 | end 45 | 46 | it "should not restart with wrong command" do 47 | @process = Eye::ChildProcess.new(@pid, {:stop_command => "kill -0 {PID}"}, nil, @parent) 48 | @process.schedule :restart 49 | 50 | sleep 0.5 51 | Eye::System.pid_alive?(@pid).should == true 52 | end 53 | end 54 | 55 | end 56 | 57 | -------------------------------------------------------------------------------- /spec/client_server_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | [:old, :new].each do |prot_type| 4 | describe "Eye::Client, Eye::Server - #{prot_type}" do 5 | before :each do 6 | @socket_path = C.socket_path 7 | @client = Eye::Client.new(@socket_path, prot_type) 8 | @server = Eye::Server.new(@socket_path) 9 | 10 | @server.async.run 11 | sleep 0.1 12 | end 13 | 14 | after :each do 15 | @server.terminate 16 | end 17 | 18 | it "client command, should send to controller" do 19 | mock(Eye::Control).command('restart', 'samples', {}){ :command_sent } 20 | mock(Eye::Control).command(:stop, {}){ :command_sent2 } 21 | @client.execute(command: 'restart', args: %w{samples}).should == :command_sent 22 | @client.execute(command: :stop).should == :command_sent2 23 | end 24 | 25 | it "another spec works too" do 26 | mock(Eye::Control).command('stop', {}){ :command_sent2 } 27 | @client.execute(command: 'stop').should == :command_sent2 28 | end 29 | 30 | it "if server already listen should recreate" do 31 | mock(Eye::Control).command('stop', {}){ :command_sent2 } 32 | @server2 = Eye::Server.new(@socket_path) 33 | @server2.async.run 34 | sleep 0.1 35 | @client.execute(command: 'stop').should == :command_sent2 36 | end 37 | 38 | it "if error server should be alive" do 39 | @client.send(:attempt_command, 'trash', 1).should == :corrupted_data 40 | @server.alive?.should == true 41 | end 42 | 43 | if prot_type == :new 44 | it "big message, to pass env variables in future" do 45 | a = "a" * 10000 46 | mock(Eye::Control).command('stop', a, {}){ :command_sent2 } 47 | @client.execute(command: 'stop', args: [a]).should == :command_sent2 48 | end 49 | end 50 | 51 | it "big message, to answer" do 52 | a = "a" * 50000 53 | mock(Eye::Control).command('stop', {}){ a } 54 | @client.execute(command: 'stop').size.should == 50000 55 | end 56 | 57 | # TODO, remove in 1.0 58 | describe "old message format" do 59 | it "ok message" do 60 | mock(Eye::Control).command('restart', 'samples', {}){ :command_sent } 61 | @client.send(:attempt_command, Marshal.dump(%w{restart samples}), 1).should == :command_sent 62 | end 63 | end 64 | end 65 | end -------------------------------------------------------------------------------- /spec/controller/data_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::Controller data spec" do 4 | subject{ Eye::Controller.new } 5 | before { subject.load(fixture("dsl/load.eye")) } 6 | 7 | it "info_data" do 8 | res = subject.command(:info_data) 9 | st = res[:subtree] 10 | st.size.should == 2 11 | p = st[1][:subtree][0][:subtree][0] 12 | p.should include(:name=>"z1", :state=>"unmonitored", 13 | :type=>:process, :resources=>{:memory=>nil, :cpu=>nil, :start_time=>nil, :pid=>nil}) 14 | end 15 | 16 | it "info_data + filter" do 17 | res = subject.info_data('app2') 18 | st = res[:subtree] 19 | st.size.should == 1 20 | p = st[0][:subtree][0][:subtree][0] 21 | p.should include(:name=>"z1", :state=>"unmonitored", 22 | :type=>:process, :resources=>{:memory=>nil, :cpu=>nil, :start_time=>nil, :pid=>nil}) 23 | end 24 | 25 | it "short_data" do 26 | sleep 0.2 27 | res = subject.command(:short_data) 28 | res.should == {:subtree=>[{:name=>"app1", :type=>:application, :subtree=>[{:name=>"gr1", :type=>:group, :states=>{"unmonitored"=>2}}, 29 | {:name=>"gr2", :type=>:group, :states=>{"unmonitored"=>1}}, {:name=>"default", :type=>:group, :states=>{"unmonitored"=>2}}]}, 30 | {:name=>"app2", :type=>:application, :subtree=>[{:name=>"default", :type=>:group, :states=>{"unmonitored"=>1}}]}]} 31 | end 32 | 33 | it "debug_data" do 34 | res = subject.command(:debug_data) 35 | res[:resources].should be_a(Hash) 36 | res[:config_yaml].should == nil 37 | 38 | res = subject.debug_data(:config => true) 39 | res[:resources].should be_a(Hash) 40 | res[:config_yaml].should be_a(String) 41 | end 42 | 43 | it "history_data" do 44 | h = subject.command(:history_data, 'app1') 45 | h.size.should == 5 46 | h.keys.sort.should == ["app1:g4", "app1:g5", "app1:gr1:p1", "app1:gr1:p2", "app1:gr2:q3"] 47 | end 48 | 49 | end 50 | -------------------------------------------------------------------------------- /spec/controller/races_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Some crazy situations on load config" do 4 | 5 | before :each do 6 | start_controller do 7 | @controller.load_erb(fixture("dsl/integration.erb")) 8 | end 9 | end 10 | 11 | after :each do 12 | stop_controller 13 | 14 | File.delete(File.join(C.sample_dir, "lock1.lock")) rescue nil 15 | File.delete(File.join(C.sample_dir, "lock2.lock")) rescue nil 16 | end 17 | 18 | it "load another config, with same processes but changed names" do 19 | @controller.load_erb(fixture("dsl/integration2.erb")) 20 | 21 | sleep 10 22 | 23 | # @p1, @p2 recreates 24 | # @p3 the same 25 | 26 | procs = @controller.all_processes 27 | @p1_ = procs.detect{|c| c.name == 'sample1_'} 28 | @p2_ = procs.detect{|c| c.name == 'sample2_'} 29 | @p3_ = procs.detect{|c| c.name == 'forking'} 30 | 31 | @p3.object_id.should == @p3_.object_id 32 | @p1.alive?.should == false 33 | @p1_.alive?.should == true 34 | 35 | @p2.alive?.should == false 36 | @p2_.alive?.should == true 37 | 38 | @p1_.pid.should == @old_pid1 39 | @p2_.pid.should == @old_pid2 40 | @p3_.pid.should == @old_pid3 41 | 42 | @p1_.state_name.should == :up 43 | end 44 | 45 | end -------------------------------------------------------------------------------- /spec/controller/restart_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Intergration restart" do 4 | before :each do 5 | start_controller do 6 | @controller.load_erb(fixture("dsl/integration.erb")) 7 | end 8 | 9 | @processes.size.should == 3 10 | @processes.map{|c| c.state_name}.uniq.should == [:up] 11 | @samples = @controller.all_groups.detect{|c| c.name == 'samples'} 12 | end 13 | 14 | after :each do 15 | stop_controller 16 | end 17 | 18 | it "restart process group samples" do 19 | @controller.command(:restart, "samples") 20 | sleep 11 # while they restarting 21 | 22 | @processes.map{|c| c.state_name}.uniq.should == [:up] 23 | @p1.pid.should_not == @old_pid1 24 | @p2.pid.should_not == @old_pid2 25 | @p3.pid.should == @old_pid3 26 | end 27 | 28 | it "restart process" do 29 | @controller.command(:restart, "sample1") 30 | sleep 10 # while they restarting 31 | 32 | @processes.map{|c| c.state_name}.uniq.should == [:up] 33 | @p1.pid.should_not == @old_pid1 34 | @p2.pid.should == @old_pid2 35 | @p3.pid.should == @old_pid3 36 | end 37 | 38 | it "restart process with signal" do 39 | should_spend(3, 0.3) do 40 | c = Celluloid::Condition.new 41 | @controller.command(:restart, "sample1", :signal => c) 42 | c.wait 43 | end 44 | 45 | @processes.map{|c| c.state_name}.uniq.should == [:up] 46 | @p1.pid.should_not == @old_pid1 47 | end 48 | 49 | it "restart process forking" do 50 | @controller.command(:restart, "forking") 51 | sleep 11 # while they restarting 52 | 53 | @processes.map{|c| c.state_name}.uniq.should == [:up] 54 | @p1.pid.should == @old_pid1 55 | @p2.pid.should == @old_pid2 56 | @p3.pid.should_not == @old_pid3 57 | 58 | @p1.scheduler_last_reason.should == 'monitor by user' 59 | @p3.scheduler_last_reason.should == 'restart by user' 60 | end 61 | 62 | it "restart forking named child" do 63 | @p3.wait_for_condition(15, 0.3) { @p3.children.size == 3 } 64 | @children = @p3.children.keys 65 | @children.size.should == 3 66 | dead_pid = @children.sample 67 | 68 | @controller.command(:restart, "child-#{dead_pid}").should == {:result => ["int:forking:child-#{dead_pid}"]} 69 | sleep 11 # while it 70 | 71 | new_children = @p3.children.keys 72 | new_children.size.should == 3 73 | new_children.should_not include(dead_pid) 74 | (@children - [dead_pid]).each do |pid| 75 | new_children.should include(pid) 76 | end 77 | 78 | @p3.scheduler_history.states.should include("restart_child") 79 | end 80 | 81 | it "restart missing" do 82 | @controller.command(:restart, "blabla").should == {:result => []} 83 | sleep 1 84 | @processes.map{|c| c.state_name}.uniq.should == [:up] 85 | @p1.pid.should == @old_pid1 86 | @p2.pid.should == @old_pid2 87 | @p3.pid.should == @old_pid3 88 | end 89 | 90 | end 91 | -------------------------------------------------------------------------------- /spec/controller/user_command_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Controller user_command" do 4 | subject { Eye::Controller.new } 5 | 6 | it "should execute string cmd" do 7 | cfg = <<-D 8 | Eye.application("app") do 9 | process("proc") do 10 | pid_file "#{C.p1_pid}" 11 | start_command "sleep 10" 12 | daemonize! 13 | start_grace 0.3 14 | 15 | command :abcd, "touch #{C.tmp_file}" 16 | end 17 | end 18 | D 19 | 20 | subject.load_content(cfg) 21 | sleep 0.5 22 | 23 | File.exist?(C.tmp_file).should == false 24 | subject.command('user_command', 'abcd', 'proc') 25 | sleep 0.5 26 | File.exist?(C.tmp_file).should == true 27 | end 28 | 29 | it "should send notify when command exitstatus != 0" do 30 | cfg = <<-D 31 | Eye.application("app") do 32 | process("proc") do 33 | pid_file "#{C.p1_pid}" 34 | start_command "sleep 10" 35 | daemonize! 36 | start_grace 0.3 37 | 38 | command :abcd, "ruby -e 'exit 1'" 39 | end 40 | end 41 | D 42 | 43 | subject.load_content(cfg) 44 | @process = subject.process_by_name("proc") 45 | mock(@process).notify(:debug, anything) 46 | sleep 0.5 47 | subject.command('user_command', 'abcd', 'proc') 48 | sleep 2.0 49 | end 50 | 51 | it "should execute signals cmd" do 52 | 53 | cfg = <<-D 54 | Eye.application("app") do 55 | process("proc") do 56 | pid_file "#{C.p1_pid}" 57 | start_command "sleep 10" 58 | daemonize! 59 | start_grace 0.3 60 | 61 | command :abcd, [:quit, 0.2, :term, 0.1, :kill] 62 | end 63 | end 64 | D 65 | 66 | subject.load_content(cfg) 67 | sleep 0.5 68 | 69 | @process = subject.process_by_name("proc") 70 | Eye::System.pid_alive?(@process.pid).should == true 71 | 72 | subject.command('user_command', 'abcd', 'app') 73 | sleep 2.5 74 | 75 | Eye::System.pid_alive?(@process.pid).should == false 76 | end 77 | 78 | it "check identity before execute user_command" do 79 | cfg = <<-D 80 | Eye.application("app") do 81 | process("proc") do 82 | pid_file "#{C.p1_pid}" 83 | start_command "sleep 10" 84 | daemonize! 85 | start_grace 0.3 86 | 87 | command :abcd, "touch #{C.tmp_file}" 88 | end 89 | end 90 | D 91 | 92 | subject.load_content(cfg) 93 | @process = subject.process_by_name("proc") 94 | File.exist?(C.tmp_file).should == false 95 | sleep 1 96 | change_ctime(C.p1_pid, 5.days.ago) 97 | subject.command('user_command', 'abcd', 'proc') 98 | sleep 1 99 | File.exist?(C.tmp_file).should == false 100 | end 101 | 102 | end 103 | -------------------------------------------------------------------------------- /spec/dsl/config_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::Dsl::Config" do 4 | 5 | it "logger" do 6 | conf = <<-E 7 | Eye.config do 8 | logger "/tmp/1.loG" 9 | end 10 | E 11 | Eye::Dsl.parse(conf).to_h.should == {:applications => {}, :settings => {:logger => ["/tmp/1.loG"]}, :defaults => {}} 12 | end 13 | 14 | it "logger with params" do 15 | conf = <<-E 16 | Eye.config do 17 | logger "/tmp/1.loG", 'dayly', 1_000_000 18 | end 19 | E 20 | Eye::Dsl.parse(conf).to_h.should == {:applications => {}, :settings => {:logger => ["/tmp/1.loG", 'dayly', 1_000_000]}, :defaults => {}} 21 | end 22 | 23 | it "should merge sections" do 24 | conf = <<-E 25 | Eye.config do 26 | logger "/tmp/1.log" 27 | logger_level 2 28 | end 29 | 30 | Eye.config do 31 | logger "/tmp/2.log" 32 | end 33 | 34 | E 35 | Eye::Dsl.parse(conf).to_h.should == {:applications => {}, :settings => {:logger => ["/tmp/2.log"], 36 | :logger_level => 2}, :defaults => {}} 37 | end 38 | 39 | end -------------------------------------------------------------------------------- /spec/dsl/load_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Subfolder load spec" do 4 | 5 | it "should load file with full_path" do 6 | f = fixture("dsl/subfolder1/proc1.rb") 7 | conf = <<-E 8 | Eye.load("#{f}") 9 | Eye.application("bla") do 10 | proc1(self, "e1") 11 | end 12 | E 13 | Eye::Dsl.parse_apps(conf).should == {"bla" => {:name => "bla", :groups=>{ 14 | "__default__"=>{:name => "__default__", :application => "bla", :processes=>{ 15 | "e1"=>{:pid_file=>"e1.pid", :application=>"bla", :group=>"__default__", :name=>"e1"}}}}}} 16 | end 17 | 18 | it "file loaded, but proc not exists in it" do 19 | f = fixture("dsl/subfolder1/proc1.rb") 20 | conf = <<-E 21 | Eye.load("#{f}") 22 | Eye.application("bla") do 23 | proc55(self, "e1") 24 | end 25 | E 26 | expect{Eye::Dsl.parse_apps(conf)}.to raise_error(NoMethodError) 27 | end 28 | 29 | it "subfolder2" do 30 | file = fixture('dsl/subfolder2.eye') 31 | Eye::Dsl.parse_apps(nil, file).should == { 32 | "subfolder2" => {:name => "subfolder2", :working_dir=>"/tmp", :groups=>{ 33 | "__default__"=>{:name => "__default__", :application => "subfolder2", :working_dir=>"/tmp", :processes=>{ 34 | "e3"=>{:working_dir=>"/tmp", :pid_file=>"e3.pid2", :application=>"subfolder2", :group=>"__default__", :name=>"e3"}, 35 | "e4"=>{:working_dir=>"/", :pid_file=>"e4.pid3", :application=>"subfolder2", :group=>"__default__", :name=>"e4"}}}}}} 36 | end 37 | 38 | it "subfolder3" do 39 | file = fixture('dsl/subfolder3.eye') 40 | Eye::Dsl.parse_apps(nil, file).should == { 41 | "subfolder3" => {:name => "subfolder3", :working_dir=>"/tmp", :groups=>{ 42 | "__default__"=>{:name => "__default__", :application => "subfolder3", :working_dir=>"/tmp", :processes=>{ 43 | "e1"=>{:working_dir=>"/tmp", :pid_file=>"e1.pid4", :application=>"subfolder3", :group=>"__default__", :name=>"e1"}, 44 | "e2"=>{:working_dir=>"/var", :pid_file=>"e2.pid5", :application=>"subfolder3", :group=>"__default__", :name=>"e2"}}}}}} 45 | end 46 | 47 | it "subfolder4" do 48 | file = fixture('dsl/subfolder4.eye') 49 | Eye::Dsl.parse_apps(nil, file).should == {"subfolder4"=>{:name=>"subfolder4", :environment=>{"a"=>1, "b"=>2, "c"=>3}}} 50 | end 51 | 52 | end -------------------------------------------------------------------------------- /spec/example/em.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'eventmachine' 3 | require 'fileutils' 4 | 5 | def answer(data) 6 | case data 7 | when 'ping' then "pong\n" 8 | when 'bad' then "what\n" 9 | when 'raw' then 'raw_ans' 10 | when 'timeout' then sleep 5; "ok\n" 11 | when 'exception' then raise 'haha' 12 | when 'quit' then EM.stop 13 | when 'big' then 'a' * 10_000_000 14 | end 15 | end 16 | 17 | class Echo < EM::Connection 18 | def post_init 19 | puts "-- someone connected to the echo server!" 20 | end 21 | 22 | def receive_data data 23 | puts "receive #{data.inspect}" 24 | send_data(answer(data)) 25 | end 26 | 27 | def unbind 28 | puts "-- someone disconnected from the echo server!" 29 | end 30 | end 31 | 32 | class EchoObj < EM::Connection 33 | include EM::P::ObjectProtocol 34 | 35 | def post_init 36 | puts "-- someone connected to the echo server!" 37 | end 38 | 39 | def receive_object obj # {:command => 'ping'} 40 | puts "receive #{obj.inspect}" 41 | send_object(answer(obj[:command]).chop) 42 | end 43 | 44 | def unbind 45 | puts "-- someone disconnected from the echo server!" 46 | end 47 | end 48 | 49 | class SslServ < EM::Connection 50 | def post_init 51 | start_tls 52 | end 53 | 54 | def receive_data(data) 55 | puts "ssl received: #{data}" 56 | send_data(data + ":1\n") 57 | end 58 | end 59 | 60 | trap "TERM" do 61 | EM.stop 62 | FileUtils.rm($sock) rescue nil 63 | end 64 | 65 | EM.run do 66 | $port1 = (ARGV[0] || 33231).to_i 67 | $port2 = (ARGV[1] || 33232).to_i 68 | $port3 = (ARGV[3] || 33233).to_i 69 | $sock = (ARGV[2] || "/tmp/em_test_sock_spec") 70 | EM.start_server '127.0.0.1', $port1, Echo 71 | EM.start_server '127.0.0.1', $port2, EchoObj 72 | EM.start_server $sock, nil, Echo 73 | EM.start_server '127.0.0.1', $port3, SslServ 74 | puts 'started' 75 | end 76 | -------------------------------------------------------------------------------- /spec/example/forking.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'forking' 3 | 4 | root = File.expand_path(File.dirname(__FILE__)) 5 | 6 | PID_NAME = ENV["PID_NAME"] || "forking.pid" 7 | 8 | f = Forking.new(:name => 'forking', :working_dir => root, 9 | :log_file => "#{root}/forking.log", 10 | :pid_file => "#{root}/#{PID_NAME}", :sync_log => true) 11 | 12 | 3.times do |i| 13 | f.spawn(:log_file => "#{root}/child#{i}.log", :sync_log => true) do 14 | $0 = "forking child" 15 | start_at = Time.now 16 | loop do 17 | tm = Time.now 18 | p "#{tm} - #{tm.to_f} - #{i} - tick" 19 | sleep 0.1 20 | return if tm - start_at > 600 21 | end 22 | end 23 | end 24 | 25 | f.run! -------------------------------------------------------------------------------- /spec/example/leaf_child.sh: -------------------------------------------------------------------------------- 1 | echo 1 2 | sleep 11& 3 | sleep 12& 4 | sleep 13& 5 | sleep 14& 6 | sleep 15 7 | -------------------------------------------------------------------------------- /spec/example/thin.ru: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'sinatra' 3 | 4 | class Test < Sinatra::Base 5 | 6 | get '/hello' do 7 | sleep 0.5 8 | "Hello World!" 9 | end 10 | 11 | get '/timeout' do 12 | sleep 5 13 | "hehe" 14 | end 15 | end 16 | 17 | run Test.new 18 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/0.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.join(File.dirname(__FILE__), %w{1.rb})) 2 | 3 | Eye.application("bla") do 4 | working_dir "/tmp" 5 | 6 | process_1(self, "11") 7 | process_1(self, "12") 8 | end 9 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/0a.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), %w{1.rb}) 2 | 3 | Eye.application("bla") do |app| 4 | working_dir "/tmp" 5 | 6 | process_1(app, "11") 7 | process_1(app, "12") 8 | end 9 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/0c.rb: -------------------------------------------------------------------------------- 1 | Eye.load(File.dirname(__FILE__) + "/1*.rb") 2 | 3 | Eye.application("bla") do |app| 4 | working_dir "/tmp" 5 | 6 | process_1(app, "11") 7 | process_1(app, "12") 8 | end 9 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/1.rb: -------------------------------------------------------------------------------- 1 | def process_1(proxy, name) 2 | proxy.process(name) do 3 | pid_file "#{name}.pid" 4 | end 5 | end -------------------------------------------------------------------------------- /spec/fixtures/dsl/Eyefile: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- /spec/fixtures/dsl/bad.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'bla' do 2 | process 'bad' do 3 | # bad because not pid_file 4 | working_dir '/tmp' 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/configs/1.eye: -------------------------------------------------------------------------------- 1 | Eye.config do 2 | logger '/tmp/a.log' 3 | end 4 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/configs/2.eye: -------------------------------------------------------------------------------- 1 | Eye.config { http enable: true } 2 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/configs/3.eye: -------------------------------------------------------------------------------- 1 | Eye.config { http enable: false } 2 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/configs/4.eye: -------------------------------------------------------------------------------- 1 | Eye.config do 2 | self.logger = nil 3 | end 4 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/configs/5.eye: -------------------------------------------------------------------------------- 1 | Eye.config do 2 | mail host: 'localhost', port: 192 3 | end 4 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/contact1.eye: -------------------------------------------------------------------------------- 1 | Eye.config do 2 | mail host: 'host', port: 22 3 | contact :contact1, :mail, 'aaa@mail' 4 | end 5 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/contact2.eye: -------------------------------------------------------------------------------- 1 | Eye.config do 2 | jabber host: 'host', port: 22, user: 'asdf' 3 | contact :contact2, :jabber, 'asdf2' 4 | end 5 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/default1.eye: -------------------------------------------------------------------------------- 1 | Eye.app :__default__ do 2 | env 'A' => 'B' 3 | end 4 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/default2.eye: -------------------------------------------------------------------------------- 1 | Eye.app :some do 2 | end 3 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/default3.eye: -------------------------------------------------------------------------------- 1 | Eye.app :__default__ do 2 | env 'C' => 'D' 3 | end 4 | 5 | Eye.app :some do 6 | end 7 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/default4.eye: -------------------------------------------------------------------------------- 1 | Eye.app :__default__ do 2 | trigger :stop_children 3 | check :memory, below: 10 4 | end 5 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/empty.eye: -------------------------------------------------------------------------------- 1 | Eye.application('bla') do 2 | environment 'RAILS_ENV' => 'production' 3 | keep_alive false 4 | stdall '12.log' 5 | 6 | group('ha') do 7 | 5.times do |i| 8 | process("ha_#{i}") do 9 | environment 'HA' => '1' 10 | pid_file "/tmp/#{i}" 11 | stdout '11.log' 12 | end 13 | end 14 | end 15 | 16 | process('1') do 17 | pid_file '1' 18 | stdall '1' 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/env1: -------------------------------------------------------------------------------- 1 | A=11 2 | B=12=13 3 | # C=1 4 | #D 5 | E=55 # D=11 6 | F="stuff" 7 | G='more' 8 | 9 | a 10 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/include_test.eye: -------------------------------------------------------------------------------- 1 | Eye.app :test do 2 | use 'include_test/1.rb' 3 | end 4 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/include_test/1.rb: -------------------------------------------------------------------------------- 1 | 2 | env "a" => 'b' 3 | 4 | process :bla do 5 | pid_file "10" 6 | end -------------------------------------------------------------------------------- /spec/fixtures/dsl/include_test/ha.rb: -------------------------------------------------------------------------------- 1 | 2 | group "ha" do 3 | use "1.rb" 4 | end -------------------------------------------------------------------------------- /spec/fixtures/dsl/include_test2.eye: -------------------------------------------------------------------------------- 1 | Eye.app :test2 do 2 | use 'include_test/ha.rb' 3 | end 4 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/integration.erb: -------------------------------------------------------------------------------- 1 | Eye.app("int") do 2 | working_dir File.join(File.dirname(__FILE__), %w{.. .. example}) 3 | stdall "shlak.log" 4 | 5 | group "samples" do 6 | process("sample1") do 7 | pid_file "<%= C.p1_pid %>" 8 | start_command "ruby sample.rb" 9 | daemonize true 10 | end 11 | 12 | process("sample2") do 13 | pid_file "<%= C.p2_pid %>" 14 | start_command "ruby sample.rb -d --pid <%= C.p2_pid %> --log shlak.log" 15 | checks :memory, :below => 300.megabytes 16 | end 17 | end 18 | 19 | process("forking") do 20 | env "PID_NAME" => "<%= C.p3_pid %>" 21 | pid_file "<%= C.p3_pid %>" 22 | 23 | start_command "ruby forking.rb start" 24 | stop_command "ruby forking.rb stop" 25 | 26 | monitor_children do 27 | children_update_period 5.seconds 28 | restart_command "kill -2 {PID}" 29 | end 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/integration2.erb: -------------------------------------------------------------------------------- 1 | # different from integration.eye, with names of the processes 2 | 3 | Eye.app("int") do 4 | working_dir File.join(File.dirname(__FILE__), %w{.. .. example}) 5 | stdall "shlak.log" 6 | 7 | group "samples" do 8 | process("sample1_") do 9 | pid_file "<%= C.p1_pid %>" 10 | start_command "ruby sample.rb" 11 | daemonize true 12 | end 13 | 14 | process("sample2_") do 15 | pid_file "<%= C.p2_pid %>" 16 | start_command "ruby sample.rb -d --pid <%= C.p2_pid %> --log shlak.log" 17 | checks :memory, :below => 300.megabytes 18 | end 19 | end 20 | 21 | process("forking") do 22 | env "PID_NAME" => "<%= C.p3_pid %>" 23 | pid_file "<%= C.p3_pid %>" 24 | 25 | start_command "ruby forking.rb start" 26 | stop_command "ruby forking.rb stop" 27 | 28 | monitor_children do 29 | children_update_period 5.seconds 30 | restart_command "kill -2 {PID}" 31 | end 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/integration_locks.eye: -------------------------------------------------------------------------------- 1 | Eye.app('int') do 2 | working_dir File.join(File.dirname(__FILE__), %w[.. .. example]) 3 | stdall 'shlak.log' 4 | 5 | group 'samples' do 6 | process('sample1') do 7 | pid_file '1.pid' 8 | start_command 'ruby sample.rb -L lock1.lock' 9 | daemonize true 10 | end 11 | 12 | process('sample2') do 13 | pid_file '2.pid' 14 | start_command 'ruby sample.rb -d --pid 2.pid --log shlak.log -L lock2.lock' 15 | checks :memory, below: 300.megabytes 16 | end 17 | end 18 | 19 | process('forking') do 20 | pid_file 'forking.pid' 21 | start_command 'ruby forking.rb start' 22 | stop_command 'ruby forking.rb stop' 23 | 24 | monitor_children do 25 | children_update_period 5.seconds 26 | restart_command 'kill -2 {PID}' 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/integration_sor.erb: -------------------------------------------------------------------------------- 1 | Eye.app("int") do 2 | stop_on_delete true # !!! 3 | 4 | working_dir File.join(File.dirname(__FILE__), %w{.. .. example}) 5 | stdall "shlak.log" 6 | 7 | group "samples" do 8 | process("sample1") do 9 | pid_file "<%= C.p1_pid %>" 10 | start_command "ruby sample.rb" 11 | daemonize true 12 | end 13 | 14 | process("sample2") do 15 | pid_file "<%= C.p2_pid %>" 16 | start_command "ruby sample.rb -d --pid <%= C.p2_pid %> --log shlak.log" 17 | checks :memory, :below => 300.megabytes 18 | end 19 | end 20 | 21 | process("forking") do 22 | env "PID_NAME" => "<%= C.p3_pid %>" 23 | pid_file "<%= C.p3_pid %>" 24 | 25 | start_command "ruby forking.rb start" 26 | stop_command "ruby forking.rb stop" 27 | 28 | monitor_children do 29 | children_update_period 5.seconds 30 | restart_command "kill -2 {PID}" 31 | end 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/integration_sor2.erb: -------------------------------------------------------------------------------- 1 | Eye.app("int") do 2 | stop_on_delete true # !!! 3 | 4 | working_dir File.join(File.dirname(__FILE__), %w{.. .. example}) 5 | stdall "shlak.log" 6 | 7 | group "samples" do 8 | process("sample1") do 9 | pid_file "<%= C.p1_pid %>" 10 | start_command "ruby sample.rb" 11 | daemonize true 12 | end 13 | 14 | process("sample2") do 15 | pid_file "<%= C.p2_pid %>" 16 | start_command "ruby sample.rb -d --pid <%= C.p2_pid %> --log shlak.log" 17 | checks :memory, :below => 300.megabytes 18 | end 19 | 20 | process("sample3") do 21 | pid_file "<%= C.p3_pid %>" 22 | start_command "ruby sample.rb" 23 | daemonize true 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /spec/fixtures/dsl/integration_sor3.erb: -------------------------------------------------------------------------------- 1 | Eye.app("int") do 2 | stop_on_delete true # !!! 3 | 4 | working_dir File.join(File.dirname(__FILE__), %w{.. .. example}) 5 | stdall "shlak.log" 6 | 7 | group "samples" do 8 | process("sample1_") do 9 | pid_file "<%= C.p1_pid %>" 10 | start_command "ruby sample.rb" 11 | daemonize true 12 | end 13 | 14 | process("sample2_") do 15 | pid_file "<%= C.p2_pid %>" 16 | start_command "ruby sample.rb -d --pid <%= C.p2_pid %> --log shlak.log" 17 | checks :memory, :below => 300.megabytes 18 | end 19 | end 20 | 21 | process("forking") do 22 | env "PID_NAME" => "<%= C.p3_pid %>" 23 | pid_file "<%= C.p3_pid %>" 24 | 25 | start_command "ruby forking.rb start" 26 | stop_command "ruby forking.rb stop" 27 | 28 | monitor_children do 29 | children_update_period 5.seconds 30 | restart_command "kill -2 {PID}" 31 | end 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/just_sleep.eye: -------------------------------------------------------------------------------- 1 | Thread.current[:celluloid_actor].sleep 0.5 2 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | working_dir '/tmp' 3 | 4 | group 'gr1' do 5 | process('p1') { pid_file 'app1-gr1-p1.pid' } 6 | process('p2') { pid_file 'app1-gr1-p2.pid' } 7 | end 8 | 9 | group 'gr2' do 10 | chain grace: 0.5.seconds 11 | process('q3') { pid_file 'app1-gr2-q3.pid' } 12 | end 13 | 14 | process('g4') { pid_file 'app1-g4.pid' } 15 | process('g5') { pid_file 'app1-g5.pid' } 16 | end 17 | 18 | Eye.application 'app2' do 19 | working_dir '/tmp' 20 | 21 | process 'z1' do 22 | pid_file 'app2-z1.pid' 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load2.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app3' do 2 | working_dir '/tmp' 3 | 4 | process 'e1' do 5 | pid_file 'app3-e1.pid' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load2_dup2.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | process 'server_1' do 3 | working_dir '/tmp' 4 | pid_file 'server.pid' 5 | end 6 | 7 | process 'server_2' do 8 | working_dir '/' 9 | pid_file 'server_2.pid' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load2_dup_pid.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app4' do 2 | working_dir '/tmp' 3 | 4 | process 'e1' do 5 | pid_file 'app3-e1.pid' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load3.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app3' do 2 | working_dir '/tmp' 3 | 4 | group('wow') do 5 | process 'e1' do 6 | daemonize true 7 | pid_file 'app3-e1.pid' 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load4.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app4' do 2 | working_dir '/tmp' 3 | 4 | process 'e1' do 5 | pid_file 'app3-e1.pid' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load5.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | working_dir '/tmp' 3 | 4 | group 'gr1' do 5 | process('p1') { pid_file 'app1-gr1-p1.pid' } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load6.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | working_dir '/tmp' 3 | 4 | group 'gr2' do 5 | chain grace: 1.0.seconds 6 | process('p1') { pid_file 'app1-gr1-p1.pid' } 7 | process('p2') { pid_file 'app1-gr1-p2.pid' } 8 | end 9 | 10 | group 'gr1' do 11 | process('q3') { pid_file 'app1-gr2-q3.pid' } 12 | end 13 | 14 | process('g4') { pid_file 'app1-g4.pid' } 15 | process('g5') { pid_file 'app1-g5.pid' } 16 | end 17 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_dup_ex_names.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | group 'p1' do 3 | process 'server' do 4 | pid_file 'server1.pid' 5 | end 6 | end 7 | 8 | group 'p2' do 9 | process 'server' do 10 | pid_file 'server2.pid' 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_dup_ex_names2.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | group 'p2' do 3 | process 'server' do 4 | pid_file 'server2.pid' 5 | end 6 | end 7 | 8 | group 'p1' do 9 | process 'server' do 10 | pid_file 'server1.pid' 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_dup_ex_names3.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | group 'gr' do 3 | process 'server' do 4 | pid_file 'server2.pid' 5 | end 6 | end 7 | end 8 | 9 | Eye.application 'app2' do 10 | group 'gr' do 11 | process 'server' do 12 | pid_file 'server2.pid' 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_dup_ex_names4.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | group 'gr' do 3 | process 'server' do 4 | pid_file 'server1.pid' 5 | end 6 | end 7 | end 8 | 9 | Eye.application 'app2' do 10 | group 'gr' do 11 | process 'server' do 12 | pid_file 'server2.pid' 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_dupls.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | working_dir '/tmp' 3 | 4 | group 'gr1' do 5 | process('p1') { pid_file 'app1-gr1-p1.pid' } 6 | process('p2') { pid_file 'app1-gr1-p2.pid' } 7 | end 8 | 9 | group 'gr2' do 10 | chain grace: 5.seconds 11 | process('q3') { pid_file 'app1-gr2-q3.pid' } 12 | end 13 | 14 | process('g4') { pid_file 'app1-g4.pid' } 15 | process('g5') { pid_file 'app1-g5.pid' } 16 | process('gr2and') { pid_file 'app1-gr2and.pid' } 17 | end 18 | 19 | Eye.application 'app2' do 20 | working_dir '/tmp' 21 | 22 | process 'z1' do 23 | pid_file 'app2-z1.pid' 24 | end 25 | 26 | group 'gr1' do 27 | process 'z2' do 28 | pid_file 'app2-gr1-z2.pid' 29 | end 30 | 31 | process 'gr1and' do 32 | pid_file 'app2-gr1-gr1and.pid' 33 | end 34 | end 35 | end 36 | 37 | Eye.application 'app5' do 38 | process('some') { pid_file 'some.pid' } 39 | process('some2') { pid_file 'some2.pid' } 40 | process('some_name') { pid_file 'some_name.pid' } 41 | process('am') { pid_file 'am.pid' } 42 | process('am2') { pid_file 'am2.pid' } 43 | process('one') { pid_file 'one.pid' } 44 | group :gr7 do 45 | process('mu') { pid_file 'mu.pid' } 46 | process('mu2') { pid_file 'mu2.pid' } 47 | end 48 | group :serv do 49 | process('serv') { pid_file 'serv.pid' } 50 | end 51 | end 52 | 53 | Eye.application 'app6' do 54 | process('one') { pid_file 'app6-one.pid' } 55 | end 56 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_dupls2.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | working_dir '/tmp' 3 | 4 | process('a1') { pid_file 'app1-a1.pid' } 5 | 6 | group 'admin' do 7 | process('e1') { pid_file 'app1-p1.pid' } 8 | process('e3') { pid_file 'app1-p3.pid' } 9 | end 10 | 11 | group(:zoo) {} 12 | end 13 | 14 | Eye.application 'app2' do 15 | working_dir '/tmp' 16 | 17 | process('a2') { pid_file 'app2-a2.pid' } 18 | 19 | group 'admin' do 20 | process('e1') { pid_file 'app2-p1.pid' } 21 | process('e2') { pid_file 'app2-p2.pid' } 22 | end 23 | 24 | process('zoo') { pid_file 'app2-zoo.pid' } 25 | 26 | group(:koo) do 27 | process('koo') { pid_file 'app2-koo.pid' } 28 | end 29 | 30 | process('p1') { pid_file 'app2-pp1.pid' } 31 | end 32 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_dupls3.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'someapp' do 2 | end 3 | 4 | Eye.application 'app' do 5 | process('someprocess') { pid_file 'someprocess.pid' } 6 | end 7 | 8 | Eye.application 'app2' do 9 | process('app') { pid_file 'app.pid' } 10 | end 11 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_dupls5.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | working_dir '/tmp' 3 | 4 | process('p1') { pid_file 'app1-p1.pid' } 5 | process('p2') { pid_file 'app1-p2.pid' } 6 | process('p3') { pid_file 'app1-p3.pid' } 7 | 8 | group :a do 9 | process('p1') { pid_file 'app1-a-p1.pid' } 10 | process('p2') { pid_file 'app1-a-p2.pid' } 11 | end 12 | 13 | group :b do 14 | process('p1') { pid_file 'app1-b-p1.pid' } 15 | process('p2') { pid_file 'app1-b-p2.pid' } 16 | end 17 | end 18 | 19 | Eye.application 'app2' do 20 | process('p1') { pid_file 'app2-p1.pid' } 21 | process('p2') { pid_file 'app2-p2.pid' } 22 | process('p4') { pid_file 'app2-p4.pid' } 23 | end 24 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_error.eye: -------------------------------------------------------------------------------- 1 | # bad logger 2 | Eye.config do 3 | logger '/asd/fasd/fas/df/asd/fas/df/d' 4 | end 5 | 6 | Eye.application 'app1' do 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_error_folder/load3.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app3' do 2 | working_diasdf asdf asdf asd fasd fr '/tmp' 3 | 4 | group('wow') do 5 | process 'e1' do 6 | daemonize true 7 | pid_file 'app3-e1.pid' 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_error_folder/load4.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app4' do 2 | working_dir '/tmp' 3 | 4 | process 'e1' do 5 | pid_file 'app3-e1.pid' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_folder/load3.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app3' do 2 | working_dir '/tmp' 3 | 4 | group('wow') do 5 | process 'e1' do 6 | daemonize true 7 | pid_file 'app3-e1.pid' 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_folder/load4.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app4' do 2 | working_dir '/tmp' 3 | 4 | process 'e2' do 5 | pid_file 'app4-e2.pid' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_int.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | group 'gr1' do 3 | process('p1') { pid_file 'app1-gr1-p1.pid' } 4 | process('p2') { pid_file 'app1-gr1-p2.pid' } 5 | end 6 | 7 | process('p0') { pid_file 'app1-p0.pid' } 8 | end 9 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_int2.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app1' do 2 | group 'gr1' do 3 | process('p2') { pid_file 'app1-gr1-p2.pid' } 4 | process('p3') { pid_file 'app1-gr1-p3.pid' } 5 | end 6 | 7 | group 'gr2' do 8 | process('p4') { pid_file 'app1-gr2-p4.pid' } 9 | process('p5') { pid_file 'app1-gr2-p5.pid' } 10 | end 11 | 12 | process('p0-1') { pid_file 'app1-p0-1.pid' } 13 | end 14 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_logger.eye: -------------------------------------------------------------------------------- 1 | Eye.config do 2 | logger '/tmp/1.loG' 3 | logger_level Logger::DEBUG 4 | end 5 | 6 | Eye.application 'app1' do 7 | working_dir '/tmp' 8 | 9 | group 'gr1' do 10 | process('p1') { pid_file 'app1-gr1-p1.pid' } 11 | process('p2') { pid_file 'app1-gr1-p2.pid' } 12 | end 13 | 14 | group 'gr2' do 15 | process('q3') { pid_file 'app1-gr2-q3.pid' } 16 | end 17 | 18 | process('g4') { pid_file 'app1-g4.pid' } 19 | process('g5') { pid_file 'app1-g5.pid' } 20 | end 21 | 22 | Eye.application 'app2' do 23 | working_dir '/tmp' 24 | 25 | process 'z1' do 26 | pid_file 'app2-z1.pid' 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/load_logger2.eye: -------------------------------------------------------------------------------- 1 | Eye.application 'app4' do 2 | working_dir '/tmp' 3 | end 4 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/long_load.eye: -------------------------------------------------------------------------------- 1 | Eye.info 'haha' 2 | 3 | Eye.application 'long_load' do 4 | sleep 1 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/multiple_checks.eye: -------------------------------------------------------------------------------- 1 | class Cpu9 < Eye::Checker::Custom 2 | 3 | param :below, [Integer, Float], true 4 | 5 | def initialize(*args) 6 | super 7 | @a = [true, true, false, false, false] 8 | end 9 | 10 | def get_value 11 | @a.shift 12 | end 13 | 14 | def good?(value) 15 | value 16 | end 17 | 18 | end 19 | 20 | Eye.application('bla') do 21 | working_dir File.expand_path(File.join(File.dirname(__FILE__), %w[.])) 22 | process('1') do 23 | pid_file '1.pid' 24 | start_command 'sleep 30' 25 | daemonize true 26 | checks :cpu, below: 100, times: 3, every: 10 27 | checks :cpu9, below: 100, times: 3, every: 10 28 | checks :cpu3, below: 100, times: 3, every: 10 29 | checks :cpu_4, below: 100, times: 3, every: 10 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder1/proc1.rb: -------------------------------------------------------------------------------- 1 | def proc1(proxy, name) 2 | proxy.process(name){ pid_file "#{name}.pid" } 3 | end -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder2.eye: -------------------------------------------------------------------------------- 1 | Eye.load('./subfolder2/sub/*.rb') 2 | Eye.load('subfolder2/*.rb') 3 | 4 | Eye.application 'subfolder2' do 5 | working_dir '/tmp' 6 | 7 | proc2 self, 'e3' 8 | proc3 self, 'e4' 9 | end 10 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder2/common.rb: -------------------------------------------------------------------------------- 1 | ROOT = '/' -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder2/proc2.rb: -------------------------------------------------------------------------------- 1 | def proc2(proxy, name) 2 | proxy.process(name){ pid_file "#{name}.pid2" } 3 | end -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder2/sub/proc3.rb: -------------------------------------------------------------------------------- 1 | def proc3(proxy, name) 2 | proxy.process(name){ 3 | working_dir ROOT 4 | pid_file "#{name}.pid3" 5 | } 6 | end -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder3.eye: -------------------------------------------------------------------------------- 1 | Eye.load('./subfolder3/**/*.rb') 2 | 3 | Eye.application 'subfolder3' do 4 | working_dir '/tmp' 5 | 6 | proc4 self, 'e1' 7 | proc5 self, 'e2' 8 | end 9 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder3/common.rb: -------------------------------------------------------------------------------- 1 | ROOT = '/var' -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder3/proc4.rb: -------------------------------------------------------------------------------- 1 | def proc4(proxy, name) 2 | proxy.process(name){ pid_file "#{name}.pid4" } 3 | end -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder3/sub/proc5.rb: -------------------------------------------------------------------------------- 1 | def proc5(proxy, name) 2 | proxy.process(name){ 3 | working_dir ROOT 4 | pid_file "#{name}.pid5" 5 | } 6 | end -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder4.eye: -------------------------------------------------------------------------------- 1 | Eye.load('./subfolder4/a.rb') 2 | Eye.load('./subfolder4/c.rb') 3 | 4 | Eye.application 'subfolder4' do 5 | env 'a' => A, 'b' => B, 'c' => D 6 | end 7 | -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder4/a.rb: -------------------------------------------------------------------------------- 1 | A = 1 2 | Eye.load("b.rb") -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder4/b.rb: -------------------------------------------------------------------------------- 1 | B=2 -------------------------------------------------------------------------------- /spec/fixtures/dsl/subfolder4/c.rb: -------------------------------------------------------------------------------- 1 | D=3 -------------------------------------------------------------------------------- /spec/local_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | def join_path(arr) 4 | File.join(File.dirname(__FILE__), arr) 5 | end 6 | 7 | describe "Eye::Local" do 8 | it "should find_eyefile" do 9 | Eye::Local.find_eyefile(join_path %w[ fixtures ]).should == nil 10 | Eye::Local.find_eyefile(join_path %w[]).should == nil 11 | 12 | result = join_path %w[ fixtures dsl Eyefile ] 13 | Eye::Local.find_eyefile(join_path %w[ fixtures dsl ]).should == result 14 | Eye::Local.find_eyefile(join_path %w[ fixtures dsl configs ]).should == result 15 | Eye::Local.find_eyefile(join_path %w[ fixtures dsl subfolder3 sub ]).should == result 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/logger_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | class A22 # duck 4 | def logger_tag 5 | "some" 6 | end 7 | def full_name 8 | end 9 | end 10 | 11 | describe "Eye::Logger" do 12 | it "should use smart logger with auto prefix" do 13 | Eye::Process.logger.prefix.should == "Eye::Process" 14 | Eye.logger.prefix.should == "Eye" 15 | Eye::Checker.logger.prefix.should == "Eye::Checker" 16 | Eye::Checker.create(123, {:type => :cpu, :every => 5.seconds, :times => 1}, A22.new).logger.prefix.should == "some" 17 | Eye::Server.new(C.socket_path).logger.prefix.should == "" 18 | Eye::Controller.new.logger.prefix.should == "Eye" 19 | Eye::Process.new(C.p1).logger.prefix.should == "main:default:blocking process" 20 | end 21 | end -------------------------------------------------------------------------------- /spec/mock_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | class Aaa 4 | include Celluloid 5 | 6 | def int 7 | 1 8 | end 9 | 10 | def ext 11 | int 12 | end 13 | 14 | end 15 | 16 | describe "Actor mocking" do 17 | before :each do 18 | @a = Aaa.new 19 | end 20 | 21 | it "int" do 22 | mock(@a).int{2} 23 | @a.int.should == 2 24 | end 25 | 26 | it "ext" do 27 | mock(@a).int{2} 28 | @a.ext.should == 2 29 | end 30 | 31 | end 32 | -------------------------------------------------------------------------------- /spec/notify/jabber_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::Notify::Jabber" do 4 | before :each do 5 | @message = {:message=>"something", :name=>"blocking process", 6 | :full_name=>"main:default:blocking process", :pid=>123, 7 | :host=>'host1', :level=>:crit, :at => Time.now} 8 | @h = {:host=>"mx.some.host.ru", :type=>:mail, :port=>25, :contact=>"vasya@mail.ru", :password => "123"} 9 | end 10 | 11 | it "should send jabber" do 12 | require 'xmpp4r' 13 | 14 | @m = Eye::Notify::Jabber.new(@h, @message) 15 | 16 | ob = "" 17 | mock(Jabber::Client).new(anything){ ob } 18 | mock(ob).connect('mx.some.host.ru', 25) 19 | mock(ob).auth('123') 20 | mock(ob).send(is_a(Jabber::Message)) 21 | mock(ob).close 22 | 23 | @m.execute 24 | end 25 | end -------------------------------------------------------------------------------- /spec/notify/mail_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::Notify::Mail" do 4 | before :each do 5 | @message = {:message=>"something", :name=>"blocking process", 6 | :full_name=>"main:default:blocking process", :pid=>123, 7 | :host=>'host1', :level=>:crit, :at => Time.now} 8 | @h = {:host=>"mx.some.host.ru", :type=>:mail, :port=>25, :domain=>"some.host", :contact=>"vasya@mail.ru"} 9 | end 10 | 11 | it "should send mail" do 12 | @m = Eye::Notify::Mail.new(@h, @message) 13 | 14 | smtp = Net::SMTP.new 'mx.some.host.ru', 25 15 | mock(Net::SMTP).new('mx.some.host.ru', 25){ smtp } 16 | 17 | ob = "" 18 | mock(smtp).start('some.host', nil, nil, nil){ ob } 19 | 20 | @m.execute 21 | 22 | @m.message_subject.should == "[host1] [main:default:blocking process] something" 23 | @m.contact.should == "vasya@mail.ru" 24 | 25 | m = @m.message.split("\n") 26 | m.should include("To: ") 27 | m.should include("Subject: [host1] [main:default:blocking process] something") 28 | end 29 | end -------------------------------------------------------------------------------- /spec/notify/slack_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::Notify::Slack" do 4 | before :each do 5 | @time = Time.new 2015, 2, 25, 12 6 | @message = {:message=>"something", :name=>"blocking process", 7 | :full_name=>"main:default:blocking process", :pid=>123, 8 | :host=>'host1', :level=>:crit, :at => @time} 9 | @h = {:webhook_url=>"https://hooks.slack.com/services/some_token", 10 | :type=>:slack, :username=>"eye", :contact=>"@channel", :channel => "#default"} 11 | end 12 | 13 | it "should send slack" do 14 | require 'slack-notifier' 15 | 16 | @m = Eye::Notify::Slack.new(@h, @message) 17 | 18 | slack = ::Slack::Notifier.new @h[:webhook_url], channel: "#default", username: "eye" 19 | mock(::Slack::Notifier).new(@h[:webhook_url], channel: "#default", username: "eye"){ slack } 20 | 21 | mock(slack).ping("@channel: *host1* _main:default:blocking process_ at 25 Feb 12:00\n> something") { nil } 22 | 23 | @m.message_body.should == "@channel: *host1* _main:default:blocking process_ at 25 Feb 12:00\n> something" 24 | 25 | @m.execute 26 | end 27 | end -------------------------------------------------------------------------------- /spec/process/behaviour_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Behaviour" do 4 | before :each do 5 | @process = process C.p1 6 | end 7 | 8 | describe "sync with signals" do 9 | it "restart process with signal" do 10 | should_spend(2.5, 0.3) do 11 | c = Celluloid::Condition.new 12 | @process.send_call(:command => :start, :signal => c) 13 | c.wait 14 | end 15 | 16 | should_spend(3.0, 0.3) do 17 | c = Celluloid::Condition.new 18 | @process.send_call(:command => :restart, :signal => c) 19 | c.wait 20 | end 21 | 22 | @process.state_name.should == :up 23 | @process.states_history.states.should == [:unmonitored, :starting, :up, :restarting, :stopping, :down, :starting, :up] 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/process/checks/child_checks_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../spec_helper' 2 | 3 | describe "ChildProcess" do 4 | 5 | describe "starting, monitoring" do 6 | after :each do 7 | @process.stop if @process 8 | end 9 | 10 | it "should just monitoring, and do nothin" do 11 | start_ok_process(C.p3.merge(:monitor_children => {:checks => join(C.check_mem, C.check_cpu)})) 12 | sleep 6 13 | 14 | @process.state_name.should == :up 15 | @process.children.keys.should_not == [] 16 | @process.children.keys.size.should == 3 17 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_children] 18 | 19 | @children = @process.children.values 20 | @children.each do |child| 21 | child.watchers.keys.should == [:check_memory, :check_cpu] 22 | dont_allow(child).schedule :restart 23 | end 24 | 25 | sleep 7 26 | end 27 | 28 | it "should check children even when one of them respawned" do 29 | start_ok_process(C.p3.merge(:monitor_children => {:checks => join(C.check_mem, C.check_cpu)}, :children_update_period => Eye::SystemResources::cache.expire + 1)) 30 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_children] 31 | 32 | sleep 6 # ensure that children are found 33 | 34 | @process.children.size.should == 3 35 | 36 | # now restarting 37 | died = @process.children.keys.sample 38 | die_process!(died, 9) 39 | 40 | # sleep enought for update list 41 | sleep (Eye::SystemResources::cache.expire * 2 + 3).seconds 42 | 43 | @process.children.size.should == 3 44 | @process.children.keys.should_not include(died) 45 | 46 | @children = @process.children.values 47 | @children.each do |child| 48 | child.watchers.keys.should == [:check_memory, :check_cpu] 49 | dont_allow(child).schedule :restart 50 | end 51 | end 52 | 53 | it "some child get condition" do 54 | start_ok_process(C.p3.merge(:monitor_children => {:checks => 55 | join(C.check_mem, C.check_cpu(:below => 50, :times => 2))})) 56 | sleep 6 57 | 58 | @process.children.size.should == 3 59 | 60 | @children = @process.children.values 61 | crazy = @children.shift 62 | 63 | @children.each do |child| 64 | child.watchers.keys.should == [:check_memory, :check_cpu] 65 | dont_allow(child).schedule :command => :restart 66 | end 67 | 68 | stub(Eye::SystemResources).cpu(crazy.pid){ 55 } 69 | stub(Eye::SystemResources).cpu(anything){ 5 } 70 | 71 | crazy.watchers.keys.should == [:check_memory, :check_cpu] 72 | mock(crazy).notify(:warn, "Bounded cpu(<50%): [*55%, *55%] send to [:restart]") 73 | mock(crazy).schedule :command => :restart 74 | 75 | sleep 4 76 | crazy.remove_watchers # for safe end spec 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/process/checks/children_count_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../spec_helper' 2 | 3 | describe "Eye::Checker::ChildrenCount" do 4 | it "should not restart because all is ok" do 5 | @process = start_ok_process(C.p1.merge(:checks => C.check_children_count(:below => 5), :monitor_children => {})) 6 | 7 | 3.times { @pids << Eye::System.daemonize("sleep 10")[:pid] } 8 | stub(Eye::SystemResources).children(@process.pid){ @pids } 9 | @process.add_children 10 | 11 | dont_allow(@process).schedule(:command => :restart) 12 | 13 | sleep 5 14 | end 15 | 16 | it "should restart with strategy :restart" do 17 | @process = start_ok_process(C.p1.merge(:checks => C.check_children_count(:below => 5), :monitor_children => {})) 18 | 19 | 10.times { @pids << Eye::System.daemonize("sleep 10")[:pid] } 20 | stub(Eye::SystemResources).children(@process.pid){ @pids } 21 | @process.add_children 22 | 23 | mock(@process).schedule(:command => :restart) 24 | 25 | sleep 5 26 | end 27 | 28 | it "should kill 5 older childs" do 29 | @process = start_ok_process(C.p1.merge(:checks => C.check_children_count(:below => 3, :strategy => :kill_old), 30 | :monitor_children => {})) 31 | 32 | 10.times { @pids << Eye::System.daemonize("sleep 10")[:pid] } 33 | stub(Eye::SystemResources).children(@process.pid){ @pids } 34 | @process.add_children 35 | 36 | sleep 5 37 | 38 | @pids[0...7].each { |p| Eye::System.pid_alive?(p).should == false } 39 | @pids[7..-1].each { |p| Eye::System.pid_alive?(p).should == true } 40 | end 41 | 42 | it "should kill 5 newer childs" do 43 | @process = start_ok_process(C.p1.merge(:checks => C.check_children_count(:below => 3, :strategy => :kill_new), 44 | :monitor_children => {})) 45 | 46 | 10.times { @pids << Eye::System.daemonize("sleep 10")[:pid] } 47 | stub(Eye::SystemResources).children(@process.pid){ @pids } 48 | @process.add_children 49 | 50 | sleep 5 51 | 52 | @pids[0...3].each { |p| Eye::System.pid_alive?(p).should == true } 53 | @pids[3..-1].each { |p| Eye::System.pid_alive?(p).should == false } 54 | end 55 | 56 | end 57 | -------------------------------------------------------------------------------- /spec/process/checks/children_memory_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../spec_helper' 2 | 3 | describe "Eye::Checker::ChildrenMemory" do 4 | it "should not restart because all is ok" do 5 | @process = start_ok_process(C.p1.merge(:checks => C.check_children_memory(:below => 50), :monitor_children => {})) 6 | 7 | 3.times { @pids << Eye::System.daemonize("sleep 10")[:pid] } 8 | stub(Eye::SystemResources).children(@process.pid){ @pids } 9 | @process.add_children 10 | 11 | stub(Eye::SystemResources).memory(anything) { 1 } 12 | 13 | dont_allow(@process).schedule(:command => :restart) 14 | 15 | sleep 5 16 | end 17 | 18 | it "should restart with strategy :restart" do 19 | @process = start_ok_process(C.p1.merge(:checks => C.check_children_memory(:below => 50), :monitor_children => {})) 20 | 21 | 10.times { @pids << Eye::System.daemonize("sleep 10")[:pid] } 22 | stub(Eye::SystemResources).children(@process.pid){ @pids } 23 | @process.add_children 24 | 25 | stub(Eye::SystemResources).memory(anything) { 11 } 26 | mock(@process).schedule(:command => :restart) 27 | 28 | sleep 5 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/process/checks/ctime_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../spec_helper' 2 | 3 | describe "Check CTime" do 4 | before :each do 5 | @c = C.p1.merge( 6 | :checks => C.check_ctime(:times => 3) 7 | ) 8 | end 9 | 10 | it "should start periodical watcher" do 11 | start_ok_process(@c) 12 | 13 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_ctime] 14 | sbj = @process.watchers[:check_ctime][:subject] 15 | sbj.file.should == "#{C.sample_dir}/#{C.log_name}" 16 | 17 | @process.stop 18 | 19 | # after process stop should remove watcher 20 | @process.watchers.keys.should == [] 21 | end 22 | 23 | it "if ctime changes should_not restart" do 24 | start_ok_process(@c) 25 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_ctime] 26 | 27 | dont_allow(@process).schedule(:restart) 28 | 29 | sleep 6 30 | end 31 | 32 | it "if ctime not changed should restart" do 33 | start_ok_process(@c) 34 | 35 | mock(@process).schedule(:command => :restart) 36 | 37 | sleep 3 38 | 39 | FileUtils.rm(C.p1[:stdout]) 40 | 41 | sleep 5 42 | end 43 | 44 | end 45 | 46 | -------------------------------------------------------------------------------- /spec/process/checks/fsize_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../spec_helper' 2 | 3 | describe "Check FSize" do 4 | before :each do 5 | @c = C.p1.merge( 6 | :checks => C.check_fsize(:times => 3) 7 | ) 8 | end 9 | 10 | it "should start periodical watcher" do 11 | start_ok_process(@c) 12 | 13 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_fsize] 14 | 15 | @process.stop 16 | 17 | # after process stop should remove watcher 18 | @process.watchers.keys.should == [] 19 | end 20 | 21 | end 22 | 23 | -------------------------------------------------------------------------------- /spec/process/checks/http_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../spec_helper' 2 | 3 | describe "Process Http check" do 4 | before :each do 5 | @c = C.p1.merge( 6 | :checks => C.check_http 7 | ) 8 | FakeWeb.register_uri(:get, "http://localhost:3000/bla", :body => "Somebody OK") 9 | end 10 | 11 | after :each do 12 | FakeWeb.clean_registry 13 | end 14 | 15 | it "all ok" do 16 | start_ok_process(@c) 17 | 18 | dont_allow(@process).schedule(:command => :restart) 19 | 20 | # should not happens anything 21 | sleep 6 22 | end 23 | 24 | it "bad body" do 25 | start_ok_process(@c) 26 | sleep 2 27 | 28 | mock(@process).schedule(:command => :restart) 29 | FakeWeb.register_uri(:get, "http://localhost:3000/bla", :body => "Somebody BAD") 30 | sleep 2 31 | end 32 | 33 | it "bad status" do 34 | start_ok_process(@c) 35 | sleep 2 36 | 37 | mock(@process).schedule(:command => :restart) 38 | FakeWeb.register_uri(:get, "http://localhost:3000/bla", :body => "Somebody OK", :status => [500, 'err']) 39 | sleep 2 40 | end 41 | 42 | it "not responded url" do 43 | start_ok_process(@c) 44 | sleep 2 45 | 46 | mock(@process).schedule(:command => :restart) 47 | FakeWeb.clean_registry 48 | FakeWeb.allow_net_connect = false 49 | sleep 2 50 | end 51 | 52 | end -------------------------------------------------------------------------------- /spec/process/checks/intergration_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../spec_helper' 2 | 3 | describe "Process Integration checks" do 4 | before :each do 5 | @c = C.p1.merge( 6 | :checks => join(C.check_cpu, C.check_mem, C.check_ctime, C.check_http) 7 | ) 8 | 9 | FakeWeb.register_uri(:get, "http://localhost:3000/bla", :body => "Somebody OK") 10 | end 11 | 12 | it "should start periodical watcher" do 13 | start_ok_process(@c) 14 | 15 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_cpu, :check_memory, :check_ctime, :check_http] 16 | 17 | @process.stop 18 | 19 | # after process stop should remove watcher 20 | @process.watchers.keys.should == [] 21 | end 22 | 23 | it "intergration" do 24 | start_ok_process(@c) 25 | 26 | dont_allow(@process).schedule(:restart) 27 | 28 | # should not happens anything 29 | sleep 10 30 | end 31 | 32 | it "timeouted http, should not lock actor-mailbox" do 33 | cfg = C.p5.merge( 34 | :checks => join(C.check_http( 35 | :url => "http://127.0.0.1:#{C.p5_port}/timeout", :timeout => 6.seconds, :every => 5.seconds, 36 | :times => 3), 37 | C.check_cpu(:every => 1.second) 38 | ) 39 | ) 40 | 41 | start_ok_process(cfg) 42 | 43 | should_spend(10, 0.5) do 44 | 10.times do 45 | @process.name.should be_a(String) # actor should be free here 46 | sleep 1 47 | end 48 | end 49 | 50 | w_http = @process.watchers[:check_http][:subject] 51 | w_cpu = @process.watchers[:check_cpu][:subject] 52 | w_http.check_count.should <= 2 53 | w_cpu.check_count.should >= 9 54 | 55 | w_http.inspect.size.should > 100 56 | end 57 | 58 | it "timeouted socket, should not lock actor-mailbox" do 59 | cfg = C.p4.merge( 60 | :checks => join(C.check_sock(:timeout => 6.seconds, :every => 5.seconds, 61 | :times => 3, :send_data => "timeout"), 62 | C.check_cpu(:every => 1.second) 63 | ) 64 | ) 65 | 66 | start_ok_process(cfg) 67 | 68 | should_spend(10, 1) do 69 | 10.times do 70 | @process.name.should be_a(String) # actor should be free here 71 | sleep 1 72 | end 73 | end 74 | 75 | w_socket = @process.watchers[:check_socket][:subject] 76 | w_cpu = @process.watchers[:check_cpu][:subject] 77 | w_socket.check_count.should <= 2 78 | w_cpu.check_count.should >= 9 79 | end 80 | 81 | end 82 | -------------------------------------------------------------------------------- /spec/process/checks/multiple_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../spec_helper' 2 | 3 | describe "Multiple checks" do 4 | it "should create many checks with the same type" do 5 | @c = Eye::Controller.new 6 | r = @c.load(fixture("dsl/multiple_checks.eye")) 7 | sleep 3 8 | @process = @c.process_by_name("1") 9 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_cpu, :check_cpu9, :check_cpu3, :check_cpu_4] 10 | @process.watchers[:check_cpu][:subject].class.should == Eye::Checker::Cpu 11 | @process.watchers[:check_cpu9][:subject].class.should == Cpu9 12 | @process.watchers[:check_cpu3][:subject].class.should == Eye::Checker::Cpu 13 | @process.watchers[:check_cpu_4][:subject].class.should == Eye::Checker::Cpu 14 | end 15 | end -------------------------------------------------------------------------------- /spec/process/data_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::Process::Data" do 4 | subject { process(C.p1) } 5 | 6 | it "shell_string" do 7 | subject.shell_string(false).should == 'ENV1=SUPER ruby sample.rb &' 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/process/double_restart_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe Eye::Process do 4 | before :each do 5 | @process = process(C.p1) 6 | end 7 | 8 | it "should not double restarts if auto restart" do 9 | @process.schedule :command => :restart, :reason => "bounded memory" 10 | @process.schedule :command => :restart, :reason => "bounded cpu" 11 | 12 | sleep 3 13 | 14 | @process.states_history.count { |c| c[:state] == :restarting }.should == 1 15 | end 16 | 17 | it "should double restart if second is by hands" do 18 | @process.schedule :command => :restart, :reason => "bounded memory" 19 | @process.send_call(:command => :restart) 20 | 21 | sleep 3 22 | 23 | @process.states_history.count { |c| c[:state] == :restarting }.should == 2 24 | end 25 | 26 | it "double restart by hands should pass both" do 27 | @process.send_call(:command => :restart) 28 | @process.send_call(:command => :restart) 29 | 30 | sleep 3 31 | 32 | @process.states_history.count { |c| c[:state] == :restarting }.should == 2 33 | end 34 | 35 | it "triple restart by hands should pass only 2" do 36 | @process.send_call(:command => :restart) 37 | @process.send_call(:command => :restart) 38 | @process.send_call(:command => :restart) 39 | 40 | sleep 6 41 | 42 | @process.states_history.count { |c| c[:state] == :restarting }.should == 2 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /spec/process/notify_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::Process::Notify" do 4 | before :each do 5 | stub(Eye::Local).host{ 'host1' } 6 | @process = process(C.p1.merge(:notify => {'vasya' => :info, 7 | 'petya' => :warn, 'somebody' => :warn})) 8 | end 9 | 10 | it "should send to notifies warn message" do 11 | m = {:message=>"something", :name=>"blocking process", :full_name=>"main:default:blocking process", :pid=>nil, :host=>"host1", :level=>:info} 12 | mock(Eye::Notify).notify('vasya', hash_including(m)) 13 | @process.notify(:info, 'something') 14 | end 15 | 16 | it "should send to notifies crit message" do 17 | m = {:message=>"something", :name=>"blocking process", 18 | :full_name=>"main:default:blocking process", :pid=>nil, 19 | :host=>'host1', :level=>:warn} 20 | 21 | mock(Eye::Notify).notify('vasya', hash_including(m)) 22 | mock(Eye::Notify).notify('petya', hash_including(m)) 23 | mock(Eye::Notify).notify('somebody', hash_including(m)) 24 | @process.notify(:warn, 'something') 25 | end 26 | 27 | end -------------------------------------------------------------------------------- /spec/process/pid_managment_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | [C.p1, C.p2].each do |cfg| 4 | describe "Process Pid Managment '#{cfg[:name]}'" do 5 | 6 | it "crashed of process should remove pid_file for daemonize only" do 7 | start_ok_process(cfg) 8 | die_process!(@pid) 9 | 10 | mock(@process).start # stub start for clean test 11 | 12 | sleep 4 13 | 14 | if cfg[:daemonize] 15 | @process.load_pid_from_file.should == nil 16 | else 17 | @process.load_pid_from_file.should == @pid 18 | end 19 | end 20 | 21 | it "someone remove pid_file. should rewrite" do 22 | start_ok_process(cfg) 23 | old_pid = @pid 24 | File.exist?(cfg[:pid_file]).should == true 25 | 26 | FileUtils.rm(cfg[:pid_file]) # someone removes it (bad man) 27 | File.exist?(cfg[:pid_file]).should == false 28 | 29 | sleep 5 # wait until monitor understand it 30 | 31 | File.exist?(cfg[:pid_file]).should == true 32 | @process.pid.should == old_pid 33 | @process.load_pid_from_file.should == @process.pid 34 | @process.state_name.should == :up 35 | end 36 | 37 | it "someone rewrite pid_file. should rewrite for daemonize only" do 38 | start_ok_process(cfg) 39 | old_pid = @pid 40 | @process.load_pid_from_file.should == @pid 41 | 42 | File.open(cfg[:pid_file], 'w'){|f| f.write(99999) } 43 | @process.load_pid_from_file.should == 99999 44 | 45 | sleep 5 # wait until monitor understand it 46 | 47 | if cfg[:daemonize] 48 | @process.load_pid_from_file.should == @pid 49 | else 50 | @process.load_pid_from_file.should == 99999 51 | end 52 | 53 | @process.pid.should == old_pid 54 | @process.state_name.should == :up 55 | end 56 | 57 | it "someone rewrite pid_file. and ctime > limit, should rewrite for both" do 58 | start_ok_process(cfg.merge(:revert_fuckup_pidfile_grace => 3.seconds)) 59 | old_pid = @pid 60 | @process.load_pid_from_file.should == @pid 61 | 62 | File.open(cfg[:pid_file], 'w'){|f| f.write(99999) } 63 | @process.load_pid_from_file.should == 99999 64 | 65 | sleep 8 # wait until monitor understand it 66 | 67 | @process.load_pid_from_file.should == @pid 68 | 69 | @process.pid.should == old_pid 70 | @process.state_name.should == :up 71 | end 72 | 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/process/restart_emulate_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Process Restart, emulate some real hard cases" do 4 | [C.p1, C.p2].each do |cfg| 5 | it "emulate restart as stop,start where stop command does not kill" do 6 | # should send command, than wait grace time, 7 | # and than even if old process doesnot die, start another one, (looks like bug, but this is not, it just bad using, commands) 8 | 9 | # same situation, when stop command kills so long time, that process cant stop 10 | start_ok_process(cfg.merge(:stop_command => "kill -USR1 {PID}")) 11 | old_pid = @pid 12 | 13 | dont_allow(@process).check_crash 14 | @process.restart 15 | 16 | sleep 3 17 | @process.pid.should_not == old_pid 18 | 19 | Eye::System.pid_alive?(@pid).should == true 20 | 21 | @process.state_name.should == :up 22 | @process.watchers.keys.should == [:check_alive, :check_identity] 23 | 24 | @process.load_pid_from_file.should == @process.pid 25 | @process.states_history.states.should end_with(:up, :restarting, :stopping, :unmonitored, :starting, :up) 26 | 27 | File.read(@log).should include("USR1") 28 | end 29 | 30 | it "Bad restart command, invalid" do 31 | start_ok_process(cfg.merge(:restart_command => "asdfasdf sdf asd fasdf asdf")) 32 | 33 | dont_allow(@process).check_crash 34 | 35 | @process.restart 36 | Eye::System.pid_alive?(@pid).should == true 37 | @process.states_history.states.should seq(:up, :restarting, :up) 38 | end 39 | 40 | it "restart command timeouted" do 41 | start_ok_process(cfg.merge(:restart_command => "sleep 5", :restart_timeout => 3)) 42 | @process.restart 43 | 44 | sleep 1 45 | @process.pid.should == @pid 46 | 47 | Eye::System.pid_alive?(@pid).should == true 48 | 49 | @process.state_name.should == :up 50 | @process.watchers.keys.should == [:check_alive, :check_identity] 51 | 52 | @process.load_pid_from_file.should == @process.pid 53 | @process.states_history.states.should end_with(:up, :restarting, :up) 54 | end 55 | end 56 | 57 | it "restart eye-daemonized lock-process from unmonitored status, and process really running (WAS a problem)" do 58 | start_ok_process(C.p4) 59 | @pid = @process.pid 60 | @process.unmonitor 61 | Eye::System.pid_alive?(@pid).should == true 62 | 63 | @process.restart 64 | @process.state_name.should == :up 65 | 66 | Eye::System.pid_alive?(@pid).should == false 67 | @process.load_pid_from_file.should_not == @pid 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/process/states_history_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::Process::StatesHistory" do 4 | before :each do 5 | @h = Eye::Process::StatesHistory.new 6 | end 7 | 8 | it "should work" do 9 | @h << :up 10 | @h.push :down, 'bla' 11 | 12 | @h.states.should == [:up, :down] 13 | @h.last_state.should == :down 14 | @h.last_state_changed_at.should be_within(2.seconds).of(Time.now) 15 | @h.last[:reason].should == 'bla' 16 | end 17 | 18 | it "states for period" do 19 | @h.push :up, nil, 5.minutes.ago 20 | @h.push :down, nil, 4.minutes.ago 21 | @h.push :start, nil, 3.minutes.ago 22 | @h.push :stop, nil, 2.minutes.ago 23 | @h.push :up, nil, 1.minutes.ago 24 | @h.push :down, nil, 0.minutes.ago 25 | 26 | @h.states_for_period(1.5.minutes).should == [:up, :down] 27 | @h.states_for_period(2.5.minutes).should == [:stop, :up, :down] 28 | @h.states_for_period(6.minutes).should == [:up, :down, :start, :stop, :up, :down] 29 | 30 | # with start_point 31 | @h.states_for_period(2.5.minutes, 5.minutes.ago).should == [:stop, :up, :down] 32 | @h.states_for_period(2.5.minutes, nil).should == [:stop, :up, :down] 33 | @h.states_for_period(2.5.minutes, 1.5.minutes.ago).should == [:up, :down] 34 | @h.states_for_period(2.5.minutes, Time.now).should == [] 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/process/triggers/stop_children_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../spec_helper' 2 | 3 | describe "StopChildren State" do 4 | before :each do 5 | @c = Eye::Controller.new 6 | cfg = <<-D 7 | Eye.application("bla") do 8 | working_dir "#{C.sample_dir}" 9 | process("fork") do 10 | env "PID_NAME" => "#{C.p3_pid}" 11 | pid_file "#{C.p3_pid}" 12 | start_command "ruby forking.rb start" 13 | stop_command "kill -9 {PID}" # SPECIALLY here wrong command for kill parent 14 | stdall "trash.log" 15 | monitor_children { children_update_period 1.second } 16 | check_alive_period 1 17 | trigger :stop_children 18 | end 19 | end 20 | D 21 | 22 | @c.load_content(cfg) 23 | sleep 5 24 | @process = @c.process_by_name("fork") 25 | @process.wait_for_condition(15, 0.3) { @process.children.size == 3 } 26 | @process.state_name.should == :up 27 | @pid = @process.pid 28 | @chpids = @process.children.keys 29 | end 30 | 31 | it "when process crashed it should kill all children too" do 32 | @process.children.size.should == 3 33 | Eye::System.pid_alive?(@pid).should == true 34 | @chpids.each { |pid| Eye::System.pid_alive?(pid).should == true } 35 | 36 | die_process!(@process.pid) 37 | sleep 10 38 | 39 | Eye::System.pid_alive?(@pid).should == false 40 | @chpids.each { |pid| Eye::System.pid_alive?(pid).should == false } 41 | 42 | @process.state_name.should == :up 43 | 44 | @pids = @process.children.keys # to ensure spec kill them 45 | @process.children.size.should == 3 46 | end 47 | 48 | it "when process restarted should kill children too" do 49 | @process.children.size.should == 3 50 | Eye::System.pid_alive?(@pid).should == true 51 | @chpids.each { |pid| Eye::System.pid_alive?(pid).should == true } 52 | 53 | @process.schedule :restart 54 | sleep 10 55 | 56 | Eye::System.pid_alive?(@pid).should == false 57 | @chpids.each { |pid| Eye::System.pid_alive?(pid).should == false } 58 | 59 | @process.state_name.should == :up 60 | 61 | @pids = @process.children.keys # to ensure spec kill them 62 | @process.children.size.should == 3 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/process/update_config_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "#update_config" do 4 | before :each do 5 | @cfg = C.p3.merge(:checks => join(C.check_mem, C.check_cpu), :monitor_children => {}) 6 | start_ok_process(@cfg) 7 | sleep 6 8 | end 9 | 10 | after :each do 11 | @process.stop if @process 12 | end 13 | 14 | it "update only env" do 15 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_children, :check_memory, :check_cpu] 16 | @process.children.keys.size.should == 3 17 | child_pids = @process.children.keys 18 | @process[:environment]["PID_NAME"].should be 19 | 20 | @process.update_config(@cfg.merge(:environment => @cfg[:environment].merge({"ENV2" => "SUPER"}))) 21 | sleep 5 22 | 23 | @process.state_name.should == :up 24 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_children, :check_memory, :check_cpu] 25 | @process.children.keys.size.should == 3 26 | @process.children.keys.should == child_pids 27 | @process[:environment]["ENV2"].should == "SUPER" 28 | @process.pid.should == @pid 29 | end 30 | 31 | it "update watchers" do 32 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_children, :check_memory, :check_cpu] 33 | @process.children.keys.size.should == 3 34 | child_pids = @process.children.keys 35 | 36 | @process.update_config(@cfg.merge(:checks => C.check_mem)) 37 | sleep 5 38 | 39 | @process.state_name.should == :up 40 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_children, :check_memory] 41 | @process.children.keys.size.should == 3 42 | @process.children.keys.should == child_pids 43 | @process.pid.should == @pid 44 | end 45 | 46 | it "when disable monitor_children they should remove" do 47 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_children, :check_memory, :check_cpu] 48 | @process.children.keys.size.should == 3 49 | child_pids = @process.children.keys 50 | 51 | @process.update_config(@cfg.merge(:monitor_children => nil)) 52 | sleep 5 53 | 54 | @process.state_name.should == :up 55 | @process.watchers.keys.should == [:check_alive, :check_identity, :check_memory, :check_cpu] 56 | @process.children.keys.size.should == 0 57 | @process.pid.should == @pid 58 | end 59 | 60 | end 61 | 62 | 63 | -------------------------------------------------------------------------------- /spec/process/use_leaf_child_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Process with use_leaf_child" do 4 | 5 | before { @process = process(C.p6) } 6 | 7 | # monitor leaf child in process tree 8 | # sh -c 9 | # sleep 10 10 | 11 | # pid should of `sleep 10` 12 | 13 | it "start" do 14 | @process.start 15 | ps = `ps ax | grep #{@process.pid} | grep -v grep` 16 | ps.should include('sleep 10') 17 | ps.should_not include('sh -c') 18 | @process.pid.should be 19 | @process.parent_pid.should be 20 | @process.parent_pid.should_not == @process.pid 21 | @process.state_name.should == :up 22 | 23 | Eye::System.pid_alive?(@process.pid).should == true 24 | Eye::System.pid_alive?(@process.parent_pid).should == true 25 | end 26 | 27 | it "stop" do 28 | @process.start 29 | Eye::System.pid_alive?(@process.pid).should == true 30 | Eye::System.pid_alive?(@process.parent_pid).should == true 31 | `ps ax | grep #{C.p6_word} | grep -v grep`.should_not be_blank 32 | 33 | @process.stop 34 | `ps ax | grep #{C.p6_word} | grep -v grep`.should be_blank 35 | 36 | # parent_pid also should die by itself 37 | Eye::System.pid_alive?(@process.pid).should == nil 38 | Eye::System.pid_alive?(@process.parent_pid).should == false 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/support/load_result.rb: -------------------------------------------------------------------------------- 1 | class Hash 2 | def should_be_ok(files_count = 1) 3 | self.size.should == files_count 4 | self.errors_count.should == 0 5 | end 6 | 7 | def ok_count 8 | self.values.count{ |res| !res[:error] } 9 | end 10 | 11 | def errors_count 12 | self.size - self.ok_count 13 | end 14 | 15 | def only_value 16 | if self.size == 1 17 | self.values.first 18 | else 19 | raise "request for 1 value, but there is more: #{self.size}" 20 | end 21 | end 22 | 23 | def only_match(pattern) 24 | keys = self.keys.grep(pattern) 25 | if keys.size == 1 26 | key = keys.first 27 | self[key] 28 | else 29 | raise "incorrect pattern: matched #{keys.size} with #{pattern} (expected only 1)" 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/support/rr_celluloid.rb: -------------------------------------------------------------------------------- 1 | require 'rr' 2 | require 'rspec/core/mocking/with_rr' 3 | 4 | module RR::CelluloidExt 5 | %w{mock stub dont_allow proxy strong}.each do |_method| 6 | module_eval <<-Q 7 | def #{_method}(*args) 8 | args[0] = args[0].wrapped_object if args[0].respond_to?(:wrapped_object) 9 | super 10 | end 11 | Q 12 | end 13 | end 14 | 15 | RSpec::Core::MockFrameworkAdapter.send(:include, RR::CelluloidExt) 16 | -------------------------------------------------------------------------------- /spec/utils/alive_array_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | class AliveArrayActor 4 | include Celluloid 5 | 6 | attr_reader :name 7 | 8 | def initialize(name) 9 | @name = name 10 | end 11 | end 12 | 13 | describe "Eye::Utils::AliveArray" do 14 | 15 | it "act like array" do 16 | a = Eye::Utils::AliveArray.new([1,2,3]) 17 | a.size.should == 3 18 | a.empty?.should == false 19 | a << 4 20 | a.pure.should == [1,2,3,4] 21 | end 22 | 23 | it "alive actions" do 24 | a = AliveArrayActor.new('a') 25 | b = AliveArrayActor.new('b'); b.terminate 26 | c = AliveArrayActor.new('c') 27 | 28 | l = Eye::Utils::AliveArray.new([a,b,c]) 29 | l.size.should == 3 30 | l.map{|a| a.name}.sort.should == %w{a c} 31 | 32 | l.detect{|c| c.name == 'a'}.name.should == 'a' 33 | l.detect{|c| c.name == 'b'}.should == nil 34 | 35 | l.any?{|c| c.name == 'a'}.should == true 36 | l.any?{|c| c.name == 'b'}.should == false 37 | 38 | l.include?(a).should == true 39 | l.include?(b).should == false 40 | 41 | l.sort_by(&:name).class.should == Eye::Utils::AliveArray 42 | l.sort_by(&:name).pure.should == [a, c] 43 | 44 | l.to_a.map{|c| c.name}.sort.should == %w{a c} 45 | 46 | a.terminate 47 | c.terminate 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/utils/matchers_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Custom rspec matchers" do 4 | it "contain_only" do 5 | [1,2,9].should contain_only(1, 9, 2) 6 | [1,2,9].should contain_only(1, 2, 9) 7 | [1].should contain_only(1) 8 | [1,2,9].should_not contain_only(1, 2) 9 | [1,2,9].should_not contain_only(1, 9) 10 | [1,2,9].should_not contain_only(1, 2, 3) 11 | [1].should_not contain_only(2) 12 | [1].should_not contain_only(1, 2) 13 | end 14 | 15 | it "seq" do 16 | [1,2,:-,4].should seq(1, 2) 17 | [1,2,:-,4].should seq(2, :-) 18 | [1,2,:-,4].should seq(1, 2, :-, 4) 19 | [1,2,:-,4].should seq(4) 20 | [1,2,:-,4].should seq(:-, 4) 21 | 22 | 23 | [1,2,:-,4].should_not seq(4, :-) 24 | [1,2,:-,4].should_not seq(5) 25 | [1,2,:-,4].should_not seq(2, 1) 26 | [1,2,:-,4].should_not seq(1, 2, :-, 5) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/utils/signals_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | class TestEnumerable 4 | include Celluloid 5 | 6 | def bla(x, sig) 7 | sleep(x) 8 | sig.signal(x) 9 | end 10 | end 11 | 12 | describe "signals methods" do 13 | before(:each) do 14 | @actor = TestEnumerable.new 15 | end 16 | 17 | after(:each) do 18 | @actor.terminate 19 | end 20 | 21 | describe "wait_signal" do 22 | it "ok1" do 23 | should_spend(1) do 24 | res = Eye::Utils.wait_signal(2) do |s| 25 | @actor.async.bla(1, s) 26 | end 27 | 28 | res.should == :ok 29 | end 30 | end 31 | 32 | it "timeouted1" do 33 | should_spend(0.5) do 34 | res = Eye::Utils.wait_signal(0.5) do |s| 35 | @actor.async.bla(1, s) 36 | end 37 | res.should == :timeouted 38 | end 39 | end 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /spec/utils/tail_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe "Eye::Utils::Tail" do 4 | subject{ Eye::Utils::Tail.new(5) } 5 | 6 | it "should rotate" do 7 | subject << 1 8 | subject << 2 9 | subject << 3 10 | subject.push 4 11 | subject.should == [1,2,3,4] 12 | 13 | subject << 5 14 | subject.should == [1,2,3,4,5] 15 | 16 | subject << 6 17 | subject.should == [2,3,4,5,6] 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /spec/utils_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe "Eye::Utils" do 4 | it "human_time" do 5 | s = Eye::Utils.human_time(Time.now.to_i) 6 | s.size.should == 5 7 | s.should include(':') 8 | 9 | s = Eye::Utils.human_time(1377978030) 10 | s.should == 'Aug31' 11 | end 12 | end 13 | --------------------------------------------------------------------------------