Skip to content

Commit 6bd8645

Browse files
easimonshakuzen
andauthored
Correct PostgreSQL metrics for dead tuples, with integration tests and refactoring (#2474)
`pg_stat_user_tables` has a row for each table in the database, so selecting the first row of it results in "dead rows for a single random table". Calculating the sum over `n_dead_tup` results in all dead rows instead. `pg_stat_database` has only one row for each database, so the sum() over `numbackends` was harmless but unnecessary, so I removed that one. At least for PostgreSQL, there's a difference between `getObject(n, Long.class)` and `getLong(n)`. The former can only convert INT4 and INT8 (but not NUMERIC and DECIMAL) to Long, the latter converts all numeric types. `sum(n_dead_tup)` is such a case where this matters, since its type is NUMERIC. Co-authored-by: Tommy Ludwig <[email protected]>
1 parent 0060763 commit 6bd8645

File tree

6 files changed

+272
-52
lines changed

6 files changed

+272
-52
lines changed

dependencies.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,12 @@ def VERSIONS = [
7272
'org.mockito:mockito-core:3.+',
7373
'org.mockito:mockito-inline:3.+',
7474
'org.mongodb:mongodb-driver-sync:latest.release',
75+
'org.postgresql:postgresql:latest.release',
7576
'org.slf4j:slf4j-api:1.7.+',
7677
'org.springframework:spring-context:latest.release',
7778
'org.testcontainers:junit-jupiter:latest.release',
7879
'org.testcontainers:kafka:latest.release',
80+
'org.testcontainers:postgresql:latest.release',
7981
'org.testcontainers:testcontainers:latest.release',
8082
'ru.lanwen.wiremock:wiremock-junit5:latest.release',
8183
'software.amazon.awssdk:cloudwatch:2.16.+'

micrometer-core/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ dependencies {
9898
testImplementation 'org.testcontainers:testcontainers'
9999
testImplementation 'org.testcontainers:junit-jupiter'
100100
testImplementation 'org.testcontainers:kafka'
101+
102+
// Postgres Binder IT dependencies
103+
testImplementation 'org.testcontainers:postgresql'
104+
testImplementation 'org.postgresql:postgresql'
101105
}
102106

103107
task shenandoahTest(type: Test) {

micrometer-core/gradle.lockfile

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

micrometer-core/src/main/java/io/micrometer/core/instrument/binder/db/PostgreSQLDatabaseMetrics.java

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
* @author Kristof Depypere
3737
* @author Jon Schneider
3838
* @author Johnny Lim
39+
* @author Markus Dobel
3940
* @since 1.1.0
4041
*/
4142
@NonNullApi
@@ -44,7 +45,7 @@ public class PostgreSQLDatabaseMetrics implements MeterBinder {
4445

4546
private static final String SELECT = "SELECT ";
4647

47-
private static final String QUERY_DEAD_TUPLE_COUNT = getUserTableQuery("n_dead_tup");
48+
private static final String QUERY_DEAD_TUPLE_COUNT = getUserTableQuery("SUM(n_dead_tup)");
4849
private static final String QUERY_TIMED_CHECKPOINTS_COUNT = getBgWriterQuery("checkpoints_timed");
4950
private static final String QUERY_REQUESTED_CHECKPOINTS_COUNT = getBgWriterQuery("checkpoints_req");
5051
private static final String QUERY_BUFFERS_CLEAN = getBgWriterQuery("buffers_clean");
@@ -78,7 +79,7 @@ public PostgreSQLDatabaseMetrics(DataSource postgresDataSource, String database,
7879
this.beforeResetValuesCacheMap = new ConcurrentHashMap<>();
7980
this.previousValueCacheMap = new ConcurrentHashMap<>();
8081

81-
this.queryConnectionCount = getDBStatQuery(database, "SUM(numbackends)");
82+
this.queryConnectionCount = getDBStatQuery(database, "numbackends");
8283
this.queryReadCount = getDBStatQuery(database, "tup_fetched");
8384
this.queryInsertCount = getDBStatQuery(database, "tup_inserted");
8485
this.queryTempBytes = getDBStatQuery(database, "temp_bytes");
@@ -95,38 +96,38 @@ private static Tag createDbTag(String database) {
9596

9697
@Override
9798
public void bindTo(MeterRegistry registry) {
98-
Gauge.builder("postgres.size", postgresDataSource, dataSource -> getDatabaseSize())
99+
Gauge.builder(Names.SIZE, postgresDataSource, dataSource -> getDatabaseSize())
99100
.tags(tags)
100101
.description("The database size")
101102
.register(registry);
102-
Gauge.builder("postgres.connections", postgresDataSource, dataSource -> getConnectionCount())
103+
Gauge.builder(Names.CONNECTIONS, postgresDataSource, dataSource -> getConnectionCount())
103104
.tags(tags)
104105
.description("Number of active connections to the given db")
105106
.register(registry);
106107

107108
// Hit ratio can be derived from dividing hits/reads
108-
FunctionCounter.builder("postgres.blocks.hits", postgresDataSource,
109-
dataSource -> resettableFunctionalCounter("postgres.blocks.hits", this::getBlockHits))
109+
FunctionCounter.builder(Names.BLOCKS_HITS, postgresDataSource,
110+
dataSource -> resettableFunctionalCounter(Names.BLOCKS_HITS, this::getBlockHits))
110111
.tags(tags)
111112
.description("Number of times disk blocks were found already in the buffer cache, so that a read was not necessary")
112113
.register(registry);
113-
FunctionCounter.builder("postgres.blocks.reads", postgresDataSource,
114-
dataSource -> resettableFunctionalCounter("postgres.blocks.reads", this::getBlockReads))
114+
FunctionCounter.builder(Names.BLOCKS_READS, postgresDataSource,
115+
dataSource -> resettableFunctionalCounter(Names.BLOCKS_READS, this::getBlockReads))
115116
.tags(tags)
116117
.description("Number of disk blocks read in this database")
117118
.register(registry);
118119

119-
FunctionCounter.builder("postgres.transactions", postgresDataSource,
120-
dataSource -> resettableFunctionalCounter("postgres.transactions", this::getTransactionCount))
120+
FunctionCounter.builder(Names.TRANSACTIONS, postgresDataSource,
121+
dataSource -> resettableFunctionalCounter(Names.TRANSACTIONS, this::getTransactionCount))
121122
.tags(tags)
122123
.description("Total number of transactions executed (commits + rollbacks)")
123124
.register(registry);
124-
Gauge.builder("postgres.locks", postgresDataSource, dataSource -> getLockCount())
125+
Gauge.builder(Names.LOCKS, postgresDataSource, dataSource -> getLockCount())
125126
.tags(tags)
126127
.description("Number of locks on the given db")
127128
.register(registry);
128-
FunctionCounter.builder("postgres.temp.writes", postgresDataSource,
129-
dataSource -> resettableFunctionalCounter("postgres.temp.writes", this::getTempBytes))
129+
FunctionCounter.builder(Names.TEMP_WRITES, postgresDataSource,
130+
dataSource -> resettableFunctionalCounter(Names.TEMP_WRITES, this::getTempBytes))
130131
.tags(tags)
131132
.description("The total amount of temporary writes to disk to execute queries")
132133
.baseUnit(BaseUnits.BYTES)
@@ -137,56 +138,56 @@ public void bindTo(MeterRegistry registry) {
137138
}
138139

139140
private void registerRowCountMetrics(MeterRegistry registry) {
140-
FunctionCounter.builder("postgres.rows.fetched", postgresDataSource,
141-
dataSource -> resettableFunctionalCounter("postgres.rows.fetched", this::getReadCount))
141+
FunctionCounter.builder(Names.ROWS_FETCHED, postgresDataSource,
142+
dataSource -> resettableFunctionalCounter(Names.ROWS_FETCHED, this::getReadCount))
142143
.tags(tags)
143144
.description("Number of rows fetched from the db")
144145
.register(registry);
145-
FunctionCounter.builder("postgres.rows.inserted", postgresDataSource,
146-
dataSource -> resettableFunctionalCounter("postgres.rows.inserted", this::getInsertCount))
146+
FunctionCounter.builder(Names.ROWS_INSERTED, postgresDataSource,
147+
dataSource -> resettableFunctionalCounter(Names.ROWS_INSERTED, this::getInsertCount))
147148
.tags(tags)
148149
.description("Number of rows inserted from the db")
149150
.register(registry);
150-
FunctionCounter.builder("postgres.rows.updated", postgresDataSource,
151-
dataSource -> resettableFunctionalCounter("postgres.rows.updated", this::getUpdateCount))
151+
FunctionCounter.builder(Names.ROWS_UPDATED, postgresDataSource,
152+
dataSource -> resettableFunctionalCounter(Names.ROWS_UPDATED, this::getUpdateCount))
152153
.tags(tags)
153154
.description("Number of rows updated from the db")
154155
.register(registry);
155-
FunctionCounter.builder("postgres.rows.deleted", postgresDataSource,
156-
dataSource -> resettableFunctionalCounter("postgres.rows.deleted", this::getDeleteCount))
156+
FunctionCounter.builder(Names.ROWS_DELETED, postgresDataSource,
157+
dataSource -> resettableFunctionalCounter(Names.ROWS_DELETED, this::getDeleteCount))
157158
.tags(tags)
158159
.description("Number of rows deleted from the db")
159160
.register(registry);
160-
Gauge.builder("postgres.rows.dead", postgresDataSource, dataSource -> getDeadTupleCount())
161+
Gauge.builder(Names.ROWS_DEAD, postgresDataSource, dataSource -> getDeadTupleCount())
161162
.tags(tags)
162163
.description("Total number of dead rows in the current database")
163164
.register(registry);
164165
}
165166

166167
private void registerCheckpointMetrics(MeterRegistry registry) {
167-
FunctionCounter.builder("postgres.checkpoints.timed", postgresDataSource,
168-
dataSource -> resettableFunctionalCounter("postgres.checkpoints.timed", this::getTimedCheckpointsCount))
168+
FunctionCounter.builder(Names.CHECKPOINTS_TIMED, postgresDataSource,
169+
dataSource -> resettableFunctionalCounter(Names.CHECKPOINTS_TIMED, this::getTimedCheckpointsCount))
169170
.tags(tags)
170171
.description("Number of checkpoints timed")
171172
.register(registry);
172-
FunctionCounter.builder("postgres.checkpoints.requested", postgresDataSource,
173-
dataSource -> resettableFunctionalCounter("postgres.checkpoints.requested", this::getRequestedCheckpointsCount))
173+
FunctionCounter.builder(Names.CHECKPOINTS_REQUESTED, postgresDataSource,
174+
dataSource -> resettableFunctionalCounter(Names.CHECKPOINTS_REQUESTED, this::getRequestedCheckpointsCount))
174175
.tags(tags)
175176
.description("Number of checkpoints requested")
176177
.register(registry);
177178

178-
FunctionCounter.builder("postgres.buffers.checkpoint", postgresDataSource,
179-
dataSource -> resettableFunctionalCounter("postgres.buffers.checkpoint", this::getBuffersCheckpoint))
179+
FunctionCounter.builder(Names.BUFFERS_CHECKPOINT, postgresDataSource,
180+
dataSource -> resettableFunctionalCounter(Names.BUFFERS_CHECKPOINT, this::getBuffersCheckpoint))
180181
.tags(tags)
181182
.description("Number of buffers written during checkpoints")
182183
.register(registry);
183-
FunctionCounter.builder("postgres.buffers.clean", postgresDataSource,
184-
dataSource -> resettableFunctionalCounter("postgres.buffers.clean", this::getBuffersClean))
184+
FunctionCounter.builder(Names.BUFFERS_CLEAN, postgresDataSource,
185+
dataSource -> resettableFunctionalCounter(Names.BUFFERS_CLEAN, this::getBuffersClean))
185186
.tags(tags)
186187
.description("Number of buffers written by the background writer")
187188
.register(registry);
188-
FunctionCounter.builder("postgres.buffers.backend", postgresDataSource,
189-
dataSource -> resettableFunctionalCounter("postgres.buffers.backend", this::getBuffersBackend))
189+
FunctionCounter.builder(Names.BUFFERS_BACKEND, postgresDataSource,
190+
dataSource -> resettableFunctionalCounter(Names.BUFFERS_BACKEND, this::getBuffersBackend))
190191
.tags(tags)
191192
.description("Number of buffers written directly by a backend")
192193
.register(registry);
@@ -282,7 +283,7 @@ private Long runQuery(String query) {
282283
Statement statement = connection.createStatement();
283284
ResultSet resultSet = statement.executeQuery(query)) {
284285
if (resultSet.next()) {
285-
return resultSet.getObject(1, Long.class);
286+
return resultSet.getLong(1);
286287
}
287288
} catch (SQLException ignored) {
288289
}
@@ -300,4 +301,34 @@ private static String getUserTableQuery(String statName) {
300301
private static String getBgWriterQuery(String statName) {
301302
return SELECT + statName + " FROM pg_stat_bgwriter";
302303
}
304+
305+
static final class Names {
306+
public static final String SIZE = of("size");
307+
public static final String CONNECTIONS = of("connections");
308+
public static final String BLOCKS_HITS = of("blocks.hits");
309+
public static final String BLOCKS_READS = of("blocks.reads");
310+
public static final String TRANSACTIONS = of("transactions");
311+
public static final String LOCKS = of("locks");
312+
public static final String TEMP_WRITES = of("temp.writes");
313+
314+
public static final String ROWS_FETCHED = of("rows.fetched");
315+
public static final String ROWS_INSERTED = of("rows.inserted");
316+
public static final String ROWS_UPDATED = of("rows.updated");
317+
public static final String ROWS_DELETED = of("rows.deleted");
318+
public static final String ROWS_DEAD = of("rows.dead");
319+
320+
public static final String CHECKPOINTS_TIMED = of("checkpoints.timed");
321+
public static final String CHECKPOINTS_REQUESTED = of("checkpoints.requested");
322+
323+
public static final String BUFFERS_CHECKPOINT = of("buffers.checkpoint");
324+
public static final String BUFFERS_CLEAN = of("buffers.clean");
325+
public static final String BUFFERS_BACKEND = of("buffers.backend");
326+
327+
private static String of(String name) {
328+
return "postgres." + name;
329+
}
330+
331+
private Names() {
332+
}
333+
}
303334
}

0 commit comments

Comments
 (0)