wvanbergen-request-log-analyzer 1.3.5 → 1.3.6
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.
- data/lib/cli/database_console_init.rb +2 -1
 - data/lib/request_log_analyzer.rb +1 -1
 - data/lib/request_log_analyzer/aggregator.rb +1 -5
 - data/lib/request_log_analyzer/aggregator/database_inserter.rb +4 -5
 - data/lib/request_log_analyzer/controller.rb +10 -21
 - data/lib/request_log_analyzer/database.rb +16 -91
 - data/lib/request_log_analyzer/database/base.rb +4 -4
 - data/lib/request_log_analyzer/database/request.rb +22 -0
 - data/lib/request_log_analyzer/database/source.rb +13 -0
 - data/lib/request_log_analyzer/database/warning.rb +14 -0
 - data/lib/request_log_analyzer/file_format.rb +1 -13
 - data/lib/request_log_analyzer/file_format/amazon_s3.rb +1 -2
 - data/lib/request_log_analyzer/file_format/apache.rb +8 -10
 - data/lib/request_log_analyzer/file_format/merb.rb +21 -5
 - data/lib/request_log_analyzer/file_format/rails.rb +8 -14
 - data/lib/request_log_analyzer/filter.rb +6 -10
 - data/lib/request_log_analyzer/filter/anonymize.rb +2 -1
 - data/lib/request_log_analyzer/log_processor.rb +6 -8
 - data/lib/request_log_analyzer/request.rb +47 -35
 - data/lib/request_log_analyzer/source.rb +4 -6
 - data/lib/request_log_analyzer/source/database_loader.rb +3 -7
 - data/lib/request_log_analyzer/source/log_parser.rb +3 -6
 - data/lib/request_log_analyzer/tracker.rb +12 -19
 - data/lib/request_log_analyzer/tracker/hourly_spread.rb +1 -2
 - data/request-log-analyzer.gemspec +3 -3
 - data/spec/database.yml +6 -0
 - data/spec/unit/aggregator/database_inserter_spec.rb +3 -3
 - data/spec/unit/database/base_class_spec.rb +9 -16
 - data/spec/unit/database/database_spec.rb +9 -14
 - data/spec/unit/tracker/tracker_api_spec.rb +111 -36
 - metadata +7 -4
 
| 
         @@ -3,6 +3,7 @@ $:.unshift(File.dirname(__FILE__) + '/..') 
     | 
|
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            $database = RequestLogAnalyzer::Database.new(ENV['RLA_DBCONSOLE_DATABASE'])
         
     | 
| 
       5 
5 
     | 
    
         
             
            $database.load_database_schema!
         
     | 
| 
      
 6 
     | 
    
         
            +
            $database.register_default_orm_classes!
         
     | 
| 
       6 
7 
     | 
    
         | 
| 
       7 
8 
     | 
    
         
             
            require 'cli/tools'
         
     | 
| 
       8 
9 
     | 
    
         | 
| 
         @@ -39,4 +40,4 @@ end 
     | 
|
| 
       39 
40 
     | 
    
         
             
            puts "request-log-analyzer database console"
         
     | 
| 
       40 
41 
     | 
    
         
             
            puts "-------------------------------------"
         
     | 
| 
       41 
42 
     | 
    
         
             
            puts "The following ActiveRecord classes are available:"
         
     | 
| 
       42 
     | 
    
         
            -
            puts $database.orm_classes.join(", ")
         
     | 
| 
      
 43 
     | 
    
         
            +
            puts $database.orm_classes.map { |k| k.name.split('::').last }.join(", ")
         
     | 
    
        data/lib/request_log_analyzer.rb
    CHANGED
    
    | 
         @@ -11,7 +11,7 @@ module RequestLogAnalyzer 
     | 
|
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
              # The current version of request-log-analyzer.
         
     | 
| 
       13 
13 
     | 
    
         
             
              # This will be diplayed in output reports etc.
         
     | 
| 
       14 
     | 
    
         
            -
              VERSION = "1.3. 
     | 
| 
      
 14 
     | 
    
         
            +
              VERSION = "1.3.6"
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
       16 
16 
     | 
    
         
             
              # Loads constants in the RequestLogAnalyzer namespace using self.load_default_class_file(base, const)
         
     | 
| 
       17 
17 
     | 
    
         
             
              # <tt>const</tt>:: The constant that is not yet loaded in the RequestLogAnalyzer namespace. This should be passed as a string or symbol.
         
     | 
| 
         @@ -8,16 +8,12 @@ module RequestLogAnalyzer::Aggregator 
     | 
|
| 
       8 
8 
     | 
    
         
             
              # every aggregator should comply (by simply subclassing this class).
         
     | 
| 
       9 
9 
     | 
    
         
             
              class Base
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                 
     | 
| 
       12 
     | 
    
         
            -
                
         
     | 
| 
       13 
     | 
    
         
            -
                attr_reader :options
         
     | 
| 
       14 
     | 
    
         
            -
                attr_reader :source
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :options, :source
         
     | 
| 
       15 
12 
     | 
    
         | 
| 
       16 
13 
     | 
    
         
             
                # Intializes a new RequestLogAnalyzer::Aggregator::Base instance
         
     | 
| 
       17 
14 
     | 
    
         
             
                # It will include the specific file format module.
         
     | 
| 
       18 
15 
     | 
    
         
             
                def initialize(source, options = {})
         
     | 
| 
       19 
16 
     | 
    
         
             
                  @source = source
         
     | 
| 
       20 
     | 
    
         
            -
                  self.register_file_format(source.file_format)
         
     | 
| 
       21 
17 
     | 
    
         
             
                  @options = options
         
     | 
| 
       22 
18 
     | 
    
         
             
                end
         
     | 
| 
       23 
19 
     | 
    
         | 
| 
         @@ -31,7 +31,7 @@ module RequestLogAnalyzer::Aggregator 
     | 
|
| 
       31 
31 
     | 
    
         
             
                # This will create a record in the requests table and create a record for every line that has been parsed,
         
     | 
| 
       32 
32 
     | 
    
         
             
                # in which the captured values will be stored.
         
     | 
| 
       33 
33 
     | 
    
         
             
                def aggregate(request)
         
     | 
| 
       34 
     | 
    
         
            -
                  @request_object =  
     | 
| 
      
 34 
     | 
    
         
            +
                  @request_object = RequestLogAnalyzer::Database::Request.new(:first_lineno => request.first_lineno, :last_lineno => request.last_lineno)
         
     | 
| 
       35 
35 
     | 
    
         
             
                  request.lines.each do |line|
         
     | 
| 
       36 
36 
     | 
    
         
             
                    class_columns = database.get_class(line[:line_type]).column_names.reject { |column| ['id', 'source_id', 'request_id'].include?(column) }
         
     | 
| 
       37 
37 
     | 
    
         
             
                    attributes = Hash[*line.select { |(k, v)| class_columns.include?(k.to_s) }.flatten]
         
     | 
| 
         @@ -45,14 +45,14 @@ module RequestLogAnalyzer::Aggregator 
     | 
|
| 
       45 
45 
     | 
    
         | 
| 
       46 
46 
     | 
    
         
             
                # Finalizes the aggregator by closing the connection to the database
         
     | 
| 
       47 
47 
     | 
    
         
             
                def finalize
         
     | 
| 
       48 
     | 
    
         
            -
                  @request_count =  
     | 
| 
      
 48 
     | 
    
         
            +
                  @request_count = RequestLogAnalyzer::Database::Request.count
         
     | 
| 
       49 
49 
     | 
    
         
             
                  database.disconnect
         
     | 
| 
       50 
50 
     | 
    
         
             
                  database.remove_orm_classes!
         
     | 
| 
       51 
51 
     | 
    
         
             
                end
         
     | 
| 
       52 
52 
     | 
    
         | 
| 
       53 
53 
     | 
    
         
             
                # Records w warining in the warnings table.
         
     | 
| 
       54 
54 
     | 
    
         
             
                def warning(type, message, lineno)
         
     | 
| 
       55 
     | 
    
         
            -
                   
     | 
| 
      
 55 
     | 
    
         
            +
                  RequestLogAnalyzer::Database::Warning.create!(:warning_type => type.to_s, :message => message, :lineno => lineno)
         
     | 
| 
       56 
56 
     | 
    
         
             
                end
         
     | 
| 
       57 
57 
     | 
    
         | 
| 
       58 
58 
     | 
    
         
             
                # Records source changes in the sources table
         
     | 
| 
         @@ -60,8 +60,7 @@ module RequestLogAnalyzer::Aggregator 
     | 
|
| 
       60 
60 
     | 
    
         
             
                  if File.exist?(filename)
         
     | 
| 
       61 
61 
     | 
    
         
             
                    case change
         
     | 
| 
       62 
62 
     | 
    
         
             
                    when :started
         
     | 
| 
       63 
     | 
    
         
            -
                       
     | 
| 
       64 
     | 
    
         
            -
                      @sources[filename] = database.source_class.create!(:filename => filename)
         
     | 
| 
      
 63 
     | 
    
         
            +
                      @sources[filename] = RequestLogAnalyzer::Database::Source.create!(:filename => filename)
         
     | 
| 
       65 
64 
     | 
    
         
             
                    when :finished
         
     | 
| 
       66 
65 
     | 
    
         
             
                      @sources[filename].update_attributes!(:filesize => File.size(filename), :mtime => File.mtime(filename))
         
     | 
| 
       67 
66 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -17,14 +17,7 @@ module RequestLogAnalyzer 
     | 
|
| 
       17 
17 
     | 
    
         
             
              # from several logrotated log files.
         
     | 
| 
       18 
18 
     | 
    
         
             
              class Controller
         
     | 
| 
       19 
19 
     | 
    
         | 
| 
       20 
     | 
    
         
            -
                 
     | 
| 
       21 
     | 
    
         
            -
                
         
     | 
| 
       22 
     | 
    
         
            -
                attr_reader :aggregators
         
     | 
| 
       23 
     | 
    
         
            -
                attr_reader :filters
         
     | 
| 
       24 
     | 
    
         
            -
                attr_reader :log_parser
         
     | 
| 
       25 
     | 
    
         
            -
                attr_reader :source
         
     | 
| 
       26 
     | 
    
         
            -
                attr_reader :output
         
     | 
| 
       27 
     | 
    
         
            -
                attr_reader :options
         
     | 
| 
      
 20 
     | 
    
         
            +
                attr_reader :source, :filters, :aggregators, :output, :options
         
     | 
| 
       28 
21 
     | 
    
         | 
| 
       29 
22 
     | 
    
         
             
                # Builds a RequestLogAnalyzer::Controller given parsed command line arguments
         
     | 
| 
       30 
23 
     | 
    
         
             
                # <tt>arguments<tt> A CommandLine::Arguments hash containing parsed commandline parameters.
         
     | 
| 
         @@ -35,8 +28,8 @@ module RequestLogAnalyzer 
     | 
|
| 
       35 
28 
     | 
    
         
             
                  # Database command line options
         
     | 
| 
       36 
29 
     | 
    
         
             
                  options[:database]       = arguments[:database] if arguments[:database]
         
     | 
| 
       37 
30 
     | 
    
         
             
                  options[:reset_database] = arguments[:reset_database]
         
     | 
| 
       38 
     | 
    
         
            -
                  options[:debug] 
     | 
| 
       39 
     | 
    
         
            -
                  options[:dump] 
     | 
| 
      
 31 
     | 
    
         
            +
                  options[:debug]          = arguments[:debug]
         
     | 
| 
      
 32 
     | 
    
         
            +
                  options[:dump]           = arguments[:dump]
         
     | 
| 
       40 
33 
     | 
    
         
             
                  options[:parse_strategy] = arguments[:parse_strategy]
         
     | 
| 
       41 
34 
     | 
    
         
             
                  options[:no_progress]    = arguments[:no_progress]
         
     | 
| 
       42 
35 
     | 
    
         | 
| 
         @@ -125,17 +118,13 @@ module RequestLogAnalyzer 
     | 
|
| 
       125 
118 
     | 
    
         
             
                  @filters     = []
         
     | 
| 
       126 
119 
     | 
    
         
             
                  @output      = options[:output]
         
     | 
| 
       127 
120 
     | 
    
         | 
| 
       128 
     | 
    
         
            -
                  #  
     | 
| 
       129 
     | 
    
         
            -
                   
     | 
| 
       130 
     | 
    
         
            -
                  
         
     | 
| 
       131 
     | 
    
         
            -
                  # Pass all warnings to every aggregator so they can do something useful with them.
         
     | 
| 
       132 
     | 
    
         
            -
                  @source.warning = lambda { |type, message, lineno|  @aggregators.each { |agg| agg.warning(type, message, lineno) } } if @source
         
     | 
| 
       133 
     | 
    
         
            -
             
     | 
| 
       134 
     | 
    
         
            -
                  # Handle progress messagess
         
     | 
| 
       135 
     | 
    
         
            -
                  @source.progress = lambda { |message, value| handle_progress(message, value) } if @source && !options[:no_progress]
         
     | 
| 
      
 121 
     | 
    
         
            +
                  # Register the request format for this session after checking its validity
         
     | 
| 
      
 122 
     | 
    
         
            +
                  raise "Invalid file format!" unless @source.file_format.valid?
         
     | 
| 
       136 
123 
     | 
    
         | 
| 
       137 
     | 
    
         
            -
                  #  
     | 
| 
       138 
     | 
    
         
            -
                  @source. 
     | 
| 
      
 124 
     | 
    
         
            +
                  # Install event handlers for wrnings, progress updates and source changes
         
     | 
| 
      
 125 
     | 
    
         
            +
                  @source.warning        = lambda { |type, message, lineno|  @aggregators.each { |agg| agg.warning(type, message, lineno) } }
         
     | 
| 
      
 126 
     | 
    
         
            +
                  @source.progress       = lambda { |message, value| handle_progress(message, value) } unless options[:no_progress]
         
     | 
| 
      
 127 
     | 
    
         
            +
                  @source.source_changes = lambda { |change, filename| handle_source_change(change, filename) }
         
     | 
| 
       139 
128 
     | 
    
         
             
                end
         
     | 
| 
       140 
129 
     | 
    
         | 
| 
       141 
130 
     | 
    
         
             
                # Progress function.
         
     | 
| 
         @@ -176,7 +165,7 @@ module RequestLogAnalyzer 
     | 
|
| 
       176 
165 
     | 
    
         
             
                # Adds a request filter to the controller.
         
     | 
| 
       177 
166 
     | 
    
         
             
                def add_filter(filter, filter_options = {})
         
     | 
| 
       178 
167 
     | 
    
         
             
                  filter = RequestLogAnalyzer::Filter.const_get(RequestLogAnalyzer::to_camelcase(filter)) if filter.kind_of?(Symbol)
         
     | 
| 
       179 
     | 
    
         
            -
                  @filters << filter.new(file_format, @options.merge(filter_options))
         
     | 
| 
      
 168 
     | 
    
         
            +
                  @filters << filter.new(source.file_format, @options.merge(filter_options))
         
     | 
| 
       180 
169 
     | 
    
         
             
                end
         
     | 
| 
       181 
170 
     | 
    
         | 
| 
       182 
171 
     | 
    
         
             
                # Push a request through the entire filterchain (@filters).
         
     | 
| 
         @@ -10,7 +10,7 @@ class RequestLogAnalyzer::Database 
     | 
|
| 
       10 
10 
     | 
    
         
             
              include RequestLogAnalyzer::Database::Connection
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
              attr_accessor :file_format
         
     | 
| 
       13 
     | 
    
         
            -
              attr_reader : 
     | 
| 
      
 13 
     | 
    
         
            +
              attr_reader :line_classes
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
15 
     | 
    
         
             
              def initialize(connection_identifier = nil)
         
     | 
| 
       16 
16 
     | 
    
         
             
                @line_classes = []
         
     | 
| 
         @@ -24,97 +24,17 @@ class RequestLogAnalyzer::Database 
     | 
|
| 
       24 
24 
     | 
    
         
             
                Object.const_get("#{line_type}_line".camelize)
         
     | 
| 
       25 
25 
     | 
    
         
             
              end
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
     | 
    
         
            -
               
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
              # It will create the class if not previously done so. The class will
         
     | 
| 
       30 
     | 
    
         
            -
              # include a create_table! method the migrate the database.
         
     | 
| 
       31 
     | 
    
         
            -
              def request_class
         
     | 
| 
       32 
     | 
    
         
            -
                @request_class ||= begin
         
     | 
| 
       33 
     | 
    
         
            -
                  klass = Class.new(RequestLogAnalyzer::Database::Base) do
         
     | 
| 
       34 
     | 
    
         
            -
                    
         
     | 
| 
       35 
     | 
    
         
            -
                    def lines
         
     | 
| 
       36 
     | 
    
         
            -
                      @lines ||= begin
         
     | 
| 
       37 
     | 
    
         
            -
                        lines = []
         
     | 
| 
       38 
     | 
    
         
            -
                        self.class.reflections.each { |r, d| lines += self.send(r).all }
         
     | 
| 
       39 
     | 
    
         
            -
                        lines.sort
         
     | 
| 
       40 
     | 
    
         
            -
                      end
         
     | 
| 
       41 
     | 
    
         
            -
                    end
         
     | 
| 
       42 
     | 
    
         
            -
                    
         
     | 
| 
       43 
     | 
    
         
            -
                    # Creates the requests table
         
     | 
| 
       44 
     | 
    
         
            -
                    def self.create_table!
         
     | 
| 
       45 
     | 
    
         
            -
                      unless database.connection.table_exists?(:requests)
         
     | 
| 
       46 
     | 
    
         
            -
                        database.connection.create_table(:requests) do |t|
         
     | 
| 
       47 
     | 
    
         
            -
                          t.column :first_lineno, :integer
         
     | 
| 
       48 
     | 
    
         
            -
                          t.column :last_lineno,  :integer
         
     | 
| 
       49 
     | 
    
         
            -
                        end
         
     | 
| 
       50 
     | 
    
         
            -
                      end
         
     | 
| 
       51 
     | 
    
         
            -
                    end
         
     | 
| 
       52 
     | 
    
         
            -
                  end
         
     | 
| 
       53 
     | 
    
         
            -
                  
         
     | 
| 
       54 
     | 
    
         
            -
                  Object.const_set('Request', klass) 
         
     | 
| 
       55 
     | 
    
         
            -
                  Object.const_get('Request')
         
     | 
| 
       56 
     | 
    
         
            -
                end
         
     | 
| 
       57 
     | 
    
         
            -
              end
         
     | 
| 
       58 
     | 
    
         
            -
              
         
     | 
| 
       59 
     | 
    
         
            -
              # Returns the Source ORM class for the current database.
         
     | 
| 
       60 
     | 
    
         
            -
              #
         
     | 
| 
       61 
     | 
    
         
            -
              # It will create the class if not previously done so. The class will
         
     | 
| 
       62 
     | 
    
         
            -
              # include a create_table! method the migrate the database.  
         
     | 
| 
       63 
     | 
    
         
            -
              def source_class
         
     | 
| 
       64 
     | 
    
         
            -
                @source_class ||= begin
         
     | 
| 
       65 
     | 
    
         
            -
                  klass = Class.new(RequestLogAnalyzer::Database::Base) do
         
     | 
| 
       66 
     | 
    
         
            -
                    
         
     | 
| 
       67 
     | 
    
         
            -
                    # Creates the sources table
         
     | 
| 
       68 
     | 
    
         
            -
                    def self.create_table!
         
     | 
| 
       69 
     | 
    
         
            -
                      unless database.connection.table_exists?(:sources)
         
     | 
| 
       70 
     | 
    
         
            -
                        database.connection.create_table(:sources) do |t|
         
     | 
| 
       71 
     | 
    
         
            -
                          t.column :filename, :string
         
     | 
| 
       72 
     | 
    
         
            -
                          t.column :mtime,    :datetime
         
     | 
| 
       73 
     | 
    
         
            -
                          t.column :filesize, :integer
         
     | 
| 
       74 
     | 
    
         
            -
                        end
         
     | 
| 
       75 
     | 
    
         
            -
                      end
         
     | 
| 
       76 
     | 
    
         
            -
                    end
         
     | 
| 
       77 
     | 
    
         
            -
                  end
         
     | 
| 
       78 
     | 
    
         
            -
                  
         
     | 
| 
       79 
     | 
    
         
            -
                  Object.const_set('Source', klass)
         
     | 
| 
       80 
     | 
    
         
            -
                  Object.const_get('Source')
         
     | 
| 
       81 
     | 
    
         
            -
                end
         
     | 
| 
       82 
     | 
    
         
            -
              end
         
     | 
| 
       83 
     | 
    
         
            -
              
         
     | 
| 
       84 
     | 
    
         
            -
              
         
     | 
| 
       85 
     | 
    
         
            -
              # Returns the Warning ORM class for the current database.
         
     | 
| 
       86 
     | 
    
         
            -
              #
         
     | 
| 
       87 
     | 
    
         
            -
              # It will create the class if not previously done so. The class will
         
     | 
| 
       88 
     | 
    
         
            -
              # include a create_table! method the migrate the database.  
         
     | 
| 
       89 
     | 
    
         
            -
              def warning_class
         
     | 
| 
       90 
     | 
    
         
            -
                @warning_class ||= begin
         
     | 
| 
       91 
     | 
    
         
            -
                  klass = Class.new(RequestLogAnalyzer::Database::Base) do
         
     | 
| 
       92 
     | 
    
         
            -
                    
         
     | 
| 
       93 
     | 
    
         
            -
                    # Creates the warnings table
         
     | 
| 
       94 
     | 
    
         
            -
                    def self.create_table!
         
     | 
| 
       95 
     | 
    
         
            -
                      unless database.connection.table_exists?(:warnings)
         
     | 
| 
       96 
     | 
    
         
            -
                        database.connection.create_table(:warnings) do |t|
         
     | 
| 
       97 
     | 
    
         
            -
                          t.column  :warning_type, :string, :limit => 30, :null => false
         
     | 
| 
       98 
     | 
    
         
            -
                          t.column  :message, :string
         
     | 
| 
       99 
     | 
    
         
            -
                          t.column  :source_id, :integer
         
     | 
| 
       100 
     | 
    
         
            -
                          t.column  :lineno, :integer
         
     | 
| 
       101 
     | 
    
         
            -
                        end
         
     | 
| 
       102 
     | 
    
         
            -
                      end
         
     | 
| 
       103 
     | 
    
         
            -
                    end
         
     | 
| 
       104 
     | 
    
         
            -
                  end
         
     | 
| 
       105 
     | 
    
         
            -
                  
         
     | 
| 
       106 
     | 
    
         
            -
                  Object.const_set('Warning', klass)
         
     | 
| 
       107 
     | 
    
         
            -
                  Object.const_get('Warning')
         
     | 
| 
       108 
     | 
    
         
            -
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
              def default_classes
         
     | 
| 
      
 28 
     | 
    
         
            +
                [RequestLogAnalyzer::Database::Request, RequestLogAnalyzer::Database::Source, RequestLogAnalyzer::Database::Warning]
         
     | 
| 
       109 
29 
     | 
    
         
             
              end
         
     | 
| 
       110 
30 
     | 
    
         | 
| 
       111 
31 
     | 
    
         
             
              # Loads the ORM classes by inspecting the tables in the current database
         
     | 
| 
       112 
32 
     | 
    
         
             
              def load_database_schema!
         
     | 
| 
       113 
33 
     | 
    
         
             
                connection.tables.map do |table|
         
     | 
| 
       114 
34 
     | 
    
         
             
                  case table.to_sym
         
     | 
| 
       115 
     | 
    
         
            -
                  when :warnings then  
     | 
| 
       116 
     | 
    
         
            -
                  when :sources  then  
     | 
| 
       117 
     | 
    
         
            -
                  when :requests then  
     | 
| 
      
 35 
     | 
    
         
            +
                  when :warnings then RequestLogAnalyzer::Database::Warning
         
     | 
| 
      
 36 
     | 
    
         
            +
                  when :sources  then RequestLogAnalyzer::Database::Source
         
     | 
| 
      
 37 
     | 
    
         
            +
                  when :requests then RequestLogAnalyzer::Database::Request
         
     | 
| 
       118 
38 
     | 
    
         
             
                  else load_activerecord_class(table)
         
     | 
| 
       119 
39 
     | 
    
         
             
                  end
         
     | 
| 
       120 
40 
     | 
    
         
             
                end
         
     | 
| 
         @@ -122,7 +42,7 @@ class RequestLogAnalyzer::Database 
     | 
|
| 
       122 
42 
     | 
    
         | 
| 
       123 
43 
     | 
    
         
             
              # Returns an array of all the ActiveRecord-bases ORM classes for this database
         
     | 
| 
       124 
44 
     | 
    
         
             
              def orm_classes
         
     | 
| 
       125 
     | 
    
         
            -
                 
     | 
| 
      
 45 
     | 
    
         
            +
                default_classes + line_classes
         
     | 
| 
       126 
46 
     | 
    
         
             
              end
         
     | 
| 
       127 
47 
     | 
    
         | 
| 
       128 
48 
     | 
    
         
             
              # Loads an ActiveRecord-based class that correspond to the given parameter, which can either be
         
     | 
| 
         @@ -146,8 +66,6 @@ class RequestLogAnalyzer::Database 
     | 
|
| 
       146 
66 
     | 
    
         | 
| 
       147 
67 
     | 
    
         
             
              def fileformat_classes
         
     | 
| 
       148 
68 
     | 
    
         
             
                raise "No file_format provided!" unless file_format
         
     | 
| 
       149 
     | 
    
         
            -
                
         
     | 
| 
       150 
     | 
    
         
            -
                default_classes = [request_class, source_class, warning_class]
         
     | 
| 
       151 
69 
     | 
    
         
             
                line_classes    = file_format.line_definitions.map { |(name, definition)| load_activerecord_class(definition) }
         
     | 
| 
       152 
70 
     | 
    
         
             
                return default_classes + line_classes
         
     | 
| 
       153 
71 
     | 
    
         
             
              end
         
     | 
| 
         @@ -164,12 +82,19 @@ class RequestLogAnalyzer::Database 
     | 
|
| 
       164 
82 
     | 
    
         
             
                remove_orm_classes!
         
     | 
| 
       165 
83 
     | 
    
         
             
              end
         
     | 
| 
       166 
84 
     | 
    
         | 
| 
      
 85 
     | 
    
         
            +
              # Registers the default ORM classes in the default namespace
         
     | 
| 
      
 86 
     | 
    
         
            +
              def register_default_orm_classes!
         
     | 
| 
      
 87 
     | 
    
         
            +
                Object.const_set('Request', RequestLogAnalyzer::Database::Request)
         
     | 
| 
      
 88 
     | 
    
         
            +
                Object.const_set('Source',  RequestLogAnalyzer::Database::Source)
         
     | 
| 
      
 89 
     | 
    
         
            +
                Object.const_set('Warning', RequestLogAnalyzer::Database::Warning)
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
              
         
     | 
| 
       167 
92 
     | 
    
         
             
              # Unregisters every ORM class constant
         
     | 
| 
       168 
93 
     | 
    
         
             
              def remove_orm_classes!
         
     | 
| 
       169 
94 
     | 
    
         
             
                orm_classes.each do |klass|
         
     | 
| 
       170 
95 
     | 
    
         
             
                  if klass.respond_to?(:name) && !klass.name.blank?
         
     | 
| 
       171 
     | 
    
         
            -
             
     | 
| 
       172 
     | 
    
         
            -
                    Object.send(:remove_const,  
     | 
| 
      
 96 
     | 
    
         
            +
                    klass_name = klass.name.split('::').last
         
     | 
| 
      
 97 
     | 
    
         
            +
                    Object.send(:remove_const, klass_name) if Object.const_defined?(klass_name)
         
     | 
| 
       173 
98 
     | 
    
         
             
                  end
         
     | 
| 
       174 
99 
     | 
    
         
             
                end
         
     | 
| 
       175 
100 
     | 
    
         
             
              end
         
     | 
| 
         @@ -32,8 +32,8 @@ class RequestLogAnalyzer::Database::Base < ActiveRecord::Base 
     | 
|
| 
       32 
32 
     | 
    
         
             
                  klass.send(:serialize, capture[:name], Hash)
         
     | 
| 
       33 
33 
     | 
    
         
             
                end
         
     | 
| 
       34 
34 
     | 
    
         | 
| 
       35 
     | 
    
         
            -
                 
     | 
| 
       36 
     | 
    
         
            -
                 
     | 
| 
      
 35 
     | 
    
         
            +
                RequestLogAnalyzer::Database::Request.has_many  "#{definition.name}_lines".to_sym
         
     | 
| 
      
 36 
     | 
    
         
            +
                RequestLogAnalyzer::Database::Source.has_many   "#{definition.name}_lines".to_sym
         
     | 
| 
       37 
37 
     | 
    
         | 
| 
       38 
38 
     | 
    
         
             
                return klass
         
     | 
| 
       39 
39 
     | 
    
         
             
              end
         
     | 
| 
         @@ -46,12 +46,12 @@ class RequestLogAnalyzer::Database::Base < ActiveRecord::Base 
     | 
|
| 
       46 
46 
     | 
    
         | 
| 
       47 
47 
     | 
    
         
             
                if klass.column_names.include?('request_id')
         
     | 
| 
       48 
48 
     | 
    
         
             
                  klass.belongs_to :request
         
     | 
| 
       49 
     | 
    
         
            -
                   
     | 
| 
      
 49 
     | 
    
         
            +
                  RequestLogAnalyzer::Database::Request.has_many table.to_sym
         
     | 
| 
       50 
50 
     | 
    
         
             
                end
         
     | 
| 
       51 
51 
     | 
    
         | 
| 
       52 
52 
     | 
    
         
             
                if klass.column_names.include?('source_id')
         
     | 
| 
       53 
53 
     | 
    
         
             
                  klass.belongs_to :source
         
     | 
| 
       54 
     | 
    
         
            -
                   
     | 
| 
      
 54 
     | 
    
         
            +
                  RequestLogAnalyzer::Database::Source.has_many table.to_sym
         
     | 
| 
       55 
55 
     | 
    
         
             
                end
         
     | 
| 
       56 
56 
     | 
    
         | 
| 
       57 
57 
     | 
    
         
             
                return klass
         
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class RequestLogAnalyzer::Database::Request < RequestLogAnalyzer::Database::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
              
         
     | 
| 
      
 3 
     | 
    
         
            +
              # Returns an array of all the Line objects of this request in the correct order.
         
     | 
| 
      
 4 
     | 
    
         
            +
              def lines
         
     | 
| 
      
 5 
     | 
    
         
            +
                @lines ||= begin
         
     | 
| 
      
 6 
     | 
    
         
            +
                  lines = []
         
     | 
| 
      
 7 
     | 
    
         
            +
                  self.class.reflections.each { |r, d| lines += self.send(r).all }
         
     | 
| 
      
 8 
     | 
    
         
            +
                  lines.sort
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
              
         
     | 
| 
      
 12 
     | 
    
         
            +
              # Creates the table to store requests in.
         
     | 
| 
      
 13 
     | 
    
         
            +
              def self.create_table!
         
     | 
| 
      
 14 
     | 
    
         
            +
                unless database.connection.table_exists?(:requests)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  database.connection.create_table(:requests) do |t|
         
     | 
| 
      
 16 
     | 
    
         
            +
                    t.column :first_lineno, :integer
         
     | 
| 
      
 17 
     | 
    
         
            +
                    t.column :last_lineno,  :integer
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class RequestLogAnalyzer::Database::Source < RequestLogAnalyzer::Database::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
              
         
     | 
| 
      
 3 
     | 
    
         
            +
              def self.create_table!
         
     | 
| 
      
 4 
     | 
    
         
            +
                unless database.connection.table_exists?(:sources)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  database.connection.create_table(:sources) do |t|
         
     | 
| 
      
 6 
     | 
    
         
            +
                    t.column :filename, :string
         
     | 
| 
      
 7 
     | 
    
         
            +
                    t.column :mtime,    :datetime
         
     | 
| 
      
 8 
     | 
    
         
            +
                    t.column :filesize, :integer
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,14 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class RequestLogAnalyzer::Database::Warning < RequestLogAnalyzer::Database::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              def self.create_table!
         
     | 
| 
      
 4 
     | 
    
         
            +
                unless database.connection.table_exists?(:warnings)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  database.connection.create_table(:warnings) do |t|
         
     | 
| 
      
 6 
     | 
    
         
            +
                    t.column  :warning_type, :string, :limit => 30, :null => false
         
     | 
| 
      
 7 
     | 
    
         
            +
                    t.column  :message, :string
         
     | 
| 
      
 8 
     | 
    
         
            +
                    t.column  :source_id, :integer
         
     | 
| 
      
 9 
     | 
    
         
            +
                    t.column  :lineno, :integer
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -42,19 +42,7 @@ module RequestLogAnalyzer::FileFormat 
     | 
|
| 
       42 
42 
     | 
    
         
             
                raise "Invalid FileFormat class" unless klass.kind_of?(Class) && klass.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
         
     | 
| 
       43 
43 
     | 
    
         | 
| 
       44 
44 
     | 
    
         
             
                @current_file_format = klass.create(*args) # return an instance of the class
         
     | 
| 
       45 
     | 
    
         
            -
              end 
     | 
| 
       46 
     | 
    
         
            -
              
         
     | 
| 
       47 
     | 
    
         
            -
              # Makes classes aware of a file format by registering the file_format variable
         
     | 
| 
       48 
     | 
    
         
            -
              module Awareness
         
     | 
| 
       49 
     | 
    
         
            -
                
         
     | 
| 
       50 
     | 
    
         
            -
                def self.included(base)
         
     | 
| 
       51 
     | 
    
         
            -
                  base.send(:attr_reader, :file_format)
         
     | 
| 
       52 
     | 
    
         
            -
                end
         
     | 
| 
       53 
     | 
    
         
            -
                
         
     | 
| 
       54 
     | 
    
         
            -
                def register_file_format(format)
         
     | 
| 
       55 
     | 
    
         
            -
                  @file_format = format
         
     | 
| 
       56 
     | 
    
         
            -
                end
         
     | 
| 
       57 
     | 
    
         
            -
              end  
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
       58 
46 
     | 
    
         | 
| 
       59 
47 
     | 
    
         
             
              # Base class for all log file format definitions. This class provides functions for subclasses to 
         
     | 
| 
       60 
48 
     | 
    
         
             
              # define their LineDefinitions and to define a summary report.
         
     | 
| 
         @@ -48,8 +48,7 @@ module RequestLogAnalyzer::FileFormat 
     | 
|
| 
       48 
48 
     | 
    
         
             
                  # Do not use DateTime.parse, but parse the timestamp ourselves to return a integer
         
     | 
| 
       49 
49 
     | 
    
         
             
                  # to speed up parsing.
         
     | 
| 
       50 
50 
     | 
    
         
             
                  def convert_timestamp(value, definition)
         
     | 
| 
       51 
     | 
    
         
            -
                     
     | 
| 
       52 
     | 
    
         
            -
                    "#{d[2]}#{MONTHS[d[1]]}#{d[0]}#{d[3]}#{d[4]}#{d[5]}".to_i
         
     | 
| 
      
 51 
     | 
    
         
            +
                    "#{value[7,4]}#{MONTHS[value[3,3]]}#{value[0,2]}#{value[12,2]}#{value[15,2]}#{value[18,2]}".to_i
         
     | 
| 
       53 
52 
     | 
    
         
             
                  end
         
     | 
| 
       54 
53 
     | 
    
         | 
| 
       55 
54 
     | 
    
         
             
                  # Make sure that the string '-' is parsed as a nil value.
         
     | 
| 
         @@ -92,18 +92,13 @@ module RequestLogAnalyzer::FileFormat 
     | 
|
| 
       92 
92 
     | 
    
         | 
| 
       93 
93 
     | 
    
         
             
                  analyze.frequency :category => :http_method, :amount => 20, :title => "HTTP methods"  if line_definition.captures?(:http_method)
         
     | 
| 
       94 
94 
     | 
    
         
             
                  analyze.frequency :category => :http_status, :amount => 20, :title => "HTTP statuses" if line_definition.captures?(:http_status)
         
     | 
| 
       95 
     | 
    
         
            -
                  analyze.frequency :category =>  
     | 
| 
      
 95 
     | 
    
         
            +
                  analyze.frequency :category => lambda { |r| r.category }, :amount => 20, :title => "Most popular URIs"    if line_definition.captures?(:path)
         
     | 
| 
       96 
96 
     | 
    
         | 
| 
       97 
97 
     | 
    
         
             
                  analyze.frequency :category => :user_agent, :amount => 20, :title => "User agents"    if line_definition.captures?(:user_agent)
         
     | 
| 
       98 
98 
     | 
    
         
             
                  analyze.frequency :category => :referer,    :amount => 20, :title => "Referers"       if line_definition.captures?(:referer)
         
     | 
| 
       99 
99 
     | 
    
         | 
| 
       100 
     | 
    
         
            -
                   
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
       102 
     | 
    
         
            -
                  end
         
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
                  if line_definition.captures?(:path) && line_definition.captures?(:bytes_sent)
         
     | 
| 
       105 
     | 
    
         
            -
                    analyze.traffic :traffic => :bytes_sent, :category => :path , :title => 'Traffic'
         
     | 
| 
       106 
     | 
    
         
            -
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
                  analyze.duration :duration => :duration,  :category => lambda { |r| r.category }, :title => 'Request duration' if line_definition.captures?(:duration)
         
     | 
| 
      
 101 
     | 
    
         
            +
                  analyze.traffic  :traffic => :bytes_sent, :category => lambda { |r| r.category }, :title => 'Traffic'          if line_definition.captures?(:bytes_sent)
         
     | 
| 
       107 
102 
     | 
    
         | 
| 
       108 
103 
     | 
    
         
             
                  return analyze.trackers
         
     | 
| 
       109 
104 
     | 
    
         
             
                end
         
     | 
| 
         @@ -111,14 +106,17 @@ module RequestLogAnalyzer::FileFormat 
     | 
|
| 
       111 
106 
     | 
    
         
             
                # Define a custom Request class for the Apache file format to speed up timestamp handling.
         
     | 
| 
       112 
107 
     | 
    
         
             
                class Request < RequestLogAnalyzer::Request
         
     | 
| 
       113 
108 
     | 
    
         | 
| 
      
 109 
     | 
    
         
            +
                  def category
         
     | 
| 
      
 110 
     | 
    
         
            +
                    first(:path)
         
     | 
| 
      
 111 
     | 
    
         
            +
                  end
         
     | 
| 
      
 112 
     | 
    
         
            +
                  
         
     | 
| 
       114 
113 
     | 
    
         
             
                  MONTHS = {'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05', 'Jun' => '06',
         
     | 
| 
       115 
114 
     | 
    
         
             
                            'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12' }
         
     | 
| 
       116 
115 
     | 
    
         | 
| 
       117 
116 
     | 
    
         
             
                  # Do not use DateTime.parse, but parse the timestamp ourselves to return a integer
         
     | 
| 
       118 
117 
     | 
    
         
             
                  # to speed up parsing.
         
     | 
| 
       119 
118 
     | 
    
         
             
                  def convert_timestamp(value, definition)
         
     | 
| 
       120 
     | 
    
         
            -
                     
     | 
| 
       121 
     | 
    
         
            -
                    "#{d[2]}#{MONTHS[d[1]]}#{d[0]}#{d[3]}#{d[4]}#{d[5]}".to_i
         
     | 
| 
      
 119 
     | 
    
         
            +
                    "#{value[7,4]}#{MONTHS[value[3,3]]}#{value[0,2]}#{value[12,2]}#{value[15,2]}#{value[18,2]}".to_i
         
     | 
| 
       122 
120 
     | 
    
         
             
                  end
         
     | 
| 
       123 
121 
     | 
    
         | 
| 
       124 
122 
     | 
    
         
             
                  # This function can be overridden to rewrite the path for better categorization in the
         
     | 
| 
         @@ -1,11 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module RequestLogAnalyzer::FileFormat
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
              # The Merb file format parses the request header with the timestamp, the params line
         
     | 
| 
      
 4 
     | 
    
         
            +
              # with the most important request information and the durations line which contains
         
     | 
| 
      
 5 
     | 
    
         
            +
              # the different request durations that can be used for analysis.
         
     | 
| 
       3 
6 
     | 
    
         
             
              class Merb < Base
         
     | 
| 
       4 
7 
     | 
    
         | 
| 
       5 
8 
     | 
    
         
             
                # ~ Started request handling: Fri Aug 29 11:10:23 +0200 2008
         
     | 
| 
       6 
9 
     | 
    
         
             
                line_definition :started do |line|
         
     | 
| 
       7 
10 
     | 
    
         
             
                  line.header = true
         
     | 
| 
       8 
     | 
    
         
            -
                  line.teaser = /Started/
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # line.teaser = /Started/
         
     | 
| 
       9 
12 
     | 
    
         
             
                  line.regexp = /Started request handling\:\ (.+)/
         
     | 
| 
       10 
13 
     | 
    
         
             
                  line.captures << { :name => :timestamp, :type => :timestamp }
         
     | 
| 
       11 
14 
     | 
    
         
             
                end    
         
     | 
| 
         @@ -13,7 +16,7 @@ module RequestLogAnalyzer::FileFormat 
     | 
|
| 
       13 
16 
     | 
    
         
             
                # ~ Params: {"action"=>"create", "controller"=>"session"}
         
     | 
| 
       14 
17 
     | 
    
         
             
                # ~ Params: {"_method"=>"delete", "authenticity_token"=>"[FILTERED]", "action"=>"d}
         
     | 
| 
       15 
18 
     | 
    
         
             
                line_definition :params do |line|
         
     | 
| 
       16 
     | 
    
         
            -
                  line.teaser = /Params/
         
     | 
| 
      
 19 
     | 
    
         
            +
                  # line.teaser = /Params/
         
     | 
| 
       17 
20 
     | 
    
         
             
                  line.regexp = /Params\:\ (\{.+\})/
         
     | 
| 
       18 
21 
     | 
    
         
             
                  line.captures << { :name => :params, :type => :eval, :provides => { 
         
     | 
| 
       19 
22 
     | 
    
         
             
                        :namespace => :string, :controller => :string, :action => :string, :format => :string, :method => :string } }
         
     | 
| 
         @@ -36,15 +39,28 @@ module RequestLogAnalyzer::FileFormat 
     | 
|
| 
       36 
39 
     | 
    
         
             
                end
         
     | 
| 
       37 
40 
     | 
    
         | 
| 
       38 
41 
     | 
    
         
             
                report do |analyze|
         
     | 
| 
       39 
     | 
    
         
            -
                   
     | 
| 
      
 42 
     | 
    
         
            +
                  
         
     | 
| 
      
 43 
     | 
    
         
            +
                  analyze.timespan
         
     | 
| 
      
 44 
     | 
    
         
            +
                  analyze.hourly_spread
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
       40 
46 
     | 
    
         
             
                  analyze.frequency :category => REQUEST_CATEGORIZER, :amount => 20, :title => "Top 20 by hits"
         
     | 
| 
       41 
     | 
    
         
            -
                  analyze.hourly_spread :line_type => :started
         
     | 
| 
       42 
47 
     | 
    
         
             
                  analyze.duration :dispatch_time, :category => REQUEST_CATEGORIZER, :title => 'Request dispatch duration'
         
     | 
| 
      
 48 
     | 
    
         
            +
                  
         
     | 
| 
       43 
49 
     | 
    
         
             
                  # analyze.duration :action_time, :category => REQUEST_CATEGORIZER, :title => 'Request action duration'
         
     | 
| 
       44 
50 
     | 
    
         
             
                  # analyze.duration :after_filters_time, :category => REQUEST_CATEGORIZER, :title => 'Request after_filter duration'
         
     | 
| 
       45 
51 
     | 
    
         
             
                  # analyze.duration :before_filters_time, :category => REQUEST_CATEGORIZER, :title => 'Request before_filter duration'
         
     | 
| 
       46 
52 
     | 
    
         
             
                end
         
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                class Request < RequestLogAnalyzer::Request
         
     | 
| 
      
 55 
     | 
    
         
            +
                  
         
     | 
| 
      
 56 
     | 
    
         
            +
                  MONTHS = {'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05', 'Jun' => '06',
         
     | 
| 
      
 57 
     | 
    
         
            +
                            'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12' }
         
     | 
| 
      
 58 
     | 
    
         
            +
                  
         
     | 
| 
      
 59 
     | 
    
         
            +
                  # Speed up timestamp conversion
         
     | 
| 
      
 60 
     | 
    
         
            +
                  def convert_timestamp(value, definition)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    "#{value[26,4]}#{MONTHS[value[4,3]]}#{value[8,2]}#{value[11,2]}#{value[14,2]}#{value[17,2]}".to_i
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
       48 
64 
     | 
    
         
             
              end
         
     | 
| 
       49 
65 
     | 
    
         | 
| 
       50 
66 
     | 
    
         
             
            end
         
     |