rails_analyzer_tools 1.0.0 → 1.1.0

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.
@@ -1,6 +1,12 @@
1
1
  Manifest.txt
2
2
  Rakefile
3
3
  README
4
+ bin/bench
5
+ bin/crawl
4
6
  bin/rails_stat
5
7
  lib/io_tail.rb
6
- lib/rails_stat.rb
8
+ lib/analyzer_tools/bench.rb
9
+ lib/analyzer_tools/crawl.rb
10
+ lib/analyzer_tools/rails_stat.rb
11
+ lib/analyzer_tools/syslog_logger.rb
12
+ test/test_syslog_logger.rb
data/README CHANGED
@@ -1,15 +1,61 @@
1
- Currently, Rails Analyzer Tools contains just one tool, RailsStat, and one
2
- library extension, IOTail.
1
+ Rails Analyzer Tools contains Bench, Crawler, RailsStat, IOTail and
2
+ SyslogLogger libraries, and the programs bench, crawl and rails_stat based on
3
+ these libraries.
4
+
5
+ I have used bench and crawl to tune the number of FastCGI processes running on
6
+ 43things.com, determine if HyperThreading would give a performance benefit or
7
+ not (it did) and find a fatal threading bug in MySQL.
8
+
9
+ rails_stat is useful to see how much traffic your Rails site is doing.
10
+
11
+ == Bench
12
+
13
+ Bench lets you benchmark the performance of a particular page. Simply give
14
+ the URL, the number of requests to run and the number of threads to run in
15
+ parallel.
16
+
17
+ You really, really, really don't want to run bench against a live website.
18
+
19
+ $ bench -u http://coop.robotcoop.com/ -r 50 -c 2
20
+ 50....45....40....35....30....25....20....15....10....5....
21
+ Total time: 10.7073893547058
22
+ Average time: 0.214147787094116
23
+
24
+ == Crawler
25
+
26
+ Crawler lets you exercise a server by crawling it really fast. It picks URLs
27
+ at random from the returned page and always stays on the same host. When you
28
+ kill it with a ^C it prints out a summary.
29
+
30
+ You really, really, really don't want to run crawl against a live website.
31
+
32
+ $ crawl -u http://coop.robotcoop.com/ -c 2
33
+ >>> http://coop.robotcoop.com/
34
+ GET / HTTP/1.0
35
+ Host: coop.robotcoop.com
36
+ User-Agent: RubyCrawl
37
+
38
+ ...
39
+
40
+ ^C
41
+ Total time: 0.355171680450439
42
+ Requests: 3
43
+ Average time: 0.118390560150146
3
44
 
4
45
  == RailsStat
5
46
 
6
47
  RailsStat displays the approximate number of requests, queries, and lines
7
- logged per second in 10 second intervals. Simply give it the path to your
8
- production log for a live Rails site and you're done:
48
+ logged per second in 10 (or whatever) second intervals. Simply give it the
49
+ path to your production log for a live Rails site and you're done:
9
50
 
10
51
  $ rails_stat /var/log/production.log
11
52
  ~ 2.1 req/sec, 23.0 queries/sec, 32.8 lines/sec
12
53
 
54
+ == SyslogLogger
55
+
56
+ SyslogLogger is a Logger replacement that logs to syslog. It is almost
57
+ drop-in with a few caveats.
58
+
13
59
  == IOTail
14
60
 
15
61
  IOTail tails a file like the tail system utility. This lets you collect data
data/Rakefile CHANGED
@@ -8,55 +8,54 @@ require 'rake/contrib/sshpublisher'
8
8
  $VERBOSE = nil
9
9
 
10
10
  spec = Gem::Specification.new do |s|
11
- s.name = "rails_analyzer_tools"
12
- s.version = "1.0.0"
13
- s.summary = "Various tools and toys"
14
- s.author = "Eric Hodel"
15
- s.email = "eric@robotcoop.com"
16
-
17
- s.has_rdoc = true
18
- s.files = File.read("Manifest.txt").split($/)
19
- s.require_path = 'lib'
20
- s.executables = ["rails_stat"]
11
+ s.name = 'rails_analyzer_tools'
12
+ s.version = '1.1.0'
13
+ s.summary = 'Tools for analyzing the performance of web sites.'
14
+ s.author = 'Eric Hodel'
15
+ s.email = 'eric@robotcoop.com'
16
+
17
+ s.has_rdoc = true
18
+ s.files = File.read('Manifest.txt').split($/)
19
+ s.require_path = 'lib'
20
+ s.executables = %w[rails_stat bench crawl]
21
21
  end
22
22
 
23
- desc "Run tests"
23
+ desc 'Run tests'
24
24
  task :default => [ :test ]
25
25
 
26
- Rake::TestTask.new("test") do |t|
27
- t.libs << "test"
28
- t.libs << "lib"
29
- t.pattern = "test/test_*.rb"
30
- t.verbose = true
26
+ Rake::TestTask.new('test') do |t|
27
+ t.libs << 'test'
28
+ t.libs << 'lib'
29
+ t.pattern = 'test/test_*.rb'
30
+ t.verbose = true
31
31
  end
32
32
 
33
- desc "Generate RDoc"
33
+ desc 'Generate RDoc'
34
34
  Rake::RDocTask.new :rdoc do |rd|
35
- rd.rdoc_dir = "doc"
36
- rd.rdoc_files.add "lib", "README"
37
- rd.main = "README"
38
- rd.options << "-d" if `which dot` =~ /\/dot/
35
+ rd.rdoc_dir = 'doc'
36
+ rd.rdoc_files.add 'lib', 'README'
37
+ rd.main = 'README'
38
+ rd.options << '-d' if `which dot` =~ /\/dot/
39
39
  end
