Skip to content

Commit

Permalink
slick 3.4.0 scala 2.13 instrumentation module
Browse files Browse the repository at this point in the history
  • Loading branch information
smiklos authored and tbradellis committed Nov 10, 2022
1 parent 85e48ab commit f137f4f
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 0 deletions.
25 changes: 25 additions & 0 deletions instrumentation/slick-2.13_3.4.0/.snyk
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.25.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
SNYK-JAVA-COMH2DATABASE-2331071:
- '*':
reason: Instrumentation false positive
expires: 2038-01-19T00:00:00.000Z
created: 2022-11-03T17:20:53.764Z
SNYK-JAVA-COMH2DATABASE-2348247:
- '*':
reason: Instrumentation false positive
expires: 2038-01-19T00:00:00.000Z
created: 2022-11-03T17:20:57.005Z
SNYK-JAVA-COMH2DATABASE-31685:
- '*':
reason: Instrumentation false positive
expires: 2038-01-19T00:00:00.000Z
created: 2022-11-03T17:20:59.838Z
SNYK-JAVA-ORGSCALALANG-3032987:
- '*':
reason: Instrumentation false positive
expires: 2038-01-19T00:00:00.000Z
created: 2022-11-03T17:21:02.514Z
patch: {}
36 changes: 36 additions & 0 deletions instrumentation/slick-2.13_3.4.0/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
apply plugin: 'scala'
scala.zincVersion = "1.7.1"

isScalaProjectEnabled(project, "scala-2.13")

dependencies {
implementation(project(":newrelic-api"))
implementation(project(":agent-bridge"))
implementation(project(":newrelic-weaver-api"))
implementation(project(":newrelic-weaver-scala-api"))
implementation("org.scala-lang:scala-library:2.13.3")
implementation("com.typesafe.slick:slick_2.13:3.4.0")

testImplementation("com.h2database:h2:1.4.190")
testImplementation(project(":instrumentation:jdbc-h2")) { transitive = false }
testImplementation(project(":instrumentation:jdbc-generic")) { transitive = false }
}

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.slick-2.13_3.4.0' }
}

verifyInstrumentation {
// scala 11 should be instrumented by another module
fails 'com.typesafe.slick:slick_2.12:[3.4.0,)'

// scala 13
passesOnly 'com.typesafe.slick:slick_2.13:[3.4.0,)'

excludeRegex ".*(RC|M)[0-9].*"
}

site {
title 'Slick'
type 'Datastore'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package slick.util


import com.newrelic.api.agent.weaver.{SkipIfPresent, Weave, Weaver}
import com.newrelic.api.agent.weaver.scala.{ScalaMatchType, ScalaWeave}
import com.newrelic.api.agent.NewRelic
import java.util.logging.Level
import java.io.Closeable
import java.lang.Runnable

import com.newrelic.agent.bridge.AgentBridge
import com.newrelic.agent.bridge.TracedMethod
import com.newrelic.api.agent.Trace
import com.newrelic.api.agent.weaver.internal.WeavePackageType

import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.concurrent.duration._
import slick.util.AsyncExecutor

@ScalaWeave(`type` = ScalaMatchType.Object, originalName="slick.util.AsyncExecutor")
class WeavedAsyncExecutor {
def apply(name: String, minThreads: Int, maxThreads: Int, queueSize: Int, maxConnections: Int, keepAliveTime: Duration,
registerMbeans: Boolean): AsyncExecutor = {
val original :AsyncExecutor = Weaver.callOriginal()
new NewRelicAsyncExecutor(original)
}
}

class NewRelicAsyncExecutor(delegatee :AsyncExecutor) extends AsyncExecutor {
lazy val executionContext = {
val ctx = new NewRelicExecutionContext(delegatee.executionContext)
try {

AgentBridge.instrumentation.retransformUninstrumentedClass(classOf[NewRelicRunnable]);
} catch {
case t :Throwable => {
AgentBridge.instrumentation.noticeInstrumentationError(t, Weaver.getImplementationTitle())
}
case _ => ;
}
ctx
}

override def close(): Unit = {
delegatee.close()
}
}

class NewRelicExecutionContext(delegatee :ExecutionContext) extends ExecutionContext {
override def execute(runnable: Runnable): Unit = {
try {
AgentBridge.currentApiSource.set(WeavePackageType.INTERNAL)

if (null != AgentBridge.getAgent().getTransaction(false) && AgentBridge.getAgent().getTransaction().isStarted()) {
AgentBridge.getAgent().getTransaction().registerAsyncActivity(runnable)
delegatee.execute(new NewRelicRunnable(runnable))
return
}
} catch {
case t: Throwable => {
AgentBridge.instrumentation.noticeInstrumentationError(t, Weaver.getImplementationTitle());
}
case _ => ;
} finally {
AgentBridge.currentApiSource.remove();
}
delegatee.execute(runnable)
}

override def reportFailure(t: Throwable): Unit = {
delegatee.reportFailure(t)
}
}

class NewRelicRunnable(runnable :Runnable) extends Runnable {
@Trace(async = true)
override def run() {
try {
AgentBridge.currentApiSource.set(WeavePackageType.INTERNAL)

if(AgentBridge.getAgent().startAsyncActivity(runnable)) {
val tm = AgentBridge.getAgent().getTransaction().getTracedMethod().asInstanceOf[TracedMethod]
tm.setMetricName("ORM", "Slick", "slickQuery")
}
} catch {
case t: Throwable => {
AgentBridge.instrumentation.noticeInstrumentationError(t, Weaver.getImplementationTitle());
}
case _ => ;
} finally {
AgentBridge.currentApiSource.remove()
}
runnable.run()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.nr.agent.instrumentation.slickdb

import com.newrelic.agent.introspec.DatastoreHelper;
import com.newrelic.agent.introspec.InstrumentationTestConfig
import com.newrelic.agent.introspec.InstrumentationTestRunner
import com.newrelic.agent.introspec.Introspector;
import com.newrelic.agent.introspec.MetricsHelper;
import com.newrelic.agent.introspec.TransactionEvent;
import com.newrelic.api.agent.Trace;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.junit._
import org.junit.runner.RunWith;

import com.typesafe.config.ConfigFactory;

import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global;
import slick.jdbc.H2Profile.api._

import scala.jdk.CollectionConverters._

// Copied from slick-3.0.0 module
@RunWith(classOf[InstrumentationTestRunner])
@InstrumentationTestConfig(includePrefixes = Array("slick", "org.h2"))
class SlickTest {

@Before
def initData() = {
// set up data in h2
val stmt :Statement = SlickTest.CONNECTION.createStatement();
stmt.execute("CREATE TABLE IF NOT EXISTS USER(id int primary key, first_name varchar(255), last_name varchar(255))");
stmt.execute("TRUNCATE TABLE USER");
stmt.execute("INSERT INTO USER(id, first_name, last_name) VALUES(1, 'Fakus', 'Namus')");
stmt.execute("INSERT INTO USER(id, first_name, last_name) VALUES(2, 'Some', 'Guy')");
stmt.execute("INSERT INTO USER(id, first_name, last_name) VALUES(3, 'Whatsher', 'Name')");
stmt.close();
}

/*
* Our slick tests rely on h2 jdbc to assert correctly. If this test fails nothing else will work.
*/
@Test
@Ignore
def testJdbc() = {
jdbcTx()
val introspector :Introspector = InstrumentationTestRunner.getIntrospector()
Assert.assertEquals(1, introspector.getFinishedTransactionCount())
val helper :DatastoreHelper = new DatastoreHelper("JDBC");
helper.assertAggregateMetrics();
helper.assertUnscopedOperationMetricCount("select", 1);
}

@Trace(dispatcher = true)
def jdbcTx() :Unit = {
val stmt :Statement = SlickTest.CONNECTION.createStatement();
stmt.execute("select * from USER");
stmt.close()
}

@Test
@Ignore
def testResult() = {
// it would be cool to use asserts in a callback instead of awaiting
// but that would keep the transaction from finishing
Await.ready(slickResult(), 20.seconds)
val introspector :Introspector = InstrumentationTestRunner.getIntrospector()
awaitFinishedTx(introspector);
Assert.assertEquals(1, introspector.getFinishedTransactionCount())
val helper :DatastoreHelper = new DatastoreHelper("JDBC");
helper.assertAggregateMetrics();
helper.assertUnscopedOperationMetricCount("select", 1);
}

@Test
@Ignore
def testCrud() = {
slickInsert();
slickUpdate();
slickDelete();
val res :String = Await.result(slickResult(), 20.seconds)
val introspector :Introspector = InstrumentationTestRunner.getIntrospector()
awaitFinishedTx(introspector, 4);
Assert.assertEquals(4, introspector.getFinishedTransactionCount())

val helper :DatastoreHelper = new DatastoreHelper("JDBC");
helper.assertAggregateMetrics();
helper.assertUnscopedOperationMetricCount("insert", 1); // C
helper.assertUnscopedOperationMetricCount("select", 1); // R
helper.assertUnscopedOperationMetricCount("update", 1); // U
helper.assertUnscopedOperationMetricCount("delete", 1); // D
}

val slickdb = Database.forURL(SlickTest.DB_CONNECTION, driver=SlickTest.DB_DRIVER)
val users = TableQuery[Users]

@Trace(dispatcher = true)
def slickResult() :Future[String] = {
slickdb.run(users.result).map(units => {
var res :String = ""
units.foreach {
case (id, first_name, last_name) =>
res += " * " + id + ": " + first_name + " " + last_name + "\n"
}
"Got results: \n"+res
})
}

@Trace(dispatcher = true)
def slickInsert() :Future[String] = {
slickdb.run(users.map(u => (u.id, u.first_name, u.last_name)) += (4, "John", "JacobJingle")).map(rowsInserted => {
"Table now has "+rowsInserted+" users"
})
}

@Trace(dispatcher = true)
def slickUpdate() :Future[String] = {
slickdb.run(users.filter(_.id === 1).map(u => (u.first_name)).update(("Fred"))).map(result => {
"result: "+result
})
}

@Trace(dispatcher = true)
def slickDelete() :Future[String] = {
// people.filter(p => p.name === "M. Odersky").delete
slickdb.run(users.filter(_.id === 2).delete).map(result => {
"result: "+result
})
}

// introspector does not handle async tx finishing very well so we're sleeping as a workaround
private def awaitFinishedTx(introspector :Introspector, expectedTxCount: Int = 1) = {
while(introspector.getFinishedTransactionCount() <= expectedTxCount-1) {
Thread.sleep(100)
}
Thread.sleep(100)
}

}

class Users(tag: Tag) extends Table[(Int, String, String)] (tag, "user") {
def id = column[Int]("id", O.PrimaryKey)
def first_name = column[String]("first_name")
def last_name = column[String]("last_name")
// Every table needs a * projection with the same type as the table's type parameter
def * = (id, first_name, last_name)
}

object SlickTest {
val DB_DRIVER :String = "org.h2.Driver";
val DB_CONNECTION :String = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false";
val DB_USER :String = "";
val DB_PASSWORD :String = "";
val CONNECTION :Connection = getDBConnection();

@BeforeClass
def setup() = {
// set up h2
Assert.assertNotNull("Unable to get h2 connection.", CONNECTION)
CONNECTION.setAutoCommit(true);
}

@AfterClass
def teardown() = {
// tear down h2
if(null != CONNECTION) {
CONNECTION.close();
}
}

def getDBConnection() :Connection = {
var dbConnection :Connection = null
try {
Class.forName(DB_DRIVER);
dbConnection = DriverManager.getConnection(DB_CONNECTION, DB_USER, DB_PASSWORD);
return dbConnection;
} catch {
case e :Exception => {
e.printStackTrace();
};
}
return dbConnection;
}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ include 'instrumentation:servlet-user-5.0'
include 'instrumentation:slick-3.0.0'
include 'instrumentation:slick-2.11_3.2.0'
include 'instrumentation:slick-2.12_3.2.0'
include 'instrumentation:slick-2.13_3.4.0'
include 'instrumentation:solr-4.0.0'
include 'instrumentation:solr-5.0.0'
include 'instrumentation:solr-5.1.0'
Expand Down

0 comments on commit f137f4f

Please sign in to comment.