diff --git a/front50-sql-mariadb/front50-sql-mariadb.gradle b/front50-sql-mariadb/front50-sql-mariadb.gradle new file mode 100644 index 000000000..d78f6ade0 --- /dev/null +++ b/front50-sql-mariadb/front50-sql-mariadb.gradle @@ -0,0 +1,11 @@ +apply from: "$rootDir/gradle/kotlin.gradle" + +tasks.compileGroovy.enabled = false + +dependencies { + implementation "org.mariadb.jdbc:mariadb-java-client:2.4.0" + implementation "com.netflix.spinnaker.kork:kork-sql" + implementation("org.jooq:jooq:3.9.6"){ + force = true + } +} diff --git a/front50-sql-mariadb/src/main/kotlin/com/netflix/spinnaker/config/MariaDbDataSourceConfiguration.kt b/front50-sql-mariadb/src/main/kotlin/com/netflix/spinnaker/config/MariaDbDataSourceConfiguration.kt new file mode 100644 index 000000000..3f2d2e862 --- /dev/null +++ b/front50-sql-mariadb/src/main/kotlin/com/netflix/spinnaker/config/MariaDbDataSourceConfiguration.kt @@ -0,0 +1,25 @@ +package com.netflix.spinnaker.config + +import com.netflix.spectator.api.Registry +import com.netflix.spinnaker.sql.MariaDbConnectionPoolMetricsExporter +import com.netflix.spinnaker.sql.MariaDbDataSourceFactory +import com.netflix.spinnaker.kork.sql.config.DataSourceFactory +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ConditionalOnProperty("sql.enabled") +@ComponentScan("com.netflix.spinnaker.sql") +class MariaDbDataSourceConfiguration { + + @Bean + fun connectionPoolMetricsExporter(registry: Registry): MariaDbConnectionPoolMetricsExporter { + return MariaDbConnectionPoolMetricsExporter(registry) + } + + @Bean + fun dataSourceFactory(connectionPoolMetricsExporter: MariaDbConnectionPoolMetricsExporter): DataSourceFactory = + MariaDbDataSourceFactory(connectionPoolMetricsExporter) +} diff --git a/front50-sql-mariadb/src/main/kotlin/com/netflix/spinnaker/sql/MariaDbConnectionPoolMetricsExporter.kt b/front50-sql-mariadb/src/main/kotlin/com/netflix/spinnaker/sql/MariaDbConnectionPoolMetricsExporter.kt new file mode 100644 index 000000000..8b42ede3e --- /dev/null +++ b/front50-sql-mariadb/src/main/kotlin/com/netflix/spinnaker/sql/MariaDbConnectionPoolMetricsExporter.kt @@ -0,0 +1,58 @@ +package com.netflix.spinnaker.sql + +import com.netflix.spectator.api.Registry +import com.netflix.spectator.api.patterns.PolledMeter +import org.mariadb.jdbc.MariaDbPoolDataSource +import org.springframework.scheduling.annotation.Scheduled +import java.util.concurrent.atomic.AtomicLong + +class MariaDbConnectionPoolMetricsExporter( + private val registry: Registry +) { + + private val dataSourceMetrics: MutableList = mutableListOf() + + fun track(dataSource: MariaDbPoolDataSource) { + dataSourceMetrics.add( + DataSourceMetrics(dataSource).apply { + registerMeters(registry) + } + ) + } + + @Scheduled(fixedRate = 5_000) + fun record() { + dataSourceMetrics.forEach { + // They tell me not to use test methods, but yolo. Way better than dealing with MBeans. + it.dataSource.testGetPool()?.let { pool -> + it.activeConnections.set(pool.activeConnections) + it.totalConnections.set(pool.totalConnections) + it.idleConnections.set(pool.idleConnections) + it.blockedConnections.set(pool.connectionRequests) + } + } + } +} + +private data class DataSourceMetrics( + val dataSource: MariaDbPoolDataSource, + val activeConnections: AtomicLong = AtomicLong(), + val totalConnections: AtomicLong = AtomicLong(), + val idleConnections: AtomicLong = AtomicLong(), + val blockedConnections: AtomicLong = AtomicLong() +) { + + fun registerMeters(registry: Registry) { + monitorValue(registry, "active", activeConnections) + monitorValue(registry, "total", totalConnections) + monitorValue(registry, "idle", idleConnections) + monitorValue(registry, "blocked", blockedConnections) + } + + private fun monitorValue(registry: Registry, name: String, value: AtomicLong) { + PolledMeter + .using(registry) + .withName("sql.pool.${dataSource.poolName}.$name") + .monitorValue(value) + } +} diff --git a/front50-sql-mariadb/src/main/kotlin/com/netflix/spinnaker/sql/MariaDbDataSourceFactory.kt b/front50-sql-mariadb/src/main/kotlin/com/netflix/spinnaker/sql/MariaDbDataSourceFactory.kt new file mode 100644 index 000000000..b6c9f8b6b --- /dev/null +++ b/front50-sql-mariadb/src/main/kotlin/com/netflix/spinnaker/sql/MariaDbDataSourceFactory.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Netflix, 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 + * + * http://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 com.netflix.spinnaker.sql + +import com.netflix.spinnaker.kork.sql.config.ConnectionPoolProperties +import com.netflix.spinnaker.kork.sql.config.DataSourceFactory +import org.mariadb.jdbc.MariaDbPoolDataSource +import org.springframework.beans.factory.BeanCreationException +import java.sql.SQLException +import javax.sql.DataSource + +class MariaDbDataSourceFactory( + private val metricsExporter: MariaDbConnectionPoolMetricsExporter +) : DataSourceFactory { + + override fun build(poolName: String, connectionPoolProperties: ConnectionPoolProperties): DataSource { + try { + val dataSource = MariaDbPoolDataSource(connectionPoolProperties.jdbcUrl) + dataSource.poolName = poolName + dataSource.user = connectionPoolProperties.user + dataSource.setPassword(connectionPoolProperties.password) + + metricsExporter.track(dataSource) + + return dataSource + } catch (e: SQLException) { + throw BeanCreationException("Failed creating pooled data source", e) + } + } +} diff --git a/front50-sql/front50-sql.gradle b/front50-sql/front50-sql.gradle index 198b13ef4..c03883783 100644 --- a/front50-sql/front50-sql.gradle +++ b/front50-sql/front50-sql.gradle @@ -1,19 +1,20 @@ apply from: "$rootDir/gradle/kotlin.gradle" dependencies { + implementation project(":front50-sql-mariadb") + implementation "com.netflix.spinnaker.front50:front50-core:${front50Version}" implementation "com.netflix.spinnaker.kork:kork-sql" implementation "com.netflix.spinnaker.kork:kork-exceptions" - implementation "org.jooq:jooq:3.11.11" +// implementation "org.jooq:jooq:3.11.11" implementation "io.strikt:strikt-core" implementation "com.netflix.hystrix:hystrix-core" implementation "io.github.resilience4j:resilience4j-retry" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core" - implementation "org.mariadb.jdbc:mariadb-java-client:2.2.3" // runtimeOnly "mysql:mysql-connector-java" testImplementation "io.mockk:mockk"