kanshi 0.0.1
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/README.md +42 -0
- data/bin/kanshi +10 -0
- data/lib/kanshi.rb +38 -0
- data/lib/kanshi/collector.rb +32 -0
- data/lib/kanshi/queries.rb +34 -0
- data/lib/kanshi/scrolls_reporter.rb +49 -0
- metadata +100 -0
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            # 監視
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            ## Purpose
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Kanshi watches your Postgres database and reports metrics to the log
         | 
| 6 | 
            +
            stream. You can then drain this log to an application - or fork and
         | 
| 7 | 
            +
            modify kanshi itself - to send these metrics to a service, like Librato
         | 
| 8 | 
            +
            or Splunk.
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            ## Installation
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            Kanshi will work as a gem or as a standalone app.
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ### Add to existing app on Heroku
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            1. Add Kanshi to your Gemfile:
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                       gem 'kanshi', :require => false
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            2. Add Kanshi to your Procfile:
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                       kanshi: bundle exec kanshi
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            3. Run `bundle install`, commit, and deploy.
         | 
| 25 | 
            +
            4. `heroku scale kanshi=1`
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ### Create as separate Heroku app
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            1. Push the code as-is to a new Heroku app.
         | 
| 30 | 
            +
            2. Set environment variables with database URLs.
         | 
| 31 | 
            +
            3. `heroku scale kanshi=1`
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            ### Custom install
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            You can create your own version of `bin/kanshi` to run Kanshi in any way
         | 
| 36 | 
            +
            of your choosing, e.g. monitor specific databases or provide your own
         | 
| 37 | 
            +
            reporter for output. An easy way to do this would be to create a Rake
         | 
| 38 | 
            +
            task to run Kanshi.
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            ## Name
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            Kanshi (監視) is Japanese for _surveillance_.
         | 
    
        data/bin/kanshi
    ADDED
    
    
    
        data/lib/kanshi.rb
    ADDED
    
    | @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            Kanshi = Class.new
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'kanshi/collector'
         | 
| 4 | 
            +
            require 'kanshi/scrolls_reporter'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class Kanshi
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              def self.run(*args)
         | 
| 9 | 
            +
                self.new(*args).run
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def initialize(options = {})
         | 
| 13 | 
            +
                @options = {
         | 
| 14 | 
            +
                  :databases => [],
         | 
| 15 | 
            +
                  :delay => 300,
         | 
| 16 | 
            +
                  :reporter => ScrollsReporter
         | 
| 17 | 
            +
                }
         | 
| 18 | 
            +
                @options.merge!(options)
         | 
| 19 | 
            +
                @reporter = @options[:reporter].new
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def run
         | 
| 23 | 
            +
                loop do
         | 
| 24 | 
            +
                  report
         | 
| 25 | 
            +
                  sleep(@options[:delay])
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              def report
         | 
| 30 | 
            +
                @options[:databases].each do |name, database_url|
         | 
| 31 | 
            +
                  @reporter.report(
         | 
| 32 | 
            +
                    name,
         | 
| 33 | 
            +
                    database_url,
         | 
| 34 | 
            +
                    Collector.collect(database_url))
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            end
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            require 'sequel'
         | 
| 2 | 
            +
            require 'kanshi/queries'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class Kanshi::Collector
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def self.collect(*args)
         | 
| 7 | 
            +
                new(*args).collect
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def initialize(database_url)
         | 
| 11 | 
            +
                @url = database_url
         | 
| 12 | 
            +
                @db_name = URI.parse(@url).path[1..-1]
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def with_db(&block)
         | 
| 16 | 
            +
                db = Sequel.connect(@url)
         | 
| 17 | 
            +
                yield db
         | 
| 18 | 
            +
              ensure
         | 
| 19 | 
            +
                db.disconnect if db
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def collect
         | 
| 23 | 
            +
                data = {}
         | 
| 24 | 
            +
                with_db do |db|
         | 
| 25 | 
            +
                  ::Kanshi::Queries.each do |query|
         | 
| 26 | 
            +
                    data.merge! db[query, @db_name].first
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
                data
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            end
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            Kanshi::Queries = <<EOF.split(/\s*;\s*/)
         | 