40
40
 
41
- desc "Build Gem"
41
+ desc 'Build Gem'
42
42
  Rake::GemPackageTask.new spec do |pkg|
43
- pkg.need_zip = true
44
- pkg.need_tar = true
43
+ pkg.need_tar = true
45
44
  end
46
45
 
47
- desc "Sends RDoc to RubyForge"
46
+ desc 'Sends RDoc to RubyForge'
48
47
  task :send_rdoc => [ :rerdoc ] do
49
- publisher = Rake::SshDirPublisher.new('drbrain@rubyforge.org',
50
- '/var/www/gforge-projects/rails-analyzer/hacks',
51
- 'doc')
52
- publisher.upload
48
+ publisher = Rake::SshDirPublisher.new('drbrain@rubyforge.org',
49
+ '/var/www/gforge-projects/rails-analyzer/hacks',
50
+ 'doc')
51
+ publisher.upload
53
52
  end
54
53
 
55
- desc "Clean up"
54
+ desc 'Clean up'
56
55
  task :clean => [ :clobber_rdoc, :clobber_package ]
57
56
 
58
- desc "Clean up"
57
+ desc 'Clean up'
59
58
  task :clobber => [ :clean ]
60
59
 
61
- # vim: ts=4 sts=4 sw=4 syntax=Ruby
60
+ # vim: syntax=Ruby
62
61
 
@@ -0,0 +1,53 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ Thread.abort_on_exception = true
4
+
5
+ require 'optparse'
6
+ require 'analyzer_tools/bench'
7
+
8
+ OptionParser.accept URI do |u,|
9
+ begin
10
+ URI.parse u if u
11
+ rescue ArgumentError
12
+ raise OptionParser::InvalidArgument, u
13
+ end
14
+ end
15
+
16
+ uri = nil
17
+ requests = nil
18
+ concurrency = nil
19
+
20
+ opts = OptionParser.new do |opts|
21
+ opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
22
+ opts.separator ""
23
+
24
+ opts.on("-u", "--url URL", URI, "URL to access") do |val|
25
+ uri = val
26
+ end
27
+
28
+ opts.on("-r", "--requests REQUESTS", Integer, "Requests to send") do |val|
29
+ requests = val
30
+ end
31
+
32
+ opts.separator ""
33
+
34
+ opts.on("-c", "--concurrency THREADS", Integer,
35
+ "Concurrent requests to make") do |val|
36
+ concurrency = val
37
+ end
38
+
39
+ end
40
+
41
+ opts.parse!
42
+
43
+ if uri.nil? or requests.nil? then
44
+ puts opts
45
+ exit
46
+ end
47
+
48
+ bench = Bench.new uri, requests, concurrency
49
+ times = bench.run
50
+
51
+ puts "Total time: #{times.inject(0) { |a,t| a + t }}"
52
+ puts "Average time: #{times.inject(0) { |a,t| a + t } / times.length}"
53
+
@@ -0,0 +1,57 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ Thread.abort_on_exception = true
4
+
5
+ require 'optparse'
6
+ require 'analyzer_tools/crawl'
7
+
8
+ OptionParser.accept URI do |u,|
9
+ begin
10
+ URI.parse u if u
11
+ rescue ArgumentError
12
+ raise OptionParser::InvalidArgument, u
13
+ end
14
+ end
15
+
16
+ start_url = nil
17
+ concurrency = 1
18
+
19
+ opts = OptionParser.new do |opts|
20
+ opts.banner = "Usage: #{$PROGRAM_NAME} -u URL [-c CONCURRENCY]"
21
+ opts.separator ""
22
+
23
+ opts.on("-u", "--url URL", URI, "starting URL") do |val|
24
+ start_url = val
25
+ start_url.path = '/' if start_url.path.empty?
26
+ end
27
+
28
+ opts.separator ""
29
+
30
+ opts.on("-c", "--concurrency THREADS", Integer,
31
+ "Concurrent requests to make") do |val|
32
+ concurrency = val
33
+ end
34
+
35
+ end
36
+
37
+ opts.parse!
38
+
39
+ if start_url.nil? then
40
+ STDERR.puts opts
41
+ exit 1
42
+ end
43
+
44
+ crawler = Crawler.new start_url, concurrency
45
+
46
+ trap 'INT' do
47
+ crawler.stop
48
+ times = crawler.times
49
+ puts
50
+ puts "Total time: #{times.inject(0) { |a,t| a + t }}"
51
+ puts "Requests: #{times.length}"
52
+ puts "Average time: #{times.inject(0) { |a,t| a + t } / times.length}"
53
+ exit
54
+ end
55
+
56
+ crawler.run
57
+
@@ -1,14 +1,15 @@
1
1
  #!/usr/local/bin/ruby -w
2
2
 
3
3
  require 'io_tail'
4
- require 'rails_stat'
4
+ require 'analyzer_tools/rails_stat'
5
5
 
6
6
  filename = ARGV.shift
7
+ interval = ARGV.shift || 10
7
8
 
8
9
  if filename.nil? then
9
- STDERR.puts "Usage: #{$0} RAILS_LOG"
10
+ STDERR.puts "Usage: #{$0} RAILS_LOG [PRINT_INTERVAL]"
10
11
  exit 1
11
12
  end
12
13
 
13
- RailsStat.start filename
14
+ RailsStat.start filename, interval
14
15
 
@@ -0,0 +1,97 @@
1
+ require 'socket'
2
+ require 'thread'
3
+ require 'uri'
4
+
5
+ ##
6
+ # Bench measures load time for a particular page. You can run it
7
+ # multi-threaded to test web server performance, or single threaded to test
8
+ # the performance of a single page.
9
+
10
+ class Bench
11
+
12
+ ##
13
+ # Creates a new Bench instance that will +requests+ fetches of +uri+ using
14
+ # +thread+ concurrent threads.
15
+
16
+ def initialize(uri, requests, threads = 1)
17
+ raise ArgumentError, "Thread count must be more than 0" if threads < 1
18
+ @uri = uri
19
+ @total_requests = requests
20
+ @tenths = @total_requests > 10 ? @total_requests / 10 : 1
21
+ @hundredths = @total_requests > 100 ? @total_requests / 100 : 1
22
+ @num_requests = requests
23
+ @threads = threads
24
+ end
25
+
26
+ ##
27
+ # Starts the benchmark. Returns an Array of request times in seconds.
28
+
29
+ def run
30
+ done = false
31
+ times = []
32
+ threads = ThreadGroup.new
33
+ count_m = Mutex.new
34
+
35
+ @threads.times do
36
+ Thread.start do
37
+ threads.add Thread.current
38
+ until @num_requests <= 0 do
39
+ count_m.synchronize do
40
+ if @num_requests % @tenths == 0 then
41
+ print @num_requests
42
+ elsif @num_requests % @hundredths == 0 then
43
+ print '.'
44
+ end
45
+ @num_requests -= 1
46
+ end
47
+ STDOUT.flush
48
+ times << time_request
49
+ end
50
+ end
51
+ Thread.pass
52
+ end
53
+
54
+ threads.enclose
55
+
56
+ threads.list.each { |t| t.join }
57
+ puts
58
+
59
+ return times
60
+ end
61
+
62
+ ##
63
+ # Performs a request.
64
+
65
+ def do_request
66
+ s = TCPSocket.new @uri.host, @uri.port
67
+ s.puts "GET #{@uri.request_uri} HTTP/1.0\r\n"
68
+ s.puts "Host: #{@uri.host}\r\n"
69
+ s.puts "User-Agent: RubyBench\r\n"
70
+ s.puts "\r\n"
71
+ s.flush
72
+ response = s.read
73
+ ensure
74
+ s.close unless s.nil?
75
+ end
76
+
77
+ ##
78
+ # Returns the amount of time taken to execute the given block.
79
+
80
+ def time
81
+ start_time = Time.now.to_f
82
+ yield
83
+ end_time = Time.now.to_f
84
+ return end_time - start_time
85
+ end
86
+
87
+ ##
88
+ # Returns the time taken to perform a request.
89
+
90
+ def time_request
91
+ time do
92
+ do_request
93
+ end
94
+ end
95
+
96
+ end
97
+
@@ -0,0 +1,142 @@
1
+ require 'socket'
2
+ require 'thread'
3
+ require 'uri'
4
+
5
+ ##
6
+ # A fast web crawler that stays on the site it started from. Crawler
7
+ # randomly picks a URL from the page retrieved and follows it. If
8
+ # can't find a URL for the next page, Crawler starts over from the
9
+ # beginning.
10
+ #
11
+ # Crawler is multi-threaded and can run as many threads as you
12
+ # choose.
13
+
14
+ class Crawler
15
+
16
+ ##
17
+ # Array of response times in seconds.
18
+
19
+ attr_reader :times
20
+
21
+ ##
22
+ # Creates a new Crawler that will start at +start_url+ and run +threads+
23
+ # concurrent threads.
24
+
25
+ def initialize(start_url, threads = 1)
26
+ raise ArgumentError, "Thread count must be more than 0" if threads < 1
27
+ @start_url = start_url
28
+ @thread_count = threads
29
+ @threads = ThreadGroup.new
30
+ @times = []
31
+ end
32
+
33
+ ##
34
+ # Begins crawling.
35
+
36
+ def run
37
+ url = @start_url
38
+
39
+ @thread_count.times do
40
+ Thread.start do
41
+ @threads.add Thread.current
42
+ loop do
43
+ puts ">>> #{url}"
44
+ body = timed_request url
45
+ url = extract_url_from body, url
46
+ end
47
+ end
48
+ Thread.pass
49
+ end
50
+
51
+ @threads.list.first.join until @threads.list.empty?
52
+ end
53
+
54
+ ##
55
+ # Stops crawling.
56
+
57
+ def stop
58
+ @threads.list.first.kill until @threads.list.empty?
59
+ end
60
+
61
+ ##
62
+ # Performs a request of +url+ and returns the request body.
63
+
64
+ def do_request(url)
65
+ req = []
66
+ req << "GET #{url.request_uri} HTTP/1.0"
67
+ req << "Host: #{url.host}"
68
+ req << "User-Agent: RubyCrawl"
69
+ req << ""
70
+ req << ""
71
+ req = req.join "\r\n"
72
+ puts req
73
+
74
+ begin
75
+ s = TCPSocket.new url.host, url.port
76
+ s.write req
77
+ s.flush
78
+ response = s.read
79
+ ensure
80
+ s.close unless s.nil?
81
+ end
82
+
83
+ headers, body = response.split(/\r\n\r\n/)
84
+
85
+ headers = headers.split(/\r\n/)
86
+ status = headers.shift
87
+ headers = Hash[*headers.map { |h| h.split ': ', 2 }.flatten]
88
+
89
+ puts status
90
+
91
+ case status
92
+ when / 302 / then
93
+ body = "href=\"#{headers['Location']}\""
94
+ when / 500 / then
95
+ body = "href=\"#{@start_url}\""
96
+ end
97
+
98
+ return body
99
+ end
100
+
101
+ ##
102
+ # Returns the amount of time taken to execute the given block.
103
+
104
+ def time
105
+ start_time = Time.now.to_f
106
+ yield
107
+ end_time = Time.now.to_f
108
+ return end_time - start_time
109
+ end
110
+
111
+ ##
112
+ # Performs a request of +url+ and records the time taken into times.
113
+ # Returns the body of the request.
114
+
115
+ def timed_request(url)
116
+ body = nil
117
+ @times << time { body = do_request(url) }
118
+ return body
119
+ end
120
+
121
+ ##
122
+ # Returns a random URL on the same site as +original_url+ from +body+ using
123
+ # +original_url+ to resolve relative paths. If no URL valid is found then
124
+ # the start URL is returned.
125
+
126
+ def extract_url_from(body, original_url)
127
+ urls = body.scan(/href="(.+?)"/)
128
+ until urls.empty? do
129
+ begin
130
+ rand_url = urls.delete_at(rand(urls.length)).first
131
+ new_url = original_url + rand_url
132
+ return new_url if new_url.host == original_url.host
133
+ rescue URI::InvalidURIError
134
+ retry
135
+ end
136
+ end
137
+
138
+ return @start_url
139
+ end
140
+
141
+ end
142
+
@@ -2,16 +2,16 @@ require 'thread'
2
2
 
