reel-eye 0.4.1 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +4 -0
  4. data/CHANGES.md +17 -1
  5. data/README.md +3 -3
  6. data/Rakefile +8 -0
  7. data/bin/eye +0 -316
  8. data/bin/loader_eye +3 -3
  9. data/examples/test.eye +2 -2
  10. data/eye.gemspec +6 -10
  11. data/lib/eye.rb +4 -2
  12. data/lib/eye/application.rb +3 -3
  13. data/lib/eye/checker.rb +35 -7
  14. data/lib/eye/checker/cputime.rb +23 -0
  15. data/lib/eye/checker/file_touched.rb +15 -0
  16. data/lib/eye/checker/http.rb +7 -9
  17. data/lib/eye/checker/memory.rb +1 -1
  18. data/lib/eye/checker/runtime.rb +28 -0
  19. data/lib/eye/checker/socket.rb +4 -4
  20. data/lib/eye/cli.rb +166 -0
  21. data/lib/eye/cli/commands.rb +79 -0
  22. data/lib/eye/cli/render.rb +137 -0
  23. data/lib/eye/cli/server.rb +85 -0
  24. data/lib/eye/client.rb +3 -3
  25. data/lib/eye/config.rb +17 -14
  26. data/lib/eye/controller.rb +6 -10
  27. data/lib/eye/controller/commands.rb +6 -10
  28. data/lib/eye/controller/helpers.rb +1 -1
  29. data/lib/eye/controller/send_command.rb +5 -2
  30. data/lib/eye/controller/status.rb +38 -106
  31. data/lib/eye/dsl.rb +1 -1
  32. data/lib/eye/dsl/application_opts.rb +6 -2
  33. data/lib/eye/dsl/child_process_opts.rb +3 -2
  34. data/lib/eye/dsl/config_opts.rb +2 -2
  35. data/lib/eye/dsl/group_opts.rb +2 -1
  36. data/lib/eye/dsl/main.rb +4 -2
  37. data/lib/eye/dsl/opts.rb +11 -4
  38. data/lib/eye/dsl/validation.rb +49 -43
  39. data/lib/eye/group.rb +1 -1
  40. data/lib/eye/http.rb +4 -9
  41. data/lib/eye/http/router.rb +1 -1
  42. data/lib/eye/loader.rb +5 -9
  43. data/lib/eye/{settings.rb → local.rb} +1 -1
  44. data/lib/eye/logger.rb +5 -0
  45. data/lib/eye/notify.rb +12 -6
  46. data/lib/eye/notify/jabber.rb +2 -2
  47. data/lib/eye/process/child.rb +3 -1
  48. data/lib/eye/process/commands.rb +2 -2
  49. data/lib/eye/process/controller.rb +1 -1
  50. data/lib/eye/process/trigger.rb +1 -1
  51. data/lib/eye/sigar.rb +5 -0
  52. data/lib/eye/system.rb +8 -7
  53. data/lib/eye/system_resources.rb +46 -41
  54. data/lib/eye/trigger.rb +15 -8
  55. data/lib/eye/trigger/flapping.rb +1 -1
  56. data/lib/eye/trigger/stop_childs.rb +1 -1
  57. data/lib/eye/trigger/transition.rb +15 -0
  58. data/lib/eye/utils.rb +12 -0
  59. data/lib/eye/utils/leak_19.rb +7 -0
  60. data/lib/eye/utils/mini_active_support.rb +106 -0
  61. metadata +40 -17
  62. data/lib/eye/controller/show_history.rb +0 -63
  63. data/lib/eye/trigger/state.rb +0 -11
data/lib/eye.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Eye
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5"
3
3
  ABOUT = "ReelEye v#{VERSION} (c) 2012-2013 @kostya"
4
4
  PROCLINE = "reel-eye monitoring v#{VERSION}"
5
5
 
@@ -14,15 +14,17 @@ module Eye
14
14
  autoload :Group, 'eye/group'
15
15
  autoload :Dsl, 'eye/dsl'
16
16
  autoload :Application, 'eye/application'
17
- autoload :Settings, 'eye/settings'
17
+ autoload :Local, 'eye/local'
18
18
  autoload :Client, 'eye/client'
19
19
  autoload :Utils, 'eye/utils'
20
20
  autoload :Notify, 'eye/notify'
21
21
  autoload :Config, 'eye/config'
22
22
  autoload :Reason, 'eye/reason'
23
+ autoload :Sigar, 'eye/sigar'
23
24
 
24
25
  autoload :Controller, 'eye/controller'
25
26
  autoload :Control, 'eye/control'
26
27
 
27
28
  autoload :Http, 'eye/http'
29
+ autoload :Cli, 'eye/cli'
28
30
  end
@@ -33,14 +33,14 @@ class Eye::Application
33
33
  end
34
34
 
35
35
  def status_data_short
36
- h = Hash.new 0
36
+ h = Hash.new
37
37
  @groups.each do |c|
38
38
  c.processes.each do |p|
39
+ h[p.state] ||= 0
39
40
  h[p.state] += 1
40
41
  end
41
42
  end
42
- str = h.sort_by{|a,b| a}.map{|k, v| "#{k}:#{v}" } * ', '
43
- { name: @name, type: :application, state: str}
43
+ { name: @name, type: :application, states: h}
44
44
  end
45
45
 
46
46
  def debug_data
@@ -1,23 +1,28 @@
1
1
  class Eye::Checker
2
+ include Eye::Dsl::Validation
2
3
 
3
4
  autoload :Memory, 'eye/checker/memory'
4
5
  autoload :Cpu, 'eye/checker/cpu'
5
6
  autoload :Http, 'eye/checker/http'
6
7
  autoload :FileCTime, 'eye/checker/file_ctime'
7
8
  autoload :FileSize, 'eye/checker/file_size'
9
+ autoload :FileTouched,'eye/checker/file_touched'
8
10
  autoload :Socket, 'eye/checker/socket'
9
11
  autoload :Nop, 'eye/checker/nop'
12
+ autoload :Runtime, 'eye/checker/runtime'
13
+ autoload :Cputime, 'eye/checker/cputime'
10
14
 
11
- TYPES = {:memory => "Memory", :cpu => "Cpu", :http => "Http",
12
- :ctime => "FileCTime", :fsize => "FileSize", :socket => "Socket",
13
- :nop => "Nop" }
15
+ TYPES = {:memory => 'Memory', :cpu => 'Cpu', :http => 'Http',
16
+ :ctime => 'FileCTime', :fsize => 'FileSize', :file_touched => 'FileTouched',
17
+ :socket => 'Socket', :nop => 'Nop', :runtime => 'Runtime', :cputime => 'Cputime' }
14
18
 
15
19
  attr_accessor :value, :values, :options, :pid, :type, :check_count, :process
16
20
 
17
- extend Eye::Dsl::Validation
18
21
  param :every, [Fixnum, Float], false, 5
19
22
  param :times, [Fixnum, Array], nil, 1
20
23
  param :fires, [Symbol, Array], nil, nil, [:stop, :restart, :unmonitor, :nothing, :start, :delete]
24
+ param :initial_grace, [Fixnum, Float]
25
+ param :skip_initial_fails, [TrueClass, FalseClass]
21
26
 
22
27
  def self.name_and_class(type)
23
28
  type = type.to_sym
@@ -32,6 +37,9 @@ class Eye::Checker
32
37
  def self.get_class(type)
33
38
  klass = eval("Eye::Checker::#{TYPES[type]}") rescue nil
34
39
  raise "Unknown checker #{type}" unless klass
40
+ if deps = klass.depends_on
41
+ Array(deps).each { |d| require d }
42
+ end
35
43
  klass
36
44
  end
37
45
 
@@ -50,9 +58,10 @@ class Eye::Checker
50
58
  def initialize(pid, options = {}, process = nil)
51
59
  @process = process
52
60
  @pid = pid
53
- @options = options
61
+ @options = options.dup
54
62
  @type = options[:type]
55
63
  @full_name = @process.full_name if @process
64
+ @initialized_at = Time.now
56
65
 
57
66
  debug "create checker, with #{options}"
58
67
 
@@ -83,8 +92,16 @@ class Eye::Checker
83
92
  end
84
93
 
85
94
  def check
95
+ if initial_grace && (Time.now - @initialized_at < initial_grace)
96
+ debug 'skipped initial grace'
97
+ return true
98
+ else
99
+ @options[:initial_grace] = nil
100
+ end
101
+
86
102
  @value = get_value_safe
