gitlab-exporter 17.0.2 → 17.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f38401d83375e243787b0f692035d11cc9cc9e5a3bde34ca9d468261be12a41
4
- data.tar.gz: 4f8f1dbed6854da6003209745214b6687f405145b79c5dc2a0ee06d0bbedec4f
3
+ metadata.gz: 50fbeb1ab763a918c65648dcb9d400b724b15c46ff74512a226291d8d1b47a32
4
+ data.tar.gz: e80d1246a12289f9a117f91c4821bb779849b2bb3df22bdfe16063074031c804
5
5
  SHA512:
6
- metadata.gz: 7c88e061a15dcc9f640c29a910de6d81eedb345c113bce789b08a44a252ddc708bdb082e5aa7ee881eb383dcf51c72d34fd7ec8c00353fe210e0b97915e05ab4
7
- data.tar.gz: 9a26c277997d178c65607aadbba2b82b931bc1375a1f732c35665639cfb1a404869409272f85b640df8a5f101cf25eddee985c1f20dbb8d75f6904aaffd1e7c6
6
+ metadata.gz: 9d7174c179fc0cbb08c5caea1171be487a5005e1aa85817dc8f8a1495beaa92e03195a6bac17f9e60196649ded0bb8c0e18f42cfd797920e3cc2203cddfb4581
7
+ data.tar.gz: b1867eb94ccdd9fd44f79ac0e1278bb184c9923bb4e65cb4575c87dd3ec68ea9f6f753c27d2266c5f9a9510efa9d3873bd9702c48005310d97bb3394e26878c2
data/Gemfile.lock CHANGED
@@ -1,11 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab-exporter (17.0.2)
4
+ gitlab-exporter (17.1.1)
5
5
  base64 (= 0.3.0)
6
6
  connection_pool (= 2.5.5)
7
7
  deep_merge (~> 1.2.2)
8
- faraday (= 2.14.3)
8
+ faraday (= 2.14.4)
9
9
  pg (= 1.6.3)
10
10
  quantile (= 0.2.1)
11
11
  redis (= 4.8.1)
@@ -21,7 +21,7 @@ GEM
21
21
  connection_pool (2.5.5)
22
22
  deep_merge (1.2.2)
23
23
  diff-lcs (1.5.0)
24
- faraday (2.14.3)
24
+ faraday (2.14.4)
25
25
  faraday-net_http (>= 2.0, < 3.5)
26
26
  json
27
27
  logger
@@ -57,8 +57,8 @@ GEM
57
57
  diff-lcs (>= 1.2.0, < 2.0)
58
58
  rspec-support (~> 3.12.0)
59
59
  rspec-support (3.12.0)
60
- rubocop (1.89.0)
61
- json (~> 2.3)
60
+ rubocop (1.91.0)
61
+ json (>= 2.3)
62
62
  language_server-protocol (~> 3.17.0.2)
63
63
  lint_roller (~> 1.1.0)
64
64
  parallel (>= 1.10)
@@ -91,4 +91,4 @@ DEPENDENCIES
91
91
  rubocop (~> 1.0)
92
92
 
93
93
  BUNDLED WITH
94
- 4.0.10
94
+ 4.0.18
data/README.md CHANGED
@@ -32,6 +32,11 @@ metrics.
32
32
  (table bloat) and keys `bloat_ratio bloat_size extra_size real_size` (see below)
33
33
  * [Remote mirrors](lib/gitlab_exporter/database/remote_mirrors.rb) --
34
34
  `project_remote_mirror_last_successful_update_time_seconds`, `project_remote_mirror_last_update_time_seconds`
35
+ * [Geo node status](lib/gitlab_exporter/database/geo_node_status.rb) -- Geo metrics from
36
+ `geo_node_statuses`, emitted as `geo_<status_field>` gauges with trailing `_count` and
37
+ `_total` suffixes removed, matching Rails' exported metric fields and `name`/`url` labels.
38
+ Usage-data counters are excluded. Enable the `geo_node_status` probe in the `database`
39
+ endpoint configuration.
35
40
  * [Zoekt](lib/gitlab_exporter/database/zoekt.rb) --
