Skip to content

Commit

Permalink
Support Zio 2 (#1778)
Browse files Browse the repository at this point in the history
* add zio 2 api and instrumentation
  • Loading branch information
obenkenobi authored Mar 4, 2024
1 parent 4bfe27a commit 085996e
Show file tree
Hide file tree
Showing 21 changed files with 750 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/GHA-Functional-Tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
with:
name: functional-tests-results-java-${{ matrix.java-version }}
# The regex for the path below will capture functional test HTML reports generated by gradle for all
# related modules: (functional_test, newrelic-scala-api, newrelic-scala-cats-api, :newrelic-cats-effect3-api, newrelic-scala-zio-api).
# related modules: (functional_test, newrelic-scala-api, newrelic-scala-cats-api, :newrelic-cats-effect3-api, newrelic-scala-zio-api, newrelic-scala-zio2-api).
# However, it's critical that the previous build step does a ./gradlew clean or the regex will capture test reports
# that were leftover in unrelated modules for unit and instrumentation tests.
path: |
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/GHA-Scala-Functional-Tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
timeout-minutes: 25
run: |
# Removed ":newrelic-cats-effect3-api:test" temporarily
./gradlew $GRADLE_OPTIONS :newrelic-scala3-api:test :newrelic-scala-api:test :newrelic-scala-cats-api:test :newrelic-scala-zio-api:test :newrelic-scala-monix-api:test -PincludeScala -Ptest${{ matrix.java-version }} --continue
./gradlew $GRADLE_OPTIONS :newrelic-scala3-api:test :newrelic-scala-api:test :newrelic-scala-cats-api:test :newrelic-scala-zio-api:test :newrelic-scala-zio2-api:test :newrelic-scala-monix-api:test -PincludeScala -Ptest${{ matrix.java-version }} --continue
- name: Run functional tests against version defined in ${{ matrix.java-version }} (attempt 2)
id: run_tests_2
Expand All @@ -61,14 +61,14 @@ jobs:
timeout-minutes: 25
run: |
# Removed ":newrelic-cats-effect3-api:test" temporarily
./gradlew $GRADLE_OPTIONS :newrelic-scala3-api:test :newrelic-scala-api:test :newrelic-scala-cats-api:test :newrelic-scala-zio-api:test :newrelic-scala-monix-api:test -PincludeScala -Ptest${{ matrix.java-version }} --continue
./gradlew $GRADLE_OPTIONS :newrelic-scala3-api:test :newrelic-scala-api:test :newrelic-scala-cats-api:test :newrelic-scala-zio-api:test :newrelic-scala-zio2-api:test :newrelic-scala-monix-api:test -PincludeScala -Ptest${{ matrix.java-version }} --continue
- name: Run functional tests against version defined in ${{ matrix.java-version }} (attempt 3)
if: steps.run_tests_2.outcome == 'failure'
timeout-minutes: 25
run: |
# Removed ":newrelic-cats-effect3-api:test" temporarily
./gradlew $GRADLE_OPTIONS :newrelic-scala3-api:test :newrelic-scala-api:test :newrelic-scala-cats-api:test :newrelic-scala-zio-api:test :newrelic-scala-monix-api:test -PincludeScala -Ptest${{ matrix.java-version }} --continue
./gradlew $GRADLE_OPTIONS :newrelic-scala3-api:test :newrelic-scala-api:test :newrelic-scala-cats-api:test :newrelic-scala-zio-api:test :newrelic-scala-zio2-api:test :newrelic-scala-monix-api:test -PincludeScala -Ptest${{ matrix.java-version }} --continue
- name: Capture Jacoco reports
if: matrix.java-version == '11'
Expand All @@ -85,7 +85,7 @@ jobs:
with:
name: functional-tests-results-java-${{ matrix.java-version }}
# The regex for the path below will capture functional test HTML reports generated by gradle for all
# related modules: (functional_test, newrelic-scala-api, newrelic-scala-cats-api, :newrelic-cats-effect3-api, newrelic-scala-zio-api).
# related modules: (functional_test, newrelic-scala-api, newrelic-scala-cats-api, :newrelic-cats-effect3-api, newrelic-scala-zio-api, newrelic-scala-zio2-api).
# However, it's critical that the previous build step does a ./gradlew clean or the regex will capture test reports
# that were leftover in unrelated modules for unit and instrumentation tests.
path: |
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/GHA-Unit-Tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,19 @@ jobs:
id: run_tests_1
continue-on-error: true
timeout-minutes: 20
run: ./gradlew $GRADLE_OPTIONS test -x :functional_test:test -x :newrelic-scala3-api:test -x :newrelic-scala-api:test -x :newrelic-scala-cats-api:test -x :newrelic-cats-effect3-api:test -x :newrelic-scala-monix-api:test -x :newrelic-scala-zio-api:test -Ptest8 -PnoInstrumentation -PnonForkedTests --continue
run: ./gradlew $GRADLE_OPTIONS test -x :functional_test:test -x :newrelic-scala3-api:test -x :newrelic-scala-api:test -x :newrelic-scala-cats-api:test -x :newrelic-cats-effect3-api:test -x :newrelic-scala-monix-api:test -x :newrelic-scala-zio-api:test -x :newrelic-scala-zio2-api:test -Ptest8 -PnoInstrumentation -PnonForkedTests --continue

- name: Run unit tests that do not require a forked JVM (attempt 2)
id: run_tests_2
continue-on-error: true
timeout-minutes: 20
if: steps.run_tests_1.outcome == 'failure'
run: ./gradlew $GRADLE_OPTIONS test -x :functional_test:test -x :newrelic-scala3-api:test -x :newrelic-scala-api:test -x :newrelic-scala-cats-api:test -x :newrelic-cats-effect3-api:test -x :newrelic-scala-monix-api:test -x :newrelic-scala-zio-api:test -Ptest8 -PnoInstrumentation -PnonForkedTests --continue
run: ./gradlew $GRADLE_OPTIONS test -x :functional_test:test -x :newrelic-scala3-api:test -x :newrelic-scala-api:test -x :newrelic-scala-cats-api:test -x :newrelic-cats-effect3-api:test -x :newrelic-scala-monix-api:test -x :newrelic-scala-zio-api:test -x :newrelic-scala-zio2-api:test -Ptest8 -PnoInstrumentation -PnonForkedTests --continue

- name: Run unit tests that do not require a forked JVM (attempt 3)
timeout-minutes: 20
if: steps.run_tests_2.outcome == 'failure'
run: ./gradlew $GRADLE_OPTIONS test -x :functional_test:test -x :newrelic-scala3-api:test -x :newrelic-scala-api:test -x :newrelic-scala-cats-api:test -x :newrelic-cats-effect3-api:test -x :newrelic-scala-monix-api:test -x :newrelic-scala-zio-api:test -Ptest8 -PnoInstrumentation -PnonForkedTests --continue
run: ./gradlew $GRADLE_OPTIONS test -x :functional_test:test -x :newrelic-scala3-api:test -x :newrelic-scala-api:test -x :newrelic-scala-cats-api:test -x :newrelic-cats-effect3-api:test -x :newrelic-scala-monix-api:test -x :newrelic-scala-zio-api:test -x :newrelic-scala-zio2-api:test -Ptest8 -PnoInstrumentation -PnonForkedTests --continue

- name: Upload coverage to Codecov
if: matrix.java-version == '17'
Expand Down Expand Up @@ -94,20 +94,20 @@ jobs:
id: run_forked_tests_1
continue-on-error: true
timeout-minutes: 20
run: ./gradlew $GRADLE_OPTIONS test -x :functional_test:test -x :newrelic-scala3-api:test -x :newrelic-scala-api:test -x :newrelic-scala-cats-api:test -x :newrelic-cats-effect3-api:test -x :newrelic-scala-monix-api:test -x :newrelic-scala-zio-api:test -Ptest8 -PnoInstrumentation -PforkedTests --continue
run: ./gradlew $GRADLE_OPTIONS test -x :functional_test:test -x :newrelic-scala3-api:test -x :newrelic-scala-api:test -x :newrelic-scala-cats-api:test -x :newrelic-cats-effect3-api:test -x :newrelic-scala-monix-api:test -x :newrelic-scala-zio-api:test -x :newrelic-scala-zio2-api:test -Ptest8 -PnoInstrumentation -PforkedTests --continue

- name: Run unit tests that require a forked JVM (attempt 2)
id: run_forked_tests_2
continue-on-error: true
timeout-minutes: 20
if: steps.run_forked_tests_1.outcome == 'failure'
run: ./gradlew $GRADLE_OPTIONS test -x :functional_test:test -x :newrelic-scala3-api:test -x :newrelic-scala-api:test -x :newrelic-scala-cats-api:test -x :newrelic-cats-effect3-api:test -x :newrelic-scala-monix-api:test -x :newrelic-scala-zio-api:test -Ptest8 -PnoInstrumentation -PforkedTests --continue
run: ./gradlew $GRADLE_OPTIONS test -x :functional_test:test -x :newrelic-scala3-api:test -x :newrelic-scala-api:test -x :newrelic-scala-cats-api:test -x :newrelic-cats-effect3-api:test -x :newrelic-scala-monix-api:test -x :newrelic-scala-zio-api:test -x :newrelic-scala-zio2-api:test -Ptest8 -PnoInstrumentation -PforkedTests --continue

- name: Run unit tests that require a forked JVM (attempt 3)
id: run_forked_tests_3
timeout-minutes: 20
if: steps.run_forked_tests_2.outcome == 'failure'
run: ./gradlew $GRADLE_OPTIONS test -x :functional_test:test -x :newrelic-scala3-api:test -x :newrelic-scala-api:test -x :newrelic-scala-cats-api:test -x :newrelic-cats-effect3-api:test -x :newrelic-scala-monix-api:test -x :newrelic-scala-zio-api:test -Ptest8 -PnoInstrumentation -PforkedTests --continue
run: ./gradlew $GRADLE_OPTIONS test -x :functional_test:test -x :newrelic-scala3-api:test -x :newrelic-scala-api:test -x :newrelic-scala-cats-api:test -x :newrelic-cats-effect3-api:test -x :newrelic-scala-monix-api:test -x :newrelic-scala-zio-api:test -x :newrelic-scala-zio2-api:test -Ptest8 -PnoInstrumentation -PforkedTests --continue

- name: Upload coverage to Codecov
if: matrix.java-version == '17'
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish_main_snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ jobs:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.SIGNING_KEY_ID }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
run: ./gradlew $GRADLE_OPTIONS publish -x :newrelic-scala3-api:publish -x :newrelic-scala-api:publish -x :newrelic-scala-cats-api:publish -x :newrelic-cats-effect3-api:publish -x :newrelic-scala-zio-api:publish -x :agent-bridge:publish -x :agent-bridge-datastore:publish -x :newrelic-weaver:publish -x :newrelic-weaver-api:publish -x :newrelic-weaver-scala:publish -x :newrelic-weaver-scala-api:publish -x :newrelic-opentelemetry-agent-extension:publish
run: ./gradlew $GRADLE_OPTIONS publish -x :newrelic-scala3-api:publish -x :newrelic-scala-api:publish -x :newrelic-scala-cats-api:publish -x :newrelic-cats-effect3-api:publish -x :newrelic-scala-zio-api:publish -x :newrelic-scala-zio2-api:publish -x :agent-bridge:publish -x :agent-bridge-datastore:publish -x :newrelic-weaver:publish -x :newrelic-weaver-api:publish -x :newrelic-weaver-scala:publish -x :newrelic-weaver-scala-api:publish -x :newrelic-opentelemetry-agent-extension:publish
- name: Publish snapshot scala apis
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.SIGNING_KEY_ID }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
run: ./gradlew $GRADLE_OPTIONS :newrelic-scala3-api:publish :newrelic-scala-api:publish :newrelic-scala-cats-api:publish :newrelic-cats-effect3-api:publish :newrelic-scala-zio-api:publish
run: ./gradlew $GRADLE_OPTIONS :newrelic-scala3-api:publish :newrelic-scala-api:publish :newrelic-scala-cats-api:publish :newrelic-cats-effect3-api:publish :newrelic-scala-zio-api:publish :newrelic-scala-zio2-api:publish
- name: Publish snapshot apis for Security agent
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ jobs:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.SIGNING_KEY_ID }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
run: ./gradlew $GRADLE_OPTIONS publish -x :newrelic-scala3-api:publish -x :newrelic-scala-api:publish -x :newrelic-scala-cats-api:publish -x :newrelic-cats-effect3-api:publish -x :newrelic-scala-zio-api:publish -x :agent-bridge:publish -x :agent-bridge-datastore:publish -x :newrelic-weaver:publish -x :newrelic-weaver-api:publish -x :newrelic-weaver-scala:publish -x :newrelic-weaver-scala-api:publish -x :newrelic-opentelemetry-agent-extension:publish -Prelease=true
run: ./gradlew $GRADLE_OPTIONS publish -x :newrelic-scala3-api:publish -x :newrelic-scala-api:publish -x :newrelic-scala-cats-api:publish -x :newrelic-cats-effect3-api:publish -x :newrelic-scala-zio-api:publish -x :newrelic-scala-zio2-api:publish -x :agent-bridge:publish -x :agent-bridge-datastore:publish -x :newrelic-weaver:publish -x :newrelic-weaver-api:publish -x :newrelic-weaver-scala:publish -x :newrelic-weaver-scala-api:publish -x :newrelic-opentelemetry-agent-extension:publish -Prelease=true
- name: Publish release scala apis
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.SIGNING_KEY_ID }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
run: ./gradlew $GRADLE_OPTIONS :newrelic-scala3-api:publish :newrelic-scala-api:publish :newrelic-scala-cats-api:publish :newrelic-cats-effect3-api:publish :newrelic-scala-zio-api:publish -Prelease=true
run: ./gradlew $GRADLE_OPTIONS :newrelic-scala3-api:publish :newrelic-scala-api:publish :newrelic-scala-cats-api:publish :newrelic-cats-effect3-api:publish :newrelic-scala-zio-api:publish :newrelic-scala-zio2-api:publish -Prelease=true
- name: Publish apis for Security agent
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
Expand Down
3 changes: 3 additions & 0 deletions instrumentation/newrelic-scala-zio2-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This module instruments `TraceOps#txn` call in the newrelic zio 2 scala api.
This is used instead of the `@Trace` annotation which eagerly starts the Transcaction. This instrumentation takes
care of the lazy ZIO structure created in ZIO
21 changes: 21 additions & 0 deletions instrumentation/newrelic-scala-zio2-api/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apply plugin: 'scala'

isScalaProjectEnabled(project, "scala-2.13")

dependencies {
implementation(project(":agent-bridge"))
implementation(project(":newrelic-weaver-api"))
implementation(project(":newrelic-weaver-scala-api"))
implementation(project(":newrelic-scala-zio2-api"))
implementation("org.scala-lang:scala-library:2.13.3")
implementation("dev.zio:zio_2.13:2.0.0")
}

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.newrelic-scala-zio2-api',
'Implementation-Title-Alias': 'newrelic-scala-zio2-api_instrumentation' }
}

verifyInstrumentation {
verifyClasspath = false //can't verify, newrelic-scala-zio2-api is a sub project
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.newrelic.zio2.api

import com.newrelic.agent.bridge.AgentBridge
import zio._

object Util {
def wrapTrace[R, E, A](body: ZIO[R, E, A]): ZIO[R, E, A] = {
ZIO.attempt(AgentBridge.instrumentation.createScalaTxnTracer)
.foldZIO(_ => body,
tracer => if (tracer == null) {
body
} else {
body.mapBoth(
error => {
tracer.finish(new Throwable("ZIO txn body fail"))
error
},
success => {
tracer.finish(172, null)
success
})
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.newrelic.zio2.api

import com.newrelic.api.agent.weaver.Weaver
import com.newrelic.api.agent.weaver.scala.{ScalaMatchType, ScalaWeave}
import zio.ZIO


@ScalaWeave(`type` = ScalaMatchType.Object, `originalName` = "com.newrelic.zio2.api.TraceOps")
class ZIOTraceOps_Instrumentation {
def txn[R, E, A](body: ZIO[R, E, A]): ZIO[R, E, A] = Util.wrapTrace(Weaver.callOriginal)
}
25 changes: 25 additions & 0 deletions instrumentation/zio-2/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apply plugin: 'scala'

isScalaProjectEnabled(project, "scala-2.13")

dependencies {
implementation("dev.zio:zio_2.13:2.0.13")
implementation(project(":agent-bridge"))
implementation(project(":newrelic-weaver-scala-api"))
implementation(project(":newrelic-weaver-api"))
implementation('org.scala-lang:scala-library:2.13.10')
}

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.zio',
'Implementation-Title-Alias': 'zio_instrumentation' }

This comment has been minimized.

Copy link
@obenkenobi

obenkenobi Mar 4, 2024

Author Contributor

A new PR is added to change this to com.newrelic.instrumentation.zio-2

}

verifyInstrumentation {
passes 'dev.zio:zio_2.13:[2.0.0,)'
}

site {
title 'Scala'
type 'Other'
}
42 changes: 42 additions & 0 deletions instrumentation/zio-2/src/main/scala/zio/TokenAwareRunnable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package zio;

import com.newrelic.agent.bridge.AgentBridge;

import static zio.Utils.getThreadTokenAndRefCount;
import static zio.Utils.clearThreadTokenAndRefCountAndTxn;
import static zio.Utils.setThreadTokenAndRefCount;
import static zio.Utils.logTokenInfo;


public final class TokenAwareRunnable implements Runnable {
private final Runnable delegate;
private AgentBridge.TokenAndRefCount tokenAndRefCount;

public TokenAwareRunnable(Runnable delegate) {
this.delegate = delegate;
//get token state from calling Thread
this.tokenAndRefCount = getThreadTokenAndRefCount();
logTokenInfo( tokenAndRefCount, "TokenAwareRunnable token info set");
}

@Override
public void run() {
try {
if (delegate != null) {
logTokenInfo(tokenAndRefCount, "Token info set in thread");
setThreadTokenAndRefCount(tokenAndRefCount);
delegate.run();
}
} finally {
logTokenInfo(tokenAndRefCount, "Clearing token info from thread ");
clearThreadTokenAndRefCountAndTxn(tokenAndRefCount);
}
}
}
60 changes: 60 additions & 0 deletions instrumentation/zio-2/src/main/scala/zio/Utils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package zio;

import java.text.MessageFormat;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;

import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.agent.bridge.Transaction;

public class Utils {

public static AgentBridge.TokenAndRefCount getThreadTokenAndRefCount() {
AgentBridge.TokenAndRefCount tokenAndRefCount = AgentBridge.activeToken.get();
// Used to be that if the tokenAndRefCount is not null, we increment the counter and then return the tokenAndRefCount
if (tokenAndRefCount == null) {
Transaction tx = AgentBridge.getAgent().getTransaction(false);
if (tx != null) {
tokenAndRefCount = new AgentBridge.TokenAndRefCount(tx.getToken(),
AgentBridge.getAgent().getTracedMethod(), new AtomicInteger(1));
}
} else {
tokenAndRefCount.refCount.incrementAndGet();
}

return tokenAndRefCount;
}

public static void setThreadTokenAndRefCount(AgentBridge.TokenAndRefCount tokenAndRefCount) {
if (tokenAndRefCount != null) {
AgentBridge.activeToken.set(tokenAndRefCount);
tokenAndRefCount.token.link();
}
}

public static void clearThreadTokenAndRefCountAndTxn(AgentBridge.TokenAndRefCount tokenAndRefCount) {
AgentBridge.activeToken.remove();
if (tokenAndRefCount != null && tokenAndRefCount.refCount.decrementAndGet() <= 0) {
tokenAndRefCount.token.expire();
tokenAndRefCount.token = null;
}
}

public static void logTokenInfo(AgentBridge.TokenAndRefCount tokenAndRefCount, String msg) {
if (AgentBridge.getAgent().getLogger().isLoggable(Level.FINEST)) {
String tokenMsg = (tokenAndRefCount != null && tokenAndRefCount.token != null)
? String.format("[%s:%s:%d]", tokenAndRefCount.token, tokenAndRefCount.token.getTransaction(),
tokenAndRefCount.refCount.get())
: "[Empty token]";
AgentBridge.getAgent().getLogger().log(Level.FINEST, MessageFormat.format("{0}: token info {1}", tokenMsg, msg));
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package zio;

import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;

@Weave(originalName = "zio.Executor", type = MatchType.BaseClass)
public class ZIOExecutor_Instrumentation {

public boolean submit(Runnable runnable, Unsafe unsafe) {
runnable = new TokenAwareRunnable(runnable);
return Weaver.callOriginal();
}
}
Loading

0 comments on commit 085996e

Please sign in to comment.