pg_peek 0.2.0 → 0.2.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 +4 -4
- data/app/assets/javascripts/pg_peek/application.js +25 -1
- data/app/assets/stylesheets/pg_peek/application.css +24 -1
- data/app/controllers/pg_peek/databases_controller.rb +0 -1
- data/app/controllers/pg_peek/pg_stat_statements_controller.rb +17 -6
- data/app/controllers/pg_peek/tables_controller.rb +11 -1
- data/app/helpers/pg_peek/application_helper.rb +3 -2
- data/app/helpers/pg_peek/report_helper.rb +1 -1
- data/app/models/pg_peek/pg_stat_statements.rb +31 -0
- data/app/models/pg_peek/sql_format.rb +74 -0
- data/app/views/layouts/pg_peek/application.html.erb +1 -0
- data/app/views/pg_peek/databases/show.html.erb +0 -56
- data/app/views/pg_peek/endpoints/show.html.erb +1 -0
- data/app/views/pg_peek/jobs/index.html.erb +0 -7
- data/app/views/pg_peek/jobs/show.html.erb +1 -0
- data/app/views/pg_peek/pg_stat_statements/show.html.erb +87 -0
- data/app/views/pg_peek/tables/index.html.erb +18 -0
- data/app/views/pg_peek/tables/show.html.erb +1 -1
- data/config/routes.rb +2 -2
- data/lib/pg_peek/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: abc82aa27c9e7fcebbe27a38cc17ff102ae578afdd11d0be75908874ee5058e8
|
|
4
|
+
data.tar.gz: 7bc328de015861d67c0250ad0373c4639fe1a45c15426c084939d56409b74d5a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 72349c589551ec7dd8b5875f1ccbd579a7bbeaba94588fd29251cec72f041f4610077963efdfdd47683f7741469f9ac0cc7f912453770698e5e68e346ea1a7a3
|
|
7
|
+
data.tar.gz: 8c336b095487533a4862016ee4ffd1d332f3dad19f2134a03716375df017809fa123b1cfa37c286a0d833efeffbc4104c126c2df5a50ae74dce5b7e9eb6ee5d3
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// pg_peek — no framework, no CDN, no import map.
|
|
1
|
+
// pg_peek — no framework, no CDN, no import map. A few small behaviours.
|
|
2
2
|
(function () {
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
@@ -112,6 +112,29 @@
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// Long queries --------------------------------------------------------------
|
|
116
|
+
// Clamped to their first lines by CSS. Measured here, so only a query that
|
|
117
|
+
// is actually longer gets the toggle.
|
|
118
|
+
function initClamp() {
|
|
119
|
+
document.querySelectorAll("[data-clamp]").forEach(function (code) {
|
|
120
|
+
code.classList.add("clamped");
|
|
121
|
+
if (code.scrollHeight <= code.clientHeight) {
|
|
122
|
+
code.classList.remove("clamped");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
var button = document.createElement("button");
|
|
127
|
+
button.type = "button";
|
|
128
|
+
button.className = "clamp-toggle";
|
|
129
|
+
button.textContent = "show all";
|
|
130
|
+
button.addEventListener("click", function () {
|
|
131
|
+
var expanded = code.classList.toggle("expanded");
|
|
132
|
+
button.textContent = expanded ? "show less" : "show all";
|
|
133
|
+
});
|
|
134
|
+
code.insertAdjacentElement("afterend", button);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
115
138
|
// Database switcher -------------------------------------------------------
|
|
116
139
|
function initSwitcher() {
|
|
117
140
|
document.querySelectorAll("[data-switcher]").forEach(function (select) {
|
|
@@ -135,6 +158,7 @@
|
|
|
135
158
|
initTheme();
|
|
136
159
|
initFilter();
|
|
137
160
|
initConfirm();
|
|
161
|
+
initClamp();
|
|
138
162
|
initSwitcher();
|
|
139
163
|
initReload();
|
|
140
164
|
}
|
|
@@ -226,7 +226,30 @@ tbody th + td { text-align: right; }
|
|
|
226
226
|
}
|
|
227
227
|
.qualified .namespace > span { direction: ltr; unicode-bidi: embed; }
|
|
228
228
|
.qualified .leaf { flex-shrink: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
|
|
229
|
-
|
|
229
|
+
/* Formatted one clause per line (SqlFormat); a line longer than the cell
|
|
230
|
+
wraps at spaces and breaks inside a word only when nothing else fits. */
|
|
231
|
+
.sql { display: block; overflow-wrap: anywhere; white-space: pre-wrap; background: none; border: 0; padding: 0; }
|
|
232
|
+
|
|
233
|
+
/* A long query shows its first lines, with a toggle for the rest. The script
|
|
234
|
+
adds the class only where the query is longer, so short ones stay as they
|
|
235
|
+
are, and without it every query shows in full. */
|
|
236
|
+
.sql.clamped:not(.expanded) {
|
|
237
|
+
display: -webkit-box;
|
|
238
|
+
-webkit-box-orient: vertical;
|
|
239
|
+
-webkit-line-clamp: 3;
|
|
240
|
+
line-clamp: 3;
|
|
241
|
+
overflow: hidden;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
button.clamp-toggle {
|
|
245
|
+
background: none;
|
|
246
|
+
border: 0;
|
|
247
|
+
padding: 0;
|
|
248
|
+
color: var(--dim);
|
|
249
|
+
font-size: inherit;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
button.clamp-toggle:hover { color: var(--fg); }
|
|
230
253
|
|
|
231
254
|
/* What issued a query, from its SQLcommenter tags: a link line under the SQL,
|
|
232
255
|
plus every raw tag behind a disclosure. The page-level details/summary
|
|
@@ -21,7 +21,6 @@ class PgPeek::DatabasesController < PgPeek::ApplicationController
|
|
|
21
21
|
def show
|
|
22
22
|
@database = PgPeek::Database.find(params[:id]) or raise ActiveRecord::RecordNotFound, "Database not found"
|
|
23
23
|
@pg_stat_statements = PgPeek::PgStatStatements.new(database: @database)
|
|
24
|
-
@tables = PgPeek::Reports::Tables.new(database: @database)
|
|
25
24
|
@vitals = @database.vitals
|
|
26
25
|
@stats_reset_at = @pg_stat_statements.reset_at
|
|
27
26
|
@sessions = PgPeek::Reports::Sessions.new(database: @database)
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
# Manages the extension itself. What it records is read by QueriesController,
|
|
2
2
|
# EndpointsController and JobsController.
|
|
3
3
|
class PgPeek::PgStatStatementsController < PgPeek::ApplicationController
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
before_action :set_database
|
|
5
|
+
|
|
6
|
+
def show
|
|
7
|
+
return render "pg_peek/pg_stat_statements/not_preloaded" unless @pg_stat_statements.usable?
|
|
8
|
+
|
|
9
|
+
@connection_summary = @database.connection_summary
|
|
10
|
+
end
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
def reset
|
|
13
|
+
if @pg_stat_statements.resettable?
|
|
14
|
+
@pg_stat_statements.reset!
|
|
10
15
|
flash[:notice] = "pg_stat_statements has been reset."
|
|
11
16
|
else
|
|
12
17
|
flash[:alert] = "This role may not reset pg_stat_statements; the statistics were left as they are."
|
|
13
18
|
end
|
|
14
19
|
|
|
15
|
-
redirect_to
|
|
20
|
+
redirect_to database_pg_stat_statements_path(@database)
|
|
16
21
|
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
def set_database
|
|
25
|
+
@database = PgPeek::Database.find(params[:database_id]) or raise ActiveRecord::RecordNotFound, "Database not found"
|
|
26
|
+
@pg_stat_statements = PgPeek::PgStatStatements.new(database: @database)
|
|
27
|
+
end
|
|
17
28
|
end
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
class PgPeek::TablesController < PgPeek::ApplicationController
|
|
2
|
+
before_action :set_database
|
|
3
|
+
|
|
4
|
+
def index
|
|
5
|
+
@tables = PgPeek::Reports::Tables.new(database: @database)
|
|
6
|
+
end
|
|
7
|
+
|
|
2
8
|
def show
|
|
3
|
-
@database = PgPeek::Database.find(params[:database_id]) or raise ActiveRecord::RecordNotFound, "Database not found"
|
|
4
9
|
@table = @database.find_table(params[:name]) or raise ActiveRecord::RecordNotFound, "Table '#{params[:name]}' not found"
|
|
5
10
|
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
def set_database
|
|
14
|
+
@database = PgPeek::Database.find(params[:database_id]) or raise ActiveRecord::RecordNotFound, "Database not found"
|
|
15
|
+
end
|
|
6
16
|
end
|
|
@@ -102,14 +102,15 @@ module PgPeek::ApplicationHelper
|
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
# Every pg_stat_statements figure is cumulative since the last reset. Each
|
|
105
|
-
# page says so in its header and links to the
|
|
105
|
+
# page says so in its header and links to the extension's page, which
|
|
106
|
+
# resets it.
|
|
106
107
|
# Within the first hour the figures cover too little to go by, and the link
|
|
107
108
|
# is marked provisional.
|
|
108
109
|
def stats_since_link(database, reset_at)
|
|
109
110
|
provisional = reset_at && reset_at > 1.hour.ago
|
|
110
111
|
|
|
111
112
|
link_to "stats since #{format_time_ago(reset_at)}",
|
|
112
|
-
|
|
113
|
+
database_pg_stat_statements_path(database),
|
|
113
114
|
class: ("provisional" if provisional),
|
|
114
115
|
title: provisional ? "figures may not be representative yet" : "figures are cumulative since the last pg_stat_statements reset"
|
|
115
116
|
end
|
|
@@ -50,7 +50,7 @@ module PgPeek::ReportHelper
|
|
|
50
50
|
# link into the page that already aggregates this exact query across every
|
|
51
51
|
# call site) and every raw tag behind a disclosure, for the rest.
|
|
52
52
|
def sql_cell(report, sql)
|
|
53
|
-
code = tag.code(strip_sqlcommenter(sql)
|
|
53
|
+
code = tag.code(PgPeek::SqlFormat.format(strip_sqlcommenter(sql)), class: "sql", data: { clamp: true })
|
|
54
54
|
tags = PgPeek::SqlComment.tags(sql)
|
|
55
55
|
return code if tags.empty?
|
|
56
56
|
|
|
@@ -93,6 +93,37 @@ class PgPeek::PgStatStatements
|
|
|
93
93
|
Time.zone.parse(value.to_s) if value.present?
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
+
# How many statements the server dropped since the last reset because the
|
|
97
|
+
# table was full (pg_stat_statements.max). Above zero, every total is an
|
|
98
|
+
# undercount: whatever was dropped took its calls and time with it.
|
|
99
|
+
def dealloc
|
|
100
|
+
return unless usable?
|
|
101
|
+
|
|
102
|
+
connection.select_value(PgPeek::QueryLoader.mark(<<~SQL)).to_i
|
|
103
|
+
SELECT dealloc FROM pg_stat_statements_info
|
|
104
|
+
SQL
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The table is shared by every database on the server, so max is measured
|
|
108
|
+
# against all of them, not just this one.
|
|
109
|
+
def server_statement_count
|
|
110
|
+
return unless usable?
|
|
111
|
+
|
|
112
|
+
connection.select_value(PgPeek::QueryLoader.mark(<<~SQL)).to_i
|
|
113
|
+
SELECT count(*) FROM pg_stat_statements
|
|
114
|
+
SQL
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# pg_stat_statements.* settings, by name without the prefix.
|
|
118
|
+
def settings
|
|
119
|
+
return {} unless usable?
|
|
120
|
+
|
|
121
|
+
connection.select_rows(PgPeek::QueryLoader.mark(<<~SQL)).to_h
|
|
122
|
+
SELECT substr(name, length('pg_stat_statements.') + 1), setting
|
|
123
|
+
FROM pg_settings WHERE name LIKE 'pg_stat_statements.%' ORDER BY name
|
|
124
|
+
SQL
|
|
125
|
+
end
|
|
126
|
+
|
|
96
127
|
def outliers
|
|
97
128
|
return unless usable?
|
|
98
129
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Lays a query out one clause per line, so the first lines of a long query say
|
|
2
|
+
# what it reads and from where, and its joins and conditions read as a list.
|
|
3
|
+
# Only line breaks and spacing change. Breaks go in at the top level only --
|
|
4
|
+
# never inside parentheses, quotes or a CASE -- so subqueries and function
|
|
5
|
+
# calls stay on one line, and a query this does not understand comes out with
|
|
6
|
+
# an odd break at worst.
|
|
7
|
+
class PgPeek::SqlFormat
|
|
8
|
+
TOKEN = /'(?:[^']|'')*'|"(?:[^"]|"")*"|[(),;]|[^\s'"(),;]+|\s+/
|
|
9
|
+
CLAUSES = %w[SELECT FROM WHERE GROUP HAVING WINDOW ORDER LIMIT OFFSET FETCH FOR
|
|
10
|
+
UNION INTERSECT EXCEPT RETURNING SET VALUES].freeze
|
|
11
|
+
JOIN_PREFIXES = %w[INNER LEFT RIGHT FULL CROSS NATURAL].freeze
|
|
12
|
+
CONDITIONS = %w[WHERE HAVING].freeze
|
|
13
|
+
|
|
14
|
+
def self.format(sql)
|
|
15
|
+
tokens = []
|
|
16
|
+
space = false
|
|
17
|
+
sql.to_s.strip.scan(TOKEN) do |token|
|
|
18
|
+
if token.match?(/\A\s/)
|
|
19
|
+
space = true
|
|
20
|
+
else
|
|
21
|
+
tokens << [ token, space ]
|
|
22
|
+
space = false
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
out = +""
|
|
27
|
+
depth = case_depth = 0
|
|
28
|
+
clause = nil
|
|
29
|
+
between = false
|
|
30
|
+
|
|
31
|
+
tokens.each_with_index do |(token, space_before), index|
|
|
32
|
+
word = token.upcase
|
|
33
|
+
top = index.positive? && depth.zero? && case_depth.zero?
|
|
34
|
+
previous = tokens[index - 1]&.first&.upcase if index.positive?
|
|
35
|
+
following = tokens[index + 1]&.first&.upcase
|
|
36
|
+
|
|
37
|
+
line_break = nil
|
|
38
|
+
if top
|
|
39
|
+
if CLAUSES.include?(word)
|
|
40
|
+
line_break = "\n"
|
|
41
|
+
clause = word
|
|
42
|
+
elsif JOIN_PREFIXES.include?(word) && %w[JOIN OUTER].include?(following)
|
|
43
|
+
line_break = "\n"
|
|
44
|
+
clause = "JOIN"
|
|
45
|
+
elsif word == "JOIN" && !(JOIN_PREFIXES + %w[OUTER]).include?(previous)
|
|
46
|
+
line_break = "\n"
|
|
47
|
+
clause = "JOIN"
|
|
48
|
+
elsif word == "BETWEEN"
|
|
49
|
+
between = true
|
|
50
|
+
elsif word == "AND" && between
|
|
51
|
+
between = false
|
|
52
|
+
elsif %w[AND OR].include?(word) && CONDITIONS.include?(clause)
|
|
53
|
+
line_break = "\n "
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if line_break
|
|
58
|
+
out << line_break
|
|
59
|
+
elsif space_before && !out.empty?
|
|
60
|
+
out << " "
|
|
61
|
+
end
|
|
62
|
+
out << token
|
|
63
|
+
|
|
64
|
+
case word
|
|
65
|
+
when "(" then depth += 1
|
|
66
|
+
when ")" then depth -= 1 if depth.positive?
|
|
67
|
+
when "CASE" then case_depth += 1
|
|
68
|
+
when "END" then case_depth -= 1 if case_depth.positive?
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
out
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
<li><%= link_to "queries", database_queries_path(@database) %></li>
|
|
27
27
|
<li><%= link_to "endpoints", database_endpoints_path(@database) %></li>
|
|
28
28
|
<li><%= link_to "jobs", database_jobs_path(@database) %></li>
|
|
29
|
+
<li><%= link_to "tables", database_tables_path(@database) %></li>
|
|
29
30
|
<li><%= link_to "indexes", database_indexes_path(@database) %></li>
|
|
30
31
|
<li><%= link_to "activity", database_activity_path(@database) %></li>
|
|
31
32
|
<li class="current-database">
|
|
@@ -107,64 +107,8 @@ end</code></pre>
|
|
|
107
107
|
limit: PgPeek::DatabasesController::OVERVIEW_ROWS %>
|
|
108
108
|
<% end %>
|
|
109
109
|
</section>
|
|
110
|
-
|
|
111
|
-
<section class="wide" id="tables" data-filter>
|
|
112
|
-
<h2>tables (<%= @tables.rows.size %>)</h2>
|
|
113
|
-
<input type="search"
|
|
114
|
-
placeholder="filter tables… (press / to focus)"
|
|
115
|
-
data-filter-input
|
|
116
|
-
data-filter-noun="table"
|
|
117
|
-
autocomplete="off">
|
|
118
|
-
<p class="dim" data-filter-count></p>
|
|
119
|
-
|
|
120
|
-
<%= render "pg_peek/reports/report",
|
|
121
|
-
report: @tables,
|
|
122
|
-
link_column: :name,
|
|
123
|
-
link: ->(row) { database_table_path(@database, row["name"]) } %>
|
|
124
|
-
|
|
125
|
-
<p class="dim" data-filter-empty hidden>No tables match that filter.</p>
|
|
126
|
-
</section>
|
|
127
110
|
</div>
|
|
128
111
|
|
|
129
|
-
<% if @pg_stat_statements.usable? %>
|
|
130
|
-
<section id="pg_stat_statements">
|
|
131
|
-
<h2>pg_stat_statements <%= @pg_stat_statements.installed_version %></h2>
|
|
132
|
-
<p class="dim">
|
|
133
|
-
Queries, endpoints and jobs are cumulative since
|
|
134
|
-
<%= @stats_reset_at.strftime("%Y-%m-%d %H:%M:%S %Z") %> (<%= format_time_ago(@stats_reset_at) %>),
|
|
135
|
-
<%= pluralize(number_with_delimiter(@pg_stat_statements.statement_count), "statement") %> tracked.
|
|
136
|
-
A reset starts a fresh window: exact counts and attribution, at the
|
|
137
|
-
price of the history so far.
|
|
138
|
-
<% peers = @database.server_peers %>
|
|
139
|
-
<% if peers.any? %>
|
|
140
|
-
It also clears <%= peers.map(&:name).to_sentence %>, which
|
|
141
|
-
<%= peers.one? ? "shares" : "share" %> this server: PostgreSQL keeps
|
|
142
|
-
one reset time for all of them.
|
|
143
|
-
<% end %>
|
|
144
|
-
</p>
|
|
145
|
-
<%= button_to "reset statistics",
|
|
146
|
-
reset_database_pg_stat_statements_path(@database),
|
|
147
|
-
method: :delete,
|
|
148
|
-
class: "danger",
|
|
149
|
-
disabled: !@pg_stat_statements.resettable?,
|
|
150
|
-
form_class: "inline",
|
|
151
|
-
form: { data: { confirm: "Reset pg_stat_statements? This clears statement statistics for every database on this server." } } %>
|
|
152
|
-
<% unless @pg_stat_statements.resettable? %>
|
|
153
|
-
<p class="dim">
|
|
154
|
-
<%= @connection_summary["username"] %> may not reset the statistics: the extension
|
|
155
|
-
allows it only to superusers and roles granted it. To allow it:
|
|
156
|
-
</p>
|
|
157
|
-
<pre><code>GRANT EXECUTE ON FUNCTION pg_stat_statements_reset TO <%= @connection_summary["username"] %>;</code></pre>
|
|
158
|
-
<% end %>
|
|
159
|
-
<% if @pg_stat_statements.outdated? %>
|
|
160
|
-
<p class="dim">
|
|
161
|
-
Version <%= @pg_stat_statements.default_version %> is available:
|
|
162
|
-
<code>ALTER EXTENSION pg_stat_statements UPDATE;</code>
|
|
163
|
-
</p>
|
|
164
|
-
<% end %>
|
|
165
|
-
</section>
|
|
166
|
-
<% end %>
|
|
167
|
-
|
|
168
112
|
<details>
|
|
169
113
|
<summary>database details</summary>
|
|
170
114
|
<pre>name: <%= @database.name %>
|
|
@@ -24,13 +24,6 @@
|
|
|
24
24
|
collecting, so this page is working — there is simply nothing in it
|
|
25
25
|
yet. Jobs appear as they execute their first database query.
|
|
26
26
|
</p>
|
|
27
|
-
<% if @stats_reset_at %>
|
|
28
|
-
<p>
|
|
29
|
-
Statistics were last reset <strong><%= format_time_ago(@stats_reset_at) %></strong>
|
|
30
|
-
(<%= @stats_reset_at %>), so anything that ran before then is gone.
|
|
31
|
-
Restarting PostgreSQL clears them too.
|
|
32
|
-
</p>
|
|
33
|
-
<% end %>
|
|
34
27
|
</article>
|
|
35
28
|
<% end %>
|
|
36
29
|
<% else %>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<% reset_at = @pg_stat_statements.reset_at %>
|
|
2
|
+
<% settings = @pg_stat_statements.settings %>
|
|
3
|
+
<% dealloc = @pg_stat_statements.dealloc %>
|
|
4
|
+
|
|
5
|
+
<div class="toolbar">
|
|
6
|
+
<h1>pg_stat_statements <small class="dim"><%= @pg_stat_statements.installed_version %></small></h1>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<section>
|
|
10
|
+
<h2>collection</h2>
|
|
11
|
+
<table>
|
|
12
|
+
<tbody>
|
|
13
|
+
<tr>
|
|
14
|
+
<th>stats since</th>
|
|
15
|
+
<td><%= reset_at.strftime("%Y-%m-%d %H:%M:%S %Z") %> (<%= format_time_ago(reset_at) %>)</td>
|
|
16
|
+
</tr>
|
|
17
|
+
<tr>
|
|
18
|
+
<th>statements tracked</th>
|
|
19
|
+
<td>
|
|
20
|
+
<%= number_with_delimiter(@pg_stat_statements.server_statement_count) %>
|
|
21
|
+
<% if settings["max"] %>of <%= number_with_delimiter(settings["max"].to_i) %><% end %>
|
|
22
|
+
<span class="dim">(this database: <%= number_with_delimiter(@pg_stat_statements.statement_count) %>)</span>
|
|
23
|
+
</td>
|
|
24
|
+
</tr>
|
|
25
|
+
<tr>
|
|
26
|
+
<th>dropped when full</th>
|
|
27
|
+
<td><%= number_with_delimiter(dealloc) %></td>
|
|
28
|
+
</tr>
|
|
29
|
+
</tbody>
|
|
30
|
+
</table>
|
|
31
|
+
<% if dealloc.positive? %>
|
|
32
|
+
<p class="dim">
|
|
33
|
+
The server dropped statements to make room since the last reset, so every
|
|
34
|
+
total here undercounts. Raise <code>pg_stat_statements.max</code>
|
|
35
|
+
(needs a server restart) or reset more often.
|
|
36
|
+
</p>
|
|
37
|
+
<% end %>
|
|
38
|
+
</section>
|
|
39
|
+
|
|
40
|
+
<section>
|
|
41
|
+
<h2>settings</h2>
|
|
42
|
+
<table>
|
|
43
|
+
<tbody>
|
|
44
|
+
<% settings.each do |name, value| %>
|
|
45
|
+
<tr>
|
|
46
|
+
<th><code>pg_stat_statements.<%= name %></code></th>
|
|
47
|
+
<td><%= value %></td>
|
|
48
|
+
</tr>
|
|
49
|
+
<% end %>
|
|
50
|
+
</tbody>
|
|
51
|
+
</table>
|
|
52
|
+
<% if @pg_stat_statements.outdated? %>
|
|
53
|
+
<p class="dim">
|
|
54
|
+
Version <%= @pg_stat_statements.default_version %> is available:
|
|
55
|
+
<code>ALTER EXTENSION pg_stat_statements UPDATE;</code>
|
|
56
|
+
</p>
|
|
57
|
+
<% end %>
|
|
58
|
+
</section>
|
|
59
|
+
|
|
60
|
+
<section id="reset">
|
|
61
|
+
<h2>reset</h2>
|
|
62
|
+
<p class="dim">
|
|
63
|
+
Queries, endpoints and jobs are cumulative since the last reset. A reset
|
|
64
|
+
starts a fresh window: exact counts and attribution, at the price of the
|
|
65
|
+
history so far. Restarting PostgreSQL clears them too.
|
|
66
|
+
<% peers = @database.server_peers %>
|
|
67
|
+
<% if peers.any? %>
|
|
68
|
+
It also clears <%= peers.map(&:name).to_sentence %>, which
|
|
69
|
+
<%= peers.one? ? "shares" : "share" %> this server: PostgreSQL keeps
|
|
70
|
+
one reset time for all of them.
|
|
71
|
+
<% end %>
|
|
72
|
+
</p>
|
|
73
|
+
<%= button_to "reset statistics",
|
|
74
|
+
reset_database_pg_stat_statements_path(@database),
|
|
75
|
+
method: :delete,
|
|
76
|
+
class: "danger",
|
|
77
|
+
disabled: !@pg_stat_statements.resettable?,
|
|
78
|
+
form_class: "inline",
|
|
79
|
+
form: { data: { confirm: "Reset pg_stat_statements? This clears statement statistics for every database on this server." } } %>
|
|
80
|
+
<% unless @pg_stat_statements.resettable? %>
|
|
81
|
+
<p class="dim">
|
|
82
|
+
<%= @connection_summary["username"] %> may not reset the statistics: the extension
|
|
83
|
+
allows it only to superusers and roles granted it. To allow it:
|
|
84
|
+
</p>
|
|
85
|
+
<pre><code>GRANT EXECUTE ON FUNCTION pg_stat_statements_reset TO <%= @connection_summary["username"] %>;</code></pre>
|
|
86
|
+
<% end %>
|
|
87
|
+
</section>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<div class="toolbar">
|
|
2
|
+
<h1>tables</h1>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<input type="search"
|
|
6
|
+
placeholder="filter tables… (press / to focus)"
|
|
7
|
+
data-filter-input
|
|
8
|
+
data-filter-noun="table"
|
|
9
|
+
autofocus
|
|
10
|
+
autocomplete="off">
|
|
11
|
+
<p class="dim" data-filter-count></p>
|
|
12
|
+
|
|
13
|
+
<%= render "pg_peek/reports/report",
|
|
14
|
+
report: @tables,
|
|
15
|
+
link_column: :name,
|
|
16
|
+
link: ->(row) { database_table_path(@database, row["name"]) } %>
|
|
17
|
+
|
|
18
|
+
<p class="dim" data-filter-empty hidden>No tables match that filter.</p>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="toolbar">
|
|
2
2
|
<h1><code class="name"><%= @table.name %></code></h1>
|
|
3
|
-
<%= link_to "← tables",
|
|
3
|
+
<%= link_to "← tables", database_tables_path(@database) %>
|
|
4
4
|
</div>
|
|
5
5
|
|
|
6
6
|
<% seconds_elapsed = @table.seconds_since_reset %>
|
data/config/routes.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PgPeek::Engine.routes.draw do
|
|
2
2
|
resources :databases, only: [ :index, :show ] do
|
|
3
3
|
resources :queries, only: [ :index ]
|
|
4
|
-
resources :tables, only: [ :show ], param: :name
|
|
4
|
+
resources :tables, only: [ :index, :show ], param: :name
|
|
5
5
|
resources :endpoints, only: [ :index ]
|
|
6
6
|
# admin/posts#index lives at endpoints/admin/posts/index: the slashes of a
|
|
7
7
|
# namespace stay as they are, and no # needs encoding.
|
|
@@ -10,7 +10,7 @@ PgPeek::Engine.routes.draw do
|
|
|
10
10
|
resource :activity, only: [ :show ], controller: "activity"
|
|
11
11
|
resources :jobs, only: [ :index, :show ], param: :job_class
|
|
12
12
|
|
|
13
|
-
resource :pg_stat_statements, only: [] do
|
|
13
|
+
resource :pg_stat_statements, only: [ :show ] do
|
|
14
14
|
delete :reset
|
|
15
15
|
end
|
|
16
16
|
end
|
data/lib/pg_peek/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pg_peek
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tomasz Mazur
|
|
@@ -69,6 +69,7 @@ files:
|
|
|
69
69
|
- app/models/pg_peek/database.rb
|
|
70
70
|
- app/models/pg_peek/pg_stat_statements.rb
|
|
71
71
|
- app/models/pg_peek/sql_comment.rb
|
|
72
|
+
- app/models/pg_peek/sql_format.rb
|
|
72
73
|
- app/models/pg_peek/table.rb
|
|
73
74
|
- app/queries/pg_peek/pg14/activity/sessions.sql
|
|
74
75
|
- app/queries/pg_peek/pg14/activity/summary.sql
|
|
@@ -159,8 +160,10 @@ files:
|
|
|
159
160
|
- app/views/pg_peek/jobs/show.html.erb
|
|
160
161
|
- app/views/pg_peek/pg_stat_statements/_preload_instructions.html.erb
|
|
161
162
|
- app/views/pg_peek/pg_stat_statements/not_preloaded.html.erb
|
|
163
|
+
- app/views/pg_peek/pg_stat_statements/show.html.erb
|
|
162
164
|
- app/views/pg_peek/queries/index.html.erb
|
|
163
165
|
- app/views/pg_peek/reports/_report.html.erb
|
|
166
|
+
- app/views/pg_peek/tables/index.html.erb
|
|
164
167
|
- app/views/pg_peek/tables/show.html.erb
|
|
165
168
|
- config/routes.rb
|
|
166
169
|
- lib/generators/pg_peek/install/install_generator.rb
|
|
@@ -194,7 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
194
197
|
- !ruby/object:Gem::Version
|
|
195
198
|
version: '0'
|
|
196
199
|
requirements: []
|
|
197
|
-
rubygems_version:
|
|
200
|
+
rubygems_version: 4.0.21
|
|
198
201
|
specification_version: 4
|
|
199
202
|
summary: PostgreSQL query performance monitoring for Rails applications
|
|
200
203
|
test_files: []
|