pghero 0.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of pghero might be problematic. Click here for more details.

@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f98183aaa8abc79d3692c7a697fdbdacce963db5
4
+ data.tar.gz: a475c402a6e8e22ae5cb56aeab69ded88cec46e7
5
+ SHA512:
6
+ metadata.gz: 1754f5562b4479fca325777469b3fb1c5f393616b29644c8158d89e0640bc3ccf3c3bf7ba7eeca8f81e720c6b7e44d226be65d25c9432d8b2f0ca61fc791da83
7
+ data.tar.gz: ebd54924ec01f64d71d3e37d0e6558d77838aa44b5a25b324186d5b02e8965cb557449253dd1f32facee0c71f3f771e351923f73f9ee8870a99472fa8a6b2aa8
@@ -0,0 +1,22 @@
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
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pghero.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Andrew Kane, 2008-2014 Heroku (initial queries)
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,78 @@
1
+ # PgHero
2
+
3
+ :tada: Database insights made easy
4
+
5
+ Supports PostgreSQL 9.2+
6
+
7
+ For pure SQL, check out [PgHero.sql](https://github.com/ankane/pghero.sql)
8
+
9
+ :clap: Initial queries and inspiration by [Heroku](https://blog.heroku.com/archives/2013/5/10/more_insight_into_your_database_with_pgextras)
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application’s Gemfile:
14
+
15
+ ```ruby
16
+ gem 'pghero'
17
+ ```
18
+
19
+ And mount the dashboard in your router.
20
+
21
+ ```ruby
22
+ mount PgHero::Engine, at: "pghero"
23
+ ```
24
+
25
+ Be sure to [protect the dashboard](#security) in production.
26
+
27
+ ## Insights
28
+
29
+ ```ruby
30
+ PgHero.running_queries
31
+ PgHero.long_running_queries
32
+ PgHero.index_usage
33
+ PgHero.missing_indexes
34
+ PgHero.unused_indexes
35
+ PgHero.relation_sizes
36
+ PgHero.index_hit_rate
37
+ PgHero.table_hit_rate
38
+
39
+ # kill queries
40
+ PgHero.kill(pid)
41
+ PgHero.kill_all
42
+ ```
43
+
44
+ ## Security
45
+
46
+ #### Basic Authentication
47
+
48
+ Set the following variables in your environment or an initializer.
49
+
50
+ ```ruby
51
+ ENV["PGHERO_USERNAME"] = "andrew"
52
+ ENV["PGHERO_PASSWORD"] = "secret"
53
+ ```
54
+
55
+ #### Devise
56
+
57
+ ```ruby
58
+ authenticate :user, lambda {|user| user.admin? } do
59
+ mount PgHero::Engine, at: "pghero"
60
+ end
61
+ ```
62
+
63
+ ## TODO
64
+
65
+ - demo
66
+ - better explanations on dashboard
67
+ - more space metrics
68
+ - poll for running queries
69
+ - better design (no bootstrap)
70
+
71
+ ## Contributing
72
+
73
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
74
+
75
+ - [Report bugs](https://github.com/ankane/pghero/issues)
76
+ - Fix bugs and [submit pull requests](https://github.com/ankane/pghero/pulls)
77
+ - Write, clarify, or fix documentation
78
+ - Suggest or add new features
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,48 @@
1
+ module PgHero
2
+ class HomeController < ActionController::Base
3
+ layout "pg_hero/application"
4
+
5
+ protect_from_forgery
6
+
7
+ http_basic_authenticate_with name: ENV["PGHERO_USERNAME"], password: ENV["PGHERO_PASSWORD"] if ENV["PGHERO_PASSWORD"]
8
+
9
+ def index
10
+ @title = "Status"
11
+ @long_running_queries = PgHero.long_running_queries
12
+ @index_hit_rate = PgHero.index_hit_rate
13
+ @table_hit_rate = PgHero.table_hit_rate
14
+ @missing_indexes = PgHero.missing_indexes
15
+ @unused_indexes = PgHero.unused_indexes
16
+ @good_cache_rate = @table_hit_rate >= 0.99 && @index_hit_rate >= 0.99
17
+ end
18
+
19
+ def indexes
20
+ @title = "Indexes"
21
+ @index_usage = PgHero.index_usage
22
+ end
23
+
24
+ def space
25
+ @title = "Space"
26
+ @relation_sizes = PgHero.relation_sizes
27
+ end
28
+
29
+ def queries
30
+ @title = "Queries"
31
+ @running_queries = PgHero.running_queries
32
+ end
33
+
34
+ def kill
35
+ if PgHero.kill(params[:pid])
36
+ redirect_to root_path, notice: "Query killed"
37
+ else
38
+ redirect_to :back, notice: "Query no longer running"
39
+ end
40
+ end
41
+
42
+ def kill_all
43
+ PgHero.kill_all
44
+ redirect_to :back, notice: "Connections killed"
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,73 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= @title %> - PgHero</title>
5
+
6
+ <meta charset="utf-8" />
7
+
8
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
9
+
10
+ <style>
11
+ body {
12
+ padding-top: 20px;
13
+ }
14
+
15
+ h1 {
16
+ margin-top: 6px;
17
+ font-size: 20px;
18
+ }
19
+
20
+ h1, p {
21
+ margin-bottom: 20px;
22
+ }
23
+
24
+ .container {
25
+ max-width: 1000px;
26
+ }
27
+
28
+ #status {
29
+ margin-bottom: 20px;
30
+ }
31
+
32
+ #status div {
33
+ margin-bottom: 0;
34
+ border: none;
35
+ border-radius: 0;
36
+ }
37
+
38
+ .alert {
39
+ padding-top: 12px;
40
+ padding-bottom: 12px;
41
+ }
42
+
43
+ .queries > tbody > tr > td {
44
+ vertical-align: middle;
45
+ }
46
+ </style>
47
+ </head>
48
+ <body>
49
+ <div class="container">
50
+ <% if notice %>
51
+ <div class="alert alert-info"><%= notice %></div>
52
+ <% elsif Rails.env.development? %>
53
+ <div class="alert alert-info">Do not use development information to make decisions about your production environment</div>
54
+ <% end %>
55
+
56
+ <div class="row">
57
+ <div class="col-xs-3">
58
+ <ul class="nav nav-pills nav-stacked">
59
+ <!-- poor man's active_link_to -->
60
+ <li class="<%= controller.action_name == "index" ? "active" : "" %>"><%= link_to "Status", root_path %></li>
61
+ <li class="<%= controller.action_name == "indexes" ? "active" : "" %>"><%= link_to "Indexes", indexes_path %></li>
62
+ <li class="<%= controller.action_name == "space" ? "active" : "" %>"><%= link_to "Space", space_path %></li>
63
+ <li class="<%= controller.action_name == "queries" ? "active" : "" %>"><%= link_to "Queries", queries_path %></li>
64
+ </ul>
65
+ </div>
66
+
67
+ <div class="col-xs-9">
68
+ <%= yield %>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </body>
73
+ </html>
@@ -0,0 +1,27 @@
1
+ <table class="table queries">
2
+ <thead>
3
+ <tr>
4
+ <th style="width: 10%;">Pid</th>
5
+ <th style="width: 10%;">State</th>
6
+ <th>Source</th>
7
+ <th style="width: 10%;">Waiting</th>
8
+ <th style="width: 15%;">Duration</th>
9
+ <th style="width: 10%;"></th>
10
+ </tr>
11
+ </thead>
12
+ <tbody>
13
+ <% queries.each do |query| %>
14
+ <tr>
15
+ <td><%= query["pid"] %></td>
16
+ <td><%= query["state"] %></td>
17
+ <td><%= query["source"] %></td>
18
+ <td><%= query["waiting"] == "t" ? "true" : "false" %></td>
19
+ <td><%= query["duration"] %></td>
20
+ <td class="text-right"><%= button_to "Kill", kill_path(pid: query["pid"]), class: "btn btn-info" %></td>
21
+ </tr>
22
+ <tr>
23
+ <td colspan="6" style="border-top: none; padding-top: 0;"><pre><%= query["query"] %></pre></td>
24
+ </tr>
25
+ <% end %>
26
+ </tbody>
27
+ </table>
@@ -0,0 +1,101 @@
1
+ <div id="status">
2
+ <div class="alert alert-<%= @long_running_queries.empty? ? "success" : "warning" %>">
3
+ <% if @long_running_queries.any? %>
4
+ <%= pluralize(@long_running_queries.size, "long running query") %>
5
+ <% else %>
6
+ No long running queries
7
+ <% end %>
8
+ </div>
9
+ <div class="alert alert-<%= @good_cache_rate ? "success" : "warning" %>">
10
+ <% if @good_cache_rate %>
11
+ Cache hit rate above 99%
12
+ <% else %>
13
+ Cache hit rate under 99%
14
+ <% end %>
15
+ </div>
16
+ <div class="alert alert-<%= @missing_indexes.empty? ? "success" : "warning" %>">
17
+ <% if @missing_indexes.any? %>
18
+ <%= pluralize(@missing_indexes.size, "possibly missing indexes") %>
19
+ <% else %>
20
+ No missing indexes
21
+ <% end %>
22
+ </div>
23
+ <div class="alert alert-<%= @unused_indexes.empty? ? "success" : "warning" %>">
24
+ <% if @unused_indexes.any? %>
25
+ <%= pluralize(@unused_indexes.size, "unused indexes") %>
26
+ <% else %>
27
+ No unused indexes
28
+ <% end %>
29
+ </div>
30
+ </div>
31
+
32
+ <% if @long_running_queries.any? %>
33
+ <h1>Long Running Queries</h1>
34
+
35
+ <%= render partial: "queries_table", locals: {queries: @long_running_queries} %>
36
+ <% end %>
37
+
38
+ <% if !@good_cache_rate %>
39
+ <h1>Low Cache Hit Rate</h1>
40
+
41
+ <p>
42
+ Index Hit Rate: <%= (@index_hit_rate * 100).round(1) %>%
43
+ <br />
44
+ Table Hit Rate: <%= (@table_hit_rate * 100).round(1) %>%
45
+ </p>
46
+
47
+ <p>
48
+ Try adding more memory.
49
+ <!-- TODO better suggestions -->
50
+ </p>
51
+ <% end %>
52
+
53
+ <% if @missing_indexes.any? %>
54
+ <h1>Missing Indexes</h1>
55
+
56
+ <p>These tables have a large number of rows and a lower % of time the index is used. Consider adding indexes where it makes sense.</p>
57
+
58
+ <table class="table">
59
+ <thead>
60
+ <tr>
61
+ <th>Table</th>
62
+ <th>% of Time Index Used</th>
63
+ <th>Rows in Table</th>
64
+ </tr>
65
+ </thead>
66
+ <tbody>
67
+ <% @missing_indexes.each do |query| %>
68
+ <tr>
69
+ <td><%= query["table"] %></td>
70
+ <td style="width: 30%;"><%= query["percent_of_times_index_used"] %></td>
71
+ <td style="width: 20%;"><%= query["rows_in_table"] %></td>
72
+ </tr>
73
+ <% end %>
74
+ </tbody>
75
+ </table>
76
+ <% end %>
77
+
78
+ <% if @unused_indexes.any? %>
79
+ <h1>Unused Indexes</h1>
80
+
81
+ <p>Unused indexes cause unnecessary overhead.</p>
82
+
83
+ <table class="table">
84
+ <thead>
85
+ <tr>
86
+ <th>Name</th>
87
+ <th>Index Size</th>
88
+ <th>Index Scans</th>
89
+ </tr>
90
+ </thead>
91
+ <tbody>
92
+ <% @unused_indexes.each do |query| %>
93
+ <tr>
94
+ <td><%= query["index"] %><div class="text-muted">on <%= query["table"] %></div></td>
95
+ <td style="width: 15%;"><%= query["index_size"] %></td>
96
+ <td style="width: 15%;"><%= query["index_scans"] %></td>
97
+ </tr>
98
+ <% end %>
99
+ </tbody>
100
+ </table>
101
+ <% end %>
@@ -0,0 +1,20 @@
1
+ <h1>Index Usage</h1>
2
+
3
+ <table class="table">
4
+ <thead>
5
+ <tr>
6
+ <th>Table</th>
7
+ <th>% of Time Index Used</th>
8
+ <th>Rows in Table</th>
9
+ </tr>
10
+ </thead>
11
+ <tbody>
12
+ <% @index_usage.each do |query| %>
13
+ <tr>
14
+ <td><%= query["table"] %></td>
15
+ <td style="width: 30%;"><%= query["percent_of_times_index_used"] %></td>
16
+ <td style="width: 20%;"><%= query["rows_in_table"] %></td>
17
+ </tr>
18
+ <% end %>
19
+ </tbody>
20
+ </table>
@@ -0,0 +1,7 @@
1
+ <h1>Queries</h1>
2
+
3
+ <%= render partial: "queries_table", locals: {queries: @running_queries} %>
4
+
5
+ <p><%= button_to "Kill all connections", kill_all_path, class: "btn btn-danger" %></p>
6
+
7
+ <p class="text-muted">You may need to restart your Rails server afterwards.</p>
@@ -0,0 +1,20 @@
1
+ <h1>Largest Relations</h1>
2
+
3
+ <table class="table">
4
+ <thead>
5
+ <tr>
6
+ <th>Name</th>
7
+ <th style="width: 20%;">Type</th>
8
+ <th style="width: 20%;">Size</th>
9
+ </tr>
10
+ </thead>
11
+ <tbody>
12
+ <% @relation_sizes.each do |query| %>
13
+ <tr>
14
+ <td><%= query["name"] %></td>
15
+ <td><%= query["type"] %></td>
16
+ <td><%= query["size"] %></td>
17
+ </tr>
18
+ <% end %>
19
+ </tbody>
20
+ </table>
@@ -0,0 +1,9 @@
1
+ PgHero::Engine.routes.draw do
2
+ root to: "home#index"
3
+ get "indexes", to: "home#indexes"
4
+ get "space", to: "home#space"
5
+ get "queries", to: "home#queries"
6
+
7
+ post "kill", to: "home#kill"
8
+ post "kill_all", to: "home#kill_all"
9
+ end
@@ -0,0 +1,166 @@
1
+ require "pghero/version"
2
+ require "pghero/engine" if defined?(Rails)
3
+
4
+ module PgHero
5
+ class << self
6
+
7
+ def running_queries
8
+ execute %Q{
9
+ SELECT
10
+ pid,
11
+ state,
12
+ application_name AS source,
13
+ age(now(), xact_start) AS duration,
14
+ waiting,
15
+ query
16
+ FROM
17
+ pg_stat_activity
18
+ WHERE
19
+ query <> '<insufficient privilege>'
20
+ AND state <> 'idle'
21
+ AND pid <> pg_backend_pid()
22
+ ORDER BY
23
+ query_start DESC
24
+ }
25
+ end
26
+
27
+ def long_running_queries
28
+ execute %Q{
29
+ SELECT
30
+ pid,
31
+ state,
32
+ application_name AS source,
33
+ age(now(), xact_start) AS duration,
34
+ waiting,
35
+ query
36
+ FROM
37
+ pg_stat_activity
38
+ WHERE
39
+ query <> '<insufficient privilege>'
40
+ AND state <> 'idle'
41
+ AND pid <> pg_backend_pid()
42
+ AND now() - query_start > interval '5 minutes'
43
+ ORDER BY
44
+ query_start DESC
45
+ }
46
+ end
47
+
48
+ def index_hit_rate
49
+ execute(%Q{
50
+ SELECT
51
+ (sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read),0) AS rate
52
+ FROM
53
+ pg_statio_user_indexes
54
+ }).first["rate"].to_f
55
+ end
56
+
57
+ def table_hit_rate
58
+ execute(%Q{
59
+ SELECT
60
+ sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read),0) AS rate
61
+ FROM
62
+ pg_statio_user_tables
63
+ }).first["rate"].to_f
64
+ end
65
+
66
+ def index_usage
67
+ execute %Q{
68
+ SELECT
69
+ relname AS table,
70
+ CASE idx_scan
71
+ WHEN 0 THEN 'Insufficient data'
72
+ ELSE (100 * idx_scan / (seq_scan + idx_scan))::text
73
+ END percent_of_times_index_used,
74
+ n_live_tup rows_in_table
75
+ FROM
76
+ pg_stat_user_tables
77
+ ORDER BY
78
+ n_live_tup DESC,
79
+ relname ASC
80
+ }
81
+ end
82
+
83
+ def missing_indexes
84
+ execute %Q{
85
+ SELECT relname AS table,
86
+ CASE idx_scan
87
+ WHEN 0 THEN 'Insufficient data'
88
+ ELSE (100 * idx_scan / (seq_scan + idx_scan))::text
89
+ END percent_of_times_index_used,
90
+ n_live_tup rows_in_table
91
+ FROM
92
+ pg_stat_user_tables
93
+ WHERE
94
+ idx_scan < 95
95
+ AND n_live_tup >= 10000
96
+ ORDER BY
97
+ n_live_tup DESC,
98
+ relname ASC
99
+ }
100
+ end
101
+
102
+ def unused_indexes
103
+ execute %Q{
104
+ SELECT
105
+ relname AS table,
106
+ indexrelname AS index,
107
+ pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size,
108
+ idx_scan as index_scans
109
+ FROM
110
+ pg_stat_user_indexes ui
111
+ INNER JOIN
112
+ pg_index i ON ui.indexrelid = i.indexrelid
113
+ WHERE
114
+ NOT indisunique
115
+ AND idx_scan < 50 AND pg_relation_size(relid) > 5 * 8192
116
+ ORDER BY
117
+ pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
118
+ pg_relation_size(i.indexrelid) DESC,
119
+ relname ASC
120
+ }
121
+ end
122
+
123
+ def relation_sizes
124
+ execute %Q{
125
+ SELECT
126
+ c.relname AS name,
127
+ CASE WHEN c.relkind = 'r' THEN 'table' ELSE 'index' END AS type,
128
+ pg_size_pretty(pg_table_size(c.oid)) AS size
129
+ FROM
130
+ pg_class c
131
+ LEFT JOIN
132
+ pg_namespace n ON (n.oid = c.relnamespace)
133
+ WHERE
134
+ n.nspname NOT IN ('pg_catalog', 'information_schema')
135
+ AND n.nspname !~ '^pg_toast'
136
+ AND c.relkind IN ('r', 'i')
137
+ ORDER BY
138
+ pg_table_size(c.oid) DESC,
139
+ name ASC
140
+ }
141
+ end
142
+
143
+ def kill(pid)
144
+ ActiveRecord::Base.connection.execute("SELECT pg_cancel_backend(#{pid.to_i})").first["pg_cancel_backend"] == "t"
145
+ end
146
+
147
+ def kill_all
148
+ execute %Q{
149
+ SELECT
150
+ pg_terminate_backend(pid)
151
+ FROM
152
+ pg_stat_activity
153
+ WHERE
154
+ pid <> pg_backend_pid()
155
+ AND query <> '<insufficient privilege>'
156
+ }
157
+ true
158
+ end
159
+
160
+ def execute(sql)
161
+ # squish for logs
162
+ ActiveRecord::Base.connection.select_all(sql.squish).to_a
163
+ end
164
+
165
+ end
166
+ end
@@ -0,0 +1,5 @@
1
+ module PgHero
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace PgHero
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module PgHero
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pghero/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pghero"
8
+ spec.version = PgHero::VERSION
9
+ spec.authors = ["Andrew Kane"]
10
+ spec.email = ["andrew@chartkick.com"]
11
+ spec.summary = %q{Database insights made easy}
12
+ spec.description = %q{Database insights made easy}
13
+ spec.homepage = "https://github.com/ankane/pghero"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pghero
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kane
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Database insights made easy
42
+ email:
43
+ - andrew@chartkick.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - LICENSE.txt
51
+ - README.md
52
+ - Rakefile
53
+ - app/controllers/pg_hero/home_controller.rb
54
+ - app/views/layouts/pg_hero/application.html.erb
55
+ - app/views/pg_hero/home/_queries_table.html.erb
56
+ - app/views/pg_hero/home/index.html.erb
57
+ - app/views/pg_hero/home/indexes.html.erb
58
+ - app/views/pg_hero/home/queries.html.erb
59
+ - app/views/pg_hero/home/space.html.erb
60
+ - config/routes.rb
61
+ - lib/pghero.rb
62
+ - lib/pghero/engine.rb
63
+ - lib/pghero/version.rb
64
+ - pghero.gemspec
65
+ homepage: https://github.com/ankane/pghero
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.2.2
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Database insights made easy
89
+ test_files: []
90
+ has_rdoc: