elefant 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +13 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +65 -0
  9. data/Rakefile +8 -0
  10. data/bin/elefant-web +3 -0
  11. data/config.ru +4 -0
  12. data/elefant.gemspec +32 -0
  13. data/lib/elefant.rb +22 -0
  14. data/lib/elefant/connection_adapter.rb +122 -0
  15. data/lib/elefant/postgres/size_queries.rb +31 -0
  16. data/lib/elefant/postgres/stat_queries.rb +91 -0
  17. data/lib/elefant/stats.rb +56 -0
  18. data/lib/elefant/version.rb +3 -0
  19. data/lib/elefant/web.rb +86 -0
  20. data/test/ar_connection_test.rb +55 -0
  21. data/test/config.yml.example +4 -0
  22. data/test/fixtures/teardown.sql +4 -0
  23. data/test/fixtures/test_models.sql +11 -0
  24. data/test/pg_connection_test.rb +12 -0
  25. data/test/stats_test.rb +32 -0
  26. data/test/test_helper.rb +37 -0
  27. data/test/webapp_test.rb +46 -0
  28. data/watchr.rb +34 -0
  29. data/web/locales/en.yml +33 -0
  30. data/web/public/css/_fonts.scss +36 -0
  31. data/web/public/css/_lists.scss +16 -0
  32. data/web/public/css/_nav.scss +75 -0
  33. data/web/public/css/_normalize.scss +427 -0
  34. data/web/public/css/_skeleton.scss +418 -0
  35. data/web/public/css/_tables.scss +36 -0
  36. data/web/public/css/_typography.scss +24 -0
  37. data/web/public/css/_variables.scss +2 -0
  38. data/web/public/css/elefant.css +13 -0
  39. data/web/public/css/elefant.css.map +7 -0
  40. data/web/public/css/elefant.scss +45 -0
  41. data/web/public/fonts/ClearSans-Bold.eot +0 -0
  42. data/web/public/fonts/ClearSans-Bold.svg +22646 -0
  43. data/web/public/fonts/ClearSans-Bold.ttf +0 -0
  44. data/web/public/fonts/ClearSans-Bold.woff +0 -0
  45. data/web/public/fonts/ClearSans-Light.eot +0 -0
  46. data/web/public/fonts/ClearSans-Light.svg +22411 -0
  47. data/web/public/fonts/ClearSans-Light.ttf +0 -0
  48. data/web/public/fonts/ClearSans-Light.woff +0 -0
  49. data/web/public/fonts/LICENSE-2.0.txt +202 -0
  50. data/web/public/img/postgresql.ico +0 -0
  51. data/web/public/img/postgresql_logo.svg +22 -0
  52. data/web/public/img/screenshot.png +0 -0
  53. data/web/public/js/zepto.min.js +2 -0
  54. data/web/views/activity.erb +27 -0
  55. data/web/views/indices.erb +12 -0
  56. data/web/views/layout.erb +58 -0
  57. data/web/views/partials/field_table.erb +18 -0
  58. data/web/views/partials/generic_table.erb +23 -0
  59. data/web/views/partials/list.erb +6 -0
  60. data/web/views/size.erb +23 -0
  61. data/web/views/summary.erb +18 -0
  62. data/web/views/tables.erb +5 -0
  63. metadata +255 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 088dc5c6b9b4cbaa02e1497445dc9fb3cb5d1f83
4
+ data.tar.gz: f7cf82d109b27295d28a8ed2c9726fa5c7eb0965
5
+ SHA512:
6
+ metadata.gz: 7f57ab871b8301a5671b8bbdaa914b5d90502ec160ab48694d6c0e7b904322db390ae9302453284cc02f1315dae0d02e9c893c7d029619084c522f8348e92a36
7
+ data.tar.gz: e84818a8ba32bb9bf34c1fba56d26eb7e16d4b79ae85d1e7d983c8fcc612acd070b2694c063ee7205db5f2baa219fddb222b717a7e380bf99e2063994b5e851f
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ test/config.yml
19
+ .sass-cache
20
+ **/.DS_Store
21
+
@@ -0,0 +1 @@
1
+ elefant-gem
@@ -0,0 +1 @@
1
+ 2.0.0-p645
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
4
+ - 2.1.5
5
+ - 2.0.0
6
+ - 1.9.3
7
+
8
+ addons:
9
+ postgresql: "9.3"
10
+
11
+ before_script:
12
+ - psql -c 'create database elefant_test;' -U postgres
13
+ - cp test/config.yml.example test/config.yml
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in elefant.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Christoph Sassenberg
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,65 @@
1
+ # Elefant
2
+
3
+ [![Build Status](https://travis-ci.org/defsprite/elefant.svg?branch=master)](https://travis-ci.org/defsprite/elefant)
4
+
5
+ When you are running a small / medium sized project, there is usually not a dedicated DBA and you rarely care about the database analytics that PostgreSQL gives you for free.
6
+ Elefant tries to help a little with that by providing web interface for some basic database analytics as a small mountable rack application.
7
+
8
+ ![Screenshot](https://raw.githubusercontent.com/defsprite/elefant/master/web/public/img/screenshot.png)
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'elefant'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install elefant
23
+
24
+ ## Usage
25
+
26
+ ### Standalone
27
+
28
+ Run the web interface as a standalone rack application:
29
+
30
+ $ DATABASE_URL=postgres:///some_db_connection_string elefant-web
31
+
32
+ or if you want to use a different database specifically for elefant:
33
+
34
+ $ ELEFANT_DATABASE_URL=postgres:///some_db_connection_string elefant-web
35
+
36
+ ### Rails
37
+
38
+ You can mount the web interface in a rails application by adding this to `config/routes.rb`:
39
+
40
+ ```ruby
41
+ require 'elefant/web'
42
+ mount Elefant::Web => '/elefant'
43
+ ```
44
+ It will pick up a connection from the `ActiveRecord` connection pool.
45
+
46
+ :warning: There is no authentication built in! Mounting it as described above is probably a bad idea! :warning:
47
+
48
+ In case you are using [Devise](https://github.com/plataformatec/devise), here is an example of mounting only for
49
+ authenticated users of the `:admin` scope:
50
+
51
+ ```ruby
52
+ authenticate(:admin) do
53
+ require 'elefant/web'
54
+ mount Elefant::Web => '/elefant'
55
+ end
56
+ ```
57
+
58
+
59
+ ## Contributing
60
+
61
+ 1. Fork it
62
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
63
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
64
+ 4. Push to the branch (`git push origin my-new-feature`)
65
+ 5. Create new Pull Request
@@ -0,0 +1,8 @@
1
+ require "rake/testtask"
2
+
3
+ Rake::TestTask.new do |task|
4
+ task.libs << %w(test lib)
5
+ task.pattern = "test/*_test.rb"
6
+ end
7
+
8
+ task :default => :test
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ app = File.expand_path('../../config.ru', __FILE__)
3
+ system("bundle exec rackup '#{app}'")
@@ -0,0 +1,4 @@
1
+ require 'elefant'
2
+ require 'elefant/web'
3
+
4
+ run Elefant::Web
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'elefant/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "elefant"
8
+ gem.version = Elefant::VERSION
9
+ gem.authors = ["Christoph Sassenberg"]
10
+ gem.email = ["christoph.sassenberg@googlemail.com"]
11
+ gem.description = %q{The elefant gem gives you a few insights analytics about your PostgreSQL database}
12
+ gem.summary = %q{Know your PostgreSQL database}
13
+ gem.homepage = "http://github.com/defsprite/elefant"
14
+ gem.licenses = ["MIT"]
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+
21
+ gem.add_dependency 'pg', '~> 0.15'
22
+ gem.add_dependency 'sinatra', '~> 1.4'
23
+ gem.add_dependency 'sinatra-partial', '~> 0.4'
24
+ gem.add_dependency 'i18n', '~> 0.7'
25
+
26
+ gem.add_development_dependency 'rake', '~> 10.4'
27
+ gem.add_development_dependency 'minitest', '~> 5.5'
28
+ gem.add_development_dependency 'rack-test', '~> 0.6'
29
+ gem.add_development_dependency 'watchr', '~> 0.7'
30
+ gem.add_development_dependency 'activerecord', '~> 4.2'
31
+ gem.add_development_dependency 'sass', '~> 3.4'
32
+ end
@@ -0,0 +1,22 @@
1
+ require "elefant/version"
2
+ require "elefant/stats"
3
+
4
+ module Elefant
5
+
6
+ def self.configuration
7
+ @configuration ||= Configuration.new
8
+ end
9
+
10
+ def self.configure
11
+ configuration = self.configuration
12
+ yield(configuration)
13
+ end
14
+
15
+ class Configuration
16
+ attr_accessor :disable_ar
17
+
18
+ def initialize
19
+ @disable_ar = false
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,122 @@
1
+ require "uri"
2
+ require "pg"
3
+
4
+ module Elefant
5
+ class ConnectionAdapter
6
+
7
+ attr_accessor :connection
8
+
9
+ def initialize(connection=nil)
10
+ @connection = connection.nil? ? establish_new : validate!(connection)
11
+ end
12
+
13
+ def execute(stmt, params = [])
14
+ begin
15
+ params = nil if params.empty?
16
+ r = @connection.exec(stmt, params)
17
+ result = []
18
+ r.each do |t|
19
+ result << t
20
+ end
21
+ result
22
+ rescue PGError => e
23
+ @connection.reset
24
+ raise e
25
+ end
26
+ end
27
+
28
+ def disconnect
29
+ begin
30
+ if active_record?
31
+ @connection = nil
32
+ ActiveRecord::Base.clear_active_connections!
33
+ else
34
+ @connection.close
35
+ end
36
+ rescue => e
37
+ # TODO: log something and do sensible stuff
38
+ raise e
39
+ end
40
+ end
41
+
42
+ def alive?
43
+ @connection.query "SELECT 1"
44
+ true
45
+ rescue PGError
46
+ false
47
+ end
48
+
49
+ def info
50
+ @info ||= {
51
+ db_name: @connection.db,
52
+ server_version: version_str(connection.server_version),
53
+ client_version: (PG.respond_to?( :library_version ) ? version_str(PG.library_version) : 'unknown')
54
+ }
55
+ end
56
+
57
+ def db_name
58
+ @connection.db
59
+ end
60
+
61
+ def active_record?
62
+ defined?(ActiveRecord::Base) == "constant" && ActiveRecord::Base.class == Class && !Elefant.configuration.disable_ar
63
+ end
64
+
65
+ private
66
+
67
+ def version_str(number)
68
+ number.to_s.tr('0','.').gsub(/(\.)+\z/, '')
69
+ end
70
+
71
+ def validate!(c)
72
+ return c if c.is_a?(PG::Connection)
73
+ err = "connection must be an instance of PG::Connection, but was #{c.class}"
74
+ raise(ArgumentError, err)
75
+ end
76
+
77
+ def establish_new
78
+ #QC.log(:at => "establish_conn")
79
+ connection = if active_record?
80
+ establish_ar
81
+ else
82
+ establish_pg
83
+ end
84
+ connection.exec("SET application_name = 'Elefant Stats #{Elefant::VERSION}'")
85
+ connection
86
+ end
87
+
88
+ def establish_pg
89
+ PGconn.connect(*normalize_db_url(db_url))
90
+ # if conn.status != PGconn::CONNECTION_OK
91
+ # log(:error => conn.error)
92
+ # end
93
+ end
94
+
95
+ def establish_ar
96
+ ActiveRecord::Base.connection.raw_connection
97
+ end
98
+
99
+ def normalize_db_url(url)
100
+ host = url.host
101
+ host = host.gsub(/%2F/i, '/') if host
102
+
103
+ [
104
+ host, # host or percent-encoded socket path
105
+ url.port || 5432,
106
+ nil, "", #opts, tty
107
+ url.path.gsub('/', ''), # database name
108
+ url.user,
109
+ url.password
110
+ ]
111
+ end
112
+
113
+ def db_url
114
+ return @db_url if defined?(@db_url) && @db_url
115
+
116
+ url = ENV["ELEFANT_DATABASE_URL"] ||
117
+ ENV["DATABASE_URL"] ||
118
+ raise(ArgumentError, "missing ELEFANT_DATABASE_URL or DATABASE_URL")
119
+ @db_url = URI.parse(url)
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,31 @@
1
+ module Elefant
2
+ module Postgres
3
+ module SizeQueries
4
+
5
+ def top_sizes(limit = 20)
6
+ exec %Q{
7
+ SELECT
8
+ relname AS name,
9
+ relkind AS kind,
10
+ pg_size_pretty(pg_relation_size(pg_class.oid)) AS size
11
+ FROM
12
+ pg_class
13
+ ORDER BY
14
+ pg_relation_size(pg_class.oid) DESC
15
+ LIMIT #{limit}
16
+ }
17
+ end
18
+
19
+ def size
20
+ exec %Q{
21
+ SELECT
22
+ '#{@connection.db_name}' AS db_name,
23
+ count(oid) AS num_rels,
24
+ pg_size_pretty(pg_database_size('#{@connection.db_name}')) AS dbsize
25
+ FROM
26
+ pg_class
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,91 @@
1
+ module Elefant::Postgres
2
+ module StatQueries
3
+
4
+ def initialize(connection)
5
+ @connection = connection
6
+ end
7
+
8
+ def activity
9
+ exec %Q{
10
+ SELECT
11
+ -- datid
12
+ -- datname
13
+ -- pid
14
+ -- usesysid
15
+ usename AS act_user,
16
+ application_name AS act_app,
17
+ client_addr AS act_c_addr,
18
+ client_hostname AS act_c_host,
19
+ client_port AS act_c_port,
20
+ backend_start AS act_bknd_start,
21
+ xact_start AS act_tx_start,
22
+ query_start AS act_q_start,
23
+ state_change AS act_st_chng,
24
+ waiting AS act_wtng,
25
+ state AS act_state,
26
+ query AS act_qry
27
+ FROM
28
+ pg_stat_activity
29
+ WHERE
30
+ datname = '%s';
31
+ } % [@connection.db_name]
32
+ end
33
+
34
+ def user_tables
35
+ exec %Q{
36
+ SELECT
37
+ relname AS rel_name,
38
+ heap_blks_read AS heap_blks_rd,
39
+ heap_blks_hit AS heap_blks_ht,
40
+ idx_blks_read AS idx_blks_rd,
41
+ idx_blks_hit AS idx_blks_ht
42
+ FROM
43
+ pg_statio_user_tables;
44
+ }
45
+ end
46
+
47
+ def user_indexes
48
+ exec %Q{
49
+ SELECT
50
+ -- relid
51
+ -- indexrelid
52
+ -- schemaname
53
+ relname AS rel_name,
54
+ indexrelname AS idx_name,
55
+ idx_scan AS idx_scn,
56
+ idx_tup_read AS idx_tup_rd,
57
+ idx_tup_fetch AS idx_tup_ftch
58
+ FROM
59
+ pg_stat_user_indexes;
60
+ }
61
+ end
62
+
63
+ def summary
64
+ exec %Q{
65
+ SELECT
66
+ now() AS db_time,
67
+ MAX(stat_db.xact_commit) AS commits,
68
+ MAX(stat_db.xact_rollback) AS rollbks,
69
+ MAX(stat_db.blks_read) AS blksrd,
70
+ MAX(stat_db.blks_hit) AS blkshit,
71
+ MAX(stat_db.numbackends) AS bkends,
72
+ SUM(stat_tables.seq_scan) AS seqscan,
73
+ SUM(stat_tables.seq_tup_read) AS seqtprd,
74
+ SUM(stat_tables.idx_scan) AS idxscn,
75
+ SUM(stat_tables.idx_tup_fetch) AS idxtrd,
76
+ SUM(stat_tables.n_tup_ins) AS ins,
77
+ SUM(stat_tables.n_tup_upd) AS upd,
78
+ SUM(stat_tables.n_tup_del) AS del,
79
+ MAX(stat_locks.locks) AS locks,
80
+ MAX(activity.sess) AS activeq
81
+ FROM
82
+ pg_stat_database AS stat_db,
83
+ pg_stat_user_tables AS stat_tables,
84
+ (SELECT COUNT(*) AS locks FROM pg_locks ) AS stat_locks,
85
+ (SELECT COUNT(*) AS sess FROM pg_stat_activity WHERE query <> '<IDLE>') AS activity
86
+ WHERE
87
+ stat_db.datname = '%s';
88
+ } % [@connection.db_name]
89
+ end
90
+ end
91
+ end