3
3
  ##
4
4
  # RailsStat displays a the current requests, queries and lines logged per
5
- # second in 10 second intervals.
5
+ # second. Default interval is 10 seconds.
6
6
 
7
7
  class RailsStat
8
8
 
9
9
  ##
10
- # Starts a new RailsStat for +filename+.
10
+ # Starts a new RailsStat for +filename+ that prints every +interval+ seconds.
11
11
 
12
- def self.start(filename)
12
+ def self.start(filename, interval = 10)
13
13
  File.open filename do |fp|
14
- self.new(fp).start
14
+ self.new(fp).start(interval)
15
15
  end
16
16
  end
17
17
 
@@ -27,20 +27,21 @@ class RailsStat
27
27
  end
28
28
 
29
29
  ##
30
- # Starts the RailsStat running.
30
+ # Starts the RailsStat running. This method never returns.
31
31
 
32
- def start
32
+ def start(interval)
33
33
  trap('INT') { exit }
34
- start_printer
34
+ start_printer interval
35
35
  read_log
36
36
  end
37
37
 
38
38
  private
39
39
 
40
40
  ##
41
- # Starts a thread that prints log information every 10 seconds.
41
+ # Starts a thread that prints log information every +interval+ seconds.
42
42
 
43
- def start_printer
43
+ def start_printer(interval)
44
+ interval = interval.to_f
44
45
  Thread.start do
45
46
  last_len = 0
46
47
  lines_sec = 0
@@ -48,12 +49,12 @@ class RailsStat
48
49
  queries_sec = 0
49
50
 
50
51
  loop do
51
- sleep 10
52
+ sleep interval
52
53
 
53
54
  @mutex.synchronize do
54
- lines_sec = @lines / 10.0
55
- count_sec = @count / 10.0
56
- queries_sec = @queries / 10.0
55
+ lines_sec = @lines / interval
56
+ count_sec = @count / interval
57
+ queries_sec = @queries / interval
57
58
  @lines = 0
58
59
  @count = 0
59
60
  @queries = 0
