pghero 1.1.0 → 1.1.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1ed028006b69e4e3595170baffc6fb328de3cd3f
4
- data.tar.gz: 05f3f70425c1506f3ba7e6106e1be4922b256fc6
3
+ metadata.gz: aca2a34abdf1aa45e33214683e898ef7b38cf4b5
4
+ data.tar.gz: d538fd497d9e85adef16551f3e4f54b2c0f81ae0
5
5
  SHA512:
6
- metadata.gz: a7d7323653811a623f0a7843fd5aaec408570f96909a7ccec71f2aaae343619f67eb8ff81878f1f86cc035f9a1cc2c20539470025136ac004765aa0d67c6e75b
7
- data.tar.gz: 951cf7cc4359047ec384905e6dcbd0e8f6de9c205a32a09c399a38191f9edcf10492b07b05f8af6a80948d1366c34ca9edc7c7f26b20812fc39f7f8b90f43e9a
6
+ metadata.gz: 3af44d86c65e316555e8a6ac6a8692214025649d2c4b05e64244b55e1bd6b7b538d5b13efc857238330d51adc8bd9cd6e222678679bbc5dd31740017a0d95191
7
+ data.tar.gz: 6ccddb176e5131effd878d5efb7074812918e3fe5ed80733e84a9621edf77127a1c3222ec6d3f011e974020bd6752539b853ce809c2aa50ca2982fd01b02703b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 1.1.1
2
+
3
+ - Added `tables` option to `create_user` method
4
+ - Added ability to sort query stats by average_time and calls
5
+ - Only show unused indexes with no index scans in UI
6
+
1
7
  ## 1.1.0
2
8
 
3
9
  - Added historical query stats
@@ -16,7 +16,7 @@ module PgHero
16
16
  @index_hit_rate = PgHero.index_hit_rate
17
17
  @table_hit_rate = PgHero.table_hit_rate
18
18
  @missing_indexes = PgHero.missing_indexes
19
- @unused_indexes = PgHero.unused_indexes
19
+ @unused_indexes = PgHero.unused_indexes.select { |q| q["index_scans"].to_i == 0 }
20
20
  @good_cache_rate = @table_hit_rate >= 0.99 && @index_hit_rate >= 0.99
21
21
  @query_stats_available = PgHero.query_stats_available?
22
22
  @total_connections = PgHero.total_connections
@@ -47,6 +47,7 @@ module PgHero
47
47
  def queries
48
48
  @title = "Queries"
49
49
  @historical_query_stats_enabled = PgHero.historical_query_stats_enabled?
50
+ @sort = %w[average_time calls].include?(params[:sort]) ? params[:sort] : nil
50
51
 
51
52
  @query_stats =
52
53
  begin
@@ -58,7 +59,7 @@ module PgHero
58
59
  if @historical_query_stats_enabled && !request.xhr?
59
60
  []
60
61
  else
61
- PgHero.query_stats(historical: true, start_at: @start_at, end_at: @end_at)
62
+ PgHero.query_stats(historical: true, start_at: @start_at, end_at: @end_at, sort: @sort)
62
63
  end
63
64
  rescue
64
65
  @error = true
@@ -124,6 +124,10 @@
124
124
  padding: 6px 140px 20px 140px;
125
125
  }
126
126
 
127
+ .queries-table th a {
128
+ color: inherit;
129
+ }
130
+
127
131
  #slider {
128
132
  margin-bottom: 20px;
129
133
  }
@@ -9,14 +9,14 @@
9
9
  <thead>
10
10
  <tr>
11
11
  <th>Top Sources</th>
12
- <th>Connections</th>
12
+ <th style="width: 20%;">Connections</th>
13
13
  </tr>
14
14
  </thead>
15
15
  <tbody>
16
16
  <% PgHero.connection_sources.first(10).each do |source| %>
17
17
  <tr>
18
18
  <td><%= source["source"] %> <span class="text-muted"><%= source["ip"] %></span></td>
19
- <td style="width: 20%;"><%= number_with_delimiter(source["total_connections"]) %></td>
19
+ <td><%= number_with_delimiter(source["total_connections"]) %></td>
20
20
  </tr>
21
21
  <% end %>
22
22
  </tbody>
@@ -1,10 +1,10 @@
1
- <table class="table">
1
+ <table class="table queries-table">
2
2
  <% unless local_assigns[:xhr] %>
3
3
  <thead>
4
4
  <tr>
5
- <th style="width: 33.33%;">Total Time</th>
6
- <th style="width: 33.33%;">Average Time</th>
7
- <th style="width: 33.33%;">Calls</th>
5
+ <th style="width: 33.33%;"><%= local_assigns[:sort_headers] ? link_to("Total Time", {sort: nil}, data: {sort: nil}) : "Total Time" %></th>
6
+ <th style="width: 33.33%;"><%= local_assigns[:sort_headers] ? link_to("Average Time", {sort: "average_time"}, data: {sort: "average_time"}) : "Average Time" %></th>
7
+ <th style="width: 33.33%;"><%= local_assigns[:sort_headers] ? link_to("Calls", {sort: "calls"}, data: {sort: "calls"}) : "Calls" %></th>
8
8
  </tr>
9
9
  </thead>
10
10
  <% end %>
@@ -42,6 +42,8 @@
42
42
  return (num < 10) ? "0" + num : num;
43
43
  }
44
44
 
45
+ var sort = <%= @sort.to_json.html_safe %>;
46
+
45
47
  var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
46
48
 
47
49
  var days = 1;
@@ -49,7 +51,7 @@
49
51
  var sliderStartAt = roundTime(now) - days * 24 * 60 * 60 * 1000;
50
52
  var sliderMax = 24 * 12 * days;
51
53
 
52
- var startAt = <%= @start_at.to_i %> * 1000;
54
+ var startAt = <%= params[:start_at] ? @start_at.to_i * 1000 : "sliderStartAt" %>;
53
55
  var min = (startAt > 0) ? (startAt - sliderStartAt) / (1000 * 60 * 5) : 0;
54
56
 
55
57
  var endAt = <%= @end_at.to_i %> * 1000;
@@ -96,24 +98,40 @@
96
98
  return time.toISOString();
97
99
  }
98
100
 
101
+ function queriesPath(params) {
102
+ var path = "queries";
103
+ if (params.start_at || params.end_at || params.sort) {
104
+ path += "?" + $.param(params);
105
+ }
106
+ return path;
107
+ }
108
+
99
109
  function refreshStats(push) {
100
110
  var values = $slider.val();
101
- var startAt = timeAt(values[0]);
111
+ var startAt = push ? timeAt(values[0]) : new Date(window.startAt);
102
112
  var endAt = timeAt(values[1]);
103
113
 
104
114
  var params = {}
105
- if (startAt > sliderStartAt) {
115
+ if (startAt.getTime() != sliderStartAt) {
106
116
  params.start_at = timeParam(startAt);
107
117
  }
108
118
  if (endAt < now) {
109
119
  params.end_at = timeParam(endAt);
110
120
  }
111
-
112
- var path = "queries";
113
- if (params.start_at || params.end_at) {
114
- path += "?" + $.param(params);
121
+ if (sort) {
122
+ params.sort = sort;
115
123
  }
116
124
 
125
+ var path = queriesPath(params);
126
+
127
+ $(".queries-table th a").each( function () {
128
+ var p = $.extend({}, params, {sort: $(this).data("sort")});
129
+ if (!p.sort) {
130
+ delete p.sort;
131
+ }
132
+ $(this).attr("href", queriesPath(p));
133
+ });
134
+
117
135
  $("#queries").html('<tr><td colspan="3"><p class="queries-info text-muted">...</p></td></tr>').load(path);
118
136
 
119
137
  if (push && history.pushState) {
@@ -125,5 +143,7 @@
125
143
  refreshStats(true);
126
144
  });
127
145
  updateText();
128
- $(refreshStats);
146
+ $( function () {
147
+ refreshStats(false);
148
+ });
129
149
  </script>
@@ -174,16 +174,14 @@ remove_index <%= query["table"].to_sym.inspect %>, name: <%= query["index"].to_s
174
174
  <thead>
175
175
  <tr>
176
176
  <th>Name</th>
177
- <th>Index Size</th>
178
- <th>Index Scans</th>
177
+ <th style="width: 20%;">Index Size</th>
179
178
  </tr>
180
179
  </thead>
181
180
  <tbody>
182
181
  <% @unused_indexes.each do |query| %>
183
182
  <tr>
184
183
  <td><%= query["index"] %><div class="text-muted">on <%= query["table"] %></div></td>
185
- <td style="width: 15%;"><%= query["index_size"] %></td>
186
- <td style="width: 15%;"><%= query["index_scans"] %></td>
184
+ <td><%= query["index_size"] %></td>
187
185
  </tr>
188
186
  <% end %>
189
187
  </tbody>
@@ -5,16 +5,16 @@
5
5
  <thead>
6
6
  <tr>
7
7
  <th>Table</th>
8
- <th>% of Time Index Used</th>
9
- <th>Rows</th>
8
+ <th style="width: 30%;">% of Time Index Used</th>
9
+ <th style="width: 20%;">Rows</th>
10
10
  </tr>
11
11
  </thead>
12
12
  <tbody>
13
13
  <% @index_usage.each do |query| %>
14
14
  <tr>
15
15
  <td><%= query["table"] %></td>
16
- <td style="width: 30%;"><%= query["percent_of_times_index_used"] %></td>
17
- <td style="width: 20%;"><%= number_with_delimiter(query["rows_in_table"]) %></td>
16
+ <td><%= query["percent_of_times_index_used"] %></td>
17
+ <td><%= number_with_delimiter(query["rows_in_table"]) %></td>
18
18
  </tr>
19
19
  <% end %>
20
20
  </tbody>
@@ -13,7 +13,7 @@
13
13
  <% if @error %>
14
14
  <div class="alert alert-danger">Cannot understand start or end time.</div>
15
15
  <% elsif @query_stats.any? || @historical_query_stats_enabled %>
16
- <%= render partial: "queries_table", locals: {queries: @query_stats} %>
16
+ <%= render partial: "queries_table", locals: {queries: @query_stats, sort_headers: true} %>
17
17
  <% else %>
18
18
  <p>Stats are not available yet. Come back soon!</p>
19
19
  <% end %>
data/guides/Heroku.md CHANGED
@@ -21,6 +21,35 @@ For databases outside of Heroku, query stats can be enabled from the dashboard.
21
21
 
22
22
  If you run into issues, [view the guide](Query-Stats.md).
23
23
 
24
+ ## Historical Query Stats
25
+
26
+ To track query stats over time, create a table to store them.
27
+
28
+ ```sql
29
+ CREATE TABLE "pghero_query_stats" ("id" serial primary key, "database" text, "query" text, "total_time" float, "calls" bigint, "captured_at" timestamp)
30
+ CREATE INDEX "index_pghero_query_stats_on_database_and_captured_at" ON "pghero_query_stats" ("database", "captured_at")
31
+ ```
32
+
33
+ This table can be in the current database or another database. If another database, run:
34
+
35
+ ```sh
36
+ heroku config:set PGHERO_STATS_DATABASE_URL=...
37
+ ```
38
+
39
+ Schedule the task below to run every 5 minutes.
40
+
41
+ ```sh
42
+ rake pghero:capture_query_stats
43
+ ```
44
+
45
+ Or with a scheduler like Clockwork, use:
46
+
47
+ ```ruby
48
+ PgHero.capture_query_stats
49
+ ```
50
+
51
+ After this, a time range slider will appear on the Queries tab.
52
+
24
53
  ## System Stats
25
54
 
26
55
  CPU usage is available for Amazon RDS. Add these variables to your environment:
data/guides/Linux.md CHANGED
@@ -5,7 +5,9 @@ Packaged for:
5
5
  - Ubuntu 14.04 (Trusty)
6
6
  - Ubuntu 12.04 (Precise)
7
7
  - Debian 7 (Wheezy)
8
- - CentOS and RHEL 6
8
+ - Debian 8 (Jesse)
9
+ - CentOS / RHEL 6
10
+ - CentOS / RHEL 7
9
11
  - Fedora 20
10
12
  - SUSE Linux Enterprise Server
11
13
 
@@ -40,7 +42,16 @@ sudo apt-get update
40
42
  sudo apt-get install pghero
41
43
  ```
42
44
 
43
- CentOS and RHEL 6
45
+ Debian 8 (Jesse)
46
+
47
+ ```sh
48
+ wget -qO - https://deb.packager.io/key | sudo apt-key add -
49
+ echo "deb https://deb.packager.io/gh/pghero/pghero jessie master" | sudo tee /etc/apt/sources.list.d/pghero.list
50
+ sudo apt-get update
51
+ sudo apt-get install pghero
52
+ ```
53
+
54
+ CentOS / RHEL 6
44
55
 
45
56
  ```sh
46
57
  sudo rpm --import https://rpm.packager.io/key
@@ -51,6 +62,17 @@ enabled=1" | sudo tee /etc/yum.repos.d/pghero.repo
51
62
  sudo yum install pghero
52
63
  ```
53
64
 
65
+ CentOS / RHEL 7
66
+
67
+ ```sh
68
+ sudo rpm --import https://rpm.packager.io/key
69
+ echo "[pghero]
70
+ name=Repository for pghero/pghero application.
71
+ baseurl=https://rpm.packager.io/gh/pghero/pghero/centos7/master
72
+ enabled=1" | sudo tee /etc/yum.repos.d/pghero.repo
73
+ sudo yum install pghero
74
+ ```
75
+
54
76
  Fedora 20
55
77
 
56
78
  ```sh
@@ -126,6 +148,29 @@ sudo service pghero restart
126
148
 
127
149
  Query stats can be enabled from the dashboard. If you run into issues, [view the guide](Query-Stats.md).
128
150
 
151
+ ## Historical Query Stats
152
+
153
+ To track query stats over time, create a table to store them.
154
+
155
+ ```sql
156
+ CREATE TABLE "pghero_query_stats" ("id" serial primary key, "database" text, "query" text, "total_time" float, "calls" bigint, "captured_at" timestamp)
157
+ CREATE INDEX "index_pghero_query_stats_on_database_and_captured_at" ON "pghero_query_stats" ("database", "captured_at")
158
+ ```
159
+
160
+ This table can be in the current database or another database. If another database, run:
161
+
162
+ ```sh
163
+ sudo pghero config:set PGHERO_STATS_DATABASE_URL=...
164
+ ```
165
+
166
+ Schedule the task below to run every 5 minutes.
167
+
168
+ ```sh
169
+ sudo pghero run rake pghero:capture_query_stats
170
+ ```
171
+
172
+ After this, a time range slider will appear on the Queries tab.
173
+
129
174
  ## System Stats
130
175
 
131
176
  CPU usage is available for Amazon RDS. Add these variables to your environment:
data/guides/Rails.md CHANGED
@@ -32,6 +32,7 @@ PgHero.relation_sizes
32
32
  PgHero.index_hit_rate
33
33
  PgHero.table_hit_rate
34
34
  PgHero.total_connections
35
+ PgHero.locks
35
36
  ```
36
37
 
37
38
  Kill queries
@@ -68,6 +69,8 @@ PgHero.replication_lag
68
69
 
69
70
  ## Users
70
71
 
72
+ **Note:** It’s unsafe to pass user input to these commands.
73
+
71
74
  Create a user
72
75
 
73
76
  ```ruby
@@ -89,6 +92,12 @@ Set the password
89
92
  PgHero.create_user("zelda", password: "hyrule")
90
93
  ```
91
94
 
95
+ Grant access to only certain tables
96
+
97
+ ```ruby
98
+ PgHero.create_user("navi", tables: ["triforce"])
99
+ ```
100
+
92
101
  Drop a user
93
102
 
94
103
  ```ruby
@@ -141,7 +150,7 @@ PgHero.capture_query_stats
141
150
 
142
151
  After this, a time range slider will appear on the Queries tab.
143
152
 
144
- By default, historical query stats are stored in your primary database. Change this with:
153
+ By default, query stats are stored in your app’s database. Change this with:
145
154
 
146
155
  ```ruby
147
156
  ENV["PGHERO_STATS_DATABASE_URL"]
data/lib/pghero.rb CHANGED
@@ -325,7 +325,8 @@ module PgHero
325
325
  value["total_percent"] = value["total_minutes"] * 100.0 / (current_query_stats[query]["all_queries_total_minutes"].to_f + historical_query_stats[query]["all_queries_total_minutes"].to_f)
326
326
  query_stats << value
327
327
  end
328
- query_stats.sort_by { |q| -q["total_minutes"] }.first(100)
328
+ sort = options[:sort] || "total_minutes"
329
+ query_stats.sort_by { |q| -q[sort] }.first(100)
329
330
  end
330
331
 
331
332
  def slow_queries(options = {})
@@ -477,13 +478,21 @@ module PgHero
477
478
  "GRANT USAGE ON SCHEMA #{schema} TO #{user}"
478
479
  ]
479
480
  if options[:readonly]
480
- commands << "GRANT SELECT ON ALL TABLES IN SCHEMA #{schema} TO #{user}"
481
- commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT SELECT ON TABLES TO #{user}"
481
+ if options[:tables]
482
+ commands.concat table_grant_commands("SELECT", options[:tables], user)
483
+ else
484
+ commands << "GRANT SELECT ON ALL TABLES IN SCHEMA #{schema} TO #{user}"
485
+ commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT SELECT ON TABLES TO #{user}"
486
+ end
482
487
  else
483
- commands << "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA #{schema} TO #{user}"
484
- commands << "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA #{schema} TO #{user}"
485
- commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT ALL PRIVILEGES ON TABLES TO #{user}"
486
- commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT ALL PRIVILEGES ON SEQUENCES TO #{user}"
488
+ if options[:tables]
489
+ commands.concat table_grant_commands("ALL PRIVILEGES", options[:tables], user)
490
+ else
491
+ commands << "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA #{schema} TO #{user}"
492
+ commands << "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA #{schema} TO #{user}"
493
+ commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT ALL PRIVILEGES ON TABLES TO #{user}"
494
+ commands << "ALTER DEFAULT PRIVILEGES IN SCHEMA #{schema} GRANT ALL PRIVILEGES ON SEQUENCES TO #{user}"
495
+ end
487
496
  end
488
497
 
489
498
  # run commands
@@ -581,10 +590,17 @@ module PgHero
581
590
 
582
591
  private
583
592
 
593
+ def table_grant_commands(privilege, tables, user)
594
+ tables.map do |table|
595
+ "GRANT #{privilege} ON TABLE #{table} TO #{user}"
596
+ end
597
+ end
598
+
584
599
  # http://www.craigkerstiens.com/2013/01/10/more-on-postgres-performance/
585
600
  def current_query_stats(options = {})
586
601
  if query_stats_enabled?
587
602
  limit = options[:limit] || 100
603
+ sort = options[:sort] || "total_minutes"
588
604
  select_all <<-SQL
589
605
  WITH query_stats AS (
590
606
  SELECT
@@ -609,7 +625,7 @@ module PgHero
609
625
  FROM
610
626
  query_stats
611
627
  ORDER BY
612
- total_minutes DESC
628
+ #{quote_table_name(sort)} DESC
613
629
  LIMIT #{limit.to_i}
614
630
  SQL
615
631
  else
@@ -619,6 +635,7 @@ module PgHero
619
635
 
620
636
  def historical_query_stats(options = {})
621
637
  if historical_query_stats_enabled?
638
+ sort = options[:sort] || "total_minutes"
622
639
  stats_connection.select_all squish <<-SQL
623
640
  WITH query_stats AS (
624
641
  SELECT
@@ -645,7 +662,7 @@ module PgHero
645
662
  FROM
646
663
  query_stats
647
664
  ORDER BY
648
- total_minutes DESC
665
+ #{quote_table_name(sort)} DESC
649
666
  LIMIT 100
650
667
  SQL
651
668
  else
@@ -695,5 +712,9 @@ module PgHero
695
712
  def quote(value)
696
713
  connection.quote(value)
697
714
  end
715
+
716
+ def quote_table_name(value)
717
+ connection.quote_table_name(value)
718
+ end
698
719
  end
699
720
  end
@@ -1,3 +1,3 @@
1
1
  module PgHero
2
- VERSION = "1.1.0"
2
+ VERSION = "1.1.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pghero
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-26 00:00:00.000000000 Z
11
+ date: 2015-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord