skylight 0.2.0.beta.3 → 0.2.0.beta.4
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.
- checksums.yaml +4 -4
- data/bin/skylight +15 -2
- data/lib/skylight.rb +18 -9
- data/lib/skylight/instrumenter.rb +25 -9
- data/lib/skylight/messages/trace.rb +34 -13
- data/lib/skylight/normalizers.rb +34 -5
- data/lib/skylight/normalizers/process_action.rb +6 -2
- data/lib/skylight/normalizers/render_collection.rb +3 -1
- data/lib/skylight/normalizers/render_partial.rb +3 -1
- data/lib/skylight/normalizers/render_template.rb +3 -1
- data/lib/skylight/normalizers/send_file.rb +24 -5
- data/lib/skylight/normalizers/sql.rb +8 -9
- data/lib/skylight/util/allocation_free.rb +15 -0
- data/lib/skylight/vendor/beefcake.rb +44 -25
- data/lib/skylight/vendor/beefcake/buffer.rb +4 -2
- data/lib/skylight/vendor/beefcake/encode.rb +20 -3
- data/lib/skylight/version.rb +1 -1
- data/lib/skylight/worker/collector.rb +6 -0
- data/lib/sql_lexer/lexer.rb +159 -56
- metadata +4 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 6c34ce7c2bd1892ae5f05d4e9cc0ee5054cbb9f1
         | 
| 4 | 
            +
              data.tar.gz: ce556c192f1f5b1d72be7680d358d8373a80cfe7
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: a9b4be609c1d76a2075cbb9ad116d724fc35be01dfe133e00978c3b09a8e32765a78d2db0ccb2f3c43a60182891a57e42ee8bcecd53ec8d50750a6fc2f38f138
         | 
| 7 | 
            +
              data.tar.gz: 082e9dd9bfc81ea18b53b830c52bf26796b90943c6af65b78723b21a8d1207478f87ac835a8386c4f0741a4fb97d9d89200ed2746b8fdadb8d9945b869c1a975
         | 
    
        data/bin/skylight
    CHANGED
    
    | @@ -1,3 +1,16 @@ | |
| 1 1 | 
             
            #!/usr/bin/env ruby
         | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 2 | 
            +
            #
         | 
| 3 | 
            +
            # This file was generated by Bundler.
         | 
| 4 | 
            +
            #
         | 
| 5 | 
            +
            # The application 'skylight' is installed as part of a gem, and
         | 
| 6 | 
            +
            # this file is here to facilitate running it.
         | 
| 7 | 
            +
            #
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            require 'pathname'
         | 
| 10 | 
            +
            ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
         | 
| 11 | 
            +
              Pathname.new(__FILE__).realpath)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            require 'rubygems'
         | 
| 14 | 
            +
            require 'bundler/setup'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            load Gem.bin_path('skylight', 'skylight')
         | 
    
        data/lib/skylight.rb
    CHANGED
    
    | @@ -59,9 +59,10 @@ module Skylight | |
| 59 59 | 
             
                noise
         | 
| 60 60 | 
             
                other)
         | 
| 61 61 |  | 
| 62 | 
            -
              TIER_REGEX = /^(?:#{TIERS.join('|')})(?:\.|$)/
         | 
| 63 | 
            -
              CATEGORY_REGEX = /^[a-z0-9_-]+(?:\.[a-z0-9_-]+)*$/ | 
| 62 | 
            +
              TIER_REGEX = /^(?:#{TIERS.join('|')})(?:\.|$)/u
         | 
| 63 | 
            +
              CATEGORY_REGEX = /^[a-z0-9_-]+(?:\.[a-z0-9_-]+)*$/iu
         | 
| 64 64 | 
             
              DEFAULT_CATEGORY = "app.block".freeze
         | 
| 65 | 
            +
              DEFAULT_OPTIONS = { category: DEFAULT_CATEGORY }
         | 
| 65 66 |  | 
| 66 67 | 
             
              def self.start!(*args)
         | 
| 67 68 | 
             
                Instrumenter.start!(*args)
         | 
| @@ -71,23 +72,27 @@ module Skylight | |
| 71 72 | 
             
                Instrumenter.stop!(*args)
         | 
| 72 73 | 
             
              end
         | 
| 73 74 |  | 
| 74 | 
            -
              def self.trace( | 
| 75 | 
            +
              def self.trace(title=nil, desc=nil, annot=nil)
         | 
| 75 76 | 
             
                unless inst = Instrumenter.instance
         | 
| 76 77 | 
             
                  return yield if block_given?
         | 
| 77 78 | 
             
                  return
         | 
| 78 79 | 
             
                end
         | 
| 79 80 |  | 
| 80 | 
            -
                 | 
| 81 | 
            +
                if block_given?
         | 
| 82 | 
            +
                  inst.trace(title, desc, annot) { yield }
         | 
| 83 | 
            +
                else
         | 
| 84 | 
            +
                  inst.trace(title, desc, annot)
         | 
| 85 | 
            +
                end
         | 
| 81 86 | 
             
              end
         | 
| 82 87 |  | 
| 83 | 
            -
              def self.instrument(opts =  | 
| 88 | 
            +
              def self.instrument(opts = DEFAULT_OPTIONS)
         | 
| 84 89 | 
             
                unless inst = Instrumenter.instance
         | 
| 85 90 | 
             
                  return yield if block_given?
         | 
| 86 91 | 
             
                  return
         | 
| 87 92 | 
             
                end
         | 
| 88 93 |  | 
| 89 94 | 
             
                if Hash === opts
         | 
| 90 | 
            -
                  category = opts.delete(:category) | 
| 95 | 
            +
                  category = opts.delete(:category)
         | 
| 91 96 | 
             
                  title    = opts.delete(:title)
         | 
| 92 97 | 
             
                  desc     = opts.delete(:description)
         | 
| 93 98 | 
             
                else
         | 
| @@ -96,16 +101,20 @@ module Skylight | |
| 96 101 | 
             
                  desc     = nil
         | 
| 97 102 | 
             
                end
         | 
| 98 103 |  | 
| 99 | 
            -
                 | 
| 104 | 
            +
                if block_given?
         | 
| 105 | 
            +
                  inst.instrument(category, title, desc) { yield }
         | 
| 106 | 
            +
                else
         | 
| 107 | 
            +
                  inst.instrument(category, title, desc)
         | 
| 108 | 
            +
                end
         | 
| 100 109 | 
             
              end
         | 
| 101 110 |  | 
| 102 | 
            -
              def self.disable | 
| 111 | 
            +
              def self.disable
         | 
| 103 112 | 
             
                unless inst = Instrumenter.instance
         | 
| 104 113 | 
             
                  return yield if block_given?
         | 
| 105 114 | 
             
                  return
         | 
| 106 115 | 
             
                end
         | 
| 107 116 |  | 
| 108 | 
            -
                inst.disable | 
| 117 | 
            +
                inst.disable { yield }
         | 
| 109 118 | 
             
              end
         | 
| 110 119 |  | 
| 111 120 | 
             
              RUBYBIN = File.join(
         | 
| @@ -1,5 +1,6 @@ | |
| 1 1 | 
             
            require 'thread'
         | 
| 2 2 | 
             
            require 'set'
         | 
| 3 | 
            +
            require 'base64'
         | 
| 3 4 |  | 
| 4 5 | 
             
            module Skylight
         | 
| 5 6 | 
             
              class Instrumenter
         | 
| @@ -55,7 +56,7 @@ module Skylight | |
| 55 56 | 
             
                  @subscriber = Subscriber.new(config, self)
         | 
| 56 57 |  | 
| 57 58 | 
             
                  @trace_info = @config[:trace_info] || TraceInfo.new
         | 
| 58 | 
            -
                  @descriptions = Hash.new { |h,k| h[k] =  | 
| 59 | 
            +
                  @descriptions = Hash.new { |h,k| h[k] = {} }
         | 
| 59 60 | 
             
                end
         | 
| 60 61 |  | 
| 61 62 | 
             
                def current_trace
         | 
| @@ -87,7 +88,7 @@ module Skylight | |
| 87 88 | 
             
                  @worker.shutdown
         | 
| 88 89 | 
             
                end
         | 
| 89 90 |  | 
| 90 | 
            -
                def trace(endpoint, cat,  | 
| 91 | 
            +
                def trace(endpoint, cat, title=nil, desc=nil, annot=nil)
         | 
| 91 92 | 
             
                  # If a trace is already in progress, continue with that one
         | 
| 92 93 | 
             
                  if trace = @trace_info.current
         | 
| 93 94 | 
             
                    t { "already tracing" }
         | 
| @@ -96,7 +97,7 @@ module Skylight | |
| 96 97 | 
             
                  end
         | 
| 97 98 |  | 
| 98 99 | 
             
                  begin
         | 
| 99 | 
            -
                    trace = Messages::Trace::Builder.new(self, endpoint, Util::Clock.micros, cat,  | 
| 100 | 
            +
                    trace = Messages::Trace::Builder.new(self, endpoint, Util::Clock.micros, cat, title, desc, annot)
         | 
| 100 101 | 
             
                  rescue Exception => e
         | 
| 101 102 | 
             
                    error e.message
         | 
| 102 103 | 
             
                    t { e.backtrace.join("\n") }
         | 
| @@ -126,7 +127,17 @@ module Skylight | |
| 126 127 | 
             
                  @disabled
         | 
| 127 128 | 
             
                end
         | 
| 128 129 |  | 
| 129 | 
            -
                 | 
| 130 | 
            +
                @scanner = StringScanner.new('')
         | 
| 131 | 
            +
                def self.match?(string, regex)
         | 
| 132 | 
            +
                  @scanner.string = string
         | 
| 133 | 
            +
                  @scanner.match?(regex)
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                def match?(string, regex)
         | 
| 137 | 
            +
                  self.class.match?(string, regex)
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                def instrument(cat, title=nil, desc=nil, annot=nil)
         | 
| 130 141 | 
             
                  unless trace = @trace_info.current
         | 
| 131 142 | 
             
                    return yield if block_given?
         | 
| 132 143 | 
             
                    return
         | 
| @@ -134,15 +145,15 @@ module Skylight | |
| 134 145 |  | 
| 135 146 | 
             
                  cat = cat.to_s
         | 
| 136 147 |  | 
| 137 | 
            -
                  unless cat  | 
| 148 | 
            +
                  unless match?(cat, CATEGORY_REGEX)
         | 
| 138 149 | 
             
                    warn "invalid skylight instrumentation category; value=%s", cat
         | 
| 139 150 | 
             
                    return yield if block_given?
         | 
| 140 151 | 
             
                    return
         | 
| 141 152 | 
             
                  end
         | 
| 142 153 |  | 
| 143 | 
            -
                  cat = "other.#{cat}" unless cat  | 
| 154 | 
            +
                  cat = "other.#{cat}" unless match?(cat, TIER_REGEX)
         | 
| 144 155 |  | 
| 145 | 
            -
                  unless sp = trace.instrument(cat,  | 
| 156 | 
            +
                  unless sp = trace.instrument(cat, title, desc, annot)
         | 
| 146 157 | 
             
                    return yield if block_given?
         | 
| 147 158 | 
             
                    return
         | 
| 148 159 | 
             
                  end
         | 
| @@ -157,16 +168,17 @@ module Skylight | |
| 157 168 | 
             
                end
         | 
| 158 169 |  | 
| 159 170 | 
             
                def limited_description(description)
         | 
| 171 | 
            +
                  endpoint = nil
         | 
| 160 172 | 
             
                  endpoint = @trace_info.current.endpoint
         | 
| 161 173 |  | 
| 162 174 | 
             
                  DESC_LOCK.synchronize do
         | 
| 163 175 | 
             
                    set = @descriptions[endpoint]
         | 
| 164 176 |  | 
| 165 177 | 
             
                    if set.size >= 100
         | 
| 166 | 
            -
                      return TOO_MANY_UNIQUES | 
| 178 | 
            +
                      return TOO_MANY_UNIQUES
         | 
| 167 179 | 
             
                    end
         | 
| 168 180 |  | 
| 169 | 
            -
                    set  | 
| 181 | 
            +
                    set[description] = true
         | 
| 170 182 | 
             
                    description
         | 
| 171 183 | 
             
                  end
         | 
| 172 184 | 
             
                end
         | 
| @@ -174,6 +186,10 @@ module Skylight | |
| 174 186 | 
             
                def error(reason, body)
         | 
| 175 187 | 
             
                  t { fmt "processing error; reason=%s; body=%s", reason, body }
         | 
| 176 188 |  | 
| 189 | 
            +
                  if body.encoding == Encoding::BINARY || !body.valid_encoding?
         | 
| 190 | 
            +
                    body = Base64.encode64(body)
         | 
| 191 | 
            +
                  end
         | 
| 192 | 
            +
             | 
| 177 193 | 
             
                  message = Skylight::Messages::Error.new(reason: reason, body: body)
         | 
| 178 194 |  | 
| 179 195 | 
             
                  unless @worker.submit(message)
         | 
| @@ -13,14 +13,17 @@ module Skylight | |
| 13 13 |  | 
| 14 14 | 
             
                    include Util::Logging
         | 
| 15 15 |  | 
| 16 | 
            -
                     | 
| 17 | 
            -
                    attr_reader   :spans, :notifications
         | 
| 16 | 
            +
                    attr_reader   :endpoint, :spans, :notifications
         | 
| 18 17 |  | 
| 19 | 
            -
                    def  | 
| 18 | 
            +
                    def endpoint=(value)
         | 
| 19 | 
            +
                      @endpoint = value.freeze
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    def initialize(instrumenter, endpoint, start, cat, title=nil, desc=nil, annot=nil)
         | 
| 20 23 | 
             
                      raise ArgumentError, 'instrumenter is required' unless instrumenter
         | 
| 21 24 |  | 
| 22 25 | 
             
                      @instrumenter = instrumenter
         | 
| 23 | 
            -
                      @endpoint     = endpoint
         | 
| 26 | 
            +
                      @endpoint     = endpoint.freeze
         | 
| 24 27 | 
             
                      @start        = start
         | 
| 25 28 | 
             
                      @spans        = []
         | 
| 26 29 | 
             
                      @stack        = []
         | 
| @@ -32,9 +35,13 @@ module Skylight | |
| 32 35 | 
             
                      # Track time
         | 
| 33 36 | 
             
                      @last_seen_time = start
         | 
| 34 37 |  | 
| 35 | 
            -
                       | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            +
                      if Hash === title
         | 
| 39 | 
            +
                        annot = title
         | 
| 40 | 
            +
                        title = desc = nil
         | 
| 41 | 
            +
                      elsif Hash === desc
         | 
| 42 | 
            +
                        annot = desc
         | 
| 43 | 
            +
                        desc = nil
         | 
| 44 | 
            +
                      end
         | 
| 38 45 |  | 
| 39 46 | 
             
                      # Create the root node
         | 
| 40 47 | 
             
                      @root = start(@start, cat, title, desc, annot)
         | 
| @@ -60,13 +67,27 @@ module Skylight | |
| 60 67 | 
             
                      nil
         | 
| 61 68 | 
             
                    end
         | 
| 62 69 |  | 
| 63 | 
            -
                    def instrument(cat,  | 
| 64 | 
            -
                       | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
                       | 
| 70 | 
            +
                    def instrument(cat, title=nil, desc=nil, annot=nil)
         | 
| 71 | 
            +
                      if Hash === title
         | 
| 72 | 
            +
                        annot = title
         | 
| 73 | 
            +
                        title = desc = nil
         | 
| 74 | 
            +
                      elsif Hash === desc
         | 
| 75 | 
            +
                        annot = desc
         | 
| 76 | 
            +
                        desc = nil
         | 
| 77 | 
            +
                      end
         | 
| 68 78 |  | 
| 69 | 
            -
                       | 
| 79 | 
            +
                      title.freeze
         | 
| 80 | 
            +
                      desc.freeze
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                      original_desc = desc
         | 
| 83 | 
            +
                      now           = adjust_for_skew(Util::Clock.micros)
         | 
| 84 | 
            +
                      desc          = @instrumenter.limited_description(desc)
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                      if desc == Instrumenter::TOO_MANY_UNIQUES
         | 
| 87 | 
            +
                        debug "[SKYLIGHT] A payload description produced <too many uniques>"
         | 
| 88 | 
            +
                        debug "original desc=%s", original_desc
         | 
| 89 | 
            +
                        debug "cat=%s, title=%s, desc=%s, annot=%s", cat, title, desc, annot.inspect
         | 
| 90 | 
            +
                      end
         | 
| 70 91 |  | 
| 71 92 | 
             
                      start(now - gc_time, cat, title, desc, annot)
         | 
| 72 93 | 
             
                    end
         | 
    
        data/lib/skylight/normalizers.rb
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            require 'skylight/normalizers/default'
         | 
| 2 | 
            +
            require 'skylight/util/allocation_free'
         | 
| 2 3 |  | 
| 3 4 | 
             
            module Skylight
         | 
| 4 5 | 
             
              # Convert AS::N events to Skylight events
         | 
| @@ -40,6 +41,8 @@ module Skylight | |
| 40 41 | 
             
                end
         | 
| 41 42 |  | 
| 42 43 | 
             
                class RenderNormalizer < Normalizer
         | 
| 44 | 
            +
                  include AllocationFree
         | 
| 45 | 
            +
             | 
| 43 46 | 
             
                  def setup
         | 
| 44 47 | 
             
                    @paths = config['normalizers.render.view_paths'] || []
         | 
| 45 48 | 
             
                  end
         | 
| @@ -54,19 +57,45 @@ module Skylight | |
| 54 57 | 
             
                  end
         | 
| 55 58 |  | 
| 56 59 | 
             
                  def relative_path(path, annotations)
         | 
| 57 | 
            -
                    return path if  | 
| 60 | 
            +
                    return path if relative_path?(path)
         | 
| 58 61 |  | 
| 59 | 
            -
                    root = @paths | 
| 62 | 
            +
                    root = array_find(@paths) { |p| path.start_with?(p) }
         | 
| 60 63 |  | 
| 61 64 | 
             
                    if root
         | 
| 62 | 
            -
                       | 
| 63 | 
            -
                       | 
| 64 | 
            -
                       | 
| 65 | 
            +
                      start = root.size
         | 
| 66 | 
            +
                      start += 1 if path.getbyte(start) == SEPARATOR_BYTE
         | 
| 67 | 
            +
                      path[start, path.size]
         | 
| 65 68 | 
             
                    else
         | 
| 66 69 | 
             
                      annotations[:skylight_error] = ["absolute_path", path]
         | 
| 67 70 | 
             
                      "Absolute Path"
         | 
| 68 71 | 
             
                    end
         | 
| 69 72 | 
             
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                private
         | 
| 75 | 
            +
                  def relative_path?(path)
         | 
| 76 | 
            +
                    !absolute_path?(path)
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  SEPARATOR_BYTE = File::SEPARATOR.ord
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  if File::NULL == "NUL"
         | 
| 82 | 
            +
                    ALT_SEPARATOR_BYTE = File::ALT_SEPARATOR && File::ALT_SEPARATOR.ord
         | 
| 83 | 
            +
                    COLON_BYTE = ":".ord
         | 
| 84 | 
            +
                    def absolute_path?(path)
         | 
| 85 | 
            +
                      if alpha?(path.getbyte(0)) && path.getbyte(1) == COLON_BYTE
         | 
| 86 | 
            +
                        byte2 = path.getbyte(2)
         | 
| 87 | 
            +
                        byte2 == SEPARATOR_BYTE || byte2 == ALT_SEPARATOR_BYTE
         | 
| 88 | 
            +
                      end
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    def alpha?(byte)
         | 
| 92 | 
            +
                      byte >= 65 and byte <= 90 || byte >= 97 and byte <= 122
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
                  else
         | 
| 95 | 
            +
                    def absolute_path?(path)
         | 
| 96 | 
            +
                      path.getbyte(0) == SEPARATOR_BYTE
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
                  end
         | 
| 70 99 | 
             
                end
         | 
| 71 100 |  | 
| 72 101 | 
             
                class Container
         | 
| @@ -3,9 +3,11 @@ module Skylight | |
| 3 3 | 
             
                class ProcessAction < Normalizer
         | 
| 4 4 | 
             
                  register "process_action.action_controller"
         | 
| 5 5 |  | 
| 6 | 
            +
                  CAT = "app.controller.request".freeze
         | 
| 7 | 
            +
             | 
| 6 8 | 
             
                  def normalize(trace, name, payload)
         | 
| 7 9 | 
             
                    trace.endpoint = controller_action(payload)
         | 
| 8 | 
            -
                    [  | 
| 10 | 
            +
                    [ CAT, trace.endpoint, nil, normalize_payload(payload) ]
         | 
| 9 11 | 
             
                  end
         | 
| 10 12 |  | 
| 11 13 | 
             
                private
         | 
| @@ -17,7 +19,9 @@ module Skylight | |
| 17 19 | 
             
                  def normalize_payload(payload)
         | 
| 18 20 | 
             
                    normalized = {}
         | 
| 19 21 |  | 
| 20 | 
            -
                    payload. | 
| 22 | 
            +
                    payload.each_key do |key|
         | 
| 23 | 
            +
                      value = payload[key]
         | 
| 24 | 
            +
             | 
| 21 25 | 
             
                      value = value.inspect unless value.is_a?(String) || value.is_a?(Numeric)
         | 
| 22 26 | 
             
                      normalized[key] = value
         | 
| 23 27 | 
             
                    end
         | 
| @@ -3,9 +3,11 @@ module Skylight | |
| 3 3 | 
             
                class RenderCollection < RenderNormalizer
         | 
| 4 4 | 
             
                  register "render_collection.action_view"
         | 
| 5 5 |  | 
| 6 | 
            +
                  CAT = "view.render.collection".freeze
         | 
| 7 | 
            +
             | 
| 6 8 | 
             
                  def normalize(trace, name, payload)
         | 
| 7 9 | 
             
                    normalize_render(
         | 
| 8 | 
            -
                       | 
| 10 | 
            +
                      CAT,
         | 
| 9 11 | 
             
                      payload,
         | 
| 10 12 | 
             
                      count: payload[:count])
         | 
| 11 13 | 
             
                  end
         | 
| @@ -3,9 +3,11 @@ module Skylight | |
| 3 3 | 
             
                class RenderPartial < RenderNormalizer
         | 
| 4 4 | 
             
                  register "render_partial.action_view"
         | 
| 5 5 |  | 
| 6 | 
            +
                  CAT = "view.render.template".freeze
         | 
| 7 | 
            +
             | 
| 6 8 | 
             
                  def normalize(trace, name, payload)
         | 
| 7 9 | 
             
                    normalize_render(
         | 
| 8 | 
            -
                       | 
| 10 | 
            +
                      CAT,
         | 
| 9 11 | 
             
                      payload,
         | 
| 10 12 | 
             
                      partial: 1)
         | 
| 11 13 | 
             
                  end
         | 
| @@ -3,9 +3,11 @@ module Skylight | |
| 3 3 | 
             
                class RenderTemplate < RenderNormalizer
         | 
| 4 4 | 
             
                  register "render_template.action_view"
         | 
| 5 5 |  | 
| 6 | 
            +
                  CAT = "view.render.template".freeze
         | 
| 7 | 
            +
             | 
| 6 8 | 
             
                  def normalize(trace, name, payload)
         | 
| 7 9 | 
             
                    normalize_render(
         | 
| 8 | 
            -
                       | 
| 10 | 
            +
                      CAT,
         | 
| 9 11 | 
             
                      payload,
         | 
| 10 12 | 
             
                      partial: 0)
         | 
| 11 13 | 
             
                  end
         | 
| @@ -10,6 +10,9 @@ module Skylight | |
| 10 10 | 
             
                  class SendFile < Normalizer
         | 
| 11 11 | 
             
                    register "send_file.action_controller"
         | 
| 12 12 |  | 
| 13 | 
            +
                    CAT = "app.controller.send_file".freeze
         | 
| 14 | 
            +
                    TITLE = "send file".freeze
         | 
| 15 | 
            +
             | 
| 13 16 | 
             
                    def normalize(trace, name, payload)
         | 
| 14 17 | 
             
                      path = payload[:path]
         | 
| 15 18 |  | 
| @@ -20,31 +23,47 @@ module Skylight | |
| 20 23 | 
             
                        disposition: normalize_disposition(payload),
         | 
| 21 24 | 
             
                        status:      normalize_status(payload) }
         | 
| 22 25 |  | 
| 23 | 
            -
                      title =  | 
| 26 | 
            +
                      title = TITLE
         | 
| 24 27 |  | 
| 25 28 | 
             
                      # depending on normalization, we probably want this to eventually
         | 
| 26 29 | 
             
                      # include the full path, but we need to make sure we have a good
         | 
| 27 30 | 
             
                      # deduping strategy first.
         | 
| 28 31 | 
             
                      desc = nil
         | 
| 29 32 |  | 
| 30 | 
            -
                      [  | 
| 33 | 
            +
                      [ CAT, title, desc, annotations ]
         | 
| 31 34 | 
             
                    end
         | 
| 32 35 |  | 
| 33 36 | 
             
                  private
         | 
| 34 37 |  | 
| 38 | 
            +
                    OCTET_STREAM = "application/octet-stream".freeze
         | 
| 39 | 
            +
                    ATTACHMENT = "attachment".freeze
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    def initialize(*)
         | 
| 42 | 
            +
                      super
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                      @mimes = Mime::SET.reduce({}) do |hash, mime|
         | 
| 45 | 
            +
                        hash[mime.symbol] = mime.to_s.dup.freeze
         | 
| 46 | 
            +
                        hash
         | 
| 47 | 
            +
                      end
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
             | 
| 35 50 | 
             
                    def normalize_type(payload)
         | 
| 36 | 
            -
                      type = payload[:type] ||  | 
| 37 | 
            -
                      type =  | 
| 51 | 
            +
                      type = payload[:type] || OCTET_STREAM
         | 
| 52 | 
            +
                      type = @mimes[type] if type.is_a?(Symbol)
         | 
| 38 53 | 
             
                      type
         | 
| 39 54 | 
             
                    end
         | 
| 40 55 |  | 
| 56 | 
            +
                    def mime_for(type)
         | 
| 57 | 
            +
                      @mimes[type] ||= Mime[type].to_s.freeze
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
             | 
| 41 60 | 
             
                    def normalize_status(payload)
         | 
| 42 61 | 
             
                      status = payload[:status] || 200
         | 
| 43 62 | 
             
                      Rack::Utils.status_code(status)
         | 
| 44 63 | 
             
                    end
         | 
| 45 64 |  | 
| 46 65 | 
             
                    def normalize_disposition(payload)
         | 
| 47 | 
            -
                      payload[:disposition] ||  | 
| 66 | 
            +
                      payload[:disposition] || ATTACHMENT
         | 
| 48 67 | 
             
                    end
         | 
| 49 68 | 
             
                  end
         | 
| 50 69 |  | 
| @@ -6,25 +6,24 @@ module Skylight | |
| 6 6 | 
             
                class SQL < Normalizer
         | 
| 7 7 | 
             
                  register "sql.active_record"
         | 
| 8 8 |  | 
| 9 | 
            +
                  CAT = "db.sql.query".freeze
         | 
| 10 | 
            +
             | 
| 9 11 | 
             
                  def normalize(trace, name, payload)
         | 
| 10 12 | 
             
                    case payload[:name]
         | 
| 11 13 | 
             
                    when "SCHEMA", "CACHE"
         | 
| 12 14 | 
             
                      return :skip
         | 
| 13 15 | 
             
                    else
         | 
| 14 | 
            -
                      name  =  | 
| 16 | 
            +
                      name  = CAT
         | 
| 15 17 | 
             
                      title = payload[:name] || "SQL"
         | 
| 16 18 | 
             
                    end
         | 
| 17 19 |  | 
| 18 | 
            -
                     | 
| 19 | 
            -
                       | 
| 20 | 
            -
                    else
         | 
| 21 | 
            -
                      extracted_title, _, _, error = extract_binds(payload)
         | 
| 22 | 
            -
                      binds = payload[:binds].map { |col, val| val.inspect }
         | 
| 20 | 
            +
                    unless payload[:binds].empty?
         | 
| 21 | 
            +
                      payload[:binds] = payload[:binds].map { |col, val| val.inspect }
         | 
| 23 22 | 
             
                    end
         | 
| 24 23 |  | 
| 24 | 
            +
                    extracted_title, payload[:sql], binds, error = extract_binds(payload, payload[:binds])
         | 
| 25 25 | 
             
                    title = extracted_title if extracted_title
         | 
| 26 26 |  | 
| 27 | 
            -
             | 
| 28 27 | 
             
                    if payload[:sql]
         | 
| 29 28 | 
             
                      annotations = {
         | 
| 30 29 | 
             
                        sql:   payload[:sql],
         | 
| @@ -40,8 +39,8 @@ module Skylight | |
| 40 39 | 
             
                  end
         | 
| 41 40 |  | 
| 42 41 | 
             
                private
         | 
| 43 | 
            -
                  def extract_binds(payload)
         | 
| 44 | 
            -
                    title, sql, binds = SqlLexer::Lexer.bindify(payload[:sql])
         | 
| 42 | 
            +
                  def extract_binds(payload, precalculated)
         | 
| 43 | 
            +
                    title, sql, binds = SqlLexer::Lexer.bindify(payload[:sql], precalculated)
         | 
| 45 44 | 
             
                    [ title, sql, binds, nil ]
         | 
| 46 45 | 
             
                  rescue
         | 
| 47 46 | 
             
                    [ nil, nil, nil, ["sql_parse", payload[:sql]] ]
         | 
| @@ -54,13 +54,20 @@ module Skylight | |
| 54 54 | 
             
                    end
         | 
| 55 55 |  | 
| 56 56 | 
             
                    def field(rule, name, type, fn, opts)
         | 
| 57 | 
            -
                       | 
| 57 | 
            +
                      field = Field.new(rule, name, type, fn, opts)
         | 
| 58 | 
            +
                      indexed_fields[fn] = field
         | 
| 59 | 
            +
                      fields << field
         | 
| 60 | 
            +
                      fields.sort!
         | 
| 58 61 | 
             
                      __write_initializer
         | 
| 59 62 | 
             
                      attr_accessor name
         | 
| 60 63 | 
             
                    end
         | 
| 61 64 |  | 
| 62 65 | 
             
                    def fields
         | 
| 63 | 
            -
                      @fields ||=  | 
| 66 | 
            +
                      @fields ||= []
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    def indexed_fields
         | 
| 70 | 
            +
                      @indexed_fields ||= {}
         | 
| 64 71 | 
             
                    end
         | 
| 65 72 |  | 
| 66 73 | 
             
                    def __write_initializer
         | 
| @@ -68,7 +75,7 @@ module Skylight | |
| 68 75 |  | 
| 69 76 | 
             
                      lines << "def initialize(attrs=nil)"
         | 
| 70 77 | 
             
                      lines << "return unless attrs"
         | 
| 71 | 
            -
                      fields. | 
| 78 | 
            +
                      fields.each do |fld|
         | 
| 72 79 | 
             
                        lines << "@#{fld.name} = attrs[:#{fld.name}]"
         | 
| 73 80 | 
             
                      end
         | 
| 74 81 |  | 
| @@ -93,7 +100,7 @@ module Skylight | |
| 93 100 |  | 
| 94 101 | 
             
                      # TODO: Error if any required fields at nil
         | 
| 95 102 |  | 
| 96 | 
            -
                      fields. | 
| 103 | 
            +
                      fields.each do |fld|
         | 
| 97 104 | 
             
                        if fld.opts[:packed]
         | 
| 98 105 | 
             
                          bytes = encode!(Buffer.new, fld, 0)
         | 
| 99 106 | 
             
                          buf.append_info(fld.fn, Buffer.wire_for(fld.type))
         | 
| @@ -109,27 +116,36 @@ module Skylight | |
| 109 116 |  | 
| 110 117 | 
             
                    def encode!(buf, fld, fn)
         | 
| 111 118 | 
             
                      v = self[fld.name]
         | 
| 112 | 
            -
                      v = v.is_a?(Array) ? v : [v]
         | 
| 113 | 
            -
             | 
| 114 | 
            -
                      v.compact.each do |val|
         | 
| 115 | 
            -
                        case fld.type
         | 
| 116 | 
            -
                        when Class # encodable
         | 
| 117 | 
            -
                          # TODO: raise error if type != val.class
         | 
| 118 | 
            -
                          buf.append(:string, val.encode, fn)
         | 
| 119 | 
            -
                        when Module # enum
         | 
| 120 | 
            -
                          if ! valid_enum?(fld.type, val)
         | 
| 121 | 
            -
                            raise InvalidValueError.new(fld.name, val)
         | 
| 122 | 
            -
                          end
         | 
| 123 119 |  | 
| 124 | 
            -
             | 
| 125 | 
            -
                         | 
| 126 | 
            -
                          buf | 
| 120 | 
            +
                      if v.is_a?(Array)
         | 
| 121 | 
            +
                        v.each do |val|
         | 
| 122 | 
            +
                          encode_field!(buf, fld.type, val, fn)
         | 
| 127 123 | 
             
                        end
         | 
| 124 | 
            +
                      else
         | 
| 125 | 
            +
                        encode_field!(buf, fld.type, v, fn)
         | 
| 128 126 | 
             
                      end
         | 
| 129 127 |  | 
| 130 128 | 
             
                      buf
         | 
| 131 129 | 
             
                    end
         | 
| 132 130 |  | 
| 131 | 
            +
                    def encode_field!(buf, type, val, fn)
         | 
| 132 | 
            +
                      return if val.nil?
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                      case type
         | 
| 135 | 
            +
                      when Class # encodable
         | 
| 136 | 
            +
                        # TODO: raise error if type != val.class
         | 
| 137 | 
            +
                        buf.append(:string, val.encode, fn)
         | 
| 138 | 
            +
                      when Module # enum
         | 
| 139 | 
            +
                        if ! valid_enum?(type, val)
         | 
| 140 | 
            +
                          raise InvalidValueError.new(fld.name, val)
         | 
| 141 | 
            +
                        end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                        buf.append(:int32, val, fn)
         | 
| 144 | 
            +
                      else
         | 
| 145 | 
            +
                        buf.append(type, val, fn)
         | 
| 146 | 
            +
                      end
         | 
| 147 | 
            +
                    end
         | 
| 148 | 
            +
             | 
| 133 149 | 
             
                    def valid_enum?(mod, val)
         | 
| 134 150 | 
             
                      !!name_for(mod, val)
         | 
| 135 151 | 
             
                    end
         | 
| @@ -144,13 +160,12 @@ module Skylight | |
| 144 160 | 
             
                    end
         | 
| 145 161 |  | 
| 146 162 | 
             
                    def validate!
         | 
| 147 | 
            -
                      fields. | 
| 163 | 
            +
                      fields.each do |fld|
         | 
| 148 164 | 
             
                        if fld.rule == :required && self[fld.name].nil?
         | 
| 149 165 | 
             
                          raise RequiredFieldNotSetError, fld.name
         | 
| 150 166 | 
             
                        end
         | 
| 151 167 | 
             
                      end
         | 
| 152 168 | 
             
                    end
         | 
| 153 | 
            -
             | 
| 154 169 | 
             
                  end
         | 
| 155 170 |  | 
| 156 171 |  | 
| @@ -165,7 +180,7 @@ module Skylight | |
| 165 180 | 
             
                      while buf.length > 0
         | 
| 166 181 | 
             
                        fn, wire = buf.read_info
         | 
| 167 182 |  | 
| 168 | 
            -
                        fld =  | 
| 183 | 
            +
                        fld = indexed_fields[fn]
         | 
| 169 184 |  | 
| 170 185 | 
             
                        # We don't have a field for with index fn.
         | 
| 171 186 | 
             
                        # Ignore this data and move on.
         | 
| @@ -196,7 +211,7 @@ module Skylight | |
| 196 211 | 
             
                      end
         | 
| 197 212 |  | 
| 198 213 | 
             
                      # Set defaults
         | 
| 199 | 
            -
                      fields. | 
| 214 | 
            +
                      fields.each do |f|
         | 
| 200 215 | 
             
                        next if o[f.name] == false
         | 
| 201 216 | 
             
                        o[f.name] ||= f.opts[:default]
         | 
| 202 217 | 
             
                      end
         | 
| @@ -218,6 +233,10 @@ module Skylight | |
| 218 233 | 
             
                    self.class.fields
         | 
| 219 234 | 
             
                  end
         | 
| 220 235 |  | 
| 236 | 
            +
                  def indexed_fields
         | 
| 237 | 
            +
                    self.class.indexed_fields
         | 
| 238 | 
            +
                  end
         | 
| 239 | 
            +
             | 
| 221 240 | 
             
                  def [](k)
         | 
| 222 241 | 
             
                    __send__(k)
         | 
| 223 242 | 
             
                  end
         | 
| @@ -229,7 +248,7 @@ module Skylight | |
| 229 248 | 
             
                  def ==(o)
         | 
| 230 249 | 
             
                    return false if (o == nil) || (o == false)
         | 
| 231 250 | 
             
                    return false unless o.respond_to?(:[])
         | 
| 232 | 
            -
                    fields. | 
| 251 | 
            +
                    fields.all? do |fld|
         | 
| 233 252 | 
             
                      if fld.rule == :repeated
         | 
| 234 253 | 
             
                        Array(self[fld.name]) == Array(o[fld.name])
         | 
| 235 254 | 
             
                      else
         | 
| @@ -239,7 +258,7 @@ module Skylight | |
| 239 258 | 
             
                  end
         | 
| 240 259 |  | 
| 241 260 | 
             
                  def inspect
         | 
| 242 | 
            -
                    set = fields. | 
| 261 | 
            +
                    set = fields.select {|fld| self[fld.name] != nil }
         | 
| 243 262 |  | 
| 244 263 | 
             
                    flds = set.map do |fld|
         | 
| 245 264 | 
             
                      val = self[fld.name]
         | 
| @@ -259,7 +278,7 @@ module Skylight | |
| 259 278 | 
             
                  end
         | 
| 260 279 |  | 
| 261 280 | 
             
                  def to_hash
         | 
| 262 | 
            -
                    fields. | 
| 281 | 
            +
                    fields.inject({}) do |h, fld|
         | 
| 263 282 | 
             
                      if v = self[fld.name]
         | 
| 264 283 | 
             
                        h[fld.name] = v
         | 
| 265 284 | 
             
                      end
         | 
| @@ -11,6 +11,8 @@ module Skylight | |
| 11 11 | 
             
                  MinInt64  = -(1<<63)
         | 
| 12 12 | 
             
                  MaxInt64  =  (1<<63)-1
         | 
| 13 13 |  | 
| 14 | 
            +
                  MaxFixnum =  (1 << (1.size * 8 - 2) - 1)
         | 
| 15 | 
            +
             | 
| 14 16 | 
             
                  def self.wire_for(type)
         | 
| 15 17 | 
             
                    case type
         | 
| 16 18 | 
             
                    when Class
         | 
| @@ -70,7 +72,7 @@ module Skylight | |
| 70 72 |  | 
| 71 73 | 
             
                  if ''.respond_to?(:force_encoding)
         | 
| 72 74 | 
             
                    def buf=(buf)
         | 
| 73 | 
            -
                      @buf = buf.force_encoding( | 
| 75 | 
            +
                      @buf = buf.force_encoding(BINARY)
         | 
| 74 76 | 
             
                    end
         | 
| 75 77 | 
             
                  end
         | 
| 76 78 |  | 
| @@ -78,7 +80,7 @@ module Skylight | |
| 78 80 | 
             
                    @buf.respond_to?(:bytesize) ? @buf.bytesize : @buf.length
         | 
| 79 81 | 
             
                  end
         | 
| 80 82 |  | 
| 81 | 
            -
                  BINARY = 'BINARY'
         | 
| 83 | 
            +
                  BINARY = 'BINARY'.freeze
         | 
| 82 84 |  | 
| 83 85 | 
             
                  # Detect a ruby encodings bug, as far as I know, this exists in
         | 
| 84 86 | 
             
                  # most versions fo JRuby as well as 1.9.2
         | 
| @@ -10,7 +10,7 @@ module Skylight | |
| 10 10 | 
             
                      append_info(fn, wire)
         | 
| 11 11 | 
             
                    end
         | 
| 12 12 |  | 
| 13 | 
            -
                    __send__( | 
| 13 | 
            +
                    __send__(HANDLERS[type], val)
         | 
| 14 14 | 
             
                  end
         | 
| 15 15 |  | 
| 16 16 | 
             
                  def append_info(fn, wire)
         | 
| @@ -26,7 +26,7 @@ module Skylight | |
| 26 26 | 
             
                  end
         | 
| 27 27 |  | 
| 28 28 | 
             
                  def append_fixed64(n)
         | 
| 29 | 
            -
                    if n | 
| 29 | 
            +
                    if uint64?(n)
         | 
| 30 30 | 
             
                      raise OutOfRangeError, n
         | 
| 31 31 | 
             
                    end
         | 
| 32 32 |  | 
| @@ -77,8 +77,18 @@ module Skylight | |
| 77 77 | 
             
                    append_fixed64((n << 1) ^ (n >> 63))
         | 
| 78 78 | 
             
                  end
         | 
| 79 79 |  | 
| 80 | 
            +
                  def uint64?(n)
         | 
| 81 | 
            +
                    if n < MinUint64
         | 
| 82 | 
            +
                      false
         | 
| 83 | 
            +
                    elsif n < MaxFixnum
         | 
| 84 | 
            +
                      true
         | 
| 85 | 
            +
                    else
         | 
| 86 | 
            +
                      n <= MaxUint64
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 80 90 | 
             
                  def append_uint64(n)
         | 
| 81 | 
            -
                     | 
| 91 | 
            +
                    unless uint64?(n)
         | 
| 82 92 | 
             
                      raise OutOfRangeError, n
         | 
| 83 93 | 
             
                    end
         | 
| 84 94 |  | 
| @@ -110,6 +120,13 @@ module Skylight | |
| 110 120 | 
             
                  end
         | 
| 111 121 | 
             
                  alias :append_bytes :append_string
         | 
| 112 122 |  | 
| 123 | 
            +
                  HANDLERS = instance_methods.reduce({}) do |hash, meth|
         | 
| 124 | 
            +
                    if meth.to_s =~ /^append_(.*)$/
         | 
| 125 | 
            +
                      hash[$1.to_sym] = meth
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                    hash
         | 
| 129 | 
            +
                  end
         | 
| 113 130 | 
             
                end
         | 
| 114 131 | 
             
              end
         | 
| 115 132 | 
             
            end
         | 
    
        data/lib/skylight/version.rb
    CHANGED
    
    
| @@ -67,6 +67,9 @@ module Skylight | |
| 67 67 | 
             
                  def send_error(msg)
         | 
| 68 68 | 
             
                    res = @http_auth.post("/agent/error?hostname=#{escape(config[:'hostname'])}", reason: msg.reason, body: msg.body)
         | 
| 69 69 |  | 
| 70 | 
            +
                    # error already handled in Util::HTTP
         | 
| 71 | 
            +
                    return unless res
         | 
| 72 | 
            +
             | 
| 70 73 | 
             
                    unless res.success?
         | 
| 71 74 | 
             
                      if (400..499).include? res.status
         | 
| 72 75 | 
             
                        warn "error wasn't sent successfully; status=%s", res.status
         | 
| @@ -108,6 +111,9 @@ module Skylight | |
| 108 111 | 
             
                  def refresh_report_token(now)
         | 
| 109 112 | 
             
                    res = @http_auth.get("/agent/authenticate?hostname=#{escape(config[:'hostname'])}")
         | 
| 110 113 |  | 
| 114 | 
            +
                    # error already handled in Util::HTTP
         | 
| 115 | 
            +
                    return unless res
         | 
| 116 | 
            +
             | 
| 111 117 | 
             
                    unless res.success?
         | 
| 112 118 | 
             
                      if (400..499).include? res.status
         | 
| 113 119 | 
             
                        warn "token request rejected; status=%s", res.status
         | 
    
        data/lib/sql_lexer/lexer.rb
    CHANGED
    
    | @@ -59,21 +59,22 @@ module SqlLexer | |
| 59 59 |  | 
| 60 60 | 
             
                Literals      = %Q<(?:NULL|TRUE|FALSE)(?=(?:[#{WS}]|#{OpPart}|#{End}))>
         | 
| 61 61 |  | 
| 62 | 
            -
                TkWS          = %r<[#{WS}]+>
         | 
| 63 | 
            -
                TkOptWS       = %r<[#{WS}]*>
         | 
| 64 | 
            -
                TkOp          = %r<[#{OpPart}]>
         | 
| 65 | 
            -
                TkPlaceholder = %r<#{Placeholder}>
         | 
| 66 | 
            -
                TkNonBind     = %r<#{NonBind}>
         | 
| 67 | 
            -
                TkQuotedTable = %r<#{QuotedTable}> | 
| 68 | 
            -
                TkUpdateTable = %r<UPDATE#{TableNext}> | 
| 69 | 
            -
                TkInsertTable = %r<INSERT[#{WS}]+INTO#{TableNext}> | 
| 70 | 
            -
                TkDeleteTable = %r<DELETE[#{WS}]+FROM#{TableNext}> | 
| 71 | 
            -
                TkFromTable   = %r<FROM#{TableNext}> | 
| 72 | 
            -
                TkID          = %r<#{ID}>
         | 
| 73 | 
            -
                TkEnd         = %r<;?[#{WS}]*>
         | 
| 74 | 
            -
                TkBind        = %r<#{String}|#{Number}|#{Literals}>
         | 
| 75 | 
            -
                TkIn          = %r<#{InOp}> | 
| 76 | 
            -
                TkSpecialOp   = %r<#{SpecialOps}> | 
| 62 | 
            +
                TkWS          = %r<[#{WS}]+>u
         | 
| 63 | 
            +
                TkOptWS       = %r<[#{WS}]*>u
         | 
| 64 | 
            +
                TkOp          = %r<[#{OpPart}]>u
         | 
| 65 | 
            +
                TkPlaceholder = %r<#{Placeholder}>u
         | 
| 66 | 
            +
                TkNonBind     = %r<#{NonBind}>u
         | 
| 67 | 
            +
                TkQuotedTable = %r<#{QuotedTable}>iu
         | 
| 68 | 
            +
                TkUpdateTable = %r<UPDATE#{TableNext}>iu
         | 
| 69 | 
            +
                TkInsertTable = %r<INSERT[#{WS}]+INTO#{TableNext}>iu
         | 
| 70 | 
            +
                TkDeleteTable = %r<DELETE[#{WS}]+FROM#{TableNext}>iu
         | 
| 71 | 
            +
                TkFromTable   = %r<FROM#{TableNext}>iu
         | 
| 72 | 
            +
                TkID          = %r<#{ID}>u
         | 
| 73 | 
            +
                TkEnd         = %r<;?[#{WS}]*>u
         | 
| 74 | 
            +
                TkBind        = %r<#{String}|#{Number}|#{Literals}>u
         | 
| 75 | 
            +
                TkIn          = %r<#{InOp}>iu
         | 
| 76 | 
            +
                TkSpecialOp   = %r<#{SpecialOps}>iu
         | 
| 77 | 
            +
                TkStartSelect = %r<SELECT(?=(?:[#{WS}]|#{OpPart}))>iu
         | 
| 77 78 |  | 
| 78 79 | 
             
                STATE_HANDLERS = {
         | 
| 79 80 | 
             
                  begin:       :process_begin,
         | 
| @@ -81,91 +82,159 @@ module SqlLexer | |
| 81 82 | 
             
                  tokens:      :process_tokens,
         | 
| 82 83 | 
             
                  bind:        :process_bind,
         | 
| 83 84 | 
             
                  non_bind:    :process_non_bind,
         | 
| 85 | 
            +
                  placeholder: :process_placeholder,
         | 
| 84 86 | 
             
                  table_name:  :process_table_name,
         | 
| 85 87 | 
             
                  end:         :process_end,
         | 
| 86 88 | 
             
                  special:     :process_special,
         | 
| 87 89 | 
             
                  in:          :process_in
         | 
| 88 90 | 
             
                }
         | 
| 89 91 |  | 
| 90 | 
            -
                def self.bindify(string)
         | 
| 91 | 
            -
                   | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
                  end
         | 
| 92 | 
            +
                def self.bindify(string, binds=nil)
         | 
| 93 | 
            +
                  scanner = instance(string)
         | 
| 94 | 
            +
                  scanner.process(binds)
         | 
| 95 | 
            +
                  [scanner.title, scanner.output, scanner.binds]
         | 
| 95 96 | 
             
                end
         | 
| 96 97 |  | 
| 97 98 | 
             
                attr_reader :output, :binds, :title
         | 
| 98 99 |  | 
| 99 | 
            -
                def  | 
| 100 | 
            -
                   | 
| 100 | 
            +
                def self.pooled_value(name, default)
         | 
| 101 | 
            +
                  key = :"__skylight_sql_#{name}"
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  singleton_class.class_eval do
         | 
| 104 | 
            +
                    define_method(name) do
         | 
| 105 | 
            +
                      value = Thread.current[key] ||= default.dup
         | 
| 106 | 
            +
                      value.clear
         | 
| 107 | 
            +
                      value
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  __send__(name)
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                SCANNER_KEY = :__skylight_sql_scanner
         | 
| 115 | 
            +
                LEXER_KEY   = :__skylight_sql_lexer
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                def self.scanner(string='')
         | 
| 118 | 
            +
                  scanner = Thread.current[SCANNER_KEY] ||= StringScanner.new('')
         | 
| 119 | 
            +
                  scanner.string = string
         | 
| 120 | 
            +
                  scanner
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                def self.instance(string)
         | 
| 124 | 
            +
                  lexer = Thread.current[LEXER_KEY] ||= new
         | 
| 125 | 
            +
                  lexer.init(string)
         | 
| 126 | 
            +
                  lexer
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                pooled_value :binds, []
         | 
| 130 | 
            +
                pooled_value :table, "*" * 20
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                SPACE = " ".freeze
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                DEBUG = ENV["DEBUG"]
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                def init(string)
         | 
| 101 137 | 
             
                  @state   = :begin
         | 
| 102 | 
            -
                  @ | 
| 103 | 
            -
                  @ | 
| 104 | 
            -
                  @ | 
| 138 | 
            +
                  @debug   = DEBUG
         | 
| 139 | 
            +
                  @binds   = self.class.binds
         | 
| 140 | 
            +
                  @table   = self.class.table
         | 
| 141 | 
            +
                  @title   = nil
         | 
| 142 | 
            +
                  @bind    = 0
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                  self.string = string
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                def string=(value)
         | 
| 148 | 
            +
                  @input   = value
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                  @scanner = self.class.scanner(value)
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                  # intentionally allocates; we need to return a new
         | 
| 153 | 
            +
                  # string as part of this API
         | 
| 154 | 
            +
                  @output = value.dup
         | 
| 105 155 | 
             
                end
         | 
| 106 156 |  | 
| 107 | 
            -
                 | 
| 108 | 
            -
             | 
| 157 | 
            +
                PLACEHOLDER = "?".freeze
         | 
| 158 | 
            +
                UNKNOWN = "<unknown>".freeze
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                def process(binds)
         | 
| 161 | 
            +
                  @operation = nil
         | 
| 162 | 
            +
                  @provided_binds = binds
         | 
| 109 163 |  | 
| 110 164 | 
             
                  while @state
         | 
| 111 | 
            -
                    if  | 
| 165 | 
            +
                    if @debug
         | 
| 112 166 | 
             
                      p @state
         | 
| 113 167 | 
             
                      p @scanner
         | 
| 114 168 | 
             
                    end
         | 
| 115 169 |  | 
| 116 | 
            -
                     | 
| 170 | 
            +
                    __send__ STATE_HANDLERS[@state]
         | 
| 117 171 | 
             
                  end
         | 
| 118 172 |  | 
| 119 173 | 
             
                  pos = 0
         | 
| 120 174 | 
             
                  removed = 0
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                  # intentionally allocates; the returned binds must
         | 
| 177 | 
            +
                  # be in a newly produced array
         | 
| 121 178 | 
             
                  extracted_binds = Array.new(@binds.size / 2)
         | 
| 122 179 |  | 
| 123 | 
            -
                  if @operation &&  | 
| 124 | 
            -
                     | 
| 125 | 
            -
                    @title = "#{@operation} #{table}"
         | 
| 180 | 
            +
                  if @operation && !@table.empty?
         | 
| 181 | 
            +
                    @title = "" << @operation << SPACE << @table
         | 
| 126 182 | 
             
                  end
         | 
| 127 183 |  | 
| 128 184 | 
             
                  while pos < @binds.size
         | 
| 129 | 
            -
                     | 
| 130 | 
            -
             | 
| 131 | 
            -
                     | 
| 132 | 
            -
             | 
| 185 | 
            +
                    if @binds[pos] == nil
         | 
| 186 | 
            +
                      extracted_binds[pos/2] = @binds[pos+1]
         | 
| 187 | 
            +
                    else
         | 
| 188 | 
            +
                      slice = @output[@binds[pos] - removed, @binds[pos+1]]
         | 
| 189 | 
            +
                      @output[@binds[pos] - removed, @binds[pos+1]] = PLACEHOLDER
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                      extracted_binds[pos/2] = slice
         | 
| 192 | 
            +
                      removed += (@binds[pos+1] - 1)
         | 
| 193 | 
            +
                    end
         | 
| 194 | 
            +
             | 
| 133 195 | 
             
                    pos += 2
         | 
| 134 196 | 
             
                  end
         | 
| 135 197 |  | 
| 136 198 | 
             
                  @binds = extracted_binds
         | 
| 199 | 
            +
                  nil
         | 
| 137 200 | 
             
                end
         | 
| 138 201 |  | 
| 202 | 
            +
                EMPTY = "".freeze
         | 
| 203 | 
            +
             | 
| 139 204 | 
             
                def process_begin
         | 
| 140 | 
            -
                  @scanner. | 
| 205 | 
            +
                  @scanner.skip(TkOptWS)
         | 
| 141 206 | 
             
                  @state = :first_token
         | 
| 142 207 | 
             
                end
         | 
| 143 208 |  | 
| 209 | 
            +
                OP_SELECT_FROM = "SELECT FROM".freeze
         | 
| 210 | 
            +
                OP_UPDATE      = "UPDATE".freeze
         | 
| 211 | 
            +
                OP_INSERT_INTO = "INSERT INTO".freeze
         | 
| 212 | 
            +
                OP_DELETE_FROM = "DELETE FROM".freeze
         | 
| 213 | 
            +
             | 
| 144 214 | 
             
                def process_first_token
         | 
| 145 | 
            -
                  if @scanner.skip( | 
| 146 | 
            -
                    @operation =  | 
| 215 | 
            +
                  if @scanner.skip(TkStartSelect)
         | 
| 216 | 
            +
                    @operation = OP_SELECT_FROM
         | 
| 147 217 | 
             
                    @state = :tokens
         | 
| 148 | 
            -
             | 
| 149 | 
            -
             | 
| 218 | 
            +
                  else
         | 
| 219 | 
            +
                    if @scanner.skip(TkUpdateTable)
         | 
| 220 | 
            +
                      @operation = OP_UPDATE
         | 
| 221 | 
            +
                    elsif @scanner.skip(TkInsertTable)
         | 
| 222 | 
            +
                      @operation = OP_INSERT_INTO
         | 
| 223 | 
            +
                    elsif @scanner.skip(TkDeleteTable)
         | 
| 224 | 
            +
                      @operation = OP_DELETE_FROM
         | 
| 225 | 
            +
                    end
         | 
| 150 226 |  | 
| 151 | 
            -
             | 
| 152 | 
            -
                    @operation = :UPDATE
         | 
| 153 | 
            -
                  elsif @scanner.skip(TkInsertTable)
         | 
| 154 | 
            -
                    @operation = :"INSERT INTO"
         | 
| 155 | 
            -
                  elsif @scanner.skip(TkDeleteTable)
         | 
| 156 | 
            -
                    @operation = :"DELETE FROM"
         | 
| 227 | 
            +
                    @state = :table_name
         | 
| 157 228 | 
             
                  end
         | 
| 158 | 
            -
             | 
| 159 | 
            -
                  @state = :table_name
         | 
| 160 229 | 
             
                end
         | 
| 161 230 |  | 
| 162 231 | 
             
                def process_table_name
         | 
| 163 232 | 
             
                  pos = @scanner.pos
         | 
| 164 233 |  | 
| 165 234 | 
             
                  if @scanner.skip(TkQuotedTable)
         | 
| 166 | 
            -
                    @table  | 
| 235 | 
            +
                    copy_substr(@input, @table, pos + 1, @scanner.pos - 1)
         | 
| 167 236 | 
             
                  elsif @scanner.skip(TkID)
         | 
| 168 | 
            -
                    @table  | 
| 237 | 
            +
                    copy_substr(@input, @table, pos, @scanner.pos)
         | 
| 169 238 | 
             
                  end
         | 
| 170 239 |  | 
| 171 240 | 
             
                  @state = :tokens
         | 
| @@ -174,12 +243,14 @@ module SqlLexer | |
| 174 243 | 
             
                def process_tokens
         | 
| 175 244 | 
             
                  @scanner.skip(TkOptWS)
         | 
| 176 245 |  | 
| 177 | 
            -
                  if @operation ==  | 
| 246 | 
            +
                  if @operation == OP_SELECT_FROM && @table.empty? && @scanner.skip(TkFromTable)
         | 
| 178 247 | 
             
                    @state = :table_name
         | 
| 179 248 | 
             
                  elsif @scanner.match?(TkSpecialOp)
         | 
| 180 249 | 
             
                    @state = :special
         | 
| 181 250 | 
             
                  elsif @scanner.match?(TkBind)
         | 
| 182 251 | 
             
                    @state = :bind
         | 
| 252 | 
            +
                  elsif @scanner.match?(TkPlaceholder)
         | 
| 253 | 
            +
                    @state = :placeholder
         | 
| 183 254 | 
             
                  elsif @scanner.match?(TkNonBind)
         | 
| 184 255 | 
             
                    @state = :non_bind
         | 
| 185 256 | 
             
                  else
         | 
| @@ -187,10 +258,28 @@ module SqlLexer | |
| 187 258 | 
             
                  end
         | 
| 188 259 | 
             
                end
         | 
| 189 260 |  | 
| 261 | 
            +
                def process_placeholder
         | 
| 262 | 
            +
                  @scanner.skip(TkPlaceholder)
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                  binds << nil
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                  if !@provided_binds
         | 
| 267 | 
            +
                    @binds << UNKNOWN
         | 
| 268 | 
            +
                  elsif !@provided_binds[@bind]
         | 
| 269 | 
            +
                    @binds << UNKNOWN
         | 
| 270 | 
            +
                  else
         | 
| 271 | 
            +
                    @binds << @provided_binds[@bind]
         | 
| 272 | 
            +
                  end
         | 
| 273 | 
            +
             | 
| 274 | 
            +
                  @bind += 1
         | 
| 275 | 
            +
             | 
| 276 | 
            +
                  @state = :tokens
         | 
| 277 | 
            +
                end
         | 
| 278 | 
            +
             | 
| 190 279 | 
             
                def process_special
         | 
| 191 280 | 
             
                  if @scanner.skip(TkIn)
         | 
| 192 281 | 
             
                    @scanner.skip(TkOptWS)
         | 
| 193 | 
            -
                    @scanner.skip(/\(/)
         | 
| 282 | 
            +
                    @scanner.skip(/\(/u)
         | 
| 194 283 | 
             
                    @state = :in
         | 
| 195 284 | 
             
                  end
         | 
| 196 285 | 
             
                end
         | 
| @@ -209,16 +298,16 @@ module SqlLexer | |
| 209 298 | 
             
                      raise "The SQL '#{@scanner.string}' could not be parsed because of too many iterations in IN"
         | 
| 210 299 | 
             
                    end
         | 
| 211 300 |  | 
| 212 | 
            -
                    if  | 
| 301 | 
            +
                    if @debug
         | 
| 213 302 | 
             
                      p @state
         | 
| 214 303 | 
             
                      p @scanner
         | 
| 215 304 | 
             
                      p nest
         | 
| 216 305 | 
             
                    end
         | 
| 217 306 |  | 
| 218 | 
            -
                    if @scanner.skip(/\(/)
         | 
| 307 | 
            +
                    if @scanner.skip(/\(/u)
         | 
| 219 308 | 
             
                      nest += 1
         | 
| 220 309 | 
             
                      process_tokens
         | 
| 221 | 
            -
                    elsif @scanner.skip(/\)/)
         | 
| 310 | 
            +
                    elsif @scanner.skip(/\)/u)
         | 
| 222 311 | 
             
                      nest -= 1
         | 
| 223 312 | 
             
                      break if nest.zero?
         | 
| 224 313 | 
             
                      process_tokens
         | 
| @@ -263,5 +352,19 @@ module SqlLexer | |
| 263 352 |  | 
| 264 353 | 
             
                  @state = nil
         | 
| 265 354 | 
             
                end
         | 
| 355 | 
            +
             | 
| 356 | 
            +
              private
         | 
| 357 | 
            +
                def copy_substr(source, target, start_pos, end_pos)
         | 
| 358 | 
            +
                  pos = start_pos
         | 
| 359 | 
            +
             | 
| 360 | 
            +
                  while pos < end_pos
         | 
| 361 | 
            +
                    target.concat source.getbyte(pos)
         | 
| 362 | 
            +
                    pos += 1
         | 
| 363 | 
            +
                  end
         | 
| 364 | 
            +
                end
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                scanner
         | 
| 367 | 
            +
                instance('')
         | 
| 368 | 
            +
             | 
| 266 369 | 
             
              end
         | 
| 267 370 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: skylight
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.2.0.beta. | 
| 4 | 
            +
              version: 0.2.0.beta.4
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Tilde, Inc.
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2013-11- | 
| 11 | 
            +
            date: 2013-11-09 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: activesupport
         | 
| @@ -62,6 +62,7 @@ files: | |
| 62 62 | 
             
            - lib/skylight/normalizers/sql.rb
         | 
| 63 63 | 
             
            - lib/skylight/railtie.rb
         | 
| 64 64 | 
             
            - lib/skylight/subscriber.rb
         | 
| 65 | 
            +
            - lib/skylight/util/allocation_free.rb
         | 
| 65 66 | 
             
            - lib/skylight/util/clock.rb
         | 
| 66 67 | 
             
            - lib/skylight/util/gzip.rb
         | 
| 67 68 | 
             
            - lib/skylight/util/http.rb
         | 
| @@ -153,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 153 154 | 
             
                  version: 1.3.1
         | 
| 154 155 | 
             
            requirements: []
         | 
| 155 156 | 
             
            rubyforge_project: 
         | 
| 156 | 
            -
            rubygems_version: 2. | 
| 157 | 
            +
            rubygems_version: 2.1.9
         | 
| 157 158 | 
             
            signing_key: 
         | 
| 158 159 | 
             
            specification_version: 4
         | 
| 159 160 | 
             
            summary: Skylight is a ruby application monitoring tool.
         |