pghero 2.5.0 → 2.7.2
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 +25 -0
 - data/README.md +1 -7
 - data/app/assets/javascripts/pghero/Chart.bundle.js +4432 -2965
 - data/app/assets/javascripts/pghero/application.js +7 -0
 - data/app/assets/javascripts/pghero/jquery.js +756 -482
 - data/app/assets/javascripts/pghero/nouislider.js +287 -94
 - data/app/assets/stylesheets/pghero/application.css +4 -0
 - data/app/assets/stylesheets/pghero/nouislider.css +22 -11
 - data/app/controllers/pg_hero/home_controller.rb +1 -1
 - data/lib/generators/pghero/templates/config.yml.tt +3 -3
 - data/lib/pghero.rb +10 -5
 - data/lib/pghero/database.rb +3 -2
 - data/lib/pghero/methods/basic.rb +9 -1
 - data/lib/pghero/methods/explain.rb +1 -1
 - data/lib/pghero/methods/query_stats.rb +81 -22
 - data/lib/pghero/methods/space.rb +4 -0
 - data/lib/pghero/methods/system.rb +55 -22
 - data/lib/pghero/methods/users.rb +4 -0
 - data/lib/pghero/version.rb +1 -1
 - data/licenses/LICENSE-chart.js.txt +9 -0
 - data/licenses/LICENSE-chartkick.js.txt +22 -0
 - data/licenses/LICENSE-highlight.js.txt +29 -0
 - data/licenses/LICENSE-jquery.txt +20 -0
 - data/licenses/LICENSE-moment.txt +22 -0
 - data/licenses/LICENSE-nouislider.txt +21 -0
 - metadata +8 -2
 
| 
         @@ -1,4 +1,4 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            /*! nouislider - 14. 
     | 
| 
      
 1 
     | 
    
         
            +
            /*! nouislider - 14.6.1 - 8/17/2020 */
         
     | 
| 
       2 
2 
     | 
    
         
             
            /* Functional styling;
         
     | 
| 
       3 
3 
     | 
    
         
             
             * These styles are required for noUiSlider to function.
         
     | 
| 
       4 
4 
     | 
    
         
             
             * You don't need to change these rules to apply your design.
         
     | 
| 
         @@ -18,7 +18,6 @@ 
     | 
|
| 
       18 
18 
     | 
    
         
             
            }
         
     | 
| 
       19 
19 
     | 
    
         
             
            .noUi-target {
         
     | 
| 
       20 
20 
     | 
    
         
             
              position: relative;
         
     | 
| 
       21 
     | 
    
         
            -
              direction: ltr;
         
     | 
| 
       22 
21 
     | 
    
         
             
            }
         
     | 
| 
       23 
22 
     | 
    
         
             
            .noUi-base,
         
     | 
| 
       24 
23 
     | 
    
         
             
            .noUi-connects {
         
     | 
| 
         @@ -39,7 +38,7 @@ 
     | 
|
| 
       39 
38 
     | 
    
         
             
              position: absolute;
         
     | 
| 
       40 
39 
     | 
    
         
             
              z-index: 1;
         
     | 
| 
       41 
40 
     | 
    
         
             
              top: 0;
         
     | 
| 
       42 
     | 
    
         
            -
               
     | 
| 
      
 41 
     | 
    
         
            +
              right: 0;
         
     | 
| 
       43 
42 
     | 
    
         
             
              -ms-transform-origin: 0 0;
         
     | 
| 
       44 
43 
     | 
    
         
             
              -webkit-transform-origin: 0 0;
         
     | 
| 
       45 
44 
     | 
    
         
             
              -webkit-transform-style: preserve-3d;
         
     | 
| 
         @@ -56,9 +55,9 @@ 
     | 
|
| 
       56 
55 
     | 
    
         
             
            }
         
     | 
| 
       57 
56 
     | 
    
         
             
            /* Offset direction
         
     | 
| 
       58 
57 
     | 
    
         
             
             */
         
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
              left:  
     | 
| 
       61 
     | 
    
         
            -
              right:  
     | 
| 
      
 58 
     | 
    
         
            +
            .noUi-txt-dir-rtl.noUi-horizontal .noUi-origin {
         
     | 
| 
      
 59 
     | 
    
         
            +
              left: 0;
         
     | 
| 
      
 60 
     | 
    
         
            +
              right: auto;
         
     | 
| 
       62 
61 
     | 
    
         
             
            }
         
     | 
| 
       63 