36
41
  `search_zoekt_task_processing_queue_size`
37
42
  1. Git
@@ -77,6 +77,9 @@ probes:
77
77
  <<: *db_common_opts
78
78
  project_ids:
79
79
  - 1
80
+ geo_node_status:
81
+ class_name: Database::GeoNodeStatusProber
82
+ <<: *db_common
80
83
  zoekt:
81
84
  class_name: Database::ZoektProber
82
85
  <<: *db_common
@@ -28,7 +28,7 @@ Gem::Specification.new do |s|
28
28
  s.add_runtime_dependency "base64", "0.3.0"
29
29
  s.add_runtime_dependency "connection_pool", "2.5.5"
30
30
  s.add_runtime_dependency "deep_merge", "~> 1.2.2"
31
- s.add_runtime_dependency "faraday", "2.14.3"
31
+ s.add_runtime_dependency "faraday", "2.14.4"
32
32
  s.add_runtime_dependency "pg", "1.6.3"
33
33
  s.add_runtime_dependency "quantile", "0.2.1"
34
34
  s.add_runtime_dependency "redis", "4.8.1"
@@ -0,0 +1,116 @@
1
+ module GitLab
2
+ module Exporter
3
+ module Database
4
+ # A helper class to collect Geo node status metrics.
5
+ class GeoNodeStatusCollector < Base
6
+ QUERY = <<~SQL.freeze
7
+ SELECT
8
+ geo_nodes.name AS geo_node_name,
9
+ geo_node_statuses.db_replication_lag_seconds,
10
+ geo_node_statuses.replication_slots_count,
11
+ geo_node_statuses.replication_slots_used_count,
12
+ geo_node_statuses.replication_slots_max_retained_wal_bytes,
13
+ geo_node_statuses.last_event_id,
14
+ geo_node_statuses.cursor_last_event_id,
15
+ geo_node_statuses.status,
16
+ EXTRACT(EPOCH FROM geo_node_statuses.last_event_date)::bigint AS last_event_timestamp,
17
+ EXTRACT(EPOCH FROM geo_node_statuses.cursor_last_event_date)::bigint AS cursor_last_event_timestamp,
18
+ EXTRACT(EPOCH FROM geo_node_statuses.last_successful_status_check_at)::bigint
19
+ AS last_successful_status_check_timestamp
20
+ FROM geo_node_statuses
21
+ INNER JOIN geo_nodes ON geo_nodes.id = geo_node_statuses.geo_node_id
22
+ WHERE geo_nodes.enabled = TRUE
23
+ SQL
24
+
25
+ def run
26
+ with_connection_pool do |conn|
27
+ conn.exec(QUERY)
28
+ end
29
+ rescue PG::UndefinedTable, PG::UndefinedColumn
30
+ nil
31
+ end
32
+ end
33
+
34
+ # The prober which is called when gathering metrics.
35
+ class GeoNodeStatusProber
36
+ METRIC_PREFIX = "geo_".freeze
37
+ COUNT_SUFFIX = /_(count|total)\z/
38
+ METRIC_ALIASES = { "project_repositories_count" => "repositories_count" }.freeze
39
+ COLUMN_METRICS = %w[
40
+ db_replication_lag_seconds
41
+ replication_slots_count
42
+ replication_slots_used_count
43
+ replication_slots_max_retained_wal_bytes
44
+ last_event_id
45
+ cursor_last_event_id
46
+ ].freeze
47
+ TIMESTAMP_METRICS = %w[
48
+ last_event_timestamp
49
+ cursor_last_event_timestamp
50
+ last_successful_status_check_timestamp
51
+ ].freeze
52
+ REPLICATOR_METRIC_SUFFIXES = %w[
53
+ _count
54
+ _checksum_total_count
55
+ _checksummed_count
56
+ _checksum_failed_count
57
+ _synced_count
58
+ _failed_count
59
+ _registry_count
60
+ _verification_total_count
61
+ _verified_count
62
+ _verification_failed_count
63
+ _oldest_unsynced_time
64
+ ].freeze
65
+ NON_PROMETHEUS_STATUS_FIELDS = %w[projects_count].freeze
66
+
67
+ def initialize(metrics: PrometheusMetrics.new, **opts)
68
+ @metrics = metrics
69
+ @collector = opts[:collector] || GeoNodeStatusCollector.new(**opts)
70
+ end
71
+
72
+ def probe_db
73
+ @collector.run.to_a.each do |row|
74
+ labels = {
75
+ name: row["geo_node_name"],
76
+ url: row["geo_node_name"]
77
+ }
78
+
79
+ row.fetch("status", {}).to_h.each do |name, value|
80
+ add_metric(name, value, labels) if value.is_a?(Integer) && prometheus_metric?(name)
81
+ end
82
+
83
+ (COLUMN_METRICS + TIMESTAMP_METRICS).each do |name|
84
+ add_metric(name, row[name], labels) if row[name].is_a?(Integer)
85
+ end
86
+ end
87
+
88
+ self
89
+ rescue PG::ConnectionBad
90
+ self
91
+ end
92
+
93
+ def write_to(target)
94
+ target.write(@metrics.to_s)
95
+ end
96
+
97
+ private
98
+
99
+ def prometheus_metric?(name)
100
+ name = name.to_s
101
+ return false if NON_PROMETHEUS_STATUS_FIELDS.include?(name)
102
+ return false if COLUMN_METRICS.include?(name)
103
+
104
+ REPLICATOR_METRIC_SUFFIXES.any? { |suffix| name.end_with?(suffix) }
105
+ end
106
+
107
+ def add_metric(name, value, labels)
108
+ name = METRIC_ALIASES.fetch(name.to_s, name.to_s)
109
+ metric_name = "#{METRIC_PREFIX}#{name.sub(COUNT_SUFFIX, '')}"
110
+ PrometheusMetrics.describe(metric_name, "Geo node status metric", "gauge")
111
+ @metrics.add(metric_name, value, **labels)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -8,6 +8,7 @@ module GitLab
8
8
  autoload :RowCountProber, "gitlab_exporter/database/row_count"
9
9
  autoload :BloatProber, "gitlab_exporter/database/bloat"
10
10
  autoload :RemoteMirrorsProber, "gitlab_exporter/database/remote_mirrors"
11
+ autoload :GeoNodeStatusProber, "gitlab_exporter/database/geo_node_status"
11
12
  autoload :PgSequencesProber, "gitlab_exporter/database/pg_sequences"
12
13
  autoload :ZoektProber, "gitlab_exporter/database/zoekt"
13
14
  end
@@ -1,5 +1,5 @@
1
1
  module GitLab
2
2
  module Exporter
3
- VERSION = "17.0.2".freeze
3
+ VERSION = "17.1.1".freeze
4
4
  end
5
5
  end
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+ require "yaml"
3
+
4
+ describe "config/gitlab-exporter.yml.example" do
5
+ let(:config_path) { File.expand_path("../config/gitlab-exporter.yml.example", __dir__) }
6
+ let(:config) { YAML.safe_load_file(config_path, aliases: true) }
7
+
8
+ it "parses as valid YAML with aliases" do
9
+ expect { config }.not_to raise_error
10
+ expect(config).to be_a(Hash)
11
+ end
12
+
13
+ describe "database probes" do
14
+ let(:database_probes) { config.dig("probes", "database") }
15
+
16
+ it "configures geo_node_status directly under probes.database" do
17
+ expect(database_probes).to include("geo_node_status")
18
+ expect(database_probes["geo_node_status"]).to include(
19
+ "class_name" => "Database::GeoNodeStatusProber",
20
+ "methods" => ["probe_db"]
21
+ )
22
+ end
23
+
24
+ it "keeps remote_mirrors and geo_node_status as sibling probes without nested leakage" do
25
+ expect(database_probes["remote_mirrors"]).not_to have_key("geo_node_status")
26
+ expect(database_probes.keys).to include(
27
+ "ci_builds",
28
+ "tuple_stats",
29
+ "rows_count",
30
+ "remote_mirrors",
31
+ "geo_node_status",
32
+ "zoekt"
33
+ )
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,210 @@
1
+ require "spec_helper"
2
+ require "gitlab_exporter/database/geo_node_status"
3
+
4
+ describe GitLab::Exporter::Database::GeoNodeStatusCollector do
5
+ let(:connection_pool) { double("connection pool") }
6
+ let(:connection) { double("connection") }
7
+ let(:results) { [{ "geo_node_name" => "secondary", "status" => { "projects_count" => 12 } }] }
8
+
9
+ subject(:collector) { described_class.new(connection_string: "host=localhost") }
10
+
11
+ before do
12
+ allow(collector).to receive(:connection_pool).and_return(connection_pool)
13
+ allow(connection_pool).to receive(:with).and_yield(connection)
14
+ end
15
+
16
+ describe "QUERY" do
17
+ it "selects all required status columns, timestamps, and node name" do
18
+ expect(described_class::QUERY).to include("geo_nodes.name AS geo_node_name")
19
+ expect(described_class::QUERY).to include("geo_node_statuses.db_replication_lag_seconds")
20
+ expect(described_class::QUERY).to include("geo_node_statuses.replication_slots_count")
21
+ expect(described_class::QUERY).to include("geo_node_statuses.replication_slots_used_count")
22
+ expect(described_class::QUERY).to include("geo_node_statuses.replication_slots_max_retained_wal_bytes")
23
+ expect(described_class::QUERY).to include("geo_node_statuses.last_event_id")
24
+ expect(described_class::QUERY).to include("geo_node_statuses.cursor_last_event_id")
25
+ expect(described_class::QUERY).to include("geo_node_statuses.status")
26
+ expect(described_class::QUERY).to include(
27
+ "EXTRACT(EPOCH FROM geo_node_statuses.last_event_date)::bigint AS last_event_timestamp"
28
+ )
29
+ expect(described_class::QUERY).to include(
30
+ "EXTRACT(EPOCH FROM geo_node_statuses.cursor_last_event_date)::bigint AS cursor_last_event_timestamp"
31
+ )
32
+ expect(described_class::QUERY).to include(
33
+ "EXTRACT(EPOCH FROM geo_node_statuses.last_successful_status_check_at)::bigint"
34
+ )
35
+ end
36
+ end
37
+
38
+ describe "#run" do
39
+ it "queries enabled Geo nodes and returns their status data" do
40
+ expect(connection).to receive(:exec).with(described_class::QUERY).and_return(results)
41
+
42
+ expect(collector.run).to eq(results)
43
+ end
44
+
45
+ it "returns nil when Geo tables are unavailable" do
46
+ allow(connection).to receive(:exec).and_raise(PG::UndefinedTable)
47
+
48
+ expect(collector.run).to be_nil
49
+ end
50
+ end
51
+ end
52
+
53
+ describe GitLab::Exporter::Database::GeoNodeStatusProber do
54
+ let(:writer) { StringIO.new }
55
+ let(:metrics) { GitLab::Exporter::PrometheusMetrics.new(include_timestamp: false) }
56
+ let(:collector) { double(GitLab::Exporter::Database::GeoNodeStatusCollector, run: results) }
57
+ let(:results) do
58
+ [
59
+ {
60
+ "geo_node_id" => 7,
61
+ "geo_node_name" => "secondary",
62
+ "db_replication_lag_seconds" => 4,
63
+ "replication_slots_count" => 2,
64
+ "replication_slots_used_count" => 1,
65
+ "replication_slots_max_retained_wal_bytes" => 1024,
66
+ "last_event_id" => 100,
67
+ "cursor_last_event_id" => 98,
68
+ "last_event_timestamp" => 1_750_000_100,
69
+ "cursor_last_event_timestamp" => 1_750_000_090,
70
+ "last_successful_status_check_timestamp" => 1_750_000_000,
71
+ "status" => {
72
+ "projects_count" => 12,
73
+ "project_repositories_count" => 21,
74
+ "projects_verification_total_count" => 8,
75
+ "projects_synced_count" => 19,
76
+ "projects_failed_count" => 2,
77
+ "git_fetch_event_count_weekly" => 100,
78
+ "container_repositories_replication_enabled" => true,
79
+ "status_message" => "Healthy"
80
+ }
81
+ }
82
+ ]
83
+ end
84
+
85
+ subject(:prober) { described_class.new(metrics: metrics, collector: collector) }
86
+
87
+ describe "constants and data sources" do
88
+ it "defines the expected COLUMN_METRICS corresponding to table columns" do
89
+ expect(described_class::COLUMN_METRICS).to contain_exactly(
90
+ "db_replication_lag_seconds",
91
+ "replication_slots_count",
92
+ "replication_slots_used_count",
93
+ "replication_slots_max_retained_wal_bytes",
94
+ "last_event_id",
95
+ "cursor_last_event_id"
96
+ )
97
+ end
98
+
99
+ it "defines the expected TIMESTAMP_METRICS corresponding to epoch extractions" do
100
+ expect(described_class::TIMESTAMP_METRICS).to contain_exactly(
101
+ "last_event_timestamp",
102
+ "cursor_last_event_timestamp",
103
+ "last_successful_status_check_timestamp"
104
+ )
105
+ end
106
+
107
+ it "does not define CORE_STATUS_METRICS" do
108
+ expect(described_class.const_defined?(:CORE_STATUS_METRICS)).to be false
109
+ end
110
+
111
+ it "does not emit column metrics if placed only inside status jsonb" do
112
+ allow(collector).to receive(:run).and_return([
113
+ {
114
+ "geo_node_name" => "secondary",
115
+ "status" => {
116
+ "db_replication_lag_seconds" => 4,
117
+ "replication_slots_count" => 2,
118
+ "replication_slots_used_count" => 1,
119
+ "replication_slots_max_retained_wal_bytes" => 1024,
120
+ "last_event_id" => 100,
121
+ "cursor_last_event_id" => 98
122
+ }
123
+ }
124
+ ])
125
+
126
+ prober.probe_db
127
+ prober.write_to(writer)
128
+
129
+ expect(writer.string).not_to include("geo_db_replication_lag_seconds")
130
+ expect(writer.string).not_to include("geo_replication_slots")
131
+ expect(writer.string).not_to include("geo_replication_slots_used")
132
+ expect(writer.string).not_to include("geo_replication_slots_max_retained_wal_bytes")
133
+ expect(writer.string).not_to include("geo_last_event_id")
134
+ expect(writer.string).not_to include("geo_cursor_last_event_id")
135
+ end
136
+
137
+ it "does not emit replicator metrics if placed only in row columns outside status jsonb" do
138
+ allow(collector).to receive(:run).and_return([
139
+ {
140
+ "geo_node_name" => "secondary",
141
+ "project_repositories_count" => 21,
142
+ "projects_synced_count" => 19,
143
+ "status" => {}
144
+ }
145
+ ])
146
+
147
+ prober.probe_db
148
+ prober.write_to(writer)
149
+
150
+ expect(writer.string).not_to include("geo_repositories")
151
+ expect(writer.string).not_to include("geo_projects_synced")
152
+ end
153
+ end
154
+
155
+ describe "#probe_db" do
156
+ it "exports integer Geo metrics with Rails-compatible names and site labels" do
157
+ prober.probe_db
158
+ prober.write_to(writer)
159
+ labels = 'name="secondary",url="secondary"'
160
+
161
+ expect(writer.string).to include("geo_db_replication_lag_seconds{#{labels}} 4")
162
+ expect(writer.string).to include("# TYPE geo_db_replication_lag_seconds gauge")
163
+ expect(writer.string).to include("geo_replication_slots{#{labels}} 2")
164
+ expect(writer.string).to include("# TYPE geo_replication_slots gauge")
165
+ expect(writer.string).to include("geo_replication_slots_used{#{labels}} 1")
166
+ expect(writer.string).to include("# TYPE geo_replication_slots_used gauge")
167
+ expect(writer.string).to include("geo_replication_slots_max_retained_wal_bytes{#{labels}} 1024")
168
+ expect(writer.string).to include("# TYPE geo_replication_slots_max_retained_wal_bytes gauge")
169
+ expect(writer.string).to include("geo_last_event_id{#{labels}} 100")
170
+ expect(writer.string).to include("geo_cursor_last_event_id{#{labels}} 98")
171
+ expect(writer.string).to include("geo_last_event_timestamp{#{labels}} 1750000100")
172
+ expect(writer.string).to include("geo_cursor_last_event_timestamp{#{labels}} 1750000090")
173
+ expect(writer.string).to include("geo_last_successful_status_check_timestamp{#{labels}} 1750000000")
174
+ expect(writer.string).to include("geo_repositories{#{labels}} 21")
175
+ expect(writer.string).to include("geo_projects_verification_total{#{labels}} 8")
176
+ expect(writer.string).to include("geo_projects_synced{#{labels}} 19")
177
+ expect(writer.string).to include("geo_projects_failed{#{labels}} 2")
178
+
179
+ expect(writer.string).not_to include("event_log_max_id")
180
+ expect(writer.string).not_to include("geo_projects{")
181
+ expect(writer.string).not_to include("geo_git_fetch_event")
182
+ expect(writer.string).not_to include("container_repositories_replication_enabled")
183
+ expect(writer.string).not_to include("status_message")
184
+ end
185
+
186
+ it "handles nil column values gracefully" do
187
+ allow(collector).to receive(:run).and_return([
188
+ {
189
+ "geo_node_name" => "secondary",
190
+ "db_replication_lag_seconds" => nil,
191
+ "replication_slots_count" => nil,
192
+ "status" => { "project_repositories_count" => 5 }
193
+ }
194
+ ])
195
+
196
+ prober.probe_db
197
+ prober.write_to(writer)
198
+
199
+ expect(writer.string).not_to include("geo_db_replication_lag_seconds")
200
+ expect(writer.string).not_to include("geo_replication_slots")
201
+ expect(writer.string).to include('geo_repositories{name="secondary",url="secondary"} 5')
202
+ end
203
+
204
+ it "handles a missing status payload" do
205
+ allow(collector).to receive(:run).and_return([{ "geo_node_name" => "secondary" }])
206
+
207
+ expect { prober.probe_db }.not_to raise_error
208
+ end
209
+ end
210
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-exporter
3
3
  version: !ruby/object:Gem::Version