87
- @values << {:value => @value, :good => good?(value)}
103
+ @good_value = good?(value)
104
+ @values << {:value => @value, :good => @good_value}
88
105
 
89
106
  result = true
90
107
  @check_count += 1
@@ -94,6 +111,14 @@ class Eye::Checker
94
111
  result = false if bad_count >= min_tries
95
112
  end
96
113
 
114
+ if skip_initial_fails
115
+ if @good_value
116
+ @options[:skip_initial_fails] = nil
117
+ else
118
+ result = true
119
+ end
120
+ end
121
+
97
122
  info "#{last_human_values} => #{result ? 'OK' : 'Fail'}"
98
123
  result
99
124
 
@@ -166,12 +191,15 @@ class Eye::Checker
166
191
  end
167
192
 
168
193
  def self.register(base)
169
- name = base.to_s.gsub("Eye::Checker::", '')
194
+ name = base.to_s.gsub('Eye::Checker::', '')
170
195
  type = name.underscore.to_sym
171
196
  Eye::Checker::TYPES[type] = name
172
197
  Eye::Checker.const_set(name, base)
173
198
  end
174
199
 
200
+ def self.depends_on
201
+ end
202
+
175
203
  class CustomCell < Eye::Checker
176
204
  def self.inherited(base)
177
205
  super
@@ -0,0 +1,23 @@
1
+ class Eye::Checker::Cputime < Eye::Checker
2
+
3
+ # check :cputime, :every => 1.minute, :below => 120.minutes
4
+
5
+ param :below, [Fixnum, Float], true
6
+
7
+ def get_value
8
+ Eye::SystemResources.cputime(@pid).to_f
9
+ end
10
+
11
+ def human_value(value)
12
+ "#{value / 60}m"
13
+ end
14
+
15
+ def good?(value)
16
+ if below
17
+ value < below
18
+ else
19
+ true
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,15 @@
1
+ class Eye::Checker::FileTouched < Eye::Checker
2
+
3
+ param :file, [String], true
4
+ param :delete, [TrueClass, FalseClass]
5
+
6
+ def get_value
7
+ File.exists?(file)
8
+ end
9
+
10
+ def good?(value)
11
+ File.delete(file) if value && delete
12
+ !value
13
+ end
14
+
15
+ end
@@ -7,7 +7,7 @@ class Eye::Checker::Http < Eye::Checker::Defer
7
7
 
8
8
  param :url, String, true
9
9
  param :pattern, [String, Regexp]
10
- param :kind
10
+ param :kind, [String, Fixnum, Symbol]
11
11
  param :timeout, [Fixnum, Float]
12
12
  param :open_timeout, [Fixnum, Float]
13
13
  param :read_timeout, [Fixnum, Float]
@@ -17,8 +17,7 @@ class Eye::Checker::Http < Eye::Checker::Defer
17
17
  def initialize(*args)
18
18
  super
19
19
 
20
- @uri = URI.parse(url) rescue URI.parse('http://127.0.0.1')
21
- @pattern = pattern
20
+ @uri = URI.parse(url)
22
21
  @kind = case kind
23
22
  when Fixnum then Net::HTTPResponse::CODE_TO_OBJ[kind]
24
23
  when String, Symbol then Net.const_get("HTTP#{kind.to_s.camelize}") rescue Net::HTTPSuccess
@@ -40,7 +39,6 @@ class Eye::Checker::Http < Eye::Checker::Defer
40
39
  mes = ex.class.is_a?(Net::OpenTimeout) ? "OpenTimeout<#{@open_timeout}>" : "ReadTimeout<#{@read_timeout}>"
41
40
  {:exception => mes}
42
41
  else
43
- error "#{ex.message}"
44
42
  {:exception => "Timeout<#{@open_timeout},#{@read_timeout}>"}
45
43
  end
46
44
 
@@ -55,13 +53,13 @@ class Eye::Checker::Http < Eye::Checker::Defer
55
53
  return false
56
54
  end
57
55
 
58
- if @pattern
59
- matched = if @pattern.is_a?(Regexp)
60
- @pattern === value[:result].body
56
+ if pattern
57
+ matched = if pattern.is_a?(Regexp)
58
+ pattern === value[:result].body
61
59
  else
62
- value[:result].body.include?(@pattern.to_s)
60
+ value[:result].body.include?(pattern.to_s)
63
61
  end
