rails_analyzer_tools 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []