pg_monitor 0.1.1 → 0.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c4c7d58e46ed8ee3d95495bb02fc8476bbc38b6f58e00f372b685b69691f37c
4
- data.tar.gz: f59c2029bb6a99e861c65702ab590fdc6980e9b46eca30186629165da1b43e94
3
+ metadata.gz: b6771bf4e358737710bbdfc1618a2c249a1c62f20dde4b245bd12a55156ef65f
4
+ data.tar.gz: a80e060c2b717966d0b873c144e56b12bdb97f27f47953bdd237e6263e151495
5
5
  SHA512:
6
- metadata.gz: e5480cd130a99bd63468032c7cfbb45ce012b89fabbaba67495eaa418a723b08eec1b0ee67765447b84bad37211f1185d78b10244fb5f78bd46d05baaa937f34
7
- data.tar.gz: d2a72a77da40a346dee9edc1e573c9c668dce9a291e7edb043a2e4c13e05db1518aa3dc70ec65b0868a8e20bb6f12f4b0d0a700b5b60f86b78ce86cf6f712f8c
6
+ metadata.gz: acfd32102a43294fe20a7971a608f19227a34790caab53bc2b54b8f36c4cc08f31cee5436ca26f1a27b2e493309e18aca73c7a6031e6967bb403229b11b9ceac
7
+ data.tar.gz: 3fe3429f7c64b0503863acba9d590cc69935bf3e39ff4bf49947662931d95e6258b3fda56117fe77d28bbbd965470c1754fabf87f08e8f6b224a803d422154e6
data/README.md CHANGED
@@ -7,8 +7,8 @@ It would integrate seamlessly with a Rails application in the future, and would
7
7
 
8
8
  - View index usage statistics (last used, scan count, etc.)
9
9
  - Identify slow queries using `pg_stat_statements`
10
- - Works with the existing Rails database configuration
11
- - [TBD] Provides a simple HTML interface for monitoring
10
+ - Works with existing Rails database configuration, if present
11
+ - Provides a simple HTML interface for monitoring
12
12
 
13
13
  ## Installation
14
14
 
@@ -59,17 +59,19 @@ Execute the above SQL query by connecting to your database using `psql` or any o
59
59
 
60
60
  ## Usage
61
61
 
62
- Mount the engine in `config/routes.rb`:
62
+ To run the PgMonitor, follow these steps:
63
63
 
64
- ```ruby
65
- mount PgMonitor::Engine, at: '/pg_monitor'
66
- ```
64
+ 1. Ensure you have all the dependencies installed:
65
+ ```sh
66
+ bundle install
67
+ ```
67
68
 
68
- Then, navigate to:
69
+ 2. Run the server using the Rake task:
70
+ ```sh
71
+ rake server
72
+ ```
69
73
 
70
- ```
71
- http://localhost:3000/pg_monitor
72
- ```
74
+ 3. The application should now be running. You can access it in your web browser at `http://localhost:4567`.
73
75
 
74
76
  ### Programmatic Access
75
77
 
@@ -83,6 +85,16 @@ PgMonitor::IndexUsage.fetch
83
85
  PgMonitor::SlowQueries.fetch(limit: 10)
84
86
  ```
85
87
 
88
+ ### **User Table Stats**
89
+ ```ruby
90
+ PgMonitor::UserTables.fetch
91
+ ```
92
+
93
+ ### **Pg Locks**
94
+ ```ruby
95
+ PgMonitor::PgLocks.fetch
96
+ ```
97
+
86
98
  ## Running Tests
87
99
 
88
100
  ### **1. Start PostgreSQL in Docker**
data/Rakefile CHANGED
@@ -8,3 +8,10 @@ RSpec::Core::RakeTask.new(:spec)
8
8
  require "standard/rake"
9
9
 
10
10
  task default: %i[spec standard]
11
+
12
+ require_relative "lib/pg_monitor/web_app"
13
+
14
+ desc "Start the PgMonitor web application"
15
+ task :server do
16
+ PgMonitorApp.run!
17
+ end
@@ -7,13 +7,15 @@ module PgMonitor
7
7
  def fetch
8
8
  query = <<~SQL
9
9
  SELECT
10
- pg_class.relname AS table_name,
10
+ relname AS table_name,
11
11
  indexrelname AS index_name,
12
12
  idx_scan AS index_scan_count,
13
13
  idx_tup_read AS tuples_read,
14
- idx_tup_fetch AS tuples_fetched
15
- FROM pg_stat_all_indexes
16
- JOIN pg_class ON pg_stat_all_indexes.relid = pg_class.oid
14
+ idx_tup_fetch AS tuples_fetched,
15
+ last_idx_scan AS last_index_scan,
16
+ pg_size_pretty(pg_relation_size(indexrelid)) AS index_size,
17
+ pg_size_pretty(pg_total_relation_size(indexrelid)) AS total_index_size
18
+ FROM pg_stat_user_indexes
17
19
  ORDER BY idx_scan DESC;
18
20
  SQL
19
21
 
@@ -0,0 +1,33 @@
1
+ require_relative "db_connection"
2
+
3
+ module PgMonitor
4
+ module PGLocks
5
+ module_function
6
+
7
+ def fetch
8
+ query = <<~SQL
9
+ SELECT
10
+ locktype,
11
+ database,
12
+ relation,
13
+ page,
14
+ tuple,
15
+ virtualxid,
16
+ transactionid,
17
+ classid,
18
+ objid,
19
+ objsubid,
20
+ virtualtransaction,
21
+ pid,
22
+ mode,
23
+ granted,
24
+ fastpath,
25
+ waitstart
26
+ FROM pg_locks;
27
+ SQL
28
+
29
+ result = DBConnection.connection.exec(query)
30
+ result.map { |row| row }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,38 @@
1
+ require_relative "db_connection"
2
+
3
+ module PgMonitor
4
+ module UserTables
5
+ module_function
6
+
7
+ def fetch
8
+ query = <<~SQL
9
+ SELECT
10
+ relname AS table_name,
11
+ seq_scan,
12
+ last_seq_scan,
13
+ seq_tup_read,
14
+ idx_scan,
15
+ last_idx_scan,
16
+ idx_tup_fetch,
17
+ n_tup_ins,
18
+ n_tup_upd,
19
+ n_tup_del,
20
+ n_live_tup,
21
+ n_dead_tup,
22
+ last_vacuum,
23
+ last_autovacuum,
24
+ last_analyze,
25
+ last_autoanalyze,
26
+ vacuum_count,
27
+ autovacuum_count,
28
+ analyze_count,
29
+ autoanalyze_count
30
+ FROM pg_stat_user_tables
31
+ ORDER BY relname;
32
+ SQL
33
+
34
+ result = DBConnection.connection.exec(query)
35
+ result.map { |row| row }
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgMonitor
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
@@ -0,0 +1,56 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>PgMonitor</title>
5
+ <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
6
+ <script>
7
+ function showTab(tabId) {
8
+ document.querySelectorAll('.tab').forEach(tab => {
9
+ tab.classList.remove('active');
10
+ });
11
+ document.getElementById(tabId).classList.add('active');
12
+ }
13
+ </script>
14
+ <style>
15
+ .tab {
16
+ display: none;
17
+ }
18
+ .tab.active {
19
+ display: block;
20
+ }
21
+ body {
22
+ padding: 10px;
23
+ }
24
+ </style>
25
+ </head>
26
+ <body>
27
+ <div class="container">
28
+ <div class="btn-group tab-buttons" role="group">
29
+ <button type="button" class="btn btn-primary" onclick="showTab('index-usage')">Index Usage</button>
30
+ <button type="button" class="btn btn-secondary" onclick="showTab('slow-queries')">Slow Queries</button>
31
+ <button type="button" class="btn btn-secondary" onclick="showTab('user-tables')">User Tables</button>
32
+ <button type="button" class="btn btn-secondary" onclick="showTab('locks')">Locks</button>
33
+ </div>
34
+
35
+ <div id="index-usage" class="tab active">
36
+ <h1>Index Usage Statistics</h1>
37
+ <%= erb :index_usage_table, layout: false, locals: { index_usage: @index_usage } %>
38
+ </div>
39
+
40
+ <div id="slow-queries" class="tab">
41
+ <h1>Slow Queries</h1>
42
+ <%= erb :slow_queries_table, layout: false, locals: { slow_queries: @slow_queries } %>
43
+ </div>
44
+
45
+ <div id="user-tables" class="tab">
46
+ <h1>User Tables Statistics</h1>
47
+ <%= erb :user_tables_table, layout: false, locals: { user_tables: @user_tables } %>
48
+ </div>
49
+
50
+ <div id="locks" class="tab">
51
+ <h1>Locks</h1>
52
+ <%= erb :locks_table, layout: false, locals: { locks: @locks } %>
53
+ </div>
54
+ </div>
55
+ </body>
56
+ </html>
@@ -0,0 +1,28 @@
1
+ <table class="table table-striped">
2
+ <thead class="thead-light">
3
+ <tr>
4
+ <th>Table Name</th>
5
+ <th>Index Name</th>
6
+ <th>Index Scan Count</th>
7
+ <th>Tuples Read</th>
8
+ <th>Tuples Fetched</th>
9
+ <th>Last Index Scan</th>
10
+ <th>Index Size</th>
11
+ <th>Total Index Size</th>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+ <% index_usage.each do |row| %>
16
+ <tr>
17
+ <td><%= row["table_name"] %></td>
18
+ <td><%= row["index_name"] %></td>
19
+ <td><%= row["index_scan_count"] %></td>
20
+ <td><%= row["tuples_read"] %></td>
21
+ <td><%= row["tuples_fetched"] %></td>
22
+ <td><%= row["last_index_scan"] %></td>
23
+ <td><%= row["index_size"] %></td>
24
+ <td><%= row["total_index_size"] %></td>
25
+ </tr>
26
+ <% end %>
27
+ </tbody>
28
+ </table>
@@ -0,0 +1,44 @@
1
+ <table class="table table-striped">
2
+ <thead class="thead-light">
3
+ <tr>
4
+ <th>Lock Type</th>
5
+ <th>Database</th>
6
+ <th>Relation</th>
7
+ <th>Page</th>
8
+ <th>Tuple</th>
9
+ <th>Virtual XID</th>
10
+ <th>Transaction ID</th>
11
+ <th>Class ID</th>
12
+ <th>Object ID</th>
13
+ <th>Object Sub ID</th>
14
+ <th>Virtual Transaction</th>
15
+ <th>PID</th>
16
+ <th>Mode</th>
17
+ <th>Granted</th>
18
+ <th>Fast Path</th>
19
+ <th>Wait Start</th>
20
+ </tr>
21
+ </thead>
22
+ <tbody>
23
+ <% @locks.each do |row| %>
24
+ <tr>
25
+ <td><%= row["locktype"] %></td>
26
+ <td><%= row["database"] %></td>
27
+ <td><%= row["relation"] %></td>
28
+ <td><%= row["page"] %></td>
29
+ <td><%= row["tuple"] %></td>
30
+ <td><%= row["virtualxid"] %></td>
31
+ <td><%= row["transactionid"] %></td>
32
+ <td><%= row["classid"] %></td>
33
+ <td><%= row["objid"] %></td>
34
+ <td><%= row["objsubid"] %></td>
35
+ <td><%= row["virtualtransaction"] %></td>
36
+ <td><%= row["pid"] %></td>
37
+ <td><%= row["mode"] %></td>
38
+ <td><%= row["granted"] %></td>
39
+ <td><%= row["fastpath"] %></td>
40
+ <td><%= row["waitstart"] %></td>
41
+ </tr>
42
+ <% end %>
43
+ </tbody>
44
+ </table>
@@ -0,0 +1,35 @@
1
+ <table class="table table-striped">
2
+ <thead class="thead-light">
3
+ <tr>
4
+ <th>Query</th>
5
+ <th>Calls</th>
6
+ <th>Total Time</th>
7
+ <th>Mean Time</th>
8
+ <th>Max Time</th>
9
+ </tr>
10
+ </thead>
11
+ <tbody>
12
+ <% slow_queries.each do |row| %>
13
+ <tr>
14
+ <td>
15
+ <% full_query = row["query"] %>
16
+ <% if full_query.length > 100 %>
17
+ <span class="query-short" data-full-query="<%= ERB::Util.html_escape full_query %>">
18
+ <%= full_query[0, 100] %>...
19
+ </span>
20
+ <a href="#" class="show-more"
21
+ onclick="var el = this.previousElementSibling; el.textContent = el.getAttribute('data-full-query'); this.style.display='none'; return false;">
22
+ show more
23
+ </a>
24
+ <% else %>
25
+ <%= full_query %>
26
+ <% end %>
27
+ </td>
28
+ <td><%= row["calls"] %></td>
29
+ <td><%= row["total_time"] %></td>
30
+ <td><%= row["mean_time"] %></td>
31
+ <td><%= row["max_time"] %></td>
32
+ </tr>
33
+ <% end %>
34
+ </tbody>
35
+ </table>
@@ -0,0 +1,52 @@
1
+ <table class="table table-striped">
2
+ <thead class="thead-light">
3
+ <tr>
4
+ <th>Table Name</th>
5
+ <th>Seq Scan</th>
6
+ <th>Last Seq Scan</th>
7
+ <th>Seq Tup Read</th>
8
+ <th>Idx Scan</th>
9
+ <th>Last Idx Scan</th>
10
+ <th>Idx Tup Fetch</th>
11
+ <th>Tuples Inserted</th>
12
+ <th>Tuples Updated</th>
13
+ <th>Tuples Deleted</th>
14
+ <th>Live Tuples</th>
15
+ <th>Dead Tuples</th>
16
+ <th>Last Vacuum</th>
17
+ <th>Last Autovacuum</th>
18
+ <th>Last Analyze</th>
19
+ <th>Last Autoanalyze</th>
20
+ <th>Vacuum Count</th>
21
+ <th>Autovacuum Count</th>
22
+ <th>Analyze Count</th>
23
+ <th>Autoanalyze Count</th>
24
+ </tr>
25
+ </thead>
26
+ <tbody>
27
+ <% user_tables.each do |row| %>
28
+ <tr>
29
+ <td><%= row["table_name"] %></td>
30
+ <td><%= row["seq_scan"] %></td>
31
+ <td><%= row["last_seq_scan"] %></td>
32
+ <td><%= row["seq_tup_read"] %></td>
33
+ <td><%= row["idx_scan"] %></td>
34
+ <td><%= row["last_idx_scan"] %></td>
35
+ <td><%= row["idx_tup_fetch"] %></td>
36
+ <td><%= row["n_tup_ins"] %></td>
37
+ <td><%= row["n_tup_upd"] %></td>
38
+ <td><%= row["n_tup_del"] %></td>
39
+ <td><%= row["n_live_tup"] %></td>
40
+ <td><%= row["n_dead_tup"] %></td>
41
+ <td><%= row["last_vacuum"] %></td>
42
+ <td><%= row["last_autovacuum"] %></td>
43
+ <td><%= row["last_analyze"] %></td>
44
+ <td><%= row["last_autoanalyze"] %></td>
45
+ <td><%= row["vacuum_count"] %></td>
46
+ <td><%= row["autovacuum_count"] %></td>
47
+ <td><%= row["analyze_count"] %></td>
48
+ <td><%= row["autoanalyze_count"] %></td>
49
+ </tr>
50
+ <% end %>
51
+ </tbody>
52
+ </table>
@@ -0,0 +1,17 @@
1
+ require "sinatra"
2
+ require_relative "db_connection"
3
+ require_relative "index_usage"
4
+ require_relative "slow_queries"
5
+ require_relative "user_tables"
6
+ require_relative "pg_locks"
7
+
8
+ class PgMonitorApp < Sinatra::Base
9
+ get "/" do
10
+ @index_usage = PgMonitor::IndexUsage.fetch
11
+ @slow_queries = PgMonitor::SlowQueries.fetch(limit: 10)
12
+ @user_tables = PgMonitor::UserTables.fetch
13
+ @locks = PgMonitor::PGLocks.fetch
14
+
15
+ erb :index
16
+ end
17
+ end
data/lib/pg_monitor.rb CHANGED
@@ -5,6 +5,8 @@ require_relative "pg_monitor/version"
5
5
  require_relative "pg_monitor/db_connection"
6
6
  require_relative "pg_monitor/index_usage"
7
7
  require_relative "pg_monitor/slow_queries"
8
+ require_relative "pg_monitor/user_tables"
9
+ require_relative "pg_monitor/pg_locks"
8
10
 
9
11
  module PgMonitor
10
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_monitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hassan Murtaza
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-03-08 00:00:00.000000000 Z
11
+ date: 2025-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -43,8 +43,16 @@ files:
43
43
  - lib/pg_monitor.rb
44
44
  - lib/pg_monitor/db_connection.rb
45
45
  - lib/pg_monitor/index_usage.rb
46
+ - lib/pg_monitor/pg_locks.rb
46
47
  - lib/pg_monitor/slow_queries.rb
48
+ - lib/pg_monitor/user_tables.rb
47
49
  - lib/pg_monitor/version.rb
50
+ - lib/pg_monitor/views/index.erb
51
+ - lib/pg_monitor/views/index_usage_table.erb
52
+ - lib/pg_monitor/views/locks_table.erb
53
+ - lib/pg_monitor/views/slow_queries_table.erb
54
+ - lib/pg_monitor/views/user_tables_table.erb
55
+ - lib/pg_monitor/web_app.rb
48
56
  - postgresql.conf
49
57
  - sig/pg_monitor.rbs
50
58
  homepage: https://github.com/hmurtaza7/pg_monitor