| 2 | 
            +
            SELECT
         | 
| 3 | 
            +
              pg_database_size(d.datname) as size,
         | 
| 4 | 
            +
              numbackends,
         | 
| 5 | 
            +
              xact_commit,
         | 
| 6 | 
            +
              xact_rollback,
         | 
| 7 | 
            +
              blks_read,
         | 
| 8 | 
            +
              blks_hit,
         | 
| 9 | 
            +
              tup_fetched,
         | 
| 10 | 
            +
              tup_returned,
         | 
| 11 | 
            +
              tup_inserted,
         | 
| 12 | 
            +
              tup_updated,
         | 
| 13 | 
            +
              tup_deleted
         | 
| 14 | 
            +
            FROM
         | 
| 15 | 
            +
              pg_stat_database d
         | 
| 16 | 
            +
            WHERE
         | 
| 17 | 
            +
              d.datname = ?;
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            SELECT
         | 
| 20 | 
            +
              SUM(seq_scan)::bigint AS seq_scan,
         | 
| 21 | 
            +
              SUM(seq_tup_read)::bigint AS seq_tup_read,
         | 
| 22 | 
            +
              SUM(idx_scan)::bigint AS idx_scan,
         | 
| 23 | 
            +
              SUM(idx_tup_fetch)::bigint AS idx_tup_fetch
         | 
| 24 | 
            +
            FROM
         | 
| 25 | 
            +
              pg_stat_user_tables;
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            SELECT
         | 
| 28 | 
            +
              SUM(heap_blks_read)::bigint AS heap_blks_read,
         | 
| 29 | 
            +
              SUM(heap_blks_hit)::bigint AS heap_blks_hit,
         | 
| 30 | 
            +
              SUM(idx_blks_read)::bigint AS idx_blks_read,
         | 
| 31 | 
            +
              SUM(idx_blks_hit)::bigint AS idx_blks_hit
         | 
| 32 | 
            +
            FROM
         | 
| 33 | 
            +
              pg_statio_user_tables;
         | 
| 34 | 
            +
            EOF
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            require 'scrolls'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Kanshi::ScrollsReporter
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              def initialize
         | 
| 6 | 
            +
                @last_value = {}
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              ABSOLUTE = [:size, :numbackends]
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              def report(name, url, data)
         | 
| 12 | 
            +
                data = calculate_hit_rate(record_and_diff(name, data))
         | 
| 13 | 
            +
                if data
         | 
| 14 | 
            +
                  Scrolls.context(:app => "kanshi.#{name}", :measure => true) do
         | 
| 15 | 
            +
                    data.each do |k, v|
         | 
| 16 | 
            +
                      Scrolls.log(:at => k, :last => v)
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            private
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def record_and_diff(name, data)
         | 
| 25 | 
            +
                diff = nil
         | 
| 26 | 
            +
                if @last_value[name]
         | 
| 27 | 
            +
                  diff = Hash.new
         | 
| 28 | 
            +
                  data.keys.each do |key|
         | 
| 29 | 
            +
                    diff[key] = data[key] - @last_value[name][key]
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                  ABSOLUTE.each do |key|
         | 
| 32 | 
            +
                    diff["absolute_#{key}"] = data[key]
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
                @last_value[name] = data
         | 
| 36 | 
            +
                diff
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              def calculate_hit_rate(data)
         | 
| 40 | 
            +
                return nil unless data
         | 
| 41 | 
            +
                hit = data[:blks_hit] || 0
         | 
| 42 | 
            +
                read = data[:blks_read] || 0
         | 
| 43 | 
            +
                if hit + read > 0
         | 
| 44 | 
            +
                  data[:cache_hit_ratio] = 100.0 * hit / (hit + read)
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
                data
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,100 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: kanshi
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.1
         | 
| 5 | 
            +
              prerelease: 
         | 
| 6 | 
            +
            platform: ruby
         | 
| 7 | 
            +
            authors:
         | 
| 8 | 
            +
            - Jonathan Dance
         | 
| 9 | 
            +
            autorequire: 
         | 
| 10 | 
            +
            bindir: bin
         | 
| 11 | 
            +
            cert_chain: []
         | 
| 12 | 
            +
            date: 2012-10-01 00:00:00.000000000 Z
         | 
| 13 | 
            +
            dependencies:
         | 
| 14 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 15 | 
            +
              name: sequel
         | 
| 16 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 17 | 
            +
                none: false
         | 
| 18 | 
            +
                requirements:
         | 
| 19 | 
            +
                - - ~>
         | 
| 20 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 21 | 
            +
                    version: '3.0'
         | 
| 22 | 
            +
              type: :runtime
         | 
| 23 | 
            +
              prerelease: false
         | 
| 24 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 25 | 
            +
                none: false
         | 
| 26 | 
            +
                requirements:
         | 
| 27 | 
            +
                - - ~>
         | 
| 28 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 29 | 
            +
                    version: '3.0'
         | 
| 30 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 31 | 
            +
              name: scrolls
         | 
| 32 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 33 | 
            +
                none: false
         | 
| 34 | 
            +
                requirements:
         | 
| 35 | 
            +
                - - ~>
         | 
| 36 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 37 | 
            +
                    version: '0.2'
         | 
| 38 | 
            +
              type: :runtime
         | 
| 39 | 
            +
              prerelease: false
         | 
| 40 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 41 | 
            +
                none: false
         | 
| 42 | 
            +
                requirements:
         | 
| 43 | 
            +
                - - ~>
         | 
| 44 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 45 | 
            +
                    version: '0.2'
         | 
| 46 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 47 | 
            +
              name: pg
         | 
| 48 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 49 | 
            +
                none: false
         | 
| 50 | 
            +
                requirements:
         | 
| 51 | 
            +
                - - ! '>='
         | 
| 52 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 53 | 
            +
                    version: '0'
         | 
| 54 | 
            +
              type: :runtime
         | 
| 55 | 
            +
              prerelease: false
         | 
| 56 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 57 | 
            +
                none: false
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - ! '>='
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '0'
         | 
| 62 | 
            +
            description: Prints Postgres database metrics to your log stream
         | 
| 63 | 
            +
            email: jd@heroku.com
         | 
| 64 | 
            +
            executables:
         | 
| 65 | 
            +
            - kanshi
         | 
| 66 | 
            +
            extensions: []
         | 
| 67 | 
            +
            extra_rdoc_files: []
         | 
| 68 | 
            +
            files:
         | 
| 69 | 
            +
            - README.md
         | 
| 70 | 
            +
            - bin/kanshi
         | 
| 71 | 
            +
            - lib/kanshi.rb
         | 
| 72 | 
            +
            - lib/kanshi/collector.rb
         | 
| 73 | 
            +
            - lib/kanshi/queries.rb
         | 
| 74 | 
            +
            - lib/kanshi/scrolls_reporter.rb
         | 
| 75 | 
            +
            homepage: https://github.com/heroku/kanshi
         | 
| 76 | 
            +
            licenses:
         | 
| 77 | 
            +
            - MIT
         | 
| 78 | 
            +
            post_install_message: 
         | 
| 79 | 
            +
            rdoc_options: []
         | 
| 80 | 
            +
            require_paths:
         | 
| 81 | 
            +
            - lib
         | 
| 82 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 83 | 
            +
              none: false
         | 
| 84 | 
            +
              requirements:
         | 
| 85 | 
            +
              - - ! '>='
         | 
| 86 | 
            +
                - !ruby/object:Gem::Version
         | 
| 87 | 
            +
                  version: '0'
         | 
| 88 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 89 | 
            +
              none: false
         | 
| 90 | 
            +
              requirements:
         | 
| 91 | 
            +
              - - ! '>='
         | 
| 92 | 
            +
                - !ruby/object:Gem::Version
         | 
| 93 | 
            +
                  version: '0'
         | 
| 94 | 
            +
            requirements: []
         | 
| 95 | 
            +
            rubyforge_project: 
         | 
| 96 | 
            +
            rubygems_version: 1.8.23
         | 
| 97 | 
            +
            signing_key: 
         | 
| 98 | 
            +
            specification_version: 3
         | 
| 99 | 
            +
            summary: Monitors a Postgres database
         | 
| 100 | 
            +
            test_files: []
         |