scout 4.0.2 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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