64
- value[:notice] = "missing '#{@pattern.to_s}'" unless matched
62
+ value[:notice] = "missing '#{pattern.to_s}'" unless matched
65
63
  matched
66
64
  else
67
65
  true
@@ -9,7 +9,7 @@ class Eye::Checker::Memory < Eye::Checker
9
9
  end
10
10
 
11
11
  def get_value
12
- Eye::SystemResources.memory(@pid).to_i * 1024
12
+ Eye::SystemResources.memory(@pid).to_i
13
13
  end
14
14
 
15
15
  def human_value(value)
@@ -0,0 +1,28 @@
1
+ class Eye::Checker::Runtime < Eye::Checker
2
+
3
+ # check :runtime, :every => 1.minute, :below => 120.minutes
4
+
5
+ param :below, [Fixnum, Float], true
6
+
7
+ def get_value
8
+ st = Eye::SystemResources.start_time(@pid)
9
+ if st
10
+ Time.now.to_i - st.to_i
11
+ else
12
+ 0
13
+ end
14
+ end
15
+
16
+ def human_value(value)
17
+ "#{value / 60}m"
18
+ end
19
+
20
+ def good?(value)
21
+ if below
22
+ value < below
23
+ else
24
+ true
25
+ end
26
+ end
27
+
28
+ end
@@ -80,7 +80,7 @@ class Eye::Checker::Socket < Eye::Checker::Defer
80
80
 
81
81
  unless match
82
82
  warn "proc #{expect_data} not matched (#{value[:result].truncate(30)}) answer"
83
- value[:notice] = "missing proc validation"
83
+ value[:notice] = 'missing proc validation'
84
84
  end
85
85
 
86
86
  return match
@@ -104,7 +104,7 @@ class Eye::Checker::Socket < Eye::Checker::Defer
104
104
  value[:exception]
105
105
  else
106
106
  if value[:result] == :listen
107
- "listen"
107
+ 'listen'
108
108
  else
109
109
  res = "#{value[:result].to_s.size}b"
110
110
  res += "<#{value[:notice]}>" if value[:notice]
@@ -138,7 +138,7 @@ private
138
138
  def _read_data(socket)
139
139
  case protocol
140
140
  when :em_object
141
- content = ""
141
+ content = ''
142
142
  msg_size = socket.recv(4).unpack('N')[0] rescue 0
143
143
  content << socket.recv(msg_size - content.length) while content.length < msg_size
144
144
  if content.present?
@@ -149,4 +149,4 @@ private
149
149
  end
150
150
  end
151
151
 
152
- end
152
+ end
@@ -0,0 +1,166 @@
1
+ gem 'thor'
2
+ require 'thor'
3
+
4
+ class Eye::Cli < Thor
5
+ autoload :Server, 'eye/cli/server'
6
+ autoload :Commands, 'eye/cli/commands'
7
+ autoload :Render, 'eye/cli/render'
8
+
9
+ include Eye::Cli::Server
10
+ include Eye::Cli::Commands
11
+ include Eye::Cli::Render
12
+
13
+ desc "info [MASK]", "processes info"
14
+ def info(mask = nil)
15
+ res = cmd(:info_data, *Array(mask))
16
+ say render_info(res)
17
+ say
18
+ end
19
+
20
+ desc "status", "processes info (deprecated)"
21
+ def status
22
+ say ":status is deprecated, use :info instead", :yellow
23
+ info
24
+ end
25
+
26
+ desc "xinfo", "eye-deamon info (-c show current config)"
27
+ method_option :config, :type => :boolean, :aliases => "-c"
28
+ def xinfo
29
+ res = cmd(:debug_data, :config => options[:config])
30
+ say render_debug_info(res)
31
+ say
32
+ end
33
+
34
+ desc "oinfo", "onelined info"
35
+ def oinfo
36
+ res = cmd(:short_data)
37
+ say render_info(res)
38
+ say
39
+ end
40
+
41
+ desc "history [MASK,...]", "processes history"
42
+ def history(*masks)
43
+ res = cmd(:history_data, *masks)
44
+ say render_history(res)
45
+ say
46
+ end
47
+
48
+ desc "load [CONF, ...]", "load config (start eye-daemon if not) (-f foreground start)"
49
+ method_option :foreground, :type => :boolean, :aliases => "-f"
50
+ def load(*configs)
51
+ configs.map!{ |c| File.expand_path(c) } if !configs.empty?
52
+
53
+ if options[:foreground]
54
+ # in foreground we stop another server, and run just 1 current config version
55
+ error!("foreground expected only one config") if configs.size != 1
56
+ server_start_foreground(configs.first)
57
+
58
+ elsif server_started?
59
+ say_load_result cmd(:load, *configs)
60
+
61
+ else
62
+ server_start(configs)
63
+
64
+ end
65
+ end
66
+
67
+ desc "quit", "eye-daemon quit"
68
+ def quit
69
+ res = _cmd(:quit)
70
+
71
+ # if eye server got crazy, stop by force
72
+ ensure_stop_previous_server if res != :corrupted_data
73
+
74
+ # remove pid_file
75
+ File.delete(Eye::Local.pid_path) if File.exists?(Eye::Local.pid_path)
76
+
77
+ say "quit...", :yellow
78
+ end
79
+
80
+ [:start, :stop, :restart, :unmonitor, :monitor, :delete, :match].each do |_cmd|
81
+ desc "#{_cmd} MASK[,...]", "#{_cmd} app,group or process"
82
+ define_method(_cmd) do |*masks|
83
+ send_command(_cmd, *masks)
84
+ end
85
+ end
86
+
87
+ desc "signal SIG MASK[,...]", "send signal to app,group or process"
88
+ def signal(sig, *masks)
89
+ send_command(:signal, sig, *masks)
90
+ end
91
+
92
+ desc "break MASK[,...]", "break chain executing"
93
+ def break(*masks)
94
+ send_command(:break_chain, *masks)
95
+ end
96
+
97
+ desc "trace [MASK]", "tracing log(tail + grep) for app,group or process"
98
+ def trace(mask = "")
99
+ log_trace(mask)
100
+ end
101
+
102
+ map ["-v", "--version"] => :version
103
+ desc "version", "version"
104
+ def version
105
+ say Eye::ABOUT
106
+ end
107
+
108
+ desc "check CONF", "check config file syntax"
109
+ method_option :host, :type => :string, :aliases => "-h"
110
+ method_option :verbose, :type => :boolean, :aliases => "-v"
111
+ def check(conf)
112
+ conf = File.expand_path(conf) if conf && !conf.empty?
113
+
114
+ Eye::System.host = options[:host] if options[:host]
115
+ Eye::Dsl.verbose = options[:verbose]
116
+
117
+ say_load_result Eye::Controller.new.check(conf), :syntax => true
118
+ end
119
+
120
+ desc "explain CONF", "explain config tree"
121
+ method_option :host, :type => :string, :aliases => "-h"
122
+ method_option :verbose, :type => :boolean, :aliases => "-v"
123
+ def explain(conf)
124
+ conf = File.expand_path(conf) if conf && !conf.empty?
125
+
126
+ Eye::System.host = options[:host] if options[:host]
127
+ Eye::Dsl.verbose = options[:verbose]
128
+
129
+ say_load_result Eye::Controller.new.explain(conf), :print_config => true, :syntax => true
130
+ end
131
+
132
+ desc "watch [MASK]", "interactive processes info"
133
+ def watch(*args)
134
+ cmd = if `watch --version 2>&1`.chop > '0.2.0'
135
+ "watch -n 1 --color #{$0} i #{args * ' '}"
136
+ else
137
+ "watch -n 1 #{$0} i #{args * ' '}"
138
+ end
139
+
140
+ pid = Process.spawn(cmd)
141
+ Process.waitpid(pid)
142
+ rescue Interrupt
143
+ end
144
+
145
+ private
146
+
147
+ def error!(msg)
148
+ say msg, :red
149
+ exit 1
150
+ end
151
+
152
+ def print(msg, new_line = true)
153
+ say msg if msg && !msg.empty?
154
+ say if new_line
155
+ end
156
+
157
+ def log_trace(tag = '')
158
+ log_file = cmd(:logger_dev)
159
+ if log_file && File.exists?(log_file)
160
+ Process.exec "tail -n 100 -f #{log_file} | grep '#{tag}'"
161
+ else
162
+ error! "log file not found #{log_file.inspect}"
163
+ end
164
+ end
165
+
166
+ end