@@ -0,0 +1,171 @@
1
+ require 'syslog'
2
+ require 'logger'
3
+
4
+ ##
5
+ # SyslogLogger is a Logger work-alike that logs via syslog instead of to a
6
+ # file. You can add SyslogLogger to your Rails production environment to
7
+ # aggregate logs between multiple machines.
8
+ #
9
+ # By default, SyslogLogger uses the program name 'rails', but this can be
10
+ # changed via the first argument to SyslogLogger.new.
11
+ #
12
+ # NOTE! You can only set the SyslogLogger program name when you initialize
13
+ # SyslogLogger for the first time. This is a limitation of the way
14
+ # SyslogLogger uses syslog (and in some ways, a the way syslog(3) works).
15
+ # Attempts to change SyslogLogger's program name after the first
16
+ # initialization will be ignored.
17
+ #
18
+ # = Sample usage with Rails
19
+ #
20
+ # == config/environment/production.rb
21
+ #
22
+ # Add the following lines:
23
+ #
24
+ # require 'production_log/syslog_logger'
25
+ # RAILS_DEFAULT_LOGGER = SyslogLogger.new
26
+ #
27
+ # == config/environment.rb
28
+ #
29
+ # In 0.10.0, change this line:
30
+ #
31
+ # RAILS_DEFAULT_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log")
32
+ #
33
+ # to:
34
+ #
35
+ # RAILS_DEFAULT_LOGGER ||= Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log")
36
+ #
37
+ # Other versions of Rails should have a similar change.
38
+ #
39
+ # == /etc/syslog.conf
40
+ #
41
+ # Add the following lines:
42
+ #
43
+ # !rails
44
+ # *.* /var/log/production.log
45
+ #
46
+ # Then touch /var/log/production.log and signal syslogd with a HUP
47
+ # (killall -HUP syslogd, on FreeBSD).
48
+ #
49
+ # == /etc/newsyslog.conf
50
+ #
51
+ # Add the following line:
52
+ #
53
+ # /var/log/production.log 640 7 * @T00 Z
54
+ #
55
+ # This creates a log file that is rotated every day at midnight, gzip'd, then
56
+ # kept for 7 days. Consult newsyslog.conf(5) for more details.
57
+ #
58
+ # Now restart your Rails app. Your production logs should now be showing up
59
+ # in /var/log/production.log. If you have mulitple machines, you can log them
60
+ # all to a central machine with remote syslog logging for analysis. Consult
61
+ # your syslogd(8) manpage for further details.
62
+
63
+ class SyslogLogger
64
+
65
+ ##
66
+ # Maps Logger warning types to syslog(3) warning types.
67
+
68
+ LOGGER_MAP = {
69
+ :unknown => :alert,
70
+ :fatal => :err,
71
+ :error => :warning,
72
+ :warn => :notice,
73
+ :info => :info,
74
+ :debug => :debug,
75
+ }
76
+
77
+ ##
78
+ # Maps Logger log levels to their values so we can silence.
79
+
80
+ LOGGER_LEVEL_MAP = {}
81
+
82
+ LOGGER_MAP.each_key do |key|
83
+ LOGGER_LEVEL_MAP[key] = Logger.const_get key.to_s.upcase
84
+ end
85
+
86
+ ##
87
+ # Maps Logger log level values to syslog log levels.
88
+
89
+ LEVEL_LOGGER_MAP = {}
90
+
91
+ LOGGER_LEVEL_MAP.invert.each do |level, severity|
92
+ LEVEL_LOGGER_MAP[level] = LOGGER_MAP[severity]
93
+ end
94
+
95
+ ##
96
+ # Builds a logging method for level +meth+.
97
+
98
+ def self.log_method(meth)
99
+ eval <<-EOM, nil, __FILE__, __LINE__ + 1
100
+ def #{meth}(message = nil)
101
+ return true if #{LOGGER_LEVEL_MAP[meth]} < @level
102
+ SYSLOG.#{LOGGER_MAP[meth]} clean(message || yield)
103
+ return true
104
+ end
105
+ EOM
106
+ end
107
+
108
+ LOGGER_MAP.each_key do |level|
109
+ log_method level
110
+ end
111
+
112
+ ##
113
+ # Log level for Logger compatibility.
114
+
115
+ attr_accessor :level
116
+
117
+ ##
118
+ # Fills in variables for Logger compatibility. If this is the first
119
+ # instance of SyslogLogger, +program_name+ may be set to change the logged
120
+ # program name.
121
+ #
122
+ # Due to the way syslog works, only one program name may be chosen.
123
+
124
+ def initialize(program_name = 'rails')
125
+ @level = Logger::DEBUG
126
+
127
+ return if defined? SYSLOG
128
+ self.class.const_set :SYSLOG, Syslog.open(program_name)
129
+ end
130
+
131
+ ##
132
+ # Almost duplicates Logger#add. +progname+ is ignored.
133
+
134
+ def add(severity, message = nil, progname = nil, &block)
135
+ severity ||= Logger::UNKNOWN
136
+ return true if severity < @level
137
+ message = clean(message || block.call)
138
+ SYSLOG.send LEVEL_LOGGER_MAP[severity], clean(message)
139
+ return true
140
+ end
141
+
142
+ ##
143
+ # Allows messages of a particular log level to be ignored temporarily.
144
+ #
145
+ # Can you say "Broken Windows"?
146
+
147
+ def silence(temporary_level = Logger::ERROR)
148
+ old_logger_level = @level
149
+ @level = temporary_level
150
+ yield
151
+ ensure
152
+ @level = old_logger_level
153
+ end
154
+
155
+ private
156
+
157
+ ##
158
+ # Clean up messages so they're nice and pretty.
159
+
160
+ def clean(message)
161
+ message = message.to_s.dup
162
+ message.strip!
163
+ message.gsub!(/%/, '%%') # syslog(3) freaks on % (printf)
164
+ message.gsub!(/\e\[[^m]*m/, '') # remove useless ansi color codes
165
+ return message
166
+ end
167
+
168
+ end
169
+
170
+ # vim: ts=4 sts=4 sw=4
171
+
@@ -14,7 +14,7 @@ module IOTail
14
14
  self.gets
15
15
 
16
16
  loop do
17
- self.each_line &block
17
+ self.each_line(&block)
18
18
 
19
19
  if self.eof? then
20
20
  sleep 0.25
@@ -0,0 +1,443 @@
1
+ require 'test/unit'
2
+ require 'tempfile'
3
+ require 'analyzer_tools/syslog_logger'
4
+
5
+ module MockSyslog; end
6
+
7
+ class << MockSyslog
8
+
9
+ @line = nil
10
+
11
+ SyslogLogger::LOGGER_MAP.values.uniq.each do |level|
12
+ eval <<-EOM
13
+ def #{level}(message)
14
+ @line = "#{level.to_s.upcase} - \#{message}"
15
+ end
16
+ EOM
17
+ end
18
+
19
+ attr_reader :line
20
+ attr_reader :program_name
21
+
22
+ def open(program_name)
23
+ @program_name = program_name
24
+ end
25
+
26
+ def reset
27
+ @line = ''
28
+ end
29
+
30
+ end
31
+
32
+ SyslogLogger.const_set :SYSLOG, MockSyslog
33
+
34
+ class TestLogger < Test::Unit::TestCase
35
+
36
+ LEVEL_LABEL_MAP = {
37
+ Logger::DEBUG => 'DEBUG',
38
+ Logger::INFO => 'INFO',
39
+ Logger::WARN => 'WARN',
40
+ Logger::ERROR => 'ERROR',
41
+ Logger::FATAL => 'FATAL',
42
+ Logger::UNKNOWN => 'ANY',
43
+ }
44
+
45
+ def setup
46
+ @logger = Logger.new(nil)
47
+ end
48
+
49
+ class Log
50
+ attr_reader :line, :label, :datetime, :pid, :severity, :progname, :msg
51
+ def initialize(line)
52
+ @line = line
53
+ /\A(\w+), \[([^#]*)#(\d+)\]\s+(\w+) -- (\w*): ([\x0-\xff]*)/ =~ @line
54
+ @label, @datetime, @pid, @severity, @progname, @msg = $1, $2, $3, $4, $5, $6
55
+ end
56
+ end
57
+
58
+ def log_add(severity, msg, progname = nil, &block)
59
+ log(:add, severity, msg, progname, &block)
60
+ end
61
+
62
+ def log(msg_id, *arg, &block)
63
+ Log.new(log_raw(msg_id, *arg, &block))
64
+ end
65
+
66
+ def log_raw(msg_id, *arg, &block)
67
+ logdev = Tempfile.new(File.basename(__FILE__) + '.log')
68
+ @logger.instance_eval { @logdev = Logger::LogDevice.new(logdev) }
69
+ assert_equal true, @logger.__send__(msg_id, *arg, &block)
70
+ logdev.open
71
+ msg = logdev.read
72
+ logdev.close
73
+ msg
74
+ end
75
+
76
+ def test_initialize
77
+ assert_equal Logger::DEBUG, @logger.level
78
+ end
79
+
80
+ def test_add
81
+ msg = log_add nil, 'unknown level message' # nil == unknown
82
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
83
+
84
+ msg = log_add Logger::FATAL, 'fatal level message'
85
+ assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity
86
+
87
+ msg = log_add Logger::ERROR, 'error level message'
88
+ assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity
89
+
90
+ msg = log_add Logger::WARN, 'warn level message'
91
+ assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity
92
+
93
+ msg = log_add Logger::INFO, 'info level message'
94
+ assert_equal LEVEL_LABEL_MAP[Logger::INFO], msg.severity
95
+
96
+ msg = log_add Logger::DEBUG, 'debug level message'
97
+ assert_equal LEVEL_LABEL_MAP[Logger::DEBUG], msg.severity
98
+ end
99
+
100
+ def test_add_level_unknown
101
+ @logger.level = Logger::UNKNOWN
102
+
103
+ msg = log_add nil, 'unknown level message' # nil == unknown
104
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
105
+
106
+ msg = log_add Logger::FATAL, 'fatal level message'
107
+ assert_equal '', msg.line
108
+
109
+ msg = log_add Logger::ERROR, 'error level message'
110
+ assert_equal '', msg.line
111
+
112
+ msg = log_add Logger::WARN, 'warn level message'
113
+ assert_equal '', msg.line
114
+
115
+ msg = log_add Logger::INFO, 'info level message'
116
+ assert_equal '', msg.line
117
+
118
+ msg = log_add Logger::DEBUG, 'debug level message'
119
+ assert_equal '', msg.line
120
+ end
121
+
122
+ def test_add_level_fatal
123
+ @logger.level = Logger::FATAL
124
+
125
+ msg = log_add nil, 'unknown level message' # nil == unknown
126
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
127
+
128
+ msg = log_add Logger::FATAL, 'fatal level message'
129
+ assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity
130
+
131
+ msg = log_add Logger::ERROR, 'error level message'
132
+ assert_equal '', msg.line
133
+
134
+ msg = log_add Logger::WARN, 'warn level message'
135
+ assert_equal '', msg.line
136
+
137
+ msg = log_add Logger::INFO, 'info level message'
138
+ assert_equal '', msg.line
139
+
140
+ msg = log_add Logger::DEBUG, 'debug level message'
141
+ assert_equal '', msg.line
142
+ end
143
+
144
+ def test_add_level_error
145
+ @logger.level = Logger::ERROR
146
+
147
+ msg = log_add nil, 'unknown level message' # nil == unknown
148
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
149
+
150
+ msg = log_add Logger::FATAL, 'fatal level message'
151
+ assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity
152
+
153
+ msg = log_add Logger::ERROR, 'error level message'
154
+ assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity
155
+
156
+ msg = log_add Logger::WARN, 'warn level message'
157
+ assert_equal '', msg.line
158
+
159
+ msg = log_add Logger::INFO, 'info level message'
160
+ assert_equal '', msg.line
161
+
162
+ msg = log_add Logger::DEBUG, 'debug level message'
163
+ assert_equal '', msg.line
164
+ end
165
+
166
+ def test_add_level_warn
167
+ @logger.level = Logger::WARN
168
+
169
+ msg = log_add nil, 'unknown level message' # nil == unknown
170
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
171
+
172
+ msg = log_add Logger::FATAL, 'fatal level message'
173
+ assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity
174
+
175
+ msg = log_add Logger::ERROR, 'error level message'
176
+ assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity
177
+
178
+ msg = log_add Logger::WARN, 'warn level message'
179
+ assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity
180
+
181
+ msg = log_add Logger::INFO, 'info level message'
182
+ assert_equal '', msg.line
183
+
184
+ msg = log_add Logger::DEBUG, 'debug level message'
185
+ assert_equal '', msg.line
186
+ end
187
+
188
+ def test_add_level_info
189
+ @logger.level = Logger::INFO
190
+
191
+ msg = log_add nil, 'unknown level message' # nil == unknown
192
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
193
+
194
+ msg = log_add Logger::FATAL, 'fatal level message'
195
+ assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity
196
+
197
+ msg = log_add Logger::ERROR, 'error level message'
198
+ assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity
199
+
200
+ msg = log_add Logger::WARN, 'warn level message'
201
+ assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity
202
+
203
+ msg = log_add Logger::INFO, 'info level message'
204
+ assert_equal LEVEL_LABEL_MAP[Logger::INFO], msg.severity
205
+
206
+ msg = log_add Logger::DEBUG, 'debug level message'
207
+ assert_equal '', msg.line
208
+ end
209
+
210
+ def test_add_level_debug
211
+ @logger.level = Logger::DEBUG
212
+
213
+ msg = log_add nil, 'unknown level message' # nil == unknown
214
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
215
+
216
+ msg = log_add Logger::FATAL, 'fatal level message'
217
+ assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity
218
+
219
+ msg = log_add Logger::ERROR, 'error level message'
220
+ assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity
221
+
222
+ msg = log_add Logger::WARN, 'warn level message'
223
+ assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity
224
+
225
+ msg = log_add Logger::INFO, 'info level message'
226
+ assert_equal LEVEL_LABEL_MAP[Logger::INFO], msg.severity
227
+
228
+ msg = log_add Logger::DEBUG, 'debug level message'
229
+ assert_equal LEVEL_LABEL_MAP[Logger::DEBUG], msg.severity
230
+ end
231
+
232
+ def test_unknown
233
+ msg = log :unknown, 'unknown level message'
234
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
235
+
236
+ @logger.level = Logger::UNKNOWN
237
+ msg = log :unknown, 'unknown level message'
238
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
239
+
240
+ @logger.level = Logger::FATAL
241
+ msg = log :unknown, 'unknown level message'
242
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
243
+
244
+ @logger.level = Logger::ERROR
245
+ msg = log :unknown, 'unknown level message'
246
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
247
+
248
+ @logger.level = Logger::WARN
249
+ msg = log :unknown, 'unknown level message'
250
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
251
+
252
+ @logger.level = Logger::INFO
253
+ msg = log :unknown, 'unknown level message'
254
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
255
+
256
+ @logger.level = Logger::DEBUG
257
+ msg = log :unknown, 'unknown level message'
258
+ assert_equal LEVEL_LABEL_MAP[Logger::UNKNOWN], msg.severity
259
+ end
260
+
261
+ def test_fatal
262
+ msg = log :fatal, 'fatal level message'
263
+ assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity
264
+
265
+ @logger.level = Logger::UNKNOWN
266
+ msg = log :fatal, 'fatal level message'
267
+ assert_equal '', msg.line
268
+
269
+ @logger.level = Logger::FATAL
270
+ msg = log :fatal, 'fatal level message'
271
+ assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity
272
+
273
+ @logger.level = Logger::ERROR
274
+ msg = log :fatal, 'fatal level message'
275
+ assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity
276
+
277
+ @logger.level = Logger::WARN
278
+ msg = log :fatal, 'fatal level message'
279
+ assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity
280
+
281
+ @logger.level = Logger::INFO
282
+ msg = log :fatal, 'fatal level message'
283
+ assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity
284
+
285
+ @logger.level = Logger::DEBUG
286
+ msg = log :fatal, 'fatal level message'
287
+ assert_equal LEVEL_LABEL_MAP[Logger::FATAL], msg.severity
288
+ end
289
+
290
+ def test_error
291
+ msg = log :error, 'error level message'
292
+ assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity
293
+
294
+ @logger.level = Logger::UNKNOWN
295
+ msg = log :error, 'error level message'
296
+ assert_equal '', msg.line
297
+
298
+ @logger.level = Logger::FATAL
299
+ msg = log :error, 'error level message'
300
+ assert_equal '', msg.line
301
+
302
+ @logger.level = Logger::ERROR
303
+ msg = log :error, 'error level message'
304
+ assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity
305
+
306
+ @logger.level = Logger::WARN
307
+ msg = log :error, 'error level message'
308
+ assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity
309
+
310
+ @logger.level = Logger::INFO
311
+ msg = log :error, 'error level message'
312
+ assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity
313
+
314
+ @logger.level = Logger::DEBUG
315
+ msg = log :error, 'error level message'
316
+ assert_equal LEVEL_LABEL_MAP[Logger::ERROR], msg.severity
317
+ end
318
+
319
+ def test_warn
320
+ msg = log :warn, 'warn level message'
321
+ assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity
322
+
323
+ @logger.level = Logger::UNKNOWN
324
+ msg = log :warn, 'warn level message'
325
+ assert_equal '', msg.line
326
+
327
+ @logger.level = Logger::FATAL
328
+ msg = log :warn, 'warn level message'
329
+ assert_equal '', msg.line
330
+
331
+ @logger.level = Logger::ERROR
332
+ msg = log :warn, 'warn level message'
333
+ assert_equal '', msg.line
334
+
335
+ @logger.level = Logger::WARN
336
+ msg = log :warn, 'warn level message'
337
+ assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity
338
+
339
+ @logger.level = Logger::INFO
340
+ msg = log :warn, 'warn level message'
341
+ assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity
342
+
343
+ @logger.level = Logger::DEBUG
344
+ msg = log :warn, 'warn level message'
345
+ assert_equal LEVEL_LABEL_MAP[Logger::WARN], msg.severity
346
+ end
347
+
348
+ def test_info
349
+ msg = log :info, 'info level message'
350
+ assert_equal LEVEL_LABEL_MAP[Logger::INFO], msg.severity
351
+
352
+ @logger.level = Logger::UNKNOWN
353
+ msg = log :info, 'info level message'
354
+ assert_equal '', msg.line
355
+
356
+ @logger.level = Logger::FATAL
357
+ msg = log :info, 'info level message'
358
+ assert_equal '', msg.line
359
+
360
+ @logger.level = Logger::ERROR
361
+ msg = log :info, 'info level message'
362
+ assert_equal '', msg.line
363
+
364
+ @logger.level = Logger::WARN
365
+ msg = log :info, 'info level message'
366
+ assert_equal '', msg.line
367
+
368
+ @logger.level = Logger::INFO
369
+ msg = log :info, 'info level message'
370
+ assert_equal LEVEL_LABEL_MAP[Logger::INFO], msg.severity
371
+
372
+ @logger.level = Logger::DEBUG
373
+ msg = log :info, 'info level message'
374
+ assert_equal LEVEL_LABEL_MAP[Logger::INFO], msg.severity
375
+ end
376
+
377
+ def test_debug
378
+ msg = log :debug, 'debug level message'
379
+ assert_equal LEVEL_LABEL_MAP[Logger::DEBUG], msg.severity
380
+
381
+ @logger.level = Logger::UNKNOWN
382
+ msg = log :debug, 'debug level message'
383
+ assert_equal '', msg.line
384
+
385
+ @logger.level = Logger::FATAL
386
+ msg = log :debug, 'debug level message'
387
+ assert_equal '', msg.line
388
+
389
+ @logger.level = Logger::ERROR
390
+ msg = log :debug, 'debug level message'
391
+ assert_equal '', msg.line
392
+
393
+ @logger.level = Logger::WARN
394
+ msg = log :debug, 'debug level message'
395
+ assert_equal '', msg.line
396
+
397
+ @logger.level = Logger::INFO
398
+ msg = log :debug, 'debug level message'
399
+ assert_equal '', msg.line
400
+
401
+ @logger.level = Logger::DEBUG
402
+ msg = log :debug, 'debug level message'
403
+ assert_equal LEVEL_LABEL_MAP[Logger::DEBUG], msg.severity
404
+ end
405
+
406
+ end
407
+
408
+ class TestSyslogLogger < TestLogger
409
+
410
+ def setup
411
+ super
412
+ @logger = SyslogLogger.new
413
+ end
414
+
415
+ class Log
416
+ attr_reader :line, :label, :datetime, :pid, :severity, :progname, :msg
417
+ def initialize(line)
418
+ @line = line
419
+ return unless /\A(\w+) - (.*)\Z/ =~ @line
420
+ severity, @msg = $1, $2
421
+ severity = SyslogLogger::LOGGER_MAP.invert[severity.downcase.intern]
422
+ @severity = severity.to_s.upcase
423
+ @severity = 'ANY' if @severity == 'UNKNOWN'
424
+ end
425
+ end
426
+
427
+ def log_add(severity, msg, progname = nil, &block)
428
+ log(:add, severity, msg, progname, &block)
429
+ end
430
+
431
+ def log(msg_id, *arg, &block)
432
+ Log.new(log_raw(msg_id, *arg, &block))
433
+ end
434
+
435
+ def log_raw(msg_id, *arg, &block)
436
+ assert_equal true, @logger.__send__(msg_id, *arg, &block)
437
+ msg = MockSyslog.line
438
+ MockSyslog.reset
439
+ return msg
440
+ end
441
+
442
+ end
443
+
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.10
2
+ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: rails_analyzer_tools
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.0.0
7
- date: 2005-06-04
8
- summary: Various tools and toys
6
+ version: 1.1.0
7
+ date: 2005-09-22 00:00:00 -07:00
8
+ summary: Tools for analyzing the performance of web sites.
9
9
  require_paths:
10
10
  - lib
11
11
  email: eric@robotcoop.com
@@ -24,20 +24,30 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
24
24
  version: 0.0.0
25
25
  version:
26
26
  platform: ruby
27
+ signing_key:
28
+ cert_chain:
27
29
  authors:
28
30
  - Eric Hodel
29
31
  files:
30
32
  - Manifest.txt
31
33
  - Rakefile
32
34
  - README
35
+ - bin/bench
36
+ - bin/crawl
33
37
  - bin/rails_stat
34
38
  - lib/io_tail.rb
35
- - lib/rails_stat.rb
39
+ - lib/analyzer_tools/bench.rb
40
+ - lib/analyzer_tools/crawl.rb
41
+ - lib/analyzer_tools/rails_stat.rb
42
+ - lib/analyzer_tools/syslog_logger.rb
43
+ - test/test_syslog_logger.rb
36
44
  test_files: []
37
45
  rdoc_options: []
38
46
  extra_rdoc_files: []
39
47
  executables:
40
48
  - rails_stat
49
+ - bench
50
+ - crawl
41
51
  extensions: []
42
52
  requirements: []
43
53
  dependencies: []