62 
     | 
    
         
             
            /* Give origins 0 height/width so they don't interfere with clicking the
         
     | 
| 
       64 
63 
     | 
    
         
             
             * connect elements.
         
     | 
| 
         @@ -94,7 +93,7 @@ html:not([dir="rtl"]) .noUi-horizontal .noUi-origin { 
     | 
|
| 
       94 
93 
     | 
    
         
             
            .noUi-horizontal .noUi-handle {
         
     | 
| 
       95 
94 
     | 
    
         
             
              width: 34px;
         
     | 
| 
       96 
95 
     | 
    
         
             
              height: 28px;
         
     | 
| 
       97 
     | 
    
         
            -
               
     | 
| 
      
 96 
     | 
    
         
            +
              right: -17px;
         
     | 
| 
       98 
97 
     | 
    
         
             
              top: -6px;
         
     | 
| 
       99 
98 
     | 
    
         
             
            }
         
     | 
| 
       100 
99 
     | 
    
         
             
            .noUi-vertical {
         
     | 
| 
         @@ -103,12 +102,12 @@ html:not([dir="rtl"]) .noUi-horizontal .noUi-origin { 
     | 
|
| 
       103 
102 
     | 
    
         
             
            .noUi-vertical .noUi-handle {
         
     | 
| 
       104 
103 
     | 
    
         
             
              width: 28px;
         
     | 
| 
       105 
104 
     | 
    
         
             
              height: 34px;
         
     | 
| 
       106 
     | 
    
         
            -
               
     | 
| 
      
 105 
     | 
    
         
            +
              right: -6px;
         
     | 
| 
       107 
106 
     | 
    
         
             
              top: -17px;
         
     | 
| 
       108 
107 
     | 
    
         
             
            }
         
     | 
| 
       109 
     | 
    
         
            -
             
     | 
| 
       110 
     | 
    
         
            -
               
     | 
| 
       111 
     | 
    
         
            -
               
     | 
| 
      
 108 
     | 
    
         
            +
            .noUi-txt-dir-rtl.noUi-horizontal .noUi-handle {
         
     | 
| 
      
 109 
     | 
    
         
            +
              left: -17px;
         
     | 
| 
      
 110 
     | 
    
         
            +
              right: auto;
         
     | 
| 
       112 
111 
     | 
    
         
             
            }
         
     | 
| 
       113 
112 
     | 
    
         
             
            /* Styling;
         
     | 
| 
       114 
113 
     | 
    
         
             
             * Giving the connect element a border radius causes issues with using transform: scale
         
     | 
| 
         @@ -297,3 +296,15 @@ html:not([dir="rtl"]) .noUi-horizontal .noUi-handle { 
     | 
|
| 
       297 
296 
     | 
    
         
             
              top: 50%;
         
     | 
| 
       298 
297 
     | 
    
         
             
              right: 120%;
         
     | 
| 
       299 
298 
     | 
    
         
             
            }
         
     | 
| 
      
 299 
     | 
    
         
            +
            .noUi-horizontal .noUi-origin > .noUi-tooltip {
         
     | 
| 
      
 300 
     | 
    
         
            +
              -webkit-transform: translate(50%, 0);
         
     | 
| 
      
 301 
     | 
    
         
            +
              transform: translate(50%, 0);
         
     | 
| 
      
 302 
     | 
    
         
            +
              left: auto;
         
     | 
| 
      
 303 
     | 
    
         
            +
              bottom: 10px;
         
     | 
| 
      
 304 
     | 
    
         
            +
            }
         
     | 
| 
      
 305 
     | 
    
         
            +
            .noUi-vertical .noUi-origin > .noUi-tooltip {
         
     | 
| 
      
 306 
     | 
    
         
            +
              -webkit-transform: translate(0, -18px);
         
     | 
| 
      
 307 
     | 
    
         
            +
              transform: translate(0, -18px);
         
     | 
| 
      
 308 
     | 
    
         
            +
              top: auto;
         
     | 
| 
      
 309 
     | 
    
         
            +
              right: 28px;
         
     | 
| 
      
 310 
     | 
    
         
            +
            }
         
     | 
| 
         @@ -32,14 +32,14 @@ databases: 
     | 
|
| 
       32 
32 
     | 
    
         | 
| 
       33 
33 
     | 
    
         
             
            # Basic authentication
         
     | 
| 
       34 
34 
     | 
    
         
             
            # username: admin
         
     | 
| 
       35 
     | 
    
         
            -
            # password:  
     | 
| 
      
 35 
     | 
    
         
            +
            # password: <%%= ENV["PGHERO_PASSWORD"] %>
         
     | 
| 
       36 
36 
     | 
    
         | 
| 
       37 
37 
     | 
    
         
             
            # Stats database URL (defaults to app database)
         
     | 
| 
       38 
38 
     | 
    
         
             
            # stats_database_url: <%%= ENV["PGHERO_STATS_DATABASE_URL"] %>
         
     | 
| 
       39 
39 
     | 
    
         | 
| 
       40 
40 
     | 
    
         
             
            # AWS configuration (defaults to app AWS config)
         
     | 
| 
       41 
     | 
    
         
            -
            # aws_access_key_id:  
     | 
| 
       42 
     | 
    
         
            -
            # aws_secret_access_key:  
     | 
| 
      
 41 
     | 
    
         
            +
            # aws_access_key_id: <%%= ENV["AWS_ACCESS_KEY_ID"] %>
         
     | 
| 
      
 42 
     | 
    
         
            +
            # aws_secret_access_key: <%%= ENV["AWS_SECRET_ACCESS_KEY"] %>
         
     | 
| 
       43 
43 
     | 
    
         
             
            # aws_region: us-east-1
         
     | 
| 
       44 
44 
     | 
    
         | 
| 
       45 
45 
     | 
    
         
             
            # Filter data from queries (experimental)
         
     | 
    
        data/lib/pghero.rb
    CHANGED
    
    | 
         @@ -43,7 +43,7 @@ module PgHero 
     | 
|
| 
       43 
43 
     | 
    
         
             
              self.long_running_query_sec = (ENV["PGHERO_LONG_RUNNING_QUERY_SEC"] || 60).to_i
         
     | 
| 
       44 
44 
     | 
    
         
             
              self.slow_query_ms = (ENV["PGHERO_SLOW_QUERY_MS"] || 20).to_i
         
     | 
| 
       45 
45 
     | 
    
         
             
              self.slow_query_calls = (ENV["PGHERO_SLOW_QUERY_CALLS"] || 100).to_i
         
     | 
| 
       46 
     | 
    
         
            -
              self.explain_timeout_sec = (ENV["PGHERO_EXPLAIN_TIMEOUT_SEC"] || 10). 
     | 
| 
      
 46 
     | 
    
         
            +
              self.explain_timeout_sec = (ENV["PGHERO_EXPLAIN_TIMEOUT_SEC"] || 10).to_f
         
     | 
| 
       47 
47 
     | 
    
         
             
              self.total_connections_threshold = (ENV["PGHERO_TOTAL_CONNECTIONS_THRESHOLD"] || 500).to_i
         
     | 
| 
       48 
48 
     | 
    
         
             
              self.cache_hit_rate_threshold = 99
         
     | 
| 
       49 
49 
     | 
    
         
             
              self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
         
     | 
| 
         @@ -119,11 +119,16 @@ module PgHero 
     | 
|
| 
       119 
119 
     | 
    
         | 
| 
       120 
120 
     | 
    
         
             
                      if databases.empty?
         
     | 
| 
       121 
121 
     | 
    
         
             
                        databases["primary"] = {
         
     | 
| 
       122 
     | 
    
         
            -
                          "url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config 
     | 
| 
      
 122 
     | 
    
         
            +
                          "url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config
         
     | 
| 
      
 123 
     | 
    
         
            +
                        }
         
     | 
| 
      
 124 
     | 
    
         
            +
                      end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                      if databases.size == 1
         
     | 
| 
      
 127 
     | 
    
         
            +
                        databases.values.first.merge!(
         
     | 
| 
       123 
128 
     | 
    
         
             
                          "db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"],
         
     | 
| 
       124 
129 
     | 
    
         
             
                          "gcp_database_id" => ENV["PGHERO_GCP_DATABASE_ID"],
         
     | 
| 
       125 
130 
     | 
    
         
             
                          "azure_resource_id" => ENV["PGHERO_AZURE_RESOURCE_ID"]
         
     | 
| 
       126 
     | 
    
         
            -
                         
     | 
| 
      
 131 
     | 
    
         
            +
                        )
         
     | 
| 
       127 
132 
     | 
    
         
             
                      end
         
     | 
| 
       128 
133 
     | 
    
         | 
| 
       129 
134 
     | 
    
         
             
                      {
         
     | 
| 
         @@ -191,13 +196,13 @@ module PgHero 
     | 
|
| 
       191 
196 
     | 
    
         
             
                # stats for old databases are not cleaned up since we can't use an index
         
     | 
| 
       192 
197 
     | 
    
         
             
                def clean_query_stats
         
     | 
| 
       193 
198 
     | 
    
         
             
                  each_database do |database|
         
     | 
| 
       194 
     | 
    
         
            -
                     
     | 
| 
      
 199 
     | 
    
         
            +
                    database.clean_query_stats
         
     | 
| 
       195 
200 
     | 
    
         
             
                  end
         
     | 
| 
       196 
201 
     | 
    
         
             
                end
         
     | 
| 
       197 
202 
     | 
    
         | 
| 
       198 
203 
     | 
    
         
             
                def clean_space_stats
         
     | 
| 
       199 
204 
     | 
    
         
             
                  each_database do |database|
         
     | 
| 
       200 
     | 
    
         
            -
                     
     | 
| 
      
 205 
     | 
    
         
            +
                    database.clean_space_stats
         
     | 
| 
       201 
206 
     | 
    
         
             
                  end
         
     | 
| 
       202 
207 
     | 
    
         
             
                end
         
     | 
| 
       203 
208 
     | 
    
         | 
    
        data/lib/pghero/database.rb
    CHANGED
    
    | 
         @@ -55,15 +55,16 @@ module PgHero 
     | 
|
| 
       55 
55 
     | 
    
         
             
                end
         
     | 
| 
       56 
56 
     | 
    
         | 
| 
       57 
57 
     | 
    
         
             
                def explain_timeout_sec
         
     | 
| 
       58 
     | 
    
         
            -
                  (config["explain_timeout_sec"] || PgHero.config["explain_timeout_sec"] || PgHero.explain_timeout_sec). 
     | 
| 
      
 58 
     | 
    
         
            +
                  (config["explain_timeout_sec"] || PgHero.config["explain_timeout_sec"] || PgHero.explain_timeout_sec).to_f
         
     | 
| 
       59 
59 
     | 
    
         
             
                end
         
     | 
| 
       60 
60 
     | 
    
         | 
| 
       61 
61 
     | 
    
         
             
                def long_running_query_sec
         
     | 
| 
       62 
62 
     | 
    
         
             
                  (config["long_running_query_sec"] || PgHero.config["long_running_query_sec"] || PgHero.long_running_query_sec).to_i
         
     | 
| 
       63 
63 
     | 
    
         
             
                end
         
     | 
| 
       64 
64 
     | 
    
         | 
| 
      
 65 
     | 
    
         
            +
                # defaults to 100 megabytes
         
     | 
| 
       65 
66 
     | 
    
         
             
                def index_bloat_bytes
         
     | 
| 
       66 
     | 
    
         
            -
                  (config["index_bloat_bytes"] || PgHero.config["index_bloat_bytes"] ||  
     | 
| 
      
 67 
     | 
    
         
            +
                  (config["index_bloat_bytes"] || PgHero.config["index_bloat_bytes"] || 104857600).to_i
         
     | 
| 
       67 
68 
     | 
    
         
             
                end
         
     | 
| 
       68 
69 
     | 
    
         | 
| 
       69 
70 
     | 
    
         
             
                def aws_access_key_id
         
     | 
    
        data/lib/pghero/methods/basic.rb
    CHANGED
    
    | 
         @@ -18,6 +18,10 @@ module PgHero 
     | 
|
| 
       18 
18 
     | 
    
         
             
                    select_one("SELECT current_database()")
         
     | 
| 
       19 
19 
     | 
    
         
             
                  end
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
      
 21 
     | 
    
         
            +
                  def current_user
         
     | 
| 
      
 22 
     | 
    
         
            +
                    select_one("SELECT current_user")
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
       21 
25 
     | 
    
         
             
                  def server_version
         
     | 
| 
       22 
26 
     | 
    
         
             
                    @server_version ||= select_one("SHOW server_version")
         
     | 
| 
       23 
27 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -38,7 +42,11 @@ module PgHero 
     | 
|
| 
       38 
42 
     | 
    
         
             
                    retries = 0
         
     | 
| 
       39 
43 
     | 
    
         
             
                    begin
         
     | 
| 
       40 
44 
     | 
    
         
             
                      result = conn.select_all(add_source(squish(sql)))
         
     | 
| 
       41 
     | 
    
         
            -
                       
     | 
| 
      
 45 
     | 
    
         
            +
                      if ActiveRecord::VERSION::STRING.to_f >= 6.1
         
     | 
| 
      
 46 
     | 
    
         
            +
                        result = result.map(&:symbolize_keys)
         
     | 
| 
      
 47 
     | 
    
         
            +
                      else
         
     | 
| 
      
 48 
     | 
    
         
            +
                        result = result.map { |row| Hash[row.map { |col, val| [col.to_sym, result.column_types[col].send(:cast_value, val)] }] }
         
     | 
| 
      
 49 
     | 
    
         
            +
                      end
         
     | 
| 
       42 
50 
     | 
    
         
             
                      if filter_data
         
     | 
| 
       43 
51 
     | 
    
         
             
                        query_columns.each do |column|
         
     | 
| 
       44 
52 
     | 
    
         
             
                          result.each do |row|
         
     | 
| 
         @@ -6,7 +6,7 @@ module PgHero 
     | 
|
| 
       6 
6 
     | 
    
         
             
                    explanation = nil
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
                    # use transaction for safety
         
     | 
| 
       9 
     | 
    
         
            -
                    with_transaction(statement_timeout: (explain_timeout_sec * 1000), rollback: true) do
         
     | 
| 
      
 9 
     | 
    
         
            +
                    with_transaction(statement_timeout: (explain_timeout_sec * 1000).round, rollback: true) do
         
     | 
| 
       10 
10 
     | 
    
         
             
                      if (sql.sub(/;\z/, "").include?(";") || sql.upcase.include?("COMMIT")) && !explain_safe?
         
     | 
| 
       11 
11 
     | 
    
         
             
                        raise ActiveRecord::StatementInvalid, "Unsafe statement"
         
     | 
| 
       12 
12 
     | 
    
         
             
                      end
         
     | 
| 
         @@ -56,8 +56,46 @@ module PgHero 
     | 
|
| 
       56 
56 
     | 
    
         
             
                    true
         
     | 
| 
       57 
57 
     | 
    
         
             
                  end
         
     | 
| 
       58 
58 
     | 
    
         | 
| 
       59 
     | 
    
         
            -
                   
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
      
 59 
     | 
    
         
            +
                  # TODO scope by database in PgHero 3.0
         
     | 
| 
      
 60 
     | 
    
         
            +
                  # (add database: database_name to options)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  def reset_query_stats(**options)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    reset_instance_query_stats(**options)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  # resets query stats for the entire instance
         
     | 
| 
      
 66 
     | 
    
         
            +
                  # it's possible to reset stats for a specific
         
     | 
| 
      
 67 
     | 
    
         
            +
                  # database, user or query hash in Postgres 12+
         
     | 
| 
      
 68 
     | 
    
         
            +
                  def reset_instance_query_stats(database: nil, user: nil, query_hash: nil, raise_errors: false)
         
     | 
| 
      
 69 
     | 
    
         
            +
                    if database || user || query_hash
         
     | 
| 
      
 70 
     | 
    
         
            +
                      raise PgHero::Error, "Requires PostgreSQL 12+" if server_version_num < 120000
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                      if database
         
     | 
| 
      
 73 
     | 
    
         
            +
                        database_id = execute("SELECT oid FROM pg_database WHERE datname = #{quote(database)}").first.try(:[], "oid")
         
     | 
| 
      
 74 
     | 
    
         
            +
                        raise PgHero::Error, "Database not found: #{database}" unless database_id
         
     | 
| 
      
 75 
     | 
    
         
            +
                      else
         
     | 
| 
      
 76 
     | 
    
         
            +
                        database_id = 0
         
     | 
| 
      
 77 
     | 
    
         
            +
                      end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                      if user
         
     | 
| 
      
 80 
     | 
    
         
            +
                        user_id = execute("SELECT usesysid FROM pg_user WHERE usename = #{quote(user)}").first.try(:[], "usesysid")
         
     | 
| 
      
 81 
     | 
    
         
            +
                        raise PgHero::Error, "User not found: #{user}" unless user_id
         
     | 
| 
      
 82 
     | 
    
         
            +
                      else
         
     | 
| 
      
 83 
     | 
    
         
            +
                        user_id = 0
         
     | 
| 
      
 84 
     | 
    
         
            +
                      end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                      if query_hash
         
     | 
| 
      
 87 
     | 
    
         
            +
                        query_id = query_hash.to_i
         
     | 
| 
      
 88 
     | 
    
         
            +
                        # may not be needed
         
     | 
| 
      
 89 
     | 
    
         
            +
                        # but not intuitive that all query hashes are reset with 0
         
     | 
| 
      
 90 
     | 
    
         
            +
                        raise PgHero::Error, "Invalid query hash: #{query_hash}" if query_id == 0
         
     | 
| 
      
 91 
     | 
    
         
            +
                      else
         
     | 
| 
      
 92 
     | 
    
         
            +
                        query_id = 0
         
     | 
| 
      
 93 
     | 
    
         
            +
                      end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                      execute("SELECT pg_stat_statements_reset(#{quote(user_id.to_i)}, #{quote(database_id.to_i)}, #{quote(query_id.to_i)})")
         
     | 
| 
      
 96 
     | 
    
         
            +
                    else
         
     | 
| 
      
 97 
     | 
    
         
            +
                      execute("SELECT pg_stat_statements_reset()")
         
     | 
| 
      
 98 
     | 
    
         
            +
                    end
         
     | 
| 
       61 
99 
     | 
    
         
             
                    true
         
     | 
| 
       62 
100 
     | 
    
         
             
                  rescue ActiveRecord::StatementInvalid => e
         
     | 
| 
       63 
101 
     | 
    
         
             
                    raise e if raise_errors
         
     | 
| 
         @@ -104,31 +142,32 @@ module PgHero 
     | 
|
| 
       104 
142 
     | 
    
         
             
                      query_stats[database_id] = query_stats(limit: 1000000, database: database_name)
         
     | 
| 
       105 
143 
     | 
    
         
             
                    end
         
     | 
| 
       106 
144 
     | 
    
         | 
| 
       107 
     | 
    
         
            -
                     
     | 
| 
      
 145 
     | 
    
         
            +
                    query_stats = query_stats.select { |_, v| v.any? }
         
     | 
| 
       108 
146 
     | 
    
         | 
| 
       109 
     | 
    
         
            -
                     
     | 
| 
      
 147 
     | 
    
         
            +
                    # nothing to do
         
     | 
| 
      
 148 
     | 
    
         
            +
                    return if query_stats.empty?
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
                    # use mapping, not query stats here
         
     | 
| 
      
 151 
     | 
    
         
            +
                    # TODO add option for this, and make default in PgHero 3.0
         
     | 
| 
      
 152 
     | 
    
         
            +
                    if false # mapping.size == 1 && server_version_num >= 120000
         
     | 
| 
       110 
153 
     | 
    
         
             
                      query_stats.each do |db_id, db_query_stats|
         
     | 
| 
       111 
     | 
    
         
            -
                        if  
     | 
| 
       112 
     | 
    
         
            -
                           
     | 
| 
       113 
     | 
    
         
            -
             
     | 
| 
       114 
     | 
    
         
            -
             
     | 
| 
       115 
     | 
    
         
            -
             
     | 
| 
       116 
     | 
    
         
            -
             
     | 
| 
       117 
     | 
    
         
            -
             
     | 
| 
       118 
     | 
    
         
            -
             
     | 
| 
       119 
     | 
    
         
            -
                                now,
         
     | 
| 
       120 
     | 
    
         
            -
                                supports_query_hash ? qs[:query_hash] : nil,
         
     | 
| 
       121 
     | 
    
         
            -
                                qs[:user]
         
     | 
| 
       122 
     | 
    
         
            -
                              ]
         
     | 
| 
       123 
     | 
    
         
            -
                            end
         
     | 
| 
       124 
     | 
    
         
            -
             
     | 
| 
       125 
     | 
    
         
            -
                          columns = %w[database query total_time calls captured_at query_hash user]
         
     | 
| 
       126 
     | 
    
         
            -
                          insert_stats("pghero_query_stats", columns, values)
         
     | 
| 
      
 154 
     | 
    
         
            +
                        if reset_query_stats(database: mapping[db_id], raise_errors: raise_errors)
         
     | 
| 
      
 155 
     | 
    
         
            +
                          insert_query_stats(db_id, db_query_stats, now)
         
     | 
| 
      
 156 
     | 
    
         
            +
                        end
         
     | 
| 
      
 157 
     | 
    
         
            +
                      end
         
     | 
| 
      
 158 
     | 
    
         
            +
                    else
         
     | 
| 
      
 159 
     | 
    
         
            +
                      if reset_query_stats(raise_errors: raise_errors)
         
     | 
| 
      
 160 
     | 
    
         
            +
                        query_stats.each do |db_id, db_query_stats|
         
     | 
| 
      
 161 
     | 
    
         
            +
                          insert_query_stats(db_id, db_query_stats, now)
         
     | 
| 
       127 
162 
     | 
    
         
             
                        end
         
     | 
| 
       128 
163 
     | 
    
         
             
                      end
         
     | 
| 
       129 
164 
     | 
    
         
             
                    end
         
     | 
| 
       130 
165 
     | 
    
         
             
                  end
         
     | 
| 
       131 
166 
     | 
    
         | 
| 
      
 167 
     | 
    
         
            +
                  def clean_query_stats
         
     | 
| 
      
 168 
     | 
    
         
            +
                    PgHero::QueryStats.where(database: id).where("captured_at < ?", 14.days.ago).delete_all
         
     | 
| 
      
 169 
     | 
    
         
            +
                  end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
       132 
171 
     | 
    
         
             
                  def slow_queries(query_stats: nil, **options)
         
     | 
| 
       133 
172 
     | 
    
         
             
                    query_stats ||= self.query_stats(options)
         
     | 
| 
       134 
173 
     | 
    
         
             
                    query_stats.select { |q| q[:calls].to_i >= slow_query_calls.to_i && q[:average_time].to_f >= slow_query_ms.to_f }
         
     | 
| 
         @@ -166,14 +205,15 @@ module PgHero 
     | 
|
| 
       166 
205 
     | 
    
         
             
                    if query_stats_enabled?
         
     | 
| 
       167 
206 
     | 
    
         
             
                      limit ||= 100
         
     | 
| 
       168 
207 
     | 
    
         
             
                      sort ||= "total_minutes"
         
     | 
| 
      
 208 
     | 
    
         
            +
                      total_time = server_version_num >= 130000 ? "(total_plan_time + total_exec_time)" : "total_time"
         
     | 
| 
       169 
209 
     | 
    
         
             
                      query = <<-SQL
         
     | 
| 
       170 
210 
     | 
    
         
             
                        WITH query_stats AS (
         
     | 
| 
       171 
211 
     | 
    
         
             
                          SELECT
         
     | 
| 
       172 
212 
     | 
    
         
             
                            LEFT(query, 10000) AS query,
         
     | 
| 
       173 
213 
     | 
    
         
             
                            #{supports_query_hash? ? "queryid" : "md5(query)"} AS query_hash,
         
     | 
| 
       174 
214 
     | 
    
         
             
                            rolname AS user,
         
     | 
| 
       175 
     | 
    
         
            -
                            (total_time / 1000 / 60) AS total_minutes,
         
     | 
| 
       176 
     | 
    
         
            -
                            (total_time / calls) AS average_time,
         
     | 
| 
      
 215 
     | 
    
         
            +
                            (#{total_time} / 1000 / 60) AS total_minutes,
         
     | 
| 
      
 216 
     | 
    
         
            +
                            (#{total_time} / calls) AS average_time,
         
     | 
| 
       177 
217 
     | 
    
         
             
                            calls
         
     | 
| 
       178 
218 
     | 
    
         
             
                          FROM
         
     | 
| 
       179 
219 
     | 
    
         
             
                            pg_stat_statements
         
     | 
| 
         @@ -182,6 +222,7 @@ module PgHero 
     | 
|
| 
       182 
222 
     | 
    
         
             
                          INNER JOIN
         
     | 
| 
       183 
223 
     | 
    
         
             
                            pg_roles ON pg_roles.oid = pg_stat_statements.userid
         
     | 
| 
       184 
224 
     | 
    
         
             
                          WHERE
         
     | 
| 
      
 225 
     | 
    
         
            +
                            calls > 0 AND
         
     | 
| 
       185 
226 
     | 
    
         
             
                            pg_database.datname = #{database ? quote(database) : "current_database()"}
         
     | 
| 
       186 
227 
     | 
    
         
             
                            #{query_hash ? "AND queryid = #{quote(query_hash)}" : nil}
         
     | 
| 
       187 
228 
     | 
    
         
             
                        )
         
     | 
| 
         @@ -285,6 +326,24 @@ module PgHero 
     | 
|
| 
       285 
326 
     | 
    
         
             
                  def normalize_query(query)
         
     | 
| 
       286 
327 
     | 
    
         
             
                    squish(query.to_s.gsub(/\?(, ?\?)+/, "?").gsub(/\/\*.+?\*\//, ""))
         
     | 
| 
       287 
328 
     | 
    
         
             
                  end
         
     | 
| 
      
 329 
     | 
    
         
            +
             
     | 
| 
      
 330 
     | 
    
         
            +
                  def insert_query_stats(db_id, db_query_stats, now)
         
     | 
| 
      
 331 
     | 
    
         
            +
                    values =
         
     | 
| 
      
 332 
     | 
    
         
            +
                      db_query_stats.map do |qs|
         
     | 
| 
      
 333 
     | 
    
         
            +
                        [
         
     | 
| 
      
 334 
     | 
    
         
            +
                          db_id,
         
     | 
| 
      
 335 
     | 
    
         
            +
                          qs[:query],
         
     | 
| 
      
 336 
     | 
    
         
            +
                          qs[:total_minutes] * 60 * 1000,
         
     | 
| 
      
 337 
     | 
    
         
            +
                          qs[:calls],
         
     | 
| 
      
 338 
     | 
    
         
            +
                          now,
         
     | 
| 
      
 339 
     | 
    
         
            +
                          supports_query_hash? ? qs[:query_hash] : nil,
         
     | 
| 
      
 340 
     | 
    
         
            +
                          qs[:user]
         
     | 
| 
      
 341 
     | 
    
         
            +
                        ]
         
     | 
| 
      
 342 
     | 
    
         
            +
                      end
         
     | 
| 
      
 343 
     | 
    
         
            +
             
     | 
| 
      
 344 
     | 
    
         
            +
                    columns = %w[database query total_time calls captured_at query_hash user]
         
     | 
| 
      
 345 
     | 
    
         
            +
                    insert_stats("pghero_query_stats", columns, values)
         
     | 
| 
      
 346 
     | 
    
         
            +
                  end
         
     | 
| 
       288 
347 
     | 
    
         
             
                end
         
     | 
| 
       289 
348 
     | 
    
         
             
              end
         
     | 
| 
       290 
349 
     | 
    
         
             
            end
         
     | 
    
        data/lib/pghero/methods/space.rb
    CHANGED
    
    | 
         @@ -129,6 +129,10 @@ module PgHero 
     | 
|
| 
       129 
129 
     | 
    
         
             
                    insert_stats("pghero_space_stats", columns, values) if values.any?
         
     | 
| 
       130 
130 
     | 
    
         
             
                  end
         
     | 
| 
       131 
131 
     | 
    
         | 
| 
      
 132 
     | 
    
         
            +
                  def clean_space_stats
         
     | 
| 
      
 133 
     | 
    
         
            +
                    PgHero::SpaceStats.where(database: id).where("captured_at < ?", 90.days.ago).delete_all
         
     | 
| 
      
 134 
     | 
    
         
            +
                  end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
       132 
136 
     | 
    
         
             
                  def space_stats_enabled?
         
     | 
| 
       133 
137 
     | 
    
         
             
                    table_exists?("pghero_space_stats")
         
     | 
| 
       134 
138 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -5,7 +5,7 @@ module PgHero 
     | 
|
| 
       5 
5 
     | 
    
         
             
                    !system_stats_provider.nil?
         
     | 
| 
       6 
6 
     | 
    
         
             
                  end
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
                  # TODO  
     | 
| 
      
 8 
     | 
    
         
            +
                  # TODO remove defined checks in 3.0
         
     | 
| 
       9 
9 
     | 
    
         
             
                  def system_stats_provider
         
     | 
| 
       10 
10 
     | 
    
         
             
                    if aws_db_instance_identifier && (defined?(Aws) || defined?(AWS))
         
     | 
| 
       11 
11 
     | 
    
         
             
                      :aws
         
     | 
| 
         @@ -133,7 +133,7 @@ module PgHero 
     | 
|
| 
       133 
133 
     | 
    
         
             
                  private
         
     | 
| 
       134 
134 
     | 
    
         | 
| 
       135 
135 
     | 
    
         
             
                  def gcp_stats(metric_name, duration: nil, period: nil, offset: nil, series: false)
         
     | 
| 
       136 
     | 
    
         
            -
                    require "google/cloud/monitoring"
         
     | 
| 
      
 136 
     | 
    
         
            +
                    require "google/cloud/monitoring/v3"
         
     | 
| 
       137 
137 
     | 
    
         | 
| 
       138 
138 
     | 
    
         
             
                    # TODO DRY with RDS stats
         
     | 
| 
       139 
139 
     | 
    
         
             
                    duration = (duration || 1.hour).to_i
         
     | 
| 
         @@ -142,30 +142,63 @@ module PgHero 
     | 
|
| 
       142 
142 
     | 
    
         
             
                    end_time = Time.at(((Time.now - offset).to_f / period).ceil * period)
         
     | 
| 
       143 
143 
     | 
    
         
             
                    start_time = end_time - duration
         
     | 
| 
       144 
144 
     | 
    
         | 
| 
       145 
     | 
    
         
            -
                    client = Google::Cloud::Monitoring::Metric.new
         
     | 
| 
       146 
     | 
    
         
            -
             
     | 
| 
       147 
     | 
    
         
            -
                    interval = Google::Monitoring::V3::TimeInterval.new
         
     | 
| 
       148 
     | 
    
         
            -
                    interval.end_time = Google::Protobuf::Timestamp.new(seconds: end_time.to_i)
         
     | 
| 
       149 
     | 
    
         
            -
                    # subtract period to make sure we get first data point
         
     | 
| 
       150 
     | 
    
         
            -
                    interval.start_time = Google::Protobuf::Timestamp.new(seconds: (start_time - period).to_i)
         
     | 
| 
       151 
     | 
    
         
            -
             
     | 
| 
       152 
     | 
    
         
            -
                    aggregation = Google::Monitoring::V3::Aggregation.new
         
     | 
| 
       153 
     | 
    
         
            -
                    # may be better to use ALIGN_NEXT_OLDER for space stats to show most recent data point
         
     | 
| 
       154 
     | 
    
         
            -
                    # stick with average for now to match AWS
         
     | 
| 
       155 
     | 
    
         
            -
                    aggregation.per_series_aligner = Google::Monitoring::V3::Aggregation::Aligner::ALIGN_MEAN
         
     | 
| 
       156 
     | 
    
         
            -
                    aggregation.alignment_period = period
         
     | 
| 
       157 
     | 
    
         
            -
             
     | 
| 
       158 
145 
     | 
    
         
             
                    # validate input since we need to interpolate below
         
     | 
| 
       159 
146 
     | 
    
         
             
                    raise Error, "Invalid metric name" unless metric_name =~ /\A[a-z\/_]+\z/i
         
     | 
| 
       160 
147 
     | 
    
         
             
                    raise Error, "Invalid database id" unless gcp_database_id =~ /\A[a-z\-:]+\z/i
         
     | 
| 
       161 
148 
     | 
    
         | 
| 
       162 
     | 
    
         
            -
                     
     | 
| 
       163 
     | 
    
         
            -
             
     | 
| 
       164 
     | 
    
         
            -
             
     | 
| 
       165 
     | 
    
         
            -
             
     | 
| 
       166 
     | 
    
         
            -
             
     | 
| 
       167 
     | 
    
         
            -
             
     | 
| 
       168 
     | 
    
         
            -
                     
     | 
| 
      
 149 
     | 
    
         
            +
                    # we handle three situations:
         
     | 
| 
      
 150 
     | 
    
         
            +
                    # 1. google-cloud-monitoring-v3
         
     | 
| 
      
 151 
     | 
    
         
            +
                    # 2. google-cloud-monitoring >= 1
         
     | 
| 
      
 152 
     | 
    
         
            +
                    # 3. google-cloud-monitoring < 1
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                    # for situations 1 and 2
         
     | 
| 
      
 155 
     | 
    
         
            +
                    # Google::Cloud::Monitoring.metric_service is documented
         
     | 
| 
      
 156 
     | 
    
         
            +
                    # but doesn't work for situation 1
         
     | 
| 
      
 157 
     | 
    
         
            +
                    if defined?(Google::Cloud::Monitoring::V3::MetricService::Client)
         
     | 
| 
      
 158 
     | 
    
         
            +
                      client = Google::Cloud::Monitoring::V3::MetricService::Client.new
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                      interval = Google::Cloud::Monitoring::V3::TimeInterval.new
         
     | 
| 
      
 161 
     | 
    
         
            +
                      interval.end_time = Google::Protobuf::Timestamp.new(seconds: end_time.to_i)
         
     | 
| 
      
 162 
     | 
    
         
            +
                      # subtract period to make sure we get first data point
         
     | 
| 
      
 163 
     | 
    
         
            +
                      interval.start_time = Google::Protobuf::Timestamp.new(seconds: (start_time - period).to_i)
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
                      aggregation = Google::Cloud::Monitoring::V3::Aggregation.new
         
     | 
| 
      
 166 
     | 
    
         
            +
                      # may be better to use ALIGN_NEXT_OLDER for space stats to show most recent data point
         
     | 
| 
      
 167 
     | 
    
         
            +
                      # stick with average for now to match AWS
         
     | 
| 
      
 168 
     | 
    
         
            +
                      aggregation.per_series_aligner = Google::Cloud::Monitoring::V3::Aggregation::Aligner::ALIGN_MEAN
         
     | 
| 
      
 169 
     | 
    
         
            +
                      aggregation.alignment_period = period
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                      results = client.list_time_series({
         
     | 
| 
      
 172 
     | 
    
         
            +
                        name: "projects/#{gcp_database_id.split(":").first}",
         
     | 
| 
      
 173 
     | 
    
         
            +
                        filter: "metric.type = \"cloudsql.googleapis.com/database/#{metric_name}\" AND resource.label.database_id = \"#{gcp_database_id}\"",
         
     | 
| 
      
 174 
     | 
    
         
            +
                        interval: interval,
         
     | 
| 
      
 175 
     | 
    
         
            +
                        view: Google::Cloud::Monitoring::V3::ListTimeSeriesRequest::TimeSeriesView::FULL,
         
     | 
| 
      
 176 
     | 
    
         
            +
                        aggregation: aggregation
         
     | 
| 
      
 177 
     | 
    
         
            +
                      })
         
     | 
| 
      
 178 
     | 
    
         
            +
                    else
         
     | 
| 
      
 179 
     | 
    
         
            +
                      require "google/cloud/monitoring"
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                      client = Google::Cloud::Monitoring::Metric.new
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                      interval = Google::Monitoring::V3::TimeInterval.new
         
     | 
| 
      
 184 
     | 
    
         
            +
                      interval.end_time = Google::Protobuf::Timestamp.new(seconds: end_time.to_i)
         
     | 
| 
      
 185 
     | 
    
         
            +
                      # subtract period to make sure we get first data point
         
     | 
| 
      
 186 
     | 
    
         
            +
                      interval.start_time = Google::Protobuf::Timestamp.new(seconds: (start_time - period).to_i)
         
     | 
| 
      
 187 
     | 
    
         
            +
             
     | 
| 
      
 188 
     | 
    
         
            +
                      aggregation = Google::Monitoring::V3::Aggregation.new
         
     | 
| 
      
 189 
     | 
    
         
            +
                      # may be better to use ALIGN_NEXT_OLDER for space stats to show most recent data point
         
     | 
| 
      
 190 
     | 
    
         
            +
                      # stick with average for now to match AWS
         
     | 
| 
      
 191 
     | 
    
         
            +
                      aggregation.per_series_aligner = Google::Monitoring::V3::Aggregation::Aligner::ALIGN_MEAN
         
     | 
| 
      
 192 
     | 
    
         
            +
                      aggregation.alignment_period = period
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                      results = client.list_time_series(
         
     | 
| 
      
 195 
     | 
    
         
            +
                        "projects/#{gcp_database_id.split(":").first}",
         
     | 
| 
      
 196 
     | 
    
         
            +
                        "metric.type = \"cloudsql.googleapis.com/database/#{metric_name}\" AND resource.label.database_id = \"#{gcp_database_id}\"",
         
     | 
| 
      
 197 
     | 
    
         
            +
                        interval,
         
     | 
| 
      
 198 
     | 
    
         
            +
                        Google::Monitoring::V3::ListTimeSeriesRequest::TimeSeriesView::FULL,
         
     | 
| 
      
 199 
     | 
    
         
            +
                        aggregation: aggregation
         
     | 
| 
      
 200 
     | 
    
         
            +
                      )
         
     | 
| 
      
 201 
     | 
    
         
            +
                    end
         
     | 
| 
       169 
202 
     | 
    
         | 
| 
       170 
203 
     | 
    
         
             
                    data = {}
         
     | 
| 
       171 
204 
     | 
    
         
             
                    result = results.first
         
     |