Skip to content

Commit ed07f91

Browse files
committed
Merge branch '1.8.x'
2 parents 0f8ffb8 + 5a1ac14 commit ed07f91

File tree

5 files changed

+268
-52
lines changed

5 files changed

+268
-52
lines changed

dependencies.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,12 @@ def VERSIONS = [
6868
'org.mockito:mockito-core:latest.release',
6969
'org.mockito:mockito-inline:latest.release',
7070
'org.mongodb:mongodb-driver-sync:latest.release',
71+
'org.postgresql:postgresql:latest.release',
7172
'org.slf4j:slf4j-api:1.7.+',
7273
'org.springframework:spring-context:latest.release',
7374
'org.testcontainers:junit-jupiter:latest.release',
7475
'org.testcontainers:kafka:latest.release',
76+
'org.testcontainers:postgresql:latest.release',
7577
'org.testcontainers:testcontainers:latest.release',
7678
'ru.lanwen.wiremock:wiremock-junit5:latest.release',
7779
'software.amazon.awssdk:cloudwatch:latest.release'

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/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
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/**
2+
* Copyright 2019 VMware, Inc.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.core.instrument.binder.db;
17+
18+
import io.micrometer.core.instrument.MeterRegistry;
19+
import io.micrometer.core.instrument.Tags;
20+
import io.micrometer.core.instrument.search.RequiredSearch;
21+
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Tag;
24+
import org.junit.jupiter.api.Test;
25+
import org.postgresql.ds.PGSimpleDataSource;
26+
import org.testcontainers.containers.PostgreSQLContainer;
27+
import org.testcontainers.junit.jupiter.Container;
28+
import org.testcontainers.junit.jupiter.Testcontainers;
29+
import org.testcontainers.utility.DockerImageName;
30+
31+
import javax.sql.DataSource;
32+
33+
import java.sql.Connection;
34+
import java.sql.PreparedStatement;
35+
import java.sql.SQLException;
36+
import java.util.Arrays;
37+
import java.util.List;
38+
39+
import static org.assertj.core.api.Assertions.*;
40+
41+
import static io.micrometer.core.instrument.binder.db.PostgreSQLDatabaseMetrics.Names.*;
42+
43+
/**
44+
* @author Markus Dobel
45+
*/
46+
@Testcontainers
47+
@Tag("docker")
48+
public class PostgreSQLDatabaseMetricsIntegrationTest {
49+
50+
@Container
51+
private final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(getDockerImageName());
52+
private final MeterRegistry registry = new SimpleMeterRegistry();
53+
54+
private DataSource dataSource;
55+
private Tags tags;
56+
57+
// statistics are updated only every PGSTAT_STAT_INTERVAL, which is 500ms. Add a bit for stable tests.
58+
private static final long PGSTAT_STAT_INTERVAL = 500L + 50L;
59+
60+
@BeforeEach
61+
void setup() throws InterruptedException {
62+
dataSource = createDatasource();
63+
tags = Tags.of("database", postgres.getDatabaseName());
64+
65+
new PostgreSQLDatabaseMetrics(dataSource, postgres.getDatabaseName()).bindTo(registry);
66+
}
67+
68+
@Test
69+
void gaugesAreNotZero() throws Exception {
70+
/* create noise to increment gauges */
71+
executeSql(
72+
"CREATE TABLE gauge_test_table (val varchar(255))",
73+
"INSERT INTO gauge_test_table (val) VALUES ('foo')",
74+
"UPDATE gauge_test_table SET val = 'bar'",
75+
"SELECT * FROM gauge_test_table",
76+
"DELETE FROM gauge_test_table"
77+
);
78+
Thread.sleep(PGSTAT_STAT_INTERVAL);
79+
80+
final List<String> GAUGES = Arrays.asList(
81+
SIZE, CONNECTIONS, ROWS_DEAD, LOCKS
82+
);
83+
84+
for (String name: GAUGES) {
85+
assertThat(get(name).gauge().value())
86+
.withFailMessage("Gauge " + name + " is zero.")
87+
.isGreaterThan(0);
88+
}
89+
}
90+
91+
@Test
92+
void countersAreNotZero() throws Exception {
93+
/* create noise to increment counters */
94+
executeSql(
95+
"CREATE TABLE counter_test_table (val varchar(255))",
96+
"INSERT INTO counter_test_table (val) VALUES ('foo')",
97+
"UPDATE counter_test_table SET val = 'bar'",
98+
"SELECT * FROM counter_test_table",
99+
"DELETE FROM counter_test_table"
100+
);
101+
Thread.sleep(PGSTAT_STAT_INTERVAL);
102+
103+
final List<String> COUNTERS = Arrays.asList(
104+
BLOCKS_HITS, BLOCKS_READS,
105+
TRANSACTIONS,
106+
ROWS_FETCHED, ROWS_INSERTED, ROWS_UPDATED, ROWS_DELETED,
107+
BUFFERS_CHECKPOINT
108+
);
109+
110+
/* the following counters are zero on a clean database and hard to increase reliably */
111+
final List<String> ZERO_COUNTERS = Arrays.asList(
112+
TEMP_WRITES,
113+
CHECKPOINTS_TIMED, CHECKPOINTS_REQUESTED,
114+
BUFFERS_CLEAN, BUFFERS_BACKEND
115+
);
116+
117+
for (String name: COUNTERS) {
118+
assertThat(get(name).functionCounter().count())
119+
.withFailMessage("Counter " + name + " is zero.")
120+
.isGreaterThan(0);
121+
}
122+
}
123+
124+
@Test
125+
void deadTuplesGaugeIncreases() throws Exception {
126+
final double deadRowsBefore = get(ROWS_DEAD).gauge().value();
127+
128+
executeSql(
129+
"CREATE TABLE dead_tuples_test_table (val varchar(255))",
130+
"INSERT INTO dead_tuples_test_table (val) VALUES ('foo')",
131+
"UPDATE dead_tuples_test_table SET val = 'bar'"
132+
);
133+
134+
// wait for stats to be updated
135+
Thread.sleep(PGSTAT_STAT_INTERVAL);
136+
assertThat(get(ROWS_DEAD).gauge().value()).isGreaterThan(deadRowsBefore);
137+
}
138+
139+
private DataSource createDatasource() {
140+
final PGSimpleDataSource result = new PGSimpleDataSource();
141+
result.setURL(postgres.getJdbcUrl());
142+
result.setUser(postgres.getUsername());
143+
result.setPassword(postgres.getPassword());
144+
result.setDatabaseName(postgres.getDatabaseName());
145+
return result;
146+
}
147+
148+
private void executeSql(String ... statements) throws SQLException {
149+
try (final Connection connection = dataSource.getConnection()) {
150+
executeSql(connection, statements);
151+
}
152+
}
153+
154+
private void executeSql(Connection connection, String ... statements) throws SQLException {
155+
for (String sql: statements) {
156+
try (final PreparedStatement stmt = connection.prepareStatement(sql)) {
157+
stmt.execute();
158+
}
159+
}
160+
}
161+
162+
private RequiredSearch get(final String name) {
163+
return registry.get(name).tags(tags);
164+
}
165+
166+
private static DockerImageName getDockerImageName() {
167+
return DockerImageName.parse("postgres:9.6.24");
168+
}
169+
}

0 commit comments

Comments
 (0)