tomlobato/dns_one

View on GitHub
lib/dns_one/req_log/db.rb

Summary

Maintainability
A
1 hr
Test Coverage
module DnsOne; module ReqLog; class DB

    DB_FNAME = "stat.db"
    META_STAT_ON = false
    META_STAT_FILE = '/tmp/dnsone_sql_prof.log'

    def initialize conf = {}
        @conf = conf
        
        # Setup logger and current working dir
        DnsOne.new if @conf[:from_outside]
        
        ensure_db
    end

    def on_response ip_address, domain_name, res_class, rcode, resp_log, from_cache
        Global.logger.debug "saving stat (user: #{ `id -un #{Process.uid}`.strip })"
        rsql(
            "INSERT INTO responses (time, rcode, req_resource, cache) VALUES (?, ?, ?, ?)", 
            [
                Time.now.to_i, 
                Resolv::DNS::RCode.const_get(rcode), 
                res_class::TypeValue, 
                (from_cache ? 1 : 0)
            ]
        )
    rescue => e
        Global.logger.error e.desc
    end

    # select rcode, count(*) from responses where time > strftime('%s', 'now') - 300 group by rcode
    def get_counts counter, from = nil
        validate_counter counter
        validate_from from

        from ||= (Time.now - 5 * 60).to_i

        s = <<-SQL
            select #{counter}, count(*) 
            from responses 
            where time > #{from}
            group by #{counter}
        SQL

        counts = {}

        rsql(s).each do |row|
            _counter, count = row
            counts[_counter] = count
        end

        counts
    end

    def self.print
        stat = new(from_outside: true, readonly: true)
        %w(rcode req_resource cache).each do |key|
            puts "--- #{key} ---"
            stat.get_counts(key.to_sym).each_pair do |k, v|
                _k = case key
                when 'rcode'
                    stat.rcodes[k]
                when 'req_resource'
                    stat.request_resources[k]
                when 'cache'
                    k == 0 ? :miss : :hit
                end
                puts "#{_k || k}\t#{v}"
            end
        end
    end

    def rcodes
        unless defined? @@rcodes
            @@rcodes = Hash[ 
                Resolv::DNS::RCode.constants.map{|c| 
                    [
                        Resolv::DNS::RCode.const_get(c), 
                        Util.const_underscore(c)
                    ] 
                } 
            ]
        end
        @@rcodes
    end

    def request_resources
        unless defined? @@request_resources
            @@request_resources = {}
            %w(A AAAA ANY CNAME HINFO MINFO MX NS PTR SOA TXT WKS).each do |res|
                val = Object.const_get("Resolv::DNS::Resource::IN::#{res}")::TypeValue
                @@request_resources[val] = res.downcase
            end
        end
        @@request_resources
    end

    private

    def validate_counter counter
        unless [:rcode, :req_resource, :cache].include? counter
            raise "invalid arg #{counter}"
        end
    end

    def validate_from from
        case from
        when nil, Integer
        when String
            unless from =~ /^\d+$/
                raise "invalid arg #{from}"
            end
        else
            raise  "invalid arg class #{from.class}"
        end
    end

    def ensure_db
        new_db = !File.exists?(db_file)

        opts = {}
        opts[:readonly] = true if @conf[:readonly]

        # Global.logger.info "Opening stat db #{db_file} (cwd: #{Dir.pwd})."
        @db = SQLite3::Database.new db_file, opts

        if new_db
            File.chmod 0644, db_file
            File.chown `id -u #{@conf[:user]}`.to_i, nil, db_file if @conf[:user]
            create_tables 
        end
    end
    
    def create_tables
        @db.execute_batch <<-SQL
            create table responses (
                time int,
                rcode int,
                req_resource int,
                cache int
            );
            CREATE INDEX responses_time_rcode ON responses (time, rcode);
            CREATE INDEX responses_time_req_resource ON responses (time, req_resource);
            CREATE INDEX responses_time_cache ON responses (time, cache);
        SQL
    end


    def db_file
        DB_FNAME
    end

    def rsql *sql
        t0 = Time.now if META_STAT_ON
        res = @db.execute *sql
        meta_stats t0, sql if META_STAT_ON
        res
    end

    def meta_stats t0, sql
        time = Time.now.strftime '%y%m%d-%H%M%S.%L'
        dur = "%.3f" % ((Time.now - t0) * 1000.0)
        sql = [sql].flatten[0].gsub /\s+/, ' '

        @meta_stats_log ||= Logger.new META_STAT_FILE
        @meta_stats_log.info "#{time} #{dur} #{sql}"
    end

end; end; end