elefant 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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +65 -0
- data/Rakefile +8 -0
- data/bin/elefant-web +3 -0
- data/config.ru +4 -0
- data/elefant.gemspec +32 -0
- data/lib/elefant.rb +22 -0
- data/lib/elefant/connection_adapter.rb +122 -0
- data/lib/elefant/postgres/size_queries.rb +31 -0
- data/lib/elefant/postgres/stat_queries.rb +91 -0
- data/lib/elefant/stats.rb +56 -0
- data/lib/elefant/version.rb +3 -0
- data/lib/elefant/web.rb +86 -0
- data/test/ar_connection_test.rb +55 -0
- data/test/config.yml.example +4 -0
- data/test/fixtures/teardown.sql +4 -0
- data/test/fixtures/test_models.sql +11 -0
- data/test/pg_connection_test.rb +12 -0
- data/test/stats_test.rb +32 -0
- data/test/test_helper.rb +37 -0
- data/test/webapp_test.rb +46 -0
- data/watchr.rb +34 -0
- data/web/locales/en.yml +33 -0
- data/web/public/css/_fonts.scss +36 -0
- data/web/public/css/_lists.scss +16 -0
- data/web/public/css/_nav.scss +75 -0
- data/web/public/css/_normalize.scss +427 -0
- data/web/public/css/_skeleton.scss +418 -0
- data/web/public/css/_tables.scss +36 -0
- data/web/public/css/_typography.scss +24 -0
- data/web/public/css/_variables.scss +2 -0
- data/web/public/css/elefant.css +13 -0
- data/web/public/css/elefant.css.map +7 -0
- data/web/public/css/elefant.scss +45 -0
- data/web/public/fonts/ClearSans-Bold.eot +0 -0
- data/web/public/fonts/ClearSans-Bold.svg +22646 -0
- data/web/public/fonts/ClearSans-Bold.ttf +0 -0
- data/web/public/fonts/ClearSans-Bold.woff +0 -0
- data/web/public/fonts/ClearSans-Light.eot +0 -0
- data/web/public/fonts/ClearSans-Light.svg +22411 -0
- data/web/public/fonts/ClearSans-Light.ttf +0 -0
- data/web/public/fonts/ClearSans-Light.woff +0 -0
- data/web/public/fonts/LICENSE-2.0.txt +202 -0
- data/web/public/img/postgresql.ico +0 -0
- data/web/public/img/postgresql_logo.svg +22 -0
- data/web/public/img/screenshot.png +0 -0
- data/web/public/js/zepto.min.js +2 -0
- data/web/views/activity.erb +27 -0
- data/web/views/indices.erb +12 -0
- data/web/views/layout.erb +58 -0
- data/web/views/partials/field_table.erb +18 -0
- data/web/views/partials/generic_table.erb +23 -0
- data/web/views/partials/list.erb +6 -0
- data/web/views/size.erb +23 -0
- data/web/views/summary.erb +18 -0
- data/web/views/tables.erb +5 -0
- metadata +255 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require "elefant/connection_adapter"
|
|
2
|
+
require "elefant/postgres/stat_queries"
|
|
3
|
+
require "elefant/postgres/size_queries"
|
|
4
|
+
|
|
5
|
+
module Elefant
|
|
6
|
+
class Stats
|
|
7
|
+
include Elefant::Postgres::StatQueries
|
|
8
|
+
include Elefant::Postgres::SizeQueries
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@connection = Elefant::ConnectionAdapter.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def db_name
|
|
15
|
+
@connection.info[:db_name]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def version
|
|
19
|
+
@connection.info[:server_version]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def client_version
|
|
23
|
+
@connection.info[:client_version]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def get(name, params)
|
|
27
|
+
query(name, params)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def close!
|
|
31
|
+
@connection.disconnect
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.check!
|
|
35
|
+
connection = Elefant::ConnectionAdapter.new
|
|
36
|
+
raise ArgumentError.new("Could not establish connection") unless connection.alive?
|
|
37
|
+
connection.disconnect
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def exec(query, params = [])
|
|
43
|
+
@connection.execute(query, params)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def query(name, params)
|
|
47
|
+
method = name.to_sym
|
|
48
|
+
|
|
49
|
+
if respond_to?(method)
|
|
50
|
+
send(method, *params)
|
|
51
|
+
else
|
|
52
|
+
raise ArgumentError.new("Unknown Stats Query: #{name}")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/elefant/web.rb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require "sinatra"
|
|
2
|
+
require "sinatra/partial"
|
|
3
|
+
require "i18n"
|
|
4
|
+
require "i18n/backend/fallbacks"
|
|
5
|
+
require "elefant"
|
|
6
|
+
|
|
7
|
+
module Elefant
|
|
8
|
+
class Web < Sinatra::Base
|
|
9
|
+
register Sinatra::Partial
|
|
10
|
+
|
|
11
|
+
dir = File.expand_path(File.dirname(__FILE__) + "/../../web")
|
|
12
|
+
|
|
13
|
+
set :public_folder, "#{dir}/public"
|
|
14
|
+
set :views, "#{dir}/views"
|
|
15
|
+
set :root, "#{dir}/public"
|
|
16
|
+
set :locales, "#{dir}/locales"
|
|
17
|
+
|
|
18
|
+
set :partial_template_engine, :erb
|
|
19
|
+
set :layout_engine, :erb
|
|
20
|
+
|
|
21
|
+
configure do
|
|
22
|
+
Elefant::Stats.check!
|
|
23
|
+
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
|
|
24
|
+
I18n.load_path = Dir[File.join(settings.locales, '*.yml')]
|
|
25
|
+
I18n.backend.load_translations
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
before do
|
|
29
|
+
@stats ||= Elefant::Stats.new
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
after do
|
|
33
|
+
@stats.close!
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
helpers do
|
|
37
|
+
|
|
38
|
+
def stats
|
|
39
|
+
@stats ||= Elefant::Stats.new
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def get_stats(name, params = [])
|
|
43
|
+
stats.get(name, params)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def css_for(field)
|
|
47
|
+
I18n.t(field, scope: "css", default: "")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def t(*args, options)
|
|
51
|
+
options.merge!(locale: :en)
|
|
52
|
+
I18n.t(*args, options)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def link(name, target, params = {})
|
|
56
|
+
extra = params.any? ? "?" + params.map {|k, v| "#{k}=#{v}" }.join("&") : ""
|
|
57
|
+
css = target == current_path ? 'active' : ''
|
|
58
|
+
%Q{<a href="#{root_path}#{target}#{extra}" class="#{css}">#{name}</a>}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def root_path
|
|
62
|
+
%Q{#{env['SCRIPT_NAME']}/}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def current_path
|
|
66
|
+
@current_path ||= request.path_info.gsub(/^\//,'')
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
get "/" do
|
|
71
|
+
redirect "#{root_path}summary", 302
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
get "/:name" do
|
|
75
|
+
name = params[:name]
|
|
76
|
+
if %w(activity indices size summary tables).include?(name)
|
|
77
|
+
erb name.to_sym
|
|
78
|
+
else
|
|
79
|
+
not_found do
|
|
80
|
+
"Page not found! Sorry."
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
describe "ActiveRecord being loaded" do
|
|
4
|
+
|
|
5
|
+
fork do
|
|
6
|
+
before do
|
|
7
|
+
@db_url = ENV.delete("DATABASE_URL")
|
|
8
|
+
config_file = File.expand_path("config.yml", File.dirname(__FILE__))
|
|
9
|
+
|
|
10
|
+
require "active_record"
|
|
11
|
+
|
|
12
|
+
unless File.exist?(config_file)
|
|
13
|
+
puts "Please copy test/config.yml to test/config.yml.example and edit accordingly"
|
|
14
|
+
abort
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
ActiveRecord::Base.establish_connection(YAML::load(File.open(config_file)))
|
|
18
|
+
ActiveRecord::Base.connection.execute("SELECT 1")
|
|
19
|
+
|
|
20
|
+
@old_ar_config_value = Elefant.configuration.disable_ar
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
after do
|
|
24
|
+
ENV["DATABASE_URL"] = @db_url
|
|
25
|
+
|
|
26
|
+
Elefant.configure do |c|
|
|
27
|
+
c.disable_ar = @old_ar_config_value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "connects to the test database using AR" do
|
|
32
|
+
connection = Elefant::ConnectionAdapter.new
|
|
33
|
+
|
|
34
|
+
assert connection.alive?, "Connection must be alive"
|
|
35
|
+
assert connection.active_record?, "Connection must be active record"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "does not use active record when disabled in the configuration" do
|
|
39
|
+
Elefant.configure do |c|
|
|
40
|
+
c.disable_ar = true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
assert_raises ArgumentError do
|
|
44
|
+
Elefant::ConnectionAdapter.new
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "runs the ar specs successfully" do
|
|
50
|
+
Process.wait
|
|
51
|
+
exit_status = $?.to_i
|
|
52
|
+
|
|
53
|
+
assert_equal 0, exit_status
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
CREATE SEQUENCE elefant_id_seq;
|
|
2
|
+
|
|
3
|
+
CREATE TABLE elefant_test_models (
|
|
4
|
+
id integer not null default nextval('elefant_id_seq'::regclass) PRIMARY KEY,
|
|
5
|
+
name character varying(255),
|
|
6
|
+
number integer,
|
|
7
|
+
created_at timestamp without time zone,
|
|
8
|
+
updated_at timestamp without time zone
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
CREATE INDEX elefant_index ON elefant_test_models USING btree (name);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
describe "when connecting via DATABASE_URL" do
|
|
4
|
+
|
|
5
|
+
it "connects to the test database using the pg driver directly" do
|
|
6
|
+
connection = Elefant::ConnectionAdapter.new
|
|
7
|
+
|
|
8
|
+
assert connection.alive?, "Connection must be alive"
|
|
9
|
+
refute connection.active_record?, "Connection must not be active record"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
data/test/stats_test.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
require "elefant/stats"
|
|
3
|
+
|
|
4
|
+
class StatsTest < PGTest
|
|
5
|
+
|
|
6
|
+
def setup
|
|
7
|
+
super
|
|
8
|
+
@stats = Elefant::Stats.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_retrieves_activity
|
|
12
|
+
result = @stats.get("activity", [])
|
|
13
|
+
|
|
14
|
+
assert_instance_of Array, result
|
|
15
|
+
refute_empty result
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_retrieves_user_indices
|
|
19
|
+
result = @stats.get("user_indexes", [])
|
|
20
|
+
|
|
21
|
+
assert_instance_of Array, result
|
|
22
|
+
refute_empty result
|
|
23
|
+
assert_equal "elefant_test_models", result[0]["rel_name"]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_raises_for_unknown
|
|
27
|
+
assert_raises ArgumentError do
|
|
28
|
+
@stats.get("totallyweirdunknownstatname")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
$: << File.expand_path("lib")
|
|
2
|
+
$: << File.expand_path("test")
|
|
3
|
+
|
|
4
|
+
require "bundler"
|
|
5
|
+
Bundler.setup :default, :test
|
|
6
|
+
|
|
7
|
+
ENV["DATABASE_URL"] ||= "postgres:///elefant_test"
|
|
8
|
+
|
|
9
|
+
require "elefant"
|
|
10
|
+
require "minitest/autorun"
|
|
11
|
+
|
|
12
|
+
Elefant.configure do |c|
|
|
13
|
+
c.disable_ar = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class PGTest < Minitest::Test
|
|
17
|
+
|
|
18
|
+
def setup
|
|
19
|
+
init_db
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def teardown
|
|
23
|
+
reset_db
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def init_db
|
|
27
|
+
c = Elefant::ConnectionAdapter.new
|
|
28
|
+
c.execute(File.read('./test/fixtures/test_models.sql'))
|
|
29
|
+
c.disconnect
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def reset_db
|
|
33
|
+
c = Elefant::ConnectionAdapter.new
|
|
34
|
+
c.execute(File.read('./test/fixtures/teardown.sql'))
|
|
35
|
+
c.disconnect
|
|
36
|
+
end
|
|
37
|
+
end
|
data/test/webapp_test.rb
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
require "rack/test"
|
|
3
|
+
require "elefant/web"
|
|
4
|
+
|
|
5
|
+
ENV["RACK_ENV"] = "test"
|
|
6
|
+
|
|
7
|
+
describe "the elefant web interface" do
|
|
8
|
+
include Rack::Test::Methods
|
|
9
|
+
|
|
10
|
+
def app
|
|
11
|
+
Elefant::Web
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "redirects to the summary on the index page" do
|
|
15
|
+
get "/"
|
|
16
|
+
|
|
17
|
+
assert last_response.redirect?
|
|
18
|
+
assert last_response.location.match("/summary")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "shows a database summary" do
|
|
22
|
+
get "/summary"
|
|
23
|
+
assert last_response.ok?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "shows activity info" do
|
|
27
|
+
get "/activity"
|
|
28
|
+
assert last_response.ok?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "shows info about indices" do
|
|
32
|
+
get "/indices"
|
|
33
|
+
assert last_response.ok?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "shows table info" do
|
|
37
|
+
get "/tables"
|
|
38
|
+
assert last_response.ok?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "shows size info" do
|
|
42
|
+
get "/size"
|
|
43
|
+
assert last_response.ok?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
data/watchr.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Run me with:
|
|
2
|
+
# $ watchr watchr.rb
|
|
3
|
+
|
|
4
|
+
# --------------------------------------------------
|
|
5
|
+
# Rules
|
|
6
|
+
# --------------------------------------------------
|
|
7
|
+
watch( '^test.*/*_test.*\.rb' ) { rake } # or run specific one { |m| ruby m[0] }
|
|
8
|
+
watch( '^lib/(.*)\.rb' ) { rake } # { |m| ruby "test/test_#{m[1]}.rb" }
|
|
9
|
+
watch( '^test/test_helper\.rb' ) { rake }
|
|
10
|
+
watch( '^web/views/(.*)\.(.*)' ) { rake }
|
|
11
|
+
watch( '^web/public/css/(.*)\.scss' ) { sass }
|
|
12
|
+
|
|
13
|
+
# --------------------------------------------------
|
|
14
|
+
# Signal Handling
|
|
15
|
+
# --------------------------------------------------
|
|
16
|
+
Signal.trap('QUIT') { rake } # Ctrl-\
|
|
17
|
+
Signal.trap('INT' ) { abort("\n") } # Ctrl-C
|
|
18
|
+
|
|
19
|
+
# --------------------------------------------------
|
|
20
|
+
# Helpers
|
|
21
|
+
# --------------------------------------------------
|
|
22
|
+
def rake
|
|
23
|
+
run "clear"
|
|
24
|
+
run "bundle exec rake"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def sass
|
|
28
|
+
run "bundle exec sass --style compressed --scss -I web/public/css/ web/public/css/elefant.scss web/public/css/elefant.css"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def run( cmd )
|
|
32
|
+
puts cmd
|
|
33
|
+
system cmd
|
|
34
|
+
end
|
data/web/locales/en.yml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
en:
|
|
2
|
+
db:
|
|
3
|
+
db_name: "database"
|
|
4
|
+
db_time: "time"
|
|
5
|
+
dbsize: "total size"
|
|
6
|
+
num_rels: "total relations"
|
|
7
|
+
commits: "commits"
|
|
8
|
+
rollbks: "rollbacks"
|
|
9
|
+
blksrd: "blocks read"
|
|
10
|
+
blkshit: "blocks hit"
|
|
11
|
+
bkends: "backends"
|
|
12
|
+
seqscan: "sequential scans"
|
|
13
|
+
seqtprd: "sequential tupels read"
|
|
14
|
+
idxscn: "index scans"
|
|
15
|
+
idxtrd: "index tupel fetches"
|
|
16
|
+
ins: "inserts"
|
|
17
|
+
upd: "updates"
|
|
18
|
+
del: "deletes"
|
|
19
|
+
locks: "locks"
|
|
20
|
+
activeq: "max active queries"
|
|
21
|
+
kind:
|
|
22
|
+
i: "index"
|
|
23
|
+
t: "toast"
|
|
24
|
+
r: "table"
|
|
25
|
+
S: "sequence"
|
|
26
|
+
css:
|
|
27
|
+
idx_scn: "numeric"
|
|
28
|
+
idx_tup_rd: "numeric"
|
|
29
|
+
idx_tup_ftch: "numeric"
|
|
30
|
+
heap_blks_rd: "numeric"
|
|
31
|
+
heap_blks_ht: "numeric"
|
|
32
|
+
idx_blks_rd: "numeric"
|
|
33
|
+
idx_blks_ht: "numeric"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Clear Sans Webfont
|
|
3
|
+
*
|
|
4
|
+
* Webfont conversion of the Clear Sans typeface, designed by the
|
|
5
|
+
* Intel Open Source Technology Center <https://01.org/clear-sans>
|
|
6
|
+
*
|
|
7
|
+
* Original font file released under the Apache 2.0 License
|
|
8
|
+
* <http://www.apache.org/licenses/LICENSE-2.0.html>
|
|
9
|
+
*
|
|
10
|
+
* Webfont version by Resi Respati <resir014@gmail.com>
|
|
11
|
+
* Released under the MIT License.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
@font-face {
|
|
15
|
+
font-family: 'Clear Sans';
|
|
16
|
+
font-style: normal;
|
|
17
|
+
font-weight: 300;
|
|
18
|
+
src: local('ClearSans-Light'), local('Clear Sans Light'),
|
|
19
|
+
url('../fonts/ClearSans-Light.eot'),
|
|
20
|
+
url('../fonts/ClearSans-Light.eot?#iefix') format('embedded-opentype'),
|
|
21
|
+
url('../fonts/ClearSans-Light.woff') format('woff'),
|
|
22
|
+
url('../fonts/ClearSans-Light.ttf') format('truetype'),
|
|
23
|
+
url('../fonts/ClearSans-Light.svg') format('svg');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@font-face {
|
|
27
|
+
font-family: 'Clear Sans';
|
|
28
|
+
font-style: normal;
|
|
29
|
+
font-weight: 700;
|
|
30
|
+
src: local('ClearSans-Bold'), local('Clear Sans Bold'),
|
|
31
|
+
url('../fonts/ClearSans-Bold.eot'),
|
|
32
|
+
url('../fonts/ClearSans-Bold.eot?#iefix') format('embedded-opentype'),
|
|
33
|
+
url('../fonts/ClearSans-Bold.woff') format('woff'),
|
|
34
|
+
url('../fonts/ClearSans-Bold.ttf') format('truetype'),
|
|
35
|
+
url('../fonts/ClearSans-Bold.svg') format('svg');
|
|
36
|
+
}
|