ruby-prof 0.4.1-mswin32 → 0.5.0-mswin32

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.
Files changed (58) hide show
  1. data/CHANGES +30 -0
  2. data/README +65 -25
  3. data/Rakefile +33 -32
  4. data/bin/ruby-prof +100 -83
  5. data/examples/graph.html +65 -69
  6. data/ext/measure_allocations.h +43 -0
  7. data/ext/measure_cpu_time.h +138 -0
  8. data/ext/measure_process_time.h +41 -0
  9. data/ext/measure_wall_time.h +42 -0
  10. data/ext/ruby_prof.c +737 -653
  11. data/lib/ruby-prof.rb +41 -38
  12. data/lib/ruby-prof/abstract_printer.rb +42 -0
  13. data/lib/ruby-prof/call_tree_printer.rb +69 -0
  14. data/lib/ruby-prof/flat_printer.rb +78 -75
  15. data/lib/ruby-prof/graph_html_printer.rb +241 -228
  16. data/lib/ruby-prof/graph_printer.rb +160 -141
  17. data/lib/ruby-prof/profile_test_case.rb +80 -0
  18. data/lib/ruby-prof/rails_plugin/ruby-prof/init.rb +6 -0
  19. data/lib/ruby-prof/rails_plugin/ruby-prof/lib/profiling.rb +52 -0
  20. data/lib/ruby-prof/task.rb +147 -0
  21. data/lib/ruby_prof.so +0 -0
  22. data/test/basic_test.rb +65 -35
  23. data/test/duplicate_names_test.rb +20 -24
  24. data/test/gc.log +5 -0
  25. data/test/measure_mode_test.rb +79 -0
  26. data/test/module_test.rb +31 -18
  27. data/test/no_method_class_test.rb +14 -0
  28. data/test/prime1.rb +17 -0
  29. data/test/prime2.rb +26 -0
  30. data/test/prime3.rb +17 -0
  31. data/test/prime_test.rb +10 -10
  32. data/test/printers_test.rb +14 -12
  33. data/test/profile_unit_test.rb +24 -0
  34. data/test/recursive_test.rb +105 -17
  35. data/test/singleton_test.rb +38 -0
  36. data/test/start_test.rb +24 -0
  37. data/test/test_helper.rb +33 -29
  38. data/test/test_suite.rb +10 -2
  39. data/test/thread_test.rb +123 -17
  40. data/test/timing_test.rb +70 -29
  41. metadata +28 -30
  42. data/doc/created.rid +0 -1
  43. data/doc/files/LICENSE.html +0 -0
  44. data/doc/files/README.html +0 -376
  45. data/doc/files/bin/ruby-prof.html +0 -143
  46. data/doc/files/examples/flat_txt.html +0 -179
  47. data/doc/files/examples/graph_html.html +0 -948
  48. data/doc/files/examples/graph_txt.html +0 -297
  49. data/doc/files/ext/ruby_prof_c.html +0 -101
  50. data/doc/files/lib/ruby-prof/flat_printer_rb.html +0 -101
  51. data/doc/files/lib/ruby-prof/graph_html_printer_rb.html +0 -108
  52. data/doc/files/lib/ruby-prof/graph_printer_rb.html +0 -101
  53. data/doc/files/lib/ruby-prof/profiletask_rb.html +0 -109
  54. data/doc/files/lib/ruby-prof_rb.html +0 -111
  55. data/doc/files/lib/unprof_rb.html +0 -108
  56. data/doc/rdoc-style.css +0 -208
  57. data/lib/ruby-prof/profiletask.rb +0 -150
  58. data/test/clock_mode_test.rb +0 -73
@@ -1,141 +1,160 @@
1
- module RubyProf
2
- # Generates graph[link:files/examples/graph_txt.html] profile reports as text.
3
- # To use the graph printer:
4
- #
5
- # result = RubyProf.profile do
6
- # [code to profile]
7
- # end
8
- #
9
- # printer = RubyProf::GraphPrinter.new(result, 5)
10
- # printer.print(STDOUT, 0)
11
- #
12
- # The constructor takes two arguments. The first is
13
- # a RubyProf::Result object generated from a profiling
14
- # run. The second is the minimum %total (the methods
15
- # total time divided by the overall total time) that
16
- # a method must take for it to be printed out in
17
- # the report. Use this parameter to eliminate methods
18
- # that are not important to the overall profiling results.
19
-
20
- class GraphPrinter
21
- PERCENTAGE_WIDTH = 8
22
- TIME_WIDTH = 10
23
- CALL_WIDTH = 20
24
-
25
- # Create a GraphPrinter. Result is a RubyProf::Result
26
- # object generated from a profiling run.
27
- def initialize(result, min_percent = 0)
28
- @result = result
29
- @min_percent = min_percent
30
- end
31
-
32
- # Print a graph report to the provided output.
33
- #
34
- # output - Any IO oject, including STDOUT or a file.
35
- # The default value is STDOUT.
36
- #
37
- # min_percent - The minimum %total (the methods
38
- # total time divided by the overall total time) that
39
- # a method must take for it to be printed out in
40
- # the report. Default value is 0.
41
- def print(output = STDOUT, min_percent = 0)
42
- @output = output
43
- @min_percent = min_percent
44
-
45
- print_threads
46
- end
47
-
48
- private
49
- def print_threads
50
- # sort assumes that spawned threads have higher object_ids
51
- @result.threads.sort.each do |thread_id, methods|
52
- print_methods(thread_id, methods)
53
- @output << "\n" * 2
54
- end
55
- end
56
-
57
- def print_methods(thread_id, methods)
58
- toplevel = @result.toplevel(thread_id)
59
- total_time = toplevel.total_time
60
- if total_time == 0
61
- total_time = 0.01
62
- end
63
-
64
- print_heading(thread_id)
65
-
66
- # Print each method
67
- methods.sort.reverse.each do |pair|
68
- name = pair[0]
69
- method = pair[1]
70
- total_percentage = (method.total_time/total_time) * 100
71
- self_percentage = (method.self_time/total_time) * 100
72
-
73
- next if total_percentage < @min_percent
74
-
75
- @output << "-" * 80 << "\n"
76
-
77
- print_parents(thread_id, method)
78
-
79
- # 1 is for % sign
80
- @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage)
81
- @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage)
82
- @output << sprintf("%#{TIME_WIDTH}.2f", method.total_time)
83
- @output << sprintf("%#{TIME_WIDTH}.2f", method.self_time)
84
- @output << sprintf("%#{TIME_WIDTH}.2f", method.children_time)
85
- @output << sprintf("%#{CALL_WIDTH}i", method.called)
86
- @output << sprintf(" %s", name)
87
- @output << "\n"
88
-
89
- print_children(thread_id, method)
90
- end
91
- end
92
-
93
- def print_heading(thread_id)
94
- @output << "Thread ID: #{thread_id}\n"
95
- # 1 is for % sign
96
- @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%total")
97
- @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%self")
98
- @output << sprintf("%#{TIME_WIDTH}s", "total")
99
- @output << sprintf("%#{TIME_WIDTH}s", "self")
100
- @output << sprintf("%#{TIME_WIDTH+2}s", "children")
101
- @output << sprintf("%#{CALL_WIDTH-2}s", "calls")
102
- @output << " Name"
103
- @output << "\n"
104
- end
105
-
106
- def print_parents(thread_id, method)
107
- method.parents.each do |name, call_info|
108
- @output << " " * 2 * PERCENTAGE_WIDTH
109
- @output << sprintf("%#{TIME_WIDTH}.2f", call_info.total_time)
110
- @output << sprintf("%#{TIME_WIDTH}.2f", call_info.self_time)
111
- @output << sprintf("%#{TIME_WIDTH}.2f", call_info.children_time)
112
-
113
- call_called = "#{call_info.called}/#{method.called}"
114
- @output << sprintf("%#{CALL_WIDTH}s", call_called)
115
- @output << sprintf(" %s", name)
116
- @output << "\n"
117
- end
118
- end
119
-
120
- def print_children(thread_id, method)
121
- a = method.children
122
- method.children.each do |name, call_info|
123
- # Get children method
124
- methods = @result.threads[thread_id]
125
- children = methods[name]
126
-
127
- @output << " " * 2 * PERCENTAGE_WIDTH
128
-
129
- @output << sprintf("%#{TIME_WIDTH}.2f", call_info.total_time)
130
- @output << sprintf("%#{TIME_WIDTH}.2f", call_info.self_time)
131
- @output << sprintf("%#{TIME_WIDTH}.2f", call_info.children_time)
132
-
133
- call_called = "#{call_info.called}/#{children.called}"
134
- @output << sprintf("%#{CALL_WIDTH}s", call_called)
135
- @output << sprintf(" %s", name)
136
- @output << "\n"
137
- end
138
- end
139
- end
140
- end
141
-
1
+ require 'ruby-prof/abstract_printer'
2
+
3
+ module RubyProf
4
+ # Generates graph[link:files/examples/graph_txt.html] profile reports as text.
5
+ # To use the graph printer:
6
+ #
7
+ # result = RubyProf.profile do
8
+ # [code to profile]
9
+ # end
10
+ #
11
+ # printer = RubyProf::GraphPrinter.new(result, 5)
12
+ # printer.print(STDOUT, 0)
13
+ #
14
+ # The constructor takes two arguments. The first is
15
+ # a RubyProf::Result object generated from a profiling
16
+ # run. The second is the minimum %total (the methods
17
+ # total time divided by the overall total time) that
18
+ # a method must take for it to be printed out in
19
+ # the report. Use this parameter to eliminate methods
20
+ # that are not important to the overall profiling results.
21
+
22
+ class GraphPrinter < AbstractPrinter
23
+ PERCENTAGE_WIDTH = 8
24
+ TIME_WIDTH = 10
25
+ CALL_WIDTH = 17
26
+
27
+ # Create a GraphPrinter. Result is a RubyProf::Result
28
+ # object generated from a profiling run.
29
+ def initialize(result)
30
+ super(result)
31
+ @thread_times = Hash.new
32
+ calculate_thread_times
33
+ end
34
+
35
+ def calculate_thread_times
36
+ # Cache thread times since this is an expensive
37
+ # operation with the required sorting
38
+ @result.threads.each do |thread_id, methods|
39
+ top = methods.sort.last
40
+
41
+ thread_time = 0.01
42
+ thread_time = top.total_time if top.total_time > 0
43
+
44
+ @thread_times[thread_id] = thread_time
45
+ end
46
+ end
47
+
48
+ # Print a graph report to the provided output.
49
+ #
50
+ # output - Any IO oject, including STDOUT or a file.
51
+ # The default value is STDOUT.
52
+ #
53
+ # options - Hash of print options. See #setup_options
54
+ # for more information.
55
+ #
56
+ def print(output = STDOUT, options = {})
57
+ @output = output
58
+ setup_options(options)
59
+ print_threads
60
+ end
61
+
62
+ private
63
+ def print_threads
64
+ # sort assumes that spawned threads have higher object_ids
65
+ @result.threads.sort.each do |thread_id, methods|
66
+ print_methods(thread_id, methods)
67
+ @output << "\n" * 2
68
+ end
69
+ end
70
+
71
+ def print_methods(thread_id, methods)
72
+ # Sort methods from longest to shortest total time
73
+ methods = methods.sort
74
+
75
+ toplevel = methods.last
76
+ total_time = toplevel.total_time
77
+ if total_time == 0
78
+ total_time = 0.01
79
+ end
80
+
81
+ print_heading(thread_id)
82
+
83
+ # Print each method in total time order
84
+ methods.reverse_each do |method|
85
+ total_percentage = (method.total_time/total_time) * 100
86
+ self_percentage = (method.self_time/total_time) * 100
87
+
88
+ next if total_percentage < min_percent
89
+
90
+ @output << "-" * 80 << "\n"
91
+
92
+ print_parents(thread_id, method)
93
+
94
+ # 1 is for % sign
95
+ @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage)
96
+ @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage)
97
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.total_time)
98
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.self_time)
99
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.wait_time)
100
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.children_time)
101
+ @output << sprintf("%#{CALL_WIDTH}i", method.called)
102
+ @output << sprintf(" %s", method_name(method))
103
+ @output << "\n"
104
+
105
+ print_children(method)
106
+ end
107
+ end
108
+
109
+ def print_heading(thread_id)
110
+ @output << "Thread ID: #{thread_id}\n"
111
+ @output << "Total Time: #{@thread_times[thread_id]}\n"
112
+ @output << "\n"
113
+
114
+ # 1 is for % sign
115
+ @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%total")
116
+ @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%self")
117
+ @output << sprintf("%#{TIME_WIDTH}s", "total")
118
+ @output << sprintf("%#{TIME_WIDTH}s", "self")
119
+ @output << sprintf("%#{TIME_WIDTH}s", "wait")
120
+ @output << sprintf("%#{TIME_WIDTH}s", "child")
121
+ @output << sprintf("%#{CALL_WIDTH}s", "calls")
122
+ @output << " Name"
123
+ @output << "\n"
124
+ end
125
+
126
+ def print_parents(thread_id, method)
127
+ method.parents.each do |caller|
128
+ @output << " " * 2 * PERCENTAGE_WIDTH
129
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.total_time)
130
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.self_time)
131
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.wait_time)
132
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.children_time)
133
+
134
+ call_called = "#{caller.called}/#{method.called}"
135
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
136
+ @output << sprintf(" %s", caller.target.full_name)
137
+ @output << "\n"
138
+ end
139
+ end
140
+
141
+ def print_children(method)
142
+ method.children.each do |child|
143
+ # Get children method
144
+
145
+ @output << " " * 2 * PERCENTAGE_WIDTH
146
+
147
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.total_time)
148
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.self_time)
149
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.wait_time)
150
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.children_time)
151
+
152
+ call_called = "#{child.called}/#{child.target.called}"
153
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
154
+ @output << sprintf(" %s", child.target.full_name)
155
+ @output << "\n"
156
+ end
157
+ end
158
+ end
159
+ end
160
+
@@ -0,0 +1,80 @@
1
+ # Make sure to first load the libraries we will override
2
+ require 'test/unit'
3
+ require 'ruby-prof'
4
+
5
+ module Test
6
+ module Unit
7
+ class TestCase
8
+
9
+ alias :run__profile__ :run
10
+
11
+ def run(result, &block)
12
+ test_name = @method_name.to_sym
13
+ alias_test_name = (@method_name + '__profile__').to_sym
14
+
15
+ self.class.class_eval("alias :#{alias_test_name} :#{test_name}")
16
+
17
+ self.class.send(:define_method, test_name) do
18
+ # Run the profiler
19
+ RubyProf.start
20
+ __send__(alias_test_name)
21
+ result = RubyProf.stop
22
+
23
+ create_output_directory
24
+
25
+ # Get the result file name
26
+ file_name = name.gsub(/\(/, '_').gsub(/\)/, '')
27
+ file_name = self.underscore(file_name)
28
+ file_path = File.join(output_directory, file_name)
29
+ file_path += file_extension
30
+
31
+ # Create a printer
32
+ printer = self.printer.new(result)
33
+
34
+ # Write the results
35
+ File.open(file_path, 'w') do |file|
36
+ printer.print(file, min_percent)
37
+ end
38
+ end
39
+
40
+ self.run__profile__(result, &block)
41
+ end
42
+
43
+ # Taken from rails
44
+ def underscore(camel_cased_word)
45
+ camel_cased_word.to_s.gsub(/::/, '/').
46
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
47
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
48
+ tr("-", "_").downcase
49
+ end
50
+
51
+ # Add some additional methods
52
+ def min_percent
53
+ 1
54
+ end
55
+
56
+ def output_directory
57
+ # Put results in subdirectory called profile
58
+ File.join(Dir.getwd, 'profile')
59
+ end
60
+
61
+ def create_output_directory
62
+ if not File.exist?(output_directory)
63
+ Dir.mkdir(output_directory)
64
+ end
65
+ end
66
+
67
+ def file_extension
68
+ if printer == RubyProf::FlatPrinter
69
+ '.html'
70
+ else
71
+ '.txt'
72
+ end
73
+ end
74
+
75
+ def printer
76
+ RubyProf::GraphHtmlPrinter
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,6 @@
1
+ require 'profiling'
2
+
3
+ ActionController::Base.class_eval do
4
+ include ActionController::Profiling
5
+ end
6
+
@@ -0,0 +1,52 @@
1
+ require 'ruby-prof'
2
+
3
+ module ActionController #:nodoc:
4
+ # The ruby-prof module times the performance of actions and reports to the logger. If the Active Record
5
+ # package has been included, a separate timing section for database calls will be added as well.
6
+ module Profiling #:nodoc:
7
+ def self.included(base)
8
+ base.class_eval do
9
+ alias_method_chain :perform_action, :profiling
10
+ end
11
+ end
12
+
13
+ def perform_action_with_profiling
14
+ if not logger or
15
+ not logger.level == Logger::DEBUG
16
+ perform_action_without_profiling
17
+ else
18
+ result = RubyProf.profile do
19
+ perform_action_without_profiling
20
+ end
21
+
22
+ output = StringIO.new
23
+ output << " [#{complete_request_uri rescue "unknown"}]"
24
+ output << "\n\n"
25
+
26
+ # Create a flat printer
27
+ printer = RubyProf::FlatPrinter.new(result)
28
+
29
+ # Skip anything less than 1% - which is a lot of
30
+ # stuff in Rails. Don't print the source file
31
+ # its too noisy.
32
+ printer.print(output, {:min_percent => 1,
33
+ :print_file => false})
34
+ logger.info(output.string)
35
+
36
+ ## Example for Graph html printer
37
+ #printer = RubyProf::GraphHtmlPrinter.new(result)
38
+ #File.open('c:/temp/request.html', 'w') do |file|
39
+ #printer.print(file, {:min_percent => 1,
40
+ #:print_file => true})
41
+ #end
42
+
43
+ ## Used for KCacheGrind visualizations
44
+ #printer = RubyProf::CallTreePrinter.new(result)
45
+ #File.open('c:/temp/callgrind.out', 'w') do |file|
46
+ #printer.print(file, {:min_percent => 1,
47
+ #:print_file => true})
48
+ #end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'fileutils'
6
+
7
+ module RubyProf
8
+
9
+ # Define a task library for profiling unit tests with ruby-prof.
10
+ #
11
+ # All of the options provided by
12
+ # the Rake:TestTask are supported except the loader
13
+ # which is set to ruby-prof. For detailed information
14
+ # please refer to the Rake:TestTask documentation.
15
+ #
16
+ # ruby-prof specific options include:
17
+ #
18
+ # output_dir - For each file specified an output
19
+ # file with profile information will be
20
+ # written to the output directory.
21
+ # By default, the output directory is
22
+ # called "profile" and is created underneath
23
+ # the current working directory.
24
+ #
25
+ # printer - Specifies the output printer. Valid values include
26
+ # :flat, :graph, :graph_html and :call_tree.
27
+ #
28
+ # min_percent - Methods that take less than the specified percent
29
+ # will not be written out.
30
+ #
31
+ # Example:
32
+ #
33
+ # require 'ruby-prof/task'
34
+ #
35
+ # RubyProf::ProfileTask.new do |t|
36
+ # t.test_files = FileList['test/test*.rb']
37
+ # t.output_dir = "c:/temp"
38
+ # t.printer = :graph
39
+ # t.min_percent = 10
40
+ # end
41
+ #
42
+ # If rake is invoked with a "TEST=filename" command line option,
43
+ # then the list of test files will be overridden to include only the
44
+ # filename specified on the command line. This provides an easy way
45
+ # to run just one test.
46
+ #
47
+ # If rake is invoked with a "TESTOPTS=options" command line option,
48
+ # then the given options are passed to the test process after a
49
+ # '--'. This allows Test::Unit options to be passed to the test
50
+ # suite.
51
+ #
52
+ # Examples:
53
+ #
54
+ # rake profile # run tests normally
55
+ # rake profile TEST=just_one_file.rb # run just one test file.
56
+ # rake profile TESTOPTS="-v" # run in verbose mode
57
+ # rake profile TESTOPTS="--runner=fox" # use the fox test runner
58
+
59
+ class ProfileTask < Rake::TestTask
60
+ attr_accessor :output_dir
61
+ attr_accessor :min_percent
62
+ attr_accessor :printer
63
+
64
+ def initialize(name = :profile)
65
+ super(name)
66
+ end
67
+
68
+ # Create the tasks defined by this task lib.
69
+ def define
70
+ lib_path = @libs.join(File::PATH_SEPARATOR)
71
+ desc "Profile" + (@name==:profile ? "" : " for #{@name}")
72
+
73
+ task @name do
74
+ create_output_directory
75
+
76
+ @ruby_opts.unshift( "-I#{lib_path}" )
77
+ @ruby_opts.unshift( "-w" ) if @warning
78
+ @ruby_opts.push("-S ruby-prof")
79
+ @ruby_opts.push("--printer #{@printer}")
80
+ @ruby_opts.push("--min_percent #{@min_percent}")
81
+
82
+ file_list.each do |file_path|
83
+ run_script(file_path)
84
+ end
85
+ end
86
+ self
87
+ end
88
+
89
+ # Run script
90
+ def run_script(script_path)
91
+ run_code = ''
92
+ RakeFileUtils.verbose(@verbose) do
93
+ file_name = File.basename(script_path, File.extname(script_path))
94
+ case @printer
95
+ when :flat, :graph, :call_tree
96
+ file_name += ".txt"
97
+ when :graph_html
98
+ file_name += ".html"
99
+ else
100
+ file_name += ".txt"
101
+ end
102
+
103
+ output_file_path = File.join(output_directory, file_name)
104
+
105
+ command_line = @ruby_opts.join(" ") +
106
+ " --file=" + output_file_path +
107
+ " " + script_path
108
+
109
+ puts "ruby " + command_line
110
+ # We have to catch the exeption to continue on. However,
111
+ # the error message will have been output to STDERR
112
+ # already by the time we get here so we don't have to
113
+ # do that again
114
+ begin
115
+ ruby command_line
116
+ rescue => e
117
+ STDOUT << e << "\n"
118
+ STDOUT.flush
119
+ end
120
+ puts ""
121
+ puts ""
122
+ end
123
+ end
124
+
125
+ def output_directory
126
+ File.expand_path(@output_dir)
127
+ end
128
+
129
+ def create_output_directory
130
+ if not File.exist?(output_directory)
131
+ Dir.mkdir(output_directory)
132
+ end
133
+ end
134
+
135
+ def clean_output_directory
136
+ if File.exist?(output_directory)
137
+ files = Dir.glob(output_directory + '/*')
138
+ FileUtils.rm(files)
139
+ end
140
+ end
141
+
142
+ def option_list # :nodoc:
143
+ ENV['OPTIONS'] || @options.join(" ") || ""
144
+ end
145
+ end
146
+ end
147
+