All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micrometer.core.instrument.binder.db.PostgreSQLDatabaseMetrics Maven / Gradle / Ivy

/**
 * Copyright 2017 VMware, Inc.
 * 

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* https://www.apache.org/licenses/LICENSE-2.0 *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.micrometer.core.instrument.binder.db; import io.micrometer.core.instrument.*; import io.micrometer.core.instrument.binder.BaseUnits; import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.lang.NonNullApi; import io.micrometer.core.lang.NonNullFields; import javax.sql.DataSource; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.DoubleSupplier; /** * {@link MeterBinder} for a PostgreSQL database. * * @author Kristof Depypere * @author Jon Schneider * @author Johnny Lim * @since 1.1.0 */ @NonNullApi @NonNullFields public class PostgreSQLDatabaseMetrics implements MeterBinder { private static final String SELECT = "SELECT "; private static final String QUERY_DEAD_TUPLE_COUNT = getUserTableQuery("n_dead_tup"); private static final String QUERY_TIMED_CHECKPOINTS_COUNT = getBgWriterQuery("checkpoints_timed"); private static final String QUERY_REQUESTED_CHECKPOINTS_COUNT = getBgWriterQuery("checkpoints_req"); private static final String QUERY_BUFFERS_CLEAN = getBgWriterQuery("buffers_clean"); private static final String QUERY_BUFFERS_BACKEND = getBgWriterQuery("buffers_backend"); private static final String QUERY_BUFFERS_CHECKPOINT = getBgWriterQuery("buffers_checkpoint"); private final String database; private final DataSource postgresDataSource; private final Iterable tags; private final Map beforeResetValuesCacheMap; private final Map previousValueCacheMap; private final String queryConnectionCount; private final String queryReadCount; private final String queryInsertCount; private final String queryTempBytes; private final String queryUpdateCount; private final String queryDeleteCount; private final String queryBlockHits; private final String queryBlockReads; private final String queryTransactionCount; public PostgreSQLDatabaseMetrics(DataSource postgresDataSource, String database) { this(postgresDataSource, database, Tags.empty()); } public PostgreSQLDatabaseMetrics(DataSource postgresDataSource, String database, Iterable tags) { this.postgresDataSource = postgresDataSource; this.database = database; this.tags = Tags.of(tags).and(createDbTag(database)); this.beforeResetValuesCacheMap = new ConcurrentHashMap<>(); this.previousValueCacheMap = new ConcurrentHashMap<>(); this.queryConnectionCount = getDBStatQuery(database, "SUM(numbackends)"); this.queryReadCount = getDBStatQuery(database, "tup_fetched"); this.queryInsertCount = getDBStatQuery(database, "tup_inserted"); this.queryTempBytes = getDBStatQuery(database, "temp_bytes"); this.queryUpdateCount = getDBStatQuery(database, "tup_updated"); this.queryDeleteCount = getDBStatQuery(database, "tup_deleted"); this.queryBlockHits = getDBStatQuery(database, "blks_hit"); this.queryBlockReads = getDBStatQuery(database, "blks_read"); this.queryTransactionCount = getDBStatQuery(database, "xact_commit + xact_rollback"); } private static Tag createDbTag(String database) { return Tag.of("database", database); } @Override public void bindTo(MeterRegistry registry) { Gauge.builder("postgres.size", postgresDataSource, dataSource -> getDatabaseSize()) .tags(tags) .description("The database size") .register(registry); Gauge.builder("postgres.connections", postgresDataSource, dataSource -> getConnectionCount()) .tags(tags) .description("Number of active connections to the given db") .register(registry); // Hit ratio can be derived from dividing hits/reads FunctionCounter.builder("postgres.blocks.hits", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.blocks.hits", this::getBlockHits)) .tags(tags) .description("Number of times disk blocks were found already in the buffer cache, so that a read was not necessary") .register(registry); FunctionCounter.builder("postgres.blocks.reads", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.blocks.reads", this::getBlockReads)) .tags(tags) .description("Number of disk blocks read in this database") .register(registry); FunctionCounter.builder("postgres.transactions", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.transactions", this::getTransactionCount)) .tags(tags) .description("Total number of transactions executed (commits + rollbacks)") .register(registry); Gauge.builder("postgres.locks", postgresDataSource, dataSource -> getLockCount()) .tags(tags) .description("Number of locks on the given db") .register(registry); FunctionCounter.builder("postgres.temp.writes", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.temp.writes", this::getTempBytes)) .tags(tags) .description("The total amount of temporary writes to disk to execute queries") .baseUnit(BaseUnits.BYTES) .register(registry); registerRowCountMetrics(registry); registerCheckpointMetrics(registry); } private void registerRowCountMetrics(MeterRegistry registry) { FunctionCounter.builder("postgres.rows.fetched", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.rows.fetched", this::getReadCount)) .tags(tags) .description("Number of rows fetched from the db") .register(registry); FunctionCounter.builder("postgres.rows.inserted", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.rows.inserted", this::getInsertCount)) .tags(tags) .description("Number of rows inserted from the db") .register(registry); FunctionCounter.builder("postgres.rows.updated", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.rows.updated", this::getUpdateCount)) .tags(tags) .description("Number of rows updated from the db") .register(registry); FunctionCounter.builder("postgres.rows.deleted", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.rows.deleted", this::getDeleteCount)) .tags(tags) .description("Number of rows deleted from the db") .register(registry); Gauge.builder("postgres.rows.dead", postgresDataSource, dataSource -> getDeadTupleCount()) .tags(tags) .description("Total number of dead rows in the current database") .register(registry); } private void registerCheckpointMetrics(MeterRegistry registry) { FunctionCounter.builder("postgres.checkpoints.timed", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.checkpoints.timed", this::getTimedCheckpointsCount)) .tags(tags) .description("Number of checkpoints timed") .register(registry); FunctionCounter.builder("postgres.checkpoints.requested", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.checkpoints.requested", this::getRequestedCheckpointsCount)) .tags(tags) .description("Number of checkpoints requested") .register(registry); FunctionCounter.builder("postgres.buffers.checkpoint", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.buffers.checkpoint", this::getBuffersCheckpoint)) .tags(tags) .description("Number of buffers written during checkpoints") .register(registry); FunctionCounter.builder("postgres.buffers.clean", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.buffers.clean", this::getBuffersClean)) .tags(tags) .description("Number of buffers written by the background writer") .register(registry); FunctionCounter.builder("postgres.buffers.backend", postgresDataSource, dataSource -> resettableFunctionalCounter("postgres.buffers.backend", this::getBuffersBackend)) .tags(tags) .description("Number of buffers written directly by a backend") .register(registry); } private Long getDatabaseSize() { return runQuery("SELECT pg_database_size('" + database + "')"); } private Long getLockCount() { return runQuery("SELECT count(*) FROM pg_locks l JOIN pg_database d ON l.DATABASE=d.oid WHERE d.datname='" + database + "'"); } private Long getConnectionCount() { return runQuery(this.queryConnectionCount); } private Long getReadCount() { return runQuery(this.queryReadCount); } private Long getInsertCount() { return runQuery(this.queryInsertCount); } private Long getTempBytes() { return runQuery(this.queryTempBytes); } private Long getUpdateCount() { return runQuery(this.queryUpdateCount); } private Long getDeleteCount() { return runQuery(this.queryDeleteCount); } private Long getBlockHits() { return runQuery(this.queryBlockHits); } private Long getBlockReads() { return runQuery(this.queryBlockReads); } private Long getTransactionCount() { return runQuery(this.queryTransactionCount); } private Long getDeadTupleCount() { return runQuery(QUERY_DEAD_TUPLE_COUNT); } private Long getTimedCheckpointsCount() { return runQuery(QUERY_TIMED_CHECKPOINTS_COUNT); } private Long getRequestedCheckpointsCount() { return runQuery(QUERY_REQUESTED_CHECKPOINTS_COUNT); } private Long getBuffersClean() { return runQuery(QUERY_BUFFERS_CLEAN); } private Long getBuffersBackend() { return runQuery(QUERY_BUFFERS_BACKEND); } private Long getBuffersCheckpoint() { return runQuery(QUERY_BUFFERS_CHECKPOINT); } /** * Function that makes sure functional counter values survive pg_stat_reset calls. */ Double resettableFunctionalCounter(String functionalCounterKey, DoubleSupplier function) { Double result = function.getAsDouble(); Double previousResult = previousValueCacheMap.getOrDefault(functionalCounterKey, 0D); Double beforeResetValue = beforeResetValuesCacheMap.getOrDefault(functionalCounterKey, 0D); Double correctedValue = result + beforeResetValue; if (correctedValue < previousResult) { beforeResetValuesCacheMap.put(functionalCounterKey, previousResult); correctedValue = previousResult + result; } previousValueCacheMap.put(functionalCounterKey, correctedValue); return correctedValue; } private Long runQuery(String query) { try (Connection connection = postgresDataSource.getConnection(); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(query)) { if (resultSet.next()) { return resultSet.getObject(1, Long.class); } } catch (SQLException ignored) { } return 0L; } private static String getDBStatQuery(String database, String statName) { return SELECT + statName + " FROM pg_stat_database WHERE datname = '" + database + "'"; } private static String getUserTableQuery(String statName) { return SELECT + statName + " FROM pg_stat_user_tables"; } private static String getBgWriterQuery(String statName) { return SELECT + statName + " FROM pg_stat_bgwriter"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy