scout 4.0.2 → 5.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -6,32 +6,32 @@ Scout by Highgroove Studios
6
6
 
7
7
  The easier way to monitor servers and web applications.
8
8
 
9
- Scout makes monitoring and reporting on your web applications as flexible
10
- and simple as possible.
9
+ Scout makes monitoring and reporting on your web applications
10
+ as flexible and simple as possible.
11
11
 
12
- Scout is a product of Highgroove Studios. Please visit http://scoutapp.com
13
- for more information.
12
+ Scout is a product of Highgroove Studios.
13
+ Please visit http://scoutapp.com for more information.
14
14
 
15
15
  == Installing
16
16
 
17
- After installing the scout gem:
17
+ Install the Scout gem:
18
18
 
19
19
  $ sudo gem install scout
20
20
 
21
- Simply run:
21
+ Then simply run:
22
22
 
23
23
  $ scout
24
24
 
25
- to run the installation wizard. You'll need your Client Key to continue.
25
+ to run the installation wizard. You'll need your server key to continue. Get the server key from your account at http://scoutapp.com
26
26
 
27
27
  == Running the Scout Client
28
28
 
29
29
  The Scout client has several modes of operation and commands. The normal, intended usage is through a scheduled interval with no output.
30
30
 
31
31
  Normal checkin with server:
32
- $ scout [OPTIONS] CLIENT_KEY
32
+ $ scout [OPTIONS] SERVER_KEY
33
33
  ... OR ...
34
- $ scout [OPTIONS] run CLIENT_KEY
34
+ $ scout [OPTIONS] run SERVER_KEY
35
35
 
36
36
  Install:
37
37
  $ scout
@@ -41,36 +41,26 @@ Install:
41
41
  Local plugin testing:
42
42
  $ scout [OPTIONS] test PATH_TO_PLUGIN [PLUGIN_OPTIONS]
43
43
 
44
- Clone a client setup:
45
- $ scout [OPTIONS] clone CLIENT_KEY NEW_CLIENT_NAME
46
44
 
47
- CLIENT_KEY is the indentification key assigned to
48
- this client by the server.
45
+ SERVER_KEY is the identification key assigned by your account at http://scoutapp.com
49
46
 
50
47
  PATH_TO_PLUGIN is the file system path to a Ruby file
51
48
  that contains a Scout plugin.
52
49
 
53
- PLUGIN_OPTIONS can be the code for a Ruby Hash or the
54
- path to a YAML options file containing defaults. These
55
- options will be used for the plugin run.
50
+ PLUGIN_OPTIONS are one or more options in the form:
51
+ key1=val1 key2=val2
52
+ These options will be used for the plugin run.
56
53
 
57
- NEW_CLIENT_NAME is name you wish to use for the new
58
- client the server creates.
59
54
 
55
+ == Setting up in cron
60
56
 
61
- == Cloning a Client
57
+ Configure Scout to run every minute. Typically, this will look like:
62
58
 
63
- The Scout gem can clone an existing client to automate the process of setting up additional clients.
64
-
65
- An example usage, running:
66
-
67
- server1 $ scout clone CLIENT_KEY 'My New Client'
68
-
69
- Will create a new client called 'My New Client' on the Scout server with the same plugins as an already existing client (with the CLIENT_KEY specified). It will also return the new system crontab line:
70
-
71
- */30 * * * * deploy /usr/bin/scout NEW_CLIENT_KEY
59
+ * * * * * deploy /usr/bin/scout SERVER_KEY
72
60
 
61
+ It's often helpful to log the output to a file. To do so:
73
62
 
63
+ * * * * * deploy /usr/bin/scout SERVER_KEY > /path/to/anywhere/scout.out 2>&1
74
64
 
75
65
 
76
66
  For additional help, please visit http://scoutapp.com
data/Rakefile CHANGED
@@ -7,8 +7,8 @@ require "net/ssh"
7
7
  require "rubygems"
8
8
  require "rubyforge"
9
9
 
10
- dir = File.dirname(__FILE__)
11
- lib = File.join(dir, "lib", "scout.rb")
10
+ dir = File.dirname(__FILE__)
11
+ lib = File.join(dir, "lib", "scout.rb")
12
12
  version = File.read(lib)[/^\s*VERSION\s*=\s*(['"])(\d\.\d\.\d)\1/, 2]
13
13
  history = File.read("CHANGELOG").split(/^(===.*)/)
14
14
  changes ||= history[0..2].join.strip
@@ -19,50 +19,50 @@ need_zip = true
19
19
  task :default => [:test]
20
20
 
21
21
  Rake::TestTask.new do |test|
22
- test.libs << "test"
22
+ test.libs << "test"
23
23
  test.test_files = [ "test/scout_test.rb" ]
24
- test.verbose = true
24
+ test.verbose = true
25
25
  end
26
26
 
27
27
  Rake::RDocTask.new do |rdoc|
28
- rdoc.main = "README"
29
- rdoc.rdoc_dir = "doc/html"
30
- rdoc.title = "Scout Client Documentation"
31
- rdoc.rdoc_files.include( "README", "INSTALL",
32
- "TODO", "CHANGELOG",
33
- "AUTHORS", "COPYING",
34
- "LICENSE", "lib/" )
28
+ rdoc.main = "README"
29
+ rdoc.rdoc_dir = "doc/html"
30
+ rdoc.title = "Scout Client Documentation"
31
+ rdoc.rdoc_files.include( "README", "INSTALL",
32
+ "TODO", "CHANGELOG",
33
+ "AUTHORS", "COPYING",
34
+ "LICENSE", "lib/" )
35
35
  end
36
36
 
37
37
  spec = Gem::Specification.new do |spec|
38
- spec.name = "scout"
39
- spec.version = version
38
+ spec.name = "scout"
39
+ spec.version = version
40
40
 
41
- spec.platform = Gem::Platform::RUBY
42
- spec.summary = "Scout makes monitoring and reporting on your web applications as flexible and simple as possible."
41
+ spec.platform = Gem::Platform::RUBY
42
+ spec.summary = "Scout makes monitoring and reporting on your web applications as flexible and simple as possible."
43
43
 
44
44
  # TODO: test suite
45
- # spec.test_suite_file = "test/ts_all.rb"
46
- spec.files = Dir.glob("{bin,lib}/**/*.rb") +
47
- Dir.glob("{data,vendor}/**/*") +
48
- %w[Rakefile]
49
- spec.executables = ["scout"]
50
-
51
- spec.has_rdoc = true
52
- spec.extra_rdoc_files = %w[ AUTHORS COPYING README INSTALL TODO CHANGELOG
45
+ # spec.test_suite_file = "test/ts_all.rb"
46
+ spec.files = Dir.glob("{bin,lib}/**/*.rb") +
47
+ Dir.glob("{data,vendor}/**/*") +
48
+ %w[Rakefile]
49
+ spec.executables = ["scout"]
50
+
51
+ spec.has_rdoc = true
52
+ spec.extra_rdoc_files = %w[ AUTHORS COPYING README INSTALL TODO CHANGELOG
53
53
  LICENSE ]
54
- spec.rdoc_options << "--title" << "Scout Client Documentation" <<
55
- "--main" << "README"
54
+ spec.rdoc_options << "--title" << "Scout Client Documentation" <<
55
+ "--main" << "README"
56
+
57
+ spec.require_path = "lib"
56
58
 
57
- spec.require_path = "lib"
58
-
59
59
  spec.add_dependency "elif"
60
60
 
61
- spec.author = "Highgroove Studios"
62
- spec.email = "scout@highgroove.com"
63
- spec.rubyforge_project = "scout"
64
- spec.homepage = "http://scoutapp.com"
65
- spec.description = <<END_DESC
61
+ spec.author = "Highgroove Studios"
62
+ spec.email = "scout@highgroove.com"
63
+ spec.rubyforge_project = "scout"
64
+ spec.homepage = "http://scoutapp.com"
65
+ spec.description = <<END_DESC
66
66
  Scout makes monitoring and reporting on your web applications as flexible and simple as possible.
67
67
 
68
68
  Scout is a product of Highgroove Studios.
@@ -70,8 +70,8 @@ END_DESC
70
70
  end
71
71
 
72
72
  Rake::GemPackageTask.new(spec) do |pkg|
73
- pkg.need_zip = need_tar
74
- pkg.need_tar = need_zip
73
+ pkg.need_zip = need_tar
74
+ pkg.need_tar = need_zip
75
75
  end
76
76
 
77
77
  desc "Publishes to Scout Gem Server and Rubyforge"
@@ -85,12 +85,12 @@ task :publish_rubyforge => [:package] do
85
85
  puts "Logging in"
86
86
  forge.login
87
87
 
88
- release = forge.userconfig
88
+ release = forge.userconfig
89
89
  release["release_changes"] = File.read(File.join(dir, "CHANGELOG"))
90
- release["preformatted"] = true
90
+ release["preformatted"] = true
91
91
 
92
92
  package = "pkg/#{spec.name}-#{version}"
93
- files = %W[#{package}.tgz #{package}.zip #{package}.gem].compact
93
+ files = %W[#{package}.tgz #{package}.zip #{package}.gem].compact
94
94
 
95
95
  puts "Releasing #{spec.name}-#{version}"
96
96
  forge.add_release(spec.rubyforge_project, spec.name, version, *files)
@@ -98,9 +98,9 @@ end
98
98
 
99
99
  desc "Upload current documentation to Scout Gem Server and RubyForge"
100
100
  task :upload_docs => [:rdoc] do
101
- sh "scp -r doc/html/* " +
102
- "deploy@gems.scoutapp.com:/var/www/gems/docs"
103
-
101
+ sh "scp -r doc/html/* " +
102
+ "deploy@gems.scoutapp.com:/var/www/gems/docs"
103
+
104
104
  config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
105
105
  host = "#{config["username"]}@rubyforge.org"
106
106
 
@@ -112,5 +112,5 @@ end
112
112
 
113
113
  desc "Add new files to Subersion"
114
114
  task :svn_add do
115
- system "svn status | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\ /g' | xargs svn add"
115
+ system "svn status | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\ /g' | xargs svn add"
116
116
  end
data/bin/scout CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # encoding: utf-8
2
3
 
3
4
  $VERBOSE = true # -w
4
- $KCODE = "u" # -Ku
5
+ #$KCODE = "u" # -Ku
5
6
 
6
7
  $LOAD_PATH << File.join(File.dirname(__FILE__), *%w[.. lib])
7
8
  require "scout"
data/lib/scout.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env ruby -wKU
2
2
 
3
3
  module Scout
4
- VERSION = "4.0.2".freeze
4
+ VERSION = "5.0.2".freeze
5
5
  end
6
6
 
7
7
  require "scout/command"
8
8
  require "scout/plugin"
9
+ require "scout/plugin_options"
9
10
  require "scout/server"
data/lib/scout/command.rb CHANGED
@@ -9,7 +9,7 @@ module Scout
9
9
  def self.user
10
10
  @user ||= ENV["USER"] || ENV["USERNAME"] || "root"
11
11
  end
12
-
12
+
13
13
  def self.program_name
14
14
  @program_name ||= File.basename($PROGRAM_NAME)
15
15
  end
@@ -17,7 +17,7 @@ module Scout
17
17
  def self.program_path
18
18
  @program_path ||= File.expand_path($PROGRAM_NAME)
19
19
  end
20
-
20
+
21
21
  def self.usage
22
22
  @usage
23
23
  end
@@ -25,9 +25,10 @@ module Scout
25
25
  def self.parse_options(argv)
26
26
  options = { }
27
27
 
28
- ARGV.options do |opts|
28
+ op = OptionParser.new do |opts|
29
29
  opts.banner = "Usage:"
30
30
 
31
+ opts.separator "--------------------------------------------------------------------------"
31
32
  opts.separator " Normal checkin with server:"
32
33
  opts.separator " #{program_name} [OPTIONS] CLIENT_KEY"
33
34
  opts.separator " ... OR ..."
@@ -39,47 +40,32 @@ module Scout
39
40
  opts.separator " Local plugin testing:"
40
41
  opts.separator " #{program_name} [OPTIONS] test " +
41
42
  "PATH_TO_PLUGIN [PLUGIN_OPTIONS]"
42
- opts.separator " Clone a client setup:"
43
- opts.separator " #{program_name} [OPTIONS] clone " +
44
- "CLIENT_KEY NEW_CLIENT_NAME"
45
- opts.separator ""
46
- opts.separator "CLIENT_KEY is the indentification key assigned to"
47
- opts.separator "this client by the server."
48
- opts.separator ""
49
- opts.separator "PATH_TO_PLUGIN is the file system path to a Ruby file"
50
- opts.separator "that contains a Scout plugin."
51
- opts.separator ""
52
- opts.separator "PLUGIN_OPTIONS can be the code for a Ruby Hash or the"
53
- opts.separator "path to a YAML options file containing defaults. These"
54
- opts.separator "options will be used for the plugin run."
55
- opts.separator ""
56
- opts.separator "NEW_CLIENT_NAME is name you wish to use for the new"
57
- opts.separator "client the server creates."
58
- opts.separator ""
43
+ opts.separator "[PLUGIN_OPTIONS] format: opt1=val1 opt2=val2 opt2=val3 ..."
44
+ opts.separator "Plugin will use internal defaults if options aren't provided."
45
+ opts.separator " "
59
46
  opts.separator "Note: This client is meant to be installed and"
60
47
  opts.separator "invoked through cron or any other scheduler."
61
- opts.separator ""
48
+ opts.separator " "
62
49
  opts.separator "Specific Options:"
63
-
50
+ opts.separator "--------------------------------------------------------------------------"
64
51
  opts.on( "-s", "--server SERVER", String,
65
52
  "The URL for the server to report to." ) do |url|
66
53
  options[:server] = url
67
54
  end
68
55
 
69
- opts.separator ""
70
-
71
56
  opts.on( "-d", "--data DATA", String,
72
57
  "The data file used to track history." ) do |file|
73
58
  options[:history] = file
74
59
  end
75
60
  opts.on( "-l", "--level LEVEL",
76
61
  Logger::SEV_LABEL.map { |l| l.downcase },
77
- "The level of logging to report." ) do |level|
62
+ "The level of logging to report. Use -ldebug for most detail." ) do |level|
78
63
  options[:level] = level
79
64
  end
80
65
 
66
+ opts.separator " "
81
67
  opts.separator "Common Options:"
82
-
68
+ opts.separator "--------------------------------------------------------------------------"
83
69
  opts.on( "-h", "--help",
84
70
  "Show this message." ) do
85
71
  puts opts
@@ -89,31 +75,46 @@ module Scout
89
75
  "Turn on logging to STDOUT" ) do |bool|
90
76
  options[:verbose] = bool
91
77
  end
92
-
78
+
93
79
  opts.on( "-V", "--version",
94
80
  "Display the current version") do |version|
95
81
  puts Scout::VERSION
96
82
  exit
97
83
  end
98
84
 
99
- begin
100
- opts.parse!
101
- @usage = opts.to_s
102
- rescue
103
- puts opts
104
- exit
85
+ opts.on( "-F", "--force", "Force checkin to Scout server regardless of last checkin time") do |bool|
86
+ options[:force] = bool
105
87
  end
88
+
89
+ opts.separator " "
90
+ opts.separator "Examples: "
91
+ opts.separator "--------------------------------------------------------------------------"
92
+ opts.separator "1. Normal run (replace w/your own key):"
93
+ opts.separator " scout 6ecad322-0d17-4cb8-9b2c-a12c4541853f"
94
+ opts.separator "2. Normal run with logging to standard out (replace w/your own key):"
95
+ opts.separator " scout --verbose 6ecad322-0d17-4cb8-9b2c-a12c4541853f"
96
+ opts.separator "3. Test a plugin:"
97
+ opts.separator " scout test my_plugin.rb foo=18 bar=42"
98
+
99
+ end
100
+
101
+ begin
102
+ op.parse!(argv)
103
+ @usage = op.to_s
104
+ rescue
105
+ puts op
106
+ exit
106
107
  end
107
-
108
108
  options
109
109
  end
110
110
  private_class_method :parse_options
111
-
111
+
112
112
  def self.dispatch(argv)
113
+ # capture help command
114
+ argv.push("--help") if argv.first == 'help'
113
115
  options = parse_options(argv)
114
116
  command = if name_or_key = argv.shift
115
- if cls = Scout::Command.const_get(name_or_key.capitalize) \
116
- rescue nil
117
+ if cls = (Scout::Command.const_get(name_or_key.capitalize) rescue nil)
117
118
  cls.new(options, argv)
118
119
  else
119
120
  Run.new(options, [name_or_key] + argv)
@@ -121,9 +122,9 @@ module Scout
121
122
  else
122
123
  Install.new(options, argv)
123
124
  end
124
- command.create_pid_file_or_exit.run
125
+ command.run
125
126
  end
126
-
127
+
127
128
  def initialize(options, args)
128
129
  @server = options[:server] || "https://scoutapp.com/"
129
130
  @history = options[:history] ||
@@ -132,23 +133,24 @@ module Scout
132
133
  "client_history.yaml" )
133
134
  @verbose = options[:verbose] || false
134
135
  @level = options[:level] || "info"
135
-
136
+ @force = options[:force] || false
137
+
136
138
  @args = args
137
139
  end
138
-
140
+
139
141
  attr_reader :server, :history
140
-
142
+
141
143
  def config_dir
142
144
  return @config_dir if defined? @config_dir
143
145
  @config_dir = File.dirname(history)
144
146
  FileUtils.mkdir_p(@config_dir) # ensure dir exists
145
147
  @config_dir
146
148
  end
147
-
149
+
148
150
  def verbose?
149
151
  @verbose
150
152
  end
151
-
153
+
152
154
  def log
153
155
  return @log if defined? @log
154
156
  @log = if verbose?
@@ -160,15 +162,15 @@ module Scout
160
162
  nil
161
163
  end
162
164
  end
163
-
165
+
164
166
  def level
165
167
  Logger.const_get(@level.upcase) rescue Logger::INFO
166
168
  end
167
-
169
+
168
170
  def user
169
171
  @user ||= Command.user
170
172
  end
171
-
173
+
172
174
  def program_name
173
175
  @program_name ||= Command.program_name
174
176
  end
@@ -176,11 +178,11 @@ module Scout
176
178
  def program_path
177
179
  @program_path ||= Command.program_path
178
180
  end
179
-
181
+
180
182
  def usage
181
183
  @usage ||= Command.usage
182
184
  end
183
-
185
+
184
186
  def create_pid_file_or_exit
185
187
  pid_file = File.join(config_dir, "scout_client_pid.txt")
186
188
  begin
@@ -228,7 +230,7 @@ module Scout
228
230
  retry
229
231
  end
230
232
  end
231
-
233
+
232
234
  self
233
235
  end
234
236
  end
@@ -4,23 +4,25 @@ module Scout
4
4
  class Command
5
5
  class Install < Command
6
6
  def run
7
+ create_pid_file_or_exit
8
+
7
9
  abort usage unless $stdin.tty?
8
10
 
9
11
  puts <<-END_INTRO.gsub(/^ {8}/, "")
10
12
  === Scout Installation Wizard ===
11
13
 
12
- You need the Client Key displayed in the Client Settings tab.
14
+ You need the Server Key displayed in the Server Settings tab.
13
15
  It looks like:
14
16
 
15
17
  6ecad322-0d17-4cb8-9b2c-a12c4541853f
16
18
 
17
- Enter the Client Key:
19
+ Enter the Server Key:
18
20
  END_INTRO
19
21
  key = gets.to_s.strip
20
22
 
21
23
  puts "\nAttempting to contact the server..."
22
24
  begin
23
- Scout::Server.new(server, key, history, log) { |scout| scout.test }
25
+ Scout::Server.new(server, key, history, log) { |scout| scout.fetch_plan }
24
26
 
25
27
  puts <<-END_SUCCESS.gsub(/^ {10}/, "")
26
28
  Success!
@@ -31,14 +33,14 @@ module Scout
31
33
  (usually located at /etc/crontab):
32
34
 
33
35
  ****** START CRONTAB SAMPLE ******
34
- */30 * * * * #{user} #{program_path} #{key}
36
+ * * * * * #{user} #{program_path} #{key}
35
37
  ****** END CRONTAB SAMPLE ******
36
38
 
37
39
  If you are using this current user's crontab
38
40
  (using crontab -e to edit):
39
41
 
40
42
  ****** START CRONTAB SAMPLE ******
41
- */30 * * * * #{program_path} #{key}
43
+ * * * * * #{program_path} #{key}
42
44
  ****** END CRONTAB SAMPLE ******
43
45
 
44
46
  For help setting up Scout with crontab, please visit:
@@ -47,9 +49,10 @@ module Scout
47
49
 
48
50
  END_SUCCESS
49
51
  rescue SystemExit
52
+ puts $!.message
50
53
  puts <<-END_ERROR.gsub(/^ {10}/, "")
51
54
 
52
- Could not contact server. The client key may be incorrect.
55
+ Failed.
53
56
  For more help, please visit:
54
57
 
55
58
  http://scoutapp.com/help
@@ -5,8 +5,25 @@ module Scout
5
5
  class Run < Command
6
6
  def run
7
7
  key = @args.first
8
- Scout::Server.new(server, key, history, log) do |scout|
9
- scout.run_plugins_by_plan
8
+ # too much external logic of command doing things to server ... should be moved into server class
9
+ @scout = Scout::Server.new(server, key, history, log)
10
+ @scout.load_history
11
+ @scout.fetch_plan
12
+
13
+
14
+ if @scout.new_plan || @scout.time_to_checkin? || @force
15
+ if @scout.new_plan
16
+ log.info("Now checking in with new plugin plan") if log
17
+ elsif @scout.time_to_checkin?
18
+ log.info("It is time to checkin") if log
19
+ elsif @force
20
+ log.info("overriding checkin schedule with --force and checking in now.") if log
21
+ end
22
+ create_pid_file_or_exit
23
+ @scout.run_plugins_by_plan
24
+ @scout.save_history
25
+ else
26
+ log.info "Not time to checkin yet. Next checkin in #{@scout.next_checkin}. Override by passing --force to the scout command" if log
10
27
  end
11
28
  end
12
29
  end
@@ -6,22 +6,43 @@ module Scout
6
6
  class Command
7
7
  class Test < Command
8
8
  def run
9
- plugin, options = @args
10
-
9
+ create_pid_file_or_exit
10
+ plugin, *provided_options = @args
11
11
  # read the plugin_code from the file specified
12
12
  plugin_code = File.read(plugin)
13
- plugin_options = if options.to_s[0, 1] == "{"
14
- eval(options) # options from command-line
15
- elsif options
16
- #
17
- # read the plugin_options from the YAML file specified,
18
- # parse each option and use the default value specified
19
- # in the options as the value to be passed to the test plugin
20
- #
21
- Hash[ *File.open(options) { |f| YAML.load(f) }["options"].
22
- map { |name, details| [name, details["default"]] }.flatten ]
13
+
14
+ options_for_run = {}
15
+
16
+ # deal with embedded options yaml
17
+ if options_yaml = Scout::Plugin.extract_options_yaml_from_code(plugin_code)
18
+ options=Scout::PluginOptions.from_yaml(options_yaml)
19
+
20
+ if options.error
21
+ puts "Problem parsing option definition in the plugin code (ignoring and continuing):"
22
+ puts options_yaml
23
+ else
24
+ puts "== Plugin options: "
25
+ puts options.to_s
26
+ options.select{|o|o.has_default?}.each{|o|options_for_run[o.name]=o.default}
27
+ end
28
+ else
29
+ puts "== This plugin doesn't have option metadata."
30
+ end
31
+
32
+ # provided_options are what the user gave us in the command line. Here, we merge them into
33
+ # the defaults we've already established (if any) for this run.
34
+ provided_options.each do |e|
35
+ if e.include?('=')
36
+ k,v=e.split('=',2)
37
+ options_for_run[k]=v
38
+ else
39
+ puts "ERROR: Option '#{e}' is no good -- provided options should be in the format name=value."
40
+ end
41
+ end
42
+ if options_for_run.any?
43
+ puts "== Running plugin with: #{options_for_run.to_a.map{|a| "#{a.first}=#{a.last}"}.join('; ') }"
23
44
  else
24
- Hash.new
45
+ puts "== You haven't provided any options for running this plugin."
25
46
  end
26
47
 
27
48
  Scout::Server.new(nil, nil, history, log) do |scout|
@@ -30,8 +51,9 @@ module Scout
30
51
  'plugin_id' => 1,
31
52
  'name' => "Local Plugin",
32
53
  'code' => plugin_code,
33
- 'options' => plugin_options,
54
+ 'options' => options_for_run,
34
55
  'path' => plugin )
56
+ puts "== Output:"
35
57
  scout.show_checkin(:pp)
36
58
  end
37
59
  end
data/lib/scout/plugin.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  #!/usr/bin/env ruby -wKU
2
2
 
3
+
3
4
  module Scout
5
+
4
6
  class Plugin
7
+
8
+ EMBEDDED_OPTIONS_REGEX = /OPTIONS ?= ?<<-?([A-Z_]+)(.*)\1/m
9
+
5
10
  class << self
6
11
  attr_accessor :last_defined
7
12
 
@@ -32,6 +37,18 @@ module Scout
32
37
  needs.push(*libraries.flatten)
33
38
  end
34
39
  end
40
+
41
+ # true if the code seems to have embedded options
42
+ def has_embedded_options?(code)
43
+ code =~ EMBEDDED_OPTIONS_REGEX
44
+ end
45
+
46
+ # extracts the internal YAML, if any, and returns the YAML string.
47
+ # returns nil if no embedded options.
48
+ def extract_options_yaml_from_code(code)
49
+ code =~ EMBEDDED_OPTIONS_REGEX
50
+ return $2
51
+ end
35
52
  end
36
53
 
37
54
  # Creates a new Scout Plugin to run.
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ require 'yaml'
4
+
5
+ module Scout
6
+ # a data structure of an individual plugin option
7
+ class PluginOption
8
+ attr_reader :name, :notes, :default, :advanced, :password, :required
9
+ def initialize(name, h)
10
+ @name=name
11
+ @notes=h['notes'] || ''
12
+ @default=h['default'] || ''
13
+ @attributes=h['attributes'] || ''
14
+ @advanced = @attributes.include?('advanced')
15
+ @password = @attributes.include?('password')
16
+ @required = @attributes.include?('required')
17
+ end
18
+
19
+ # convenience -- for nicer syntax
20
+ def advanced?; @advanced; end
21
+ def password?; @password; end
22
+ def required?; @required; end
23
+ def has_default?; default != '';end
24
+
25
+ def to_s
26
+ required_string = required? ? " (required). " : ""
27
+ default_string = default == '' ? '' : " Default: #{default}. "
28
+ "'#{name}'#{required_string}#{default_string}#{notes}"
29
+ end
30
+ end
31
+
32
+ # A collection of pluginOption
33
+ # Create: opts=PluginOptions.from_yaml(yaml_string)
34
+ # Check if there were any problems -- opts.error -- should be nil.
35
+ #
36
+ # A valid options yaml looks like this:
37
+ # max_swap_used:
38
+ # notes: If swap is larger than this amount, an alert is generated. Amount should be in MB.
39
+ # default: 2048 # 2 GB
40
+ # max_swap_ratio:
41
+ # notes: If swap used over memory used is larger than this amount, an alert is generated
42
+ # default: 3
43
+ # attributes: required advanced
44
+ class PluginOptions < Array
45
+
46
+ attr_accessor :error
47
+
48
+ # Should be valid YAML, a hash of hashes ... if not, will be caught in the rescue below
49
+ def self.from_yaml(string)
50
+ options_array=[]
51
+ error=nil
52
+
53
+ items=YAML.load(string)
54
+ items.each_pair {|name, hash| options_array.push(PluginOption.new(name,hash)) }
55
+ rescue
56
+ error="Invalid Plugin Options"
57
+ ensure
58
+ res=PluginOptions.new(options_array)
59
+ res.error=error
60
+ return res
61
+ end
62
+
63
+ def advanced
64
+ select{|o|o.advanced? }
65
+ end
66
+
67
+ def regular
68
+ select{|o|!o.advanced? }
69
+ end
70
+
71
+ def to_s
72
+ res=[]
73
+ each_with_index do |opt,i|
74
+ res.push "#{i+1}. #{opt.to_s}"
75
+ end
76
+ res.join("\n")
77
+ end
78
+
79
+ end
80
+ end
data/lib/scout/server.rb CHANGED
@@ -34,7 +34,10 @@ module Scout
34
34
  # We consider the interval close enough at this point.
35
35
  #
36
36
  RUN_DELTA = 30
37
-
37
+
38
+ attr_reader :new_plan
39
+ attr_reader :directives
40
+
38
41
  # Creates a new Scout Server connection.
39
42
  def initialize(server, client_key, history_file, logger = nil)
40
43
  @server = server
@@ -42,56 +45,116 @@ module Scout
42
45
  @history_file = history_file
43
46
  @history = Hash.new
44
47
  @logger = logger
45
-
48
+ @plugin_plan = []
49
+ @directives = {} # take_snapshots
50
+ @new_plan = false
51
+
52
+ # the block is only passed for install and test, since we split plan retrieval outside the lockfile for run
46
53
  if block_given?
47
54
  load_history
48
55
  yield self
49
56
  save_history
50
57
  end
51
58
  end
52
-
53
- # Prepares a check-in data structure to hold Plugin generated data.
54
- def prepare_checkin
55
- @checkin = { :reports => Array.new,
56
- :alerts => Array.new,
57
- :errors => Array.new,
58
- :summaries => Array.new }
59
- end
60
-
61
- def show_checkin(printer = :p)
62
- send(printer, @checkin)
63
- end
64
-
65
- #
66
- # Loads the history file from disk. If the file does not exist,
67
- # it creates one.
59
+
68
60
  #
69
- def load_history
70
- unless File.exist? @history_file
71
- debug "Creating empty history file..."
72
- File.open(@history_file, "w") do |file|
73
- YAML.dump({"last_runs" => Hash.new, "memory" => Hash.new}, file)
61
+ # Retrieves the Plugin Plan from the server. This is the list of plugins
62
+ # to execute, along with all options.
63
+ #
64
+ # This method has a couple of side effects:
65
+ # 1) it sets the @plugin plan with either A) whatever is in history, B) the results of the /plan retrieval
66
+ # 2) it sets @checkin_to = true IF so directed by the scout server
67
+ def fetch_plan
68
+ url = urlify(:plan)
69
+ info "Pinging server at #{url}..."
70
+ headers = Hash.new
71
+ if @history["plan_last_modified"] and @history["old_plugins"]
72
+ headers["If-Modified-Since"] = @history["plan_last_modified"]
73
+ end
74
+ get(url, "Could not retrieve plan from server.", headers) do |res|
75
+ if res.is_a? Net::HTTPNotModified
76
+ info "Plan not modified. Will reuse saved plan."
77
+ @plugin_plan = Array(@history["old_plugins"])
78
+ @directives = @history["directives"] || Hash.new
79
+ else
80
+ info "plan has been modified. Will run the new plan now."
81
+ begin
82
+ body = res.body
83
+ if res["Content-Encoding"] == "gzip" and body and not body.empty?
84
+ body = Zlib::GzipReader.new(StringIO.new(body)).read
85
+ end
86
+
87
+ body_as_hash = JSON.parse(body)
88
+ @plugin_plan = Array(body_as_hash["plugins"])
89
+ @directives = body_as_hash["directives"].is_a?(Hash) ? body_as_hash["directives"] : Hash.new
90
+
91
+ @history["plan_last_modified"] = res["last-modified"]
92
+ @history["old_plugins"] = @plugin_plan
93
+ @history["directives"] = @directives
94
+
95
+ info "Plan loaded. (#{@plugin_plan.size} plugins: " +
96
+ "#{@plugin_plan.map { |p| p['name'] }.join(', ')})" +
97
+ ". Directives: #{@directives.to_a.map{|a| "#{a.first}:#{a.last}"}.join(", ")}"
98
+
99
+ @new_plan = true # used in determination if we should checkin this time or not
100
+ rescue Exception
101
+ fatal "Plan from server was malformed."
102
+ exit
103
+ end
74
104
  end
75
- info "History file created."
76
105
  end
77
- debug "Loading history file..."
78
- @history = File.open(@history_file) { |file| YAML.load(file) }
79
- info "History file loaded."
80
106
  end
81
-
82
- # Saves the history file to disk.
83
- def save_history
84
- debug "Saving history file..."
85
- File.open(@history_file, "w") { |file| YAML.dump(@history, file) }
86
- info "History file saved."
107
+
108
+ # uses values from history and current time to determine if we should checkin at this time
109
+ def time_to_checkin?
110
+ @history['last_checkin'] == nil ||
111
+ @directives['interval'] == nil ||
112
+ (Time.now.to_i - Time.at(@history['last_checkin']).to_i).abs+15 > @directives['interval'].to_i*60
113
+ rescue
114
+ debug "Failed to calculate time_to_checkin. @history['last_checkin']=#{@history['last_checkin']}. "+
115
+ "@directives['interval']=#{@directives['interval']}. Time.now.to_i=#{Time.now.to_i}"
116
+ return true
87
117
  end
88
-
89
- # Runs all plugins from a given plan. Calls process_plugin on each plugin.
118
+
119
+ # uses values from history and current time to determine if we should ping the server at this time
120
+ def time_to_ping?
121
+ return true if
122
+ @history['last_ping'] == nil ||
123
+ @directives['ping_interval'] == nil ||
124
+ (Time.now.to_i - Time.at(@history['last_ping']).to_i).abs+15 > @directives['ping_interval'].to_i*60
125
+ rescue
126
+ debug "Failed to calculate time_to_ping. @history['last_ping']=#{@history['last_ping']}. "+
127
+ "@directives['ping_interval']=#{@directives['ping_interval']}. Time.now.to_i=#{Time.now.to_i}"
128
+ return true
129
+ end
130
+
131
+ # returns a human-readable representation of the next checkin, i.e., 5min 30sec
132
+ def next_checkin
133
+ secs= @directives['interval'].to_i*60 - (Time.now.to_i - Time.at(@history['last_checkin']).to_i).abs
134
+ minutes=(secs.to_f/60).floor
135
+ secs=secs%60
136
+ "#{minutes}min #{secs} sec"
137
+ rescue
138
+ "[next scout invocation]"
139
+ end
140
+
141
+ # Runs all plugins from the given plan. Calls process_plugin on each plugin.
142
+ # @plugin_execution_plan is populated by calling fetch_plan
90
143
  def run_plugins_by_plan
91
144
  prepare_checkin
92
- plan do |plugin|
93
- process_plugin(plugin)
145
+ @plugin_plan.each do |plugin|
146
+ begin
147
+ process_plugin(plugin)
148
+ rescue Exception
149
+ @checkin[:errors] << build_report(
150
+ plugin['id'],
151
+ :subject => "Exception: #{$!.message}.",
152
+ :body => $!.backtrace
153
+ )
154
+ error("Encountered an error: #{$!.message}")
155
+ end
94
156
  end
157
+ take_snapshot if @directives['take_snapshots']
95
158
  checkin
96
159
  end
97
160
 
@@ -105,7 +168,7 @@ module Scout
105
168
  # set memory and last_run information in the history file.
106
169
  #
107
170
  def process_plugin(plugin)
108
- info "Processing the #{plugin['name']} plugin:"
171
+ info "Processing the '#{plugin['name']}' plugin:"
109
172
  id_and_name = "#{plugin['id']}-#{plugin['name']}".sub(/\A-/, "")
110
173
  last_run = @history["last_runs"][id_and_name] ||
111
174
  @history["last_runs"][plugin['name']]
@@ -126,6 +189,7 @@ module Scout
126
189
  rescue Exception
127
190
  raise if $!.is_a? SystemExit
128
191
  error "Plugin would not compile: #{$!.message}"
192
+ @checkin[:errors] << build_report(plugin['id'],:subject => "Plugin would not compile", :body=>$!.message)
129
193
  return
130
194
  end
131
195
  debug "Loading plugin..."
@@ -140,13 +204,19 @@ module Scout
140
204
  Timeout.timeout(timeout, PluginTimeoutError) do
141
205
  data = job.run
142
206
  end
143
- rescue Timeout::Error
207
+ rescue Timeout::Error, PluginTimeoutError
144
208
  error "Plugin took too long to run."
209
+ @checkin[:errors] << build_report(plugin['id'],
210
+ :subject => "Plugin took too long to run",
211
+ :body=>"Execution timed out.")
145
212
  return
146
213
  rescue Exception
147
214
  raise if $!.is_a? SystemExit
148
215
  error "Plugin failed to run: #{$!.class}: #{$!.message}\n" +
149
216
  "#{$!.backtrace.join("\n")}"
217
+ @checkin[:errors] << build_report(plugin['id'],
218
+ :subject => "Plugin failed to run",
219
+ :body=>"#{$!.class}: #{$!.message}\n#{$!.backtrace.join("\n")}")
150
220
  end
151
221
  info "Plugin completed its run."
152
222
 
@@ -168,7 +238,7 @@ module Scout
168
238
  @history["memory"][id_and_name] = data[:memory]
169
239
  else
170
240
  @checkin[:errors] << build_report(
171
- plugin_id['id'],
241
+ plugin['id'],
172
242
  :subject => "Plugin would not load."
173
243
  )
174
244
  end
@@ -189,57 +259,62 @@ module Scout
189
259
  error "Unable to remove plugin."
190
260
  end
191
261
  end
192
- info "Plugin #{plugin['name']} processing complete."
262
+ info "Plugin '#{plugin['name']}' processing complete."
193
263
  end
194
-
195
- #
196
- # Retrieves the Plugin Plan from the server. This is the list of plugins
197
- # to execute, along with all options.
198
- #
199
- def plan
200
- url = urlify(:plan)
201
- info "Loading plan from #{url}..."
202
- headers = Hash.new
203
- if @history["last_modified_for_plugins"] and @history["old_plugins"]
204
- headers["If-Modified-Since"] = @history["last_modified_for_plugins"]
264
+
265
+
266
+ # captures a list of processes running at this moment
267
+ def take_snapshot
268
+ info "Taking a process snapshot"
269
+ ps=%x(ps aux).split("\n")[1..-1].join("\n") # get rid of the header line
270
+ @checkin[:snapshot]=ps
271
+ rescue Exception
272
+ error "unable to capture processes on this server. #{$!.message}"
273
+ return nil
274
+ end
275
+
276
+ # Prepares a check-in data structure to hold Plugin generated data.
277
+ def prepare_checkin
278
+ @checkin = { :reports => Array.new,
279
+ :alerts => Array.new,
280
+ :errors => Array.new,
281
+ :summaries => Array.new,
282
+ :snapshot => '' }
283
+ end
284
+
285
+ def show_checkin(printer = :p)
286
+ send(printer, @checkin)
287
+ end
288
+
289
+ #
290
+ # Loads the history file from disk. If the file does not exist,
291
+ # it creates one.
292
+ #
293
+ def load_history
294
+ unless File.exist? @history_file
295
+ create_blank_history
205
296
  end
206
- get(url, "Could not retrieve plan from server.", headers) do |res|
207
- if res.is_a? Net::HTTPNotModified
208
- info "Plan not modified. Reusing saved plan."
209
- plugin_execution_plan = Array(@history["old_plugins"])
210
- else
211
- begin
212
- body = res.body
213
- if res["Content-Encoding"] == "gzip" and body and not body.empty?
214
- body = Zlib::GzipReader.new(StringIO.new(body)).read
215
- end
216
- plugin_execution_plan = Array(JSON.parse(body)["plugins"])
217
- if res["Last-Modified"]
218
- @history["last_modified_for_plugins"] = res["last-modified"]
219
- @history["old_plugins"] = plugin_execution_plan
220
- end
221
- info "Plan loaded. (#{plugin_execution_plan.size} plugins: " +
222
- "#{plugin_execution_plan.map { |p| p['name'] }.join(', ')})"
223
- rescue Exception
224
- fatal "Plan from server was malformed."
225
- exit
226
- end
227
- end
228
- plugin_execution_plan.each do |plugin|
229
- begin
230
- yield plugin if block_given?
231
- rescue RuntimeError
232
- @checkin[:errors] << build_report(
233
- plugin_id['id'],
234
- :subject => "Exception: #{$!.message}.",
235
- :body => $!.backtrace
236
- )
237
- end
238
- end
297
+ debug "Loading history file..."
298
+ @history = File.open(@history_file) { |file| YAML.load(file) }
299
+ info "History file loaded."
300
+ end
301
+
302
+ # creates a blank history file
303
+ def create_blank_history
304
+ debug "Creating empty history file..."
305
+ File.open(@history_file, "w") do |file|
306
+ YAML.dump({"last_runs" => Hash.new, "memory" => Hash.new}, file)
239
307
  end
308
+ info "History file created."
309
+ end
310
+
311
+ # Saves the history file to disk.
312
+ def save_history
313
+ debug "Saving history file..."
314
+ File.open(@history_file, "w") { |file| YAML.dump(@history, file) }
315
+ info "History file saved."
240
316
  end
241
- alias_method :test, :plan
242
-
317
+
243
318
  private
244
319
 
245
320
  def build_report(plugin_id, fields)
@@ -293,8 +368,9 @@ module Scout
293
368
  when Net::HTTPSuccess, Net::HTTPNotModified
294
369
  response_handler[response] unless response_handler.nil?
295
370
  else
371
+ error = "Server says: #{response['x-scout-msg']}" if response['x-scout-msg']
296
372
  fatal error
297
- exit
373
+ raise SystemExit.new(error)
298
374
  end
299
375
  rescue Timeout::Error
300
376
  fatal "Request timed out."
@@ -306,6 +382,7 @@ module Scout
306
382
  end
307
383
 
308
384
  def checkin
385
+ @history['last_checkin'] = Time.now.to_i # might have to save the time of invocation and use here to prevent drift
309
386
  io = StringIO.new
310
387
  gzip = Zlib::GzipWriter.new(io)
311
388
  gzip << @checkin.to_json
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.2
4
+ version: 5.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Highgroove Studios
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-07 00:00:00 -05:00
12
+ date: 2009-12-23 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -46,18 +46,12 @@ files:
46
46
  - lib/scout/command/test.rb
47
47
  - lib/scout/command.rb
48
48
  - lib/scout/plugin.rb
49
+ - lib/scout/plugin_options.rb
49
50
  - lib/scout/server.rb
50
51
  - lib/scout.rb
51
52
  - data/cacert.pem
52
53
  - data/gpl-2.0.txt
53
54
  - data/lgpl-2.1.txt
54
- - vendor/json_pure/CHANGES
55
- - vendor/json_pure/GPL
56
- - vendor/json_pure/README
57
- - vendor/json_pure/RUBY
58
- - vendor/json_pure/Rakefile
59
- - vendor/json_pure/TODO
60
- - vendor/json_pure/VERSION
61
55
  - vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkComparison.log
62
56
  - vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_fast-autocorrelation.dat
63
57
  - vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_fast.dat
@@ -93,6 +87,7 @@ files:
93
87
  - vendor/json_pure/benchmarks/parser_benchmark.rb
94
88
  - vendor/json_pure/bin/edit_json.rb
95
89
  - vendor/json_pure/bin/prettify_json.rb
90
+ - vendor/json_pure/CHANGES
96
91
  - vendor/json_pure/data/example.json
97
92
  - vendor/json_pure/data/index.html
98
93
  - vendor/json_pure/data/prototype.js
@@ -106,26 +101,30 @@ files:
106
101
  - vendor/json_pure/ext/json/ext/parser/parser.rl
107
102
  - vendor/json_pure/ext/json/ext/parser/unicode.c
108
103
  - vendor/json_pure/ext/json/ext/parser/unicode.h
104
+ - vendor/json_pure/GPL
109
105
  - vendor/json_pure/install.rb
110
- - vendor/json_pure/lib/json/Array.xpm
111
- - vendor/json_pure/lib/json/FalseClass.xpm
112
- - vendor/json_pure/lib/json/Hash.xpm
113
- - vendor/json_pure/lib/json/Key.xpm
114
- - vendor/json_pure/lib/json/NilClass.xpm
115
- - vendor/json_pure/lib/json/Numeric.xpm
116
- - vendor/json_pure/lib/json/String.xpm
117
- - vendor/json_pure/lib/json/TrueClass.xpm
118
106
  - vendor/json_pure/lib/json/add/core.rb
119
107
  - vendor/json_pure/lib/json/add/rails.rb
108
+ - vendor/json_pure/lib/json/Array.xpm
120
109
  - vendor/json_pure/lib/json/common.rb
121
110
  - vendor/json_pure/lib/json/editor.rb
122
111
  - vendor/json_pure/lib/json/ext.rb
112
+ - vendor/json_pure/lib/json/FalseClass.xpm
113
+ - vendor/json_pure/lib/json/Hash.xpm
123
114
  - vendor/json_pure/lib/json/json.xpm
115
+ - vendor/json_pure/lib/json/Key.xpm
116
+ - vendor/json_pure/lib/json/NilClass.xpm
117
+ - vendor/json_pure/lib/json/Numeric.xpm
124
118
  - vendor/json_pure/lib/json/pure/generator.rb
125
119
  - vendor/json_pure/lib/json/pure/parser.rb
126
120
  - vendor/json_pure/lib/json/pure.rb
121
+ - vendor/json_pure/lib/json/String.xpm
122
+ - vendor/json_pure/lib/json/TrueClass.xpm
127
123
  - vendor/json_pure/lib/json/version.rb
128
124
  - vendor/json_pure/lib/json.rb
125
+ - vendor/json_pure/Rakefile
126
+ - vendor/json_pure/README
127
+ - vendor/json_pure/RUBY
129
128
  - vendor/json_pure/tests/fixtures/fail1.json
130
129
  - vendor/json_pure/tests/fixtures/fail10.json
131
130
  - vendor/json_pure/tests/fixtures/fail11.json
@@ -163,8 +162,10 @@ files:
163
162
  - vendor/json_pure/tests/test_json_generate.rb
164
163
  - vendor/json_pure/tests/test_json_rails.rb
165
164
  - vendor/json_pure/tests/test_json_unicode.rb
165
+ - vendor/json_pure/TODO
166
166
  - vendor/json_pure/tools/fuzz.rb
167
167
  - vendor/json_pure/tools/server.rb
168
+ - vendor/json_pure/VERSION
168
169
  - Rakefile
169
170
  - AUTHORS
170
171
  - COPYING