lackac-request-log-analyzer 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ module MerbAnalyzer
2
+
3
+ class LogParser < Base::LogParser
4
+ LOG_LINES = {
5
+ # ~ Started request handling: Fri Aug 29 11:10:23 +0200 2008
6
+ :started => {
7
+ :teaser => /Started/,
8
+ :regexp => /Started request handling\:\ (.+)/,
9
+ :params => {:timestamp => 1}
10
+ },
11
+ # ~ Params: {"action"=>"create", "controller"=>"session"}
12
+ # ~ Params: {"_method"=>"delete", "authenticity_token"=>"[FILTERED]", "action"=>"d}
13
+ :params => {
14
+ :teaser => /Params/,
15
+ :regexp => /Params\:\ \{(.+)\}/,
16
+ :params => { :raw_hash => 1 }
17
+ },
18
+ # ~ {:dispatch_time=>0.006117, :after_filters_time=>6.1e-05, :before_filters_time=>0.000712, :action_time=>0.005833}
19
+ :completed => {
20
+ :teaser => /\{:dispatch_time/,
21
+ :regexp => /\{\:dispatch_time=>(.+), \:after_filters_time=>(.+), \:before_filters_time=>(.+), \:action_time=>(.+)\}/,
22
+ :params => { :dispatch_time => [1, :to_f], :after_filters_time => [2, :to_f], :before_filters_time => [3, :to_f], :action_time => [4, :to_f] }
23
+ }
24
+ }
25
+ end
26
+ end
@@ -0,0 +1,61 @@
1
+ require 'date'
2
+ module MerbAnalyzer
3
+
4
+ # Functions to summarize an array of requets.
5
+ # Can calculate request counts, duratations, mean times etc. of all the requests given.
6
+ class Summarizer < Base::Summarizer
7
+
8
+ # Initializer. Sets global variables
9
+ def initialize_hook(options = {})
10
+ @hash_cache = nil
11
+ end
12
+
13
+ # Parse a request string into a hash containing all keys found in the string.
14
+ # Yields the hash found to the block operator.
15
+ # <tt>request</tt> The request string to parse.
16
+ # <tt>&block</tt> Block operator
17
+ def group(request, &block)
18
+ request[:duration] ||= 0
19
+
20
+ case request[:type]
21
+ when :started
22
+ @first_request_at ||= request[:timestamp] # assume time-based order of file
23
+ @last_request_at = request[:timestamp] # assume time-based order of file
24
+ @request_time_graph[request[:timestamp][11..12].to_i] +=1
25
+
26
+ when :params
27
+ params_hash = {}
28
+ request[:raw_hash].split(',').collect{|x| x.split('=>')}.each do |k,v|
29
+ key = k.gsub('"', '').gsub(' ', '').to_sym
30
+ value = v.gsub('"', '')
31
+ request.store(key, value)
32
+ end
33
+
34
+ hash = block_given? ? yield(request) : request.hash
35
+
36
+ @actions[hash] ||= {:count => 0, :total_time => 0.0, :total_db_time => 0.0, :total_rendering_time => 0.0,
37
+ :min_time => request[:duration], :max_time => request[:duration] }
38
+
39
+ @actions[hash][:count] += 1
40
+ request[:method] = 'GET' unless request[:method]
41
+ @methods[request[:method].upcase.to_sym] += 1
42
+
43
+ @hash_cache = hash
44
+ when :completed
45
+ @request_count += 1
46
+
47
+ @actions[@hash_cache][:total_time] += request[:dispatch_time]
48
+ @actions[@hash_cache][:mean_time] = @actions[@hash_cache][:total_time] / @actions[@hash_cache][:count].to_f
49
+ @actions[@hash_cache][:min_time] = [@actions[@hash_cache][:min_time], request[:dispatch_time]].min
50
+ @actions[@hash_cache][:max_time] = [@actions[@hash_cache][:min_time], request[:dispatch_time]].max
51
+
52
+ @actions[@hash_cache][:total_db_time] = 0
53
+ @actions[@hash_cache][:mean_db_time] = 0
54
+ @actions[@hash_cache][:mean_rendering_time] = 0
55
+
56
+ when :failed
57
+
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ module RailsAnalyzer
2
+
3
+ class LogParser < Base::LogParser
4
+ LOG_LINES = {
5
+ # Processing EmployeeController#index (for 123.123.123.123 at 2008-07-13 06:00:00) [GET]
6
+ :started => {
7
+ :teaser => /Processing/,
8
+ :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]+)\]/,
9
+ :params => { :controller => 1, :action => 2, :ip => 3, :timestamp => 4, :method => 5}
10
+ },
11
+ # RuntimeError (Cannot destroy employee): /app/models/employee.rb:198:in `before_destroy'
12
+ :failed => {
13
+ :teaser => /Error/,
14
+ :regexp => /(\w+)(Error|Invalid) \((.*)\)\:(.*)/,
15
+ :params => { :error => 1, :exception_string => 3, :stack_trace => 4 }
16
+ },
17
+ # Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]
18
+ :completed => {
19
+ :teaser => /Completed/,
20
+ :regexp => /Completed in (\d+\.\d{5}) \(\d+ reqs\/sec\) (\| Rendering: (\d+\.\d{5}) \(\d+\%\) )?(\| DB: (\d+\.\d{5}) \(\d+\%\) )?\| (\d\d\d).+\[(http.+)\]/,
21
+ :params => { :url => 7, :status => [6, :to_i], :duration => [1, :to_f], :rendering => [3, :to_f], :db => [5, :to_f] }
22
+ }
23
+ }
24
+ end
25
+ end
@@ -0,0 +1,39 @@
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 < Base::RecordInserter
8
+
9
+ # Insert a request into the database.
10
+ # <tt>request</tt> The request to insert.
11
+ # <tt>close_statements</tt> Close prepared statements (default false)
12
+ def insert(request, close_statements = false)
13
+ unless @insert_statements
14
+ prepare_statements!
15
+ close_statements = true
16
+ end
17
+
18
+ if request[:type] && @insert_statements.has_key?(request[:type])
19
+ if request[:type] == :started
20
+ insert_warning(request[:line], "Unclosed request encountered on line #{request[:line]} (request started on line #{@current_request})") unless @current_request.nil?
21
+ @current_request = request[:line]
22
+ elsif [:failed, :completed].include?(request[:type])
23
+ @current_request = nil
24
+ end
25
+
26
+ begin
27
+ @insert_statements[request.delete(:type)].execute(request)
28
+ rescue SQLite3::Exception => e
29
+ insert_warning(request[:line], "Could not save log line to database: " + e.message.to_s)
30
+ end
31
+ else
32
+ insert_warning(request[:line], "Ignored unknown statement type")
33
+ end
34
+
35
+ close_prepared_statements! if close_statements
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,67 @@
1
+ require 'date'
2
+ module RailsAnalyzer
3
+
4
+ # Functions to summarize an array of requets.
5
+ # Can calculate request counts, duratations, mean times etc. of all the requests given.
6
+ class Summarizer < Base::Summarizer
7
+
8
+ # Initializer. Sets global variables
9
+ # Options
10
+ # * <tt>:calculate_database</tt> Calculate the database times if they are not explicitly logged.
11
+ def initialize_hook(options = {})
12
+ @calculate_database = options[:calculate_database]
13
+ end
14
+
15
+ # Parse a request string into a hash containing all keys found in the string.
16
+ # Yields the hash found to the block operator.
17
+ # <tt>request</tt> The request string to parse.
18
+ # <tt>&block</tt> Block operator
19
+ def group(request, &block)
20
+ request[:duration] ||= 0
21
+
22
+ case request[:type]
23
+ when :started
24
+ @first_request_at ||= request[:timestamp] # assume time-based order of file
25
+ @last_request_at = request[:timestamp] # assume time-based order of file
26
+ @request_time_graph[request[:timestamp][11..12].to_i] +=1
27
+ @methods[request[:method].to_sym] += 1
28
+
29
+ when :completed
30
+ @request_count += 1
31
+ hash = block_given? ? yield(request) : request.hash
32
+
33
+ @actions[hash] ||= {:count => 0, :total_time => 0.0, :total_db_time => 0.0, :total_rendering_time => 0.0,
34
+ :min_time => request[:duration], :max_time => request[:duration] }
35
+
36
+ @actions[hash][:count] += 1
37
+ @actions[hash][:total_time] += request[:duration]
38
+ @actions[hash][:total_db_time] += request[:db] if request[:db]
39
+ @actions[hash][:total_db_time] += request[:duration] - request[:rendering] if @calculate_database
40
+
41
+ @actions[hash][:total_rendering_time] += request[:rendering] if request[:rendering]
42
+
43
+ @actions[hash][:min_time] = [@actions[hash][:min_time], request[:duration]].min
44
+ @actions[hash][:max_time] = [@actions[hash][:min_time], request[:duration]].max
45
+ @actions[hash][:mean_time] = @actions[hash][:total_time] / @actions[hash][:count].to_f
46
+
47
+ @actions[hash][:mean_db_time] = @actions[hash][:total_db_time] / @actions[hash][:count].to_f
48
+ @actions[hash][:mean_rendering_time] = @actions[hash][:total_rendering_time] / @actions[hash][:count].to_f
49
+
50
+ if request[:duration] > @blocker_duration
51
+ @blockers[hash] ||= { :count => 0, :total_time => 0.0 }
52
+ @blockers[hash][:count] += 1
53
+ @blockers[hash][:total_time] += request[:duration]
54
+ end
55
+
56
+ when :failed
57
+ hash = request[:error]
58
+ @errors[hash] ||= {:count => 0, :exception_strings => {}}
59
+ @errors[hash][:count] +=1
60
+
61
+ @errors[hash][:exception_strings][request[:exception_string]] ||= 0
62
+ @errors[hash][:exception_strings][request[:exception_string]] += 1
63
+ end
64
+ end
65
+
66
+ end
67
+ 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
@@ -0,0 +1,236 @@
1
+ #
2
+ # Ruby/ProgressBar - a text progress bar library
3
+ #
4
+ # Copyright (C) 2001-2005 Satoru Takabayashi <satoru@namazu.org>
5
+ # All rights reserved.
6
+ # This is free software with ABSOLUTELY NO WARRANTY.
7
+ #
8
+ # You can redistribute it and/or modify it under the terms
9
+ # of Ruby's license.
10
+ #
11
+
12
+ class ProgressBar
13
+ VERSION = "0.9"
14
+
15
+ def initialize (title, total, out = STDERR)
16
+ @title = title
17
+ @total = total
18
+ @out = out
19
+ @terminal_width = 80
20
+ @bar_mark = '='
21
+ @current = 0
22
+ @previous = 0
23
+ @finished_p = false
24
+ @start_time = Time.now
25
+ @previous_time = @start_time
26
+ @title_width = 24
27
+ @format = "%-#{@title_width}s %3d%% %s %s"
28
+ @format_arguments = [:title, :percentage, :bar, :stat]
29
+ clear
30
+ show
31
+ end
32
+ attr_reader :title
33
+ attr_reader :current
34
+ attr_reader :total
35
+ attr_accessor :start_time
36
+
37
+ private
38
+ def fmt_bar
39
+ bar_width = do_percentage * @terminal_width / 100
40
+ sprintf("[%s%s]",
41
+ @bar_mark * bar_width,
42
+ " " * (@terminal_width - bar_width))
43
+ end
44
+
45
+ def fmt_percentage
46
+ do_percentage
47
+ end
48
+
49
+ def fmt_stat
50
+ if @finished_p then elapsed else eta end
51
+ end
52
+
53
+ def fmt_stat_for_file_transfer
54
+ if @finished_p then
55
+ sprintf("%s %s %s", bytes, transfer_rate, elapsed)
56
+ else
57
+ sprintf("%s %s %s", bytes, transfer_rate, eta)
58
+ end
59
+ end
60
+
61
+ def fmt_title
62
+ @title[0,(@title_width - 1)] + ":"
63
+ end
64
+
65
+ def convert_bytes (bytes)
66
+ if bytes < 1024
67
+ sprintf("%6dB", bytes)
68
+ elsif bytes < 1024 * 1000 # 1000kb
69
+ sprintf("%5.1fKB", bytes.to_f / 1024)
70
+ elsif bytes < 1024 * 1024 * 1000 # 1000mb
71
+ sprintf("%5.1fMB", bytes.to_f / 1024 / 1024)
72
+ else
73
+ sprintf("%5.1fGB", bytes.to_f / 1024 / 1024 / 1024)
74
+ end
75
+ end
76
+
77
+ def transfer_rate
78
+ bytes_per_second = @current.to_f / (Time.now - @start_time)
79
+ sprintf("%s/s", convert_bytes(bytes_per_second))
80
+ end
81
+
82
+ def bytes
83
+ convert_bytes(@current)
84
+ end
85
+
86
+ def format_time (t)
87
+ t = t.to_i
88
+ sec = t % 60
89
+ min = (t / 60) % 60
90
+ hour = t / 3600
91
+ sprintf("%02d:%02d:%02d", hour, min, sec);
92
+ end
93
+
94
+ # ETA stands for Estimated Time of Arrival.
95
+ def eta
96
+ if @current == 0
97
+ white("ETA: --:--:--")
98
+ else
99
+ elapsed = Time.now - @start_time
100
+ eta = elapsed * @total / @current - elapsed;
101
+ sprintf(white("ETA: %s"), format_time(eta))
102
+ end
103
+ end
104
+
105
+ def elapsed
106
+ elapsed = Time.now - @start_time
107
+ sprintf("Time: %s", format_time(elapsed))
108
+ end
109
+
110
+ def eol
111
+ if @finished_p then "\n" else "\r" end
112
+ end
113
+
114
+ def do_percentage
115
+ if @total.zero?
116
+ 100
117
+ else
118
+ @current * 100 / @total
119
+ end
120
+ end
121
+
122
+ def get_width
123
+ # FIXME: I don't know how portable it is.
124
+ default_width = 80
125
+ begin
126
+ tiocgwinsz = 0x5413
127
+ data = [0, 0, 0, 0].pack("SSSS")
128
+ if @out.ioctl(tiocgwinsz, data) >= 0 then
129
+ rows, cols, xpixels, ypixels = data.unpack("SSSS")
130
+ if cols >= 0 then cols else default_width end
131
+ else
132
+ default_width
133
+ end
134
+ rescue Exception
135
+ default_width
136
+ end
137
+ end
138
+
139
+ def show
140
+ arguments = @format_arguments.map {|method|
141
+ method = sprintf("fmt_%s", method)
142
+ send(method)
143
+ }
144
+ line = sprintf(@format, *arguments)
145
+
146
+ width = get_width
147
+ if line.length == width - 1
148
+ @out.print(line + eol)
149
+ @out.flush
150
+ elsif line.length >= width
151
+ @terminal_width = [@terminal_width - (line.length - width + 1), 0].max
152
+ if @terminal_width == 0 then @out.print(line + eol) else show end
153
+ else # line.length < width - 1
154
+ @terminal_width += width - line.length + 1
155
+ show
156
+ end
157
+ @previous_time = Time.now
158
+ end
159
+
160
+ def show_if_needed
161
+ if @total.zero?
162
+ cur_percentage = 100
163
+ prev_percentage = 0
164
+ else
165
+ cur_percentage = (@current * 100 / @total).to_i
166
+ prev_percentage = (@previous * 100 / @total).to_i
167
+ end
168
+
169
+ # Use "!=" instead of ">" to support negative changes
170
+ if cur_percentage != prev_percentage ||
171
+ Time.now - @previous_time >= 1 || @finished_p
172
+ show
173
+ end
174
+ end
175
+
176
+ public
177
+ def clear
178
+ @out.print "\r"
179
+ @out.print(" " * (get_width - 1))
180
+ @out.print "\r"
181
+ end
182
+
183
+ def finish
184
+ @current = @total
185
+ @finished_p = true
186
+ show
187
+ end
188
+
189
+ def finished?
190
+ @finished_p
191
+ end
192
+
193
+ def file_transfer_mode
194
+ @format_arguments = [:title, :percentage, :bar, :stat_for_file_transfer]
195
+ end
196
+
197
+ def format= (format)
198
+ @format = format
199
+ end
200
+
201
+ def format_arguments= (arguments)
202
+ @format_arguments = arguments
203
+ end
204
+
205
+ def halt
206
+ @finished_p = true
207
+ show
208
+ end
209
+
210
+ def inc (step = 1)
211
+ @current += step
212
+ @current = @total if @current > @total
213
+ show_if_needed
214
+ @previous = @current
215
+ end
216
+
217
+ def set (count)
218
+ if count < 0 || count > @total
219
+ raise "invalid count: #{count} (total: #{@total})"
220
+ end
221
+ @current = count
222
+ show_if_needed
223
+ @previous = @current
224
+ end
225
+
226
+ def inspect
227
+ "#<ProgressBar:#{@current}/#{@total}>"
228
+ end
229
+ end
230
+
231
+ class ReversedProgressBar < ProgressBar
232
+ def do_percentage
233
+ 100 - super
234
+ end
235
+ end
236
+