/* ========================================================================================= * Copyright © 2013-2014 the kamon project * * 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 kamon.jdbc.instrumentation import java.sql.{ DriverManager, SQLException } import com.typesafe.config.ConfigFactory import kamon.jdbc.{ JdbcExtension, JdbcNameGenerator, SqlErrorProcessor, SlowQueryProcessor } import kamon.testkit.BaseKamonSpec import kamon.trace.{ Tracer, SegmentCategory } class StatementInstrumentationSpec extends BaseKamonSpec("jdbc-spec") { val connection = DriverManager.getConnection("jdbc:h2:mem:jdbc-spec", "SA", "") override protected def beforeAll(): Unit = { connection should not be null val create = "CREATE TABLE Address (Nr INTEGER, Name VARCHAR(128));" val createStatement = connection.createStatement() createStatement.executeUpdate(create) val sleep = "CREATE ALIAS SLEEP FOR \"java.lang.Thread.sleep(long)\"" val sleepStatement = connection.createStatement() sleepStatement.executeUpdate(sleep) } "the StatementInstrumentation" should { "record the execution time of INSERT operation" in { Tracer.withContext(newContext("jdbc-trace-insert")) { for (id ← 1 to 100) { val insert = s"INSERT INTO Address (Nr, Name) VALUES($id, 'foo')" val insertStatement = connection.prepareStatement(insert) insertStatement.execute() } Tracer.currentContext.finish() } val jdbcSnapshot = takeSnapshotOf("jdbc-statements", "jdbc-statements") jdbcSnapshot.histogram("writes").get.numberOfMeasurements should be(100) val traceSnapshot = takeSnapshotOf("jdbc-trace-insert", "trace") traceSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) val segmentSnapshot = takeSnapshotOf("Jdbc[Insert]", "trace-segment", tags = Map( "trace" -> "jdbc-trace-insert", "category" -> SegmentCategory.Database, "library" -> JdbcExtension.SegmentLibraryName)) segmentSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(100) } "record the execution time of SELECT operation" in { Tracer.withContext(newContext("jdbc-trace-select")) { for (id ← 1 to 100) { val select = s"SELECT * FROM Address where Nr = $id" val selectStatement = connection.createStatement() selectStatement.execute(select) } Tracer.currentContext.finish() } val jdbcSnapshot = takeSnapshotOf("jdbc-statements", "jdbc-statements") jdbcSnapshot.histogram("reads").get.numberOfMeasurements should be(100) val traceSnapshot = takeSnapshotOf("jdbc-trace-select", "trace") traceSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) val segmentSnapshot = takeSnapshotOf("Jdbc[Select]", "trace-segment", tags = Map( "trace" -> "jdbc-trace-select", "category" -> SegmentCategory.Database, "library" -> JdbcExtension.SegmentLibraryName)) segmentSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(100) } "record the execution time of UPDATE operation" in { Tracer.withContext(newContext("jdbc-trace-update")) { for (id ← 1 to 100) { val update = s"UPDATE Address SET Name = 'bar$id' where Nr = $id" val updateStatement = connection.prepareStatement(update) updateStatement.execute() } Tracer.currentContext.finish() } val jdbcSnapshot = takeSnapshotOf("jdbc-statements", "jdbc-statements") jdbcSnapshot.histogram("writes").get.numberOfMeasurements should be(100) val traceSnapshot = takeSnapshotOf("jdbc-trace-update", "trace") traceSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) val segmentSnapshot = takeSnapshotOf("Jdbc[Update]", "trace-segment", tags = Map( "trace" -> "jdbc-trace-update", "category" -> SegmentCategory.Database, "library" -> JdbcExtension.SegmentLibraryName)) segmentSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(100) } "record the execution time of DELETE operation" in { Tracer.withContext(newContext("jdbc-trace-delete")) { for (id ← 1 to 100) { val delete = s"DELETE FROM Address where Nr = $id" val deleteStatement = connection.createStatement() deleteStatement.execute(delete) } Tracer.currentContext.finish() } val jdbcSnapshot = takeSnapshotOf("jdbc-statements", "jdbc-statements") jdbcSnapshot.histogram("writes").get.numberOfMeasurements should be(100) val traceSnapshot = takeSnapshotOf("jdbc-trace-delete", "trace") traceSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) val segmentSnapshot = takeSnapshotOf("Jdbc[Delete]", "trace-segment", tags = Map( "trace" -> "jdbc-trace-delete", "category" -> SegmentCategory.Database, "library" -> JdbcExtension.SegmentLibraryName)) segmentSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(100) } "record the execution time of SLOW QUERIES based on the kamon.jdbc.slow-query-threshold" in { Tracer.withContext(newContext("jdbc-trace-slow")) { for (id ← 1 to 2) { val select = s"SELECT * FROM Address; CALL SLEEP(100)" val selectStatement = connection.createStatement() selectStatement.execute(select) } Tracer.currentContext.finish() } val jdbcSnapshot = takeSnapshotOf("jdbc-statements", "jdbc-statements") jdbcSnapshot.counter("slows").get.count should be(2) } "count all SQL ERRORS" in { Tracer.withContext(newContext("jdbc-trace-errors")) { for (_ ← 1 to 10) { intercept[SQLException] { val error = "SELECT * FROM NO_EXISTENT_TABLE" val errorStatement = connection.createStatement() errorStatement.execute(error) } } Tracer.currentContext.finish() } val jdbcSnapshot = takeSnapshotOf("jdbc-statements", "jdbc-statements") jdbcSnapshot.counter("errors").get.count should be(10) } } } class NoOpSlowQueryProcessor extends SlowQueryProcessor { override def process(sql: String, executionTimeInMillis: Long, queryThresholdInMillis: Long): Unit = { /*do nothing!!!*/ } } class NoOpSqlErrorProcessor extends SqlErrorProcessor { override def process(sql: String, ex: Throwable): Unit = { /*do nothing!!!*/ } } class NoOpJdbcNameGenerator extends JdbcNameGenerator { override def generateJdbcSegmentName(statement: String): String = s"Jdbc[$statement]" }