wvanbergen-request-log-analyzer 0.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.
@@ -0,0 +1,83 @@
1
+ require 'date'
2
+
3
+ module RailsAnalyzer
4
+ # Parse a rails log file
5
+ class LogParser
6
+
7
+ LOG_LINES = {
8
+ # Processing EmployeeController#index (for 123.123.123.123 at 2008-07-13 06:00:00) [GET]
9
+ :started => {
10
+ :teaser => /Processing/,
11
+ :regexp => /Processing (\w+)#(\w+) \(for (\d+\.\d+\.\d+\.\d+) at (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\) \[([A-Z]+)\]/,
12
+ :params => { :controller => 1, :action => 2, :ip => 3, :timestamp => 4, :method => 5}
13
+ },
14
+ # RuntimeError (Cannot destroy employee): /app/models/employee.rb:198:in `before_destroy'
15
+ :failed => {
16
+ :teaser => /Error/,
17
+ :regexp => /(\w+)(Error|Invalid) \((.*)\)\:(.*)/,
18
+ :params => { :error => 1, :exception_string => 3, :stack_trace => 4 }
19
+ },
20
+ # Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]
21
+ :completed => {
22
+ :teaser => /Completed/,
23
+ :regexp => /Completed in (\d+\.\d{5}) \(\d+ reqs\/sec\) (\| Rendering: (\d+\.\d{5}) \(\d+\%\) )?(\| DB: (\d+\.\d{5}) \(\d+\%\) )?\| (\d\d\d).+\[(http.+)\]/,
24
+ :params => { :url => 7, :status => [6, :to_i], :duration => [1, :to_f], :rendering => [3, :to_f], :db => [5, :to_f] }
25
+ }
26
+ }
27
+
28
+ # LogParser initializer
29
+ # <tt>file</tt> The fileobject this LogParser wil operate on.
30
+ def initialize(file, options = {})
31
+ @file_name = file
32
+ @options = options
33
+ @file_size = File.size(@file_name)
34
+ end
35
+
36
+ def progress(&block)
37
+ @progress_handler = block
38
+ end
39
+
40
+ # Output a warning
41
+ # <tt>message</tt> The warning message (object)
42
+ def warn(message)
43
+ puts " -> " + message.to_s
44
+ end
45
+
46
+ # Finds a log line and then parses the information in the line.
47
+ # Yields a hash containing the information found.
48
+ # <tt>*line_types</tt> The log line types to look for (defaults to LOG_LINES.keys).
49
+ # Yeilds a Hash when it encounters a chunk of information.
50
+ def each(*line_types, &block)
51
+ # parse everything by default
52
+ line_types = LOG_LINES.keys if line_types.empty?
53
+
54
+ File.open(@file_name) do |file|
55
+
56
+ file.each_line do |line|
57
+
58
+ @progress_handler.call(file.pos, @file_size) if @progress_handler
59
+
60
+ line_types.each do |line_type|
61
+ if LOG_LINES[line_type][:teaser] =~ line
62
+ if LOG_LINES[line_type][:regexp] =~ line
63
+ request = { :type => line_type, :line => file.lineno }
64
+ LOG_LINES[line_type][:params].each do |key, value|
65
+ request[key] = case value
66
+ when Numeric; $~[value]
67
+ when Array; $~[value.first].send(value.last)
68
+ else; nil
69
+ end
70
+
71
+ end
72
+ yield(request) if block_given?
73
+ else
74
+ warn("Unparsable #{line_type} line: " + line[0..79]) unless line_type == :failed
75
+ end
76
+ end
77
+ end
78
+ end
79
+ @progress_handler.call(:finished, @file_size) if @progress_handler
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,161 @@
1
+ require 'rubygems'
2
+ require 'sqlite3'
3
+
4
+ module RailsAnalyzer
5
+
6
+ # Set of functions that can be used to easily log requests into a SQLite3 Database.
7
+ class RecordInserter
8
+
9
+ attr_reader :database
10
+ attr_reader :current_request
11
+ attr_reader :warning_count
12
+
13
+ # Initializer
14
+ # <tt>db_file</tt> The file which will be used for the SQLite3 Database storage.
15
+ def initialize(db_file)
16
+ @database = SQLite3::Database.new(db_file)
17
+ @insert_statements = nil
18
+ @warning_count = 0
19
+ create_tables_if_needed!
20
+ end
21
+
22
+ # Calculate the database durations of the requests currenty in the database.
23
+ # Used if a logfile does contain any database durations.
24
+ def calculate_db_durations!
25
+ @database.execute('UPDATE "completed_queries" SET "database" = "duration" - "rendering" WHERE "database" IS NULL OR "database" = 0.0')
26
+ end
27
+
28
+ # Insert a batch of loglines into the database.
29
+ # Function prepares insert statements, yeilds and then closes and commits.
30
+ def insert_batch(&block)
31
+ @database.transaction
32
+ prepare_statements!
33
+ block.call(self)
34
+ close_prepared_statements!
35
+ @database.commit
36
+ rescue Exception => e
37
+ puts e.message
38
+ @database.rollback
39
+ end
40
+
41
+ def insert_warning(line, warning)
42
+ @database.execute("INSERT INTO parse_warnings (line, warning) VALUES (:line, :warning)", :line => line, :warning => warning)
43
+ @warning_count += 1
44
+ end
45
+
46
+ # Insert a request into the database.
47
+ # <tt>request</tt> The request to insert.
48
+ # <tt>close_statements</tt> Close prepared statements (default false)
49
+ def insert(request, close_statements = false)
50
+ unless @insert_statements
51
+ prepare_statements!
52
+ close_statements = true
53
+ end
54
+
55
+ if request[:type] && @insert_statements.has_key?(request[:type])
56
+ if request[:type] == :started
57
+ insert_warning(request[:line], "Unclosed request encountered on line #{request[:line]} (request started on line #{@current_request})") unless @current_request.nil?
58
+ @current_request = request[:line]
59
+ elsif [:failed, :completed].include?(request[:type])
60
+ @current_request = nil
61
+ end
62
+
63
+ begin
64
+ @insert_statements[request.delete(:type)].execute(request)
65
+ rescue SQLite3::Exception => e
66
+ insert_warning(request[:line], "Could not save log line to database: " + e.message.to_s)
67
+ end
68
+ else
69
+ insert_warning(request[:line], "Ignored unknown statement type")
70
+ end
71
+
72
+ close_prepared_statements! if close_statements
73
+ end
74
+
75
+ # Insert a batch of files into the database.
76
+ # <tt>db_file</tt> The filename of the database file to use.
77
+ # Returns the created database.
78
+ def self.insert_batch_into(db_file, options = {}, &block)
79
+ db = RecordInserter.new(db_file)
80
+ db.insert_batch(&block)
81
+ return db
82
+ end
83
+
84
+ def count(type)
85
+ @database.get_first_value("SELECT COUNT(*) FROM \"#{type}_requests\"").to_i
86
+ end
87
+
88
+ protected
89
+
90
+ # Prepare insert statements.
91
+ def prepare_statements!
92
+ @insert_statements = {
93
+ :started => @database.prepare("
94
+ INSERT INTO started_requests ( line, timestamp, ip, method, controller, action)
95
+ VALUES (:line, :timestamp, :ip, :method, :controller, :action)"),
96
+
97
+ :failed => @database.prepare("
98
+ INSERT INTO failed_requests ( line, exception_string, stack_trace, error)
99
+ VALUES (:line, :exception_string, :stack_trace, :error)"),
100
+
101
+ :completed => @database.prepare("
102
+ INSERT INTO completed_requests ( line, url, status, duration, rendering_time, database_time)
103
+ VALUES (:line, :url, :status, :duration, :rendering, :db)")
104
+ }
105
+ end
106
+
107
+ # Close all prepared statments
108
+ def close_prepared_statements!
109
+ @insert_statements.each { |key, stmt| stmt.close }
110
+ end
111
+
112
+ # Create the needed database tables if they don't exist.
113
+ def create_tables_if_needed!
114
+
115
+ @database.execute("
116
+ CREATE TABLE IF NOT EXISTS started_requests (
117
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
118
+ line INTEGER NOT NULL,
119
+ timestamp DATETIME NOT NULL,
120
+ controller VARCHAR(255) NOT NULL,
121
+ action VARCHAR(255) NOT NULL,
122
+ method VARCHAR(6) NOT NULL,
123
+ ip VARCHAR(6) NOT NULL
124
+ )
125
+ ");
126
+
127
+ @database.execute("
128
+ CREATE TABLE IF NOT EXISTS failed_requests (
129
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
130
+ line INTEGER NOT NULL,
131
+ started_request_id INTEGER,
132
+ error VARCHAR(255),
133
+ exception_string VARCHAR(255),
134
+ stack_trace TEXT
135
+ )
136
+ ");
137
+
138
+ @database.execute("
139
+ CREATE TABLE IF NOT EXISTS completed_requests (
140
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
141
+ line INTEGER NOT NULL,
142
+ started_request_id INTEGER,
143
+ url VARCHAR(255) NOT NULL,
144
+ hashed_url VARCHAR(255),
145
+ status INTEGER NOT NULL,
146
+ duration FLOAT,
147
+ rendering_time FLOAT,
148
+ database_time FLOAT
149
+ )
150
+ ");
151
+
152
+ @database.execute("CREATE TABLE IF NOT EXISTS parse_warnings (
153
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
154
+ line INTEGER NOT NULL,
155
+ warning VARCHAR(255) NOT NULL
156
+ )
157
+ ");
158
+ end
159
+
160
+ end
161
+ end
@@ -0,0 +1,121 @@
1
+ module RailsAnalyzer
2
+
3
+ # Functions to summarize an array of requets.
4
+ # Can calculate request counts, duratations, mean times etc. of all the requests given.
5
+ class Summarizer
6
+
7
+ attr_reader :actions
8
+ attr_reader :errors
9
+ attr_reader :request_count
10
+ attr_reader :request_time_graph
11
+ attr_reader :first_request_at
12
+ attr_reader :last_request_at
13
+
14
+ attr_accessor :blocker_duration
15
+ DEFAULT_BLOCKER_DURATION = 1.0
16
+
17
+ # Initializer. Sets global variables
18
+ # Options
19
+ # * <tt>:calculate_database</tt> Calculate the database times if they are not explicitly logged.
20
+ def initialize(options = {})
21
+ @actions = {}
22
+ @blockers = {}
23
+ @errors = {}
24
+ @request_count = 0
25
+ @blocker_duration = DEFAULT_BLOCKER_DURATION
26
+ @calculate_database = options[:calculate_database]
27
+ @request_time_graph = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
28
+ end
29
+
30
+ # Check if any of the request parsed had a timestamp.
31
+ def has_timestamps?
32
+ @first_request_at
33
+ end
34
+
35
+ # Parse a request string into a hash containing all keys found in the string.
36
+ # Yields the hash found to the block operator.
37
+ # <tt>request</tt> The request string to parse.
38
+ # <tt>&block</tt> Block operator
39
+ def group(request, &block)
40
+ request[:duration] ||= 0
41
+
42
+ case request[:type]
43
+ when :started
44
+ @first_request_at ||= request[:timestamp] # assume time-based order of file
45
+ @last_request_at = request[:timestamp] # assume time-based order of file
46
+ @request_time_graph[request[:timestamp][11..12].to_i] +=1
47
+
48
+ when :completed
49
+ @request_count += 1
50
+ hash = block_given? ? yield(request) : request.hash
51
+
52
+ @actions[hash] ||= {:count => 0, :total_time => 0.0, :total_db_time => 0.0, :total_rendering_time => 0.0,
53
+ :min_time => request[:duration], :max_time => request[:duration] }
54
+
55
+ @actions[hash][:count] += 1
56
+ @actions[hash][:total_time] += request[:duration]
57
+ @actions[hash][:total_db_time] += request[:db] if request[:db]
58
+ @actions[hash][:total_db_time] += request[:duration] - request[:rendering] if @calculate_database
59
+
60
+ @actions[hash][:total_rendering_time] += request[:rendering] if request[:rendering]
61
+
62
+ @actions[hash][:min_time] = [@actions[hash][:min_time], request[:duration]].min
63
+ @actions[hash][:max_time] = [@actions[hash][:min_time], request[:duration]].max
64
+ @actions[hash][:mean_time] = @actions[hash][:total_time] / @actions[hash][:count].to_f
65
+
66
+ @actions[hash][:mean_db_time] = @actions[hash][:total_db_time] / @actions[hash][:count].to_f
67
+ @actions[hash][:mean_rendering_time] = @actions[hash][:total_rendering_time] / @actions[hash][:count].to_f
68
+
69
+ if request[:duration] > @blocker_duration
70
+ @blockers[hash] ||= { :count => 0, :total_time => 0.0 }
71
+ @blockers[hash][:count] += 1
72
+ @blockers[hash][:total_time] += request[:duration]
73
+ end
74
+
75
+ when :failed
76
+ hash = request[:error]
77
+ @errors[hash] ||= {:count => 0, :exception_strings => {}}
78
+ @errors[hash][:count] +=1
79
+
80
+ @errors[hash][:exception_strings][request[:exception_string]] ||= 0
81
+ @errors[hash][:exception_strings][request[:exception_string]] += 1
82
+ end
83
+ end
84
+
85
+ # Return a list of requests sorted on a specific action field
86
+ # <tt>field</tt> The action field to sort by.
87
+ # <tt>min_count</tt> Values which fall below this amount are not returned (default nil).
88
+ def sort_actions_by(field, min_count = nil)
89
+ actions = min_count.nil? ? @actions.to_a : @actions.delete_if { |k, v| v[:count] < min_count}.to_a
90
+ actions.sort { |a, b| (a[1][field.to_sym] <=> b[1][field.to_sym]) }
91
+ end
92
+
93
+ # Returns a list of request blockers sorted by a specific field
94
+ # <tt>field</tt> The action field to sort by.
95
+ # <tt>min_count</tt> Values which fall below this amount are not returned (default @blocker_duration).
96
+ def sort_blockers_by(field, min_count = @blocker_duration)
97
+ blockers = min_count.nil? ? @blockers.to_a : @blockers.delete_if { |k, v| v[:count] < min_count}.to_a
98
+ blockers.sort { |a, b| a[1][field.to_sym] <=> b[1][field.to_sym] }
99
+ end
100
+
101
+ # Returns a list of request blockers sorted by a specific field
102
+ # <tt>field</tt> The action field to sort by.
103
+ # <tt>min_count</tt> Values which fall below this amount are not returned (default @blocker_duration).
104
+ def sort_errors_by(field, min_count = nil)
105
+ errors = min_count.nil? ? @errors.to_a : @errors.delete_if { |k, v| v[:count] < min_count}.to_a
106
+ errors.sort { |a, b| a[1][field.to_sym] <=> b[1][field.to_sym] }
107
+ end
108
+
109
+ # Calculate the duration of a request
110
+ # Returns a DateTime object if possible, 0 otherwise.
111
+ def duration
112
+ (@last_request_at && @first_request_at) ? (DateTime.parse(@last_request_at) - DateTime.parse(@first_request_at)).round : 0
113
+ end
114
+
115
+ # Check if the request time graph usable data.
116
+ def request_time_graph?
117
+ @request_time_graph.uniq != [0] && duration > 0
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,103 @@
1
+ =begin
2
+ index:Ej
3
+
4
+ = Ruby/ProgressBar: A Text Progress Bar Library for Ruby
5
+
6
+ Last Modified: 2005-05-22 00:28:04
7
+
8
+ --
9
+
10
+ Ruby/ProgressBar is a text progress bar library for Ruby.
11
+ It can indicate progress with percentage, a progress bar,
12
+ and estimated remaining time.
13
+
14
+ The latest version of Ruby/ProgressBar is available at
15
+ ((<URL:http://namazu.org/~satoru/ruby-progressbar/>))
16
+ .
17
+
18
+ == Examples
19
+
20
+ % irb --simple-prompt -r progressbar
21
+ >> pbar = ProgressBar.new("test", 100)
22
+ => (ProgressBar: 0/100)
23
+ >> 100.times {sleep(0.1); pbar.inc}; pbar.finish
24
+ test: 100% |oooooooooooooooooooooooooooooooooooooooo| Time: 00:00:10
25
+ => nil
26
+
27
+ >> pbar = ProgressBar.new("test", 100)
28
+ => (ProgressBar: 0/100)
29
+ >> (1..100).each{|x| sleep(0.1); pbar.set(x)}; pbar.finish
30
+ test: 67% |oooooooooooooooooooooooooo | ETA: 00:00:03
31
+
32
+ == API
33
+
34
+ --- ProgressBar#new (title, total, out = STDERR)
35
+ Display the initial progress bar and return a
36
+ ProgressBar object. ((|title|)) specifies the title,
37
+ and ((|total|)) specifies the total cost of processing.
38
+ Optional parameter ((|out|)) specifies the output IO.
39
+
40
+ The display of the progress bar is updated when one or
41
+ more percent is proceeded or one or more seconds are
42
+ elapsed from the previous display.
43
+
44
+ --- ProgressBar#inc (step = 1)
45
+ Increase the internal counter by ((|step|)) and update
46
+ the display of the progress bar. Display the estimated
47
+ remaining time on the right side of the bar. The counter
48
+ does not go beyond the ((|total|)).
49
+
50
+ --- ProgressBar#set (count)
51
+ Set the internal counter to ((|count|)) and update the
52
+ display of the progress bar. Display the estimated
53
+ remaining time on the right side of the bar. Raise if
54
+ ((|count|)) is a negative number or a number more than
55
+ the ((|total|)).
56
+
57
+ --- ProgressBar#finish
58
+ Stop the progress bar and update the display of progress
59
+ bar. Display the elapsed time on the right side of the bar.
60
+ The progress bar always stops at 100 % by the method.
61
+
62
+ --- ProgressBar#halt
63
+ Stop the progress bar and update the display of progress
64
+ bar. Display the elapsed time on the right side of the bar.
65
+ The progress bar stops at the current percentage by the method.
66
+
67
+ --- ProgressBar#format=
68
+ Set the format for displaying a progress bar.
69
+ Default: "%-14s %3d%% %s %s".
70
+
71
+ --- ProgressBar#format_arguments=
72
+ Set the methods for displaying a progress bar.
73
+ Default: [:title, :percentage, :bar, :stat].
74
+
75
+ --- ProgressBar#file_transfer_mode
76
+ Use :stat_for_file_transfer instead of :stat to display
77
+ transfered bytes and transfer rate.
78
+
79
+
80
+ ReverseProgressBar class is also available. The
81
+ functionality is identical to ProgressBar but the direction
82
+ of the progress bar is just opposite.
83
+
84
+ == Limitations
85
+
86
+ Since the progress is calculated by the proportion to the
87
+ total cost of processing, Ruby/ProgressBar cannot be used if
88
+ the total cost of processing is unknown in advance.
89
+ Moreover, the estimation of remaining time cannot be
90
+ accurately performed if the progress does not flow uniformly.
91
+
92
+ == Download
93
+
94
+ Ruby/ProgressBar is a free software with ABSOLUTELY NO WARRANTY
95
+ under the terms of Ruby's license.
96
+
97
+ * ((<URL:http://namazu.org/~satoru/ruby-progressbar/ruby-progressbar-0.9.tar.gz>))
98
+ * ((<URL:http://cvs.namazu.org/ruby-progressbar/>))
99
+
100
+ --
101
+
102
+ - ((<Satoru Takabayashi|URL:http://namazu.org/~satoru/>)) -
103
+ =end
@@ -0,0 +1,100 @@
1
+ =begin
2
+ index:eJ
3
+
4
+ = Ruby/ProgressBar: �ץ����쥹�С���ƥ����Ȥ�ɽ������ Ruby�ѤΥ饤�֥��
5
+
6
+ �ǽ�������: 2005-05-22 00:28:53
7
+
8
+
9
+ --
10
+
11
+ Ruby/ProgressBar �ϥץ����쥹�С���ƥ����Ȥ�ɽ������ Ruby��
12
+ �Υ饤�֥��Ǥ��������ο�Ľ������ѡ�����ȡ��ץ����쥹�С���
13
+ ����ӿ���Ĥ���֤Ȥ���ɽ�����ޤ���
14
+
15
+ �ǿ��Ǥ�
16
+ ((<URL:http://namazu.org/~satoru/ruby-progressbar/>))
17
+ ���������ǽ�Ǥ�
18
+
19
+ == ������
20
+
21
+ % irb --simple-prompt -r progressbar
22
+ >> pbar = ProgressBar.new("test", 100)
23
+ => (ProgressBar: 0/100)
24
+ >> 100.times {sleep(0.1); pbar.inc}; pbar.finish
25
+ test: 100% |oooooooooooooooooooooooooooooooooooooooo| Time: 00:00:10
26
+ => nil
27
+
28
+ >> pbar = ProgressBar.new("test", 100)
29
+ => (ProgressBar: 0/100)
30
+ >> (1..100).each{|x| sleep(0.1); pbar.set(x)}; pbar.finish
31
+ test: 67% |oooooooooooooooooooooooooo | ETA: 00:00:03
32
+
33
+ == API
34
+
35
+ --- ProgressBar#new (title, total, out = STDERR)
36
+ �ץ����쥹�С��ν�����֤�ɽ������������ ProgressBar����
37
+ �������Ȥ��֤���((|title|)) �Ǹ��Ф���((|total|)) �ǽ�
38
+ �������פ�((|out|)) �ǽ������ IO �����ꤹ�롣
39
+
40
+ �ץ����쥹�С���ɽ���ϡ������ɽ�������Ľ�� 1%�ʾ夢��
41
+ ���Ȥ������뤤�� 1�ðʾ�вᤷ�����˹�������ޤ���
42
+
43
+ --- ProgressBar#inc (step = 1)
44
+ �����Υ����󥿤� ((|step|)) �������ʤ�ơ��ץ����쥹�С�
45
+ ��ɽ���򹹿����롣�С��α�¦�ˤϿ���Ĥ���֤�ɽ�����롣
46
+ �����󥿤� ((|total|)) ��ۤ��ƿʤळ�ȤϤʤ���
47
+
48
+ --- ProgressBar#set (count)
49
+ �����󥿤��ͤ� ((|count|)) �����ꤷ���ץ����쥹�С���
50
+ ɽ���򹹿����롣�С��α�¦�ˤϿ���Ĥ���֤�ɽ�����롣
51
+ ((|count|)) �˥ޥ��ʥ����ͤ��뤤�� ((|total|)) ����礭
52
+ ���ͤ��Ϥ����㳰��ȯ�����롣
53
+
54
+ --- ProgressBar#finish
55
+ �ץ����쥹�С�����ߤ����ץ����쥹�С���ɽ���򹹿����롣
56
+ �ץ����쥹�С��α�¦�ˤϷв���֤�ɽ�����롣
57
+ ���ΤȤ����ץ����쥹�С��� 100% �ǽ�λ���롣
58
+
59
+ --- ProgressBar#halt
60
+ �ץ����쥹�С�����ߤ����ץ����쥹�С���ɽ���򹹿����롣
61
+ �ץ����쥹�С��α�¦�ˤϷв���֤�ɽ�����롣
62
+ ���ΤȤ����ץ����쥹�С��Ϥ��λ����Υѡ�����ơ����ǽ�λ���롣
63
+
64
+ --- ProgressBar#format=
65
+ �ץ����쥹�С�ɽ���Υե����ޥåȤ����ꤹ�롣
66
+ ̤�ѹ����� "%-14s %3d%% %s %s"
67
+
68
+ --- ProgressBar#format_arguments=
69
+ �ץ����쥹�С�ɽ���˻Ȥ��ؿ������ꤹ�롣
70
+ ̤�ѹ����� [:title, :percentage, :bar, :stat]
71
+ �ե�����ž�����ˤ� :stat ���Ѥ��� :stat_for_file_transfer
72
+ ��Ȥ���ž���Х��ȿ���ž��®�٤�ɽ���Ǥ��롣
73
+
74
+ --- ProgressBar#file_transfer_mode
75
+ �ץ����쥹�С�ɽ���� :stat ���Ѥ��� :stat_for_file_transfer
76
+ ��Ȥ���ž���Х��ȿ���ž��®�٤�ɽ�����롣
77
+
78
+
79
+ ReverseProgressBar �Ȥ������饹���󶡤���ޤ�����ǽ��
80
+ ProgressBar �Ȥޤä���Ʊ���Ǥ������ץ����쥹�С��οʹ�������
81
+ �դˤʤäƤ��ޤ���
82
+
83
+ == ���»���
84
+
85
+ ��Ľ��������������פ��Ф�����Ȥ��Ʒ׻����뤿�ᡢ��������
86
+ �פ������ˤ狼��ʤ������ǤϻȤ��ޤ��󡣤ޤ�����Ľ��ή�줬��
87
+ ��Ǥʤ��Ȥ��ˤϻĤ���֤ο�����������Ԥ��ޤ���
88
+
89
+ == �����������
90
+
91
+ Ruby �Υ饤���󥹤˽��ä��ե꡼���եȥ������Ȥ��Ƹ������ޤ���
92
+ ������̵�ݾڤǤ���
93
+
94
+ * ((<URL:http://namazu.org/~satoru/ruby-progressbar/ruby-progressbar-0.9.tar.gz>))
95
+ * ((<URL:http://cvs.namazu.org/ruby-progressbar/>))
96
+
97
+ --
98
+
99
+ - ((<Satoru Takabayashi|URL:http://namazu.org/~satoru/>)) -
100
+ =end