pghero 2.3.0 → 2.5.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.
Potentially problematic release.
This version of pghero might be problematic. Click here for more details.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +85 -54
 - data/README.md +20 -8
 - data/app/assets/javascripts/pghero/Chart.bundle.js +16260 -15580
 - data/app/assets/javascripts/pghero/application.js +8 -7
 - data/app/assets/javascripts/pghero/chartkick.js +1973 -1325
 - data/app/assets/javascripts/pghero/highlight.pack.js +2 -2
 - data/app/assets/javascripts/pghero/jquery.js +3605 -4015
 - data/app/assets/javascripts/pghero/nouislider.js +2479 -0
 - data/app/assets/stylesheets/pghero/application.css +1 -1
 - data/app/assets/stylesheets/pghero/nouislider.css +299 -0
 - data/app/controllers/pg_hero/home_controller.rb +94 -35
 - data/app/helpers/pg_hero/home_helper.rb +11 -0
 - data/app/views/pg_hero/home/_live_queries_table.html.erb +14 -2
 - data/app/views/pg_hero/home/connections.html.erb +9 -0
 - data/app/views/pg_hero/home/index.html.erb +49 -10
 - data/app/views/pg_hero/home/live_queries.html.erb +1 -1
 - data/app/views/pg_hero/home/maintenance.html.erb +16 -2
 - data/app/views/pg_hero/home/relation_space.html.erb +2 -2
 - data/app/views/pg_hero/home/show_query.html.erb +3 -3
 - data/app/views/pg_hero/home/space.html.erb +3 -3
 - data/app/views/pg_hero/home/system.html.erb +4 -4
 - data/app/views/pg_hero/home/tune.html.erb +2 -1
 - data/lib/generators/pghero/templates/config.yml.tt +21 -1
 - data/lib/pghero.rb +63 -15
 - data/lib/pghero/database.rb +101 -17
 - data/lib/pghero/methods/basic.rb +28 -7
 - data/lib/pghero/methods/connections.rb +35 -0
 - data/lib/pghero/methods/constraints.rb +30 -0
 - data/lib/pghero/methods/indexes.rb +1 -1
 - data/lib/pghero/methods/maintenance.rb +3 -1
 - data/lib/pghero/methods/queries.rb +6 -2
 - data/lib/pghero/methods/query_stats.rb +12 -3
 - data/lib/pghero/methods/suggested_indexes.rb +1 -1
 - data/lib/pghero/methods/system.rb +219 -23
 - data/lib/pghero/stats.rb +1 -1
 - data/lib/pghero/version.rb +1 -1
 - metadata +6 -5
 - data/app/assets/javascripts/pghero/jquery.nouislider.min.js +0 -31
 - data/app/assets/stylesheets/pghero/jquery.nouislider.css +0 -165
 
| 
         @@ -15,5 +15,16 @@ module PgHero 
     | 
|
| 
       15 
15 
     | 
    
         
             
                def pghero_js_var(name, value)
         
     | 
| 
       16 
16 
     | 
    
         
             
                  "var #{name} = #{json_escape(value.to_json(root: false))};".html_safe
         
     | 
| 
       17 
17 
     | 
    
         
             
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def pghero_remove_index(query)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  if query[:columns]
         
     | 
| 
      
 21 
     | 
    
         
            +
                    columns = query[:columns].map(&:to_sym)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    columns = columns.first if columns.size == 1
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
                  ret = String.new("remove_index #{query[:table].to_sym.inspect}")
         
     | 
| 
      
 25 
     | 
    
         
            +
                  ret << ", name: #{(query[:name] || query[:index]).to_s.inspect}"
         
     | 
| 
      
 26 
     | 
    
         
            +
                  ret << ", column: #{columns.inspect}" if columns
         
     | 
| 
      
 27 
     | 
    
         
            +
                  ret
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
       18 
29 
     | 
    
         
             
              end
         
     | 
| 
       19 
30 
     | 
    
         
             
            end
         
     | 
| 
         @@ -11,7 +11,17 @@ 
     | 
|
| 
       11 
11 
     | 
    
         
             
                <% queries.reverse.each do |query| %>
         
     | 
| 
       12 
12 
     | 
    
         
             
                  <tr>
         
     | 
| 
       13 
13 
     | 
    
         
             
                    <td><%= query[:pid] %></td>
         
     | 
| 
       14 
     | 
    
         
            -
                    <td 
     | 
| 
      
 14 
     | 
    
         
            +
                    <td>
         
     | 
| 
      
 15 
     | 
    
         
            +
                      <% sec = query[:duration_ms] / 1000.0 %>
         
     | 
| 
      
 16 
     | 
    
         
            +
                      <% if sec < 1.minute %>
         
     | 
| 
      
 17 
     | 
    
         
            +
                        <%= sec.round(1) %> s
         
     | 
| 
      
 18 
     | 
    
         
            +
                      <% elsif sec < 1.day %>
         
     | 
| 
      
 19 
     | 
    
         
            +
                        <%= Time.at(sec).utc.strftime("%H:%M:%S") %>
         
     | 
| 
      
 20 
     | 
    
         
            +
                      <% else %>
         
     | 
| 
      
 21 
     | 
    
         
            +
                        <% days = (sec / 1.day).floor %>
         
     | 
| 
      
 22 
     | 
    
         
            +
                        <%= days %>d <%= Time.at(sec - days.days).utc.strftime("%H:%M:%S") %>
         
     | 
| 
      
 23 
     | 
    
         
            +
                      <% end %>
         
     | 
| 
      
 24 
     | 
    
         
            +
                    </td>
         
     | 
| 
       15 
25 
     | 
    
         
             
                    <td>
         
     | 
| 
       16 
26 
     | 
    
         
             
                      <%= query[:state] %>
         
     | 
| 
       17 
27 
     | 
    
         
             
                      <% if vacuum_progress[query[:pid]] %>
         
     | 
| 
         @@ -20,7 +30,9 @@ 
     | 
|
| 
       20 
30 
     | 
    
         
             
                      <% end %>
         
     | 
| 
       21 
31 
     | 
    
         
             
                    </td>
         
     | 
| 
       22 
32 
     | 
    
         
             
                    <td class="text-right">
         
     | 
| 
       23 
     | 
    
         
            -
                       
     | 
| 
      
 33 
     | 
    
         
            +
                      <% unless @database.filter_data %>
         
     | 
| 
      
 34 
     | 
    
         
            +
                        <%= button_to "Explain", explain_path, params: {query: query[:query]}, form: {target: "_blank"}, class: "btn btn-info" %>
         
     | 
| 
      
 35 
     | 
    
         
            +
                      <% end %>
         
     | 
| 
       24 
36 
     | 
    
         
             
                      <%= button_to "Kill", kill_path(pid: query[:pid]), class: "btn btn-danger" %>
         
     | 
| 
       25 
37 
     | 
    
         
             
                    </td>
         
     | 
| 
       26 
38 
     | 
    
         
             
                  </tr>
         
     | 
| 
         @@ -18,6 +18,15 @@ 
     | 
|
| 
       18 
18 
     | 
    
         
             
                  new Chartkick.PieChart("chart-2", <%= json_escape(@connections_by_user.to_json).html_safe %>);
         
     | 
| 
       19 
19 
     | 
    
         
             
                </script>
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
      
 21 
     | 
    
         
            +
                <% if @connections_by_ssl_status %>
         
     | 
| 
      
 22 
     | 
    
         
            +
                  <h3>By Security</h3>
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  <div id="chart-3" class="chart" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
         
     | 
| 
      
 25 
     | 
    
         
            +
                  <script>
         
     | 
| 
      
 26 
     | 
    
         
            +
                    new Chartkick.PieChart("chart-3", <%= json_escape(@connections_by_ssl_status.to_json).html_safe %>);
         
     | 
| 
      
 27 
     | 
    
         
            +
                  </script>
         
     | 
| 
      
 28 
     | 
    
         
            +
                <% end %>
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
       21 
30 
     | 
    
         
             
                <%= render partial: "connections_table", locals: {connection_sources: @connection_sources} %>
         
     | 
| 
       22 
31 
     | 
    
         
             
              <% end %>
         
     | 
| 
       23 
32 
     | 
    
         
             
            </div>
         
     | 
| 
         @@ -62,11 +62,17 @@ 
     | 
|
| 
       62 
62 
     | 
    
         
             
                  (<%= link_to pluralize(@unreadable_sequences.size, "unreadable sequence", "unreadable sequences"), {unreadable: "t"} %>)
         
     | 
| 
       63 
63 
     | 
    
         
             
                <% end %>
         
     | 
| 
       64 
64 
     | 
    
         
             
              </div>
         
     | 
| 
       65 
     | 
    
         
            -
              <div class="alert alert-<%= @invalid_indexes.empty? ? "success" : "warning" %>">
         
     | 
| 
       66 
     | 
    
         
            -
                <% if @invalid_indexes. 
     | 
| 
       67 
     | 
    
         
            -
                   
     | 
| 
      
 65 
     | 
    
         
            +
              <div class="alert alert-<%= @invalid_indexes.empty? && @invalid_constraints.empty? ? "success" : "warning" %>">
         
     | 
| 
      
 66 
     | 
    
         
            +
                <% if @invalid_indexes.empty? && @invalid_constraints.empty?  %>
         
     | 
| 
      
 67 
     | 
    
         
            +
                  No invalid indexes or constraints
         
     | 
| 
       68 
68 
     | 
    
         
             
                <% else %>
         
     | 
| 
       69 
     | 
    
         
            -
                   
     | 
| 
      
 69 
     | 
    
         
            +
                  <% if @invalid_indexes.any? %>
         
     | 
| 
      
 70 
     | 
    
         
            +
                    <%= pluralize(@invalid_indexes.size, "invalid index", "invalid indexes") %>
         
     | 
| 
      
 71 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
      
 72 
     | 
    
         
            +
                  <% if @invalid_constraints.any? %>
         
     | 
| 
      
 73 
     | 
    
         
            +
                    <% if @invalid_indexes.any? %>and<% end %>
         
     | 
| 
      
 74 
     | 
    
         
            +
                    <%= pluralize(@invalid_constraints.size, "invalid constraint", "invalid constraints") %>
         
     | 
| 
      
 75 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
       70 
76 
     | 
    
         
             
                <% end %>
         
     | 
| 
       71 
77 
     | 
    
         
             
              </div>
         
     | 
| 
       72 
78 
     | 
    
         
             
              <% if @duplicate_indexes %>
         
     | 
| 
         @@ -332,6 +338,39 @@ 
     | 
|
| 
       332 
338 
     | 
    
         
             
              </div>
         
     | 
| 
       333 
339 
     | 
    
         
             
            <% end %>
         
     | 
| 
       334 
340 
     | 
    
         | 
| 
      
 341 
     | 
    
         
            +
            <% if @invalid_constraints.any? %>
         
     | 
| 
      
 342 
     | 
    
         
            +
              <div class="content">
         
     | 
| 
      
 343 
     | 
    
         
            +
                <h1>Invalid Constraints</h1>
         
     | 
| 
      
 344 
     | 
    
         
            +
             
     | 
| 
      
 345 
     | 
    
         
            +
                <p>These constraints are marked as <code>NOT VALID</code>. You should validate them.</p>
         
     | 
| 
      
 346 
     | 
    
         
            +
             
     | 
| 
      
 347 
     | 
    
         
            +
                <table class="table">
         
     | 
| 
      
 348 
     | 
    
         
            +
                  <thead>
         
     | 
| 
      
 349 
     | 
    
         
            +
                    <tr>
         
     | 
| 
      
 350 
     | 
    
         
            +
                      <th>Name</th>
         
     | 
| 
      
 351 
     | 
    
         
            +
                    </tr>
         
     | 
| 
      
 352 
     | 
    
         
            +
                  </thead>
         
     | 
| 
      
 353 
     | 
    
         
            +
                  <tbody>
         
     | 
| 
      
 354 
     | 
    
         
            +
                    <% @invalid_constraints.each do |constraint| %>
         
     | 
| 
      
 355 
     | 
    
         
            +
                      <tr>
         
     | 
| 
      
 356 
     | 
    
         
            +
                        <td>
         
     | 
| 
      
 357 
     | 
    
         
            +
                          <%= constraint[:name] %>
         
     | 
| 
      
 358 
     | 
    
         
            +
                          <% if constraint[:schema] != "public" %>
         
     | 
| 
      
 359 
     | 
    
         
            +
                            <span class="text-muted"><%= constraint[:schema] %></span>
         
     | 
| 
      
 360 
     | 
    
         
            +
                          <% end %>
         
     | 
| 
      
 361 
     | 
    
         
            +
                        </td>
         
     | 
| 
      
 362 
     | 
    
         
            +
                      </tr>
         
     | 
| 
      
 363 
     | 
    
         
            +
                      <tr>
         
     | 
| 
      
 364 
     | 
    
         
            +
                        <td style="border-top: none; padding: 0;">
         
     | 
| 
      
 365 
     | 
    
         
            +
                          <pre><code>ALTER TABLE <%= pghero_pretty_ident(constraint[:table], schema: constraint[:schema]) %> VALIDATE CONSTRAINT <%= pghero_pretty_ident(constraint[:name]) %>;</code></pre>
         
     | 
| 
      
 366 
     | 
    
         
            +
                        </td>
         
     | 
| 
      
 367 
     | 
    
         
            +
                      </tr>
         
     | 
| 
      
 368 
     | 
    
         
            +
                    <% end %>
         
     | 
| 
      
 369 
     | 
    
         
            +
                  </tbody>
         
     | 
| 
      
 370 
     | 
    
         
            +
                </table>
         
     | 
| 
      
 371 
     | 
    
         
            +
              </div>
         
     | 
| 
      
 372 
     | 
    
         
            +
            <% end %>
         
     | 
| 
      
 373 
     | 
    
         
            +
             
     | 
| 
       335 
374 
     | 
    
         
             
            <% if @duplicate_indexes && @duplicate_indexes.any? %>
         
     | 
| 
       336 
375 
     | 
    
         
             
              <div class="content">
         
     | 
| 
       337 
376 
     | 
    
         
             
                <h1>Duplicate Indexes</h1>
         
     | 
| 
         @@ -345,10 +384,10 @@ 
     | 
|
| 
       345 
384 
     | 
    
         
             
                </p>
         
     | 
| 
       346 
385 
     | 
    
         | 
| 
       347 
386 
     | 
    
         
             
                <div id="migration2" style="display: none;">
         
     | 
| 
       348 
     | 
    
         
            -
                  <pre>rails  
     | 
| 
      
 387 
     | 
    
         
            +
                  <pre>rails generate migration remove_unneeded_indexes</pre>
         
     | 
| 
       349 
388 
     | 
    
         
             
                  <p>And paste</p>
         
     | 
| 
       350 
389 
     | 
    
         
             
                  <pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @duplicate_indexes.each do |query| %>
         
     | 
| 
       351 
     | 
    
         
            -
             
     | 
| 
      
 390 
     | 
    
         
            +
            <%= pghero_remove_index(query[:unneeded_index]) %><% end %></pre>
         
     | 
| 
       352 
391 
     | 
    
         
             
                </div>
         
     | 
| 
       353 
392 
     | 
    
         | 
| 
       354 
393 
     | 
    
         
             
                <table class="table duplicate-indexes">
         
     | 
| 
         @@ -387,12 +426,12 @@ remove_index <%= query[:unneeded_index][:table].to_sym.inspect %>, name: <%= que 
     | 
|
| 
       387 
426 
     | 
    
         
             
                </p>
         
     | 
| 
       388 
427 
     | 
    
         | 
| 
       389 
428 
     | 
    
         
             
                <div id="migration3" style="display: none;">
         
     | 
| 
       390 
     | 
    
         
            -
                  <pre>rails  
     | 
| 
      
 429 
     | 
    
         
            +
                  <pre>rails generate migration add_suggested_indexes</pre>
         
     | 
| 
       391 
430 
     | 
    
         
             
                  <p>And paste</p>
         
     | 
| 
       392 
431 
     | 
    
         
             
                  <pre style="overflow: scroll; white-space: pre; word-break: normal;">commit_db_transaction
         
     | 
| 
       393 
432 
     | 
    
         
             
            <% @suggested_indexes.each do |index| %>
         
     | 
| 
       394 
433 
     | 
    
         
             
            <% if index[:using] && index[:using] != "btree" %>
         
     | 
| 
       395 
     | 
    
         
            -
             
     | 
| 
      
 434 
     | 
    
         
            +
            add_index <%= index[:table].to_sym.inspect %>, <%= index[:columns].first.inspect %>, using: <%= index[:using].inspect %>, algorithm: :concurrently
         
     | 
| 
       396 
435 
     | 
    
         
             
            <% else %>
         
     | 
| 
       397 
436 
     | 
    
         
             
            add_index <%= index[:table].to_sym.inspect %>, [<%= index[:columns].map(&:to_sym).map(&:inspect).join(", ") %>], algorithm: :concurrently<% end %>
         
     | 
| 
       398 
437 
     | 
    
         
             
            <% end %></pre>
         
     | 
| 
         @@ -449,10 +488,10 @@ pg_stat_statements.track = all 
     | 
|
| 
       449 
488 
     | 
    
         
             
                </p>
         
     | 
| 
       450 
489 
     | 
    
         | 
| 
       451 
490 
     | 
    
         
             
                <div id="migration" style="display: none;">
         
     | 
| 
       452 
     | 
    
         
            -
                  <pre>rails  
     | 
| 
      
 491 
     | 
    
         
            +
                  <pre>rails generate migration remove_unused_indexes</pre>
         
     | 
| 
       453 
492 
     | 
    
         
             
                  <p>And paste</p>
         
     | 
| 
       454 
493 
     | 
    
         
             
                  <pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.each do |query| %>
         
     | 
| 
       455 
     | 
    
         
            -
             
     | 
| 
      
 494 
     | 
    
         
            +
            <%= pghero_remove_index(query)%><% end %></pre>
         
     | 
| 
       456 
495 
     | 
    
         
             
                </div>
         
     | 
| 
       457 
496 
     | 
    
         | 
| 
       458 
497 
     | 
    
         
             
                <table class="table">
         
     | 
| 
         @@ -7,6 +7,9 @@ 
     | 
|
| 
       7 
7 
     | 
    
         
             
                    <th>Table</th>
         
     | 
| 
       8 
8 
     | 
    
         
             
                    <th style="width: 20%;">Last Vacuum</th>
         
     | 
| 
       9 
9 
     | 
    
         
             
                    <th style="width: 20%;">Last Analyze</th>
         
     | 
| 
      
 10 
     | 
    
         
            +
                    <% if @show_dead_rows %>
         
     | 
| 
      
 11 
     | 
    
         
            +
                      <th style="width: 20%;">Dead Rows</th>
         
     | 
| 
      
 12 
     | 
    
         
            +
                    <% end %>
         
     | 
| 
       10 
13 
     | 
    
         
             
                  </tr>
         
     | 
| 
       11 
14 
     | 
    
         
             
                </thead>
         
     | 
| 
       12 
15 
     | 
    
         
             
                <tbody>
         
     | 
| 
         @@ -21,7 +24,7 @@ 
     | 
|
| 
       21 
24 
     | 
    
         
             
                      <td>
         
     | 
| 
       22 
25 
     | 
    
         
             
                        <% time = [table[:last_autovacuum], table[:last_vacuum]].compact.max %>
         
     | 
| 
       23 
26 
     | 
    
         
             
                        <% if time %>
         
     | 
| 
       24 
     | 
    
         
            -
                          <%= time.in_time_zone(@time_zone) 
     | 
| 
      
 27 
     | 
    
         
            +
                          <%= l time.in_time_zone(@time_zone), format: :short %>
         
     | 
| 
       25 
28 
     | 
    
         
             
                        <% else %>
         
     | 
| 
       26 
29 
     | 
    
         
             
                          <span class="text-muted">Unknown</span>
         
     | 
| 
       27 
30 
     | 
    
         
             
                        <% end %>
         
     | 
| 
         @@ -29,11 +32,22 @@ 
     | 
|
| 
       29 
32 
     | 
    
         
             
                      <td>
         
     | 
| 
       30 
33 
     | 
    
         
             
                        <% time = [table[:last_autoanalyze], table[:last_analyze]].compact.max %>
         
     | 
| 
       31 
34 
     | 
    
         
             
                        <% if time %>
         
     | 
| 
       32 
     | 
    
         
            -
                          <%= time.in_time_zone(@time_zone) 
     | 
| 
      
 35 
     | 
    
         
            +
                          <%= l time.in_time_zone(@time_zone), format: :short %>
         
     | 
| 
       33 
36 
     | 
    
         
             
                        <% else %>
         
     | 
| 
       34 
37 
     | 
    
         
             
                          <span class="text-muted">Unknown</span>
         
     | 
| 
       35 
38 
     | 
    
         
             
                        <% end %>
         
     | 
| 
       36 
39 
     | 
    
         
             
                      </td>
         
     | 
| 
      
 40 
     | 
    
         
            +
                      <% if @show_dead_rows %>
         
     | 
| 
      
 41 
     | 
    
         
            +
                        <td>
         
     | 
| 
      
 42 
     | 
    
         
            +
                          <% if table[:live_rows] != 0 %>
         
     | 
| 
      
 43 
     | 
    
         
            +
                            <%# use live rows only for denominator to make it easier to compare with autovacuum_vacuum_scale_factor %>
         
     | 
| 
      
 44 
     | 
    
         
            +
                            <%# it's not a true percentage, since it can go above 100% %>
         
     | 
| 
      
 45 
     | 
    
         
            +
                            <%= (100.0 * table[:dead_rows] / table[:live_rows]).round %>%
         
     | 
| 
      
 46 
     | 
    
         
            +
                          <% else %>
         
     | 
| 
      
 47 
     | 
    
         
            +
                            <span class="text-muted">Unknown</span>
         
     | 
| 
      
 48 
     | 
    
         
            +
                          <% end %>
         
     | 
| 
      
 49 
     | 
    
         
            +
                        </td>
         
     | 
| 
      
 50 
     | 
    
         
            +
                      <% end %>
         
     | 
| 
       37 
51 
     | 
    
         
             
                    </tr>
         
     | 
| 
       38 
52 
     | 
    
         
             
                  <% end %>
         
     | 
| 
       39 
53 
     | 
    
         
             
                </tbody>
         
     | 
| 
         @@ -6,9 +6,9 @@ 
     | 
|
| 
       6 
6 
     | 
    
         
             
                <% end %>
         
     | 
| 
       7 
7 
     | 
    
         
             
              </h1>
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
              <h1>Size 
     | 
| 
      
 9 
     | 
    
         
            +
              <h1>Size</h1>
         
     | 
| 
       10 
10 
     | 
    
         
             
              <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
         
     | 
| 
       11 
11 
     | 
    
         
             
              <script>
         
     | 
| 
       12 
     | 
    
         
            -
                new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, min: null})
         
     | 
| 
      
 12 
     | 
    
         
            +
                new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, min: null, bytes: true, library: {tooltips: {intersect: false, mode: "index"}}})
         
     | 
| 
       13 
13 
     | 
    
         
             
              </script>
         
     | 
| 
       14 
14 
     | 
    
         
             
            </div>
         
     | 
| 
         @@ -49,19 +49,19 @@ 
     | 
|
| 
       49 
49 
     | 
    
         
             
                <h1>Total Time <small>ms</small></h1>
         
     | 
| 
       50 
50 
     | 
    
         
             
                <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
         
     | 
| 
       51 
51 
     | 
    
         
             
                <script>
         
     | 
| 
       52 
     | 
    
         
            -
                  new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false})
         
     | 
| 
      
 52 
     | 
    
         
            +
                  new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {tooltips: {intersect: false, mode: "index"}}})
         
     | 
| 
       53 
53 
     | 
    
         
             
                </script>
         
     | 
| 
       54 
54 
     | 
    
         | 
| 
       55 
55 
     | 
    
         
             
                <h1>Average Time <small>ms</small></h1>
         
     | 
| 
       56 
56 
     | 
    
         
             
                <div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
         
     | 
| 
       57 
57 
     | 
    
         
             
                <script>
         
     | 
| 
       58 
     | 
    
         
            -
                  new Chartkick.LineChart("chart-2", <%= json_escape(@chart2_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false})
         
     | 
| 
      
 58 
     | 
    
         
            +
                  new Chartkick.LineChart("chart-2", <%= json_escape(@chart2_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {tooltips: {intersect: false, mode: "index"}}})
         
     | 
| 
       59 
59 
     | 
    
         
             
                </script>
         
     | 
| 
       60 
60 
     | 
    
         | 
| 
       61 
61 
     | 
    
         
             
                <h1>Calls</h1>
         
     | 
| 
       62 
62 
     | 
    
         
             
                <div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
         
     | 
| 
       63 
63 
     | 
    
         
             
                <script>
         
     | 
| 
       64 
     | 
    
         
            -
                  new Chartkick.LineChart("chart-3", <%= json_escape(@chart3_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false})
         
     | 
| 
      
 64 
     | 
    
         
            +
                  new Chartkick.LineChart("chart-3", <%= json_escape(@chart3_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {tooltips: {intersect: false, mode: "index"}}})
         
     | 
| 
       65 
65 
     | 
    
         
             
                </script>
         
     | 
| 
       66 
66 
     | 
    
         
             
              <% else %>
         
     | 
| 
       67 
67 
     | 
    
         
             
                <p>
         
     | 
| 
         @@ -6,7 +6,7 @@ 
     | 
|
| 
       6 
6 
     | 
    
         
             
              <% if @system_stats_enabled %>
         
     | 
| 
       7 
7 
     | 
    
         
             
                <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
         
     | 
| 
       8 
8 
     | 
    
         
             
                <script>
         
     | 
| 
       9 
     | 
    
         
            -
                  new Chartkick.LineChart("chart-1", <%= json_escape(free_space_stats_path.to_json).html_safe %>, {colors: ["#5bc0de"]})
         
     | 
| 
      
 9 
     | 
    
         
            +
                  new Chartkick.LineChart("chart-1", <%= json_escape(free_space_stats_path.to_json).html_safe %>, {colors: ["#5bc0de"], bytes: true, library: {tooltips: {intersect: false, mode: "index"}}})
         
     | 
| 
       10 
10 
     | 
    
         
             
                </script>
         
     | 
| 
       11 
11 
     | 
    
         
             
              <% end %>
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
         @@ -30,10 +30,10 @@ 
     | 
|
| 
       30 
30 
     | 
    
         
             
                </p>
         
     | 
| 
       31 
31 
     | 
    
         | 
| 
       32 
32 
     | 
    
         
             
                <div id="migration" style="display: none;">
         
     | 
| 
       33 
     | 
    
         
            -
                  <pre>rails  
     | 
| 
      
 33 
     | 
    
         
            +
                  <pre>rails generate migration remove_unused_indexes</pre>
         
     | 
| 
       34 
34 
     | 
    
         
             
                  <p>And paste</p>
         
     | 
| 
       35 
35 
     | 
    
         
             
                  <pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.sort_by { |q| [-q[:size_bytes], q[:index]] }.each do |query| %>
         
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
      
 36 
     | 
    
         
            +
            <%= pghero_remove_index(query) %><% end %></pre>
         
     | 
| 
       37 
37 
     | 
    
         
             
                </div>
         
     | 
| 
       38 
38 
     | 
    
         
             
              <% end %>
         
     | 
| 
       39 
39 
     | 
    
         | 
| 
         @@ -9,26 +9,26 @@ 
     | 
|
| 
       9 
9 
     | 
    
         
             
              <h1>CPU</h1>
         
     | 
| 
       10 
10 
     | 
    
         
             
              <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
         
     | 
| 
       11 
11 
     | 
    
         
             
              <script>
         
     | 
| 
       12 
     | 
    
         
            -
                new Chartkick.LineChart("chart-1", <%= json_escape(cpu_usage_path(path_options).to_json).html_safe %>, {max: 100, colors: ["#5bc0de"]})
         
     | 
| 
      
 12 
     | 
    
         
            +
                new Chartkick.LineChart("chart-1", <%= json_escape(cpu_usage_path(path_options).to_json).html_safe %>, {max: 100, colors: ["#5bc0de"], suffix: "%", library: {tooltips: {intersect: false, mode: "index"}}})
         
     | 
| 
       13 
13 
     | 
    
         
             
              </script>
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
15 
     | 
    
         
             
              <h1>Load</h1>
         
     | 
| 
       16 
16 
     | 
    
         
             
              <div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
         
     | 
| 
       17 
17 
     | 
    
         
             
              <script>
         
     | 
| 
       18 
     | 
    
         
            -
                new Chartkick.LineChart("chart-2", <%= json_escape(load_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de", "#d9534f"]})
         
     | 
| 
      
 18 
     | 
    
         
            +
                new Chartkick.LineChart("chart-2", <%= json_escape(load_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de", "#d9534f"], library: {tooltips: {intersect: false, mode: "nearest"}}})
         
     | 
| 
       19 
19 
     | 
    
         
             
              </script>
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
21 
     | 
    
         
             
              <h1>Connections</h1>
         
     | 
| 
       22 
22 
     | 
    
         
             
              <div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
         
     | 
| 
       23 
23 
     | 
    
         
             
              <script>
         
     | 
| 
       24 
     | 
    
         
            -
                new Chartkick.LineChart("chart-3", <%= json_escape(connection_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"]})
         
     | 
| 
      
 24 
     | 
    
         
            +
                new Chartkick.LineChart("chart-3", <%= json_escape(connection_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"], library: {tooltips: {intersect: false, mode: "index"}}})
         
     | 
| 
       25 
25 
     | 
    
         
             
              </script>
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
27 
     | 
    
         
             
              <% if @database.replica? %>
         
     | 
| 
       28 
28 
     | 
    
         
             
                <h1>Replication Lag</h1>
         
     | 
| 
       29 
29 
     | 
    
         
             
                <div id="chart-4" class="chart" style="margin-bottom: 20px;">Loading...</div>
         
     | 
| 
       30 
30 
     | 
    
         
             
                <script>
         
     | 
| 
       31 
     | 
    
         
            -
                  new Chartkick.LineChart("chart-4", <%= json_escape(replication_lag_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"]})
         
     | 
| 
      
 31 
     | 
    
         
            +
                  new Chartkick.LineChart("chart-4", <%= json_escape(replication_lag_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"], library: {tooltips: {intersect: false, mode: "index"}}})
         
     | 
| 
       32 
32 
     | 
    
         
             
                </script>
         
     | 
| 
       33 
33 
     | 
    
         
             
              <% end %>
         
     | 
| 
       34 
34 
     | 
    
         
             
            </div>
         
     | 
| 
         @@ -18,7 +18,8 @@ 
     | 
|
| 
       18 
18 
     | 
    
         
             
                </tbody>
         
     | 
| 
       19 
19 
     | 
    
         
             
              </table>
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
               
     | 
| 
      
 21 
     | 
    
         
            +
              <% version_parts = @database.server_version.split(" ").first.split(".") %>
         
     | 
| 
      
 22 
     | 
    
         
            +
              <p>Check out <%= link_to "PgTune", "https://pgtune.leopard.in.ua/", target: "_blank" %> for recommendations. DB version is <%= version_parts[0].to_i >= 10 ? version_parts[0] : version_parts.first(2).join(".") %>.</p>
         
     | 
| 
       22 
23 
     | 
    
         
             
            </div>
         
     | 
| 
       23 
24 
     | 
    
         | 
| 
       24 
25 
     | 
    
         
             
            <% if @autovacuum_settings %>
         
     | 
| 
         @@ -1,8 +1,13 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            databases:
         
     | 
| 
       2 
     | 
    
         
            -
               
     | 
| 
      
 2 
     | 
    
         
            +
              primary:
         
     | 
| 
       3 
3 
     | 
    
         
             
                # Database URL (defaults to app database)
         
     | 
| 
       4 
4 
     | 
    
         
             
                # url: <%%= ENV["DATABASE_URL"] %>
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
      
 6 
     | 
    
         
            +
                # System stats
         
     | 
| 
      
 7 
     | 
    
         
            +
                # aws_db_instance_identifier: my-instance
         
     | 
| 
      
 8 
     | 
    
         
            +
                # gcp_database_id: my-project:my-instance
         
     | 
| 
      
 9 
     | 
    
         
            +
                # azure_resource_id: my-resource-id
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
       6 
11 
     | 
    
         
             
              # Add more databases
         
     | 
| 
       7 
12 
     | 
    
         
             
              # other:
         
     | 
| 
       8 
13 
     | 
    
         
             
              #   url: <%%= ENV["OTHER_DATABASE_URL"] %>
         
     | 
| 
         @@ -24,3 +29,18 @@ databases: 
     | 
|
| 
       24 
29 
     | 
    
         | 
| 
       25 
30 
     | 
    
         
             
            # Time zone (defaults to app time zone)
         
     | 
| 
       26 
31 
     | 
    
         
             
            # time_zone: "Pacific Time (US & Canada)"
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            # Basic authentication
         
     | 
| 
      
 34 
     | 
    
         
            +
            # username: admin
         
     | 
| 
      
 35 
     | 
    
         
            +
            # password: <%%= ENV["PGHERO_PASSWORD"] %>
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            # Stats database URL (defaults to app database)
         
     | 
| 
      
 38 
     | 
    
         
            +
            # stats_database_url: <%%= ENV["PGHERO_STATS_DATABASE_URL"] %>
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            # AWS configuration (defaults to app AWS config)
         
     | 
| 
      
 41 
     | 
    
         
            +
            # aws_access_key_id: <%%= ENV["AWS_ACCESS_KEY_ID"] %>
         
     | 
| 
      
 42 
     | 
    
         
            +
            # aws_secret_access_key: <%%= ENV["AWS_SECRET_ACCESS_KEY"] %>
         
     | 
| 
      
 43 
     | 
    
         
            +
            # aws_region: us-east-1
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            # Filter data from queries (experimental)
         
     | 
| 
      
 46 
     | 
    
         
            +
            # filter_data: true
         
     | 
    
        data/lib/pghero.rb
    CHANGED
    
    | 
         @@ -5,6 +5,7 @@ require "forwardable" 
     | 
|
| 
       5 
5 
     | 
    
         
             
            # methods
         
     | 
| 
       6 
6 
     | 
    
         
             
            require "pghero/methods/basic"
         
     | 
| 
       7 
7 
     | 
    
         
             
            require "pghero/methods/connections"
         
     | 
| 
      
 8 
     | 
    
         
            +
            require "pghero/methods/constraints"
         
     | 
| 
       8 
9 
     | 
    
         
             
            require "pghero/methods/explain"
         
     | 
| 
       9 
10 
     | 
    
         
             
            require "pghero/methods/indexes"
         
     | 
| 
       10 
11 
     | 
    
         
             
            require "pghero/methods/kill"
         
     | 
| 
         @@ -33,9 +34,11 @@ module PgHero 
     | 
|
| 
       33 
34 
     | 
    
         
             
              class Error < StandardError; end
         
     | 
| 
       34 
35 
     | 
    
         
             
              class NotEnabled < Error; end
         
     | 
| 
       35 
36 
     | 
    
         | 
| 
      
 37 
     | 
    
         
            +
              MUTEX = Mutex.new
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
       36 
39 
     | 
    
         
             
              # settings
         
     | 
| 
       37 
40 
     | 
    
         
             
              class << self
         
     | 
| 
       38 
     | 
    
         
            -
                attr_accessor :long_running_query_sec, :slow_query_ms, :slow_query_calls, :explain_timeout_sec, :total_connections_threshold, :cache_hit_rate_threshold, :env, :show_migrations, :config_path
         
     | 
| 
      
 41 
     | 
    
         
            +
                attr_accessor :long_running_query_sec, :slow_query_ms, :slow_query_calls, :explain_timeout_sec, :total_connections_threshold, :cache_hit_rate_threshold, :env, :show_migrations, :config_path, :filter_data
         
     | 
| 
       39 
42 
     | 
    
         
             
              end
         
     | 
| 
       40 
43 
     | 
    
         
             
              self.long_running_query_sec = (ENV["PGHERO_LONG_RUNNING_QUERY_SEC"] || 60).to_i
         
     | 
| 
       41 
44 
     | 
    
         
             
              self.slow_query_ms = (ENV["PGHERO_SLOW_QUERY_MS"] || 20).to_i
         
     | 
| 
         @@ -46,14 +49,15 @@ module PgHero 
     | 
|
| 
       46 
49 
     | 
    
         
             
              self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
         
     | 
| 
       47 
50 
     | 
    
         
             
              self.show_migrations = true
         
     | 
| 
       48 
51 
     | 
    
         
             
              self.config_path = ENV["PGHERO_CONFIG_PATH"] || "config/pghero.yml"
         
     | 
| 
      
 52 
     | 
    
         
            +
              self.filter_data = ENV["PGHERO_FILTER_DATA"].to_s.size > 0
         
     | 
| 
       49 
53 
     | 
    
         | 
| 
       50 
54 
     | 
    
         
             
              class << self
         
     | 
| 
       51 
55 
     | 
    
         
             
                extend Forwardable
         
     | 
| 
       52 
56 
     | 
    
         
             
                def_delegators :primary_database, :access_key_id, :analyze, :analyze_tables, :autoindex, :autovacuum_danger,
         
     | 
| 
       53 
     | 
    
         
            -
                  :best_index, :blocked_queries, :connection_sources, :connection_states, :connection_stats,
         
     | 
| 
      
 57 
     | 
    
         
            +
                  :best_index, :blocked_queries, :connections, :connection_sources, :connection_states, :connection_stats,
         
     | 
| 
       54 
58 
     | 
    
         
             
                  :cpu_usage, :create_user, :database_size, :db_instance_identifier, :disable_query_stats, :drop_user,
         
     | 
| 
       55 
59 
     | 
    
         
             
                  :duplicate_indexes, :enable_query_stats, :explain, :historical_query_stats_enabled?, :index_caching,
         
     | 
| 
       56 
     | 
    
         
            -
                  :index_hit_rate, :index_usage, :indexes, :invalid_indexes, :kill, :kill_all, :kill_long_running_queries,
         
     | 
| 
      
 60 
     | 
    
         
            +
                  :index_hit_rate, :index_usage, :indexes, :invalid_constraints, :invalid_indexes, :kill, :kill_all, :kill_long_running_queries,
         
     | 
| 
       57 
61 
     | 
    
         
             
                  :last_stats_reset_time, :long_running_queries, :maintenance_info, :missing_indexes, :query_stats,
         
     | 
| 
       58 
62 
     | 
    
         
             
                  :query_stats_available?, :query_stats_enabled?, :query_stats_extension_enabled?, :query_stats_readable?,
         
     | 
| 
       59 
63 
     | 
    
         
             
                  :rds_stats, :read_iops_stats, :region, :relation_sizes, :replica?, :replication_lag, :replication_lag_stats,
         
     | 
| 
         @@ -70,6 +74,22 @@ module PgHero 
     | 
|
| 
       70 
74 
     | 
    
         
             
                  @time_zone || Time.zone
         
     | 
| 
       71 
75 
     | 
    
         
             
                end
         
     | 
| 
       72 
76 
     | 
    
         | 
| 
      
 77 
     | 
    
         
            +
                # use method instead of attr_accessor to ensure
         
     | 
| 
      
 78 
     | 
    
         
            +
                # this works if variable set after PgHero is loaded
         
     | 
| 
      
 79 
     | 
    
         
            +
                def username
         
     | 
| 
      
 80 
     | 
    
         
            +
                  @username ||= config["username"] || ENV["PGHERO_USERNAME"]
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                # use method instead of attr_accessor to ensure
         
     | 
| 
      
 84 
     | 
    
         
            +
                # this works if variable set after PgHero is loaded
         
     | 
| 
      
 85 
     | 
    
         
            +
                def password
         
     | 
| 
      
 86 
     | 
    
         
            +
                  @password ||= config["password"] || ENV["PGHERO_PASSWORD"]
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                def stats_database_url
         
     | 
| 
      
 90 
     | 
    
         
            +
                  @stats_database_url ||= config["stats_database_url"] || ENV["PGHERO_STATS_DATABASE_URL"]
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
       73 
93 
     | 
    
         
             
                def config
         
     | 
| 
       74 
94 
     | 
    
         
             
                  @config ||= begin
         
     | 
| 
       75 
95 
     | 
    
         
             
                    require "erb"
         
     | 
| 
         @@ -89,26 +109,49 @@ module PgHero 
     | 
|
| 
       89 
109 
     | 
    
         
             
                    elsif config_file_exists
         
     | 
| 
       90 
110 
     | 
    
         
             
                      raise "Invalid config file"
         
     | 
| 
       91 
111 
     | 
    
         
             
                    else
         
     | 
| 
       92 
     | 
    
         
            -
                      {
         
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
       94 
     | 
    
         
            -
             
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
       96 
     | 
    
         
            -
             
     | 
| 
       97 
     | 
    
         
            -
             
     | 
| 
      
 112 
     | 
    
         
            +
                      databases = {}
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                      if !ENV["PGHERO_DATABASE_URL"] && spec_supported?
         
     | 
| 
      
 115 
     | 
    
         
            +
                        ActiveRecord::Base.configurations.configs_for(env_name: env, include_replicas: true).each do |db|
         
     | 
| 
      
 116 
     | 
    
         
            +
                          databases[db.spec_name] = {"spec" => db.spec_name}
         
     | 
| 
      
 117 
     | 
    
         
            +
                        end
         
     | 
| 
      
 118 
     | 
    
         
            +
                      end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                      if databases.empty?
         
     | 
| 
      
 121 
     | 
    
         
            +
                        databases["primary"] = {
         
     | 
| 
      
 122 
     | 
    
         
            +
                          "url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config
         
     | 
| 
       98 
123 
     | 
    
         
             
                        }
         
     | 
| 
      
 124 
     | 
    
         
            +
                      end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                      if databases.size == 1
         
     | 
| 
      
 127 
     | 
    
         
            +
                        databases.values.first.merge!(
         
     | 
| 
      
 128 
     | 
    
         
            +
                          "db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"],
         
     | 
| 
      
 129 
     | 
    
         
            +
                          "gcp_database_id" => ENV["PGHERO_GCP_DATABASE_ID"],
         
     | 
| 
      
 130 
     | 
    
         
            +
                          "azure_resource_id" => ENV["PGHERO_AZURE_RESOURCE_ID"]
         
     | 
| 
      
 131 
     | 
    
         
            +
                        )
         
     | 
| 
      
 132 
     | 
    
         
            +
                      end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                      {
         
     | 
| 
      
 135 
     | 
    
         
            +
                        "databases" => databases
         
     | 
| 
       99 
136 
     | 
    
         
             
                      }
         
     | 
| 
       100 
137 
     | 
    
         
             
                    end
         
     | 
| 
       101 
138 
     | 
    
         
             
                  end
         
     | 
| 
       102 
139 
     | 
    
         
             
                end
         
     | 
| 
       103 
140 
     | 
    
         | 
| 
      
 141 
     | 
    
         
            +
                # ensure we only have one copy of databases
         
     | 
| 
      
 142 
     | 
    
         
            +
                # so there's only one connection pool per database
         
     | 
| 
       104 
143 
     | 
    
         
             
                def databases
         
     | 
| 
       105 
     | 
    
         
            -
                  @databases 
     | 
| 
       106 
     | 
    
         
            -
                     
     | 
| 
       107 
     | 
    
         
            -
             
     | 
| 
       108 
     | 
    
         
            -
             
     | 
| 
       109 
     | 
    
         
            -
                       
     | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
      
 144 
     | 
    
         
            +
                  unless defined?(@databases)
         
     | 
| 
      
 145 
     | 
    
         
            +
                    # only use mutex on initialization
         
     | 
| 
      
 146 
     | 
    
         
            +
                    MUTEX.synchronize do
         
     | 
| 
      
 147 
     | 
    
         
            +
                      # return if another process initialized while we were waiting
         
     | 
| 
      
 148 
     | 
    
         
            +
                      return @databases if defined?(@databases)
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
                      @databases = config["databases"].map { |id, c| [id.to_sym, Database.new(id, c)] }.to_h
         
     | 
| 
      
 151 
     | 
    
         
            +
                    end
         
     | 
| 
       111 
152 
     | 
    
         
             
                  end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                  @databases
         
     | 
| 
       112 
155 
     | 
    
         
             
                end
         
     | 
| 
       113 
156 
     | 
    
         | 
| 
       114 
157 
     | 
    
         
             
                def primary_database
         
     | 
| 
         @@ -163,6 +206,11 @@ module PgHero 
     | 
|
| 
       163 
206 
     | 
    
         
             
                  end
         
     | 
| 
       164 
207 
     | 
    
         
             
                end
         
     | 
| 
       165 
208 
     | 
    
         | 
| 
      
 209 
     | 
    
         
            +
                # private
         
     | 
| 
      
 210 
     | 
    
         
            +
                def spec_supported?
         
     | 
| 
      
 211 
     | 
    
         
            +
                  ActiveRecord::VERSION::MAJOR >= 6
         
     | 
| 
      
 212 
     | 
    
         
            +
                end
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
       166 
214 
     | 
    
         
             
                private
         
     | 
| 
       167 
215 
     | 
    
         | 
| 
       168 
216 
     | 
    
         
             
                def each_database
         
     |