4
- version: 17.0.2
4
+ version: 17.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Carranza
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: 2.14.3
61
+ version: 2.14.4
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - '='
67
67
  - !ruby/object:Gem::Version
68
- version: 2.14.3
68
+ version: 2.14.4
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: pg
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -209,6 +209,7 @@ files:
209
209
  - lib/gitlab_exporter/database/bloat_btree.sql
210
210
  - lib/gitlab_exporter/database/bloat_table.sql
211
211
  - lib/gitlab_exporter/database/ci_builds.rb
212
+ - lib/gitlab_exporter/database/geo_node_status.rb
212
213
  - lib/gitlab_exporter/database/pg_sequences.rb
213
214
  - lib/gitlab_exporter/database/remote_mirrors.rb
214
215
  - lib/gitlab_exporter/database/row_count.rb
@@ -228,8 +229,10 @@ files:
228
229
  - lib/gitlab_exporter/version.rb
229
230
  - lib/gitlab_exporter/web_exporter.rb
230
231
  - spec/cli_spec.rb
232
+ - spec/config_spec.rb
231
233
  - spec/database/bloat_spec.rb
232
234
  - spec/database/ci_builds_spec.rb
235
+ - spec/database/geo_node_status_spec.rb
233
236
  - spec/database/pg_sequences_spec.rb
234
237
  - spec/database/row_count_spec.rb
235
238
  - spec/database/tuple_stats_spec.rb
@@ -272,8 +275,10 @@ specification_version: 4
272
275
  summary: GitLab metrics exporter
273
276
  test_files:
274
277
  - spec/cli_spec.rb
278
+ - spec/config_spec.rb
275
279
  - spec/database/bloat_spec.rb
276
280
  - spec/database/ci_builds_spec.rb
281
+ - spec/database/geo_node_status_spec.rb
277
282
  - spec/database/pg_sequences_spec.rb
278
283
  - spec/database/row_count_spec.rb
279
284
  - spec/database/tuple_stats_spec.rb