-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
slick 3.4.0 scala 2.13 instrumentation module
- Loading branch information
1 parent
85e48ab
commit f137f4f
Showing
5 changed files
with
362 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | ||
} |
102 changes: 102 additions & 0 deletions
102
instrumentation/slick-2.13_3.4.0/src/main/scala/slick/util/AsyncExecutor.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
198 changes: 198 additions & 0 deletions
198
...tion/slick-2.13_3.4.0/src/test/scala/com/nr/agent/instrumentation/slickdb/SlickTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters