diff --git a/.claude/skills/compile/SKILL.md b/.claude/skills/compile/SKILL.md new file mode 100644 index 0000000000..caebde530a --- /dev/null +++ b/.claude/skills/compile/SKILL.md @@ -0,0 +1,156 @@ +--- +name: compile +description: Build the SkyWalking Java agent — full build, skip tests, single module, or plugin test scenarios +user-invocable: true +allowed-tools: Bash, Read, Glob, Grep +--- + +# Compile SkyWalking Java Agent + +Build the project based on user request. Detect what they want to build and run the appropriate command. + +## Prerequisites + +- JDK 17, 21, or 25 (JDK 8 is supported at runtime but JDK 17+ is needed to compile) +- Maven is bundled as `./mvnw` (Maven wrapper) +- Git submodules must be initialized for protocol definitions + +Check JDK version first: +```bash +java -version +``` + +If submodules are not initialized: +```bash +git submodule init && git submodule update +``` + +## Build Commands + +### Full build (with tests) +```bash +./mvnw clean package -Pall +``` + +### Full build (skip tests — recommended for development) +```bash +./mvnw clean package -Dmaven.test.skip=true +``` + +### CI build (with javadoc verification) +```bash +./mvnw clean verify install javadoc:javadoc -Dmaven.test.skip=true +``` + +### Build a single plugin module +```bash +./mvnw clean package -pl apm-sniffer/apm-sdk-plugin/{plugin-name} -am -Dmaven.test.skip=true +``` +The `-am` flag builds required dependencies. Replace `{plugin-name}` with the actual plugin directory name. + +### Run checkstyle only +```bash +./mvnw checkstyle:check +``` + +### Run unit tests for a single module +```bash +./mvnw test -pl apm-sniffer/apm-sdk-plugin/{plugin-name} +``` + +### Build agent distribution only (after full build) +The built agent is in `skywalking-agent/` directory after a full build. + +### Run a plugin E2E test scenario + +The E2E test framework has a **two-phase build** (matching CI): + +**Phase 1 — Build agent + test tools + Docker images (one-time setup):** +```bash +# Build the agent (JDK 17+ required) +./mvnw clean package -Dmaven.test.skip=true + +# Switch to JDK 8 to build test tools and Docker images +# The test/plugin/pom.xml builds: runner-helper, agent-test-tools, JVM/Tomcat container images +export JAVA_HOME=$(/usr/libexec/java_home -v 8) +export PATH=$JAVA_HOME/bin:$PATH + +./mvnw --batch-mode -f test/plugin/pom.xml \ + -Dmaven.test.skip \ + -Dbase_image_java=eclipse-temurin:8-jdk \ + -Dbase_image_tomcat=tomcat:8.5-jdk8-openjdk \ + -Dcontainer_image_version=1.0.0 \ + clean package +``` + +This builds `skywalking/agent-test-jvm:1.0.0` and `skywalking/agent-test-tomcat:1.0.0` Docker images, +plus `test/plugin/dist/plugin-runner-helper.jar` and `test/plugin/agent-test-tools/dist/` (mock-collector, validator). + +**Phase 2 — Run test scenarios (per scenario):** +```bash +# Use JDK 8 (matching CI). JDK 17 works for runner-helper but JDK 8 matches CI exactly. +export JAVA_HOME=$(/usr/libexec/java_home -v 8) +export PATH=$JAVA_HOME/bin:$PATH + +# Run WITHOUT -f (reuses pre-built tools and images from Phase 1) +bash ./test/plugin/run.sh --debug {scenario-name} +``` + +**IMPORTANT flags:** +- `--debug` — keeps workspace with logs and `actualData.yaml` for inspection after test +- `-f` (force) — rebuilds ALL test tools and Docker images from scratch. **Do NOT use** if Phase 1 already completed — it re-clones `skywalking-agent-test-tool` from GitHub and rebuilds everything, which is slow and may fail due to network issues. +- Without `-f` — reuses existing tools/images. This is the normal way to run tests. + +**Key rules:** +- Run scenarios **one at a time** — they share Docker ports (8080, etc.) and will conflict if parallel +- JDK 8 test scenarios use `eclipse-temurin:8-jdk` base image +- JDK 17 test scenarios (in `plugins-jdk17-test` workflows) use `eclipse-temurin:17-jdk` base image +- After a test, check `test/plugin/workspace/{scenario}/{version}/data/actualData.yaml` vs `expectedData.yaml` for debugging failures +- Check `test/plugin/workspace/{scenario}/{version}/logs/` for container logs + +**Diagnosing "startup script not exists" failures:** +This error means the scenario ZIP wasn't built or copied into the container. The root cause is almost always a **silent Maven build failure** — `run.sh` uses `mvnw -q` (quiet mode) which hides errors. Common causes: +1. **Maven Central network timeout** — downloading a new library version fails silently. The `mvnw clean package` exits non-zero but the `-q` flag hides the error, and `run.sh` continues with missing artifacts. +2. **Docker Hub timeout** — pulling dependency images (mongo, mysql, kafka, zookeeper) fails with EOF/TLS errors. +3. **Killed previous run** — if a prior parallel run was killed mid-execution, leftover state in `test/plugin/workspace/` can interfere. Always `rm -rf test/plugin/workspace/{scenario}` before rerunning. + +To debug: run the Maven build manually in the scenario directory with verbose output: +```bash +cd test/plugin/scenarios/{scenario-name} +../../../../mvnw clean package -Dtest.framework.version={version} -Dmaven.test.skip=true +``` +If this succeeds but `run.sh` fails, it's likely a transient Maven Central network issue. Pre-download dependencies first: +```bash +# Pre-warm Maven cache for all versions before running tests +for v in $(grep -v '^#' test/plugin/scenarios/{scenario}/support-version.list | grep -v '^$'); do + cd test/plugin/scenarios/{scenario} + ../../../../mvnw dependency:resolve -Dtest.framework.version=$v -q + cd - +done +``` + +**Pre-pulling Docker dependency images:** +Scenarios with `dependencies:` in `configuration.yml` need external Docker images. Pre-pull them before running tests to avoid mid-test Docker Hub failures: +```bash +# Check what images a scenario needs +grep "image:" test/plugin/scenarios/{scenario}/configuration.yml +# Pull them +docker pull {image:tag} +``` + +### Generate protobuf sources (needed before IDE import) +```bash +./mvnw compile -Dmaven.test.skip=true +``` +Then mark `*/target/generated-sources/protobuf/java` and `*/target/generated-sources/protobuf/grpc-java` as generated source folders in your IDE. + +## Common Issues + +- **Submodule not initialized**: If proto files are missing, run `git submodule init && git submodule update` +- **Wrong JDK version**: Agent build requires JDK 17+. Test tools build (test/plugin/pom.xml) works best with JDK 8. Check with `java -version`. +- **Checkstyle failures**: Run `./mvnw checkstyle:check` to see violations. Common: star imports, unused imports, System.out.println, missing @Override. +- **Test scenario Docker issues**: Ensure Docker daemon is running. Use `--debug` flag to inspect `actualData.yaml`. +- **`run.sh -f` fails on agent-test-tools**: The `-f` flag clones `skywalking-agent-test-tool` from GitHub and rebuilds from source. If GitHub is slow or unreachable, this fails. Solution: run Phase 1 build separately (see above), then use `run.sh` without `-f`. +- **Lombok errors in runner-helper on JDK 25**: The test framework uses Lombok 1.18.20 which doesn't support JDK 25. Use JDK 8 or JDK 17 for building and running test tools. +- **"startup script not exists" inside container**: The scenario ZIP wasn't built or copied correctly. Check that `mvnw clean package` succeeds in the scenario directory and produces both a `.jar` and `.zip` in `target/`. +- **Port conflicts**: Never run multiple E2E scenarios simultaneously — they all bind to the same Docker ports. diff --git a/.claude/skills/new-plugin/SKILL.md b/.claude/skills/new-plugin/SKILL.md new file mode 100644 index 0000000000..14008fdf35 --- /dev/null +++ b/.claude/skills/new-plugin/SKILL.md @@ -0,0 +1,1141 @@ +--- +name: new-plugin +description: Develop a new SkyWalking Java agent plugin — instrumentation, interceptor, tracing/meter, tests, and all boilerplate +user-invocable: true +allowed-tools: Read, Write, Edit, Glob, Grep, Bash, Agent +--- + +# SkyWalking Java Agent Plugin Development + +Develop a new plugin for the Apache SkyWalking Java Agent. Ask the user what library/framework to instrument and what to observe (tracing, metrics, or both), then generate all required files. + +## Step 0 - Gather Requirements + +Ask the user: +1. **Target library/framework** and version range (e.g., "Jedis 3.x-4.x", "Spring Kafka 2.7+") +2. **Observation type**: tracing plugin, meter plugin, or both +3. **Plugin category**: SDK plugin (default), bootstrap plugin, or optional plugin +4. **Span type needed**: Entry (server/consumer), Exit (client/producer), Local (internal), or combination + +If the user already provided this info, skip asking. + +## Step 1 - Understand the Library and Identify Interception Points + +This is the most critical step. Do NOT jump to picking method names. Follow these phases in order. + +### Phase 1: Understand How the Library Is Used + +Read the target library's documentation, quickstart guides, or sample code. Understand the **user-facing API** — how developers create clients, make calls, and handle responses. This tells you: +- What objects are long-lived (clients, connections, pools) vs. per-request (requests, commands) +- Where configuration lives (server address, credentials, timeouts) +- Whether the library is sync, async, or reactive +- Whether it uses callbacks, futures, or blocking calls + +Example thought process for a Redis client: +``` +User creates: RedisClient client = RedisClient.create("redis://localhost:6379"); +User connects: StatefulRedisConnection conn = client.connect(); +User executes: conn.sync().get("key"); // or conn.async().get("key") +``` +This tells you: connection holds the server address, commands are executed on the connection. + +### Phase 2: Trace the Execution Flow + +Starting from the user-facing API, trace inward through the library source code to understand the execution workflow: +1. What happens when the user calls the API method? +2. Where does the request object get built? +3. Where is the actual network I/O or dispatch? +4. Where is the response available? +5. For RPC/MQ: where are headers/metadata accessible for inject/extract? + +**Key question at each point:** What data is directly accessible as method arguments, return values, or fields on `this`? You want interception points where you can read the data you need **without reflection**. + +### Phase 3: Choose Interception Points + +Pick interception points based on these principles: + +**Principle 1: Data accessibility without reflection.** +Choose methods where the information you need (peer address, operation name, request/response details, headers for inject/extract) is directly available as method arguments, return values, or accessible through the `this` object's public API. **Never use reflection to read private fields.** If the data is not accessible at one method, look at a different point in the execution flow. + +If the target class is **package-private** (e.g., `final class` without `public`), you cannot import or cast to it. **Same-package helper classes do NOT work** because the agent and application use different classloaders — Java treats them as different runtime packages even with the same package name (`IllegalAccessError`). Use `setAccessible` reflection to call public methods: +```java +try { + java.lang.reflect.Method method = objInst.getClass().getMethod("publicMethodName"); + method.setAccessible(true); // Required for package-private class + Object result = method.invoke(objInst); +} catch (Exception e) { + LOGGER.warn("Failed to access method", e); +} +``` + +**Principle 2: Use `EnhancedInstance` dynamic field to propagate context inside the library.** +This is the primary mechanism for passing data between interception points. The agent adds a dynamic field to every enhanced class via `EnhancedInstance`. Use it to: +- Store server address (peer) at connection/client creation time, retrieve it at command execution time +- Store request info at request-build time, retrieve it at send time +- Pass span references from the initiating method to the completion callback + +**Do NOT use `Map` or other caches to store per-instance context.** Always use the dynamic field on the relevant `EnhancedInstance`. Maps introduce memory leaks, concurrency issues, and are slower than the direct field access that `EnhancedInstance` provides. + +**Principle 3: Intercept the minimal set of methods.** +Prefer one well-chosen interception point over many surface-level ones. If a library has 20 command methods that all flow through a single `dispatch()` method internally, intercept `dispatch()` — not all 20. + +**Principle 4: Pick points where you can do inject/extract for cross-process propagation.** +For RPC/HTTP/MQ plugins, you need to inject trace context into outgoing requests (ExitSpan) or extract from incoming requests (EntrySpan). The interception point MUST be where headers/metadata are writable (inject) or readable (extract). If headers are not accessible at the execution method, look for: +- A request builder/decorator stage where headers can be added +- A channel/transport layer where metadata is attached +- A message properties object accessible from the method arguments + +**Principle 5: Consider the span lifecycle across threads.** +If the library dispatches work asynchronously: +- Identify where the work is submitted (original thread) and where the result arrives (callback/future thread) +- You may need interception points in both threads +- Use `EnhancedInstance` dynamic field on the task/callback/future object to carry the span or `ContextSnapshot` across the thread boundary +- Use `prepareForAsync()` / `asyncFinish()` if the span must stay open across threads + +### Phase 4: Map Out the Interception Plan + +Before writing code, create a clear plan listing: + +| Target Class | Method/Constructor | What to Do | Data Available | +|---|---|---|---| +| `XxxClient` | constructor | Store peer address in dynamic field | host, port from args | +| `XxxConnection` | `execute(Command)` | Create ExitSpan, inject carrier into command headers | command name, peer from dynamic field | +| `XxxResponseHandler` | `onComplete(Response)` | Set response tags, stop span or asyncFinish | status code, error from args | + +For each interception point, verify: +- [ ] The data I need is readable from method args, `this`, or `EnhancedInstance` dynamic field — no reflection needed +- [ ] For inject: I can write headers/metadata through a public API on a method argument +- [ ] For extract: I can read headers/metadata through a public API on a method argument +- [ ] The `this` object (or a method argument) will be enhanced as `EnhancedInstance`, so I can use the dynamic field + +### Choosing Span Type + +| Scenario | Span Type | Requires | +|----------|-----------|----------| +| Receiving requests (HTTP server, MQ consumer, RPC provider) | EntrySpan | Extract ContextCarrier from incoming headers | +| Making outgoing calls (HTTP client, DB, cache, MQ producer, RPC consumer) | ExitSpan | Peer address; inject ContextCarrier into outgoing headers (for RPC/HTTP/MQ) | +| Internal processing (annotation-driven, local logic) | LocalSpan | Nothing extra | + +### For Meter Plugins + +Meter plugins follow the same understand-then-intercept process, but the goal is to find objects that expose numeric state: +- **Gauges**: Intercept the creation of pool/executor/connection-manager objects. Register a `MeterFactory.gauge()` with a supplier lambda that calls the object's own getter methods (e.g., `pool.getActiveCount()`). Store the gauge reference in the dynamic field if needed. +- **Counters**: Intercept execution methods and call `counter.increment()` on each invocation. +- **Histograms**: Intercept methods where duration or size is computable (measure between before/after, or read from response). + +### Check Existing Plugins for Reference + +Before writing a new plugin, check if a similar library already has a plugin: +``` +apm-sniffer/apm-sdk-plugin/ # 70+ standard plugins +apm-sniffer/optional-plugins/ # Optional plugins +apm-sniffer/bootstrap-plugins/ # JDK-level plugins +``` + +Similar libraries often share execution patterns. Study how an existing plugin for a similar library solved the same problems — especially how it chains dynamic fields across multiple interception points and where it does inject/extract. + +### Verify Against Actual Source Code — Never Speculate + +**This applies to both new plugin development AND extending existing plugins to newer library versions.** + +When assessing whether a plugin works with a new library version, or when choosing interception points for a new plugin, you MUST read the **actual source code** of the target library at the specific version. Do NOT rely on: +- Version number assumptions ("it's still 4.x so it should be compatible") +- Changelog summaries (they don't list every internal class rename or method removal) +- General knowledge about the library's public API (plugins intercept internal classes, which change without notice) + +**What to verify for each intercepted class/method:** +1. Does the target class still exist at the exact FQCN? (internal classes get renamed, extracted, or removed between minor versions) +2. Does the intercepted method still exist with a compatible signature? (parameters may be added/removed/reordered) +3. Do the witness classes still correctly distinguish versions? (a witness class that exists in both old and new versions won't prevent the plugin from loading on an incompatible version) +4. Do the runtime APIs called by the interceptor still exist? (e.g., calling `cluster.getDescription()` will crash if that method was removed, even if the plugin loaded successfully) + +**How to verify — clone the library source locally:** +```bash +# Clone specific version tag to /tmp for easy source code inspection +cd /tmp && git clone --depth 1 --branch {tag} https://github.com/{org}/{repo}.git {local-name} + +# Examples: +git clone --depth 1 --branch r4.9.0 https://github.com/mongodb/mongo-java-driver.git mongo-4.9 +git clone --depth 1 --branch v2.4.13.RELEASE https://github.com/spring-projects/spring-kafka.git spring-kafka-2.4 + +# Then grep/read the actual source files to check class/method existence +grep -rn "getFilter\|getWriteRequests" /tmp/mongo-4.9/driver-core/src/main/com/mongodb/internal/operation/ +``` + +This is faster and more reliable than fetching individual files via raw GitHub URLs. You can `grep`, `diff` between versions, and trace the full execution path. + +**Also check for import-time class loading failures:** +If a plugin helper class (not just the instrumentation class) imports a library class that was removed, the entire helper class will fail to load with `NoClassDefFoundError` at runtime. This silently breaks ALL functionality in that helper — not just the code paths using the removed class. Verify that every `import` statement in plugin support classes resolves to an existing class in the target version. + +**Real examples of why this matters:** +- MongoDB driver 4.9 removed `InsertOperation`, `DeleteOperation`, `UpdateOperation` — `MongoOperationHelper` imported all three, causing the entire class to fail loading with `NoClassDefFoundError`, silently losing ALL `db.bind_vars` tags even for operations that still exist (like `FindOperation`, `AggregateOperation`) +- MongoDB driver 4.11 removed `Cluster.getDescription()` — the plugin loads (witness classes pass) but crashes at runtime with `NoSuchMethodError` +- Feign 12.2 moved `ReflectiveFeign$BuildTemplateByResolvingArgs` to `RequestTemplateFactoryResolver$BuildTemplateByResolvingArgs` — the path variable interception silently stops working +- MariaDB 3.0 renamed every JDBC wrapper class (`MariaDbConnection` → `Connection`) — none of the plugin's `byName` matchers match anything + +**When extending `support-version.list` to add newer versions:** +Before adding a version, verify that every class and method the plugin intercepts still exists in that version's source. A plugin test passing does not mean everything works — it only means the test scenario's specific code path exercised the intercepted methods. Missing interception points may go undetected if the test doesn't cover them. + +## Step 2 - Create Plugin Module + +### Directory Structure + +**SDK plugin** (most common): +``` +apm-sniffer/apm-sdk-plugin/{framework}-{version}-plugin/ + pom.xml + src/main/java/org/apache/skywalking/apm/plugin/{framework}/v{N}/ + define/ + {Target}Instrumentation.java # One per target class + {Target}Interceptor.java # One per interception concern + {Target}ConstructorInterceptor.java # If intercepting constructors + {PluginName}PluginConfig.java # If plugin needs configuration + src/main/resources/ + skywalking-plugin.def # Plugin registration + src/test/java/org/apache/skywalking/apm/plugin/{framework}/v{N}/ + {Target}InterceptorTest.java # Unit tests +``` + +**Bootstrap plugin** (for JDK classes): +``` +apm-sniffer/bootstrap-plugins/{name}-plugin/ + (same structure, but instrumentation class overrides isBootstrapInstrumentation) +``` + +**Optional plugin**: +``` +apm-sniffer/optional-plugins/{name}-plugin/ + (same structure as SDK plugin) +``` + +### pom.xml Template + +```xml + + + 4.0.0 + + + org.apache.skywalking + apm-sdk-plugin + ${revision} + + + {framework}-{version}-plugin + jar + + + X.Y.Z + + + + + + com.example + target-library + ${target-library.version} + provided + + + +``` + +**CRITICAL dependency rules:** +- Target library: **always `provided` scope** (supplied by the application at runtime) +- `apm-agent-core`: inherited from parent POM as `provided` +- `apm-util`: inherited from parent POM as `provided` +- Never bundle target library classes into the plugin JAR +- If the plugin needs a 3rd-party utility not already in agent-core, discuss with maintainers first + +**CRITICAL compiler level rule:** +- Do NOT set `maven.compiler.release` or `maven.compiler.source/target` unless the plugin source code itself uses JDK 9+ language features (e.g., `var`, records, sealed classes, `java.net.http.HttpClient`). +- A `provided`-scope dependency targeting a higher JDK (e.g., Spring AI requires JDK 17+) does NOT require raising the compiler level — the plugin only references the library's API at compile time. +- Bootstrap plugins like `jdk-httpclient-plugin` (which uses JDK 11 `HttpClient` API directly in plugin source code) legitimately need a higher compiler target. SDK plugins and optional plugins generally should not. + +### Register in Parent POM + +Add the new module to the parent `pom.xml`: +```xml + + ... + {framework}-{version}-plugin + +``` + +## Step 3 - Implement Instrumentation Class (V2 API) + +**ALWAYS use V2 API for new plugins.** V1 is legacy. + +### Import Rules (Enforced by Checkstyle) + +Plugins may ONLY import from: +- `java.*` - Java standard library +- `org.apache.skywalking.*` - SkyWalking modules +- `net.bytebuddy.*` - ByteBuddy (for matchers in instrumentation classes) + +**No other 3rd-party imports are allowed in instrumentation/activation files.** This is enforced by `apm-checkstyle/importControl.xml`. Interceptor classes CAN reference target library classes (they're loaded after the target library). + +### Class Matching + +**CRITICAL: NEVER use `.class` references in instrumentation definitions.** Always use string literals. + +```java +// WRONG - breaks agent if class doesn't exist at runtime +byName(SomeThirdPartyClass.class.getName()) +takesArgument(0, SomeThirdPartyClass.class) + +// CORRECT - safe string literals +byName("com.example.SomeThirdPartyClass") +takesArgumentWithType(0, "com.example.SomeThirdPartyClass") +``` + +Available ClassMatch types (from `org.apache.skywalking.apm.agent.core.plugin.match`): + +| Matcher | Usage | Performance | +|---------|-------|-------------| +| `NameMatch.byName(String)` | Exact class name | Best (HashMap lookup) | +| `MultiClassNameMatch.byMultiClassMatch(String...)` | Multiple exact names | Good | +| `HierarchyMatch.byHierarchyMatch(String...)` | Implements interface / extends class | Expensive - avoid unless necessary | +| `ClassAnnotationMatch.byClassAnnotationMatch(String...)` | Has annotation(s) | Moderate | +| `MethodAnnotationMatch.byMethodAnnotationMatch(String...)` | Has method with annotation | Moderate | +| `PrefixMatch.nameStartsWith(String...)` | Class name prefix | Moderate | +| `RegexMatch.byRegexMatch(String...)` | Regex on class name | Expensive | +| `LogicalMatchOperation.and(match1, match2)` | AND composition | Depends on operands | +| `LogicalMatchOperation.or(match1, match2)` | OR composition | Depends on operands | + +**Prefer `NameMatch.byName()` whenever possible.** It uses a fast HashMap lookup. All other matchers require linear scanning. + +### Method Matching (ByteBuddy ElementMatcher API) + +Common matchers from `net.bytebuddy.matcher.ElementMatchers`: + +```java +// By name +named("methodName") + +// By argument count +takesArguments(2) +takesArguments(0) // no-arg methods + +// By argument type (use SkyWalking's helper - string-based, safe) +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; +takesArgumentWithType(0, "com.example.SomeType") // arg at index 0 + +// By return type (SkyWalking helper) +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ReturnTypeNameMatch.returnsWithType; +returnsWithType("java.util.List") + +// By annotation (SkyWalking helper - string-based) +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.AnnotationTypeNameMatch.isAnnotatedWithType; +isAnnotatedWithType("org.springframework.web.bind.annotation.RequestMapping") + +// Visibility +isPublic() +isPrivate() + +// Composition +named("execute").and(takesArguments(1)) +named("method1").or(named("method2")) +not(isDeclaredBy(Object.class)) + +// Match any (use sparingly) +any() +``` + +### Instrumentation Template - Instance Methods + +```java +package org.apache.skywalking.apm.plugin.xxx.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.ClassInstanceMethodsEnhancePluginDefineV2; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class XxxInstrumentation extends ClassInstanceMethodsEnhancePluginDefineV2 { + + private static final String ENHANCE_CLASS = "com.example.TargetClass"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.xxx.XxxInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return null; // null or empty array if not intercepting constructors + } + + @Override + public InstanceMethodsInterceptV2Point[] getInstanceMethodsInterceptV2Points() { + return new InstanceMethodsInterceptV2Point[] { + new InstanceMethodsInterceptV2Point() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("targetMethod"); + } + + @Override + public String getMethodsInterceptorV2() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} +``` + +### Instrumentation Template - Static Methods + +```java +import org.apache.skywalking.apm.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.ClassStaticMethodsEnhancePluginDefineV2; + +public class XxxStaticInstrumentation extends ClassStaticMethodsEnhancePluginDefineV2 { + @Override + public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() { + return new StaticMethodsInterceptPoint[] { + new StaticMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("factoryMethod").and(takesArguments(2)); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } +} +``` + +### Instrumentation Template - Both Instance + Static Methods + +Extend `ClassEnhancePluginDefineV2` and implement all four methods: +- `enhanceClass()` +- `getConstructorsInterceptPoints()` +- `getInstanceMethodsInterceptV2Points()` +- `getStaticMethodsInterceptPoints()` + +### Matching Interface/Abstract Class Implementations + +Use `HierarchyMatch` when you need to intercept all implementations of an interface: + +```java +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +@Override +protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch("com.example.SomeInterface"); +} +``` + +**When to use HierarchyMatch:** +- The library has an interface/abstract class with multiple implementations +- You cannot enumerate all implementation class names +- Example: intercepting all `javax.servlet.Servlet` implementations + +**Performance warning:** HierarchyMatch checks every loaded class against the hierarchy. Prefer `NameMatch` or `MultiClassNameMatch` if you know the concrete class names. + +**Combining with other matchers:** +```java +import org.apache.skywalking.apm.agent.core.plugin.match.logical.LogicalMatchOperation; + +@Override +protected ClassMatch enhanceClass() { + return LogicalMatchOperation.and( + PrefixMatch.nameStartsWith("com.example"), + HierarchyMatch.byHierarchyMatch("java.lang.Runnable") + ); +} +``` + +### Witness Classes for Version Detection + +Override `witnessClasses()` or `witnessMethods()` to activate the plugin only for specific library versions: + +```java +@Override +protected String[] witnessClasses() { + // Plugin only loads if this class exists in the application + return new String[] {"com.example.VersionSpecificClass"}; +} + +@Override +protected List witnessMethods() { + return Collections.singletonList( + new WitnessMethod("com.example.SomeClass", ElementMatchers.named("methodAddedInV2")) + ); +} +``` + +### Bootstrap Plugin Override + +For bootstrap plugins (instrumenting JDK classes), add: +```java +@Override +public boolean isBootstrapInstrumentation() { + return true; +} +``` + +**Bootstrap plugin rules:** +- Only for JDK core classes (java.*, javax.*, sun.*) +- Minimal interception scope (performance-critical paths) +- Extra care with class loading (bootstrap classloader visibility) +- Test with `runningMode: with_bootstrap` + +## Step 4 - Implement Interceptor Class (V2 API) + +### Available Interceptor Interfaces + +| Interface | Use Case | +|-----------|----------| +| `InstanceMethodsAroundInterceptorV2` | Instance method interception | +| `StaticMethodsAroundInterceptorV2` | Static method interception | +| `InstanceConstructorInterceptor` | Constructor interception (shared V1/V2) | + +### Core APIs Available in Interceptors + +**ContextManager** - Central tracing API (ThreadLocal-based): + +**CRITICAL threading rule:** All span lifecycle APIs (`createEntrySpan`, `createExitSpan`, `createLocalSpan`, `activeSpan`, `stopSpan`) operate on a **per-thread context via ThreadLocal**. By default, `createXxxSpan` and `stopSpan` MUST be called in the **same thread**. There are only two ways to work across threads: +1. **`ContextSnapshot` (capture/continued)** — snapshot the context in thread A, then `continued()` in thread B to link a NEW span in thread B back to the parent trace. Each thread manages its own span lifecycle independently. +2. **Async mode (`prepareForAsync`/`asyncFinish`)** — keeps a single span alive beyond the creating thread. Call `prepareForAsync()` in the original thread (before `stopSpan`), then `asyncFinish()` from any thread when the async work completes. Between `prepareForAsync` and `asyncFinish`, you may call tag/log/error on the span from any thread, but you must NOT call `ContextManager.stopSpan()` for that span again. + +```java +import org.apache.skywalking.apm.agent.core.context.ContextManager; + +// Create spans (must stopSpan in the SAME thread, unless async mode) +AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier); +AbstractSpan span = ContextManager.createLocalSpan(operationName); +AbstractSpan span = ContextManager.createExitSpan(operationName, contextCarrier, remotePeer); +AbstractSpan span = ContextManager.createExitSpan(operationName, remotePeer); + +// Span lifecycle (same thread as create, unless async mode) +ContextManager.activeSpan(); // Get current span in THIS thread +ContextManager.stopSpan(); // Stop current span in THIS thread +ContextManager.isActive(); // Check if context exists in THIS thread + +// Cross-process propagation (inject/extract ContextCarrier into headers/metadata) +ContextManager.inject(carrier); // Inject into outgoing carrier +ContextManager.extract(carrier); // Extract from incoming carrier + +// Cross-thread propagation (ContextSnapshot — link spans across threads) +ContextManager.capture(); // Capture snapshot in originating thread +ContextManager.continued(snapshot); // Continue from snapshot in receiving thread + +// Trace metadata +ContextManager.getGlobalTraceId(); +ContextManager.getSegmentId(); +ContextManager.getSpanId(); +``` + +**AbstractSpan** - Span configuration: +```java +span.setComponent(ComponentsDefine.YOUR_COMPONENT); // Required for Entry/Exit +span.setLayer(SpanLayer.HTTP); // Required for Entry/Exit +span.setOperationName("GET:/api/users"); +span.setPeer("host:port"); // Required for Exit spans + +// Tags +span.tag(Tags.URL, url); +span.tag(Tags.HTTP_RESPONSE_STATUS_CODE, statusCode); +span.tag(Tags.DB_TYPE, "sql"); +span.tag(Tags.DB_STATEMENT, sql); +span.tag(Tags.ofKey("custom.key"), value); + +// Error handling +span.errorOccurred(); +span.log(throwable); + +// Async support +span.prepareForAsync(); // Must call in original thread +span.asyncFinish(); // Call in async thread when done +``` + +**SpanLayer** values: `DB`, `RPC_FRAMEWORK`, `HTTP`, `MQ`, `CACHE`, `GEN_AI` + +**Standard Tags** (from `org.apache.skywalking.apm.agent.core.context.tag.Tags`): + +| Tag | Constant | Purpose | +|---------------------|----------------------------------|--------------------------| +| `url` | `Tags.URL` | Request URL | +| `http.status_code` | `Tags.HTTP_RESPONSE_STATUS_CODE` | HTTP status (IntegerTag) | +| `http.method` | `Tags.HTTP.METHOD` | HTTP method | +| `db.type` | `Tags.DB_TYPE` | Database type | +| `db.instance` | `Tags.DB_INSTANCE` | Database name | +| `db.statement` | `Tags.DB_STATEMENT` | SQL/query | +| `db.bind_variables` | `Tags.DB_BIND_VARIABLES` | Bound params | +| `mq.queue` | `Tags.MQ_QUEUE` | Queue name | +| `mq.topic` | `Tags.MQ_TOPIC` | Topic name | +| `mq.broker` | `Tags.MQ_BROKER` | Broker address | +| `cache.type` | `Tags.CACHE_TYPE` | Cache type | +| `cache.op` | `Tags.CACHE_OP` | "read" or "write" | +| `cache.cmd` | `Tags.CACHE_CMD` | Cache command | +| `cache.key` | `Tags.CACHE_KEY` | Cache key | +| Custom | `Tags.ofKey("key")` | Any custom tag | + +**EnhancedInstance** - Dynamic field for cross-interceptor data: +```java +// Store data (e.g., in constructor interceptor) +objInst.setSkyWalkingDynamicField(connectionInfo); + +// Retrieve data (e.g., in method interceptor) +ConnectionInfo info = (ConnectionInfo) objInst.getSkyWalkingDynamicField(); +``` + +**Logging** - Agent-internal logging (NOT application logging): +```java +import org.apache.skywalking.apm.agent.core.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.logging.api.LogManager; + +private static final ILog LOGGER = LogManager.getLogger(MyInterceptor.class); +LOGGER.info("message: {}", value); +LOGGER.error("error", throwable); +``` + +**MeterFactory** - For meter plugins: +```java +import org.apache.skywalking.apm.toolkit.meter.MeterFactory; +import org.apache.skywalking.apm.toolkit.meter.Counter; +import org.apache.skywalking.apm.toolkit.meter.Gauge; +import org.apache.skywalking.apm.toolkit.meter.Histogram; + +Counter counter = MeterFactory.counter("metric_name") + .tag("key", "value") + .mode(Counter.Mode.INCREMENT) + .build(); +counter.increment(1.0); + +Gauge gauge = MeterFactory.gauge("metric_name", () -> pool.getActiveCount()) + .tag("pool_name", name) + .build(); + +Histogram histogram = MeterFactory.histogram("metric_name") + .steps(Arrays.asList(10.0, 50.0, 100.0, 500.0)) + .minValue(0) + .build(); +histogram.addValue(latencyMs); +``` + +### Interceptor Template - ExitSpan (Client/Producer) + +```java +package org.apache.skywalking.apm.plugin.xxx; + +import java.lang.reflect.Method; +import org.apache.skywalking.apm.agent.core.context.CarrierItem; +import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +public class XxxClientInterceptor implements InstanceMethodsAroundInterceptorV2 { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, MethodInvocationContext context) throws Throwable { + // 1. Build peer address from stored connection info + String remotePeer = (String) objInst.getSkyWalkingDynamicField(); + + // 2. Create ExitSpan with ContextCarrier for cross-process propagation + ContextCarrier contextCarrier = new ContextCarrier(); + AbstractSpan span = ContextManager.createExitSpan("operation/name", contextCarrier, remotePeer); + span.setComponent(ComponentsDefine.YOUR_COMPONENT); + SpanLayer.asHttp(span); // or asDB, asMQ, asRPCFramework, asCache + + // 3. Inject trace context into outgoing request headers + // The request object is typically one of the method arguments + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + // Set header on the outgoing request: + // request.setHeader(next.getHeadKey(), next.getHeadValue()); + } + + // 4. Set tags + Tags.URL.set(span, url); + + // 5. Store span in context for afterMethod + context.setContext(span); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Object ret, MethodInvocationContext context) throws Throwable { + // Check response status, set tags/errors + AbstractSpan span = (AbstractSpan) context.getContext(); + // Example: Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode); + // if (statusCode >= 400) span.errorOccurred(); + + ContextManager.stopSpan(); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t, MethodInvocationContext context) { + ContextManager.activeSpan().log(t); + ContextManager.activeSpan().errorOccurred(); + } +} +``` + +### Interceptor Template - EntrySpan (Server/Consumer) + +```java +public class XxxServerInterceptor implements InstanceMethodsAroundInterceptorV2 { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, MethodInvocationContext context) throws Throwable { + // 1. Extract trace context from incoming request headers + ContextCarrier contextCarrier = new ContextCarrier(); + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + // Read header from incoming request: + // next.setHeadValue(request.getHeader(next.getHeadKey())); + } + + // 2. Create EntrySpan (extracts context automatically) + AbstractSpan span = ContextManager.createEntrySpan("operation/name", contextCarrier); + span.setComponent(ComponentsDefine.YOUR_COMPONENT); + SpanLayer.asHttp(span); // or asMQ, asRPCFramework + span.setPeer(clientAddress); // Optional: client address + + context.setContext(span); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Object ret, MethodInvocationContext context) throws Throwable { + ContextManager.stopSpan(); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t, MethodInvocationContext context) { + ContextManager.activeSpan().log(t); + ContextManager.activeSpan().errorOccurred(); + } +} +``` + +### Interceptor Template - Constructor (Store Connection Info) + +```java +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; + +public class XxxConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + // Store connection info for later use by method interceptors + String host = (String) allArguments[0]; + int port = (int) allArguments[1]; + objInst.setSkyWalkingDynamicField(host + ":" + port); + } +} +``` + +### Cross-Thread Context Propagation (ContextSnapshot) + +Use `ContextSnapshot` when the library dispatches work to another thread and you want the new thread's spans to be linked to the parent trace. Each thread creates and stops its OWN spans — the snapshot only provides the link. + +```java +// Thread A (originating thread) — create span, capture snapshot, stop span (all same thread) +@Override +public void beforeMethod(..., MethodInvocationContext context) { + AbstractSpan span = ContextManager.createLocalSpan("async/dispatch"); + // Capture context snapshot BEFORE handing off to another thread + ContextSnapshot snapshot = ContextManager.capture(); + // Store snapshot on the task object via EnhancedInstance dynamic field + ((EnhancedInstance) allArguments[0]).setSkyWalkingDynamicField(snapshot); + ContextManager.stopSpan(); // Stop span in THIS thread (same thread as create) +} + +// Thread B (receiving thread) — create its OWN span, link to parent via continued() +@Override +public void beforeMethod(EnhancedInstance objInst, ...) { + ContextSnapshot snapshot = (ContextSnapshot) objInst.getSkyWalkingDynamicField(); + if (snapshot != null) { + AbstractSpan span = ContextManager.createLocalSpan("async/execute"); + ContextManager.continued(snapshot); // Link this span to the parent trace + } +} + +@Override +public Object afterMethod(...) { + if (ContextManager.isActive()) { + ContextManager.stopSpan(); // Stop span in THIS thread (same thread as create) + } + return ret; +} +``` + +### Async Span Pattern (prepareForAsync / asyncFinish) + +Use this when a **single span** needs to stay open across thread boundaries — e.g., an ExitSpan created before an async call, finished when the callback fires in another thread. The key difference from ContextSnapshot: here one span lives across threads instead of each thread having its own span. + +```java +// Thread A — create span, mark async, stop context (all same thread) +@Override +public void beforeMethod(..., MethodInvocationContext context) { + AbstractSpan span = ContextManager.createExitSpan("async/call", remotePeer); + span.setComponent(ComponentsDefine.YOUR_COMPONENT); + SpanLayer.asHttp(span); + + span.prepareForAsync(); // Mark: this span will finish in another thread + ContextManager.stopSpan(); // Detach from THIS thread's context (required, same thread as create) + + // Store span reference on the callback object's dynamic field + ((EnhancedInstance) callback).setSkyWalkingDynamicField(span); +} + +// Thread B (callback/completion handler) — finish the async span +@Override +public void beforeMethod(EnhancedInstance objInst, ...) { + AbstractSpan span = (AbstractSpan) objInst.getSkyWalkingDynamicField(); + if (span != null) { + // Add response info to the span (tag/log/error are thread-safe after prepareForAsync) + span.tag(Tags.HTTP_RESPONSE_STATUS_CODE, statusCode); + if (isError) span.errorOccurred(); + span.asyncFinish(); // Must match prepareForAsync count + } +} +``` + +### Plugin Configuration (Optional) + +```java +import org.apache.skywalking.apm.agent.core.boot.PluginConfig; + +public class XxxPluginConfig { + public static class Plugin { + @PluginConfig(root = XxxPluginConfig.class) + public static class Xxx { + // Config key: plugin.xxx.trace_param + public static boolean TRACE_PARAM = false; + // Config key: plugin.xxx.max_length + public static int MAX_LENGTH = 256; + } + } +} +``` + +## Step 5 - Register Plugin + +Create `src/main/resources/skywalking-plugin.def`: +``` +plugin-name=org.apache.skywalking.apm.plugin.xxx.define.XxxInstrumentation +plugin-name=org.apache.skywalking.apm.plugin.xxx.define.XxxOtherInstrumentation +``` + +Format: `{plugin-id}={fully.qualified.InstrumentationClassName}` (one line per instrumentation class, all sharing the same plugin-id prefix). + +## Step 6 - Write Unit Tests + +### Test Setup Pattern + +```java +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; + +@RunWith(TracingSegmentRunner.class) +public class XxxInterceptorTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule agentServiceRule = new AgentServiceRule(); + + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @Mock + private EnhancedInstance enhancedInstance; + + private XxxInterceptor interceptor; + + @Before + public void setUp() { + interceptor = new XxxInterceptor(); + // Setup mocks + } + + @Test + public void testNormalRequest() throws Throwable { + // Arrange + Object[] allArguments = new Object[] { /* mock args */ }; + Class[] argumentsTypes = new Class[] { /* arg types */ }; + + // Act + interceptor.beforeMethod(enhancedInstance, null, allArguments, argumentsTypes, null); + interceptor.afterMethod(enhancedInstance, null, allArguments, argumentsTypes, mockResponse, null); + + // Assert spans + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment segment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(segment); + assertThat(spans.size(), is(1)); + // Verify span properties... + } +} +``` + +## Step 7 - Write E2E Plugin Tests + +### Test Scenario Structure + +``` +test/plugin/scenarios/{framework}-{version}-scenario/ + bin/startup.sh + config/expectedData.yaml + src/main/java/org/apache/skywalking/apm/testcase/{framework}/ + controller/CaseController.java # HTTP endpoints + pom.xml + configuration.yml + support-version.list +``` + +**When copying an existing scenario to create a new one**, update the scenario name in ALL of these files: +- `pom.xml` — `artifactId`, `name`, `finalName` +- `src/main/assembly/assembly.xml` — JAR filename reference +- `bin/startup.sh` — JAR filename in java -jar command +- `config/expectedData.yaml` — `serviceName` field AND `parentService` in refs (but NOT URL paths — those are the app context path) +- `support-version.list` — new versions +- For JDK 17+ scenarios: update `compiler.version` to `17`, `spring.boot.version` to `3.x`, change `javax.annotation` imports to `jakarta.annotation` in Java source +- Add the scenario to the appropriate CI workflow (`plugins-test.*.yaml` for JDK 8, `plugins-jdk17-test.*.yaml` for JDK 17) + +### configuration.yml + +```yaml +type: jvm +entryService: http://localhost:8080/{scenario-name}/case/{endpoint} +healthCheck: http://localhost:8080/{scenario-name}/case/healthCheck +startScript: ./bin/startup.sh +environment: [] +dependencies: {} +``` + +### expectedData.yaml for Tracing + +```yaml +segmentItems: + - serviceName: {scenario-name} + segmentSize: ge 1 + segments: + - segmentId: not null + spans: + - operationName: your/operation + parentSpanId: -1 + spanId: 0 + spanLayer: Http # Http, DB, RPCFramework, MQ, CACHE + spanType: Exit # Entry, Exit, Local + startTime: nq 0 + endTime: nq 0 + componentId: 2 # Must match ComponentsDefine ID + isError: false + peer: 'host:port' + skipAnalysis: 'false' + tags: + - {key: url, value: not null} + - {key: http.method, value: GET} + logs: [] + refs: [] +``` + +### expectedData.yaml for Meters + +```yaml +meterItems: + - serviceName: {scenario-name} + meterSize: ge 1 + meters: + - meterId: + name: your_counter_name + tags: + - {name: tag_key, value: tag_value} + singleValue: gt 0 +``` + +### Assertion Operators + +| Operator | Meaning | +|----------|---------| +| `eq VALUE` | Equals | +| `nq VALUE` | Not equals | +| `ge VALUE` | Greater or equal | +| `gt VALUE` | Greater than | +| `not null` | Must be present | +| `null` | Must be absent | + +### Running Tests + +```bash +bash ./test/plugin/run.sh -f {scenario-name} +bash ./test/plugin/run.sh --debug {scenario-name} # Save actualData.yaml for debugging +``` + +**IMPORTANT: Run E2E test scenarios ONE AT A TIME.** Multiple scenarios use the same Docker ports (8080, etc.) and will conflict if run in parallel. Always wait for one scenario to finish before starting the next. + +## Step 8 - Shading (Package Relocation) + +Plugins automatically inherit ByteBuddy shading from the parent POM: +```xml + + net.bytebuddy + org.apache.skywalking.apm.dependencies.net.bytebuddy + +``` + +The agent-core module handles shading of all core dependencies: +- `com.google.*` -> `org.apache.skywalking.apm.dependencies.com.google.*` +- `io.grpc.*` -> `org.apache.skywalking.apm.dependencies.io.grpc.*` +- `io.netty.*` -> `org.apache.skywalking.apm.dependencies.io.netty.*` +- `org.slf4j.*` -> `org.apache.skywalking.apm.dependencies.org.slf4j.*` + +**Plugins should NOT add their own shade configurations** unless they need to bundle a library not in agent-core (rare, requires maintainer approval). If a plugin needs a reporter-level dependency, see `optional-reporter-plugins/kafka-reporter-plugin/pom.xml` for the pattern. + +## Step 9 - Code Style Checklist + +Before submitting: +- [ ] No `System.out.println` (use `ILog` from `LogManager`) +- [ ] No `@author` tags (ASF policy) +- [ ] No Chinese characters in source +- [ ] No tab characters (use 4 spaces) +- [ ] No star imports (`import xxx.*`) +- [ ] No unused imports +- [ ] `@Override` on all overridden methods +- [ ] Apache 2.0 license header on all source files +- [ ] File length under 3000 lines +- [ ] Constants in `UPPER_SNAKE_CASE` +- [ ] Types in `PascalCase`, variables in `camelCase` +- [ ] Imports only from `java.*`, `org.apache.skywalking.*`, `net.bytebuddy.*` (in instrumentation files) +- [ ] Target library dependencies in `provided` scope +- [ ] No unnecessary `maven.compiler.release` or `maven.compiler.source/target` (only set if plugin source uses JDK 9+ language features) +- [ ] Using V2 API for new plugins +- [ ] String literals (not `.class` references) in instrumentation definitions +- [ ] `skywalking-plugin.def` registered +- [ ] Module added to parent POM + +## Step 10 - Update Documentation + +After verifying the plugin works (locally or via CI), update these documentation files: + +**1. `docs/en/setup/service-agent/java-agent/Supported-list.md`** +Update the version range for the relevant entry. Format example: +``` + * [MongoDB Java Driver](https://github.com/mongodb/mongo-java-driver) 2.13-2.14, 3.4.0-3.12.7, 4.0.0-4.10.2 +``` +For new plugins, add a new bullet under the appropriate category. + +**2. `CHANGES.md`** +Add a changelog entry under the current unreleased version section. Format: +``` +* Extend {plugin-name} plugin to support {library} {version-range}. +``` +Or for new plugins: +``` +* Add {framework} {version} plugin. +``` + +**3. `test/plugin/scenarios/{scenario}/support-version.list`** +Add verified versions. Only include the **latest patch version for each minor version** — do not list every patch release. + +The version list supports **extra Maven properties** per version line using comma-separated `key=value` pairs: +``` +# Simple version (default pom properties) +2.3.10.RELEASE + +# Version with overridden Maven properties +2.7.14,spring.boot.version=2.5.15 +2.8.11,spring.boot.version=2.7.18 +3.1.4,spring.boot.version=3.2.12 +``` + +This is useful when different framework versions need different dependency versions (e.g., spring-kafka minor versions require matching Spring Boot versions). The extra properties are passed as `-D` flags to Maven during the scenario build. + +**IMPORTANT:** Maven `-D` overrides work for `` and direct `${prop}` references, but do NOT override BOM versions resolved via `` imports. If a BOM version needs to change, set the default in the pom to the highest needed version, not the lowest. + +**Spring Boot / Spring Kafka compatibility mapping** (for reference): +- spring-kafka 2.3-2.6 → Spring Boot 2.3 (default) +- spring-kafka 2.7 → Spring Boot 2.5 (2.6+ autoconfigure requires `CommonErrorHandler` from spring-kafka 2.8) +- spring-kafka 2.8-2.9 → Spring Boot 2.7 +- spring-kafka 3.0 → Spring Boot 3.0 +- spring-kafka 3.1-3.3 → Spring Boot 3.2 (requires Spring Framework 6.1) + +**This step is mandatory.** Documentation updates are part of the PR requirements. + +## Quick Reference - Plugin Type Decision Tree + +``` +Is the target a JDK core class (java.*, sun.*)? + YES -> Bootstrap plugin (isBootstrapInstrumentation = true) + NO -> Is it commonly used and should be auto-activated? + YES -> SDK plugin (apm-sdk-plugin/) + NO -> Optional plugin (optional-plugins/) + +What span type? + Receiving requests (HTTP server, MQ consumer, RPC provider) -> EntrySpan + Making outgoing calls (HTTP client, DB, cache, MQ producer) -> ExitSpan + Internal processing (annotation, local logic) -> LocalSpan + +Need cross-process propagation? + YES -> Use ContextCarrier (inject on exit, extract on entry) + NO -> No carrier needed + +Need cross-thread propagation? + YES -> Use ContextSnapshot (capture + continued) OR prepareForAsync/asyncFinish + NO -> Standard span lifecycle + +Need metrics without traces? + YES -> Meter plugin (Counter/Gauge/Histogram via MeterFactory) + +Need both traces and metrics? + YES -> Separate interceptors or combine in same interceptor +``` \ No newline at end of file diff --git a/.dlc.json b/.dlc.json index 1ae0883a40..483a8c1255 100644 --- a/.dlc.json +++ b/.dlc.json @@ -26,16 +26,23 @@ }, { "pattern": "^https://twitter.com*" + }, + { + "pattern": "^https://blogs.oracle.com/javamagazine/post/a-peek-into-java-17-continuing-the-drive-to-encapsulate-the-java-runtime-internals" + }, + { + "pattern": "^https://www.oceanbase.com/" } ], "timeout": "10s", "retryOn429": true, "retryCount": 10, - "fallbackRetryDelay": "1000s", + "fallbackRetryDelay": "60s", "aliveStatusCodes": [ 200, 301, 302, - 401 + 401, + 403 ] } diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 2b63165d0d..80e4344366 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -39,7 +39,7 @@ runs: sed -i "/<\/sourceDirectories>/i scenarios\/""${{ inputs.test_case }}""<\/sourceDirectory>" test/plugin/pom.xml echo "::endgroup::" - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-agent-test-${{ hashFiles('**/pom.xml') }} @@ -56,7 +56,7 @@ runs: ./mvnw -q --batch-mode clean package -Dmaven.test.skip || \ ./mvnw -q --batch-mode clean package -Dmaven.test.skip echo "::endgroup::" - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 name: Upload Agent with: name: skywalking-agent @@ -88,8 +88,9 @@ runs: mkdir -p test-containers docker save -o test-containers/skywalking-agent-test-jvm-1.0.0.tgz skywalking/agent-test-jvm:1.0.0 docker save -o test-containers/skywalking-agent-test-tomcat-1.0.0.tgz skywalking/agent-test-tomcat:1.0.0 + ls -la test-containers/ echo "::endgroup::" - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 name: Upload Test Containers with: name: test-tools diff --git a/.github/actions/run/action.yml b/.github/actions/run/action.yml index 02fdb8503c..bb9b3c6143 100644 --- a/.github/actions/run/action.yml +++ b/.github/actions/run/action.yml @@ -25,13 +25,29 @@ inputs: runs: using: "composite" steps: - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: skywalking-agent path: skywalking-agent - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: test-tools + - name: Disable containerd image store + shell: bash + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" - name: Load Test Containers shell: bash run: | @@ -41,8 +57,9 @@ runs: if ls test-containers/skywalking-agent-test-tomcat-1.0.0.tgz; then docker load -i test-containers/skywalking-agent-test-tomcat-1.0.0.tgz fi + docker images - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-agent-test-run-${{ hashFiles('**/pom.xml') }} @@ -51,9 +68,9 @@ runs: shell: bash run: | echo "::group::Run Plugin Test ${{ inputs.test_case }}" - bash test/plugin/run.sh ${{ inputs.test_case }} + bash test/plugin/run.sh --debug ${{ inputs.test_case }} echo "::endgroup::" - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 name: Upload Agent if: always() with: diff --git a/.github/workflows/Dockerfile-tomcat-jdk25-withCurl b/.github/workflows/Dockerfile-tomcat-jdk25-withCurl new file mode 100644 index 0000000000..dc510ffb4c --- /dev/null +++ b/.github/workflows/Dockerfile-tomcat-jdk25-withCurl @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +FROM tomcat:10.1.50-jdk25-temurin + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f286166990..e8a4a2bfb1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,7 +42,7 @@ jobs: build: name: Java ${{ matrix.java-version }} / ${{ matrix.os }} runs-on: ${{ matrix.os }}-latest - timeout-minutes: 60 + timeout-minutes: 90 needs: [ license ] strategy: fail-fast: true @@ -52,12 +52,17 @@ jobs: include: - os: ubuntu java-version: 21 + - os: ubuntu + java-version: 25 steps: + - if: matrix.os == 'windows' + name: Support longpaths + run: git config --system core.longpaths true - uses: actions/checkout@v2 with: submodules: true - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-ci-${{ hashFiles('**/pom.xml') }} @@ -68,6 +73,8 @@ jobs: java-version: ${{ matrix.java-version }} - name: Check Javaagent Plugin List run: tools/plugin/check-javaagent-plugin-list.sh + - name: Check Plugin Compiler Overrides + run: tools/plugin/check-compiler-overrides.sh - name: Install and Test run: ./mvnw -q --batch-mode clean verify install javadoc:javadoc diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 0932d63cee..1abc057ad0 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -54,11 +54,11 @@ jobs: java-version: 17 - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - run: ./mvnw -q -Dmaven.test.skip=true clean install || ./mvnw -q -Dmaven.test.skip=true clean install - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index ad1536c3d9..a2393b4942 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -58,8 +58,14 @@ jobs: - name: Setup go uses: actions/setup-go@v2 with: - go-version: '1.18' + go-version: '1.24' - name: Run E2E Tests - uses: apache/skywalking-infra-e2e@c1558dba921a36320f9fdc3d774b66592243589d + uses: apache/skywalking-infra-e2e@8c21e43e241a32a54bdf8eeceb9099eb27e5e9b4 with: e2e-file: ${{ matrix.case.path }} + - uses: actions/upload-artifact@v4 + if: ${{ failure() }} + name: Upload Logs + with: + name: test-logs-${{ matrix.case.name }} + path: "${{ env.SW_INFRA_E2E_LOG_DIR }}" diff --git a/.github/workflows/plugins-jdk11-test.3.yaml b/.github/workflows/plugins-jdk11-test.3.yaml index a1f90fff14..238fd4047c 100644 --- a/.github/workflows/plugins-jdk11-test.3.yaml +++ b/.github/workflows/plugins-jdk11-test.3.yaml @@ -41,6 +41,21 @@ jobs: - uses: actions/checkout@v2 with: submodules: true + - name: Disable containerd image store + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" - name: Build uses: ./.github/actions/build with: @@ -55,6 +70,7 @@ jobs: matrix: case: - jdk11-forkjoinpool-scenario + - jdk-httpclient-scenario steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/plugins-jdk17-test.0.yaml b/.github/workflows/plugins-jdk17-test.0.yaml index 6d2ad4c2b6..b62166b155 100644 --- a/.github/workflows/plugins-jdk17-test.0.yaml +++ b/.github/workflows/plugins-jdk17-test.0.yaml @@ -41,6 +41,21 @@ jobs: - uses: actions/checkout@v2 with: submodules: true + - name: Disable containerd image store + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" - name: Build uses: ./.github/actions/build with: @@ -64,6 +79,12 @@ jobs: - grizzly-2.3.x-4.x-workthreadpool-scenario - jetty-11.x-scenario - jetty-10.x-scenario + - jetty-12.x-scenario + - spring-ai-1.x-scenario + - spring-rabbitmq-scenario + - graphql-20plus-scenario + - spring-kafka-3.x-scenario + - undertow-2.3.x-scenario steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/plugins-jdk17-test.1.yaml b/.github/workflows/plugins-jdk17-test.1.yaml index d469d4c2ef..7738b85f44 100644 --- a/.github/workflows/plugins-jdk17-test.1.yaml +++ b/.github/workflows/plugins-jdk17-test.1.yaml @@ -41,6 +41,21 @@ jobs: - uses: actions/checkout@v2 with: submodules: true + - name: Disable containerd image store + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" - name: Build uses: ./.github/actions/build with: @@ -56,10 +71,20 @@ jobs: matrix: case: - spring-6.x-scenario + - spring-6.x-mixed-servlet-scenario + - spring-boot-3.x-scenario + - struts2.7-scenario - resteasy-6.x-scenario - gateway-4.x-scenario + - gateway-4.1.2.x-scenario - httpexchange-scenario - activemq-artemis-2.x-scenario + - c3p0-0.9.0.x-0.9.1.x-scenario + - c3p0-0.9.2.x-0.10.x-scenario + - spring-scheduled-6.x-scenario + - caffeine-3.x-scenario + - lettuce-webflux-6x-scenario + - elasticsearch-java-9.x-scenario steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/plugins-jdk21-test.0.yaml b/.github/workflows/plugins-jdk21-test.0.yaml index 2851619f12..9e6805515f 100644 --- a/.github/workflows/plugins-jdk21-test.0.yaml +++ b/.github/workflows/plugins-jdk21-test.0.yaml @@ -41,6 +41,21 @@ jobs: - uses: actions/checkout@v2 with: submodules: true + - name: Disable containerd image store + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" - name: Build uses: ./.github/actions/build with: @@ -56,6 +71,7 @@ jobs: matrix: case: - spring-6.x-scenario + - jdk-virtual-thread-executor-scenario steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/plugins-jdk25-test.0.yaml b/.github/workflows/plugins-jdk25-test.0.yaml new file mode 100644 index 0000000000..06526df29f --- /dev/null +++ b/.github/workflows/plugins-jdk25-test.0.yaml @@ -0,0 +1,89 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +name: Test + +on: + pull_request: + paths: + - '.github/workflows/plugins-*.yaml' + - 'apm-application-toolkit/**' + - 'apm-commons/**' + - 'apm-protocol/**' + - 'apm-sniffer/**' + - 'test/plugin/**' + - '**/pom.xml' + - '!**.md' + +concurrency: + group: plugins-jdk25-1-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - name: Disable containerd image store + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" + - name: Build local tomcat-curl image + run: | + docker build -t tomcat-curl:10.1.50-jdk25-temurin \ + -f ./.github/workflows/Dockerfile-tomcat-jdk25-withCurl . + - name: Build + uses: ./.github/actions/build + with: + base_image_java: eclipse-temurin:25-jdk + base_image_tomcat: tomcat-curl:10.1.50-jdk25-temurin + + test: + needs: [ build ] + name: ${{ matrix.case }}-jdk25 + runs-on: ubuntu-latest + timeout-minutes: 90 + strategy: + matrix: + case: + - spring-6.x-scenario + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '25' + - name: Run Test + uses: ./.github/actions/run + with: + test_case: ${{ matrix.case }} diff --git a/.github/workflows/plugins-test.0.yaml b/.github/workflows/plugins-test.0.yaml index 57cec68cf8..0c86b2e8ad 100644 --- a/.github/workflows/plugins-test.0.yaml +++ b/.github/workflows/plugins-test.0.yaml @@ -41,6 +41,21 @@ jobs: - uses: actions/checkout@v2 with: submodules: true + - name: Disable containerd image store + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" - name: Build uses: ./.github/actions/build @@ -53,6 +68,7 @@ jobs: matrix: case: - activemq-scenario + - agent-so11y-scenario - apm-toolkit-trace-scenario - apm-toolkit-tracer-scenario - armeria-0.96minus-scenario @@ -79,6 +95,8 @@ jobs: - gateway-2.1.x-scenario - gateway-2.0.x-scenario - grpc-scenario + - grpc-1.59.x-1.70.x-scenario + - grpc-1.30.x-1.39.x-scenario - gson-scenario - guava-cache-scenario - elasticjob-3.x-scenario diff --git a/.github/workflows/plugins-test.1.yaml b/.github/workflows/plugins-test.1.yaml index e271ad915b..19e20deb5c 100644 --- a/.github/workflows/plugins-test.1.yaml +++ b/.github/workflows/plugins-test.1.yaml @@ -41,6 +41,21 @@ jobs: - uses: actions/checkout@v2 with: submodules: true + - name: Disable containerd image store + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" - name: Build uses: ./.github/actions/build @@ -72,8 +87,11 @@ jobs: - kafka-scenario - kotlin-coroutine-scenario - lettuce-scenario + - lettuce-6.5.x-scenario + - lettuce-webflux-5x-scenario - mongodb-3.x-scenario - mongodb-4.x-scenario + - mongodb-5.x-scenario - netty-socketio-scenario - postgresql-above9.4.1207-scenario - mssql-jtds-scenario diff --git a/.github/workflows/plugins-test.2.yaml b/.github/workflows/plugins-test.2.yaml index ccb6a7865d..042a14d947 100644 --- a/.github/workflows/plugins-test.2.yaml +++ b/.github/workflows/plugins-test.2.yaml @@ -41,6 +41,21 @@ jobs: - uses: actions/checkout@v2 with: submodules: true + - name: Disable containerd image store + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" - name: Build uses: ./.github/actions/build @@ -59,6 +74,7 @@ jobs: - rabbitmq-scenario - redisson-scenario - resttemplate-4.x-scenario + - spring-boot-2.x-scenario - servicecomb-1.x-scenario - servicecomb-2.x-scenario - shardingsphere-3.x-scenario @@ -76,12 +92,13 @@ jobs: - struts2.5-scenario - cxf-scenario - okhttp2-scenario - - rocketmq-scenario - jersey-2.0.x-2.25.x-scenario - jersey-2.26.x-2.39.x-scenario - websphere-liberty-23.x-scenario - nacos-client-2.x-scenario + - rocketmq-scenario - rocketmq-5-grpc-scenario + - elasticsearch-java-scenario steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/plugins-test.3.yaml b/.github/workflows/plugins-test.3.yaml index 7451dcf4b5..0c667a6d06 100644 --- a/.github/workflows/plugins-test.3.yaml +++ b/.github/workflows/plugins-test.3.yaml @@ -41,6 +41,21 @@ jobs: - uses: actions/checkout@v2 with: submodules: true + - name: Disable containerd image store + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" - name: Build uses: ./.github/actions/build @@ -54,6 +69,7 @@ jobs: case: - aerospike-scenario - mysql-scenario + - mysql-9.x-scenario - undertow-scenario - webflux-scenario - zookeeper-scenario @@ -66,8 +82,9 @@ jobs: - vertx-web-3.54minus-scenario - vertx-web-3.6plus-scenario - mariadb-scenario + - mariadb-3.x-scenario - micronaut-http-scenario - - nats-2.14.x-2.15.x-scenario + - nats-2.14.x-2.16.5-scenario - quasar-scenario - baidu-brpc-scenario - baidu-brpc-3.x-scenario @@ -81,7 +98,7 @@ jobs: - spring-kafka-1.3.x-scenario - spring-kafka-2.2.x-scenario - spring-kafka-2.3.x-scenario - - spring-scheduled-scenario + - spring-scheduled-3.x-5.x-scenario - elasticjob-2.x-scenario - quartz-scheduler-2.x-scenario - xxl-job-2.2.0-scenario @@ -92,7 +109,6 @@ jobs: - gateway-3.x-scenario - gateway-3.x-filter-context-scenario - neo4j-4.x-scenario - - oracle-scenario - druid-1.x-scenario - hikaricp-scenario - clickhouse-0.3.1-scenario @@ -108,6 +124,7 @@ jobs: - grpc-generic-call-scenario - shenyu-2.4.x-grpc-scenario - shenyu-2.4.x-sofarpc-scenario + - solon-2.x-scenario steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/plugins-tomcat10-test.0.yaml b/.github/workflows/plugins-tomcat10-test.0.yaml index 6ba26a754c..9bac4133d9 100644 --- a/.github/workflows/plugins-tomcat10-test.0.yaml +++ b/.github/workflows/plugins-tomcat10-test.0.yaml @@ -41,6 +41,21 @@ jobs: - uses: actions/checkout@v2 with: submodules: true + - name: Disable containerd image store + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" - name: Build uses: ./.github/actions/build with: diff --git a/.github/workflows/plugins-tomcat9-test.0.yaml b/.github/workflows/plugins-tomcat9-test.0.yaml index 8f54080254..400e8a0ff6 100644 --- a/.github/workflows/plugins-tomcat9-test.0.yaml +++ b/.github/workflows/plugins-tomcat9-test.0.yaml @@ -41,6 +41,21 @@ jobs: - uses: actions/checkout@v2 with: submodules: true + - name: Disable containerd image store + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" - name: Build uses: ./.github/actions/build with: diff --git a/.github/workflows/publish-docker.yaml b/.github/workflows/publish-docker.yaml index be696af980..13dd95e4ae 100644 --- a/.github/workflows/publish-docker.yaml +++ b/.github/workflows/publish-docker.yaml @@ -36,7 +36,7 @@ jobs: with: submodules: true - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-publish-docker-${{ hashFiles('**/pom.xml') }} @@ -47,7 +47,7 @@ jobs: java-version: 17 - name: Build Agent run: make build - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 name: Upload Agent with: name: skywalking-agent @@ -64,19 +64,34 @@ jobs: timeout-minutes: 60 strategy: matrix: - java-version: [ 8, 11, 17, 21 ] + java-version: [ 8, 11, 17, 21, 25 ] env: TAG: ${{ github.sha }} steps: - uses: actions/checkout@v2 with: submodules: true - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: skywalking-agent path: skywalking-agent + - name: Disable containerd image store + run: | + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + sudo systemctl restart docker + docker version + docker info + echo "DOCKER_API_VERSION=$(docker version --format '{{.Server.APIVersion}}')" >> "$GITHUB_ENV" - name: Log in to the Container registry - uses: docker/login-action@v1.10.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ${{ env.HUB }} username: ${{ github.actor }} diff --git a/.gitignore b/.gitignore index e127805e50..1efb481266 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,9 @@ packages/ /test/jacoco/classes /test/jacoco/*.exec test/jacoco +.claude/settings.local.json + +# Generated by tools/plugin/check-javaagent-plugin-list.sh (not source) +/javaagent-position.txt +/tools/plugin/genernate-javaagent-plugin-list.txt +/tools/plugin/md-javaagent-plugin-list.txt diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 54ab0bc2f6..94a275913a 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,3 +1,3 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.1/apache-maven-3.6.1-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar diff --git a/CHANGES.md b/CHANGES.md index ce908f8278..4daf294f74 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,15 +2,38 @@ Changes by Version ================== Release Notes. -9.3.0 +9.7.0 ------------------ +* Fix `plugin.http.include_http_headers` not working on Spring Boot 3.x / Jakarta EE (apache/skywalking#13938). +* Support `plugin.http.include_http_headers` in the Tomcat plugin, collecting the configured request headers as the `http.headers` tag on Tomcat entry spans (e.g. RESTEasy on Tomcat), consistent with the Spring MVC plugin. Honors the shared `plugin.http.http_headers_length_threshold`. +* Unify the Tomcat plugins into a single `tomcat` plugin (Tomcat 7 - 10) and the Jetty server plugins into a single `jetty-server` plugin (Jetty 9 - 11), each supporting both `javax.servlet` and `jakarta.servlet`. Breaking change: plugin names `tomcat-7.x/8.x` and `tomcat-10.x` are replaced by `tomcat`, and `jetty-server-9.x` and `jetty-server-11.x` by `jetty-server`; update `plugin.exclude_plugins` if you reference the old names. +* Add a Jetty 12 server plugin (`jetty-server-12.x`). Jetty 12 removed the `HttpChannel` handle target and moved request handling to the async `Server#handle(Request, Response, Callback)` core API, so it needs a separate plugin from the merged `jetty-server`. +* Add a Struts 7 plugin (`struts2-7.x`) for Jakarta Struts, whose `DefaultActionInvocation` moved to `org.apache.struts2`. +* Added support for Lettuce reactive Redis commands. +* Add Spring AI 1.x plugin and GenAI layer. +* Fix httpclient-5.x plugin injecting sw8 propagation headers into ClickHouse HTTP requests (port 8123), causing HTTP 400. Add `PROPAGATION_EXCLUDE_PORTS` config to skip tracing (including header injection) for specified ports in the classic client interceptor. +* Add Spring RabbitMQ 2.x - 4.x plugin. +* Extend MySQL plugin to support MySQL Connector/J 8.4.0 and 9.x (9.0 -> 9.6). +* Extend MariaDB plugin to support MariaDB Connector/J 2.7.x. +* Extend MongoDB 4.x plugin to support MongoDB Java Driver 4.2 -> 4.10. Fix db.bind_vars extraction for driver 4.9+ where InsertOperation/DeleteOperation/UpdateOperation classes were removed. +* Fix MongoDB 4.x plugin for driver 4.11+ where Cluster.getDescription() was removed, use getCurrentDescription() instead. +* Extend Feign plugin to support OpenFeign 10.x, 11.x, 12.1. +* Add Feign 12.2+ PathVar support (BuildTemplateByResolvingArgs moved to RequestTemplateFactoryResolver). +* Extend Undertow plugin to support Undertow 2.1.x, 2.2.x, 2.3.x. +* Extend GraphQL plugin to support graphql-java 18 -> 24 (20+ requires JDK 17). +* Extend Spring Kafka plugin to support Spring Kafka 2.4 -> 2.9 and 3.0 -> 3.3. +* Enhance test/plugin/run.sh to support extra Maven properties per version in support-version.list (format: version,key=value). +* Add MariaDB 3.x plugin (all classes renamed in 3.x). +* Extend Jedis 4.x plugin to support Jedis 5.x (fix witness method for 5.x compatibility). +* Add Elasticsearch Java client (co.elastic.clients:elasticsearch-java) plugin for 7.16.x-9.x. +* Only publish `apm-application-toolkit` modules to Maven Central. Agent and plugins are distributed via download package and Docker images. +* Add unified release script (`tools/releasing/release.sh`) with two-step flow: `prepare-vote` and `vote-passed`. +* Fix an issue where `JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH` was not honored by clickhouse-0.3.1 and clickhouse-0.3.2.x plugins. +- Add tracing support for vector-store retrieval operations. +* Fix agent lifecycle events: the Start event now carries the service instance name, and the Shutdown event is delivered on graceful JVM exit. `ServiceManager` prepares/starts higher-priority `BootService`s first and shuts them down last (matching `BootService#priority()`), and the shutdown event refreshes its gRPC deadline before sending. - -#### Documentation -* Update docs to describe `expired-plugins`. - -All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/213?closed=1) +All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/249?closed=1) ------------------ Find change logs of all versions [here](changes). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..ab26fb02eb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,296 @@ +# CLAUDE.md - AI Assistant Guide for Apache SkyWalking Java Agent + +This file provides guidance for AI assistants working with the Apache SkyWalking Java Agent codebase. + +## Project Overview + +Apache SkyWalking Java Agent is a Java-based APM (Application Performance Monitoring) agent designed for microservices, cloud-native, and container-based architectures. It provides automatic instrumentation for distributed tracing, performance metrics collection, and context propagation across service boundaries using bytecode manipulation via ByteBuddy. + +## Repository Structure + +``` +skywalking-java/ +├── apm-commons/ # Shared utilities and libraries +│ ├── apm-datacarrier/ # Data buffering and transport +│ └── apm-util/ # Common utilities +├── apm-protocol/ # Protocol definitions +│ └── apm-network/ # gRPC protocol (submodule: skywalking-data-collect-protocol) +├── apm-sniffer/ # Core agent and plugins (MAIN MODULE) +│ ├── apm-agent/ # Main agent bootstrap and premain entry +│ ├── apm-agent-core/ # Core agent logic, instrumentation engine +│ ├── apm-sdk-plugin/ # Standard SDK plugins (70+ plugins) +│ ├── bootstrap-plugins/ # Bootstrap-level plugins (JDK-level) +│ ├── optional-plugins/ # Optional framework plugins +│ ├── optional-reporter-plugins/ # Reporter plugins (Kafka, etc.) +│ ├── apm-toolkit-activation/ # Toolkit activations +│ ├── apm-test-tools/ # Testing utilities +│ ├── bytebuddy-patch/ # ByteBuddy patches +│ └── config/ # Default agent configurations +├── apm-application-toolkit/ # Public API for applications +│ ├── apm-toolkit-trace/ # Tracing API +│ ├── apm-toolkit-log4j-1.x/ # Log4j 1.x integration +│ ├── apm-toolkit-log4j-2.x/ # Log4j 2.x integration +│ ├── apm-toolkit-logback-1.x/ # Logback integration +│ ├── apm-toolkit-meter/ # Meter API +│ └── apm-toolkit-opentracing/ # OpenTracing API +├── apm-checkstyle/ # Code style configuration +│ ├── checkStyle.xml # Checkstyle rules +│ └── importControl.xml # Import control rules +├── test/ # Testing infrastructure +│ ├── plugin/ # Plugin E2E tests +│ │ ├── scenarios/ # Test scenarios (100+ scenarios) +│ │ ├── agent-test-tools/ # Mock collector, test utilities +│ │ ├── runner-helper/ # Test runner +│ │ └── containers/ # Docker test containers +│ └── e2e/ # End-to-end tests +├── docs/ # Documentation +├── tools/ # Build and utility tools +├── skywalking-agent/ # Built agent distribution output +├── changes/ # Changelog +└── dist-material/ # Distribution materials +``` + +## Build System + +### Prerequisites +- JDK 8, 11, 17, 21, or 25 +- Maven 3.6+ +- Git (with submodule support) + +### Common Build Commands + +```bash +# Clone with submodules +git clone --recurse-submodules https://github.com/apache/skywalking-java.git + +# Or initialize submodules after clone +git submodule init && git submodule update + +# Full build with tests +./mvnw clean install + +# Build without tests (recommended for development) +./mvnw clean package -Dmaven.test.skip=true + +# CI build with javadoc verification +./mvnw clean verify install javadoc:javadoc + +# Run checkstyle only +./mvnw checkstyle:check + +# Build with submodule update +./mvnw clean package -Pall + +# Docker build +make build +make docker +``` + +### Maven Profiles +- `all`: Includes git submodule update for protocol definitions + +### Key Build Properties +- ByteBuddy: 1.17.6 (bytecode manipulation) +- gRPC: 1.74.0 (communication protocol) +- Netty: 4.1.124.Final (network framework) +- Protobuf: 3.25.5 (protocol buffers) +- Lombok: 1.18.42 (annotation processing) + +## Architecture & Key Concepts + +### Agent Architecture +The agent uses ByteBuddy for bytecode manipulation at runtime: + +1. **Premain Entry**: `apm-agent/` contains the agent bootstrap via Java's `-javaagent` mechanism +2. **Instrumentation Engine**: `apm-agent-core/` handles class transformation and plugin loading +3. **Plugins**: Define which classes/methods to intercept and how to collect telemetry + +### Plugin Categories + +**1. SDK Plugins** (`apm-sniffer/apm-sdk-plugin/`) +- Framework-specific instrumentations (70+ plugins) +- Examples: grpc-1.x, spring, dubbo, mybatis, mongodb, redis, etc. +- See `apm-sniffer/apm-sdk-plugin/CLAUDE.md` for plugin development guide + +**2. Bootstrap Plugins** (`apm-sniffer/bootstrap-plugins/`) +- Load at JVM bootstrap phase for JDK-level instrumentation +- Examples: jdk-threading, jdk-http, jdk-httpclient, jdk-virtual-thread-executor +- See `apm-sniffer/bootstrap-plugins/CLAUDE.md` for bootstrap plugin guide + +**3. Optional Plugins** (`apm-sniffer/optional-plugins/`) +- Not included by default, user must copy to plugins directory + +**4. Optional Reporter Plugins** (`apm-sniffer/optional-reporter-plugins/`) +- Alternative data collection backends (e.g., Kafka) + +### Data Flow +1. Agent attaches to JVM via `-javaagent` flag +2. ByteBuddy transforms target classes at load time +3. Interceptors collect span/trace data on method entry/exit +4. Data is buffered via DataCarrier +5. gRPC reporter sends data to OAP backend + +## Code Style & Conventions + +### Checkstyle Rules (enforced via `apm-checkstyle/checkStyle.xml`) + +**Prohibited patterns:** +- No `System.out.println` - use proper logging +- No `@author` tags - ASF projects don't use author annotations +- No Chinese characters in source files +- No tab characters (use 4 spaces) +- No star imports (`import xxx.*`) +- No unused or redundant imports + +**Required patterns:** +- `@Override` annotation required for overridden methods +- `equals()` and `hashCode()` must be overridden together +- Apache 2.0 license header on all source files + +**Naming conventions:** +- Constants/static variables: `UPPER_CASE_WITH_UNDERSCORES` +- Package names: `org.apache.skywalking.apm.*` or `test.apache.skywalking.apm.*` +- Type names: `PascalCase` +- Local variables/parameters/members: `camelCase` +- Plugin directories: `{framework}-{version}-plugin` +- Instrumentation classes: `*Instrumentation.java` +- Interceptor classes: `*Interceptor.java` + +**File limits:** +- Max file length: 3000 lines + +### Lombok Usage +Use Lombok annotations for boilerplate code: +- `@Getter`, `@Setter`, `@Data` +- `@Builder` +- `@Slf4j` for logging + +## Testing + +### Test Frameworks +- JUnit 4.12 for unit tests +- Mockito 5.0.0 for mocking + +### Test Categories + +**Unit Tests** (in each module's `src/test/java`) +- Standard JUnit tests +- Pattern: `*Test.java` + +**Plugin E2E Tests** (`test/plugin/scenarios/`) +- 100+ test scenarios for plugin validation +- Docker-based testing with actual frameworks +- Pattern: `{framework}-{version}-scenario` +- See `apm-sniffer/apm-sdk-plugin/CLAUDE.md` for full test framework documentation + +**End-to-End Tests** (`test/e2e/`) +- Full system integration testing + +### Running Tests +```bash +# Unit tests +./mvnw test + +# Full verification including checkstyle +./mvnw clean verify + +# Skip tests during build +./mvnw package -Dmaven.test.skip=true +``` + +## Git Submodules + +The project uses submodules for protocol definitions: +- `apm-protocol/apm-network/src/main/proto` - skywalking-data-collect-protocol + +Always use `--recurse-submodules` when cloning or update submodules manually: +```bash +git submodule init && git submodule update +``` + +## IDE Setup (IntelliJ IDEA) + +1. Import as Maven project +2. Run `./mvnw compile -Dmaven.test.skip=true` to generate protobuf sources +3. Mark generated source folders: + - `*/target/generated-sources/protobuf/java` + - `*/target/generated-sources/protobuf/grpc-java` +4. Enable annotation processing for Lombok + +## Key Files for Understanding the Codebase + +- `apm-sniffer/apm-agent/` - Agent entry point (premain) +- `apm-sniffer/apm-agent-core/src/main/java/.../enhance/` - Instrumentation engine +- `apm-sniffer/apm-agent-core/src/main/java/.../plugin/` - Plugin loading system +- `apm-sniffer/apm-sdk-plugin/` - All standard plugins (reference implementations) +- `apm-sniffer/config/agent.config` - Default agent configuration + +## Common Development Tasks + +### Adding a New Plugin +See `apm-sniffer/apm-sdk-plugin/CLAUDE.md` for detailed guide. + +### Adding an Optional Plugin +1. Create in `apm-sniffer/optional-plugins/` +2. Update documentation in `docs/en/setup/service-agent/java-agent/Optional-plugins.md` + +### Modifying Agent Configuration +1. Edit `apm-sniffer/config/agent.config` +2. Update documentation if adding new options + +## Documentation + +- `docs/en/setup/service-agent/java-agent/` - Main agent documentation +- `docs/en/setup/service-agent/java-agent/Plugin-list.md` - Complete plugin list +- `docs/en/setup/service-agent/java-agent/Optional-plugins.md` - Optional plugins guide +- `CHANGES.md` - Changelog (update when making changes) + +## Community + +- GitHub Issues: https://github.com/apache/skywalking-java/issues +- Mailing List: dev@skywalking.apache.org +- Slack: #skywalking channel at Apache Slack + +## Submitting Pull Requests + +### Branch Strategy +- **Never work directly on main branch** +- Create a new branch for your changes + +### PR Template +Follow `.github/PULL_REQUEST_TEMPLATE` based on change type: +- **Bug fix**: Add unit test, explain bug cause and fix +- **New plugin**: Add test case, component ID in OAP, logo in UI repo +- **Performance improvement**: Add benchmark with results, link to theory/discussion +- **New feature**: Link design doc if non-trivial, update docs, add tests + +### PR Requirements +- Follow Apache Code of Conduct +- Include updated documentation for new features +- Include tests for new functionality +- Reference original issue (e.g., "Resolves #123") +- Update `CHANGES.md` for user-facing changes +- Pass all CI checks (checkstyle, tests, license headers) + +### PR Description +- Bug fixes: Explain the bug and how it's fixed, add regression test +- New features: Link to design doc if non-trivial, update docs, add tests +- Do NOT add AI assistant as co-author + +## CI/CD + +GitHub Actions workflows: +- **CI**: Multi-OS (Ubuntu, macOS, Windows), Multi-Java (8, 11, 17, 21, 25) +- **Plugin Tests**: Parallel E2E tests for all plugins +- **E2E Tests**: Full system integration +- **Docker Publishing**: Multi-variant images + +## Tips for AI Assistants + +1. **Always check submodules**: Protocol changes may require submodule updates +2. **Generate sources first**: Run `mvnw compile` before analyzing generated code +3. **Respect checkstyle**: No System.out, no @author, no Chinese characters +4. **Use Lombok**: Prefer annotations over boilerplate code +5. **Test both unit and E2E**: Different test patterns for different scopes +6. **Java version compatibility**: Agent core must maintain Java 8 compatibility, but individual plugins may target higher JDK versions (e.g., jdk-httpclient-plugin for JDK 11+, virtual-thread plugins for JDK 21+). **Do NOT set `maven.compiler.release` or `maven.compiler.source/target` in a plugin pom.xml unless the plugin source code itself uses JDK 9+ language features.** A `provided`-scope dependency targeting a higher JDK does not require raising the compiler level — the plugin code only references the library's API at compile time and does not need to match the library's runtime JDK requirement. +7. **For plugin development**: See `apm-sniffer/apm-sdk-plugin/CLAUDE.md` and `apm-sniffer/bootstrap-plugins/CLAUDE.md` diff --git a/Makefile b/Makefile index b0b6adde5e..965c6572bc 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ dist: build # Docker build -base.all := alpine java8 java11 java17 java21 +base.all := alpine java8 java11 java17 java21 java25 base.each = $(word 1, $@) base.image.alpine := alpine:3 @@ -42,6 +42,7 @@ base.image.java8 := eclipse-temurin:8-jre base.image.java11 := eclipse-temurin:11-jre base.image.java17 := eclipse-temurin:17-jre base.image.java21 := eclipse-temurin:21-jre +base.image.java25 := eclipse-temurin:25-jre docker.%: PLATFORMS = docker.%: LOAD_OR_PUSH = --load diff --git a/apm-application-toolkit/apm-toolkit-kafka/pom.xml b/apm-application-toolkit/apm-toolkit-kafka/pom.xml index 6edc74c7fb..695985162e 100644 --- a/apm-application-toolkit/apm-toolkit-kafka/pom.xml +++ b/apm-application-toolkit/apm-toolkit-kafka/pom.xml @@ -21,7 +21,7 @@ apm-application-toolkit org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-application-toolkit/apm-toolkit-log4j-1.x/pom.xml b/apm-application-toolkit/apm-toolkit-log4j-1.x/pom.xml index 057ac5676d..6983c0d2e9 100644 --- a/apm-application-toolkit/apm-toolkit-log4j-1.x/pom.xml +++ b/apm-application-toolkit/apm-toolkit-log4j-1.x/pom.xml @@ -21,7 +21,7 @@ apm-application-toolkit org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-application-toolkit/apm-toolkit-log4j-2.x/pom.xml b/apm-application-toolkit/apm-toolkit-log4j-2.x/pom.xml index e334a37b0e..393ea94496 100644 --- a/apm-application-toolkit/apm-toolkit-log4j-2.x/pom.xml +++ b/apm-application-toolkit/apm-toolkit-log4j-2.x/pom.xml @@ -21,7 +21,7 @@ apm-application-toolkit org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-application-toolkit/apm-toolkit-logback-1.x/pom.xml b/apm-application-toolkit/apm-toolkit-logback-1.x/pom.xml index 2484061d6e..f5b9d63625 100644 --- a/apm-application-toolkit/apm-toolkit-logback-1.x/pom.xml +++ b/apm-application-toolkit/apm-toolkit-logback-1.x/pom.xml @@ -21,7 +21,7 @@ apm-application-toolkit org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-application-toolkit/apm-toolkit-meter/pom.xml b/apm-application-toolkit/apm-toolkit-meter/pom.xml index c8453f9f17..9c2af46e51 100644 --- a/apm-application-toolkit/apm-toolkit-meter/pom.xml +++ b/apm-application-toolkit/apm-toolkit-meter/pom.xml @@ -20,7 +20,7 @@ apm-application-toolkit org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-application-toolkit/apm-toolkit-micrometer-1.10/pom.xml b/apm-application-toolkit/apm-toolkit-micrometer-1.10/pom.xml index b9c1a09c78..70e3a957d6 100644 --- a/apm-application-toolkit/apm-toolkit-micrometer-1.10/pom.xml +++ b/apm-application-toolkit/apm-toolkit-micrometer-1.10/pom.xml @@ -20,7 +20,7 @@ apm-application-toolkit org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/pom.xml b/apm-application-toolkit/apm-toolkit-micrometer-registry/pom.xml index 5554bbf8a0..5658bea135 100644 --- a/apm-application-toolkit/apm-toolkit-micrometer-registry/pom.xml +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/pom.xml @@ -20,7 +20,7 @@ apm-application-toolkit org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-application-toolkit/apm-toolkit-opentracing/pom.xml b/apm-application-toolkit/apm-toolkit-opentracing/pom.xml index 5bfe3f8f26..a7d9aefd2a 100644 --- a/apm-application-toolkit/apm-toolkit-opentracing/pom.xml +++ b/apm-application-toolkit/apm-toolkit-opentracing/pom.xml @@ -21,7 +21,7 @@ apm-application-toolkit org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-application-toolkit/apm-toolkit-opentracing/src/main/resources/META-INF.services/io.opentracing.Tracer b/apm-application-toolkit/apm-toolkit-opentracing/src/main/resources/META-INF/services/io.opentracing.Tracer similarity index 92% rename from apm-application-toolkit/apm-toolkit-opentracing/src/main/resources/META-INF.services/io.opentracing.Tracer rename to apm-application-toolkit/apm-toolkit-opentracing/src/main/resources/META-INF/services/io.opentracing.Tracer index 894da6a64f..4cb914e314 100644 --- a/apm-application-toolkit/apm-toolkit-opentracing/src/main/resources/META-INF.services/io.opentracing.Tracer +++ b/apm-application-toolkit/apm-toolkit-opentracing/src/main/resources/META-INF/services/io.opentracing.Tracer @@ -16,4 +16,4 @@ # # -org.apache.skywalking.apm.toolkit.opentracing.SkyWalkingTracer +org.apache.skywalking.apm.toolkit.opentracing.SkywalkingTracer diff --git a/apm-application-toolkit/apm-toolkit-trace/pom.xml b/apm-application-toolkit/apm-toolkit-trace/pom.xml index b942b7afb5..642a551645 100644 --- a/apm-application-toolkit/apm-toolkit-trace/pom.xml +++ b/apm-application-toolkit/apm-toolkit-trace/pom.xml @@ -20,7 +20,7 @@ apm-application-toolkit org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-application-toolkit/apm-toolkit-webflux/pom.xml b/apm-application-toolkit/apm-toolkit-webflux/pom.xml index 3a74445896..8397b9193f 100644 --- a/apm-application-toolkit/apm-toolkit-webflux/pom.xml +++ b/apm-application-toolkit/apm-toolkit-webflux/pom.xml @@ -20,7 +20,7 @@ apm-application-toolkit org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-application-toolkit/pom.xml b/apm-application-toolkit/pom.xml index 03e7a72ec0..b018a7ef16 100644 --- a/apm-application-toolkit/pom.xml +++ b/apm-application-toolkit/pom.xml @@ -20,12 +20,17 @@ java-agent org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 apm-application-toolkit pom + + + false + + apm-toolkit-log4j-1.x apm-toolkit-log4j-2.x diff --git a/apm-commons/apm-datacarrier/pom.xml b/apm-commons/apm-datacarrier/pom.xml index 899bcb889c..a3c91d10ba 100644 --- a/apm-commons/apm-datacarrier/pom.xml +++ b/apm-commons/apm-datacarrier/pom.xml @@ -21,7 +21,7 @@ java-agent-commons org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-commons/apm-datacarrier/src/main/java/org/apache/skywalking/apm/commons/datacarrier/consumer/ConsumeDriver.java b/apm-commons/apm-datacarrier/src/main/java/org/apache/skywalking/apm/commons/datacarrier/consumer/ConsumeDriver.java index a6d808fead..853c7fc3bc 100644 --- a/apm-commons/apm-datacarrier/src/main/java/org/apache/skywalking/apm/commons/datacarrier/consumer/ConsumeDriver.java +++ b/apm-commons/apm-datacarrier/src/main/java/org/apache/skywalking/apm/commons/datacarrier/consumer/ConsumeDriver.java @@ -27,7 +27,7 @@ * Pool of consumers

Created by wusheng on 2016/10/25. */ public class ConsumeDriver implements IDriver { - private boolean running; + private volatile boolean running; private ConsumerThread[] consumerThreads; private Channels channels; private ReentrantLock lock; @@ -88,6 +88,9 @@ public void begin(Channels channels) { } lock.lock(); try { + if (running) { + return; + } this.allocateBuffer2Thread(); for (ConsumerThread consumerThread : consumerThreads) { consumerThread.start(); @@ -124,8 +127,14 @@ private void allocateBuffer2Thread() { @Override public void close(Channels channels) { + if (!running) { + return; + } lock.lock(); try { + if (!running) { + return; + } this.running = false; for (ConsumerThread consumerThread : consumerThreads) { consumerThread.shutdown(); diff --git a/apm-commons/apm-util/pom.xml b/apm-commons/apm-util/pom.xml index b87ce0fdd3..1bbb7009a4 100644 --- a/apm-commons/apm-util/pom.xml +++ b/apm-commons/apm-util/pom.xml @@ -20,7 +20,7 @@ java-agent-commons org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-commons/pom.xml b/apm-commons/pom.xml index 210ef6205f..79e66796ca 100644 --- a/apm-commons/pom.xml +++ b/apm-commons/pom.xml @@ -20,7 +20,7 @@ java-agent org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-protocol/apm-network/pom.xml b/apm-protocol/apm-network/pom.xml index f7d68fbcbe..a1531c3ca0 100644 --- a/apm-protocol/apm-network/pom.xml +++ b/apm-protocol/apm-network/pom.xml @@ -21,7 +21,7 @@ java-agent-protocol org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -35,12 +35,15 @@ io.netty netty-handler-proxy - ${netty.version} io.netty netty-codec-http2 - ${netty.version} + + + io.grpc + grpc-core + ${grpc.version} io.grpc @@ -70,7 +73,6 @@ io.netty netty-tcnative-boringssl-static - ${netty-tcnative-boringssl-static.version} org.apache.tomcat diff --git a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java index 2334cb58e0..a8b57ba7f8 100755 --- a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java +++ b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java @@ -244,4 +244,55 @@ public class ComponentsDefine { public static final OfficialComponent NACOS = new OfficialComponent(150, "Nacos"); public static final OfficialComponent NETTY_HTTP = new OfficialComponent(151, "Netty-http"); + + public static final OfficialComponent C3P0 = new OfficialComponent(152, "c3p0"); + + public static final OfficialComponent DERBY_JDBC_DRIVER = new OfficialComponent(153, "Derby-jdbc-driver"); + + public static final OfficialComponent SQLITE_JDBC_DRIVER = new OfficialComponent(154, "Sqlite-jdbc-driver"); + + public static final OfficialComponent DB2_JDBC_DRIVER = new OfficialComponent(155, "Db2-jdbc-driver"); + + public static final OfficialComponent SYBASE_JDBC_DRIVER = new OfficialComponent(156, "Sybase-jdbc-driver"); + + public static final OfficialComponent OCEANBASE_JDBC_DRIVER = new OfficialComponent(157, "OceanBase-jdbc-driver"); + + public static final OfficialComponent SOLON_MVC = new OfficialComponent(158, "SolonMVC"); + + public static final OfficialComponent CAFFEINE = new OfficialComponent(160, "Caffeine"); + + public static final OfficialComponent THREAD_PER_TASK_EXECUTOR = new OfficialComponent(161, "ThreadPerTask-executor"); + + public static final OfficialComponent DMDB_JDBC_DRIVER = new OfficialComponent(163, "Dmdb-jdbc-driver"); + + public static final OfficialComponent SPRING_AI_UNKNOWN = new OfficialComponent(164, "spring-ai-unknown"); + + public static final OfficialComponent SPRING_AI_ANTHROPIC = new OfficialComponent(165, "spring-ai-anthropic"); + + public static final OfficialComponent SPRING_AI_BEDROCK = new OfficialComponent(166, "spring-ai-aws-bedrock"); + + public static final OfficialComponent SPRING_AI_AZURE_OPENAI = new OfficialComponent(167, "spring-ai-azure-openai"); + + public static final OfficialComponent SPRING_AI_COHERE = new OfficialComponent(168, "spring-ai-cohere"); + + public static final OfficialComponent SPRING_AI_DEEPSEEK = new OfficialComponent(169, "spring-ai-deepseek"); + + public static final OfficialComponent SPRING_AI_GOOGLE_GENAI = new OfficialComponent(170, "spring-ai-gcp-genai"); + + public static final OfficialComponent SPRING_AI_VERTEXAI = new OfficialComponent(171, "spring-ai-gcp-vertex-ai"); + + public static final OfficialComponent SPRING_AI_MISTRAL_AI = new OfficialComponent(172, "spring-ai-mistral-ai"); + + public static final OfficialComponent SPRING_AI_OPENAI = new OfficialComponent(173, "spring-ai-openai"); + + public static final OfficialComponent SPRING_AI_HUGGINGFACE = new OfficialComponent(174, "spring-ai-huggingface"); + + public static final OfficialComponent SPRING_AI_MINIMAX = new OfficialComponent(175, "spring-ai-minimax"); + + public static final OfficialComponent SPRING_AI_OLLAMA = new OfficialComponent(176, "spring-ai-ollama"); + + public static final OfficialComponent SPRING_AI_ZHIPU_AI = new OfficialComponent(177, "spring-ai-zhipu-ai"); + + public static final OfficialComponent SPRING_AI = new OfficialComponent(178, "spring-ai"); + } diff --git a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/command/AsyncProfilerTaskCommand.java b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/command/AsyncProfilerTaskCommand.java new file mode 100644 index 0000000000..41d6043dee --- /dev/null +++ b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/command/AsyncProfilerTaskCommand.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.network.trace.component.command; + +import org.apache.skywalking.apm.network.common.v3.Command; +import org.apache.skywalking.apm.network.common.v3.KeyStringValuePair; + +import java.util.List; +import java.util.Objects; + +public class AsyncProfilerTaskCommand extends BaseCommand implements Serializable, Deserializable { + public static final Deserializable DESERIALIZER = new AsyncProfilerTaskCommand("", "", 0, null, "", 0); + public static final String NAME = "AsyncProfilerTaskQuery"; + + /** + * async-profiler taskId + */ + private final String taskId; + /** + * run profiling for duration (second) + */ + private final int duration; + /** + * async profiler extended parameters. Here is a table of optional parameters. + * + *

lock[=DURATION] - profile contended locks overflowing the DURATION ns bucket (default: 10us)

+ *

alloc[=BYTES] - profile allocations with BYTES interval

+ *

interval=N - sampling interval in ns (default: 10'000'000, i.e. 10 ms)

+ *

jstackdepth=N - maximum Java stack depth (default: 2048)

+ *

chunksize=N - approximate size of JFR chunk in bytes (default: 100 MB)

+ *

chunktime=N - duration of JFR chunk in seconds (default: 1 hour)

+ * details @see async-profiler argument + */ + private final String execArgs; + /** + * task create time + */ + private final long createTime; + + public AsyncProfilerTaskCommand(String serialNumber, String taskId, int duration, + List events, String execArgs, long createTime) { + super(NAME, serialNumber); + this.taskId = taskId; + this.duration = duration; + this.createTime = createTime; + String comma = ","; + StringBuilder sb = new StringBuilder(); + if (Objects.nonNull(events) && !events.isEmpty()) { + sb.append("event=") + .append(String.join(comma, events)) + .append(comma); + } + if (execArgs != null && !execArgs.isEmpty()) { + sb.append(execArgs); + } + this.execArgs = sb.toString(); + } + + public AsyncProfilerTaskCommand(String serialNumber, String taskId, int duration, + String execArgs, long createTime) { + super(NAME, serialNumber); + this.taskId = taskId; + this.duration = duration; + this.execArgs = execArgs; + this.createTime = createTime; + } + + @Override + public AsyncProfilerTaskCommand deserialize(Command command) { + final List argsList = command.getArgsList(); + String taskId = null; + int duration = 0; + String execArgs = null; + long createTime = 0; + String serialNumber = null; + for (final KeyStringValuePair pair : argsList) { + if ("SerialNumber".equals(pair.getKey())) { + serialNumber = pair.getValue(); + } else if ("TaskId".equals(pair.getKey())) { + taskId = pair.getValue(); + } else if ("Duration".equals(pair.getKey())) { + duration = Integer.parseInt(pair.getValue()); + } else if ("ExecArgs".equals(pair.getKey())) { + execArgs = pair.getValue(); + } else if ("CreateTime".equals(pair.getKey())) { + createTime = Long.parseLong(pair.getValue()); + } + } + return new AsyncProfilerTaskCommand(serialNumber, taskId, duration, execArgs, createTime); + } + + @Override + public Command.Builder serialize() { + final Command.Builder builder = commandBuilder(); + builder.addArgs(KeyStringValuePair.newBuilder().setKey("TaskId").setValue(taskId)) + .addArgs(KeyStringValuePair.newBuilder().setKey("Duration").setValue(String.valueOf(duration))) + .addArgs(KeyStringValuePair.newBuilder().setKey("ExecArgs").setValue(execArgs)) + .addArgs(KeyStringValuePair.newBuilder().setKey("CreateTime").setValue(String.valueOf(createTime))); + return builder; + } + + public String getTaskId() { + return taskId; + } + + public int getDuration() { + return duration; + } + + public String getExecArgs() { + return execArgs; + } + + public long getCreateTime() { + return createTime; + } +} diff --git a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/command/CommandDeserializer.java b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/command/CommandDeserializer.java index ff8680bcb3..4fd737ff98 100644 --- a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/command/CommandDeserializer.java +++ b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/command/CommandDeserializer.java @@ -27,7 +27,10 @@ public static BaseCommand deserialize(final Command command) { return ProfileTaskCommand.DESERIALIZER.deserialize(command); } else if (ConfigurationDiscoveryCommand.NAME.equals(commandName)) { return ConfigurationDiscoveryCommand.DESERIALIZER.deserialize(command); + } else if (AsyncProfilerTaskCommand.NAME.equals(commandName)) { + return AsyncProfilerTaskCommand.DESERIALIZER.deserialize(command); } + throw new UnsupportedCommandException(command); } diff --git a/apm-protocol/apm-network/src/main/proto b/apm-protocol/apm-network/src/main/proto index d4da569991..07882d57be 160000 --- a/apm-protocol/apm-network/src/main/proto +++ b/apm-protocol/apm-network/src/main/proto @@ -1 +1 @@ -Subproject commit d4da5699915ee52288f8ff1c954decf6363485bc +Subproject commit 07882d57becb37e341f7fc492c11f9f5a5f311cf diff --git a/apm-protocol/pom.xml b/apm-protocol/pom.xml index b29b3b9732..3cfb939955 100644 --- a/apm-protocol/pom.xml +++ b/apm-protocol/pom.xml @@ -21,7 +21,7 @@ java-agent org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-agent-core/pom.xml b/apm-sniffer/apm-agent-core/pom.xml index 132e2dbfd0..43670f03ba 100644 --- a/apm-sniffer/apm-agent-core/pom.xml +++ b/apm-sniffer/apm-agent-core/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking java-agent-sniffer - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-agent-core @@ -34,10 +34,7 @@ UTF-8 ${project.build.outputDirectory}/skywalking-agent-version.properties - 32.0.1-jre 2.6.0 - 2.0.7.Final - 1.4.1.Final 4.9.10 com.google ${shade.package}.${shade.com.google.source} @@ -53,6 +50,8 @@ ${shade.package}.${shade.org.slf4j.source} 1.18.0 1.7.25 + one.profiler + ${shade.package}.${shade.one.profiler.source} @@ -79,7 +78,6 @@ net.bytebuddy byte-buddy-agent - ${bytebuddy.version} test @@ -138,6 +136,10 @@ org.slf4j slf4j-api + + tools.profiler + async-profiler + @@ -244,6 +246,10 @@ ${shade.org.slf4j.source} ${shade.org.slf4j.target} + + ${shade.one.profiler.source} + ${shade.one.profiler.target} + diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/asyncprofiler/AsyncProfilerDataSender.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/asyncprofiler/AsyncProfilerDataSender.java new file mode 100644 index 0000000000..be5af4c360 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/asyncprofiler/AsyncProfilerDataSender.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.core.asyncprofiler; + +import com.google.protobuf.ByteString; +import io.grpc.Channel; +import io.grpc.stub.ClientCallStreamObserver; +import io.grpc.stub.ClientResponseObserver; +import io.grpc.stub.StreamObserver; +import org.apache.skywalking.apm.agent.core.boot.BootService; +import org.apache.skywalking.apm.agent.core.boot.DefaultImplementor; +import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.conf.Config; +import org.apache.skywalking.apm.agent.core.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.apache.skywalking.apm.agent.core.remote.GRPCChannelListener; +import org.apache.skywalking.apm.agent.core.remote.GRPCChannelManager; +import org.apache.skywalking.apm.agent.core.remote.GRPCChannelStatus; +import org.apache.skywalking.apm.agent.core.remote.GRPCStreamServiceStatus; +import org.apache.skywalking.apm.network.language.asyncprofiler.v10.AsyncProfilerCollectionResponse; +import org.apache.skywalking.apm.network.language.asyncprofiler.v10.AsyncProfilerData; +import org.apache.skywalking.apm.network.language.asyncprofiler.v10.AsyncProfilerMetaData; +import org.apache.skywalking.apm.network.language.asyncprofiler.v10.AsyncProfilerTaskGrpc; +import org.apache.skywalking.apm.network.language.asyncprofiler.v10.AsyncProfilingStatus; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.concurrent.TimeUnit; + +import static org.apache.skywalking.apm.agent.core.conf.Config.AsyncProfiler.DATA_CHUNK_SIZE; +import static org.apache.skywalking.apm.agent.core.conf.Config.Collector.GRPC_UPSTREAM_TIMEOUT; + +@DefaultImplementor +public class AsyncProfilerDataSender implements BootService, GRPCChannelListener { + private static final ILog LOGGER = LogManager.getLogger(AsyncProfilerDataSender.class); + + private volatile GRPCChannelStatus status = GRPCChannelStatus.DISCONNECT; + + private volatile AsyncProfilerTaskGrpc.AsyncProfilerTaskStub asyncProfilerTaskStub; + + @Override + public void prepare() throws Throwable { + ServiceManager.INSTANCE.findService(GRPCChannelManager.class).addChannelListener(this); + } + + @Override + public void boot() throws Throwable { + + } + + @Override + public void onComplete() throws Throwable { + + } + + @Override + public void shutdown() throws Throwable { + + } + + @Override + public void statusChanged(GRPCChannelStatus status) { + if (GRPCChannelStatus.CONNECTED.equals(status)) { + Channel channel = ServiceManager.INSTANCE.findService(GRPCChannelManager.class).getChannel(); + asyncProfilerTaskStub = AsyncProfilerTaskGrpc.newStub(channel); + } else { + asyncProfilerTaskStub = null; + } + this.status = status; + } + + public void sendData(AsyncProfilerTask task, File dumpFile) throws IOException, InterruptedException { + if (status != GRPCChannelStatus.CONNECTED) { + return; + } + + try (FileInputStream fileInputStream = new FileInputStream(dumpFile)) { + long fileSize = Files.size(dumpFile.toPath()); + int size = Math.toIntExact(fileSize); + final GRPCStreamServiceStatus status = new GRPCStreamServiceStatus(false); + StreamObserver dataStreamObserver = asyncProfilerTaskStub.withDeadlineAfter( + GRPC_UPSTREAM_TIMEOUT, TimeUnit.SECONDS + ).collect(new ClientResponseObserver() { + ClientCallStreamObserver requestStream; + final byte[] buf = new byte[DATA_CHUNK_SIZE]; + + @Override + public void beforeStart(ClientCallStreamObserver requestStream) { + this.requestStream = requestStream; + } + + @Override + public void onNext(AsyncProfilerCollectionResponse value) { + if (AsyncProfilingStatus.TERMINATED_BY_OVERSIZE.equals(value.getType())) { + LOGGER.warn("JFR is too large to be received by the oap server"); + } else { + try { + int bytesRead; + while ((bytesRead = fileInputStream.read(buf)) != -1) { + AsyncProfilerData asyncProfilerData = AsyncProfilerData.newBuilder() + .setContent(ByteString.copyFrom(buf, 0, bytesRead)) + .build(); + requestStream.onNext(asyncProfilerData); + } + } catch (IOException e) { + LOGGER.error("Failed to read JFR file and failed to upload to oap", e); + } + } + + requestStream.onCompleted(); + } + + @Override + public void onError(Throwable t) { + status.finished(); + LOGGER.error(t, "Send async profiler task data to collector fail with a grpc internal exception."); + ServiceManager.INSTANCE.findService(GRPCChannelManager.class).reportError(t); + } + + @Override + public void onCompleted() { + status.finished(); + } + }); + AsyncProfilerMetaData metaData = AsyncProfilerMetaData.newBuilder() + .setService(Config.Agent.SERVICE_NAME) + .setServiceInstance(Config.Agent.INSTANCE_NAME) + .setType(AsyncProfilingStatus.PROFILING_SUCCESS) + .setContentSize(size) + .setTaskId(task.getTaskId()) + .build(); + AsyncProfilerData asyncProfilerData = AsyncProfilerData.newBuilder().setMetaData(metaData).build(); + dataStreamObserver.onNext(asyncProfilerData); + + status.wait4Finish(); + } + } + + public void sendError(AsyncProfilerTask task, String errorMessage) { + if (status != GRPCChannelStatus.CONNECTED) { + return; + } + final GRPCStreamServiceStatus status = new GRPCStreamServiceStatus(false); + StreamObserver dataStreamObserver = asyncProfilerTaskStub.withDeadlineAfter( + GRPC_UPSTREAM_TIMEOUT, TimeUnit.SECONDS + ).collect(new StreamObserver() { + @Override + public void onNext(AsyncProfilerCollectionResponse value) { + } + + @Override + public void onError(Throwable t) { + status.finished(); + LOGGER.error(t, "Send async profiler task execute error fail with a grpc internal exception."); + ServiceManager.INSTANCE.findService(GRPCChannelManager.class).reportError(t); + } + + @Override + public void onCompleted() { + status.finished(); + } + }); + AsyncProfilerMetaData metaData = AsyncProfilerMetaData.newBuilder() + .setService(Config.Agent.SERVICE_NAME) + .setServiceInstance(Config.Agent.INSTANCE_NAME) + .setTaskId(task.getTaskId()) + .setType(AsyncProfilingStatus.EXECUTION_TASK_ERROR) + .setContentSize(-1) + .build(); + AsyncProfilerData asyncProfilerData = AsyncProfilerData.newBuilder() + .setMetaData(metaData) + .setErrorMessage(errorMessage) + .build(); + dataStreamObserver.onNext(asyncProfilerData); + dataStreamObserver.onCompleted(); + status.wait4Finish(); + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/asyncprofiler/AsyncProfilerTask.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/asyncprofiler/AsyncProfilerTask.java new file mode 100644 index 0000000000..95948762b2 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/asyncprofiler/AsyncProfilerTask.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.core.asyncprofiler; + +import one.profiler.AsyncProfiler; +import org.apache.skywalking.apm.agent.core.conf.Config; +import org.apache.skywalking.apm.agent.core.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.apache.skywalking.apm.util.StringUtil; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class AsyncProfilerTask { + private static final ILog LOGGER = LogManager.getLogger(AsyncProfilerTask.class); + private static final String COMMA = ","; + /** + * task id + */ + private String taskId; + /** + * async profiler optional extended parameters + * + * @see org.apache.skywalking.apm.network.trace.component.command.AsyncProfilerTaskCommand + */ + private String execArgs; + /** + * run profiling for duration (second) + */ + private int duration; + /** + * The time when oap server created this task + */ + private long createTime; + /** + * tempFile generated by async-profiler execution + */ + private Path tempFile; + + private static String execute(AsyncProfiler asyncProfiler, String args) + throws IllegalArgumentException, IOException { + LOGGER.info("async profiler execute args:{}", args); + return asyncProfiler.execute(args); + } + + /** + * start async profiler + */ + public String start(AsyncProfiler asyncProfiler) throws IOException { + tempFile = getProfilerFilePath(); + StringBuilder startArgs = new StringBuilder(); + startArgs.append("start").append(COMMA); + if (StringUtil.isNotEmpty(execArgs)) { + startArgs.append(execArgs).append(COMMA); + } + startArgs.append("file=").append(tempFile.toString()); + + return execute(asyncProfiler, startArgs.toString()); + } + + /** + * stop async profiler and get file + */ + public File stop(AsyncProfiler asyncProfiler) throws IOException { + LOGGER.info("async profiler process stop and dump file"); + String stopArgs = "stop" + COMMA + "file=" + tempFile.toAbsolutePath(); + execute(asyncProfiler, stopArgs); + return tempFile.toFile(); + } + + /** + * if outputPath is configured, the JFR file will be generated at outputPath, + * otherwise createTemp will be used to create the file + */ + public Path getProfilerFilePath() throws IOException { + if (StringUtil.isNotEmpty(Config.AsyncProfiler.OUTPUT_PATH)) { + Path tempFilePath = Paths.get(Config.AsyncProfiler.OUTPUT_PATH, taskId + getFileExtension()); + return Files.createFile(tempFilePath.toAbsolutePath()); + } else { + return Files.createTempFile(taskId, getFileExtension()); + } + } + + private String getFileExtension() { + return ".jfr"; + } + + public void setExecArgs(String execArgs) { + this.execArgs = execArgs; + } + + public void setDuration(int duration) { + this.duration = duration; + } + + public void setTempFile(Path tempFile) { + this.tempFile = tempFile; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public void setCreateTime(long createTime) { + this.createTime = createTime; + } + + public String getExecArgs() { + return execArgs; + } + + public int getDuration() { + return duration; + } + + public Path getTempFile() { + return tempFile; + } + + public String getTaskId() { + return taskId; + } + + public long getCreateTime() { + return createTime; + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/asyncprofiler/AsyncProfilerTaskChannelService.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/asyncprofiler/AsyncProfilerTaskChannelService.java new file mode 100644 index 0000000000..7a2b26a46a --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/asyncprofiler/AsyncProfilerTaskChannelService.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.core.asyncprofiler; + +import io.grpc.Channel; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import org.apache.skywalking.apm.agent.core.boot.BootService; +import org.apache.skywalking.apm.agent.core.boot.DefaultImplementor; +import org.apache.skywalking.apm.agent.core.boot.DefaultNamedThreadFactory; +import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.commands.CommandService; +import org.apache.skywalking.apm.agent.core.conf.Config; +import org.apache.skywalking.apm.agent.core.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.apache.skywalking.apm.agent.core.remote.GRPCChannelListener; +import org.apache.skywalking.apm.agent.core.remote.GRPCChannelManager; +import org.apache.skywalking.apm.agent.core.remote.GRPCChannelStatus; +import org.apache.skywalking.apm.network.common.v3.Commands; +import org.apache.skywalking.apm.network.language.asyncprofiler.v10.AsyncProfilerTaskCommandQuery; +import org.apache.skywalking.apm.network.language.asyncprofiler.v10.AsyncProfilerTaskGrpc; +import org.apache.skywalking.apm.util.RunnableWithExceptionProtection; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import static org.apache.skywalking.apm.agent.core.conf.Config.Collector.GRPC_UPSTREAM_TIMEOUT; + +@DefaultImplementor +public class AsyncProfilerTaskChannelService implements BootService, Runnable, GRPCChannelListener { + private static final ILog LOGGER = LogManager.getLogger(AsyncProfilerTaskChannelService.class); + + // channel status + private volatile GRPCChannelStatus status = GRPCChannelStatus.DISCONNECT; + private volatile AsyncProfilerTaskGrpc.AsyncProfilerTaskBlockingStub asyncProfilerTaskBlockingStub; + + // query task schedule + private volatile ScheduledFuture getTaskFuture; + + @Override + public void run() { + if (status == GRPCChannelStatus.CONNECTED) { + try { + // test start command and 10s after put stop command + long lastCommandCreateTime = ServiceManager.INSTANCE + .findService(AsyncProfilerTaskExecutionService.class).getLastCommandCreateTime(); + + AsyncProfilerTaskCommandQuery query = AsyncProfilerTaskCommandQuery.newBuilder() + .setServiceInstance(Config.Agent.INSTANCE_NAME) + .setService(Config.Agent.SERVICE_NAME) + .setLastCommandTime(lastCommandCreateTime) + .build(); + Commands commands = asyncProfilerTaskBlockingStub.withDeadlineAfter(GRPC_UPSTREAM_TIMEOUT, TimeUnit.SECONDS) + .getAsyncProfilerTaskCommands(query); + ServiceManager.INSTANCE.findService(CommandService.class).receiveCommand(commands); + } catch (Throwable t) { + if (!(t instanceof StatusRuntimeException)) { + LOGGER.error(t, "fail to query async-profiler task from backend"); + return; + } + final StatusRuntimeException statusRuntimeException = (StatusRuntimeException) t; + if (Status.Code.UNIMPLEMENTED.equals(statusRuntimeException.getStatus().getCode())) { + LOGGER.warn("Backend doesn't support async-profiler, async-profiler will be disabled"); + if (getTaskFuture != null) { + getTaskFuture.cancel(true); + } + } + } + } + } + + @Override + public void statusChanged(GRPCChannelStatus status) { + if (GRPCChannelStatus.CONNECTED.equals(status)) { + Channel channel = ServiceManager.INSTANCE.findService(GRPCChannelManager.class).getChannel(); + asyncProfilerTaskBlockingStub = AsyncProfilerTaskGrpc.newBlockingStub(channel); + } else { + asyncProfilerTaskBlockingStub = null; + } + this.status = status; + } + + @Override + public void prepare() throws Throwable { + ServiceManager.INSTANCE.findService(GRPCChannelManager.class).addChannelListener(this); + } + + @Override + public void boot() throws Throwable { + + if (Config.AsyncProfiler.ACTIVE) { + getTaskFuture = Executors.newSingleThreadScheduledExecutor( + new DefaultNamedThreadFactory("AsyncProfilerGetTaskService") + ).scheduleWithFixedDelay( + new RunnableWithExceptionProtection( + this, + t -> LOGGER.error("Query async profiler task list failure.", t) + ), 0, Config.Collector.GET_PROFILE_TASK_INTERVAL, TimeUnit.SECONDS + ); + } + } + + @Override + public void onComplete() throws Throwable { + + } + + @Override + public void shutdown() throws Throwable { + if (getTaskFuture != null) { + getTaskFuture.cancel(true); + } + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/asyncprofiler/AsyncProfilerTaskExecutionService.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/asyncprofiler/AsyncProfilerTaskExecutionService.java new file mode 100644 index 0000000000..b5d7a5b643 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/asyncprofiler/AsyncProfilerTaskExecutionService.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.core.asyncprofiler; + +import one.profiler.AsyncProfiler; +import org.apache.skywalking.apm.agent.core.boot.BootService; +import org.apache.skywalking.apm.agent.core.boot.DefaultImplementor; +import org.apache.skywalking.apm.agent.core.boot.DefaultNamedThreadFactory; +import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.logging.api.LogManager; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@DefaultImplementor +public class AsyncProfilerTaskExecutionService implements BootService { + + private static final ILog LOGGER = LogManager.getLogger(AsyncProfilerTaskChannelService.class); + + private AsyncProfiler asyncProfilerInstance; + + private static final String SUCCESS_RESULT = "Profiling started\n"; + + // profile executor thread pool, only running one thread + private volatile ScheduledExecutorService asyncProfilerExecutor; + + // last command create time, use to next query task list + private volatile long lastCommandCreateTime = -1; + + // task schedule future + private volatile ScheduledFuture scheduledFuture; + + public void processAsyncProfilerTask(AsyncProfilerTask task) { + if (task.getCreateTime() <= lastCommandCreateTime) { + LOGGER.warn("get repeat task because createTime is less than lastCommandCreateTime"); + return; + } + lastCommandCreateTime = task.getCreateTime(); + LOGGER.info("add async profiler task: {}", task.getTaskId()); + // add task to list + getAsyncProfilerExecutor().execute(() -> { + try { + if (Objects.nonNull(scheduledFuture) && !scheduledFuture.isDone()) { + LOGGER.info("AsyncProfilerTask already running"); + return; + } + + String result = task.start(getAsyncProfiler()); + if (!SUCCESS_RESULT.equals(result)) { + stopWhenError(task, result); + return; + } + scheduledFuture = getAsyncProfilerExecutor().schedule( + () -> stopWhenSuccess(task), task.getDuration(), TimeUnit.SECONDS + ); + } catch (IOException e) { + LOGGER.error("AsyncProfilerTask executor error:" + e.getMessage(), e); + } + }); + } + + private void stopWhenError(AsyncProfilerTask task, String errorMessage) { + LOGGER.error("AsyncProfilerTask fails to start: " + errorMessage); + AsyncProfilerDataSender dataSender = ServiceManager.INSTANCE.findService(AsyncProfilerDataSender.class); + dataSender.sendError(task, errorMessage); + } + + private void stopWhenSuccess(AsyncProfilerTask task) { + // stop task and send data + try { + File dumpFile = task.stop(getAsyncProfiler()); + if (dumpFile != null && dumpFile.exists()) { + AsyncProfilerDataSender dataSender = ServiceManager.INSTANCE.findService(AsyncProfilerDataSender.class); + dataSender.sendData(task, dumpFile); + if (!dumpFile.delete()) { + LOGGER.warn("Fail to delete the dump file of async profiler."); + } + } + } catch (Exception e) { + LOGGER.error("stop async profiler task error", e); + return; + } + } + + public long getLastCommandCreateTime() { + return lastCommandCreateTime; + } + + @Override + public void prepare() throws Throwable { + + } + + @Override + public void boot() throws Throwable { + + } + + @Override + public void onComplete() throws Throwable { + + } + + @Override + public void shutdown() throws Throwable { + getAsyncProfilerExecutor().shutdown(); + if (Objects.nonNull(scheduledFuture)) { + scheduledFuture.cancel(true); + scheduledFuture = null; + } + } + + private AsyncProfiler getAsyncProfiler() { + if (asyncProfilerInstance == null) { + asyncProfilerInstance = AsyncProfiler.getInstance(); + } + return asyncProfilerInstance; + } + + private ScheduledExecutorService getAsyncProfilerExecutor() { + if (asyncProfilerExecutor == null) { + synchronized (this) { + if (asyncProfilerExecutor == null) { + asyncProfilerExecutor = Executors.newSingleThreadScheduledExecutor( + new DefaultNamedThreadFactory("ASYNC-PROFILING-TASK")); + } + } + } + return asyncProfilerExecutor; + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/boot/ServiceManager.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/boot/ServiceManager.java index ab3042646d..222a1d8221 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/boot/ServiceManager.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/boot/ServiceManager.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.ServiceLoader; +import lombok.Getter; import org.apache.skywalking.apm.agent.core.logging.api.ILog; import org.apache.skywalking.apm.agent.core.logging.api.LogManager; import org.apache.skywalking.apm.agent.core.plugin.loader.AgentClassLoader; @@ -37,6 +38,8 @@ public enum ServiceManager { private static final ILog LOGGER = LogManager.getLogger(ServiceManager.class); private Map bootedServices = Collections.emptyMap(); + @Getter + private volatile boolean isBooted = false; public void boot() { bootedServices = loadAllServices(); @@ -47,7 +50,7 @@ public void boot() { } public void shutdown() { - bootedServices.values().stream().sorted(Comparator.comparingInt(BootService::priority).reversed()).forEach(service -> { + bootedServices.values().stream().sorted(Comparator.comparingInt(BootService::priority)).forEach(service -> { try { service.shutdown(); } catch (Throwable e) { @@ -100,7 +103,7 @@ private Map loadAllServices() { } private void prepare() { - bootedServices.values().stream().sorted(Comparator.comparingInt(BootService::priority)).forEach(service -> { + bootedServices.values().stream().sorted(Comparator.comparingInt(BootService::priority).reversed()).forEach(service -> { try { service.prepare(); } catch (Throwable e) { @@ -110,7 +113,7 @@ private void prepare() { } private void startup() { - bootedServices.values().stream().sorted(Comparator.comparingInt(BootService::priority)).forEach(service -> { + bootedServices.values().stream().sorted(Comparator.comparingInt(BootService::priority).reversed()).forEach(service -> { try { service.boot(); } catch (Throwable e) { @@ -127,6 +130,7 @@ private void onComplete() { LOGGER.error(e, "Service [{}] AfterBoot process fails.", service.getClass().getName()); } } + isBooted = true; } /** diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/commands/CommandExecutorService.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/commands/CommandExecutorService.java index 819b0b9ff1..c619051c9b 100755 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/commands/CommandExecutorService.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/commands/CommandExecutorService.java @@ -21,9 +21,11 @@ import java.util.Map; import org.apache.skywalking.apm.agent.core.boot.BootService; import org.apache.skywalking.apm.agent.core.boot.DefaultImplementor; +import org.apache.skywalking.apm.agent.core.commands.executor.AsyncProfilerCommandExecutor; import org.apache.skywalking.apm.agent.core.commands.executor.ConfigurationDiscoveryCommandExecutor; import org.apache.skywalking.apm.agent.core.commands.executor.NoopCommandExecutor; import org.apache.skywalking.apm.agent.core.commands.executor.ProfileTaskCommandExecutor; +import org.apache.skywalking.apm.network.trace.component.command.AsyncProfilerTaskCommand; import org.apache.skywalking.apm.network.trace.component.command.BaseCommand; import org.apache.skywalking.apm.network.trace.component.command.ConfigurationDiscoveryCommand; import org.apache.skywalking.apm.network.trace.component.command.ProfileTaskCommand; @@ -48,6 +50,9 @@ public void prepare() throws Throwable { //Get ConfigurationDiscoveryCommand executor. commandExecutorMap.put(ConfigurationDiscoveryCommand.NAME, new ConfigurationDiscoveryCommandExecutor()); + + // AsyncProfiler task executor + commandExecutorMap.put(AsyncProfilerTaskCommand.NAME, new AsyncProfilerCommandExecutor()); } @Override diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/commands/executor/AsyncProfilerCommandExecutor.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/commands/executor/AsyncProfilerCommandExecutor.java new file mode 100644 index 0000000000..530b655f84 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/commands/executor/AsyncProfilerCommandExecutor.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.core.commands.executor; + +import org.apache.skywalking.apm.agent.core.asyncprofiler.AsyncProfilerTask; +import org.apache.skywalking.apm.agent.core.asyncprofiler.AsyncProfilerTaskExecutionService; +import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.commands.CommandExecutionException; +import org.apache.skywalking.apm.agent.core.commands.CommandExecutor; +import org.apache.skywalking.apm.agent.core.conf.Config; +import org.apache.skywalking.apm.network.trace.component.command.AsyncProfilerTaskCommand; +import org.apache.skywalking.apm.network.trace.component.command.BaseCommand; + +public class AsyncProfilerCommandExecutor implements CommandExecutor { + @Override + public void execute(BaseCommand command) throws CommandExecutionException { + AsyncProfilerTaskCommand asyncProfilerTaskCommand = (AsyncProfilerTaskCommand) command; + + AsyncProfilerTask asyncProfilerTask = new AsyncProfilerTask(); + asyncProfilerTask.setTaskId(asyncProfilerTaskCommand.getTaskId()); + int duration = Math.min(Config.AsyncProfiler.MAX_DURATION, asyncProfilerTaskCommand.getDuration()); + asyncProfilerTask.setDuration(duration); + asyncProfilerTask.setExecArgs(asyncProfilerTaskCommand.getExecArgs()); + asyncProfilerTask.setCreateTime(asyncProfilerTaskCommand.getCreateTime()); + ServiceManager.INSTANCE.findService(AsyncProfilerTaskExecutionService.class) + .processAsyncProfilerTask(asyncProfilerTask); + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java index be7d54a4e4..87cf57955b 100755 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java @@ -97,6 +97,11 @@ public static class Agent { */ public static int TRACE_SEGMENT_REF_LIMIT_PER_SPAN = 500; + /** + * The max number of logs in a single span to keep memory cost estimatable. + */ + public static int LOG_LIMIT_PER_SPAN = 300; + /** * The max number of spans in a single segment. Through this config item, SkyWalking keep your application * memory cost estimated. @@ -252,6 +257,33 @@ public static class Profile { public static int SNAPSHOT_TRANSPORT_BUFFER_SIZE = 500; } + public static class AsyncProfiler { + /** + * If true, async profiler will be enabled when user creates a new async profiler task. + * If false, it will be disabled. + * The default value is true. + */ + public static boolean ACTIVE = true; + + /** + * Max execution time(second) for the Async Profiler. The task will be stopped even if a longer time is specified. + * default 20min. + */ + public static int MAX_DURATION = 1200; + + /** + * Path for the JFR outputs from the Async Profiler. + * If the parameter is not empty, the file will be created in the specified directory, + * otherwise the Files.createTemp method will be used to create the file. + */ + public static String OUTPUT_PATH = ""; + + /** + * The size of the chunk when uploading jfr + */ + public static final int DATA_CHUNK_SIZE = 1024 * 1024; + } + public static class Meter { /** * If true, skywalking agent will enable sending meters. Otherwise disable meter report. diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/AbstractTracerContext.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/AbstractTracerContext.java index a212e126e9..01d8ba99d2 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/AbstractTracerContext.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/AbstractTracerContext.java @@ -138,4 +138,10 @@ public interface AbstractTracerContext { * Get current primary endpoint name */ String getPrimaryEndpointName(); + + /** + * Change the current context to be in ignoring status. + */ + AbstractTracerContext forceIgnoring(); + } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManager.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManager.java index c1ed28fd9d..1da79e9cce 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManager.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManager.java @@ -26,6 +26,7 @@ import org.apache.skywalking.apm.agent.core.logging.api.ILog; import org.apache.skywalking.apm.agent.core.logging.api.LogManager; import org.apache.skywalking.apm.agent.core.sampling.SamplingService; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; import org.apache.skywalking.apm.util.StringUtil; import static org.apache.skywalking.apm.agent.core.conf.Config.Agent.OPERATION_NAME_THRESHOLD; @@ -52,6 +53,7 @@ private static AbstractTracerContext getOrCreate(String operationName, boolean f if (LOGGER.isDebugEnable()) { LOGGER.debug("No operation name, ignore this trace."); } + AgentSo11y.measureTracingContextCreation(forceSampling, true); context = new IgnoredTracerContext(); } else { if (EXTEND_SERVICE == null) { @@ -163,7 +165,14 @@ public static void continued(ContextSnapshot snapshot) { throw new IllegalArgumentException("ContextSnapshot can't be null."); } if (!snapshot.isFromCurrent()) { - get().continued(snapshot); + // Invalid snapshot is only created by {@link IgnoredTracerContext#capture()}. + // When the snapshot is not valid, need to force ignoring the current tracing context. + if (snapshot.isValid()) { + get().continued(snapshot); + } else { + AbstractTracerContext context = get().forceIgnoring(); + CONTEXT.set(context); + } } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManagerExtendService.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManagerExtendService.java index 1e408df81b..560d57c660 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManagerExtendService.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManagerExtendService.java @@ -18,7 +18,10 @@ package org.apache.skywalking.apm.agent.core.context; -import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.apache.skywalking.apm.agent.core.boot.BootService; import org.apache.skywalking.apm.agent.core.boot.DefaultImplementor; import org.apache.skywalking.apm.agent.core.boot.ServiceManager; @@ -30,12 +33,13 @@ import org.apache.skywalking.apm.agent.core.remote.GRPCChannelManager; import org.apache.skywalking.apm.agent.core.remote.GRPCChannelStatus; import org.apache.skywalking.apm.agent.core.sampling.SamplingService; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; import org.apache.skywalking.apm.util.StringUtil; @DefaultImplementor public class ContextManagerExtendService implements BootService, GRPCChannelListener { - private volatile String[] ignoreSuffixArray = new String[0]; + private volatile Set ignoreSuffixSet; private volatile GRPCChannelStatus status = GRPCChannelStatus.DISCONNECT; @@ -50,7 +54,7 @@ public void prepare() { @Override public void boot() { - ignoreSuffixArray = Config.Agent.IGNORE_SUFFIX.split(","); + ignoreSuffixSet = Stream.of(Config.Agent.IGNORE_SUFFIX.split(",")).collect(Collectors.toSet()); ignoreSuffixPatternsWatcher = new IgnoreSuffixPatternsWatcher("agent.ignore_suffix", this); spanLimitWatcher = new SpanLimitWatcher("agent.span_limit_per_segment"); @@ -78,18 +82,22 @@ public AbstractTracerContext createTraceContext(String operationName, boolean fo * Don't trace anything if the backend is not available. */ if (!Config.Agent.KEEP_TRACING && GRPCChannelStatus.DISCONNECT.equals(status)) { + AgentSo11y.measureTracingContextCreation(forceSampling, true); return new IgnoredTracerContext(); } int suffixIdx = operationName.lastIndexOf("."); - if (suffixIdx > -1 && Arrays.stream(ignoreSuffixArray) - .anyMatch(a -> a.equals(operationName.substring(suffixIdx)))) { + if (suffixIdx > -1 && ignoreSuffixSet.contains(operationName.substring(suffixIdx))) { + AgentSo11y.measureTracingContextCreation(forceSampling, true); context = new IgnoredTracerContext(); } else { SamplingService samplingService = ServiceManager.INSTANCE.findService(SamplingService.class); if (forceSampling || samplingService.trySampling(operationName)) { + AgentSo11y.measureTracingContextCreation(forceSampling, false); context = new TracingContext(operationName, spanLimitWatcher); } else { + AgentSo11y.measureTracingContextCreation(false, true); + AgentSo11y.measureLeakedTracingContext(true); context = new IgnoredTracerContext(); } } @@ -104,7 +112,7 @@ public void statusChanged(final GRPCChannelStatus status) { public void handleIgnoreSuffixPatternsChanged() { if (StringUtil.isNotBlank(ignoreSuffixPatternsWatcher.getIgnoreSuffixPatterns())) { - ignoreSuffixArray = ignoreSuffixPatternsWatcher.getIgnoreSuffixPatterns().split(","); + ignoreSuffixSet = Stream.of(ignoreSuffixPatternsWatcher.getIgnoreSuffixPatterns().split(",")).collect(Collectors.toSet()); } } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/IgnoredTracerContext.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/IgnoredTracerContext.java index 67fabd3e0a..8bf57ee064 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/IgnoredTracerContext.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/IgnoredTracerContext.java @@ -23,6 +23,7 @@ import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.context.trace.NoopSpan; import org.apache.skywalking.apm.agent.core.profile.ProfileStatusContext; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; /** * The IgnoredTracerContext represent a context should be ignored. So it just maintains the stack with an @@ -47,6 +48,13 @@ public IgnoredTracerContext() { this.profileStatusContext = ProfileStatusContext.createWithNone(); } + public IgnoredTracerContext(int stackDepth) { + this.stackDepth = stackDepth; + this.correlationContext = new CorrelationContext(); + this.extensionContext = new ExtensionContext(); + this.profileStatusContext = ProfileStatusContext.createWithNone(); + } + @Override public void inject(ContextCarrier carrier) { this.correlationContext.inject(carrier); @@ -109,6 +117,7 @@ public AbstractSpan activeSpan() { public boolean stopSpan(AbstractSpan span) { stackDepth--; if (stackDepth == 0) { + AgentSo11y.measureTracingContextCompletion(true); ListenerManager.notifyFinish(this); } return stackDepth == 0; @@ -134,6 +143,11 @@ public String getPrimaryEndpointName() { return null; } + @Override + public AbstractTracerContext forceIgnoring() { + return this; + } + public static class ListenerManager { private static List LISTENERS = new LinkedList<>(); diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/TracingContext.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/TracingContext.java index 305672bb2a..cc1faa9af7 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/TracingContext.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/TracingContext.java @@ -43,6 +43,7 @@ import org.apache.skywalking.apm.agent.core.logging.api.LogManager; import org.apache.skywalking.apm.agent.core.profile.ProfileStatusContext; import org.apache.skywalking.apm.agent.core.profile.ProfileTaskExecutionService; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; import org.apache.skywalking.apm.util.StringUtil; import static org.apache.skywalking.apm.agent.core.conf.Config.Agent.CLUSTER; @@ -419,6 +420,14 @@ public String getPrimaryEndpointName() { return primaryEndpoint.getName(); } + @Override + public AbstractTracerContext forceIgnoring() { + for (AbstractSpan span: activeSpanStack) { + span.forceIgnoring(); + } + return new IgnoredTracerContext(activeSpanStack.size()); + } + /** * Re-check current trace need profiling, encase third part plugin change the operation name. * @@ -452,7 +461,12 @@ private void finish() { } if (isFinishedInMainThread && (!isRunningInAsyncMode || asyncSpanCounter == 0)) { - TraceSegment finishedSegment = segment.finish(isLimitMechanismWorking()); + boolean limitMechanismWorking = isLimitMechanismWorking(); + if (limitMechanismWorking) { + AgentSo11y.measureLeakedTracingContext(false); + } + AgentSo11y.measureTracingContextCompletion(false); + TraceSegment finishedSegment = segment.finish(limitMechanismWorking); TracingContext.ListenerManager.notifyFinish(finishedSegment); running = false; } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/tag/Tags.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/tag/Tags.java index e538241368..8acb2b9879 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/tag/Tags.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/tag/Tags.java @@ -155,6 +155,116 @@ public static final class HTTP { */ public static final StringTag THREAD_ID = new StringTag(23, "thread.id"); + /** + * THREAD_CARRIER represents the actual operating system thread that carries out the execution of the virtual thread. + */ + public static final StringTag THREAD_CARRIER = new StringTag(24, "thread.carrier"); + + /** + * GEN_AI_OPERATION_NAME represents the name of the operation being performed + */ + public static final StringTag GEN_AI_OPERATION_NAME = new StringTag(25, "gen_ai.operation.name"); + + /** + * GEN_AI_PROVIDER_NAME represents the Generative AI provider as identified by the client or server instrumentation. + */ + public static final StringTag GEN_AI_PROVIDER_NAME = new StringTag(26, "gen_ai.provider.name"); + + /** + * GEN_AI_REQUEST_MODEL represents the name of the GenAI model a request is being made to. + */ + public static final StringTag GEN_AI_REQUEST_MODEL = new StringTag(27, "gen_ai.request.model"); + + /** + * GEN_AI_TOP_K represents the top_k sampling setting for the GenAI request. + */ + public static final StringTag GEN_AI_TOP_K = new StringTag(28, "gen_ai.request.top_k"); + + /** + * GEN_AI_TOP_P represents the top_p sampling setting for the GenAI request. + */ + public static final StringTag GEN_AI_TOP_P = new StringTag(29, "gen_ai.request.top_p"); + + /** + * GEN_AI_TEMPERATURE represents the temperature setting for the GenAI request. + */ + public static final StringTag GEN_AI_TEMPERATURE = new StringTag(30, "gen_ai.request.temperature"); + + /** + * GEN_AI_TOOL_NAME represents the name of the tool utilized by the agent. + */ + public static final StringTag GEN_AI_TOOL_NAME = new StringTag(31, "gen_ai.tool.name"); + + /** + * GEN_AI_TOOL_CALL_ARGUMENTS represents the parameters passed to the tool call. + */ + public static final StringTag GEN_AI_TOOL_CALL_ARGUMENTS = new StringTag(32, "gen_ai.tool.call.arguments"); + + /** + * GEN_AI_TOOL_CALL_RESULT represents the result returned by the tool call (if any and if execution was successful). + */ + public static final StringTag GEN_AI_TOOL_CALL_RESULT = new StringTag(33, "gen_ai.tool.call.result"); + + /** + * GEN_AI_RESPONSE_MODEL represents the name of the model that generated the response. + */ + public static final StringTag GEN_AI_RESPONSE_MODEL = new StringTag(34, "gen_ai.response.model"); + + /** + * GEN_AI_RESPONSE_ID represents the unique identifier for the completion. + */ + public static final StringTag GEN_AI_RESPONSE_ID = new StringTag(35, "gen_ai.response.id"); + + /** + * GEN_AI_USAGE_INPUT_TOKENS represents the number of tokens used in the GenAI input (prompt). + */ + public static final StringTag GEN_AI_USAGE_INPUT_TOKENS = new StringTag(36, "gen_ai.usage.input_tokens"); + + /** + * GEN_AI_USAGE_OUTPUT_TOKENS represents the number of tokens used in the GenAI response (completion). + */ + public static final StringTag GEN_AI_USAGE_OUTPUT_TOKENS = new StringTag(37, "gen_ai.usage.output_tokens"); + + /** + * GEN_AI_USAGE_TOTAL_TOKENS represents the total number of tokens used in the GenAI exchange. + */ + public static final StringTag GEN_AI_CLIENT_TOKEN_USAGE = new StringTag(38, "gen_ai.client.token.usage"); + + /** + * GEN_AI_RESPONSE_FINISH_REASONS represents the array of reasons the model stopped generating tokens. + */ + public static final StringTag GEN_AI_RESPONSE_FINISH_REASONS = new StringTag(39, "gen_ai.response.finish_reasons"); + + /** + * GEN_AI_STREAM_TTFR represents the time to first response (TTFR) for streaming operations. + */ + public static final StringTag GEN_AI_SERVER_TIME_TO_FIRST_TOKEN = new StringTag(40, "gen_ai.server.time_to_first_token"); + + /** + * GEN_AI_INPUT_MESSAGES represents the chat history provided to the model as an input. + */ + public static final StringTag GEN_AI_INPUT_MESSAGES = new StringTag(41, "gen_ai.input.messages"); + + /** + * GEN_AI_OUTPUT_MESSAGES represents the messages returned by the model where each message represents a specific model response (choice, candidate). + */ + public static final StringTag GEN_AI_OUTPUT_MESSAGES = new StringTag(42, "gen_ai.output.messages"); + + /** + * GEN_AI_DATA_SOURCE_ID represents the data source identifier. + */ + public static final StringTag GEN_AI_DATA_SOURCE_ID = new StringTag(43, "gen_ai.data_source.id"); + + /** + * GEN_AI_RETRIEVAL_DOCUMENTS represents the documents retrieved. + */ + public static final StringTag GEN_AI_RETRIEVAL_DOCUMENTS = new StringTag(44, "gen_ai.retrieval.documents"); + + /** + * GEN_AI_RETRIEVAL_QUERY_TEXT represents the query text used for retrieval. + */ + public static final StringTag GEN_AI_RETRIEVAL_QUERY_TEXT = new StringTag(45, "gen_ai.retrieval.query.text"); + /** * Creates a {@code StringTag} with the given key and cache it, if it's created before, simply return it without * creating a new one. diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractSpan.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractSpan.java index 91a8c79432..9a1b6c93eb 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractSpan.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractSpan.java @@ -124,4 +124,9 @@ public interface AbstractSpan extends AsyncSpan { * Should skip analysis in the backend. */ void skipAnalysis(); + + /** + * Set to ignored status. + */ + void forceIgnoring(); } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractTracingSpan.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractTracingSpan.java index 83736bd5d8..a8ca96e07a 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractTracingSpan.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractTracingSpan.java @@ -63,7 +63,8 @@ public abstract class AbstractTracingSpan implements AbstractSpan { private volatile boolean isAsyncStopped = false; /** - * The context to which the span belongs + * The context to which the span belongs. + * This should not be called when {@link #ignored} is true. */ protected final TracingContext owner; @@ -99,6 +100,11 @@ public abstract class AbstractTracingSpan implements AbstractSpan { */ protected boolean skipAnalysis; + /** + * The ignore flag of this span. + */ + protected boolean ignored; + protected AbstractTracingSpan(int spanId, int parentSpanId, String operationName, TracingContext owner) { this.operationName = operationName; this.spanId = spanId; @@ -169,6 +175,9 @@ public AbstractTracingSpan log(Throwable t) { if (!errorOccurred && ServiceManager.INSTANCE.findService(StatusCheckService.class).isError(t)) { errorOccurred(); } + if (logs.size() >= Config.Agent.LOG_LIMIT_PER_SPAN) { + return this; + } logs.add(new LogDataEntity.Builder().add(new KeyValuePair("event", "error")) .add(new KeyValuePair("error.kind", t.getClass().getName())) .add(new KeyValuePair("message", t.getMessage())) @@ -190,6 +199,9 @@ public AbstractTracingSpan log(long timestampMicroseconds, Map fields if (logs == null) { logs = new LinkedList<>(); } + if (logs.size() >= Config.Agent.LOG_LIMIT_PER_SPAN) { + return this; + } LogDataEntity.Builder builder = new LogDataEntity.Builder(); for (Map.Entry entry : fields.entrySet()) { builder.add(new KeyValuePair(entry.getKey(), entry.getValue().toString())); @@ -318,7 +330,9 @@ public AbstractSpan prepareForAsync() { if (isInAsyncMode) { throw new RuntimeException("Prepare for async repeatedly. Span is already in async mode."); } - ContextManager.awaitFinishAsync(this); + if (!ignored) { + ContextManager.awaitFinishAsync(this); + } isInAsyncMode = true; return this; } @@ -332,13 +346,18 @@ public AbstractSpan asyncFinish() { throw new RuntimeException("Can not do async finish for the span repeatedly."); } this.endTime = System.currentTimeMillis(); - owner.asyncStop(this); + if (!ignored) { + owner.asyncStop(this); + } isAsyncStopped = true; return this; } @Override public boolean isProfiling() { + if (ignored) { + return false; + } return this.owner.profileStatus().isProfiling(); } @@ -346,4 +365,9 @@ public boolean isProfiling() { public void skipAnalysis() { this.skipAnalysis = true; } + + @Override + public void forceIgnoring() { + this.ignored = true; + } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/ExitSpan.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/ExitSpan.java index e61a8d1cab..18e391be4e 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/ExitSpan.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/ExitSpan.java @@ -111,7 +111,9 @@ public String getPeer() { @Override public ExitSpan inject(final ContextCarrier carrier) { - this.owner.inject(this, carrier); + if (!ignored) { + this.owner.inject(this, carrier); + } return this; } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/NoopSpan.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/NoopSpan.java index c67148abea..59dfb1a78a 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/NoopSpan.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/NoopSpan.java @@ -123,6 +123,10 @@ public boolean isProfiling() { public void skipAnalysis() { } + @Override + public void forceIgnoring() { + } + @Override public AbstractSpan prepareForAsync() { return this; diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/SpanLayer.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/SpanLayer.java index 4ee9395ac9..c31fdc4cc1 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/SpanLayer.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/SpanLayer.java @@ -19,7 +19,8 @@ package org.apache.skywalking.apm.agent.core.context.trace; public enum SpanLayer { - DB(1), RPC_FRAMEWORK(2), HTTP(3), MQ(4), CACHE(5); + DB(1), RPC_FRAMEWORK(2), HTTP(3), MQ(4), CACHE(5), + GEN_AI(7); private int code; @@ -50,4 +51,8 @@ public static void asHttp(AbstractSpan span) { public static void asMQ(AbstractSpan span) { span.setLayer(SpanLayer.MQ); } + + public static void asGenAI(AbstractSpan span) { + span.setLayer(SpanLayer.GEN_AI); + } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/logging/core/AbstractLogger.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/logging/core/AbstractLogger.java index 20b55589f9..4020359d0a 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/logging/core/AbstractLogger.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/logging/core/AbstractLogger.java @@ -32,7 +32,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; /** * An abstract class to simplify the real implementation of the loggers. @@ -189,18 +188,17 @@ protected String replaceParam(String message, Object... parameters) { int startSize = 0; int parametersIndex = 0; int index; - String tmpMessage = message; - while ((index = message.indexOf("{}", startSize)) != -1) { + StringBuilder sb = new StringBuilder(message); + while ((index = sb.indexOf("{}", startSize)) != -1) { if (parametersIndex >= parameters.length) { break; } - /** - * @Fix the Illegal group reference issue - */ - tmpMessage = tmpMessage.replaceFirst("\\{\\}", Matcher.quoteReplacement(String.valueOf(parameters[parametersIndex++]))); - startSize = index + 2; + + String replaced = String.valueOf(parameters[parametersIndex++]); + sb.replace(index, index + 2, replaced); + startSize = index + replaced.length(); } - return tmpMessage; + return sb.toString(); } protected void logger(LogLevel level, String message, Throwable e) { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/logging/core/FileWriter.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/logging/core/FileWriter.java index 4020b7a98a..adc7194e45 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/logging/core/FileWriter.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/logging/core/FileWriter.java @@ -97,6 +97,7 @@ public void run() { public void handle(Throwable t) { } }), "SkywalkingAgent-LogFileWriter"); + logFlusherThread.setDaemon(true); logFlusherThread.start(); } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java index 949f8d5813..f0ab14a75b 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java @@ -43,10 +43,22 @@ public abstract class AbstractClassEnhancePluginDefine { private static final ILog LOGGER = LogManager.getLogger(AbstractClassEnhancePluginDefine.class); + /** + * plugin name defined in skywalking-plugin.def + */ + private String pluginName; /** * New field name. */ public static final String CONTEXT_ATTR_NAME = "_$EnhancedClassField_ws"; + /** + * Getter method name. + */ + public static final String CONTEXT_GETTER_NAME = "getSkyWalkingDynamicField"; + /** + * Setter method name. + */ + public static final String CONTEXT_SETTER_NAME = "setSkyWalkingDynamicField"; /** * Main entrance of enhancing the class. @@ -199,4 +211,17 @@ public boolean isBootstrapInstrumentation() { * @return collections of {@link InstanceMethodsInterceptV2Point} */ public abstract StaticMethodsInterceptV2Point[] getStaticMethodsInterceptV2Points(); + + /** + * plugin name should be set after create PluginDefine instance + * + * @param pluginName key defined in skywalking-plugin.def + */ + protected void setPluginName(final String pluginName) { + this.pluginName = pluginName; + } + + public String getPluginName() { + return pluginName; + } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/PluginBootstrap.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/PluginBootstrap.java index 7d54eebd73..bea0c3a7fa 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/PluginBootstrap.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/PluginBootstrap.java @@ -33,6 +33,15 @@ public class PluginBootstrap { private static final ILog LOGGER = LogManager.getLogger(PluginBootstrap.class); + // Preload ThreadLocalRandom in case of intermittent ClassCircularityError since ByteBuddy 1.12.11 + static { + try { + Class.forName("java.util.concurrent.ThreadLocalRandom"); + } catch (Exception e) { + LOGGER.warn(e, "Preload ThreadLocalRandom failure."); + } + } + /** * load all plugins. * @@ -65,6 +74,7 @@ public List loadPlugins() throws AgentPackageN LOGGER.debug("loading plugin class {}.", pluginDefine.getDefineClass()); AbstractClassEnhancePluginDefine plugin = (AbstractClassEnhancePluginDefine) Class.forName(pluginDefine.getDefineClass(), true, AgentClassLoader .getDefault()).newInstance(); + plugin.setPluginName(pluginDefine.getName()); plugins.add(plugin); } catch (Throwable t) { LOGGER.error(t, "load plugin [{}] failure.", pluginDefine.getDefineClass()); diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/PluginFinder.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/PluginFinder.java index 4ef608dccc..5a7404e729 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/PluginFinder.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/PluginFinder.java @@ -95,13 +95,14 @@ public boolean matches(NamedElement target) { return nameMatchDefine.containsKey(target.getActualName()); } }; - judge = judge.and(not(isInterface())); for (AbstractClassEnhancePluginDefine define : signatureMatchDefine) { ClassMatch match = define.enhanceClass(); if (match instanceof IndirectMatch) { judge = judge.or(((IndirectMatch) match).buildJunction()); } } + // Filter out all matchers returns to exclude pure interface types. + judge = not(isInterface()).and(judge); return new ProtectiveShieldMatcher(judge); } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/BootstrapInstrumentBoost.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/BootstrapInstrumentBoost.java index 523340a401..f5875a7616 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/BootstrapInstrumentBoost.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/BootstrapInstrumentBoost.java @@ -71,6 +71,9 @@ public class BootstrapInstrumentBoost { "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2", "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.StaticMethodsAroundInterceptorV2", "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext", + + //SO11Y + "org.apache.skywalking.apm.agent.core.so11y.bootstrap.BootstrapPluginSo11y" }; private static String INSTANCE_METHOD_DELEGATE_TEMPLATE = "org.apache.skywalking.apm.agent.core.plugin.bootstrap.template.InstanceMethodInterTemplate"; @@ -162,11 +165,14 @@ private static boolean prepareJREInstrumentation(PluginFinder pluginFinder, for (InstanceMethodsInterceptPoint point : define.getInstanceMethodsInterceptPoints()) { if (point.isOverrideArgs()) { generateDelegator( - classesTypeMap, typePool, INSTANCE_METHOD_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE, point - .getMethodsInterceptor()); + classesTypeMap, typePool, define.getPluginName(), + INSTANCE_METHOD_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE, point.getMethodsInterceptor() + ); } else { generateDelegator( - classesTypeMap, typePool, INSTANCE_METHOD_DELEGATE_TEMPLATE, point.getMethodsInterceptor()); + classesTypeMap, typePool, define.getPluginName(), + INSTANCE_METHOD_DELEGATE_TEMPLATE, point.getMethodsInterceptor() + ); } } } @@ -174,7 +180,9 @@ private static boolean prepareJREInstrumentation(PluginFinder pluginFinder, if (Objects.nonNull(define.getConstructorsInterceptPoints())) { for (ConstructorInterceptPoint point : define.getConstructorsInterceptPoints()) { generateDelegator( - classesTypeMap, typePool, CONSTRUCTOR_DELEGATE_TEMPLATE, point.getConstructorInterceptor()); + classesTypeMap, typePool, define.getPluginName(), + CONSTRUCTOR_DELEGATE_TEMPLATE, point.getConstructorInterceptor() + ); } } @@ -182,11 +190,14 @@ private static boolean prepareJREInstrumentation(PluginFinder pluginFinder, for (StaticMethodsInterceptPoint point : define.getStaticMethodsInterceptPoints()) { if (point.isOverrideArgs()) { generateDelegator( - classesTypeMap, typePool, STATIC_METHOD_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE, point - .getMethodsInterceptor()); + classesTypeMap, typePool, define.getPluginName(), + STATIC_METHOD_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE, point.getMethodsInterceptor() + ); } else { generateDelegator( - classesTypeMap, typePool, STATIC_METHOD_DELEGATE_TEMPLATE, point.getMethodsInterceptor()); + classesTypeMap, typePool, define.getPluginName(), + STATIC_METHOD_DELEGATE_TEMPLATE, point.getMethodsInterceptor() + ); } } } @@ -202,14 +213,14 @@ private static boolean prepareJREInstrumentationV2(PluginFinder pluginFinder, if (Objects.nonNull(define.getInstanceMethodsInterceptV2Points())) { for (InstanceMethodsInterceptV2Point point : define.getInstanceMethodsInterceptV2Points()) { if (point.isOverrideArgs()) { - generateDelegator(classesTypeMap, typePool, - INSTANCE_METHOD_V2_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE, - point.getMethodsInterceptorV2() + generateDelegator( + classesTypeMap, typePool, define.getPluginName(), + INSTANCE_METHOD_V2_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE, point.getMethodsInterceptorV2() ); } else { generateDelegator( - classesTypeMap, typePool, INSTANCE_METHOD_V2_DELEGATE_TEMPLATE, - point.getMethodsInterceptorV2() + classesTypeMap, typePool, define.getPluginName(), + INSTANCE_METHOD_V2_DELEGATE_TEMPLATE, point.getMethodsInterceptorV2() ); } } @@ -218,14 +229,14 @@ private static boolean prepareJREInstrumentationV2(PluginFinder pluginFinder, if (Objects.nonNull(define.getStaticMethodsInterceptV2Points())) { for (StaticMethodsInterceptV2Point point : define.getStaticMethodsInterceptV2Points()) { if (point.isOverrideArgs()) { - generateDelegator(classesTypeMap, typePool, - STATIC_METHOD_V2_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE, - point.getMethodsInterceptorV2() + generateDelegator( + classesTypeMap, typePool, define.getPluginName(), + STATIC_METHOD_V2_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE, point.getMethodsInterceptorV2() ); } else { generateDelegator( - classesTypeMap, typePool, STATIC_METHOD_V2_DELEGATE_TEMPLATE, - point.getMethodsInterceptorV2() + classesTypeMap, typePool, define.getPluginName(), + STATIC_METHOD_V2_DELEGATE_TEMPLATE, point.getMethodsInterceptorV2() ); } } @@ -245,7 +256,7 @@ private static boolean prepareJREInstrumentationV2(PluginFinder pluginFinder, * pre-defined in SkyWalking agent core. */ private static void generateDelegator(Map classesTypeMap, TypePool typePool, - String templateClassName, String methodsInterceptor) { + String pluginName, String templateClassName, String methodsInterceptor) { String internalInterceptorName = internalDelegate(methodsInterceptor); try { TypeDescription templateTypeDescription = typePool.describe(templateClassName).resolve(); @@ -253,6 +264,8 @@ private static void generateDelegator(Map classesTypeMap, TypePo DynamicType.Unloaded interceptorType = new ByteBuddy().redefine(templateTypeDescription, ClassFileLocator.ForClassLoader .of(BootstrapInstrumentBoost.class.getClassLoader())) .name(internalInterceptorName) + .field(named("PLUGIN_NAME")) + .value(pluginName) .field(named("TARGET_INTERCEPTOR")) .value(methodsInterceptor) .make(); diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/ConstructorInterTemplate.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/ConstructorInterTemplate.java index 756d03f299..06b7e8132e 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/ConstructorInterTemplate.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/ConstructorInterTemplate.java @@ -25,6 +25,7 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.agent.core.so11y.bootstrap.BootstrapPluginSo11y; /** * --------CLASS TEMPLATE--------- @@ -37,6 +38,12 @@ * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. */ public class ConstructorInterTemplate { + + private static final String INTERCEPTOR_TYPE = "constructor"; + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String PLUGIN_NAME; /** * This field is never set in the template, but has value in the runtime. */ @@ -44,6 +51,7 @@ public class ConstructorInterTemplate { private static InstanceConstructorInterceptor INTERCEPTOR; private static IBootstrapLog LOGGER; + private static BootstrapPluginSo11y PLUGIN_SO11Y; /** * Intercept the target constructor. @@ -53,6 +61,8 @@ public class ConstructorInterTemplate { */ @RuntimeType public static void intercept(@This Object obj, @AllArguments Object[] allArguments) { + long interceptorTimeCost = 0L; + long startTime = System.nanoTime(); try { prepare(); @@ -64,7 +74,10 @@ public static void intercept(@This Object obj, @AllArguments Object[] allArgumen INTERCEPTOR.onConstruct(targetObject, allArguments); } catch (Throwable t) { LOGGER.error("ConstructorInter failure.", t); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTime; + PLUGIN_SO11Y.duration(interceptorTimeCost); } /** @@ -79,6 +92,7 @@ private static void prepare() { if (logger != null) { LOGGER = logger; + PLUGIN_SO11Y = BootstrapInterRuntimeAssist.getSO11Y(loader); INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); } } else { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/InstanceMethodInterTemplate.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/InstanceMethodInterTemplate.java index ef70a5722b..62a0c8fac9 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/InstanceMethodInterTemplate.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/InstanceMethodInterTemplate.java @@ -30,6 +30,7 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.agent.core.so11y.bootstrap.BootstrapPluginSo11y; /** * --------CLASS TEMPLATE--------- @@ -42,6 +43,12 @@ * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. */ public class InstanceMethodInterTemplate { + + private static final String INTERCEPTOR_TYPE = "inst"; + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String PLUGIN_NAME; /** * This field is never set in the template, but has value in the runtime. */ @@ -49,6 +56,7 @@ public class InstanceMethodInterTemplate { private static InstanceMethodsAroundInterceptor INTERCEPTOR; private static IBootstrapLog LOGGER; + private static BootstrapPluginSo11y PLUGIN_SO11Y; /** * Intercept the target instance method. @@ -68,6 +76,8 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum prepare(); + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInterceptResult result = new MethodInterceptResult(); try { if (INTERCEPTOR != null) { @@ -77,7 +87,9 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum if (LOGGER != null) { LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); } + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -87,6 +99,7 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum ret = zuper.call(); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { if (INTERCEPTOR != null) { INTERCEPTOR.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t); @@ -95,9 +108,12 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum if (LOGGER != null) { LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); } + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { if (INTERCEPTOR != null) { ret = INTERCEPTOR.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret); @@ -106,8 +122,11 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum if (LOGGER != null) { LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); } + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + PLUGIN_SO11Y.duration(interceptorTimeCost); return ret; } @@ -124,6 +143,7 @@ private static void prepare() { if (logger != null) { LOGGER = logger; + PLUGIN_SO11Y = BootstrapInterRuntimeAssist.getSO11Y(loader); INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); } } else { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/InstanceMethodInterWithOverrideArgsTemplate.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/InstanceMethodInterWithOverrideArgsTemplate.java index 1ba0464de0..16053055d3 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/InstanceMethodInterWithOverrideArgsTemplate.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/InstanceMethodInterWithOverrideArgsTemplate.java @@ -30,6 +30,7 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.OverrideCallable; +import org.apache.skywalking.apm.agent.core.so11y.bootstrap.BootstrapPluginSo11y; /** * --------CLASS TEMPLATE--------- @@ -42,6 +43,12 @@ * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. */ public class InstanceMethodInterWithOverrideArgsTemplate { + + private static final String INTERCEPTOR_TYPE = "inst"; + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String PLUGIN_NAME; /** * This field is never set in the template, but has value in the runtime. */ @@ -49,6 +56,7 @@ public class InstanceMethodInterWithOverrideArgsTemplate { private static InstanceMethodsAroundInterceptor INTERCEPTOR; private static IBootstrapLog LOGGER; + private static BootstrapPluginSo11y PLUGIN_SO11Y; /** * Intercept the target instance method. @@ -68,6 +76,8 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum prepare(); + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInterceptResult result = new MethodInterceptResult(); try { if (INTERCEPTOR != null) { @@ -77,7 +87,9 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum if (LOGGER != null) { LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); } + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -87,6 +99,7 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum ret = zuper.call(allArguments); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { if (INTERCEPTOR != null) { INTERCEPTOR.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t); @@ -95,9 +108,12 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum if (LOGGER != null) { LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); } + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { if (INTERCEPTOR != null) { ret = INTERCEPTOR.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret); @@ -106,8 +122,11 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum if (LOGGER != null) { LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); } + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + PLUGIN_SO11Y.duration(interceptorTimeCost); return ret; } @@ -124,6 +143,7 @@ private static void prepare() { if (logger != null) { LOGGER = logger; + PLUGIN_SO11Y = BootstrapInterRuntimeAssist.getSO11Y(loader); INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); } } else { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/StaticMethodInterTemplate.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/StaticMethodInterTemplate.java index a4caf96528..e32b1fc554 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/StaticMethodInterTemplate.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/StaticMethodInterTemplate.java @@ -28,6 +28,7 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.StaticMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.so11y.bootstrap.BootstrapPluginSo11y; /** * --------CLASS TEMPLATE--------- @@ -40,6 +41,12 @@ * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. */ public class StaticMethodInterTemplate { + + private static final String INTERCEPTOR_TYPE = "static"; + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String PLUGIN_NAME; /** * This field is never set in the template, but has value in the runtime. */ @@ -47,6 +54,7 @@ public class StaticMethodInterTemplate { private static StaticMethodsAroundInterceptor INTERCEPTOR; private static IBootstrapLog LOGGER; + private static BootstrapPluginSo11y PLUGIN_SO11Y; /** * Intercept the target static method. @@ -64,6 +72,8 @@ public static Object intercept(@Origin Class clazz, @AllArguments Object[] al @SuperCall Callable zuper) throws Throwable { prepare(); + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInterceptResult result = new MethodInterceptResult(); try { if (INTERCEPTOR != null) { @@ -71,7 +81,9 @@ public static Object intercept(@Origin Class clazz, @AllArguments Object[] al } } catch (Throwable t) { LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -81,23 +93,31 @@ public static Object intercept(@Origin Class clazz, @AllArguments Object[] al ret = zuper.call(); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { if (INTERCEPTOR != null) { INTERCEPTOR.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t); } } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { if (INTERCEPTOR != null) { ret = INTERCEPTOR.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret); } } catch (Throwable t) { LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + PLUGIN_SO11Y.duration(interceptorTimeCost); + return ret; } @@ -113,6 +133,7 @@ private static void prepare() { if (logger != null) { LOGGER = logger; + PLUGIN_SO11Y = BootstrapInterRuntimeAssist.getSO11Y(loader); INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); } } else { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/StaticMethodInterWithOverrideArgsTemplate.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/StaticMethodInterWithOverrideArgsTemplate.java index 6dd7ac0b00..c182878148 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/StaticMethodInterWithOverrideArgsTemplate.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/StaticMethodInterWithOverrideArgsTemplate.java @@ -28,6 +28,7 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.OverrideCallable; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.StaticMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.so11y.bootstrap.BootstrapPluginSo11y; /** * --------CLASS TEMPLATE--------- @@ -40,6 +41,12 @@ * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. */ public class StaticMethodInterWithOverrideArgsTemplate { + + private static final String INTERCEPTOR_TYPE = "static"; + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String PLUGIN_NAME; /** * This field is never set in the template, but has value in the runtime. */ @@ -47,6 +54,7 @@ public class StaticMethodInterWithOverrideArgsTemplate { private static StaticMethodsAroundInterceptor INTERCEPTOR; private static IBootstrapLog LOGGER; + private static BootstrapPluginSo11y PLUGIN_SO11Y; /** * Intercept the target static method. @@ -64,6 +72,8 @@ public static Object intercept(@Origin Class clazz, @AllArguments Object[] al @Morph OverrideCallable zuper) throws Throwable { prepare(); + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInterceptResult result = new MethodInterceptResult(); try { if (INTERCEPTOR != null) { @@ -71,7 +81,9 @@ public static Object intercept(@Origin Class clazz, @AllArguments Object[] al } } catch (Throwable t) { LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -81,23 +93,31 @@ public static Object intercept(@Origin Class clazz, @AllArguments Object[] al ret = zuper.call(allArguments); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { if (INTERCEPTOR != null) { INTERCEPTOR.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t); } } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { if (INTERCEPTOR != null) { ret = INTERCEPTOR.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret); } } catch (Throwable t) { LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + PLUGIN_SO11Y.duration(interceptorTimeCost); + return ret; } @@ -113,6 +133,7 @@ private static void prepare() { if (logger != null) { LOGGER = logger; + PLUGIN_SO11Y = BootstrapInterRuntimeAssist.getSO11Y(loader); INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); } } else { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2Template.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2Template.java index 88be482f5f..92be67f5f8 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2Template.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2Template.java @@ -29,12 +29,18 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import org.apache.skywalking.apm.agent.core.so11y.bootstrap.BootstrapPluginSo11y; /** * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. */ public class InstanceMethodInterV2Template { + private static final String INTERCEPTOR_TYPE = "inst"; + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String PLUGIN_NAME; /** * This field is never set in the template, but has value in the runtime. */ @@ -42,6 +48,7 @@ public class InstanceMethodInterV2Template { private static InstanceMethodsAroundInterceptorV2 INTERCEPTOR; private static IBootstrapLog LOGGER; + private static BootstrapPluginSo11y PLUGIN_SO11Y; /** * Intercept the target instance method. @@ -61,6 +68,8 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum prepare(); + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInvocationContext context = new MethodInvocationContext(); try { if (INTERCEPTOR != null) { @@ -70,7 +79,9 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum if (LOGGER != null) { LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); } + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -80,6 +91,7 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum ret = zuper.call(); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { if (INTERCEPTOR != null) { INTERCEPTOR.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t, context); @@ -88,9 +100,12 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum if (LOGGER != null) { LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); } + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { if (INTERCEPTOR != null) { ret = INTERCEPTOR.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret, context); @@ -99,8 +114,11 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum if (LOGGER != null) { LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); } + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + PLUGIN_SO11Y.duration(interceptorTimeCost); return ret; } @@ -117,6 +135,7 @@ private static void prepare() { if (logger != null) { LOGGER = logger; + PLUGIN_SO11Y = BootstrapInterRuntimeAssist.getSO11Y(loader); INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); } } else { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2WithOverrideArgsTemplate.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2WithOverrideArgsTemplate.java index 83fc8a9711..b027f086d3 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2WithOverrideArgsTemplate.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2WithOverrideArgsTemplate.java @@ -30,11 +30,18 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.OverrideCallable; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import org.apache.skywalking.apm.agent.core.so11y.bootstrap.BootstrapPluginSo11y; /** * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. */ public class InstanceMethodInterV2WithOverrideArgsTemplate { + + private static final String INTERCEPTOR_TYPE = "inst"; + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String PLUGIN_NAME; /** * This field is never set in the template, but has value in the runtime. */ @@ -42,6 +49,7 @@ public class InstanceMethodInterV2WithOverrideArgsTemplate { private static InstanceMethodsAroundInterceptorV2 INTERCEPTOR; private static IBootstrapLog LOGGER; + private static BootstrapPluginSo11y PLUGIN_SO11Y; /** * Intercept the target instance method. @@ -61,6 +69,8 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum prepare(); + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInvocationContext context = new MethodInvocationContext(); try { if (INTERCEPTOR != null) { @@ -70,7 +80,9 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum if (LOGGER != null) { LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); } + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -80,6 +92,7 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum ret = zuper.call(allArguments); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { if (INTERCEPTOR != null) { INTERCEPTOR.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t, context); @@ -88,9 +101,12 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum if (LOGGER != null) { LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); } + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { if (INTERCEPTOR != null) { ret = INTERCEPTOR.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret, context); @@ -99,8 +115,11 @@ public static Object intercept(@This Object obj, @AllArguments Object[] allArgum if (LOGGER != null) { LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); } + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + PLUGIN_SO11Y.duration(interceptorTimeCost); return ret; } @@ -117,6 +136,7 @@ private static void prepare() { if (logger != null) { LOGGER = logger; + PLUGIN_SO11Y = BootstrapInterRuntimeAssist.getSO11Y(loader); INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); } } else { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2Template.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2Template.java index 74cfb1e26a..4541716c2f 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2Template.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2Template.java @@ -28,11 +28,18 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.StaticMethodsAroundInterceptorV2; +import org.apache.skywalking.apm.agent.core.so11y.bootstrap.BootstrapPluginSo11y; /** * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. */ public class StaticMethodInterV2Template { + + private static final String INTERCEPTOR_TYPE = "static"; + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String PLUGIN_NAME; /** * This field is never set in the template, but has value in the runtime. */ @@ -40,6 +47,7 @@ public class StaticMethodInterV2Template { private static StaticMethodsAroundInterceptorV2 INTERCEPTOR; private static IBootstrapLog LOGGER; + private static BootstrapPluginSo11y PLUGIN_SO11Y; /** * Intercept the target static method. @@ -57,6 +65,8 @@ public static Object intercept(@Origin Class clazz, @AllArguments Object[] al @SuperCall Callable zuper) throws Throwable { prepare(); + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInvocationContext context = new MethodInvocationContext(); try { if (INTERCEPTOR != null) { @@ -64,7 +74,9 @@ public static Object intercept(@Origin Class clazz, @AllArguments Object[] al } } catch (Throwable t) { LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -74,23 +86,31 @@ public static Object intercept(@Origin Class clazz, @AllArguments Object[] al ret = zuper.call(); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { if (INTERCEPTOR != null) { INTERCEPTOR.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t, context); } } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { if (INTERCEPTOR != null) { ret = INTERCEPTOR.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret, context); } } catch (Throwable t) { LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + PLUGIN_SO11Y.duration(interceptorTimeCost); + return ret; } @@ -106,6 +126,7 @@ private static void prepare() { if (logger != null) { LOGGER = logger; + PLUGIN_SO11Y = BootstrapInterRuntimeAssist.getSO11Y(loader); INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); } } else { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2WithOverrideArgsTemplate.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2WithOverrideArgsTemplate.java index 09607882d7..9297f36582 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2WithOverrideArgsTemplate.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2WithOverrideArgsTemplate.java @@ -28,11 +28,18 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.OverrideCallable; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.StaticMethodsAroundInterceptorV2; +import org.apache.skywalking.apm.agent.core.so11y.bootstrap.BootstrapPluginSo11y; /** * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. */ public class StaticMethodInterV2WithOverrideArgsTemplate { + + private static final String INTERCEPTOR_TYPE = "static"; + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String PLUGIN_NAME; /** * This field is never set in the template, but has value in the runtime. */ @@ -40,6 +47,7 @@ public class StaticMethodInterV2WithOverrideArgsTemplate { private static StaticMethodsAroundInterceptorV2 INTERCEPTOR; private static IBootstrapLog LOGGER; + private static BootstrapPluginSo11y PLUGIN_SO11Y; /** * Intercept the target static method. @@ -57,6 +65,8 @@ public static Object intercept(@Origin Class clazz, @AllArguments Object[] al @Morph OverrideCallable zuper) throws Throwable { prepare(); + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInvocationContext context = new MethodInvocationContext(); try { if (INTERCEPTOR != null) { @@ -64,7 +74,9 @@ public static Object intercept(@Origin Class clazz, @AllArguments Object[] al } } catch (Throwable t) { LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -74,23 +86,31 @@ public static Object intercept(@Origin Class clazz, @AllArguments Object[] al ret = zuper.call(allArguments); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { if (INTERCEPTOR != null) { INTERCEPTOR.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t, context); } } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { if (INTERCEPTOR != null) { ret = INTERCEPTOR.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret, context); } } catch (Throwable t) { LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + PLUGIN_SO11Y.error(PLUGIN_NAME, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + PLUGIN_SO11Y.duration(interceptorTimeCost); + return ret; } @@ -106,6 +126,7 @@ private static void prepare() { if (logger != null) { LOGGER = logger; + PLUGIN_SO11Y = BootstrapInterRuntimeAssist.getSO11Y(loader); INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); } } else { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/BootstrapInterRuntimeAssist.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/BootstrapInterRuntimeAssist.java index f667667a3e..9289391ccb 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/BootstrapInterRuntimeAssist.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/BootstrapInterRuntimeAssist.java @@ -22,6 +22,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import org.apache.skywalking.apm.agent.core.plugin.bootstrap.IBootstrapLog; +import org.apache.skywalking.apm.agent.core.so11y.bootstrap.BootstrapPluginSo11y; /** * This assist help all bootstrap class core interceptor. @@ -29,6 +30,8 @@ public class BootstrapInterRuntimeAssist { private static final String AGENT_CLASSLOADER_DEFAULT = "org.apache.skywalking.apm.agent.core.plugin.loader.AgentClassLoader"; private static final String DEFAULT_AGENT_CLASSLOADER_INSTANCE = "DEFAULT_LOADER"; + private static final String SO11Y_BRIDGE_CLASS = "org.apache.skywalking.apm.agent.core.so11y.bootstrap.BootstrapPluginSo11yBridge"; + private static final String SO11Y_BRIDGE_GET_SO11Y_METHOD = "getSo11y"; private static final String LOG_MANAGER_CLASS = "org.apache.skywalking.apm.agent.core.plugin.bootstrap.BootstrapPluginLogBridge"; private static final String LOG_MANAGER_GET_LOGGER_METHOD = "getLogger"; private static final PrintStream OUT = System.out; @@ -62,6 +65,17 @@ public static IBootstrapLog getLogger(ClassLoader defaultAgentClassLoader, Strin } } + public static BootstrapPluginSo11y getSO11Y(ClassLoader defaultAgentClassLoader) { + try { + Class logManagerClass = Class.forName(SO11Y_BRIDGE_CLASS, true, defaultAgentClassLoader); + Method getLogger = logManagerClass.getMethod(SO11Y_BRIDGE_GET_SO11Y_METHOD); + return (BootstrapPluginSo11y) getLogger.invoke(null); + } catch (Exception e) { + e.printStackTrace(OUT); + return null; + } + } + public static T createInterceptor(ClassLoader defaultAgentClassLoader, String className, IBootstrapLog log) { try { Class interceptor = Class.forName(className, true, defaultAgentClassLoader); diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java index e1a1fa4261..ec6628fe95 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java @@ -19,6 +19,7 @@ package org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance; import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.FieldAccessor; @@ -102,6 +103,9 @@ protected DynamicType.Builder enhanceInstance(TypeDescription typeDescription newClassBuilder = newClassBuilder.defineField( CONTEXT_ATTR_NAME, Object.class, ACC_PRIVATE | ACC_VOLATILE) .implement(EnhancedInstance.class) + .defineMethod(CONTEXT_GETTER_NAME, Object.class, Visibility.PUBLIC) + .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME)) + .defineMethod(CONTEXT_SETTER_NAME, void.class, Visibility.PUBLIC).withParameters(Object.class) .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME)); context.extendObjectCompleted(); } @@ -112,17 +116,20 @@ protected DynamicType.Builder enhanceInstance(TypeDescription typeDescription */ if (existedConstructorInterceptPoint) { for (ConstructorInterceptPoint constructorInterceptPoint : constructorInterceptPoints) { + String constructorInterceptor = constructorInterceptPoint.getConstructorInterceptor(); + if (StringUtil.isEmpty(constructorInterceptor)) { + throw new EnhanceException("no InstanceConstructorInterceptor define to enhance class " + enhanceOriginClassName); + } if (isBootstrapInstrumentation()) { newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher()) - .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration() - .to(BootstrapInstrumentBoost - .forInternalDelegateClass(constructorInterceptPoint - .getConstructorInterceptor())))); + .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration() + .to(BootstrapInstrumentBoost + .forInternalDelegateClass(constructorInterceptor)))); } else { newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher()) - .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration() - .to(new ConstructorInter(constructorInterceptPoint - .getConstructorInterceptor(), classLoader), delegateNamingResolver.resolve(constructorInterceptPoint)))); + .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration() + .to(new ConstructorInter(getPluginName(), constructorInterceptor, classLoader), + delegateNamingResolver.resolve(constructorInterceptPoint)))); } } } @@ -150,7 +157,7 @@ protected DynamicType.Builder enhanceInstance(TypeDescription typeDescription newClassBuilder = newClassBuilder.method(junction) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Morph.Binder.install(OverrideCallable.class)) - .to(new InstMethodsInterWithOverrideArgs(interceptor, classLoader), delegateNamingResolver.resolve(instanceMethodsInterceptPoint))); + .to(new InstMethodsInterWithOverrideArgs(getPluginName(), interceptor, classLoader), delegateNamingResolver.resolve(instanceMethodsInterceptPoint))); } } else { if (isBootstrapInstrumentation()) { @@ -160,7 +167,7 @@ protected DynamicType.Builder enhanceInstance(TypeDescription typeDescription } else { newClassBuilder = newClassBuilder.method(junction) .intercept(MethodDelegation.withDefaultConfiguration() - .to(new InstMethodsInter(interceptor, classLoader), delegateNamingResolver.resolve(instanceMethodsInterceptPoint))); + .to(new InstMethodsInter(getPluginName(), interceptor, classLoader), delegateNamingResolver.resolve(instanceMethodsInterceptPoint))); } } } @@ -202,7 +209,7 @@ protected DynamicType.Builder enhanceClass(TypeDescription typeDescription, D newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Morph.Binder.install(OverrideCallable.class)) - .to(new StaticMethodsInterWithOverrideArgs(interceptor), delegateNamingResolver.resolve(staticMethodsInterceptPoint))); + .to(new StaticMethodsInterWithOverrideArgs(getPluginName(), interceptor), delegateNamingResolver.resolve(staticMethodsInterceptPoint))); } } else { if (isBootstrapInstrumentation()) { @@ -212,7 +219,7 @@ protected DynamicType.Builder enhanceClass(TypeDescription typeDescription, D } else { newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) .intercept(MethodDelegation.withDefaultConfiguration() - .to(new StaticMethodsInter(interceptor), delegateNamingResolver.resolve(staticMethodsInterceptPoint))); + .to(new StaticMethodsInter(getPluginName(), interceptor), delegateNamingResolver.resolve(staticMethodsInterceptPoint))); } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/ConstructorInter.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/ConstructorInter.java index 8a057df259..3a1063da03 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/ConstructorInter.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/ConstructorInter.java @@ -25,6 +25,7 @@ import org.apache.skywalking.apm.agent.core.plugin.PluginException; import org.apache.skywalking.apm.agent.core.plugin.loader.InterceptorInstanceLoader; import org.apache.skywalking.apm.agent.core.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; /** * The actual byte-buddy's interceptor to intercept constructor methods. In this class, it provides a bridge between @@ -33,6 +34,9 @@ public class ConstructorInter { private static final ILog LOGGER = LogManager.getLogger(ConstructorInter.class); + private static final String INTERCEPTOR_TYPE = "constructor"; + + private String pluginName; /** * An {@link InstanceConstructorInterceptor} This name should only stay in {@link String}, the real {@link Class} * type will trigger classloader failure. If you want to know more, please check on books about Classloader or @@ -43,7 +47,8 @@ public class ConstructorInter { /** * @param constructorInterceptorClassName class full name. */ - public ConstructorInter(String constructorInterceptorClassName, ClassLoader classLoader) throws PluginException { + public ConstructorInter(String pluginName, String constructorInterceptorClassName, ClassLoader classLoader) throws PluginException { + this.pluginName = pluginName; try { interceptor = InterceptorInstanceLoader.load(constructorInterceptorClassName, classLoader); } catch (Throwable t) { @@ -59,13 +64,17 @@ public ConstructorInter(String constructorInterceptorClassName, ClassLoader clas */ @RuntimeType public void intercept(@This Object obj, @AllArguments Object[] allArguments) { + long interceptorTimeCost = 0L; + long startTime = System.nanoTime(); try { EnhancedInstance targetObject = (EnhancedInstance) obj; interceptor.onConstruct(targetObject, allArguments); } catch (Throwable t) { LOGGER.error("ConstructorInter failure.", t); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } - + interceptorTimeCost += System.nanoTime() - startTime; + AgentSo11y.durationOfInterceptor(interceptorTimeCost); } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/InstMethodsInter.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/InstMethodsInter.java index 81ebfb1b78..581b854de3 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/InstMethodsInter.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/InstMethodsInter.java @@ -29,6 +29,7 @@ import org.apache.skywalking.apm.agent.core.plugin.PluginException; import org.apache.skywalking.apm.agent.core.plugin.loader.InterceptorInstanceLoader; import org.apache.skywalking.apm.agent.core.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; /** * The actual byte-buddy's interceptor to intercept class instance methods. In this class, it provides a bridge between @@ -37,6 +38,9 @@ public class InstMethodsInter { private static final ILog LOGGER = LogManager.getLogger(InstMethodsInter.class); + private static final String INTERCEPTOR_TYPE = "inst"; + + private String pluginName; /** * An {@link InstanceMethodsAroundInterceptor} This name should only stay in {@link String}, the real {@link Class} * type will trigger classloader failure. If you want to know more, please check on books about Classloader or @@ -47,7 +51,8 @@ public class InstMethodsInter { /** * @param instanceMethodsAroundInterceptorClassName class full name. */ - public InstMethodsInter(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { + public InstMethodsInter(String pluginName, String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { + this.pluginName = pluginName; try { interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader); } catch (Throwable t) { @@ -71,12 +76,16 @@ public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @ @Origin Method method) throws Throwable { EnhancedInstance targetObject = (EnhancedInstance) obj; + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInterceptResult result = new MethodInterceptResult(); try { interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), result); } catch (Throwable t) { LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -86,19 +95,27 @@ public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @ ret = zuper.call(); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t); } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret); } catch (Throwable t) { LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + AgentSo11y.durationOfInterceptor(interceptorTimeCost); + return ret; } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/InstMethodsInterWithOverrideArgs.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/InstMethodsInterWithOverrideArgs.java index 1bba49c177..fabe5094e3 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/InstMethodsInterWithOverrideArgs.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/InstMethodsInterWithOverrideArgs.java @@ -28,6 +28,7 @@ import org.apache.skywalking.apm.agent.core.plugin.loader.InterceptorInstanceLoader; import org.apache.skywalking.apm.agent.core.logging.api.ILog; import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; /** * The actual byte-buddy's interceptor to intercept class instance methods. In this class, it provides a bridge between @@ -36,6 +37,9 @@ public class InstMethodsInterWithOverrideArgs { private static final ILog LOGGER = LogManager.getLogger(InstMethodsInterWithOverrideArgs.class); + private static final String INTERCEPTOR_TYPE = "inst"; + + private String pluginName; /** * An {@link InstanceMethodsAroundInterceptor} This name should only stay in {@link String}, the real {@link Class} * type will trigger classloader failure. If you want to know more, please check on books about Classloader or @@ -46,7 +50,8 @@ public class InstMethodsInterWithOverrideArgs { /** * @param instanceMethodsAroundInterceptorClassName class full name. */ - public InstMethodsInterWithOverrideArgs(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { + public InstMethodsInterWithOverrideArgs(String pluginName, String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { + this.pluginName = pluginName; try { interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader); } catch (Throwable t) { @@ -70,12 +75,16 @@ public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @ @Morph OverrideCallable zuper) throws Throwable { EnhancedInstance targetObject = (EnhancedInstance) obj; + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInterceptResult result = new MethodInterceptResult(); try { interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), result); } catch (Throwable t) { LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -85,19 +94,26 @@ public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @ ret = zuper.call(allArguments); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t); } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret); } catch (Throwable t) { LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + AgentSo11y.durationOfInterceptor(interceptorTimeCost); return ret; } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/StaticMethodsInter.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/StaticMethodsInter.java index 6507118aaf..c1f3564dfe 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/StaticMethodsInter.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/StaticMethodsInter.java @@ -27,6 +27,7 @@ import org.apache.skywalking.apm.agent.core.plugin.loader.InterceptorInstanceLoader; import org.apache.skywalking.apm.agent.core.logging.api.ILog; import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; /** * The actual byte-buddy's interceptor to intercept class static methods. In this class, it provides a bridge between @@ -35,6 +36,9 @@ public class StaticMethodsInter { private static final ILog LOGGER = LogManager.getLogger(StaticMethodsInter.class); + private static final String INTERCEPTOR_TYPE = "static"; + + private String pluginName; /** * A class full name, and instanceof {@link StaticMethodsAroundInterceptor} This name should only stay in {@link * String}, the real {@link Class} type will trigger classloader failure. If you want to know more, please check on @@ -47,7 +51,8 @@ public class StaticMethodsInter { * * @param staticMethodsAroundInterceptorClassName class full name. */ - public StaticMethodsInter(String staticMethodsAroundInterceptorClassName) { + public StaticMethodsInter(String pluginName, String staticMethodsAroundInterceptorClassName) { + this.pluginName = pluginName; this.staticMethodsAroundInterceptorClassName = staticMethodsAroundInterceptorClassName; } @@ -68,12 +73,16 @@ public Object intercept(@Origin Class clazz, @AllArguments Object[] allArgume StaticMethodsAroundInterceptor interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, clazz .getClassLoader()); + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInterceptResult result = new MethodInterceptResult(); try { interceptor.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), result); } catch (Throwable t) { LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -83,19 +92,27 @@ public Object intercept(@Origin Class clazz, @AllArguments Object[] allArgume ret = zuper.call(); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { interceptor.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t); } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { ret = interceptor.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret); } catch (Throwable t) { LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + AgentSo11y.durationOfInterceptor(interceptorTimeCost); + return ret; } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/StaticMethodsInterWithOverrideArgs.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/StaticMethodsInterWithOverrideArgs.java index 83e66e4bcd..23b136fdcf 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/StaticMethodsInterWithOverrideArgs.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/StaticMethodsInterWithOverrideArgs.java @@ -26,6 +26,7 @@ import org.apache.skywalking.apm.agent.core.plugin.loader.InterceptorInstanceLoader; import org.apache.skywalking.apm.agent.core.logging.api.ILog; import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; /** * The actual byte-buddy's interceptor to intercept class static methods. In this class, it provides a bridge between @@ -34,6 +35,9 @@ public class StaticMethodsInterWithOverrideArgs { private static final ILog LOGGER = LogManager.getLogger(StaticMethodsInterWithOverrideArgs.class); + private static final String INTERCEPTOR_TYPE = "static"; + + private String pluginName; /** * A class full name, and instanceof {@link StaticMethodsAroundInterceptor} This name should only stay in {@link * String}, the real {@link Class} type will trigger classloader failure. If you want to know more, please check on @@ -44,9 +48,11 @@ public class StaticMethodsInterWithOverrideArgs { /** * Set the name of {@link StaticMethodsInterWithOverrideArgs#staticMethodsAroundInterceptorClassName} * + * @param pluginName name of interceptor plugin * @param staticMethodsAroundInterceptorClassName class full name. */ - public StaticMethodsInterWithOverrideArgs(String staticMethodsAroundInterceptorClassName) { + public StaticMethodsInterWithOverrideArgs(String pluginName, String staticMethodsAroundInterceptorClassName) { + this.pluginName = pluginName; this.staticMethodsAroundInterceptorClassName = staticMethodsAroundInterceptorClassName; } @@ -67,12 +73,16 @@ public Object intercept(@Origin Class clazz, @AllArguments Object[] allArgume StaticMethodsAroundInterceptor interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, clazz .getClassLoader()); + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInterceptResult result = new MethodInterceptResult(); try { interceptor.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), result); } catch (Throwable t) { LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -82,19 +92,27 @@ public Object intercept(@Origin Class clazz, @AllArguments Object[] allArgume ret = zuper.call(allArguments); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { interceptor.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t); } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { ret = interceptor.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret); } catch (Throwable t) { LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + AgentSo11y.durationOfInterceptor(interceptorTimeCost); + return ret; } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/ClassEnhancePluginDefineV2.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/ClassEnhancePluginDefineV2.java index 2436c2994b..3423ac1392 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/ClassEnhancePluginDefineV2.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/ClassEnhancePluginDefineV2.java @@ -18,6 +18,7 @@ package org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2; import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.FieldAccessor; @@ -87,7 +88,7 @@ protected DynamicType.Builder enhanceClass(TypeDescription typeDescription, isStatic().and(staticMethodsInterceptV2Point.getMethodsMatcher())) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Morph.Binder.install(OverrideCallable.class)) - .to(new StaticMethodsInterV2WithOverrideArgs(interceptor), delegateNamingResolver.resolve(staticMethodsInterceptV2Point))); + .to(new StaticMethodsInterV2WithOverrideArgs(getPluginName(), interceptor), delegateNamingResolver.resolve(staticMethodsInterceptV2Point))); } } else { if (isBootstrapInstrumentation()) { @@ -99,7 +100,7 @@ protected DynamicType.Builder enhanceClass(TypeDescription typeDescription, newClassBuilder = newClassBuilder.method( isStatic().and(staticMethodsInterceptV2Point.getMethodsMatcher())) .intercept(MethodDelegation.withDefaultConfiguration() - .to(new StaticMethodsInterV2(interceptor), delegateNamingResolver.resolve(staticMethodsInterceptV2Point))); + .to(new StaticMethodsInterV2(getPluginName(), interceptor), delegateNamingResolver.resolve(staticMethodsInterceptV2Point))); } } @@ -135,6 +136,9 @@ protected DynamicType.Builder enhanceInstance(TypeDescription typeDescription newClassBuilder = newClassBuilder.defineField( CONTEXT_ATTR_NAME, Object.class, ACC_PRIVATE | ACC_VOLATILE) .implement(EnhancedInstance.class) + .defineMethod(CONTEXT_GETTER_NAME, Object.class, Visibility.PUBLIC) + .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME)) + .defineMethod(CONTEXT_SETTER_NAME, void.class, Visibility.PUBLIC).withParameters(Object.class) .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME)); context.extendObjectCompleted(); } @@ -151,7 +155,7 @@ protected DynamicType.Builder enhanceInstance(TypeDescription typeDescription } else { newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher()) .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration() - .to(new ConstructorInter(constructorInterceptPoint + .to(new ConstructorInter(getPluginName(), constructorInterceptPoint .getConstructorInterceptor(), classLoader), fieldNamingResolver.resolve(constructorInterceptPoint)))); } } @@ -179,7 +183,7 @@ protected DynamicType.Builder enhanceInstance(TypeDescription typeDescription newClassBuilder = newClassBuilder.method(junction) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Morph.Binder.install(OverrideCallable.class)) - .to(new InstMethodsInterV2WithOverrideArgs(interceptor, classLoader), fieldNamingResolver.resolve(instanceMethodsInterceptV2Point))); + .to(new InstMethodsInterV2WithOverrideArgs(getPluginName(), interceptor, classLoader), fieldNamingResolver.resolve(instanceMethodsInterceptV2Point))); } } else { if (isBootstrapInstrumentation()) { @@ -189,7 +193,7 @@ protected DynamicType.Builder enhanceInstance(TypeDescription typeDescription } else { newClassBuilder = newClassBuilder.method(junction) .intercept(MethodDelegation.withDefaultConfiguration() - .to(new InstMethodsInterV2(interceptor, classLoader), fieldNamingResolver.resolve(instanceMethodsInterceptV2Point))); + .to(new InstMethodsInterV2(getPluginName(), interceptor, classLoader), fieldNamingResolver.resolve(instanceMethodsInterceptV2Point))); } } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2.java index 8638980931..a46092ad92 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2.java @@ -29,6 +29,7 @@ import org.apache.skywalking.apm.agent.core.plugin.PluginException; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.loader.InterceptorInstanceLoader; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; /** * The actual byte-buddy's interceptor to intercept class instance methods. In this class, it provides a bridge between @@ -37,9 +38,13 @@ public class InstMethodsInterV2 { private static final ILog LOGGER = LogManager.getLogger(InstMethodsInterV2.class); + private static final String INTERCEPTOR_TYPE = "inst"; + + private String pluginName; private InstanceMethodsAroundInterceptorV2 interceptor; - public InstMethodsInterV2(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { + public InstMethodsInterV2(String pluginName, String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { + this.pluginName = pluginName; try { interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader); } catch (Throwable t) { @@ -52,12 +57,16 @@ public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @ @Origin Method method) throws Throwable { EnhancedInstance targetObject = (EnhancedInstance) obj; + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInvocationContext context = new MethodInvocationContext(); try { interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), context); } catch (Throwable t) { LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -67,19 +76,27 @@ public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @ ret = zuper.call(); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t, context); } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret, context); } catch (Throwable t) { LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + AgentSo11y.durationOfInterceptor(interceptorTimeCost); + return ret; } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2WithOverrideArgs.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2WithOverrideArgs.java index 5779a386f1..2a937c961a 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2WithOverrideArgs.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2WithOverrideArgs.java @@ -30,6 +30,7 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.OverrideCallable; import org.apache.skywalking.apm.agent.core.plugin.loader.InterceptorInstanceLoader; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; /** * The actual byte-buddy's interceptor to intercept class instance methods. In this class, it provides a bridge between @@ -38,6 +39,9 @@ public class InstMethodsInterV2WithOverrideArgs { private static final ILog LOGGER = LogManager.getLogger(InstMethodsInterV2WithOverrideArgs.class); + private static final String INTERCEPTOR_TYPE = "inst"; + + private String pluginName; /** * An {@link InstanceMethodsAroundInterceptorV2} This name should only stay in {@link String}, the real {@link Class} * type will trigger classloader failure. If you want to know more, please check on books about Classloader or @@ -48,7 +52,8 @@ public class InstMethodsInterV2WithOverrideArgs { /** * @param instanceMethodsAroundInterceptorClassName class full name. */ - public InstMethodsInterV2WithOverrideArgs(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { + public InstMethodsInterV2WithOverrideArgs(String pluginName, String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { + this.pluginName = pluginName; try { interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader); } catch (Throwable t) { @@ -72,12 +77,16 @@ public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @ @Morph OverrideCallable zuper) throws Throwable { EnhancedInstance targetObject = (EnhancedInstance) obj; + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInvocationContext context = new MethodInvocationContext(); try { interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), context); } catch (Throwable t) { LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -87,19 +96,27 @@ public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @ ret = zuper.call(allArguments); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t, context); } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret, context); } catch (Throwable t) { LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + AgentSo11y.durationOfInterceptor(interceptorTimeCost); + return ret; } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2.java index 677ffa3697..add92b4fe1 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2.java @@ -27,6 +27,7 @@ import org.apache.skywalking.apm.agent.core.logging.api.ILog; import org.apache.skywalking.apm.agent.core.logging.api.LogManager; import org.apache.skywalking.apm.agent.core.plugin.loader.InterceptorInstanceLoader; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; /** * The actual byte-buddy's interceptor to intercept class instance methods. In this class, it provides a bridge between @@ -35,6 +36,9 @@ public class StaticMethodsInterV2 { private static final ILog LOGGER = LogManager.getLogger(StaticMethodsInterV2.class); + private static final String INTERCEPTOR_TYPE = "static"; + + private String pluginName; /** * A class full name, and instanceof {@link StaticMethodsAroundInterceptorV2} This name should only stay in {@link * String}, the real {@link Class} type will trigger classloader failure. If you want to know more, please check on @@ -47,7 +51,8 @@ public class StaticMethodsInterV2 { * * @param staticMethodsAroundInterceptorClassName class full name. */ - public StaticMethodsInterV2(String staticMethodsAroundInterceptorClassName) { + public StaticMethodsInterV2(String pluginName, String staticMethodsAroundInterceptorClassName) { + this.pluginName = pluginName; this.staticMethodsAroundInterceptorClassName = staticMethodsAroundInterceptorClassName; } @@ -68,12 +73,16 @@ public Object intercept(@Origin Class clazz, @AllArguments Object[] allArgume StaticMethodsAroundInterceptorV2 interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, clazz.getClassLoader()); + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInvocationContext context = new MethodInvocationContext(); try { interceptor.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), context); } catch (Throwable t) { LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -83,19 +92,27 @@ public Object intercept(@Origin Class clazz, @AllArguments Object[] allArgume ret = zuper.call(); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { interceptor.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t, context); } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { ret = interceptor.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret, context); } catch (Throwable t) { LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + AgentSo11y.durationOfInterceptor(interceptorTimeCost); + return ret; } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2WithOverrideArgs.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2WithOverrideArgs.java index e7d326b43a..0341e67d0d 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2WithOverrideArgs.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2WithOverrideArgs.java @@ -27,6 +27,7 @@ import org.apache.skywalking.apm.agent.core.logging.api.LogManager; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.OverrideCallable; import org.apache.skywalking.apm.agent.core.plugin.loader.InterceptorInstanceLoader; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; /** * The actual byte-buddy's interceptor to intercept class instance methods. In this class, it provides a bridge between @@ -35,6 +36,9 @@ public class StaticMethodsInterV2WithOverrideArgs { private static final ILog LOGGER = LogManager.getLogger(StaticMethodsInterV2WithOverrideArgs.class); + private static final String INTERCEPTOR_TYPE = "static"; + + private String pluginName; /** * A class full name, and instanceof {@link StaticMethodsAroundInterceptorV2} This name should only stay in {@link * String}, the real {@link Class} type will trigger classloader failure. If you want to know more, please check on @@ -47,7 +51,8 @@ public class StaticMethodsInterV2WithOverrideArgs { * * @param staticMethodsAroundInterceptorClassName class full name. */ - public StaticMethodsInterV2WithOverrideArgs(String staticMethodsAroundInterceptorClassName) { + public StaticMethodsInterV2WithOverrideArgs(String pluginName, String staticMethodsAroundInterceptorClassName) { + this.pluginName = pluginName; this.staticMethodsAroundInterceptorClassName = staticMethodsAroundInterceptorClassName; } @@ -68,12 +73,16 @@ public Object intercept(@Origin Class clazz, @AllArguments Object[] allArgume StaticMethodsAroundInterceptorV2 interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, clazz.getClassLoader()); + long interceptorTimeCost = 0L; + long startTimeOfMethodBeforeInter = System.nanoTime(); MethodInvocationContext context = new MethodInvocationContext(); try { interceptor.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), context); } catch (Throwable t) { LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodBeforeInter; Object ret = null; try { @@ -83,19 +92,27 @@ public Object intercept(@Origin Class clazz, @AllArguments Object[] allArgume ret = zuper.call(allArguments); } } catch (Throwable t) { + long startTimeOfMethodHandleExceptionInter = System.nanoTime(); try { interceptor.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t, context); } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodHandleExceptionInter; throw t; } finally { + long startTimeOfMethodAfterInter = System.nanoTime(); try { ret = interceptor.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret, context); } catch (Throwable t) { LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + AgentSo11y.errorOfPlugin(pluginName, INTERCEPTOR_TYPE); } + interceptorTimeCost += System.nanoTime() - startTimeOfMethodAfterInter; } + AgentSo11y.durationOfInterceptor(interceptorTimeCost); + return ret; } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/remote/EventReportServiceClient.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/remote/EventReportServiceClient.java index 73860707fd..475405c726 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/remote/EventReportServiceClient.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/remote/EventReportServiceClient.java @@ -50,11 +50,13 @@ public class EventReportServiceClient implements BootService, GRPCChannelListene private final AtomicBoolean reported = new AtomicBoolean(); + private volatile boolean bootCompleted; + private Event.Builder startingEvent; - private EventServiceGrpc.EventServiceStub eventServiceStub; + private volatile EventServiceGrpc.EventServiceStub eventServiceStub; - private GRPCChannelStatus status; + private volatile GRPCChannelStatus status = GRPCChannelStatus.DISCONNECT; @Override public void prepare() throws Throwable { @@ -91,6 +93,7 @@ public void boot() throws Throwable { @Override public void onComplete() throws Throwable { startingEvent.setEndTime(System.currentTimeMillis()); + bootCompleted = true; reportStartingEvent(); } @@ -117,7 +120,8 @@ public void shutdown() throws Throwable { ) .setLayer(EVENT_LAYER_NAME); - final StreamObserver collector = eventServiceStub.collect(new StreamObserver() { + final EventServiceGrpc.EventServiceStub stub = eventServiceStub.withDeadlineAfter(GRPC_UPSTREAM_TIMEOUT, TimeUnit.SECONDS); + final StreamObserver collector = stub.collect(new StreamObserver() { @Override public void onNext(final Commands commands) { ServiceManager.INSTANCE.findService(CommandService.class).receiveCommand(commands); @@ -143,25 +147,27 @@ public void onCompleted() { @Override public void statusChanged(final GRPCChannelStatus status) { + if (CONNECTED.equals(status)) { + final Channel channel = ServiceManager.INSTANCE.findService(GRPCChannelManager.class).getChannel(); + eventServiceStub = EventServiceGrpc.newStub(channel); + } this.status = status; - if (!CONNECTED.equals(status)) { - return; + if (CONNECTED.equals(status)) { + reportStartingEvent(); } - - final Channel channel = ServiceManager.INSTANCE.findService(GRPCChannelManager.class).getChannel(); - eventServiceStub = EventServiceGrpc.newStub(channel); - eventServiceStub = eventServiceStub.withDeadlineAfter(GRPC_UPSTREAM_TIMEOUT, TimeUnit.SECONDS); - - reportStartingEvent(); } private void reportStartingEvent() { - if (reported.compareAndSet(false, true)) { + if (!bootCompleted || !CONNECTED.equals(status)) { + return; + } + if (!reported.compareAndSet(false, true)) { return; } - final StreamObserver collector = eventServiceStub.collect(new StreamObserver() { + final EventServiceGrpc.EventServiceStub stub = eventServiceStub.withDeadlineAfter(GRPC_UPSTREAM_TIMEOUT, TimeUnit.SECONDS); + final StreamObserver collector = stub.collect(new StreamObserver() { @Override public void onNext(final Commands commands) { ServiceManager.INSTANCE.findService(CommandService.class).receiveCommand(commands); diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/so11y/AgentSo11y.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/so11y/AgentSo11y.java new file mode 100644 index 0000000000..c8da7ee262 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/so11y/AgentSo11y.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.core.so11y; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.meter.Counter; +import org.apache.skywalking.apm.agent.core.meter.Histogram; +import org.apache.skywalking.apm.agent.core.meter.MeterFactory; + +/** + * Agent self-observability meters collect through skywalking native protocols + */ +public class AgentSo11y { + + // A map to cache meter obj(s) for plugins. The key is the plugin name. + private static final Map ERROR_COUNTER_CACHE = new ConcurrentHashMap<>(); + + // Steps of interceptor time cost histogram + private static final List TIME_COST_HISTOGRAM_STEPS = Arrays.asList( + 1000d, 10000d, 50000d, 100000d, 300000d, 500000d, + 1000000d, 5000000d, 10000000d, 20000000d, 50000000d, 100000000d + ); + + // context counter + private static Counter PROPAGATED_CONTEXT_COUNTER; + private static Counter SAMPLER_CONTEXT_COUNTER; + private static Counter FINISH_CONTEXT_COUNTER; + + // ignore context counter + private static Counter PROPAGATED_IGNORE_CONTEXT_COUNTER; + private static Counter SAMPLER_IGNORE_CONTEXT_COUNTER; + private static Counter FINISH_IGNORE_CONTEXT_COUNTER; + + // leaked context counter + private static Counter LEAKED_CONTEXT_COUNTER; + private static Counter LEAKED_IGNORE_CONTEXT_COUNTER; + + // context perf histogram + private static Histogram INTERCEPTOR_TIME_COST; + + public static void measureTracingContextCreation(boolean forceSampling, boolean ignoredTracingContext) { + if (!ServiceManager.INSTANCE.isBooted()) { + // Agent kernel services could be not-booted-yet as ServiceManager#INSTANCE#boot executed after agent + // transfer initialization. + // Skip when the services are not ready to avoid MeterService status is not initialized. + return; + } + if (forceSampling) { + if (ignoredTracingContext) { + if (PROPAGATED_IGNORE_CONTEXT_COUNTER == null) { + PROPAGATED_IGNORE_CONTEXT_COUNTER = MeterFactory + .counter("created_ignored_context_counter") + .tag("created_by", "propagated") + .build(); + } + PROPAGATED_IGNORE_CONTEXT_COUNTER.increment(1); + } else { + if (PROPAGATED_CONTEXT_COUNTER == null) { + PROPAGATED_CONTEXT_COUNTER = MeterFactory + .counter("created_tracing_context_counter") + .tag("created_by", "propagated") + .build(); + } + PROPAGATED_CONTEXT_COUNTER.increment(1); + } + } else { + if (ignoredTracingContext) { + if (SAMPLER_IGNORE_CONTEXT_COUNTER == null) { + SAMPLER_IGNORE_CONTEXT_COUNTER = MeterFactory + .counter("created_ignored_context_counter") + .tag("created_by", "sampler") + .build(); + } + SAMPLER_IGNORE_CONTEXT_COUNTER.increment(1); + } else { + if (SAMPLER_CONTEXT_COUNTER == null) { + SAMPLER_CONTEXT_COUNTER = MeterFactory + .counter("created_tracing_context_counter") + .tag("created_by", "sampler") + .build(); + } + SAMPLER_CONTEXT_COUNTER.increment(1); + } + } + } + + public static void measureTracingContextCompletion(boolean ignoredTracingContext) { + if (!ServiceManager.INSTANCE.isBooted()) { + // Agent kernel services could be not-booted-yet as ServiceManager#INSTANCE#boot executed after agent + // transfer initialization. + // Skip when the services are not ready to avoid MeterService status is not initialized. + return; + } + if (ignoredTracingContext) { + if (FINISH_IGNORE_CONTEXT_COUNTER == null) { + FINISH_IGNORE_CONTEXT_COUNTER = MeterFactory.counter("finished_ignored_context_counter").build(); + } + FINISH_IGNORE_CONTEXT_COUNTER.increment(1); + } else { + if (FINISH_CONTEXT_COUNTER == null) { + FINISH_CONTEXT_COUNTER = MeterFactory.counter("finished_tracing_context_counter").build(); + } + FINISH_CONTEXT_COUNTER.increment(1); + } + } + + public static void measureLeakedTracingContext(boolean ignoredTracingContext) { + if (!ServiceManager.INSTANCE.isBooted()) { + // Agent kernel services could be not-booted-yet as ServiceManager#INSTANCE#boot executed after agent + // transfer initialization. + // Skip when the services are not ready to avoid MeterService status is not initialized. + return; + } + if (ignoredTracingContext) { + if (LEAKED_IGNORE_CONTEXT_COUNTER == null) { + LEAKED_IGNORE_CONTEXT_COUNTER = MeterFactory + .counter("possible_leaked_context_counter") + .tag("source", "ignore") + .build(); + } + LEAKED_IGNORE_CONTEXT_COUNTER.increment(1); + } else { + if (LEAKED_CONTEXT_COUNTER == null) { + LEAKED_CONTEXT_COUNTER = MeterFactory + .counter("possible_leaked_context_counter") + .tag("source", "tracing") + .build(); + } + LEAKED_CONTEXT_COUNTER.increment(1); + } + } + + public static void durationOfInterceptor(double timeCostInNanos) { + if (!ServiceManager.INSTANCE.isBooted()) { + // Agent kernel services could be not-booted-yet as ServiceManager#INSTANCE#boot executed after agent + // transfer initialization. + // Skip when the services are not ready to avoid MeterService status is not initialized. + return; + } + if (INTERCEPTOR_TIME_COST == null) { + INTERCEPTOR_TIME_COST = MeterFactory + .histogram("tracing_context_performance") + .steps(TIME_COST_HISTOGRAM_STEPS) + .build(); + } + INTERCEPTOR_TIME_COST.addValue(timeCostInNanos); + } + + public static void errorOfPlugin(String pluginName, String interType) { + if (!ServiceManager.INSTANCE.isBooted()) { + // Agent kernel services could be not-booted-yet as ServiceManager#INSTANCE#boot executed after agent + // transfer initialization. + // Skip when the services are not ready to avoid MeterService status is not initialized. + return; + } + Counter counter = ERROR_COUNTER_CACHE.computeIfAbsent(pluginName + interType, key -> MeterFactory + .counter("interceptor_error_counter") + .tag("plugin_name", pluginName) + .tag("inter_type", interType) + .build() + ); + counter.increment(1); + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/so11y/bootstrap/BootstrapPluginSo11y.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/so11y/bootstrap/BootstrapPluginSo11y.java new file mode 100644 index 0000000000..e0032ac7d3 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/so11y/bootstrap/BootstrapPluginSo11y.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.core.so11y.bootstrap; + +public interface BootstrapPluginSo11y { + void duration(double timeCostInNanos); + + void error(String pluginName, String interType); +} diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/define/AbstractWitnessInstrumentation.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/so11y/bootstrap/BootstrapPluginSo11yBridge.java similarity index 51% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/define/AbstractWitnessInstrumentation.java rename to apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/so11y/bootstrap/BootstrapPluginSo11yBridge.java index ae0eef39f3..2c9ac37600 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/define/AbstractWitnessInstrumentation.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/so11y/bootstrap/BootstrapPluginSo11yBridge.java @@ -6,7 +6,7 @@ * (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 + * 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, @@ -14,26 +14,33 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * */ -package org.apache.skywalking.apm.plugin.jetty.v11.server.define; +package org.apache.skywalking.apm.agent.core.so11y.bootstrap; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import org.apache.skywalking.apm.agent.core.so11y.AgentSo11y; + +/** + * used by {@link BootstrapInterRuntimeAssist} + */ +@SuppressWarnings("unused") +public class BootstrapPluginSo11yBridge implements BootstrapPluginSo11y { -import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; + public static BootstrapPluginSo11y getSo11y() { + return new BootstrapPluginSo11yBridge(); + } -import java.util.Collections; -import java.util.List; + private BootstrapPluginSo11yBridge() { + } -import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + @Override + public void duration(final double timeCostInNanos) { + AgentSo11y.durationOfInterceptor(timeCostInNanos); + } -public abstract class AbstractWitnessInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { @Override - protected List witnessMethods() { - return Collections.singletonList(new WitnessMethod( - "org.eclipse.jetty.server.Handler", - named("handle").and(takesArgument(2, named("jakarta.servlet.http.HttpServletRequest"))) - )); + public void error(final String pluginName, final String interType) { + AgentSo11y.errorOfPlugin(pluginName, interType); } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/util/GsonUtil.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/util/GsonUtil.java new file mode 100644 index 0000000000..fb710e4a61 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/util/GsonUtil.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.core.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class GsonUtil { + + private static final Gson GSON = new GsonBuilder() + .disableHtmlEscaping() + .create(); + + public static String toJson(Object src) { + if (src == null) { + return null; + } + return GSON.toJson(src); + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/resources/META-INF/services/org.apache.skywalking.apm.agent.core.boot.BootService b/apm-sniffer/apm-agent-core/src/main/resources/META-INF/services/org.apache.skywalking.apm.agent.core.boot.BootService index cfda93521c..f75d28cd78 100644 --- a/apm-sniffer/apm-agent-core/src/main/resources/META-INF/services/org.apache.skywalking.apm.agent.core.boot.BootService +++ b/apm-sniffer/apm-agent-core/src/main/resources/META-INF/services/org.apache.skywalking.apm.agent.core.boot.BootService @@ -36,3 +36,6 @@ org.apache.skywalking.apm.agent.core.remote.LogReportServiceClient org.apache.skywalking.apm.agent.core.conf.dynamic.ConfigurationDiscoveryService org.apache.skywalking.apm.agent.core.remote.EventReportServiceClient org.apache.skywalking.apm.agent.core.ServiceInstanceGenerator +org.apache.skywalking.apm.agent.core.asyncprofiler.AsyncProfilerTaskExecutionService +org.apache.skywalking.apm.agent.core.asyncprofiler.AsyncProfilerTaskChannelService +org.apache.skywalking.apm.agent.core.asyncprofiler.AsyncProfilerDataSender \ No newline at end of file diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/boot/ServiceManagerOrderingTest.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/boot/ServiceManagerOrderingTest.java new file mode 100644 index 0000000000..b232c5540c --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/boot/ServiceManagerOrderingTest.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.core.boot; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.After; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class ServiceManagerOrderingTest { + + private final List prepareOrder = new ArrayList<>(); + private final List bootOrder = new ArrayList<>(); + private final List shutdownOrder = new ArrayList<>(); + + private class RecordingService implements BootService { + private final String name; + private final int priority; + + private RecordingService(String name, int priority) { + this.name = name; + this.priority = priority; + } + + @Override + public void prepare() { + prepareOrder.add(name); + } + + @Override + public void boot() { + bootOrder.add(name); + } + + @Override + public void onComplete() { + } + + @Override + public void shutdown() { + shutdownOrder.add(name); + } + + @Override + public int priority() { + return priority; + } + } + + @After + public void tearDown() throws Exception { + setBootedServices(new LinkedHashMap<>()); + } + + @Test + public void higherPriorityBootsFirstAndShutsDownLast() throws Exception { + Map services = new LinkedHashMap<>(); + services.put(Integer.class, new RecordingService("low", 0)); + services.put(Long.class, new RecordingService("high", Integer.MAX_VALUE)); + services.put(Short.class, new RecordingService("mid", 100)); + setBootedServices(services); + + invoke("prepare"); + invoke("startup"); + ServiceManager.INSTANCE.shutdown(); + + assertThat(prepareOrder, is(Arrays.asList("high", "mid", "low"))); + assertThat(bootOrder, is(Arrays.asList("high", "mid", "low"))); + assertThat(shutdownOrder, is(Arrays.asList("low", "mid", "high"))); + } + + private void setBootedServices(Map services) throws Exception { + Field field = ServiceManager.class.getDeclaredField("bootedServices"); + field.setAccessible(true); + field.set(ServiceManager.INSTANCE, services); + } + + private void invoke(String method) throws Exception { + Method m = ServiceManager.class.getDeclaredMethod(method); + m.setAccessible(true); + m.invoke(ServiceManager.INSTANCE); + } +} diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/boot/ServiceManagerTest.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/boot/ServiceManagerTest.java index 48af242a43..e177920cfe 100644 --- a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/boot/ServiceManagerTest.java +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/boot/ServiceManagerTest.java @@ -59,7 +59,7 @@ public static void afterClass() { public void testServiceDependencies() throws Exception { HashMap registryService = getFieldValue(ServiceManager.INSTANCE, "bootedServices"); - assertThat(registryService.size(), is(20)); + assertThat(registryService.size(), is(23)); assertTraceSegmentServiceClient(ServiceManager.INSTANCE.findService(TraceSegmentServiceClient.class)); assertContextManager(ServiceManager.INSTANCE.findService(ContextManager.class)); @@ -109,7 +109,7 @@ private void assertGRPCChannelManager(GRPCChannelManager service) throws Excepti assertNotNull(service); List listeners = getFieldValue(service, "listeners"); - assertEquals(listeners.size(), 10); + assertEquals(listeners.size(), 12); } private void assertSamplingService(SamplingService service) { diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/context/ContinuedContextTest.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/context/ContinuedContextTest.java new file mode 100644 index 0000000000..55bfadada2 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/context/ContinuedContextTest.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.core.context; + +import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.conf.Config; +import org.apache.skywalking.apm.agent.core.context.ids.NewDistributedTraceId; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.LocalSpan; +import org.apache.skywalking.apm.agent.core.context.trace.NoopSpan; +import org.apache.skywalking.apm.agent.core.profile.ProfileStatusContext; +import org.apache.skywalking.apm.agent.core.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.core.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.core.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.core.test.tools.TracingSegmentRunner; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.Assert; + +@RunWith(TracingSegmentRunner.class) +public class ContinuedContextTest { + + @SegmentStoragePoint + private SegmentStorage tracingData; + + @Rule + public AgentServiceRule agentServiceRule = new AgentServiceRule(); + + @BeforeClass + public static void beforeClass() { + Config.Agent.KEEP_TRACING = true; + } + + @AfterClass + public static void afterClass() { + Config.Agent.KEEP_TRACING = false; + ServiceManager.INSTANCE.shutdown(); + } + + @Test + public void testContinued() { + + NewDistributedTraceId distributedTraceId = new NewDistributedTraceId(); + ContextSnapshot snapshot = new ContextSnapshot( + "1, 2, 3", + 1, + distributedTraceId, + "/for-test-continued", + new CorrelationContext(), + new ExtensionContext(), + ProfileStatusContext.createWithNone() + ); + + AbstractSpan span = ContextManager.createLocalSpan("test-span"); + ContextManager.continued(snapshot); + + Assert.assertEquals(distributedTraceId.getId(), ContextManager.getGlobalTraceId()); + ContextManager.stopSpan(); + } + + @Test + public void testContinuedWithIgnoredSnapshot() { + + ContextSnapshot snapshot = + new ContextSnapshot(null, -1, null, null, new CorrelationContext(), new ExtensionContext(), ProfileStatusContext.createWithNone()); + + AbstractSpan span = ContextManager.createLocalSpan("test-span"); + ContextManager.continued(snapshot); + + Assert.assertTrue(span instanceof LocalSpan); + + AbstractSpan span2 = ContextManager.createLocalSpan("test-span2"); + Assert.assertTrue(span2 instanceof NoopSpan); + + ContextManager.stopSpan(); + ContextManager.stopSpan(); + } + +} diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/context/IgnoreSuffixBenchmark.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/context/IgnoreSuffixBenchmark.java new file mode 100644 index 0000000000..623d674d06 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/context/IgnoreSuffixBenchmark.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.core.context; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; + +import java.util.Arrays; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * ISSUE-12355 + */ +@State(Scope.Benchmark) +@BenchmarkMode({Mode.Throughput, Mode.SampleTime}) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +public class IgnoreSuffixBenchmark { + public static String IGNORE_SUFFIX = ".jpg,.jpeg,.js,.css,.png,.bmp,.gif,.ico,.mp3,.mp4,.html,.svg"; + private String[] ignoreSuffixArray; + private Set ignoreSuffixSet; + + private String operationName = "test.api"; + + @Setup + public void setup() { + ignoreSuffixArray = IGNORE_SUFFIX.split(","); + ignoreSuffixSet = Stream.of(ignoreSuffixArray).collect(Collectors.toSet()); + } + + @Benchmark + public boolean testArray() { + int suffixIdx = operationName.lastIndexOf("."); + return Arrays.stream(ignoreSuffixArray) + .anyMatch(a -> a.equals(operationName.substring(suffixIdx))); + } + + @Benchmark + public boolean testHashSet() { + int suffixIdx = operationName.lastIndexOf("."); + return ignoreSuffixSet.contains(operationName.substring(suffixIdx)); + } + + public static void main(String[] args) throws Exception { + Options opt = new OptionsBuilder() + .include(IgnoreSuffixBenchmark.class.getName()) + .warmupIterations(3) + .warmupTime(TimeValue.seconds(10)) + .measurementIterations(3) + .measurementTime(TimeValue.seconds(10)) + .forks(1) + .build(); + new Runner(opt).run(); + } + /** + * # JMH version: 1.33 + * # VM version: JDK 11.0.21, Java HotSpot(TM) 64-Bit Server VM, 11.0.21+9-LTS-193 + * # VM invoker: /Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home/bin/java + * # VM options: -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=60939:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 + * # Blackhole mode: full + dont-inline hint (default, use -Djmh.blackhole.autoDetect=true to auto-detect) + * # Warmup: 3 iterations, 10 s each + * # Measurement: 3 iterations, 10 s each + * # Timeout: 10 min per iteration + * # Threads: 1 thread, will synchronize iterations + * # Benchmark mode: Sampling time + * # Benchmark: org.apache.skywalking.apm.agent.core.context.IgnoreSuffixBenchmark.testHashSet + * + * Benchmark Mode Cnt Score Error Units + * IgnoreSuffixBenchmark.testArray thrpt 3 0.007 ± 0.003 ops/ns + * IgnoreSuffixBenchmark.testHashSet thrpt 3 0.084 ± 0.035 ops/ns + * IgnoreSuffixBenchmark.testArray sample 823984 183.234 ± 11.201 ns/op + * IgnoreSuffixBenchmark.testArray:testArray·p0.00 sample 41.000 ns/op + * IgnoreSuffixBenchmark.testArray:testArray·p0.50 sample 166.000 ns/op + * IgnoreSuffixBenchmark.testArray:testArray·p0.90 sample 167.000 ns/op + * IgnoreSuffixBenchmark.testArray:testArray·p0.95 sample 209.000 ns/op + * IgnoreSuffixBenchmark.testArray:testArray·p0.99 sample 375.000 ns/op + * IgnoreSuffixBenchmark.testArray:testArray·p0.999 sample 1124.630 ns/op + * IgnoreSuffixBenchmark.testArray:testArray·p0.9999 sample 29971.248 ns/op + * IgnoreSuffixBenchmark.testArray:testArray·p1.00 sample 1130496.000 ns/op + * IgnoreSuffixBenchmark.testHashSet sample 972621 27.117 ± 1.788 ns/op + * IgnoreSuffixBenchmark.testHashSet:testHashSet·p0.00 sample ≈ 0 ns/op + * IgnoreSuffixBenchmark.testHashSet:testHashSet·p0.50 sample 41.000 ns/op + * IgnoreSuffixBenchmark.testHashSet:testHashSet·p0.90 sample 42.000 ns/op + * IgnoreSuffixBenchmark.testHashSet:testHashSet·p0.95 sample 42.000 ns/op + * IgnoreSuffixBenchmark.testHashSet:testHashSet·p0.99 sample 83.000 ns/op + * IgnoreSuffixBenchmark.testHashSet:testHashSet·p0.999 sample 167.000 ns/op + * IgnoreSuffixBenchmark.testHashSet:testHashSet·p0.9999 sample 6827.950 ns/op + * IgnoreSuffixBenchmark.testHashSet:testHashSet·p1.00 sample 478208.000 ns/op + */ +} diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/logging/core/AbstractLoggerTest.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/logging/core/AbstractLoggerTest.java new file mode 100644 index 0000000000..1d56d7700b --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/logging/core/AbstractLoggerTest.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.core.logging.core; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class AbstractLoggerTest { + + @Test + public void replaceParamNormal() { + TestLogger logger = new TestLogger("AbstractLoggerTest"); + String result = logger.testReplaceParam("from {} to {}", "a", "b"); + assertEquals("from a to b", result); + } + + @Test + public void replaceParamWithReplaceMark() { + TestLogger logger = new TestLogger("AbstractLoggerTest"); + String result = logger.testReplaceParam("from {} to {}", "a={}", "b={}"); + assertEquals("from a={} to b={}", result); + } + + private static class TestLogger extends AbstractLogger { + + public TestLogger(String targetClass) { + super(targetClass); + } + + @Override + protected String format(LogLevel level, String message, Throwable e) { + return message; + } + + String testReplaceParam(String message, Object... parameters) { + return replaceParam(message, parameters); + } + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/test/tools/TracingSegmentRunner.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/test/tools/TracingSegmentRunner.java index 1b88070fda..a9fa848e21 100644 --- a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/test/tools/TracingSegmentRunner.java +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/test/tools/TracingSegmentRunner.java @@ -55,7 +55,7 @@ protected Object createTest() throws Exception { @Override protected Statement withAfters(FrameworkMethod method, Object target, final Statement statement) { - return new Statement() { + Statement st = new Statement() { @Override public void evaluate() throws Throwable { if (field != null) { @@ -89,5 +89,7 @@ public void afterFinished(IgnoredTracerContext tracerContext) { } } }; + + return super.withAfters(method, target, st); } } diff --git a/apm-sniffer/apm-agent/pom.xml b/apm-sniffer/apm-agent/pom.xml index d11eb7d06a..78a3b733ac 100644 --- a/apm-sniffer/apm-agent/pom.xml +++ b/apm-sniffer/apm-agent/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking java-agent-sniffer - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-agent @@ -92,6 +92,7 @@ org.codehaus.mojo:animal-sniffer-annotations io.perfmark:* org.slf4j:* + tools.profiler:async-profiler @@ -105,6 +106,7 @@ net.bytebuddy:byte-buddy META-INF/versions/9/module-info.class + META-INF/versions/24/** diff --git a/apm-sniffer/apm-agent/src/main/java/org/apache/skywalking/apm/agent/SkyWalkingAgent.java b/apm-sniffer/apm-agent/src/main/java/org/apache/skywalking/apm/agent/SkyWalkingAgent.java index 2dd3fe8b38..b0dc55913f 100644 --- a/apm-sniffer/apm-agent/src/main/java/org/apache/skywalking/apm/agent/SkyWalkingAgent.java +++ b/apm-sniffer/apm-agent/src/main/java/org/apache/skywalking/apm/agent/SkyWalkingAgent.java @@ -128,6 +128,7 @@ static void installClassTransformer(Instrumentation instrumentation, PluginFinde .or(nameContains(".asm.")) .or(nameContains(".reflectasm.")) .or(nameStartsWith("sun.reflect")) + .or(nameStartsWith("sun.nio.cs")) .or(allSkyWalkingAgentExcludeToolkit()) .or(ElementMatchers.isSynthetic())); diff --git a/apm-sniffer/apm-sdk-plugin/CLAUDE.md b/apm-sniffer/apm-sdk-plugin/CLAUDE.md new file mode 100644 index 0000000000..81de9bc670 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/CLAUDE.md @@ -0,0 +1,426 @@ +# CLAUDE.md - SDK Plugin Development Guide + +This guide covers developing standard SDK plugins in `apm-sniffer/apm-sdk-plugin/`. + +## Plugin Instrumentation APIs (v1 vs v2) + +The agent provides two instrumentation APIs. **V2 is recommended** for all new plugins; v1 is legacy and should only be used for maintaining existing plugins. + +### V2 API (Recommended) + +V2 provides a `MethodInvocationContext` that is shared across all interception phases (`beforeMethod`, `afterMethod`, `handleMethodException`), allowing you to pass data (e.g., spans) between phases. + +**Instrumentation class (extends `ClassEnhancePluginDefineV2`):** +```java +public class XxxInstrumentation extends ClassInstanceMethodsEnhancePluginDefineV2 { + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName("target.class.Name"); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { ... }; + } + + @Override + public InstanceMethodsInterceptV2Point[] getInstanceMethodsInterceptV2Points() { + return new InstanceMethodsInterceptV2Point[] { + new InstanceMethodsInterceptV2Point() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("targetMethod"); + } + + @Override + public String getMethodsInterceptorV2() { + return "org.apache.skywalking.apm.plugin.xxx.XxxInterceptor"; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} +``` + +**Interceptor class (implements `InstanceMethodsAroundInterceptorV2`):** +```java +public class XxxInterceptor implements InstanceMethodsAroundInterceptorV2 { + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, MethodInvocationContext context) { + AbstractSpan span = ContextManager.createLocalSpan("operationName"); + context.setContext(span); // Pass to afterMethod/handleMethodException + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Object ret, MethodInvocationContext context) { + AbstractSpan span = (AbstractSpan) context.getContext(); + span.asyncFinish(); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t, MethodInvocationContext context) { + AbstractSpan span = (AbstractSpan) context.getContext(); + span.log(t); + } +} +``` + +**Key V2 classes:** +- `ClassEnhancePluginDefineV2` - Base class for plugins with both instance and static methods +- `ClassInstanceMethodsEnhancePluginDefineV2` - For instance methods only +- `ClassStaticMethodsEnhancePluginDefineV2` - For static methods only +- `InstanceMethodsAroundInterceptorV2` - Interceptor interface with `MethodInvocationContext` +- `StaticMethodsAroundInterceptorV2` - Static method interceptor with context + +### V1 API (Legacy) + +V1 uses `MethodInterceptResult` only in `beforeMethod` and has no shared context between phases. **Only use for maintaining existing legacy plugins.** + +**Key V1 classes (legacy):** +- `ClassEnhancePluginDefine` +- `ClassInstanceMethodsEnhancePluginDefine` +- `ClassStaticMethodsEnhancePluginDefine` +- `InstanceMethodsAroundInterceptor` +- `StaticMethodsAroundInterceptor` + +## Plugin Development Rules + +### Class Matching Restrictions + +**CRITICAL: Never use `.class` references in instrumentation definitions:** +```java +// WRONG - will break the agent if ThirdPartyClass doesn't exist +takesArguments(ThirdPartyClass.class) +byName(ThirdPartyClass.class.getName()) + +// CORRECT - use string literals +takesArguments("com.example.ThirdPartyClass") +byName("com.example.ThirdPartyClass") +``` + +**ClassMatch options:** +- `byName(String)`: Match by full class name (package + class name) - **preferred** +- `byClassAnnotationMatch`: Match classes with specific annotations (does NOT support inherited annotations) +- `byMethodAnnotationMatch`: Match classes with methods having specific annotations +- `byHierarchyMatch`: Match by parent class/interface - **avoid unless necessary** (performance impact) + +### Witness Classes/Methods + +Use witness classes/methods to activate plugins only for specific library versions: +```java +@Override +protected String[] witnessClasses() { + return new String[] { "com.example.VersionSpecificClass" }; +} + +@Override +protected List witnessMethods() { + return Collections.singletonList( + new WitnessMethod("com.example.SomeClass", ElementMatchers.named("specificMethod")) + ); +} +``` + +### Plugin Configuration + +Use `@PluginConfig` annotation for custom plugin settings: +```java +public class MyPluginConfig { + public static class Plugin { + @PluginConfig(root = MyPluginConfig.class) + public static class MyPlugin { + public static boolean SOME_SETTING = false; + } + } +} +``` +Config key becomes: `plugin.myplugin.some_setting` + +### Accessing Package-Private Classes + +When a plugin needs to call methods on a **package-private** class in the target library (e.g., `MongoClusterImpl` which is `final class` without `public`), you cannot import or cast to it from the plugin package. + +**Same-package helper classes do NOT work** because the agent and application use different classloaders. Even though the package names match, Java considers them different runtime packages, so package-private access is denied (`IllegalAccessError`). + +**Solution: use `setAccessible` reflection** to call public methods on package-private classes: +```java +try { + java.lang.reflect.Method method = objInst.getClass().getMethod("publicMethodName"); + method.setAccessible(true); // Required for package-private class + Object result = method.invoke(objInst); +} catch (Exception e) { + LOGGER.warn("Failed to access method", e); +} +``` + +**When to use:** Only when the target class is package-private and you need to call its public methods. Prefer normal casting when the class is public. + +### Dependency Management + +**Plugin dependencies must use `provided` scope:** +```xml + + com.example + target-library + ${version} + provided + +``` + +**Agent core dependency policy:** +- New dependencies in agent core are treated with extreme caution +- Prefer using existing imported libraries already in the project +- Prefer JDK standard libraries over third-party libraries +- Plugins should rely on the target application's libraries (provided scope), not bundle them + +## Tracing Concepts + +### Span Types +- **EntrySpan**: Service provider/endpoint (HTTP server, MQ consumer) +- **LocalSpan**: Internal method (no remote calls) +- **ExitSpan**: Client call (HTTP client, DB access, MQ producer) + +### SpanLayer (required for EntrySpan/ExitSpan) +- `DB`: Database access +- `RPC_FRAMEWORK`: RPC calls (not ordinary HTTP) +- `HTTP`: HTTP calls +- `MQ`: Message queue +- `UNKNOWN`: Default + +### Context Propagation +- **ContextCarrier**: Cross-process propagation (serialize to headers/attachments) +- **ContextSnapshot**: Cross-thread propagation (in-memory, no serialization) + +### Required Span Attributes +For EntrySpan and ExitSpan, always set: +```java +span.setComponent(ComponentsDefine.YOUR_COMPONENT); +span.setLayer(SpanLayer.HTTP); // or DB, MQ, RPC_FRAMEWORK +``` + +### Special Tags for OAP Analysis +| Tag | Purpose | +|-----|---------| +| `http.status_code` | HTTP response code (integer) | +| `db.type` | Database type (e.g., "sql", "redis") | +| `db.statement` | SQL/query statement (enables slow query analysis) | +| `cache.type`, `cache.op`, `cache.cmd`, `cache.key` | Cache metrics | +| `mq.queue`, `mq.topic` | MQ metrics | + +## Meter Plugin APIs + +For collecting numeric metrics (alternative to tracing): +```java +// Counter +Counter counter = MeterFactory.counter("metric_name") + .tag("key", "value") + .mode(Counter.Mode.INCREMENT) + .build(); +counter.increment(1d); + +// Gauge +Gauge gauge = MeterFactory.gauge("metric_name", () -> getValue()) + .tag("key", "value") + .build(); + +// Histogram +Histogram histogram = MeterFactory.histogram("metric_name") + .steps(Arrays.asList(1, 5, 10)) + .build(); +histogram.addValue(3); +``` + +## Adding a New SDK Plugin + +1. Create directory: `apm-sniffer/apm-sdk-plugin/{framework}-{version}-plugin/` +2. Implement instrumentation class using **V2 API** (extend `ClassInstanceMethodsEnhancePluginDefineV2`) +3. Implement interceptor class using **V2 API** (implement `InstanceMethodsAroundInterceptorV2`) +4. Register plugin in `skywalking-plugin.def` file +5. Add test scenario in `test/plugin/scenarios/` + +## Plugin Test Framework + +The plugin test framework verifies plugin functionality using Docker containers with real services and a mock OAP backend. + +> See `test/plugin/CLAUDE.md` for the testing philosophy: a plugin's scenario (with one-version-per-minor coverage) is the **key** test — it proves the interceptor's cast/fetch-data logic and version compatibility by running the real framework. Do **not** over-engineer with many mock-based unit tests for a plugin. + +### Environment Requirements +- MacOS/Linux +- JDK 8+ +- Docker & Docker Compose + +### Test Case Structure + +**JVM-container (preferred):** +``` +{scenario}-scenario/ +├── bin/ +│ └── startup.sh # JVM startup script (required) +├── config/ +│ └── expectedData.yaml # Expected trace/meter/log data +├── src/main/java/... # Test application code +├── pom.xml +├── configuration.yml # Test case configuration +└── support-version.list # Supported versions (one per line) +``` + +**Tomcat-container:** +``` +{scenario}-scenario/ +├── config/ +│ └── expectedData.yaml +├── src/main/ +│ ├── java/... +│ └── webapp/WEB-INF/web.xml +├── pom.xml +├── configuration.yml +└── support-version.list +``` + +### Key Configuration Files + +**configuration.yml:** +```yaml +type: jvm # or tomcat +entryService: http://localhost:8080/case # Entry endpoint (GET) +healthCheck: http://localhost:8080/health # Health check endpoint (HEAD) +startScript: ./bin/startup.sh # JVM-container only +runningMode: default # default|with_optional|with_bootstrap +withPlugins: apm-spring-annotation-plugin-*.jar # For optional/bootstrap modes +environment: + - KEY=value +dependencies: # External services (docker-compose style) + mysql: + image: mysql:8.0 + hostname: mysql + environment: + - MYSQL_ROOT_PASSWORD=root +``` + +**support-version.list:** +``` +# One version per line, use # for comments +# Only include ONE version per minor version (not all patch versions) +4.3.6 +4.4.1 +4.5.0 + +# Optional: extra Maven properties per version (comma-separated key=value) +# Useful when different versions need different dependency versions +2.7.14,spring.boot.version=2.5.15 +``` + +**expectedData.yaml:** + +Trace and meter expectations are typically in separate scenarios. + +*For tracing plugins:* +```yaml +segmentItems: + - serviceName: your-scenario + segmentSize: ge 1 # Operators: eq, ge, gt, nq + segments: + - segmentId: not null + spans: + - operationName: /your/endpoint + parentSpanId: -1 # -1 for root span + spanId: 0 + spanLayer: Http # Http, DB, RPC_FRAMEWORK, MQ, CACHE, Unknown + spanType: Entry # Entry, Exit, Local + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + peer: '' # Empty string for Entry/Local, required for Exit + skipAnalysis: false + tags: + - {key: url, value: not null} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + logs: [] + refs: [] # SegmentRefs for cross-process/cross-thread +``` + +*For meter plugins:* +```yaml +meterItems: + - serviceName: your-scenario + meterSize: ge 1 + meters: + - meterId: + name: test_counter + tags: + - {name: key1, value: value1} # Note: uses 'name' not 'key' + singleValue: gt 0 # For counter/gauge + - meterId: + name: test_histogram + tags: + - {name: key1, value: value1} + histogramBuckets: # For histogram + - 0.0 + - 1.0 + - 5.0 + - 10.0 +``` + +**startup.sh (JVM-container):** +```bash +#!/bin/bash +home="$(cd "$(dirname $0)"; pwd)" +# ${agent_opts} is REQUIRED - contains agent parameters +java -jar ${agent_opts} ${home}/../libs/your-scenario.jar & +``` + +### Running Plugin Tests Locally + +```bash +# Run a specific scenario +bash ./test/plugin/run.sh -f {scenario_name} + +# IMPORTANT: Rebuild agent if apm-sniffer code changed +./mvnw clean package -DskipTests -pl apm-sniffer + +# Use generator to create new test case +bash ./test/plugin/generator.sh +``` + +### Adding Tests to CI + +Add scenario to the appropriate `.github/workflows/` file: +- Use `python3 tools/select-group.py` to find the file with fewest cases +- **JDK 8 tests**: `plugins-test..yaml` +- **JDK 17 tests**: `plugins-jdk17-test..yaml` +- **JDK 21 tests**: `plugins-jdk21-test..yaml` +- **JDK 25 tests**: `plugins-jdk25-test..yaml` + +```yaml +matrix: + case: + - your-scenario-scenario +``` + +### Test Code Package Naming +- Test code: `org.apache.skywalking.apm.testcase.*` +- Code to be instrumented: `test.org.apache.skywalking.apm.testcase.*` + +## Tips for AI Assistants + +1. **Use V2 instrumentation API**: Always use V2 classes for new plugins; V1 is legacy +2. **NEVER use `.class` references**: Always use string literals for class names +3. **Always set component and layer**: For EntrySpan and ExitSpan, always call `setComponent()` and `setLayer()` +4. **Prefer `byName` for class matching**: Avoid `byHierarchyMatch` unless necessary (performance impact) +5. **Use witness classes for version-specific plugins**: Implement `witnessClasses()` or `witnessMethods()` +6. **Follow plugin patterns**: Use existing V2 plugins as templates +7. **Plugin naming**: Follow `{framework}-{version}-plugin` convention +8. **Register plugins**: Always add plugin definition to `skywalking-plugin.def` file +9. **Java version compatibility**: Agent core must maintain Java 8 compatibility, but individual plugins may target higher JDK versions +10. **Shaded dependencies**: Core dependencies are shaded to avoid classpath conflicts diff --git a/apm-sniffer/apm-sdk-plugin/activemq-5.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/activemq-5.x-plugin/pom.xml index 8420d03312..00abc003dc 100644 --- a/apm-sniffer/apm-sdk-plugin/activemq-5.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/activemq-5.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/activemq-artemis-jakarta-client-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/activemq-artemis-jakarta-client-2.x-plugin/pom.xml index 6527e2dae4..17f0dc4442 100644 --- a/apm-sniffer/apm-sdk-plugin/activemq-artemis-jakarta-client-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/activemq-artemis-jakarta-client-2.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/aerospike-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/aerospike-plugin/pom.xml index 554cd600c8..be9a30a3a3 100644 --- a/apm-sniffer/apm-sdk-plugin/aerospike-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/aerospike-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -38,9 +38,5 @@ provided - - 8 - 8 - diff --git a/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/apm-armeria-0.84.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/apm-armeria-0.84.x-plugin/pom.xml index 9bda76fd4d..81ea378be9 100644 --- a/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/apm-armeria-0.84.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/apm-armeria-0.84.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-armeria-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/apm-armeria-0.85.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/apm-armeria-0.85.x-plugin/pom.xml index b44db06e67..d4897e1325 100644 --- a/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/apm-armeria-0.85.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/apm-armeria-0.85.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-armeria-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/apm-armeria-1.0.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/apm-armeria-1.0.x-plugin/pom.xml index 3423b90100..6a32acee05 100644 --- a/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/apm-armeria-1.0.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/apm-armeria-1.0.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-armeria-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/pom.xml b/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/pom.xml index 1ecdee4e30..f55ffcb8fc 100644 --- a/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/apm-armeria-plugins/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 pom @@ -35,7 +35,6 @@ UTF-8 - /.. \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/pom.xml index 075c51cb44..fe491b9ee2 100644 --- a/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/avro-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/avro-plugin/pom.xml index 5e6d29aac1..cf5039fc31 100644 --- a/apm-sniffer/apm-sdk-plugin/avro-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/avro-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/baidu-brpc-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/baidu-brpc-3.x-plugin/pom.xml index 35c4df05a9..6716583e00 100644 --- a/apm-sniffer/apm-sdk-plugin/baidu-brpc-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/baidu-brpc-3.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 jar diff --git a/apm-sniffer/apm-sdk-plugin/baidu-brpc-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/baidu-brpc-plugin/pom.xml index f5d447f483..7f31b50046 100644 --- a/apm-sniffer/apm-sdk-plugin/baidu-brpc-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/baidu-brpc-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 jar diff --git a/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/pom.xml new file mode 100644 index 0000000000..7b7a82628a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/pom.xml @@ -0,0 +1,49 @@ + + + + + + apm-sdk-plugin + org.apache.skywalking + 9.7.0-SNAPSHOT + + 4.0.0 + + apm-c3p0-0.9.x-plugin + jar + c3p0-0.9.x-plugin + + + 0.9.5 + + + + + com.mchange + c3p0 + ${c3p0.version} + provided + + + org.apache.skywalking + apm-jdbc-commons + ${project.version} + provided + + + diff --git a/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/PoolConstants.java b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/PoolConstants.java new file mode 100644 index 0000000000..4536cd5112 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/PoolConstants.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.c3p0; + +public class PoolConstants { + public static final String POOL_CONNECTION = "C3P0/Connection/"; + public static final String METER_NAME = "datasource"; + public static final String METER_TAG_NAME = "name"; + public static final String METER_TAG_STATUS = "status"; + + //==================== METRICS ========================// + public static final String NUM_TOTAL_CONNECTIONS = "numTotalConnections"; + public static final String NUM_BUSY_CONNECTIONS = "numBusyConnections"; + public static final String NUM_IDLE_CONNECTIONS = "numIdleConnections"; + public static final String MAX_IDLE_TIME = "maxIdleTime"; + public static final String MIN_POOL_SIZE = "minPoolSize"; + public static final String MAX_POOL_SIZE = "maxPoolSize"; + public static final String INITIAL_POOL_SIZE = "initialPoolSize"; +} diff --git a/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/PoolingConnectionCloseInterceptor.java b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/PoolingConnectionCloseInterceptor.java new file mode 100644 index 0000000000..aa3a41fa33 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/PoolingConnectionCloseInterceptor.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.c3p0; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +import java.lang.reflect.Method; + +/** + * {@link PoolingConnectionCloseInterceptor} intercepted the method of C3P0 closing connection. + */ +public class PoolingConnectionCloseInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, + Method method, + Object[] allArguments, + Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + AbstractSpan span = ContextManager.createLocalSpan(PoolConstants.POOL_CONNECTION + method.getName()); + span.setComponent(ComponentsDefine.C3P0); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, + Method method, + Object[] allArguments, + Class[] argumentsTypes, + Object ret) throws Throwable { + ContextManager.stopSpan(); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, + Method method, + Object[] allArguments, + Class[] argumentsTypes, + Throwable t) { + ContextManager.activeSpan().errorOccurred().log(t); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/PoolingConnectionGetInterceptor.java b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/PoolingConnectionGetInterceptor.java new file mode 100644 index 0000000000..aff1e69e6c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/PoolingConnectionGetInterceptor.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.c3p0; + +import java.lang.reflect.Method; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +/** + * {@link PoolingConnectionGetInterceptor} intercepted the method of C3P0 getting connection. + */ +public class PoolingConnectionGetInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, + Method method, + Object[] allArguments, + Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + AbstractSpan span = ContextManager.createLocalSpan(PoolConstants.POOL_CONNECTION + method.getName()); + span.setComponent(ComponentsDefine.C3P0); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, + Method method, + Object[] allArguments, + Class[] argumentsTypes, + Object ret) throws Throwable { + ContextManager.stopSpan(); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, + Method method, + Object[] allArguments, + Class[] argumentsTypes, + Throwable t) { + ContextManager.activeSpan().errorOccurred().log(t); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/PoolingCreationInterceptor.java b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/PoolingCreationInterceptor.java new file mode 100644 index 0000000000..6a5514e8c5 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/PoolingCreationInterceptor.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.c3p0; + +import com.mchange.v2.c3p0.C3P0Registry; +import com.mchange.v2.c3p0.ComboPooledDataSource; +import java.lang.reflect.Method; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.meter.MeterFactory; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.jdbc.connectionurl.parser.URLParser; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; + +/** + * {@link PoolingCreationInterceptor} intercepted the method of pool creation. + */ +public class PoolingCreationInterceptor implements InstanceMethodsAroundInterceptor { + private static final Set TOKEN_MAP = new HashSet<>(16); + + @Override + public void beforeMethod(final EnhancedInstance enhancedInstance, + final Method method, + final Object[] objects, + final Class[] classes, + final MethodInterceptResult methodInterceptResult) throws Throwable { + + } + + @Override + public Object afterMethod(final EnhancedInstance enhancedInstance, + final Method method, + final Object[] objects, + final Class[] classes, + final Object ret) throws Throwable { + C3P0Registry.getPooledDataSources().forEach(obj -> { + ComboPooledDataSource pooledDataSource = (ComboPooledDataSource) obj; + if (!TOKEN_MAP.contains(pooledDataSource.getIdentityToken())) { + ConnectionInfo connectionInfo = URLParser.parser(pooledDataSource.getJdbcUrl()); + String tagValue = connectionInfo.getDatabaseName() + "_" + connectionInfo.getDatabasePeer(); + Map>> metricMap = getMetrics(); + metricMap.forEach( + (key, value) -> MeterFactory.gauge(PoolConstants.METER_NAME, value.apply(pooledDataSource)) + .tag(PoolConstants.METER_TAG_NAME, tagValue) + .tag(PoolConstants.METER_TAG_STATUS, key) + .build()); + TOKEN_MAP.add(pooledDataSource.getIdentityToken()); + } + }); + return ret; + } + + @Override + public void handleMethodException(final EnhancedInstance enhancedInstance, + final Method method, + final Object[] objects, + final Class[] classes, + final Throwable t) { + ContextManager.activeSpan().errorOccurred().log(t); + } + + private Map>> getMetrics() { + Map>> metricMap = new HashMap<>(); + metricMap.put(PoolConstants.NUM_TOTAL_CONNECTIONS, (ComboPooledDataSource pooledDataSource) -> () -> { + double numConnections = 0; + try { + numConnections = pooledDataSource.getNumConnections(); + } catch (SQLException e) { + ContextManager.activeSpan().errorOccurred().log(e); + } + return numConnections; + }); + metricMap.put(PoolConstants.NUM_BUSY_CONNECTIONS, (ComboPooledDataSource pooledDataSource) -> () -> { + double numBusyConnections = 0; + try { + numBusyConnections = pooledDataSource.getNumBusyConnections(); + } catch (SQLException e) { + ContextManager.activeSpan().errorOccurred().log(e); + } + return numBusyConnections; + }); + metricMap.put(PoolConstants.NUM_IDLE_CONNECTIONS, (ComboPooledDataSource pooledDataSource) -> () -> { + double numIdleConnections = 0; + try { + numIdleConnections = pooledDataSource.getNumIdleConnections(); + } catch (SQLException e) { + ContextManager.activeSpan().errorOccurred().log(e); + } + return numIdleConnections; + }); + metricMap.put( + PoolConstants.MAX_IDLE_TIME, + (ComboPooledDataSource pooledDataSource) -> () -> (double) pooledDataSource.getMaxIdleTime() + ); + metricMap.put( + PoolConstants.MIN_POOL_SIZE, + (ComboPooledDataSource pooledDataSource) -> () -> (double) pooledDataSource.getMinPoolSize() + ); + metricMap.put( + PoolConstants.MAX_POOL_SIZE, + (ComboPooledDataSource pooledDataSource) -> () -> (double) pooledDataSource.getMaxPoolSize() + ); + metricMap.put( + PoolConstants.INITIAL_POOL_SIZE, + (ComboPooledDataSource pooledDataSource) -> () -> (double) pooledDataSource.getInitialPoolSize() + ); + return metricMap; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/define/C3P0NewProxyConnectionInstrumentation.java b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/define/C3P0NewProxyConnectionInstrumentation.java new file mode 100644 index 0000000000..e049023a23 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/define/C3P0NewProxyConnectionInstrumentation.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.c3p0.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * C3p0 is a mature, highly concurrent JDBC Connection pooling library, with support for caching and reuse of + * PreparedStatement objects. Defined by the jdbc3 spec and the optional extensions to jdbc2. c3p0 now also fully + * supports the jdbc4. {@link C3P0NewProxyConnectionInstrumentation} defines the instance methods enhance plugin for + * connection closed + */ +public class C3P0NewProxyConnectionInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + private static final String ENHANCE_CLASS = "com.mchange.v2.c3p0.impl.NewProxyConnection"; + private static final String ENHANCE_METHOD = "close"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.c3p0.PoolingConnectionCloseInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(ENHANCE_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/define/C3P0PoolManagerInstrumentation.java b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/define/C3P0PoolManagerInstrumentation.java new file mode 100644 index 0000000000..57510c2362 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/define/C3P0PoolManagerInstrumentation.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.c3p0.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * C3p0 is a mature, highly concurrent JDBC Connection pooling library, with support for caching and reuse of + * PreparedStatement objects. Defined by the jdbc3 spec and the optional extensions to jdbc2. c3p0 now also fully + * supports the jdbc4. { @link C3P0PoolManagerInstrumentation} defines the instance methods enhance plugin for pool + * creation. + */ +public class C3P0PoolManagerInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + private static final String ENHANCE_CLASS = "com.mchange.v2.c3p0.impl.C3P0PooledConnectionPoolManager"; + private static final String ENHANCE_METHOD = "createPooledConnectionPool"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.c3p0.PoolingCreationInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(ENHANCE_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/define/C3P0PooledDataSourceInstrumentation.java b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/define/C3P0PooledDataSourceInstrumentation.java new file mode 100644 index 0000000000..e2ae84731b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/c3p0/define/C3P0PooledDataSourceInstrumentation.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.c3p0.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * C3p0 is a mature, highly concurrent JDBC Connection pooling library, with support for caching and reuse of + * PreparedStatement objects. Defined by the jdbc3 spec and the optional extensions to jdbc2. c3p0 now also fully + * supports the jdbc4. { @link C3P0PooledDataSourceInstrumentation} defines the instance methods enhance plugin for + * connection init + */ +public class C3P0PooledDataSourceInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + private static final String ENHANCE_CLASS = "com.mchange.v2.c3p0.ComboPooledDataSource"; + private static final String ENHANCE_METHOD = "getConnection"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.c3p0.PoolingConnectionGetInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(ENHANCE_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..bbb99e5908 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/c3p0-0.9.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +c3p0-0.9.x=org.apache.skywalking.apm.plugin.c3p0.define.C3P0NewProxyConnectionInstrumentation +c3p0-0.9.x=org.apache.skywalking.apm.plugin.c3p0.define.C3P0PoolManagerInstrumentation +c3p0-0.9.x=org.apache.skywalking.apm.plugin.c3p0.define.C3P0PooledDataSourceInstrumentation \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/canal-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/canal-1.x-plugin/pom.xml index b7416bc262..fd4b295a51 100644 --- a/apm-sniffer/apm-sdk-plugin/canal-1.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/canal-1.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/cassandra-java-driver-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/cassandra-java-driver-3.x-plugin/pom.xml index be6fbca7ec..ecc5d3535d 100644 --- a/apm-sniffer/apm-sdk-plugin/cassandra-java-driver-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/cassandra-java-driver-3.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.1-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.1-plugin/pom.xml index b71127cd6d..c80e82160b 100755 --- a/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.1-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.1-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.1-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/ClickHouseStatementTracingWrapper.java b/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.1-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/ClickHouseStatementTracingWrapper.java index 6f2e9bcaa0..2599fb3455 100644 --- a/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.1-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/ClickHouseStatementTracingWrapper.java +++ b/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.1-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/ClickHouseStatementTracingWrapper.java @@ -23,6 +23,7 @@ import org.apache.skywalking.apm.agent.core.context.tag.Tags; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.plugin.jdbc.SqlBodyUtil; import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; /** @@ -37,7 +38,7 @@ public static T of(ConnectionInfo connectionInfo, String methodName, String try { Tags.DB_TYPE.set(span, connectionInfo.getDBType()); Tags.DB_INSTANCE.set(span, connectionInfo.getDatabaseName()); - Tags.DB_STATEMENT.set(span, sql); + Tags.DB_STATEMENT.set(span, SqlBodyUtil.limitSqlBodySize(sql)); span.setComponent(connectionInfo.getComponent()); SpanLayer.asDB(span); return supplier.get(); diff --git a/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.1-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/ClickHouseStatementTracingWrapperTest.java b/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.1-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/ClickHouseStatementTracingWrapperTest.java new file mode 100644 index 0000000000..3c330478fc --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.1-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/ClickHouseStatementTracingWrapperTest.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.clickhouse; + +import java.util.List; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.context.util.TagValuePair; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.helper.SpanHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.jdbc.JDBCPluginConfig; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Verify that {@link ClickHouseStatementTracingWrapper} truncates the SQL body + * recorded on the exit span according to {@link JDBCPluginConfig.Plugin.JDBC#SQL_BODY_MAX_LENGTH}. + */ +@RunWith(TracingSegmentRunner.class) +public class ClickHouseStatementTracingWrapperTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + + private ConnectionInfo connectionInfo; + + private int originalLimit; + + @Before + public void setUp() { + connectionInfo = new ConnectionInfo( + ComponentsDefine.CLICKHOUSE_JDBC_DRIVER, "ClickHouse", "127.0.0.1", 8123, "default"); + originalLimit = JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH; + } + + @After + public void tearDown() { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = originalLimit; + } + + @Test + public void shortSqlIsRecordedAsIs() throws Exception { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = 2048; + String sql = "SELECT 1"; + + ClickHouseStatementTracingWrapper.of(connectionInfo, "execute", sql, () -> Boolean.TRUE); + + assertThat(dbStatementTagValue(), is(sql)); + } + + @Test + public void longSqlIsTruncatedToConfiguredLimit() throws Exception { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = 16; + // Longer than the limit, so the helper must truncate to first 16 chars and append "..." + String sql = "SELECT * FROM table_with_many_cols"; + + ClickHouseStatementTracingWrapper.of(connectionInfo, "execute", sql, () -> Boolean.TRUE); + + assertThat(dbStatementTagValue(), is(sql.substring(0, 16) + "...")); + } + + @Test + public void negativeLimitDisablesTruncation() throws Exception { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = -1; + StringBuilder builder = new StringBuilder("SELECT "); + for (int i = 0; i < 1000; i++) { + builder.append("a, "); + } + builder.append("a FROM t"); + String sql = builder.toString(); + + ClickHouseStatementTracingWrapper.of(connectionInfo, "execute", sql, () -> Boolean.TRUE); + + assertThat(dbStatementTagValue(), is(sql)); + } + + private String dbStatementTagValue() { + List traceSegments = segmentStorage.getTraceSegments(); + assertThat(traceSegments.size(), is(1)); + List spans = SegmentHelper.getSpans(traceSegments.get(0)); + assertThat(spans.size(), is(1)); + List tags = SpanHelper.getTags(spans.get(0)); + // tag order: db.type, db.instance, db.statement + return (String) tags.get(2).getValue(); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.2.x-plugin/pom.xml index 6e7edbe2ef..e50763327d 100755 --- a/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.2.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/v32/ClickHousePrepareStatementTracing.java b/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/v32/ClickHousePrepareStatementTracing.java index 292dd413a7..5c94003cf2 100644 --- a/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/v32/ClickHousePrepareStatementTracing.java +++ b/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/v32/ClickHousePrepareStatementTracing.java @@ -22,6 +22,7 @@ import org.apache.skywalking.apm.agent.core.context.tag.Tags; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.plugin.jdbc.SqlBodyUtil; import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; import java.sql.SQLException; @@ -39,7 +40,7 @@ public static T of(ConnectionInfo connectionInfo, String methodName, String try { Tags.DB_TYPE.set(span, connectionInfo.getDBType()); Tags.DB_INSTANCE.set(span, connectionInfo.getDatabaseName()); - Tags.DB_STATEMENT.set(span, sql); + Tags.DB_STATEMENT.set(span, SqlBodyUtil.limitSqlBodySize(sql)); span.setComponent(connectionInfo.getComponent()); SpanLayer.asDB(span); return supplier.get(); diff --git a/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/ClickHousePrepareStatementTracingTest.java b/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/ClickHousePrepareStatementTracingTest.java new file mode 100644 index 0000000000..49d2de6ff2 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/clickhouse-0.3.2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/clickhouse/ClickHousePrepareStatementTracingTest.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.clickhouse; + +import java.util.List; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.context.util.TagValuePair; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.helper.SpanHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.jdbc.JDBCPluginConfig; +import org.apache.skywalking.apm.plugin.jdbc.clickhouse.v32.ClickHousePrepareStatementTracing; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Verify that {@link ClickHousePrepareStatementTracing} truncates the SQL body + * recorded on the exit span according to {@link JDBCPluginConfig.Plugin.JDBC#SQL_BODY_MAX_LENGTH}. + */ +@RunWith(TracingSegmentRunner.class) +public class ClickHousePrepareStatementTracingTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + + private ConnectionInfo connectionInfo; + + private int originalLimit; + + @Before + public void setUp() { + connectionInfo = new ConnectionInfo( + ComponentsDefine.CLICKHOUSE_JDBC_DRIVER, "ClickHouse", "127.0.0.1", 8123, "test"); + originalLimit = JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH; + } + + @After + public void tearDown() { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = originalLimit; + } + + @Test + public void shortSqlIsRecordedAsIs() throws Exception { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = 2048; + String sql = "SELECT 1"; + + ClickHousePrepareStatementTracing.of(connectionInfo, "execute", sql, () -> Boolean.TRUE); + + assertThat(dbStatementTagValue(), is(sql)); + } + + @Test + public void longSqlIsTruncatedToConfiguredLimit() throws Exception { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = 16; + // Longer than the configured limit, so the helper must truncate to the first 16 chars and append "..." + String sql = "SELECT * FROM table_with_many_cols"; + + ClickHousePrepareStatementTracing.of(connectionInfo, "execute", sql, () -> Boolean.TRUE); + + assertThat(dbStatementTagValue(), is(sql.substring(0, 16) + "...")); + } + + @Test + public void negativeLimitDisablesTruncation() throws Exception { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = -1; + StringBuilder builder = new StringBuilder("SELECT "); + for (int i = 0; i < 1000; i++) { + builder.append("a, "); + } + builder.append("a FROM t"); + String sql = builder.toString(); + + ClickHousePrepareStatementTracing.of(connectionInfo, "execute", sql, () -> Boolean.TRUE); + + assertThat(dbStatementTagValue(), is(sql)); + } + + private String dbStatementTagValue() { + List traceSegments = segmentStorage.getTraceSegments(); + assertThat(traceSegments.size(), is(1)); + List spans = SegmentHelper.getSpans(traceSegments.get(0)); + assertThat(spans.size(), is(1)); + List tags = SpanHelper.getTags(spans.get(0)); + // tag order: db.type, db.instance, db.statement + return (String) tags.get(2).getValue(); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/cxf-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/cxf-3.x-plugin/pom.xml index 0f056f4135..9744820eca 100644 --- a/apm-sniffer/apm-sdk-plugin/cxf-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/cxf-3.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/dbcp-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/dbcp-2.x-plugin/pom.xml index f6326650e5..2ae4ae4b3f 100644 --- a/apm-sniffer/apm-sdk-plugin/dbcp-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/dbcp-2.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/druid-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/druid-1.x-plugin/pom.xml index 10e1afb8f9..bd8f9d6e39 100644 --- a/apm-sniffer/apm-sdk-plugin/druid-1.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/druid-1.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/druid-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/druid/v1/PoolingAddDruidDataSourceInterceptor.java b/apm-sniffer/apm-sdk-plugin/druid-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/druid/v1/PoolingAddDruidDataSourceInterceptor.java index 63a85409de..03416103b2 100644 --- a/apm-sniffer/apm-sdk-plugin/druid-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/druid/v1/PoolingAddDruidDataSourceInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/druid-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/druid/v1/PoolingAddDruidDataSourceInterceptor.java @@ -58,10 +58,9 @@ public void handleMethodException(Class clazz, Method method, Object[] allArgume } private Map>> getMetrics() { - Map>> metricMap = new HashMap(); + Map>> metricMap = new HashMap<>(); metricMap.put("activeCount", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getActiveCount()); metricMap.put("poolingCount", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getPoolingCount()); - metricMap.put("idleCount", (DruidDataSourceMBean druidDataSource) -> () -> (double) (druidDataSource.getPoolingCount() - druidDataSource.getActiveCount())); metricMap.put("lockQueueLength", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getLockQueueLength()); metricMap.put("maxWaitThreadCount", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getMaxWaitThreadCount()); metricMap.put("commitCount", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getCommitCount()); diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/pom.xml b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/pom.xml index ee46e542ff..4880f503ff 100644 --- a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/pom.xml index 8ff3bb504e..4608dc3006 100644 --- a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-3.x-conflict-patch/pom.xml b/apm-sniffer/apm-sdk-plugin/dubbo-3.x-conflict-patch/pom.xml index 3fbd5240e3..f1eb7380c9 100644 --- a/apm-sniffer/apm-sdk-plugin/dubbo-3.x-conflict-patch/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/dubbo-3.x-conflict-patch/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/dubbo-3.x-plugin/pom.xml index 1728372ca7..48e35f1950 100644 --- a/apm-sniffer/apm-sdk-plugin/dubbo-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/dubbo-3.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-conflict-patch/pom.xml b/apm-sniffer/apm-sdk-plugin/dubbo-conflict-patch/pom.xml index 326b3c0ca7..70adb55678 100644 --- a/apm-sniffer/apm-sdk-plugin/dubbo-conflict-patch/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/dubbo-conflict-patch/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/dubbo-plugin/pom.xml index 1555484e02..de7bd3752b 100644 --- a/apm-sniffer/apm-sdk-plugin/dubbo-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/dubbo-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/elastic-job-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/elastic-job-2.x-plugin/pom.xml index 94bc082bf4..01e5f81dfe 100644 --- a/apm-sniffer/apm-sdk-plugin/elastic-job-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/elastic-job-2.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/elasticjob-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/elasticjob-3.x-plugin/pom.xml index d82734c045..a965673abb 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticjob-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/elasticjob-3.x-plugin/pom.xml @@ -20,7 +20,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-5.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/elasticsearch-5.x-plugin/pom.xml index 6205465134..6f048e9caf 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-5.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-5.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -32,9 +32,6 @@ UTF-8 - 1.8 - ${java.version} - ${java.version} 5.6.6 diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/pom.xml index 204b52615a..e1a226cc08 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/define/IndicesClientInstrumentation.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/define/IndicesClientInstrumentation.java index 0031efb96a..75b494b249 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/define/IndicesClientInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/define/IndicesClientInstrumentation.java @@ -68,7 +68,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -85,7 +85,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -102,7 +102,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -119,7 +119,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } } }; diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/define/RestHighLevelClientInstrumentation.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/define/RestHighLevelClientInstrumentation.java index 8185926181..68b434740a 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/define/RestHighLevelClientInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/define/RestHighLevelClientInstrumentation.java @@ -86,7 +86,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -102,7 +102,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -118,7 +118,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -134,7 +134,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -182,7 +182,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -198,7 +198,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -214,7 +214,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -230,7 +230,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } } }; diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/Constants.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/Constants.java index bf9322ed7f..6d61006e9f 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/Constants.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/Constants.java @@ -79,4 +79,7 @@ public class Constants { public static final AbstractTag ES_TOOK_MILLIS = Tags.ofKey("es.took_millis"); public static final AbstractTag ES_TOTAL_HITS = Tags.ofKey("es.total_hits"); public static final AbstractTag ES_INGEST_TOOK_MILLIS = Tags.ofKey("es.ingest_took_millis"); + + public static final String ON_RESPONSE_SUFFIX = "/onResponse"; + public static final String ON_FAILURE_SUFFIX = "/onFailure"; } diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientAnalyzeMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientAnalyzeMethodsInterceptor.java index 8ab77d4e86..6d8338e497 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientAnalyzeMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientAnalyzeMethodsInterceptor.java @@ -30,6 +30,8 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest; import java.lang.reflect.Method; @@ -58,6 +60,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr Tags.DB_STATEMENT.set(span, analyzeRequest.text()[0]); } SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.ANALYZE_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } } diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientCreateMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientCreateMethodsInterceptor.java index ae17f85d75..b1b95481ec 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientCreateMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientCreateMethodsInterceptor.java @@ -28,6 +28,8 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.indices.CreateIndexRequest; import static org.apache.skywalking.apm.plugin.elasticsearch.v6.ElasticsearchPluginConfig.Plugin.Elasticsearch.TRACE_DSL; @@ -52,6 +54,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr Tags.DB_STATEMENT.set(span, createIndexRequest.mappings().utf8ToString()); } SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.CREATE_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } } diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientDeleteMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientDeleteMethodsInterceptor.java index 454afa08b6..68c7377dfe 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientDeleteMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientDeleteMethodsInterceptor.java @@ -29,6 +29,8 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import static org.apache.skywalking.apm.plugin.elasticsearch.v6.interceptor.Constants.DB_TYPE; @@ -48,6 +50,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr Tags.DB_TYPE.set(span, DB_TYPE); Tags.DB_INSTANCE.set(span, Arrays.asList(deleteIndexRequest.indices()).toString()); SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.DELETE_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } } diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientRefreshMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientRefreshMethodsInterceptor.java index 9e3cb6fc17..cb9dfbb384 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientRefreshMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/IndicesClientRefreshMethodsInterceptor.java @@ -27,6 +27,8 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import java.lang.reflect.Method; @@ -48,6 +50,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr Tags.DB_TYPE.set(span, DB_TYPE); Tags.DB_INSTANCE.set(span, buildIndicesString(request.indices())); SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.REFRESH_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } } diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientClearScrollMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientClearScrollMethodsInterceptor.java index d1099f8ebc..2a65908af4 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientClearScrollMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientClearScrollMethodsInterceptor.java @@ -27,6 +27,8 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.ClearScrollRequest; import java.lang.reflect.Method; @@ -52,6 +54,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr } SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.CLEAR_SCROLL_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } @Override diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientDeleteByQueryMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientDeleteByQueryMethodsInterceptor.java index 654c7df7b9..aba54fe54e 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientDeleteByQueryMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientDeleteByQueryMethodsInterceptor.java @@ -27,6 +27,8 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.index.reindex.DeleteByQueryRequest; import java.lang.reflect.Method; @@ -58,6 +60,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr } SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.DELETE_BY_QUERY_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } @Override diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientGetMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientGetMethodsInterceptor.java index 171bd29802..32c9c24c73 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientGetMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientGetMethodsInterceptor.java @@ -28,6 +28,8 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.get.GetRequest; import static org.apache.skywalking.apm.plugin.elasticsearch.v6.ElasticsearchPluginConfig.Plugin.Elasticsearch.TRACE_DSL; @@ -51,6 +53,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr } SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.GET_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } @Override diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientIndexMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientIndexMethodsInterceptor.java index c2075207d6..0e7744f7c4 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientIndexMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientIndexMethodsInterceptor.java @@ -28,6 +28,8 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.index.IndexRequest; import static org.apache.skywalking.apm.plugin.elasticsearch.v6.ElasticsearchPluginConfig.Plugin.Elasticsearch.TRACE_DSL; @@ -51,6 +53,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr } SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.INDEX_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } @Override diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientSearchMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientSearchMethodsInterceptor.java index cc709d6273..fb941490ba 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientSearchMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientSearchMethodsInterceptor.java @@ -29,6 +29,8 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.SearchRequest; import static org.apache.skywalking.apm.plugin.elasticsearch.v6.ElasticsearchPluginConfig.Plugin.Elasticsearch.TRACE_DSL; @@ -52,6 +54,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr } SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.SEARCH_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } @Override diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientSearchScrollMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientSearchScrollMethodsInterceptor.java index 64d503618e..1b4be55ea6 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientSearchScrollMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientSearchScrollMethodsInterceptor.java @@ -27,6 +27,8 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.SearchScrollRequest; import java.lang.reflect.Method; @@ -51,6 +53,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr } SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.SEARCH_SCROLL_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } @Override diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientSearchTemplateMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientSearchTemplateMethodsInterceptor.java index f57a652b15..b4d945fdb2 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientSearchTemplateMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientSearchTemplateMethodsInterceptor.java @@ -27,6 +27,8 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.script.mustache.SearchTemplateRequest; import java.lang.reflect.Method; @@ -55,6 +57,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr } SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.SEARCH_TEMPLATE_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } @Override diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientUpdateMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientUpdateMethodsInterceptor.java index b3a34e1aa1..7d76894ade 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientUpdateMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/interceptor/RestHighLevelClientUpdateMethodsInterceptor.java @@ -28,6 +28,8 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.update.UpdateRequest; import static org.apache.skywalking.apm.plugin.elasticsearch.v6.ElasticsearchPluginConfig.Plugin.Elasticsearch.TRACE_DSL; @@ -51,6 +53,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr } SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.UPDATE_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } @Override diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/support/ActionListenerAdapter.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/support/ActionListenerAdapter.java new file mode 100644 index 0000000000..8bcb7d23ea --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/support/ActionListenerAdapter.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.elasticsearch.v6.support; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.elasticsearch.v6.interceptor.Constants; +import org.elasticsearch.action.ActionListener; + +import static org.apache.skywalking.apm.plugin.elasticsearch.v6.interceptor.Constants.DB_TYPE; + +public class ActionListenerAdapter implements ActionListener, EnhancedInstance { + + private final RestClientCache restClientCache; + + public ActionListenerAdapter(RestClientCache restClientCache) { + this.restClientCache = restClientCache; + } + + @Override + public void onResponse(final T o) { + try { + if (restClientCache.getContextSnapshot() != null) { + continueContext(Constants.ON_RESPONSE_SUFFIX); + } + if (restClientCache.getActionListener() != null) { + restClientCache.getActionListener().onResponse(o); + } + } catch (Throwable t) { + ContextManager.activeSpan().log(t); + throw t; + } finally { + restClientCache.setContextSnapshot(null); + ContextManager.stopSpan(); + } + } + + @Override + public void onFailure(final Exception e) { + try { + if (restClientCache.getContextSnapshot() != null) { + continueContext(Constants.ON_FAILURE_SUFFIX); + } + if (restClientCache.getActionListener() != null) { + restClientCache.getActionListener().onFailure(e); + } + } catch (Throwable t) { + ContextManager.activeSpan().log(t); + throw t; + } finally { + ContextManager.stopSpan(); + } + } + + @Override + public Object getSkyWalkingDynamicField() { + return restClientCache; + } + + @Override + public void setSkyWalkingDynamicField(final Object enhanceInfo) { + + } + + private void continueContext(String action) { + AbstractSpan activeSpan = ContextManager.createLocalSpan(restClientCache.getOperationName() + action); + activeSpan.setComponent(ComponentsDefine.REST_HIGH_LEVEL_CLIENT); + Tags.DB_TYPE.set(activeSpan, DB_TYPE); + SpanLayer.asDB(activeSpan); + ContextManager.continued(restClientCache.getContextSnapshot()); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/support/AdapterUtil.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/support/AdapterUtil.java new file mode 100644 index 0000000000..f275fc98c8 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/support/AdapterUtil.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.elasticsearch.v6.support; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.elasticsearch.action.ActionListener; + +public class AdapterUtil { + + public static ActionListener wrapActionListener(RestClientEnhanceInfo restClientEnhanceInfo, + String operation, + ActionListener listener) { + ContextSnapshot capture = ContextManager.capture(); + capture.getExtensionContext().setSendingTimestamp(System.currentTimeMillis()); + RestClientCache cache = new RestClientCache(); + cache.setEnhanceInfo(restClientEnhanceInfo); + cache.setContextSnapshot(capture); + cache.setOperationName(operation); + cache.setActionListener(listener); + return new ActionListenerAdapter(cache); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/support/RestClientCache.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/support/RestClientCache.java new file mode 100644 index 0000000000..2c81df0024 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v6/support/RestClientCache.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.elasticsearch.v6.support; + +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.elasticsearch.action.ActionListener; + +public class RestClientCache { + private RestClientEnhanceInfo enhanceInfo; + private ActionListener actionListener; + private ContextSnapshot contextSnapshot; + private String operationName; + + public ContextSnapshot getContextSnapshot() { + return contextSnapshot; + } + + public void setContextSnapshot(final ContextSnapshot contextSnapshot) { + this.contextSnapshot = contextSnapshot; + } + + public String getOperationName() { + return operationName; + } + + public void setOperationName(final String operationName) { + this.operationName = operationName; + } + + public ActionListener getActionListener() { + return actionListener; + } + + public void setActionListener(final ActionListener actionListener) { + this.actionListener = actionListener; + } + + public RestClientEnhanceInfo getEnhanceInfo() { + return enhanceInfo; + } + + public void setEnhanceInfo(final RestClientEnhanceInfo enhanceInfo) { + this.enhanceInfo = enhanceInfo; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/pom.xml index d8d15a0d32..89a90b55f2 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/Constants.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/Constants.java index b7408b7c94..f9f72eb21d 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/Constants.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/Constants.java @@ -48,4 +48,8 @@ public class Constants { public static final AbstractTag ES_TOOK_MILLIS = Tags.ofKey("es.took_millis"); public static final AbstractTag ES_TOTAL_HITS = Tags.ofKey("es.total_hits"); public static final AbstractTag ES_INGEST_TOOK_MILLIS = Tags.ofKey("es.ingest_took_millis"); + + public static final String ON_RESPONSE_SUFFIX = "/onResponse"; + public static final String ON_FAILURE_SUFFIX = "/onFailure"; + } diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/define/IndicesClientInstrumentation.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/define/IndicesClientInstrumentation.java index 158a3b1ce6..5ce24546ee 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/define/IndicesClientInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/define/IndicesClientInstrumentation.java @@ -62,7 +62,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -79,7 +79,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -96,7 +96,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } }, new InstanceMethodsInterceptPoint() { @@ -113,7 +113,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } } }; diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientAnalyzeMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientAnalyzeMethodsInterceptor.java index 15d3992066..3a1ff1e7f1 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientAnalyzeMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientAnalyzeMethodsInterceptor.java @@ -31,6 +31,8 @@ import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; import org.apache.skywalking.apm.plugin.elasticsearch.v7.Constants; +import org.apache.skywalking.apm.plugin.elasticsearch.v7.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.indices.AnalyzeRequest; import java.lang.reflect.Method; @@ -59,6 +61,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr Tags.DB_STATEMENT.set(span, analyzeRequest.text()[0]); } SpanLayer.asDB(span); + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.ANALYZE_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } } diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientCreateMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientCreateMethodsInterceptor.java index e4ef7379fa..56b2af778a 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientCreateMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientCreateMethodsInterceptor.java @@ -28,6 +28,8 @@ import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; import org.apache.skywalking.apm.plugin.elasticsearch.v7.Constants; +import org.apache.skywalking.apm.plugin.elasticsearch.v7.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.indices.CreateIndexRequest; import java.lang.reflect.Method; @@ -49,6 +51,11 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr Tags.DB_TYPE.set(span, DB_TYPE); Tags.DB_INSTANCE.set(span, createIndexRequest.index()); SpanLayer.asDB(span); + + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.CREATE_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } } diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientDeleteMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientDeleteMethodsInterceptor.java index 37ee7658fd..e327688d91 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientDeleteMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientDeleteMethodsInterceptor.java @@ -28,6 +28,8 @@ import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; import org.apache.skywalking.apm.plugin.elasticsearch.v7.Constants; +import org.apache.skywalking.apm.plugin.elasticsearch.v7.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import java.lang.reflect.Method; @@ -50,6 +52,11 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr Tags.DB_TYPE.set(span, DB_TYPE); Tags.DB_INSTANCE.set(span, Arrays.asList(deleteIndexRequest.indices()).toString()); SpanLayer.asDB(span); + + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.DELETE_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } } diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientRefreshMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientRefreshMethodsInterceptor.java index 8602be4272..95886ed82b 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientRefreshMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/interceptor/IndicesClientRefreshMethodsInterceptor.java @@ -28,6 +28,8 @@ import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; import org.apache.skywalking.apm.plugin.elasticsearch.v7.Constants; +import org.apache.skywalking.apm.plugin.elasticsearch.v7.support.AdapterUtil; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import java.lang.reflect.Method; @@ -49,6 +51,11 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr Tags.DB_TYPE.set(span, DB_TYPE); Tags.DB_INSTANCE.set(span, buildIndicesString(request.indices())); SpanLayer.asDB(span); + + if (allArguments.length > 2 && allArguments[2] != null && allArguments[2] instanceof ActionListener) { + allArguments[2] = AdapterUtil.wrapActionListener(restClientEnhanceInfo, Constants.REFRESH_OPERATOR_NAME, + (ActionListener) allArguments[2]); + } } } diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/support/ActionListenerAdapter.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/support/ActionListenerAdapter.java new file mode 100644 index 0000000000..f99178412b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/support/ActionListenerAdapter.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.elasticsearch.v7.support; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.elasticsearch.v7.Constants; +import org.elasticsearch.action.ActionListener; + +import static org.apache.skywalking.apm.plugin.elasticsearch.v7.Constants.DB_TYPE; + +public class ActionListenerAdapter implements ActionListener, EnhancedInstance { + + private final RestClientCache restClientCache; + + public ActionListenerAdapter(RestClientCache restClientCache) { + this.restClientCache = restClientCache; + } + + @Override + public void onResponse(final T o) { + try { + if (restClientCache.getContextSnapshot() != null) { + continueContext(Constants.ON_RESPONSE_SUFFIX); + } + if (restClientCache.getActionListener() != null) { + restClientCache.getActionListener().onResponse(o); + } + } catch (Throwable t) { + ContextManager.activeSpan().log(t); + throw t; + } finally { + restClientCache.setContextSnapshot(null); + ContextManager.stopSpan(); + } + } + + @Override + public void onFailure(final Exception e) { + try { + if (restClientCache.getContextSnapshot() != null) { + continueContext(Constants.ON_FAILURE_SUFFIX); + } + if (restClientCache.getActionListener() != null) { + restClientCache.getActionListener().onFailure(e); + } + } catch (Throwable t) { + ContextManager.activeSpan().log(t); + throw t; + } finally { + ContextManager.stopSpan(); + } + } + + @Override + public Object getSkyWalkingDynamicField() { + return restClientCache; + } + + @Override + public void setSkyWalkingDynamicField(final Object enhanceInfo) { + + } + + private void continueContext(String action) { + AbstractSpan activeSpan = ContextManager.createLocalSpan(restClientCache.getOperationName() + action); + activeSpan.setComponent(ComponentsDefine.REST_HIGH_LEVEL_CLIENT); + Tags.DB_TYPE.set(activeSpan, DB_TYPE); + SpanLayer.asDB(activeSpan); + ContextManager.continued(restClientCache.getContextSnapshot()); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/support/AdapterUtil.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/support/AdapterUtil.java new file mode 100644 index 0000000000..093b6c92ea --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/support/AdapterUtil.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.elasticsearch.v7.support; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.elasticsearch.action.ActionListener; + +public class AdapterUtil { + + public static ActionListener wrapActionListener(RestClientEnhanceInfo restClientEnhanceInfo, + String operation, + ActionListener listener) { + ContextSnapshot capture = ContextManager.capture(); + capture.getExtensionContext().setSendingTimestamp(System.currentTimeMillis()); + RestClientCache cache = new RestClientCache(); + cache.setEnhanceInfo(restClientEnhanceInfo); + cache.setContextSnapshot(capture); + cache.setOperationName(operation); + cache.setActionListener(listener); + return new ActionListenerAdapter(cache); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/support/RestClientCache.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/support/RestClientCache.java new file mode 100644 index 0000000000..18f895578a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/v7/support/RestClientCache.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.elasticsearch.v7.support; + +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.plugin.elasticsearch.common.RestClientEnhanceInfo; +import org.elasticsearch.action.ActionListener; + +public class RestClientCache { + private RestClientEnhanceInfo enhanceInfo; + private ActionListener actionListener; + private ContextSnapshot contextSnapshot; + private String operationName; + + public ContextSnapshot getContextSnapshot() { + return contextSnapshot; + } + + public void setContextSnapshot(final ContextSnapshot contextSnapshot) { + this.contextSnapshot = contextSnapshot; + } + + public String getOperationName() { + return operationName; + } + + public void setOperationName(final String operationName) { + this.operationName = operationName; + } + + public ActionListener getActionListener() { + return actionListener; + } + + public void setActionListener(final ActionListener actionListener) { + this.actionListener = actionListener; + } + + public RestClientEnhanceInfo getEnhanceInfo() { + return enhanceInfo; + } + + public void setEnhanceInfo(final RestClientEnhanceInfo enhanceInfo) { + this.enhanceInfo = enhanceInfo; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-common/pom.xml b/apm-sniffer/apm-sdk-plugin/elasticsearch-common/pom.xml index 1658b62cce..51acbee581 100644 --- a/apm-sniffer/apm-sdk-plugin/elasticsearch-common/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-common/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/pom.xml new file mode 100644 index 0000000000..0126147e82 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/pom.xml @@ -0,0 +1,48 @@ + + + + + 4.0.0 + + + apm-sdk-plugin + org.apache.skywalking + 9.7.0-SNAPSHOT + + + apm-elasticsearch-java-plugin + jar + + elasticsearch-java-plugin + + + 8.17.0 + + + + + co.elastic.clients + elasticsearch-java + ${elasticsearch-java.version} + provided + + + diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/ElasticsearchPluginConfig.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/ElasticsearchPluginConfig.java new file mode 100644 index 0000000000..c435f439a8 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/ElasticsearchPluginConfig.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.elasticsearch.java; + +import org.apache.skywalking.apm.agent.core.boot.PluginConfig; + +public class ElasticsearchPluginConfig { + public static class Plugin { + @PluginConfig(root = ElasticsearchPluginConfig.class) + public static class Elasticsearch { + /** + * If true, trace all the DSL(Domain Specific Language) in ElasticSearch access, default is false + */ + public static boolean TRACE_DSL = false; + + public static int ELASTICSEARCH_DSL_LENGTH_THRESHOLD = 1024; + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/define/ElasticsearchTransportBaseInstrumentation.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/define/ElasticsearchTransportBaseInstrumentation.java new file mode 100644 index 0000000000..6ac75f023c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/define/ElasticsearchTransportBaseInstrumentation.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.elasticsearch.java.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * Enhance {@code co.elastic.clients.transport.ElasticsearchTransportBase} + * which exists in elasticsearch-java 8.x+. The performRequest method + * moved from RestClientTransport to this base class in 8.x. + *

+ * The peer is propagated from RestClientTransport (subclass) via dynamic field. + */ +public class ElasticsearchTransportBaseInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "co.elastic.clients.transport.ElasticsearchTransportBase"; + + private static final String PERFORM_REQUEST_INTERCEPTOR = + "org.apache.skywalking.apm.plugin.elasticsearch.java.interceptor.TransportPerformRequestInterceptor"; + + @Override + protected String[] witnessClasses() { + return new String[] {ENHANCE_CLASS}; + } + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("performRequest"); + } + + @Override + public String getMethodsInterceptor() { + return PERFORM_REQUEST_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/define/TomcatInstrumentation.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/define/RestClientTransportInstrumentation.java similarity index 58% rename from apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/define/TomcatInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/define/RestClientTransportInstrumentation.java index 0ba3356a1d..e5dacab15f 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/define/TomcatInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/define/RestClientTransportInstrumentation.java @@ -16,7 +16,7 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat10x.define; +package org.apache.skywalking.apm.plugin.elasticsearch.java.define; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -26,28 +26,29 @@ import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; -public class TomcatInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { +/** + * Enhance {@code co.elastic.clients.transport.rest_client.RestClientTransport} + * for the Elasticsearch Java client (co.elastic.clients:elasticsearch-java). + *

+ * Covers both 7.x (performRequest on RestClientTransport) and 8.x+ + * (performRequest inherited from ElasticsearchTransportBase, but constructor on RestClientTransport). + */ +public class RestClientTransportInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { - /** - * Enhance class. - */ - private static final String ENHANCE_CLASS = "org.apache.catalina.core.StandardHostValve"; + private static final String ENHANCE_CLASS = "co.elastic.clients.transport.rest_client.RestClientTransport"; - /** - * The intercept class for "invoke" method in the class "org.apache.catalina.core.StandardHostValve" - */ - private static final String INVOKE_INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.tomcat10x.TomcatInvokeInterceptor"; + private static final String CONSTRUCTOR_INTERCEPTOR = + "org.apache.skywalking.apm.plugin.elasticsearch.java.interceptor.RestClientTransportConstructorInterceptor"; - /** - * The intercept class for "exception" method in the class "org.apache.catalina.core.StandardHostValve" - */ - private static final String EXCEPTION_INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.tomcat10x.TomcatExceptionInterceptor"; + private static final String PERFORM_REQUEST_INTERCEPTOR = + "org.apache.skywalking.apm.plugin.elasticsearch.java.interceptor.TransportPerformRequestInterceptor"; @Override protected String[] witnessClasses() { - return new String[]{"jakarta.servlet.http.HttpServletResponse"}; + return new String[] {ENHANCE_CLASS}; } @Override @@ -57,37 +58,33 @@ protected ClassMatch enhanceClass() { @Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { - return null; - } - - @Override - public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { - return new InstanceMethodsInterceptPoint[] { - new InstanceMethodsInterceptPoint() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { @Override - public ElementMatcher getMethodsMatcher() { - return named("invoke"); + public ElementMatcher getConstructorMatcher() { + return takesArgumentWithType(0, "org.elasticsearch.client.RestClient"); } @Override - public String getMethodsInterceptor() { - return INVOKE_INTERCEPT_CLASS; + public String getConstructorInterceptor() { + return CONSTRUCTOR_INTERCEPTOR; } + } + }; + } - @Override - public boolean isOverrideArgs() { - return false; - } - }, + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { new InstanceMethodsInterceptPoint() { @Override public ElementMatcher getMethodsMatcher() { - return named("throwable"); + return named("performRequest"); } @Override public String getMethodsInterceptor() { - return EXCEPTION_INTERCEPT_CLASS; + return PERFORM_REQUEST_INTERCEPTOR; } @Override diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/interceptor/RestClientTransportConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/interceptor/RestClientTransportConstructorInterceptor.java new file mode 100644 index 0000000000..e96932ace3 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/interceptor/RestClientTransportConstructorInterceptor.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.elasticsearch.java.interceptor; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.elasticsearch.client.Node; +import org.elasticsearch.client.RestClient; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Intercept RestClientTransport constructor to extract peer address from RestClient nodes. + */ +public class RestClientTransportConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + RestClient restClient = (RestClient) allArguments[0]; + List nodes = restClient.getNodes(); + String peers = nodes.stream() + .map(node -> node.getHost().toHostString()) + .collect(Collectors.joining(",")); + objInst.setSkyWalkingDynamicField(peers); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/interceptor/TransportPerformRequestInterceptor.java b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/interceptor/TransportPerformRequestInterceptor.java new file mode 100644 index 0000000000..ebf8ec6d41 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/java/org/apache/skywalking/apm/plugin/elasticsearch/java/interceptor/TransportPerformRequestInterceptor.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.elasticsearch.java.interceptor; + +import co.elastic.clients.transport.Endpoint; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.elasticsearch.java.ElasticsearchPluginConfig; + +import java.lang.reflect.Method; + +/** + * Intercept ElasticsearchTransport.performRequest() to create exit spans. + *

+ * Args: [0] request, [1] endpoint (Endpoint), [2] options (TransportOptions) + * The endpoint.id() provides the operation name (e.g., "search", "index", "bulk"). + */ +public class TransportPerformRequestInterceptor implements InstanceMethodsAroundInterceptor { + + private static final String DB_TYPE = "Elasticsearch"; + + @Override + @SuppressWarnings("unchecked") + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + Endpoint endpoint = (Endpoint) allArguments[1]; + String operationName = "Elasticsearch/" + endpoint.id(); + + String peers = (String) objInst.getSkyWalkingDynamicField(); + if (peers == null || peers.isEmpty()) { + peers = "Unknown"; + } + + AbstractSpan span = ContextManager.createExitSpan(operationName, peers); + span.setComponent(ComponentsDefine.REST_HIGH_LEVEL_CLIENT); + Tags.DB_TYPE.set(span, DB_TYPE); + SpanLayer.asDB(span); + + Object request = allArguments[0]; + String requestUrl = endpoint.requestUrl(request); + String index = extractIndex(requestUrl); + if (index != null) { + span.tag(Tags.ofKey("db.instance"), index); + } + if (ElasticsearchPluginConfig.Plugin.Elasticsearch.TRACE_DSL) { + String dsl = request.toString(); + if (dsl != null && !dsl.isEmpty()) { + int maxLen = ElasticsearchPluginConfig.Plugin.Elasticsearch.ELASTICSEARCH_DSL_LENGTH_THRESHOLD; + if (maxLen > 0 && dsl.length() > maxLen) { + dsl = dsl.substring(0, maxLen) + "..."; + } + Tags.DB_STATEMENT.set(span, dsl); + } + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Object ret) throws Throwable { + ContextManager.stopSpan(); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + ContextManager.activeSpan().errorOccurred(); + } + + /** + * Extract index name from request URL. + * E.g., "/test-index/_doc/1" → "test-index", "/_bulk" → null + */ + static String extractIndex(String requestUrl) { + if (requestUrl == null || requestUrl.isEmpty()) { + return null; + } + // Remove leading slash + String path = requestUrl.startsWith("/") ? requestUrl.substring(1) : requestUrl; + if (path.isEmpty()) { + return null; + } + // First segment before '/' or '_' prefix means no index + int slashIdx = path.indexOf('/'); + String firstSegment = slashIdx > 0 ? path.substring(0, slashIdx) : path; + if (firstSegment.startsWith("_")) { + return null; + } + return firstSegment; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..b13b674473 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/elasticsearch-java-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Elasticsearch Java client (co.elastic.clients:elasticsearch-java) 7.16.x-9.x +elasticsearch-java=org.apache.skywalking.apm.plugin.elasticsearch.java.define.RestClientTransportInstrumentation +elasticsearch-java=org.apache.skywalking.apm.plugin.elasticsearch.java.define.ElasticsearchTransportBaseInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/pom.xml index e641e9512f..a97bc44739 100644 --- a/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/feign/http/v9/define/PathVarV2Instrumentation.java b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/feign/http/v9/define/PathVarV2Instrumentation.java new file mode 100644 index 0000000000..434d3c2d55 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/feign/http/v9/define/PathVarV2Instrumentation.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.feign.http.v9.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +public class PathVarV2Instrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + /** + * Enhance class. + */ + private static final String ENHANCE_CLASS = "feign.RequestTemplateFactoryResolver$BuildTemplateByResolvingArgs"; + + /** + * Intercept class. + */ + private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.feign.http.v9.PathVarInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + protected String[] witnessClasses() { + return new String[] {"feign.RequestTemplateFactoryResolver"}; + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("resolve"); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/resources/skywalking-plugin.def index eda47ffa99..778a03f68f 100644 --- a/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/resources/skywalking-plugin.def @@ -15,4 +15,5 @@ # limitations under the License. feign-default-http-9.x=org.apache.skywalking.apm.plugin.feign.http.v9.define.DefaultHttpClientInstrumentation -feign-pathvar-9.x=org.apache.skywalking.apm.plugin.feign.http.v9.define.PathVarInstrumentation \ No newline at end of file +feign-pathvar-9.x=org.apache.skywalking.apm.plugin.feign.http.v9.define.PathVarInstrumentation +feign-pathvar-9.x=org.apache.skywalking.apm.plugin.feign.http.v9.define.PathVarV2Instrumentation \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/finagle-6.25.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/finagle-6.25.x-plugin/pom.xml index cc875605fa..89f906c289 100644 --- a/apm-sniffer/apm-sdk-plugin/finagle-6.25.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/finagle-6.25.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -35,8 +35,6 @@ UTF-8 6.34.0 2.11.12 - 1.8 - 1.8 diff --git a/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-12.x-15.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-12.x-15.x-plugin/pom.xml index b56a1905e3..170586098e 100644 --- a/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-12.x-15.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-12.x-15.x-plugin/pom.xml @@ -21,7 +21,7 @@ graphql-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-16plus-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-16plus-plugin/pom.xml index 7cd4ed7172..474b404f95 100644 --- a/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-16plus-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-16plus-plugin/pom.xml @@ -21,7 +21,7 @@ graphql-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-8.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-8.x-plugin/pom.xml index c9d0fa4de0..cb6d9e0e02 100644 --- a/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-8.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-8.x-plugin/pom.xml @@ -21,7 +21,7 @@ graphql-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-9.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-9.x-plugin/pom.xml index 39666ba3e1..d1e081d15c 100644 --- a/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-9.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/graphql-plugin/graphql-9.x-plugin/pom.xml @@ -21,7 +21,7 @@ graphql-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/graphql-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/graphql-plugin/pom.xml index dd0f22b3fd..74e296d1bd 100644 --- a/apm-sniffer/apm-sdk-plugin/graphql-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/graphql-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 pom @@ -37,6 +37,5 @@ UTF-8 - /.. \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/grizzly-2.3.x-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/grizzly-2.3.x-4.x-plugin/pom.xml index 5c4f29668d..191aa45db8 100644 --- a/apm-sniffer/apm-sdk-plugin/grizzly-2.3.x-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/grizzly-2.3.x-4.x-plugin/pom.xml @@ -20,7 +20,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/grizzly-2.3.x-4.x-work-threadpool-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/grizzly-2.3.x-4.x-work-threadpool-plugin/pom.xml index 958782012c..5db8506875 100644 --- a/apm-sniffer/apm-sdk-plugin/grizzly-2.3.x-4.x-work-threadpool-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/grizzly-2.3.x-4.x-work-threadpool-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-grizzly-2.x-4.x-work-threadpool-plugin diff --git a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/pom.xml index 76b32efb36..72884e2d48 100644 --- a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-grpc-1.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/AbstractServerImplBuilderInstrumentation.java b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/AbstractServerImplBuilderInstrumentation.java index f32987f4bc..bd71445d7c 100644 --- a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/AbstractServerImplBuilderInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/AbstractServerImplBuilderInstrumentation.java @@ -24,14 +24,13 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; -import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; public class AbstractServerImplBuilderInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { - public static final String ENHANCE_CLASS = "io.grpc.internal.AbstractServerImplBuilder"; public static final String ENHANCE_METHOD = "build"; public static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.grpc.v1.server.AbstractServerImplBuilderInterceptor"; @@ -64,6 +63,9 @@ public boolean isOverrideArgs() { @Override protected ClassMatch enhanceClass() { - return byName(ENHANCE_CLASS); + return MultiClassNameMatch.byMultiClassMatch( + "io.grpc.internal.AbstractServerImplBuilder", //grpc version <= 1.58.1 + "io.grpc.internal.ServerImplBuilder" //grpc version >= 1.59.0 + ); } } diff --git a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/AbstractServerImplBuilderInterceptor.java b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/AbstractServerImplBuilderInterceptor.java index c02cf566de..cf1bcf4f76 100644 --- a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/AbstractServerImplBuilderInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/AbstractServerImplBuilderInterceptor.java @@ -20,7 +20,11 @@ import io.grpc.ServerBuilder; +import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; @@ -30,14 +34,26 @@ * {@link AbstractServerImplBuilderInterceptor} add the {@link ServerInterceptor} interceptor for every ServerService. */ public class AbstractServerImplBuilderInterceptor implements InstanceMethodsAroundInterceptor { + private final static Map, Field> FIELD_CACHE = new HashMap<>(); + @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - MethodInterceptResult result) { + MethodInterceptResult result) throws Throwable { if (objInst.getSkyWalkingDynamicField() == null) { ServerBuilder builder = (ServerBuilder) objInst; - ServerInterceptor interceptor = new ServerInterceptor(); - builder.intercept(interceptor); - objInst.setSkyWalkingDynamicField(interceptor); + Field field = findField(builder.getClass()); + if (field != null) { + List interceptors = (List) field.get(builder); + boolean hasCustomInterceptor = interceptors.stream() + .anyMatch(i -> i.getClass() == ServerInterceptor.class); + + if (!hasCustomInterceptor) { + ServerInterceptor interceptor = new ServerInterceptor(); + builder.intercept(interceptor); + objInst.setSkyWalkingDynamicField(interceptor); + } + + } } } @@ -52,4 +68,31 @@ public void handleMethodException(EnhancedInstance objInst, Method method, Objec Class[] argumentsTypes, Throwable t) { } + + private static Field findField(Class clazz) { + if (FIELD_CACHE.containsKey(clazz)) { + return FIELD_CACHE.get(clazz); + } + synchronized (AbstractServerImplBuilderInterceptor.class) { + if (FIELD_CACHE.containsKey(clazz)) { + return FIELD_CACHE.get(clazz); + } + Field field = doFindField(clazz); + FIELD_CACHE.put(clazz, field); + return field; + } + } + + private static Field doFindField(Class clazz) { + while (clazz != null) { + for (Field f : clazz.getDeclaredFields()) { + if (f.getName().equals("interceptors")) { + f.setAccessible(true); + return f; + } + } + clazz = clazz.getSuperclass(); + } + return null; + } } diff --git a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/ServerInterceptor.java b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/ServerInterceptor.java index f970d813f0..b285ff169d 100644 --- a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/ServerInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/ServerInterceptor.java @@ -36,9 +36,6 @@ public class ServerInterceptor implements io.grpc.ServerInterceptor { - static final Context.Key CONTEXT_SNAPSHOT_KEY = Context.key("skywalking-grpc-context-snapshot"); - static final Context.Key ACTIVE_SPAN_KEY = Context.key("skywalking-grpc-active-span"); - @Override public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, ServerCallHandler handler) { @@ -59,15 +56,15 @@ public ServerCall.Listener interceptCall(ServerCall ContextSnapshot contextSnapshot = ContextManager.capture(); AbstractSpan asyncSpan = span.prepareForAsync(); - Context context = Context.current().withValues(CONTEXT_SNAPSHOT_KEY, contextSnapshot, ACTIVE_SPAN_KEY, asyncSpan); - ServerCall.Listener listener = Contexts.interceptCall( - context, - new TracingServerCall<>(call), + Context.current(), + new TracingServerCall<>(call, contextSnapshot, asyncSpan), headers, (serverCall, metadata) -> new TracingServerCallListener<>( handler.startCall(serverCall, metadata), - serverCall.getMethodDescriptor() + serverCall.getMethodDescriptor(), + contextSnapshot, + asyncSpan ) ); ContextManager.stopSpan(asyncSpan); diff --git a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/TracingServerCall.java b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/TracingServerCall.java index 3361c4b7db..c674b5e16b 100644 --- a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/TracingServerCall.java +++ b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/TracingServerCall.java @@ -18,38 +18,46 @@ package org.apache.skywalking.apm.plugin.grpc.v1.server; -import static org.apache.skywalking.apm.plugin.grpc.v1.Constants.RESPONSE_ON_CLOSE_OPERATION_NAME; -import static org.apache.skywalking.apm.plugin.grpc.v1.Constants.RESPONSE_ON_MESSAGE_OPERATION_NAME; -import static org.apache.skywalking.apm.plugin.grpc.v1.Constants.SERVER; - import io.grpc.ForwardingServerCall; import io.grpc.Metadata; import io.grpc.ServerCall; import io.grpc.Status; import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; import org.apache.skywalking.apm.agent.core.context.tag.Tags; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.grpc.v1.OperationNameFormatUtil; +import static org.apache.skywalking.apm.plugin.grpc.v1.Constants.RESPONSE_ON_CLOSE_OPERATION_NAME; +import static org.apache.skywalking.apm.plugin.grpc.v1.Constants.RESPONSE_ON_MESSAGE_OPERATION_NAME; +import static org.apache.skywalking.apm.plugin.grpc.v1.Constants.SERVER; + public class TracingServerCall extends ForwardingServerCall.SimpleForwardingServerCall { private final String operationPrefix; + private final ContextSnapshot contextSnapshot; + private final AbstractSpan parentEntrySpan; - protected TracingServerCall(ServerCall delegate) { + protected TracingServerCall(ServerCall delegate, + final ContextSnapshot contextSnapshot, + final AbstractSpan parentEntrySpan) { super(delegate); this.operationPrefix = OperationNameFormatUtil.formatOperationName(delegate.getMethodDescriptor()) + SERVER; + this.contextSnapshot = contextSnapshot; + this.parentEntrySpan = parentEntrySpan; } @Override public void sendMessage(RESPONSE message) { // We just create the request on message span for server stream calls. if (!getMethodDescriptor().getType().serverSendsOneMessage()) { - final AbstractSpan span = ContextManager.createLocalSpan(operationPrefix + RESPONSE_ON_MESSAGE_OPERATION_NAME); + final AbstractSpan span = ContextManager.createLocalSpan( + operationPrefix + RESPONSE_ON_MESSAGE_OPERATION_NAME); span.setComponent(ComponentsDefine.GRPC); span.setLayer(SpanLayer.RPC_FRAMEWORK); - ContextManager.continued(ServerInterceptor.CONTEXT_SNAPSHOT_KEY.get()); + ContextManager.continued(contextSnapshot); try { super.sendMessage(message); } catch (Throwable t) { @@ -68,7 +76,7 @@ public void close(Status status, Metadata trailers) { final AbstractSpan span = ContextManager.createLocalSpan(operationPrefix + RESPONSE_ON_CLOSE_OPERATION_NAME); span.setComponent(ComponentsDefine.GRPC); span.setLayer(SpanLayer.RPC_FRAMEWORK); - ContextManager.continued(ServerInterceptor.CONTEXT_SNAPSHOT_KEY.get()); + ContextManager.continued(contextSnapshot); switch (status.getCode()) { case OK: break; @@ -94,7 +102,7 @@ public void close(Status status, Metadata trailers) { break; } Tags.RPC_RESPONSE_STATUS_CODE.set(span, status.getCode().name()); - ServerInterceptor.ACTIVE_SPAN_KEY.get().asyncFinish(); + parentEntrySpan.asyncFinish(); try { super.close(status, trailers); diff --git a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/TracingServerCallListener.java b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/TracingServerCallListener.java index cb19e86c6c..65910f003f 100644 --- a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/TracingServerCallListener.java +++ b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/server/TracingServerCallListener.java @@ -22,6 +22,7 @@ import io.grpc.MethodDescriptor; import io.grpc.ServerCall; import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; @@ -34,11 +35,17 @@ public class TracingServerCallListener extends ForwardingServerCallListener.SimpleForwardingServerCallListener { private final MethodDescriptor.MethodType methodType; private final String operationPrefix; + private final ContextSnapshot contextSnapshot; + private final AbstractSpan parentEntrySpan; - protected TracingServerCallListener(ServerCall.Listener delegate, MethodDescriptor descriptor) { + protected TracingServerCallListener(ServerCall.Listener delegate, MethodDescriptor descriptor, + final ContextSnapshot contextSnapshot, + final AbstractSpan parentEntrySpan) { super(delegate); this.methodType = descriptor.getType(); this.operationPrefix = OperationNameFormatUtil.formatOperationName(descriptor) + SERVER; + this.contextSnapshot = contextSnapshot; + this.parentEntrySpan = parentEntrySpan; } @Override @@ -48,7 +55,7 @@ public void onMessage(REQUEST message) { final AbstractSpan span = ContextManager.createLocalSpan(operationPrefix + REQUEST_ON_MESSAGE_OPERATION_NAME); span.setComponent(ComponentsDefine.GRPC); span.setLayer(SpanLayer.RPC_FRAMEWORK); - ContextManager.continued(ServerInterceptor.CONTEXT_SNAPSHOT_KEY.get()); + ContextManager.continued(contextSnapshot); try { super.onMessage(message); } catch (Throwable t) { @@ -67,7 +74,7 @@ public void onCancel() { final AbstractSpan span = ContextManager.createLocalSpan(operationPrefix + REQUEST_ON_CANCEL_OPERATION_NAME); span.setComponent(ComponentsDefine.GRPC); span.setLayer(SpanLayer.RPC_FRAMEWORK); - ContextManager.continued(ServerInterceptor.CONTEXT_SNAPSHOT_KEY.get()); + ContextManager.continued(contextSnapshot); try { super.onCancel(); } catch (Throwable t) { @@ -75,7 +82,7 @@ public void onCancel() { throw t; } finally { ContextManager.stopSpan(); - ServerInterceptor.ACTIVE_SPAN_KEY.get().asyncFinish(); + parentEntrySpan.asyncFinish(); } } @@ -84,7 +91,7 @@ public void onHalfClose() { final AbstractSpan span = ContextManager.createLocalSpan(operationPrefix + REQUEST_ON_HALF_CLOSE_OPERATION_NAME); span.setComponent(ComponentsDefine.GRPC); span.setLayer(SpanLayer.RPC_FRAMEWORK); - ContextManager.continued(ServerInterceptor.CONTEXT_SNAPSHOT_KEY.get()); + ContextManager.continued(contextSnapshot); try { super.onHalfClose(); } catch (Throwable t) { diff --git a/apm-sniffer/apm-sdk-plugin/guava-eventbus-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/guava-eventbus-plugin/pom.xml index 31109f5f26..d2ad8e9292 100644 --- a/apm-sniffer/apm-sdk-plugin/guava-eventbus-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/guava-eventbus-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/h2-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/h2-1.x-plugin/pom.xml index ef577b70b2..4c359960a0 100755 --- a/apm-sniffer/apm-sdk-plugin/h2-1.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/h2-1.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/hbase-1.x-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/hbase-1.x-2.x-plugin/pom.xml index e852e6cb80..d256acb1c5 100644 --- a/apm-sniffer/apm-sdk-plugin/hbase-1.x-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/hbase-1.x-2.x-plugin/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-hbase-1.x-2.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/hikaricp-3.x-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/hikaricp-3.x-4.x-plugin/pom.xml index a48229f282..864b91b125 100644 --- a/apm-sniffer/apm-sdk-plugin/hikaricp-3.x-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/hikaricp-3.x-4.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/hikaricp-3.x-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/hikaricp/PoolingSealInterceptor.java b/apm-sniffer/apm-sdk-plugin/hikaricp-3.x-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/hikaricp/PoolingSealInterceptor.java index 7be4b562c2..a2673afbf9 100644 --- a/apm-sniffer/apm-sdk-plugin/hikaricp-3.x-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/hikaricp/PoolingSealInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/hikaricp-3.x-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/hikaricp/PoolingSealInterceptor.java @@ -49,8 +49,13 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { HikariDataSource hikariDataSource = (HikariDataSource) objInst; - ConnectionInfo connectionInfo = URLParser.parser(hikariDataSource.getJdbcUrl()); - String tagValue = connectionInfo.getDatabaseName() + "_" + connectionInfo.getDatabasePeer(); + String tagValue; + if (hikariDataSource.getJdbcUrl() != null) { + ConnectionInfo connectionInfo = URLParser.parser(hikariDataSource.getJdbcUrl()); + tagValue = connectionInfo.getDatabaseName() + "_" + connectionInfo.getDatabasePeer(); + } else { + tagValue = hikariDataSource.getPoolName(); + } final Map>> poolMetricMap = getPoolMetrics(); final Map>> metricConfigMap = getConfigMetrics(); poolMetricMap.forEach((key, value) -> MeterFactory.gauge(METER_NAME, value.apply(hikariDataSource.getHikariPoolMXBean())) diff --git a/apm-sniffer/apm-sdk-plugin/httpClient-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/httpClient-4.x-plugin/pom.xml index 772529e458..0fcdd24c95 100644 --- a/apm-sniffer/apm-sdk-plugin/httpClient-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/httpClient-4.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-httpClient-4.x-plugin @@ -55,7 +55,6 @@ junit junit - ${junit.version} test diff --git a/apm-sniffer/apm-sdk-plugin/httpClient-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpClient/v4/HttpClientExecuteInterceptor.java b/apm-sniffer/apm-sdk-plugin/httpClient-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpClient/v4/HttpClientExecuteInterceptor.java index b02de707af..24403bda2f 100644 --- a/apm-sniffer/apm-sdk-plugin/httpClient-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpClient/v4/HttpClientExecuteInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/httpClient-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpClient/v4/HttpClientExecuteInterceptor.java @@ -22,6 +22,10 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; @@ -34,6 +38,8 @@ import org.apache.skywalking.apm.agent.core.context.tag.Tags; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.logging.api.LogManager; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; @@ -43,12 +49,19 @@ public class HttpClientExecuteInterceptor implements InstanceMethodsAroundInterceptor { private static final String ERROR_URI = "/_blank"; + private static final ILog LOGGER = LogManager.getLogger(HttpClientExecuteInterceptor.class); + + /** + * Lazily-resolved set of ports that must not receive SkyWalking + * propagation headers. Built once from + * {@link HttpClientPluginConfig.Plugin.HttpClient#PROPAGATION_EXCLUDE_PORTS}. + */ + private volatile Set excludePortsCache; @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { - if (allArguments[0] == null || allArguments[1] == null) { - // illegal args, can't trace. ignore. + if (skipIntercept(allArguments)) { return; } final HttpHost httpHost = (HttpHost) allArguments[0]; @@ -82,7 +95,7 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr @Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { - if (allArguments[0] == null || allArguments[1] == null) { + if (skipIntercept(allArguments)) { return ret; } @@ -111,10 +124,62 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA @Override public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + if (skipIntercept(allArguments)) { + return; + } AbstractSpan activeSpan = ContextManager.activeSpan(); activeSpan.log(t); } + private boolean skipIntercept(Object[] allArguments) { + if (allArguments[0] == null || allArguments[1] == null) { + return true; + } + HttpHost httpHost = (HttpHost) allArguments[0]; + return isExcludedPort(port(httpHost)); + } + + /** + * Returns {@code true} when {@code port} is listed in + * {@link HttpClientPluginConfig.Plugin.HttpClient#PROPAGATION_EXCLUDE_PORTS}. + * + *

The config value is parsed lazily and cached so that it is read after + * the agent has fully initialised its configuration subsystem. + */ + private boolean isExcludedPort(int port) { + if (port <= 0) { + return false; + } + if (excludePortsCache == null) { + synchronized (this) { + if (excludePortsCache == null) { + excludePortsCache = parseExcludePorts( + HttpClientPluginConfig.Plugin.HttpClient.PROPAGATION_EXCLUDE_PORTS); + } + } + } + return excludePortsCache.contains(port); + } + + private static Set parseExcludePorts(String raw) { + if (raw == null || raw.trim().isEmpty()) { + return Collections.emptySet(); + } + return Arrays.stream(raw.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map(s -> { + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + LOGGER.warn("Ignoring invalid port in PROPAGATION_EXCLUDE_PORTS: {}", s); + return -1; + } + }) + .filter(p -> p > 0) + .collect(Collectors.toSet()); + } + private String getRequestURI(String uri) { if (isUrl(uri)) { String requestPath; diff --git a/apm-sniffer/apm-sdk-plugin/httpClient-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/httpClient/v4/HttpClientPropagationExcludePortTest.java b/apm-sniffer/apm-sdk-plugin/httpClient-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/httpClient/v4/HttpClientPropagationExcludePortTest.java new file mode 100644 index 0000000000..9975bd92fc --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpClient-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/httpClient/v4/HttpClientPropagationExcludePortTest.java @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.httpClient.v4; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.mock; + +import java.util.List; + +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.RequestLine; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.HttpGet; +import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.plugin.httpclient.HttpClientPluginConfig; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** + * Verifies that requests to ports listed in + * {@link HttpClientPluginConfig.Plugin.HttpClient#PROPAGATION_EXCLUDE_PORTS} + * are silently skipped (no span created, no sw8 header injected). + */ +@RunWith(TracingSegmentRunner.class) +public class HttpClientPropagationExcludePortTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule agentServiceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @Mock + private HttpHost clickHouseHost; + @Mock + private HttpHost regularHost; + @Mock + private HttpGet request; + @Mock + private HttpResponse httpResponse; + @Mock + private StatusLine statusLine; + @Mock + private EnhancedInstance enhancedInstance; + + private HttpClientExecuteInterceptor interceptor; + + private Object[] clickHouseArgs; + private Object[] regularArgs; + private Class[] argumentsType; + + @Before + public void setUp() throws Exception { + ServiceManager.INSTANCE.boot(); + + HttpClientPluginConfig.Plugin.HttpClient.PROPAGATION_EXCLUDE_PORTS = "8123"; + interceptor = new HttpClientExecuteInterceptor(); + + when(statusLine.getStatusCode()).thenReturn(200); + when(httpResponse.getStatusLine()).thenReturn(statusLine); + + RequestLine requestLine = new RequestLine() { + @Override + public String getMethod() { + return "GET"; + } + + @Override + public ProtocolVersion getProtocolVersion() { + return new ProtocolVersion("http", 1, 1); + } + + @Override + public String getUri() { + return "http://my-service:8080/api/ping"; + } + }; + when(request.getRequestLine()).thenReturn(requestLine); + + when(clickHouseHost.getHostName()).thenReturn("clickhouse-server"); + when(clickHouseHost.getSchemeName()).thenReturn("http"); + when(clickHouseHost.getPort()).thenReturn(8123); + + when(regularHost.getHostName()).thenReturn("my-service"); + when(regularHost.getSchemeName()).thenReturn("http"); + when(regularHost.getPort()).thenReturn(8080); + + clickHouseArgs = new Object[]{clickHouseHost, request}; + regularArgs = new Object[]{regularHost, request}; + argumentsType = new Class[]{HttpHost.class, HttpGet.class}; + } + + @Test + public void requestToExcludedPort_noSpanAndNoHeaderInjected() throws Throwable { + interceptor.beforeMethod(enhancedInstance, null, clickHouseArgs, argumentsType, null); + interceptor.afterMethod(enhancedInstance, null, clickHouseArgs, argumentsType, httpResponse); + + List segments = segmentStorage.getTraceSegments(); + assertThat(segments.size(), is(0)); + verify(request, never()).setHeader(anyString(), anyString()); + } + + @Test + public void requestToRegularPort_spanCreatedAndHeadersInjected() throws Throwable { + interceptor.beforeMethod(enhancedInstance, null, regularArgs, argumentsType, null); + interceptor.afterMethod(enhancedInstance, null, regularArgs, argumentsType, httpResponse); + + List segments = segmentStorage.getTraceSegments(); + assertThat(segments.size(), is(1)); + verify(request, times(3)).setHeader(anyString(), anyString()); + } + + @Test + public void whenExcludePortsEmpty_allPortsAreTraced() throws Throwable { + HttpClientPluginConfig.Plugin.HttpClient.PROPAGATION_EXCLUDE_PORTS = ""; + + HttpClientExecuteInterceptor freshInterceptor = new HttpClientExecuteInterceptor(); + freshInterceptor.beforeMethod(enhancedInstance, null, clickHouseArgs, argumentsType, null); + freshInterceptor.afterMethod(enhancedInstance, null, clickHouseArgs, argumentsType, httpResponse); + + List segments = segmentStorage.getTraceSegments(); + assertThat(segments.size(), is(1)); + } + + @Test + public void multipleExcludedPorts_allSkippedAndNonExcludedStillTraced() throws Throwable { + HttpClientPluginConfig.Plugin.HttpClient.PROPAGATION_EXCLUDE_PORTS = "8123,9200"; + + HttpClientExecuteInterceptor freshInterceptor = new HttpClientExecuteInterceptor(); + + // 8123 should be excluded + freshInterceptor.beforeMethod(enhancedInstance, null, clickHouseArgs, argumentsType, null); + freshInterceptor.afterMethod(enhancedInstance, null, clickHouseArgs, argumentsType, httpResponse); + assertThat(segmentStorage.getTraceSegments().size(), is(0)); + + // 9200 should also be excluded + HttpHost esHost = mock(HttpHost.class); + when(esHost.getHostName()).thenReturn("es-server"); + when(esHost.getSchemeName()).thenReturn("http"); + when(esHost.getPort()).thenReturn(9200); + Object[] esArgs = new Object[]{esHost, request}; + + freshInterceptor = new HttpClientExecuteInterceptor(); + HttpClientPluginConfig.Plugin.HttpClient.PROPAGATION_EXCLUDE_PORTS = "8123,9200"; + freshInterceptor.beforeMethod(enhancedInstance, null, esArgs, argumentsType, null); + freshInterceptor.afterMethod(enhancedInstance, null, esArgs, argumentsType, httpResponse); + assertThat(segmentStorage.getTraceSegments().size(), is(0)); + + // 8080 should still be traced + freshInterceptor = new HttpClientExecuteInterceptor(); + HttpClientPluginConfig.Plugin.HttpClient.PROPAGATION_EXCLUDE_PORTS = "8123,9200"; + freshInterceptor.beforeMethod(enhancedInstance, null, regularArgs, argumentsType, null); + freshInterceptor.afterMethod(enhancedInstance, null, regularArgs, argumentsType, httpResponse); + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + } + + @Test + public void excludedPort_handleMethodExceptionSkipped() throws Throwable { + interceptor.beforeMethod(enhancedInstance, null, clickHouseArgs, argumentsType, null); + interceptor.handleMethodException(enhancedInstance, null, clickHouseArgs, argumentsType, new RuntimeException("test")); + interceptor.afterMethod(enhancedInstance, null, clickHouseArgs, argumentsType, httpResponse); + + List segments = segmentStorage.getTraceSegments(); + assertThat(segments.size(), is(0)); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/httpasyncclient-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/httpasyncclient-4.x-plugin/pom.xml index 87656a9dbc..2cd5025cd6 100644 --- a/apm-sniffer/apm-sdk-plugin/httpasyncclient-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/httpasyncclient-4.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-httpasyncclient-4.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/httpclient-3.x-plugin/pom.xml index e03fcef53c..2127da9e0d 100644 --- a/apm-sniffer/apm-sdk-plugin/httpclient-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/httpclient-3.x-plugin/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-httpclient-3.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/pom.xml index d205761cee..ba159639a4 100644 --- a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-httpclient-5.x-plugin @@ -46,7 +46,6 @@ junit junit - ${junit.version} test diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClient5PluginConfig.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClient5PluginConfig.java new file mode 100644 index 0000000000..3b6aaf6aa9 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClient5PluginConfig.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.httpclient.v5; + +import org.apache.skywalking.apm.agent.core.boot.PluginConfig; + +public class HttpClient5PluginConfig { + public static class Plugin { + @PluginConfig(root = HttpClient5PluginConfig.class) + public static class HttpClient5 { + /** + * Comma-separated list of destination ports whose outbound HTTP requests + * will be completely skipped by the classic client interceptor: no exit + * span is created and no SkyWalking propagation headers are injected. + * + *

Some HTTP-based database protocols (e.g. ClickHouse on port 8123) + * reject requests that contain unknown HTTP headers, returning HTTP 400. + * Adding such ports here prevents the agent from creating exit spans + * and from injecting the {@code sw8} tracing headers into those outbound + * requests, meaning these requests are completely untraced by SkyWalking, + * while leaving all other HTTP calls fully traced. + * + *

Default: {@code "8123"} (ClickHouse HTTP interface). + * + *

Example – also exclude port 9200 (Elasticsearch): + * {@code plugin.httpclient5.PROPAGATION_EXCLUDE_PORTS=8123,9200} + */ + public static String PROPAGATION_EXCLUDE_PORTS = "8123"; + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientDoExecuteInterceptor.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientDoExecuteInterceptor.java index d53eb7a246..238a745b69 100644 --- a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientDoExecuteInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientDoExecuteInterceptor.java @@ -37,12 +37,23 @@ import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; public abstract class HttpClientDoExecuteInterceptor implements InstanceMethodsAroundInterceptor { private static final String ERROR_URI = "/_blank"; private static final ILog LOGGER = LogManager.getLogger(HttpClientDoExecuteInterceptor.class); + /** + * Lazily-resolved set of ports that must not receive SkyWalking + * propagation headers. Built once from + * {@link HttpClient5PluginConfig.Plugin.HttpClient5#PROPAGATION_EXCLUDE_PORTS}. + */ + private volatile Set excludePortsCache; + @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { @@ -77,7 +88,55 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr protected boolean skipIntercept(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes) { - return allArguments[1] == null || getHttpHost(objInst, method, allArguments, argumentsTypes) == null; + if (allArguments[1] == null) { + return true; + } + HttpHost host = getHttpHost(objInst, method, allArguments, argumentsTypes); + if (host == null) { + return true; + } + return isExcludedPort(port(host)); + } + + /** + * Returns {@code true} when {@code port} is listed in + * {@link HttpClient5PluginConfig.Plugin.HttpClient5#PROPAGATION_EXCLUDE_PORTS}. + * + *

The config value is parsed lazily and cached so that it is read after + * the agent has fully initialised its configuration subsystem. + */ + private boolean isExcludedPort(int port) { + if (port <= 0) { + return false; + } + if (excludePortsCache == null) { + synchronized (this) { + if (excludePortsCache == null) { + excludePortsCache = parseExcludePorts( + HttpClient5PluginConfig.Plugin.HttpClient5.PROPAGATION_EXCLUDE_PORTS); + } + } + } + return excludePortsCache.contains(port); + } + + private static Set parseExcludePorts(String raw) { + if (raw == null || raw.trim().isEmpty()) { + return Collections.emptySet(); + } + return Arrays.stream(raw.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map(s -> { + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + LOGGER.warn("Ignoring invalid port in PROPAGATION_EXCLUDE_PORTS: {}", s); + return -1; + } + }) + .filter(p -> p > 0) + .collect(Collectors.toSet()); } protected abstract HttpHost getHttpHost(EnhancedInstance objInst, Method method, Object[] allArguments, diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientPropagationExcludePortTest.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientPropagationExcludePortTest.java new file mode 100644 index 0000000000..717eb61a3e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientPropagationExcludePortTest.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.httpclient.v5; + +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpHost; +import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.net.URI; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Verifies that requests to ports listed in + * {@link HttpClient5PluginConfig.Plugin.HttpClient5#PROPAGATION_EXCLUDE_PORTS} + * are silently skipped (no span created, no {@code sw8} header injected). + * + *

This regression-covers the ClickHouse HTTP-interface issue: ClickHouse + * listens on port 8123 and rejects HTTP requests that carry unknown headers + * (such as the SkyWalking {@code sw8} propagation header), responding with + * HTTP 400 Bad Request. By excluding port 8123 the agent leaves those + * requests untouched while continuing to trace all other HTTP calls. + */ +@RunWith(TracingSegmentRunner.class) +public class HttpClientPropagationExcludePortTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule agentServiceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @Mock + private HttpHost clickHouseHost; // port 8123 – should be excluded + @Mock + private HttpHost regularHost; // port 8080 – should be traced + @Mock + private ClassicHttpRequest request; + @Mock + private ClassicHttpResponse httpResponse; + @Mock + private EnhancedInstance enhancedInstance; + + private HttpClientDoExecuteInterceptor internalInterceptor; + private HttpClientDoExecuteInterceptor minimalInterceptor; + + private Object[] clickHouseArgs; + private Object[] regularArgs; + private Class[] argumentsType; + + @Before + public void setUp() throws Exception { + ServiceManager.INSTANCE.boot(); + + // Set the exclusion list to the default (includes 8123) + HttpClient5PluginConfig.Plugin.HttpClient5.PROPAGATION_EXCLUDE_PORTS = "8123"; + + internalInterceptor = new InternalClientDoExecuteInterceptor(); + minimalInterceptor = new MinimalClientDoExecuteInterceptor(); + + when(httpResponse.getCode()).thenReturn(200); + + // ClickHouse-like host on port 8123 + when(clickHouseHost.getHostName()).thenReturn("clickhouse-server"); + when(clickHouseHost.getSchemeName()).thenReturn("http"); + when(clickHouseHost.getPort()).thenReturn(8123); + + // Regular application host on port 8080 + when(regularHost.getHostName()).thenReturn("my-service"); + when(regularHost.getSchemeName()).thenReturn("http"); + when(regularHost.getPort()).thenReturn(8080); + + when(request.getUri()).thenReturn(new URI("http://my-service:8080/api/ping")); + when(request.getMethod()).thenReturn("GET"); + + clickHouseArgs = new Object[]{clickHouseHost, request}; + regularArgs = new Object[]{regularHost, request}; + argumentsType = new Class[]{HttpHost.class, ClassicHttpRequest.class}; + } + + // ----------------------------------------------------------------------- + // InternalHttpClient path + // ----------------------------------------------------------------------- + + /** + * Requests to port 8123 via {@code InternalHttpClient} must not produce a + * trace segment and must NOT set any propagation header on the request. + * + *

Before this fix the agent injected {@code sw8} (and two companion + * headers) into every outbound request regardless of the destination port. + * ClickHouse interprets unknown headers as malformed requests and returns + * HTTP 400, making all JDBC queries fail while the SkyWalking agent is + * attached. + */ + @Test + public void internalClient_requestToExcludedPort_noSpanAndNoHeaderInjected() throws Throwable { + internalInterceptor.beforeMethod(enhancedInstance, null, clickHouseArgs, argumentsType, null); + internalInterceptor.afterMethod(enhancedInstance, null, clickHouseArgs, argumentsType, httpResponse); + + List segments = segmentStorage.getTraceSegments(); + assertThat("No trace segment should be created for excluded port", segments.size(), is(0)); + verify(request, never()).setHeader(anyString(), anyString()); + } + + /** + * Requests to a non-excluded port via {@code InternalHttpClient} must still + * be traced and have propagation headers injected. + */ + @Test + public void internalClient_requestToRegularPort_spanCreatedAndHeadersInjected() throws Throwable { + internalInterceptor.beforeMethod(enhancedInstance, null, regularArgs, argumentsType, null); + internalInterceptor.afterMethod(enhancedInstance, null, regularArgs, argumentsType, httpResponse); + + List segments = segmentStorage.getTraceSegments(); + assertThat("A trace segment must be created for a non-excluded port", segments.size(), is(1)); + // sw8, sw8-correlation, sw8-x – exactly 3 propagation headers, consistent with existing tests + verify(request, org.mockito.Mockito.times(3)).setHeader(anyString(), anyString()); + } + + // ----------------------------------------------------------------------- + // MinimalHttpClient path + // ----------------------------------------------------------------------- + + /** + * Same assertion for the {@code MinimalHttpClient} code path. + */ + @Test + public void minimalClient_requestToExcludedPort_noSpanAndNoHeaderInjected() throws Throwable { + minimalInterceptor.beforeMethod(enhancedInstance, null, clickHouseArgs, argumentsType, null); + minimalInterceptor.afterMethod(enhancedInstance, null, clickHouseArgs, argumentsType, httpResponse); + + List segments = segmentStorage.getTraceSegments(); + assertThat("No trace segment should be created for excluded port", segments.size(), is(0)); + verify(request, never()).setHeader(anyString(), anyString()); + } + + /** + * Normal (non-excluded) port via {@code MinimalHttpClient} must still be + * traced. + */ + @Test + public void minimalClient_requestToRegularPort_spanCreatedAndHeadersInjected() throws Throwable { + minimalInterceptor.beforeMethod(enhancedInstance, null, regularArgs, argumentsType, null); + minimalInterceptor.afterMethod(enhancedInstance, null, regularArgs, argumentsType, httpResponse); + + List segments = segmentStorage.getTraceSegments(); + assertThat("A trace segment must be created for a non-excluded port", segments.size(), is(1)); + verify(request, org.mockito.Mockito.times(3)).setHeader(anyString(), anyString()); + } + + // ----------------------------------------------------------------------- + // Configuration edge cases + // ----------------------------------------------------------------------- + + /** + * When {@code PROPAGATION_EXCLUDE_PORTS} is cleared (empty string), every + * port – including 8123 – must be traced normally. + */ + @Test + public void whenExcludePortsEmpty_allPortsAreTraced() throws Throwable { + HttpClient5PluginConfig.Plugin.HttpClient5.PROPAGATION_EXCLUDE_PORTS = ""; + + // Use a fresh interceptor so the cache is not pre-populated + HttpClientDoExecuteInterceptor freshInterceptor = new MinimalClientDoExecuteInterceptor(); + freshInterceptor.beforeMethod(enhancedInstance, null, clickHouseArgs, argumentsType, null); + freshInterceptor.afterMethod(enhancedInstance, null, clickHouseArgs, argumentsType, httpResponse); + + List segments = segmentStorage.getTraceSegments(); + assertThat("Port 8123 should be traced when exclusion list is empty", segments.size(), is(1)); + } + + /** + * Multiple ports can be listed: verify that both excluded ports are silently + * skipped while a non-excluded port is still traced under the same config. + */ + @Test + public void multipleExcludedPorts_allSkippedAndNonExcludedStillTraced() throws Throwable { + HttpClient5PluginConfig.Plugin.HttpClient5.PROPAGATION_EXCLUDE_PORTS = "8123,9200"; + + HttpClientDoExecuteInterceptor freshInterceptor = new MinimalClientDoExecuteInterceptor(); + + // 8123 – must be excluded + freshInterceptor.beforeMethod(enhancedInstance, null, clickHouseArgs, argumentsType, null); + freshInterceptor.afterMethod(enhancedInstance, null, clickHouseArgs, argumentsType, httpResponse); + assertThat("Port 8123 should be excluded", segmentStorage.getTraceSegments().size(), is(0)); + + // 9200 (Elasticsearch) – must also be excluded + HttpHost esHost = org.mockito.Mockito.mock(HttpHost.class); + when(esHost.getHostName()).thenReturn("es-server"); + when(esHost.getSchemeName()).thenReturn("http"); + when(esHost.getPort()).thenReturn(9200); + Object[] esArgs = new Object[]{esHost, request}; + + freshInterceptor = new MinimalClientDoExecuteInterceptor(); + freshInterceptor.beforeMethod(enhancedInstance, null, esArgs, argumentsType, null); + freshInterceptor.afterMethod(enhancedInstance, null, esArgs, argumentsType, httpResponse); + assertThat("Port 9200 should also be excluded", segmentStorage.getTraceSegments().size(), is(0)); + + // 8080 (regular service) – must still be traced under the same multi-port config + freshInterceptor = new MinimalClientDoExecuteInterceptor(); + freshInterceptor.beforeMethod(enhancedInstance, null, regularArgs, argumentsType, null); + freshInterceptor.afterMethod(enhancedInstance, null, regularArgs, argumentsType, httpResponse); + assertThat("Non-excluded port 8080 must still produce a trace segment", + segmentStorage.getTraceSegments().size(), is(1)); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-commons/pom.xml b/apm-sniffer/apm-sdk-plugin/httpclient-commons/pom.xml index e0cdc80bbe..c9976aadf2 100644 --- a/apm-sniffer/apm-sdk-plugin/httpclient-commons/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/httpclient-commons/pom.xml @@ -22,7 +22,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-httpclient-commons diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-commons/src/main/java/org/apache/skywalking/apm/plugin/httpclient/HttpClientPluginConfig.java b/apm-sniffer/apm-sdk-plugin/httpclient-commons/src/main/java/org/apache/skywalking/apm/plugin/httpclient/HttpClientPluginConfig.java index 9a4a94cdb1..2c54e1b220 100644 --- a/apm-sniffer/apm-sdk-plugin/httpclient-commons/src/main/java/org/apache/skywalking/apm/plugin/httpclient/HttpClientPluginConfig.java +++ b/apm-sniffer/apm-sdk-plugin/httpclient-commons/src/main/java/org/apache/skywalking/apm/plugin/httpclient/HttpClientPluginConfig.java @@ -28,6 +28,22 @@ public static class HttpClient { * This config item controls that whether the HttpClient plugin should collect the parameters of the request. */ public static boolean COLLECT_HTTP_PARAMS = false; + + /** + * Comma-separated list of destination ports whose outbound HTTP requests + * will be completely skipped by the httpclient-4.x interceptor: no exit + * span is created and no SkyWalking propagation headers are injected. + * + *

Some HTTP-based database protocols (e.g. ClickHouse on port 8123) + * reject requests that contain unknown HTTP headers, returning HTTP 400. + * Adding such ports here prevents the agent from creating exit spans + * and from injecting the {@code sw8} tracing headers into those outbound + * requests. + * + *

Example – exclude ClickHouse and Elasticsearch ports: + * {@code plugin.httpclient.propagation_exclude_ports=8123,9200} + */ + public static String PROPAGATION_EXCLUDE_PORTS = ""; } @PluginConfig(root = HttpClientPluginConfig.class) diff --git a/apm-sniffer/apm-sdk-plugin/hutool-plugins/hutool-http-5.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/hutool-plugins/hutool-http-5.x-plugin/pom.xml index f2d0495cc8..b31ba12cf4 100644 --- a/apm-sniffer/apm-sdk-plugin/hutool-plugins/hutool-http-5.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/hutool-plugins/hutool-http-5.x-plugin/pom.xml @@ -20,7 +20,7 @@ hutool-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/hutool-plugins/pom.xml b/apm-sniffer/apm-sdk-plugin/hutool-plugins/pom.xml index 3394b0092c..6c41996fb0 100644 --- a/apm-sniffer/apm-sdk-plugin/hutool-plugins/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/hutool-plugins/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -32,7 +32,6 @@ UTF-8 - /.. diff --git a/apm-sniffer/apm-sdk-plugin/hystrix-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/hystrix-1.x-plugin/pom.xml index b4ed0b93b3..83f8023afe 100644 --- a/apm-sniffer/apm-sdk-plugin/hystrix-1.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/hystrix-1.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/influxdb-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/influxdb-2.x-plugin/pom.xml index ab281eaeb1..e360c9ac4b 100644 --- a/apm-sniffer/apm-sdk-plugin/influxdb-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/influxdb-2.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/jdbc-commons/pom.xml b/apm-sniffer/apm-sdk-plugin/jdbc-commons/pom.xml index a6485b4582..1eecc0d35c 100755 --- a/apm-sniffer/apm-sdk-plugin/jdbc-commons/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/jdbc-commons/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/DMURLParser.java b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/DMURLParser.java new file mode 100644 index 0000000000..2cb4c1c4c0 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/DMURLParser.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.connectionurl.parser; + +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; + +public class DMURLParser extends AbstractURLParser { + private static final int DEFAULT_PORT = 5236; + private static final String DB_TYPE = "DM"; + private static final String URL_PARAMS_HOST_KEY = "host"; + private static final String URL_PARAMS_PORT_KEY = "port"; + private static final String URL_PARAMS_SCHEMA_KEY = "schema"; + + public DMURLParser(String url) { + super(url); + } + + @Override + protected URLLocation fetchDatabaseHostsIndexRange() { + int hostLabelStartIndex = url.indexOf("//"); + if (hostLabelStartIndex == -1) { + return new URLLocation(0, 0); + } + int hostLabelEndIndex = url.indexOf("?", hostLabelStartIndex + 2); + if (hostLabelEndIndex == -1) { + hostLabelEndIndex = url.length(); + } + return new URLLocation(hostLabelStartIndex + 2, hostLabelEndIndex); + } + + @Override + protected URLLocation fetchDatabaseNameIndexRange() { + return new URLLocation(0, 0); + } + + @Override + public ConnectionInfo parse() { + URLLocation location = fetchDatabaseHostsIndexRange(); + String hostPortSegment = ""; + if (location.endIndex() > location.startIndex()) { + hostPortSegment = url.substring(location.startIndex(), location.endIndex()); + } + + String host = ""; + String port = ""; + + if (!hostPortSegment.isEmpty()) { + String[] parts = hostPortSegment.split(":"); + if (parts.length >= 1) { + host = parts[0]; + } + if (parts.length == 2) { + port = parts[1]; + } + } + + if (host.isEmpty()) { + host = fetchFromUrlParams(URL_PARAMS_HOST_KEY); + } + if (port == null || port.isEmpty()) { + port = fetchFromUrlParams(URL_PARAMS_PORT_KEY); + } + + if (port == null || port.isEmpty()) { + port = String.valueOf(DEFAULT_PORT); + } + + String schema = fetchFromUrlParams(URL_PARAMS_SCHEMA_KEY); + + return new ConnectionInfo( + ComponentsDefine.DMDB_JDBC_DRIVER, + DB_TYPE, + host, + Integer.parseInt(port), + schema == null ? "" : schema + ); + } + + private String fetchFromUrlParams(String key) { + int paramIndex = url.indexOf("?"); + if (paramIndex == -1) { + return null; + } + String[] params = url.substring(paramIndex + 1).split("&"); + for (String pair : params) { + if (pair.startsWith(key + "=")) { + String[] segments = pair.split("=", 2); + if (segments.length == 2) { + return segments[1]; + } + } + } + return null; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/Db2URLParser.java b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/Db2URLParser.java new file mode 100644 index 0000000000..8fe2b36515 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/Db2URLParser.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.connectionurl.parser; + +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; + +public class Db2URLParser extends AbstractURLParser { + private static final String DEFAULT_HOST = "localhost"; + private static final int DEFAULT_PORT = 50000; + private static final String DB_TYPE = "DB2"; + private static final String JDBC_PREFIX = "jdbc:db2:"; + + public Db2URLParser(String url) { + super(url); + } + + @Override + protected URLLocation fetchDatabaseHostsIndexRange() { + int hostLabelStartIndex = url.indexOf("//"); + if (hostLabelStartIndex == -1) { + return null; + } + int hostLabelEndIndex = url.indexOf("/", hostLabelStartIndex + 2); + int hostLabelEndIndexWithParameter = url.indexOf(":", hostLabelEndIndex + 1); + if (hostLabelEndIndex == -1) { + hostLabelEndIndex = hostLabelEndIndexWithParameter; + } + if (hostLabelEndIndexWithParameter < hostLabelEndIndex && hostLabelEndIndexWithParameter != -1) { + hostLabelEndIndex = hostLabelEndIndexWithParameter; + } + if (hostLabelEndIndex == -1) { + hostLabelEndIndex = url.length(); + } + return new URLLocation(hostLabelStartIndex + 2, hostLabelEndIndex); + } + + protected String fetchDatabaseNameFromURL(int startSize) { + URLLocation hostsLocation = fetchDatabaseNameIndexRange(startSize); + if (hostsLocation == null) { + return ""; + } + return url.substring(hostsLocation.startIndex(), hostsLocation.endIndex()); + } + + protected URLLocation fetchDatabaseNameIndexRange(int startSize) { + int databaseStartTag = url.indexOf("/", startSize); + int parameterStartTag = url.indexOf(":", startSize); + if (parameterStartTag < databaseStartTag && parameterStartTag != -1) { + return null; + } + if (databaseStartTag == -1) { + databaseStartTag = startSize - 1; + } + int databaseEndTag = url.indexOf(":", startSize); + if (databaseEndTag == -1) { + databaseEndTag = url.length(); + } + return new URLLocation(databaseStartTag + 1, databaseEndTag); + } + + @Override + protected URLLocation fetchDatabaseNameIndexRange() { + int databaseStartTag = url.lastIndexOf("/"); + int databaseEndTag = url.indexOf(":", databaseStartTag); + if (databaseEndTag == -1) { + databaseEndTag = url.length(); + } + return new URLLocation(databaseStartTag + 1, databaseEndTag); + } + + @Override + public ConnectionInfo parse() { + URLLocation location = fetchDatabaseHostsIndexRange(); + if (location == null) { + return new ConnectionInfo( + ComponentsDefine.DB2_JDBC_DRIVER, DB_TYPE, DEFAULT_HOST, DEFAULT_PORT, + fetchDatabaseNameFromURL(JDBC_PREFIX.length()) + ); + } + String hosts = url.substring(location.startIndex(), location.endIndex()); + String[] hostSegment = hosts.split(","); + if (hostSegment.length > 1) { + StringBuilder sb = new StringBuilder(); + for (String host : hostSegment) { + if (host.split(":").length == 1) { + sb.append(host).append(":").append(DEFAULT_PORT).append(","); + } else { + sb.append(host).append(","); + } + } + return new ConnectionInfo( + ComponentsDefine.DB2_JDBC_DRIVER, DB_TYPE, sb.substring(0, sb.length() - 1), + fetchDatabaseNameFromURL() + ); + } else { + String[] hostAndPort = hostSegment[0].split(":"); + if (hostAndPort.length != 1) { + return new ConnectionInfo( + ComponentsDefine.DB2_JDBC_DRIVER, DB_TYPE, hostAndPort[0], Integer.valueOf(hostAndPort[1]), + fetchDatabaseNameFromURL(location + .endIndex()) + ); + } else { + return new ConnectionInfo( + ComponentsDefine.DB2_JDBC_DRIVER, DB_TYPE, hostAndPort[0], DEFAULT_PORT, + fetchDatabaseNameFromURL(location + .endIndex()) + ); + } + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/DerbyURLParser.java b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/DerbyURLParser.java new file mode 100644 index 0000000000..dd96b8e06a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/DerbyURLParser.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.connectionurl.parser; + +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; + +public class DerbyURLParser extends AbstractURLParser { + + private static final String DEFAULT_HOST = "localhost"; + private static final int DEFAULT_PORT = 1527; + private static final String DB_TYPE = "Derby"; + private static final String DERBY_JDBC_URL_PREFIX = "jdbc:derby"; + /** + * Flag that running with directory mode. + */ + private static final String DIRECTORY_MODE_FLAG = "derby:directory"; + /** + * Flag that running with memory mode. + */ + private static final String MEMORY_MODE_FLAG = "derby:memory"; + /** + * Flag that running with classpath mode. + */ + private static final String CLASSPATH_MODE_FLAG = "derby:classpath"; + /** + * Flag that running with jar mode. + */ + private static final String JAR_MODE_FLAG = "derby:jar"; + + public DerbyURLParser(String url) { + super(url); + } + + /** + * Fetch range index that the database name from connection url if Derby database running in client/server + * environment. eg: jdbc:derby://host[:port]/[databaseName][;attribute=value]* + * + * @return range index that the database name. + */ + @Override + protected URLLocation fetchDatabaseHostsIndexRange() { + int hostLabelStartIndex = url.indexOf("//"); + int hostLabelEndIndex = url.indexOf("/", hostLabelStartIndex + 2); + return new URLLocation(hostLabelStartIndex + 2, hostLabelEndIndex); + } + + @Override + protected URLLocation fetchDatabaseNameIndexRange() { + int databaseEndTag = url.indexOf(";"); + if (databaseEndTag == -1) { + databaseEndTag = url.length(); + } + int databaseStartTag = url.lastIndexOf("\\"); + if (databaseStartTag == -1) { + databaseStartTag = url.lastIndexOf("/"); + } + if (url.indexOf(":", databaseStartTag) != -1) { + databaseStartTag = url.indexOf(":", databaseStartTag); + } + return new URLLocation(databaseStartTag + 1, databaseEndTag); + } + + @Override + public ConnectionInfo parse() { + int[] databaseNameRangeIndex = fetchDatabaseNameRangeIndexForSubProtocol(DIRECTORY_MODE_FLAG); + if (databaseNameRangeIndex != null) { + return defaultConnection(databaseNameRangeIndex); + } + databaseNameRangeIndex = fetchDatabaseNameRangeIndexForSubProtocol(MEMORY_MODE_FLAG); + if (databaseNameRangeIndex != null) { + return defaultConnection(databaseNameRangeIndex); + } + databaseNameRangeIndex = fetchDatabaseNameRangeIndexForSubProtocol(CLASSPATH_MODE_FLAG); + if (databaseNameRangeIndex != null) { + return defaultConnection(databaseNameRangeIndex); + } + databaseNameRangeIndex = fetchDatabaseNameRangeIndexForSubProtocol(JAR_MODE_FLAG); + if (databaseNameRangeIndex != null) { + return defaultConnection(databaseNameRangeIndex); + } + databaseNameRangeIndex = fetchDatabaseNameRangeIndexWithoutHosts(); + if (databaseNameRangeIndex != null) { + return defaultConnection(databaseNameRangeIndex); + } + String[] hostAndPort = fetchDatabaseHostsFromURL().split(":"); + if (hostAndPort.length == 1) { + return new ConnectionInfo( + ComponentsDefine.DERBY_JDBC_DRIVER, DB_TYPE, hostAndPort[0], DEFAULT_PORT, fetchDatabaseNameFromURL()); + } else { + return new ConnectionInfo( + ComponentsDefine.DERBY_JDBC_DRIVER, DB_TYPE, hostAndPort[0], Integer.valueOf(hostAndPort[1]), + fetchDatabaseNameFromURL() + ); + } + } + + /** + * Fetch range index that the database name from connection url if Derby database running in embedded environment. + * eg: jdbc:derby:[databaseName][;attribute=value]* + * + * @return range index that the database name. + */ + private int[] fetchDatabaseNameRangeIndexWithoutHosts() { + if (url.contains("//")) { + return null; + } + int fileLabelIndex = url.indexOf(DERBY_JDBC_URL_PREFIX); + int parameterLabelIndex = url.indexOf(";"); + if (parameterLabelIndex == -1) { + parameterLabelIndex = url.length(); + } + + if (fileLabelIndex != -1) { + int pathLabelIndexForLinux = url.lastIndexOf("/"); + if (pathLabelIndexForLinux != -1 && pathLabelIndexForLinux > fileLabelIndex) { + return new int[] { + pathLabelIndexForLinux + 1, + parameterLabelIndex + }; + } + int pathLabelIndexForWin = url.lastIndexOf("\\"); + if (pathLabelIndexForWin != -1 && pathLabelIndexForWin > fileLabelIndex) { + return new int[] { + pathLabelIndexForWin + 1, + parameterLabelIndex + }; + } + return new int[] { + fileLabelIndex + DERBY_JDBC_URL_PREFIX.length() + 1, + parameterLabelIndex + }; + } else { + return null; + } + } + + /** + * Fetch range index that the database name from connection url if Derby database running with subprotocol. eg: + * jdbc:derby:subprotocol:[databaseName][;attribute=value]* + * + * @return range index that the database name. + */ + private int[] fetchDatabaseNameRangeIndexForSubProtocol(String mode) { + int fileLabelIndex = url.indexOf(mode); + int parameterLabelIndex = url.indexOf(";", fileLabelIndex); + if (parameterLabelIndex == -1) { + parameterLabelIndex = url.length(); + } + + if (fileLabelIndex != -1) { + int pathLabelIndexForLinux = url.lastIndexOf("/"); + if (pathLabelIndexForLinux != -1 && pathLabelIndexForLinux > fileLabelIndex) { + return new int[] { + pathLabelIndexForLinux + 1, + parameterLabelIndex + }; + } + int pathLabelIndexForWin = url.lastIndexOf("\\"); + if (pathLabelIndexForWin != -1 && pathLabelIndexForWin > fileLabelIndex) { + return new int[] { + pathLabelIndexForWin + 1, + parameterLabelIndex + }; + } + return new int[] { + fileLabelIndex + mode.length() + 1, + parameterLabelIndex + }; + } else { + return null; + } + } + + private ConnectionInfo defaultConnection(int[] databaseNameRangeIndex) { + return new ConnectionInfo( + ComponentsDefine.DERBY_JDBC_DRIVER, DB_TYPE, DEFAULT_HOST, -1, + fetchDatabaseNameFromURL(databaseNameRangeIndex) + ); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/OceanBaseURLParser.java b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/OceanBaseURLParser.java new file mode 100644 index 0000000000..f459c268e8 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/OceanBaseURLParser.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.connectionurl.parser; + +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +public class OceanBaseURLParser extends MysqlURLParser { + public OceanBaseURLParser(String url) { + super(url, "OceanBase", ComponentsDefine.OCEANBASE_JDBC_DRIVER, 2881); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/SqliteURLParser.java b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/SqliteURLParser.java new file mode 100644 index 0000000000..21f44492b3 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/SqliteURLParser.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.connectionurl.parser; + +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; + +public class SqliteURLParser extends AbstractURLParser { + private static final String DEFAULT_HOST = "localhost"; + private static final int DEFAULT_PORT = -1; + private static final String DB_TYPE = "Sqlite"; + + public SqliteURLParser(String url) { + super(url); + } + + @Override + protected URLLocation fetchDatabaseHostsIndexRange() { + return null; + } + + @Override + protected URLLocation fetchDatabaseNameIndexRange() { + int databaseStartTag = url.lastIndexOf("/"); + if (databaseStartTag == -1) { + databaseStartTag = url.lastIndexOf(":"); + } + int databaseEndTag = url.indexOf("?"); + if (databaseEndTag == -1) { + databaseEndTag = url.length(); + } + + return new URLLocation(databaseStartTag + 1, databaseEndTag); + } + + @Override + public ConnectionInfo parse() { + return new ConnectionInfo( + ComponentsDefine.SQLITE_JDBC_DRIVER, DB_TYPE, DEFAULT_HOST, DEFAULT_PORT, fetchDatabaseNameFromURL()); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/SybaseURLParser.java b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/SybaseURLParser.java new file mode 100644 index 0000000000..dfc8a57519 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/SybaseURLParser.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.connectionurl.parser; + +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; + +public class SybaseURLParser extends AbstractURLParser { + private static final int DEFAULT_PORT = 5000; + private static final String DB_TYPE = "Sybase"; + private static final String JDBC_PREFIX = "jdbc:sybase:Tds:"; + + public SybaseURLParser(String url) { + super(url); + } + + @Override + protected URLLocation fetchDatabaseHostsIndexRange() { + int hostLabelStartIndex = JDBC_PREFIX.length(); + int hostLabelEndIndex = url.indexOf("/", hostLabelStartIndex); + int hostLabelEndIndexWithParameter = url.indexOf("?", hostLabelStartIndex); + if (hostLabelEndIndex == -1) { + hostLabelEndIndex = hostLabelEndIndexWithParameter; + } + if (hostLabelEndIndexWithParameter < hostLabelEndIndex && hostLabelEndIndexWithParameter != -1) { + hostLabelEndIndex = hostLabelEndIndexWithParameter; + } + if (hostLabelEndIndex == -1) { + hostLabelEndIndex = url.length(); + } + return new URLLocation(hostLabelStartIndex, hostLabelEndIndex); + } + + protected String fetchDatabaseNameFromURL(int startSize) { + URLLocation hostsLocation = fetchDatabaseNameIndexRange(startSize); + if (hostsLocation == null) { + return ""; + } + return url.substring(hostsLocation.startIndex(), hostsLocation.endIndex()); + } + + protected URLLocation fetchDatabaseNameIndexRange(int startSize) { + int databaseStartTag = url.indexOf("/", startSize); + int parameterStartTag = url.indexOf("?", startSize); + if (parameterStartTag < databaseStartTag && parameterStartTag != -1) { + return null; + } + if (databaseStartTag == -1) { + return null; + } + int databaseEndTag = url.indexOf("?", databaseStartTag); + if (databaseEndTag == -1) { + databaseEndTag = url.length(); + } + return new URLLocation(databaseStartTag + 1, databaseEndTag); + } + + @Override + protected URLLocation fetchDatabaseNameIndexRange() { + int databaseStartTag = url.lastIndexOf("/"); + int databaseEndTag = url.indexOf("?", databaseStartTag); + if (databaseEndTag == -1) { + databaseEndTag = url.length(); + } + return new URLLocation(databaseStartTag + 1, databaseEndTag); + } + + @Override + public ConnectionInfo parse() { + URLLocation location = fetchDatabaseHostsIndexRange(); + String hosts = url.substring(location.startIndex(), location.endIndex()); + String[] hostSegment = hosts.split(","); + if (hostSegment.length > 1) { + StringBuilder sb = new StringBuilder(); + for (String host : hostSegment) { + if (host.split(":").length == 1) { + sb.append(host).append(":").append(DEFAULT_PORT).append(","); + } else { + sb.append(host).append(","); + } + } + return new ConnectionInfo(ComponentsDefine.SYBASE_JDBC_DRIVER, DB_TYPE, sb.substring(0, sb.length() - 1), fetchDatabaseNameFromURL()); + } else { + String[] hostAndPort = hostSegment[0].split(":"); + if (hostAndPort.length != 1) { + return new ConnectionInfo( + ComponentsDefine.SYBASE_JDBC_DRIVER, DB_TYPE, hostAndPort[0], Integer.valueOf(hostAndPort[1]), + fetchDatabaseNameFromURL(location + .endIndex()) + ); + } else { + return new ConnectionInfo( + ComponentsDefine.SYBASE_JDBC_DRIVER, DB_TYPE, hostAndPort[0], DEFAULT_PORT, fetchDatabaseNameFromURL(location + .endIndex())); + } + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/URLParser.java b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/URLParser.java index ddf2f20fc7..83afcf9efa 100644 --- a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/URLParser.java +++ b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/URLParser.java @@ -33,9 +33,15 @@ public class URLParser { private static final String MARIADB_JDBC_URL_PREFIX = "jdbc:mariadb"; private static final String MSSQL_JTDS_URL_PREFIX = "jdbc:jtds:sqlserver:"; private static final String MSSQL_JDBC_URL_PREFIX = "jdbc:sqlserver:"; - private static final String KYLIN_JDBC_URK_PREFIX = "jdbc:kylin"; - private static final String IMPALA_JDBC_URK_PREFIX = "jdbc:impala"; - private static final String CLICKHOUSE_JDBC_URK_PREFIX = "jdbc:clickhouse"; + private static final String KYLIN_JDBC_URL_PREFIX = "jdbc:kylin"; + private static final String IMPALA_JDBC_URL_PREFIX = "jdbc:impala"; + private static final String CLICKHOUSE_JDBC_URL_PREFIX = "jdbc:clickhouse"; + private static final String DERBY_JDBC_URL_PREFIX = "jdbc:derby:"; + private static final String SQLITE_JDBC_URL_PREFIX = "jdbc:sqlite:"; + private static final String DB2_JDBC_URL_PREFIIX = "jdbc:db2:"; + private static final String SYBASE_JDBC_URL_PREFIX = "jdbc:sybase:tds:"; + private static final String OCEANBASE_JDBC_URL_PREFIX = "jdbc:oceanbase:"; + private static final String DM_JDBC_URL_PREFIX = "jdbc:dm:"; public static ConnectionInfo parser(String url) { ConnectionURLParser parser = null; @@ -54,13 +60,26 @@ public static ConnectionInfo parser(String url) { parser = new MssqlJtdsURLParser(url); } else if (lowerCaseUrl.startsWith(MSSQL_JDBC_URL_PREFIX)) { parser = new MssqlJdbcURLParser(url); - } else if (lowerCaseUrl.startsWith(KYLIN_JDBC_URK_PREFIX)) { + } else if (lowerCaseUrl.startsWith(KYLIN_JDBC_URL_PREFIX)) { parser = new KylinJdbcURLParser(url); - } else if (lowerCaseUrl.startsWith(IMPALA_JDBC_URK_PREFIX)) { + } else if (lowerCaseUrl.startsWith(IMPALA_JDBC_URL_PREFIX)) { parser = new ImpalaJdbcURLParser(url); - } else if (lowerCaseUrl.startsWith(CLICKHOUSE_JDBC_URK_PREFIX)) { + } else if (lowerCaseUrl.startsWith(CLICKHOUSE_JDBC_URL_PREFIX)) { parser = new ClickHouseURLParser(url); + } else if (lowerCaseUrl.startsWith(DERBY_JDBC_URL_PREFIX)) { + parser = new DerbyURLParser(url); + } else if (lowerCaseUrl.startsWith(SQLITE_JDBC_URL_PREFIX)) { + parser = new SqliteURLParser(url); + } else if (lowerCaseUrl.startsWith(DB2_JDBC_URL_PREFIIX)) { + parser = new Db2URLParser(url); + } else if (lowerCaseUrl.startsWith(SYBASE_JDBC_URL_PREFIX)) { + parser = new SybaseURLParser(url); + } else if (lowerCaseUrl.startsWith(OCEANBASE_JDBC_URL_PREFIX)) { + parser = new OceanBaseURLParser(url); + } else if (lowerCaseUrl.startsWith(DM_JDBC_URL_PREFIX)) { + parser = new DMURLParser(url); } + return parser.parse(); } } diff --git a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/trace/CallableStatementTracing.java b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/trace/CallableStatementTracing.java index 58bea0fd22..2a3a1f33b0 100644 --- a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/trace/CallableStatementTracing.java +++ b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/trace/CallableStatementTracing.java @@ -23,6 +23,7 @@ import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.plugin.jdbc.SqlBodyUtil; /** * {@link CallableStatementTracing} create an exit span when the client call the method in the class that extend {@link @@ -38,7 +39,7 @@ public static R execute(java.sql.CallableStatement realStatement, Connection Tags.DB_TYPE.set(span, connectInfo.getDBType()); SpanLayer.asDB(span); Tags.DB_INSTANCE.set(span, connectInfo.getDatabaseName()); - Tags.DB_STATEMENT.set(span, sql); + Tags.DB_STATEMENT.set(span, SqlBodyUtil.limitSqlBodySize(sql)); span.setComponent(connectInfo.getComponent()); return exec.exe(realStatement, sql); } catch (SQLException e) { diff --git a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/trace/PreparedStatementTracing.java b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/trace/PreparedStatementTracing.java index 86989b7b3d..7c70cb65b0 100644 --- a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/trace/PreparedStatementTracing.java +++ b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/trace/PreparedStatementTracing.java @@ -26,6 +26,7 @@ import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; import org.apache.skywalking.apm.plugin.jdbc.JDBCPluginConfig; import org.apache.skywalking.apm.plugin.jdbc.PreparedStatementParameterBuilder; +import org.apache.skywalking.apm.plugin.jdbc.SqlBodyUtil; import org.apache.skywalking.apm.plugin.jdbc.define.StatementEnhanceInfos; /** @@ -42,7 +43,7 @@ public static R execute(java.sql.PreparedStatement realStatement, Connection try { Tags.DB_TYPE.set(span, connectInfo.getDBType()); Tags.DB_INSTANCE.set(span, connectInfo.getDatabaseName()); - Tags.DB_STATEMENT.set(span, sql); + Tags.DB_STATEMENT.set(span, SqlBodyUtil.limitSqlBodySize(sql)); span.setComponent(connectInfo.getComponent()); SpanLayer.asDB(span); if (JDBCPluginConfig.Plugin.JDBC.TRACE_SQL_PARAMETERS && Objects.nonNull(statementEnhanceInfos)) { diff --git a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/trace/StatementTracing.java b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/trace/StatementTracing.java index c94b777441..cbac44a6fd 100644 --- a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/trace/StatementTracing.java +++ b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/main/java/org/apache/skywalking/apm/plugin/jdbc/trace/StatementTracing.java @@ -23,6 +23,7 @@ import org.apache.skywalking.apm.agent.core.context.tag.Tags; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.plugin.jdbc.SqlBodyUtil; /** * {@link PreparedStatementTracing} create an exit span when the client call the method in the class that extend {@link @@ -36,7 +37,7 @@ public static R execute(java.sql.Statement realStatement, ConnectionInfo con .getDatabasePeer()); Tags.DB_TYPE.set(span, connectInfo.getDBType()); Tags.DB_INSTANCE.set(span, connectInfo.getDatabaseName()); - Tags.DB_STATEMENT.set(span, sql); + Tags.DB_STATEMENT.set(span, SqlBodyUtil.limitSqlBodySize(sql)); span.setComponent(connectInfo.getComponent()); SpanLayer.asDB(span); return exec.exe(realStatement, sql); diff --git a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/test/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/URLParserTest.java b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/test/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/URLParserTest.java index f679f05cdf..a9a38450e0 100644 --- a/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/test/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/URLParserTest.java +++ b/apm-sniffer/apm-sdk-plugin/jdbc-commons/src/test/java/org/apache/skywalking/apm/plugin/jdbc/connectionurl/parser/URLParserTest.java @@ -27,7 +27,7 @@ public class URLParserTest { @Test public void testParseMysqlJDBCURLWithHost() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:mysql//primaryhost/test"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:mysql//primaryhost/test"); assertThat(connectionInfo.getDBType(), is("Mysql")); assertThat(connectionInfo.getDatabaseName(), is("test")); assertThat(connectionInfo.getDatabasePeer(), is("primaryhost:3306")); @@ -35,7 +35,7 @@ public void testParseMysqlJDBCURLWithHost() { @Test public void testParseMysqlJDBCURLWithoutDB() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:mysql//primaryhost?profileSQL=true"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:mysql//primaryhost?profileSQL=true"); assertThat(connectionInfo.getDBType(), is("Mysql")); assertThat(connectionInfo.getDatabaseName(), is("")); assertThat(connectionInfo.getDatabasePeer(), is("primaryhost:3306")); @@ -43,7 +43,7 @@ public void testParseMysqlJDBCURLWithoutDB() { @Test public void testParseMysqlJDBCURLWithHostAndPort() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:mysql//primaryhost:3307/test?profileSQL=true"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:mysql//primaryhost:3307/test?profileSQL=true"); assertThat(connectionInfo.getDBType(), is("Mysql")); assertThat(connectionInfo.getDatabaseName(), is("test")); assertThat(connectionInfo.getDatabasePeer(), is("primaryhost:3307")); @@ -51,7 +51,7 @@ public void testParseMysqlJDBCURLWithHostAndPort() { @Test public void testParseMysqlJDBCURLWithMultiHost() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:mysql//primaryhost:3307,secondaryhost1,secondaryhost2/test?profileSQL=true"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:mysql//primaryhost:3307,secondaryhost1,secondaryhost2/test?profileSQL=true"); assertThat(connectionInfo.getDBType(), is("Mysql")); assertThat(connectionInfo.getDatabaseName(), is("test")); assertThat(connectionInfo.getDatabasePeer(), is("primaryhost:3307,secondaryhost1:3306,secondaryhost2:3306")); @@ -59,7 +59,7 @@ public void testParseMysqlJDBCURLWithMultiHost() { @Test public void testParseMysqlJDBCURLWitOutDatabase() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:mysql//primaryhost:3307?profileSQL=true"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:mysql//primaryhost:3307?profileSQL=true"); assertThat(connectionInfo.getDBType(), is("Mysql")); assertThat(connectionInfo.getDatabaseName(), is("")); assertThat(connectionInfo.getDatabasePeer(), is("primaryhost:3307")); @@ -67,7 +67,7 @@ public void testParseMysqlJDBCURLWitOutDatabase() { @Test public void testParseMysqlJDBCURLWithConnectorJs() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:mysql:replication://master,slave1,slave2,slave3/test"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:mysql:replication://master,slave1,slave2,slave3/test"); assertThat(connectionInfo.getDBType(), is("Mysql")); assertThat(connectionInfo.getDatabaseName(), is("test")); assertThat(connectionInfo.getDatabasePeer(), is("master:3306,slave1:3306,slave2:3306,slave3:3306")); @@ -75,7 +75,7 @@ public void testParseMysqlJDBCURLWithConnectorJs() { @Test public void testParseOracleJDBCURLWithHost() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:oracle:thin:@localhost:orcl"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:oracle:thin:@localhost:orcl"); assertThat(connectionInfo.getDBType(), is("Oracle")); assertThat(connectionInfo.getDatabaseName(), is("orcl")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:1521")); @@ -83,7 +83,7 @@ public void testParseOracleJDBCURLWithHost() { @Test public void testParseOracleJDBCURLWithHostAndPort() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:oracle:thin:@localhost:1522:orcl"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:oracle:thin:@localhost:1522:orcl"); assertThat(connectionInfo.getDBType(), is("Oracle")); assertThat(connectionInfo.getDatabaseName(), is("orcl")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:1522")); @@ -91,7 +91,7 @@ public void testParseOracleJDBCURLWithHostAndPort() { @Test public void testParseOracleSID() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:oracle:thin:@localhost:1522/orcl"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:oracle:thin:@localhost:1522/orcl"); assertThat(connectionInfo.getDBType(), is("Oracle")); assertThat(connectionInfo.getDatabaseName(), is("orcl")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:1522")); @@ -99,7 +99,7 @@ public void testParseOracleSID() { @Test public void testParseOracleServiceName() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:oracle:thin:@//localhost:1531/orcl"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:oracle:thin:@//localhost:1531/orcl"); assertThat(connectionInfo.getDBType(), is("Oracle")); assertThat(connectionInfo.getDatabaseName(), is("orcl")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:1531")); @@ -107,7 +107,7 @@ public void testParseOracleServiceName() { @Test public void testParseOracleTNSName() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST= localhost )(PORT= 1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=orcl)))"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST= localhost )(PORT= 1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=orcl)))"); assertThat(connectionInfo.getDBType(), is("Oracle")); assertThat(connectionInfo.getDatabaseName(), is("orcl")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:1521")); @@ -115,7 +115,7 @@ public void testParseOracleTNSName() { @Test public void testParseOracleLowerTNSName() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:oracle:thin:@(description=(address=(protocol=tcp)(host= localhost )(port= 1521))(connect_data=(server=dedicated)(service_name=orcl)))"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:oracle:thin:@(description=(address=(protocol=tcp)(host= localhost )(port= 1521))(connect_data=(server=dedicated)(service_name=orcl)))"); assertThat(connectionInfo.getDBType(), is("Oracle")); assertThat(connectionInfo.getDatabaseName(), is("orcl")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:1521")); @@ -123,7 +123,7 @@ public void testParseOracleLowerTNSName() { @Test public void testParseOracleTNSNameWithMultiAddress() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL= TCP)(HOST=hostA)(PORT= 1523 ))(ADDRESS=(PROTOCOL=TCP)(HOST=hostB)(PORT= 1521 )))(SOURCE_ROUTE=yes)(CONNECT_DATA=(SERVICE_NAME=orcl)))"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL= TCP)(HOST=hostA)(PORT= 1523 ))(ADDRESS=(PROTOCOL=TCP)(HOST=hostB)(PORT= 1521 )))(SOURCE_ROUTE=yes)(CONNECT_DATA=(SERVICE_NAME=orcl)))"); assertThat(connectionInfo.getDBType(), is("Oracle")); assertThat(connectionInfo.getDatabaseName(), is("orcl")); assertThat(connectionInfo.getDatabasePeer(), is("hostA:1523,hostB:1521")); @@ -131,7 +131,7 @@ public void testParseOracleTNSNameWithMultiAddress() { @Test public void testParseOracleJDBCURLWithUserNameAndPassword() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:oracle:thin:scott/tiger@myhost:1521:orcl"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:oracle:thin:scott/tiger@myhost:1521:orcl"); assertThat(connectionInfo.getDBType(), is("Oracle")); assertThat(connectionInfo.getDatabaseName(), is("orcl")); assertThat(connectionInfo.getDatabasePeer(), is("myhost:1521")); @@ -139,7 +139,7 @@ public void testParseOracleJDBCURLWithUserNameAndPassword() { @Test public void testParseH2JDBCURLWithEmbedded() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:h2:file:/data/sample"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:h2:file:/data/sample"); assertThat(connectionInfo.getDBType(), is("H2")); assertThat(connectionInfo.getDatabaseName(), is("/data/sample")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:-1")); @@ -147,7 +147,7 @@ public void testParseH2JDBCURLWithEmbedded() { @Test public void testParseH2JDBCURLWithEmbeddedRunningInWindows() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:h2:file:C:/data/sample"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:h2:file:C:/data/sample"); assertThat(connectionInfo.getDBType(), is("H2")); assertThat(connectionInfo.getDatabaseName(), is("C:/data/sample")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:-1")); @@ -155,7 +155,7 @@ public void testParseH2JDBCURLWithEmbeddedRunningInWindows() { @Test public void testParseH2JDBCURLWithMemoryMode() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:h2:mem:test_mem"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:h2:mem:test_mem"); assertThat(connectionInfo.getDBType(), is("H2")); assertThat(connectionInfo.getDatabaseName(), is("test_mem")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:-1")); @@ -163,7 +163,7 @@ public void testParseH2JDBCURLWithMemoryMode() { @Test public void testParseH2JDBCURL() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:h2:tcp://localhost:8084/~/sample"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:h2:tcp://localhost:8084/~/sample"); assertThat(connectionInfo.getDBType(), is("H2")); assertThat(connectionInfo.getDatabaseName(), is("sample")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:8084")); @@ -171,7 +171,7 @@ public void testParseH2JDBCURL() { @Test public void testParseMariadbJDBCURLWithHost() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:mariadb//primaryhost/test"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:mariadb//primaryhost/test"); assertThat(connectionInfo.getDBType(), is("Mariadb")); assertThat(connectionInfo.getDatabaseName(), is("test")); assertThat(connectionInfo.getDatabasePeer(), is("primaryhost:3306")); @@ -179,7 +179,7 @@ public void testParseMariadbJDBCURLWithHost() { @Test public void testParseClickhouseJDBCURL() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:clickhouse://localhost:8123/test"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:clickhouse://localhost:8123/test"); assertThat(connectionInfo.getDBType(), is("ClickHouse")); assertThat(connectionInfo.getDatabaseName(), is("test")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:8123")); @@ -187,7 +187,7 @@ public void testParseClickhouseJDBCURL() { @Test public void testParseImpalaJDBCURL() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:impala://localhost:21050/test;AuthMech=3;UID=UserName;PWD=Password"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:impala://localhost:21050/test;AuthMech=3;UID=UserName;PWD=Password"); assertThat(connectionInfo.getDBType(), is("Impala")); assertThat(connectionInfo.getDatabaseName(), is("test")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:21050")); @@ -195,7 +195,7 @@ public void testParseImpalaJDBCURL() { @Test public void testParseImpalaJDBCURLWithSchema() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:impala://localhost:21050/test"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:impala://localhost:21050/test"); assertThat(connectionInfo.getDBType(), is("Impala")); assertThat(connectionInfo.getDatabaseName(), is("test")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:21050")); @@ -203,14 +203,14 @@ public void testParseImpalaJDBCURLWithSchema() { @Test public void testParseImpalaJDBCURLWithoutSchema() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:impala://localhost:21050"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:impala://localhost:21050"); assertThat(connectionInfo.getDBType(), is("Impala")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:21050")); } @Test public void testParsePostgresqlJDBCURLWithHostAndParams() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:postgresql://localhost:5432/testdb?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&allowMultiQueries=true"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:postgresql://localhost:5432/testdb?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&allowMultiQueries=true"); assertThat(connectionInfo.getDBType(), is("PostgreSQL")); assertThat(connectionInfo.getDatabaseName(), is("testdb")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:5432")); @@ -218,7 +218,7 @@ public void testParsePostgresqlJDBCURLWithHostAndParams() { @Test public void testParsePostgresqlJDBCURLWithMultiHost() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:postgresql://localhost1:5432,localhost2:5433/testdb?target_session_attrs=any&application_name=myapp"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:postgresql://localhost1:5432,localhost2:5433/testdb?target_session_attrs=any&application_name=myapp"); assertThat(connectionInfo.getDBType(), is("PostgreSQL")); assertThat(connectionInfo.getDatabaseName(), is("testdb")); assertThat(connectionInfo.getDatabasePeer(), is("localhost1:5432,localhost2:5433")); @@ -226,7 +226,7 @@ public void testParsePostgresqlJDBCURLWithMultiHost() { @Test public void testParsePostgresqlJDBCURLWithHostNoPort() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:postgresql://localhost/testdb"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:postgresql://localhost/testdb"); assertThat(connectionInfo.getDBType(), is("PostgreSQL")); assertThat(connectionInfo.getDatabaseName(), is("testdb")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:5432")); @@ -234,7 +234,7 @@ public void testParsePostgresqlJDBCURLWithHostNoPort() { @Test public void testParsePostgresqlJDBCURLWithHostNoDb() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:postgresql://localhost:5432"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:postgresql://localhost:5432"); assertThat(connectionInfo.getDBType(), is("PostgreSQL")); assertThat(connectionInfo.getDatabaseName(), is("")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:5432")); @@ -242,7 +242,7 @@ public void testParsePostgresqlJDBCURLWithHostNoDb() { @Test public void testParsePostgresqlJDBCURLWithHostNoPortAndDb() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:postgresql://localhost"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:postgresql://localhost"); assertThat(connectionInfo.getDBType(), is("PostgreSQL")); assertThat(connectionInfo.getDatabaseName(), is("")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:5432")); @@ -250,7 +250,7 @@ public void testParsePostgresqlJDBCURLWithHostNoPortAndDb() { @Test public void testParsePostgresqlJDBCURLEmpty() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:postgresql://"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:postgresql://"); assertThat(connectionInfo.getDBType(), is("PostgreSQL")); assertThat(connectionInfo.getDatabaseName(), is("")); assertThat(connectionInfo.getDatabasePeer(), is(":5432")); @@ -258,7 +258,7 @@ public void testParsePostgresqlJDBCURLEmpty() { @Test public void testParsePostgresqlJDBCURLWithNamedParams() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:postgresql:///testdb?host=localhost&port=5433"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:postgresql:///testdb?host=localhost&port=5433"); assertThat(connectionInfo.getDBType(), is("PostgreSQL")); assertThat(connectionInfo.getDatabaseName(), is("testdb")); assertThat(connectionInfo.getDatabasePeer(), is("localhost:5433")); @@ -266,7 +266,7 @@ public void testParsePostgresqlJDBCURLWithNamedParams() { @Test public void testParsePostgresqlJDBCURLWithSingleIpv6() { - ConnectionInfo connectionInfo = new URLParser().parser("jdbc:postgresql://[2001:db8::1234]/testdb"); + ConnectionInfo connectionInfo = URLParser.parser("jdbc:postgresql://[2001:db8::1234]/testdb"); assertThat(connectionInfo.getDBType(), is("PostgreSQL")); assertThat(connectionInfo.getDatabaseName(), is("testdb")); assertThat(connectionInfo.getDatabasePeer(), is("[2001:db8::1234]:5432")); @@ -274,10 +274,190 @@ public void testParsePostgresqlJDBCURLWithSingleIpv6() { @Test public void testParsePostgresqlJDBCURLWithMultiIpv6() { - ConnectionInfo connectionInfo = new URLParser().parser( + ConnectionInfo connectionInfo = URLParser.parser( "jdbc:postgresql://[2001:db8::1234],[2001:db8::1235]/testdb"); assertThat(connectionInfo.getDBType(), is("PostgreSQL")); assertThat(connectionInfo.getDatabaseName(), is("testdb")); assertThat(connectionInfo.getDatabasePeer(), is("[2001:db8::1234]:5432,[2001:db8::1235]:5432")); } + + @Test + public void testParseOceanBaseJDBCURL() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:oceanbase://localhost:2881/mydb?user=root@sys&password=pass&pool=false&useBulkStmts=true&rewriteBatchedStatements=false&useServerPrepStmts=true"); + assertThat(connectionInfo.getDBType(), is("OceanBase")); + assertThat(connectionInfo.getDatabaseName(), is("mydb")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:2881")); + } + + @Test + public void testParseOceanBaseJDBCURLWithMultiHosts() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:oceanbase://primaryhost:2888,secondaryhost1,secondaryhost2/mydb?user=root@sys&password=pass&pool=false&useBulkStmts=true&rewriteBatchedStatements=false&useServerPrepStmts=true"); + assertThat(connectionInfo.getDBType(), is("OceanBase")); + assertThat(connectionInfo.getDatabaseName(), is("mydb")); + assertThat(connectionInfo.getDatabasePeer(), is("primaryhost:2888,secondaryhost1:2881,secondaryhost2:2881")); + } + + @Test + public void testParseDerbyJDBCURLWithDirMode() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:derby:directory:mydb"); + assertThat(connectionInfo.getDBType(), is("Derby")); + assertThat(connectionInfo.getDatabaseName(), is("mydb")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:-1")); + } + + @Test + public void testParseDerbyJDBCURLWithMemMode() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:derby:memory:mydb;create=true"); + assertThat(connectionInfo.getDBType(), is("Derby")); + assertThat(connectionInfo.getDatabaseName(), is("mydb")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:-1")); + } + + @Test + public void testParseDerbyJDBCURLWithClassPathMode() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:derby:classpath:/test/mydb"); + assertThat(connectionInfo.getDBType(), is("Derby")); + assertThat(connectionInfo.getDatabaseName(), is("mydb")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:-1")); + } + + @Test + public void testParseDerbyJDBCURLWithJarMode() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:derby:jar:(C:/dbs.jar)test/mydb"); + assertThat(connectionInfo.getDBType(), is("Derby")); + assertThat(connectionInfo.getDatabaseName(), is("mydb")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:-1")); + } + + @Test + public void testParseDerbyJDBCURLWithEmbeddedMode() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:derby:test/mydb;create=true"); + assertThat(connectionInfo.getDBType(), is("Derby")); + assertThat(connectionInfo.getDatabaseName(), is("mydb")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:-1")); + } + + @Test + public void testParseDerbyJDBCURLWithMemModeAndClientServerMode() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:derby://localhost:1527/memory:/test/mydb;create=true"); + assertThat(connectionInfo.getDBType(), is("Derby")); + assertThat(connectionInfo.getDatabaseName(), is("mydb")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:1527")); + } + + @Test + public void testParseDerbyJDBCURLWithClientServerMode() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:derby://localhost:1527/mydb;create=true;user=root;password=pass"); + assertThat(connectionInfo.getDBType(), is("Derby")); + assertThat(connectionInfo.getDatabaseName(), is("mydb")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:1527")); + } + + @Test + public void testParseDB2JDBCURL() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:db2://localhost:50000/mydb:user=root;password=pass"); + assertThat(connectionInfo.getDBType(), is("DB2")); + assertThat(connectionInfo.getDatabaseName(), is("mydb")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:50000")); + } + + @Test + public void testParseDB2JDBCURLWithoutHost() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:db2:mydb:user=root;password=pass"); + assertThat(connectionInfo.getDBType(), is("DB2")); + assertThat(connectionInfo.getDatabaseName(), is("mydb")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:50000")); + } + + @Test + public void testParseSqliteJDBCURL() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:sqlite:C/test/mydb.db"); + assertThat(connectionInfo.getDBType(), is("Sqlite")); + assertThat(connectionInfo.getDatabaseName(), is("mydb.db")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:-1")); + } + + @Test + public void testParseSqliteJDBCURLWithMem() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:sqlite::memory:?jdbc.explicit_readonly=true"); + assertThat(connectionInfo.getDBType(), is("Sqlite")); + assertThat(connectionInfo.getDatabaseName(), is("")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:-1")); + } + + @Test + public void testParseSqliteJDBCURLWithResource() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:sqlite::resource:org/test/mydb.db"); + assertThat(connectionInfo.getDBType(), is("Sqlite")); + assertThat(connectionInfo.getDatabaseName(), is("mydb.db")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:-1")); + } + + @Test + public void testParseSybaseJDBCURL() { + ConnectionInfo connectionInfo = URLParser.parser( + "jdbc:sybase:Tds:localhost:5000/mydb?charset=utf-8"); + assertThat(connectionInfo.getDBType(), is("Sybase")); + assertThat(connectionInfo.getDatabaseName(), is("mydb")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:5000")); + } + + @Test + public void testParseDMJDBCURLWithNamedParams() + { + ConnectionInfo connectionInfo = URLParser.parser("jdbc:dm://?host=localhost&port=5236"); + assertThat(connectionInfo.getDBType(), is("DM")); + assertThat(connectionInfo.getDatabaseName(), is("")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:5236")); + } + + @Test + public void testParseDMJDBCURLWithNamedParamsAndScheme() + { + ConnectionInfo connectionInfo = URLParser.parser("jdbc:dm://?host=localhost&port=5236&schema=dm"); + assertThat(connectionInfo.getDBType(), is("DM")); + assertThat(connectionInfo.getDatabaseName(), is("dm")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:5236")); + } + + @Test + public void testParseDMJDBCURLWithoutHost() + { + ConnectionInfo connectionInfo = URLParser.parser("jdbc:dm://localhost"); + assertThat(connectionInfo.getDBType(), is("DM")); + assertThat(connectionInfo.getDatabaseName(), is("")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:5236")); + } + + @Test + public void testParseDMJDBCURLWithoutHostAndScheme() + { + ConnectionInfo connectionInfo = URLParser.parser("jdbc:dm://localhost?schema=dm"); + assertThat(connectionInfo.getDBType(), is("DM")); + assertThat(connectionInfo.getDatabaseName(), is("dm")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:5236")); + } + + @Test + public void testParseDMJDBCURLWithoutHostPort() + { + ConnectionInfo connectionInfo = URLParser.parser("jdbc:dm://localhost:5237?schema=dm"); + assertThat(connectionInfo.getDBType(), is("DM")); + assertThat(connectionInfo.getDatabaseName(), is("dm")); + assertThat(connectionInfo.getDatabasePeer(), is("localhost:5237")); + } } diff --git a/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-2.x-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-2.x-3.x-plugin/pom.xml index 310e07a400..53f72f82af 100644 --- a/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-2.x-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-2.x-3.x-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking jedis-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-4.x-plugin/pom.xml index 1f6cc9db71..4593b1c654 100644 --- a/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-4.x-plugin/pom.xml @@ -21,7 +21,7 @@ jedis-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v4/define/AbstractWitnessInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v4/define/AbstractWitnessInstrumentation.java index 8c736751a3..03e5dc6792 100644 --- a/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v4/define/AbstractWitnessInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v4/define/AbstractWitnessInstrumentation.java @@ -35,8 +35,11 @@ protected String[] witnessClasses() { @Override protected List witnessMethods() { + // Connection.executeCommand(CommandObject) exists in Jedis 4.x+ and 5.x, + // but not in 3.x. Previous witness Pipeline.persist(1) broke in 5.x + // because persist moved from Pipeline to PipeliningBase parent class. return Collections.singletonList(new WitnessMethod( - "redis.clients.jedis.Pipeline", - named("persist").and(takesArguments(1)))); + "redis.clients.jedis.Connection", + named("executeCommand").and(takesArguments(1)))); } } diff --git a/apm-sniffer/apm-sdk-plugin/jedis-plugins/pom.xml b/apm-sniffer/apm-sdk-plugin/jedis-plugins/pom.xml index 56bf312da3..440d8a3232 100644 --- a/apm-sniffer/apm-sdk-plugin/jedis-plugins/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/jedis-plugins/pom.xml @@ -20,14 +20,11 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT - 8 - 8 UTF-8 - /.. 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/jersey-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jersey-2.x-plugin/pom.xml index 0f56f34dbf..f3a8001daf 100644 --- a/apm-sniffer/apm-sdk-plugin/jersey-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/jersey-2.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/jersey-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jersey-3.x-plugin/pom.xml index 36070531c2..94fd991b19 100644 --- a/apm-sniffer/apm-sdk-plugin/jersey-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/jersey-3.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-11.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-11.x-plugin/pom.xml index cde6fd5449..d800a09ddd 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-11.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-11.x-plugin/pom.xml @@ -20,7 +20,7 @@ jetty-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/pom.xml index 199bc45aac..be26052758 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/pom.xml @@ -20,7 +20,7 @@ jetty-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/AsyncHttpRequestSendInterceptor.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/AsyncHttpRequestSendInterceptor.java index 669b43ffda..2858afce44 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/AsyncHttpRequestSendInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/AsyncHttpRequestSendInterceptor.java @@ -58,8 +58,13 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr span.prepareForAsync(); request.attribute(Constants.SW_JETTY_EXIT_SPAN_KEY, span); - Response.CompleteListener callback = (Response.CompleteListener) allArguments[0]; - allArguments[0] = new CompleteListenerWrapper(callback, ContextManager.capture()); + if (allArguments[0] instanceof Response.Listener) { + Response.Listener listener = (Response.Listener) allArguments[0]; + allArguments[0] = new ResponseListenerWrapper(listener, ContextManager.capture()); + } else { + Response.CompleteListener listener = (Response.CompleteListener) allArguments[0]; + allArguments[0] = new CompleteListenerWrapper(listener, ContextManager.capture()); + } } @Override diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/CompleteListenerWrapper.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/CompleteListenerWrapper.java index 0318bd4c00..294b32fe7d 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/CompleteListenerWrapper.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/CompleteListenerWrapper.java @@ -27,11 +27,11 @@ import org.eclipse.jetty.client.api.Result; public class CompleteListenerWrapper implements Response.CompleteListener { - private Response.CompleteListener callback; + private Response.CompleteListener listener; private ContextSnapshot context; - public CompleteListenerWrapper(Response.CompleteListener callback, ContextSnapshot context) { - this.callback = callback; + public CompleteListenerWrapper(Response.CompleteListener listener, ContextSnapshot context) { + this.listener = listener; this.context = context; } @@ -43,9 +43,9 @@ public void onComplete(Result result) { if (context != null) { ContextManager.continued(context); } - if (callback != null) { - callback.onComplete(result); + if (listener != null) { + listener.onComplete(result); } ContextManager.stopSpan(); } -} +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/ResponseListenerWrapper.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/ResponseListenerWrapper.java new file mode 100644 index 0000000000..cf3e097dda --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/ResponseListenerWrapper.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jetty.v90.client; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.http.HttpField; +import java.nio.ByteBuffer; + +public class ResponseListenerWrapper implements Response.Listener { + + private final Response.Listener listener; + + private final ContextSnapshot context; + + public ResponseListenerWrapper(Response.Listener listener, ContextSnapshot context) { + this.listener = listener; + this.context = context; + } + + @Override + public void onComplete(Result result) { + AbstractSpan span = ContextManager.createLocalSpan(Constants.PLUGIN_NAME + "/CompleteListener/onComplete"); + span.setComponent(ComponentsDefine.JETTY_CLIENT); + SpanLayer.asHttp(span); + if (context != null) { + ContextManager.continued(context); + } + if (listener != null) { + listener.onComplete(result); + } + ContextManager.stopSpan(); + } + + @Override + public void onHeaders(Response response) { + listener.onHeaders(response); + } + + @Override + public void onContent(Response response, ByteBuffer content) { + listener.onContent(response, content); + } + + @Override + public void onBegin(Response response) { + listener.onBegin(response); + } + + @Override + public boolean onHeader(Response response, HttpField field) { + return listener.onHeader(response, field); + } + + @Override + public void onSuccess(Response response) { + listener.onSuccess(response); + } + + @Override + public void onFailure(Response response, Throwable failure) { + listener.onFailure(response, failure); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/define/HttpRequestInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/define/HttpRequestInstrumentation.java index ef8daf105a..2bee7ecdc1 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/define/HttpRequestInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/define/HttpRequestInstrumentation.java @@ -41,7 +41,7 @@ public class HttpRequestInstrumentation extends ClassInstanceMethodsEnhancePlugi private static final String ENHANCE_CLASS = "org.eclipse.jetty.client.HttpRequest"; private static final String ENHANCE_CLASS_NAME = "send"; public static final String SYNC_SEND_INTERCEPTOR = - "org.apache.skywalking.apm.plugin.jetty.v90.client.SyncHttpRequestSendV90Interceptor"; + "org.apache.skywalking.apm.plugin.jetty.v90.client.SyncHttpRequestSendInterceptor"; public static final String ASYNC_SEND_INTERCEPTOR = "org.apache.skywalking.apm.plugin.jetty.v90.client.AsyncHttpRequestSendInterceptor"; @@ -85,7 +85,7 @@ public String getMethodsInterceptor() { @Override public boolean isOverrideArgs() { - return false; + return true; } } }; diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/define/ResponseNotifierInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/define/ResponseNotifierInstrumentation.java index c4f39c4147..5502c8dc78 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/define/ResponseNotifierInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/define/ResponseNotifierInstrumentation.java @@ -41,10 +41,10 @@ */ public class ResponseNotifierInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { - private static final String ENHANCE_CLASS = "org.eclipse.jetty.client.tar"; + private static final String ENHANCE_CLASS = "org.eclipse.jetty.client.ResponseNotifier"; private static final String ENHANCE_CLASS_NAME = "notifyComplete"; public static final String SYNC_SEND_INTERCEPTOR = - "org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor"; + "org.apache.skywalking.apm.plugin.jetty.v90.client.ResponseNotifierInterceptor"; @Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/pom.xml index 8e091d09b2..a7f7168743 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/pom.xml @@ -20,7 +20,7 @@ jetty-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -31,7 +31,7 @@ http://maven.apache.org - 9.1.0.v20131115 + 9.2.23.v20171218 diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptor.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptor.java index 6f8870af49..6f39452e0c 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptor.java @@ -57,8 +57,13 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr span.prepareForAsync(); request.attribute(Constants.SW_JETTY_EXIT_SPAN_KEY, span); - Response.CompleteListener callback = (Response.CompleteListener) allArguments[0]; - allArguments[0] = new CompleteListenerWrapper(callback, ContextManager.capture()); + if (allArguments[0] instanceof Response.Listener) { + Response.Listener listener = (Response.Listener) allArguments[0]; + allArguments[0] = new ResponseListenerWrapper(listener, ContextManager.capture()); + } else { + Response.CompleteListener listener = (Response.CompleteListener) allArguments[0]; + allArguments[0] = new CompleteListenerWrapper(listener, ContextManager.capture()); + } } @Override diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/CompleteListenerWrapper.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/CompleteListenerWrapper.java index 50697bf3e4..156e636884 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/CompleteListenerWrapper.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/CompleteListenerWrapper.java @@ -27,11 +27,11 @@ import org.eclipse.jetty.client.api.Result; public class CompleteListenerWrapper implements Response.CompleteListener { - private Response.CompleteListener callback; + private Response.CompleteListener listener; private ContextSnapshot context; - public CompleteListenerWrapper(Response.CompleteListener callback, ContextSnapshot context) { - this.callback = callback; + public CompleteListenerWrapper(Response.CompleteListener listener, ContextSnapshot context) { + this.listener = listener; this.context = context; } @@ -43,9 +43,9 @@ public void onComplete(Result result) { if (context != null) { ContextManager.continued(context); } - if (callback != null) { - callback.onComplete(result); + if (listener != null) { + listener.onComplete(result); } ContextManager.stopSpan(); } -} +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/ResponseListenerWrapper.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/ResponseListenerWrapper.java new file mode 100644 index 0000000000..09102cdc43 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/ResponseListenerWrapper.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jetty.v9.client; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.util.Callback; + +import java.nio.ByteBuffer; + +public class ResponseListenerWrapper implements Response.Listener { + + private final Response.Listener listener; + + private final ContextSnapshot context; + + public ResponseListenerWrapper(Response.Listener listener, ContextSnapshot context) { + this.listener = listener; + this.context = context; + } + + @Override + public void onComplete(Result result) { + AbstractSpan span = ContextManager.createLocalSpan(Constants.PLUGIN_NAME + "/CompleteListener/onComplete"); + span.setComponent(ComponentsDefine.JETTY_CLIENT); + SpanLayer.asHttp(span); + if (context != null) { + ContextManager.continued(context); + } + if (listener != null) { + listener.onComplete(result); + } + ContextManager.stopSpan(); + } + + @Override + public void onHeaders(Response response) { + listener.onHeaders(response); + } + + @Override + public void onContent(Response response, ByteBuffer content, Callback callback) { + listener.onContent(response, content, callback); + } + + @Override + public void onContent(Response response, ByteBuffer content) { + listener.onContent(response, content); + } + + @Override + public void onBegin(Response response) { + listener.onBegin(response); + } + + @Override + public boolean onHeader(Response response, HttpField field) { + return listener.onHeader(response, field); + } + + @Override + public void onSuccess(Response response) { + listener.onSuccess(response); + } + + @Override + public void onFailure(Response response, Throwable failure) { + listener.onFailure(response, failure); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptorTest.java index ae3dc47db1..fb044435da 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptorTest.java @@ -33,6 +33,7 @@ import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpConversation; import org.eclipse.jetty.client.HttpRequest; import org.eclipse.jetty.client.ResponseNotifier; import org.eclipse.jetty.client.api.Response; @@ -82,8 +83,8 @@ public class AsyncHttpRequestSendInterceptorTest { @Before public void setUp() throws Exception { - httpRequestEnhancedInstance = new MockHttpRequest(httpClient, uri); - responseNotifierEnhancedInstance = new MockResponseNotifier(httpClient); + httpRequestEnhancedInstance = new MockHttpRequest(httpClient, new HttpConversation(), uri); + responseNotifierEnhancedInstance = new MockResponseNotifier(); Result results = new Result(httpRequestEnhancedInstance, response); allArguments = new Object[]{(Response.CompleteListener) result -> { }, results}; @@ -146,8 +147,8 @@ private void assertJettySpan() { } private class MockHttpRequest extends HttpRequest implements EnhancedInstance { - public MockHttpRequest(HttpClient httpClient, URI uri) { - super(httpClient, uri); + public MockHttpRequest(HttpClient client, HttpConversation conversation, URI uri) { + super(httpClient, conversation, uri); } @Override @@ -172,8 +173,8 @@ public void setSkyWalkingDynamicField(Object value) { } private class MockResponseNotifier extends ResponseNotifier implements EnhancedInstance { - public MockResponseNotifier(HttpClient client) { - super(client); + public MockResponseNotifier() { + super(); } @Override diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/client/SyncHttpRequestSendInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/client/SyncHttpRequestSendInterceptorTest.java index e4a861fbc7..6f9d2ec489 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/client/SyncHttpRequestSendInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/client/SyncHttpRequestSendInterceptorTest.java @@ -34,6 +34,7 @@ import org.apache.skywalking.apm.agent.test.tools.SpanAssert; import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpConversation; import org.eclipse.jetty.client.HttpRequest; import org.junit.Assert; import org.junit.Before; @@ -67,7 +68,7 @@ public class SyncHttpRequestSendInterceptorTest { @Before public void setUp() throws Exception { - enhancedInstance = new MockHttpRequest(httpClient, uri); + enhancedInstance = new MockHttpRequest(httpClient, new HttpConversation(), uri); allArguments = new Object[] { "OperationKey", "OperationValue" @@ -123,8 +124,8 @@ public void testMethodsAroundError() throws Throwable { } private class MockHttpRequest extends HttpRequest implements EnhancedInstance { - public MockHttpRequest(HttpClient httpClient, URI uri) { - super(httpClient, uri); + public MockHttpRequest(HttpClient client, HttpConversation conversation, URI uri) { + super(httpClient, conversation, uri); } @Override diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/pom.xml new file mode 100644 index 0000000000..dfc04b5933 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/pom.xml @@ -0,0 +1,52 @@ + + + + + jetty-plugins + org.apache.skywalking + 9.7.0-SNAPSHOT + + 4.0.0 + + apm-jetty-server-12.x-plugin + jar + + jetty-server-12.x-plugin + http://maven.apache.org + + + + 12.0.36 + + + + + org.eclipse.jetty + jetty-server + ${jetty-server.version} + provided + + + diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v12/server/HandleInterceptor.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v12/server/HandleInterceptor.java new file mode 100644 index 0000000000..74e08614dd --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v12/server/HandleInterceptor.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jetty.v12.server; + +import java.lang.reflect.Method; +import org.apache.skywalking.apm.agent.core.context.CarrierItem; +import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +/** + * Enhances the core, namespace-neutral {@code org.eclipse.jetty.server.Server#handle(Request, Response, + * Callback)} — the single top-level entry invoked once per request in Jetty 12. Handling is async, so + * the entry span is prepared for async in beforeMethod, its synchronous segment ends in afterMethod, and + * it is finished (with the response status) when the wrapped {@link SwCallback} completes. + */ +public class HandleInterceptor implements InstanceMethodsAroundInterceptor { + + private static final ThreadLocal HOLDER = new ThreadLocal(); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + Request request = (Request) allArguments[0]; + Response response = (Response) allArguments[1]; + Callback callback = (Callback) allArguments[2]; + + ContextCarrier contextCarrier = new ContextCarrier(); + HttpFields headers = request.getHeaders(); + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + next.setHeadValue(headers.get(next.getHeadKey())); + } + + HttpURI uri = request.getHttpURI(); + AbstractSpan span = ContextManager.createEntrySpan(uri.getPath(), contextCarrier); + Tags.URL.set(span, uri.asString()); + Tags.HTTP.METHOD.set(span, request.getMethod()); + span.setComponent(ComponentsDefine.JETTY_SERVER); + SpanLayer.asHttp(span); + span.prepareForAsync(); + + SwCallback swCallback = new SwCallback(callback, response, span); + allArguments[2] = swCallback; + HOLDER.set(swCallback); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + SwCallback swCallback = HOLDER.get(); + HOLDER.remove(); + ContextManager.stopSpan(); + // ret == true: handled; the wrapped callback fires on response completion and finishes the span. + // ret == false: not handled — Jetty writes 404 on the original callback, bypassing ours. + // ret == null: handle() threw (see handleMethodException) — Jetty writes 500. + // For the latter two the wrapped callback never fires and the status is written only after + // handle() returns, so finish here with the status Jetty's contract guarantees. + if (swCallback != null && !Boolean.TRUE.equals(ret)) { + swCallback.finishWithStatus(ret == null ? 500 : 404); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + AbstractSpan span = ContextManager.activeSpan(); + span.log(t); + span.errorOccurred(); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v12/server/SwCallback.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v12/server/SwCallback.java new file mode 100644 index 0000000000..f5193d7c05 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v12/server/SwCallback.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jetty.v12.server; + +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.thread.Invocable; + +/** + * Wraps the {@link Callback} passed to {@code Server#handle}. Jetty 12 request handling is async — the + * response status is only known when the callback completes on the (possibly different) response thread. + * On completion this reads the status, records it on the entry span and finishes the async span. It + * delegates every method to the original callback so Jetty's threading model is preserved. + */ +public class SwCallback implements Callback { + + private final Callback delegate; + private final Response response; + private final AbstractSpan span; + private final AtomicBoolean finished = new AtomicBoolean(); + + public SwCallback(Callback delegate, Response response, AbstractSpan span) { + this.delegate = delegate; + this.response = response; + this.span = span; + } + + @Override + public void succeeded() { + finishOnce(); + delegate.succeeded(); + } + + @Override + public void failed(Throwable x) { + if (finished.compareAndSet(false, true)) { + span.log(x); + span.errorOccurred(); + Tags.HTTP_RESPONSE_STATUS_CODE.set(span, response.getStatus()); + span.asyncFinish(); + } + delegate.failed(x); + } + + @Override + public Invocable.InvocationType getInvocationType() { + return delegate.getInvocationType(); + } + + /** + * Records the response status and finishes the async span exactly once. Safe to call from either the + * callback completion or the interceptor's afterMethod fallback (not-handled / exception paths). + */ + void finishOnce() { + if (finished.compareAndSet(false, true)) { + int statusCode = response.getStatus(); + Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode); + if (statusCode >= 400) { + span.errorOccurred(); + } + span.asyncFinish(); + } + } + + /** + * Finishes the async span exactly once with an explicit status. Used for the not-handled / exception + * fallback paths where Jetty writes the error status on the original callback after {@code handle()} + * returns — so the wrapped callback never fires and {@link Response#getStatus()} is still 0 (uncommitted). + */ + void finishWithStatus(int statusCode) { + if (finished.compareAndSet(false, true)) { + Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode); + if (statusCode >= 400) { + span.errorOccurred(); + } + span.asyncFinish(); + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v12/server/define/Jetty12ServerInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v12/server/define/Jetty12ServerInstrumentation.java new file mode 100644 index 0000000000..8b4d347af9 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v12/server/define/Jetty12ServerInstrumentation.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jetty.v12.server.define; + +import java.util.Collections; +import java.util.List; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * Enhances {@code org.eclipse.jetty.server.Server#handle(Request, Response, Callback)} by + * {@code HandleInterceptor}. Only Jetty 12+ declares this three-argument, {@code Callback}-terminated + * signature, so the witness method both activates the plugin exclusively on Jetty 12 and keeps it from + * overlapping the merged jetty-server plugin (which enhances the Jetty 9/10/11 {@code HttpChannel}, + * removed in Jetty 12). + */ +public class Jetty12ServerInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.eclipse.jetty.server.Server"; + private static final String ENHANCE_METHOD = "handle"; + private static final String CALLBACK_CLASS = "org.eclipse.jetty.util.Callback"; + private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.jetty.v12.server.HandleInterceptor"; + + @Override + protected List witnessMethods() { + return Collections.singletonList(new WitnessMethod( + ENHANCE_CLASS, + named(ENHANCE_METHOD).and(takesArgument(2, named(CALLBACK_CLASS))) + )); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(ENHANCE_METHOD).and(takesArguments(3)).and(takesArgument(2, named(CALLBACK_CLASS))); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..2c466d5f66 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-12.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +jetty-server-12.x=org.apache.skywalking.apm.plugin.jetty.v12.server.define.Jetty12ServerInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/pom.xml new file mode 100644 index 0000000000..8477c1b9b8 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/pom.xml @@ -0,0 +1,58 @@ + + + + + jetty-plugins + org.apache.skywalking + 9.7.0-SNAPSHOT + + 4.0.0 + + apm-jetty-server-plugin + jar + + jetty-server-plugin + http://maven.apache.org + + + + 11.0.15 + + + + + org.eclipse.jetty + jetty-server + ${jetty-server.version} + provided + + + org.apache.skywalking + apm-servlet-commons + ${project.version} + provided + + + diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/Constants.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/Constants.java similarity index 94% rename from apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/Constants.java rename to apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/Constants.java index 3aa2d92ae3..22948d4a7d 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/Constants.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/Constants.java @@ -17,7 +17,7 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat10x; +package org.apache.skywalking.apm.plugin.jetty.server; public class Constants { public static final String FORWARD_REQUEST_FLAG = "SW_FORWARD_REQUEST_FLAG"; diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/ForwardInterceptor.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/ForwardInterceptor.java similarity index 97% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/ForwardInterceptor.java rename to apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/ForwardInterceptor.java index 669fae1770..0855f9961a 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/ForwardInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/ForwardInterceptor.java @@ -17,11 +17,8 @@ * */ -package org.apache.skywalking.apm.plugin.jetty.v9.server; +package org.apache.skywalking.apm.plugin.jetty.server; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; import org.apache.skywalking.apm.agent.core.context.ContextManager; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; @@ -29,6 +26,10 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + public class ForwardInterceptor implements InstanceMethodsAroundInterceptor, InstanceConstructorInterceptor { @Override diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/HandleInterceptor.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/HandleInterceptor.java similarity index 75% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/HandleInterceptor.java rename to apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/HandleInterceptor.java index a8f663a917..386587d270 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/HandleInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/HandleInterceptor.java @@ -16,10 +16,8 @@ * */ -package org.apache.skywalking.apm.plugin.jetty.v11.server; +package org.apache.skywalking.apm.plugin.jetty.server; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.apache.skywalking.apm.agent.core.context.CarrierItem; import org.apache.skywalking.apm.agent.core.context.ContextCarrier; import org.apache.skywalking.apm.agent.core.context.ContextManager; @@ -30,6 +28,10 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.servlet.HttpRequestWrapper; +import org.apache.skywalking.apm.plugin.servlet.HttpRequestWrappers; +import org.apache.skywalking.apm.plugin.servlet.HttpResponseWrapper; +import org.apache.skywalking.apm.plugin.servlet.HttpResponseWrappers; import org.eclipse.jetty.server.HttpChannel; import java.lang.reflect.Method; @@ -40,7 +42,9 @@ public class HandleInterceptor implements InstanceMethodsAroundInterceptor { public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { HttpChannel httpChannel = (HttpChannel) objInst; - HttpServletRequest servletRequest = httpChannel.getRequest(); + // getRequest() returns the concrete org.eclipse.jetty.server.Request; the wrapper resolves the + // javax vs jakarta servlet namespace at runtime, keeping this interceptor namespace-neutral. + HttpRequestWrapper servletRequest = HttpRequestWrappers.wrap(httpChannel.getRequest()); ContextCarrier contextCarrier = new ContextCarrier(); @@ -61,11 +65,15 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { HttpChannel httpChannel = (HttpChannel) objInst; - HttpServletResponse servletResponse = httpChannel.getResponse(); AbstractSpan span = ContextManager.activeSpan(); - Tags.HTTP_RESPONSE_STATUS_CODE.set(span, servletResponse.getStatus()); - if (servletResponse.getStatus() >= 400) { - span.errorOccurred(); + // A null wrapper means getStatus() is unavailable (pre-Servlet-3.0), matching the historical guard. + HttpResponseWrapper servletResponse = HttpResponseWrappers.wrap(httpChannel.getResponse()); + if (servletResponse != null) { + int statusCode = servletResponse.getStatus(); + Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode); + if (statusCode >= 400) { + span.errorOccurred(); + } } ContextManager.stopSpan(); ContextManager.getRuntimeContext().remove(Constants.FORWARD_REQUEST_FLAG); diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/define/AbstractWitnessInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/define/AbstractWitnessInstrumentation.java similarity index 75% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/define/AbstractWitnessInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/define/AbstractWitnessInstrumentation.java index 9ed1aba497..3a0993fee7 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/define/AbstractWitnessInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/define/AbstractWitnessInstrumentation.java @@ -17,7 +17,7 @@ * */ -package org.apache.skywalking.apm.plugin.jetty.v9.server.define; +package org.apache.skywalking.apm.plugin.jetty.server.define; import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; @@ -31,9 +31,13 @@ public abstract class AbstractWitnessInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { @Override protected List witnessMethods() { + // Match the Jetty Handler#handle whose request argument is either the javax (Jetty 9/10) or the + // jakarta (Jetty 11) servlet namespace, so a single merged plugin activates on both. A single + // Jetty distribution carries only one namespace, so exactly one branch matches at runtime. return Collections.singletonList(new WitnessMethod( "org.eclipse.jetty.server.Handler", - named("handle").and(takesArgument(2, named("javax.servlet.http.HttpServletRequest"))) + named("handle").and(takesArgument(2, named("javax.servlet.http.HttpServletRequest") + .or(named("jakarta.servlet.http.HttpServletRequest")))) )); } } diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/define/DispatcherInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/define/DispatcherInstrumentation.java similarity index 96% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/define/DispatcherInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/define/DispatcherInstrumentation.java index d843ae629f..ba468ad1b3 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/define/DispatcherInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/define/DispatcherInstrumentation.java @@ -17,7 +17,7 @@ * */ -package org.apache.skywalking.apm.plugin.jetty.v11.server.define; +package org.apache.skywalking.apm.plugin.jetty.server.define; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -32,7 +32,7 @@ public class DispatcherInstrumentation extends AbstractWitnessInstrumentation { private static final String ENHANCE_CLASS = "org.eclipse.jetty.server.Dispatcher"; - public static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.jetty.v11.server.ForwardInterceptor"; + public static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.jetty.server.ForwardInterceptor"; @Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/define/JettyInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/define/JettyInstrumentation.java similarity index 96% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/define/JettyInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/define/JettyInstrumentation.java index 417dc67df3..1c8eb083ac 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/define/JettyInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/server/define/JettyInstrumentation.java @@ -16,7 +16,7 @@ * */ -package org.apache.skywalking.apm.plugin.jetty.v11.server.define; +package org.apache.skywalking.apm.plugin.jetty.server.define; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -35,7 +35,7 @@ public class JettyInstrumentation extends AbstractWitnessInstrumentation { private static final String ENHANCE_CLASS = "org.eclipse.jetty.server.HttpChannel"; private static final String ENHANCE_METHOD = "handle"; - private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.jetty.v11.server" + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.jetty.server" + ".HandleInterceptor"; @Override diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..f54cd54edb --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +jetty-server=org.apache.skywalking.apm.plugin.jetty.server.define.JettyInstrumentation +jetty-server=org.apache.skywalking.apm.plugin.jetty.server.define.DispatcherInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/server/HandleInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/server/HandleInterceptorTest.java similarity index 89% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/server/HandleInterceptorTest.java rename to apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/server/HandleInterceptorTest.java index f4c8fef0dd..77100bf9d0 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/server/HandleInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/server/HandleInterceptorTest.java @@ -16,7 +16,7 @@ * */ -package org.apache.skywalking.apm.plugin.jetty.v9.server; +package org.apache.skywalking.apm.plugin.jetty.server; import static org.apache.skywalking.apm.agent.test.tools.SpanAssert.assertComponent; import static org.hamcrest.CoreMatchers.is; @@ -44,7 +44,6 @@ import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.server.HttpTransport; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; @@ -87,6 +86,7 @@ public void setUp() throws Exception { jettyInvokeInterceptor = new HandleInterceptor(); when(request.getRequestURI()).thenReturn("/test/testRequestURL"); when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/testRequestURL")); + when(request.getMethod()).thenReturn("GET"); when(response.getStatus()).thenReturn(200); when(service.getResponse()).thenReturn(response); when(service.getRequest()).thenReturn(request); @@ -152,11 +152,18 @@ private void assertHttpSpan(AbstractTracingSpan span) { SpanAssert.assertLayer(span, SpanLayer.HTTP); } - public static class MockService extends HttpChannel implements EnhancedInstance { + /** + * The merged {@link HandleInterceptor} casts the enhanced instance to the concrete Jetty + * {@link HttpChannel} and reads the request/response through the servlet-commons wrappers, so the + * mock target must be a Jetty 11 (jakarta) {@link HttpChannel} whose {@link #getRequest()} / + * {@link #getResponse()} return jakarta servlet request/response objects. Declared abstract to + * skip Jetty 11 {@code HttpChannel}'s abstract members, which Mockito stubs automatically. + */ + public abstract static class MockService extends HttpChannel implements EnhancedInstance { public MockService(Connector connector, HttpConfiguration configuration, EndPoint endPoint, - HttpTransport transport, HttpInput input) { - super(connector, configuration, endPoint, transport, input); + HttpTransport transport) { + super(connector, configuration, endPoint, transport); } @Override diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jetty-plugin/pom.xml index d939bf88b5..671cc92f91 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/pom.xml @@ -23,15 +23,15 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT jetty-plugins jetty-client-9.x-plugin - jetty-server-9.x-plugin jetty-client-9.0-plugin - jetty-server-11.x-plugin + jetty-server-plugin + jetty-server-12.x-plugin jetty-client-11.x-plugin pom @@ -41,6 +41,5 @@ UTF-8 - /.. diff --git a/apm-sniffer/apm-sdk-plugin/jetty-thread-pool-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jetty-thread-pool-plugin/pom.xml index 101dcaae14..533868c65d 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-thread-pool-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/jetty-thread-pool-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/jsonrpc4j-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jsonrpc4j-1.x-plugin/pom.xml index 533e961137..f4ef9b5118 100644 --- a/apm-sniffer/apm-sdk-plugin/jsonrpc4j-1.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/jsonrpc4j-1.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/kafka-commons/pom.xml b/apm-sniffer/apm-sdk-plugin/kafka-commons/pom.xml index bf86caceda..9870095054 100644 --- a/apm-sniffer/apm-sdk-plugin/kafka-commons/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/kafka-commons/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-kafka-commons diff --git a/apm-sniffer/apm-sdk-plugin/kafka-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/kafka-plugin/pom.xml index c5e17d52ed..9c03ef164a 100644 --- a/apm-sniffer/apm-sdk-plugin/kafka-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/kafka-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/java/org/apache/skywalking/apm/plugin/kafka/define/ClassicKafkaConsumerInstrumentation.java b/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/java/org/apache/skywalking/apm/plugin/kafka/define/ClassicKafkaConsumerInstrumentation.java new file mode 100644 index 0000000000..c2092c6de6 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/java/org/apache/skywalking/apm/plugin/kafka/define/ClassicKafkaConsumerInstrumentation.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.kafka.define; + +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * For Kafka 3.9.x change + * + *

+ *  1. The class named LegacyKafkaConsumer was rename to ClassicKafkaConsumer
+ *  2. Because of the enhance class was changed, so we should create new Instrumentation to enhance the new class
+ * 
+ */ +public class ClassicKafkaConsumerInstrumentation extends KafkaConsumerInstrumentation { + private static final String ENHANCE_CLASS_39_CLASSIC = "org.apache.kafka.clients.consumer.internals.ClassicKafkaConsumer"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS_39_CLASSIC); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/java/org/apache/skywalking/apm/plugin/kafka/define/Kafka37AsyncConsumerInstrumentation.java b/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/java/org/apache/skywalking/apm/plugin/kafka/define/Kafka37AsyncConsumerInstrumentation.java new file mode 100644 index 0000000000..7c24a135ac --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/java/org/apache/skywalking/apm/plugin/kafka/define/Kafka37AsyncConsumerInstrumentation.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.kafka.define; + +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * For Kafka 3.7.x change + * + *
+ *  1. The method named pollForFetchs was removed from KafkaConsumer to AsyncKafkaConsumer and LegacyKafkaConsumer
+ *  2. Because of the enhance class was changed, so we should create new Instrumentation to intercept the method
+ * 
+ */ +public class Kafka37AsyncConsumerInstrumentation extends KafkaConsumerInstrumentation { + + private static final String ENHANCE_CLASS_37_ASYNC = "org.apache.kafka.clients.consumer.internals.AsyncKafkaConsumer"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS_37_ASYNC); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/java/org/apache/skywalking/apm/plugin/kafka/define/Kafka37LegacyConsumerInstrumentation.java b/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/java/org/apache/skywalking/apm/plugin/kafka/define/Kafka37LegacyConsumerInstrumentation.java new file mode 100644 index 0000000000..95f33bfe82 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/java/org/apache/skywalking/apm/plugin/kafka/define/Kafka37LegacyConsumerInstrumentation.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.kafka.define; + +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * For Kafka 3.7.x change + * + *
+ *  1. The method named pollForFetchs was removed from KafkaConsumer to AsyncKafkaConsumer and LegacyKafkaConsumer
+ *  2. Because of the enhance class was changed, so we should create new Instrumentation to intercept the method
+ * 
+ */ +public class Kafka37LegacyConsumerInstrumentation extends KafkaConsumerInstrumentation { + + private static final String ENHANCE_CLASS_37_LEGACY = "org.apache.kafka.clients.consumer.internals.LegacyKafkaConsumer"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS_37_LEGACY); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/resources/skywalking-plugin.def index d807aceefe..bb98b86a97 100644 --- a/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/apm-sdk-plugin/kafka-plugin/src/main/resources/skywalking-plugin.def @@ -18,4 +18,7 @@ kafka-0.11.x/1.x/2.x=org.apache.skywalking.apm.plugin.kafka.define.CallbackInstr kafka-0.11.x/1.x/2.x=org.apache.skywalking.apm.plugin.kafka.define.KafkaConsumerInstrumentation kafka-0.11.x/1.x/2.x=org.apache.skywalking.apm.plugin.kafka.define.KafkaProducerInstrumentation kafka-0.11.x/1.x/2.x=org.apache.skywalking.apm.plugin.kafka.define.KafkaProducerMapInstrumentation -kafka-0.11.x/1.x/2.x=org.apache.skywalking.apm.plugin.kafka.define.KafkaTemplateCallbackInstrumentation \ No newline at end of file +kafka-0.11.x/1.x/2.x=org.apache.skywalking.apm.plugin.kafka.define.KafkaTemplateCallbackInstrumentation +kafka-3.7.x=org.apache.skywalking.apm.plugin.kafka.define.Kafka37AsyncConsumerInstrumentation +kafka-3.7.x=org.apache.skywalking.apm.plugin.kafka.define.Kafka37LegacyConsumerInstrumentation +kafka-3.9.x=org.apache.skywalking.apm.plugin.kafka.define.ClassicKafkaConsumerInstrumentation \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/kylin-jdbc-2.6.x-3.x-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/kylin-jdbc-2.6.x-3.x-4.x-plugin/pom.xml index 74ee2c3c55..54d4d9e605 100644 --- a/apm-sniffer/apm-sdk-plugin/kylin-jdbc-2.6.x-3.x-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/kylin-jdbc-2.6.x-3.x-4.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/pom.xml new file mode 100644 index 0000000000..c2158fcb4a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/pom.xml @@ -0,0 +1,53 @@ + + + + + 4.0.0 + + org.apache.skywalking + lettuce-plugins + 9.7.0-SNAPSHOT + + + apm-lettuce-5.x-6.4.x-plugin + jar + + lettuce-5.x-6.4.x-plugin + + + 5.0.0.RELEASE + + + + + org.apache.skywalking + apm-lettuce-common + ${project.version} + provided + + + + io.lettuce + lettuce-core + ${lettuce-core.version} + provided + + + + \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisChannelWriterInterceptorV5.java b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisChannelWriterInterceptorV5.java new file mode 100644 index 0000000000..d4984d6fd0 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisChannelWriterInterceptorV5.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.lettuce.v5; + +import io.lettuce.core.protocol.ProtocolKeyword; +import org.apache.skywalking.apm.plugin.lettuce.common.RedisChannelWriterInterceptor; + +public class RedisChannelWriterInterceptorV5 extends RedisChannelWriterInterceptor { + @Override + protected String getCommandName(final ProtocolKeyword protocol) { + return protocol.name(); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisReactiveCreatePublisherMethodInterceptorV5.java b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisReactiveCreatePublisherMethodInterceptorV5.java new file mode 100644 index 0000000000..12ddea18f7 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/RedisReactiveCreatePublisherMethodInterceptorV5.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.lettuce.v5; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; + +import java.lang.reflect.Method; +import java.util.function.Function; + +/** + * Intercepts reactive publisher factory methods (createMono/createFlux) + * to ensure the SkyWalking context snapshot is propagated via Reactor Context. + * + *

If the Reactor Context does not already contain a snapshot, this interceptor + * captures the current active context and writes it into the subscriber context + * as a fallback propagation mechanism.

+ */ +public class RedisReactiveCreatePublisherMethodInterceptorV5 implements InstanceMethodsAroundInterceptorV2 { + + private static final String SNAPSHOT_KEY = "SKYWALKING_CONTEXT_SNAPSHOT"; + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInvocationContext context) { + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret, MethodInvocationContext context) { + + if (!(ret instanceof Mono) && !(ret instanceof Flux)) { + return ret; + } + final AbstractSpan localSpan = ContextManager.createLocalSpan("Lettuce/Reactive/" + method.getName()); + localSpan.setComponent(ComponentsDefine.LETTUCE); + SpanLayer.asCache(localSpan); + + try { + final ContextSnapshot snapshot = ContextManager.capture(); + + Function contextFunction = ctx -> { + if (ctx.hasKey(SNAPSHOT_KEY)) { + return ctx; + } + return ctx.put(SNAPSHOT_KEY, snapshot); + }; + + if (ret instanceof Mono) { + Mono original = (Mono) ret; + return original.subscriberContext(contextFunction); + } else { + Flux original = (Flux) ret; + return original.subscriberContext(contextFunction); + } + } finally { + ContextManager.stopSpan(); + } + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] + argumentsTypes, Throwable t, MethodInvocationContext context) { + } +} diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/define/RedisChannelWriterInstrumentation.java b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/define/RedisChannelWriterInstrumentationV5.java similarity index 81% rename from apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/define/RedisChannelWriterInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/define/RedisChannelWriterInstrumentationV5.java index e0a4d8c819..bf3e52f79c 100644 --- a/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/define/RedisChannelWriterInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/define/RedisChannelWriterInstrumentationV5.java @@ -18,27 +18,26 @@ package org.apache.skywalking.apm.plugin.lettuce.v5.define; +import java.util.Collections; +import java.util.List; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; -import java.util.List; - import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch.byHierarchyMatch; -/** - * The writeAndFlush method is used in versions lower than 5.0.2.RELEASE - */ -public class RedisChannelWriterInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { +public class RedisChannelWriterInstrumentationV5 extends ClassInstanceMethodsEnhancePluginDefine { private static final String ENHANCE_CLASS = "io.lettuce.core.RedisChannelWriter"; - private static final String REDIS_CHANNEL_WRITER_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.lettuce.v5.RedisChannelWriterInterceptor"; + private static final String REDIS_CHANNEL_WRITER_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.lettuce.v5.RedisChannelWriterInterceptorV5"; @Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { @@ -47,12 +46,12 @@ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { @Override public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { - return new InstanceMethodsInterceptPoint[]{ + return new InstanceMethodsInterceptPoint[] { new InstanceMethodsInterceptPoint() { @Override public ElementMatcher getMethodsMatcher() { return named("write").and(takesArgument(0, named("io.lettuce.core.protocol.RedisCommand")).or( - takesArgument(0, List.class))); + takesArgument(0, List.class))); } @Override @@ -65,11 +64,19 @@ public boolean isOverrideArgs() { return false; } }, - }; + }; } @Override public ClassMatch enhanceClass() { return byHierarchyMatch(ENHANCE_CLASS); } + + @Override + protected List witnessMethods() { + return Collections.singletonList(new WitnessMethod( + "io.lettuce.core.protocol.ProtocolKeyword", + ElementMatchers.named("name") + )); + } } diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/define/RedisReactiveCommandsInstrumentationV5.java b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/define/RedisReactiveCommandsInstrumentationV5.java new file mode 100644 index 0000000000..65716edd68 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/define/RedisReactiveCommandsInstrumentationV5.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.lettuce.v5.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.ClassInstanceMethodsEnhancePluginDefineV2; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import java.util.Collections; +import java.util.List; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch.byHierarchyMatch; + +/** + * + */ +public class RedisReactiveCommandsInstrumentationV5 extends ClassInstanceMethodsEnhancePluginDefineV2 { + + private static final String ENHANCE_CLASS = "io.lettuce.core.AbstractRedisReactiveCommands"; + + private static final String REDIS_REACTIVE_CREATEMONO_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.lettuce.v5.RedisReactiveCreatePublisherMethodInterceptorV5"; + + @Override + public InstanceMethodsInterceptV2Point[] getInstanceMethodsInterceptV2Points() { + return new InstanceMethodsInterceptV2Point[]{ + new InstanceMethodsInterceptV2Point() { + @Override + public ElementMatcher getMethodsMatcher() { + return namedOneOf( + "createMono", + "createFlux", + "createDissolvingFlux" + ).and(takesArguments(1)); + } + + @Override + public String getMethodsInterceptorV2() { + return REDIS_REACTIVE_CREATEMONO_METHOD_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } + + @Override + public ClassMatch enhanceClass() { + return byHierarchyMatch(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + protected List witnessMethods() { + return Collections.singletonList(new WitnessMethod( + "reactor.core.publisher.Mono", + named("subscriberContext") + )); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..006ef23680 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-5.x-6.4.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +lettuce-5.x-6.4.x=org.apache.skywalking.apm.plugin.lettuce.v5.define.RedisChannelWriterInstrumentationV5 +lettuce-5.x-6.4.x=org.apache.skywalking.apm.plugin.lettuce.v5.define.RedisReactiveCommandsInstrumentationV5 \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/pom.xml new file mode 100644 index 0000000000..2bf20982cf --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/pom.xml @@ -0,0 +1,53 @@ + + + + + 4.0.0 + + org.apache.skywalking + lettuce-plugins + 9.7.0-SNAPSHOT + + apm-lettuce-6.5.x-plugin + + jar + + lettuce-6.5.x-plugin + + + 6.5.0.RELEASE + + + + + org.apache.skywalking + apm-lettuce-common + ${project.version} + provided + + + + io.lettuce + lettuce-core + ${lettuce-core.version} + provided + + + + \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v65/RedisChannelWriterInterceptorV65.java b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v65/RedisChannelWriterInterceptorV65.java new file mode 100644 index 0000000000..2205fa3519 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v65/RedisChannelWriterInterceptorV65.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.lettuce.v65; + +import io.lettuce.core.protocol.ProtocolKeyword; +import org.apache.skywalking.apm.plugin.lettuce.common.RedisChannelWriterInterceptor; + +public class RedisChannelWriterInterceptorV65 extends RedisChannelWriterInterceptor { + @Override + protected String getCommandName(final ProtocolKeyword protocol) { + return protocol.toString(); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v65/RedisReactiveCreatePublisherMethodInterceptorV65.java b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v65/RedisReactiveCreatePublisherMethodInterceptorV65.java new file mode 100644 index 0000000000..36320b5cd1 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v65/RedisReactiveCreatePublisherMethodInterceptorV65.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.lettuce.v65; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.lang.reflect.Method; + +/** + * Intercepts reactive publisher factory methods (createMono/createFlux) + * to ensure the SkyWalking context snapshot is propagated via Reactor Context. + * + *

If the Reactor Context does not already contain a snapshot, this interceptor + * captures the current active context and writes it into the subscriber context + * as a fallback propagation mechanism.

+ */ +public class RedisReactiveCreatePublisherMethodInterceptorV65 implements InstanceMethodsAroundInterceptorV2 { + + private static final String SNAPSHOT_KEY = "SKYWALKING_CONTEXT_SNAPSHOT"; + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInvocationContext context) { + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret, MethodInvocationContext context) { + + if (!(ret instanceof Mono) && !(ret instanceof Flux)) { + return ret; + } + + final AbstractSpan localSpan = ContextManager.createLocalSpan("Lettuce/Reactive/" + method.getName()); + localSpan.setComponent(ComponentsDefine.LETTUCE); + SpanLayer.asCache(localSpan); + + try { + final ContextSnapshot snapshot = ContextManager.capture(); + + return wrapPublisher((Publisher) ret, snapshot); + } finally { + ContextManager.stopSpan(); + } + } + + private Publisher wrapPublisher(Publisher original, ContextSnapshot snapshot) { + if (original instanceof Mono) { + return Mono.deferContextual(ctxView -> { + if (ctxView.hasKey(SNAPSHOT_KEY)) { + return (Mono) original; + } + return ((Mono) original).contextWrite(c -> c.put(SNAPSHOT_KEY, snapshot)); + }); + } else { + return Flux.deferContextual(ctxView -> { + if (ctxView.hasKey(SNAPSHOT_KEY)) { + return original; + } + return ((Flux) original).contextWrite(c -> c.put(SNAPSHOT_KEY, snapshot)); + }); + } + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t, MethodInvocationContext context) { + } +} diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v65/define/RedisChannelWriterInstrumentationV65.java b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v65/define/RedisChannelWriterInstrumentationV65.java new file mode 100644 index 0000000000..5e10c43561 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v65/define/RedisChannelWriterInstrumentationV65.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.lettuce.v65.define; + +import java.util.Collections; +import java.util.List; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch.byHierarchyMatch; + +public class RedisChannelWriterInstrumentationV65 extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "io.lettuce.core.RedisChannelWriter"; + + private static final String REDIS_CHANNEL_WRITER_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.lettuce.v65.RedisChannelWriterInterceptorV65"; + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("write").and(takesArgument(0, named("io.lettuce.core.protocol.RedisCommand")).or( + takesArgument(0, List.class))); + } + + @Override + public String getMethodsInterceptor() { + return REDIS_CHANNEL_WRITER_INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + }; + } + + @Override + public ClassMatch enhanceClass() { + return byHierarchyMatch(ENHANCE_CLASS); + } + + @Override + protected List witnessMethods() { + return Collections.singletonList(new WitnessMethod( + "io.lettuce.core.protocol.ProtocolKeyword", + ElementMatchers.named("toString") + )); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v65/define/RedisReactiveCommandsInstrumentationV65.java b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v65/define/RedisReactiveCommandsInstrumentationV65.java new file mode 100644 index 0000000000..f1c22b88ee --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v65/define/RedisReactiveCommandsInstrumentationV65.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.lettuce.v65.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.ClassInstanceMethodsEnhancePluginDefineV2; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import java.util.Collections; +import java.util.List; + +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch.byHierarchyMatch; + +/** + * + */ +public class RedisReactiveCommandsInstrumentationV65 extends ClassInstanceMethodsEnhancePluginDefineV2 { + + private static final String ENHANCE_CLASS = "io.lettuce.core.AbstractRedisReactiveCommands"; + + private static final String REDIS_REACTIVE_CREATEMONO_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.lettuce.v65.RedisReactiveCreatePublisherMethodInterceptorV65"; + + @Override + public InstanceMethodsInterceptV2Point[] getInstanceMethodsInterceptV2Points() { + return new InstanceMethodsInterceptV2Point[]{ + new InstanceMethodsInterceptV2Point() { + @Override + public ElementMatcher getMethodsMatcher() { + return namedOneOf( + "createMono", + "createFlux", + "createDissolvingFlux" + ).and(takesArguments(1)); + } + + @Override + public String getMethodsInterceptorV2() { + return REDIS_REACTIVE_CREATEMONO_METHOD_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } + + @Override + public ClassMatch enhanceClass() { + return byHierarchyMatch(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + protected List witnessMethods() { + return Collections.singletonList(new WitnessMethod( + "reactor.core.publisher.Mono", + ElementMatchers.named("deferContextual") + )); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/resources/skywalking-plugin.def similarity index 79% rename from apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/resources/skywalking-plugin.def rename to apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/resources/skywalking-plugin.def index 761d3970d3..aac68f9479 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-6.5.x-plugin/src/main/resources/skywalking-plugin.def @@ -14,5 +14,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -tomcat-7.x/8.x=org.apache.skywalking.apm.plugin.tomcat78x.define.TomcatInstrumentation -tomcat-7.x/8.x=org.apache.skywalking.apm.plugin.tomcat78x.define.ApplicationDispatcherInstrumentation +lettuce-6.5.x=org.apache.skywalking.apm.plugin.lettuce.v65.define.RedisChannelWriterInstrumentationV65 +lettuce-6.5.x=org.apache.skywalking.apm.plugin.lettuce.v65.define.RedisReactiveCommandsInstrumentationV65 \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-common/pom.xml similarity index 86% rename from apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/pom.xml rename to apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-common/pom.xml index ed057fe5ef..6062c90bce 100644 --- a/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-common/pom.xml @@ -1,4 +1,4 @@ - + + + + 4.0.0 + + org.apache.skywalking + apm-sdk-plugin + 9.7.0-SNAPSHOT + + + lettuce-plugins + pom + + + lettuce-common + lettuce-5.x-6.4.x-plugin + lettuce-6.5.x-plugin + + + \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/pom.xml index 722371d8b5..12b4e3a59e 100644 --- a/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/pom.xml @@ -20,7 +20,7 @@ light4j-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/light4j-plugins/pom.xml b/apm-sniffer/apm-sdk-plugin/light4j-plugins/pom.xml index 5d738e3b5f..cd9ab3c418 100644 --- a/apm-sniffer/apm-sdk-plugin/light4j-plugins/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/light4j-plugins/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT light4j-plugins @@ -37,6 +37,5 @@ UTF-8 - /.. diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/mariadb-2.x-plugin/pom.xml index 05ab767ccb..2ec8ab3063 100644 --- a/apm-sniffer/apm-sdk-plugin/mariadb-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/mariadb-2.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/pom.xml new file mode 100644 index 0000000000..1011fdda12 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/pom.xml @@ -0,0 +1,60 @@ + + + + + apm-sdk-plugin + org.apache.skywalking + 9.7.0-SNAPSHOT + + 4.0.0 + + apm-mariadb-3.x-plugin + jar + + mariadb-3.x-plugin + http://maven.apache.org + + + UTF-8 + 3.5.1 + + + + + org.mariadb.jdbc + mariadb-java-client + ${mariadb-java-client.version} + provided + + + org.apache.skywalking + apm-jdbc-commons + ${project.version} + provided + + + + + + + maven-deploy-plugin + + + + diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreateCallableStatementInterceptor.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreateCallableStatementInterceptor.java new file mode 100644 index 0000000000..e7642c9fbb --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreateCallableStatementInterceptor.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.jdbc.define.StatementEnhanceInfos; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; + +import java.lang.reflect.Method; + +public class CreateCallableStatementInterceptor implements InstanceMethodsAroundInterceptor { + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) { + + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) { + if (ret instanceof EnhancedInstance) { + ((EnhancedInstance) ret).setSkyWalkingDynamicField(new StatementEnhanceInfos((ConnectionInfo) objInst.getSkyWalkingDynamicField(), (String) allArguments[0], "CallableStatement")); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/ForwardInterceptor.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreatePreparedStatementInterceptor.java similarity index 54% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/ForwardInterceptor.java rename to apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreatePreparedStatementInterceptor.java index f98e15c8af..a17b112718 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/ForwardInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreatePreparedStatementInterceptor.java @@ -6,7 +6,7 @@ * (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 + * 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, @@ -14,50 +14,37 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * */ -package org.apache.skywalking.apm.plugin.jetty.v11.server; +package org.apache.skywalking.apm.plugin.jdbc.mariadb.v3; -import org.apache.skywalking.apm.agent.core.context.ContextManager; -import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.jdbc.define.StatementEnhanceInfos; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; - -public class ForwardInterceptor implements InstanceMethodsAroundInterceptor, InstanceConstructorInterceptor { +public class CreatePreparedStatementInterceptor implements InstanceMethodsAroundInterceptor { @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - MethodInterceptResult result) throws Throwable { - if (ContextManager.isActive()) { - AbstractSpan abstractTracingSpan = ContextManager.activeSpan(); - Map eventMap = new HashMap(); - eventMap.put("forward-url", (String) objInst.getSkyWalkingDynamicField()); - abstractTracingSpan.log(System.currentTimeMillis(), eventMap); - ContextManager.getRuntimeContext().put(Constants.FORWARD_REQUEST_FLAG, true); - } + MethodInterceptResult result) { + } @Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - Object ret) throws Throwable { + Object ret) { + if (ret instanceof EnhancedInstance) { + ((EnhancedInstance) ret).setSkyWalkingDynamicField(new StatementEnhanceInfos((ConnectionInfo) objInst.getSkyWalkingDynamicField(), (String) allArguments[0], "PreparedStatement")); + } return ret; } @Override public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, - Class[] argumentsTypes, Throwable t) { - - } + Class[] argumentsTypes, Throwable t) { - @Override - public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { - objInst.setSkyWalkingDynamicField(allArguments[2]); } } diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreateStatementInterceptor.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreateStatementInterceptor.java new file mode 100644 index 0000000000..036fe00a39 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreateStatementInterceptor.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.jdbc.define.StatementEnhanceInfos; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; + +import java.lang.reflect.Method; + +public class CreateStatementInterceptor implements InstanceMethodsAroundInterceptor { + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) { + + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) { + if (ret instanceof EnhancedInstance) { + ((EnhancedInstance) ret).setSkyWalkingDynamicField(new StatementEnhanceInfos((ConnectionInfo) objInst.getSkyWalkingDynamicField(), "", "Statement")); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/PreparedStatementExecuteMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/PreparedStatementExecuteMethodsInterceptor.java new file mode 100644 index 0000000000..8ec30304b7 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/PreparedStatementExecuteMethodsInterceptor.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.jdbc.JDBCPluginConfig; +import org.apache.skywalking.apm.plugin.jdbc.PreparedStatementParameterBuilder; +import org.apache.skywalking.apm.plugin.jdbc.SqlBodyUtil; +import org.apache.skywalking.apm.plugin.jdbc.define.StatementEnhanceInfos; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; + +import java.lang.reflect.Method; + +public class PreparedStatementExecuteMethodsInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public final void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, MethodInterceptResult result) { + StatementEnhanceInfos cacheObject = (StatementEnhanceInfos) objInst.getSkyWalkingDynamicField(); + ConnectionInfo connectInfo = cacheObject.getConnectionInfo(); + if (connectInfo == null) { + return; + } + AbstractSpan span = ContextManager.createExitSpan(buildOperationName(connectInfo, method.getName(), cacheObject + .getStatementName()), connectInfo.getDatabasePeer()); + Tags.DB_TYPE.set(span, connectInfo.getDBType()); + Tags.DB_INSTANCE.set(span, connectInfo.getDatabaseName()); + Tags.DB_STATEMENT.set(span, SqlBodyUtil.limitSqlBodySize(cacheObject.getSql())); + span.setComponent(connectInfo.getComponent()); + + if (JDBCPluginConfig.Plugin.JDBC.TRACE_SQL_PARAMETERS) { + final Object[] parameters = cacheObject.getParameters(); + if (parameters != null && parameters.length > 0) { + int maxIndex = cacheObject.getMaxIndex(); + Tags.SQL_PARAMETERS.set(span, getParameterString(parameters, maxIndex)); + } + } + + SpanLayer.asDB(span); + } + + @Override + public final Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Object ret) { + StatementEnhanceInfos cacheObject = (StatementEnhanceInfos) objInst.getSkyWalkingDynamicField(); + if (cacheObject.getConnectionInfo() != null) { + ContextManager.stopSpan(); + } + return ret; + } + + @Override + public final void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + StatementEnhanceInfos cacheObject = (StatementEnhanceInfos) objInst.getSkyWalkingDynamicField(); + if (cacheObject.getConnectionInfo() != null) { + ContextManager.activeSpan().log(t); + } + } + + private String buildOperationName(ConnectionInfo connectionInfo, String methodName, String statementName) { + return connectionInfo.getDBType() + "/JDBC/" + statementName + "/" + methodName; + } + + private String getParameterString(Object[] parameters, int maxIndex) { + return new PreparedStatementParameterBuilder() + .setParameters(parameters) + .setMaxIndex(maxIndex) + .build(); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/TomcatExceptionInterceptor.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/SetCatalogInterceptor.java similarity index 71% rename from apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/TomcatExceptionInterceptor.java rename to apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/SetCatalogInterceptor.java index 28c8a9a20a..48b9b3e926 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/TomcatExceptionInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/SetCatalogInterceptor.java @@ -16,30 +16,33 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat78x; +package org.apache.skywalking.apm.plugin.jdbc.mariadb.v3; -import java.lang.reflect.Method; -import org.apache.skywalking.apm.agent.core.context.ContextManager; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; + +import java.lang.reflect.Method; -public class TomcatExceptionInterceptor implements InstanceMethodsAroundInterceptor { +public class SetCatalogInterceptor implements InstanceMethodsAroundInterceptor { @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - MethodInterceptResult result) throws Throwable { - ContextManager.activeSpan().log((Throwable) allArguments[2]); + MethodInterceptResult result) { + Object dynamicField = objInst.getSkyWalkingDynamicField(); + if (dynamicField instanceof ConnectionInfo) { + ((ConnectionInfo) dynamicField).setDatabaseName(String.valueOf(allArguments[0])); + } } @Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - Object ret) throws Throwable { + Object ret) { return ret; } @Override public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, - Class[] argumentsTypes, Throwable t) { - + Class[] argumentsTypes, Throwable t) { } } diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/StatementExecuteMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/StatementExecuteMethodsInterceptor.java new file mode 100644 index 0000000000..c88f921881 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/StatementExecuteMethodsInterceptor.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.jdbc.SqlBodyUtil; +import org.apache.skywalking.apm.plugin.jdbc.define.StatementEnhanceInfos; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; + +import java.lang.reflect.Method; + +public class StatementExecuteMethodsInterceptor implements InstanceMethodsAroundInterceptor { + @Override + public final void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, MethodInterceptResult result) { + StatementEnhanceInfos cacheObject = (StatementEnhanceInfos) objInst.getSkyWalkingDynamicField(); + ConnectionInfo connectInfo = cacheObject.getConnectionInfo(); + if (connectInfo != null) { + AbstractSpan span = ContextManager.createExitSpan(buildOperationName(connectInfo, method.getName(), cacheObject + .getStatementName()), connectInfo.getDatabasePeer()); + Tags.DB_TYPE.set(span, connectInfo.getDBType()); + Tags.DB_INSTANCE.set(span, connectInfo.getDatabaseName()); + String sql = allArguments.length > 0 ? (String) allArguments[0] : ""; + sql = SqlBodyUtil.limitSqlBodySize(sql); + Tags.DB_STATEMENT.set(span, sql); + span.setComponent(connectInfo.getComponent()); + SpanLayer.asDB(span); + } + } + + @Override + public final Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Object ret) { + StatementEnhanceInfos cacheObject = (StatementEnhanceInfos) objInst.getSkyWalkingDynamicField(); + if (cacheObject.getConnectionInfo() != null) { + ContextManager.stopSpan(); + } + return ret; + } + + @Override + public final void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + StatementEnhanceInfos cacheObject = (StatementEnhanceInfos) objInst.getSkyWalkingDynamicField(); + if (cacheObject.getConnectionInfo() != null) { + ContextManager.activeSpan().log(t); + } + } + + private String buildOperationName(ConnectionInfo connectionInfo, String methodName, String statementName) { + return connectionInfo.getDBType() + "/JDBC/" + statementName + "/" + methodName; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/ConnectionInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/ConnectionInstrumentation.java new file mode 100644 index 0000000000..2f7083eca4 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/ConnectionInstrumentation.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.plugin.jdbc.define.Constants; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +public class ConnectionInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.mariadb.jdbc.Connection"; + private static final String CREATE_STATEMENT_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.CreateStatementInterceptor"; + private static final String CREATE_PREPARED_STATEMENT_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.CreatePreparedStatementInterceptor"; + private static final String CREATE_CALLABLE_STATEMENT_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.CreateCallableStatementInterceptor"; + private static final String SET_CATALOG_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.SetCatalogInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(Constants.CREATE_STATEMENT_METHOD_NAME); + } + + @Override + public String getMethodsInterceptor() { + return CREATE_STATEMENT_INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(Constants.PREPARE_STATEMENT_METHOD_NAME); + } + + @Override + public String getMethodsInterceptor() { + return CREATE_PREPARED_STATEMENT_INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(Constants.PREPARE_CALL_METHOD_NAME); + } + + @Override + public String getMethodsInterceptor() { + return CREATE_CALLABLE_STATEMENT_INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(Constants.COMMIT_METHOD_NAME) + .or(named(Constants.ROLLBACK_METHOD_NAME)) + .or(named(Constants.CLOSE_METHOD_NAME)) + .or(named(Constants.RELEASE_SAVE_POINT_METHOD_NAME)); + } + + @Override + public String getMethodsInterceptor() { + return Constants.SERVICE_METHOD_INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("setCatalog"); + } + + @Override + public String getMethodsInterceptor() { + return SET_CATALOG_INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + + } + + @Override + protected String[] witnessClasses() { + return new String[]{"org.mariadb.jdbc.Connection"}; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/DriverInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/DriverInstrumentation.java new file mode 100644 index 0000000000..b0a3dba6b0 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/DriverInstrumentation.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define; + +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.plugin.jdbc.define.AbstractDriverInstrumentation; + +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * {@link DriverInstrumentation} presents that skywalking intercepts {@link org.mariadb.jdbc.Driver}. + */ +public class DriverInstrumentation extends AbstractDriverInstrumentation { + private static final String ENHANCE_CLASS = "org.mariadb.jdbc.Driver"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/PreparedStatementIgnoredSetterInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/PreparedStatementIgnoredSetterInstrumentation.java new file mode 100644 index 0000000000..7e036281fb --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/PreparedStatementIgnoredSetterInstrumentation.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.plugin.jdbc.PSSetterDefinitionOfJDBCInstrumentation; + +public class PreparedStatementIgnoredSetterInstrumentation extends PreparedStatementInstrumentation { + + @Override + public final InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new PSSetterDefinitionOfJDBCInstrumentation(true) + }; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/PreparedStatementInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/PreparedStatementInstrumentation.java new file mode 100644 index 0000000000..fced984b0b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/PreparedStatementInstrumentation.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch.byMultiClassMatch; + +public class PreparedStatementInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String CLIENT_PREPARED_STATEMENT_CLASS = "org.mariadb.jdbc.ClientPreparedStatement"; + private static final String SERVER_PREPARED_STATEMENT_CLASS = "org.mariadb.jdbc.ServerPreparedStatement"; + private static final String PREPARED_STATEMENT_EXECUTE_METHODS_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.PreparedStatementExecuteMethodsInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byMultiClassMatch( + CLIENT_PREPARED_STATEMENT_CLASS, + SERVER_PREPARED_STATEMENT_CLASS + ); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("execute") + .or(named("executeQuery")) + .or(named("executeUpdate")); + } + + @Override + public String getMethodsInterceptor() { + return PREPARED_STATEMENT_EXECUTE_METHODS_INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/PreparedStatementNullSetterInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/PreparedStatementNullSetterInstrumentation.java new file mode 100644 index 0000000000..30a1865174 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/PreparedStatementNullSetterInstrumentation.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.plugin.jdbc.JDBCPreparedStatementNullSetterInstanceMethodsInterceptPoint; + +public class PreparedStatementNullSetterInstrumentation extends PreparedStatementInstrumentation { + + @Override + public final InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new JDBCPreparedStatementNullSetterInstanceMethodsInterceptPoint() + }; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/PreparedStatementSetterInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/PreparedStatementSetterInstrumentation.java new file mode 100644 index 0000000000..bc625974eb --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/PreparedStatementSetterInstrumentation.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.plugin.jdbc.PSSetterDefinitionOfJDBCInstrumentation; + +public class PreparedStatementSetterInstrumentation extends PreparedStatementInstrumentation { + + @Override + public final InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new PSSetterDefinitionOfJDBCInstrumentation(false) + }; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/StatementInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/StatementInstrumentation.java new file mode 100644 index 0000000000..13a8c7e033 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/define/StatementInstrumentation.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +public class StatementInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + private static final String MARIADB_STATEMENT_CLASS_NAME = "org.mariadb.jdbc.Statement"; + private static final String STATEMENT_EXECUTE_METHODS_INTERCEPTOR = "org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.StatementExecuteMethodsInterceptor"; + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("execute") + .or(named("executeQuery")) + .or(named("executeUpdate")) + .or(named("executeLargeUpdate")) + .or(named("executeBatch")) + .or(named("executeLargeBatch")); + } + + @Override + public String getMethodsInterceptor() { + return STATEMENT_EXECUTE_METHODS_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } + + @Override + protected ClassMatch enhanceClass() { + return byName(MARIADB_STATEMENT_CLASS_NAME); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..d1bdb703d4 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +mariadb-3.x=org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define.DriverInstrumentation +mariadb-3.x=org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define.ConnectionInstrumentation +mariadb-3.x=org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define.StatementInstrumentation +mariadb-3.x=org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define.PreparedStatementInstrumentation +mariadb-3.x=org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define.PreparedStatementSetterInstrumentation +mariadb-3.x=org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define.PreparedStatementNullSetterInstrumentation +mariadb-3.x=org.apache.skywalking.apm.plugin.jdbc.mariadb.v3.define.PreparedStatementIgnoredSetterInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreateCallableStatementInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreateCallableStatementInterceptorTest.java new file mode 100644 index 0000000000..a034865659 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreateCallableStatementInterceptorTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class CreateCallableStatementInterceptorTest { + + private static final String SQL = "Select * from test"; + + private CreateCallableStatementInterceptor interceptor; + + @Mock + private EnhancedInstance ret; + + @Mock + private EnhancedInstance objectInstance; + + @Mock + private ConnectionInfo connectionInfo; + + @Before + public void setUp() { + interceptor = new CreateCallableStatementInterceptor(); + + when(objectInstance.getSkyWalkingDynamicField()).thenReturn(connectionInfo); + } + + @Test + public void testResultIsEnhanceInstance() { + interceptor.afterMethod(objectInstance, null, new Object[]{SQL}, null, ret); + verify(ret).setSkyWalkingDynamicField(any()); + } + + @Test + public void testResultIsNotEnhanceInstance() { + interceptor.afterMethod(objectInstance, null, new Object[]{SQL}, null, new Object()); + verify(ret, times(0)).setSkyWalkingDynamicField(any()); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreatePreparedStatementInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreatePreparedStatementInterceptorTest.java new file mode 100644 index 0000000000..0c2c51c9ad --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreatePreparedStatementInterceptorTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class CreatePreparedStatementInterceptorTest { + + private static final String SQL = "Select * from test"; + + private CreatePreparedStatementInterceptor interceptor; + + @Mock + private EnhancedInstance ret; + + @Mock + private EnhancedInstance objectInstance; + + @Mock + private ConnectionInfo connectionInfo; + + @Before + public void setUp() { + interceptor = new CreatePreparedStatementInterceptor(); + + when(objectInstance.getSkyWalkingDynamicField()).thenReturn(connectionInfo); + } + + @Test + public void testResultIsEnhanceInstance() { + interceptor.afterMethod(objectInstance, null, new Object[]{SQL}, null, ret); + verify(ret).setSkyWalkingDynamicField(any()); + } + + @Test + public void testResultIsNotEnhanceInstance() { + interceptor.afterMethod(objectInstance, null, new Object[]{SQL}, null, new Object()); + verify(ret, times(0)).setSkyWalkingDynamicField(any()); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreateStatementInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreateStatementInterceptorTest.java new file mode 100644 index 0000000000..663ce62d32 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/CreateStatementInterceptorTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class CreateStatementInterceptorTest { + + private static final String SQL = "Select * from test"; + + private CreateStatementInterceptor interceptor; + + @Mock + private EnhancedInstance ret; + + @Mock + private EnhancedInstance objectInstance; + + @Mock + private ConnectionInfo connectionInfo; + + @Before + public void setUp() { + interceptor = new CreateStatementInterceptor(); + + when(objectInstance.getSkyWalkingDynamicField()).thenReturn(connectionInfo); + } + + @Test + public void testResultIsEnhanceInstance() { + interceptor.afterMethod(objectInstance, null, new Object[]{SQL}, null, ret); + verify(ret).setSkyWalkingDynamicField(any()); + } + + @Test + public void testResultIsNotEnhanceInstance() { + interceptor.afterMethod(objectInstance, null, new Object[]{SQL}, null, new Object()); + verify(ret, times(0)).setSkyWalkingDynamicField(any()); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/PreparedStatementExecuteMethodsInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/PreparedStatementExecuteMethodsInterceptorTest.java new file mode 100644 index 0000000000..3e624d7952 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/PreparedStatementExecuteMethodsInterceptorTest.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; +import java.lang.reflect.Method; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.jdbc.JDBCPluginConfig; +import org.apache.skywalking.apm.plugin.jdbc.JDBCPreparedStatementSetterInterceptor; +import org.apache.skywalking.apm.plugin.jdbc.define.StatementEnhanceInfos; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(TracingSegmentRunner.class) +public class PreparedStatementExecuteMethodsInterceptorTest { + + private static final String SQL = "Select * from test where id = ?"; + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + private PreparedStatementExecuteMethodsInterceptor serviceMethodInterceptor; + + private JDBCPreparedStatementSetterInterceptor preparedStatementSetterInterceptor; + + @Mock + private ConnectionInfo connectionInfo; + @Mock + private EnhancedInstance objectInstance; + @Mock + private Method method; + private StatementEnhanceInfos enhanceRequireCacheObject; + + @Before + public void setUp() { + JDBCPluginConfig.Plugin.JDBC.TRACE_SQL_PARAMETERS = true; + preparedStatementSetterInterceptor = new JDBCPreparedStatementSetterInterceptor(); + serviceMethodInterceptor = new PreparedStatementExecuteMethodsInterceptor(); + + enhanceRequireCacheObject = new StatementEnhanceInfos(connectionInfo, SQL, "PreparedStatement"); + when(objectInstance.getSkyWalkingDynamicField()).thenReturn(enhanceRequireCacheObject); + when(method.getName()).thenReturn("executeQuery"); + when(connectionInfo.getComponent()).thenReturn(ComponentsDefine.MARIADB_JDBC); + when(connectionInfo.getDBType()).thenReturn("Mariadb"); + when(connectionInfo.getDatabaseName()).thenReturn("test"); + when(connectionInfo.getDatabasePeer()).thenReturn("localhost:3306"); + } + + @After + public void clean() { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = 2048; + JDBCPluginConfig.Plugin.JDBC.TRACE_SQL_PARAMETERS = false; + } + + @Test + public void testExecutePreparedStatement() throws Throwable { + preparedStatementSetterInterceptor.beforeMethod( + objectInstance, method, new Object[] { + 1, + "abcd" + }, null, null); + preparedStatementSetterInterceptor.beforeMethod( + objectInstance, method, new Object[] { + 2, + "efgh" + }, null, null); + + serviceMethodInterceptor.beforeMethod(objectInstance, method, new Object[] {SQL}, null, null); + serviceMethodInterceptor.afterMethod(objectInstance, method, new Object[] {SQL}, null, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment segment = segmentStorage.getTraceSegments().get(0); + assertThat(SegmentHelper.getSpans(segment).size(), is(1)); + AbstractTracingSpan span = SegmentHelper.getSpans(segment).get(0); + SpanAssert.assertLayer(span, SpanLayer.DB); + assertThat(span.getOperationName(), is("Mariadb/JDBC/PreparedStatement/executeQuery")); + SpanAssert.assertTag(span, 0, "Mariadb"); + SpanAssert.assertTag(span, 1, "test"); + SpanAssert.assertTag(span, 2, SQL); + SpanAssert.assertTag(span, 3, "[abcd,efgh]"); + } + + @Test + public void testExecutePreparedStatementWithLimitSqlBody() throws Throwable { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = 10; + + preparedStatementSetterInterceptor.beforeMethod( + objectInstance, method, new Object[] { + 1, + "abcd" + }, null, null); + preparedStatementSetterInterceptor.beforeMethod( + objectInstance, method, new Object[] { + 2, + "efgh" + }, null, null); + + serviceMethodInterceptor.beforeMethod(objectInstance, method, new Object[] {SQL}, null, null); + serviceMethodInterceptor.afterMethod(objectInstance, method, new Object[] {SQL}, null, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment segment = segmentStorage.getTraceSegments().get(0); + assertThat(SegmentHelper.getSpans(segment).size(), is(1)); + AbstractTracingSpan span = SegmentHelper.getSpans(segment).get(0); + SpanAssert.assertLayer(span, SpanLayer.DB); + assertThat(span.getOperationName(), is("Mariadb/JDBC/PreparedStatement/executeQuery")); + SpanAssert.assertTag(span, 0, "Mariadb"); + SpanAssert.assertTag(span, 1, "test"); + SpanAssert.assertTag(span, 2, "Select * f..."); + SpanAssert.assertTag(span, 3, "[abcd,efgh]"); + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/StatementExecuteMethodsInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/StatementExecuteMethodsInterceptorTest.java new file mode 100644 index 0000000000..ce2e83c69a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mariadb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mariadb/v3/StatementExecuteMethodsInterceptorTest.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jdbc.mariadb.v3; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; +import java.lang.reflect.Method; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.jdbc.JDBCPluginConfig; +import org.apache.skywalking.apm.plugin.jdbc.define.StatementEnhanceInfos; +import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(TracingSegmentRunner.class) +public class StatementExecuteMethodsInterceptorTest { + + private static final String SQL = "Select * from test"; + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + private StatementExecuteMethodsInterceptor serviceMethodInterceptor; + + @Mock + private ConnectionInfo connectionInfo; + @Mock + private EnhancedInstance objectInstance; + @Mock + private Method method; + private StatementEnhanceInfos enhanceRequireCacheObject; + + @Before + public void setUp() { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = 2048; + + serviceMethodInterceptor = new StatementExecuteMethodsInterceptor(); + + enhanceRequireCacheObject = new StatementEnhanceInfos(connectionInfo, SQL, "CallableStatement"); + when(objectInstance.getSkyWalkingDynamicField()).thenReturn(enhanceRequireCacheObject); + when(method.getName()).thenReturn("executeQuery"); + when(connectionInfo.getComponent()).thenReturn(ComponentsDefine.MARIADB_JDBC); + when(connectionInfo.getDBType()).thenReturn("Mariadb"); + when(connectionInfo.getDatabaseName()).thenReturn("test"); + when(connectionInfo.getDatabasePeer()).thenReturn("localhost:3306"); + } + + @Test + public void testExecuteStatement() { + serviceMethodInterceptor.beforeMethod(objectInstance, method, new Object[]{SQL}, null, null); + serviceMethodInterceptor.afterMethod(objectInstance, method, new Object[]{SQL}, null, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment segment = segmentStorage.getTraceSegments().get(0); + assertThat(SegmentHelper.getSpans(segment).size(), is(1)); + AbstractTracingSpan span = SegmentHelper.getSpans(segment).get(0); + SpanAssert.assertLayer(span, SpanLayer.DB); + assertThat(span.getOperationName(), is("Mariadb/JDBC/CallableStatement/executeQuery")); + SpanAssert.assertTag(span, 0, "Mariadb"); + SpanAssert.assertTag(span, 1, "test"); + SpanAssert.assertTag(span, 2, SQL); + } + + @Test + public void testExecuteStatementWithLimitSqlBody() { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = 10; + serviceMethodInterceptor.beforeMethod(objectInstance, method, new Object[]{SQL}, null, null); + serviceMethodInterceptor.afterMethod(objectInstance, method, new Object[]{SQL}, null, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment segment = segmentStorage.getTraceSegments().get(0); + assertThat(SegmentHelper.getSpans(segment).size(), is(1)); + AbstractTracingSpan span = SegmentHelper.getSpans(segment).get(0); + SpanAssert.assertLayer(span, SpanLayer.DB); + assertThat(span.getOperationName(), is("Mariadb/JDBC/CallableStatement/executeQuery")); + SpanAssert.assertTag(span, 0, "Mariadb"); + SpanAssert.assertTag(span, 1, "test"); + SpanAssert.assertTag(span, 2, "Select * f..."); + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/micronaut-plugins/micronaut-http-client-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/micronaut-plugins/micronaut-http-client-plugin/pom.xml index 30b35cca03..0dbafa710d 100644 --- a/apm-sniffer/apm-sdk-plugin/micronaut-plugins/micronaut-http-client-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/micronaut-plugins/micronaut-http-client-plugin/pom.xml @@ -21,17 +21,12 @@ org.apache.skywalking micronaut-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 micronaut-http-client-plugin - - 8 - 8 - - io.projectreactor diff --git a/apm-sniffer/apm-sdk-plugin/micronaut-plugins/micronaut-http-server-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/micronaut-plugins/micronaut-http-server-plugin/pom.xml index 6f3b7c80bd..df12088c75 100644 --- a/apm-sniffer/apm-sdk-plugin/micronaut-plugins/micronaut-http-server-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/micronaut-plugins/micronaut-http-server-plugin/pom.xml @@ -21,18 +21,13 @@ org.apache.skywalking micronaut-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 micronaut-http-server-plugin jar - - 8 - 8 - - io.micronaut diff --git a/apm-sniffer/apm-sdk-plugin/micronaut-plugins/pom.xml b/apm-sniffer/apm-sdk-plugin/micronaut-plugins/pom.xml index 931bc20085..0025bcf432 100644 --- a/apm-sniffer/apm-sdk-plugin/micronaut-plugins/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/micronaut-plugins/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -29,7 +29,6 @@ UTF-8 - /.. diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/mongodb-2.x-plugin/pom.xml index 4fb80d5e87..e16638a4b9 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/mongodb-2.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/pom.xml index 68c1ade2e2..212222c6d6 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-mongodb-3.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/AggregateExplainOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/AggregateExplainOperationInstrumentation.java new file mode 100644 index 0000000000..929935a2c1 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/AggregateExplainOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class AggregateExplainOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.AggregateExplainOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/AggregateOperationImplInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/AggregateOperationImplInstrumentation.java new file mode 100644 index 0000000000..208d0a2146 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/AggregateOperationImplInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class AggregateOperationImplInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.AggregateOperationImpl"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/AggregateOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/AggregateOperationInstrumentation.java new file mode 100644 index 0000000000..bffd857fcb --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/AggregateOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class AggregateOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.AggregateOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/ChangeStreamOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/ChangeStreamOperationInstrumentation.java new file mode 100644 index 0000000000..f71ba7e4db --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/ChangeStreamOperationInstrumentation.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class ChangeStreamOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.ChangeStreamOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(5); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/CommandReadOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/CommandReadOperationInstrumentation.java new file mode 100644 index 0000000000..ab317963f2 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/CommandReadOperationInstrumentation.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class CommandReadOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.CommandReadOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationDatabaseConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/CountOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/CountOperationInstrumentation.java new file mode 100644 index 0000000000..6ee8e1ea29 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/CountOperationInstrumentation.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class CountOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.CountOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/DistinctOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/DistinctOperationInstrumentation.java new file mode 100644 index 0000000000..43cf1f9db3 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/DistinctOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class DistinctOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.DistinctOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/FindOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/FindOperationInstrumentation.java new file mode 100644 index 0000000000..702d78138a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/FindOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class FindOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.FindOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/GroupOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/GroupOperationInstrumentation.java new file mode 100644 index 0000000000..0ad153cdef --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/GroupOperationInstrumentation.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class GroupOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.GroupOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/ListCollectionsOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/ListCollectionsOperationInstrumentation.java new file mode 100644 index 0000000000..0eff3237db --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/ListCollectionsOperationInstrumentation.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class ListCollectionsOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.ListCollectionsOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationDatabaseConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/ListIndexesOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/ListIndexesOperationInstrumentation.java new file mode 100644 index 0000000000..7a1870badc --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/ListIndexesOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class ListIndexesOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.ListIndexesOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/MapReduceWithInlineResultsOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/MapReduceWithInlineResultsOperationInstrumentation.java new file mode 100644 index 0000000000..e0fcadfecf --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/MapReduceWithInlineResultsOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class MapReduceWithInlineResultsOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.MapReduceWithInlineResultsOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/ParallelCollectionScanOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/ParallelCollectionScanOperationInstrumentation.java new file mode 100644 index 0000000000..554d178570 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/ParallelCollectionScanOperationInstrumentation.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class ParallelCollectionScanOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.ParallelCollectionScanOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/UserExistsOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/UserExistsOperationInstrumentation.java new file mode 100644 index 0000000000..393b0096a7 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/UserExistsOperationInstrumentation.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class UserExistsOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.UserExistsOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationDatabaseConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/WrappedMapReduceReadOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/WrappedMapReduceReadOperationInstrumentation.java new file mode 100644 index 0000000000..4e76a8e06c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/readOperation/WrappedMapReduceReadOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class WrappedMapReduceReadOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.MapReduceIterableImpl"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/v36/readOperation/WrappedMapReduceReadOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/v36/readOperation/WrappedMapReduceReadOperationInstrumentation.java new file mode 100644 index 0000000000..c3fabf603e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/v36/readOperation/WrappedMapReduceReadOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.v36.readOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class WrappedMapReduceReadOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.client.internal.MapReduceIterableImpl$WrappedMapReduceReadOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.WrappedMapReduceReadOperationInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/AggregateToCollectionOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/AggregateToCollectionOperationInstrumentation.java new file mode 100644 index 0000000000..62bde0f7f8 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/AggregateToCollectionOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class AggregateToCollectionOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.AggregateToCollectionOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(5); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/BaseFindAndModifyOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/BaseFindAndModifyOperationInstrumentation.java new file mode 100644 index 0000000000..bb1c0445ee --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/BaseFindAndModifyOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class BaseFindAndModifyOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.BaseFindAndModifyOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/BaseWriteOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/BaseWriteOperationInstrumentation.java new file mode 100644 index 0000000000..fdb6b8ffe3 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/BaseWriteOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class BaseWriteOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.BaseWriteOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(4); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/CommandWriteOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/CommandWriteOperationInstrumentation.java new file mode 100644 index 0000000000..77d3d4f165 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/CommandWriteOperationInstrumentation.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class CommandWriteOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.CommandWriteOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationDatabaseConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/CreateCollectionOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/CreateCollectionOperationInstrumentation.java new file mode 100644 index 0000000000..a5f45f68e3 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/CreateCollectionOperationInstrumentation.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class CreateCollectionOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.CreateCollectionOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationDatabaseConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(3); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/CreateIndexesOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/CreateIndexesOperationInstrumentation.java new file mode 100644 index 0000000000..556205173a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/CreateIndexesOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class CreateIndexesOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.CreateIndexesOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(3); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/CreateViewOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/CreateViewOperationInstrumentation.java new file mode 100644 index 0000000000..e16c434818 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/CreateViewOperationInstrumentation.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class CreateViewOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.CreateViewOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationDatabaseConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/DropCollectionOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/DropCollectionOperationInstrumentation.java new file mode 100644 index 0000000000..88a04f6900 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/DropCollectionOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class DropCollectionOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.DropCollectionOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(2); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/DropDatabaseOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/DropDatabaseOperationInstrumentation.java new file mode 100644 index 0000000000..88450fec86 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/DropDatabaseOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class DropDatabaseOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.DropDatabaseOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationDatabaseConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(2); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/DropIndexOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/DropIndexOperationInstrumentation.java new file mode 100644 index 0000000000..8be077745b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/DropIndexOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class DropIndexOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.DropIndexOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(3); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/MapReduceToCollectionOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/MapReduceToCollectionOperationInstrumentation.java new file mode 100644 index 0000000000..cc72bd315b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/MapReduceToCollectionOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class MapReduceToCollectionOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.MapReduceToCollectionOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(5); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/MixedBulkWriteOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/MixedBulkWriteOperationInstrumentation.java new file mode 100644 index 0000000000..44a90e3001 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/MixedBulkWriteOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class MixedBulkWriteOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.MixedBulkWriteOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/RenameCollectionOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/RenameCollectionOperationInstrumentation.java new file mode 100644 index 0000000000..95c9a4fdb9 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/define/writeOperation/RenameCollectionOperationInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class RenameCollectionOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.operation.RenameCollectionOperation"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation.OperationNamespaceConstructInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(3); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/operation/OperationDatabaseConstructInterceptor.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/operation/OperationDatabaseConstructInterceptor.java new file mode 100644 index 0000000000..a948de5128 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/operation/OperationDatabaseConstructInterceptor.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.mongodb.v3.support.MongoNamespaceInfo; + +public class OperationDatabaseConstructInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + String databaseName = (String) allArguments[0]; + objInst.setSkyWalkingDynamicField(new MongoNamespaceInfo(databaseName)); + } + +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/operation/OperationNamespaceConstructInterceptor.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/operation/OperationNamespaceConstructInterceptor.java new file mode 100644 index 0000000000..f6c97a6d37 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/operation/OperationNamespaceConstructInterceptor.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation; + +import com.mongodb.MongoNamespace; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.mongodb.v3.support.MongoNamespaceInfo; + +public class OperationNamespaceConstructInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + MongoNamespace mongoNamespace = (MongoNamespace) allArguments[0]; + objInst.setSkyWalkingDynamicField(new MongoNamespaceInfo(mongoNamespace)); + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/operation/WrappedMapReduceReadOperationInterceptor.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/operation/WrappedMapReduceReadOperationInterceptor.java new file mode 100644 index 0000000000..3cb37c8cb5 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/operation/WrappedMapReduceReadOperationInterceptor.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.operation; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; + +public class WrappedMapReduceReadOperationInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + EnhancedInstance enhancedInstance = (EnhancedInstance) allArguments[0]; + objInst.setSkyWalkingDynamicField(enhancedInstance.getSkyWalkingDynamicField()); + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/support/MongoNamespaceInfo.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/support/MongoNamespaceInfo.java new file mode 100644 index 0000000000..16ba7f8aae --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/support/MongoNamespaceInfo.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v3.support; + +import com.mongodb.MongoNamespace; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.apache.skywalking.apm.util.StringUtil; + +@EqualsAndHashCode +@Getter +public class MongoNamespaceInfo { + + private final String databaseName; + private final String collectionName; + + public MongoNamespaceInfo(String databaseName) { + this(databaseName, null); + } + + public MongoNamespaceInfo(MongoNamespace mongoNamespace) { + this(mongoNamespace.getDatabaseName(), mongoNamespace.getCollectionName()); + } + + public MongoNamespaceInfo(String databaseName, String collectionName) { + this.databaseName = databaseName; + this.collectionName = collectionName; + } + + public String getDatabaseName() { + return databaseName; + } + + public String getCollectionName() { + return collectionName; + } + + public String toString() { + if (StringUtil.isNotBlank(collectionName)) { + return databaseName + '.' + collectionName; + } else { + return databaseName; + } + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/support/MongoOperationHelper.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/support/MongoOperationHelper.java index 99ab80c051..d68d642ec0 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/support/MongoOperationHelper.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/support/MongoOperationHelper.java @@ -22,6 +22,7 @@ import com.mongodb.bulk.InsertRequest; import com.mongodb.bulk.UpdateRequest; import com.mongodb.bulk.WriteRequest; +import com.mongodb.operation.AggregateOperation; import com.mongodb.operation.CountOperation; import com.mongodb.operation.CreateCollectionOperation; import com.mongodb.operation.CreateIndexesOperation; @@ -104,6 +105,9 @@ public static String getTraceParam(Object obj) { } else if (obj instanceof FindAndUpdateOperation) { BsonDocument filter = ((FindAndUpdateOperation) obj).getFilter(); return limitFilter(filter.toString()); + } else if (obj instanceof AggregateOperation) { + List pipelines = ((AggregateOperation) obj).getPipeline(); + return getPipelines(pipelines); } else if (obj instanceof MapReduceToCollectionOperation) { BsonDocument filter = ((MapReduceToCollectionOperation) obj).getFilter(); return limitFilter(filter.toString()); @@ -115,6 +119,18 @@ public static String getTraceParam(Object obj) { } } + private static String getPipelines(List pipelines) { + StringBuilder params = new StringBuilder(); + for (BsonDocument pipeline : pipelines) { + params.append(pipeline.toString()).append(","); + final int filterLengthLimit = MongoPluginConfig.Plugin.MongoDB.FILTER_LENGTH_LIMIT; + if (filterLengthLimit > 0 && params.length() > filterLengthLimit) { + return params.substring(0, filterLengthLimit) + "..."; + } + } + return params.toString(); + } + private static String getFilter(List writeRequestList) { StringBuilder params = new StringBuilder(); for (WriteRequest request : writeRequestList) { diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/support/MongoSpanHelper.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/support/MongoSpanHelper.java index 759abc4cd0..c7a88151a4 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/support/MongoSpanHelper.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v3/support/MongoSpanHelper.java @@ -18,28 +18,48 @@ package org.apache.skywalking.apm.plugin.mongodb.v3.support; +import lombok.SneakyThrows; import org.apache.skywalking.apm.agent.core.context.ContextCarrier; import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.AbstractTag; import org.apache.skywalking.apm.agent.core.context.tag.Tags; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.mongodb.v3.MongoPluginConfig; +import org.apache.skywalking.apm.util.StringUtil; public class MongoSpanHelper { + private static final AbstractTag DB_COLLECTION_TAG = Tags.ofKey("db.collection"); + private MongoSpanHelper() { } + @SneakyThrows public static void createExitSpan(String executeMethod, String remotePeer, Object operation) { AbstractSpan span = ContextManager.createExitSpan( - MongoConstants.MONGO_DB_OP_PREFIX + executeMethod, new ContextCarrier(), remotePeer); + MongoConstants.MONGO_DB_OP_PREFIX + executeMethod, new ContextCarrier(), remotePeer); span.setComponent(ComponentsDefine.MONGO_DRIVER); Tags.DB_TYPE.set(span, MongoConstants.DB_TYPE); SpanLayer.asDB(span); + if (operation instanceof EnhancedInstance) { + MongoNamespaceInfo mongoNamespaceInfo = (MongoNamespaceInfo) ((EnhancedInstance) operation).getSkyWalkingDynamicField(); + if (mongoNamespaceInfo != null) { + if (StringUtil.isNotEmpty(mongoNamespaceInfo.getDatabaseName())) { + Tags.DB_INSTANCE.set(span, mongoNamespaceInfo.getDatabaseName()); + } + if (StringUtil.isNotEmpty(mongoNamespaceInfo.getCollectionName())) { + span.tag(DB_COLLECTION_TAG, mongoNamespaceInfo.getCollectionName()); + } + } + } + if (MongoPluginConfig.Plugin.MongoDB.TRACE_PARAM) { Tags.DB_BIND_VARIABLES.set(span, MongoOperationHelper.getTraceParam(operation)); } } + } diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/resources/skywalking-plugin.def index 8416fd8e52..8e6b05fdf1 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/main/resources/skywalking-plugin.def @@ -19,8 +19,40 @@ mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.v30.MongoDBInstru # v3.7.x~ mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.v37.MongoDBClientDelegateInstrumentation mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.v37.MongoDBOperationExecutorInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.v36.readOperation.WrappedMapReduceReadOperationInstrumentation # v3.8.x~ mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.v38.MongoDBOperationExecutorInstrumentation # v3.6.x mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.v36.MongoDBInstrumentation -mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.v36.MongoDBOperationExecutorInstrumentation \ No newline at end of file +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.v36.MongoDBOperationExecutorInstrumentation + +# readOperation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.AggregateExplainOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.AggregateOperationImplInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.AggregateOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.ChangeStreamOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.CommandReadOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.CountOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.DistinctOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.FindOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.ListCollectionsOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.ListIndexesOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.MapReduceWithInlineResultsOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.WrappedMapReduceReadOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.GroupOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.ParallelCollectionScanOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.readOperation.UserExistsOperationInstrumentation +# writeOperation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.AggregateToCollectionOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.BaseFindAndModifyOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.BaseWriteOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.CommandWriteOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.CreateCollectionOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.CreateIndexesOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.CreateViewOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.DropCollectionOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.DropDatabaseOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.DropIndexOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.MapReduceToCollectionOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.MixedBulkWriteOperationInstrumentation +mongodb-3.x=org.apache.skywalking.apm.plugin.mongodb.v3.define.writeOperation.RenameCollectionOperationInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/v30/MongoDBInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/v30/MongoDBInterceptorTest.java index 62d28a6c14..2319ad54e3 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/v30/MongoDBInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/v30/MongoDBInterceptorTest.java @@ -18,12 +18,10 @@ package org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.v30; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import java.lang.reflect.Method; -import java.util.List; +import com.mongodb.Mongo; +import com.mongodb.MongoNamespace; +import com.mongodb.operation.AggregateOperation; +import com.mongodb.operation.FindOperation; import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; import org.apache.skywalking.apm.agent.core.context.trace.LogDataEntity; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; @@ -38,6 +36,7 @@ import org.apache.skywalking.apm.agent.test.tools.SpanAssert; import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; import org.apache.skywalking.apm.plugin.mongodb.v3.MongoPluginConfig; +import org.apache.skywalking.apm.plugin.mongodb.v3.support.MongoNamespaceInfo; import org.bson.BsonDocument; import org.bson.BsonString; import org.bson.codecs.Decoder; @@ -48,11 +47,19 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import com.mongodb.Mongo; -import com.mongodb.MongoNamespace; -import com.mongodb.operation.FindOperation; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @RunWith(TracingSegmentRunner.class) public class MongoDBInterceptorTest { @@ -67,11 +74,16 @@ public class MongoDBInterceptorTest { private MongoDBInterceptor interceptor; + private FindOperation enhancedInstanceForFindOperation; + @Mock private EnhancedInstance enhancedInstance; private Object[] arguments; + private Class[] argumentTypes; + private Decoder decoder; + private MongoNamespace mongoNamespace; @SuppressWarnings({ "rawtypes", @@ -88,12 +100,14 @@ public void setUp() throws Exception { BsonDocument document = new BsonDocument(); document.append("name", new BsonString("by")); - MongoNamespace mongoNamespace = new MongoNamespace("test.user"); - Decoder decoder = mock(Decoder.class); + mongoNamespace = new MongoNamespace("test.user"); + decoder = mock(Decoder.class); FindOperation findOperation = new FindOperation(mongoNamespace, decoder); findOperation.filter(document); - - arguments = new Object[] {findOperation}; + enhancedInstanceForFindOperation = mock(FindOperation.class, Mockito.withSettings().extraInterfaces(EnhancedInstance.class)); + when(((EnhancedInstance) enhancedInstanceForFindOperation).getSkyWalkingDynamicField()).thenReturn(new MongoNamespaceInfo(mongoNamespace)); + when(enhancedInstanceForFindOperation.getFilter()).thenReturn(findOperation.getFilter()); + arguments = new Object[] {enhancedInstanceForFindOperation}; argumentTypes = new Class[] {findOperation.getClass()}; } @@ -105,7 +119,29 @@ public void testIntercept() throws Throwable { MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); List spans = SegmentHelper.getSpans(traceSegment); - assertRedisSpan(spans.get(0)); + assertFindOperationSpan(spans.get(0)); + } + + @Test + public void testAggregateOperationIntercept() throws Throwable { + MongoNamespace mongoNamespace = new MongoNamespace("test.user"); + BsonDocument matchStage = new BsonDocument("$match", new BsonDocument("name", new BsonString("by"))); + List pipeline = Collections.singletonList(matchStage); + AggregateOperation aggregateOperation = new AggregateOperation(mongoNamespace, pipeline, decoder); + + AggregateOperation enhancedInstanceForAggregateOperation = mock(AggregateOperation.class, Mockito.withSettings().extraInterfaces(EnhancedInstance.class)); + when(((EnhancedInstance) enhancedInstanceForAggregateOperation).getSkyWalkingDynamicField()).thenReturn(new MongoNamespaceInfo(mongoNamespace)); + when(enhancedInstanceForAggregateOperation.getPipeline()).thenReturn(aggregateOperation.getPipeline()); + Object[] arguments = {enhancedInstanceForAggregateOperation}; + Class[] argumentTypes = {aggregateOperation.getClass()}; + + interceptor.beforeMethod(enhancedInstance, getExecuteMethod(), arguments, argumentTypes, null); + interceptor.afterMethod(enhancedInstance, getExecuteMethod(), arguments, argumentTypes, null); + + MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertMongoAggregateOperationSpan(spans.get(0)); } @Test @@ -118,18 +154,32 @@ public void testInterceptWithException() throws Throwable { MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); List spans = SegmentHelper.getSpans(traceSegment); - assertRedisSpan(spans.get(0)); + assertFindOperationSpan(spans.get(0)); List logDataEntities = SpanHelper.getLogs(spans.get(0)); assertThat(logDataEntities.size(), is(1)); SpanAssert.assertException(logDataEntities.get(0), RuntimeException.class); } - private void assertRedisSpan(AbstractTracingSpan span) { - assertThat(span.getOperationName(), is("MongoDB/FindOperation")); + private void assertFindOperationSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), startsWith("MongoDB/FindOperation")); + assertThat(SpanHelper.getComponentId(span), is(42)); + List tags = SpanHelper.getTags(span); + assertThat(tags.get(0).getValue(), is("MongoDB")); + assertThat(tags.get(1).getValue(), is("test")); + assertThat(tags.get(2).getValue(), is("user")); + assertThat(tags.get(3).getValue(), is("{\"name\": \"by\"}")); + assertThat(span.isExit(), is(true)); + assertThat(SpanHelper.getLayer(span), CoreMatchers.is(SpanLayer.DB)); + } + + private void assertMongoAggregateOperationSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), startsWith("MongoDB/AggregateOperation")); assertThat(SpanHelper.getComponentId(span), is(42)); List tags = SpanHelper.getTags(span); - assertThat(tags.get(1).getValue(), is("{\"name\": \"by\"}")); assertThat(tags.get(0).getValue(), is("MongoDB")); + assertThat(tags.get(1).getValue(), is("test")); + assertThat(tags.get(2).getValue(), is("user")); + assertThat(tags.get(3).getValue(), is("{\"$match\": {\"name\": \"by\"}},")); assertThat(span.isExit(), is(true)); assertThat(SpanHelper.getLayer(span), CoreMatchers.is(SpanLayer.DB)); } diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/v37/MongoDBOperationExecutorInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/v37/MongoDBOperationExecutorInterceptorTest.java index 60de9f5c21..8c6112e7fc 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/v37/MongoDBOperationExecutorInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v3/interceptor/v37/MongoDBOperationExecutorInterceptorTest.java @@ -18,12 +18,13 @@ package org.apache.skywalking.apm.plugin.mongodb.v3.interceptor.v37; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import java.lang.reflect.Method; -import java.util.List; +import com.mongodb.MongoNamespace; +import com.mongodb.ReadConcern; +import com.mongodb.client.internal.OperationExecutor; +import com.mongodb.operation.AggregateOperation; +import com.mongodb.operation.CreateCollectionOperation; +import com.mongodb.operation.FindOperation; +import com.mongodb.operation.WriteOperation; import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; import org.apache.skywalking.apm.agent.core.context.trace.LogDataEntity; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; @@ -38,6 +39,7 @@ import org.apache.skywalking.apm.agent.test.tools.SpanAssert; import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; import org.apache.skywalking.apm.plugin.mongodb.v3.MongoPluginConfig; +import org.apache.skywalking.apm.plugin.mongodb.v3.support.MongoNamespaceInfo; import org.bson.BsonDocument; import org.bson.BsonString; import org.bson.codecs.Decoder; @@ -48,13 +50,19 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import com.mongodb.MongoNamespace; -import com.mongodb.ReadConcern; -import com.mongodb.client.internal.OperationExecutor; -import com.mongodb.operation.FindOperation; -import com.mongodb.operation.WriteOperation; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @RunWith(TracingSegmentRunner.class) public class MongoDBOperationExecutorInterceptorTest { @@ -70,11 +78,15 @@ public class MongoDBOperationExecutorInterceptorTest { @Mock private EnhancedInstance enhancedInstance; + private FindOperation enhancedInstanceForFindOperation; + private MongoDBOperationExecutorInterceptor interceptor; private Object[] arguments; private Class[] argumentTypes; + private Decoder decoder; + private MongoNamespace mongoNamespace; @Before public void setUp() { @@ -87,12 +99,14 @@ public void setUp() { BsonDocument document = new BsonDocument(); document.append("name", new BsonString("by")); - MongoNamespace mongoNamespace = new MongoNamespace("test.user"); - Decoder decoder = mock(Decoder.class); + mongoNamespace = new MongoNamespace("test.user"); + decoder = mock(Decoder.class); FindOperation findOperation = new FindOperation(mongoNamespace, decoder); findOperation.filter(document); - - arguments = new Object[] {findOperation}; + enhancedInstanceForFindOperation = mock(FindOperation.class, Mockito.withSettings().extraInterfaces(EnhancedInstance.class)); + when(((EnhancedInstance) enhancedInstanceForFindOperation).getSkyWalkingDynamicField()).thenReturn(new MongoNamespaceInfo(mongoNamespace)); + when(enhancedInstanceForFindOperation.getFilter()).thenReturn(findOperation.getFilter()); + arguments = new Object[] {enhancedInstanceForFindOperation}; argumentTypes = new Class[] {findOperation.getClass()}; } @@ -104,11 +118,51 @@ public void testIntercept() throws Throwable { MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); List spans = SegmentHelper.getSpans(traceSegment); - assertRedisSpan(spans.get(0)); + assertMongoFindOperationSpan(spans.get(0)); } @Test - public void testInterceptWithException() throws Throwable { + public void testCreateCollectionOperationIntercept() throws Throwable { + CreateCollectionOperation createCollectionOperation = new CreateCollectionOperation("test", "user"); + CreateCollectionOperation enhancedInstanceForCreateCollectionOperation = mock(CreateCollectionOperation.class, Mockito.withSettings().extraInterfaces(EnhancedInstance.class)); + when(((EnhancedInstance) enhancedInstanceForCreateCollectionOperation).getSkyWalkingDynamicField()).thenReturn(new MongoNamespaceInfo(mongoNamespace)); + when(enhancedInstanceForCreateCollectionOperation.getCollectionName()).thenReturn("user"); + Object[] arguments = {enhancedInstanceForCreateCollectionOperation}; + Class[] argumentTypes = {createCollectionOperation.getClass()}; + + interceptor.beforeMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); + interceptor.afterMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); + + MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertMongoCreateCollectionOperationSpan(spans.get(0)); + } + + @Test + public void testAggregateOperationIntercept() throws Throwable { + MongoNamespace mongoNamespace = new MongoNamespace("test.user"); + BsonDocument matchStage = new BsonDocument("$match", new BsonDocument("name", new BsonString("by"))); + List pipeline = Collections.singletonList(matchStage); + AggregateOperation aggregateOperation = new AggregateOperation(mongoNamespace, pipeline, decoder); + + AggregateOperation enhancedInstanceForAggregateOperation = mock(AggregateOperation.class, Mockito.withSettings().extraInterfaces(EnhancedInstance.class)); + when(((EnhancedInstance) enhancedInstanceForAggregateOperation).getSkyWalkingDynamicField()).thenReturn(new MongoNamespaceInfo(mongoNamespace)); + when(enhancedInstanceForAggregateOperation.getPipeline()).thenReturn(aggregateOperation.getPipeline()); + Object[] arguments = {enhancedInstanceForAggregateOperation}; + Class[] argumentTypes = {aggregateOperation.getClass()}; + + interceptor.beforeMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); + interceptor.afterMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); + + MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertMongoAggregateOperationSpan(spans.get(0)); + } + + @Test + public void testInterceptFindOperationWithException() throws Throwable { interceptor.beforeMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); interceptor.handleMethodException( enhancedInstance, getMethod(), arguments, argumentTypes, new RuntimeException()); @@ -117,18 +171,44 @@ public void testInterceptWithException() throws Throwable { MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); List spans = SegmentHelper.getSpans(traceSegment); - assertRedisSpan(spans.get(0)); + assertMongoFindOperationSpan(spans.get(0)); List logDataEntities = SpanHelper.getLogs(spans.get(0)); assertThat(logDataEntities.size(), is(1)); SpanAssert.assertException(logDataEntities.get(0), RuntimeException.class); } - private void assertRedisSpan(AbstractTracingSpan span) { - assertThat(span.getOperationName(), is("MongoDB/FindOperation")); + private void assertMongoCreateCollectionOperationSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), startsWith("MongoDB/CreateCollectionOperation")); + assertThat(SpanHelper.getComponentId(span), is(42)); + List tags = SpanHelper.getTags(span); + assertThat(tags.get(0).getValue(), is("MongoDB")); + assertThat(tags.get(1).getValue(), is("test")); + assertThat(tags.get(2).getValue(), is("user")); + assertThat(tags.get(3).getValue(), is("user")); + assertThat(span.isExit(), is(true)); + assertThat(SpanHelper.getLayer(span), CoreMatchers.is(SpanLayer.DB)); + } + + private void assertMongoAggregateOperationSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), startsWith("MongoDB/AggregateOperation")); + assertThat(SpanHelper.getComponentId(span), is(42)); + List tags = SpanHelper.getTags(span); + assertThat(tags.get(0).getValue(), is("MongoDB")); + assertThat(tags.get(1).getValue(), is("test")); + assertThat(tags.get(2).getValue(), is("user")); + assertThat(tags.get(3).getValue(), is("{\"$match\": {\"name\": \"by\"}},")); + assertThat(span.isExit(), is(true)); + assertThat(SpanHelper.getLayer(span), CoreMatchers.is(SpanLayer.DB)); + } + + private void assertMongoFindOperationSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), startsWith("MongoDB/FindOperation")); assertThat(SpanHelper.getComponentId(span), is(42)); List tags = SpanHelper.getTags(span); - assertThat(tags.get(1).getValue(), is("{\"name\": \"by\"}")); assertThat(tags.get(0).getValue(), is("MongoDB")); + assertThat(tags.get(1).getValue(), is("test")); + assertThat(tags.get(2).getValue(), is("user")); + assertThat(tags.get(3).getValue(), is("{\"name\": \"by\"}")); assertThat(span.isExit(), is(true)); assertThat(SpanHelper.getLayer(span), CoreMatchers.is(SpanLayer.DB)); } diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/pom.xml index 868f1d9ab7..cb0363cb8e 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-mongodb-4.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/readOperation/AggregateOperationImplInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/readOperation/AggregateOperationImplInstrumentation.java index 49cb53e085..1e50102399 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/readOperation/AggregateOperationImplInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/readOperation/AggregateOperationImplInstrumentation.java @@ -26,7 +26,7 @@ import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static net.bytebuddy.matcher.ElementMatchers.any; public class AggregateOperationImplInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { @@ -53,7 +53,7 @@ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { @Override public ElementMatcher getConstructorMatcher() { - return takesArguments(4); + return any(); } @Override diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/readOperation/AggregateOperationInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/readOperation/AggregateOperationInstrumentation.java index b95124195b..6e56abd504 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/readOperation/AggregateOperationInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/readOperation/AggregateOperationInstrumentation.java @@ -26,7 +26,7 @@ import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static net.bytebuddy.matcher.ElementMatchers.any; public class AggregateOperationInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { @@ -53,7 +53,7 @@ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { @Override public ElementMatcher getConstructorMatcher() { - return takesArguments(5); + return any(); } @Override diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/operation/OperationDatabaseConstructInterceptor.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/operation/OperationDatabaseConstructInterceptor.java index a643e3f201..850b625153 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/operation/OperationDatabaseConstructInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/operation/OperationDatabaseConstructInterceptor.java @@ -20,13 +20,14 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.mongodb.v4.support.MongoNamespaceInfo; public class OperationDatabaseConstructInterceptor implements InstanceConstructorInterceptor { @Override public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { String databaseName = (String) allArguments[0]; - objInst.setSkyWalkingDynamicField(databaseName); + objInst.setSkyWalkingDynamicField(new MongoNamespaceInfo(databaseName)); } } \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/operation/OperationNamespaceConstructInterceptor.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/operation/OperationNamespaceConstructInterceptor.java index 6641012fbf..9a9fec49f0 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/operation/OperationNamespaceConstructInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/operation/OperationNamespaceConstructInterceptor.java @@ -21,14 +21,14 @@ import com.mongodb.MongoNamespace; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.mongodb.v4.support.MongoNamespaceInfo; public class OperationNamespaceConstructInterceptor implements InstanceConstructorInterceptor { @Override public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { MongoNamespace mongoNamespace = (MongoNamespace) allArguments[0]; - String databaseName = mongoNamespace.getDatabaseName(); - objInst.setSkyWalkingDynamicField(databaseName); + objInst.setSkyWalkingDynamicField(new MongoNamespaceInfo(mongoNamespace)); } } diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/operation/WrappedMapReduceReadOperationInterceptor.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/operation/WrappedMapReduceReadOperationInterceptor.java index 39c4699ffb..90614d3fc7 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/operation/WrappedMapReduceReadOperationInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/operation/WrappedMapReduceReadOperationInterceptor.java @@ -25,11 +25,8 @@ public class WrappedMapReduceReadOperationInterceptor implements InstanceConstru @Override public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { - if (allArguments[0] instanceof EnhancedInstance) { EnhancedInstance enhancedInstance = (EnhancedInstance) allArguments[0]; - String databaseName = (String) enhancedInstance.getSkyWalkingDynamicField(); - objInst.setSkyWalkingDynamicField(databaseName); - } + objInst.setSkyWalkingDynamicField(enhancedInstance.getSkyWalkingDynamicField()); } } diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/LegacyOperationHelper.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/LegacyOperationHelper.java new file mode 100644 index 0000000000..3c3a19a7b7 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/LegacyOperationHelper.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v4.support; + +import com.mongodb.internal.bulk.DeleteRequest; +import com.mongodb.internal.bulk.InsertRequest; +import com.mongodb.internal.bulk.UpdateRequest; +import com.mongodb.internal.operation.DeleteOperation; +import com.mongodb.internal.operation.InsertOperation; +import com.mongodb.internal.operation.UpdateOperation; + +import java.util.List; + +/** + * Handles trace parameter extraction for legacy MongoDB driver versions (4.0 - 4.8). + * InsertOperation, DeleteOperation, and UpdateOperation were removed in driver 4.9. + * This class is only loaded when those classes exist (guarded by + * {@link MongoOperationHelper#HAS_LEGACY_WRITE_OPERATIONS}). + */ +@SuppressWarnings("deprecation") +class LegacyOperationHelper { + + private LegacyOperationHelper() { + } + + /** + * Extract trace parameters from legacy write operation types. + * @return the trace parameter string, or null if obj is not a legacy write operation + */ + static String getTraceParam(Object obj) { + if (obj instanceof DeleteOperation) { + List writeRequestList = ((DeleteOperation) obj).getDeleteRequests(); + return MongoOperationHelper.getFilter(writeRequestList); + } else if (obj instanceof InsertOperation) { + List writeRequestList = ((InsertOperation) obj).getInsertRequests(); + return MongoOperationHelper.getFilter(writeRequestList); + } else if (obj instanceof UpdateOperation) { + List writeRequestList = ((UpdateOperation) obj).getUpdateRequests(); + return MongoOperationHelper.getFilter(writeRequestList); + } + return null; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoNamespaceInfo.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoNamespaceInfo.java new file mode 100644 index 0000000000..54d54b4d8a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoNamespaceInfo.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v4.support; + +import com.mongodb.MongoNamespace; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.apache.skywalking.apm.util.StringUtil; + +@EqualsAndHashCode +@Getter +public class MongoNamespaceInfo { + + private final String databaseName; + private final String collectionName; + + public MongoNamespaceInfo(String databaseName) { + this(databaseName, null); + } + + public MongoNamespaceInfo(MongoNamespace mongoNamespace) { + this(mongoNamespace.getDatabaseName(), mongoNamespace.getCollectionName()); + } + + public MongoNamespaceInfo(String databaseName, String collectionName) { + this.databaseName = databaseName; + this.collectionName = collectionName; + } + + public String getDatabaseName() { + return databaseName; + } + + public String getCollectionName() { + return collectionName; + } + + public String toString() { + if (StringUtil.isNotBlank(collectionName)) { + return databaseName + '.' + collectionName; + } else { + return databaseName; + } + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoOperationHelper.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoOperationHelper.java index 45d0481fbc..96cf84c985 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoOperationHelper.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoOperationHelper.java @@ -22,22 +22,20 @@ import com.mongodb.internal.bulk.InsertRequest; import com.mongodb.internal.bulk.UpdateRequest; import com.mongodb.internal.bulk.WriteRequest; +import com.mongodb.internal.operation.AggregateOperation; import com.mongodb.internal.operation.CountOperation; import com.mongodb.internal.operation.CreateCollectionOperation; import com.mongodb.internal.operation.CreateIndexesOperation; import com.mongodb.internal.operation.CreateViewOperation; -import com.mongodb.internal.operation.DeleteOperation; import com.mongodb.internal.operation.DistinctOperation; import com.mongodb.internal.operation.FindAndDeleteOperation; import com.mongodb.internal.operation.FindAndReplaceOperation; import com.mongodb.internal.operation.FindAndUpdateOperation; import com.mongodb.internal.operation.FindOperation; -import com.mongodb.internal.operation.InsertOperation; import com.mongodb.internal.operation.ListCollectionsOperation; import com.mongodb.internal.operation.MapReduceToCollectionOperation; import com.mongodb.internal.operation.MapReduceWithInlineResultsOperation; import com.mongodb.internal.operation.MixedBulkWriteOperation; -import com.mongodb.internal.operation.UpdateOperation; import org.bson.BsonDocument; import java.util.List; @@ -48,6 +46,21 @@ }) public class MongoOperationHelper { + // InsertOperation, DeleteOperation, UpdateOperation were removed in MongoDB driver 4.9. + // Use class existence check to determine which extraction path to use. + private static final boolean HAS_LEGACY_WRITE_OPERATIONS; + + static { + boolean hasLegacy; + try { + Class.forName("com.mongodb.internal.operation.InsertOperation"); + hasLegacy = true; + } catch (ClassNotFoundException e) { + hasLegacy = false; + } + HAS_LEGACY_WRITE_OPERATIONS = hasLegacy; + } + private MongoOperationHelper() { } @@ -68,22 +81,25 @@ public static String getTraceParam(Object obj) { } else if (obj instanceof FindOperation) { BsonDocument filter = ((FindOperation) obj).getFilter(); return limitFilter(filter.toString()); - } else if (obj instanceof ListCollectionsOperation) { + } else if (obj instanceof ListCollectionsOperation) { BsonDocument filter = ((ListCollectionsOperation) obj).getFilter(); return limitFilter(filter.toString()); } else if (obj instanceof MapReduceWithInlineResultsOperation) { BsonDocument filter = ((MapReduceWithInlineResultsOperation) obj).getFilter(); return limitFilter(filter.toString()); - } else if (obj instanceof DeleteOperation) { - List writeRequestList = ((DeleteOperation) obj).getDeleteRequests(); - return getFilter(writeRequestList); - } else if (obj instanceof InsertOperation) { - List writeRequestList = ((InsertOperation) obj).getInsertRequests(); - return getFilter(writeRequestList); - } else if (obj instanceof UpdateOperation) { - List writeRequestList = ((UpdateOperation) obj).getUpdateRequests(); - return getFilter(writeRequestList); - } else if (obj instanceof CreateCollectionOperation) { + } else if (HAS_LEGACY_WRITE_OPERATIONS) { + String result = LegacyOperationHelper.getTraceParam(obj); + if (result != null) { + return result; + } + return getCommonTraceParam(obj); + } else { + return getCommonTraceParam(obj); + } + } + + private static String getCommonTraceParam(Object obj) { + if (obj instanceof CreateCollectionOperation) { String filter = ((CreateCollectionOperation) obj).getCollectionName(); return limitFilter(filter); } else if (obj instanceof CreateIndexesOperation) { @@ -101,6 +117,9 @@ public static String getTraceParam(Object obj) { } else if (obj instanceof FindAndUpdateOperation) { BsonDocument filter = ((FindAndUpdateOperation) obj).getFilter(); return limitFilter(filter.toString()); + } else if (obj instanceof AggregateOperation) { + List pipelines = ((AggregateOperation) obj).getPipeline(); + return getPipelines(pipelines); } else if (obj instanceof MapReduceToCollectionOperation) { BsonDocument filter = ((MapReduceToCollectionOperation) obj).getFilter(); return limitFilter(filter.toString()); @@ -112,7 +131,19 @@ public static String getTraceParam(Object obj) { } } - private static String getFilter(List writeRequestList) { + private static String getPipelines(List pipelines) { + StringBuilder params = new StringBuilder(); + for (BsonDocument pipeline : pipelines) { + params.append(pipeline.toString()).append(","); + final int filterLengthLimit = MongoPluginConfig.Plugin.MongoDB.FILTER_LENGTH_LIMIT; + if (filterLengthLimit > 0 && params.length() > filterLengthLimit) { + return params.substring(0, filterLengthLimit) + "..."; + } + } + return params.toString(); + } + + static String getFilter(List writeRequestList) { StringBuilder params = new StringBuilder(); for (WriteRequest request : writeRequestList) { if (request instanceof InsertRequest) { diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoRemotePeerHelper.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoRemotePeerHelper.java index 1dcdb288f8..8fec2f8f4b 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoRemotePeerHelper.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoRemotePeerHelper.java @@ -35,7 +35,7 @@ private MongoRemotePeerHelper() { */ public static String getRemotePeer(Cluster cluster) { StringBuilder peersBuilder = new StringBuilder(); - for (ServerDescription description : cluster.getDescription().getServerDescriptions()) { + for (ServerDescription description : cluster.getCurrentDescription().getServerDescriptions()) { ServerAddress address = description.getAddress(); peersBuilder.append(address.getHost()).append(":").append(address.getPort()).append(";"); } diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoSpanHelper.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoSpanHelper.java index 4f81a0f058..d87b66a825 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoSpanHelper.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoSpanHelper.java @@ -20,34 +20,44 @@ import org.apache.skywalking.apm.agent.core.context.ContextCarrier; import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.AbstractTag; import org.apache.skywalking.apm.agent.core.context.tag.Tags; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.util.StringUtil; public class MongoSpanHelper { + private static final AbstractTag DB_COLLECTION_TAG = Tags.ofKey("db.collection"); + private MongoSpanHelper() { } /** * createExitSpan + * * @param executeMethod executeMethod - * @param remotePeer remotePeer - * @param operation operation + * @param remotePeer remotePeer + * @param operation operation */ public static void createExitSpan(String executeMethod, String remotePeer, Object operation) { AbstractSpan span = ContextManager.createExitSpan( - MongoConstants.MONGO_DB_OP_PREFIX + executeMethod, new ContextCarrier(), remotePeer); + MongoConstants.MONGO_DB_OP_PREFIX + executeMethod, new ContextCarrier(), remotePeer); span.setComponent(ComponentsDefine.MONGO_DRIVER); Tags.DB_TYPE.set(span, MongoConstants.DB_TYPE); SpanLayer.asDB(span); if (operation instanceof EnhancedInstance) { - Object databaseName = ((EnhancedInstance) operation).getSkyWalkingDynamicField(); - if (databaseName != null) { - Tags.DB_INSTANCE.set(span, (String) databaseName); + MongoNamespaceInfo mongoNamespaceInfo = (MongoNamespaceInfo) ((EnhancedInstance) operation).getSkyWalkingDynamicField(); + if (mongoNamespaceInfo != null) { + if (StringUtil.isNotEmpty(mongoNamespaceInfo.getDatabaseName())) { + Tags.DB_INSTANCE.set(span, mongoNamespaceInfo.getDatabaseName()); + } + if (StringUtil.isNotEmpty(mongoNamespaceInfo.getCollectionName())) { + span.tag(DB_COLLECTION_TAG, mongoNamespaceInfo.getCollectionName()); + } } } @@ -56,3 +66,4 @@ public static void createExitSpan(String executeMethod, String remotePeer, Objec } } } + diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v4/MongoDBOperationExecutorInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v4/MongoDBOperationExecutorInterceptorTest.java index ed2154056e..4bb4b1213b 100644 --- a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v4/MongoDBOperationExecutorInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v4/MongoDBOperationExecutorInterceptorTest.java @@ -18,12 +18,13 @@ package org.apache.skywalking.apm.plugin.mongodb.v4; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import java.lang.reflect.Method; -import java.util.List; +import com.mongodb.MongoNamespace; +import com.mongodb.ReadConcern; +import com.mongodb.client.internal.OperationExecutor; +import com.mongodb.internal.operation.AggregateOperation; +import com.mongodb.internal.operation.CreateCollectionOperation; +import com.mongodb.internal.operation.FindOperation; +import com.mongodb.internal.operation.WriteOperation; import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; import org.apache.skywalking.apm.agent.core.context.trace.LogDataEntity; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; @@ -38,24 +39,34 @@ import org.apache.skywalking.apm.agent.test.tools.SpanAssert; import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; import org.apache.skywalking.apm.plugin.mongodb.v4.interceptor.MongoDBOperationExecutorInterceptor; +import org.apache.skywalking.apm.plugin.mongodb.v4.support.MongoNamespaceInfo; +import org.apache.skywalking.apm.plugin.mongodb.v4.interceptor.operation.OperationNamespaceConstructInterceptor; import org.apache.skywalking.apm.plugin.mongodb.v4.support.MongoPluginConfig; import org.bson.BsonDocument; import org.bson.BsonString; import org.bson.codecs.Decoder; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; +import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import com.mongodb.MongoNamespace; -import com.mongodb.ReadConcern; -import com.mongodb.client.internal.OperationExecutor; -import com.mongodb.internal.operation.FindOperation; -import com.mongodb.internal.operation.WriteOperation; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @RunWith(TracingSegmentRunner.class) public class MongoDBOperationExecutorInterceptorTest { @@ -71,32 +82,85 @@ public class MongoDBOperationExecutorInterceptorTest { @Mock private EnhancedInstance enhancedInstance; + private FindOperation enhancedInstanceForFindOperation; + + @Spy + private EnhancedInstance enhancedObjInstance = new EnhancedInstance() { + private MongoNamespaceInfo namespace; + + @Override + public Object getSkyWalkingDynamicField() { + return namespace; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.namespace = (MongoNamespaceInfo) value; + } + }; + private MongoDBOperationExecutorInterceptor interceptor; + private OperationNamespaceConstructInterceptor constructInterceptor; + private Object[] arguments; private Class[] argumentTypes; + private Decoder decoder; + + private MongoNamespace mongoNamespace; + + private MongoNamespaceInfo mongoNamespaceInfo; + @Before public void setUp() { interceptor = new MongoDBOperationExecutorInterceptor(); - + constructInterceptor = new OperationNamespaceConstructInterceptor(); + enhancedInstanceForFindOperation = mock(FindOperation.class, Mockito.withSettings().extraInterfaces(EnhancedInstance.class)); MongoPluginConfig.Plugin.MongoDB.TRACE_PARAM = true; - - when(enhancedInstance.getSkyWalkingDynamicField()).thenReturn("127.0.0.1:27017"); - + decoder = mock(Decoder.class); + mongoNamespace = new MongoNamespace("test.user"); + mongoNamespaceInfo = new MongoNamespaceInfo(mongoNamespace); BsonDocument document = new BsonDocument(); document.append("name", new BsonString("by")); - MongoNamespace mongoNamespace = new MongoNamespace("test.user"); - Decoder decoder = mock(Decoder.class); + FindOperation findOperation = new FindOperation(mongoNamespace, decoder); findOperation.filter(document); - - arguments = new Object[] {findOperation}; + decoder = mock(Decoder.class); + when(enhancedInstance.getSkyWalkingDynamicField()).thenReturn("127.0.0.1:27017"); + enhancedInstanceForFindOperation = mock(FindOperation.class, Mockito.withSettings().extraInterfaces(EnhancedInstance.class)); + when(((EnhancedInstance) enhancedInstanceForFindOperation).getSkyWalkingDynamicField()).thenReturn(mongoNamespaceInfo); + when(enhancedInstanceForFindOperation.getFilter()).thenReturn(findOperation.getFilter()); + arguments = new Object[] {enhancedInstanceForFindOperation}; argumentTypes = new Class[] {findOperation.getClass()}; } + @Test + public void testConstructIntercept() throws Throwable { + constructInterceptor.onConstruct(enhancedObjInstance, new Object[]{mongoNamespace}); + MatcherAssert.assertThat(enhancedObjInstance.getSkyWalkingDynamicField(), Is.is(new MongoNamespaceInfo(mongoNamespace))); + } + + @Test + public void testCreateCollectionOperationIntercept() throws Throwable { + CreateCollectionOperation createCollectionOperation = new CreateCollectionOperation("test", "user"); + CreateCollectionOperation enhancedInstanceForCreateCollectionOperation = mock(CreateCollectionOperation.class, Mockito.withSettings().extraInterfaces(EnhancedInstance.class)); + when(((EnhancedInstance) enhancedInstanceForCreateCollectionOperation).getSkyWalkingDynamicField()).thenReturn(new MongoNamespaceInfo("test")); + when(enhancedInstanceForCreateCollectionOperation.getCollectionName()).thenReturn("user"); + + Object[] arguments = {enhancedInstanceForCreateCollectionOperation}; + Class[] argumentTypes = {createCollectionOperation.getClass()}; + interceptor.beforeMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); + interceptor.afterMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); + + MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertMongoCreateCollectionOperationSpan(spans.get(0)); + } + @Test public void testIntercept() throws Throwable { interceptor.beforeMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); @@ -105,31 +169,77 @@ public void testIntercept() throws Throwable { MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); List spans = SegmentHelper.getSpans(traceSegment); - assertMongoSpan(spans.get(0)); + assertMongoFindOperationSpan(spans.get(0)); + } + + @Test + public void testAggregateOperationIntercept() throws Throwable { + MongoNamespace mongoNamespace = new MongoNamespace("test.user"); + BsonDocument matchStage = new BsonDocument("$match", new BsonDocument("name", new BsonString("by"))); + List pipeline = Collections.singletonList(matchStage); + AggregateOperation aggregateOperation = new AggregateOperation(mongoNamespace, pipeline, decoder); + + AggregateOperation enhancedInstanceForAggregateOperation = mock(AggregateOperation.class, Mockito.withSettings().extraInterfaces(EnhancedInstance.class)); + when(((EnhancedInstance) enhancedInstanceForAggregateOperation).getSkyWalkingDynamicField()).thenReturn(new MongoNamespaceInfo(mongoNamespace)); + when(enhancedInstanceForAggregateOperation.getPipeline()).thenReturn(aggregateOperation.getPipeline()); + Object[] arguments = {enhancedInstanceForAggregateOperation}; + Class[] argumentTypes = {aggregateOperation.getClass()}; + + interceptor.beforeMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); + interceptor.afterMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); + + MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertMongoAggregateOperationSpan(spans.get(0)); } @Test public void testInterceptWithException() throws Throwable { interceptor.beforeMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); - interceptor.handleMethodException( - enhancedInstance, getMethod(), arguments, argumentTypes, new RuntimeException()); + interceptor.handleMethodException(enhancedInstance, getMethod(), arguments, argumentTypes, new RuntimeException()); interceptor.afterMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); List spans = SegmentHelper.getSpans(traceSegment); - assertMongoSpan(spans.get(0)); + assertMongoFindOperationSpan(spans.get(0)); List logDataEntities = SpanHelper.getLogs(spans.get(0)); assertThat(logDataEntities.size(), is(1)); SpanAssert.assertException(logDataEntities.get(0), RuntimeException.class); } - private void assertMongoSpan(AbstractTracingSpan span) { - assertThat(span.getOperationName(), is("MongoDB/FindOperation")); + private void assertMongoFindOperationSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), startsWith("MongoDB/FindOperation")); + assertThat(SpanHelper.getComponentId(span), is(42)); + List tags = SpanHelper.getTags(span); + assertThat(tags.get(0).getValue(), is("MongoDB")); + assertThat(tags.get(1).getValue(), is("test")); + assertThat(tags.get(2).getValue(), is("user")); + assertThat(tags.get(3).getValue(), is("{\"name\": \"by\"}")); + assertThat(span.isExit(), is(true)); + assertThat(SpanHelper.getLayer(span), CoreMatchers.is(SpanLayer.DB)); + } + + private void assertMongoCreateCollectionOperationSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), startsWith("MongoDB/CreateCollectionOperation")); + assertThat(SpanHelper.getComponentId(span), is(42)); + List tags = SpanHelper.getTags(span); + assertThat(tags.get(0).getValue(), is("MongoDB")); + assertThat(tags.get(1).getValue(), is("test")); + assertThat(tags.get(2).getValue(), is("user")); + assertThat(span.isExit(), is(true)); + assertThat(SpanHelper.getLayer(span), CoreMatchers.is(SpanLayer.DB)); + } + + private void assertMongoAggregateOperationSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), startsWith("MongoDB/AggregateOperation")); assertThat(SpanHelper.getComponentId(span), is(42)); List tags = SpanHelper.getTags(span); - assertThat(tags.get(1).getValue(), is("{\"name\": \"by\"}")); assertThat(tags.get(0).getValue(), is("MongoDB")); + assertThat(tags.get(1).getValue(), is("test")); + assertThat(tags.get(2).getValue(), is("user")); + assertThat(tags.get(3).getValue(), is("{\"$match\": {\"name\": \"by\"}},")); assertThat(span.isExit(), is(true)); assertThat(SpanHelper.getLayer(span), CoreMatchers.is(SpanLayer.DB)); } diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/pom.xml new file mode 100644 index 0000000000..141dbd464a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/pom.xml @@ -0,0 +1,50 @@ + + + + + 4.0.0 + + + apm-sdk-plugin + org.apache.skywalking + 9.7.0-SNAPSHOT + + + apm-mongodb-5.x-plugin + jar + + mongodb-5.x-plugin + + + + org.mongodb + mongodb-driver-sync + 5.2.0 + provided + + + org.apache.skywalking + apm-mongodb-4.x-plugin + ${project.version} + provided + + + diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/define/ApplicationDispatcherInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/define/MongoClusterImplInstrumentation.java similarity index 71% rename from apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/define/ApplicationDispatcherInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/define/MongoClusterImplInstrumentation.java index 048ee28f38..96b834fbca 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/define/ApplicationDispatcherInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/define/MongoClusterImplInstrumentation.java @@ -16,7 +16,7 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat10x.define; +package org.apache.skywalking.apm.plugin.mongodb.v5.define; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -25,19 +25,30 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; -import static net.bytebuddy.matcher.ElementMatchers.any; import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; -public class ApplicationDispatcherInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { +/** + * Enhance {@code com.mongodb.client.internal.MongoClusterImpl} which replaces + * {@code MongoClientDelegate} in MongoDB driver 5.2+. + * Extract remotePeer from Cluster (constructor arg[1]) and store in dynamic field. + */ +public class MongoClusterImplInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.client.internal.MongoClusterImpl"; - private static final String ENHANCE_CLASS = "org.apache.catalina.core.ApplicationDispatcher"; - private static final String ENHANCE_METHOD = "forward"; - public static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.tomcat10x.ForwardInterceptor"; + private static final String CONSTRUCTOR_INTERCEPTOR = + "org.apache.skywalking.apm.plugin.mongodb.v5.interceptor.MongoClusterImplConstructorInterceptor"; @Override protected String[] witnessClasses() { - return new String[]{"jakarta.servlet.http.HttpServletResponse"}; + return new String[] {ENHANCE_CLASS}; + } + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); } @Override @@ -46,12 +57,12 @@ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { new ConstructorInterceptPoint() { @Override public ElementMatcher getConstructorMatcher() { - return any(); + return takesArgumentWithType(1, "com.mongodb.internal.connection.Cluster"); } @Override public String getConstructorInterceptor() { - return INTERCEPTOR_CLASS; + return CONSTRUCTOR_INTERCEPTOR; } } }; @@ -63,12 +74,12 @@ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { new InstanceMethodsInterceptPoint() { @Override public ElementMatcher getMethodsMatcher() { - return named(ENHANCE_METHOD); + return named("getOperationExecutor"); } @Override public String getMethodsInterceptor() { - return INTERCEPTOR_CLASS; + return CONSTRUCTOR_INTERCEPTOR; } @Override @@ -78,9 +89,4 @@ public boolean isOverrideArgs() { } }; } - - @Override - protected ClassMatch enhanceClass() { - return byName(ENHANCE_CLASS); - } } diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/define/MongoClusterOperationExecutorInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/define/MongoClusterOperationExecutorInstrumentation.java new file mode 100644 index 0000000000..53bdb6da93 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/define/MongoClusterOperationExecutorInstrumentation.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v5.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +/** + * Enhance {@code MongoClusterImpl$OperationExecutorImpl} in MongoDB driver 5.2+. + *

+ * Constructor interception: propagate remotePeer from enclosing MongoClusterImpl + * (synthetic arg[0] for non-static inner class). + *

+ * Method interception: create exit spans on execute() calls. + * Reuses the 4.x MongoDBOperationExecutorInterceptor. + */ +public class MongoClusterOperationExecutorInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = + "com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl"; + + private static final String CONSTRUCTOR_INTERCEPTOR = + "org.apache.skywalking.apm.plugin.mongodb.v5.interceptor.OperationExecutorImplConstructorInterceptor"; + + private static final String EXECUTE_INTERCEPTOR = + "org.apache.skywalking.apm.plugin.mongodb.v4.interceptor.MongoDBOperationExecutorInterceptor"; + + private static final String METHOD_NAME = "execute"; + + private static final String ARGUMENT_TYPE = "com.mongodb.client.ClientSession"; + + @Override + protected String[] witnessClasses() { + return new String[] {"com.mongodb.client.internal.MongoClusterImpl"}; + } + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return ElementMatchers.any(); + } + + @Override + public String getConstructorInterceptor() { + return CONSTRUCTOR_INTERCEPTOR; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers + .named(METHOD_NAME) + .and(ArgumentTypeNameMatch.takesArgumentWithType(2, ARGUMENT_TYPE)) + .or(ElementMatchers.named(METHOD_NAME) + .and(ArgumentTypeNameMatch.takesArgumentWithType(3, ARGUMENT_TYPE))); + } + + @Override + public String getMethodsInterceptor() { + return EXECUTE_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/interceptor/MongoClusterImplConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/interceptor/MongoClusterImplConstructorInterceptor.java new file mode 100644 index 0000000000..242b450c45 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/interceptor/MongoClusterImplConstructorInterceptor.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v5.interceptor; + +import com.mongodb.internal.connection.Cluster; +import org.apache.skywalking.apm.agent.core.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.mongodb.v4.support.MongoRemotePeerHelper; + +import java.lang.reflect.Method; + +/** + * Intercept {@code MongoClusterImpl} constructor and {@code getOperationExecutor()}. + *

+ * Constructor: extract remotePeer from Cluster (arg[1]) and store in dynamic field. + * getOperationExecutor(): pass remotePeer to the returned OperationExecutor. + */ +public class MongoClusterImplConstructorInterceptor + implements InstanceConstructorInterceptor, InstanceMethodsAroundInterceptor { + + private static final ILog LOGGER = LogManager.getLogger(MongoClusterImplConstructorInterceptor.class); + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + Cluster cluster = (Cluster) allArguments[1]; + String remotePeer = MongoRemotePeerHelper.getRemotePeer(cluster); + objInst.setSkyWalkingDynamicField(remotePeer); + + // The OperationExecutorImpl is created INSIDE this constructor (before onConstruct fires), + // so its constructor interceptor couldn't read the peer yet. Set it now. + // MongoClusterImpl is package-private and loaded by application classloader. + // Same-package helpers from agent classloader cannot access it (different runtime packages). + // Use setAccessible reflection to call getOperationExecutor(). + try { + java.lang.reflect.Method getExecutor = objInst.getClass().getMethod("getOperationExecutor"); + getExecutor.setAccessible(true); + Object executor = getExecutor.invoke(objInst); + if (executor instanceof EnhancedInstance) { + ((EnhancedInstance) executor).setSkyWalkingDynamicField(remotePeer); + } + } catch (Exception e) { + LOGGER.warn("Failed to set remotePeer on OperationExecutor", e); + } + } + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, MethodInterceptResult result) { + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Object ret) { + if (ret instanceof EnhancedInstance) { + EnhancedInstance retInstance = (EnhancedInstance) ret; + String remotePeer = (String) objInst.getSkyWalkingDynamicField(); + if (LOGGER.isDebugEnable()) { + LOGGER.debug("Mark OperationExecutor remotePeer: {}", remotePeer); + } + retInstance.setSkyWalkingDynamicField(remotePeer); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/interceptor/OperationExecutorImplConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/interceptor/OperationExecutorImplConstructorInterceptor.java new file mode 100644 index 0000000000..0b7c11862e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/interceptor/OperationExecutorImplConstructorInterceptor.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.mongodb.v5.interceptor; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; + +/** + * Intercept {@code MongoClusterImpl$OperationExecutorImpl} constructor. + * As a non-static inner class, the compiled constructor has the enclosing + * {@code MongoClusterImpl} instance as a synthetic first argument (arg index 0). + * + * Note: This interceptor fires during MongoClusterImpl's constructor, before + * MongoClusterImpl.onConstruct() sets the remotePeer. So the dynamic field + * on the enclosing instance is not yet set. The primary peer propagation + * happens in MongoClusterImplConstructorInterceptor.onConstruct() which + * calls getOperationExecutor() after the constructor completes. + * + * This interceptor serves as a secondary path for OperationExecutorImpl + * instances created later (e.g., via withTimeoutSettings()). + */ +public class OperationExecutorImplConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + for (Object arg : allArguments) { + if (arg instanceof EnhancedInstance) { + EnhancedInstance enclosingInstance = (EnhancedInstance) arg; + String remotePeer = (String) enclosingInstance.getSkyWalkingDynamicField(); + if (remotePeer != null && !remotePeer.isEmpty()) { + objInst.setSkyWalkingDynamicField(remotePeer); + return; + } + } + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..fcd2e7be8f --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# MongoDB 5.2+ (MongoClusterImpl replaces MongoClientDelegate) +mongodb-5.x=org.apache.skywalking.apm.plugin.mongodb.v5.define.MongoClusterImplInstrumentation +mongodb-5.x=org.apache.skywalking.apm.plugin.mongodb.v5.define.MongoClusterOperationExecutorInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/motan-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/motan-plugin/pom.xml index 51b3fec800..b74bc6d54d 100644 --- a/apm-sniffer/apm-sdk-plugin/motan-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/motan-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/mssql-commons/pom.xml b/apm-sniffer/apm-sdk-plugin/mssql-commons/pom.xml index 6cbcb3a4af..ff9f083f6d 100644 --- a/apm-sniffer/apm-sdk-plugin/mssql-commons/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/mssql-commons/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/mssql-jdbc-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/mssql-jdbc-plugin/pom.xml index e8da306dd9..ff678a1a00 100644 --- a/apm-sniffer/apm-sdk-plugin/mssql-jdbc-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/mssql-jdbc-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/mssql-jtds-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/mssql-jtds-1.x-plugin/pom.xml index be33212cbe..2f3dd90ea6 100644 --- a/apm-sniffer/apm-sdk-plugin/mssql-jtds-1.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/mssql-jtds-1.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/mysql-5.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/mysql-5.x-plugin/pom.xml index babac7d15d..361f5bc821 100755 --- a/apm-sniffer/apm-sdk-plugin/mysql-5.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/mysql-5.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/mysql-6.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/mysql-6.x-plugin/pom.xml index 932e2aadf6..edaef2cf28 100755 --- a/apm-sniffer/apm-sdk-plugin/mysql-6.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/mysql-6.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/mysql-8.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/mysql-8.x-plugin/pom.xml index fd95d69452..11ee950a57 100755 --- a/apm-sniffer/apm-sdk-plugin/mysql-8.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/mysql-8.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/mysql-common/pom.xml b/apm-sniffer/apm-sdk-plugin/mysql-common/pom.xml index 4ce77ba866..92aa8ee5d8 100755 --- a/apm-sniffer/apm-sdk-plugin/mysql-common/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/mysql-common/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/mysql-common/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mysql/StatementExecuteMethodsInterceptor.java b/apm-sniffer/apm-sdk-plugin/mysql-common/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mysql/StatementExecuteMethodsInterceptor.java index 8895e118a5..0c2f5047b9 100644 --- a/apm-sniffer/apm-sdk-plugin/mysql-common/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mysql/StatementExecuteMethodsInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/mysql-common/src/main/java/org/apache/skywalking/apm/plugin/jdbc/mysql/StatementExecuteMethodsInterceptor.java @@ -28,6 +28,7 @@ import org.apache.skywalking.apm.plugin.jdbc.SqlBodyUtil; import org.apache.skywalking.apm.plugin.jdbc.define.StatementEnhanceInfos; import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo; +import org.apache.skywalking.apm.util.StringUtil; import java.lang.reflect.Method; @@ -52,14 +53,17 @@ public final void beforeMethod(EnhancedInstance objInst, Method method, Object[] Tags.DB_INSTANCE.set(span, connectInfo.getDatabaseName()); /** - * The first argument of all intercept method in `com.mysql.jdbc.StatementImpl` class is SQL, except the - * `executeBatch` method that the jdbc plugin need to trace, because of this method argument size is zero. + * Except for the `executeBatch` method, the first parameter of all enhanced methods in `com.mysql.jdbc.StatementImpl` is the SQL statement. + * Therefore, executeBatch will attempt to obtain the SQL from `cacheObject`. */ String sql = ""; if (allArguments.length > 0) { sql = (String) allArguments[0]; sql = SqlBodyUtil.limitSqlBodySize(sql); + } else if (StringUtil.isNotBlank(cacheObject.getSql())) { + sql = SqlBodyUtil.limitSqlBodySize(cacheObject.getSql()); } + Tags.DB_STATEMENT.set(span, sql); span.setComponent(connectInfo.getComponent()); diff --git a/apm-sniffer/apm-sdk-plugin/mysql-common/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mysql/StatementExecuteMethodsInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/mysql-common/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mysql/StatementExecuteMethodsInterceptorTest.java index e3ceaa6228..df2769b8cd 100644 --- a/apm-sniffer/apm-sdk-plugin/mysql-common/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mysql/StatementExecuteMethodsInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/mysql-common/src/test/java/org/apache/skywalking/apm/plugin/jdbc/mysql/StatementExecuteMethodsInterceptorTest.java @@ -72,7 +72,8 @@ public void setUp() { JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = 2048; serviceMethodInterceptor = new StatementExecuteMethodsInterceptor(); - enhanceRequireCacheObject = new StatementEnhanceInfos(connectionInfo, "SELECT * FROM test", "CallableStatement"); + enhanceRequireCacheObject = new StatementEnhanceInfos(connectionInfo, SQL, "CallableStatement"); + when(objectInstance.getSkyWalkingDynamicField()).thenReturn(enhanceRequireCacheObject); when(method.getName()).thenReturn("executeQuery"); when(connectionInfo.getComponent()).thenReturn(ComponentsDefine.H2_JDBC_DRIVER); @@ -81,6 +82,24 @@ public void setUp() { when(connectionInfo.getDatabasePeer()).thenReturn("localhost:3307"); } + @Test + public void testCreateDatabaseSpanWithNoMethodParamButWithCache() throws Throwable { + JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = 2048; + + serviceMethodInterceptor.beforeMethod(objectInstance, method, new Object[0], null, null); + serviceMethodInterceptor.afterMethod(objectInstance, method, new Object[0], null, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment segment = segmentStorage.getTraceSegments().get(0); + assertThat(SegmentHelper.getSpans(segment).size(), is(1)); + AbstractTracingSpan span = SegmentHelper.getSpans(segment).get(0); + SpanAssert.assertLayer(span, SpanLayer.DB); + assertThat(span.getOperationName(), is("H2/JDBC/CallableStatement/executeQuery")); + SpanAssert.assertTag(span, 0, "H2"); + SpanAssert.assertTag(span, 1, "test"); + SpanAssert.assertTag(span, 2, SQL); + } + @Test public void testCreateDatabaseSpan() throws Throwable { JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH = 2048; diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/pom.xml similarity index 85% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/pom.xml rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/pom.xml index 8ec619ea33..6e115d8ade 100644 --- a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/pom.xml @@ -21,19 +21,14 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 - nats-2.14.x-2.15.x-plugin + nats-2.14.x-2.16.5-plugin jar - - 8 - 8 - - io.nats diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/CreateDispatcherInterceptor.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/CreateDispatcherInterceptor.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/CreateDispatcherInterceptor.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/CreateDispatcherInterceptor.java diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/CreateSubInterceptor.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/CreateSubInterceptor.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/CreateSubInterceptor.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/CreateSubInterceptor.java diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/DeliverReplyInterceptor.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/DeliverReplyInterceptor.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/DeliverReplyInterceptor.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/DeliverReplyInterceptor.java diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsCommons.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsCommons.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsCommons.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsCommons.java diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsConnectionWriterConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsConnectionWriterConstructorInterceptor.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsConnectionWriterConstructorInterceptor.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsConnectionWriterConstructorInterceptor.java diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsJetStreamConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsJetStreamConstructorInterceptor.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsJetStreamConstructorInterceptor.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsJetStreamConstructorInterceptor.java diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsMessageInterceptor.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsMessageInterceptor.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsMessageInterceptor.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsMessageInterceptor.java diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsSubscriptionConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsSubscriptionConstructorInterceptor.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsSubscriptionConstructorInterceptor.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/NatsSubscriptionConstructorInterceptor.java diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/SubscriptionNextMsgInterceptor.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/SubscriptionNextMsgInterceptor.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/SubscriptionNextMsgInterceptor.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/SubscriptionNextMsgInterceptor.java diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/WriterQueueInterceptor.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/WriterQueueInterceptor.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/WriterQueueInterceptor.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/WriterQueueInterceptor.java diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/WriterSendMessageBatchInterceptor.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/WriterSendMessageBatchInterceptor.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/WriterSendMessageBatchInterceptor.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/WriterSendMessageBatchInterceptor.java diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/AbstractWitnessInstrumentation.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/AbstractWitnessInstrumentation.java new file mode 100644 index 0000000000..1acf6e525e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/AbstractWitnessInstrumentation.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.nats.client.define; + +import java.util.Collections; +import java.util.List; +import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +public abstract class AbstractWitnessInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + /* + * Currently, we only support 2.14.x-2.16.5, as there is no proper way and opportunity + * to change the message header and re-calculate the message length for 2.16.5+ yet. + * This method prevents users from applying this plugin to unsupported versions, + * which may cause unknown errors. + * For more information: https://github.com/apache/skywalking/discussions/11650 + */ + @Override + protected List witnessMethods() { + return Collections.singletonList(new WitnessMethod( + "io.nats.client.impl.NatsMessage", + named("calculateIfDirty") + )); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsConnectionInstrumentation.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsConnectionInstrumentation.java similarity index 94% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsConnectionInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsConnectionInstrumentation.java index 1822732b15..9b2d5087d7 100644 --- a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsConnectionInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsConnectionInstrumentation.java @@ -21,14 +21,13 @@ import net.bytebuddy.matcher.ElementMatcher; import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; -public class NatsConnectionInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { +public class NatsConnectionInstrumentation extends AbstractWitnessInstrumentation { private static final String ENHANCE_CLASS = "io.nats.client.impl.NatsConnection"; @@ -82,4 +81,4 @@ public boolean isOverrideArgs() { } }; } -} \ No newline at end of file +} diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsConnectionWriterInstrumentation.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsConnectionWriterInstrumentation.java similarity index 95% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsConnectionWriterInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsConnectionWriterInstrumentation.java index f3a315314f..b6e56ddc4a 100644 --- a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsConnectionWriterInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsConnectionWriterInstrumentation.java @@ -21,14 +21,13 @@ import net.bytebuddy.matcher.ElementMatcher; import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; -public class NatsConnectionWriterInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { +public class NatsConnectionWriterInstrumentation extends AbstractWitnessInstrumentation { private static final String ENHANCE_CLASS = "io.nats.client.impl.NatsConnectionWriter"; private static final String PUBLISH_INTERCEPTOR_CLASS_NAME = "org.apache.skywalking.apm.plugin.nats.client.WriterSendMessageBatchInterceptor"; diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsJetStreamInstrumentation.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsJetStreamInstrumentation.java similarity index 94% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsJetStreamInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsJetStreamInstrumentation.java index a635658c4a..cf940e7e39 100644 --- a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsJetStreamInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsJetStreamInstrumentation.java @@ -21,14 +21,13 @@ import net.bytebuddy.matcher.ElementMatcher; import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; -public class NatsJetStreamInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { +public class NatsJetStreamInstrumentation extends AbstractWitnessInstrumentation { private static final String ENHANCE_CLASS = "io.nats.client.impl.NatsJetStream"; private static final String CREATE_SUB_INTERCEPTOR = "org.apache.skywalking.apm.plugin.nats.client.CreateSubInterceptor"; @@ -78,4 +77,4 @@ public boolean isOverrideArgs() { }; } -} \ No newline at end of file +} diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsMessageInstrumentation.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsMessageInstrumentation.java similarity index 93% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsMessageInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsMessageInstrumentation.java index 79d061a396..95d229690b 100644 --- a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsMessageInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsMessageInstrumentation.java @@ -21,7 +21,6 @@ import net.bytebuddy.matcher.ElementMatcher; import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -35,7 +34,7 @@ * * BTW , ACK is done by publishing a message , So we needn't enhance ACK method */ -public class NatsMessageInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { +public class NatsMessageInstrumentation extends AbstractWitnessInstrumentation { private static final String ENHANCE_CLASS = "io.nats.client.impl.NatsMessage"; private static final String PUBLISH_INTERCEPTOR = "org.apache.skywalking.apm.plugin.nats.client.NatsMessageInterceptor"; diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsSubscriptionInstrumentation.java b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsSubscriptionInstrumentation.java similarity index 93% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsSubscriptionInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsSubscriptionInstrumentation.java index a04433722a..62bc14b017 100644 --- a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsSubscriptionInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/java/org/apache/skywalking/apm/plugin/nats/client/define/NatsSubscriptionInstrumentation.java @@ -21,14 +21,13 @@ import net.bytebuddy.matcher.ElementMatcher; import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; -public class NatsSubscriptionInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { +public class NatsSubscriptionInstrumentation extends AbstractWitnessInstrumentation { private static final String ENHANCE_CLASS = "io.nats.client.impl.NatsSubscription"; private static final String NEXT_MSG_INTERCEPTOR = "org.apache.skywalking.apm.plugin.nats.client.SubscriptionNextMsgInterceptor"; diff --git a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/resources/skywalking-plugin.def similarity index 72% rename from apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/resources/skywalking-plugin.def rename to apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/resources/skywalking-plugin.def index cc15654fcf..cbbf148d66 100644 --- a/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.15.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/apm-sdk-plugin/nats-2.14.x-2.16.5-plugin/src/main/resources/skywalking-plugin.def @@ -14,8 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -nats-client-2.14.x-2.15.x=org.apache.skywalking.apm.plugin.nats.client.define.NatsMessageInstrumentation -nats-client-2.14.x-2.15.x=org.apache.skywalking.apm.plugin.nats.client.define.NatsConnectionInstrumentation -nats-client-2.14.x-2.15.x=org.apache.skywalking.apm.plugin.nats.client.define.NatsConnectionWriterInstrumentation -nats-client-2.14.x-2.15.x=org.apache.skywalking.apm.plugin.nats.client.define.NatsSubscriptionInstrumentation -nats-client-2.14.x-2.15.x=org.apache.skywalking.apm.plugin.nats.client.define.NatsJetStreamInstrumentation \ No newline at end of file +nats-client-2.14.x-2.16.5=org.apache.skywalking.apm.plugin.nats.client.define.NatsMessageInstrumentation +nats-client-2.14.x-2.16.5=org.apache.skywalking.apm.plugin.nats.client.define.NatsConnectionInstrumentation +nats-client-2.14.x-2.16.5=org.apache.skywalking.apm.plugin.nats.client.define.NatsConnectionWriterInstrumentation +nats-client-2.14.x-2.16.5=org.apache.skywalking.apm.plugin.nats.client.define.NatsSubscriptionInstrumentation +nats-client-2.14.x-2.16.5=org.apache.skywalking.apm.plugin.nats.client.define.NatsJetStreamInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/pom.xml index 8cb025d1be..1bf1de4bfd 100644 --- a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/netty-socketio-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/netty-socketio-plugin/pom.xml index bd80a20010..2fc96f1c2f 100644 --- a/apm-sniffer/apm-sdk-plugin/netty-socketio-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/netty-socketio-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/nutz-plugins/http-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/nutz-plugins/http-1.x-plugin/pom.xml index da79858c39..3f2b4d0b67 100644 --- a/apm-sniffer/apm-sdk-plugin/nutz-plugins/http-1.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/nutz-plugins/http-1.x-plugin/pom.xml @@ -20,7 +20,7 @@ nutz-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/nutz-plugins/mvc-annotation-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/nutz-plugins/mvc-annotation-1.x-plugin/pom.xml index b9ba31e760..cd772375c6 100644 --- a/apm-sniffer/apm-sdk-plugin/nutz-plugins/mvc-annotation-1.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/nutz-plugins/mvc-annotation-1.x-plugin/pom.xml @@ -20,7 +20,7 @@ nutz-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/nutz-plugins/pom.xml b/apm-sniffer/apm-sdk-plugin/nutz-plugins/pom.xml index 669753f580..0b8fe2d545 100644 --- a/apm-sniffer/apm-sdk-plugin/nutz-plugins/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/nutz-plugins/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT nutz-plugins @@ -39,7 +39,6 @@ UTF-8 - /.. diff --git a/apm-sniffer/apm-sdk-plugin/okhttp-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/okhttp-2.x-plugin/pom.xml index 770d413b09..14d05d0135 100644 --- a/apm-sniffer/apm-sdk-plugin/okhttp-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/okhttp-2.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/pom.xml index eca6ff770b..b25f764a6e 100644 --- a/apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/okhttp-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/okhttp-4.x-plugin/pom.xml index ce9ce36b53..ce5434d9cb 100644 --- a/apm-sniffer/apm-sdk-plugin/okhttp-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/okhttp-4.x-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/okhttp-common/pom.xml b/apm-sniffer/apm-sdk-plugin/okhttp-common/pom.xml index 3881fd2396..68437b314a 100644 --- a/apm-sniffer/apm-sdk-plugin/okhttp-common/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/okhttp-common/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/play-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/play-2.x-plugin/pom.xml index e5ce15cff8..5c74e133d8 100644 --- a/apm-sniffer/apm-sdk-plugin/play-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/play-2.x-plugin/pom.xml @@ -22,7 +22,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-play-2.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml index bf07afa7a6..8bc2e1faaa 100644 --- a/apm-sniffer/apm-sdk-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pom.xml @@ -23,26 +23,28 @@ org.apache.skywalking java-agent-sniffer - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-sdk-plugin dubbo-plugin jdbc-commons + servlet-commons httpClient-4.x-plugin redisson-3.x-plugin - tomcat-7.x-8.x-plugin - tomcat-10x-plugin + tomcat-plugin motan-plugin mongodb-3.x-plugin mongodb-4.x-plugin + mongodb-5.x-plugin feign-default-http-9.x-plugin okhttp-3.x-plugin okhttp-4.x-plugin okhttp-common spring-plugins struts2-2.x-plugin + struts2-7.x-plugin nutz-plugins jetty-plugin spymemcached-2.x-plugin @@ -70,6 +72,7 @@ elasticsearch-5.x-plugin elasticsearch-6.x-plugin elasticsearch-7.x-plugin + elasticsearch-java-plugin undertow-plugins rabbitmq-plugin dubbo-conflict-patch @@ -87,11 +90,12 @@ netty-socketio-plugin httpclient-3.x-plugin play-2.x-plugin - lettuce-5.x-plugin + lettuce-plugins avro-plugin finagle-6.25.x-plugin quasar-plugin mariadb-2.x-plugin + mariadb-3.x-plugin influxdb-2.x-plugin baidu-brpc-plugin baidu-brpc-3.x-plugin @@ -123,7 +127,7 @@ guava-eventbus-plugin hutool-plugins micronaut-plugins - nats-2.14.x-2.15.x-plugin + nats-2.14.x-2.16.5-plugin jedis-plugins apm-armeria-plugins jetty-thread-pool-plugin @@ -136,6 +140,8 @@ aerospike-plugin rocketMQ-client-java-5.x-plugin activemq-artemis-jakarta-client-2.x-plugin + c3p0-0.9.x-plugin + solon-2.x-plugin pom @@ -144,13 +150,10 @@ UTF-8 - net.bytebuddy ${shade.package}.${shade.net.bytebuddy.source} - ${project.build.directory}${sdk.plugin.related.dir}/../../../../skywalking-agent - - ${agent.package.dest.dir}/plugins + ${maven.multiModuleProjectDirectory}/skywalking-agent/plugins 1.0b3 1.8.1 @@ -166,7 +169,6 @@ net.bytebuddy byte-buddy - ${bytebuddy.version} provided diff --git a/apm-sniffer/apm-sdk-plugin/postgresql-8.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/postgresql-8.x-plugin/pom.xml index 46da28a309..181a48d08c 100755 --- a/apm-sniffer/apm-sdk-plugin/postgresql-8.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/postgresql-8.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/pulsar-2.2-2.7-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pulsar-2.2-2.7-plugin/pom.xml index 27dbb81b6a..862269d01e 100644 --- a/apm-sniffer/apm-sdk-plugin/pulsar-2.2-2.7-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pulsar-2.2-2.7-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/pulsar-2.8.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pulsar-2.8.x-plugin/pom.xml index 7f219a6086..c464911398 100644 --- a/apm-sniffer/apm-sdk-plugin/pulsar-2.8.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pulsar-2.8.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/pulsar-common/pom.xml b/apm-sniffer/apm-sdk-plugin/pulsar-common/pom.xml index ab1a87f751..9679ea88fb 100644 --- a/apm-sniffer/apm-sdk-plugin/pulsar-common/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pulsar-common/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/quasar-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/quasar-plugin/pom.xml index a0a5219174..a980a658d8 100644 --- a/apm-sniffer/apm-sdk-plugin/quasar-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/quasar-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-quasar-plugin diff --git a/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/pom.xml index a4ffebf7d3..492b479b07 100644 --- a/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/rabbitmq/RabbitMQConsumerInterceptor.java b/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/rabbitmq/RabbitMQConsumerInterceptor.java index 50240c9729..3ac0caf121 100644 --- a/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/rabbitmq/RabbitMQConsumerInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/rabbitmq/RabbitMQConsumerInterceptor.java @@ -26,10 +26,19 @@ public class RabbitMQConsumerInterceptor implements InstanceMethodsAroundInterceptor { + public static final String SMLC_INTERNAL_CONSUMER = "org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$InternalConsumer"; + public static final String DMLC_INTERNAL_CONSUMER = "org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer$SimpleConsumer"; + @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { Consumer consumer = (Consumer) allArguments[6]; + if (consumer != null) { + String className = consumer.getClass().getName(); + if (SMLC_INTERNAL_CONSUMER.equals(className) || DMLC_INTERNAL_CONSUMER.equals(className)) { + return; + } + } allArguments[6] = new TracerConsumer(consumer, (String) objInst.getSkyWalkingDynamicField()); } diff --git a/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/rabbitmq/TracerConsumer.java b/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/rabbitmq/TracerConsumer.java index bb6f134fca..c3ae14470e 100644 --- a/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/rabbitmq/TracerConsumer.java +++ b/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/rabbitmq/TracerConsumer.java @@ -50,7 +50,7 @@ public void handleConsumeOk(final String consumerTag) { @Override public void handleCancelOk(final String consumerTag) { - this.delegate.handleRecoverOk(consumerTag); + this.delegate.handleCancelOk(consumerTag); } @Override diff --git a/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/pom.xml index 2e0d14219d..7ba9d05be3 100644 --- a/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-redisson-3.x-plugin @@ -31,7 +31,7 @@ redisson-3.x-plugin http://maven.apache.org - 3.6.0 + 3.20.0 diff --git a/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/ConnectionManagerInterceptor.java b/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/ConnectionManagerInterceptor.java index d4b352deea..8cdb79b9ff 100644 --- a/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/ConnectionManagerInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/ConnectionManagerInterceptor.java @@ -18,6 +18,7 @@ package org.apache.skywalking.apm.plugin.redisson.v3; +import java.util.Objects; import org.apache.skywalking.apm.agent.core.context.util.PeerFormat; import org.apache.skywalking.apm.agent.core.logging.api.ILog; import org.apache.skywalking.apm.agent.core.logging.api.LogManager; @@ -26,11 +27,12 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.plugin.redisson.v3.util.ClassUtil; import org.redisson.config.Config; -import org.redisson.connection.ConnectionManager; import java.lang.reflect.Method; import java.net.URI; import java.util.Collection; +import org.redisson.connection.MasterSlaveConnectionManager; +import org.redisson.connection.ServiceManager; public class ConnectionManagerInterceptor implements InstanceMethodsAroundInterceptor { @@ -45,14 +47,19 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { try { - ConnectionManager connectionManager = (ConnectionManager) objInst; - Config config = connectionManager.getCfg(); - - Object singleServerConfig = ClassUtil.getObjectField(config, "singleServerConfig"); - Object sentinelServersConfig = ClassUtil.getObjectField(config, "sentinelServersConfig"); - Object masterSlaveServersConfig = ClassUtil.getObjectField(config, "masterSlaveServersConfig"); - Object clusterServersConfig = ClassUtil.getObjectField(config, "clusterServersConfig"); - Object replicatedServersConfig = ClassUtil.getObjectField(config, "replicatedServersConfig"); + Config config = getConfig(objInst); + Object singleServerConfig = null; + Object sentinelServersConfig = null; + Object masterSlaveServersConfig = null; + Object clusterServersConfig = null; + Object replicatedServersConfig = null; + if (Objects.nonNull(config)) { + singleServerConfig = ClassUtil.getObjectField(config, "singleServerConfig"); + sentinelServersConfig = ClassUtil.getObjectField(config, "sentinelServersConfig"); + masterSlaveServersConfig = ClassUtil.getObjectField(config, "masterSlaveServersConfig"); + clusterServersConfig = ClassUtil.getObjectField(config, "clusterServersConfig"); + replicatedServersConfig = ClassUtil.getObjectField(config, "replicatedServersConfig"); + } StringBuilder peer = new StringBuilder(); EnhancedInstance retInst = (EnhancedInstance) ret; @@ -70,7 +77,7 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA } if (masterSlaveServersConfig != null) { Object masterAddress = ClassUtil.getObjectField(masterSlaveServersConfig, "masterAddress"); - peer.append(getPeer(masterAddress)); + peer.append(getPeer(masterAddress)).append(";"); appendAddresses(peer, (Collection) ClassUtil.getObjectField(masterSlaveServersConfig, "slaveAddresses")); retInst.setSkyWalkingDynamicField(PeerFormat.shorten(peer.toString())); return ret; @@ -118,6 +125,22 @@ static String getPeer(Object obj) { } } + private Config getConfig(EnhancedInstance objInst) { + Config config = null; + MasterSlaveConnectionManager connectionManager = (MasterSlaveConnectionManager) objInst; + try { + config = (Config) ClassUtil.getObjectField(connectionManager, "cfg"); + } catch (NoSuchFieldException | IllegalAccessException ignore) { + try { + ServiceManager serviceManager = (ServiceManager) ClassUtil.getObjectField( + connectionManager, "serviceManager"); + config = serviceManager.getCfg(); + } catch (NoSuchFieldException | IllegalAccessException ignore2) { + } + } + return config; + } + @Override public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { diff --git a/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/RedisConnectionMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/RedisConnectionMethodInterceptor.java index 60956c7a64..d475382c8f 100644 --- a/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/RedisConnectionMethodInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/RedisConnectionMethodInterceptor.java @@ -19,6 +19,9 @@ package org.apache.skywalking.apm.plugin.redisson.v3; import io.netty.channel.Channel; + +import java.util.Objects; +import java.util.stream.Collectors; import org.apache.skywalking.apm.agent.core.context.ContextManager; import org.apache.skywalking.apm.agent.core.context.tag.Tags; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; @@ -27,8 +30,8 @@ import org.apache.skywalking.apm.agent.core.logging.api.LogManager; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.apache.skywalking.apm.plugin.redisson.v3.util.ClassUtil; import org.apache.skywalking.apm.util.StringUtil; @@ -41,17 +44,17 @@ import java.net.InetSocketAddress; import java.util.Optional; -public class RedisConnectionMethodInterceptor implements InstanceMethodsAroundInterceptor, InstanceConstructorInterceptor { +public class RedisConnectionMethodInterceptor implements InstanceMethodsAroundInterceptorV2, InstanceConstructorInterceptor { private static final ILog LOGGER = LogManager.getLogger(RedisConnectionMethodInterceptor.class); private static final String ABBR = "..."; private static final String QUESTION_MARK = "?"; private static final String DELIMITER_SPACE = " "; + public static final Object STOP_SPAN_FLAG = new Object(); @Override - public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - MethodInterceptResult result) throws Throwable { + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInvocationContext context) throws Throwable { String peer = (String) objInst.getSkyWalkingDynamicField(); RedisConnection connection = (RedisConnection) objInst; @@ -66,14 +69,22 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr if (allArguments[0] instanceof CommandsData) { operationName = operationName + "BATCH_EXECUTE"; command = "BATCH_EXECUTE"; + if (RedissonPluginConfig.Plugin.Redisson.SHOW_BATCH_COMMANDS) { + command += ":" + showBatchCommands((CommandsData) allArguments[0]); + } } else if (allArguments[0] instanceof CommandData) { CommandData commandData = (CommandData) allArguments[0]; command = commandData.getCommand().getName(); - operationName = operationName + command; - arguments = commandData.getParams(); + if ("PING".equals(command) && !RedissonPluginConfig.Plugin.Redisson.SHOW_PING_COMMAND) { + return; + } else { + operationName = operationName + command; + arguments = commandData.getParams(); + } } AbstractSpan span = ContextManager.createExitSpan(operationName, peer); + context.setContext(STOP_SPAN_FLAG); span.setComponent(ComponentsDefine.REDISSON); Tags.CACHE_TYPE.set(span, "Redis"); Tags.CACHE_INSTANCE.set(span, dbInstance); @@ -85,17 +96,19 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr } @Override - public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - Object ret) throws Throwable { - ContextManager.stopSpan(); + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret, MethodInvocationContext context) throws Throwable { + if (Objects.nonNull(context.getContext())) { + ContextManager.stopSpan(); + } return ret; } @Override - public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, - Class[] argumentsTypes, Throwable t) { - AbstractSpan span = ContextManager.activeSpan(); - span.log(t); + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t, MethodInvocationContext context) { + if (Objects.nonNull(context.getContext())) { + AbstractSpan span = ContextManager.activeSpan(); + span.log(t); + } } @Override @@ -143,4 +156,11 @@ private Optional parseOperation(String cmd) { } return Optional.empty(); } + + private String showBatchCommands(CommandsData commandsData) { + return commandsData.getCommands() + .stream() + .map(data -> data.getCommand().getName()) + .collect(Collectors.joining(";")); + } } diff --git a/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/RedissonPluginConfig.java b/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/RedissonPluginConfig.java index 52dedcb10a..aeb0a5f270 100644 --- a/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/RedissonPluginConfig.java +++ b/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/RedissonPluginConfig.java @@ -40,6 +40,14 @@ public static class Redisson { * Set a negative number to save specified length of parameter string to the tag. */ public static int REDIS_PARAMETER_MAX_LENGTH = 128; + /** + * If set to true, the PING command would be collected. + */ + public static boolean SHOW_PING_COMMAND = false; + /** + * If set to true, the detail of the Redis batch commands would be collected. + */ + public static boolean SHOW_BATCH_COMMANDS = false; /** * Operation represent a cache span is "write" or "read" action , and "op"(operation) is tagged with key "cache.op" usually diff --git a/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/define/RedisConnectionInstrumentation.java b/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/define/RedisConnectionInstrumentation.java index 84bc82535f..34678af6b7 100644 --- a/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/define/RedisConnectionInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/define/RedisConnectionInstrumentation.java @@ -21,15 +21,15 @@ import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.ClassInstanceMethodsEnhancePluginDefineV2; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import static net.bytebuddy.matcher.ElementMatchers.named; import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; -public class RedisConnectionInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { +public class RedisConnectionInstrumentation extends ClassInstanceMethodsEnhancePluginDefineV2 { private static final String ENHANCE_CLASS = "org.redisson.client.RedisConnection"; @@ -53,16 +53,16 @@ public String getConstructorInterceptor() { } @Override - public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { - return new InstanceMethodsInterceptPoint[] { - new InstanceMethodsInterceptPoint() { + public InstanceMethodsInterceptV2Point[] getInstanceMethodsInterceptV2Points() { + return new InstanceMethodsInterceptV2Point[] { + new InstanceMethodsInterceptV2Point() { @Override public ElementMatcher getMethodsMatcher() { return named("send"); } @Override - public String getMethodsInterceptor() { + public String getMethodsInterceptorV2() { return REDISSON_METHOD_INTERCEPTOR_CLASS; } diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/pom.xml index aca4e0a2e2..4f661c38ba 100644 --- a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT resteasy-plugin @@ -38,6 +38,5 @@ UTF-8 - /.. \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-3.x-plugin/pom.xml index 7f911f2507..83b9d357cc 100644 --- a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-3.x-plugin/pom.xml @@ -21,7 +21,7 @@ resteasy-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT resteasy-server-3.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v3/server/define/SynchronousDispatcherInstrumentation.java b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v3/server/define/SynchronousDispatcherInstrumentation.java index aaade19398..77093e85a8 100644 --- a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v3/server/define/SynchronousDispatcherInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v3/server/define/SynchronousDispatcherInstrumentation.java @@ -83,4 +83,9 @@ public boolean isOverrideArgs() { protected ClassMatch enhanceClass() { return NameMatch.byName(ENHANCE_CLASS); } + + @Override + protected String[] witnessClasses() { + return new String[]{"org.jboss.resteasy.core.Dispatcher"}; + } } diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/pom.xml index 4590d8e2ec..63eb0c4826 100644 --- a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/pom.xml @@ -21,7 +21,7 @@ resteasy-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT resteasy-server-4.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/define/SynchronousDispatcherInstrumentation.java b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/define/SynchronousDispatcherInstrumentation.java index 245994dc69..206af100d8 100644 --- a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/define/SynchronousDispatcherInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/define/SynchronousDispatcherInstrumentation.java @@ -20,13 +20,18 @@ import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; +import java.util.Collections; +import java.util.List; + import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; public class SynchronousDispatcherInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { @@ -83,4 +88,17 @@ public boolean isOverrideArgs() { protected ClassMatch enhanceClass() { return NameMatch.byName(ENHANCE_CLASS); } + + @Override + protected String[] witnessClasses() { + return new String[]{"org.jboss.resteasy.core.InternalDispatcher"}; + } + + @Override + protected List witnessMethods() { + return Collections.singletonList(new WitnessMethod( + "org.jboss.resteasy.spi.Dispatcher", + named("internalInvocation").and(returns(named("javax.ws.rs.core.Response"))) + )); + } } diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-6.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-6.x-plugin/pom.xml index 1b91053387..f20ab70b3e 100644 --- a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-6.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-6.x-plugin/pom.xml @@ -21,7 +21,7 @@ resteasy-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT resteasy-server-6.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v6/server/define/SynchronousDispatcherInstrumentation.java b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v6/server/define/SynchronousDispatcherInstrumentation.java index c4a472487c..d38fb5775d 100644 --- a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v6/server/define/SynchronousDispatcherInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v6/server/define/SynchronousDispatcherInstrumentation.java @@ -20,13 +20,18 @@ import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; +import java.util.Collections; +import java.util.List; + import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; public class SynchronousDispatcherInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { @@ -83,4 +88,17 @@ public boolean isOverrideArgs() { protected ClassMatch enhanceClass() { return NameMatch.byName(ENHANCE_CLASS); } + + @Override + protected String[] witnessClasses() { + return new String[]{"org.jboss.resteasy.core.InternalDispatcher"}; + } + + @Override + protected List witnessMethods() { + return Collections.singletonList(new WitnessMethod( + "org.jboss.resteasy.spi.Dispatcher", + named("internalInvocation").and(returns(named("jakarta.ws.rs.core.Response"))) + )); + } } diff --git a/apm-sniffer/apm-sdk-plugin/rocketMQ-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/rocketMQ-3.x-plugin/pom.xml index 72feb352bf..5a7c711dd2 100644 --- a/apm-sniffer/apm-sdk-plugin/rocketMQ-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/rocketMQ-3.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/rocketMQ-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v3/OnExceptionInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/rocketMQ-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v3/OnExceptionInterceptorTest.java index 59bf780148..6b5ba982f8 100644 --- a/apm-sniffer/apm-sdk-plugin/rocketMQ-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v3/OnExceptionInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/rocketMQ-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v3/OnExceptionInterceptorTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import java.util.List; import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.MockContextSnapshot; import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; @@ -55,7 +56,6 @@ public class OnExceptionInterceptorTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); - @Mock private ContextSnapshot contextSnapshot; private SendCallBackEnhanceInfo enhanceInfo; @@ -65,6 +65,7 @@ public class OnExceptionInterceptorTest { @Before public void setUp() { exceptionInterceptor = new OnExceptionInterceptor(); + contextSnapshot = MockContextSnapshot.INSTANCE.mockContextSnapshot(); enhanceInfo = new SendCallBackEnhanceInfo("test", contextSnapshot); when(enhancedInstance.getSkyWalkingDynamicField()).thenReturn(enhanceInfo); diff --git a/apm-sniffer/apm-sdk-plugin/rocketMQ-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v3/OnSuccessInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/rocketMQ-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v3/OnSuccessInterceptorTest.java index 0927f8a793..77c69a01e6 100644 --- a/apm-sniffer/apm-sdk-plugin/rocketMQ-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v3/OnSuccessInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/rocketMQ-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v3/OnSuccessInterceptorTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import java.util.List; import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.MockContextSnapshot; import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; @@ -57,7 +58,6 @@ public class OnSuccessInterceptorTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); - @Mock private ContextSnapshot contextSnapshot; @Mock private SendResult sendResult; @@ -70,6 +70,7 @@ public class OnSuccessInterceptorTest { @Before public void setUp() { successInterceptor = new OnSuccessInterceptor(); + contextSnapshot = MockContextSnapshot.INSTANCE.mockContextSnapshot(); enhanceInfo = new SendCallBackEnhanceInfo("test", contextSnapshot); when(enhancedInstance.getSkyWalkingDynamicField()).thenReturn(enhanceInfo); diff --git a/apm-sniffer/apm-sdk-plugin/rocketMQ-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/rocketMQ-4.x-plugin/pom.xml index 7b3c637a92..a30e1b09e0 100644 --- a/apm-sniffer/apm-sdk-plugin/rocketMQ-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/rocketMQ-4.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/rocketMQ-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v4/OnExceptionInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/rocketMQ-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v4/OnExceptionInterceptorTest.java index 169c3b59bb..3bc9436b1d 100644 --- a/apm-sniffer/apm-sdk-plugin/rocketMQ-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v4/OnExceptionInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/rocketMQ-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v4/OnExceptionInterceptorTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import java.util.List; import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.MockContextSnapshot; import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; @@ -55,7 +56,6 @@ public class OnExceptionInterceptorTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); - @Mock private ContextSnapshot contextSnapshot; private SendCallBackEnhanceInfo enhanceInfo; @@ -69,6 +69,7 @@ public void setUp() { @Test public void testOnException() throws Throwable { + contextSnapshot = MockContextSnapshot.INSTANCE.mockContextSnapshot(); enhanceInfo = new SendCallBackEnhanceInfo("test", contextSnapshot); when(enhancedInstance.getSkyWalkingDynamicField()).thenReturn(enhanceInfo); diff --git a/apm-sniffer/apm-sdk-plugin/rocketMQ-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v4/OnSuccessInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/rocketMQ-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v4/OnSuccessInterceptorTest.java index d432daafd8..40c718d9b3 100644 --- a/apm-sniffer/apm-sdk-plugin/rocketMQ-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v4/OnSuccessInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/rocketMQ-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/rocketMQ/v4/OnSuccessInterceptorTest.java @@ -25,6 +25,7 @@ import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.MockContextSnapshot; import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; @@ -57,7 +58,6 @@ public class OnSuccessInterceptorTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); - @Mock private ContextSnapshot contextSnapshot; @Mock private SendResult sendResult; @@ -70,6 +70,7 @@ public class OnSuccessInterceptorTest { @Before public void setUp() { successInterceptor = new OnSuccessInterceptor(); + contextSnapshot = MockContextSnapshot.INSTANCE.mockContextSnapshot(); enhanceInfo = new SendCallBackEnhanceInfo("test", contextSnapshot); when(enhancedInstance.getSkyWalkingDynamicField()).thenReturn(enhanceInfo); diff --git a/apm-sniffer/apm-sdk-plugin/rocketMQ-5.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/rocketMQ-5.x-plugin/pom.xml index 5669605bb4..62c1fbb3e3 100644 --- a/apm-sniffer/apm-sdk-plugin/rocketMQ-5.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/rocketMQ-5.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/rocketMQ-client-java-5.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/rocketMQ-client-java-5.x-plugin/pom.xml index c48f2da760..e4dc8d8988 100644 --- a/apm-sniffer/apm-sdk-plugin/rocketMQ-client-java-5.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/rocketMQ-client-java-5.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/servicecomb-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/servicecomb-plugin/pom.xml index 9a64259d48..06f9d58672 100644 --- a/apm-sniffer/apm-sdk-plugin/servicecomb-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/servicecomb-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -37,6 +37,5 @@ UTF-8 - /.. diff --git a/apm-sniffer/apm-sdk-plugin/servicecomb-plugin/servicecomb-java-chassis-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/servicecomb-plugin/servicecomb-java-chassis-2.x-plugin/pom.xml index 71b5934e03..6c26908ce4 100644 --- a/apm-sniffer/apm-sdk-plugin/servicecomb-plugin/servicecomb-java-chassis-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/servicecomb-plugin/servicecomb-java-chassis-2.x-plugin/pom.xml @@ -21,7 +21,7 @@ servicecomb-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/servlet-commons/pom.xml b/apm-sniffer/apm-sdk-plugin/servlet-commons/pom.xml new file mode 100644 index 0000000000..55fc22ab3e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/servlet-commons/pom.xml @@ -0,0 +1,75 @@ + + + + + org.apache.skywalking + apm-sdk-plugin + 9.7.0-SNAPSHOT + + 4.0.0 + + apm-servlet-commons + jar + + servlet-commons + + + 3.0.1 + 6.0.0 + + + + + + javax.servlet + javax.servlet-api + ${javax-servlet-api.version} + provided + + + jakarta.servlet + jakarta.servlet-api + ${jakarta-servlet-api.version} + provided + + + + junit + junit + test + + + org.mockito + mockito-core + test + + + diff --git a/apm-sniffer/apm-sdk-plugin/servlet-commons/src/main/java/org/apache/skywalking/apm/plugin/servlet/HttpRequestWrapper.java b/apm-sniffer/apm-sdk-plugin/servlet-commons/src/main/java/org/apache/skywalking/apm/plugin/servlet/HttpRequestWrapper.java new file mode 100644 index 0000000000..eee5d80dfe --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/servlet-commons/src/main/java/org/apache/skywalking/apm/plugin/servlet/HttpRequestWrapper.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.servlet; + +import java.util.Enumeration; +import java.util.Map; + +/** + * A namespace-agnostic view over a servlet HTTP request. It abstracts away the javax.servlet vs + * jakarta.servlet split so servlet plugin code can be written once. Obtain instances via + * {@link HttpRequestWrappers#wrap(Object)}. + *

+ * The method set is the union of what every servlet plugin actually reads; it intentionally does + * not expose cookies, sessions, multipart parts or the servlet context, none of which are collected. + */ +public interface HttpRequestWrapper { + + String getHeader(String name); + + Enumeration getHeaders(String name); + + String getMethod(); + + String getRequestURI(); + + StringBuffer getRequestURL(); + + String getRemoteHost(); + + Map getParameterMap(); +} diff --git a/apm-sniffer/apm-sdk-plugin/servlet-commons/src/main/java/org/apache/skywalking/apm/plugin/servlet/HttpRequestWrappers.java b/apm-sniffer/apm-sdk-plugin/servlet-commons/src/main/java/org/apache/skywalking/apm/plugin/servlet/HttpRequestWrappers.java new file mode 100644 index 0000000000..6b35d9925b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/servlet-commons/src/main/java/org/apache/skywalking/apm/plugin/servlet/HttpRequestWrappers.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.servlet; + +import java.util.Enumeration; +import java.util.Map; + +/** + * Factory producing a {@link HttpRequestWrapper} from either a {@code javax.servlet} or a + * {@code jakarta.servlet} {@code HttpServletRequest}, selected at runtime by the concrete type of + * the argument. Both servlet namespaces may be present on the classpath at once (e.g. a Jakarta + * application that also carries {@code javax.servlet-api} transitively); the flags below are + * computed independently so the live request type — not classpath presence order — decides. + *

+ * CLASSLOADING INVARIANT — DO NOT VIOLATE: every {@code javax}/{@code jakarta} reference lives + * inside its own nested wrapper class and is reached only through its {@link #IS_JAVAX} / + * {@link #IS_JAKARTA} flag guarded by a {@code &&} short-circuit. Never move a servlet type into + * {@link #wrap(Object)}'s shared body, never widen a method descriptor of this class to a servlet + * type, and never add a typed public {@code wrap(javax...)}/{@code wrap(jakarta...)} overload. Any + * of those makes the JVM eagerly resolve an absent namespace and reintroduces + * {@code NoClassDefFoundError} in single-namespace applications. Lazy constant-pool resolution + * keeps the guarded-but-unexecuted branch safe. + */ +public final class HttpRequestWrappers { + + private static final boolean IS_JAVAX = classExists("javax.servlet.http.HttpServletRequest"); + private static final boolean IS_JAKARTA = classExists("jakarta.servlet.http.HttpServletRequest"); + + private HttpRequestWrappers() { + } + + /** + * @param request any object; typically the servlet request captured by a plugin. + * @return a wrapper when {@code request} is a javax or jakarta {@code HttpServletRequest}, or + * {@code null} otherwise (e.g. a reactive request or an unrecognized type), letting the caller + * fall through to its own non-servlet handling. + */ + public static HttpRequestWrapper wrap(Object request) { + if (IS_JAVAX && request instanceof javax.servlet.http.HttpServletRequest) { + return new JavaxHttpServletRequest((javax.servlet.http.HttpServletRequest) request); + } + if (IS_JAKARTA && request instanceof jakarta.servlet.http.HttpServletRequest) { + return new JakartaHttpServletRequest((jakarta.servlet.http.HttpServletRequest) request); + } + return null; + } + + private static boolean classExists(String className) { + try { + Class.forName(className, false, HttpRequestWrappers.class.getClassLoader()); + return true; + } catch (Throwable ignore) { + return false; + } + } + + private static final class JavaxHttpServletRequest implements HttpRequestWrapper { + private final javax.servlet.http.HttpServletRequest request; + + private JavaxHttpServletRequest(javax.servlet.http.HttpServletRequest request) { + this.request = request; + } + + @Override + public String getHeader(String name) { + return request.getHeader(name); + } + + @Override + public Enumeration getHeaders(String name) { + return request.getHeaders(name); + } + + @Override + public String getMethod() { + return request.getMethod(); + } + + @Override + public String getRequestURI() { + return request.getRequestURI(); + } + + @Override + public StringBuffer getRequestURL() { + return request.getRequestURL(); + } + + @Override + public String getRemoteHost() { + return request.getRemoteHost(); + } + + @Override + public Map getParameterMap() { + return request.getParameterMap(); + } + } + + private static final class JakartaHttpServletRequest implements HttpRequestWrapper { + private final jakarta.servlet.http.HttpServletRequest request; + + private JakartaHttpServletRequest(jakarta.servlet.http.HttpServletRequest request) { + this.request = request; + } + + @Override + public String getHeader(String name) { + return request.getHeader(name); + } + + @Override + public Enumeration getHeaders(String name) { + return request.getHeaders(name); + } + + @Override + public String getMethod() { + return request.getMethod(); + } + + @Override + public String getRequestURI() { + return request.getRequestURI(); + } + + @Override + public StringBuffer getRequestURL() { + return request.getRequestURL(); + } + + @Override + public String getRemoteHost() { + return request.getRemoteHost(); + } + + @Override + public Map getParameterMap() { + return request.getParameterMap(); + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/servlet-commons/src/main/java/org/apache/skywalking/apm/plugin/servlet/HttpResponseWrapper.java b/apm-sniffer/apm-sdk-plugin/servlet-commons/src/main/java/org/apache/skywalking/apm/plugin/servlet/HttpResponseWrapper.java new file mode 100644 index 0000000000..b31ded6456 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/servlet-commons/src/main/java/org/apache/skywalking/apm/plugin/servlet/HttpResponseWrapper.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.servlet; + +/** + * A namespace-agnostic view over a servlet HTTP response. Obtain instances via + * {@link HttpResponseWrappers#wrap(Object)}. Only the response status code is exposed — the sole + * response datum servlet plugins collect. + */ +public interface HttpResponseWrapper { + + int getStatus(); +} diff --git a/apm-sniffer/apm-sdk-plugin/servlet-commons/src/main/java/org/apache/skywalking/apm/plugin/servlet/HttpResponseWrappers.java b/apm-sniffer/apm-sdk-plugin/servlet-commons/src/main/java/org/apache/skywalking/apm/plugin/servlet/HttpResponseWrappers.java new file mode 100644 index 0000000000..4a85c44bac --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/servlet-commons/src/main/java/org/apache/skywalking/apm/plugin/servlet/HttpResponseWrappers.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.servlet; + +/** + * Factory producing a {@link HttpResponseWrapper} from either a {@code javax.servlet} or a + * {@code jakarta.servlet} {@code HttpServletResponse}, selected at runtime. + *

+ * Detection deliberately checks that the {@code getStatus()} method exists (added in Servlet 3.0), + * not merely that the response class is present, preserving the guard for pre-3.0 containers where + * status is unavailable. {@link #servletStatusSupported()} lets a caller gate a non-servlet (e.g. + * reactive) status fallback on the same capability. + *

+ * The same CLASSLOADING INVARIANT as {@link HttpRequestWrappers} applies: keep every + * {@code javax}/{@code jakarta} reference inside its nested class behind its {@code &&}-guarded + * flag, and never widen this class's method descriptors to a servlet type. + */ +public final class HttpResponseWrappers { + + private static final boolean IS_JAVAX_STATUS = statusMethodExists("javax.servlet.http.HttpServletResponse"); + private static final boolean IS_JAKARTA_STATUS = statusMethodExists("jakarta.servlet.http.HttpServletResponse"); + + private HttpResponseWrappers() { + } + + /** + * @return {@code true} when a servlet {@code getStatus()} is available in either namespace. + */ + public static boolean servletStatusSupported() { + return IS_JAVAX_STATUS || IS_JAKARTA_STATUS; + } + + /** + * @param response any object; typically the servlet response captured by a plugin. + * @return a wrapper when {@code response} is a javax or jakarta {@code HttpServletResponse} that + * exposes {@code getStatus()}, or {@code null} otherwise (e.g. a reactive response), letting the + * caller fall through to its own handling. + */ + public static HttpResponseWrapper wrap(Object response) { + if (IS_JAVAX_STATUS && response instanceof javax.servlet.http.HttpServletResponse) { + return new JavaxHttpServletResponse((javax.servlet.http.HttpServletResponse) response); + } + if (IS_JAKARTA_STATUS && response instanceof jakarta.servlet.http.HttpServletResponse) { + return new JakartaHttpServletResponse((jakarta.servlet.http.HttpServletResponse) response); + } + return null; + } + + private static boolean statusMethodExists(String className) { + try { + Class clazz = Class.forName(className, false, HttpResponseWrappers.class.getClassLoader()); + clazz.getMethod("getStatus"); + return true; + } catch (Throwable ignore) { + return false; + } + } + + private static final class JavaxHttpServletResponse implements HttpResponseWrapper { + private final javax.servlet.http.HttpServletResponse response; + + private JavaxHttpServletResponse(javax.servlet.http.HttpServletResponse response) { + this.response = response; + } + + @Override + public int getStatus() { + return response.getStatus(); + } + } + + private static final class JakartaHttpServletResponse implements HttpResponseWrapper { + private final jakarta.servlet.http.HttpServletResponse response; + + private JakartaHttpServletResponse(jakarta.servlet.http.HttpServletResponse response) { + this.response = response; + } + + @Override + public int getStatus() { + return response.getStatus(); + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/servlet-commons/src/test/java/org/apache/skywalking/apm/plugin/servlet/HttpRequestWrappersTest.java b/apm-sniffer/apm-sdk-plugin/servlet-commons/src/test/java/org/apache/skywalking/apm/plugin/servlet/HttpRequestWrappersTest.java new file mode 100644 index 0000000000..87cd1a1a91 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/servlet-commons/src/test/java/org/apache/skywalking/apm/plugin/servlet/HttpRequestWrappersTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.servlet; + +import java.util.Collections; +import java.util.Enumeration; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Both javax.servlet-api and jakarta.servlet-api are on this module's test classpath, reproducing the + * exact apache/skywalking#13938 condition: both namespaces present at once. These tests assert that + * {@link HttpRequestWrappers#wrap(Object)} selects the wrapper matching the live request type rather + * than the classpath-presence order that the old exclusive detection got wrong. + */ +public class HttpRequestWrappersTest { + + @Test + public void wrapJakartaRequest() { + jakarta.servlet.http.HttpServletRequest request = mock(jakarta.servlet.http.HttpServletRequest.class); + when(request.getHeader("h")).thenReturn("v"); + when(request.getMethod()).thenReturn("GET"); + when(request.getRequestURI()).thenReturn("/uri"); + when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost/uri")); + when(request.getRemoteHost()).thenReturn("remote"); + when(request.getHeaders("h")).thenReturn(Collections.enumeration(Collections.singletonList("v"))); + + HttpRequestWrapper wrapper = HttpRequestWrappers.wrap(request); + + assertTrue(wrapper instanceof HttpRequestWrapper); + assertEquals("v", wrapper.getHeader("h")); + assertEquals("GET", wrapper.getMethod()); + assertEquals("/uri", wrapper.getRequestURI()); + assertEquals("http://localhost/uri", wrapper.getRequestURL().toString()); + assertEquals("remote", wrapper.getRemoteHost()); + Enumeration headers = wrapper.getHeaders("h"); + assertEquals("v", headers.nextElement()); + } + + @Test + public void wrapJavaxRequest() { + javax.servlet.http.HttpServletRequest request = mock(javax.servlet.http.HttpServletRequest.class); + when(request.getHeader("h")).thenReturn("v"); + when(request.getMethod()).thenReturn("POST"); + + HttpRequestWrapper wrapper = HttpRequestWrappers.wrap(request); + + assertTrue(wrapper instanceof HttpRequestWrapper); + assertEquals("v", wrapper.getHeader("h")); + assertEquals("POST", wrapper.getMethod()); + } + + @Test + public void wrapNonServletReturnsNull() { + assertNull(HttpRequestWrappers.wrap(new Object())); + assertNull(HttpRequestWrappers.wrap("not a request")); + assertNull(HttpRequestWrappers.wrap(null)); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/servlet-commons/src/test/java/org/apache/skywalking/apm/plugin/servlet/HttpResponseWrappersTest.java b/apm-sniffer/apm-sdk-plugin/servlet-commons/src/test/java/org/apache/skywalking/apm/plugin/servlet/HttpResponseWrappersTest.java new file mode 100644 index 0000000000..c470a2c19b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/servlet-commons/src/test/java/org/apache/skywalking/apm/plugin/servlet/HttpResponseWrappersTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.servlet; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class HttpResponseWrappersTest { + + @Test + public void wrapJakartaResponse() { + jakarta.servlet.http.HttpServletResponse response = mock(jakarta.servlet.http.HttpServletResponse.class); + when(response.getStatus()).thenReturn(503); + + HttpResponseWrapper wrapper = HttpResponseWrappers.wrap(response); + + assertNotNull(wrapper); + assertEquals(503, wrapper.getStatus()); + } + + @Test + public void wrapJavaxResponse() { + javax.servlet.http.HttpServletResponse response = mock(javax.servlet.http.HttpServletResponse.class); + when(response.getStatus()).thenReturn(200); + + HttpResponseWrapper wrapper = HttpResponseWrappers.wrap(response); + + assertNotNull(wrapper); + assertEquals(200, wrapper.getStatus()); + } + + @Test + public void servletStatusSupportedWhenApiPresent() { + // Both servlet APIs (with getStatus, added in Servlet 3.0) are on the test classpath. + assertTrue(HttpResponseWrappers.servletStatusSupported()); + } + + @Test + public void wrapNonServletReturnsNull() { + assertNull(HttpResponseWrappers.wrap(new Object())); + assertNull(HttpResponseWrappers.wrap(null)); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/pom.xml b/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/pom.xml index 7f124736d2..6d38f040f2 100644 --- a/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 pom @@ -36,6 +36,5 @@ UTF-8 - /.. diff --git a/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-3.x-plugin/pom.xml index 41131a2b2d..033f5afc04 100644 --- a/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-3.x-plugin/pom.xml @@ -21,7 +21,7 @@ shardingsphere-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-4.0.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-4.0.x-plugin/pom.xml index 3493c95e09..fa282b6571 100644 --- a/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-4.0.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-4.0.x-plugin/pom.xml @@ -20,7 +20,7 @@ shardingsphere-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-4.1.0-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-4.1.0-plugin/pom.xml index b86c520ac5..803c61dbeb 100644 --- a/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-4.1.0-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-4.1.0-plugin/pom.xml @@ -20,7 +20,7 @@ shardingsphere-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-5.0.0-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-5.0.0-plugin/pom.xml index 176dba0ace..e17d47b46d 100644 --- a/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-5.0.0-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/shardingsphere-plugins/sharding-sphere-5.0.0-plugin/pom.xml @@ -21,7 +21,7 @@ shardingsphere-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/sofarpc-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/sofarpc-plugin/pom.xml index 5a6e57662a..21f8795238 100644 --- a/apm-sniffer/apm-sdk-plugin/sofarpc-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/sofarpc-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/sofarpc-plugin/src/test/java/org/apache/skywalking/apm/plugin/sofarpc/InvokeCallbackWrapperTest.java b/apm-sniffer/apm-sdk-plugin/sofarpc-plugin/src/test/java/org/apache/skywalking/apm/plugin/sofarpc/InvokeCallbackWrapperTest.java index ef9c9f3cb0..d05d4026c2 100644 --- a/apm-sniffer/apm-sdk-plugin/sofarpc-plugin/src/test/java/org/apache/skywalking/apm/plugin/sofarpc/InvokeCallbackWrapperTest.java +++ b/apm-sniffer/apm-sdk-plugin/sofarpc-plugin/src/test/java/org/apache/skywalking/apm/plugin/sofarpc/InvokeCallbackWrapperTest.java @@ -141,7 +141,10 @@ public void testOnResponse() throws InterruptedException { TraceSegment traceSegment2 = segmentStorage.getTraceSegments().get(1); List spans2 = SegmentHelper.getSpans(traceSegment2); assertThat(spans2.size(), is(1)); - assertEquals("sofarpc", traceSegment2.getRef().getParentEndpoint()); + + // Segment order is non-deterministic; find the child segment (the one with a ref) + TraceSegment childSegment = traceSegment.getRef() != null ? traceSegment : traceSegment2; + assertEquals("sofarpc", childSegment.getRef().getParentEndpoint()); } @Test @@ -162,7 +165,11 @@ public void testOnException() throws InterruptedException { TraceSegment traceSegment2 = segmentStorage.getTraceSegments().get(1); List spans2 = SegmentHelper.getSpans(traceSegment2); assertThat(spans2.size(), is(1)); - assertThat(SpanHelper.getLogs(spans2.get(0)).size(), is(1)); + + // Segment order is non-deterministic; find the child segment (the one with a ref) + TraceSegment childSegment = traceSegment.getRef() != null ? traceSegment : traceSegment2; + List childSpans = SegmentHelper.getSpans(childSegment); + assertThat(SpanHelper.getLogs(childSpans.get(0)).size(), is(1)); } diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/pom.xml similarity index 73% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/pom.xml rename to apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/pom.xml index 2f0ae53188..543d27c2c4 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/pom.xml @@ -1,3 +1,4 @@ + + 4.0.0 - jetty-plugins + apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT - 4.0.0 - apm-jetty-server-11.x-plugin + solon-2.x-plugin jar - jetty-server-11.x-plugin + solon-2.x-plugin http://maven.apache.org - 11.0.15 + UTF-8 + 4.3 + 4.12 - org.eclipse.jetty - jetty-server - ${jetty-server.version} + org.noear + solon-lib + 2.8.3 provided diff --git a/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solon/SolonActionExecuteInterceptor.java b/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solon/SolonActionExecuteInterceptor.java new file mode 100644 index 0000000000..239e21cb80 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solon/SolonActionExecuteInterceptor.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.solon; + +import lombok.extern.slf4j.Slf4j; +import org.apache.skywalking.apm.agent.core.context.CarrierItem; +import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.util.StringUtil; +import org.noear.solon.core.NvMap; +import org.noear.solon.core.handle.Context; + +import java.lang.reflect.Method; + +@Slf4j +public class SolonActionExecuteInterceptor implements InstanceMethodsAroundInterceptorV2 { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInvocationContext context) throws Throwable { + Context ctx = (Context) allArguments[0]; + ContextCarrier contextCarrier = new ContextCarrier(); + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + next.setHeadValue(ctx.header(next.getHeadKey())); + } + String operationName = ctx.method() + ":" + ctx.path(); + AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier); + span.setComponent(ComponentsDefine.SOLON_MVC); + SpanLayer.asHttp(span); + Tags.URL.set(span, ctx.url()); + Tags.HTTP.METHOD.set(span, ctx.method()); + if (SolonPluginConfig.Plugin.Solon.INCLUDE_HTTP_HEADERS != null && !SolonPluginConfig.Plugin.Solon.INCLUDE_HTTP_HEADERS.isEmpty()) { + NvMap includeHeaders = new NvMap(); + for (String header : SolonPluginConfig.Plugin.Solon.INCLUDE_HTTP_HEADERS) { + String value = ctx.header(header); + if (StringUtil.isNotBlank(value)) { + includeHeaders.put(header, value); + } + } + Tags.HTTP.HEADERS.set(span, includeHeaders.toString()); + } + if (SolonPluginConfig.Plugin.Solon.HTTP_BODY_LENGTH_THRESHOLD != 0) { + String body = ctx.body(); + if (StringUtil.isNotBlank(body)) { + if (SolonPluginConfig.Plugin.Solon.HTTP_BODY_LENGTH_THRESHOLD > 0 && body.length() > SolonPluginConfig.Plugin.Solon.HTTP_BODY_LENGTH_THRESHOLD) { + body = body.substring(0, SolonPluginConfig.Plugin.Solon.HTTP_BODY_LENGTH_THRESHOLD); + } + Tags.HTTP.BODY.set(span, body); + } + } + if (SolonPluginConfig.Plugin.Solon.HTTP_PARAMS_LENGTH_THRESHOLD != 0) { + String param = ctx.paramMap().toString(); + if (SolonPluginConfig.Plugin.Solon.HTTP_PARAMS_LENGTH_THRESHOLD > 0 && param.length() > SolonPluginConfig.Plugin.Solon.HTTP_PARAMS_LENGTH_THRESHOLD) { + param = param.substring(0, SolonPluginConfig.Plugin.Solon.HTTP_PARAMS_LENGTH_THRESHOLD); + } + Tags.HTTP.PARAMS.set(span, param); + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret, MethodInvocationContext context) { + Context ctx = (Context) allArguments[0]; + Tags.HTTP_RESPONSE_STATUS_CODE.set(ContextManager.activeSpan(), ctx.status()); + if (ctx.errors != null && context.getContext() == null) { + AbstractSpan activeSpan = ContextManager.activeSpan(); + activeSpan.errorOccurred(); + activeSpan.log(ctx.errors); + } + ContextManager.stopSpan(); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t, MethodInvocationContext context) { + AbstractSpan activeSpan = ContextManager.activeSpan(); + activeSpan.errorOccurred(); + activeSpan.log(t); + context.setContext(true); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/TomcatPluginConfig.java b/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solon/SolonPluginConfig.java similarity index 55% rename from apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/TomcatPluginConfig.java rename to apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solon/SolonPluginConfig.java index 4bc6174d73..63a3f3ba98 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/TomcatPluginConfig.java +++ b/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solon/SolonPluginConfig.java @@ -16,28 +16,28 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat78x; +package org.apache.skywalking.apm.plugin.solon; import org.apache.skywalking.apm.agent.core.boot.PluginConfig; -public class TomcatPluginConfig { +import java.util.List; + +public class SolonPluginConfig { public static class Plugin { - @PluginConfig(root = TomcatPluginConfig.class) - public static class Tomcat { + @PluginConfig(root = SolonPluginConfig.class) + public static class Solon { /** - * This config item controls that whether the Tomcat plugin should collect the parameters of the request. + * Define the max length of collected HTTP parameters. The default value(=0) means not collecting. */ - public static boolean COLLECT_HTTP_PARAMS = false; - } - - @PluginConfig(root = TomcatPluginConfig.class) - public static class Http { + public static int HTTP_PARAMS_LENGTH_THRESHOLD = 0; + /** + * Define the max length of collected HTTP body. The default value(=0) means not collecting. + */ + public static int HTTP_BODY_LENGTH_THRESHOLD = 0; /** - * When either {@link Tomcat#COLLECT_HTTP_PARAMS} is enabled, how many characters to keep and send to the - * OAP backend, use negative values to keep and send the complete parameters, NB. this config item is added - * for the sake of performance + * It controls what header data should be collected, values must be in lower case, if empty, no header data will be collected. default is empty. */ - public static int HTTP_PARAMS_LENGTH_THRESHOLD = 1024; + public static List INCLUDE_HTTP_HEADERS ; } } } diff --git a/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solon/define/SolonActionInstrumentation.java b/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solon/define/SolonActionInstrumentation.java new file mode 100644 index 0000000000..d41d15ed45 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solon/define/SolonActionInstrumentation.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.solon.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.ClassInstanceMethodsEnhancePluginDefineV2; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class SolonActionInstrumentation extends ClassInstanceMethodsEnhancePluginDefineV2 { + + private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.solon.SolonActionExecuteInterceptor"; + + @Override + public ClassMatch enhanceClass() { + return NameMatch.byName("org.noear.solon.SolonApp"); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptV2Point[] getInstanceMethodsInterceptV2Points() { + return new InstanceMethodsInterceptV2Point[] { + new InstanceMethodsInterceptV2Point() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("tryHandle"); + } + + @Override + public String getMethodsInterceptorV2() { + return INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..01e6af6f77 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/solon-2.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +solon-2.x=org.apache.skywalking.apm.plugin.solon.define.SolonActionInstrumentation \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/pom.xml index 64a46cd10c..2377a233f7 100644 --- a/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/async-annotation-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/async-annotation-plugin/pom.xml index c0484a07f1..337a154135 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/async-annotation-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/async-annotation-plugin/pom.xml @@ -20,7 +20,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/concurrent-util-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/concurrent-util-4.x-plugin/pom.xml index 0c49963f86..fa85530d61 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/concurrent-util-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/concurrent-util-4.x-plugin/pom.xml @@ -20,7 +20,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/pom.xml index 5807a025ba..58e530913a 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/pom.xml @@ -21,7 +21,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/AutoProxyCreatorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/AutoProxyCreatorInterceptor.java new file mode 100644 index 0000000000..943ac8983d --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/AutoProxyCreatorInterceptor.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.patch; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; + +import java.lang.reflect.Method; + +/** + * AutoProxyCreatorInterceptor determines whether the given interface is {@link EnhancedInstance}, + * and therefore does not consider it a reasonable proxy interface. + */ +public class AutoProxyCreatorInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + + Class ifc = (Class) allArguments[0]; + return ((boolean) ret) || ifc.getName().equals("org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance"); + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/CreateAopProxyInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/CreateAopProxyInterceptor.java index 97bab4ad33..6e55d80b1a 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/CreateAopProxyInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/CreateAopProxyInterceptor.java @@ -18,12 +18,14 @@ package org.apache.skywalking.apm.plugin.spring.patch; -import java.lang.reflect.Method; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.springframework.aop.SpringProxy; import org.springframework.aop.framework.AdvisedSupport; +import java.lang.reflect.Method; + /** * CreateAopProxyInterceptor check that the bean has been implement {@link EnhancedInstance}. * if yes, true will be returned. @@ -32,25 +34,45 @@ public class CreateAopProxyInterceptor implements InstanceMethodsAroundIntercept @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - MethodInterceptResult result) throws Throwable { + MethodInterceptResult result) throws Throwable { } @Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - Object ret) throws Throwable { + Object ret) throws Throwable { AdvisedSupport advisedSupport = (AdvisedSupport) allArguments[0]; - Class targetClass = advisedSupport.getTargetClass(); - if (targetClass != null && EnhancedInstance.class.isAssignableFrom(targetClass)) { - return true; + if (maybeHasUserSuppliedProxyInterfaces(ret)) { + Class targetClass = advisedSupport.getTargetClass(); + if (targetClass != null) { + if (onlyImplementsEnhancedInstance(advisedSupport) || onlyImplementsEnhancedInstanceAndSpringProxy(advisedSupport)) { + return true; + } + } } return ret; } + private boolean maybeHasUserSuppliedProxyInterfaces(Object ret) { + return !(Boolean) ret; + } + + private boolean onlyImplementsEnhancedInstanceAndSpringProxy(AdvisedSupport advisedSupport) { + Class[] ifcs = advisedSupport.getProxiedInterfaces(); + Class targetClass = advisedSupport.getTargetClass(); + return ifcs.length == 2 && EnhancedInstance.class.isAssignableFrom(targetClass) && SpringProxy.class.isAssignableFrom(targetClass); + } + + private boolean onlyImplementsEnhancedInstance(AdvisedSupport advisedSupport) { + Class[] ifcs = advisedSupport.getProxiedInterfaces(); + Class targetClass = advisedSupport.getTargetClass(); + return ifcs.length == 1 && EnhancedInstance.class.isAssignableFrom(targetClass); + } + @Override public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, - Class[] argumentsTypes, Throwable t) { + Class[] argumentsTypes, Throwable t) { } } diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/ProxyProcessorSupportInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/ProxyProcessorSupportInterceptor.java new file mode 100644 index 0000000000..d8840d4ef2 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/ProxyProcessorSupportInterceptor.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.patch; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; + +import java.lang.reflect.Method; + +/** + * ProxyProcessorSupportInterceptor determines whether the given interface is {@link EnhancedInstance}, + * and therefore does not consider it a reasonable proxy interface. + */ +public class ProxyProcessorSupportInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + Class ifc = (Class) allArguments[0]; + return ((boolean) ret) || ifc.getName().equals("org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance"); + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/define/AutoProxyCreatorInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/define/AutoProxyCreatorInstrumentation.java new file mode 100644 index 0000000000..26e6147e69 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/define/AutoProxyCreatorInstrumentation.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.patch.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import java.util.Collections; +import java.util.List; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class AutoProxyCreatorInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator"; + public static final String ENHANCE_METHOD = "isConfigurationCallbackInterface"; + public static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.spring.patch.AutoProxyCreatorInterceptor"; + + @Override + public final ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public final InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(ENHANCE_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + protected List witnessMethods() { + return Collections.singletonList(new WitnessMethod(ENHANCE_CLASS, named(ENHANCE_METHOD))); + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/define/ProxyProcessorSupportInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/define/ProxyProcessorSupportInstrumentation.java new file mode 100644 index 0000000000..78de2969c2 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/java/org/apache/skywalking/apm/plugin/spring/patch/define/ProxyProcessorSupportInstrumentation.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.patch.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class ProxyProcessorSupportInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.aop.framework.ProxyProcessorSupport"; + public static final String ENHANCE_METHOD = "isInternalLanguageInterface"; + public static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.spring.patch.ProxyProcessorSupportInterceptor"; + + @Override + public final ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public final InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(ENHANCE_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/resources/skywalking-plugin.def index e51178b97b..3a21d2b4aa 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/main/resources/skywalking-plugin.def @@ -19,3 +19,5 @@ spring-core-patch=org.apache.skywalking.apm.plugin.spring.patch.define.Autowired spring-core-patch=org.apache.skywalking.apm.plugin.spring.patch.define.AopExpressionMatchInstrumentation spring-core-patch=org.apache.skywalking.apm.plugin.spring.patch.define.AspectJExpressionPointCutInstrumentation spring-core-patch=org.apache.skywalking.apm.plugin.spring.patch.define.BeanWrapperImplInstrumentation +spring-core-patch=org.apache.skywalking.apm.plugin.spring.patch.define.ProxyProcessorSupportInstrumentation +spring-core-patch=org.apache.skywalking.apm.plugin.spring.patch.define.AutoProxyCreatorInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/test/java/org/apache/skywalking/apm/plugin/spring/patch/CreateAopProxyInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/test/java/org/apache/skywalking/apm/plugin/spring/patch/CreateAopProxyInterceptorTest.java index 12b5c96633..ae513567da 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/test/java/org/apache/skywalking/apm/plugin/spring/patch/CreateAopProxyInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/core-patch/src/test/java/org/apache/skywalking/apm/plugin/spring/patch/CreateAopProxyInterceptorTest.java @@ -24,6 +24,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.aop.SpringProxy; import org.springframework.aop.framework.AdvisedSupport; import static org.hamcrest.core.Is.is; @@ -48,18 +49,69 @@ public void setUp() { } @Test - public void testInterceptNormalObject() throws Throwable { - doReturn(Object.class).when(advisedSupport).getTargetClass(); + public void testInterceptClassImplementsNoInterfaces() throws Throwable { + // doReturn(Object.class).when(advisedSupport).getTargetClass(); + // doReturn(Object.class.getInterfaces()).when(advisedSupport).getProxiedInterfaces(); + assertThat(true, is(interceptor.afterMethod(enhancedInstance, null, new Object[] {advisedSupport}, new Class[] {Object.class}, true))); + } + + @Test + public void testInterceptClassImplementsUserSuppliedInterface() throws Throwable { + doReturn(MockClassImplementsUserSuppliedInterface.class).when(advisedSupport).getTargetClass(); + doReturn(MockClassImplementsUserSuppliedInterface.class.getInterfaces()).when(advisedSupport).getProxiedInterfaces(); assertThat(false, is(interceptor.afterMethod(enhancedInstance, null, new Object[] {advisedSupport}, new Class[] {Object.class}, false))); } @Test - public void testInterceptEnhanceInstanceObject() throws Throwable { - doReturn(MockClass.class).when(advisedSupport).getTargetClass(); + public void testInterceptClassImplementsSpringProxy() throws Throwable { + // doReturn(MockClassImplementsSpringProxy.class).when(advisedSupport).getTargetClass(); + // doReturn(MockClassImplementsSpringProxy.class.getInterfaces()).when(advisedSupport).getProxiedInterfaces(); + assertThat(true, is(interceptor.afterMethod(enhancedInstance, null, new Object[] {advisedSupport}, new Class[] {Object.class}, true))); + } + + @Test + public void testInterceptClassImplementsEnhancedInstance() throws Throwable { + doReturn(MockClassImplementsEnhancedInstance.class).when(advisedSupport).getTargetClass(); + doReturn(MockClassImplementsEnhancedInstance.class.getInterfaces()).when(advisedSupport).getProxiedInterfaces(); assertThat(true, is(interceptor.afterMethod(enhancedInstance, null, new Object[] {advisedSupport}, new Class[] {Object.class}, false))); } - private class MockClass implements EnhancedInstance { + @Test + public void testClassImplementsEnhancedInstanceAndUserSuppliedInterface() throws Throwable { + doReturn(MockClassImplementsSpringProxyAndUserSuppliedInterface.class).when(advisedSupport).getTargetClass(); + doReturn(MockClassImplementsSpringProxyAndUserSuppliedInterface.class.getInterfaces()).when(advisedSupport).getProxiedInterfaces(); + assertThat(false, is(interceptor.afterMethod(enhancedInstance, null, new Object[] {advisedSupport}, new Class[] {Object.class}, false))); + } + + @Test + public void testInterceptClassImplementsSpringProxyAndEnhancedInstance() throws Throwable { + doReturn(MockClassImplementsSpringProxyAndEnhancedInstance.class).when(advisedSupport).getTargetClass(); + doReturn(MockClassImplementsSpringProxyAndEnhancedInstance.class.getInterfaces()).when(advisedSupport).getProxiedInterfaces(); + assertThat(true, is(interceptor.afterMethod(enhancedInstance, null, new Object[] {advisedSupport}, new Class[] {Object.class}, false))); + } + + @Test + public void testInterceptClassImplementsSpringProxyAndUserSuppliedInterface() throws Throwable { + doReturn(MockClassImplementsSpringProxyAndUserSuppliedInterface.class).when(advisedSupport).getTargetClass(); + doReturn(MockClassImplementsSpringProxyAndUserSuppliedInterface.class.getInterfaces()).when(advisedSupport).getProxiedInterfaces(); + assertThat(false, is(interceptor.afterMethod(enhancedInstance, null, new Object[] {advisedSupport}, new Class[] {Object.class}, false))); + } + + @Test + public void testInterceptClassImplementsEnhancedInstanceAndUserSuppliedInterface() throws Throwable { + doReturn(MockClassImplementsEnhancedInstanceAndUserSuppliedInterface.class).when(advisedSupport).getTargetClass(); + doReturn(MockClassImplementsEnhancedInstanceAndUserSuppliedInterface.class.getInterfaces()).when(advisedSupport).getProxiedInterfaces(); + assertThat(false, is(interceptor.afterMethod(enhancedInstance, null, new Object[] {advisedSupport}, new Class[] {Object.class}, false))); + } + + @Test + public void testInterceptClassImplementsSpringProxyAndEnhancedInstanceAndUserSuppliedInterface() throws Throwable { + doReturn(MockClassImplementsSpringProxyAndEnhancedInstanceAndUserSuppliedInterface.class).when(advisedSupport).getTargetClass(); + doReturn(MockClassImplementsSpringProxyAndEnhancedInstanceAndUserSuppliedInterface.class.getInterfaces()).when(advisedSupport).getProxiedInterfaces(); + assertThat(false, is(interceptor.afterMethod(enhancedInstance, null, new Object[] {advisedSupport}, new Class[] {Object.class}, false))); + } + + private class MockClassImplementsEnhancedInstance implements EnhancedInstance { @Override public Object getSkyWalkingDynamicField() { @@ -72,4 +124,81 @@ public void setSkyWalkingDynamicField(Object value) { } } + private class MockClassImplementsUserSuppliedInterface implements UserSuppliedInterface { + + @Override + public void methodOfUserSuppliedInterface() { + + } + + } + + private class MockClassImplementsSpringProxy implements SpringProxy { + + } + + private class MockClassImplementsSpringProxyAndEnhancedInstance implements EnhancedInstance, SpringProxy { + + @Override + public Object getSkyWalkingDynamicField() { + return null; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + + } + + } + + private class MockClassImplementsEnhancedInstanceAndUserSuppliedInterface implements EnhancedInstance, UserSuppliedInterface { + + @Override + public Object getSkyWalkingDynamicField() { + return null; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + + } + + @Override + public void methodOfUserSuppliedInterface() { + } + + } + + private class MockClassImplementsSpringProxyAndUserSuppliedInterface implements SpringProxy, UserSuppliedInterface { + + @Override + public void methodOfUserSuppliedInterface() { + } + + } + + private class MockClassImplementsSpringProxyAndEnhancedInstanceAndUserSuppliedInterface implements EnhancedInstance, SpringProxy, UserSuppliedInterface { + + @Override + public Object getSkyWalkingDynamicField() { + return null; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + + } + + @Override + public void methodOfUserSuppliedInterface() { + } + + } + + interface UserSuppliedInterface { + + void methodOfUserSuppliedInterface(); + + } + } diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-3.x-plugin/pom.xml index dfab514b02..02fa8639d0 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-3.x-plugin/pom.xml @@ -20,7 +20,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/pom.xml index 0d6d45e1e4..006febb79f 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/pom.xml @@ -20,7 +20,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -61,5 +61,14 @@ ${project.version} provided + + + org.apache.skywalking + apm-servlet-commons + ${project.version} + provided + diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-5.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-5.x-plugin/pom.xml index f74cf3733c..bea4ce00a9 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-5.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-5.x-plugin/pom.xml @@ -19,7 +19,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/pom.xml index eb71af642d..07bb6769f1 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/pom.xml @@ -20,7 +20,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -55,16 +55,24 @@ 5.2.4.RELEASE provided + - javax.servlet - javax.servlet-api - ${javax-servlet-api.version} + org.apache.skywalking + apm-servlet-commons + ${project.version} provided - jakarta.servlet - jakarta.servlet-api - 6.0.0 + javax.servlet + javax.servlet-api + ${javax-servlet-api.version} provided diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/RequestUtil.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/RequestUtil.java index b278c8f9e2..f6cc0168d8 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/RequestUtil.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/RequestUtil.java @@ -20,10 +20,10 @@ import org.apache.skywalking.apm.agent.core.context.tag.Tags; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.util.CollectionUtil; +import org.apache.skywalking.apm.plugin.servlet.HttpRequestWrapper; import org.apache.skywalking.apm.util.StringUtil; import org.springframework.http.server.reactive.ServerHttpRequest; -import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; @@ -32,17 +32,7 @@ import java.util.Map; public class RequestUtil { - public static void collectHttpParam(HttpServletRequest request, AbstractSpan span) { - final Map parameterMap = request.getParameterMap(); - if (parameterMap != null && !parameterMap.isEmpty()) { - String tagValue = CollectionUtil.toString(parameterMap); - tagValue = SpringMVCPluginConfig.Plugin.Http.HTTP_PARAMS_LENGTH_THRESHOLD > 0 ? - StringUtil.cut(tagValue, SpringMVCPluginConfig.Plugin.Http.HTTP_PARAMS_LENGTH_THRESHOLD) : tagValue; - Tags.HTTP.PARAMS.set(span, tagValue); - } - } - - public static void collectHttpParam(jakarta.servlet.http.HttpServletRequest request, AbstractSpan span) { + public static void collectHttpParam(HttpRequestWrapper request, AbstractSpan span) { final Map parameterMap = request.getParameterMap(); if (parameterMap != null && !parameterMap.isEmpty()) { String tagValue = CollectionUtil.toString(parameterMap); @@ -65,26 +55,7 @@ public static void collectHttpParam(ServerHttpRequest request, AbstractSpan span } } - public static void collectHttpHeaders(HttpServletRequest request, AbstractSpan span) { - final List headersList = new ArrayList<>(SpringMVCPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS.size()); - SpringMVCPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS.stream() - .filter( - headerName -> request.getHeaders(headerName) != null) - .forEach(headerName -> { - Enumeration headerValues = request.getHeaders( - headerName); - List valueList = Collections.list( - headerValues); - if (!CollectionUtil.isEmpty(valueList)) { - String headerValue = valueList.toString(); - headersList.add(headerName + "=" + headerValue); - } - }); - - collectHttpHeaders(headersList, span); - } - - public static void collectHttpHeaders(jakarta.servlet.http.HttpServletRequest request, AbstractSpan span) { + public static void collectHttpHeaders(HttpRequestWrapper request, AbstractSpan span) { final List headersList = new ArrayList<>(SpringMVCPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS.size()); SpringMVCPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS.stream() .filter( diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/interceptor/AbstractMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/interceptor/AbstractMethodInterceptor.java index df3aa7583f..8f70a71b3c 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/interceptor/AbstractMethodInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/interceptor/AbstractMethodInterceptor.java @@ -18,6 +18,7 @@ package org.apache.skywalking.apm.plugin.spring.mvc.commons.interceptor; +import java.lang.reflect.Method; import org.apache.skywalking.apm.agent.core.context.CarrierItem; import org.apache.skywalking.apm.agent.core.context.ContextCarrier; import org.apache.skywalking.apm.agent.core.context.ContextManager; @@ -33,6 +34,10 @@ import org.apache.skywalking.apm.agent.core.util.CollectionUtil; import org.apache.skywalking.apm.agent.core.util.MethodUtil; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.servlet.HttpRequestWrapper; +import org.apache.skywalking.apm.plugin.servlet.HttpRequestWrappers; +import org.apache.skywalking.apm.plugin.servlet.HttpResponseWrapper; +import org.apache.skywalking.apm.plugin.servlet.HttpResponseWrappers; import org.apache.skywalking.apm.plugin.spring.mvc.commons.EnhanceRequireObjectCache; import org.apache.skywalking.apm.plugin.spring.mvc.commons.RequestUtil; import org.apache.skywalking.apm.plugin.spring.mvc.commons.SpringMVCPluginConfig; @@ -41,10 +46,6 @@ import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.lang.reflect.Method; - import static org.apache.skywalking.apm.plugin.spring.mvc.commons.Constants.CONTROLLER_METHOD_STACK_DEPTH; import static org.apache.skywalking.apm.plugin.spring.mvc.commons.Constants.FORWARD_REQUEST_FLAG; import static org.apache.skywalking.apm.plugin.spring.mvc.commons.Constants.REACTIVE_ASYNC_SPAN_IN_RUNTIME_CONTEXT; @@ -56,37 +57,6 @@ */ public abstract class AbstractMethodInterceptor implements InstanceMethodsAroundInterceptor { private static final ILog LOGGER = LogManager.getLogger(AbstractMethodInterceptor.class); - private static boolean IS_SERVLET_GET_STATUS_METHOD_EXIST; - private static boolean IS_JAKARTA_SERVLET_GET_STATUS_METHOD_EXIST; - private static final String SERVLET_RESPONSE_CLASS = "javax.servlet.http.HttpServletResponse"; - private static final String JAKARTA_SERVLET_RESPONSE_CLASS = "jakarta.servlet.http.HttpServletResponse"; - private static final String GET_STATUS_METHOD = "getStatus"; - - private static boolean IN_SERVLET_CONTAINER; - private static boolean IS_JAVAX = false; - private static boolean IS_JAKARTA = false; - - static { - IS_SERVLET_GET_STATUS_METHOD_EXIST = MethodUtil.isMethodExist( - AbstractMethodInterceptor.class.getClassLoader(), SERVLET_RESPONSE_CLASS, GET_STATUS_METHOD); - IS_JAKARTA_SERVLET_GET_STATUS_METHOD_EXIST = MethodUtil.isMethodExist( - AbstractMethodInterceptor.class.getClassLoader(), - JAKARTA_SERVLET_RESPONSE_CLASS, GET_STATUS_METHOD); - try { - Class.forName(SERVLET_RESPONSE_CLASS, true, AbstractMethodInterceptor.class.getClassLoader()); - IN_SERVLET_CONTAINER = true; - IS_JAVAX = true; - } catch (Exception ignore) { - try { - Class.forName( - JAKARTA_SERVLET_RESPONSE_CLASS, true, AbstractMethodInterceptor.class.getClassLoader()); - IN_SERVLET_CONTAINER = true; - IS_JAKARTA = true; - } catch (Exception ignore2) { - IN_SERVLET_CONTAINER = false; - } - } - } public abstract String getRequestURL(Method method); @@ -111,55 +81,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr if (stackDepth == null) { final ContextCarrier contextCarrier = new ContextCarrier(); - if (IN_SERVLET_CONTAINER && IS_JAVAX && HttpServletRequest.class.isAssignableFrom(request.getClass())) { - final HttpServletRequest httpServletRequest = (HttpServletRequest) request; - CarrierItem next = contextCarrier.items(); - while (next.hasNext()) { - next = next.next(); - next.setHeadValue(httpServletRequest.getHeader(next.getHeadKey())); - } - - String operationName = this.buildOperationName(method, httpServletRequest.getMethod(), - (EnhanceRequireObjectCache) objInst.getSkyWalkingDynamicField()); - AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier); - Tags.URL.set(span, httpServletRequest.getRequestURL().toString()); - Tags.HTTP.METHOD.set(span, httpServletRequest.getMethod()); - span.setComponent(ComponentsDefine.SPRING_MVC_ANNOTATION); - SpanLayer.asHttp(span); + final HttpRequestWrapper httpServletRequest = HttpRequestWrappers.wrap(request); + if (httpServletRequest != null) { + handleBeforeMethod(objInst, method, httpServletRequest, contextCarrier); - if (SpringMVCPluginConfig.Plugin.SpringMVC.COLLECT_HTTP_PARAMS) { - RequestUtil.collectHttpParam(httpServletRequest, span); - } - - if (!CollectionUtil.isEmpty(SpringMVCPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS)) { - RequestUtil.collectHttpHeaders(httpServletRequest, span); - } - } else if (IN_SERVLET_CONTAINER && IS_JAKARTA && jakarta.servlet.http.HttpServletRequest.class.isAssignableFrom(request.getClass())) { - final jakarta.servlet.http.HttpServletRequest httpServletRequest = (jakarta.servlet.http.HttpServletRequest) request; - CarrierItem next = contextCarrier.items(); - while (next.hasNext()) { - next = next.next(); - next.setHeadValue(httpServletRequest.getHeader(next.getHeadKey())); - } - - String operationName = - this.buildOperationName(method, httpServletRequest.getMethod(), - (EnhanceRequireObjectCache) objInst.getSkyWalkingDynamicField()); - AbstractSpan span = - ContextManager.createEntrySpan(operationName, contextCarrier); - Tags.URL.set(span, httpServletRequest.getRequestURL().toString()); - Tags.HTTP.METHOD.set(span, httpServletRequest.getMethod()); - span.setComponent(ComponentsDefine.SPRING_MVC_ANNOTATION); - SpanLayer.asHttp(span); - - if (SpringMVCPluginConfig.Plugin.SpringMVC.COLLECT_HTTP_PARAMS) { - RequestUtil.collectHttpParam(httpServletRequest, span); - } - - if (!CollectionUtil - .isEmpty(SpringMVCPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS)) { - RequestUtil.collectHttpHeaders(httpServletRequest, span); - } } else if (ServerHttpRequest.class.isAssignableFrom(request.getClass())) { final ServerHttpRequest serverHttpRequest = (ServerHttpRequest) request; CarrierItem next = contextCarrier.items(); @@ -168,8 +93,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr next.setHeadValue(serverHttpRequest.getHeaders().getFirst(next.getHeadKey())); } - String operationName = this.buildOperationName(method, serverHttpRequest.getMethod().name(), - (EnhanceRequireObjectCache) objInst.getSkyWalkingDynamicField()); + String operationName = this.buildOperationName( + method, serverHttpRequest.getMethod().name(), + (EnhanceRequireObjectCache) objInst.getSkyWalkingDynamicField() + ); AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier); Tags.URL.set(span, serverHttpRequest.getURI().toString()); Tags.HTTP.METHOD.set(span, serverHttpRequest.getMethod().name()); @@ -198,6 +125,31 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr } } + private void handleBeforeMethod(EnhancedInstance objInst, Method method, + HttpRequestWrapper httpServletRequest, ContextCarrier contextCarrier) { + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + next.setHeadValue(httpServletRequest.getHeader(next.getHeadKey())); + } + + String operationName = this.buildOperationName( + method, httpServletRequest.getMethod(), (EnhanceRequireObjectCache) objInst.getSkyWalkingDynamicField()); + AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier); + Tags.URL.set(span, httpServletRequest.getRequestURL().toString()); + Tags.HTTP.METHOD.set(span, httpServletRequest.getMethod()); + span.setComponent(ComponentsDefine.SPRING_MVC_ANNOTATION); + SpanLayer.asHttp(span); + + if (SpringMVCPluginConfig.Plugin.SpringMVC.COLLECT_HTTP_PARAMS) { + RequestUtil.collectHttpParam(httpServletRequest, span); + } + + if (!CollectionUtil.isEmpty(SpringMVCPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS)) { + RequestUtil.collectHttpHeaders(httpServletRequest, span); + } + } + @Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { @@ -232,12 +184,11 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA Integer statusCode = null; - if (IS_SERVLET_GET_STATUS_METHOD_EXIST && HttpServletResponse.class.isAssignableFrom(response.getClass())) { - statusCode = ((HttpServletResponse) response).getStatus(); - } else if (IS_JAKARTA_SERVLET_GET_STATUS_METHOD_EXIST && jakarta.servlet.http.HttpServletResponse.class.isAssignableFrom(response.getClass())) { - statusCode = ((jakarta.servlet.http.HttpServletResponse) response).getStatus(); + final HttpResponseWrapper httpServletResponse = HttpResponseWrappers.wrap(response); + if (httpServletResponse != null) { + statusCode = httpServletResponse.getStatus(); } else if (ServerHttpResponse.class.isAssignableFrom(response.getClass())) { - if (IS_SERVLET_GET_STATUS_METHOD_EXIST || IS_JAKARTA_SERVLET_GET_STATUS_METHOD_EXIST) { + if (HttpResponseWrappers.servletStatusSupported()) { statusCode = ((ServerHttpResponse) response).getRawStatusCode(); } Object context = runtimeContext.get(REACTIVE_ASYNC_SPAN_IN_RUNTIME_CONTEXT); @@ -261,10 +212,9 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA // Active HTTP parameter collection automatically in the profiling context. if (!SpringMVCPluginConfig.Plugin.SpringMVC.COLLECT_HTTP_PARAMS && span.isProfiling()) { - if (IS_JAVAX && HttpServletRequest.class.isAssignableFrom(request.getClass())) { - RequestUtil.collectHttpParam((HttpServletRequest) request, span); - } else if (IS_JAKARTA && jakarta.servlet.http.HttpServletRequest.class.isAssignableFrom(request.getClass())) { - RequestUtil.collectHttpParam((jakarta.servlet.http.HttpServletRequest) request, span); + final HttpRequestWrapper httpServletRequest = HttpRequestWrappers.wrap(request); + if (httpServletRequest != null) { + RequestUtil.collectHttpParam(httpServletRequest, span); } else if (ServerHttpRequest.class.isAssignableFrom(request.getClass())) { RequestUtil.collectHttpParam((ServerHttpRequest) request, span); } @@ -309,7 +259,7 @@ private String buildOperationName(Method method, String httpMethod, EnhanceRequi pathMappingCache.addPathMapping(method, requestURL); requestURL = pathMappingCache.findPathMapping(method); } - operationName = String.join(":", httpMethod, requestURL); + operationName = String.join(":", httpMethod, requestURL); } return operationName; diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/pom.xml index 64abfdb7eb..4326ec7354 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT spring-plugins @@ -45,15 +45,16 @@ spring-webflux-5.x-webclient-plugin spring-webflux-6.x-webclient-plugin resttemplate-commons + spring-ai-1.x-plugin + spring-rabbitmq-plugin pom - apm-sdk-plugin + spring-plugins http://maven.apache.org UTF-8 - /.. diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-3.x-plugin/pom.xml index 4814e20078..2dab6a46c9 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-3.x-plugin/pom.xml @@ -20,7 +20,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/pom.xml index 08789aa954..966de1995f 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/pom.xml @@ -20,7 +20,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-commons/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-commons/pom.xml index 4976c79954..19ecc7989c 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-commons/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-commons/pom.xml @@ -20,7 +20,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/scheduled-annotation-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/scheduled-annotation-plugin/pom.xml index 23a5790757..c4dfd6d15f 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/scheduled-annotation-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/scheduled-annotation-plugin/pom.xml @@ -20,7 +20,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/scheduled-annotation-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/scheduled/define/ScheduledMethodInterceptorInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/scheduled-annotation-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/scheduled/define/ScheduledMethodInterceptorInstrumentation.java index 2aacd3720b..44b36778bb 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/scheduled-annotation-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/scheduled/define/ScheduledMethodInterceptorInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/scheduled-annotation-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/scheduled/define/ScheduledMethodInterceptorInstrumentation.java @@ -82,6 +82,19 @@ public ElementMatcher getConstructorMatcher() { public String getConstructorInterceptor() { return CONSTRUCTOR_WITH_STRING_INTERCEPTOR_CLASS; } + }, + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(4) + .and(takesArgument(0, Object.class)) + .and(takesArgument(1, Method.class)); + } + + @Override + public String getConstructorInterceptor() { + return CONSTRUCTOR_WITH_METHOD_INTERCEPTOR_CLASS; + } } }; } diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/pom.xml new file mode 100644 index 0000000000..583ce9c90c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/pom.xml @@ -0,0 +1,69 @@ + + + + + + spring-plugins + org.apache.skywalking + 9.7.0-SNAPSHOT + + 4.0.0 + + spring-ai-1.x-plugin + jar + + spring-ai-1.x-plugin + http://maven.apache.org + + + + + + + + + org.springframework.ai + spring-ai-client-chat + 1.1.0 + provided + + + + org.springframework.ai + spring-ai-vector-store + 1.1.0 + provided + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + false + + + + + \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java new file mode 100644 index 0000000000..faefa999bc --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.EmbeddingModelEnhanceContext; + +public class AbstractObservationVectorStoreConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + objInst.setSkyWalkingDynamicField(new VectorStoreEnhanceContext(resolveContextFromArgument(allArguments[0]))); + } + + private EmbeddingModelEnhanceContext resolveContextFromArgument(Object argument) { + if (argument instanceof EnhancedInstance) { + return getOrCreateContext((EnhancedInstance) argument); + } + return null; + } + + private EmbeddingModelEnhanceContext getOrCreateContext(EnhancedInstance embeddingModel) { + Object context = embeddingModel.getSkyWalkingDynamicField(); + if (context instanceof EmbeddingModelEnhanceContext) { + return (EmbeddingModelEnhanceContext) context; + } + EmbeddingModelEnhanceContext embeddingModelEnhanceContext = new EmbeddingModelEnhanceContext(); + embeddingModel.setSkyWalkingDynamicField(embeddingModelEnhanceContext); + return embeddingModelEnhanceContext; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java new file mode 100644 index 0000000000..473ffb5b95 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.agent.core.util.GsonUtil; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ErrorTypeResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.config.SpringAiPluginConfig; +import org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants; +import org.springframework.ai.document.Document; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore; +import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class AbstractObservationVectorStoreInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + SearchRequest request = (SearchRequest) allArguments[0]; + String dataSourceId = objInst.getClass().getSimpleName(); + + try { + VectorStoreObservationContext context = + createObservationContext(objInst, request); + + String resolved = + resolveDataSourceId(context, objInst); + + if (StringUtils.hasText(resolved)) { + dataSourceId = resolved; + } + } catch (Throwable ignored) { + + } + + AbstractSpan span = ContextManager.createExitSpan(Constants.RETRIEVAL + "/" + dataSourceId, dataSourceId); + + SpanLayer.asGenAI(span); + span.setComponent(ComponentsDefine.SPRING_AI); + Tags.GEN_AI_OPERATION_NAME.set(span, Constants.RETRIEVAL); + Tags.GEN_AI_DATA_SOURCE_ID.set(span, dataSourceId); + String model = resolveEmbeddingModelName(objInst); + if (StringUtils.hasText(model)) { + Tags.GEN_AI_REQUEST_MODEL.set(span, model); + } + + if (request != null) { + Tags.GEN_AI_TOP_K.set(span, String.valueOf(request.getTopK())); + String query = request.getQuery(); + if (StringUtils.hasText(query) && SpringAiPluginConfig.Plugin.SpringAi.COLLECT_RETRIEVAL_QUERY) { + int limit = SpringAiPluginConfig.Plugin.SpringAi.RETRIEVAL_QUERY_LENGTH_LIMIT; + if (limit >= 0 && query.length() > limit) { + query = query.substring(0, limit); + } + Tags.GEN_AI_RETRIEVAL_QUERY_TEXT.set(span, query); + } + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + if (!ContextManager.isActive()) { + return ret; + } + try { + if (ret instanceof List && SpringAiPluginConfig.Plugin.SpringAi.COLLECT_RETRIEVAL_DOCUMENTS) { + Tags.GEN_AI_RETRIEVAL_DOCUMENTS.set(ContextManager.activeSpan(), toDocumentsJson((List) ret)); + } + } finally { + ContextManager.stopSpan(); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + if (ContextManager.isActive()) { + AbstractSpan span = ContextManager.activeSpan(); + span.log(t); + ErrorTypeResolver.setErrorType(span, t); + } + } + + private VectorStoreObservationContext createObservationContext(EnhancedInstance objInst, SearchRequest request) { + VectorStoreObservationContext.Builder builder = ((AbstractObservationVectorStore) objInst) + .createObservationContextBuilder(VectorStoreObservationContext.Operation.QUERY.value()); + if (request != null) { + builder.queryRequest(request); + } + return builder.build(); + } + + private String resolveEmbeddingModelName(EnhancedInstance objInst) { + Object context = objInst.getSkyWalkingDynamicField(); + if (context instanceof VectorStoreEnhanceContext) { + return ((VectorStoreEnhanceContext) context).getEmbeddingModelName(); + } + return null; + } + + private String resolveDataSourceId(VectorStoreObservationContext context, EnhancedInstance objInst) { + StringBuilder dataSourceId = new StringBuilder(); + appendDataSourcePart(dataSourceId, context.getDatabaseSystem()); + appendDataSourcePart(dataSourceId, context.getNamespace()); + appendDataSourcePart(dataSourceId, context.getCollectionName()); + if (dataSourceId.length() > 0) { + return dataSourceId.toString(); + } + return objInst.getClass().getSimpleName(); + } + + private void appendDataSourcePart(StringBuilder dataSourceId, String value) { + if (!StringUtils.hasText(value)) { + return; + } + if (dataSourceId.length() > 0) { + dataSourceId.append('/'); + } + dataSourceId.append(value); + } + + private String toDocumentsJson(List documents) { + List> retrievalDocuments = new ArrayList<>(documents.size()); + for (Object item : documents) { + if (!(item instanceof Document)) { + continue; + } + Document document = (Document) item; + Map documentMap = new LinkedHashMap<>(); + documentMap.put("id", document.getId()); + if (document.getScore() != null) { + documentMap.put("score", document.getScore()); + } + retrievalDocuments.add(documentMap); + } + return GsonUtil.toJson(retrievalDocuments); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelCallInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelCallInterceptor.java new file mode 100644 index 0000000000..2e25f9e13b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelCallInterceptor.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ErrorTypeResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.config.SpringAiPluginConfig; +import org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants; +import org.apache.skywalking.apm.plugin.spring.ai.v1.messages.InputMessages; +import org.apache.skywalking.apm.plugin.spring.ai.v1.messages.OutputMessages; +import org.springframework.ai.chat.metadata.ChatResponseMetadata; +import org.springframework.ai.chat.metadata.Usage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; + +import java.lang.reflect.Method; + +public class ChatModelCallInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + ChatModelMetadataResolver.ApiMetadata apiMetadata = ChatModelMetadataResolver.getMetadata(objInst); + AbstractSpan span = ContextManager.createExitSpan("Spring-ai/" + apiMetadata.getProviderName() + "/call", apiMetadata.getPeer()); + SpanLayer.asGenAI(span); + span.setComponent(apiMetadata.getComponent()); + Tags.GEN_AI_OPERATION_NAME.set(span, Constants.CHAT); + Tags.GEN_AI_PROVIDER_NAME.set(span, apiMetadata.getProviderName()); + + Prompt prompt = (Prompt) allArguments[0]; + ChatOptions chatOptions = prompt.getOptions(); + if (chatOptions == null) { + return; + } + + if (chatOptions.getModel() != null) { + Tags.GEN_AI_REQUEST_MODEL.set(span, chatOptions.getModel()); + } + if (chatOptions.getTemperature() != null) { + Tags.GEN_AI_TEMPERATURE.set(span, String.valueOf(chatOptions.getTemperature())); + } + if (chatOptions.getTopK() != null) { + Tags.GEN_AI_TOP_K.set(span, String.valueOf(chatOptions.getTopK())); + } + if (chatOptions.getTopP() != null) { + Tags.GEN_AI_TOP_P.set(span, String.valueOf(chatOptions.getTopP())); + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + if (!ContextManager.isActive()) { + return ret; + } + + try { + if (!(ret instanceof ChatResponse)) { + return ret; + } + + ChatResponse response = (ChatResponse) ret; + + AbstractSpan span = ContextManager.activeSpan(); + ChatResponseMetadata metadata = response.getMetadata(); + + long totalTokens = 0; + + if (metadata != null) { + if (metadata.getId() != null) { + Tags.GEN_AI_RESPONSE_ID.set(span, metadata.getId()); + } + if (metadata.getModel() != null) { + Tags.GEN_AI_RESPONSE_MODEL.set(span, metadata.getModel()); + } + + Usage usage = metadata.getUsage(); + if (usage != null) { + if (usage.getPromptTokens() != null) { + Tags.GEN_AI_USAGE_INPUT_TOKENS.set(span, String.valueOf(usage.getPromptTokens())); + } + if (usage.getCompletionTokens() != null) { + Tags.GEN_AI_USAGE_OUTPUT_TOKENS.set(span, String.valueOf(usage.getCompletionTokens())); + } + if (usage.getTotalTokens() != null) { + totalTokens = usage.getTotalTokens(); + Tags.GEN_AI_CLIENT_TOKEN_USAGE.set(span, String.valueOf(totalTokens)); + } + } + } + + Generation generation = response.getResult(); + if (generation != null && generation.getMetadata() != null) { + String finishReason = generation.getMetadata().getFinishReason(); + if (finishReason != null) { + Tags.GEN_AI_RESPONSE_FINISH_REASONS.set(span, finishReason); + } + } + + collectContent(span, allArguments, response, totalTokens); + } finally { + ContextManager.stopSpan(); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + if (ContextManager.isActive()) { + AbstractSpan span = ContextManager.activeSpan(); + span.log(t); + ErrorTypeResolver.setErrorType(span, t); + } + } + + private void collectContent(AbstractSpan span, Object[] allArguments, ChatResponse response, long totalTokens) { + int tokenThreshold = SpringAiPluginConfig.Plugin.SpringAi.CONTENT_COLLECT_THRESHOLD_TOKENS; + + if (tokenThreshold >= 0 && totalTokens < tokenThreshold) { + return; + } + + if (SpringAiPluginConfig.Plugin.SpringAi.COLLECT_INPUT_MESSAGES) { + collectPrompt(span, allArguments); + } + + if (SpringAiPluginConfig.Plugin.SpringAi.COLLECT_OUTPUT_MESSAGES) { + collectCompletion(span, response); + } + } + + private void collectPrompt(AbstractSpan span, Object[] allArguments) { + Prompt prompt = (Prompt) allArguments[0]; + if (prompt == null) { + return; + } + + InputMessages inputMessages = InputMessages.fromPrompt(prompt); + String inputMessagesJson = inputMessages.toJson(); + int limit = SpringAiPluginConfig.Plugin.SpringAi.INPUT_MESSAGES_LENGTH_LIMIT; + if (limit >= 0 && inputMessagesJson.length() > limit) { + inputMessagesJson = inputMessagesJson.substring(0, limit); + } + + Tags.GEN_AI_INPUT_MESSAGES.set(span, inputMessagesJson); + } + + private void collectCompletion(AbstractSpan span, ChatResponse response) { + + OutputMessages outputMessages = OutputMessages.fromChatResponse(response); + String outputMessagesJson = outputMessages.toJson(); + int limit = SpringAiPluginConfig.Plugin.SpringAi.OUTPUT_MESSAGES_LENGTH_LIMIT; + + if (limit >= 0 && outputMessagesJson.length() > limit) { + outputMessagesJson = outputMessagesJson.substring(0, limit); + } + Tags.GEN_AI_OUTPUT_MESSAGES.set(span, outputMessagesJson); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelStreamInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelStreamInterceptor.java new file mode 100644 index 0000000000..7231a99aa1 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelStreamInterceptor.java @@ -0,0 +1,274 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ErrorTypeResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.config.SpringAiPluginConfig; +import org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants; +import org.apache.skywalking.apm.plugin.spring.ai.v1.messages.InputMessages; +import org.apache.skywalking.apm.plugin.spring.ai.v1.messages.OutputMessages; +import org.springframework.ai.chat.metadata.ChatResponseMetadata; +import org.springframework.ai.chat.metadata.Usage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.util.StringUtils; +import reactor.core.publisher.Flux; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +public class ChatModelStreamInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + ChatModelMetadataResolver.ApiMetadata apiMetadata = ChatModelMetadataResolver.getMetadata(objInst); + AbstractSpan span = ContextManager.createExitSpan("Spring-ai/" + apiMetadata.getProviderName() + "/stream", apiMetadata.getPeer()); + SpanLayer.asGenAI(span); + + span.setComponent(apiMetadata.getComponent()); + Tags.GEN_AI_OPERATION_NAME.set(span, Constants.CHAT); + Tags.GEN_AI_PROVIDER_NAME.set(span, apiMetadata.getProviderName()); + + Prompt prompt = (Prompt) allArguments[0]; + if (prompt == null) { + return; + } + + ContextManager.getRuntimeContext().put(Constants.SPRING_AI_STREAM_START_TIME, System.currentTimeMillis()); + + ChatOptions chatOptions = prompt.getOptions(); + if (chatOptions == null) { + return; + } + + Tags.GEN_AI_REQUEST_MODEL.set(span, chatOptions.getModel()); + Tags.GEN_AI_TEMPERATURE.set(span, String.valueOf(chatOptions.getTemperature())); + Tags.GEN_AI_TOP_K.set(span, String.valueOf(chatOptions.getTopK())); + Tags.GEN_AI_TOP_P.set(span, String.valueOf(chatOptions.getTopP())); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + if (!ContextManager.isActive()) { + return ret; + } + + final AbstractSpan span = ContextManager.activeSpan(); + final ContextSnapshot snapshot = ContextManager.capture(); + + span.prepareForAsync(); + ContextManager.stopSpan(); + + @SuppressWarnings("unchecked") final Flux flux = (Flux) ret; + + final StreamState state = new StreamState(readAndClearStartTime()); + + return flux + .doOnNext(response -> onStreamNext(span, response, state)) + .doOnError(t -> recordError(span, t)) + .doFinally(signalType -> onStreamFinally(span, allArguments, state)) + .contextWrite(c -> c.put(Constants.SKYWALKING_CONTEXT_SNAPSHOT, snapshot)); + } + + private void recordError(AbstractSpan span, Throwable t) { + span.log(t); + ErrorTypeResolver.setErrorType(span, t); + } + + private void onStreamNext(AbstractSpan span, ChatResponse response, StreamState state) { + state.lastResponseRef.set(response); + + final Generation generation = response.getResult(); + if (generation == null) { + return; + } + + recordTtfrIfFirstToken(span, generation, state); + recordFinishReason(generation, state); + appendCompletionChunk(generation, state); + } + + private void onStreamFinally(AbstractSpan span, Object[] allArguments, StreamState state) { + try { + ChatResponse finalResponse = state.lastResponseRef.get(); + long totalTokens = 0; + + if (finalResponse != null && finalResponse.getMetadata() != null) { + ChatResponseMetadata metadata = finalResponse.getMetadata(); + collectResponseTags(span, metadata, state); + totalTokens = collectUsageTags(span, metadata.getUsage()); + } + + int tokenThreshold = SpringAiPluginConfig.Plugin.SpringAi.CONTENT_COLLECT_THRESHOLD_TOKENS; + if (tokenThreshold >= 0 && totalTokens < tokenThreshold) { + return; + } + + if (SpringAiPluginConfig.Plugin.SpringAi.COLLECT_INPUT_MESSAGES) { + collectPrompt(span, allArguments); + } + + if (SpringAiPluginConfig.Plugin.SpringAi.COLLECT_OUTPUT_MESSAGES) { + collectCompletion(span, state); + } + } catch (Throwable t) { + span.log(t); + } finally { + span.asyncFinish(); + } + } + + private void recordTtfrIfFirstToken(AbstractSpan span, Generation generation, StreamState state) { + if (state.startTime == null) { + return; + } + if (generation.getOutput() == null || !StringUtils.hasText(generation.getOutput().getText())) { + return; + } + if (state.firstResponseReceived.compareAndSet(false, true)) { + Tags.GEN_AI_SERVER_TIME_TO_FIRST_TOKEN.set(span, String.valueOf(System.currentTimeMillis() - state.startTime)); + } + } + + private void recordFinishReason(Generation generation, StreamState state) { + if (generation.getMetadata() == null) { + return; + } + String reason = generation.getMetadata().getFinishReason(); + if (reason != null) { + state.finishReason.set(reason); + } + } + + private void appendCompletionChunk(Generation generation, StreamState state) { + if (generation.getOutput() == null) { + return; + } + String text = generation.getOutput().getText(); + if (text != null) { + state.completionBuilder.append(text); + } + } + + private void collectResponseTags(AbstractSpan span, ChatResponseMetadata metadata, StreamState state) { + if (metadata.getId() != null) { + Tags.GEN_AI_RESPONSE_ID.set(span, metadata.getId()); + } + if (metadata.getModel() != null) { + Tags.GEN_AI_RESPONSE_MODEL.set(span, metadata.getModel()); + } + Tags.GEN_AI_RESPONSE_FINISH_REASONS.set(span, state.finishReason.get()); + } + + private long collectUsageTags(AbstractSpan span, Usage usage) { + if (usage == null) { + return 0; + } + + if (usage.getPromptTokens() != null) { + Tags.GEN_AI_USAGE_INPUT_TOKENS.set(span, String.valueOf(usage.getPromptTokens())); + } + + if (usage.getCompletionTokens() != null) { + Tags.GEN_AI_USAGE_OUTPUT_TOKENS.set(span, String.valueOf(usage.getCompletionTokens())); + } + + long total = usage.getTotalTokens() != null ? usage.getTotalTokens() : 0; + Tags.GEN_AI_CLIENT_TOKEN_USAGE.set(span, String.valueOf(total)); + return total; + } + + private void collectPrompt(AbstractSpan span, Object[] allArguments) { + Prompt prompt = (Prompt) allArguments[0]; + if (prompt == null) { + return; + } + + InputMessages inputMessages = InputMessages.fromPrompt(prompt); + String inputMessagesJson = inputMessages.toJson(); + + int limit = SpringAiPluginConfig.Plugin.SpringAi.INPUT_MESSAGES_LENGTH_LIMIT; + if (limit >= 0 && inputMessagesJson.length() > limit) { + inputMessagesJson = inputMessagesJson.substring(0, limit); + } + + Tags.GEN_AI_INPUT_MESSAGES.set(span, inputMessagesJson); + } + + private void collectCompletion(AbstractSpan span, StreamState state) { + + String fullText = state.completionBuilder.toString(); + String finishReason = state.finishReason.get(); + List parts = new ArrayList<>(); + if (fullText != null && !fullText.isEmpty()) { + parts.add(new InputMessages.TextPart(fullText)); + } + + OutputMessages outputMessages = OutputMessages.create().append(OutputMessages.OutputMessage.create("assistant", parts, finishReason)); + String outputMessagesJson = outputMessages.toJson(); + + int limit = SpringAiPluginConfig.Plugin.SpringAi.OUTPUT_MESSAGES_LENGTH_LIMIT; + if (limit >= 0 && outputMessagesJson.length() > limit) { + outputMessagesJson = outputMessagesJson.substring(0, limit); + } + + Tags.GEN_AI_OUTPUT_MESSAGES.set(span, outputMessagesJson); + } + + private Long readAndClearStartTime() { + Long startTime = (Long) ContextManager.getRuntimeContext().get(Constants.SPRING_AI_STREAM_START_TIME); + ContextManager.getRuntimeContext().remove(Constants.SPRING_AI_STREAM_START_TIME); + return startTime; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + if (ContextManager.isActive()) { + AbstractSpan span = ContextManager.activeSpan(); + span.log(t); + ErrorTypeResolver.setErrorType(span, t); + } + } + + private static final class StreamState { + final AtomicReference lastResponseRef = new AtomicReference<>(); + final StringBuilder completionBuilder = new StringBuilder(); + final AtomicReference finishReason = new AtomicReference<>(""); + final AtomicBoolean firstResponseReceived = new AtomicBoolean(false); + final Long startTime; + + StreamState(Long startTime) { + this.startTime = startTime; + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/ForwardInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/DefaultToolCallingManagerInterceptor.java similarity index 52% rename from apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/ForwardInterceptor.java rename to apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/DefaultToolCallingManagerInterceptor.java index ab43e22fb3..929fd9bbd8 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/ForwardInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/DefaultToolCallingManagerInterceptor.java @@ -6,7 +6,7 @@ * (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 + * 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, @@ -14,50 +14,50 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * */ -package org.apache.skywalking.apm.plugin.tomcat78x; +package org.apache.skywalking.apm.plugin.spring.ai.v1; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants; +import org.springframework.ai.model.tool.internal.ToolCallReactiveContextHolder; +import reactor.util.context.ContextView; + +import java.lang.reflect.Method; -public class ForwardInterceptor implements InstanceMethodsAroundInterceptor, InstanceConstructorInterceptor { +public class DefaultToolCallingManagerInterceptor implements InstanceMethodsAroundInterceptor { @Override - public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - MethodInterceptResult result) throws Throwable { - if (ContextManager.isActive()) { - AbstractSpan abstractTracingSpan = ContextManager.activeSpan(); - Map eventMap = new HashMap(); - eventMap.put("forward-url", objInst.getSkyWalkingDynamicField() == null ? "" : String.valueOf(objInst.getSkyWalkingDynamicField())); - abstractTracingSpan.log(System.currentTimeMillis(), eventMap); - ContextManager.getRuntimeContext().put(Constants.FORWARD_REQUEST_FLAG, true); + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + AbstractSpan span = ContextManager.createLocalSpan("Spring-ai/tool/call"); + span.setComponent(ComponentsDefine.SPRING_AI); + + ContextView reactorCtx = ToolCallReactiveContextHolder.getContext(); + + if (reactorCtx != null && reactorCtx.hasKey(Constants.SKYWALKING_CONTEXT_SNAPSHOT)) { + ContextSnapshot snapshot = reactorCtx.get(Constants.SKYWALKING_CONTEXT_SNAPSHOT); + ContextManager.continued(snapshot); } } @Override - public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - Object ret) throws Throwable { - ContextManager.getRuntimeContext().remove(Constants.FORWARD_REQUEST_FLAG); + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } return ret; } @Override - public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, - Class[] argumentsTypes, Throwable t) { - - } - - @Override - public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { - objInst.setSkyWalkingDynamicField(allArguments[1]); + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + if (ContextManager.isActive()) { + ContextManager.activeSpan().log(t); + } } } diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java new file mode 100644 index 0000000000..8e2b57fae9 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.EmbeddingModelEnhanceContext; +import org.springframework.ai.embedding.EmbeddingResponse; +import org.springframework.ai.embedding.EmbeddingResponseMetadata; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Method; + +public class EmbeddingModelInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) { + + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) { + if (!(ret instanceof EmbeddingResponse)) { + return ret; + } + + EmbeddingResponseMetadata metadata = ((EmbeddingResponse) ret).getMetadata(); + if (metadata == null) { + return ret; + } + String model = metadata.getModel(); + if (!StringUtils.hasText(model)) { + return ret; + } + EmbeddingModelEnhanceContext context = getOrCreateContext(objInst); + context.setEmbeddingModelNameIfAbsent(model); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + } + + private EmbeddingModelEnhanceContext getOrCreateContext(EnhancedInstance objInst) { + Object context = objInst.getSkyWalkingDynamicField(); + if (context instanceof EmbeddingModelEnhanceContext) { + return (EmbeddingModelEnhanceContext) context; + } + EmbeddingModelEnhanceContext embeddingModelEnhanceContext = new EmbeddingModelEnhanceContext(); + objInst.setSkyWalkingDynamicField(embeddingModelEnhanceContext); + return embeddingModelEnhanceContext; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ToolCallbackCallInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ToolCallbackCallInterceptor.java new file mode 100644 index 0000000000..637599da57 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ToolCallbackCallInterceptor.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.spring.ai.v1.config.SpringAiPluginConfig; +import org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.definition.ToolDefinition; + +import java.lang.reflect.Method; + +public class ToolCallbackCallInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + ToolCallback toolCallback = (ToolCallback) objInst; + ToolDefinition definition = toolCallback.getToolDefinition(); + + String toolName = definition.name(); + + AbstractSpan span = ContextManager.createLocalSpan("Spring-ai/tool/execute/" + toolName); + span.setComponent(ComponentsDefine.SPRING_AI); + + Tags.GEN_AI_TOOL_NAME.set(span, toolName); + Tags.GEN_AI_OPERATION_NAME.set(span, Constants.EXECUTE_TOOL); + + if (SpringAiPluginConfig.Plugin.SpringAi.COLLECT_TOOL_INPUT) { + String toolInput = (String) allArguments[0]; + Tags.GEN_AI_TOOL_CALL_ARGUMENTS.set(span, toolInput); + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + if (ContextManager.isActive()) { + AbstractSpan span = ContextManager.activeSpan(); + if (SpringAiPluginConfig.Plugin.SpringAi.COLLECT_TOOL_OUTPUT && ret != null) { + Tags.GEN_AI_TOOL_CALL_RESULT.set(span, (String) ret); + } + + ContextManager.stopSpan(); + } + + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + if (ContextManager.isActive()) { + ContextManager.activeSpan().log(t); + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java new file mode 100644 index 0000000000..76916a59ea --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1; + +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.EmbeddingModelEnhanceContext; + +public class VectorStoreEnhanceContext { + + private final EmbeddingModelEnhanceContext embeddingModelEnhanceContext; + + public VectorStoreEnhanceContext(EmbeddingModelEnhanceContext embeddingModelEnhanceContext) { + this.embeddingModelEnhanceContext = embeddingModelEnhanceContext; + } + + public String getEmbeddingModelName() { + if (embeddingModelEnhanceContext == null) { + return null; + } + return embeddingModelEnhanceContext.getEmbeddingModelName(); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ChatModelMetadataResolver.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ChatModelMetadataResolver.java new file mode 100644 index 0000000000..501f5e026c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ChatModelMetadataResolver.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.common; + +import org.apache.skywalking.apm.agent.core.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.network.trace.component.OfficialComponent; +import org.apache.skywalking.apm.plugin.spring.ai.v1.enums.AiProviderEnum; + +import javax.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.Map; + +public class ChatModelMetadataResolver { + + private static final ILog LOGGER = LogManager.getLogger(ChatModelMetadataResolver.class); + + private static final Map MODEL_METADATA_MAP = new HashMap<>(); + + static { + for (AiProviderEnum provider : AiProviderEnum.values()) { + if (provider.getModelClassName() != null && provider.getValue() != null) { + MODEL_METADATA_MAP.put( + provider.getModelClassName(), + new ApiMetadata(provider.getValue(), matchComponent(provider)) + ); + } + } + } + + private static OfficialComponent matchComponent(AiProviderEnum provider) { + switch (provider) { + case ANTHROPIC_CLAUDE: + return ComponentsDefine.SPRING_AI_ANTHROPIC; + case AMAZON_BEDROCK_CONVERSE: + return ComponentsDefine.SPRING_AI_BEDROCK; + case AZURE_OPENAI: + return ComponentsDefine.SPRING_AI_AZURE_OPENAI; + case OCI_GENAI_COHERE: + return ComponentsDefine.SPRING_AI_COHERE; + case DEEPSEEK: + return ComponentsDefine.SPRING_AI_DEEPSEEK; + case GOOGLE_GENAI: + return ComponentsDefine.SPRING_AI_GOOGLE_GENAI; + case GOOGLE_VERTEXAI_GEMINI: + return ComponentsDefine.SPRING_AI_VERTEXAI; + case MISTRAL_AI: + return ComponentsDefine.SPRING_AI_MISTRAL_AI; + case OPENAI: + return ComponentsDefine.SPRING_AI_OPENAI; + case HUGGINGFACE: + return ComponentsDefine.SPRING_AI_HUGGINGFACE; + case MINIMAX: + return ComponentsDefine.SPRING_AI_MINIMAX; + case OLLAMA: + return ComponentsDefine.SPRING_AI_OLLAMA; + case OPENAI_SDK_OFFICIAL: + return ComponentsDefine.SPRING_AI_OPENAI; + case ZHIPU_AI: + return ComponentsDefine.SPRING_AI_ZHIPU_AI; + case UNKNOWN: + default: + return ComponentsDefine.SPRING_AI_UNKNOWN; + } + } + + @NotNull + public static ApiMetadata getMetadata(Object chatModelInstance) { + ApiMetadata metadata = MODEL_METADATA_MAP.get(chatModelInstance.getClass().getName()); + if (metadata == null) { + MODEL_METADATA_MAP.get(AiProviderEnum.UNKNOWN); + } + return metadata; + } + + public static ApiMetadata getMetadata(String modelClassName) { + try { + return MODEL_METADATA_MAP.get(modelClassName); + } catch (Exception e) { + LOGGER.error("spring-ai plugin get modelMetadata error: ", e); + return null; + } + } + + public static class ApiMetadata { + + private final String providerName; + private final OfficialComponent component; + private volatile String baseUrl; + private volatile String completionsPath; + + ApiMetadata(String providerName, OfficialComponent component) { + this.providerName = providerName; + this.component = component; + } + + public String getProviderName() { + return providerName; + } + + public OfficialComponent getComponent() { + return component; + } + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public String getCompletionsPath() { + return completionsPath; + } + + public void setCompletionsPath(String completionsPath) { + this.completionsPath = completionsPath; + } + + public String getPeer() { + if (baseUrl != null && !baseUrl.isEmpty()) { + return completionsPath != null && !completionsPath.isEmpty() + ? baseUrl + completionsPath + : baseUrl; + } + return providerName; + } + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/EmbeddingModelEnhanceContext.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/EmbeddingModelEnhanceContext.java new file mode 100644 index 0000000000..199bd97cd4 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/EmbeddingModelEnhanceContext.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.common; + +import org.springframework.util.StringUtils; + +public class EmbeddingModelEnhanceContext { + + private volatile String embeddingModelName; + + public String getEmbeddingModelName() { + return embeddingModelName; + } + + public void setEmbeddingModelNameIfAbsent(String embeddingModelName) { + if (!StringUtils.hasText(this.embeddingModelName) && StringUtils.hasText(embeddingModelName)) { + this.embeddingModelName = embeddingModelName; + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java new file mode 100644 index 0000000000..1d3172cf3d --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.common; + +import org.apache.skywalking.apm.agent.core.context.tag.AbstractTag; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; + +import javax.net.ssl.SSLHandshakeException; +import java.net.SocketTimeoutException; +import java.security.cert.CertPathValidatorException; +import java.security.cert.CertificateException; +import java.util.concurrent.TimeoutException; + +public final class ErrorTypeResolver { + + private static final AbstractTag ERROR_TYPE = Tags.ofKey("error.type"); + private static final String TIMEOUT = "timeout"; + private static final String SERVER_CERTIFICATE_INVALID = "server_certificate_invalid"; + + private ErrorTypeResolver() { + } + + public static void setErrorType(AbstractSpan span, Throwable throwable) { + span.tag(ERROR_TYPE, resolve(throwable)); + } + + private static String resolve(Throwable throwable) { + if (matches(throwable, ErrorTypeResolver::isTimeout)) { + return TIMEOUT; + } + if (matches(throwable, ErrorTypeResolver::isCertificateInvalid)) { + return SERVER_CERTIFICATE_INVALID; + } + return throwable.getClass().getName(); + } + + private static boolean isTimeout(Throwable throwable) { + return throwable instanceof SocketTimeoutException + || throwable instanceof TimeoutException + || throwable.getClass().getName().contains("TimeoutException"); + } + + private static boolean isCertificateInvalid(Throwable throwable) { + return throwable instanceof SSLHandshakeException + || throwable instanceof CertificateException + || throwable instanceof CertPathValidatorException; + } + + private static boolean matches(Throwable throwable, Matcher matcher) { + Throwable current = throwable; + while (current != null) { + if (matcher.matches(current)) { + return true; + } + current = current.getCause(); + } + return false; + } + + private interface Matcher { + boolean matches(Throwable throwable); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/config/SpringAiPluginConfig.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/config/SpringAiPluginConfig.java new file mode 100644 index 0000000000..b0a56635ce --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/config/SpringAiPluginConfig.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.config; + +import org.apache.skywalking.apm.agent.core.boot.PluginConfig; + +public class SpringAiPluginConfig { + + public static class Plugin { + + @PluginConfig(root = SpringAiPluginConfig.class) + public static class SpringAi { + + /** + * Whether to collect the prompt content (input text) of the GenAI request. + */ + public static boolean COLLECT_INPUT_MESSAGES = false; + + /** + * Whether to collect the completion content (output text) of the GenAI response. + */ + public static boolean COLLECT_OUTPUT_MESSAGES = false; + + /** + * The maximum characters of the collected prompt content. + * If the content exceeds this limit, it will be truncated. + * Use a negative value to represent no limit, but be aware this could cause OOM. + */ + public static int INPUT_MESSAGES_LENGTH_LIMIT = 1024; + + /** + * The maximum characters of the collected completion content. + * If the content exceeds this limit, it will be truncated. + * Use a negative value to represent no limit, but be aware this could cause OOM. + */ + public static int OUTPUT_MESSAGES_LENGTH_LIMIT = 1024; + + /** + * The threshold for token usage to trigger content collection. + * When set to a positive value, prompt and completion will only be collected + * if the total token usage of the request exceeds this threshold. + * * This requires {@link #COLLECT_INPUT_MESSAGES} or {@link #COLLECT_OUTPUT_MESSAGES} to be enabled first. + * Use a negative value to disable this threshold-based filtering (collect all). + */ + public static int CONTENT_COLLECT_THRESHOLD_TOKENS = -1; + + /** + * Whether to collect the arguments (input parameters) of the tool/function call. + */ + public static boolean COLLECT_TOOL_INPUT = false; + + /** + * Whether to collect the execution result (output) of the tool/function call. + */ + public static boolean COLLECT_TOOL_OUTPUT = false; + + /** + * Whether to collect the query of the rag call. + */ + public static boolean COLLECT_RETRIEVAL_QUERY = false; + + /** + * The maximum characters of the collected rag query content. + * If the content exceeds this limit, it will be truncated. + * Use a negative value to represent no limit, but be aware this could cause OOM. + */ + public static int RETRIEVAL_QUERY_LENGTH_LIMIT = 1024; + + /** + * Whether to collect the documents of the rag call. + */ + public static boolean COLLECT_RETRIEVAL_DOCUMENTS = false; + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/contant/Constants.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/contant/Constants.java new file mode 100644 index 0000000000..688f4c321a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/contant/Constants.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.contant; + +public class Constants { + public static final String SPRING_AI_STREAM_START_TIME = "Spring-ai.stream.startTime"; + + public static final String SKYWALKING_CONTEXT_SNAPSHOT = "SKYWALKING_CONTEXT_SNAPSHOT"; + + public static final String CHAT = "chat"; + + public static final String EXECUTE_TOOL = "execute_tool"; + + public static final String RETRIEVAL = "retrieval"; + + public static final String DEFAULT_COMPLETIONS_PATH = "/v1/chat/completions"; +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/AbstractObservationVectorStoreInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/AbstractObservationVectorStoreInstrumentation.java new file mode 100644 index 0000000000..f379c0464c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/AbstractObservationVectorStoreInstrumentation.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.logical.LogicalMatchOperation; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; + +public class AbstractObservationVectorStoreInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore"; + + private static final String INTERCEPT_METHOD = "doSimilaritySearch"; + + private static final String INTERCEPTOR_CLASS = + "org.apache.skywalking.apm.plugin.spring.ai.v1.AbstractObservationVectorStoreInterceptor"; + + private static final String CONSTRUCTOR_INTERCEPTOR_CLASS = + "org.apache.skywalking.apm.plugin.spring.ai.v1.AbstractObservationVectorStoreConstructorInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return LogicalMatchOperation.or(HierarchyMatch.byHierarchyMatch(ENHANCE_CLASS), MultiClassNameMatch.byMultiClassMatch(ENHANCE_CLASS)); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArgumentWithType(0, "org.springframework.ai.embedding.EmbeddingModel"); + } + + @Override + public String getConstructorInterceptor() { + return CONSTRUCTOR_INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(INTERCEPT_METHOD) + .and(takesArguments(1)) + .and(takesArgumentWithType(0, "org.springframework.ai.vectorstore.SearchRequest")); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/ChatModelInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/ChatModelInstrumentation.java new file mode 100644 index 0000000000..13aa1d381d --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/ChatModelInstrumentation.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; + +public class ChatModelInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.ai.chat.model.ChatModel"; + + private static final String INTERCEPT_CALL_METHOD = "call"; + + private static final String INTERCEPT_STREAM_METHOD = "stream"; + + private static final String INTERCEPTOR_CALL_CLASS = "org.apache.skywalking.apm.plugin.spring.ai.v1.ChatModelCallInterceptor"; + + private static final String INTERCEPTOR_STREAM_CLASS = "org.apache.skywalking.apm.plugin.spring.ai.v1.ChatModelStreamInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(INTERCEPT_CALL_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_CALL_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(INTERCEPT_STREAM_METHOD).and(takesArguments(1)).and(takesArgumentWithType(0, "org.springframework.ai.chat.prompt.Prompt")); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_STREAM_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/define/JettyInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/DefaultToolCallingManagerInstrumentation.java similarity index 72% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/define/JettyInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/DefaultToolCallingManagerInstrumentation.java index 59ac7307e6..595472834f 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/define/JettyInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/DefaultToolCallingManagerInstrumentation.java @@ -16,26 +16,32 @@ * */ -package org.apache.skywalking.apm.plugin.jetty.v9.server.define; +package org.apache.skywalking.apm.plugin.spring.ai.v1.define; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; -/** - * {@link JettyInstrumentation} enhance the handle method in org.eclipse.jetty.server.handler.HandlerList - * by HandleInterceptor - */ -public class JettyInstrumentation extends AbstractWitnessInstrumentation { +public class DefaultToolCallingManagerInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.ai.model.tool.DefaultToolCallingManager"; - private static final String ENHANCE_CLASS = "org.eclipse.jetty.server.HttpChannel"; - private static final String ENHANCE_METHOD = "handle"; - private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.jetty.v9.server.HandleInterceptor"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.spring.ai.v1.DefaultToolCallingManagerInterceptor"; + + private static final String INTERCEPT_METHOD = "executeToolCall"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } @Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { @@ -48,7 +54,7 @@ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { new InstanceMethodsInterceptPoint() { @Override public ElementMatcher getMethodsMatcher() { - return named(ENHANCE_METHOD); + return named(INTERCEPT_METHOD).and(takesArguments(3)).and(takesArgumentWithType(0, "org.springframework.ai.chat.prompt.Prompt")); } @Override @@ -63,9 +69,4 @@ public boolean isOverrideArgs() { } }; } - - @Override - protected ClassMatch enhanceClass() { - return NameMatch.byName(ENHANCE_CLASS); - } } diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/define/DispatcherInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/EmbeddingModelInstrumentation.java similarity index 64% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/define/DispatcherInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/EmbeddingModelInstrumentation.java index 343e7d28c8..12a327ae29 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/define/DispatcherInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/EmbeddingModelInstrumentation.java @@ -6,7 +6,7 @@ * (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 + * 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, @@ -14,41 +14,39 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * */ -package org.apache.skywalking.apm.plugin.jetty.v9.server.define; +package org.apache.skywalking.apm.plugin.spring.ai.v1.define; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; -import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; -public class DispatcherInstrumentation extends AbstractWitnessInstrumentation { +public class EmbeddingModelInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.ai.embedding.EmbeddingModel"; + + private static final String INTERCEPT_METHOD = "call"; - private static final String ENHANCE_CLASS = "org.eclipse.jetty.server.Dispatcher"; - public static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.jetty.v9.server.ForwardInterceptor"; + private static final String INTERCEPTOR_CLASS = + "org.apache.skywalking.apm.plugin.spring.ai.v1.EmbeddingModelInterceptor"; @Override - public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { - return new ConstructorInterceptPoint[]{ - new ConstructorInterceptPoint() { - @Override - public ElementMatcher getConstructorMatcher() { - return takesArgumentWithType(2, "java.lang.String"); - } + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(ENHANCE_CLASS); + } - @Override - public String getConstructorInterceptor() { - return INTERCEPT_CLASS; - } - } - }; + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; } @Override @@ -57,12 +55,14 @@ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { new InstanceMethodsInterceptPoint() { @Override public ElementMatcher getMethodsMatcher() { - return named("forward"); + return named(INTERCEPT_METHOD) + .and(takesArguments(1)) + .and(takesArgumentWithType(0, "org.springframework.ai.embedding.EmbeddingRequest")); } @Override public String getMethodsInterceptor() { - return INTERCEPT_CLASS; + return INTERCEPTOR_CLASS; } @Override @@ -72,9 +72,4 @@ public boolean isOverrideArgs() { } }; } - - @Override - protected ClassMatch enhanceClass() { - return byName(ENHANCE_CLASS); - } } diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/ToolCallbackInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/ToolCallbackInstrumentation.java new file mode 100644 index 0000000000..99a4029b00 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/ToolCallbackInstrumentation.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; + +public class ToolCallbackInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_INTERFACE = "org.springframework.ai.tool.ToolCallback"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.spring.ai.v1.ToolCallbackCallInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(ENHANCE_INTERFACE); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("call") + .and(takesArguments(2)) + .and(takesArgumentWithType(0, "java.lang.String")) + .and(returns(named("java.lang.String"))); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/AnthropicApiInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/AnthropicApiInstrumentation.java new file mode 100644 index 0000000000..e226864906 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/AnthropicApiInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class AnthropicApiInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.ai.anthropic.api.AnthropicApi"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.spring.ai.v1.provider.AnthropicApiConstructorInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(8).and(takesArgument(0, named("java.lang.String"))).and(takesArgument(1, named("java.lang.String"))); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/DeepSeekApiInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/DeepSeekApiInstrumentation.java new file mode 100644 index 0000000000..17382e75eb --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/DeepSeekApiInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class DeepSeekApiInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.ai.deepseek.api.DeepSeekApi"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.spring.ai.v1.provider.DeepSeekApiConstructorInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(8).and(takesArgument(0, named("java.lang.String"))).and(takesArgument(3, named("java.lang.String"))); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/HuggingfaceChatModelInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/HuggingfaceChatModelInstrumentation.java new file mode 100644 index 0000000000..93f7b57af4 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/HuggingfaceChatModelInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class HuggingfaceChatModelInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.ai.huggingface.HuggingfaceChatModel"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.spring.ai.v1.provider.HuggingfaceChatModelConstructorInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(2).and(takesArgument(1, named("java.lang.String"))); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/MiniMaxApiInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/MiniMaxApiInstrumentation.java new file mode 100644 index 0000000000..c8a5ca3ad1 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/MiniMaxApiInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class MiniMaxApiInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.ai.minimax.api.MiniMaxApi"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.spring.ai.v1.provider.MiniMaxApiConstructorInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(4).and(takesArgument(0, named("java.lang.String"))); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/MistralAiApiInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/MistralAiApiInstrumentation.java new file mode 100644 index 0000000000..ffcaee9198 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/MistralAiApiInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class MistralAiApiInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.ai.mistralai.api.MistralAiApi"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.spring.ai.v1.provider.MistralAiApiConstructorInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(5).and(takesArgument(0, named("java.lang.String"))); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/OllamaApiInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/OllamaApiInstrumentation.java new file mode 100644 index 0000000000..7e8fd5ebd8 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/OllamaApiInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class OllamaApiInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.ai.ollama.api.OllamaApi"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.spring.ai.v1.provider.OllamaApiConstructorInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(4).and(takesArgument(0, named("java.lang.String"))); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/OpenAiApiInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/OpenAiApiInstrumentation.java new file mode 100644 index 0000000000..624262322e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/OpenAiApiInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class OpenAiApiInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.ai.openai.api.OpenAiApi"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.spring.ai.v1.provider.OpenAiApiConstructorInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(8).and(takesArgument(0, named("java.lang.String"))).and(takesArgument(3, named("java.lang.String"))); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/ZhiPuAiApiInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/ZhiPuAiApiInstrumentation.java new file mode 100644 index 0000000000..583f045f6b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/provider/ZhiPuAiApiInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class ZhiPuAiApiInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.springframework.ai.zhipuai.api.ZhiPuAiApi"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.spring.ai.v1.provider.ZhiPuAiApiConstructorInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArguments(8).and(takesArgument(0, named("java.lang.String"))).and(takesArgument(3, named("java.lang.String"))); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/enums/AiProviderEnum.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/enums/AiProviderEnum.java new file mode 100644 index 0000000000..5137ab7d7e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/enums/AiProviderEnum.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.enums; + +public enum AiProviderEnum { + + UNKNOWN("unknown", null), + + ANTHROPIC_CLAUDE("anthropic", "org.springframework.ai.anthropic.AnthropicChatModel"), + + AMAZON_BEDROCK_CONVERSE("aws.bedrock", "org.springframework.ai.bedrock.converse.BedrockProxyChatModel"), + + AZURE_OPENAI("azure.openai", "org.springframework.ai.azure.openai.AzureOpenAiChatModel"), + + OCI_GENAI_COHERE("cohere", "org.springframework.ai.oci.cohere.OCICohereChatModel"), + + DEEPSEEK("deepseek", "org.springframework.ai.deepseek.DeepSeekChatModel"), + + GOOGLE_GENAI("gcp.gen_ai", "org.springframework.ai.google.genai.GoogleGenAiChatModel"), + + GOOGLE_VERTEXAI_GEMINI("gcp.vertex_ai", "org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel"), + + MISTRAL_AI("mistral_ai", "org.springframework.ai.mistralai.MistralAiChatModel"), + + OPENAI("openai", "org.springframework.ai.openai.OpenAiChatModel"), + + HUGGINGFACE("huggingface", "org.springframework.ai.huggingface.HuggingfaceChatModel"), + + MINIMAX("minimax", "org.springframework.ai.minimax.MiniMaxChatModel"), + + OLLAMA("ollama", "org.springframework.ai.ollama.OllamaChatModel"), + + OPENAI_SDK_OFFICIAL("openai", "org.springframework.ai.openaisdk.OpenAiSdkChatModel"), + + ZHIPU_AI("zhipu_ai", "org.springframework.ai.zhipuai.ZhiPuAiChatModel"); + + private final String value; + private final String modelClassName; + + AiProviderEnum(String value, String modelClassName) { + this.value = value; + this.modelClassName = modelClassName; + } + + public String getValue() { + return value; + } + + public String getModelClassName() { + return modelClassName; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/messages/InputMessages.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/messages/InputMessages.java new file mode 100644 index 0000000000..9a1b10ae8c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/messages/InputMessages.java @@ -0,0 +1,275 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.messages; + +import org.apache.skywalking.apm.agent.core.util.GsonUtil; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.MessageType; +import org.springframework.ai.chat.messages.ToolResponseMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.content.Media; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class InputMessages { + + private final List messages = new ArrayList<>(); + + public static InputMessages create() { + return new InputMessages(); + } + + public InputMessages append(InputMessage message) { + this.messages.add(message); + return this; + } + + public String toJson() { + return GsonUtil.toJson( + messages.stream() + .map(InputMessage::toMap) + .collect(Collectors.toList()) + ); + } + + public static class InputMessage { + private final String role; + private final List parts; + + private InputMessage(String role, List parts) { + this.role = role; + this.parts = parts; + } + + public static InputMessage create(String role, List parts) { + return new InputMessage(role, parts); + } + + public Map toMap() { + List> partMaps = parts.stream() + .map(MessagePart::toMap) + .collect(Collectors.toList()); + + Map map = new HashMap<>(); + map.put("role", role != null ? role : "unknown"); + map.put("parts", partMaps); + return map; + } + } + + public interface MessagePart { + Map toMap(); + } + + public static class TextPart implements MessagePart { + private final String content; + + public TextPart(String content) { + this.content = content; + } + + @Override + public Map toMap() { + Map map = new LinkedHashMap<>(); + map.put("type", "text"); + map.put("content", content != null ? content : ""); + return map; + } + } + + public static class ToolCallPart implements MessagePart { + private final String id; + private final String name; + private final String arguments; + + public ToolCallPart(String id, String name, String arguments) { + this.id = id; + this.name = name; + this.arguments = arguments; + } + + @Override + public Map toMap() { + Map map = new java.util.LinkedHashMap<>(); + map.put("type", "tool_call"); + if (id != null) { + map.put("id", id); + } + map.put("name", name != null ? name : ""); + map.put("arguments", arguments != null ? arguments : ""); + return map; + } + } + + public static class ToolCallResponsePart implements MessagePart { + private final String id; + private final String result; + + public ToolCallResponsePart(String id, String result) { + this.id = id; + this.result = result; + } + + @Override + public Map toMap() { + Map map = new LinkedHashMap<>(); + map.put("type", "tool_call_response"); + if (id != null) { + map.put("id", id); + } + map.put("result", result != null ? result : ""); + return map; + } + } + + public static InputMessages fromPrompt(Prompt prompt) { + InputMessages inputMessages = InputMessages.create(); + + if (prompt == null || prompt.getInstructions() == null) { + return inputMessages; + } + + for (Message message : prompt.getInstructions()) { + MessageType type = message.getMessageType(); + + switch (type) { + case SYSTEM: + inputMessages.append(InputMessage.create( + type.getValue(), + textParts(message.getText()) + )); + break; + + case USER: + inputMessages.append(InputMessage.create( + type.getValue(), + userMessageParts(message) + )); + break; + + case ASSISTANT: + inputMessages.append(InputMessage.create( + type.getValue(), + assistantMessageParts(message) + )); + break; + + case TOOL: + inputMessages.append(InputMessage.create( + type.getValue(), + toolMessageParts(message) + )); + break; + default: + inputMessages.append(InputMessage.create( + type.getValue(), + textParts(message.getText()) + )); + break; + } + } + + return inputMessages; + } + + private static List textParts(String text) { + List parts = new ArrayList<>(); + if (text != null && !text.isEmpty()) { + parts.add(new TextPart(text)); + } + return parts; + } + + private static List userMessageParts(Message message) { + List parts = new ArrayList<>(); + + String text = message.getText(); + if (text != null && !text.isEmpty()) { + parts.add(new TextPart(text)); + } + + if (message instanceof UserMessage) { + UserMessage userMessage = (UserMessage) message; + if (userMessage.getMedia() != null) { + for (Media media : userMessage.getMedia()) { + parts.add(new TextPart("[media: " + media.getMimeType() + "]")); + } + } + } + + return parts; + } + + private static List assistantMessageParts(Message message) { + List parts = new ArrayList<>(); + + String text = message.getText(); + if (text != null && !text.isEmpty()) { + parts.add(new TextPart(text)); + } + + if (message instanceof AssistantMessage) { + AssistantMessage assistantMessage = (AssistantMessage) message; + List toolCalls = assistantMessage.getToolCalls(); + if (toolCalls != null) { + for (AssistantMessage.ToolCall toolCall : toolCalls) { + parts.add(new ToolCallPart( + toolCall.id(), + toolCall.name(), + toolCall.arguments() + )); + } + } + } + + return parts; + } + + private static List toolMessageParts(Message message) { + List parts = new ArrayList<>(); + + if (message instanceof ToolResponseMessage) { + ToolResponseMessage toolResponse = (ToolResponseMessage) message; + List responses = toolResponse.getResponses(); + if (responses != null) { + for (ToolResponseMessage.ToolResponse response : responses) { + parts.add(new ToolCallResponsePart( + response.id(), + response.responseData() + )); + } + } + } else { + String text = message.getText(); + if (text != null && !text.isEmpty()) { + parts.add(new ToolCallResponsePart(null, text)); + } + } + + return parts; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/messages/OutputMessages.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/messages/OutputMessages.java new file mode 100644 index 0000000000..bc73165fc0 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/messages/OutputMessages.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.messages; + +import org.apache.skywalking.apm.agent.core.util.GsonUtil; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class OutputMessages { + private final List messages = new ArrayList<>(); + + public static OutputMessages create() { + return new OutputMessages(); + } + + public OutputMessages append(OutputMessage message) { + this.messages.add(message); + return this; + } + + public String toJson() { + return GsonUtil.toJson( + messages.stream() + .map(OutputMessage::toMap) + .collect(Collectors.toList()) + ); + } + + public static class OutputMessage { + private final String role; + private final List parts; + private final String finishReason; + + private OutputMessage(String role, List parts, String finishReason) { + this.role = role; + this.parts = parts; + this.finishReason = finishReason; + } + + public static OutputMessage create(String role, List parts, String finishReason) { + return new OutputMessage(role, parts, finishReason); + } + + public Map toMap() { + Map map = new LinkedHashMap<>(); + map.put("role", role != null ? role : "assistant"); + map.put("parts", parts.stream() + .map(InputMessages.MessagePart::toMap) + .collect(Collectors.toList())); + if (finishReason != null && !finishReason.isEmpty()) { + map.put("finish_reason", finishReason); + } + return map; + } + } + + public static OutputMessages fromChatResponse(ChatResponse chatResponse) { + OutputMessages outputMessages = OutputMessages.create(); + + if (chatResponse == null || chatResponse.getResults() == null) { + return outputMessages; + } + + for (Generation generation : chatResponse.getResults()) { + List messageParts = new ArrayList<>(); + + AssistantMessage assistantMessage = generation.getOutput(); + if (assistantMessage != null) { + // Text content + String text = assistantMessage.getText(); + if (text != null && !text.isEmpty()) { + messageParts.add(new InputMessages.TextPart(text)); + } + + // Tool calls + List toolCalls = assistantMessage.getToolCalls(); + if (toolCalls != null) { + for (AssistantMessage.ToolCall toolCall : toolCalls) { + messageParts.add(new InputMessages.ToolCallPart( + toolCall.id(), + toolCall.name(), + toolCall.arguments() + )); + } + } + } + + String finishReason = ""; + if (generation.getMetadata() != null && generation.getMetadata().getFinishReason() != null) { + finishReason = generation.getMetadata().getFinishReason().toLowerCase(); + } + + outputMessages.append(OutputMessage.create("assistant", messageParts, finishReason)); + } + + return outputMessages; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/AnthropicApiConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/AnthropicApiConstructorInterceptor.java new file mode 100644 index 0000000000..4f9e73ca7e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/AnthropicApiConstructorInterceptor.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.provider; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.enums.AiProviderEnum; + +public class AnthropicApiConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable { + ChatModelMetadataResolver.ApiMetadata metadata = ChatModelMetadataResolver.getMetadata(AiProviderEnum.ANTHROPIC_CLAUDE.getModelClassName()); + if (metadata == null) { + return; + } + + metadata.setBaseUrl((String) allArguments[0]); + metadata.setCompletionsPath((String) allArguments[1]); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/DeepSeekApiConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/DeepSeekApiConstructorInterceptor.java new file mode 100644 index 0000000000..e1f26e5e8b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/DeepSeekApiConstructorInterceptor.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.provider; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.enums.AiProviderEnum; + +public class DeepSeekApiConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable { + ChatModelMetadataResolver.ApiMetadata metadata = ChatModelMetadataResolver.getMetadata(AiProviderEnum.DEEPSEEK.getModelClassName()); + if (metadata == null) { + return; + } + + metadata.setBaseUrl((String) allArguments[0]); + metadata.setCompletionsPath((String) allArguments[3]); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/HuggingfaceChatModelConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/HuggingfaceChatModelConstructorInterceptor.java new file mode 100644 index 0000000000..234b876434 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/HuggingfaceChatModelConstructorInterceptor.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.provider; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.enums.AiProviderEnum; + +public class HuggingfaceChatModelConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable { + ChatModelMetadataResolver.ApiMetadata metadata = ChatModelMetadataResolver.getMetadata(AiProviderEnum.HUGGINGFACE.getModelClassName()); + if (metadata == null) { + return; + } + + metadata.setBaseUrl((String) allArguments[1]); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/MiniMaxApiConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/MiniMaxApiConstructorInterceptor.java new file mode 100644 index 0000000000..939e440ae1 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/MiniMaxApiConstructorInterceptor.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.provider; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.enums.AiProviderEnum; + +public class MiniMaxApiConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable { + ChatModelMetadataResolver.ApiMetadata metadata = ChatModelMetadataResolver.getMetadata(AiProviderEnum.MINIMAX.getModelClassName()); + if (metadata == null) { + return; + } + + metadata.setBaseUrl((String) allArguments[0]); + metadata.setCompletionsPath("/v1/text/chatcompletion_v2"); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/MistralAiApiConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/MistralAiApiConstructorInterceptor.java new file mode 100644 index 0000000000..07eac44d5b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/MistralAiApiConstructorInterceptor.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.provider; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.enums.AiProviderEnum; + +import static org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants.DEFAULT_COMPLETIONS_PATH; + +public class MistralAiApiConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable { + ChatModelMetadataResolver.ApiMetadata metadata = ChatModelMetadataResolver.getMetadata(AiProviderEnum.MISTRAL_AI.getModelClassName()); + if (metadata == null) { + return; + } + + metadata.setBaseUrl((String) allArguments[0]); + metadata.setCompletionsPath(DEFAULT_COMPLETIONS_PATH); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/OllamaApiConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/OllamaApiConstructorInterceptor.java new file mode 100644 index 0000000000..abdc2d9f5a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/OllamaApiConstructorInterceptor.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.provider; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.enums.AiProviderEnum; + +public class OllamaApiConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable { + ChatModelMetadataResolver.ApiMetadata metadata = ChatModelMetadataResolver.getMetadata(AiProviderEnum.OLLAMA.getModelClassName()); + if (metadata == null) { + return; + } + + metadata.setBaseUrl((String) allArguments[0]); + metadata.setCompletionsPath("/api/chat"); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/OpenAiApiConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/OpenAiApiConstructorInterceptor.java new file mode 100644 index 0000000000..da0f3bbf2f --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/OpenAiApiConstructorInterceptor.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.provider; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.enums.AiProviderEnum; + +public class OpenAiApiConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable { + ChatModelMetadataResolver.ApiMetadata metadata = ChatModelMetadataResolver.getMetadata(AiProviderEnum.OPENAI.getModelClassName()); + if (metadata == null) { + return; + } + + metadata.setBaseUrl((String) allArguments[0]); + metadata.setCompletionsPath((String) allArguments[3]); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/ZhiPuAiApiConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/ZhiPuAiApiConstructorInterceptor.java new file mode 100644 index 0000000000..734c807a6a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/provider/ZhiPuAiApiConstructorInterceptor.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.ai.v1.provider; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver; +import org.apache.skywalking.apm.plugin.spring.ai.v1.enums.AiProviderEnum; + +public class ZhiPuAiApiConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable { + ChatModelMetadataResolver.ApiMetadata metadata = ChatModelMetadataResolver.getMetadata(AiProviderEnum.ZHIPU_AI.getModelClassName()); + if (metadata == null) { + return; + } + + metadata.setBaseUrl((String) allArguments[0]); + metadata.setCompletionsPath((String) allArguments[3]); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..62e1f6af3e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.ChatModelInstrumentation +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.EmbeddingModelInstrumentation +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.ToolCallbackInstrumentation +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.DefaultToolCallingManagerInstrumentation +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.AbstractObservationVectorStoreInstrumentation +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.AnthropicApiInstrumentation +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.DeepSeekApiInstrumentation +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.HuggingfaceChatModelInstrumentation +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.MiniMaxApiInstrumentation +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.MistralAiApiInstrumentation +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.OllamaApiInstrumentation +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.OpenAiApiInstrumentation +spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.ZhiPuAiApiInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/netflix-plugins/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/netflix-plugins/pom.xml index 89283b1370..34769eadbb 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/netflix-plugins/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/netflix-plugins/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking spring-cloud - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT netflix-plugins @@ -37,7 +37,6 @@ UTF-8 - /../../.. diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/netflix-plugins/spring-cloud-feign-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/netflix-plugins/spring-cloud-feign-1.x-plugin/pom.xml index d11a119602..6fb11aaa57 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/netflix-plugins/spring-cloud-feign-1.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/netflix-plugins/spring-cloud-feign-1.x-plugin/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking netflix-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-spring-cloud-feign-1.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/pom.xml index 08afa3d3f0..d1f4409bce 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking spring-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT spring-cloud @@ -38,7 +38,6 @@ UTF-8 - /../.. diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/spring-cloud-feign-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/spring-cloud-feign-2.x-plugin/pom.xml index a9d0430b86..579c404b7a 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/spring-cloud-feign-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-cloud/spring-cloud-feign-2.x-plugin/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking spring-cloud - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-spring-cloud-feign-2.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-commons/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-commons/pom.xml index 5e48bb5251..ff67ee32df 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-commons/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-commons/pom.xml @@ -20,7 +20,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-kafka-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-kafka-1.x-plugin/pom.xml index 3f06456a34..d8bf75bf9e 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-kafka-1.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-kafka-1.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking spring-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-spring-kafka-1.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-kafka-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-kafka-2.x-plugin/pom.xml index 9741509279..2cff5cf300 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-kafka-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-kafka-2.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking spring-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-spring-kafka-2.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/pom.xml new file mode 100644 index 0000000000..cb0c687268 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/pom.xml @@ -0,0 +1,46 @@ + + + + 4.0.0 + + + org.apache.skywalking + spring-plugins + 9.7.0-SNAPSHOT + + + apm-spring-rabbitmq-plugin + apm-spring-rabbitmq-plugin + jar + + + 3.0.0 + + + + + org.springframework.amqp + spring-rabbit + ${spring-rabbit.version} + provided + + + \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/SpringRabbitMQConsumerInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/SpringRabbitMQConsumerInterceptor.java new file mode 100644 index 0000000000..29bf40599f --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/SpringRabbitMQConsumerInterceptor.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.rabbitmq; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +import org.apache.skywalking.apm.agent.core.context.CarrierItem; +import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; + +public class SpringRabbitMQConsumerInterceptor implements InstanceMethodsAroundInterceptorV2 { + public static final String OPERATE_NAME_PREFIX = "RabbitMQ/"; + public static final String CONSUMER_OPERATE_NAME_SUFFIX = "/Consumer"; + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInvocationContext context) throws Throwable { + Channel channel = (Channel) allArguments[0]; + + if (allArguments[1] instanceof Message) { + // Single message consume + Message message = (Message) allArguments[1]; + MessageProperties messageProperties = message.getMessageProperties(); + Map headers = messageProperties.getHeaders(); + + ContextCarrier contextCarrier = buildContextCarrier(headers); + String operationName = buildOperationName(messageProperties); + AbstractSpan activeSpan = ContextManager.createEntrySpan(operationName, contextCarrier); + + setSpanAttributes(activeSpan, channel, messageProperties); + } else if (allArguments[1] instanceof List) { + // Batch message consume + List messages = (List) allArguments[1]; + if (messages.isEmpty()) { + return; + } + + // Use the first message to create EntrySpan + Message firstMessage = (Message) messages.get(0); + MessageProperties firstMessageProperties = firstMessage.getMessageProperties(); + Map firstMessageHeaders = firstMessageProperties.getHeaders(); + + ContextCarrier contextCarrier = buildContextCarrier(firstMessageHeaders); + String operationName = buildOperationName(firstMessageProperties); + AbstractSpan activeSpan = ContextManager.createEntrySpan(operationName, contextCarrier); + + setSpanAttributes(activeSpan, channel, firstMessageProperties); + + // Extract trace context from remaining messages (skip first, already used for EntrySpan) + // to correlate all producer traces with this consumer span + for (int i = 1; i < messages.size(); i++) { + Object msg = messages.get(i); + if (msg instanceof Message) { + Message message = (Message) msg; + MessageProperties messageProperties = message.getMessageProperties(); + Map headers = messageProperties.getHeaders(); + + ContextCarrier carrier = buildContextCarrier(headers); + if (carrier.isValid()) { + ContextManager.extract(carrier); + } + } + } + } + } + + /** + * Build ContextCarrier from message headers + */ + private ContextCarrier buildContextCarrier(Map headers) { + ContextCarrier contextCarrier = new ContextCarrier(); + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + Object value = headers.get(next.getHeadKey()); + if (value != null) { + next.setHeadValue(value.toString()); + } + } + return contextCarrier; + } + + private String buildOperationName(MessageProperties messageProperties) { + return OPERATE_NAME_PREFIX + "Topic/" + messageProperties.getReceivedExchange() + + "Queue/" + messageProperties.getReceivedRoutingKey() + + CONSUMER_OPERATE_NAME_SUFFIX; + } + + private void setSpanAttributes(AbstractSpan span, Channel channel, MessageProperties messageProperties) { + Connection connection = channel.getConnection(); + String serverUrl = connection.getAddress().getHostAddress() + ":" + connection.getPort(); + Tags.MQ_BROKER.set(span, serverUrl); + Tags.MQ_TOPIC.set(span, messageProperties.getReceivedExchange()); + Tags.MQ_QUEUE.set(span, messageProperties.getReceivedRoutingKey()); + span.setComponent(ComponentsDefine.RABBITMQ_CONSUMER); + span.setPeer(serverUrl); + SpanLayer.asMQ(span); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret, MethodInvocationContext context) throws Throwable { + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t, MethodInvocationContext context) { + if (ContextManager.isActive()) { + ContextManager.activeSpan().log(t); + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/define/SpringRabbitMQConsumerInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/define/SpringRabbitMQConsumerInstrumentation.java new file mode 100644 index 0000000000..b4a541adc8 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/define/SpringRabbitMQConsumerInstrumentation.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.rabbitmq.define; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.ClassInstanceMethodsEnhancePluginDefineV2; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.DeclaredInstanceMethodsInterceptV2Point; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class SpringRabbitMQConsumerInstrumentation extends ClassInstanceMethodsEnhancePluginDefineV2 { + public static final String ENHANCE_CLASS = "org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer"; + public static final String ENHANCE_METHOD = "executeListener"; + public static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.spring.rabbitmq.SpringRabbitMQConsumerInterceptor"; + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return null; + } + + @Override + public InstanceMethodsInterceptV2Point[] getInstanceMethodsInterceptV2Points() { + return new InstanceMethodsInterceptV2Point[] { + new DeclaredInstanceMethodsInterceptV2Point() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(ENHANCE_METHOD); + } + + @Override + public String getMethodsInterceptorV2() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..94d87de414 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +spring-rabbitmq=org.apache.skywalking.apm.plugin.spring.rabbitmq.define.SpringRabbitMQConsumerInstrumentation \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/RabbitMQSpringConsumerInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/RabbitMQSpringConsumerInterceptorTest.java new file mode 100644 index 0000000000..e29864204e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/RabbitMQSpringConsumerInterceptorTest.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.rabbitmq; + +import static org.hamcrest.CoreMatchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.skywalking.apm.agent.core.context.SW8CarrierItem; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; + +@RunWith(TracingSegmentRunner.class) +public class RabbitMQSpringConsumerInterceptorTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + private SpringRabbitMQConsumerInterceptor rabbitMQConsumerInterceptor; + + @Mock + private EnhancedInstance enhancedInstance; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + + @Before + public void setUp() throws Exception { + rabbitMQConsumerInterceptor = new SpringRabbitMQConsumerInterceptor(); + } + + @Test + public void testRabbitMQConsumerInterceptorWithNilHeaders() throws Throwable { + Object[] args = prepareMockData(false); + rabbitMQConsumerInterceptor.beforeMethod(enhancedInstance, null, args, new Class[0], null); + rabbitMQConsumerInterceptor.afterMethod(enhancedInstance, null, args, new Class[0], null, null); + List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertThat(traceSegments.size(), is(1)); + } + + @Test + public void testRabbitMQConsumerInterceptor() throws Throwable { + Object[] args = prepareMockData(true); + rabbitMQConsumerInterceptor.beforeMethod(enhancedInstance, null, args, new Class[0], null); + rabbitMQConsumerInterceptor.afterMethod(enhancedInstance, null, args, new Class[0], null, null); + List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertThat(traceSegments.size(), is(1)); + } + + @Test + public void testBatchMessageConsumption() throws Throwable { + Object[] args = prepareMockBatchData(true); + rabbitMQConsumerInterceptor.beforeMethod(enhancedInstance, null, args, new Class[0], null); + rabbitMQConsumerInterceptor.afterMethod(enhancedInstance, null, args, new Class[0], null, null); + List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertThat(traceSegments.size(), is(1)); + } + + @Test + public void testEmptyBatchMessageConsumption() throws Throwable { + Channel channel = mock(Channel.class); + Connection connection = mock(Connection.class); + InetAddress address = mock(InetAddress.class); + + when(channel.getConnection()).thenReturn(connection); + when(connection.getAddress()).thenReturn(address); + when(address.getHostAddress()).thenReturn("127.0.0.1"); + when(connection.getPort()).thenReturn(5672); + + Object[] args = new Object[] {channel, new java.util.ArrayList()}; + rabbitMQConsumerInterceptor.beforeMethod(enhancedInstance, null, args, new Class[0], null); + rabbitMQConsumerInterceptor.afterMethod(enhancedInstance, null, args, new Class[0], null, null); + List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertThat(traceSegments.size(), is(0)); + } + + private Object[] prepareMockData(boolean withHeaders) throws Exception { + Channel channel = mock(Channel.class); + Connection connection = mock(Connection.class); + InetAddress address = mock(InetAddress.class); + Message message = mock(Message.class); + MessageProperties messageProperties = mock(MessageProperties.class); + + when(channel.getConnection()).thenReturn(connection); + when(connection.getAddress()).thenReturn(address); + when(address.getHostAddress()).thenReturn("127.0.0.1"); + when(connection.getPort()).thenReturn(5672); + when(message.getMessageProperties()).thenReturn(messageProperties); + when(messageProperties.getReceivedExchange()).thenReturn("test-exchange"); + when(messageProperties.getReceivedRoutingKey()).thenReturn("test-routing-key"); + + if (withHeaders) { + Map headers = new HashMap<>(); + headers.put(SW8CarrierItem.HEADER_NAME, + "1-My40LjU=-MS4yLjM=-3-c2VydmljZQ==-aW5zdGFuY2U=-L2FwcA==-MTI3LjAuMC4xOjgwODA="); + when(messageProperties.getHeader(SW8CarrierItem.HEADER_NAME)) + .thenReturn(headers.get(SW8CarrierItem.HEADER_NAME)); + } + + return new Object[] {channel, message}; + } + + private Object[] prepareMockBatchData(boolean withHeaders) throws Exception { + Channel channel = mock(Channel.class); + Connection connection = mock(Connection.class); + InetAddress address = mock(InetAddress.class); + + when(channel.getConnection()).thenReturn(connection); + when(connection.getAddress()).thenReturn(address); + when(address.getHostAddress()).thenReturn("127.0.0.1"); + when(connection.getPort()).thenReturn(5672); + + Message message1 = mock(Message.class); + Message message2 = mock(Message.class); + Message message3 = mock(Message.class); + MessageProperties props1 = mock(MessageProperties.class); + MessageProperties props2 = mock(MessageProperties.class); + MessageProperties props3 = mock(MessageProperties.class); + + when(message1.getMessageProperties()).thenReturn(props1); + when(message2.getMessageProperties()).thenReturn(props2); + when(message3.getMessageProperties()).thenReturn(props3); + + when(props1.getReceivedExchange()).thenReturn("test-exchange"); + when(props1.getReceivedRoutingKey()).thenReturn("test-routing-key"); + when(props2.getReceivedExchange()).thenReturn("test-exchange"); + when(props2.getReceivedRoutingKey()).thenReturn("test-routing-key"); + when(props3.getReceivedExchange()).thenReturn("test-exchange"); + when(props3.getReceivedRoutingKey()).thenReturn("test-routing-key"); + + if (withHeaders) { + Map headers1 = new HashMap<>(); + headers1.put(SW8CarrierItem.HEADER_NAME, + "1-My40LjU=-MS4yLjM=-3-c2VydmljZQ==-aW5zdGFuY2U=-L2FwcA==-MTI3LjAuMC4xOjgwODA="); + when(props1.getHeader(SW8CarrierItem.HEADER_NAME)) + .thenReturn(headers1.get(SW8CarrierItem.HEADER_NAME)); + + Map headers2 = new HashMap<>(); + headers2.put(SW8CarrierItem.HEADER_NAME, + "1-NTY3Ljg=-OS4xMC4xMQ==-12-ZXJ2aWNlMg==-aW5zdGFuY2UyLU9hcHA=-MTI3LjAuMC4yOjgwODA="); + when(props2.getHeader(SW8CarrierItem.HEADER_NAME)) + .thenReturn(headers2.get(SW8CarrierItem.HEADER_NAME)); + + Map headers3 = new HashMap<>(); + headers3.put(SW8CarrierItem.HEADER_NAME, + "1-MTExLjIyMi4zMzM=-NDQ0LjU1NS42NjY=-MzQ1-ZXJ2aWNlMw==-aW5zdGFuY2UzLU9hcHA=-MTI3LjAuMC4zOjgwODA="); + when(props3.getHeader(SW8CarrierItem.HEADER_NAME)) + .thenReturn(headers3.get(SW8CarrierItem.HEADER_NAME)); + } + + List messages = new ArrayList<>(Arrays.asList(message1, message2, message3)); + return new Object[] {channel, messages}; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-webflux-5.x-webclient-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-webflux-5.x-webclient-plugin/pom.xml index 868bd94200..1a3cfbfeb7 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-webflux-5.x-webclient-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-webflux-5.x-webclient-plugin/pom.xml @@ -21,7 +21,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-webflux-6.x-webclient-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-webflux-6.x-webclient-plugin/pom.xml index 528735645b..6f67c4b024 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-webflux-6.x-webclient-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-webflux-6.x-webclient-plugin/pom.xml @@ -21,7 +21,7 @@ spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/pom.xml index a44cd64c18..1b123e2207 100644 --- a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-spymemcached-2.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/pom.xml index b116ccd389..eb3e342fd3 100644 --- a/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/pom.xml @@ -20,7 +20,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/struts2-7.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/struts2-7.x-plugin/pom.xml new file mode 100644 index 0000000000..f314ff5fba --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/struts2-7.x-plugin/pom.xml @@ -0,0 +1,56 @@ + + + + + org.apache.skywalking + apm-sdk-plugin + 9.7.0-SNAPSHOT + + 4.0.0 + + apm-struts2-7.x-plugin + jar + + struts2-7.x-plugin + + + + 7.2.1 + 6.0.0 + + + + + org.apache.struts + struts2-core + ${struts.version} + provided + + + jakarta.servlet + jakarta.servlet-api + ${jakarta-servlet-api.version} + provided + + + diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/HandleInterceptor.java b/apm-sniffer/apm-sdk-plugin/struts2-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/struts2/v7/Struts2Interceptor.java similarity index 60% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/HandleInterceptor.java rename to apm-sniffer/apm-sdk-plugin/struts2-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/struts2/v7/Struts2Interceptor.java index 25006dd14c..80a42b2eaa 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/HandleInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/struts2-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/struts2/v7/Struts2Interceptor.java @@ -16,11 +16,11 @@ * */ -package org.apache.skywalking.apm.plugin.jetty.v9.server; +package org.apache.skywalking.apm.plugin.struts2.v7; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.lang.reflect.Method; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.apache.skywalking.apm.agent.core.context.CarrierItem; import org.apache.skywalking.apm.agent.core.context.ContextCarrier; import org.apache.skywalking.apm.agent.core.context.ContextManager; @@ -30,55 +30,47 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; -import org.apache.skywalking.apm.agent.core.util.MethodUtil; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; -import org.eclipse.jetty.server.HttpChannel; +import org.apache.struts2.ServletActionContext; -public class HandleInterceptor implements InstanceMethodsAroundInterceptor { - - private static boolean IS_SERVLET_GET_STATUS_METHOD_EXIST; - private static final String SERVLET_RESPONSE_CLASS = "javax.servlet.http.HttpServletResponse"; - private static final String GET_STATUS_METHOD = "getStatus"; - - static { - IS_SERVLET_GET_STATUS_METHOD_EXIST = MethodUtil.isMethodExist(HandleInterceptor.class.getClassLoader(), SERVLET_RESPONSE_CLASS, GET_STATUS_METHOD); - } +/** + * Enhances {@code org.apache.struts2.DefaultActionInvocation#invokeAction} in Struts 7.x. Struts 7 is + * Jakarta-only (Jakarta Servlet 6.0), so the servlet request/response are referenced through the + * jakarta.servlet namespace directly. + */ +public class Struts2Interceptor implements InstanceMethodsAroundInterceptor { @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { - HttpChannel httpChannel = (HttpChannel) objInst; - HttpServletRequest servletRequest = httpChannel.getRequest(); - + HttpServletRequest request = ServletActionContext.getRequest(); ContextCarrier contextCarrier = new ContextCarrier(); CarrierItem next = contextCarrier.items(); while (next.hasNext()) { next = next.next(); - next.setHeadValue(servletRequest.getHeader(next.getHeadKey())); + next.setHeadValue(request.getHeader(next.getHeadKey())); } - AbstractSpan span = ContextManager.createEntrySpan(servletRequest.getRequestURI(), contextCarrier); - Tags.URL.set(span, servletRequest.getRequestURL().toString()); - Tags.HTTP.METHOD.set(span, servletRequest.getMethod()); - span.setComponent(ComponentsDefine.JETTY_SERVER); + AbstractSpan span = ContextManager.createEntrySpan(request.getRequestURI(), contextCarrier); + Tags.URL.set(span, request.getRequestURL().toString()); + Tags.HTTP.METHOD.set(span, request.getMethod()); + span.setComponent(ComponentsDefine.STRUTS2); SpanLayer.asHttp(span); } @Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { - HttpChannel httpChannel = (HttpChannel) objInst; - HttpServletResponse servletResponse = httpChannel.getResponse(); + HttpServletResponse response = ServletActionContext.getResponse(); + AbstractSpan span = ContextManager.activeSpan(); - if (IS_SERVLET_GET_STATUS_METHOD_EXIST) { - Tags.HTTP_RESPONSE_STATUS_CODE.set(span, servletResponse.getStatus()); - if (servletResponse.getStatus() >= 400) { - span.errorOccurred(); - } + int statusCode = response.getStatus(); + Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode); + if (statusCode >= 400) { + span.errorOccurred(); } ContextManager.stopSpan(); - ContextManager.getRuntimeContext().remove(Constants.FORWARD_REQUEST_FLAG); return ret; } diff --git a/apm-sniffer/apm-sdk-plugin/struts2-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/struts2/v7/define/Struts2Instrumentation.java b/apm-sniffer/apm-sdk-plugin/struts2-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/struts2/v7/define/Struts2Instrumentation.java new file mode 100644 index 0000000000..68302e8c5a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/struts2-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/struts2/v7/define/Struts2Instrumentation.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.struts2.v7.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * {@link Struts2Instrumentation} enhances the invokeAction method of Struts 7's + * org.apache.struts2.DefaultActionInvocation (relocated from + * com.opensymphony.xwork2.DefaultActionInvocation in Struts 2.x). The class name is + * unique to Struts 7, so it self-witnesses — no witness class is needed, and there is no overlap + * with the struts2-2.x plugin, which enhances the old XWork class. + */ +public class Struts2Instrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.apache.struts2.DefaultActionInvocation"; + private static final String ENHANCE_METHOD = "invokeAction"; + private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.struts2.v7.Struts2Interceptor"; + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(ENHANCE_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/struts2-7.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/struts2-7.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..a9a45c666b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/struts2-7.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +struts2-7.x=org.apache.skywalking.apm.plugin.struts2.v7.define.Struts2Instrumentation diff --git a/apm-sniffer/apm-sdk-plugin/thrift-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/thrift-plugin/pom.xml index 9d8692028e..dc51e01a99 100644 --- a/apm-sniffer/apm-sdk-plugin/thrift-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/thrift-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/TomcatInvokeInterceptor.java b/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/TomcatInvokeInterceptor.java deleted file mode 100644 index 52bedf994d..0000000000 --- a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/TomcatInvokeInterceptor.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.tomcat10x; - -import jakarta.servlet.http.HttpServletResponse; -import org.apache.catalina.connector.Request; -import org.apache.skywalking.apm.agent.core.context.CarrierItem; -import org.apache.skywalking.apm.agent.core.context.ContextCarrier; -import org.apache.skywalking.apm.agent.core.context.ContextManager; -import org.apache.skywalking.apm.agent.core.context.tag.Tags; -import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; -import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; - -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; -import org.apache.skywalking.apm.agent.core.util.CollectionUtil; -import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; -import org.apache.skywalking.apm.util.StringUtil; -import org.apache.tomcat.util.http.Parameters; - -import java.lang.reflect.Method; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; - -public class TomcatInvokeInterceptor implements InstanceMethodsAroundInterceptor { - - private static final String SERVLET_RESPONSE_CLASS = "jakarta.servlet.http.HttpServletResponse"; - private static final String GET_STATUS_METHOD = "getStatus"; - - @Override - public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - MethodInterceptResult result) throws Throwable { - Request request = (Request) allArguments[0]; - ContextCarrier contextCarrier = new ContextCarrier(); - - CarrierItem next = contextCarrier.items(); - while (next.hasNext()) { - next = next.next(); - next.setHeadValue(request.getHeader(next.getHeadKey())); - } - String operationName = String.join(":", request.getMethod(), request.getRequestURI()); - AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier); - Tags.URL.set(span, request.getRequestURL().toString()); - Tags.HTTP.METHOD.set(span, request.getMethod()); - span.setComponent(ComponentsDefine.TOMCAT); - SpanLayer.asHttp(span); - - if (TomcatPluginConfig.Plugin.Tomcat.COLLECT_HTTP_PARAMS) { - collectHttpParam(request, span); - } - } - - @Override - public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - Object ret) throws Throwable { - Request request = (Request) allArguments[0]; - HttpServletResponse response = (HttpServletResponse) allArguments[1]; - - AbstractSpan span = ContextManager.activeSpan(); - Tags.HTTP_RESPONSE_STATUS_CODE.set(span, response.getStatus()); - if (response.getStatus() >= 400) { - span.errorOccurred(); - } - // Active HTTP parameter collection automatically in the profiling context. - if (!TomcatPluginConfig.Plugin.Tomcat.COLLECT_HTTP_PARAMS && span.isProfiling()) { - collectHttpParam(request, span); - } - ContextManager.getRuntimeContext().remove(Constants.FORWARD_REQUEST_FLAG); - ContextManager.stopSpan(); - return ret; - } - - @Override - public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, - Class[] argumentsTypes, Throwable t) { - AbstractSpan span = ContextManager.activeSpan(); - span.log(t); - } - - private void collectHttpParam(Request request, AbstractSpan span) { - final Map parameterMap = new HashMap<>(); - final org.apache.coyote.Request coyoteRequest = request.getCoyoteRequest(); - final Parameters parameters = coyoteRequest.getParameters(); - for (final Enumeration names = parameters.getParameterNames(); names.hasMoreElements(); ) { - final String name = names.nextElement(); - parameterMap.put(name, parameters.getParameterValues(name)); - } - - if (!parameterMap.isEmpty()) { - String tagValue = CollectionUtil.toString(parameterMap); - tagValue = TomcatPluginConfig.Plugin.Http.HTTP_PARAMS_LENGTH_THRESHOLD > 0 ? - StringUtil.cut(tagValue, TomcatPluginConfig.Plugin.Http.HTTP_PARAMS_LENGTH_THRESHOLD) : - tagValue; - Tags.HTTP.PARAMS.set(span, tagValue); - } - } -} diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/pom.xml similarity index 55% rename from apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/pom.xml rename to apm-sniffer/apm-sdk-plugin/tomcat-plugin/pom.xml index c881962896..d0b23363e1 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/pom.xml @@ -1,3 +1,4 @@ + - 8.0.36 - 4.12 - 4.5.2 + 10.0.22 - - - junit - junit - ${junit.version} - test - org.apache.tomcat.embed tomcat-embed-core @@ -50,22 +48,11 @@ provided - org.apache.tomcat.embed - tomcat-embed-logging-juli - ${tomcat.version} - test - - - org.apache.tomcat.embed - tomcat-embed-jasper - ${tomcat.version} - test - - - org.apache.httpcomponents - httpclient - ${apache-httpclient.version} - test + org.apache.skywalking + apm-servlet-commons + ${project.version} + provided + diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/Constants.java b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/Constants.java similarity index 94% rename from apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/Constants.java rename to apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/Constants.java index 64501083e1..9f783d15c0 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/Constants.java +++ b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/Constants.java @@ -17,7 +17,7 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat78x; +package org.apache.skywalking.apm.plugin.tomcat; public class Constants { public static final String FORWARD_REQUEST_FLAG = "SW_FORWARD_REQUEST_FLAG"; diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/ForwardInterceptor.java b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/ForwardInterceptor.java similarity index 98% rename from apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/ForwardInterceptor.java rename to apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/ForwardInterceptor.java index 8c64a1d0a9..6f48059390 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/ForwardInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/ForwardInterceptor.java @@ -17,7 +17,7 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat10x; +package org.apache.skywalking.apm.plugin.tomcat; import org.apache.skywalking.apm.agent.core.context.ContextManager; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/TomcatExceptionInterceptor.java b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatExceptionInterceptor.java similarity index 97% rename from apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/TomcatExceptionInterceptor.java rename to apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatExceptionInterceptor.java index ac9ba9f61e..ab854aa554 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/TomcatExceptionInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatExceptionInterceptor.java @@ -16,7 +16,7 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat10x; +package org.apache.skywalking.apm.plugin.tomcat; import org.apache.skywalking.apm.agent.core.context.ContextManager; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/TomcatInvokeInterceptor.java b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatInvokeInterceptor.java similarity index 71% rename from apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/TomcatInvokeInterceptor.java rename to apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatInvokeInterceptor.java index 29004e6e46..8208a2a4c6 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/TomcatInvokeInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatInvokeInterceptor.java @@ -16,14 +16,8 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat78x; +package org.apache.skywalking.apm.plugin.tomcat; -import java.lang.reflect.Method; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.apache.catalina.connector.Request; import org.apache.skywalking.apm.agent.core.context.CarrierItem; import org.apache.skywalking.apm.agent.core.context.ContextCarrier; @@ -31,38 +25,27 @@ import org.apache.skywalking.apm.agent.core.context.tag.Tags; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; -import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; + import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.apache.skywalking.apm.agent.core.util.CollectionUtil; -import org.apache.skywalking.apm.agent.core.util.MethodUtil; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.servlet.HttpResponseWrapper; +import org.apache.skywalking.apm.plugin.servlet.HttpResponseWrappers; import org.apache.skywalking.apm.util.StringUtil; import org.apache.tomcat.util.http.Parameters; -/** - * {@link TomcatInvokeInterceptor} fetch the serialized context data by using {@link - * HttpServletRequest#getHeader(String)}. The {@link TraceSegment#ref} of current trace segment will reference to the - * trace segment id of the previous level if the serialized context is not null. - */ -public class TomcatInvokeInterceptor implements InstanceMethodsAroundInterceptor { - - private static boolean IS_SERVLET_GET_STATUS_METHOD_EXIST; - private static final String SERVLET_RESPONSE_CLASS = "javax.servlet.http.HttpServletResponse"; - private static final String GET_STATUS_METHOD = "getStatus"; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; - static { - IS_SERVLET_GET_STATUS_METHOD_EXIST = MethodUtil.isMethodExist( - TomcatInvokeInterceptor.class.getClassLoader(), SERVLET_RESPONSE_CLASS, GET_STATUS_METHOD); - } +public class TomcatInvokeInterceptor implements InstanceMethodsAroundInterceptor { - /** - * * The {@link TraceSegment#ref} of current trace segment will reference to the trace segment id of the previous - * level if the serialized context is not null. - * - * @param result change this result, if you want to truncate the method. - */ @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { @@ -84,18 +67,25 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr if (TomcatPluginConfig.Plugin.Tomcat.COLLECT_HTTP_PARAMS) { collectHttpParam(request, span); } + + if (!CollectionUtil.isEmpty(TomcatPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS)) { + collectHttpHeaders(request, span); + } } @Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { Request request = (Request) allArguments[0]; - HttpServletResponse response = (HttpServletResponse) allArguments[1]; AbstractSpan span = ContextManager.activeSpan(); - if (IS_SERVLET_GET_STATUS_METHOD_EXIST) { - Tags.HTTP_RESPONSE_STATUS_CODE.set(span, response.getStatus()); - if (response.getStatus() >= 400) { + // The response status is read through the namespace-agnostic wrapper; it returns null when + // getStatus() is unavailable (pre-Servlet-3.0), preserving the historical guard behavior. + HttpResponseWrapper response = HttpResponseWrappers.wrap(allArguments[1]); + if (response != null) { + int statusCode = response.getStatus(); + Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode); + if (statusCode >= 400) { span.errorOccurred(); } } @@ -132,4 +122,25 @@ private void collectHttpParam(Request request, AbstractSpan span) { Tags.HTTP.PARAMS.set(span, tagValue); } } + + private void collectHttpHeaders(Request request, AbstractSpan span) { + final List headersList = new ArrayList<>(TomcatPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS.size()); + TomcatPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS.stream() + .filter(headerName -> request.getHeaders(headerName) != null) + .forEach(headerName -> { + Enumeration headerValues = request.getHeaders(headerName); + List valueList = Collections.list(headerValues); + if (!CollectionUtil.isEmpty(valueList)) { + headersList.add(headerName + "=" + valueList); + } + }); + + if (!headersList.isEmpty()) { + String tagValue = String.join("\n", headersList); + tagValue = TomcatPluginConfig.Plugin.Http.HTTP_HEADERS_LENGTH_THRESHOLD > 0 ? + StringUtil.cut(tagValue, TomcatPluginConfig.Plugin.Http.HTTP_HEADERS_LENGTH_THRESHOLD) : + tagValue; + Tags.HTTP.HEADERS.set(span, tagValue); + } + } } diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/TomcatPluginConfig.java b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatPluginConfig.java similarity index 68% rename from apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/TomcatPluginConfig.java rename to apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatPluginConfig.java index 2c2865d63a..8a99337863 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat10x/TomcatPluginConfig.java +++ b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatPluginConfig.java @@ -16,8 +16,9 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat10x; +package org.apache.skywalking.apm.plugin.tomcat; +import java.util.List; import org.apache.skywalking.apm.agent.core.boot.PluginConfig; public class TomcatPluginConfig { @@ -38,6 +39,19 @@ public static class Http { * for the sake of performance */ public static int HTTP_PARAMS_LENGTH_THRESHOLD = 1024; + + /** + * When {@link Http#INCLUDE_HTTP_HEADERS} declares header names, this threshold controls the length + * limitation of all header values. use negative values to keep and send the complete headers. + * Note. this config item is added for the sake of performance. + */ + public static int HTTP_HEADERS_LENGTH_THRESHOLD = 2048; + + /** + * It controls what header data should be collected, this is for security purpose, values must be in lower + * case. Shares the {@code plugin.http.include_http_headers} config key with the other HTTP server plugins. + */ + public static List INCLUDE_HTTP_HEADERS; } } } diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/define/ApplicationDispatcherInstrumentation.java b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/define/ApplicationDispatcherInstrumentation.java similarity index 92% rename from apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/define/ApplicationDispatcherInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/define/ApplicationDispatcherInstrumentation.java index 14158594fa..f1d8f0370d 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/define/ApplicationDispatcherInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/define/ApplicationDispatcherInstrumentation.java @@ -16,7 +16,7 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat78x.define; +package org.apache.skywalking.apm.plugin.tomcat.define; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -33,12 +33,7 @@ public class ApplicationDispatcherInstrumentation extends ClassInstanceMethodsEn private static final String ENHANCE_CLASS = "org.apache.catalina.core.ApplicationDispatcher"; private static final String ENHANCE_METHOD = "forward"; - public static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.tomcat78x.ForwardInterceptor"; - - @Override - protected String[] witnessClasses() { - return new String[]{"javax.servlet.http.HttpServletResponse"}; - } + public static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.tomcat.ForwardInterceptor"; @Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/define/TomcatInstrumentation.java b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/define/TomcatInstrumentation.java similarity index 91% rename from apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/define/TomcatInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/define/TomcatInstrumentation.java index ffcae42fe8..67e9a4296d 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat78x/define/TomcatInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/define/TomcatInstrumentation.java @@ -16,13 +16,13 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat78x.define; +package org.apache.skywalking.apm.plugin.tomcat.define; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -38,17 +38,12 @@ public class TomcatInstrumentation extends ClassInstanceMethodsEnhancePluginDefi /** * The intercept class for "invoke" method in the class "org.apache.catalina.core.StandardHostValve" */ - private static final String INVOKE_INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.tomcat78x.TomcatInvokeInterceptor"; + private static final String INVOKE_INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.tomcat.TomcatInvokeInterceptor"; /** * The intercept class for "exception" method in the class "org.apache.catalina.core.StandardHostValve" */ - private static final String EXCEPTION_INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.tomcat78x.TomcatExceptionInterceptor"; - - @Override - protected String[] witnessClasses() { - return new String[]{"javax.servlet.http.HttpServletResponse"}; - } + private static final String EXCEPTION_INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.tomcat.TomcatExceptionInterceptor"; @Override protected ClassMatch enhanceClass() { diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..35eb529458 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +tomcat=org.apache.skywalking.apm.plugin.tomcat.define.TomcatInstrumentation +tomcat=org.apache.skywalking.apm.plugin.tomcat.define.ApplicationDispatcherInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/tomcat78x/TomcatInvokeInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/test/java/org/apache/skywalking/apm/plugin/tomcat/TomcatInvokeInterceptorTest.java similarity index 90% rename from apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/tomcat78x/TomcatInvokeInterceptorTest.java rename to apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/test/java/org/apache/skywalking/apm/plugin/tomcat/TomcatInvokeInterceptorTest.java index 4aeb68c89a..b3fe9b9022 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/tomcat78x/TomcatInvokeInterceptorTest.java +++ b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/test/java/org/apache/skywalking/apm/plugin/tomcat/TomcatInvokeInterceptorTest.java @@ -16,15 +16,15 @@ * */ -package org.apache.skywalking.apm.plugin.tomcat78x; +package org.apache.skywalking.apm.plugin.tomcat; import static org.apache.skywalking.apm.agent.test.tools.SpanAssert.assertComponent; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.when; import java.util.List; -import javax.servlet.http.HttpServletResponse; import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; import org.apache.skywalking.apm.agent.core.context.SW8CarrierItem; import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; import org.apache.skywalking.apm.agent.core.context.trace.LogDataEntity; @@ -65,8 +65,14 @@ public class TomcatInvokeInterceptorTest { @Mock private Request request; + /** + * The merged tomcat-plugin compiles against tomcat-embed-core 10.0.22, whose Catalina + * {@link Response} implements {@code jakarta.servlet.http.HttpServletResponse}. The interceptor + * reads the status through {@code HttpResponseWrappers.wrap(response).getStatus()}, which selects + * the jakarta branch for this mock and returns the stubbed status below. + */ @Mock - private HttpServletResponse response; + private Response response; @Mock private MethodInterceptResult methodInterceptResult; @@ -177,6 +183,10 @@ private void assertHttpSpan(AbstractTracingSpan span) { assertThat(span.getOperationName(), is("GET:/test/testRequestURL")); assertComponent(span, ComponentsDefine.TOMCAT); SpanAssert.assertTag(span, 0, "http://localhost:8080/test/testRequestURL"); + // The status read via HttpResponseWrappers.wrap(response).getStatus() is recorded as the + // http.status_code tag; the original tomcat78x test omitted this assertion. + assertThat(SpanHelper.getTags(span).get(2).getKey().key(), is("http.status_code")); + SpanAssert.assertTag(span, 2, "200"); assertThat(span.isEntry(), is(true)); SpanAssert.assertLayer(span, SpanLayer.HTTP); } diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker similarity index 100% rename from apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker rename to apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-thread-pool-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/tomcat-thread-pool-plugin/pom.xml index 6fc859269b..2007b9711e 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-thread-pool-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/tomcat-thread-pool-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/undertow-plugins/pom.xml b/apm-sniffer/apm-sdk-plugin/undertow-plugins/pom.xml index 85ad76522d..14d23e7692 100644 --- a/apm-sniffer/apm-sdk-plugin/undertow-plugins/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/undertow-plugins/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT undertow-plugins @@ -37,6 +37,5 @@ UTF-8 - /.. diff --git a/apm-sniffer/apm-sdk-plugin/undertow-plugins/undertow-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/undertow-plugins/undertow-2.x-plugin/pom.xml index b5abe20db9..8b722364c6 100644 --- a/apm-sniffer/apm-sdk-plugin/undertow-plugins/undertow-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/undertow-plugins/undertow-2.x-plugin/pom.xml @@ -20,7 +20,7 @@ undertow-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/pom.xml index 9e500f59ae..aed38b73f1 100644 --- a/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -29,5 +29,12 @@ undertow-worker-thread-pool-plugin http://maven.apache.org - + + + org.jboss.xnio + xnio-api + 3.8.4.Final + provided + + \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/XnioWorkerConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/XnioWorkerConstructorInterceptor.java new file mode 100644 index 0000000000..88cecc5ca1 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/XnioWorkerConstructorInterceptor.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.undertow.worker.thread.pool; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; +import org.apache.skywalking.apm.agent.core.meter.MeterFactory; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.undertow.worker.thread.pool.util.XnioWorkerTaskPoolAccessor; +import org.xnio.XnioWorker; + +public class XnioWorkerConstructorInterceptor implements InstanceConstructorInterceptor { + + private static final String THREAD_POOL_NAME = "undertow_worker_pool"; + + private static final Map>> METRIC_MAP = new HashMap>>() {{ + put("core_pool_size", (XnioWorkerTaskPoolAccessor threadPoolExecutor) -> () -> (double) threadPoolExecutor.getCorePoolSize()); + put("max_pool_size", (XnioWorkerTaskPoolAccessor threadPoolExecutor) -> () -> (double) threadPoolExecutor.getMaximumPoolSize()); + put("pool_size", (XnioWorkerTaskPoolAccessor threadPoolExecutor) -> () -> (double) threadPoolExecutor.getPoolSize()); + put("queue_size", (XnioWorkerTaskPoolAccessor threadPoolExecutor) -> () -> (double) threadPoolExecutor.getQueueSize()); + put("active_size", (XnioWorkerTaskPoolAccessor threadPoolExecutor) -> () -> (double) threadPoolExecutor.getActiveCount()); + }}; + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable { + buildThreadPoolMeterMetric(new XnioWorkerTaskPoolAccessor((XnioWorker) objInst)); + } + + private void buildThreadPoolMeterMetric(XnioWorkerTaskPoolAccessor xnioWorkerTaskPoolAccessor) { + String threadPoolMeterName = "thread_pool"; + String poolNameTag = "pool_name"; + String metricTypeTag = "metric_type"; + METRIC_MAP.forEach((key, value) -> { + if (Objects.equals(key, "pool_size")) { + if (xnioWorkerTaskPoolAccessor.isContainsGetPoolSizeMethod()) { + MeterFactory.gauge(threadPoolMeterName, value.apply(xnioWorkerTaskPoolAccessor)) + .tag(poolNameTag, THREAD_POOL_NAME).tag(metricTypeTag, key).build(); + } + } else { + MeterFactory.gauge(threadPoolMeterName, value.apply(xnioWorkerTaskPoolAccessor)) + .tag(poolNameTag, THREAD_POOL_NAME).tag(metricTypeTag, key).build(); + } + }); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/define/UndertowWorkerThreadPoolInstrumentation.java b/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/define/UndertowWorkerThreadPoolInstrumentation.java index edca92a998..ce827e349c 100644 --- a/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/define/UndertowWorkerThreadPoolInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/define/UndertowWorkerThreadPoolInstrumentation.java @@ -19,43 +19,56 @@ package org.apache.skywalking.apm.plugin.undertow.worker.thread.pool.define; import static net.bytebuddy.matcher.ElementMatchers.any; -import static org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch.byHierarchyMatch; -import static org.apache.skywalking.apm.agent.core.plugin.match.PrefixMatch.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; +import java.util.Collections; +import java.util.List; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; -import org.apache.skywalking.apm.agent.core.plugin.match.logical.LogicalMatchOperation; +/** + * ThreadPoolExecutor implemented xnio worker task pool before 3.6.0 + */ public class UndertowWorkerThreadPoolInstrumentation extends ClassEnhancePluginDefine { - private static final String THREAD_POOL_EXECUTOR_CLASS = "java.util.concurrent.ThreadPoolExecutor"; + private static final String THREAD_POOL_EXECUTOR_CLASS = "org.xnio.XnioWorker$TaskPool"; private static final String UNDERTOW_WORKER_THREAD_POOL_INTERCEPT = "org.apache.skywalking.apm.plugin.undertow.worker.thread.pool.UndertowWorkerThreadPoolConstructorIntercept"; + @Override + protected List witnessMethods() { + return Collections.singletonList(new WitnessMethod( + "org.xnio.XnioWorker$TaskPool", + named("terminated") + )); + } + @Override protected ClassMatch enhanceClass() { - return LogicalMatchOperation.and(nameStartsWith("org.xnio"), byHierarchyMatch(THREAD_POOL_EXECUTOR_CLASS)); + return byName(THREAD_POOL_EXECUTOR_CLASS); } @Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { - return new ConstructorInterceptPoint[]{ - new ConstructorInterceptPoint() { - @Override - public ElementMatcher getConstructorMatcher() { - return any(); - } + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } - @Override - public String getConstructorInterceptor() { - return UNDERTOW_WORKER_THREAD_POOL_INTERCEPT; - } + @Override + public String getConstructorInterceptor() { + return UNDERTOW_WORKER_THREAD_POOL_INTERCEPT; } + } }; } diff --git a/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/define/XnioWorkerConstructorInstrumentation.java b/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/define/XnioWorkerConstructorInstrumentation.java new file mode 100644 index 0000000000..2bc13dd20f --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/define/XnioWorkerConstructorInstrumentation.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.undertow.worker.thread.pool.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * xnio task pool new implementation since 3.6.0 + * https://github.com/xnio/xnio/commit/071800e0a85c9da9b88a976ac7ecb85760924dbf + */ +public class XnioWorkerConstructorInstrumentation extends ClassEnhancePluginDefine { + + private static final String XNIO_WORKER_CLASS = "org.xnio.XnioWorker"; + + private static final String UNDERTOW_WORKER_THREAD_POOL_INTERCEPT = "org.apache.skywalking.apm.plugin.undertow.worker.thread.pool.XnioWorkerConstructorInterceptor"; + + @Override + protected String[] witnessClasses() { + return new String[] {"org.xnio.XnioWorker$EnhancedQueueExecutorTaskPool"}; + } + + @Override + protected ClassMatch enhanceClass() { + return byName(XNIO_WORKER_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return UNDERTOW_WORKER_THREAD_POOL_INTERCEPT; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[0]; + } + + @Override + public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() { + return new StaticMethodsInterceptPoint[0]; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/util/XnioWorkerTaskPoolAccessor.java b/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/util/XnioWorkerTaskPoolAccessor.java new file mode 100644 index 0000000000..2284115437 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/java/org/apache/skywalking/apm/plugin/undertow/worker/thread/pool/util/XnioWorkerTaskPoolAccessor.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.undertow.worker.thread.pool.util; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import lombok.Getter; +import org.xnio.XnioWorker; + +public class XnioWorkerTaskPoolAccessor { + + private final Object taskPool; + @Getter + private boolean containsGetPoolSizeMethod; + + private Method getCorePoolSizeMethod; + private Method getMaximumPoolSizeMethod; + private Method getActiveCountMethod; + private Method getPoolSizeMethod; + private Method getQueueSizeMethod; + + public XnioWorkerTaskPoolAccessor(final XnioWorker worker) throws NoSuchFieldException, IllegalAccessException { + Field field = worker.getClass().getSuperclass().getDeclaredField("taskPool"); + field.setAccessible(true); + this.taskPool = field.get(worker); + + try { + getCorePoolSizeMethod = taskPool.getClass().getDeclaredMethod("getCorePoolSize"); + getCorePoolSizeMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + // ignore + } + try { + getMaximumPoolSizeMethod = taskPool.getClass().getDeclaredMethod("getMaximumPoolSize"); + getMaximumPoolSizeMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + // ignore + } + try { + getActiveCountMethod = taskPool.getClass().getDeclaredMethod("getActiveCount"); + getActiveCountMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + // ignore + } + try { + // getPoolSize add since 3.8.0 + getPoolSizeMethod = taskPool.getClass().getDeclaredMethod("getPoolSize"); + getPoolSizeMethod.setAccessible(true); + containsGetPoolSizeMethod = true; + } catch (NoSuchMethodException e) { + containsGetPoolSizeMethod = false; + } + try { + getQueueSizeMethod = taskPool.getClass().getDeclaredMethod("getQueueSize"); + getQueueSizeMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + // ignore + } + } + + public int getCorePoolSize() { + try { + return (int) getCorePoolSizeMethod.invoke(taskPool); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public int getMaximumPoolSize() { + try { + return (int) getMaximumPoolSizeMethod.invoke(taskPool); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public int getActiveCount() { + try { + return (int) getActiveCountMethod.invoke(taskPool); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public int getPoolSize() { + try { + return (int) getPoolSizeMethod.invoke(taskPool); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public int getQueueSize() { + try { + return (int) getQueueSizeMethod.invoke(taskPool); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/resources/skywalking-plugin.def index 22837a7aad..8ff6aacdbd 100644 --- a/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/apm-sdk-plugin/undertow-worker-thread-pool-plugin/src/main/resources/skywalking-plugin.def @@ -14,4 +14,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -undertow-worker-thread-pool=org.apache.skywalking.apm.plugin.undertow.worker.thread.pool.define.UndertowWorkerThreadPoolInstrumentation \ No newline at end of file +undertow-worker-thread-pool=org.apache.skywalking.apm.plugin.undertow.worker.thread.pool.define.UndertowWorkerThreadPoolInstrumentation +undertow-worker-thread-pool=org.apache.skywalking.apm.plugin.undertow.worker.thread.pool.define.XnioWorkerConstructorInstrumentation \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/vertx-plugins/pom.xml b/apm-sniffer/apm-sdk-plugin/vertx-plugins/pom.xml index b5818b98f3..a7f441800d 100644 --- a/apm-sniffer/apm-sdk-plugin/vertx-plugins/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/vertx-plugins/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT vertx-plugins @@ -38,7 +38,6 @@ UTF-8 - /.. diff --git a/apm-sniffer/apm-sdk-plugin/vertx-plugins/vertx-core-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/vertx-plugins/vertx-core-3.x-plugin/pom.xml index e9c720d80f..b1d89698a5 100644 --- a/apm-sniffer/apm-sdk-plugin/vertx-plugins/vertx-core-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/vertx-plugins/vertx-core-3.x-plugin/pom.xml @@ -20,7 +20,7 @@ vertx-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/vertx-plugins/vertx-core-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/vertx-plugins/vertx-core-4.x-plugin/pom.xml index 42a48a40e3..9e21fe716a 100644 --- a/apm-sniffer/apm-sdk-plugin/vertx-plugins/vertx-core-4.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/vertx-plugins/vertx-core-4.x-plugin/pom.xml @@ -20,7 +20,7 @@ vertx-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-sdk-plugin/websphere-liberty-23.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/websphere-liberty-23.x-plugin/pom.xml index a2ba27cf03..7795fd9659 100644 --- a/apm-sniffer/apm-sdk-plugin/websphere-liberty-23.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/websphere-liberty-23.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -35,7 +35,6 @@ junit junit - ${junit.version} test diff --git a/apm-sniffer/apm-sdk-plugin/xmemcached-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/xmemcached-2.x-plugin/pom.xml index 841535f8db..35876e8aff 100644 --- a/apm-sniffer/apm-sdk-plugin/xmemcached-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/xmemcached-2.x-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking apm-sdk-plugin - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-xmemcached-2.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/xxl-job-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/xxl-job-2.x-plugin/pom.xml index a008627a7a..da1409f18d 100644 --- a/apm-sniffer/apm-sdk-plugin/xxl-job-2.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/xxl-job-2.x-plugin/pom.xml @@ -21,7 +21,7 @@ apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-test-tools/pom.xml b/apm-sniffer/apm-test-tools/pom.xml index db5fa5298b..9dd0f43cb3 100644 --- a/apm-sniffer/apm-test-tools/pom.xml +++ b/apm-sniffer/apm-test-tools/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking java-agent-sniffer - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-test-tools @@ -38,7 +38,6 @@ junit junit - ${junit.version} provided diff --git a/apm-sniffer/apm-test-tools/src/main/java/org/apache/skywalking/apm/agent/core/context/MockContextSnapshot.java b/apm-sniffer/apm-test-tools/src/main/java/org/apache/skywalking/apm/agent/core/context/MockContextSnapshot.java index 505a4924a9..c6d5f91fcc 100644 --- a/apm-sniffer/apm-test-tools/src/main/java/org/apache/skywalking/apm/agent/core/context/MockContextSnapshot.java +++ b/apm-sniffer/apm-test-tools/src/main/java/org/apache/skywalking/apm/agent/core/context/MockContextSnapshot.java @@ -26,6 +26,8 @@ public enum MockContextSnapshot { private ContextSnapshot contextSnapshot; + private ContextSnapshot ignoreContextSnapshot; + MockContextSnapshot() { contextSnapshot = new ContextSnapshot( "1, 2, 3", @@ -36,9 +38,21 @@ public enum MockContextSnapshot { new ExtensionContext(), ProfileStatusContext.createWithNone() ); + ignoreContextSnapshot = new ContextSnapshot( + null, + -1, + null, + null, + new CorrelationContext(), + new ExtensionContext(), + ProfileStatusContext.createWithNone()); } public ContextSnapshot mockContextSnapshot() { return contextSnapshot; } + + public ContextSnapshot mockIgnoreContextSnapshot() { + return ignoreContextSnapshot; + } } diff --git a/apm-sniffer/apm-test-tools/src/main/java/org/apache/skywalking/apm/agent/test/tools/TracingSegmentRunner.java b/apm-sniffer/apm-test-tools/src/main/java/org/apache/skywalking/apm/agent/test/tools/TracingSegmentRunner.java index 0626f36d01..8dbb6cf1a3 100644 --- a/apm-sniffer/apm-test-tools/src/main/java/org/apache/skywalking/apm/agent/test/tools/TracingSegmentRunner.java +++ b/apm-sniffer/apm-test-tools/src/main/java/org/apache/skywalking/apm/agent/test/tools/TracingSegmentRunner.java @@ -55,7 +55,7 @@ protected Object createTest() throws Exception { @Override protected Statement withAfters(FrameworkMethod method, Object target, final Statement statement) { - return new Statement() { + Statement st = new Statement() { @Override public void evaluate() throws Throwable { if (field != null) { @@ -89,5 +89,7 @@ public void afterFinished(IgnoredTracerContext tracerContext) { } } }; + + return super.withAfters(method, target, st); } } diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-kafka-activation/pom.xml b/apm-sniffer/apm-toolkit-activation/apm-toolkit-kafka-activation/pom.xml index 5c7df0c206..a914b8d528 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-kafka-activation/pom.xml +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-kafka-activation/pom.xml @@ -21,7 +21,7 @@ apm-toolkit-activation org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-log4j-1.x-activation/pom.xml b/apm-sniffer/apm-toolkit-activation/apm-toolkit-log4j-1.x-activation/pom.xml index 1b937485b5..86e57d28bb 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-log4j-1.x-activation/pom.xml +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-log4j-1.x-activation/pom.xml @@ -21,7 +21,7 @@ apm-toolkit-activation org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-log4j-2.x-activation/pom.xml b/apm-sniffer/apm-toolkit-activation/apm-toolkit-log4j-2.x-activation/pom.xml index a4d7f78bbe..5e1a0fb598 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-log4j-2.x-activation/pom.xml +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-log4j-2.x-activation/pom.xml @@ -21,7 +21,7 @@ apm-toolkit-activation org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-logback-1.x-activation/pom.xml b/apm-sniffer/apm-toolkit-activation/apm-toolkit-logback-1.x-activation/pom.xml index 9134874b44..341faac073 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-logback-1.x-activation/pom.xml +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-logback-1.x-activation/pom.xml @@ -21,7 +21,7 @@ apm-toolkit-activation org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-logging-common/pom.xml b/apm-sniffer/apm-toolkit-activation/apm-toolkit-logging-common/pom.xml index 85659cfb98..ae19d88816 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-logging-common/pom.xml +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-logging-common/pom.xml @@ -20,7 +20,7 @@ apm-toolkit-activation org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/pom.xml b/apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/pom.xml index 30a272ff2c..4d260f63e5 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/pom.xml +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/pom.xml @@ -21,7 +21,7 @@ apm-toolkit-activation org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-micrometer-activation/pom.xml b/apm-sniffer/apm-toolkit-activation/apm-toolkit-micrometer-activation/pom.xml index d63725b992..7e9c7d103f 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-micrometer-activation/pom.xml +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-micrometer-activation/pom.xml @@ -20,7 +20,7 @@ apm-toolkit-activation org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-micrometer-activation/src/test/java/org/apache/skywalking/apm/toolkit/activation/micrometer/MicrometerContextSnapshotThreadLocalAccessorInterceptorTest.java b/apm-sniffer/apm-toolkit-activation/apm-toolkit-micrometer-activation/src/test/java/org/apache/skywalking/apm/toolkit/activation/micrometer/MicrometerContextSnapshotThreadLocalAccessorInterceptorTest.java index 3e3f66c844..258808f8f1 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-micrometer-activation/src/test/java/org/apache/skywalking/apm/toolkit/activation/micrometer/MicrometerContextSnapshotThreadLocalAccessorInterceptorTest.java +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-micrometer-activation/src/test/java/org/apache/skywalking/apm/toolkit/activation/micrometer/MicrometerContextSnapshotThreadLocalAccessorInterceptorTest.java @@ -92,8 +92,6 @@ public void clear() { @AfterClass public static void clearAfterAll() { // test from threadlocalaccessor test x 2 TODO: I have no idea what is going on - ContextManager.stopSpan(); - ContextManager.stopSpan(); assertThat(ContextManager.isActive(), is(false)); } @@ -103,6 +101,7 @@ public void testServiceFromPlugin() { PluginBootService.class); Assert.assertNotNull(service); + ContextManager.stopSpan(); } @Test @@ -110,6 +109,7 @@ public void testServiceOverrideFromPlugin() { ContextManagerExtendService service = ServiceManager.INSTANCE.findService(ContextManagerExtendService.class); Assert.assertTrue(service instanceof ContextManagerExtendOverrideService); + ContextManager.stopSpan(); } @Test diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-opentracing-activation/pom.xml b/apm-sniffer/apm-toolkit-activation/apm-toolkit-opentracing-activation/pom.xml index d0484207c1..05dc1b4ac9 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-opentracing-activation/pom.xml +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-opentracing-activation/pom.xml @@ -21,7 +21,7 @@ apm-toolkit-activation org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-trace-activation/pom.xml b/apm-sniffer/apm-toolkit-activation/apm-toolkit-trace-activation/pom.xml index 56a268c4a1..f93d0a3397 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-trace-activation/pom.xml +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-trace-activation/pom.xml @@ -21,7 +21,7 @@ apm-toolkit-activation org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-trace-activation/src/test/java/org/apache/skywalking/apm/toolkit/activation/trace/CallableOrRunnableInterceptorTest.java b/apm-sniffer/apm-toolkit-activation/apm-toolkit-trace-activation/src/test/java/org/apache/skywalking/apm/toolkit/activation/trace/CallableOrRunnableInterceptorTest.java index 45e00baa73..3aacc88c67 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-trace-activation/src/test/java/org/apache/skywalking/apm/toolkit/activation/trace/CallableOrRunnableInterceptorTest.java +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-trace-activation/src/test/java/org/apache/skywalking/apm/toolkit/activation/trace/CallableOrRunnableInterceptorTest.java @@ -22,7 +22,7 @@ import java.lang.reflect.Method; import java.util.List; import java.util.concurrent.Callable; -import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.MockContextSnapshot; import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; @@ -36,7 +36,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -69,9 +68,6 @@ public void setSkyWalkingDynamicField(Object value) { } }; - @Mock - private ContextSnapshot contextSnapshot; - private Object[] arguments; private Method callMethod; @@ -100,7 +96,7 @@ public void testOnConstructor() { @Test public void testCall() throws Throwable { - enhancedInstance.setSkyWalkingDynamicField(contextSnapshot); + enhancedInstance.setSkyWalkingDynamicField(MockContextSnapshot.INSTANCE.mockContextSnapshot()); callableCallInterceptor.beforeMethod(enhancedInstance, callMethod, arguments, null, null); callableCallInterceptor.afterMethod(enhancedInstance, callMethod, arguments, null, "result"); @@ -111,4 +107,16 @@ public void testCall() throws Throwable { } + @Test + public void testCallWithIgnoreSnapshot() throws Throwable { + + enhancedInstance.setSkyWalkingDynamicField(MockContextSnapshot.INSTANCE.mockIgnoreContextSnapshot()); + callableCallInterceptor.beforeMethod(enhancedInstance, callMethod, arguments, null, null); + callableCallInterceptor.afterMethod(enhancedInstance, callMethod, arguments, null, "result"); + + assertThat(segmentStorage.getTraceSegments().size(), is(0)); + assertThat(segmentStorage.getIgnoredTracerContexts().size(), is(1)); + + } + } diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/pom.xml b/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/pom.xml index f56c2edf8f..d8832e24f5 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/pom.xml +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/pom.xml @@ -21,7 +21,7 @@ apm-toolkit-activation org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/apm-toolkit-activation/pom.xml b/apm-sniffer/apm-toolkit-activation/pom.xml index f597ebabea..f6e3b542c5 100644 --- a/apm-sniffer/apm-toolkit-activation/pom.xml +++ b/apm-sniffer/apm-toolkit-activation/pom.xml @@ -21,7 +21,7 @@ java-agent-sniffer org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 pom diff --git a/apm-sniffer/bootstrap-plugins/CLAUDE.md b/apm-sniffer/bootstrap-plugins/CLAUDE.md new file mode 100644 index 0000000000..51b4db99e7 --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/CLAUDE.md @@ -0,0 +1,53 @@ +# CLAUDE.md - Bootstrap Plugin Development Guide + +This guide covers developing bootstrap-level plugins in `apm-sniffer/bootstrap-plugins/`. + +For general plugin development concepts (V2 API, tracing, class matching, testing), see `apm-sniffer/apm-sdk-plugin/CLAUDE.md`. + +## What Are Bootstrap Plugins? + +Bootstrap plugins instrument JDK core classes (rt.jar / java.base module) at the JVM bootstrap phase. They are loaded before application classes and can intercept fundamental JDK APIs. + +**Examples:** +- `jdk-threading-plugin` - Thread pool context propagation +- `jdk-http-plugin` - `HttpURLConnection` instrumentation +- `jdk-httpclient-plugin` - JDK 11+ `HttpClient` instrumentation +- `jdk-virtual-thread-executor-plugin` - JDK 21+ virtual thread support +- `jdk-forkjoinpool-plugin` - `ForkJoinPool` instrumentation + +## Key Difference from SDK Plugins + +Bootstrap plugins **must** override `isBootstrapInstrumentation()`: +```java +@Override +public boolean isBootstrapInstrumentation() { + return true; +} +``` + +**WARNING**: Use bootstrap instrumentation only where absolutely necessary. It affects JDK core classes and has broader impact than SDK plugins. + +## Development Rules + +All general plugin development rules apply (see `apm-sdk-plugin/CLAUDE.md`), plus: + +1. **Use V2 API** for new bootstrap plugins, same as SDK plugins +2. **Minimal scope**: Only intercept what is strictly necessary in JDK classes +3. **Performance critical**: Bootstrap plugins run on core JDK paths - keep interceptor logic lightweight +4. **Class loading awareness**: JDK core classes are loaded by the bootstrap classloader; be careful with class references that might not be visible at bootstrap level + +## Testing Bootstrap Plugins + +Bootstrap plugin test scenarios use `runningMode: with_bootstrap` in `configuration.yml`: +```yaml +type: jvm +entryService: http://localhost:8080/case +healthCheck: http://localhost:8080/health +startScript: ./bin/startup.sh +runningMode: with_bootstrap +withPlugins: jdk-threading-plugin-*.jar +``` + +This tells the test framework to load the plugin at the bootstrap level instead of the normal plugin directory. + +See `apm-sdk-plugin/CLAUDE.md` for full test framework documentation. diff --git a/apm-sniffer/bootstrap-plugins/jdk-forkjoinpool-plugin/pom.xml b/apm-sniffer/bootstrap-plugins/jdk-forkjoinpool-plugin/pom.xml index 0f9790e758..5de97c5a33 100644 --- a/apm-sniffer/bootstrap-plugins/jdk-forkjoinpool-plugin/pom.xml +++ b/apm-sniffer/bootstrap-plugins/jdk-forkjoinpool-plugin/pom.xml @@ -20,7 +20,7 @@ org.apache.skywalking bootstrap-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/bootstrap-plugins/jdk-http-plugin/pom.xml b/apm-sniffer/bootstrap-plugins/jdk-http-plugin/pom.xml index a1bcb92708..1692e684a6 100755 --- a/apm-sniffer/bootstrap-plugins/jdk-http-plugin/pom.xml +++ b/apm-sniffer/bootstrap-plugins/jdk-http-plugin/pom.xml @@ -20,7 +20,7 @@ org.apache.skywalking bootstrap-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -32,6 +32,8 @@ UTF-8 + + diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/pom.xml b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/pom.xml similarity index 69% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/pom.xml rename to apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/pom.xml index 03f8740533..f537f685e6 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/pom.xml +++ b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/pom.xml @@ -18,28 +18,29 @@ - jetty-plugins + bootstrap-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 - apm-jetty-server-9.x-plugin + apm-jdk-httpclient-plugin jar - jetty-server-9.x-plugin + apm-jdk-httpclient-plugin http://maven.apache.org - 9.0.0.v20130308 + UTF-8 + 11 - - - org.eclipse.jetty - jetty-server - ${jetty-server.version} - provided - - - + + + + maven-deploy-plugin + + + + + \ No newline at end of file diff --git a/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/HttpClientSendAsyncInterceptor.java b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/HttpClientSendAsyncInterceptor.java new file mode 100644 index 0000000000..0661c4705f --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/HttpClientSendAsyncInterceptor.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin; + +import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +import java.lang.reflect.Method; +import java.net.URI; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class HttpClientSendAsyncInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + HttpRequest request = (HttpRequest) allArguments[0]; + URI uri = request.uri(); + + ContextCarrier contextCarrier = new ContextCarrier(); + AbstractSpan span = ContextManager.createExitSpan(buildOperationName(request.method(), uri), contextCarrier, getPeer(uri)); + + if (request instanceof EnhancedInstance) { + ((EnhancedInstance) request).setSkyWalkingDynamicField(contextCarrier); + } + + span.setComponent(ComponentsDefine.JDK_HTTP); + Tags.HTTP.METHOD.set(span, request.method()); + Tags.URL.set(span, String.valueOf(uri)); + SpanLayer.asHttp(span); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + + if (ContextManager.isActive()) { + AbstractSpan span = ContextManager.activeSpan(); + span.prepareForAsync(); + + if (ret != null) { + CompletableFuture future = (CompletableFuture) ret; + ret = future.whenComplete((response, throwable) -> { + try { + if (throwable != null) { + span.errorOccurred(); + span.log(throwable); + return; + } + if (response instanceof HttpResponse) { + HttpResponse httpResponse = (HttpResponse) response; + int statusCode = httpResponse.statusCode(); + Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode); + if (statusCode >= 400) { + span.errorOccurred(); + } + } + } finally { + span.asyncFinish(); + } + }); + } else { + Map eventMap = new HashMap(); + eventMap.put("error", "No response"); + span.log(System.currentTimeMillis(), eventMap); + span.errorOccurred(); + } + ContextManager.stopSpan(); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + if (ContextManager.isActive()) { + ContextManager.activeSpan().log(t); + } + } + + private String buildOperationName(String method, URI uri) { + String path = uri.getPath(); + if (path == null || path.isEmpty()) { + path = "/"; + } + return method + ":" + path; + } + + private String getPeer(URI uri) { + String host = uri.getHost(); + int port = uri.getPort(); + + if (port == -1) { + port = "https".equalsIgnoreCase(uri.getScheme()) ? 443 : 80; + } + + return host + ":" + port; + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/HttpClientSendInterceptor.java b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/HttpClientSendInterceptor.java new file mode 100644 index 0000000000..3a06f59ad9 --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/HttpClientSendInterceptor.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin; + +import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +import java.lang.reflect.Method; +import java.net.URI; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.HashMap; +import java.util.Map; + +public class HttpClientSendInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + HttpRequest request = (HttpRequest) allArguments[0]; + URI uri = request.uri(); + + ContextCarrier contextCarrier = new ContextCarrier(); + AbstractSpan span = ContextManager.createExitSpan(buildOperationName(request.method(), uri), contextCarrier, getPeer(uri)); + + if (request instanceof EnhancedInstance) { + ((EnhancedInstance) request).setSkyWalkingDynamicField(contextCarrier); + } + + span.setComponent(ComponentsDefine.JDK_HTTP); + Tags.HTTP.METHOD.set(span, request.method()); + Tags.URL.set(span, String.valueOf(uri)); + SpanLayer.asHttp(span); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + + if (ContextManager.isActive()) { + AbstractSpan span = ContextManager.activeSpan(); + if (ret != null) { + HttpResponse response = (HttpResponse) ret; + int statusCode = response.statusCode(); + + Tags.HTTP_RESPONSE_STATUS_CODE.set(span, response.statusCode()); + if (statusCode >= 400) { + span.errorOccurred(); + } + } else { + Map eventMap = new HashMap(); + eventMap.put("error", "No response"); + span.log(System.currentTimeMillis(), eventMap); + span.errorOccurred(); + } + + ContextManager.stopSpan(); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + if (ContextManager.isActive()) { + ContextManager.activeSpan().log(t); + } + } + + private String buildOperationName(String method, URI uri) { + String path = uri.getPath(); + if (path == null || path.isEmpty()) { + path = "/"; + } + return method + ":" + path; + } + + private String getPeer(URI uri) { + String host = uri.getHost(); + int port = uri.getPort(); + + if (port == -1) { + port = "https".equalsIgnoreCase(uri.getScheme()) ? 443 : 80; + } + + return host + ":" + port; + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/HttpRequestHeadersInterceptor.java b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/HttpRequestHeadersInterceptor.java new file mode 100644 index 0000000000..a6dc76650f --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/HttpRequestHeadersInterceptor.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin; + +import org.apache.skywalking.apm.agent.core.context.CarrierItem; +import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; + +import java.lang.reflect.Method; +import java.net.http.HttpHeaders; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HttpRequestHeadersInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + + ContextCarrier contextCarrier = (ContextCarrier) objInst.getSkyWalkingDynamicField(); + HttpHeaders originalHeaders = (HttpHeaders) ret; + final Map> headerMap = new HashMap<>(originalHeaders.map()); + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + headerMap.put(next.getHeadKey(), List.of(next.getHeadValue())); + } + + return HttpHeaders.of(headerMap, (k, v) -> true); + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + if (ContextManager.isActive()) { + ContextManager.activeSpan().log(t); + } + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/HttpClientInstrumentation.java b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/HttpClientInstrumentation.java new file mode 100644 index 0000000000..18f7cf298b --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/HttpClientInstrumentation.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.IndirectMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.logical.LogicalMatchOperation; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class HttpClientInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_PARENT_CLASS = "java.net.http.HttpClient"; + + private static final String EXCLUDE_CLASS = "jdk.internal.net.http.HttpClientFacade"; + + private static final String INTERCEPT_SEND_METHOD = "send"; + + private static final String INTERCEPT_SEND_ASYNC_METHOD = "sendAsync"; + + private static final String INTERCEPT_SEND_HANDLE = "org.apache.skywalking.apm.plugin.HttpClientSendInterceptor"; + + private static final String INTERCEPT_SEND_ASYNC_HANDLE = "org.apache.skywalking.apm.plugin.HttpClientSendAsyncInterceptor"; + + @Override + public boolean isBootstrapInstrumentation() { + return true; + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + protected ClassMatch enhanceClass() { + IndirectMatch parentType = HierarchyMatch.byHierarchyMatch(ENHANCE_PARENT_CLASS); + IndirectMatch excludeClass = LogicalMatchOperation.not(MultiClassNameMatch.byMultiClassMatch(EXCLUDE_CLASS)); + return LogicalMatchOperation.and(parentType, excludeClass); + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers.named(INTERCEPT_SEND_METHOD) + .and(ElementMatchers.takesArgument(0, named("java.net.http.HttpRequest"))) + .and(ElementMatchers.takesArguments(2)); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_SEND_HANDLE; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers.named(INTERCEPT_SEND_ASYNC_METHOD) + .and(ElementMatchers.takesArgument(0, named("java.net.http.HttpRequest"))) + .and(ElementMatchers.takesArgument(1, named("java.net.http.HttpResponse$BodyHandler"))) + .and(ElementMatchers.takesArgument(2, named("java.net.http.HttpResponse$PushPromiseHandler"))) + .and(ElementMatchers.takesArguments(3)); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_SEND_ASYNC_HANDLE; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + }; + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/HttpRequestInstrumentation.java b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/HttpRequestInstrumentation.java new file mode 100644 index 0000000000..18d47b6923 --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/HttpRequestInstrumentation.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +public class HttpRequestInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "jdk.internal.net.http.ImmutableHttpRequest"; + + private static final String INTERCEPT_HEADERS_METHOD = "headers"; + + private static final String INTERCEPT_HEADERS_HANDLE = "org.apache.skywalking.apm.plugin.HttpRequestHeadersInterceptor"; + + @Override + public boolean isBootstrapInstrumentation() { + return true; + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers.named(INTERCEPT_HEADERS_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_HEADERS_HANDLE; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..e4d1ef2562 --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-httpclient-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +jdk-httpclient-plugin=org.apache.skywalking.apm.plugin.define.HttpClientInstrumentation +jdk-httpclient-plugin=org.apache.skywalking.apm.plugin.define.HttpRequestInstrumentation \ No newline at end of file diff --git a/apm-sniffer/bootstrap-plugins/jdk-threading-plugin/pom.xml b/apm-sniffer/bootstrap-plugins/jdk-threading-plugin/pom.xml index 29efafb1ae..33dd7a3b4d 100755 --- a/apm-sniffer/bootstrap-plugins/jdk-threading-plugin/pom.xml +++ b/apm-sniffer/bootstrap-plugins/jdk-threading-plugin/pom.xml @@ -20,7 +20,7 @@ org.apache.skywalking bootstrap-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/pom.xml b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/pom.xml index 91799d8558..3da184c381 100644 --- a/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/pom.xml +++ b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/pom.xml @@ -20,7 +20,7 @@ bootstrap-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/AbstractThreadingPoolInterceptor.java b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/AbstractThreadingPoolInterceptor.java index 7239453074..152064960b 100644 --- a/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/AbstractThreadingPoolInterceptor.java +++ b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/AbstractThreadingPoolInterceptor.java @@ -24,26 +24,16 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import java.lang.reflect.Method; +import java.util.concurrent.ForkJoinTask; public abstract class AbstractThreadingPoolInterceptor implements InstanceMethodsAroundInterceptor { @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { - if (!ContextManager.isActive()) { + if (notToEnhance(allArguments)) { return; } - if (allArguments == null || allArguments.length < 1) { - return; - } - - Object argument = allArguments[0]; - - // Avoid duplicate enhancement, such as the case where it has already been enhanced by RunnableWrapper or CallableWrapper with toolkit. - if (argument instanceof EnhancedInstance && ((EnhancedInstance) argument).getSkyWalkingDynamicField() instanceof ContextSnapshot) { - return; - } - - Object wrappedObject = wrap(argument); + Object wrappedObject = wrap(allArguments[0]); if (wrappedObject != null) { allArguments[0] = wrappedObject; } @@ -63,6 +53,27 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA @Override public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + if (notToEnhance(allArguments)) { + return; + } + ContextManager.activeSpan().log(t); } + + private boolean notToEnhance(Object[] allArguments) { + if (!ContextManager.isActive()) { + return true; + } + + if (allArguments == null || allArguments.length < 1) { + return true; + } + + Object argument = allArguments[0]; + + // Avoid duplicate enhancement, such as the case where it has already been enhanced by RunnableWrapper or CallableWrapper with toolkit. + return argument instanceof EnhancedInstance + && ((EnhancedInstance) argument).getSkyWalkingDynamicField() instanceof ContextSnapshot + && !(argument instanceof ForkJoinTask); + } } diff --git a/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/pom.xml b/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/pom.xml new file mode 100644 index 0000000000..9a3eee820f --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/pom.xml @@ -0,0 +1,46 @@ + + + + + bootstrap-plugins + org.apache.skywalking + 9.7.0-SNAPSHOT + + 4.0.0 + + apm-jdk-virtual-thread-executor-plugin + jar + + apm-jdk-virtual-thread-executor-plugin + http://maven.apache.org + + + UTF-8 + + + + + + + maven-deploy-plugin + + + + + \ No newline at end of file diff --git a/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/java/org/apache/skywalking/apm/plugin/ThreadPerTaskExecutorConstructInterceptor.java b/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/java/org/apache/skywalking/apm/plugin/ThreadPerTaskExecutorConstructInterceptor.java new file mode 100644 index 0000000000..c8f18193f9 --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/java/org/apache/skywalking/apm/plugin/ThreadPerTaskExecutorConstructInterceptor.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; + +public class ThreadPerTaskExecutorConstructInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable { + if (ContextManager.isActive()) { + objInst.setSkyWalkingDynamicField(ContextManager.capture()); + } + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/java/org/apache/skywalking/apm/plugin/ThreadPerTaskExecutorRunInterceptor.java b/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/java/org/apache/skywalking/apm/plugin/ThreadPerTaskExecutorRunInterceptor.java new file mode 100644 index 0000000000..9cd9289116 --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/java/org/apache/skywalking/apm/plugin/ThreadPerTaskExecutorRunInterceptor.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +import java.lang.reflect.Method; + +public class ThreadPerTaskExecutorRunInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + Object skyWalkingDynamicField = objInst.getSkyWalkingDynamicField(); + if (skyWalkingDynamicField == null) { + return; + } + + if (!(skyWalkingDynamicField instanceof ContextSnapshot)) { + return; + } + + AbstractSpan span = ContextManager.createLocalSpan(getOperationName(objInst, method)); + span.setComponent(ComponentsDefine.THREAD_PER_TASK_EXECUTOR); + setCarrierThread(span); + + ContextSnapshot contextSnapshot = (ContextSnapshot) skyWalkingDynamicField; + ContextManager.continued(contextSnapshot); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + if (ContextManager.isActive()) { + ContextManager.activeSpan().log(t); + } + } + + private String getOperationName(EnhancedInstance objInst, Method method) { + return objInst.getClass().getName() + "." + method.getName(); + } + + private void setCarrierThread(AbstractSpan span) { + String threadInfo = Thread.currentThread().toString(); + if (threadInfo.startsWith("VirtualThread")) { + String[] parts = threadInfo.split("@"); + if (parts.length >= 1) { + Tags.THREAD_CARRIER.set(span, parts[1]); + } + } + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/ThreadPerTaskExecutorFutureInstrumentation.java b/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/ThreadPerTaskExecutorFutureInstrumentation.java new file mode 100644 index 0000000000..3ed5788a1e --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/ThreadPerTaskExecutorFutureInstrumentation.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class ThreadPerTaskExecutorFutureInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "java.util.concurrent.ThreadPerTaskExecutor$ThreadBoundFuture"; + + private static final String INTERCEPT_RUN_METHOD = "run"; + + private static final String INTERCEPT_RUN_HANDLE = "org.apache.skywalking.apm.plugin.ThreadPerTaskExecutorRunInterceptor"; + + private static final String TASK_RUNNER_CONSTRUCT_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.ThreadPerTaskExecutorConstructInterceptor"; + + @Override + public boolean isBootstrapInstrumentation() { + return true; + } + + @Override + protected ClassMatch enhanceClass() { + return MultiClassNameMatch.byMultiClassMatch(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return TASK_RUNNER_CONSTRUCT_METHOD_INTERCEPTOR; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers.named(INTERCEPT_RUN_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_RUN_HANDLE; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/ThreadPerTaskExecutorTaskRunnerInstrumentation.java b/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/ThreadPerTaskExecutorTaskRunnerInstrumentation.java new file mode 100644 index 0000000000..9a0efd075f --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/ThreadPerTaskExecutorTaskRunnerInstrumentation.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +public class ThreadPerTaskExecutorTaskRunnerInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "java.util.concurrent.ThreadPerTaskExecutor$TaskRunner"; + + private static final String INTERCEPT_RUN_METHOD = "run"; + + private static final String INTERCEPT_RUN_HANDLE = "org.apache.skywalking.apm.plugin.ThreadPerTaskExecutorRunInterceptor"; + + private static final String TASK_RUNNER_CONSTRUCT_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.ThreadPerTaskExecutorConstructInterceptor"; + + @Override + public boolean isBootstrapInstrumentation() { + return true; + } + + @Override + protected ClassMatch enhanceClass() { + return MultiClassNameMatch.byMultiClassMatch(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override + public String getConstructorInterceptor() { + return TASK_RUNNER_CONSTRUCT_METHOD_INTERCEPTOR; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers.named(INTERCEPT_RUN_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_RUN_HANDLE; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..3ab6a970a9 --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-virtual-thread-executor-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +jdk-virtual-thread-executor-plugin=org.apache.skywalking.apm.plugin.define.ThreadPerTaskExecutorTaskRunnerInstrumentation +jdk-virtual-thread-executor-plugin=org.apache.skywalking.apm.plugin.define.ThreadPerTaskExecutorFutureInstrumentation \ No newline at end of file diff --git a/apm-sniffer/bootstrap-plugins/pom.xml b/apm-sniffer/bootstrap-plugins/pom.xml index bc540de6ab..fe3bf24930 100644 --- a/apm-sniffer/bootstrap-plugins/pom.xml +++ b/apm-sniffer/bootstrap-plugins/pom.xml @@ -21,7 +21,7 @@ java-agent-sniffer org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -32,10 +32,7 @@ ${shade.package}.${shade.net.bytebuddy.source} UTF-8 - - ${project.build.directory}${sdk.plugin.related.dir}/../../../../skywalking-agent - - ${agent.package.dest.dir}/bootstrap-plugins + ${maven.multiModuleProjectDirectory}/skywalking-agent/bootstrap-plugins 1.0b3 1.8.1 @@ -46,6 +43,8 @@ jdk-threading-plugin jdk-threadpool-plugin jdk-forkjoinpool-plugin + jdk-virtual-thread-executor-plugin + jdk-httpclient-plugin diff --git a/apm-sniffer/bytebuddy-patch/pom.xml b/apm-sniffer/bytebuddy-patch/pom.xml index e5ec1d0cda..460631dfc3 100644 --- a/apm-sniffer/bytebuddy-patch/pom.xml +++ b/apm-sniffer/bytebuddy-patch/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking java-agent-sniffer - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/bytebuddy-patch/src/main/java/net/bytebuddy/agent/builder/SWDescriptionStrategy.java b/apm-sniffer/bytebuddy-patch/src/main/java/net/bytebuddy/agent/builder/SWDescriptionStrategy.java index 62e8a0606c..0d10a039bb 100644 --- a/apm-sniffer/bytebuddy-patch/src/main/java/net/bytebuddy/agent/builder/SWDescriptionStrategy.java +++ b/apm-sniffer/bytebuddy-patch/src/main/java/net/bytebuddy/agent/builder/SWDescriptionStrategy.java @@ -34,10 +34,12 @@ import java.io.Serializable; import java.lang.annotation.Annotation; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -125,9 +127,16 @@ static class SWTypeDescriptionWrapper extends TypeDescription.AbstractBase imple /** * Original type cache. - * classloader hashcode -> ( typeName -> type cache ) + * ClassLoader -> (typeName -> TypeCache) + * Using WeakHashMap to automatically clean up cache when ClassLoader is garbage collected. */ - private static final Map> CLASS_LOADER_TYPE_CACHE = new ConcurrentHashMap<>(); + private static final Map> CLASS_LOADER_TYPE_CACHE = + Collections.synchronizedMap(new WeakHashMap<>()); + + /** + * Bootstrap ClassLoader cache (null key cannot be stored in WeakHashMap) + */ + private static final Map BOOTSTRAP_TYPE_CACHE = new ConcurrentHashMap<>(); private static final List IGNORED_INTERFACES = Arrays.asList(EnhancedInstance.class.getName()); @@ -153,10 +162,15 @@ public SWTypeDescriptionWrapper(TypeDescription delegate, String nameTrait, Clas } private TypeCache getTypeCache() { - int classLoaderHashCode = classLoader != null ? classLoader.hashCode() : 0; - Map typeCacheMap = CLASS_LOADER_TYPE_CACHE.computeIfAbsent(classLoaderHashCode, k -> new ConcurrentHashMap<>()); - TypeCache typeCache = typeCacheMap.computeIfAbsent(typeName, k -> new TypeCache(typeName)); - return typeCache; + if (classLoader == null) { + // Bootstrap ClassLoader - use separate cache since null key cannot be stored in WeakHashMap + return BOOTSTRAP_TYPE_CACHE.computeIfAbsent(typeName, k -> new TypeCache(typeName)); + } else { + // Regular ClassLoader - use WeakHashMap for automatic cleanup + Map typeCacheMap = CLASS_LOADER_TYPE_CACHE.computeIfAbsent( + classLoader, k -> new ConcurrentHashMap<>()); + return typeCacheMap.computeIfAbsent(typeName, k -> new TypeCache(typeName)); + } } @Override diff --git a/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java b/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java new file mode 100644 index 0000000000..90f9e4a437 --- /dev/null +++ b/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java @@ -0,0 +1,239 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 net.bytebuddy.agent.builder; + +import org.junit.Test; +import org.junit.Assert; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URLClassLoader; +import java.net.URL; +import java.util.Map; + +/** + * Tests the behavior of the WeakHashMap caching mechanism in SWDescriptionStrategy. + */ +public class SWDescriptionStrategyCacheTest { + + @Test + public void testWeakHashMapCacheCleanup() throws Exception { + // Get static cache field + Field cacheField = SWDescriptionStrategy.SWTypeDescriptionWrapper.class + .getDeclaredField("CLASS_LOADER_TYPE_CACHE"); + cacheField.setAccessible(true); + @SuppressWarnings("unchecked") + Map> cache = + (Map>) cacheField.get(null); + + // Record initial cache size + int initialCacheSize = cache.size(); + + // Create test ClassLoader + URLClassLoader testClassLoader = new URLClassLoader(new URL[0], null); + String testTypeName = "com.test.DynamicClass"; + + // Create SWTypeDescriptionWrapper instance + SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper = + new SWDescriptionStrategy.SWTypeDescriptionWrapper( + null, "test", testClassLoader, testTypeName); + + // Call getTypeCache method via reflection to trigger caching + Method getTypeCacheMethod = wrapper.getClass() + .getDeclaredMethod("getTypeCache"); + getTypeCacheMethod.setAccessible(true); + SWDescriptionStrategy.TypeCache typeCache = + (SWDescriptionStrategy.TypeCache) getTypeCacheMethod.invoke(wrapper); + + // Verify that the ClassLoader exists in cache + Assert.assertTrue("Cache should contain the test ClassLoader", + cache.containsKey(testClassLoader)); + Assert.assertNotNull("TypeCache should be created", typeCache); + Assert.assertEquals("Cache size should increase by 1", + initialCacheSize + 1, cache.size()); + + // Clear ClassLoader references, prepare for GC test + testClassLoader = null; + wrapper = null; + typeCache = null; + + // Force garbage collection + System.gc(); + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + + // WeakHashMap should automatically clean up garbage collected ClassLoader entries + int attempts = 0; + int currentCacheSize = cache.size(); + while (currentCacheSize > initialCacheSize && attempts < 20) { + System.gc(); + Thread.sleep(50); + currentCacheSize = cache.size(); + attempts++; + } + + System.out.println("Cache size after GC: " + currentCacheSize + + " (initial: " + initialCacheSize + ", attempts: " + attempts + ")"); + + // Verify that WeakHashMap cleanup mechanism works properly + Assert.assertTrue("WeakHashMap should clean up entries or attempts should be reasonable", + currentCacheSize <= initialCacheSize + 1 && attempts < 20); + } + + @Test + public void testBootstrapClassLoaderHandling() throws Exception { + // Get Bootstrap ClassLoader cache field + Field bootstrapCacheField = SWDescriptionStrategy.SWTypeDescriptionWrapper.class + .getDeclaredField("BOOTSTRAP_TYPE_CACHE"); + bootstrapCacheField.setAccessible(true); + @SuppressWarnings("unchecked") + Map bootstrapCache = + (Map) bootstrapCacheField.get(null); + + int initialBootstrapCacheSize = bootstrapCache.size(); + + // Test Bootstrap ClassLoader (null) handling + String testTypeName = "test.BootstrapClass"; + SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper = + new SWDescriptionStrategy.SWTypeDescriptionWrapper( + null, "test", null, testTypeName); + + // Call getTypeCache method via reflection + Method getTypeCacheMethod = wrapper.getClass() + .getDeclaredMethod("getTypeCache"); + getTypeCacheMethod.setAccessible(true); + SWDescriptionStrategy.TypeCache typeCache = + (SWDescriptionStrategy.TypeCache) getTypeCacheMethod.invoke(wrapper); + + // Verify Bootstrap ClassLoader cache handling + Assert.assertNotNull("TypeCache should be created for bootstrap classloader", typeCache); + Assert.assertTrue("Bootstrap cache should contain the type", + bootstrapCache.containsKey(testTypeName)); + Assert.assertEquals("Bootstrap cache size should increase by 1", + initialBootstrapCacheSize + 1, bootstrapCache.size()); + } + + @Test + public void testMultipleClassLoadersIndependentCache() throws Exception { + Field cacheField = SWDescriptionStrategy.SWTypeDescriptionWrapper.class + .getDeclaredField("CLASS_LOADER_TYPE_CACHE"); + cacheField.setAccessible(true); + @SuppressWarnings("unchecked") + Map> cache = + (Map>) cacheField.get(null); + + int initialCacheSize = cache.size(); + + // Create two different ClassLoaders + URLClassLoader classLoader1 = new URLClassLoader(new URL[0], null); + URLClassLoader classLoader2 = new URLClassLoader(new URL[0], null); + String testTypeName = "com.test.SameClassName"; + + // Create TypeCache with same class name for both ClassLoaders + SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper1 = + new SWDescriptionStrategy.SWTypeDescriptionWrapper( + null, "test", classLoader1, testTypeName); + SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper2 = + new SWDescriptionStrategy.SWTypeDescriptionWrapper( + null, "test", classLoader2, testTypeName); + + // Call getTypeCache method via reflection + Method getTypeCacheMethod = + SWDescriptionStrategy.SWTypeDescriptionWrapper.class.getDeclaredMethod("getTypeCache"); + getTypeCacheMethod.setAccessible(true); + + SWDescriptionStrategy.TypeCache typeCache1 = + (SWDescriptionStrategy.TypeCache) getTypeCacheMethod.invoke(wrapper1); + SWDescriptionStrategy.TypeCache typeCache2 = + (SWDescriptionStrategy.TypeCache) getTypeCacheMethod.invoke(wrapper2); + + // Verify that the two ClassLoaders have independent cache entries + Assert.assertNotNull("TypeCache1 should be created", typeCache1); + Assert.assertNotNull("TypeCache2 should be created", typeCache2); + Assert.assertNotSame("TypeCaches should be different instances", typeCache1, typeCache2); + + // Verify cache structure + Assert.assertEquals("Cache should contain both classloaders", + initialCacheSize + 2, cache.size()); + Assert.assertTrue("Cache should contain classloader1", cache.containsKey(classLoader1)); + Assert.assertTrue("Cache should contain classloader2", cache.containsKey(classLoader2)); + + // Verify each ClassLoader has independent type cache + Map typeCacheMap1 = cache.get(classLoader1); + Map typeCacheMap2 = cache.get(classLoader2); + + Assert.assertNotNull("ClassLoader1 should have type cache map", typeCacheMap1); + Assert.assertNotNull("ClassLoader2 should have type cache map", typeCacheMap2); + Assert.assertNotSame("Type cache maps should be different", typeCacheMap1, typeCacheMap2); + + Assert.assertTrue("ClassLoader1 cache should contain the type", + typeCacheMap1.containsKey(testTypeName)); + Assert.assertTrue("ClassLoader2 cache should contain the type", + typeCacheMap2.containsKey(testTypeName)); + } + + @Test + public void testConcurrentAccess() throws Exception { + // Test concurrent access scenario + final String testTypeName = "com.test.ConcurrentClass"; + final int threadCount = 10; + final Thread[] threads = new Thread[threadCount]; + final URLClassLoader[] classLoaders = new URLClassLoader[threadCount]; + final SWDescriptionStrategy.TypeCache[] results = new SWDescriptionStrategy.TypeCache[threadCount]; + + // Create multiple threads to access cache simultaneously + for (int i = 0; i < threadCount; i++) { + final int index = i; + classLoaders[i] = new URLClassLoader(new URL[0], null); + threads[i] = new Thread(() -> { + try { + SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper = + new SWDescriptionStrategy.SWTypeDescriptionWrapper( + null, "test", classLoaders[index], testTypeName); + + Method getTypeCacheMethod = wrapper.getClass() + .getDeclaredMethod("getTypeCache"); + getTypeCacheMethod.setAccessible(true); + results[index] = (SWDescriptionStrategy.TypeCache) + getTypeCacheMethod.invoke(wrapper); + } catch (Exception e) { + Assert.fail("Concurrent access should not throw exception: " + e.getMessage()); + } + }); + } + + // Start all threads + for (Thread thread : threads) { + thread.start(); + } + + // Wait for all threads to complete + for (Thread thread : threads) { + thread.join(1000); // Wait at most 1 second + } + + // Verify all results + for (int i = 0; i < threadCount; i++) { + Assert.assertNotNull("Result " + i + " should not be null", results[i]); + } + + System.out.println("Concurrent access test completed successfully with " + threadCount + " threads"); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/Constants.java b/apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/biz/ChildBar.java similarity index 77% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/Constants.java rename to apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/biz/ChildBar.java index e1e8d84d65..e7ac8d49ff 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/server/Constants.java +++ b/apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/biz/ChildBar.java @@ -6,7 +6,7 @@ * (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 + * 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, @@ -14,11 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * */ -package org.apache.skywalking.apm.plugin.jetty.v9.server; +package org.apache.skywalking.apm.agent.bytebuddy.biz; + +public class ChildBar extends ParentBar { + + public String sayHelloChild() { + return "Joe"; + } -public class Constants { - public static final String FORWARD_REQUEST_FLAG = "SW_FORWARD_REQUEST_FLAG"; } diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/Constants.java b/apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/biz/ParentBar.java similarity index 77% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/Constants.java rename to apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/biz/ParentBar.java index 156bb0517b..6113b4c41a 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v11/server/Constants.java +++ b/apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/biz/ParentBar.java @@ -6,7 +6,7 @@ * (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 + * 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, @@ -14,11 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * */ -package org.apache.skywalking.apm.plugin.jetty.v11.server; +package org.apache.skywalking.apm.agent.bytebuddy.biz; + +public class ParentBar { -public class Constants { - public static final String FORWARD_REQUEST_FLAG = "SW_FORWARD_REQUEST_FLAG"; + public String sayHelloParent() { + return "Joe"; + } } diff --git a/apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/cases/AbstractInterceptTest.java b/apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/cases/AbstractInterceptTest.java index ba9cf4d372..627fc8888d 100644 --- a/apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/cases/AbstractInterceptTest.java +++ b/apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/cases/AbstractInterceptTest.java @@ -24,6 +24,7 @@ import net.bytebuddy.agent.builder.SWAgentBuilderDefault; import net.bytebuddy.agent.builder.SWDescriptionStrategy; import net.bytebuddy.agent.builder.SWNativeMethodStrategy; +import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.SWImplementationContextFactory; @@ -38,6 +39,7 @@ import org.apache.skywalking.apm.agent.bytebuddy.SWAuxiliaryTypeNamingStrategy; import org.apache.skywalking.apm.agent.bytebuddy.SWClassFileLocator; import org.apache.skywalking.apm.agent.bytebuddy.biz.BizFoo; +import org.apache.skywalking.apm.agent.bytebuddy.biz.ChildBar; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.junit.Assert; import org.junit.BeforeClass; @@ -58,11 +60,15 @@ import static net.bytebuddy.jar.asm.Opcodes.ACC_PRIVATE; import static net.bytebuddy.jar.asm.Opcodes.ACC_VOLATILE; import static org.apache.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine.CONTEXT_ATTR_NAME; +import static org.apache.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine.CONTEXT_GETTER_NAME; +import static org.apache.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine.CONTEXT_SETTER_NAME; public class AbstractInterceptTest { public static final String BIZ_FOO_CLASS_NAME = "org.apache.skywalking.apm.agent.bytebuddy.biz.BizFoo"; public static final String PROJECT_SERVICE_CLASS_NAME = "org.apache.skywalking.apm.agent.bytebuddy.biz.ProjectService"; public static final String DOC_SERVICE_CLASS_NAME = "org.apache.skywalking.apm.agent.bytebuddy.biz.DocService"; + public static final String PARENT_BAR_CLASS_NAME = "org.apache.skywalking.apm.agent.bytebuddy.biz.ParentBar"; + public static final String CHILD_BAR_CLASS_NAME = "org.apache.skywalking.apm.agent.bytebuddy.biz.ChildBar"; public static final String SAY_HELLO_METHOD = "sayHello"; public static final int BASE_INT_VALUE = 100; public static final String CONSTRUCTOR_INTERCEPTOR_CLASS = "constructorInterceptorClass"; @@ -85,6 +91,20 @@ protected void failed(Throwable e, Description description) { } }; + protected static void callBar(int round) { + Log.info("-------------"); + Log.info("callChildBar: " + round); + // load target class + String strResultChild = new ChildBar().sayHelloChild(); + Log.info("result: " + strResultChild); + + String strResultParent = new ChildBar().sayHelloParent(); + Log.info("result: " + strResultParent); + + Assert.assertEquals("String value is unexpected", "John", strResultChild); + Assert.assertEquals("String value is unexpected", "John", strResultParent); + } + protected static void callBizFoo(int round) { Log.info("-------------"); Log.info("callBizFoo: " + round); @@ -115,7 +135,7 @@ protected static void checkConstructorInterceptor(String className, int round) { protected static void checkInterface(Class testClass, Class interfaceCls) { Assert.assertTrue("Check interface failure, the test class: " + testClass + " does not implement the expected interface: " + interfaceCls, - EnhancedInstance.class.isAssignableFrom(BizFoo.class)); + EnhancedInstance.class.isAssignableFrom(testClass)); } protected static void checkErrors() { @@ -195,6 +215,9 @@ protected void installInterface(String className) { builder = builder.defineField( CONTEXT_ATTR_NAME, Object.class, ACC_PRIVATE | ACC_VOLATILE) .implement(EnhancedInstance.class) + .defineMethod(CONTEXT_GETTER_NAME, Object.class, Visibility.PUBLIC) + .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME)) + .defineMethod(CONTEXT_SETTER_NAME, void.class, Visibility.PUBLIC).withParameters(Object.class) .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME)); } return builder; @@ -223,7 +246,7 @@ protected void installTraceClassTransformer(String msg) { ClassFileTransformer classFileTransformer = new ClassFileTransformer() { @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { - if (className.endsWith("BizFoo") || className.endsWith("ProjectService") || className.endsWith("DocService")) { + if (className.endsWith("BizFoo") || className.endsWith("ProjectService") || className.endsWith("DocService") || className.endsWith("ChildBar") || className.endsWith("ParentBar")) { Log.error(msg + className); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ClassReader cr = new ClassReader(classfileBuffer); diff --git a/apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/cases/ReTransform4Test.java b/apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/cases/ReTransform4Test.java new file mode 100644 index 0000000000..877dd416e7 --- /dev/null +++ b/apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/cases/ReTransform4Test.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.agent.bytebuddy.cases; + +import net.bytebuddy.agent.ByteBuddyAgent; +import org.apache.skywalking.apm.agent.bytebuddy.biz.ChildBar; +import org.apache.skywalking.apm.agent.bytebuddy.biz.ParentBar; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.junit.Test; + +import java.lang.instrument.Instrumentation; + +public class ReTransform4Test extends AbstractReTransformTest { + + @Test + public void testInterceptConstructor() throws Exception { + Instrumentation instrumentation = ByteBuddyAgent.install(); + + // install transformer + installMethodInterceptor(PARENT_BAR_CLASS_NAME, "sayHelloParent", 1); + installMethodInterceptor(CHILD_BAR_CLASS_NAME, "sayHelloChild", 1); + // implement EnhancedInstance + installInterface(PARENT_BAR_CLASS_NAME); + installInterface(CHILD_BAR_CLASS_NAME); + + // call target class + callBar(1); + + // check interceptors + checkInterface(ParentBar.class, EnhancedInstance.class); + checkInterface(ChildBar.class, EnhancedInstance.class); + checkErrors(); + + installTraceClassTransformer("Trace class: "); + + // do retransform + reTransform(instrumentation, ChildBar.class); + + // check interceptors + checkInterface(ParentBar.class, EnhancedInstance.class); + checkInterface(ChildBar.class, EnhancedInstance.class); + checkErrors(); + } + +} + diff --git a/apm-sniffer/config/agent.config b/apm-sniffer/config/agent.config index 65b95a6fbd..b9101f1f1c 100755 --- a/apm-sniffer/config/agent.config +++ b/apm-sniffer/config/agent.config @@ -36,6 +36,9 @@ agent.authentication=${SW_AGENT_AUTHENTICATION:} # The max number of TraceSegmentRef in a single span to keep memory cost estimatable. agent.trace_segment_ref_limit_per_span=${SW_TRACE_SEGMENT_LIMIT:500} +# The max number of logs in a single span to keep memory cost estimatable. +agent.log_limit_per_span=${SW_LOG_LIMIT_PER_SPAN:500} + # The max amount of spans in a single segment. # Through this config item, SkyWalking keep your application memory cost estimated. agent.span_limit_per_segment=${SW_AGENT_SPAN_LIMIT:300} @@ -164,6 +167,12 @@ profile.duration=${SW_AGENT_PROFILE_DURATION:10} profile.dump_max_stack_depth=${SW_AGENT_PROFILE_DUMP_MAX_STACK_DEPTH:500} # Snapshot transport to backend buffer size profile.snapshot_transport_buffer_size=${SW_AGENT_PROFILE_SNAPSHOT_TRANSPORT_BUFFER_SIZE:4500} +# If true, async profiler will be enabled when user creates a new async profiler task. If false, it will be disabled. The default value is true. +asyncprofiler.active=${SW_AGENT_ASYNC_PROFILER_ACTIVE:true} +# Max execution time(second) for the Async Profiler. The task will be stopped even if a longer time is specified. default 20min. +asyncprofiler.max_duration=${SW_AGENT_ASYNC_PROFILER_MAX_DURATION:1200} +# Path for the JFR outputs from the Async Profiler. If the parameter is not empty, the file will be created in the specified directory, otherwise the Files.createTemp method will be used to create the file. +asyncprofiler.output_path=${SW_AGENT_ASYNC_PROFILER_OUTPUT_PATH:} # If true, the agent collects and reports metrics to the backend. meter.active=${SW_METER_ACTIVE:true} # Report meters interval. The unit is second @@ -209,8 +218,10 @@ plugin.jdkthreading.threading_class_prefixes=${SW_PLUGIN_JDKTHREADING_THREADING_ plugin.tomcat.collect_http_params=${SW_PLUGIN_TOMCAT_COLLECT_HTTP_PARAMS:false} # This config item controls that whether the SpringMVC plugin should collect the parameters of the request, when your Spring application is based on Tomcat, consider only setting either `plugin.tomcat.collect_http_params` or `plugin.springmvc.collect_http_params`. Also, activate implicitly in the profiled trace. plugin.springmvc.collect_http_params=${SW_PLUGIN_SPRINGMVC_COLLECT_HTTP_PARAMS:false} -# This config item controls that whether the HttpClient plugin should collect the parameters of the request +# This config item controls that whether the HttpClient plugin should collect the parameters of the request plugin.httpclient.collect_http_params=${SW_PLUGIN_HTTPCLIENT_COLLECT_HTTP_PARAMS:false} +# Comma-separated list of destination ports whose outbound HTTP requests will be completely skipped by the httpclient-4.x interceptor (no exit span created, no sw8 headers injected). +plugin.httpclient.propagation_exclude_ports=${SW_PLUGIN_HTTPCLIENT_PROPAGATION_EXCLUDE_PORTS:} # When `COLLECT_HTTP_PARAMS` is enabled, how many characters to keep and send to the OAP backend, use negative values to keep and send the complete parameters, NB. this config item is added for the sake of performance. plugin.http.http_params_length_threshold=${SW_PLUGIN_HTTP_HTTP_PARAMS_LENGTH_THRESHOLD:1024} # When `include_http_headers` declares header names, this threshold controls the length limitation of all header values. use negative values to keep and send the complete headers. Note. this config item is added for the sake of performance. @@ -320,3 +331,43 @@ plugin.nettyhttp.supported_content_types_prefix=${SW_PLUGIN_NETTYHTTP_SUPPORTED_ plugin.rocketmqclient.collect_message_keys=${SW_PLUGIN_ROCKETMQCLIENT_COLLECT_MESSAGE_KEYS:false} # If set to true, the tags of messages would be collected by the plugin for RocketMQ Java client. plugin.rocketmqclient.collect_message_tags=${SW_PLUGIN_ROCKETMQCLIENT_COLLECT_MESSAGE_TAGS:false} +# Define the max length of collected HTTP parameters. The default value(=0) means not collecting. +plugin.solon.http_params_length_threshold=${SW_PLUGIN_SOLON_HTTP_PARAMS_LENGTH_THRESHOLD:0} +# It controls what header data should be collected, values must be in lower case, if empty, no header data will be collected. default is empty. +plugin.solon.include_http_headers=${SW_PLUGIN_SOLON_INCLUDE_HTTP_HEADERS:} +# Define the max length of collected HTTP body. The default value(=0) means not collecting. +plugin.solon.http_body_length_threshold=${SW_PLUGIN_SOLON_HTTP_BODY_LENGTH_THRESHOLD:0} +# Specify which command should be converted to write operation +plugin.caffeine.operation_mapping_write=${SW_PLUGIN_CAFFEINE_OPERATION_MAPPING_WRITE:put,putAll,remove,clear} +# Specify which command should be converted to read operation +plugin.caffeine.operation_mapping_read=${SW_PLUGIN_CAFFEINE_OPERATION_MAPPING_READ:getIfPresent,getAllPresent,computeIfAbsent} +# Whether to collect the input messages of the GenAI request. +plugin.springai.collect_input_messages=${SW_PLUGIN_SPRINGAI_COLLECT_INPUT_MESSAGES:false} +# Whether to collect the output messages of the GenAI response. +plugin.springai.collect_output_messages=${SW_PLUGIN_SPRINGAI_COLLECT_OUTPUT_MESSAGES:false} +# The maximum characters of the collected prompt content. +# If the content exceeds this limit, it will be truncated. +# Use a negative value to represent no limit, but be aware this could cause OOM. +plugin.springai.prompt_length_limit=${SW_PLUGIN_SPRINGAI_PROMPT_LENGTH_LIMIT:1024} +# The maximum characters of the collected completion content. +# If the content exceeds this limit, it will be truncated. +# Use a negative value to represent no limit, but be aware this could cause OOM. +plugin.springai.completion_length_limit=${SW_PLUGIN_SPRINGAI_COMPLETION_LENGTH_LIMIT:1024} +# The threshold for token usage to trigger content collection. +# When set to a positive value, prompt and completion will only be collected +# if the total token usage of the request exceeds this threshold. +# This requires collect_prompt or collect_completion to be enabled first. +# Use a negative value to disable this threshold-based filtering (collect all). +plugin.springai.content_collect_threshold_tokens=${SW_PLUGIN_SPRINGAI_CONTENT_COLLECT_THRESHOLD_TOKENS:-1} +# Whether to collect the arguments (input parameters) of the tool/function call. +plugin.springai.collect_tool_input=${SW_PLUGIN_SPRINGAI_COLLECT_TOOL_INPUT:false} +# Whether to collect the execution result (output) of the tool/function call. +plugin.springai.collect_tool_output=${SW_PLUGIN_SPRINGAI_COLLECT_TOOL_OUTPUT:false} +# Whether to collect the query of the rag call. +plugin.springai.collect_retrieval_query=${SW_PLUGIN_SPRINGAI_COLLECT_RETRIEVAL_QUERY:false} +# The maximum characters of the collected rag query. +# If the content exceeds this limit, it will be truncated. +# Use a negative value to represent no limit, but be aware this could cause OOM. +plugin.springai.retrieval_query_length_limit=${SW_PLUGIN_SPRINGAI_RETRIEVAL_QUERY_LENGTH_LIMIT:1024} +# Whether to collect the documents of the rag call. +plugin.springai.collect_retrieval_documents=${SW_PLUGIN_SPRINGAI_COLLECT_RETRIEVAL_DOCUMENTS:false} \ No newline at end of file diff --git a/apm-sniffer/expired-plugins/impala-jdbc-2.6.x-plugin/pom.xml b/apm-sniffer/expired-plugins/impala-jdbc-2.6.x-plugin/pom.xml index 38b2880c74..5dfd4ed7ab 100644 --- a/apm-sniffer/expired-plugins/impala-jdbc-2.6.x-plugin/pom.xml +++ b/apm-sniffer/expired-plugins/impala-jdbc-2.6.x-plugin/pom.xml @@ -24,7 +24,7 @@ org.apache.skywalking expired-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-impala-jdbc-2.6.x-plugin diff --git a/apm-sniffer/expired-plugins/pom.xml b/apm-sniffer/expired-plugins/pom.xml index 817506785a..4385464c61 100644 --- a/apm-sniffer/expired-plugins/pom.xml +++ b/apm-sniffer/expired-plugins/pom.xml @@ -21,7 +21,7 @@ java-agent-sniffer org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -32,10 +32,7 @@ ${shade.package}.${shade.net.bytebuddy.source} UTF-8 - - ${project.build.directory}${sdk.plugin.related.dir}/../../../../skywalking-agent - - ${agent.package.dest.dir}/expired-plugins + ${maven.multiModuleProjectDirectory}/skywalking-agent/expired-plugins 1.0b3 1.8.1 diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/pom.xml b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/pom.xml similarity index 73% rename from apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/pom.xml rename to apm-sniffer/optional-plugins/caffeine-3.x-plugin/pom.xml index 48b7ce02ff..89d8d96b65 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/pom.xml @@ -18,28 +18,28 @@ --> + 4.0.0 - apm-sdk-plugin org.apache.skywalking - 9.3.0-SNAPSHOT + optional-plugins + 9.7.0-SNAPSHOT - 4.0.0 - tomcat-10x-plugin - 8.11.0 + apm-caffeine-3.x-plugin + jar + caffeine-3.x-plugin - 8 - 8 - 10.0.22 + UTF-8 + 3.1.8 + - org.apache.tomcat.embed - tomcat-embed-core - ${tomcat.version} + com.github.ben-manes.caffeine + caffeine + ${caffeine.version} provided - diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/AbstractCaffeineInterceptor.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/AbstractCaffeineInterceptor.java new file mode 100644 index 0000000000..9c66cabd08 --- /dev/null +++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/AbstractCaffeineInterceptor.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.caffeine.v3; + +import java.lang.reflect.Method; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +import static org.apache.skywalking.apm.plugin.caffeine.v3.CaffeineOperationTransform.transformOperation; + +abstract public class AbstractCaffeineInterceptor implements InstanceMethodsAroundInterceptor { + + protected AbstractSpan generateSpanInfo(String methodName) { + AbstractSpan span = ContextManager.createLocalSpan("Caffeine/" + methodName); + span.setComponent(ComponentsDefine.CAFFEINE); + Tags.CACHE_TYPE.set(span, ComponentsDefine.CAFFEINE.getName()); + Tags.CACHE_CMD.set(span, methodName); + transformOperation(methodName).ifPresent(op -> Tags.CACHE_OP.set(span, op)); + SpanLayer.asCache(span); + return span; + } + + @Override + public void beforeMethod(final EnhancedInstance objInst, + final Method method, + final Object[] allArguments, + final Class[] argumentsTypes, + final MethodInterceptResult result) throws Throwable { + } + + @Override + public Object afterMethod(final EnhancedInstance objInst, + final Method method, + final Object[] allArguments, + final Class[] argumentsTypes, + final Object ret) throws Throwable { + ContextManager.stopSpan(); + return ret; + } + + @Override + public void handleMethodException(final EnhancedInstance objInst, + final Method method, + final Object[] allArguments, + final Class[] argumentsTypes, + final Throwable t) { + ContextManager.activeSpan().log(t); + } +} diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineIterableInterceptor.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineIterableInterceptor.java new file mode 100644 index 0000000000..ef5860e46f --- /dev/null +++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineIterableInterceptor.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.caffeine.v3; + +import java.lang.reflect.Method; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; + +public class CaffeineIterableInterceptor extends AbstractCaffeineInterceptor { + + @Override + public void beforeMethod(final EnhancedInstance objInst, + final Method method, + final Object[] allArguments, + final Class[] argumentsTypes, + final MethodInterceptResult result) throws Throwable { + AbstractSpan span = generateSpanInfo(method.getName()); + if (allArguments != null && allArguments.length > 0) { + String keys = StreamSupport + .stream(((Iterable) allArguments[0]).spliterator(), false) + .map(String::valueOf) + .collect(Collectors.joining(",")); + Tags.CACHE_KEY.set(span, keys); + } + } +} diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineMapInterceptor.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineMapInterceptor.java new file mode 100644 index 0000000000..98258bbb38 --- /dev/null +++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineMapInterceptor.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.caffeine.v3; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; + +public class CaffeineMapInterceptor extends AbstractCaffeineInterceptor { + + @Override + public void beforeMethod(final EnhancedInstance objInst, + final Method method, + final Object[] allArguments, + final Class[] argumentsTypes, + final MethodInterceptResult result) throws Throwable { + AbstractSpan span = generateSpanInfo(method.getName()); + if (allArguments != null && allArguments.length > 0) { + String keys = ((Map) allArguments[0]) + .keySet().stream().map(String::valueOf) + .collect(Collectors.joining(",")); + Tags.CACHE_KEY.set(span, keys); + } + } +} diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineOperationTransform.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineOperationTransform.java new file mode 100644 index 0000000000..0f4a128a92 --- /dev/null +++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineOperationTransform.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.caffeine.v3; + +import java.util.Optional; + +public class CaffeineOperationTransform { + + public static Optional transformOperation(String cmd) { + if (CaffeinePluginConfig.Plugin.Caffeine.OPERATION_MAPPING_READ.contains(cmd)) { + return Optional.of("read"); + } + if (CaffeinePluginConfig.Plugin.Caffeine.OPERATION_MAPPING_WRITE.contains(cmd)) { + return Optional.of("write"); + } + return Optional.empty(); + } +} diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeinePluginConfig.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeinePluginConfig.java new file mode 100644 index 0000000000..f775668bee --- /dev/null +++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeinePluginConfig.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.caffeine.v3; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import org.apache.skywalking.apm.agent.core.boot.PluginConfig; + +/** + * Operation represent a cache span is "write" or "read" action , and "op"(operation) is tagged with key "cache.op" + * usually This config term define which command should be converted to write Operation . + * + * @see org.apache.skywalking.apm.agent.core.context.tag.Tags#CACHE_OP + * @see CaffeineOperationTransform#transformOperation(String) + */ +public class CaffeinePluginConfig { + public static class Plugin { + @PluginConfig(root = CaffeinePluginConfig.class) + public static class Caffeine { + public static Set OPERATION_MAPPING_WRITE = new HashSet<>(Arrays.asList( + "put", + "putAll", + "remove", + "clear" + )); + public static Set OPERATION_MAPPING_READ = new HashSet<>(Arrays.asList( + "getIfPresent", + "getAllPresent", + "computeIfAbsent" + )); + } + } +} \ No newline at end of file diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineStringInterceptor.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineStringInterceptor.java new file mode 100644 index 0000000000..82b34ca48d --- /dev/null +++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineStringInterceptor.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.caffeine.v3; + +import java.lang.reflect.Method; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; + +public class CaffeineStringInterceptor extends AbstractCaffeineInterceptor { + + @Override + public void beforeMethod(final EnhancedInstance objInst, + final Method method, + final Object[] allArguments, + final Class[] argumentsTypes, + final MethodInterceptResult result) throws Throwable { + AbstractSpan span = generateSpanInfo(method.getName()); + if (allArguments != null && allArguments.length > 0 && allArguments[0] instanceof String) { + Tags.CACHE_KEY.set(span, allArguments[0].toString()); + } + } +} diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/define/CaffeineInstrumentation.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/define/CaffeineInstrumentation.java new file mode 100644 index 0000000000..50e5b3751a --- /dev/null +++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/define/CaffeineInstrumentation.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.caffeine.v3.define; + +import java.util.Map; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch.byMultiClassMatch; + +public class CaffeineInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + public static final String BOUNDED_LOCAL_INTERCEPT_CLASS = "com.github.benmanes.caffeine.cache.BoundedLocalCache"; + public static final String UNBOUNDED_LOCAL_INTERCEPT_CLASS = "com.github.benmanes.caffeine.cache.UnboundedLocalCache"; + public static final String CAFFEINE_ITERABLE_ENHANCE_CLASS = "org.apache.skywalking.apm.plugin.caffeine.v3.CaffeineIterableInterceptor"; + public static final String CAFFEINE_MAP_ENHANCE_CLASS = "org.apache.skywalking.apm.plugin.caffeine.v3.CaffeineMapInterceptor"; + public static final String CAFFEINE_STRING_ENHANCE_CLASS = "org.apache.skywalking.apm.plugin.caffeine.v3.CaffeineStringInterceptor"; + + // read/write operations + public static final String GET_IF_PRESENT_METHOD = "getIfPresent"; + public static final String GET_ALL_PRESENT_METHOD = "getAllPresent"; + public static final String COMPUTE_IF_ABSENT_METHOD = "computeIfAbsent"; + public static final String PUT_METHOD = "put"; + public static final String PUT_ALL_METHOD = "putAll"; + public static final String REMOVE_METHOD = "remove"; + public static final String CLEAR_METHOD = "clear"; + + @Override + protected ClassMatch enhanceClass() { + return byMultiClassMatch(BOUNDED_LOCAL_INTERCEPT_CLASS, UNBOUNDED_LOCAL_INTERCEPT_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(GET_IF_PRESENT_METHOD) + .and(takesArguments(2)) + .or(named(COMPUTE_IF_ABSENT_METHOD).and(takesArguments(4))) + .or(named(PUT_METHOD).and(takesArguments(2))) + .or(named(REMOVE_METHOD).and(takesArguments(1))) + .or(named(CLEAR_METHOD).and(takesArguments(0))); + } + + @Override + public String getMethodsInterceptor() { + return CAFFEINE_STRING_ENHANCE_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(GET_ALL_PRESENT_METHOD).and(takesArgument(0, Iterable.class)); + } + + @Override + public String getMethodsInterceptor() { + return CAFFEINE_ITERABLE_ENHANCE_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(PUT_ALL_METHOD).and(takesArgument(0, Map.class)); + } + + @Override + public String getMethodsInterceptor() { + return CAFFEINE_MAP_ENHANCE_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..1edf1e3a0e --- /dev/null +++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +caffeine-3.x=org.apache.skywalking.apm.plugin.caffeine.v3.define.CaffeineInstrumentation diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineInterceptorTest.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineInterceptorTest.java new file mode 100644 index 0000000000..7642ae110d --- /dev/null +++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineInterceptorTest.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.caffeine.v3; + +import com.github.benmanes.caffeine.cache.Expiry; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import static org.hamcrest.CoreMatchers.is; + +@RunWith(TracingSegmentRunner.class) +public class CaffeineInterceptorTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + private CaffeineIterableInterceptor caffeineIterableInterceptor; + private CaffeineMapInterceptor caffeineMapInterceptor; + private CaffeineStringInterceptor caffeineStringInterceptor; + private Object[] operateObjectArguments; + + private Exception exception; + + private Method getAllPresentMethod; + private Method getIfPresentMethod; + private Method computeIfAbsentMethod; + + private Method putMethod; + private Method putAllMethod; + private Method removeMethod; + private Method cleanMethod; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @Before + public void setUp() throws Exception { + caffeineIterableInterceptor = new CaffeineIterableInterceptor(); + caffeineMapInterceptor = new CaffeineMapInterceptor(); + caffeineStringInterceptor = new CaffeineStringInterceptor(); + exception = new Exception(); + operateObjectArguments = new Object[] {"dataKey"}; + Class cache = Class.forName("com.github.benmanes.caffeine.cache.BoundedLocalCache"); + getAllPresentMethod = cache.getDeclaredMethod("getAllPresent", Iterable.class); + getIfPresentMethod = cache.getDeclaredMethod("getIfPresent", Object.class, boolean.class); + computeIfAbsentMethod = cache.getDeclaredMethod( + "computeIfAbsent", Object.class, Function.class, boolean.class, boolean.class); + putMethod = cache.getDeclaredMethod("put", Object.class, Object.class, Expiry.class, boolean.class); + putAllMethod = cache.getDeclaredMethod("putAll", Map.class); + removeMethod = cache.getDeclaredMethod("remove", Object.class); + cleanMethod = cache.getDeclaredMethod("clear"); + } + + @Test + public void testGetAllPresentSuccess() throws Throwable { + caffeineIterableInterceptor.beforeMethod(null, getAllPresentMethod, null, null, null); + caffeineIterableInterceptor.handleMethodException(null, getAllPresentMethod, null, null, exception); + caffeineIterableInterceptor.afterMethod(null, getAllPresentMethod, null, null, null); + List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertThat(traceSegments.size(), is(1)); + } + + @Test + public void testGetIfPresentSuccess() throws Throwable { + caffeineStringInterceptor.beforeMethod(null, getIfPresentMethod, operateObjectArguments, null, null); + caffeineStringInterceptor.handleMethodException( + null, getIfPresentMethod, operateObjectArguments, null, exception); + caffeineStringInterceptor.afterMethod(null, getIfPresentMethod, operateObjectArguments, null, null); + List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertThat(traceSegments.size(), is(1)); + } + + @Test + public void testComputeIfAbsentMethodSuccess() throws Throwable { + caffeineStringInterceptor.beforeMethod(null, computeIfAbsentMethod, null, null, null); + caffeineStringInterceptor.handleMethodException(null, computeIfAbsentMethod, null, null, exception); + caffeineStringInterceptor.afterMethod(null, computeIfAbsentMethod, null, null, null); + List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertThat(traceSegments.size(), is(1)); + } + + @Test + public void testPutMethodSuccess() throws Throwable { + caffeineStringInterceptor.beforeMethod(null, putMethod, operateObjectArguments, null, null); + caffeineStringInterceptor.handleMethodException(null, putMethod, operateObjectArguments, null, exception); + caffeineStringInterceptor.afterMethod(null, putMethod, operateObjectArguments, null, null); + List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertThat(traceSegments.size(), is(1)); + } + + @Test + public void testPutAllMethodSuccess() throws Throwable { + caffeineMapInterceptor.beforeMethod(null, putAllMethod, null, null, null); + caffeineMapInterceptor.handleMethodException(null, putAllMethod, null, null, exception); + caffeineMapInterceptor.afterMethod(null, putAllMethod, null, null, null); + List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertThat(traceSegments.size(), is(1)); + } + + @Test + public void testRemoveMethodSuccess() throws Throwable { + caffeineStringInterceptor.beforeMethod(null, removeMethod, operateObjectArguments, null, null); + caffeineStringInterceptor.handleMethodException(null, removeMethod, operateObjectArguments, null, exception); + caffeineStringInterceptor.afterMethod(null, removeMethod, operateObjectArguments, null, null); + List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertThat(traceSegments.size(), is(1)); + } + + @Test + public void testClearMethodSuccess() throws Throwable { + caffeineStringInterceptor.beforeMethod(null, cleanMethod, null, null, null); + caffeineStringInterceptor.handleMethodException(null, cleanMethod, null, null, exception); + caffeineStringInterceptor.afterMethod(null, cleanMethod, null, null, null); + List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertThat(traceSegments.size(), is(1)); + } +} diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..1f0955d450 --- /dev/null +++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/apm-sniffer/optional-plugins/customize-enhance-plugin/pom.xml b/apm-sniffer/optional-plugins/customize-enhance-plugin/pom.xml index 2c9ee683f6..fb100e0d94 100644 --- a/apm-sniffer/optional-plugins/customize-enhance-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/customize-enhance-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/optional-plugins/customize-enhance-plugin/src/main/java/org/apache/skywalking/apm/plugin/customize/conf/CustomizeConfiguration.java b/apm-sniffer/optional-plugins/customize-enhance-plugin/src/main/java/org/apache/skywalking/apm/plugin/customize/conf/CustomizeConfiguration.java index ba48a9280e..ba3dd5f843 100644 --- a/apm-sniffer/optional-plugins/customize-enhance-plugin/src/main/java/org/apache/skywalking/apm/plugin/customize/conf/CustomizeConfiguration.java +++ b/apm-sniffer/optional-plugins/customize-enhance-plugin/src/main/java/org/apache/skywalking/apm/plugin/customize/conf/CustomizeConfiguration.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -67,6 +68,8 @@ public enum CustomizeConfiguration { private static final Map CONTEXT_ENHANCE_CLASSES = new HashMap<>(); private static final AtomicBoolean LOAD_FOR_CONFIGURATION = new AtomicBoolean(false); + private static volatile List> RESOLVED_FILE_CONFIGURATIONS; + private static final ReentrantLock RESOLVED_FILE_LOCK = new ReentrantLock(); /** * The loadForEnhance method is resolver configuration file, and parse it */ @@ -107,13 +110,26 @@ public synchronized void loadForConfiguration() { * @throws SAXException link {@link SAXException} */ private List> resolver() throws ParserConfigurationException, IOException, SAXException { - List> customizeMethods = new ArrayList>(); - File file = new File(CustomizePluginConfig.Plugin.Customize.ENHANCE_FILE); - if (file.exists() && file.isFile()) { - NodeList classNodeList = resolverFileClassDesc(file); - resolverClassNodeList(classNodeList, customizeMethods); + if (RESOLVED_FILE_CONFIGURATIONS != null) { + return RESOLVED_FILE_CONFIGURATIONS; } - return customizeMethods; + + RESOLVED_FILE_LOCK.lock(); + try { + if (RESOLVED_FILE_CONFIGURATIONS != null) { + return RESOLVED_FILE_CONFIGURATIONS; + } + List> customizeMethods = new ArrayList>(); + File file = new File(CustomizePluginConfig.Plugin.Customize.ENHANCE_FILE); + if (file.exists() && file.isFile()) { + NodeList classNodeList = resolverFileClassDesc(file); + resolverClassNodeList(classNodeList, customizeMethods); + } + RESOLVED_FILE_CONFIGURATIONS = customizeMethods; + } finally { + RESOLVED_FILE_LOCK.unlock(); + } + return RESOLVED_FILE_CONFIGURATIONS; } /** diff --git a/apm-sniffer/optional-plugins/ehcache-2.x-plugin/pom.xml b/apm-sniffer/optional-plugins/ehcache-2.x-plugin/pom.xml index 02c5ad33f5..60906ea1df 100644 --- a/apm-sniffer/optional-plugins/ehcache-2.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/ehcache-2.x-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/optional-plugins/fastjson-1.2.x-plugin/pom.xml b/apm-sniffer/optional-plugins/fastjson-1.2.x-plugin/pom.xml index f0019fa5ec..fd47548245 100644 --- a/apm-sniffer/optional-plugins/fastjson-1.2.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/fastjson-1.2.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-fastjson-1.x-plugin diff --git a/apm-sniffer/optional-plugins/gson-2.8.x-plugin/pom.xml b/apm-sniffer/optional-plugins/gson-2.8.x-plugin/pom.xml index 8211d2e4df..3cacbd71f0 100644 --- a/apm-sniffer/optional-plugins/gson-2.8.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/gson-2.8.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-gson-2.x-plugin diff --git a/apm-sniffer/optional-plugins/guava-cache-plugin/pom.xml b/apm-sniffer/optional-plugins/guava-cache-plugin/pom.xml index 79cfd12796..4b678e32ed 100644 --- a/apm-sniffer/optional-plugins/guava-cache-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/guava-cache-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-guava-cache-plugin diff --git a/apm-sniffer/optional-plugins/jackson-2.x-plugin/pom.xml b/apm-sniffer/optional-plugins/jackson-2.x-plugin/pom.xml index 75279981da..6cc5017d9c 100644 --- a/apm-sniffer/optional-plugins/jackson-2.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/jackson-2.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-jackson-2.x-plugin diff --git a/apm-sniffer/optional-plugins/kotlin-coroutine-plugin/pom.xml b/apm-sniffer/optional-plugins/kotlin-coroutine-plugin/pom.xml index 6c34690e6e..ab831b6c1b 100644 --- a/apm-sniffer/optional-plugins/kotlin-coroutine-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/kotlin-coroutine-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-kotlin-coroutine-plugin diff --git a/apm-sniffer/optional-plugins/mybatis-3.x-plugin/pom.xml b/apm-sniffer/optional-plugins/mybatis-3.x-plugin/pom.xml index b738e93ad5..7da71f0c1c 100644 --- a/apm-sniffer/optional-plugins/mybatis-3.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/mybatis-3.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-mybatis-3.x-plugin diff --git a/apm-sniffer/optional-plugins/nacos-client-2.x-plugin/pom.xml b/apm-sniffer/optional-plugins/nacos-client-2.x-plugin/pom.xml index 6da357f801..7f5ea9441e 100644 --- a/apm-sniffer/optional-plugins/nacos-client-2.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/nacos-client-2.x-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/optional-plugins/netty-http-4.1.x-plugin/pom.xml b/apm-sniffer/optional-plugins/netty-http-4.1.x-plugin/pom.xml index a135366369..b8313a57c7 100644 --- a/apm-sniffer/optional-plugins/netty-http-4.1.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/netty-http-4.1.x-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -30,14 +30,12 @@ UTF-8 - 4.1.51.Final io.netty netty-all - ${netty.version} provided diff --git a/apm-sniffer/optional-plugins/netty-http-4.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/netty/http/handler/NettyHttpRequestEncoderTracingHandler.java b/apm-sniffer/optional-plugins/netty-http-4.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/netty/http/handler/NettyHttpRequestEncoderTracingHandler.java index bb34adbaac..38c8518fb3 100644 --- a/apm-sniffer/optional-plugins/netty-http-4.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/netty/http/handler/NettyHttpRequestEncoderTracingHandler.java +++ b/apm-sniffer/optional-plugins/netty-http-4.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/netty/http/handler/NettyHttpRequestEncoderTracingHandler.java @@ -80,7 +80,6 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress(); String peer = address.getHostString() + ":" + address.getPort(); String url = peer + uri; - String method = request.method().toString(); ContextCarrier contextCarrier = new ContextCarrier(); AbstractSpan span = ContextManager.createExitSpan(NettyConstants.NETTY_HTTP_OPERATION_PREFIX + uri, contextCarrier, peer); diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/mvc-annotation-6.x-plugin/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/mvc-annotation-6.x-plugin/pom.xml index ed3daad261..a2399d0bc6 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/mvc-annotation-6.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/mvc-annotation-6.x-plugin/pom.xml @@ -19,7 +19,7 @@ optional-spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/pom.xml index c483e6b29d..b2963c4c23 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking optional-spring-cloud - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-spring-cloud-gateway-2.0.x-plugin @@ -61,7 +61,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 package diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptor.java new file mode 100644 index 0000000000..b5174773c3 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptor.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.ServerWebExchangeDecorator; +import org.springframework.web.server.adapter.DefaultServerWebExchange; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY; + +public class GatewayFilterInterceptor implements InstanceMethodsAroundInterceptor { + + private static final ThreadLocal STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0)); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + if (isEntry()) { + ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; + + EnhancedInstance enhancedInstance = getInstance(exchange); + + AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter"); + if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) { + ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField()); + } + span.setComponent(SPRING_CLOUD_GATEWAY); + } + } + + public static EnhancedInstance getInstance(Object o) { + EnhancedInstance instance = null; + if (o instanceof DefaultServerWebExchange) { + instance = (EnhancedInstance) o; + } else if (o instanceof ServerWebExchangeDecorator) { + ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate(); + return getInstance(delegate); + } + return instance; + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + if (isExit()) { + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } + + private boolean isEntry() { + return STACK_DEEP.get().getAndIncrement() == 0; + } + + private boolean isExit() { + boolean isExit = STACK_DEEP.get().decrementAndGet() == 0; + if (isExit) { + STACK_DEEP.remove(); + } + return isExit; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/AbstractGateway200EnhancePluginDefine.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/AbstractGateway200EnhancePluginDefine.java index baeb619eca..e260b8813c 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/AbstractGateway200EnhancePluginDefine.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/AbstractGateway200EnhancePluginDefine.java @@ -24,7 +24,7 @@ public abstract class AbstractGateway200EnhancePluginDefine extends ClassInstanc @Override protected String[] witnessClasses() { return new String[] { - "org.springframework.cloud.gateway.config.GatewayAutoConfiguration$1" + "org.springframework.cloud.gateway.config.GatewayAutoConfiguration" }; } } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/AbstractGateway200EnhancePluginDefineV2.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/AbstractGateway200EnhancePluginDefineV2.java index 1f07c0ba89..c42e2df60d 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/AbstractGateway200EnhancePluginDefineV2.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/AbstractGateway200EnhancePluginDefineV2.java @@ -24,7 +24,7 @@ public abstract class AbstractGateway200EnhancePluginDefineV2 extends ClassInsta @Override protected String[] witnessClasses() { return new String[] { - "org.springframework.cloud.gateway.config.GatewayAutoConfiguration$1" + "org.springframework.cloud.gateway.config.GatewayAutoConfiguration" }; } } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/Constants.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/Constants.java index 450699c959..f20a1c22a6 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/Constants.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/Constants.java @@ -29,4 +29,8 @@ public interface Constants { // HttpClientRequest String INTERCEPT_CLASS_HTTP_CLIENT_REQUEST = "reactor.ipc.netty.http.client.HttpClientRequest"; String HTTPCLIENT_REQUEST_HEADERS_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.HttpclientRequestHeadersInterceptor"; + + // GatewayFilter + String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter"; + String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.GatewayFilterInterceptor"; } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/DispatcherHandlerInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/DispatcherHandlerInstrumentation.java index be9f8668ab..1188ae1804 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/DispatcherHandlerInstrumentation.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/DispatcherHandlerInstrumentation.java @@ -23,7 +23,7 @@ public class DispatcherHandlerInstrumentation extends org.apache.skywalking.apm. @Override protected String[] witnessClasses() { return new String[] { - "org.springframework.cloud.gateway.config.GatewayAutoConfiguration$1" + "org.springframework.cloud.gateway.config.GatewayAutoConfiguration" }; } } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/GatewayFilterInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/GatewayFilterInstrumentation.java new file mode 100644 index 0000000000..5435af8a08 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/GatewayFilterInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; + +public class GatewayFilterInstrumentation extends AbstractGateway200EnhancePluginDefine { + + @Override + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(Constants.INTERCEPT_CLASS_GATEWAY_FILTER); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("filter").and( + takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange")); + } + + @Override + public String getMethodsInterceptor() { + return Constants.GATEWAY_FILTER_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/ServerWebExchangeInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/ServerWebExchangeInstrumentation.java index 029172d69d..bb2af27b54 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/ServerWebExchangeInstrumentation.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/ServerWebExchangeInstrumentation.java @@ -23,7 +23,7 @@ public class ServerWebExchangeInstrumentation extends org.apache.skywalking.apm. @Override protected String[] witnessClasses() { return new String[] { - "org.springframework.cloud.gateway.config.GatewayAutoConfiguration$1" + "org.springframework.cloud.gateway.config.GatewayAutoConfiguration" }; } } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/resources/skywalking-plugin.def index 4bd0c3f419..0e4bf641bd 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/resources/skywalking-plugin.def @@ -18,4 +18,5 @@ spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.HttpClientInstrumentation spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.HttpClientRequestInstrumentation spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.ServerWebExchangeInstrumentation -spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.DispatcherHandlerInstrumentation \ No newline at end of file +spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.DispatcherHandlerInstrumentation +spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.GatewayFilterInstrumentation \ No newline at end of file diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptorTest.java new file mode 100644 index 0000000000..3cd7bc8705 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptorTest.java @@ -0,0 +1,333 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; + +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.context.ApplicationContext; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.http.codec.multipart.Part; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import reactor.core.publisher.Mono; + +import java.security.Principal; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@RunWith(TracingSegmentRunner.class) +public class GatewayFilterInterceptorTest { + + private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter"; + + private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance { + private ContextSnapshot snapshot; + Map attributes = new HashMap<>(); + + @Override + public Object getSkyWalkingDynamicField() { + return snapshot; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.snapshot = (ContextSnapshot) value; + } + + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + } + + private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance(); + private final GatewayFilterInterceptor interceptor = new GatewayFilterInterceptor(); + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Before + public void setUp() throws Exception { + } + + private final ServerWebExchange exchange = new ServerWebExchange() { + Map attributes = new HashMap<>(); + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + }; + + @Test + public void testInterceptOnlyOnce() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null); + Assert.assertTrue(ContextManager.isActive()); + AbstractSpan activeSpan = ContextManager.activeSpan(); + Assert.assertTrue(activeSpan instanceof AbstractTracingSpan); + Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + + } + + @Test + public void testNestedInterception() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithNullDynamicField() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithContextSnapshot() throws Throwable { + final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null); + SpanLayer.asHttp(entrySpan); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture()); + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + ContextManager.stopSpan(entrySpan); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 2); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX); + SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP); + } + +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/pom.xml index 9973fe43dc..c9a0dfb040 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking optional-spring-cloud - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-spring-cloud-gateway-2.1.x-plugin @@ -61,7 +61,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 package diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptor.java new file mode 100644 index 0000000000..18b93a08d1 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptor.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.ServerWebExchangeDecorator; +import org.springframework.web.server.adapter.DefaultServerWebExchange; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY; + +public class GatewayFilterInterceptor implements InstanceMethodsAroundInterceptor { + + private static final ThreadLocal STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0)); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + if (isEntry()) { + ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; + + EnhancedInstance enhancedInstance = getInstance(exchange); + + AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter"); + if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) { + ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField()); + } + span.setComponent(SPRING_CLOUD_GATEWAY); + } + } + + public static EnhancedInstance getInstance(Object o) { + EnhancedInstance instance = null; + if (o instanceof DefaultServerWebExchange) { + instance = (EnhancedInstance) o; + } else if (o instanceof ServerWebExchangeDecorator) { + ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate(); + return getInstance(delegate); + } + return instance; + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + if (isExit()) { + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } + + private boolean isEntry() { + return STACK_DEEP.get().getAndIncrement() == 0; + } + + private boolean isExit() { + boolean isExit = STACK_DEEP.get().decrementAndGet() == 0; + if (isExit) { + STACK_DEEP.remove(); + } + return isExit; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/Constants.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/Constants.java index 1dbb09ff16..5c88626779 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/Constants.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/Constants.java @@ -33,4 +33,7 @@ public interface Constants { String INTERCEPT_CLASS_TCP_CLIENT = "reactor.netty.tcp.TcpClient"; String TCP_CLIENT_CONSTRUCTOR_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.TcpClientConstructorInterceptor"; + // GatewayFilter + String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter"; + String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.GatewayFilterInterceptor"; } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/GatewayFilterInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/GatewayFilterInstrumentation.java new file mode 100644 index 0000000000..1031478c4a --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/GatewayFilterInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; + +public class GatewayFilterInstrumentation extends AbstractGateway210EnhancePluginDefine { + + @Override + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(Constants.INTERCEPT_CLASS_GATEWAY_FILTER); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("filter").and( + takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange")); + } + + @Override + public String getMethodsInterceptor() { + return Constants.GATEWAY_FILTER_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/resources/skywalking-plugin.def index 9929db9240..8ce727bb5d 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/resources/skywalking-plugin.def @@ -19,3 +19,4 @@ spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define.TcpClientInstrumentation spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define.ServerWebExchangeInstrumentation spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define.DispatcherHandlerInstrumentation +spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define.GatewayFilterInstrumentation diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptorTest.java new file mode 100644 index 0000000000..c2250e3beb --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptorTest.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.context.ApplicationContext; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.http.codec.multipart.Part; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import reactor.core.publisher.Mono; + +import java.security.Principal; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@RunWith(TracingSegmentRunner.class) +public class GatewayFilterInterceptorTest { + + private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter"; + + private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance { + private ContextSnapshot snapshot; + Map attributes = new HashMap<>(); + + @Override + public Object getSkyWalkingDynamicField() { + return snapshot; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.snapshot = (ContextSnapshot) value; + } + + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + } + + private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance(); + private final GatewayFilterInterceptor interceptor = new GatewayFilterInterceptor(); + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Before + public void setUp() throws Exception { + } + + private final ServerWebExchange exchange = new ServerWebExchange() { + Map attributes = new HashMap<>(); + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + }; + + @Test + public void testInterceptOnlyOnce() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null); + Assert.assertTrue(ContextManager.isActive()); + AbstractSpan activeSpan = ContextManager.activeSpan(); + Assert.assertTrue(activeSpan instanceof AbstractTracingSpan); + Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + + } + + @Test + public void testNestedInterception() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithNullDynamicField() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithContextSnapshot() throws Throwable { + final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null); + SpanLayer.asHttp(entrySpan); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture()); + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + ContextManager.stopSpan(entrySpan); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 2); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX); + SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP); + } + +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/pom.xml index dae580784f..33180c560c 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/pom.xml @@ -21,7 +21,7 @@ optional-spring-cloud org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -52,7 +52,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 package diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptor.java new file mode 100644 index 0000000000..133424539b --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptor.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.ServerWebExchangeDecorator; +import org.springframework.web.server.adapter.DefaultServerWebExchange; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY; + +public class GatewayFilterInterceptor implements InstanceMethodsAroundInterceptor { + + private static final ThreadLocal STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0)); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + if (isEntry()) { + ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; + + EnhancedInstance enhancedInstance = getInstance(exchange); + + AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter"); + if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) { + ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField()); + } + span.setComponent(SPRING_CLOUD_GATEWAY); + } + } + + public static EnhancedInstance getInstance(Object o) { + EnhancedInstance instance = null; + if (o instanceof DefaultServerWebExchange) { + instance = (EnhancedInstance) o; + } else if (o instanceof ServerWebExchangeDecorator) { + ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate(); + return getInstance(delegate); + } + return instance; + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + if (isExit()) { + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } + + private boolean isEntry() { + return STACK_DEEP.get().getAndIncrement() == 0; + } + + private boolean isExit() { + boolean isExit = STACK_DEEP.get().decrementAndGet() == 0; + if (isExit) { + STACK_DEEP.remove(); + } + return isExit; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/define/GatewayFilterInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/define/GatewayFilterInstrumentation.java new file mode 100644 index 0000000000..4488516f19 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/define/GatewayFilterInstrumentation.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; + +public class GatewayFilterInstrumentation extends AbstractGatewayV3EnhancePluginDefine { + + private static final String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter"; + private static final String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.GatewayFilterInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(INTERCEPT_CLASS_GATEWAY_FILTER); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("filter").and( + takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange")); + } + + @Override + public String getMethodsInterceptor() { + return GATEWAY_FILTER_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/resources/skywalking-plugin.def index ba4b480501..10525a931a 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/resources/skywalking-plugin.def @@ -18,3 +18,4 @@ spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define.NettyRoutingFilterInstrumentation spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define.ServerWebExchangeInstrumentation spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define.DispatcherHandlerInstrumentation +spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define.GatewayFilterInstrumentation diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptorTest.java new file mode 100644 index 0000000000..db0e0e3174 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptorTest.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.context.ApplicationContext; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.http.codec.multipart.Part; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import reactor.core.publisher.Mono; + +import java.security.Principal; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@RunWith(TracingSegmentRunner.class) +public class GatewayFilterInterceptorTest { + + private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter"; + + private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance { + private ContextSnapshot snapshot; + Map attributes = new HashMap<>(); + + @Override + public Object getSkyWalkingDynamicField() { + return snapshot; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.snapshot = (ContextSnapshot) value; + } + + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + } + + private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance(); + private final GatewayFilterInterceptor interceptor = new GatewayFilterInterceptor(); + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Before + public void setUp() throws Exception { + } + + private final ServerWebExchange exchange = new ServerWebExchange() { + Map attributes = new HashMap<>(); + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + }; + + @Test + public void testInterceptOnlyOnce() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null); + Assert.assertTrue(ContextManager.isActive()); + AbstractSpan activeSpan = ContextManager.activeSpan(); + Assert.assertTrue(activeSpan instanceof AbstractTracingSpan); + Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + + } + + @Test + public void testNestedInterception() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithNullDynamicField() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithContextSnapshot() throws Throwable { + final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null); + SpanLayer.asHttp(entrySpan); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture()); + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + ContextManager.stopSpan(entrySpan); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 2); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX); + SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP); + } + +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/pom.xml index 73ed729672..bcd6ffcca4 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/pom.xml @@ -21,7 +21,7 @@ optional-spring-cloud org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -52,7 +52,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 package diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/GatewayFilterV412Interceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/GatewayFilterV412Interceptor.java new file mode 100644 index 0000000000..c1a9ec505f --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/GatewayFilterV412Interceptor.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.ServerWebExchangeDecorator; +import org.springframework.web.server.adapter.DefaultServerWebExchange; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY; + +public class GatewayFilterV412Interceptor implements InstanceMethodsAroundInterceptor { + + private static final ThreadLocal STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0)); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + if (isEntry()) { + ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; + + EnhancedInstance enhancedInstance = getInstance(exchange); + + AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter"); + if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) { + ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField()); + } + span.setComponent(SPRING_CLOUD_GATEWAY); + } + } + + public static EnhancedInstance getInstance(Object o) { + EnhancedInstance instance = null; + if (o instanceof DefaultServerWebExchange) { + instance = (EnhancedInstance) o; + } else if (o instanceof ServerWebExchangeDecorator) { + ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate(); + return getInstance(delegate); + } + return instance; + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + if (isExit()) { + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } + + private boolean isEntry() { + return STACK_DEEP.get().getAndIncrement() == 0; + } + + private boolean isExit() { + boolean isExit = STACK_DEEP.get().decrementAndGet() == 0; + if (isExit) { + STACK_DEEP.remove(); + } + return isExit; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientConnectDuplicateV412Interceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientConnectDuplicateV412Interceptor.java new file mode 100644 index 0000000000..5658134748 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientConnectDuplicateV412Interceptor.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; + +import java.lang.reflect.Method; + +public class HttpClientConnectDuplicateV412Interceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + if (objInst.getSkyWalkingDynamicField() != null) { + EnhanceObjectCache enhanceObjectCache = (EnhanceObjectCache) objInst.getSkyWalkingDynamicField(); + if (ret instanceof EnhancedInstance) { + EnhancedInstance retEnhancedInstance = (EnhancedInstance) ret; + retEnhancedInstance.setSkyWalkingDynamicField(enhanceObjectCache); + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientConnectRequestV412Interceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientConnectRequestV412Interceptor.java new file mode 100644 index 0000000000..f35f3551fb --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientConnectRequestV412Interceptor.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; + +import java.lang.reflect.Method; + +public class HttpClientConnectRequestV412Interceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + if (objInst.getSkyWalkingDynamicField() != null) { + if (ret instanceof EnhancedInstance) { + EnhancedInstance retEnhancedInstance = (EnhancedInstance) ret; + Object retSkyWalkingDynamicField = retEnhancedInstance.getSkyWalkingDynamicField(); + if (retSkyWalkingDynamicField != null) { + EnhanceObjectCache retEnhanceObjectCache = (EnhanceObjectCache) retSkyWalkingDynamicField; + EnhanceObjectCache objEnhanceObjectCache = (EnhanceObjectCache) objInst.getSkyWalkingDynamicField(); + retEnhanceObjectCache.setContextSnapshot(objEnhanceObjectCache.getContextSnapshot()); + } + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerConstructorV412Interceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerConstructorV412Interceptor.java new file mode 100644 index 0000000000..410baa61a7 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerConstructorV412Interceptor.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; +import reactor.netty.http.client.HttpClientConfig; + +/** + * Intercept the constructor and inject {@link EnhanceObjectCache}. + *

+ * The first constructor argument is {@link HttpClientConfig} class instance which can get the + * request uri string. + */ +public class HttpClientFinalizerConstructorV412Interceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + final HttpClientConfig httpClientConfig = (HttpClientConfig) allArguments[0]; + if (httpClientConfig == null) { + return; + } + final EnhanceObjectCache enhanceObjectCache = new EnhanceObjectCache(); + enhanceObjectCache.setUrl(httpClientConfig.uri()); + objInst.setSkyWalkingDynamicField(enhanceObjectCache); + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerResponseConnectionV412Interceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerResponseConnectionV412Interceptor.java new file mode 100644 index 0000000000..7199c85744 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerResponseConnectionV412Interceptor.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import io.netty.handler.codec.http.HttpResponseStatus; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.SignalType; +import reactor.netty.Connection; +import reactor.netty.http.client.HttpClientResponse; + +import java.lang.reflect.Method; +import java.util.function.BiFunction; + +/** + * This class intercept responseConnection method. + *

+ * After downstream service response, finish the span in the {@link EnhanceObjectCache}. + */ +public class HttpClientFinalizerResponseConnectionV412Interceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) { + BiFunction finalReceiver = (BiFunction) allArguments[0]; + EnhanceObjectCache cache = (EnhanceObjectCache) objInst.getSkyWalkingDynamicField(); + allArguments[0] = (BiFunction) (response, connection) -> { + Publisher publisher = finalReceiver.apply(response, connection); + if (cache == null) { + return publisher; + } + // receive the response. + if (cache.getSpan() != null) { + if (response.status().code() >= HttpResponseStatus.BAD_REQUEST.code()) { + cache.getSpan().errorOccurred(); + } + Tags.HTTP_RESPONSE_STATUS_CODE.set(cache.getSpan(), response.status().code()); + } + + return publisher; + }; + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) { + Flux responseFlux = (Flux) ret; + + responseFlux = responseFlux + .doOnError(e -> { + EnhanceObjectCache cache = (EnhanceObjectCache) objInst.getSkyWalkingDynamicField(); + if (cache == null) { + return; + } + + if (cache.getSpan() != null) { + cache.getSpan().errorOccurred(); + cache.getSpan().log(e); + } + }) + .doFinally(signalType -> { + EnhanceObjectCache cache = (EnhanceObjectCache) objInst.getSkyWalkingDynamicField(); + if (cache == null) { + return; + } + // do finally. Finish the span. + if (cache.getSpan() != null) { + if (signalType == SignalType.CANCEL) { + cache.getSpan().errorOccurred(); + } + cache.getSpan().asyncFinish(); + } + + if (cache.getSpan1() != null) { + cache.getSpan1().asyncFinish(); + } + }); + + return responseFlux; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerSendV412Interceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerSendV412Interceptor.java new file mode 100644 index 0000000000..a1bc32f329 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerSendV412Interceptor.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.context.CarrierItem; +import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; +import org.apache.skywalking.apm.util.StringUtil; +import org.reactivestreams.Publisher; +import reactor.netty.NettyOutbound; +import reactor.netty.http.client.HttpClientRequest; + +import java.lang.reflect.Method; +import java.net.URL; +import java.util.function.BiFunction; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY; + +/** + * This class intercept send method. + *

+ * In before method, create a new BiFunction lambda expression for setting ContextCarrier to http header + * and replace the original lambda in argument + */ +public class HttpClientFinalizerSendV412Interceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + EnhanceObjectCache enhanceObjectCache = (EnhanceObjectCache) objInst.getSkyWalkingDynamicField(); + if (enhanceObjectCache == null) { + return; + } + ContextSnapshot contextSnapshot = enhanceObjectCache.getContextSnapshot(); + if (contextSnapshot == null) { + return; + } + AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/send"); + ContextManager.continued(contextSnapshot); + span.setComponent(SPRING_CLOUD_GATEWAY); + span.prepareForAsync(); + + if (StringUtil.isNotEmpty(enhanceObjectCache.getUrl())) { + URL url = new URL(enhanceObjectCache.getUrl()); + + ContextCarrier contextCarrier = new ContextCarrier(); + AbstractSpan abstractSpan = ContextManager.createExitSpan( + "SpringCloudGateway/sendRequest", contextCarrier, getPeer(url)); + Tags.URL.set(abstractSpan, enhanceObjectCache.getUrl()); + abstractSpan.prepareForAsync(); + abstractSpan.setComponent(SPRING_CLOUD_GATEWAY); + abstractSpan.setLayer(SpanLayer.HTTP); + ContextManager.stopSpan(abstractSpan); + + BiFunction> finalSender = (BiFunction>) allArguments[0]; + allArguments[0] = (BiFunction>) (request, outbound) -> { + Publisher publisher = finalSender.apply(request, outbound); + + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + request.requestHeaders().remove(next.getHeadKey()); + request.requestHeaders().set(next.getHeadKey(), next.getHeadValue()); + } + return publisher; + }; + enhanceObjectCache.setCacheSpan(abstractSpan); + } + ContextManager.stopSpan(span); + enhanceObjectCache.setSpan1(span); + } + + private String getPeer(URL url) { + return url.getHost() + ":" + url.getPort(); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) { + ((EnhancedInstance) ret).setSkyWalkingDynamicField(objInst.getSkyWalkingDynamicField()); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerUriV412Interceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerUriV412Interceptor.java new file mode 100644 index 0000000000..1d5c020210 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerUriV412Interceptor.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; + +import java.lang.reflect.Method; + +/** + * This class intercept uri method to get the url of downstream service + */ +public class HttpClientFinalizerUriV412Interceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + if (ret instanceof EnhancedInstance) { + EnhanceObjectCache retEnhanceObjectCache = (EnhanceObjectCache) ((EnhancedInstance) ret).getSkyWalkingDynamicField(); + if (retEnhanceObjectCache != null) { + retEnhanceObjectCache.setUrl(String.valueOf(allArguments[0])); + if (objInst.getSkyWalkingDynamicField() != null) { + EnhanceObjectCache objInstEnhanceObjectCache = (EnhanceObjectCache) objInst.getSkyWalkingDynamicField(); + retEnhanceObjectCache.setContextSnapshot(objInstEnhanceObjectCache.getContextSnapshot()); + } + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/NettyRoutingFilterV412Interceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/NettyRoutingFilterV412Interceptor.java new file mode 100644 index 0000000000..1d6aa76da8 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/NettyRoutingFilterV412Interceptor.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.ServerWebExchangeDecorator; + +import java.lang.reflect.Method; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY; + +public class NettyRoutingFilterV412Interceptor implements InstanceMethodsAroundInterceptor { + + private static final String NETTY_ROUTING_FILTER_TRACED_ATTR = NettyRoutingFilterV412Interceptor.class.getName() + ".isTraced"; + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; + if (isTraced(exchange)) { + return; + } + + setTracedStatus(exchange); + + EnhancedInstance enhancedInstance = getInstance(allArguments[0]); + + AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/RoutingFilter"); + if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) { + ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField()); + } + span.setComponent(SPRING_CLOUD_GATEWAY); + } + + private static void setTracedStatus(ServerWebExchange exchange) { + exchange.getAttributes().put(NETTY_ROUTING_FILTER_TRACED_ATTR, true); + } + + private static boolean isTraced(ServerWebExchange exchange) { + return exchange.getAttributeOrDefault(NETTY_ROUTING_FILTER_TRACED_ATTR, false); + } + + private EnhancedInstance getInstance(Object o) { + EnhancedInstance instance = null; + if (o instanceof EnhancedInstance) { + instance = (EnhancedInstance) o; + } else if (o instanceof ServerWebExchangeDecorator) { + ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate(); + return getInstance(delegate); + } + return instance; + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + if (ContextManager.isActive()) { + // if HttpClientFinalizerSendInterceptor does not invoke, we will stop the span there to avoid context leak. + ContextManager.stopSpan(); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/NettyRoutingGetHttpClientV412Interceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/NettyRoutingGetHttpClientV412Interceptor.java new file mode 100644 index 0000000000..282e6f5f17 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/NettyRoutingGetHttpClientV412Interceptor.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; + +import java.lang.reflect.Method; + +public class NettyRoutingGetHttpClientV412Interceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + if (ret instanceof EnhancedInstance) { + if (ContextManager.isActive()) { + ContextSnapshot contextSnapshot = ContextManager.capture(); + EnhanceObjectCache retEnhanceObjectCache = new EnhanceObjectCache(); + retEnhanceObjectCache.setContextSnapshot(contextSnapshot); + EnhancedInstance retEnhancedInstance = (EnhancedInstance) ret; + retEnhancedInstance.setSkyWalkingDynamicField(retEnhanceObjectCache); + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + } + +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/AbstractGatewayV412EnhancePluginDefine.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/AbstractGatewayV412EnhancePluginDefine.java new file mode 100644 index 0000000000..7cde134ef8 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/AbstractGatewayV412EnhancePluginDefine.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.define; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; + +/** + * This abstract class defines the witnessClasses() method, + * and other plugin define classes need to inherit from this class + */ +public abstract class AbstractGatewayV412EnhancePluginDefine extends ClassInstanceMethodsEnhancePluginDefine { + + /** + * @since 4.0.0 + */ + @Override + protected String[] witnessClasses() { + return new String[]{"org.springframework.cloud.gateway.event.RouteDeletedEvent"}; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/GatewayFilterV412Instrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/GatewayFilterV412Instrumentation.java new file mode 100644 index 0000000000..499502a07d --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/GatewayFilterV412Instrumentation.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; + +public class GatewayFilterV412Instrumentation extends AbstractGatewayV412EnhancePluginDefine { + + private static final String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter"; + private static final String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.GatewayFilterV412Interceptor"; + + @Override + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(INTERCEPT_CLASS_GATEWAY_FILTER); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("filter").and( + takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange")); + } + + @Override + public String getMethodsInterceptor() { + return GATEWAY_FILTER_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/HttpClientConnectV412Instrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/HttpClientConnectV412Instrumentation.java new file mode 100644 index 0000000000..3d1bb3462b --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/HttpClientConnectV412Instrumentation.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +public class HttpClientConnectV412Instrumentation extends AbstractGatewayV412EnhancePluginDefine { + + private static final String ENHANCE_CLASS = "reactor.netty.http.client.HttpClientConnect"; + private static final String HTTP_CLIENT_CONNECT_DUPLICATE_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.HttpClientConnectDuplicateV412Interceptor"; + private static final String HTTP_CLIENT_CONNECT_REQUEST_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.HttpClientConnectRequestV412Interceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("duplicate").and(takesNoArguments()) + .and(returns(named("reactor.netty.http.client.HttpClient"))); + } + + @Override + public String getMethodsInterceptor() { + return HTTP_CLIENT_CONNECT_DUPLICATE_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("request").and(takesArgumentWithType(0, "io.netty.handler.codec.http.HttpMethod")) + .and(returns(named("reactor.netty.http.client.HttpClient$RequestSender"))); + } + + @Override + public String getMethodsInterceptor() { + return HTTP_CLIENT_CONNECT_REQUEST_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/HttpClientFinalizerV412Instrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/HttpClientFinalizerV412Instrumentation.java new file mode 100644 index 0000000000..7a5b9b888e --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/HttpClientFinalizerV412Instrumentation.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +public class HttpClientFinalizerV412Instrumentation extends AbstractGatewayV412EnhancePluginDefine { + + private static final String INTERCEPT_CLASS_HTTP_CLIENT_FINALIZER = "reactor.netty.http.client.HttpClientFinalizer"; + private static final String CLIENT_FINALIZER_CONSTRUCTOR_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.HttpClientFinalizerConstructorV412Interceptor"; + private static final String CLIENT_FINALIZER_CONSTRUCTOR_ARGUMENT_TYPE = "reactor.netty.http.client.HttpClientConfig"; + private static final String HTTP_CLIENT_FINALIZER_URI_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.HttpClientFinalizerUriV412Interceptor"; + private static final String HTTP_CLIENT_FINALIZER_SEND_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.HttpClientFinalizerSendV412Interceptor"; + private static final String HTTP_CLIENT_FINALIZER_RESPONSE_CONNECTION_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.HttpClientFinalizerResponseConnectionV412Interceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byName(INTERCEPT_CLASS_HTTP_CLIENT_FINALIZER); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArgumentWithType(0, CLIENT_FINALIZER_CONSTRUCTOR_ARGUMENT_TYPE); + } + + @Override + public String getConstructorInterceptor() { + return CLIENT_FINALIZER_CONSTRUCTOR_INTERCEPTOR; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("uri"); + } + + @Override + public String getMethodsInterceptor() { + return HTTP_CLIENT_FINALIZER_URI_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("send").and(takesArgumentWithType(0, "java.util.function.BiFunction")); + } + + @Override + public String getMethodsInterceptor() { + return HTTP_CLIENT_FINALIZER_SEND_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("responseConnection"); + } + + @Override + public String getMethodsInterceptor() { + return HTTP_CLIENT_FINALIZER_RESPONSE_CONNECTION_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/NettyRoutingFilterV412Instrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/NettyRoutingFilterV412Instrumentation.java new file mode 100644 index 0000000000..ca06147ba3 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/define/NettyRoutingFilterV412Instrumentation.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +public class NettyRoutingFilterV412Instrumentation extends AbstractGatewayV412EnhancePluginDefine { + + private static final String INTERCEPT_CLASS_NETTY_ROUTING_FILTER = "org.springframework.cloud.gateway.filter.NettyRoutingFilter"; + private static final String NETTY_ROUTING_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.NettyRoutingFilterV412Interceptor"; + private static final String NETTY_ROUTING_GET_HTTP_CLIENT_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.NettyRoutingGetHttpClientV412Interceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byName(INTERCEPT_CLASS_NETTY_ROUTING_FILTER); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("filter").and( + takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange")); + } + + @Override + public String getMethodsInterceptor() { + return NETTY_ROUTING_FILTER_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("getHttpClient") + .and(takesArgumentWithType(0, "org.springframework.cloud.gateway.route.Route")) + .and(takesArgumentWithType(1, "org.springframework.web.server.ServerWebExchange")) + .and(returns(named("reactor.netty.http.client.HttpClient"))); + } + + @Override + public String getMethodsInterceptor() { + return NETTY_ROUTING_GET_HTTP_CLIENT_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptor.java new file mode 100644 index 0000000000..233eb24ea7 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptor.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.ServerWebExchangeDecorator; +import org.springframework.web.server.adapter.DefaultServerWebExchange; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY; + +public class GatewayFilterInterceptor implements InstanceMethodsAroundInterceptor { + + private static final ThreadLocal STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0)); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + if (isEntry()) { + ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; + + EnhancedInstance enhancedInstance = getInstance(exchange); + + AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter"); + if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) { + ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField()); + } + span.setComponent(SPRING_CLOUD_GATEWAY); + } + } + + public static EnhancedInstance getInstance(Object o) { + EnhancedInstance instance = null; + if (o instanceof DefaultServerWebExchange) { + instance = (EnhancedInstance) o; + } else if (o instanceof ServerWebExchangeDecorator) { + ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate(); + return getInstance(delegate); + } + return instance; + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + if (isExit()) { + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } + + private boolean isEntry() { + return STACK_DEEP.get().getAndIncrement() == 0; + } + + private boolean isExit() { + boolean isExit = STACK_DEEP.get().decrementAndGet() == 0; + if (isExit) { + STACK_DEEP.remove(); + } + return isExit; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/AbstractGatewayV4EnhancePluginDefine.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/AbstractGatewayV4EnhancePluginDefine.java index 803097ba62..f40d8832d4 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/AbstractGatewayV4EnhancePluginDefine.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/AbstractGatewayV4EnhancePluginDefine.java @@ -17,8 +17,15 @@ package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define; +import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import java.util.Collections; +import java.util.List; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; + /** * This abstract class defines the witnessClasses() method, * and other plugin define classes need to inherit from this class @@ -32,4 +39,12 @@ public abstract class AbstractGatewayV4EnhancePluginDefine extends ClassInstance protected String[] witnessClasses() { return new String[]{"org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties"}; } + + @Override + protected List witnessMethods() { + return Collections.singletonList( + new WitnessMethod("org.springframework.cloud.gateway.config.LocalResponseCacheAutoConfiguration", + named("responseCacheSizeWeigher"). + and(returns(named("org.springframework.cloud.gateway.filter.factory.cache.ResponseCacheSizeWeigher"))))); + } } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/EnhanceObjectCache.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/EnhanceObjectCache.java index 419d5118a2..335d5ab6cd 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/EnhanceObjectCache.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/EnhanceObjectCache.java @@ -17,6 +17,7 @@ package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; /** @@ -27,6 +28,7 @@ public class EnhanceObjectCache { private String url; private AbstractSpan span; private AbstractSpan span1; + private ContextSnapshot contextSnapshot; public String getUrl() { return url; @@ -51,4 +53,13 @@ public AbstractSpan getSpan1() { public void setSpan1(final AbstractSpan span) { span1 = span; } + + public ContextSnapshot getContextSnapshot() { + return contextSnapshot; + } + + public void setContextSnapshot(final ContextSnapshot contextSnapshot) { + this.contextSnapshot = contextSnapshot; + } + } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/GatewayFilterInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/GatewayFilterInstrumentation.java new file mode 100644 index 0000000000..e6bbde9c8a --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/GatewayFilterInstrumentation.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; + +public class GatewayFilterInstrumentation extends AbstractGatewayV4EnhancePluginDefine { + + private static final String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter"; + private static final String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.GatewayFilterInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(INTERCEPT_CLASS_GATEWAY_FILTER); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("filter").and( + takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange")); + } + + @Override + public String getMethodsInterceptor() { + return GATEWAY_FILTER_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/resources/skywalking-plugin.def index 5cbcbfcdd8..bbb2bb4cf9 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/resources/skywalking-plugin.def @@ -18,3 +18,8 @@ spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.NettyRoutingFilterInstrumentation spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.ServerWebExchangeInstrumentation spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.DispatcherHandlerInstrumentation +spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.GatewayFilterInstrumentation +spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.define.HttpClientFinalizerV412Instrumentation +spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.define.NettyRoutingFilterV412Instrumentation +spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.define.GatewayFilterV412Instrumentation +spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x.define.HttpClientConnectV412Instrumentation diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/GatewayFilterV412InterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/GatewayFilterV412InterceptorTest.java new file mode 100644 index 0000000000..d8265ee075 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/GatewayFilterV412InterceptorTest.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.context.ApplicationContext; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.http.codec.multipart.Part; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import reactor.core.publisher.Mono; + +import java.security.Principal; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@RunWith(TracingSegmentRunner.class) +public class GatewayFilterV412InterceptorTest { + + private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter"; + + private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance { + private ContextSnapshot snapshot; + Map attributes = new HashMap<>(); + + @Override + public Object getSkyWalkingDynamicField() { + return snapshot; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.snapshot = (ContextSnapshot) value; + } + + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + } + + private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance(); + private final GatewayFilterV412Interceptor interceptor = new GatewayFilterV412Interceptor(); + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Before + public void setUp() throws Exception { + } + + private final ServerWebExchange exchange = new ServerWebExchange() { + Map attributes = new HashMap<>(); + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + }; + + @Test + public void testInterceptOnlyOnce() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null); + Assert.assertTrue(ContextManager.isActive()); + AbstractSpan activeSpan = ContextManager.activeSpan(); + Assert.assertTrue(activeSpan instanceof AbstractTracingSpan); + Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + + } + + @Test + public void testNestedInterception() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithNullDynamicField() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithContextSnapshot() throws Throwable { + final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null); + SpanLayer.asHttp(entrySpan); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture()); + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + ContextManager.stopSpan(entrySpan); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 2); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX); + SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP); + } + +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientConnectDuplicateV412InterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientConnectDuplicateV412InterceptorTest.java new file mode 100644 index 0000000000..8cf3e9d830 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientConnectDuplicateV412InterceptorTest.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +@RunWith(TracingSegmentRunner.class) +public class HttpClientConnectDuplicateV412InterceptorTest { + + private final static String ENTRY_OPERATION_NAME = "/get"; + private final HttpClientConnectDuplicateV412Interceptor duplicateInterceptor = new HttpClientConnectDuplicateV412Interceptor(); + private final EnhancedInstance enhancedInstance = new EnhancedInstance() { + private EnhanceObjectCache enhanceObjectCache; + + @Override + public Object getSkyWalkingDynamicField() { + return enhanceObjectCache; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.enhanceObjectCache = (EnhanceObjectCache) value; + } + }; + private final EnhancedInstance retEnhancedInstance = new EnhancedInstance() { + private EnhanceObjectCache enhanceObjectCache; + @Override + public Object getSkyWalkingDynamicField() { + return enhanceObjectCache; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.enhanceObjectCache = (EnhanceObjectCache) value; + } + }; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + private AbstractSpan entrySpan; + + @Before + public void setUp() throws Exception { + entrySpan = ContextManager.createEntrySpan(ENTRY_OPERATION_NAME, null); + entrySpan.setLayer(SpanLayer.HTTP); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + } + + @Test + public void testWithDynamicFieldNull() throws Throwable { + enhancedInstance.setSkyWalkingDynamicField(null); + duplicateInterceptor.afterMethod(enhancedInstance, null, null, null, retEnhancedInstance); + assertNull(retEnhancedInstance.getSkyWalkingDynamicField()); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(traceSegments.size(), 0); + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + + @Test + public void testWithDynamicFieldNotNull() throws Throwable { + ContextSnapshot contextSnapshot = ContextManager.capture(); + EnhanceObjectCache enhanceObjectCache = new EnhanceObjectCache(); + enhanceObjectCache.setContextSnapshot(contextSnapshot); + enhancedInstance.setSkyWalkingDynamicField(enhanceObjectCache); + duplicateInterceptor.afterMethod(enhancedInstance, null, null, null, retEnhancedInstance); + Object retEnhancedInstanceSkyWalkingDynamicField = retEnhancedInstance.getSkyWalkingDynamicField(); + assertNotNull(retEnhancedInstanceSkyWalkingDynamicField); + assertTrue(retEnhancedInstanceSkyWalkingDynamicField instanceof EnhanceObjectCache); + EnhanceObjectCache retEnhanceObjectCache = (EnhanceObjectCache) retEnhancedInstanceSkyWalkingDynamicField; + assertNotNull(retEnhanceObjectCache.getContextSnapshot()); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(traceSegments.size(), 0); + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientConnectRequestV412InterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientConnectRequestV412InterceptorTest.java new file mode 100644 index 0000000000..06668c1eb2 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientConnectRequestV412InterceptorTest.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +@RunWith(TracingSegmentRunner.class) +public class HttpClientConnectRequestV412InterceptorTest { + private final static String URI = "http://localhost:8080/get"; + private final static String ENTRY_OPERATION_NAME = "/get"; + private final HttpClientConnectRequestV412Interceptor requestInterceptor = new HttpClientConnectRequestV412Interceptor(); + private final EnhancedInstance enhancedInstance = new EnhancedInstance() { + private EnhanceObjectCache enhanceObjectCache; + + @Override + public Object getSkyWalkingDynamicField() { + return enhanceObjectCache; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.enhanceObjectCache = (EnhanceObjectCache) value; + } + }; + private final EnhancedInstance retEnhancedInstance = new EnhancedInstance() { + private EnhanceObjectCache enhanceObjectCache; + @Override + public Object getSkyWalkingDynamicField() { + return enhanceObjectCache; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.enhanceObjectCache = (EnhanceObjectCache) value; + } + }; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + private AbstractSpan entrySpan; + + @Before + public void setUp() throws Exception { + entrySpan = ContextManager.createEntrySpan(ENTRY_OPERATION_NAME, null); + entrySpan.setLayer(SpanLayer.HTTP); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + } + + @Test + public void testWithDynamicFieldNull() throws Throwable { + enhancedInstance.setSkyWalkingDynamicField(null); + retEnhancedInstance.setSkyWalkingDynamicField(null); + requestInterceptor.afterMethod(enhancedInstance, null, null, null, retEnhancedInstance); + assertNull(retEnhancedInstance.getSkyWalkingDynamicField()); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(traceSegments.size(), 0); + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + + @Test + public void testWithRetDynamicFieldNotNull() throws Throwable { + enhancedInstance.setSkyWalkingDynamicField(null); + EnhanceObjectCache retEnhanceObjectCache = new EnhanceObjectCache(); + retEnhancedInstance.setSkyWalkingDynamicField(retEnhanceObjectCache); + + requestInterceptor.afterMethod(enhancedInstance, null, null, null, retEnhancedInstance); + assertNotNull(retEnhancedInstance.getSkyWalkingDynamicField()); + assertTrue(retEnhancedInstance.getSkyWalkingDynamicField() instanceof EnhanceObjectCache); + assertNull(((EnhanceObjectCache) retEnhancedInstance.getSkyWalkingDynamicField()).getContextSnapshot()); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(traceSegments.size(), 0); + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + + @Test + public void testWithDynamicFieldNotNull() throws Throwable { + ContextSnapshot contextSnapshot = ContextManager.capture(); + EnhanceObjectCache enhanceObjectCache = new EnhanceObjectCache(); + enhanceObjectCache.setContextSnapshot(contextSnapshot); + enhancedInstance.setSkyWalkingDynamicField(enhanceObjectCache); + + EnhanceObjectCache enhanceObjectCache2 = new EnhanceObjectCache(); + enhanceObjectCache2.setUrl(URI); + retEnhancedInstance.setSkyWalkingDynamicField(enhanceObjectCache2); + requestInterceptor.afterMethod(enhancedInstance, null, null, null, retEnhancedInstance); + Object retEnhancedInstanceSkyWalkingDynamicField = retEnhancedInstance.getSkyWalkingDynamicField(); + assertNotNull(retEnhancedInstanceSkyWalkingDynamicField); + assertTrue(retEnhancedInstanceSkyWalkingDynamicField instanceof EnhanceObjectCache); + EnhanceObjectCache retEnhanceObjectCache = (EnhanceObjectCache) retEnhancedInstanceSkyWalkingDynamicField; + assertNotNull(retEnhanceObjectCache.getContextSnapshot()); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(traceSegments.size(), 0); + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + + @Test + public void testWithRetDynamicFieldNull() throws Throwable { + ContextSnapshot contextSnapshot = ContextManager.capture(); + EnhanceObjectCache enhanceObjectCache = new EnhanceObjectCache(); + enhanceObjectCache.setContextSnapshot(contextSnapshot); + enhancedInstance.setSkyWalkingDynamicField(enhanceObjectCache); + retEnhancedInstance.setSkyWalkingDynamicField(null); + + requestInterceptor.afterMethod(enhancedInstance, null, null, null, retEnhancedInstance); + Object retEnhancedInstanceSkyWalkingDynamicField = retEnhancedInstance.getSkyWalkingDynamicField(); + assertNull(retEnhancedInstanceSkyWalkingDynamicField); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(traceSegments.size(), 0); + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerConstructorV412InterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerConstructorV412InterceptorTest.java new file mode 100644 index 0000000000..a87a4e4979 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerConstructorV412InterceptorTest.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import reactor.netty.http.client.HttpClientConfig; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class HttpClientFinalizerConstructorV412InterceptorTest { + + private static final String URI = "http://localhost:8080/get"; + private final EnhancedInstance enhancedInstance = new EnhancedInstance() { + private EnhanceObjectCache enhanceObjectCache; + + @Override + public Object getSkyWalkingDynamicField() { + return enhanceObjectCache; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.enhanceObjectCache = (EnhanceObjectCache) value; + } + }; + private HttpClientConfig httpClientConfig; + private HttpClientFinalizerConstructorV412Interceptor httpClientFinalizerConstructorInterceptor; + + @Before + public void setUp() { + httpClientConfig = mock(HttpClientConfig.class); + when(httpClientConfig.uri()).thenReturn(URI); + httpClientFinalizerConstructorInterceptor = new HttpClientFinalizerConstructorV412Interceptor(); + } + + @Test + public void onConstruct() { + httpClientFinalizerConstructorInterceptor.onConstruct(enhancedInstance, new Object[]{httpClientConfig}); + final EnhanceObjectCache enhanceCache = (EnhanceObjectCache) enhancedInstance.getSkyWalkingDynamicField(); + assertNotNull(enhanceCache); + assertEquals(enhanceCache.getUrl(), URI); + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerUriV412InterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerUriV412InterceptorTest.java new file mode 100644 index 0000000000..0cb079b299 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerUriV412InterceptorTest.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +@RunWith(TracingSegmentRunner.class) +public class HttpClientFinalizerUriV412InterceptorTest { + + private final static String URI = "http://localhost:8080/get"; + private final static String ENTRY_OPERATION_NAME = "/get"; + private final HttpClientFinalizerUriV412Interceptor uriInterceptor = new HttpClientFinalizerUriV412Interceptor(); + private final EnhancedInstance enhancedInstance = new EnhancedInstance() { + private EnhanceObjectCache enhanceObjectCache; + + @Override + public Object getSkyWalkingDynamicField() { + return enhanceObjectCache; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.enhanceObjectCache = (EnhanceObjectCache) value; + } + }; + private final EnhancedInstance retEnhancedInstance = new EnhancedInstance() { + private EnhanceObjectCache enhanceObjectCache; + @Override + public Object getSkyWalkingDynamicField() { + return enhanceObjectCache; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.enhanceObjectCache = (EnhanceObjectCache) value; + } + }; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + private AbstractSpan entrySpan; + + @Before + public void setUp() throws Exception { + entrySpan = ContextManager.createEntrySpan(ENTRY_OPERATION_NAME, null); + entrySpan.setLayer(SpanLayer.HTTP); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + } + + @Test + public void testWithDynamicFieldNull() throws Throwable { + enhancedInstance.setSkyWalkingDynamicField(null); + retEnhancedInstance.setSkyWalkingDynamicField(null); + uriInterceptor.afterMethod(enhancedInstance, null, null, null, retEnhancedInstance); + assertNull(retEnhancedInstance.getSkyWalkingDynamicField()); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(traceSegments.size(), 0); + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + + @Test + public void testWithRetDynamicFieldNotNull() throws Throwable { + enhancedInstance.setSkyWalkingDynamicField(null); + EnhanceObjectCache enhanceObjectCache2 = new EnhanceObjectCache(); + retEnhancedInstance.setSkyWalkingDynamicField(enhanceObjectCache2); + + uriInterceptor.afterMethod(enhancedInstance, null, new Object[]{URI}, null, retEnhancedInstance); + Object retEnhancedInstanceSkyWalkingDynamicField = retEnhancedInstance.getSkyWalkingDynamicField(); + assertNotNull(retEnhancedInstanceSkyWalkingDynamicField); + assertTrue(retEnhancedInstanceSkyWalkingDynamicField instanceof EnhanceObjectCache); + EnhanceObjectCache retEnhanceObjectCache = (EnhanceObjectCache) retEnhancedInstanceSkyWalkingDynamicField; + assertNull(retEnhanceObjectCache.getContextSnapshot()); + assertNotNull(retEnhanceObjectCache.getUrl()); + assertEquals(URI, retEnhanceObjectCache.getUrl()); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(traceSegments.size(), 0); + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + + @Test + public void testWithDynamicFieldNotNull() throws Throwable { + ContextSnapshot contextSnapshot = ContextManager.capture(); + EnhanceObjectCache enhanceObjectCache = new EnhanceObjectCache(); + enhanceObjectCache.setContextSnapshot(contextSnapshot); + enhancedInstance.setSkyWalkingDynamicField(enhanceObjectCache); + EnhanceObjectCache enhanceObjectCache2 = new EnhanceObjectCache(); + retEnhancedInstance.setSkyWalkingDynamicField(enhanceObjectCache2); + + uriInterceptor.afterMethod(enhancedInstance, null, new Object[]{URI}, null, retEnhancedInstance); + Object retEnhancedInstanceSkyWalkingDynamicField = retEnhancedInstance.getSkyWalkingDynamicField(); + assertNotNull(retEnhancedInstanceSkyWalkingDynamicField); + assertTrue(retEnhancedInstanceSkyWalkingDynamicField instanceof EnhanceObjectCache); + EnhanceObjectCache retEnhanceObjectCache = (EnhanceObjectCache) retEnhancedInstanceSkyWalkingDynamicField; + assertNotNull(retEnhanceObjectCache.getContextSnapshot()); + assertTrue(contextSnapshot == retEnhanceObjectCache.getContextSnapshot()); + assertNotNull(retEnhanceObjectCache.getUrl()); + assertEquals(URI, retEnhanceObjectCache.getUrl()); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(traceSegments.size(), 0); + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + + @Test + public void testWithRetDynamicFieldNull() throws Throwable { + ContextSnapshot contextSnapshot = ContextManager.capture(); + EnhanceObjectCache enhanceObjectCache = new EnhanceObjectCache(); + enhanceObjectCache.setContextSnapshot(contextSnapshot); + enhancedInstance.setSkyWalkingDynamicField(enhanceObjectCache); + retEnhancedInstance.setSkyWalkingDynamicField(null); + + uriInterceptor.afterMethod(enhancedInstance, null, new Object[]{URI}, null, retEnhancedInstance); + Object retEnhancedInstanceSkyWalkingDynamicField = retEnhancedInstance.getSkyWalkingDynamicField(); + assertNull(retEnhancedInstanceSkyWalkingDynamicField); + assertNotNull(enhancedInstance.getSkyWalkingDynamicField()); + assertTrue(enhancedInstance.getSkyWalkingDynamicField() instanceof EnhanceObjectCache); + EnhanceObjectCache objInstObjectCache = (EnhanceObjectCache) enhancedInstance.getSkyWalkingDynamicField(); + assertTrue(contextSnapshot == objInstObjectCache.getContextSnapshot()); + assertNull(objInstObjectCache.getUrl()); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(traceSegments.size(), 0); + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerV412InterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerV412InterceptorTest.java new file mode 100644 index 0000000000..8018013db0 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/HttpClientFinalizerV412InterceptorTest.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import io.netty.handler.codec.http.HttpResponseStatus; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.netty.Connection; +import reactor.netty.NettyOutbound; +import reactor.netty.http.client.HttpClientRequest; +import reactor.netty.http.client.HttpClientResponse; + +import java.util.List; +import java.util.function.BiFunction; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@RunWith(TracingSegmentRunner.class) +public class HttpClientFinalizerV412InterceptorTest { + + private final static String URI = "http://localhost:8080/get"; + private final static String ENTRY_OPERATION_NAME = "/get"; + private final HttpClientFinalizerSendV412Interceptor sendInterceptor = new HttpClientFinalizerSendV412Interceptor(); + private final HttpClientFinalizerResponseConnectionV412Interceptor responseConnectionInterceptor = new HttpClientFinalizerResponseConnectionV412Interceptor(); + private final BiFunction> originalSendBiFunction = (httpClientRequest, nettyOutbound) -> (Publisher) s -> { + }; + private final BiFunction> originalResponseConnectionBiFunction = (httpClientResponse, connection) -> (Publisher) s -> { + }; + private final EnhancedInstance enhancedInstance = new EnhancedInstance() { + private EnhanceObjectCache enhanceObjectCache; + + @Override + public Object getSkyWalkingDynamicField() { + return enhanceObjectCache; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.enhanceObjectCache = (EnhanceObjectCache) value; + } + }; + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + private HttpClientResponse mockResponse; + private HttpClientRequest mockRequest; + @SegmentStoragePoint + private SegmentStorage segmentStorage; + private AbstractSpan entrySpan; + + @Before + public void setUp() throws Exception { + entrySpan = ContextManager.createEntrySpan(ENTRY_OPERATION_NAME, null); + entrySpan.setLayer(SpanLayer.HTTP); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + mockRequest = new MockClientRequest(); + mockResponse = new MockClientResponse(); + final EnhanceObjectCache enhanceObjectCache = new EnhanceObjectCache(); + enhanceObjectCache.setUrl(URI); + enhanceObjectCache.setContextSnapshot(ContextManager.capture()); + enhancedInstance.setSkyWalkingDynamicField(enhanceObjectCache); + } + + @Test + public void testWithDynamicFieldNull() throws Throwable { + enhancedInstance.setSkyWalkingDynamicField(null); + executeSendRequest(); + stopEntrySpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(1, traceSegments.size()); + } + + @Test + public void testWithContextSnapshotNull() throws Throwable { + EnhanceObjectCache objectCache = (EnhanceObjectCache) enhancedInstance.getSkyWalkingDynamicField(); + objectCache.setContextSnapshot(null); + executeSendRequest(); + stopEntrySpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(1, traceSegments.size()); + } + + @Test + public void testWithEmptyUri() throws Throwable { + final EnhanceObjectCache objectCache = (EnhanceObjectCache) enhancedInstance.getSkyWalkingDynamicField(); + objectCache.setUrl(""); + executeSendRequest(); + stopEntrySpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(1, traceSegments.size()); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + assertNotNull(spans); + assertEquals(2, spans.size()); + assertNotNull(spans.get(1)); + assertUpstreamSpan(spans.get(1)); + assertNotNull(spans.get(0)); + assertSendSpan(spans.get(0)); + assertNotNull(objectCache.getSpan1()); + assertEquals(objectCache.getSpan1().getSpanId(), spans.get(0).getSpanId()); + } + + @Test + public void testWithUri() throws Throwable { + executeSendRequest(); + stopEntrySpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(1, traceSegments.size()); + final EnhanceObjectCache objectCache = (EnhanceObjectCache) enhancedInstance + .getSkyWalkingDynamicField(); + assertNotNull(objectCache.getSpan1()); + assertNotNull(objectCache.getSpan()); + assertTrue(objectCache.getSpan().isExit()); + assertEquals(objectCache.getSpan().getOperationName(), "SpringCloudGateway/sendRequest"); + assertEquals(objectCache.getSpan1().getOperationName(), "SpringCloudGateway/send"); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + assertNotNull(spans); + assertEquals(3, spans.size()); + assertUpstreamSpan(spans.get(2)); + assertSendSpan(spans.get(1)); + assertDownstreamSpan(spans.get(0)); + } + + private void executeSendRequest() throws Throwable { + Object[] sendArguments = new Object[]{originalSendBiFunction}; + sendInterceptor.beforeMethod(enhancedInstance, null, sendArguments, null, null); + sendInterceptor.afterMethod(enhancedInstance, null, new Object[0], null, enhancedInstance); + ((BiFunction>) sendArguments[0]) + .apply(mockRequest, null); + Object[] responseConnectionArguments = new Object[]{originalResponseConnectionBiFunction}; + responseConnectionInterceptor + .beforeMethod(enhancedInstance, null, responseConnectionArguments, null, null); + Flux flux = Flux.just(0); + + flux = (Flux) responseConnectionInterceptor.afterMethod(enhancedInstance, null, new Object[0], null, flux); + + ((BiFunction>) responseConnectionArguments[0]) + .apply(mockResponse, null); + flux.blockFirst(); + } + + private void assertUpstreamSpan(AbstractSpan span) { + SpanAssert.assertLayer(span, SpanLayer.HTTP); + SpanAssert.assertComponent(span, ComponentsDefine.SPRING_WEBFLUX); + } + + private void assertSendSpan(AbstractSpan span) { + SpanAssert.assertComponent(span, ComponentsDefine.SPRING_CLOUD_GATEWAY); + assertEquals(span.getOperationName(), "SpringCloudGateway/send"); + } + + private void assertDownstreamSpan(AbstractSpan span) { + SpanAssert.assertLayer(span, SpanLayer.HTTP); + SpanAssert.assertComponent(span, ComponentsDefine.SPRING_CLOUD_GATEWAY); + SpanAssert.assertTagSize(span, 2); + SpanAssert.assertTag(span, 0, URI); + SpanAssert.assertTag(span, 1, String.valueOf(HttpResponseStatus.OK.code())); + } + + private void stopEntrySpan() { + if (ContextManager.isActive() && ContextManager.activeSpan() == entrySpan) { + ContextManager.stopSpan(entrySpan); + } + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/MockClientRequest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/MockClientRequest.java new file mode 100644 index 0000000000..ad357e3e29 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/MockClientRequest.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.cookie.Cookie; +import reactor.netty.http.client.HttpClientRequest; +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +import java.time.Duration; +import java.util.Map; +import java.util.Set; + +public class MockClientRequest implements HttpClientRequest { + + @Override + public HttpClientRequest addCookie(Cookie cookie) { + return null; + } + + @Override + public HttpClientRequest addHeader(CharSequence charSequence, CharSequence charSequence1) { + return null; + } + + @Override + public HttpClientRequest header(CharSequence charSequence, CharSequence charSequence1) { + return null; + } + + @Override + public HttpClientRequest headers(HttpHeaders httpHeaders) { + return null; + } + + @Override + public boolean isFollowRedirect() { + return false; + } + + @Override + public HttpClientRequest responseTimeout(Duration duration) { + return null; + } + + @Override + public Context currentContext() { + return null; + } + + @Override + public ContextView currentContextView() { + return null; + } + + @Override + public String[] redirectedFrom() { + return new String[0]; + } + + @Override + public HttpHeaders requestHeaders() { + return new DefaultHttpHeaders(); + } + + @Override + public String resourceUrl() { + return null; + } + + @Override + public Map> cookies() { + return null; + } + + @Override + public boolean isKeepAlive() { + return false; + } + + @Override + public boolean isWebsocket() { + return false; + } + + @Override + public HttpMethod method() { + return null; + } + + @Override + public String path() { + return HttpClientRequest.super.path(); + } + + @Override + public String fullPath() { + return null; + } + + @Override + public String requestId() { + return null; + } + + @Override + public String uri() { + return null; + } + + @Override + public HttpVersion version() { + return null; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/MockClientResponse.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/MockClientResponse.java new file mode 100644 index 0000000000..a2381d0b15 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/MockClientResponse.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.cookie.Cookie; +import reactor.core.publisher.Mono; +import reactor.netty.http.client.HttpClientResponse; +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +import java.util.Map; +import java.util.Set; + +public class MockClientResponse implements HttpClientResponse { + + @Override + public HttpHeaders responseHeaders() { + return null; + } + + @Override + public HttpResponseStatus status() { + return HttpResponseStatus.OK; + } + + @Override + public Mono trailerHeaders() { + return null; + } + + @Override + public Context currentContext() { + return null; + } + + @Override + public ContextView currentContextView() { + return null; + } + + @Override + public String[] redirectedFrom() { + return new String[0]; + } + + @Override + public HttpHeaders requestHeaders() { + return null; + } + + @Override + public String resourceUrl() { + return null; + } + + @Override + public Map> cookies() { + return null; + } + + @Override + public boolean isKeepAlive() { + return false; + } + + @Override + public boolean isWebsocket() { + return false; + } + + @Override + public HttpMethod method() { + return null; + } + + @Override + public String path() { + return HttpClientResponse.super.path(); + } + + @Override + public String fullPath() { + return null; + } + + @Override + public String requestId() { + return null; + } + + @Override + public String uri() { + return null; + } + + @Override + public HttpVersion version() { + return null; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/NettyRoutingFilterV412InterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/NettyRoutingFilterV412InterceptorTest.java new file mode 100644 index 0000000000..75232fcc1c --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/NettyRoutingFilterV412InterceptorTest.java @@ -0,0 +1,222 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.context.ApplicationContext; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.http.codec.multipart.Part; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import reactor.core.publisher.Mono; + +import java.security.Principal; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@RunWith(TracingSegmentRunner.class) +public class NettyRoutingFilterV412InterceptorTest { + private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance { + private ContextSnapshot snapshot; + Map attributes = new HashMap<>(); + + @Override + public Object getSkyWalkingDynamicField() { + return snapshot; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.snapshot = (ContextSnapshot) value; + } + + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + } + + private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance(); + private final NettyRoutingFilterV412Interceptor interceptor = new NettyRoutingFilterV412Interceptor(); + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Before + public void setUp() throws Exception { + } + + @Test + public void testWithNullDynamicField() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(1, spans.size()); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithContextSnapshot() throws Throwable { + final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null); + SpanLayer.asHttp(entrySpan); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture()); + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + ContextManager.stopSpan(entrySpan); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 2); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX); + SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP); + } + + private static final String NETTY_ROUTING_FILTER_TRACED_ATTR = + NettyRoutingFilterV412Interceptor.class.getName() + ".isTraced"; + + @Test + public void testIsTraced() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + Assert.assertEquals(enhancedInstance.getAttributes().get(NETTY_ROUTING_FILTER_TRACED_ATTR), true); + Assert.assertFalse(ContextManager.isActive()); + + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + Assert.assertEquals(enhancedInstance.getAttributes().get(NETTY_ROUTING_FILTER_TRACED_ATTR), true); + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/NettyRoutingGetHttpClientV412InterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/NettyRoutingGetHttpClientV412InterceptorTest.java new file mode 100644 index 0000000000..5f3f8811ae --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v412x/NettyRoutingGetHttpClientV412InterceptorTest.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v412x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +@RunWith(TracingSegmentRunner.class) +public class NettyRoutingGetHttpClientV412InterceptorTest { + private final static String ENTRY_OPERATION_NAME = "/get"; + private final NettyRoutingGetHttpClientV412Interceptor interceptor = new NettyRoutingGetHttpClientV412Interceptor(); + private final EnhancedInstance enhancedInstance = new EnhancedInstance() { + private EnhanceObjectCache enhanceObjectCache; + + @Override + public Object getSkyWalkingDynamicField() { + return enhanceObjectCache; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.enhanceObjectCache = (EnhanceObjectCache) value; + } + }; + private final EnhancedInstance retEnhancedInstance = new EnhancedInstance() { + private EnhanceObjectCache enhanceObjectCache; + @Override + public Object getSkyWalkingDynamicField() { + return enhanceObjectCache; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.enhanceObjectCache = (EnhanceObjectCache) value; + } + }; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + private AbstractSpan entrySpan; + + @Before + public void setUp() throws Exception { + } + + @Test + public void testWithContextIsActive() throws Throwable { + entrySpan = ContextManager.createEntrySpan(ENTRY_OPERATION_NAME, null); + entrySpan.setLayer(SpanLayer.HTTP); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + interceptor.afterMethod(enhancedInstance, null, null, null, retEnhancedInstance); + assertNotNull(retEnhancedInstance.getSkyWalkingDynamicField()); + assertTrue(retEnhancedInstance.getSkyWalkingDynamicField() instanceof EnhanceObjectCache); + EnhanceObjectCache enhanceObjectCache = (EnhanceObjectCache) retEnhancedInstance.getSkyWalkingDynamicField(); + assertNotNull(enhanceObjectCache.getContextSnapshot()); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(traceSegments.size(), 0); + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } + + @Test + public void testWithContextNotActive() throws Throwable { + interceptor.afterMethod(enhancedInstance, null, null, null, retEnhancedInstance); + assertNull(retEnhancedInstance.getSkyWalkingDynamicField()); + final List traceSegments = segmentStorage.getTraceSegments(); + assertEquals(traceSegments.size(), 0); + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptorTest.java new file mode 100644 index 0000000000..d59c63cd17 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptorTest.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.context.ApplicationContext; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.http.codec.multipart.Part; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import reactor.core.publisher.Mono; + +import java.security.Principal; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@RunWith(TracingSegmentRunner.class) +public class GatewayFilterInterceptorTest { + + private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter"; + + private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance { + private ContextSnapshot snapshot; + Map attributes = new HashMap<>(); + + @Override + public Object getSkyWalkingDynamicField() { + return snapshot; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.snapshot = (ContextSnapshot) value; + } + + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + } + + private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance(); + private final GatewayFilterInterceptor interceptor = new GatewayFilterInterceptor(); + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Before + public void setUp() throws Exception { + } + + private final ServerWebExchange exchange = new ServerWebExchange() { + Map attributes = new HashMap<>(); + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + }; + + @Test + public void testInterceptOnlyOnce() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null); + Assert.assertTrue(ContextManager.isActive()); + AbstractSpan activeSpan = ContextManager.activeSpan(); + Assert.assertTrue(activeSpan instanceof AbstractTracingSpan); + Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + + } + + @Test + public void testNestedInterception() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithNullDynamicField() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithContextSnapshot() throws Throwable { + final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null); + SpanLayer.asHttp(entrySpan); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture()); + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + ContextManager.stopSpan(entrySpan); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 2); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX); + SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP); + } + +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/pom.xml index bec5510816..8e5547689c 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/pom.xml +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking optional-spring-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT optional-spring-cloud @@ -40,7 +40,6 @@ UTF-8 - /../.. diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/pom.xml index 6e599a881a..3b34789ab4 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/pom.xml +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/pom.xml @@ -21,7 +21,7 @@ optional-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 pom @@ -39,7 +39,6 @@ - /.. diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/resttemplate-6.x-plugin/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/resttemplate-6.x-plugin/pom.xml index dc75494925..8fc5797da0 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/resttemplate-6.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/resttemplate-6.x-plugin/pom.xml @@ -20,7 +20,7 @@ optional-spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-annotation-plugin/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-annotation-plugin/pom.xml index e4d9a96ddc..367a58c61b 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-annotation-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-annotation-plugin/pom.xml @@ -21,7 +21,7 @@ optional-spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 jar diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-tx-plugin/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-tx-plugin/pom.xml index f8c0c38b0e..74f0694de2 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-tx-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-tx-plugin/pom.xml @@ -21,7 +21,7 @@ optional-spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 jar diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/pom.xml index e85753da89..e597375432 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/pom.xml @@ -21,7 +21,7 @@ optional-spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/pom.xml index a9a8514fff..98c5f9bc71 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/pom.xml @@ -21,7 +21,7 @@ optional-spring-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/optional-plugins/pom.xml b/apm-sniffer/optional-plugins/pom.xml index 590d2b4094..c1d0d8adb7 100644 --- a/apm-sniffer/optional-plugins/pom.xml +++ b/apm-sniffer/optional-plugins/pom.xml @@ -21,7 +21,7 @@ java-agent-sniffer org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -32,10 +32,7 @@ ${shade.package}.${shade.net.bytebuddy.source} UTF-8 - - ${project.build.directory}${sdk.plugin.related.dir}/../../../../skywalking-agent - - ${agent.package.dest.dir}/optional-plugins + ${maven.multiModuleProjectDirectory}/skywalking-agent/optional-plugins 1.0b3 1.8.1 @@ -59,6 +56,7 @@ trace-sampler-cpu-policy-plugin nacos-client-2.x-plugin netty-http-4.1.x-plugin + caffeine-3.x-plugin diff --git a/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/pom.xml b/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/pom.xml index c79b83a730..acc3b85a35 100644 --- a/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/pom.xml @@ -23,7 +23,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-quartz-scheduler-2.x-plugin diff --git a/apm-sniffer/optional-plugins/sentinel-1.x-plugin/pom.xml b/apm-sniffer/optional-plugins/sentinel-1.x-plugin/pom.xml index 7a936d292d..fd4abb4af5 100644 --- a/apm-sniffer/optional-plugins/sentinel-1.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/sentinel-1.x-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/optional-plugins/shenyu-2.4.x-plugin/pom.xml b/apm-sniffer/optional-plugins/shenyu-2.4.x-plugin/pom.xml index 6cb94a441d..7f712e7582 100644 --- a/apm-sniffer/optional-plugins/shenyu-2.4.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/shenyu-2.4.x-plugin/pom.xml @@ -21,7 +21,7 @@ optional-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -63,7 +63,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 package diff --git a/apm-sniffer/optional-plugins/trace-ignore-plugin/pom.xml b/apm-sniffer/optional-plugins/trace-ignore-plugin/pom.xml index 710c5df2da..ec9d193fc6 100644 --- a/apm-sniffer/optional-plugins/trace-ignore-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/trace-ignore-plugin/pom.xml @@ -20,7 +20,7 @@ optional-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/optional-plugins/trace-sampler-cpu-policy-plugin/pom.xml b/apm-sniffer/optional-plugins/trace-sampler-cpu-policy-plugin/pom.xml index c55bf77f12..cc4f683ed6 100644 --- a/apm-sniffer/optional-plugins/trace-sampler-cpu-policy-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/trace-sampler-cpu-policy-plugin/pom.xml @@ -20,7 +20,7 @@ optional-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/optional-plugins/zookeeper-3.4.x-plugin/pom.xml b/apm-sniffer/optional-plugins/zookeeper-3.4.x-plugin/pom.xml index 8f9166a80a..530f490fd5 100644 --- a/apm-sniffer/optional-plugins/zookeeper-3.4.x-plugin/pom.xml +++ b/apm-sniffer/optional-plugins/zookeeper-3.4.x-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.skywalking optional-plugins - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT apm-zookeeper-3.4.x-plugin diff --git a/apm-sniffer/optional-reporter-plugins/kafka-config-extension/pom.xml b/apm-sniffer/optional-reporter-plugins/kafka-config-extension/pom.xml index 63c5e84eec..52374f2631 100644 --- a/apm-sniffer/optional-reporter-plugins/kafka-config-extension/pom.xml +++ b/apm-sniffer/optional-reporter-plugins/kafka-config-extension/pom.xml @@ -20,7 +20,7 @@ optional-reporter-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/apm-sniffer/optional-reporter-plugins/kafka-reporter-plugin/pom.xml b/apm-sniffer/optional-reporter-plugins/kafka-reporter-plugin/pom.xml index 9a752ab8e0..127e923448 100644 --- a/apm-sniffer/optional-reporter-plugins/kafka-reporter-plugin/pom.xml +++ b/apm-sniffer/optional-reporter-plugins/kafka-reporter-plugin/pom.xml @@ -21,7 +21,7 @@ optional-reporter-plugins org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -137,7 +137,6 @@ org.apache.maven.plugins maven-dependency-plugin - ${maven-dependency-plugin.version} copy diff --git a/apm-sniffer/optional-reporter-plugins/kafka-reporter-plugin/src/main/java/org/apache/skywalking/apm/agent/core/kafka/KafkaProducerManager.java b/apm-sniffer/optional-reporter-plugins/kafka-reporter-plugin/src/main/java/org/apache/skywalking/apm/agent/core/kafka/KafkaProducerManager.java index 31dc8482f1..d8dafc0438 100644 --- a/apm-sniffer/optional-reporter-plugins/kafka-reporter-plugin/src/main/java/org/apache/skywalking/apm/agent/core/kafka/KafkaProducerManager.java +++ b/apm-sniffer/optional-reporter-plugins/kafka-reporter-plugin/src/main/java/org/apache/skywalking/apm/agent/core/kafka/KafkaProducerManager.java @@ -44,12 +44,10 @@ import org.apache.skywalking.apm.agent.core.boot.BootService; import org.apache.skywalking.apm.agent.core.boot.DefaultImplementor; import org.apache.skywalking.apm.agent.core.boot.DefaultNamedThreadFactory; -import org.apache.skywalking.apm.agent.core.boot.ServiceManager; import org.apache.skywalking.apm.agent.core.kafka.KafkaReporterPluginConfig.Plugin.Kafka; import org.apache.skywalking.apm.agent.core.logging.api.ILog; import org.apache.skywalking.apm.agent.core.logging.api.LogManager; import org.apache.skywalking.apm.agent.core.plugin.loader.AgentClassLoader; -import org.apache.skywalking.apm.agent.core.remote.GRPCChannelManager; import org.apache.skywalking.apm.util.RunnableWithExceptionProtection; import org.apache.skywalking.apm.util.StringUtil; @@ -183,14 +181,10 @@ public final KafkaProducer getProducer() { return producer; } - /** - * make kafka producer init later but before {@link GRPCChannelManager} - * - * @return priority value - */ + // Higher than the Kafka reporters sharing this producer, so the producer closes only after they stop. @Override public int priority() { - return ServiceManager.INSTANCE.findService(GRPCChannelManager.class).priority() - 1; + return 1; } @Override diff --git a/apm-sniffer/optional-reporter-plugins/kafka-reporter-plugin/src/test/java/org/apache/skywalking/apm/agent/core/kafka/KafkaProducerManagerTest.java b/apm-sniffer/optional-reporter-plugins/kafka-reporter-plugin/src/test/java/org/apache/skywalking/apm/agent/core/kafka/KafkaProducerManagerTest.java index 3a6b7c2d49..ed3c593f5e 100644 --- a/apm-sniffer/optional-reporter-plugins/kafka-reporter-plugin/src/test/java/org/apache/skywalking/apm/agent/core/kafka/KafkaProducerManagerTest.java +++ b/apm-sniffer/optional-reporter-plugins/kafka-reporter-plugin/src/test/java/org/apache/skywalking/apm/agent/core/kafka/KafkaProducerManagerTest.java @@ -19,6 +19,7 @@ package org.apache.skywalking.apm.agent.core.kafka; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.lang.reflect.Method; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; @@ -46,6 +47,11 @@ public void testAddListener() throws Exception { assertEquals(counter.get(), times); } + @Test + public void outranksKafkaReportersSoProducerClosesLast() { + assertTrue(new KafkaProducerManager().priority() > new KafkaTraceSegmentServiceClient().priority()); + } + @Test public void testFormatTopicNameThenRegister() { KafkaProducerManager kafkaProducerManager = new KafkaProducerManager(); diff --git a/apm-sniffer/optional-reporter-plugins/pom.xml b/apm-sniffer/optional-reporter-plugins/pom.xml index 608e4171ee..13b9fc86f7 100644 --- a/apm-sniffer/optional-reporter-plugins/pom.xml +++ b/apm-sniffer/optional-reporter-plugins/pom.xml @@ -21,7 +21,7 @@ java-agent-sniffer org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 @@ -36,13 +36,12 @@ UTF-8 - ${project.build.directory}/../../../../skywalking-agent - ${agent.package.dest.dir}/optional-reporter-plugins + ${maven.multiModuleProjectDirectory}/skywalking-agent/optional-reporter-plugins 1.0b3 1.8.1 - 2.4.1 + 3.9.1 2.4.6.RELEASE diff --git a/apm-sniffer/pom.xml b/apm-sniffer/pom.xml index 8f5d3c0c32..94ea5a16bd 100644 --- a/apm-sniffer/pom.xml +++ b/apm-sniffer/pom.xml @@ -21,7 +21,7 @@ java-agent org.apache.skywalking - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT 4.0.0 diff --git a/changes/changes-9.3.0.md b/changes/changes-9.3.0.md new file mode 100644 index 0000000000..304feb01ae --- /dev/null +++ b/changes/changes-9.3.0.md @@ -0,0 +1,28 @@ +Changes by Version +================== +Release Notes. + +9.3.0 +------------------ + +* Remove `idleCount` tag in Alibaba Druid meter plugin. +* Fix NPE in handleMethodException method of apm-jdk-threadpool-plugin. +* Support for C3P0 connection pool tracing. +* Use a daemon thread to flush logs. +* Fix typos in `URLParser`. +* Add support for `Derby`/`Sybase`/`SQLite`/`DB2`/`OceanBase` jdbc url format in `URLParser`. +* Optimize spring-plugins:scheduled-annotation-plugin compatibility about Spring 6.1.x support. +* Add a forceIgnoring mechanism in a CROSS_THREAD scenario. +* Fix NPE in Redisson plugin since Redisson 3.20.0. +* Support for showing batch command details and ignoring PING commands in Redisson plugin. +* Fix peer value of Master-Slave mode in Redisson plugin. +* Support for tracing the callbacks of asynchronous methods in elasticsearch-6.x-plugin/elasticsearch-7.x-plugin. +* Fixed the invalid issue in the isInterface method in PluginFinder. +* Fix the opentracing toolkit SPI config +* Improve 4x performance of ContextManagerExtendService.createTraceContext() +* Add a plugin that supports the Solon framework. +* Fixed issues in the MySQL component where the executeBatch method could result in empty SQL statements. +* Support kafka-clients-3.7.x. + +All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/213?closed=1) + diff --git a/changes/changes-9.4.0.md b/changes/changes-9.4.0.md new file mode 100644 index 0000000000..c3fa1a4b7e --- /dev/null +++ b/changes/changes-9.4.0.md @@ -0,0 +1,33 @@ +Changes by Version +================== +Release Notes. + +9.4.0 +------------------ + +* Upgrade nats plugin to support 2.16.5 +* Add agent self-observability. +* Fix intermittent ClassCircularityError by preloading ThreadLocalRandom since ByteBuddy 1.12.11 +* Add witness class/method for resteasy-server plugin(v3/v4/v6) +* Add async-profiler feature for performance analysis. This requires OAP server 10.2.0 +* Support db.instance tag,db.collection tag and AggregateOperation span for mongodb plugin(3.x/4.x) +* Improve CustomizeConfiguration by avoiding repeatedly resolve file config +* Add empty judgment for constructorInterceptPoint +* Bump up gRPC to 1.68.1 +* Bump up netty to 4.1.115.Final +* Fix the `CreateAopProxyInterceptor` in the Spring core-patch to prevent it from changing the implementation of the + Spring AOP proxy +* Support Tracing for GlobalFilter and GatewayFilter in Spring Gateway +* [doc] Enhance Custom Trace Ignoring Plugin document about conflicts with the plugin of **sampler plugin with CPU + policy** +* [doc] Add Spring Gateway Plugin document +* [doc] Add 4 menu items guiding users to find important notices for Spring Annotation Plugin, Custom Trace Ignoring + Plugin, Kotlin Coroutine Plugin, and Spring Gateway Plugin +* Change context and parent entry span propagation mechanism from gRPC ThreadLocal context to SkyWalking native dynamic + field as new propagation mechanism, to better support async scenarios. +* Add Caffeine plugin as optional. +* Add Undertow 2.1.7.final+ worker thread pool metrics. +* Support for tracking in spring gateway versions 4.1.2 and above. +* Fix `ConsumeDriver` running status concurrency issues. + +All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/222?closed=1) \ No newline at end of file diff --git a/changes/changes-9.5.0.md b/changes/changes-9.5.0.md new file mode 100644 index 0000000000..8a74ea3b82 --- /dev/null +++ b/changes/changes-9.5.0.md @@ -0,0 +1,28 @@ +Changes by Version +================== +Release Notes. + +9.5.0 +------------------ + +* Add the virtual thread executor plugin +* Fix Conflicts apm-jdk-threadpool-plugin conflicts with apm-jdk-forkjoinpool-plugin +* Fix NPE in hikaricp-plugin if JDBC URL is not set +* Agent kernel services could be not-booted-yet as ServiceManager#INSTANCE#boot executed after agent transfer + initialization. Delay so11y metrics#build when the services are not ready to avoid MeterService status is not + initialized. +* Fix retransform failure when enhancing both parent and child classes. +* Add support for `dameng(DM)` JDBC url format in `URLParser`. +* Fix RabbitMQ Consumer could not receive handleCancelOk callback. +* Support for tracking in lettuce versions 6.5.x and above. +* Upgrade byte-buddy version to 1.17.6. +* Support gRPC 1.59.x and 1.70.x server interceptor trace +* Fix the `CreateAopProxyInterceptor` in the Spring core-patch changes the AOP proxy type when a class is + enhanced by both SkyWalking and Spring AOP. +* Build: Centralized plugin version management in the root POM and remove redundant declarations. +* Support Spring Cloud Gateway 4.3.x. +* Add `PROPAGATION_EXCLUDE_PORTS` config to httpclient-4.x plugin to skip tracing and header injection for + specified ports (default: 8123), fixing ClickHouse HTTP 400 caused by injected sw8 headers. + +All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/236?closed=1) + diff --git a/changes/changes-9.6.0.md b/changes/changes-9.6.0.md new file mode 100644 index 0000000000..74a4739aaa --- /dev/null +++ b/changes/changes-9.6.0.md @@ -0,0 +1,33 @@ +Changes by Version +================== +Release Notes. + +9.6.0 +------------------ + +* Add CLAUDE.md for AI assistant guidance. +* Bump up agent-oap protocol to latest(16c51358ebcf42629bf4ffdf952253971f20eb25). +* Bump up gRPC to v1.74.0. +* Bump up netty to v4.1.124.Final. +* Bump up GSON to v2.13.1. +* Bump up guava to v32.1.3. +* Bump up oap to the 10.3-dev.latest(dc8740d4757b35374283c4850a9a080e40f0eb79) in e2e. +* Bump up cli to the 0.15.0-dev.latest(77b4c49e89c9c000278f44e62729d534f2ec842e) in e2e. +* Bump up apache parent pom to v35. +* Update Maven to 3.6.3 in mvnw. +* Fix OOM due to too many span logs. +* Fix ClassLoader cache OOM issue with WeakHashMap. +* Fix Jetty client cannot receive the HTTP response body. +* Eliminate repeated code with HttpServletRequestWrapper in mvc-annotation-commons. +* Add the jdk httpclient plugin. +* Fix Gateway 2.0.x plugin not activated for spring-cloud-starter-gateway 2.0.0.RELEASE. +* Support kafka-clients-3.9.x intercept. +* Upgrade kafka-clients version in optional-reporter-plugins to 3.9.1. +* Fix AbstractLogger replaceParam when the replaced string contains a replacement marker. +* Fix `JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH` was not working in some plugins. +* Bump up Lombok to v1.18.42 to adopt JDK25 compiling. +* Add `eclipse-temurin:25-jre` as another base image. +* Add JDK25 plugin tests for Spring 6. +* Ignore classes starting with "sun.nio.cs" in bytebuddy due to potential class loading deadlock. + +All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/242?closed=1) diff --git a/dist-material/LICENSE b/dist-material/LICENSE index a5c6f781cf..a7cb7efaeb 100755 --- a/dist-material/LICENSE +++ b/dist-material/LICENSE @@ -215,13 +215,14 @@ Apache 2.0 licenses The following components are provided under the Apache License. See project link for details. The text of each license is the standard Apache 2.0 license. - raphw (byte-buddy) 1.14.9: http://bytebuddy.net/ , Apache 2.0 - Google: grpc-java 1.53.0: https://github.com/grpc/grpc-java, Apache 2.0 - Google: gson 2.8.9: https://github.com/google/gson , Apache 2.0 - Google: proto-google-common-protos 2.0.1: https://github.com/googleapis/googleapis , Apache 2.0 + raphw (byte-buddy) 1.17.6: http://bytebuddy.net/ , Apache 2.0 + Google: grpc-java 1.74.0: https://github.com/grpc/grpc-java, Apache 2.0 + Google: gson 2.13.1: https://github.com/google/gson , Apache 2.0 + Google: proto-google-common-protos 2.59.2: https://github.com/googleapis/googleapis , Apache 2.0 Google: jsr305 3.0.2: http://central.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.0/jsr305-3.0.0.pom , Apache 2.0 - Google: guava 32.0.1: https://github.com/google/guava , Apache 2.0 - netty 4.1.100: https://github.com/netty/netty/blob/4.1/LICENSE.txt, Apache 2.0 + Google: guava 32.1.3: https://github.com/google/guava , Apache 2.0 + netty 4.1.124: https://github.com/netty/netty/blob/4.1/LICENSE.txt, Apache 2.0 + async-profiler 3.0: https://github.com/async-profiler/async-profiler/blob/v3.0/LICENSE, Apache 2.0 ======================================================================== BSD licenses diff --git a/docs/en/contribution/compiling.md b/docs/en/contribution/compiling.md index d1507cc47a..da69b48a91 100644 --- a/docs/en/contribution/compiling.md +++ b/docs/en/contribution/compiling.md @@ -1,7 +1,7 @@ # Compiling project This document will help you compile and build a project in your maven and set your IDE. -Prepare JDK 17 or 21. +Prepare JDK 17, 21 or 25. * If you clone codes from https://github.com/apache/skywalking-java ```shell diff --git a/docs/en/contribution/release-java-agent.md b/docs/en/contribution/release-java-agent.md index f0149636da..5b941a822c 100644 --- a/docs/en/contribution/release-java-agent.md +++ b/docs/en/contribution/release-java-agent.md @@ -36,151 +36,47 @@ If you are a committer, use your Apache ID and password to log in this svn, and 1. Upload your GPG public key to the public GPG site, such as [MIT's site](http://pgp.mit.edu:11371/). This site should be in the Apache maven staging repository checklist. -## Test your settings -This step is only for testing purpose. If your env is correctly set, you don't need to check every time. -``` -./mvnw clean install(this will build artifacts, sources and sign) -``` - -## Prepare for the release -``` -./mvnw release:clean -./mvnw release:prepare -DautoVersionSubmodules=true -Pall -``` - -- Set version number as x.y.z, and tag as **v**x.y.z (The version tag must start with **v**. You will find out why this is necessary in the next step.) - -_You could do a GPG signature before preparing for the release. If you need to input the password to sign, and the maven doesn't provide you with the opportunity to do so, this may lead to failure of the release. To resolve this, you may run `gpg --sign xxx` in any file. This will allow it to remember the password for long enough to prepare for the release._ - -## Stage the release -``` -./mvnw release:perform -DskipTests -Pall -``` +## Release using the release script -- The release will be automatically inserted into a temporary staging repository. +The release script `tools/releasing/release.sh` automates the full release workflow. The release is a **two-step process** with a vote in between. -## Build and sign the source code and binary package +### Quick start ```shell -export RELEASE_VERSION=x.y.z (example: RELEASE_VERSION=5.0.0-alpha) -cd tools/releasing -bash create_release.sh -``` - -This script takes care of the following things: -1. Use `v` + `RELEASE_VERSION` as tag to clone the codes. -2. Complete `git submodule init/update`. -3. Exclude all unnecessary files in the target source tar, such as `.git`, `.github`, and `.gitmodules`. See the script for more details. -4. Execute `gpg` and `shasum 512` for source code tar. -5. Use maven package to build the agent tar. -6. Execute `gpg` and `shasum 512` for binary tar. - -`apache-skywalking-java-agent-x.y.z-src.tgz` and files ending with `.asc` and `.sha512` may be found in the `tools/releasing` folder. -`apache-skywalking-java-agent-x.y.z.tgz` and files ending with `.asc` and `.sha512` may be found in the `tools/releasing/apache-skywalking-java-agent-x.y.z` folder. - - -## Upload to Apache svn -1. Use your Apache ID to log in to `https://dist.apache.org/repos/dist/dev/skywalking/java-agent/`. -1. Create a folder and name it by the release version and round, such as: `x.y.z` -1. Upload the source code package to the folder with files ending with `.asc` and `.sha512`. -1. Upload the distribution package to the folder with files ending with `.asc` and `.sha512`. +# Step 1: Build, stage, upload, and generate vote email +./tools/releasing/release.sh prepare-vote x.y.z -## Make the internal announcements -Send an announcement mail in dev mail list. +# (send vote email to dev@skywalking.apache.org, wait 72h for vote to pass) +# Step 2: Promote, push Docker images, generate announce email, and clean up +./tools/releasing/release.sh vote-passed [old_version_to_remove] ``` -Mail title: [ANNOUNCE] SkyWalking Java Agent x.y.z test build available - -Mail content: -The test build of Java Agent x.y.z is available. - -We welcome any comments you may have, and will take all feedback into -account if a quality vote is called for this build. - -Release notes: - - * https://github.com/apache/skywalking-java/blob/master/changes/changes-x.y.z.md -Release Candidate: +Run `./tools/releasing/release.sh` without arguments to see all available commands, including individual steps if you need to run them separately. - * https://dist.apache.org/repos/dist/dev/skywalking/java-agent/xxxx - * sha512 checksums +### Pre-flight checks +Before starting, the script verifies: +- Required tools are installed (git, gpg, svn, shasum, mvn, java, tar, gh) +- GPG signing works **without password prompt** (critical for maven release) +- Maven settings contain Apache server credentials +- Git working tree is clean -Maven 2 staging repository: - - * https://repository.apache.org/content/repositories/xxxx/org/apache/skywalking-java/ - -Release Tag : - - * (Git Tag) x.y.z - -Release CommitID : - - * https://github.com/apache/skywalking-java/tree/(Git Commit ID) - * Git submodule - * apm-protocol/apm-network/src/main/proto: https://github.com/apache/skywalking-data-collect-protocol/tree/(Git Commit ID) - -Keys to verify the Release Candidate : - - * https://dist.apache.org/repos/dist/release/skywalking/KEYS - -Guide to build the release from source : - - > ./mvnw clean package - -A vote regarding the quality of this test build will be initiated -within the next couple of days. +If GPG signing fails, configure gpg-agent to cache the passphrase: ``` - -## Wait for at least 48 hours for test responses -Any PMC member, committer or contributor can test the release features and provide feedback. -Based on that, the PMC will decide whether to start the voting process. - -## Call a vote in dev -Call a vote in `dev@skywalking.apache.org` - +# ~/.gnupg/gpg-agent.conf +default-cache-ttl 86400 +max-cache-ttl 86400 ``` -Mail title: [VOTE] Release Apache SkyWalking Java Agent version x.y.z - -Mail content: -Hi All, -This is a call for vote to release Apache SkyWalking Java Agent version x.y.z. - -Release notes: - - * https://github.com/apache/skywalking-java/blob/master/changes/changes-x.y.z.md - -Release Candidate: - - * https://dist.apache.org/repos/dist/dev/skywalking/java-agent/xxxx - * sha512 checksums - -Maven 2 staging repository: +Then run `gpgconf --kill gpg-agent` and `gpg --sign /dev/null` to cache it. - * https://repository.apache.org/content/repositories/xxxx/org/apache/skywalking/ +### prepare-vote +`prepare-vote` runs the following steps in sequence: +1. **preflight** — verify tools and environment +2. **prepare** — create `release/x.y.z` branch, run `mvn release:prepare` (creates tag `vx.y.z` with full CHANGES.md), then archive changelog and reset for next version, push branch and tag, create PR +3. **stage** — run `mvn release:perform`, build source and binary tars with GPG signatures and sha512 checksums +4. **upload** — upload to Apache SVN `dist/dev` (prompts for SVN credentials) +5. **email vote** — print vote email template with pre-filled version, commit ID, submodule commit, and checksums -Release Tag : - - * (Git Tag) x.y.z - -Release CommitID : - - * https://github.com/apache/skywalking-java/tree/(Git Commit ID) - * Git submodule - * apm-protocol/apm-network/src/main/proto: https://github.com/apache/skywalking-data-collect-protocol/tree/(Git Commit ID) - -Keys to verify the Release Candidate : - - * https://dist.apache.org/repos/dist/release/skywalking/KEYS - -Guide to build the release from source : - - > ./mvnw clean package - -Voting will start now (xxxx date) and will remain open for at least 72 hours, Request all PMC members to give their vote. -[ ] +1 Release this package. -[ ] +0 No opinion. -[ ] -1 Do not release this package because.... -``` +Copy the generated email and send it to `dev@skywalking.apache.org`. Voting remains open for at least 72 hours. At least 3 (+1 binding) PMC votes with more +1 than -1 are required. ## Vote Check All PMC members and committers should check these before casting +1 votes. @@ -195,91 +91,9 @@ are found in `https://dist.apache.org/repos/dist/dev/skywalking/java-agent/x.y.z 1. Build a distribution package from the source code package (`apache-skywalking-java-agent-x.y.z-src.tar.gz`). 1. Check the Apache License Header. Run `docker run --rm -v $(pwd):/github/workspace apache/skywalking-eyes header check`. (No binaries in source codes) - -The voting process is as follows: -1. All PMC member votes are +1 binding, and all other votes are +1 but non-binding. -1. If you obtain at least 3 (+1 binding) votes with more +1 than -1 votes within 72 hours, the release will be approved. - - -## Publish the release -1. Move source codes tar and distribution packages to `https://dist.apache.org/repos/dist/release/skywalking/java-agent/`. -``` -> export SVN_EDITOR=vim -> svn mv https://dist.apache.org/repos/dist/dev/skywalking/java-agent/x.y.z https://dist.apache.org/repos/dist/release/skywalking/java-agent -.... -enter your apache password -.... - -``` -2. Release in the nexus staging repo. -3. Public download source and distribution tar/zip are located in `http://www.apache.org/dyn/closer.cgi/skywalking/java-agent/x.y.z/xxx`. -The Apache mirror path is the only release information that we publish. -4. Public asc and sha512 are located in `https://www.apache.org/dist/skywalking/java-agent/x.y.z/xxx`. -5. Public KEYS point to `https://www.apache.org/dist/skywalking/KEYS`. -6. Update the website download page. http://skywalking.apache.org/downloads/ . Add a new download source, distribution, sha512, asc, and document -links. The links can be found following rules (3) to (6) above. -7. Add a release event on the website homepage and event page. Announce the public release with changelog or key features. -8. Send ANNOUNCE email to `dev@skywalking.apache.org`, `announce@apache.org`. The sender should use the Apache email account. -``` -Mail title: [ANNOUNCE] Apache SkyWalking Java Agent x.y.z released - -Mail content: -Hi all, - -Apache SkyWalking Team is glad to announce the first release of Apache SkyWalking Java Agent x.y.z. - -SkyWalking: APM (application performance monitor) tool for distributed systems, -especially designed for microservices, cloud native and container-based (Docker, Kubernetes, Mesos) architectures. - -The Java Agent for Apache SkyWalking, which provides the native tracing/metrics/logging abilities for Java projects. - -This release contains a number of new features, bug fixes and improvements compared to -version a.b.c(last release). The notable changes since x.y.z include: - -(Highlight key changes) -1. ... -2. ... -3. ... - -Please refer to the change log for the complete list of changes: -https://github.com/apache/skywalking-java/blob/master/changes/changes-x.y.z.md - -Apache SkyWalking website: -http://skywalking.apache.org/ - -Downloads: -http://skywalking.apache.org/downloads/ - -Twitter: -https://twitter.com/AsfSkyWalking - -SkyWalking Resources: -- GitHub: https://github.com/apache/skywalking-java -- Issue: https://github.com/apache/skywalking/issues -- Mailing list: dev@skywalkiing.apache.org - - -- Apache SkyWalking Team -``` - -## Release Docker images - -```shell -export SW_VERSION=x.y.z -git clone --depth 1 --branch v$SW_VERSION https://github.com/apache/skywalking-java.git -cd skywalking-java - -curl -O https://dist.apache.org/repos/dist/release/skywalking/java-agent/$SW_VERSION/apache-skywalking-java-agent-$SW_VERSION.tgz -tar -xzvf apache-skywalking-java-agent-$SW_VERSION.tgz - -export NAME=skywalking-java-agent -export HUB=apache -export TAG=$SW_VERSION - -make docker.push.alpine docker.push.java8 docker.push.java11 docker.push.java17 docker.push.java21 -``` - -## Clean up the old releases -Once the latest release has been published, you should clean up the old releases from the mirror system. -1. Update the download links (source, dist, asc, and sha512) on the website to the archive repo (https://archive.apache.org/dist/skywalking). -2. Remove previous releases from https://dist.apache.org/repos/dist/release/skywalking/java-agent. +## vote-passed +After the vote passes, run `vote-passed` which executes: +1. **promote** — move packages from `dist/dev` to `dist/release` in Apache SVN (prompts for SVN credentials), then release the Nexus staging repository at https://repository.apache.org and update the website download page +2. **docker** — build and push all Docker image variants (alpine, java8, java11, java17, java21, java25) +3. **email announce** — print announcement email template. Copy and send to `dev@skywalking.apache.org` and `announce@apache.org` +4. **cleanup** (optional) — if old version is provided, remove it from `dist/release`. Update download page links to point to `https://archive.apache.org/dist/skywalking` diff --git a/docs/en/setup/service-agent/java-agent/Agent-self-observability.md b/docs/en/setup/service-agent/java-agent/Agent-self-observability.md new file mode 100644 index 0000000000..7efa7ee1b4 --- /dev/null +++ b/docs/en/setup/service-agent/java-agent/Agent-self-observability.md @@ -0,0 +1,16 @@ +# Agent Self Observability +The Java Agent self-observability feature is built-in and used to measure the tracing performance and error statistics of plugins. + +It reports meters to SkyWalking oap through native meter protocol, OAP receives and analyzes meters, +which are ultimately presented on the [Java Agent self-observability dashboard](https://skywalking.apache.org/docs/main/next/en/setup/backend/dashboards-so11y-java-agent/). + +***Note: Java Agent self-observability dashboard is available since OAP 10.1.0*** + +# Details of agent so11y meters +- `created_tracing_context_counter` - Counter. The number of created tracing contexts. This includes a label=created_by(value=sampler,propagated). `created_by=propagated` means the agent created the context due to downstream service added sw8 header to trigger force sampling. `created_by=sampler` means the agent created this context by local sampler no matter which policy it uses. +- `finished_tracing_context_counter` - Counter. The number of finished contexts. The gap between `finished_tracing_context_counter` and `created_tracing_context_counter` should be relatively stable, otherwise, the memory cost would be increased. +- `created_ignored_context_counter` and `finished_ignored_context_counter`. Same concepts like `*_tracing_context_counter`. +- `interceptor_error_counter` - Counter. The number of errors happened in the interceptor logic, with `label=plugin_name, inter_type(constructor, inst, static)`. We don't add interceptor names into labels in case of OOM. The number of plugins is only dozens, it is predictable, but the number of interceptors will be hundreds. +- `possible_leaked_context_counter` - Counter. The number of detected leaked contexts. It should include the `label=source(value=tracing, ignore)`. When `source=tracing`, it is today's shadow tracing context. But now, it is measured. +- `tracing_context_performance` - Histogram. For successfully finished tracing context, it measures every interceptor's time cost(by using nanoseconds), the buckets of the histogram are {1000, 10000, 50000, 100000, 300000, 500000, + 1000000, 5000000, 10000000, 20000000, 50000000, 100000000}ns. This provides the performance behavior for the tracing operations. \ No newline at end of file diff --git a/docs/en/setup/service-agent/java-agent/Application-toolkit-log4j-1.x.md b/docs/en/setup/service-agent/java-agent/Application-toolkit-log4j-1.x.md index b180d164ae..2768fa4a72 100644 --- a/docs/en/setup/service-agent/java-agent/Application-toolkit-log4j-1.x.md +++ b/docs/en/setup/service-agent/java-agent/Application-toolkit-log4j-1.x.md @@ -14,7 +14,7 @@ log4j.appender.CONSOLE.layout=org.apache.skywalking.apm.toolkit.log.log4j.v1.x.TraceIdPatternLayout ``` -* set `%T` in `layout.ConversionPattern` ( In 2.0-2016, you should use %x, [Why change?](https://github.com/wu-sheng/sky-walking/issues/77) ) +* set `%T` in `layout.ConversionPattern` ( In 2.0-2016, you should use %x, [Why change?](https://github.com/apache/skywalking/issues/77) ) ```properties log4j.appender.CONSOLE.layout.ConversionPattern=%d [%T] %-5p %c{1}:%L - %m%n ``` diff --git a/docs/en/setup/service-agent/java-agent/Bootstrap-plugins.md b/docs/en/setup/service-agent/java-agent/Bootstrap-plugins.md index 10dd955a9c..f03a4e21a7 100644 --- a/docs/en/setup/service-agent/java-agent/Bootstrap-plugins.md +++ b/docs/en/setup/service-agent/java-agent/Bootstrap-plugins.md @@ -7,6 +7,8 @@ Now, we have the following known bootstrap plugins. * Plugin of JDK Callable and Runnable. Agent is compatible with JDK 1.8+ * Plugin of JDK ThreadPoolExecutor. Agent is compatible with JDK 1.8+ * Plugin of JDK ForkJoinPool. Agent is compatible with JDK 1.8+ +* Plugin of JDK VirtualThreadExecutor. Agent is compatible with JDK 21+ +* Plugin of JDK HttpClient. Agent is compatible with JDK 11+ ### HttpURLConnection Plugin Notice The plugin of JDK HttpURLConnection depended on `sun.net.*`. When using Java 9+, You should add some JVM options as follows: diff --git a/docs/en/setup/service-agent/java-agent/Claude-Skills.md b/docs/en/setup/service-agent/java-agent/Claude-Skills.md new file mode 100644 index 0000000000..f851ee6c52 --- /dev/null +++ b/docs/en/setup/service-agent/java-agent/Claude-Skills.md @@ -0,0 +1,64 @@ +# Claude Code Skills + +[Claude Code](https://claude.ai/claude-code) is an AI-powered CLI tool by Anthropic. This project includes +custom **skills** (`.claude/skills/`) that teach Claude Code how to work with the SkyWalking Java Agent codebase. + +Skills are reusable prompt templates that Claude Code can invoke via slash commands. They encode project-specific +knowledge so that common development tasks can be performed consistently and correctly. + +## Available Skills + +### `/new-plugin` — Develop a New Plugin + +Guides the full lifecycle of creating a new SkyWalking Java agent plugin: + +1. **Gather requirements** — target library, observation type (tracing/meter), span types +2. **Identify interception points** — understand library usage, trace execution flow, choose classes/methods +3. **Create plugin module** — directory structure, pom.xml, dependencies (`provided` scope) +4. **Implement instrumentation** — V2 API, class matching (ByteBuddy), method matching +5. **Implement interceptors** — ContextManager spans, ContextCarrier inject/extract, EnhancedInstance dynamic fields +6. **Register plugin** — `skywalking-plugin.def` +7. **Write unit tests** — TracingSegmentRunner, SegmentStorage +8. **Write E2E tests** — Docker-based scenarios, expectedData.yaml +9. **Code style** — checkstyle compliance, import restrictions +10. **Update documentation** — Supported-list.md, CHANGES.md + +Key principles encoded in this skill: +- Always use **V2 API** (`ClassEnhancePluginDefineV2`, `InstanceMethodsAroundInterceptorV2`) +- **Never use `.class` references** in instrumentation — always string literals +- **Never use reflection** to access private fields — choose interception points with accessible data +- **Never use Maps** to cache per-instance context — use `EnhancedInstance.setSkyWalkingDynamicField()` +- **Verify actual source code** of target libraries — never speculate from version numbers +- Span lifecycle APIs are **ThreadLocal-based** — create/stop in same thread unless async mode + +### `/compile` — Build the Project + +Runs the appropriate build command based on what you need: +- Full build with or without tests +- Single module build +- Checkstyle check +- Plugin E2E test scenarios +- Protobuf source generation for IDE setup + +## How to Use + +1. Install [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) +2. Navigate to the `skywalking-java` repository root +3. Run `claude` to start Claude Code +4. Type `/new-plugin` or `/compile` to invoke a skill + +Skills can also be triggered implicitly — when you describe a task that matches a skill's purpose, +Claude Code may suggest or invoke it automatically. + +## Project Context Files + +In addition to skills, the project includes `CLAUDE.md` files that provide codebase context: + +| File | Purpose | +|------|---------| +| `CLAUDE.md` (root) | Project overview, build system, architecture, conventions | +| `apm-sniffer/apm-sdk-plugin/CLAUDE.md` | SDK plugin development guide (V2 API, class matching, testing) | +| `apm-sniffer/bootstrap-plugins/CLAUDE.md` | Bootstrap plugin specifics (JDK class instrumentation) | + +These files are automatically loaded by Claude Code when working in the repository, providing it with +the knowledge needed to assist with development tasks. diff --git a/docs/en/setup/service-agent/java-agent/Java-Plugin-Development-Guide.md b/docs/en/setup/service-agent/java-agent/Java-Plugin-Development-Guide.md index 2aa6d148c3..c581000ea5 100644 --- a/docs/en/setup/service-agent/java-agent/Java-Plugin-Development-Guide.md +++ b/docs/en/setup/service-agent/java-agent/Java-Plugin-Development-Guide.md @@ -11,7 +11,7 @@ We also provide the [plugin test tool](#plugin-test-tool) to verify the data col ## Concepts ### Span The span is an important and recognized concept in the distributed tracing system. Learn about the **span** from the -[Google Dapper Paper](https://research.google.com/pubs/pub36356.html) and +[Google Dapper Paper](https://research.google/pubs/dapper-a-large-scale-distributed-systems-tracing-infrastructure/) and [OpenTracing](http://opentracing.io) SkyWalking has supported OpenTracing and OpenTracing-Java API since 2017. Our concepts of the span are similar to that of the Google Dapper Paper and OpenTracing. We have also extended the span. @@ -360,7 +360,7 @@ The following sections will tell you how to implement the interceptor. 3. Add plugin definition into the `skywalking-plugin.def` file. ```properties -tomcat-7.x/8.x=TomcatInstrumentation +tomcat=TomcatInstrumentation ``` 4. Set up `witnessClasses` and/or `witnessMethods` if the instrumentation has to be activated in specific versions. @@ -621,3 +621,26 @@ Please follow these steps: 1. Send a pull request and ask for review. 1. The plugin committers will approve your plugins, plugin CI-with-IT, e2e, and the plugin tests will be passed. 1. The plugin is accepted by SkyWalking. + +### Accessing package-private target classes + +When a plugin needs to call methods on a **package-private** class in the target library (e.g., `MongoClusterImpl` which is `final class` without `public`), you cannot import or cast to it from the plugin's `org.apache.skywalking` package. + +**Important:** Same-package helper classes do NOT work because the agent and application use different classloaders. Java treats them as different runtime packages even with identical package names, so package-private access is denied with `IllegalAccessError`. + +**Solution:** Use `setAccessible` reflection to call public methods on package-private classes: + +```java +try { + java.lang.reflect.Method method = objInst.getClass().getMethod("publicMethodName"); + method.setAccessible(true); // Required: class is package-private + Object result = method.invoke(objInst); + if (result instanceof EnhancedInstance) { + ((EnhancedInstance) result).setSkyWalkingDynamicField(value); + } +} catch (Exception e) { + logger.warn("Failed to access method", e); +} +``` + +**When to use:** Only when the target class is package-private and you need to call its public methods. Prefer normal casting in interceptors when the class is public. diff --git a/docs/en/setup/service-agent/java-agent/Optional-plugins.md b/docs/en/setup/service-agent/java-agent/Optional-plugins.md index 8b8a0ec1c3..b8ab3524f9 100644 --- a/docs/en/setup/service-agent/java-agent/Optional-plugins.md +++ b/docs/en/setup/service-agent/java-agent/Optional-plugins.md @@ -8,13 +8,12 @@ Now, we have the following known 2 kinds of optional plugins. These plugins affect the performance or must be used under some conditions, from experiences. So only released in `/optional-plugins` or `/bootstrap-plugins`, copy to `/plugins` in order to make them work. -* [Plugin of tracing Spring annotation beans](agent-optional-plugins/Spring-annotation-plugin.md) -* [Plugin of tracing Oracle and Resin](agent-optional-plugins/Oracle-Resin-plugins.md) +* Plugin of [tracing Spring annotation beans](agent-optional-plugins/Spring-annotation-plugin.md) * [Filter traces through specified endpoint name patterns](agent-optional-plugins/trace-ignore-plugin.md) * Plugin of Gson serialization lib in optional plugin folder. * Plugin of Zookeeper 3.4.x in optional plugin folder. The reason of being optional plugin is, many business irrelevant traces are generated, which cause extra payload to agents and backends. At the same time, those traces may be just heartbeat(s). * [Customize enhance](Customize-enhance-trace.md) Trace methods based on description files, rather than write plugin or change source codes. -* Plugin of Spring Cloud Gateway 2.x and 3.x and 4.x in optional plugin folder. Please only activate this plugin when you install agent in Spring Gateway. +* Plugin of [Spring Cloud Gateway 2.x and 3.x and 4.x](agent-optional-plugins/spring-gateway.md) in optional plugin folder. Please only activate this plugin when you install agent in Spring Gateway. * Plugin of Spring Transaction in optional plugin folder. The reason of being optional plugin is, many local span are generated, which also spend more CPU, memory and network. * [Plugin of Kotlin coroutine](agent-optional-plugins/Kotlin-Coroutine-plugin.md) provides the tracing across coroutines automatically. As it will add local spans to all across routines scenarios, Please assess the performance impact. * Plugin of quartz-scheduler-2.x in the optional plugin folder. The reason for being an optional plugin is, many task scheduling systems are based on quartz-scheduler, this will cause duplicate tracing and link different sub-tasks as they share the same quartz level trigger, such as ElasticJob. @@ -26,11 +25,18 @@ So only released in `/optional-plugins` or `/bootstrap-plugins`, copy to `/plugi * Plugin of fastjson serialization lib in optional plugin folder. * Plugin of jackson serialization lib in optional plugin folder. * Plugin of Apache ShenYu(incubating) Gateway 2.4.x in optional plugin folder. Please only activate this plugin when you install agent in Apache ShenYu Gateway. -* Plugin of trace sampler CPU policy in the optional plugin folder. Please only activate this plugin when you need to disable trace collecting when the agent process CPU usage is too high(over threshold). +* Plugin of sampler plugin with CPU policy in the optional plugin folder. Please only activate this plugin when you need to disable trace collecting when the agent process CPU usage is too high(over threshold). * Plugin for Spring 6.x and RestTemplate 6.x are in the optional plugin folder. Spring 6 requires Java 17 but SkyWalking is still compatible with Java 8. So, we put it in the optional plugin folder. * Plugin of nacos-client 2.x lib in optional plugin folder. The reason is many business irrelevant traces are generated, which cause extra payload to agents and backends, also spend more CPU, memory and network. * Plugin of netty-http 4.1.x lib in optional plugin folder. The reason is some frameworks use Netty HTTP as kernel, which could double the unnecessary spans and create incorrect RPC relative metrics. +### Notice due to Licence Restrictions +These plugins can't be provided in Apache release because of Oracle and Resin Licenses. +If you want to know details, please read [Apache license legal document](https://www.apache.org/legal/resolved.html) + +Due to license incompatibilities/restrictions these plugins are hosted and released in 3rd part repository, +go to [OpenSkywalking java plugin extension repository](https://github.com/OpenSkywalking/java-plugin-extensions) to get these. + ## Optional Level 3 Plugins. Expired Plugins These plugins are not tested in the CI/CD pipeline, as the previous added tests are not able to run according to the latest CI/CD infrastructure limitations, lack of maintenance, or dependencies/images not available(e.g. removed from DockerHub). diff --git a/docs/en/setup/service-agent/java-agent/Plugin-list.md b/docs/en/setup/service-agent/java-agent/Plugin-list.md index 16136fce70..ac9ea19eeb 100644 --- a/docs/en/setup/service-agent/java-agent/Plugin-list.md +++ b/docs/en/setup/service-agent/java-agent/Plugin-list.md @@ -25,6 +25,7 @@ - elasticsearch-5.x - elasticsearch-6.x - elasticsearch-7.x +- elasticsearch-java - fastjson-1.2.x - feign-default-http-9.x - feign-pathvar-9.x @@ -47,22 +48,31 @@ - influxdb-2.x - jackson-2.x - jdk-http-plugin +- jdk-httpclient-plugin - jdk-threading-plugin +- jdk-virtual-thread-executor-plugin - jedis-2.x-3.x - jedis-4.x - jetty-client-9.0 - jetty-client-9.x -- jetty-server-9.x +- jetty-server +- jetty-server-12.x - kafka-0.11.x/1.x/2.x +- kafka-3.7.x +- kafka-3.9.x - kotlin-coroutine -- lettuce-5.x +- lettuce-common +- lettuce-5.x-6.4.x +- lettuce-6.5.x - light4j - mariadb-2.x +- mariadb-3.x - micrometer-1.10.x - memcache-2.x - mongodb-2.x - mongodb-3.x - mongodb-4.x +- mongodb-5.x - motan-0.x - mybatis-3.x - mysql-5.x @@ -97,6 +107,7 @@ - sharding-sphere-5.0.0 - sofarpc - solrj-7.x +- spring-ai-1.x - spring-annotation - spring-async-annotation-5.x - spring-cloud-feign-1.x @@ -107,6 +118,7 @@ - spring-core-patch - spring-kafka-1.x - spring-kafka-2.x +- spring-rabbitmq - spring-mvc-annotation - spring-mvc-annotation-3.x - spring-mvc-annotation-4.x @@ -121,9 +133,9 @@ - spring-webflux-5.x-webclient - spymemcached-2.x - struts2-2.x +- struts2-7.x - thrift -- tomcat-7.x/8.x -- tomcat-10.x +- tomcat - toolkit-counter - toolkit-gauge - toolkit-histogram @@ -161,7 +173,7 @@ - hutool-http-5.x - micronaut-http-client-3.2.x-3.6.x - micronaut-http-server-3.2.x-3.6.x -- nats-client-2.14.x-2.15.x +- nats-client-2.14.x-2.16.5 - impala-jdbc-2.6.x - jdk-forkjoinpool-plugin - jetty-thread-pool @@ -169,10 +181,12 @@ - jersey-3.x - grizzly-2.3.x-4.x - grizzly-2.3.x-4.x-threadpool -- jetty-server-11.x - jetty-client-11.x - websphere-liberty-23.x - spring-cloud-gateway-4.x - spring-webflux-6.x - spring-webflux-6.x-webclient - activemq-artemis-jakarta-client-2.x +- c3p0-0.9.x +- solon-2.x +- caffeine-3.x diff --git a/docs/en/setup/service-agent/java-agent/Plugin-test.md b/docs/en/setup/service-agent/java-agent/Plugin-test.md index e67c365672..ca1d6ef2c2 100644 --- a/docs/en/setup/service-agent/java-agent/Plugin-test.md +++ b/docs/en/setup/service-agent/java-agent/Plugin-test.md @@ -16,11 +16,11 @@ Every plugin maintained in the main repo requires corresponding test cases as we ## Case Base Image Introduction -The test framework provides `JVM-container` and `Tomcat-container` base images including JDK8 and JDK17. You can choose the best one for your test case. If both are suitable for your case, **`JVM-container` is preferred**. +The test framework provides `JVM-container` and `Tomcat-container` base images. You can choose the best one for your test case. If both are suitable for your case, **`JVM-container` is preferred**. ### JVM-container Image Introduction -[JVM-container](../../../../../test/plugin/containers/jvm-container) uses `eclipse-temurin:8-jdk` as the base image. `JVM-container` supports JDK8 and JDK17 as well in CI, which inherits `eclipse-temurin:8-jdk` and `eclipse-temurin:17-jdk`. +[JVM-container](../../../../../test/plugin/containers/jvm-container) uses `eclipse-temurin:8-jdk` as the default base image. In CI it is exercised on **JDK 8, 11, 17, 21 and 25** (via the `eclipse-temurin:{8,11,17,21,25}-jdk` images). It is supported to custom the base Java docker image by specify `base_image_java`. The test case project must be packaged as `project-name.zip`, including `startup.sh` and uber jar, by using `mvn clean package`. @@ -31,13 +31,17 @@ Take the following test projects as examples: ### Tomcat-container Image Introduction -[Tomcat-container](../../../../../test/plugin/containers/tomcat-container) uses `tomcat:8.5-jdk8-openjdk`, `tomcat:8.5-jdk17-openjdk` as the base image. -It is supported to custom the base Tomcat docker image by specify `base_image_tomcat`. +[Tomcat-container](../../../../../test/plugin/containers/tomcat-container) uses `tomcat:8.5-jdk8-openjdk` as the default base image. In CI it is exercised on **Tomcat 8.5, 9.0, 10.0 and 10.1** (e.g. `tomcat:8.5-jdk8-openjdk`, `tomcat:9.0.71-jdk8`, `tomcat:10.0.22-jdk8`, `tomcat:8.5-jdk17-openjdk`, `tomcat:10.1-jdk17-temurin`, `tomcat:10.1-jdk21-temurin`). Tomcat 8.5/9.0 are `javax.servlet`; Tomcat 10.0/10.1 are `jakarta.servlet`. +It is supported to custom the base Tomcat docker image by specify `base_image_tomcat`. The base image must provide `curl` (used by the container's health check); if an official image lacks it, build a small custom image that adds `curl` first — as the JDK 25 lane does with `tomcat-curl:10.1.50-jdk25-temurin`. The test case project must be packaged as `project-name.war` by using `mvn package`. Take the following test project as an example * [spring-4.3.x-scenario](../../../../../test/plugin/scenarios/spring-4.3.x-scenario) +Note on `tomcat:10.1.50-jdk25-temurin` image: + +Starting from JDK 22, the Tomcat community removed `curl` from the official Tomcat JDK images. Since `curl` is required by our plugin test framework, we provide a custom `tomcat-jdk25` image built with `curl` installed. +You can refer to the [tomcat-jdk25-dockerFile](../../../../../.github/workflows/Dockerfile-tomcat-jdk25-withCurl) to build the image for local plugin tests. ## Test project hierarchical structure The test case is an independent maven project, and it must be packaged as a war tar ball or zip file, depending on the chosen base image. Also, two external accessible endpoints usually two URLs) are required. @@ -97,6 +101,16 @@ File Name | Descriptions `*` support-version.list format requires every line for a single version (contains only the last version number of each minor version). You may use `#` to comment out this version. +Each version line supports optional extra Maven properties using comma-separated `key=value` pairs: +``` +# Simple version +2.3.10.RELEASE + +# Version with extra Maven properties (passed as -D flags) +2.7.14,spring.boot.version=2.5.15 +``` +This allows different framework versions to use different dependency versions without creating separate test scenarios. + ### configuration.yml | Field | description @@ -107,7 +121,7 @@ File Name | Descriptions | startScript | Path of the start up script. Required in `type: jvm` only. | runningMode | Running mode with the optional plugin, options, `default`(default), `with_optional`, or `with_bootstrap`. | withPlugins | Plugin selector rule, e.g.:`apm-spring-annotation-plugin-*.jar`. Required for `runningMode=with_optional` or `runningMode=with_bootstrap`. -| environment | Same as `docker-compose#environment`. +| environment | Same as `docker-compose#environment` and also used as `docker run` environment variables. | depends_on | Same as `docker-compose#depends_on`. | dependencies | Same as `docker-compose#services`, `image`, `links`, `hostname`, `command`, `environment` and `depends_on` are supported. @@ -682,6 +696,7 @@ You can run `python3 tools/select-group.py` to see which file contains the least If a test case required to run in JDK 17 environment, please add you test case into file `plugins-jdk17-test..yaml`. If a test case required to run in JDK 21 environment, please add you test case into file `plugins-jdk21-test..yaml`. +If a test case required to run in JDK 25 environment, please add you test case into file `plugins-jdk25-test..yaml`. ```yaml jobs: diff --git a/docs/en/setup/service-agent/java-agent/README.md b/docs/en/setup/service-agent/java-agent/README.md index f6702c006f..d829b741fc 100755 --- a/docs/en/setup/service-agent/java-agent/README.md +++ b/docs/en/setup/service-agent/java-agent/README.md @@ -1,6 +1,6 @@ # Setup java agent -1. Agent is available for JDK 8 - 21. +1. Agent is available for JDK 8 - 25. 1. Find `agent` folder in SkyWalking release package 1. Set `agent.service_name` in `config/agent.config`. Could be any String in English. 1. Set `collector.backend_service` in `config/agent.config`. Default point to `127.0.0.1:11800`, only works for local diff --git a/docs/en/setup/service-agent/java-agent/Supported-list.md b/docs/en/setup/service-agent/java-agent/Supported-list.md index da1aef6411..1416ec3471 100644 --- a/docs/en/setup/service-agent/java-agent/Supported-list.md +++ b/docs/en/setup/service-agent/java-agent/Supported-list.md @@ -11,13 +11,13 @@ metrics based on the tracing data. * Spring MVC 3.x, 4.x 5.x with servlet 3.x * Spring MVC 6.x (Optional²) * [Nutz Web Framework](https://github.com/nutzam/nutz) 1.x - * [Struts2 MVC](http://struts.apache.org/) 2.3.x -> 2.5.x - * Resin 3 (Optional¹) - * Resin 4 (Optional¹) - * [Jetty Server](http://www.eclipse.org/jetty/) 9.x -> 11.x - * [Spring WebFlux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) 5.x (Optional¹) -> 6.x (Optional¹) - * [Undertow](http://undertow.io/) 1.3.0.Final -> 2.0.27.Final - * [RESTEasy](https://resteasy.github.io/) 3.1.0.Final -> 6.2.4.Final + * [Struts2 MVC](http://struts.apache.org/) 2.3.x -> 2.5.x, 7.x (Jakarta, JDK17) + * Resin 3 (Optional¹), See [SkySPM Plugin Repository](https://github.com/SkyAPM/java-plugin-extensions) + * Resin 4 (Optional¹), See [SkySPM Plugin Repository](https://github.com/SkyAPM/java-plugin-extensions) + * [Jetty Server](http://www.eclipse.org/jetty/) 9.x -> 12.x + * [Spring WebFlux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) 5.x (Optional²) -> 6.x (Optional²) + * [Undertow](http://undertow.io/) 1.3.0.Final -> 2.3.18.Final + * [RESTEasy](https://resteasy.dev/) 3.1.0.Final -> 6.2.4.Final * [Play Framework](https://www.playframework.com/) 2.6.x -> 2.8.x * [Light4J Microservices Framework](https://doc.networknt.com/) 1.6.x -> 2.x * [Netty SocketIO](https://github.com/mrniko/netty-socketio) 1.x @@ -26,8 +26,9 @@ metrics based on the tracing data. * [Grizzly](https://github.com/eclipse-ee4j/grizzly) 2.3.x -> 4.x * [WebSphere Liberty](https://github.com/OpenLiberty/open-liberty) 23.x * [Netty HTTP](https://github.com/netty/netty) 4.1.x (Optional²) + * [Solon](https://github.com/opensolon/solon) 2.7.x -> 2.8.x * HTTP Client - * [Feign](https://github.com/OpenFeign/feign) 9.x + * [Feign](https://github.com/OpenFeign/feign) 9.x -> 13.5 * [Netflix Spring Cloud Feign](https://github.com/spring-cloud/spring-cloud-openfeign) 1.1.x -> 2.x * [Okhttp](https://github.com/square/okhttp) 2.x -> 3.x -> 4.x * [Apache httpcomponent HttpClient](http://hc.apache.org/) 2.0 -> 3.1, 4.2, 4.3, 5.0, 5.1 @@ -41,15 +42,15 @@ metrics based on the tracing data. * [Hutool-http](https://www.hutool.cn/) client 5.x * [Micronaut HTTP Client](https://github.com/micronaut-projects/micronaut-core) 3.2.x -> 3.6.x * HTTP Gateway - * [Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) 2.0.2.RELEASE -> 4.1.x (Optional²) + * [Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) 2.0.2.RELEASE -> 4.3.x (Optional²) * [Apache ShenYu](https://shenyu.apache.org) (Rich protocol support: `HTTP`,`Spring Cloud`,`gRPC`,`Dubbo`,`SOFARPC`,`Motan`,`Tars`) 2.4.x (Optional²) * JDBC - * Mysql Driver 5.x, 6.x, 8.x - * Oracle Driver (Optional¹) + * Mysql Driver 5.x, 6.x, 8.x, 9.x + * Oracle Driver (Optional¹), See [SkySPM Plugin Repository](https://github.com/SkyAPM/java-plugin-extensions) * H2 Driver 1.3.x -> 1.4.x * [ShardingSphere](https://github.com/apache/shardingsphere) 3.0.0, 4.0.0, 4.0.1, 4.1.0, 4.1.1, 5.0.0 * PostgreSQL Driver 8.x, 9.x, 42.x - * Mariadb Driver 2.x, 1.8 + * Mariadb Driver 1.8, 2.x (2.0 -> 2.7), 3.x (3.0 -> 3.5) * [InfluxDB](https://github.com/influxdata/influxdb-java) 2.5 -> 2.17 * [Mssql-Jtds](https://github.com/milesibastos/jTDS) 1.x * [Mssql-jdbc](https://github.com/microsoft/mssql-jdbc) 6.x -> 8.x @@ -75,21 +76,22 @@ metrics based on the tracing data. * MQ * [RocketMQ](https://github.com/apache/rocketmq) 3.x-> 5.x * [RocketMQ-gRPC](http://github.com/apache/rocketmq-clients) 5.x - * [Kafka](http://kafka.apache.org) 0.11.0.0 -> 3.2.3 - * [Spring-Kafka](https://github.com/spring-projects/spring-kafka) Spring Kafka Consumer 1.3.x -> 2.3.x (2.0.x and 2.1.x not tested and not recommended by [the official document](https://spring.io/projects/spring-kafka)) + * [Kafka](http://kafka.apache.org) 0.11.0.0 -> 3.9.1 + * [Spring-Kafka](https://github.com/spring-projects/spring-kafka) Spring Kafka Consumer 1.3.x -> 3.3.x (2.0.x and 2.1.x not tested and not recommended by [the official document](https://spring.io/projects/spring-kafka)) * [ActiveMQ](https://github.com/apache/activemq) 5.10.0 -> 5.15.4 * [RabbitMQ](https://www.rabbitmq.com/) 3.x-> 5.x + * [Spring-RabbitMQ](https://github.com/spring-projects/spring-amqp) 2.x -> 4.x * [Pulsar](http://pulsar.apache.org) 2.2.x -> 2.9.x - * [NATS](https://github.com/nats-io/nats.java) 2.14.x -> 2.15.x + * [NATS](https://github.com/nats-io/nats.java) 2.14.x -> 2.16.5 * [ActiveMQ-Artemis](https://github.com/apache/activemq) 2.30.0 -> 2.31.2 - * Aliyun ONS 1.x (Optional¹) + * Aliyun ONS 1.x (Optional¹), See [SkySPM Plugin Repository](https://github.com/SkyAPM/java-plugin-extensions) * NoSQL * [aerospike](https://github.com/aerospike/aerospike-client-java) 3.x -> 6.x * Redis - * [Jedis](https://github.com/xetorthio/jedis) 2.x-4.x - * [Redisson](https://github.com/redisson/redisson) Easy Java Redis client 3.5.2+ - * [Lettuce](https://github.com/lettuce-io/lettuce-core) 5.x - * [MongoDB Java Driver](https://github.com/mongodb/mongo-java-driver) 2.13-2.14, 3.4.0-3.12.7, 4.0.0-4.1.0 + * [Jedis](https://github.com/xetorthio/jedis) 2.x-5.x + * [Redisson](https://github.com/redisson/redisson) Easy Java Redis client 3.5.0 -> 3.30.0 + * [Lettuce](https://github.com/lettuce-io/lettuce-core) 5.x -> 6.7.1 + * [MongoDB Java Driver](https://github.com/mongodb/mongo-java-driver) 2.13-2.14, 3.4.0-3.12.7, 4.0.0-5.5.x * Memcached Client * [Spymemcached](https://github.com/couchbase/spymemcached) 2.x * [Xmemcached](https://github.com/killme2008/xmemcached) 2.x @@ -98,7 +100,8 @@ metrics based on the tracing data. * [transport-client](https://github.com/elastic/elasticsearch/tree/v6.2.3/client/transport) 6.2.3-6.8.4 * [transport-client](https://github.com/elastic/elasticsearch/tree/7.0/client/transport) 7.0.0-7.5.2 * [rest-high-level-client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/6.7/index.html) 6.7.1-6.8.4 - * [rest-high-level-client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.0/java-rest-high.html) 7.0.0-7.5.2 + * [rest-high-level-client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.0/java-rest-high.html) 7.x + * [elasticsearch-java](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html) 7.17.x-9.x * [Solr](https://github.com/apache/solr/) * [SolrJ](https://github.com/apache/solr/tree/main/solr/solrj) 7.x * [Cassandra](https://github.com/apache/cassandra) 3.x @@ -107,6 +110,7 @@ metrics based on the tracing data. * [hbase-client](https://github.com/apache/hbase) HTable 1.0.0-2.4.2 * Neo4j * [Neo4j-java](https://neo4j.com/docs/java-manual/current/) 4.x + * [Couchbase Java SDK](https://docs.couchbase.com/java-sdk/current/hello-world/overview.html) 3.8.3 (Optional¹), See [leralik's Plugin Repository](https://github.com/leralik/skywalking-java-agent-couchbase-plugin) * Service Discovery * [Netflix Eureka](https://github.com/Netflix/eureka) * Distributed Coordination @@ -120,7 +124,7 @@ metrics based on the tracing data. * Scheduler * [Elastic Job](https://github.com/elasticjob/elastic-job) 2.x * [Apache ShardingSphere-Elasticjob](https://github.com/apache/shardingsphere-elasticjob) 3.x - * [Spring @Scheduled](https://github.com/spring-projects/spring-framework) 3.1+ + * [Spring @Scheduled](https://github.com/spring-projects/spring-framework) 3.1.x -> 6.1.x * [Quartz Scheduler](https://github.com/quartz-scheduler/quartz) 2.x (Optional²) * [XXL Job](https://github.com/xuxueli/xxl-job) 2.x * OpenTracing community supported @@ -138,16 +142,18 @@ metrics based on the tracing data. * JRE Callable and Runnable (Optional²) * JRE ForkJoinPool (Optional²) * Cache - * [Ehcache](https://www.ehcache.org/) 2.x + * [Ehcache](https://www.ehcache.org/) 2.x (Optional²) * [GuavaCache](https://github.com/google/guava) 18.x -> 23.x (Optional²) + * [Caffeine](https://github.com/ben-manes/caffeine) 3.x (Optional²) * Kotlin * [Coroutine](https://kotlinlang.org/docs/coroutines-overview.html) 1.0.1 -> 1.3.x (Optional²) * GraphQL - * [Graphql](https://github.com/graphql-java) 8.0 -> 17.x + * [Graphql](https://github.com/graphql-java) 8.0 -> 24.x * Pool * [Apache Commons DBCP](https://github.com/apache/commons-dbcp) 2.x * [Alibaba Druid](https://github.com/alibaba/druid) 1.x * [HikariCP](https://github.com/brettwooldridge/HikariCP) 3.x -> 4.x + * [C3P0](https://github.com/swaldman/c3p0) 0.9.0 -> 0.10.0 * Logging Framework * [log4j](https://github.com/apache/log4j) 2.x * [log4j2](https://github.com/apache/logging-log4j2) 1.2.x @@ -156,6 +162,8 @@ metrics based on the tracing data. * [MyBatis](https://github.com/mybatis/mybatis-3) 3.4.x -> 3.5.x * Event * [GuavaEventBus](https://github.com/google/guava) 19.x -> 31.x-jre +* GenAI + * [spring-ai](https://github.com/spring-projects/spring-ai) 1.x (OpenAI, Azure OpenAI, Anthropic, Amazon Bedrock, Google GenAI, Google VertexAI, DeepSeek, Mistral AI, ZhiPu AI, MiniMax, Ollama, Cohere, HuggingFace) # Meter Plugins The meter plugin provides the advanced metrics collections, which are not a part of tracing. @@ -164,11 +172,34 @@ The meter plugin provides the advanced metrics collections, which are not a part * [Undertow](https://github.com/undertow-io/undertow) 2.1.x -> 2.6.x * [Tomcat](https://github.com/apache/tomcat) 7.0.x -> 10.0.x * [Dubbo](https://github.com/apache/dubbo) 2.5.x -> 2.7.x - * [Jetty](https://github.com/eclipse/jetty.project) 9.1.x -> 11.x + * [Jetty](https://github.com/eclipse/jetty.project) 9.1.x -> 12.x * [Grizzly](https://github.com/eclipse-ee4j/grizzly) 2.3.x -> 4.x +* Connection Pool + * Supported JDBC drviers + * [MySQL](https://www.mysql.com/) + * [Oracle](https://www.oracle.com/) + * [H2](https://h2database.com/html/main.html) + * [PostgreSQL](https://www.postgresql.org/) + * [MariaDB](https://mariadb.org/) + * [SQL Server](https://www.microsoft.com/en-us/sql-server/) + * [Apache Kylin](https://kylin.apache.org/) + * [Impala](https://impala.apache.org/) + * [ClickHouse](https://clickhouse.com/) + * [Derby](https://db.apache.org/derby/) + * [SQLite](https://www.sqlite.org/index.html) + * [DB2](https://www.ibm.com/products/db2/database) + * Sybase + * [OceanBase](https://www.oceanbase.com/) + * [DaMeng(DM)](https://www.dameng.com/) + * Supported Connection Pool Frameworks + * [Apache Commons DBCP](https://github.com/apache/commons-dbcp) 2.x + * [Alibaba Druid](https://github.com/alibaba/druid) 1.x + * [HikariCP](https://github.com/brettwooldridge/HikariCP) 3.x -> 4.x + * [C3P0](https://github.com/swaldman/c3p0) 0.9.0 -> 0.10.0 + ___ ¹Due to license incompatibilities/restrictions these plugins are hosted and released in 3rd part repository, - go to [SkyAPM java plugin extension repository](https://github.com/SkyAPM/java-plugin-extensions) to get these. + the address following the plugin can help you to get it. ²These plugins affect the performance or must be used under some conditions, from experiences. So only released in `/optional-plugins` or `/bootstrap-plugins`, copy to `/plugins` in order to make them work. diff --git a/docs/en/setup/service-agent/java-agent/advanced-reporters.md b/docs/en/setup/service-agent/java-agent/advanced-reporters.md index 44264d4e9f..7ecede0190 100644 --- a/docs/en/setup/service-agent/java-agent/advanced-reporters.md +++ b/docs/en/setup/service-agent/java-agent/advanced-reporters.md @@ -1,5 +1,5 @@ # Advanced Reporters -The advanced report provides an alternative way to submit the agent collected data to the backend. All of them are in the `optional-reporter-plugins` folder, move the one you needed into the `reporter-plugins` folder for the activation. **Notice, don't try to activate multiple reporters, that could cause unexpected fatal errors.** +The advanced report provides an alternative way to submit the agent collected data to the backend. All of them are in the `optional-reporter-plugins` folder, move the one you needed into the `plugins` folder for the activation. **Notice, don't try to activate multiple reporters, that could cause unexpected fatal errors.** ## Kafka Reporter The Kafka reporter plugin support report traces, JVM metrics, Instance Properties, and profiled snapshots to Kafka cluster, which is disabled in default. Move the jar of the plugin, `kafka-reporter-plugin-x.y.z.jar`, from `agent/optional-reporter-plugins` to `agent/plugins` for activating. diff --git a/docs/en/setup/service-agent/java-agent/agent-optional-plugins/Oracle-Resin-plugins.md b/docs/en/setup/service-agent/java-agent/agent-optional-plugins/Oracle-Resin-plugins.md deleted file mode 100644 index 145ef50133..0000000000 --- a/docs/en/setup/service-agent/java-agent/agent-optional-plugins/Oracle-Resin-plugins.md +++ /dev/null @@ -1,6 +0,0 @@ -## Oracle and Resin plugins -These plugins can't be provided in Apache release because of Oracle and Resin Licenses. -If you want to know details, please read [Apache license legal document](https://www.apache.org/legal/resolved.html) - -Due to license incompatibilities/restrictions these plugins are hosted and released in 3rd part repository, -go to [OpenSkywalking java plugin extension repository](https://github.com/OpenSkywalking/java-plugin-extensions) to get these. diff --git a/docs/en/setup/service-agent/java-agent/agent-optional-plugins/Spring-annotation-plugin.md b/docs/en/setup/service-agent/java-agent/agent-optional-plugins/Spring-annotation-plugin.md index 8f2336adee..ddf088ca11 100644 --- a/docs/en/setup/service-agent/java-agent/agent-optional-plugins/Spring-annotation-plugin.md +++ b/docs/en/setup/service-agent/java-agent/agent-optional-plugins/Spring-annotation-plugin.md @@ -1,4 +1,4 @@ -## Spring annotation plugin +# Spring annotation plugin This plugin allows to trace all methods of beans in Spring context, which are annotated with `@Bean`, `@Service`, `@Component` and `@Repository`. diff --git a/docs/en/setup/service-agent/java-agent/agent-optional-plugins/spring-gateway.md b/docs/en/setup/service-agent/java-agent/agent-optional-plugins/spring-gateway.md new file mode 100644 index 0000000000..6b84d91bec --- /dev/null +++ b/docs/en/setup/service-agent/java-agent/agent-optional-plugins/spring-gateway.md @@ -0,0 +1,96 @@ +# Spring Gateway Plugin + +Spring Gateway Plugin only support Spring Gateway 2.x, 3.x and 4.x. It has capabilities to create entry spans for +incoming calls, continue tracing context propagation in Spring Gateway and create exit spans for outgoing calls. + +About the filter extension of Gateway, it provides automatically support as much as possible, including GlobalFilter and GatewayFilter +support. However, the filter extension by using `chain.filter(exchange).then(...)` is not able to transparently. + +## Supported Auto-Instrument Filters + +```java +@Component +public class Filter1 implements GlobalFilter, Ordered { + + private static final Logger log = LoggerFactory.getLogger(Filter1.class); + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + // Available trace context + // For log framework integration(trace context log output) and manual trace context usage. + String traceId = TraceContext.traceId(); + log.info("available traceId: {}", traceId); + + String segmentId = TraceContext.segmentId(); + log.info("available segmentId: {}", segmentId); + + int spanId = TraceContext.spanId(); + log.info("available spanId: {}", spanId); + + return chain.filter(exchange); + } + @Override + public int getOrder() { + return -100; + } +} +``` +```java +@Component +public class GatewayFilter1 implements GatewayFilter { + + private static final Logger log = LoggerFactory.getLogger(GatewayFilter1.class); + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + // Available trace context + log.info("gatewayFilter1 running"); + return chain.filter(exchange); + } +} +``` + +## Unsupported Auto-Instrument Filters +Typically, in the following case, you need to read via [Webflux Tracing Assistant APIs](../Application-toolkit-webflux.md) to get the trace context. + +```java +@Component +public class UnsupportedFilter implements GlobalFilter, Ordered { + private static final Logger log = LoggerFactory.getLogger(UnsupportedFilter.class); + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + String traceId = TraceContext.traceId(); + // Trace ID is available as it's in the GlobalFilter. + log.info("available traceId: {}", traceId); + + String segmentId = TraceContext.segmentId(); + // Segment ID is available as it's in the GlobalFilter. + log.info("available segmentId: {}", segmentId); + + int spanId = TraceContext.spanId(); + // Span ID is available as it's in the GlobalFilter. + log.info("available spanId: {}", spanId); + + return chain.filter(exchange).then(Mono.fromRunnable(() -> { + // Trace ID/context is not available, N/A in the all following logs. + // The trace context is not available in the then-closure. + // Only webflux assistant API can get the trace context. + String traceId2 = WebFluxSkyWalkingTraceContext.traceId(exchange); + // Trace ID is not available, N/A in the logs. + log.info("unavailable in then-closure, available traceId: {} through webflux assistant API", traceId2); + + String segmentId2 = WebFluxSkyWalkingTraceContext.segmentId(exchange); + // Segment ID is not available, N/A in the logs. + log.info("unavailable in then-closure, available segmentId: {} through webflux assistant API", segmentId2); + + int spanId2 = WebFluxSkyWalkingTraceContext.spanId(exchange); + // Span ID is not available, N/A in the logs. + log.info("unavailable in then-closure, available spanId: {} through webflux assistant API", spanId2); + })); + } + + @Override + public int getOrder() { + return 10; + } +} + +``` \ No newline at end of file diff --git a/docs/en/setup/service-agent/java-agent/agent-optional-plugins/trace-ignore-plugin.md b/docs/en/setup/service-agent/java-agent/agent-optional-plugins/trace-ignore-plugin.md index dfaf2a1bed..cc42b54602 100644 --- a/docs/en/setup/service-agent/java-agent/agent-optional-plugins/trace-ignore-plugin.md +++ b/docs/en/setup/service-agent/java-agent/agent-optional-plugins/trace-ignore-plugin.md @@ -1,4 +1,4 @@ -## Support custom trace ignore +# Support Custom Trace Ignoring Here is an optional plugin `apm-trace-ignore-plugin` **Notice:** @@ -18,3 +18,5 @@ There are two ways to configure ignore patterns. Settings through system env has trace.ignore_path=/your/path/1/**,/your/path/2/** ``` +## Conflicts Notice +Due to the mechanism sharing, this plugin has conflicts with the plugin of **sampler plugin with CPU policy**(`trace-sampler-cpu-policy-plugin-*.jar`) in the optional plugin folder. \ No newline at end of file diff --git a/docs/en/setup/service-agent/java-agent/configurations.md b/docs/en/setup/service-agent/java-agent/configurations.md index 4126407d18..ddc14a145e 100644 --- a/docs/en/setup/service-agent/java-agent/configurations.md +++ b/docs/en/setup/service-agent/java-agent/configurations.md @@ -107,15 +107,15 @@ This is the properties list supported in `agent/config/agent.config`. | `plugin.lettuce.trace_redis_parameters` | If set to true, the parameters of Redis commands would be collected by Lettuce agent. | SW_PLUGIN_LETTUCE_TRACE_REDIS_PARAMETERS | `false` | | `plugin.lettuce.redis_parameter_max_length` | If set to positive number and `plugin.lettuce.trace_redis_parameters` is set to `true`, Redis command parameters would be collected and truncated to this length. | SW_PLUGIN_LETTUCE_REDIS_PARAMETER_MAX_LENGTH | `128` | | `plugin.lettuce.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_LETTUCE_OPERATION_MAPPING_WRITE | | -| `plugin.lettuce.operation_mapping_read ` | Specify which command should be converted to `read` operation | SW_PLUGIN_LETTUCE_OPERATION_MAPPING_READ | Referenc [Lettuce-5.x-plugin](https://github.com/apache/skywalking-java/blob/main/apm-sniffer/apm-sdk-plugin/lettuce-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/lettuce/v5/LettucePluginConfig.java) | +| `plugin.lettuce.operation_mapping_read ` | Specify which command should be converted to `read` operation | SW_PLUGIN_LETTUCE_OPERATION_MAPPING_READ | Reference [Lettuce-5.x-plugin](https://github.com/apache/skywalking-java/blob/main/apm-sniffer/apm-sdk-plugin/lettuce-plugins/lettuce-common/src/main/java/org/apache/skywalking/apm/plugin/lettuce/common/LettucePluginConfig.java) | | `plugin.jedis.trace_redis_parameters` | If set to true, the parameters of Redis commands would be collected by Jedis agent. | SW_PLUGIN_JEDIS_TRACE_REDIS_PARAMETERS | `false` | | `plugin.jedis.redis_parameter_max_length` | If set to positive number and `plugin.jedis.trace_redis_parameters` is set to `true`, Redis command parameters would be collected and truncated to this length. | SW_PLUGIN_JEDIS_REDIS_PARAMETER_MAX_LENGTH | `128` | | `plugin.jedis.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_JEDIS_OPERATION_MAPPING_WRITE | | -| `plugin.jedis.operation_mapping_read ` | Specify which command should be converted to `read` operation | SW_PLUGIN_JEDIS_OPERATION_MAPPING_READ | Referenc [Jedis-4.x-plugin](https://github.com/apache/skywalking-java/blob/main/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v4/JedisPluginConfig.java) [jedis-2.x-3.x-plugin](https://github.com/apache/skywalking-java/blob/main/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-2.x-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v3/JedisPluginConfig.java) | +| `plugin.jedis.operation_mapping_read ` | Specify which command should be converted to `read` operation | SW_PLUGIN_JEDIS_OPERATION_MAPPING_READ | Reference [Jedis-4.x-plugin](https://github.com/apache/skywalking-java/blob/main/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v4/JedisPluginConfig.java) [jedis-2.x-3.x-plugin](https://github.com/apache/skywalking-java/blob/main/apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-2.x-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v3/JedisPluginConfig.java) | | `plugin.redisson.trace_redis_parameters` | If set to true, the parameters of Redis commands would be collected by Redisson agent. | SW_PLUGIN_REDISSON_TRACE_REDIS_PARAMETERS | `false` | | `plugin.redisson.redis_parameter_max_length` | If set to positive number and `plugin.redisson.trace_redis_parameters` is set to `true`, Redis command parameters would be collected and truncated to this length. | SW_PLUGIN_REDISSON_REDIS_PARAMETER_MAX_LENGTH | `128` | | `plugin.redisson.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_REDISSON_OPERATION_MAPPING_WRITE | | -| `plugin.redisson.operation_mapping_read ` | Specify which command should be converted to `read` operation | SW_PLUGIN_REDISSON_OPERATION_MAPPING_READ | Referenc [Redisson-3.x-plugin](https://github.com/apache/skywalking-java/blob/main/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/RedissonPluginConfig.java) | +| `plugin.redisson.operation_mapping_read ` | Specify which command should be converted to `read` operation | SW_PLUGIN_REDISSON_OPERATION_MAPPING_READ | Reference [Redisson-3.x-plugin](https://github.com/apache/skywalking-java/blob/main/apm-sniffer/apm-sdk-plugin/redisson-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/redisson/v3/RedissonPluginConfig.java) | | `plugin.neo4j.trace_cypher_parameters` | If set to true, the parameters of the cypher would be collected. | SW_PLUGIN_NEO4J_TRACE_CYPHER_PARAMETERS | `false` | | `plugin.neo4j.cypher_parameters_max_length` | If set to positive number, the `db.cypher.parameters` would be truncated to this length, otherwise it would be completely saved, which may cause performance problem. | SW_PLUGIN_NEO4J_CYPHER_PARAMETERS_MAX_LENGTH | `512` | | `plugin.neo4j.cypher_body_max_length` | If set to positive number, the `db.statement` would be truncated to this length, otherwise it would be completely saved, which may cause performance problem. | SW_PLUGIN_NEO4J_CYPHER_BODY_MAX_LENGTH | `2048` | @@ -126,14 +126,18 @@ This is the properties list supported in `agent/config/agent.config`. | `plugin.memcached.operation_mapping_read` | Specify which command should be converted to `read` operation | SW_PLUGIN_MEMCACHED_OPERATION_MAPPING_READ | `set,add,replace,append,prepend,cas,delete,touch,incr,decr` | | `plugin.ehcache.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_EHCACHE_OPERATION_MAPPING_WRITE | `get,getAll,getQuiet,getKeys,getKeysWithExpiryCheck,getKeysNoDuplicateCheck,releaseRead,tryRead,getWithLoader,getAll,loadAll,getAllWithLoader` | | `plugin.ehcache.operation_mapping_read` | Specify which command should be converted to `read` operation | SW_PLUGIN_EHCACHE_OPERATION_MAPPING_READ | `tryRemoveImmediately,remove,removeAndReturnElement,removeAll,removeQuiet,removeWithWriter,put,putAll,replace,removeQuiet,removeWithWriter,removeElement,removeAll,putWithWriter,putQuiet,putIfAbsent,putIfAbsent` | -| `plugin.guavacache.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_GUAVACACHE_OPERATION_MAPPING_WRITE | `getIfPresent,get,getAllPresent,size` | -| `plugin.guavacache.operation_mapping_read` | Specify which command should be converted to `read` operation | SW_PLUGIN_GUAVACACHE_OPERATION_MAPPING_READ | `put,putAll,invalidate,invalidateAll,invalidateAll,cleanUp` -| `plugin.nettyhttp.collect_request_body` | This config item controls that whether the Netty-http plugin should collect the http body of the request. | SW_PLUGIN_NETTY_HTTP_COLLECT_REQUEST_BODY | `false` | -| `plugin.nettyhttp.filter_length_limit` | When `COLLECT_REQUEST_BODY` is enabled, how many characters to keep and send to the OAP backend, use negative values to keep and send the complete body. | SW_PLUGIN_NETTY_HTTP_FILTER_LENGTH_LIMIT | `1024` | -| `plugin.nettyhttp.supported_content_types_prefix` | When `COLLECT_REQUEST_BODY` is enabled and content-type start with `HTTP_SUPPORTED_CONTENT_TYPES_PREFIX`, collect the body of the request , multiple paths should be separated by `,` | SW_PLUGIN_NETTY_HTTP_SUPPORTED_CONTENT_TYPES_PREFIX | `application/json,text/` | -| `plugin.rocketmqclient.collect_message_keys` | If set to true, the keys of messages would be collected by the plugin for RocketMQ Java client. -| `plugin.rocketmqclient.collect_message_tags` | If set to true, the tags of messages would be collected by the plugin for RocketMQ Java client. -| +| `plugin.guavacache.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_GUAVACACHE_OPERATION_MAPPING_WRITE | `put,putAll,invalidate,invalidateAll,invalidateAll,cleanUp` | +| `plugin.guavacache.operation_mapping_read` | Specify which command should be converted to `read` operation | SW_PLUGIN_GUAVACACHE_OPERATION_MAPPING_READ | `getIfPresent,get,getAllPresent,size` | +| `plugin.nettyhttp.collect_request_body` | This config item controls that whether the Netty-http plugin should collect the http body of the request. | SW_PLUGIN_NETTY_HTTP_COLLECT_REQUEST_BODY | `false` | +| `plugin.nettyhttp.filter_length_limit` | When `COLLECT_REQUEST_BODY` is enabled, how many characters to keep and send to the OAP backend, use negative values to keep and send the complete body. | SW_PLUGIN_NETTY_HTTP_FILTER_LENGTH_LIMIT | `1024` | +| `plugin.nettyhttp.supported_content_types_prefix` | When `COLLECT_REQUEST_BODY` is enabled and content-type start with `HTTP_SUPPORTED_CONTENT_TYPES_PREFIX`, collect the body of the request , multiple paths should be separated by `,` | SW_PLUGIN_NETTY_HTTP_SUPPORTED_CONTENT_TYPES_PREFIX | `application/json,text/` | +| `plugin.rocketmqclient.collect_message_keys` | If set to true, the keys of messages would be collected by the plugin for RocketMQ Java client. | | | +| `plugin.rocketmqclient.collect_message_tags` | If set to true, the tags of messages would be collected by the plugin for RocketMQ Java client. | | | +| `plugin.solon.http_params_length_threshold` | Define the max length of collected HTTP parameters. The default value(=0) means not collecting. | SW_PLUGIN_SOLON_HTTP_PARAMS_LENGTH_THRESHOLD | `0` | +| `plugin.solon.include_http_headers` | It controls what header data should be collected, values must be in lower case, if empty, no header data will be collected. default is empty. | SW_PLUGIN_SOLON_INCLUDE_HTTP_HEADERS | ``(No header would be collected) | +| `plugin.solon.http_body_length_threshold` | Define the max length of collected HTTP body. The default value(=0) means not collecting. | SW_PLUGIN_SOLON_HTTP_BODY_LENGTH_THRESHOLD | `0` | +| `plugin.caffeine.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_EHCACHE_OPERATION_MAPPING_WRITE | `put,putAll,remove,clear` | +| `plugin.caffeine.operation_mapping_read` | Specify which command should be converted to `read` operation | SW_PLUGIN_EHCACHE_OPERATION_MAPPING_READ | `getIfPresent,getAllPresent,computeIfAbsent` | # Reset Collection/Map type configurations as empty collection. diff --git a/docs/menu.yml b/docs/menu.yml index 3964b059be..edf1dfa77b 100644 --- a/docs/menu.yml +++ b/docs/menu.yml @@ -74,12 +74,24 @@ catalog: path: "/en/setup/service-agent/java-agent/application-toolkit-logback-1.x/" - name: "Logic Endpoint" path: "/en/setup/service-agent/java-agent/logic-endpoint" + - name: "Agent Self Observability" + path: "/en/setup/service-agent/java-agent/Agent-self-observability" - name: "Plugins" catalog: - name: "Supported middleware, framework and library" path: "/en/setup/service-agent/java-agent/Supported-list" - name: "Optional Plugins" - path: "/en/setup/service-agent/java-agent/Optional-plugins" + catalog: + - name: "Plugin Manifest" + path: "/en/setup/service-agent/java-agent/Optional-plugins" + - name: "Spring Annotation Plugin" + path: "/en/setup/service-agent/java-agent/agent-optional-plugins/Spring-annotation-plugin" + - name: "Spring Gateway" + path: "/en/setup/service-agent/java-agent/agent-optional-plugins/spring-gateway" + - name: "Custom Trace Ignoring" + path: "/en/setup/service-agent/java-agent/agent-optional-plugins/trace-ignore-plugin" + - name: "Kotlin Coroutine" + path: "/en/setup/service-agent/java-agent/agent-optional-plugins/Kotlin-Coroutine-plugin" - name: "Bootstrap/JVM class plugin" path: "/en/setup/service-agent/java-agent/Bootstrap-plugins" - name: "Agent Configuration Properties" @@ -88,10 +100,14 @@ catalog: path: "/en/setup/service-agent/java-agent/configuration-discovery" - name: "Advanced reporters" path: "/en/setup/service-agent/java-agent/advanced-reporters" - - name: "Plugin development guide" + - name: "Plugin Test Guide" + path: "/en/setup/service-agent/java-agent/Plugin-test/" + - name: "Plugin Development Guide" path: "/en/setup/service-agent/java-agent/java-plugin-development-guide/" - name: "Java Agent Performance Test" path: "https://skyapmtest.github.io/Agent-Benchmarks/" + - name: "Claude Code Skills" + path: "/en/setup/service-agent/java-agent/Claude-Skills" - name: "FAQs" catalog: - name: "Why is java.ext.dirs not supported?" diff --git a/pom.xml b/pom.xml index c6dca564f9..1f3297d2d1 100755 --- a/pom.xml +++ b/pom.xml @@ -22,12 +22,12 @@ org.apache.skywalking java-agent - 9.3.0-SNAPSHOT + 9.7.0-SNAPSHOT org.apache apache - 21 + 35 @@ -78,25 +78,33 @@ UTF-8 + 8 + 8 + 8 8 6.18 4.12 5.0.0 2.0.2 - 1.18.30 + 1.18.42 - 1.14.9 - 1.53.0 - 4.1.100.Final - 2.8.9 - 1.6.2 + 1.17.6 + 1.74.0 + 4.1.124.Final + 2.13.1 + + + 32.1.3-jre + + 1.7.1 0.6.1 - 3.21.7 - 1.53.0 - 2.0.48.Final + 3.25.5 + 1.74.0 + 2.0.70.Final 1.3.2 3.1 + 3.0 6.0.53 @@ -111,18 +119,21 @@ 2.22.0 3.2.0 3.1.0 - 3.1.1 + 3.3.0 3.0.0-M2 3.10.1 3.1.0 3.0.1 + 3.0.0-M8 2.5 4.3.0 3.2.1 1.33 1.5 true - + + true + 0.46.0 @@ -260,6 +271,28 @@ ${jmh.version} test + + tools.profiler + async-profiler + ${async-profiler.version} + + + io.netty + netty-bom + ${netty.version} + pom + import + + + io.netty + netty-tcnative-boringssl-static + ${netty-tcnative-boringssl-static.version} + + + io.netty + netty-tcnative-classes + ${netty-tcnative-boringssl-static.version} + @@ -307,6 +340,32 @@ %a-%t-%i + + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + maven-resources-plugin + ${maven-resource-plugin.version} + + + maven-source-plugin + ${maven-source-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.apache.maven.plugins + maven-dependency-plugin + ${maven-dependency-plugin.version} + @@ -325,7 +384,6 @@ maven-enforcer-plugin - ${maven-enforcer-plugin.version} enforce-java @@ -349,16 +407,21 @@ maven-compiler-plugin - ${maven-compiler-plugin.version} ${compiler.version} ${compiler.version} ${project.build.sourceEncoding} + + + org.projectlombok + lombok + ${lombok.version} + + maven-resources-plugin - ${maven-resource-plugin.version} ${project.build.sourceEncoding} @@ -373,7 +436,6 @@ maven-source-plugin - ${maven-source-plugin.version} attach-sources @@ -390,7 +452,7 @@ ${maven.multiModuleProjectDirectory}/apm-checkstyle/checkStyle.xml ${maven.multiModuleProjectDirectory}/apm-checkstyle/CHECKSTYLE_HEAD - UTF-8 + ${project.build.sourceEncoding} true true ${checkstyle.fails.on.error} @@ -448,7 +510,6 @@ org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M8 1 false diff --git a/test/e2e/base/base-compose.yml b/test/e2e/base/base-compose.yml index e6b4b70c4b..f25eb8ce25 100644 --- a/test/e2e/base/base-compose.yml +++ b/test/e2e/base/base-compose.yml @@ -18,7 +18,7 @@ version: '2.1' services: oap: - image: ghcr.io/apache/skywalking/oap:1730f2c84bbd4da999ec2c74d1c26db31d5a0d24 + image: "ghcr.io/apache/skywalking/oap:${SW_OAP_COMMIT}" expose: - 11800 - 12800 @@ -26,7 +26,23 @@ services: - e2e restart: on-failure healthcheck: - test: ["CMD", "sh", "-c", "nc -zn 127.0.0.1 11800"] + test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/11800" ] + interval: 5s + timeout: 60s + retries: 120 + environment: + SW_STORAGE_BANYANDB_TARGETS: banyandb:17912 + + banyandb: + image: "ghcr.io/apache/skywalking-banyandb:${SW_BANYANDB_COMMIT}" + networks: + - e2e + ports: + - 17912:17912 + - 17913:17913 + command: standalone --stream-root-path /tmp/stream-data --measure-root-path /tmp/measure-data --measure-metadata-cache-wait-duration 1m --stream-metadata-cache-wait-duration 1m + healthcheck: + test: [ "CMD", "sh", "-c", "nc -nz 127.0.0.1 17912" ] interval: 5s timeout: 60s retries: 120 diff --git a/test/e2e/base/pom.xml b/test/e2e/base/pom.xml index 9ae8d9e70e..63b6a687f2 100644 --- a/test/e2e/base/pom.xml +++ b/test/e2e/base/pom.xml @@ -51,7 +51,7 @@ ${java.version} ${java.version} UTF-8 - 2.2.5.RELEASE + 2.7.18 30.1.1-jre 1.18.20 diff --git a/test/e2e/case/expected/event-list.yml b/test/e2e/case/expected/event-list.yml index e5a19bfe07..e020869abc 100644 --- a/test/e2e/case/expected/event-list.yml +++ b/test/e2e/case/expected/event-list.yml @@ -30,5 +30,5 @@ events: {{- end }} starttime: {{ gt .starttime 0 }} endtime: {{ gt .endtime 0 }} -{{- end }} -total: {{ gt .total 0 }} \ No newline at end of file + layer: "GENERAL" +{{- end }} \ No newline at end of file diff --git a/test/e2e/case/expected/logs-list.yml b/test/e2e/case/expected/logs-list.yml index 23de580d25..2e02cbd076 100644 --- a/test/e2e/case/expected/logs-list.yml +++ b/test/e2e/case/expected/logs-list.yml @@ -36,4 +36,5 @@ logs: value: {{ notEmpty .value }} {{- end }} {{- end }} -total: {{ gt .total 0 }} +debuggingtrace: null +errorreason: null \ No newline at end of file diff --git a/test/e2e/case/expected/metrics-has-value.yml b/test/e2e/case/expected/metrics-has-value.yml index 27ae47c8ff..baeae56d1c 100644 --- a/test/e2e/case/expected/metrics-has-value.yml +++ b/test/e2e/case/expected/metrics-has-value.yml @@ -13,9 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -{{- contains . }} + {{- contains . }} - key: {{ notEmpty .key }} - value: {{ ge .value 0 }} + value: + value: 0 + isemptyvalue: true - key: {{ notEmpty .key }} - value: {{ ge .value 1 }} -{{- end }} \ No newline at end of file + value: + value: {{ ge .value.value 1 }} + isemptyvalue: false + {{- end }} \ No newline at end of file diff --git a/test/e2e/case/expected/service.yml b/test/e2e/case/expected/service.yml index 129f9d3e8b..e60a3313db 100644 --- a/test/e2e/case/expected/service.yml +++ b/test/e2e/case/expected/service.yml @@ -13,9 +13,28 @@ # See the License for the specific language governing permissions and # limitations under the License. +{{- contains . }} - id: {{ b64enc "e2e-service-provider" }}.1 name: e2e-service-provider group: "" + shortname: e2e-service-provider + normal: true + layers: + - SO11Y_JAVA_AGENT + - GENERAL - id: {{ b64enc "e2e-service-consumer" }}.1 name: e2e-service-consumer - group: "" \ No newline at end of file + group: "" + shortname: e2e-service-consumer + normal: true + layers: + - SO11Y_JAVA_AGENT + - GENERAL +- id: {{ b64enc "localhost:-1" }}.0 + name: localhost:-1 + group: "" + shortname: localhost:-1 + normal: false + layers: + - VIRTUAL_DATABASE +{{- end }} \ No newline at end of file diff --git a/test/e2e/case/expected/trace-info-detail.yml b/test/e2e/case/expected/trace-info-detail.yml index f50b094f53..fad2fdbe70 100644 --- a/test/e2e/case/expected/trace-info-detail.yml +++ b/test/e2e/case/expected/trace-info-detail.yml @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +debuggingtrace: null spans: {{- contains .spans}} - traceid: {{ notEmpty .traceid }} @@ -46,4 +47,5 @@ spans: value: '200' {{- end }} logs: [] + attachedevents: [] {{- end }} diff --git a/test/e2e/case/expected/trace-users-detail.yml b/test/e2e/case/expected/trace-users-detail.yml index 3a1030eea6..ef0d1a39cf 100644 --- a/test/e2e/case/expected/trace-users-detail.yml +++ b/test/e2e/case/expected/trace-users-detail.yml @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +debuggingtrace: null spans: {{- contains .spans}} - traceid: {{ notEmpty .traceid }} @@ -40,4 +41,5 @@ spans: value: '200' {{- end }} logs: [] + attachedevents: [] {{- end }} diff --git a/test/e2e/case/expected/traces-list.yml b/test/e2e/case/expected/traces-list.yml index bed95518e2..8996767757 100644 --- a/test/e2e/case/expected/traces-list.yml +++ b/test/e2e/case/expected/traces-list.yml @@ -13,17 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +debuggingtrace: null traces: {{- contains .traces }} - - segmentid: {{ notEmpty .segmentid }} - endpointnames: - {{- contains .endpointnames }} - - POST:/info - {{- end }} - duration: {{ ge .duration 0 }} - start: "{{ notEmpty .start}}" - iserror: false - traceids: - - {{ (index .traceids 0) }} +- segmentid: {{ notEmpty .segmentid }} + endpointnames: + - POST:/info + duration: {{ ge .duration 0 }} + start: "{{ notEmpty .start}}" + iserror: false + traceids: + - {{ index .traceids 0 }} {{- end }} -total: {{ gt .total 0 }} diff --git a/test/e2e/case/grpc/docker-compose.yml b/test/e2e/case/grpc/docker-compose.yml index 621587bd60..f79160cb8c 100644 --- a/test/e2e/case/grpc/docker-compose.yml +++ b/test/e2e/case/grpc/docker-compose.yml @@ -23,6 +23,13 @@ services: ports: - 12800 + banyandb: + extends: + file: ../../base/base-compose.yml + service: banyandb + ports: + - 17912 + provider: extends: file: ../../base/base-compose.yml diff --git a/test/e2e/case/kafka/docker-compose.yml b/test/e2e/case/kafka/docker-compose.yml index 3760fd44fb..4f25a14cd6 100644 --- a/test/e2e/case/kafka/docker-compose.yml +++ b/test/e2e/case/kafka/docker-compose.yml @@ -32,7 +32,7 @@ services: retries: 120 broker-a: - image: bitnami/kafka:2.4.1 + image: bitnamilegacy/kafka:2.4.1 hostname: broker-a expose: - 9092 @@ -52,7 +52,7 @@ services: retries: 120 broker-b: - image: bitnami/kafka:2.4.1 + image: bitnamilegacy/kafka:2.4.1 hostname: broker-b expose: - 9092 @@ -89,6 +89,13 @@ services: broker-b: condition: service_healthy + banyandb: + extends: + file: ../../base/base-compose.yml + service: banyandb + ports: + - 17912 + kafkaprovider: extends: file: ../../base/base-compose.yml diff --git a/test/e2e/script/env b/test/e2e/script/env index e57ec37b21..01f311a60b 100644 --- a/test/e2e/script/env +++ b/test/e2e/script/env @@ -13,4 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -SW_CTL_COMMIT=b90255132f916f53eb90955cc8a6445b03a4bec3 +SW_CTL_COMMIT=77b4c49e89c9c000278f44e62729d534f2ec842e +SW_OAP_COMMIT=dc8740d4757b35374283c4850a9a080e40f0eb79 +SW_BANYANDB_COMMIT=7e5b2d0404e8ad6d5835eee6fe589a2544d0decb \ No newline at end of file diff --git a/test/e2e/script/prepare/install-swctl.sh b/test/e2e/script/prepare/install-swctl.sh index 587541f7ee..d435f5ad92 100644 --- a/test/e2e/script/prepare/install-swctl.sh +++ b/test/e2e/script/prepare/install-swctl.sh @@ -22,12 +22,18 @@ BASE_DIR=$1 BIN_DIR=$2 -set -ex - -if ! command -v swctl &> /dev/null; then +install_swctl() { mkdir -p $BASE_DIR/swctl && cd $BASE_DIR/swctl curl -kLo skywalking-cli.tar.gz https://github.com/apache/skywalking-cli/archive/${SW_CTL_COMMIT}.tar.gz tar -zxf skywalking-cli.tar.gz --strip=1 - utype=$(uname | awk '{print tolower($0)}') - make $utype && mv bin/swctl-*-$utype-amd64 $BIN_DIR/swctl -fi \ No newline at end of file + VERSION=${SW_CTL_COMMIT} make install DESTDIR=$BIN_DIR +} + +if ! command -v swctl &> /dev/null; then + echo "swctl is not installed" + install_swctl +elif ! swctl --version | grep -q "${SW_CTL_COMMIT::7}"; then + # Check if the installed version is correct + echo "swctl is already installed, but version is not ${SW_CTL_COMMIT}, will re-install it" + install_swctl +fi diff --git a/test/plugin/CLAUDE.md b/test/plugin/CLAUDE.md new file mode 100644 index 0000000000..6b73f93540 --- /dev/null +++ b/test/plugin/CLAUDE.md @@ -0,0 +1,134 @@ +# CLAUDE.md - Plugin Test Guide + +This file guides AI assistants writing and changing **plugin tests** under `test/plugin/scenarios/`. +For the full test-framework mechanics (scenario layout, `configuration.yml`, `expectedData.yaml`, +containers, running locally) see the **"Plugin Test Framework"** section of +`apm-sniffer/apm-sdk-plugin/CLAUDE.md`, and `docs/.../Plugin-test.md`. + +## Testing philosophy: plugin scenarios are the key test — not unit tests + +For an agent plugin, the **plugin test scenario is the primary and sufficient test**. Do **not** +over-engineer by adding many unit tests for a plugin. + +A plugin's real risk is two things, and a scenario proves both at once by running the **real +framework**: + +1. **Cast / fetch-data logic** — the interceptor casting the intercepted arguments to the framework + types and reading request/response fields off them (headers, method, URI, status, params). Mocks + can't validate that the real framework class actually exposes those methods the way you assumed. +2. **Version compatibility** — the same interceptor must work across the supported version range. + +So: **when adding or changing a plugin, add or extend its scenario**, not a pile of mock-based unit +tests. Reserve unit tests for genuinely logic/namespace-critical **shared** code where a deterministic +in-JVM assertion earns its keep (e.g. `servlet-commons` `wrap()` resolving javax vs jakarta with both +APIs on the test classpath) — never for per-plugin interceptor mocking. + +## Validation path: assert the full span shape your plugin produces + +A scenario proves a plugin by asserting the **exact spans** the real framework makes the agent +produce. Pick the shape by what the framework actually does — and don't settle for a single entry +span when the plugin also does propagation, or you silently skip the inject/extract path. + +- **RPC / web frameworks that have both a server and a client side** (Struts, Spring MVC, gRPC, + Dubbo, HTTP servers …): drive the full round-trip — + `curl → server → its own client call → another service's endpoint`. The trace then contains + **2 entry spans + 1 exit span**: + 1. the **server entry** span for the request `curl` hit (proves the server side: extract + entry), + 2. the **client exit** span for the outbound call (proves inject + exit), + 3. a **second entry** span on the other endpoint carrying a **cross-process ref** back to the + first (proves propagation across the wire — the receiving/extract side). + + **The ref itself must be asserted** — for RPC the cross-process `refs:` block is the whole point, + so `expectedData.yaml` must include it on that second entry span with `refType: CrossProcess`, + `parentEndpoint`, `parentSpanId` (pointing at the exit span), `parentTraceSegmentId: not null`, + `parentService`, and `traceId: not null`. A scenario that asserts spans but not the ref does not + prove propagation. + + The struts2.7 scenario is exactly this: `case.action` (entry) → HttpClient (exit) → `case1.action` + (entry + ref); jetty-12 uses the same shape via a JDK-HttpURLConnection self-call. Keep that nested + self-call — it is the only thing that proves inject / extract / propagation; a lone entry span does not. + +- **Client-only plugins** (JDBC/PostgreSQL, Redis, MongoDB, an HTTP client, a message producer): the + peer is a database/broker, not another instrumented SkyWalking service, so there is **no second + entry span**. Assert just the **client exit** span (component, `peer`, and the `db.*` / `http.*` + tags). You do NOT need to stand up another service endpoint. + +## Version coverage: one version per minor (latest patch) + +`support-version.list` must cover the framework's supported range, but keep **one version per minor +version — the latest patch**, not every patch. E.g. Jetty `12.0.36` + `12.1.10`; Struts `7.0.3`, +`7.1.1`, `7.2.1`; Spring `6.0.4`, `6.1.1`, `6.2.19`. Verify each version resolves on Maven Central. +This is the compatibility proof; CI runs the scenario against each listed version. + +## How the test containers work (and where the JDK/Tomcat version really comes from) + +A scenario runs inside one of two prebuilt container images, selected by `type:` in +`configuration.yml`: + +- **`type: jvm`** → the **`agent-test-jvm`** container. Your app is a fat-jar started by + `bin/startup.sh` with `${agent_opts}` (the `-javaagent:` line — you MUST include it in + `startup.sh`). Built `FROM ${base_image_java}`. +- **`type: tomcat`** → the **`agent-test-tomcat`** container. Your app is a `*.war` dropped into + `/usr/local/tomcat/webapps/`; the agent is wired in via the container's patched `catalina.sh`. + Built `FROM ${base_image_tomcat}`. + +**The version pin is in the CI lane, not `configuration.yml`.** `configuration.yml` only says +`type: tomcat` — it does **not** choose a Tomcat version. The concrete JDK and Tomcat versions come +from the `base_image_java` / `base_image_tomcat` inputs passed to `./.github/actions/build` in the +lane's `Build` job, and **every scenario in that lane shares that one image**. So: + +- `type: tomcat` on the `plugins-jdk8-*` lane → `tomcat:8.5-jdk8` (javax); + on `plugins-jdk11-*` → `tomcat:9.0-jdk11` (javax); + on `plugins-jdk17-*` → `tomcat:10.1-jdk17` (**jakarta**). +- That is why a jakarta/Servlet-6 framework (Struts 7, Jetty 12, Spring 6) MUST be registered on a + JDK-17 lane — it needs the Tomcat-10.1 base — and a javax one on an older lane. Putting a jakarta + WAR on a Tomcat-8.5 lane just won't deploy. + +To run locally you pass the same two knobs explicitly (the defaults are the javax/JDK-8 pair): +`bash test/plugin/run.sh --base_image_java eclipse-temurin:17-jdk --base_image_tomcat tomcat:10.1-jdk17-temurin `. +Some newer bases lack `curl` (the container health check needs it) — the JDK-25 lane bakes it in +via `.github/workflows/Dockerfile-tomcat-jdk25-withCurl`. + +## How the GitHub Actions lanes work + +Scenarios are load-balanced across `.github/workflows/plugins--test..yaml` files. Each +file is one lane: its `Build` job pins `base_image_java` + `base_image_tomcat`, and its `test` job's +`matrix.case:` list names the scenarios that run on that image. Lanes exist per JDK +(`jdk8`/`jdk11`/`jdk17`/`jdk21`/`jdk25`); the trailing `.0`/`.1`/… is just a bucket to spread load. +Register a new scenario by adding its directory name to the `matrix.case:` list of the lane whose +base image matches the framework's needs — use `python3 tools/select-group.py` to pick the +least-loaded bucket in that JDK lane. + +## Cold-start and the 3s entry-service timeout (a real gotcha) + +The container's `run.sh` runs under `set -e` and hits the entry service with +`curl -s --max-time 3 ${SCENARIO_ENTRY_SERVICE}` — **no warm-up first**. So the very first (cold) +entry request must finish within **3 seconds** or `curl` exits 28 and the whole scenario fails. That +first request pays a lot of one-time cost at once: the agent's ByteBuddy enhancement of every class +it touches on that path, JIT, and — for WARs — a cold Jasper **JSP compile**, all under the +container's tight `-Xmx256m` heap and whatever CPU the runner gives, with **jacoco** simultaneously +instrumenting the `org.apache.skywalking.*` classes for coverage. + +Keep the entry path's cold cost low: + +- Don't render a **JSP** from the entry action if a lightweight result works (e.g. Struts + `httpheader`, a plain-text/stream result). The view is irrelevant to what a plugin scenario + verifies (entry/exit spans, tags, propagation), and a cold Jasper compile is expensive. +- Be extra careful when the entry action makes a **nested internal call** (for cross-process + propagation coverage): if that nested request also does cold work (a JSP compile) *while* the + outer request is still cold-enhancing Struts/HttpClient, the two cold-start storms contend for the + limited CPU and heap and blow up **super-linearly** (observed ~0.5s each in isolation but ~3–20s + nested under throttle). This is a slow first request, not a plugin bug — verify with a + CPU-throttled container (`docker run --cpus=0.5 …`) before assuming a hang. + +## Practical notes + +- One scenario per plugin (or per javax/jakarta era). Mirror an existing sibling scenario as a + template and change only what differs (framework version, namespace, deployment model). +- Match `type:` to the deployment: `jvm` (fat-jar started by `bin/startup.sh`, `${agent_opts}` + required) or `tomcat` (WAR on a container). Jakarta/newer frameworks go in a JDK-17 CI group. +- Assert the entry span shape that exercises the plugin's data logic: `componentId`, `spanType: Entry`, + `spanLayer: Http`, and the `url` / `http.method` / `http.status_code` (and `http.params` / + `http.headers` where the plugin collects them) tags, plus cross-process refs. +- Register the scenario in the right `.github/workflows/plugins-*.yaml` matrix (use + `python3 tools/select-group.py` to pick the least-loaded group for its JDK lane). diff --git a/test/plugin/agent-test-tools/pom.xml b/test/plugin/agent-test-tools/pom.xml index 374263e7f4..13a08a39da 100644 --- a/test/plugin/agent-test-tools/pom.xml +++ b/test/plugin/agent-test-tools/pom.xml @@ -35,7 +35,7 @@ pom - cf62c1b733fe2861229201a67b9cc0075ac3e236 + 7220c715d280cf0d7421f17bbc8c8de57249914d ${project.basedir}/target/agent-test-tools https://github.com/apache/skywalking-agent-test-tool.git diff --git a/test/plugin/containers/jvm-container/pom.xml b/test/plugin/containers/jvm-container/pom.xml index 65d4045469..e71083c86e 100644 --- a/test/plugin/containers/jvm-container/pom.xml +++ b/test/plugin/containers/jvm-container/pom.xml @@ -53,7 +53,7 @@ chmod +x /usr/local/skywalking/run.sh tar -xvf ../tools/skywalking-mock-collector.tar.gz -C ../tools apt-get update -y - apt-get install -y unzip + apt-get install -y unzip bash rm -rf /var/lib/apt/lists/* ["/usr/local/skywalking/run.sh"] diff --git a/test/plugin/containers/jvm-container/src/main/docker/run.sh b/test/plugin/containers/jvm-container/src/main/docker/run.sh index 9d1327c621..3a70fe4930 100644 --- a/test/plugin/containers/jvm-container/src/main/docker/run.sh +++ b/test/plugin/containers/jvm-container/src/main/docker/run.sh @@ -24,7 +24,10 @@ exitOnError() { } exitAndClean() { - [[ -n $DEBUG_MODE ]] && exit $1; + if [[ -n $DEBUG_MODE ]]; then + chmod -R a+r ${SCENARIO_HOME} 2>/dev/null + exit $1 + fi [[ -f ${SCENARIO_HOME}/data/actualData.yaml ]] && rm -rf ${SCENARIO_HOME}/data/actualData.yaml [[ -d ${LOGS_HOME} ]] && rm -rf ${LOGS_HOME} @@ -84,7 +87,7 @@ export agent_opts=" -Dskywalking.meter.report_interval=1 -Xms256m -Xmx256m ${agent_opts}" -bash /var/run/${SCENARIO_NAME}/${SCENARIO_START_SCRIPT} 1>${LOGS_HOME}/scenario.out & +bash /var/run/${SCENARIO_NAME}/${SCENARIO_START_SCRIPT} >${LOGS_HOME}/scenario.out 2>&1 & sleep 5 healthCheck ${SCENARIO_HEALTH_CHECK_URL} diff --git a/test/plugin/pom.xml b/test/plugin/pom.xml index 0b3033000e..b42f4a69b7 100644 --- a/test/plugin/pom.xml +++ b/test/plugin/pom.xml @@ -34,6 +34,18 @@ containers + + + set-compiler-release + + [9,) + + + ${java.version} + + + + 8 ${java.version} @@ -47,7 +59,7 @@ 1.18.20 1.24 - 3.8.1 + 3.14.0 true diff --git a/test/plugin/run.sh b/test/plugin/run.sh index 47a87de456..b530c78e98 100755 --- a/test/plugin/run.sh +++ b/test/plugin/run.sh @@ -159,6 +159,8 @@ remove_dir() { start_stamp=`date +%s` parse_commandline "$@" +echo "Scenario=$scenario_name JavaImage=$base_image_java TomcatImage=$base_image_tomcat" + if [[ "$cleanup" == "on" ]]; then do_cleanup [[ -z "${scenario_name}" ]] && exit 0 @@ -229,8 +231,21 @@ ls "${jacoco_home}"/jacocoagent.jar || curl -Lso "${jacoco_home}"/jacocoagent.ja ls "${jacoco_home}"/jacococli.jar || curl -Lso "${jacoco_home}"/jacococli.jar https://repo1.maven.org/maven2/org/jacoco/org.jacoco.cli/${jacoco_version}/org.jacoco.cli-${jacoco_version}-nodeps.jar supported_versions=`grep -v -E "^$|^#" ${supported_version_file}` -for version in ${supported_versions} +for version_line in ${supported_versions} do + # Support format: version[,key=value[,key=value...]] + # e.g., "2.7.14,spring.boot.version=2.7.19" + # First token is test.framework.version, rest are extra Maven properties. + version=$(echo "${version_line}" | cut -d',' -f1) + extra_props="" + remaining=$(echo "${version_line}" | cut -d',' -f2- -s) + if [[ -n "${remaining}" ]]; then + IFS=',' read -ra props <<< "${remaining}" + for prop in "${props[@]}"; do + extra_props="${extra_props} -D${prop}" + done + fi + testcase_name="${scenario_name}-${version}" # testcase working directory, there are logs, data and packages. @@ -243,7 +258,7 @@ do cp ./config/expectedData.yaml ${case_work_base}/data # echo "build ${testcase_name}" - ${mvnw} -q --batch-mode clean package -Dtest.framework.version=${version} && \ + ${mvnw} -q --batch-mode clean package -Dtest.framework.version=${version} ${extra_props} && \ mv ./target/${scenario_name}.* ${case_work_base} java -jar \ diff --git a/test/plugin/runner-helper/src/main/resources/compose-start-script.template b/test/plugin/runner-helper/src/main/resources/compose-start-script.template index 6ee08de4b5..c94e415bf2 100644 --- a/test/plugin/runner-helper/src/main/resources/compose-start-script.template +++ b/test/plugin/runner-helper/src/main/resources/compose-start-script.template @@ -26,20 +26,51 @@ docker_container_name="${docker_container_name}_1" <#noparse> container_name="${project_name}_${docker_container_name}" -docker-compose -p ${project_name} -f ${compose_file} up -d + +# Diagnostic: log docker compose version +docker compose version >&2 || true + +echo "=== Starting docker compose ===" >&2 +compose_output=$(docker compose -p ${project_name} -f ${compose_file} up -d 2>&1) +compose_exit=$? +echo "${compose_output}" >&2 +if [[ ${compose_exit} -ne 0 ]]; then + echo "docker compose up exited with code ${compose_exit}" >&2 +fi container_id=`docker ps -qf "name=${container_name}"` if [[ -z "${container_id}" ]]; then - echo "docker startup failure!" >&2 + echo "docker startup failure! container_name=${container_name} not found." >&2 + echo "=== docker ps -a ===" >&2 + docker ps -a >&2 || true + echo "=== docker compose logs ===" >&2 + docker compose -p ${project_name} -f ${compose_file} logs 2>&1 >&2 || true + # Create container.log with diagnostic info + mkdir -p ${SCENARIO_HOME}/logs + { + echo "=== CONTAINER STARTUP FAILURE ===" + echo "container_name=${container_name} was not found by docker ps" + echo "compose_exit_code=${compose_exit}" + echo "compose_output:" + echo "${compose_output}" + echo "=== docker ps -a ===" + docker ps -a 2>&1 || true + echo "=== docker compose logs ===" + docker compose -p ${project_name} -f ${compose_file} logs 2>&1 || true + } >${SCENARIO_HOME}/logs/container.log + docker compose -p ${project_name} -f ${compose_file} kill 2>/dev/null || true + docker compose -p ${project_name} -f ${compose_file} rm -f 2>/dev/null || true status=1 else status=`docker wait ${container_id}` [[ $status -ne 0 ]] && docker logs ${container_id} >&2 + # Recreate logs dir in case the container removed it via volume mount + mkdir -p ${SCENARIO_HOME}/logs docker logs ${container_id} >${SCENARIO_HOME}/logs/container.log - docker-compose -p ${project_name} -f ${compose_file} kill - docker-compose -p ${project_name} -f ${compose_file} rm -f + docker compose -p ${project_name} -f ${compose_file} kill + docker compose -p ${project_name} -f ${compose_file} rm -f fi diff --git a/test/plugin/runner-helper/src/main/resources/container-start-script.template b/test/plugin/runner-helper/src/main/resources/container-start-script.template index 108fb0bf20..97ee284649 100644 --- a/test/plugin/runner-helper/src/main/resources/container-start-script.template +++ b/test/plugin/runner-helper/src/main/resources/container-start-script.template @@ -33,6 +33,11 @@ docker run -d \ <#if debug_mode??> --env DEBUG_MODE=${debug_mode} \ + <#if environments??> + <#list environments as env> + --env ${env} \ + + -v ${agent_home}:/usr/local/skywalking/scenario/agent \ -v ${scenario_home}:/usr/local/skywalking/scenario \ -v ${jacoco_home}:/jacoco \ @@ -42,13 +47,25 @@ sleep 3 container_name=`docker ps -aqf "name=${docker_container_name}"` <#noparse> -status=$(docker wait ${container_name}) - if [[ -z ${container_name} ]]; then echo "docker startup failure!" >&2 + echo "=== docker ps -a ===" >&2 + docker ps -a >&2 || true + # Create container.log with diagnostic info + mkdir -p ${SCENARIO_HOME}/logs + { + echo "=== CONTAINER STARTUP FAILURE ===" + echo "Container not found by docker ps" + echo "=== docker ps -a ===" + docker ps -a 2>&1 || true + } >${SCENARIO_HOME}/logs/container.log status=1 else + status=$(docker wait ${container_name}) + [[ $status -ne 0 ]] && docker logs ${container_name} >&2 + # Recreate logs dir in case the container removed it via volume mount + mkdir -p ${SCENARIO_HOME}/logs docker logs ${container_name} >${SCENARIO_HOME}/logs/container.log docker container rm -f $container_name fi diff --git a/test/plugin/scenarios/agent-so11y-scenario/bin/startup.sh b/test/plugin/scenarios/agent-so11y-scenario/bin/startup.sh new file mode 100644 index 0000000000..e4140338be --- /dev/null +++ b/test/plugin/scenarios/agent-so11y-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/agent-so11y-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/agent-so11y-scenario/config/expectedData.yaml b/test/plugin/scenarios/agent-so11y-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..8e044e041f --- /dev/null +++ b/test/plugin/scenarios/agent-so11y-scenario/config/expectedData.yaml @@ -0,0 +1,177 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: agent-so11y-scenario + segmentSize: gt 1 + segments: + - segmentId: not null + spans: + - operationName: H2/JDBC/PreparedStatement/execute + parentSpanId: 0 + spanId: 1 + spanLayer: Database + startTime: gt 0 + endTime: gt 0 + componentId: 32 + isError: false + spanType: Exit + peer: localhost:-1 + tags: + - {key: db.type, value: H2} + - {key: db.instance, value: test} + - key: db.statement + value: "CREATE TABLE test_007(\nid VARCHAR(1) PRIMARY KEY, \nvalue VARCHAR(1)\ + \ NOT NULL)" + skipAnalysis: 'false' + - operationName: H2/JDBC/CallableStatement/execute + parentSpanId: 0 + spanId: 2 + spanLayer: Database + startTime: gt 0 + endTime: gt 0 + componentId: 32 + isError: false + spanType: Exit + peer: localhost:-1 + tags: + - {key: db.type, value: H2} + - {key: db.instance, value: test} + - {key: db.statement, value: 'INSERT INTO test_007(id, value) VALUES(?,?)'} + skipAnalysis: 'false' + - operationName: H2/JDBC/Statement/execute + parentSpanId: 0 + spanId: 3 + spanLayer: Database + startTime: gt 0 + endTime: gt 0 + componentId: 32 + isError: false + spanType: Exit + peer: localhost:-1 + tags: + - {key: db.type, value: H2} + - {key: db.instance, value: test} + - {key: db.statement, value: DROP table test_007} + skipAnalysis: 'false' + - operationName: /agent-so11y-scenario/case/ignore.html + parentSpanId: 0 + spanId: 4 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 66 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/agent-so11y-scenario/case/ignore.html'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /agent-so11y-scenario/case/propagated + parentSpanId: 0 + spanId: 5 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 66 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/agent-so11y-scenario/case/propagated'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: GET:/agent-so11y-scenario/case/agent-so11y-scenario + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/agent-so11y-scenario/case/agent-so11y-scenario'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GET:/agent-so11y-scenario/case/propagated + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/agent-so11y-scenario/case/propagated'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + refs: + - {parentEndpoint: 'GET:/agent-so11y-scenario/case/agent-so11y-scenario', networkAddress: 'localhost:8080', + refType: CrossProcess, parentSpanId: 5, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: agent-so11y-scenario, + traceId: not null} + +meterItems: +- serviceName: agent-so11y-scenario + meterSize: ge 9 + meters: + - meterId: + name: created_tracing_context_counter + tags: + - {name: created_by, value: propagated} + singleValue: 1.0 + - meterId: + name: created_tracing_context_counter + tags: + - {name: created_by, value: sampler} + singleValue: 1.0 + - meterId: + name: finished_tracing_context_counter + tags: [] + singleValue: 2.0 + - meterId: + name: created_ignored_context_counter + tags: + - {name: created_by, value: propagated} + singleValue: 1.0 + - meterId: + name: created_ignored_context_counter + tags: + - {name: created_by, value: sampler} + singleValue: 1.0 + - meterId: + name: finished_ignored_context_counter + tags: [] + singleValue: 2.0 + - meterId: + name: tracing_context_performance + tags: [] + histogramBuckets: [0.0, 1000.0, 10000.0, 50000.0, 100000.0, 300000.0, 500000.0, 1000000.0, 5000000.0, 1.0E7, 2.0E7, 5.0E7, 1.0E8] + - meterId: + name: possible_leaked_context_counter + tags: + - {name: source, value: tracing} + singleValue: 1.0 diff --git a/test/plugin/scenarios/agent-so11y-scenario/configuration.yml b/test/plugin/scenarios/agent-so11y-scenario/configuration.yml new file mode 100644 index 0000000000..7d9a31395c --- /dev/null +++ b/test/plugin/scenarios/agent-so11y-scenario/configuration.yml @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/agent-so11y-scenario/case/agent-so11y-scenario +healthCheck: http://localhost:8080/agent-so11y-scenario/case/healthCheck.html +runningMode: with_bootstrap +withPlugins: apm-jdk-http-plugin-*.jar +startScript: ./bin/startup.sh +environment: + - SW_AGENT_SPAN_LIMIT=6 + - SW_METER_REPORT_INTERVAL=1 diff --git a/test/plugin/scenarios/agent-so11y-scenario/pom.xml b/test/plugin/scenarios/agent-so11y-scenario/pom.xml new file mode 100644 index 0000000000..39095c57ad --- /dev/null +++ b/test/plugin/scenarios/agent-so11y-scenario/pom.xml @@ -0,0 +1,128 @@ + + + + + org.apache.skywalking.apm.testcase + agent-so11y-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 1.8 + 3.8.1 + 1.0.0 + 1.4.177 + 2.5.6 + 1.18.20 + + + skywalking-agent-so11y-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + com.h2database + h2 + ${h2.version} + + + + + agent-so11y-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + diff --git a/test/plugin/scenarios/agent-so11y-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/agent-so11y-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..abd3f9e799 --- /dev/null +++ b/test/plugin/scenarios/agent-so11y-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/agent-so11y-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/agent-so11y-scenario/src/main/java/org/apache/skywalking/apm/testcase/h2/Application.java b/test/plugin/scenarios/agent-so11y-scenario/src/main/java/org/apache/skywalking/apm/testcase/h2/Application.java new file mode 100644 index 0000000000..e063f8d641 --- /dev/null +++ b/test/plugin/scenarios/agent-so11y-scenario/src/main/java/org/apache/skywalking/apm/testcase/h2/Application.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.h2; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/agent-so11y-scenario/src/main/java/org/apache/skywalking/apm/testcase/h2/controller/CaseController.java b/test/plugin/scenarios/agent-so11y-scenario/src/main/java/org/apache/skywalking/apm/testcase/h2/controller/CaseController.java new file mode 100644 index 0000000000..ee8760cb50 --- /dev/null +++ b/test/plugin/scenarios/agent-so11y-scenario/src/main/java/org/apache/skywalking/apm/testcase/h2/controller/CaseController.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.h2.controller; + +import java.net.URL; +import lombok.extern.log4j.Log4j2; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/case") +@Log4j2 +public class CaseController { + + private static final String SUCCESS = "Success"; + + private static final String CREATE_TABLE_SQL = "CREATE TABLE test_007(\n" + + "id VARCHAR(1) PRIMARY KEY, \n" + + "value VARCHAR(1) NOT NULL)"; + private static final String INSERT_DATA_SQL = "INSERT INTO test_007(id, value) VALUES(?,?)"; + private static final String DROP_TABLE_SQL = "DROP table test_007"; + + @RequestMapping("/healthCheck.html") + @ResponseBody + public String healthCheck() { + // your codes + return SUCCESS; + } + + @RequestMapping("/agent-so11y-scenario") + @ResponseBody + public String testcase() throws Exception { + try (SQLExecutor sqlExecute = new SQLExecutor()) { + sqlExecute.createTable(CREATE_TABLE_SQL); + sqlExecute.insertData(INSERT_DATA_SQL, "1", "1"); + sqlExecute.dropTable(DROP_TABLE_SQL); + + // test bootstrap plugin & ignore context counter + new URL("http://localhost:8080/agent-so11y-scenario/case/ignore.html").getContent(); + new URL("http://localhost:8080/agent-so11y-scenario/case/propagated").getContent(); + } catch (Exception e) { + log.error("Failed to execute sql.", e); + throw e; + } + return SUCCESS; + } + + @RequestMapping("/ignore.html") + @ResponseBody + public String ignorePath() { + return SUCCESS; + } + + @RequestMapping("/propagated") + @ResponseBody + public String propagated() { + return SUCCESS; + } +} diff --git a/test/plugin/scenarios/agent-so11y-scenario/src/main/java/org/apache/skywalking/apm/testcase/h2/controller/SQLExecutor.java b/test/plugin/scenarios/agent-so11y-scenario/src/main/java/org/apache/skywalking/apm/testcase/h2/controller/SQLExecutor.java new file mode 100644 index 0000000000..1f6e3b3463 --- /dev/null +++ b/test/plugin/scenarios/agent-so11y-scenario/src/main/java/org/apache/skywalking/apm/testcase/h2/controller/SQLExecutor.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.h2.controller; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +public class SQLExecutor implements AutoCloseable { + + private static final String URL = "jdbc:h2:mem:test"; + private static final String USERNAME = "root"; + private static final String PASSWORD = "root"; + + private Connection connection; + + public SQLExecutor() throws SQLException { + try { + Class.forName("org.h2.Driver"); + } catch (ClassNotFoundException e) { + // + } + connection = DriverManager.getConnection(URL, USERNAME, PASSWORD); + } + + public void createTable(String sql) throws SQLException { + PreparedStatement preparedStatement = connection.prepareStatement(sql); + preparedStatement.execute(); + preparedStatement.close(); + } + + public void insertData(String sql, String id, String value) throws SQLException { + CallableStatement preparedStatement = connection.prepareCall(sql); + preparedStatement.setString(1, id); + preparedStatement.setString(2, value); + preparedStatement.execute(); + preparedStatement.close(); + } + + public void dropTable(String sql) throws SQLException { + Statement preparedStatement = connection.createStatement(); + preparedStatement.execute(sql); + preparedStatement.close(); + } + + public void closeConnection() throws SQLException { + if (this.connection != null) { + this.connection.close(); + } + } + + @Override + public void close() throws Exception { + closeConnection(); + } +} diff --git a/test/plugin/scenarios/agent-so11y-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/agent-so11y-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000..8e987fed1a --- /dev/null +++ b/test/plugin/scenarios/agent-so11y-scenario/src/main/resources/application.yaml @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server: + port: 8080 + servlet: + context-path: /agent-so11y-scenario +logging: + config: classpath:log4j2.xml \ No newline at end of file diff --git a/test/plugin/scenarios/oracle-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/agent-so11y-scenario/src/main/resources/log4j2.xml similarity index 100% rename from test/plugin/scenarios/oracle-scenario/src/main/resources/log4j2.xml rename to test/plugin/scenarios/agent-so11y-scenario/src/main/resources/log4j2.xml diff --git a/test/plugin/scenarios/oracle-scenario/support-version.list b/test/plugin/scenarios/agent-so11y-scenario/support-version.list similarity index 98% rename from test/plugin/scenarios/oracle-scenario/support-version.list rename to test/plugin/scenarios/agent-so11y-scenario/support-version.list index 58b2e2f6dd..293331ad63 100644 --- a/test/plugin/scenarios/oracle-scenario/support-version.list +++ b/test/plugin/scenarios/agent-so11y-scenario/support-version.list @@ -14,4 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -10.2.0.4.0 +1.0.0 diff --git a/test/plugin/scenarios/apm-toolkit-trace-scenario/config/expectedData.yaml b/test/plugin/scenarios/apm-toolkit-trace-scenario/config/expectedData.yaml index ab105a82ab..8c6b47877a 100644 --- a/test/plugin/scenarios/apm-toolkit-trace-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/apm-toolkit-trace-scenario/config/expectedData.yaml @@ -492,7 +492,7 @@ segmentItems: skipAnalysis: 'true' meterItems: - serviceName: apm-toolkit-trace-scenario - meterSize: 8 + meterSize: ge 8 meters: - meterId: name: test_counter diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/bin/startup.sh b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/bin/startup.sh new file mode 100755 index 0000000000..ec6c3c19a5 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/c3p0-0.9.0.x-0.9.1.x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/config/expectedData.yaml new file mode 100755 index 0000000000..631a5bb4c3 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/config/expectedData.yaml @@ -0,0 +1,288 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: c3p0-0.9.0.x-0.9.1.x-scenario + segmentSize: nq 0 + segments: + - segmentId: not null + spans: + - operationName: C3P0/Connection/getConnection + parentSpanId: 0 + spanId: 1 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 2 + startTime: gt 0 + endTime: gt 0 + componentId: 33 + isError: false + spanType: Exit + peer: mysql-server:3306 + skipAnalysis: false + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - key: db.statement + value: "CREATE TABLE test_C3P0(\nid VARCHAR(1) PRIMARY KEY, \nvalue VARCHAR(1)\ + \ NOT NULL)" + - operationName: C3P0/Connection/close + parentSpanId: 0 + spanId: 3 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: C3P0/Connection/getConnection + parentSpanId: 0 + spanId: 4 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 5 + startTime: gt 0 + endTime: gt 0 + componentId: 33 + isError: false + spanType: Exit + peer: mysql-server:3306 + skipAnalysis: false + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - key: db.statement + value: 'INSERT INTO test_C3P0(id, value) VALUES(1,1)' + - operationName: C3P0/Connection/close + parentSpanId: 0 + spanId: 6 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: C3P0/Connection/getConnection + parentSpanId: 0 + spanId: 7 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 8 + startTime: gt 0 + endTime: gt 0 + componentId: 33 + isError: false + spanType: Exit + peer: mysql-server:3306 + skipAnalysis: false + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - key: db.statement + value: 'SELECT id, value FROM test_C3P0 WHERE id=1' + - operationName: C3P0/Connection/close + parentSpanId: 0 + spanId: 9 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: C3P0/Connection/getConnection + parentSpanId: 0 + spanId: 10 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 11 + startTime: gt 0 + endTime: gt 0 + componentId: 33 + isError: false + spanType: Exit + peer: mysql-server:3306 + skipAnalysis: false + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - key: db.statement + value: "DELETE FROM test_C3P0 WHERE id=1" + - operationName: C3P0/Connection/close + parentSpanId: 0 + spanId: 12 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: C3P0/Connection/getConnection + parentSpanId: 0 + spanId: 13 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 14 + startTime: gt 0 + endTime: gt 0 + componentId: 33 + isError: false + spanType: Exit + peer: mysql-server:3306 + skipAnalysis: false + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - key: db.statement + value: "DROP table test_C3P0" + - operationName: C3P0/Connection/close + parentSpanId: 0 + spanId: 15 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: GET:/c3p0-0.9.0.x-0.9.1.x-scenario/case/c3p0-0.9.0.x-0.9.1.x-scenario + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/c3p0-0.9.0.x-0.9.1.x-scenario/case/c3p0-0.9.0.x-0.9.1.x-scenario'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} +meterItems: +- serviceName: c3p0-0.9.0.x-0.9.1.x-scenario + meterSize: ge 12 + meters: + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: initialPoolSize} + singleValue: ge 0 + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: maxIdleTime} + singleValue: ge 0 + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: maxPoolSize} + singleValue: ge 0 + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: minPoolSize} + singleValue: ge -1 + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: numBusyConnections} + singleValue: ge 0 + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: numIdleConnections} + singleValue: ge 0 + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: numTotalConnections} + singleValue: ge 0 + - meterId: + name: thread_pool + tags: + - {name: metric_type, value: core_pool_size} + - {name: pool_name, value: tomcat_execute_pool} + singleValue: ge 1 + - meterId: + name: thread_pool + tags: + - {name: metric_type, value: max_pool_size} + - {name: pool_name, value: tomcat_execute_pool} + singleValue: ge 1 + - meterId: + name: thread_pool + tags: + - {name: metric_type, value: pool_size} + - {name: pool_name, value: tomcat_execute_pool} + singleValue: ge 0 + - meterId: + name: thread_pool + tags: + - {name: metric_type, value: active_size} + - {name: pool_name, value: tomcat_execute_pool} + singleValue: ge 0 + - meterId: + name: thread_pool + tags: + - {name: metric_type, value: queue_size} + - {name: pool_name, value: tomcat_execute_pool} + singleValue: ge 0 diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/configuration.yml b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/configuration.yml new file mode 100755 index 0000000000..d05855afe4 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/configuration.yml @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/c3p0-0.9.0.x-0.9.1.x-scenario/case/c3p0-0.9.0.x-0.9.1.x-scenario +healthCheck: http://localhost:8080/c3p0-0.9.0.x-0.9.1.x-scenario/case/healthCheck +startScript: ./bin/startup.sh +environment: +depends_on: + - mysql-server +dependencies: + mysql-server: + image: mysql:5.7 + hostname: mysql-server + expose: + - "3306" + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=test diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/pom.xml b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/pom.xml new file mode 100755 index 0000000000..0a3408ade0 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/pom.xml @@ -0,0 +1,127 @@ + + + + + org.apache.skywalking.apm.testcase + c3p0-0.9.0.x-0.9.1.x-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 1.8 + 3.8.1 + 0.9.1.2 + ${test.framework.version} + 2.1.6.RELEASE + + + skywalking-c3p0-0.9.0.x-0.9.1.x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + mysql + mysql-connector-java + 5.1.25 + + + + c3p0 + c3p0 + ${test.framework.version} + + + + + c3p0-0.9.0.x-0.9.1.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/assembly/assembly.xml new file mode 100755 index 0000000000..6e2d37867c --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/c3p0-0.9.0.x-0.9.1.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/c3p0/Application.java b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/c3p0/Application.java new file mode 100755 index 0000000000..2accc46e0f --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/c3p0/Application.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.c3p0.c3p0; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + try { + SpringApplication.run(Application.class, args); + } catch (Exception e) { + // Never do this + } + } +} diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/c3p0/MysqlConfig.java b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/c3p0/MysqlConfig.java new file mode 100755 index 0000000000..bdc0102b61 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/c3p0/MysqlConfig.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.c3p0.c3p0; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class MysqlConfig { + private static Logger LOGGER = LogManager.getLogger(MysqlConfig.class); + private static String URL; + private static String USER_NAME; + private static String PASSWORD; + + static { + InputStream inputStream = MysqlConfig.class.getClassLoader().getResourceAsStream("jdbc.properties"); + Properties properties = new Properties(); + try { + properties.load(inputStream); + } catch (IOException e) { + LOGGER.error("Failed to load config", e); + } + URL = properties.getProperty("mysql.url"); + USER_NAME = properties.getProperty("mysql.username"); + PASSWORD = properties.getProperty("mysql.password"); + } + + public static String getUrl() { + return URL; + } + + public static String getUserName() { + return USER_NAME; + } + + public static String getPassword() { + return PASSWORD; + } +} diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/c3p0/controller/CaseController.java b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/c3p0/controller/CaseController.java new file mode 100755 index 0000000000..447f843413 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/c3p0/controller/CaseController.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.c3p0.c3p0.controller; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.skywalking.apm.testcase.c3p0.c3p0.service.CaseService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/case") +public class CaseController { + + @Autowired + CaseService caseService; + + private static final Logger LOGGER = LogManager.getLogger(CaseController.class); + + private static final String SUCCESS = "Success"; + + @RequestMapping("/c3p0-0.9.0.x-0.9.1.x-scenario") + @ResponseBody + public String testcase() throws Exception { + try { + caseService.testCase(); + } catch (Exception e) { + LOGGER.error("Failed to execute sql.", e); + throw e; + } + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() throws Exception { + return SUCCESS; + } + +} diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/c3p0/service/CaseService.java b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/c3p0/service/CaseService.java new file mode 100644 index 0000000000..ee2c38ed21 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/c3p0/service/CaseService.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.c3p0.c3p0.service; + +import com.mchange.v2.c3p0.ComboPooledDataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import org.apache.skywalking.apm.testcase.c3p0.c3p0.MysqlConfig; +import org.springframework.stereotype.Service; + +@Service +public class CaseService { + + public static ComboPooledDataSource DS; + private static final String CREATE_TABLE_SQL = "CREATE TABLE test_C3P0(\n" + "id VARCHAR(1) PRIMARY KEY, \n" + "value VARCHAR(1) NOT NULL)"; + private static final String INSERT_DATA_SQL = "INSERT INTO test_C3P0(id, value) VALUES(1,1)"; + private static final String QUERY_DATA_SQL = "SELECT id, value FROM test_C3P0 WHERE id=1"; + private static final String DELETE_DATA_SQL = "DELETE FROM test_C3P0 WHERE id=1"; + private static final String DROP_TABLE_SQL = "DROP table test_C3P0"; + + static { + try { + DS = new ComboPooledDataSource(); + DS.setDriverClass("com.mysql.jdbc.Driver"); + DS.setJdbcUrl(MysqlConfig.getUrl()); + DS.setUser(MysqlConfig.getUserName()); + DS.setPassword(MysqlConfig.getPassword()); + DS.setAcquireIncrement(1); + DS.setInitialPoolSize(5); + DS.setMinPoolSize(1); + DS.setMaxIdleTime(10); + DS.setTestConnectionOnCheckin(false); + DS.setTestConnectionOnCheckout(false); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void testCase() { + sqlExecutor(DS, CREATE_TABLE_SQL); + sqlExecutor(DS, INSERT_DATA_SQL); + sqlExecutor(DS, QUERY_DATA_SQL); + sqlExecutor(DS, DELETE_DATA_SQL); + sqlExecutor(DS, DROP_TABLE_SQL); + } + + public void sqlExecutor(ComboPooledDataSource dataSource, String sql) { + try (Connection conn = dataSource.getConnection();) { + Statement statement = conn.createStatement(); + statement.execute(sql); + } catch (SQLException e) { + e.printStackTrace(); + } + } +} diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/resources/application.yaml new file mode 100755 index 0000000000..66a7a51fd1 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/resources/application.yaml @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server: + port: 8080 + servlet: + context-path: /c3p0-0.9.0.x-0.9.1.x-scenario +logging: + config: classpath:log4j2.xml \ No newline at end of file diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/resources/jdbc.properties b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/resources/jdbc.properties new file mode 100755 index 0000000000..8051777cce --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/resources/jdbc.properties @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +mysql.url=jdbc:mysql://mysql-server:3306/test?serverTimezone=CST&useLocalSessionState=true +mysql.username=root +mysql.password=root \ No newline at end of file diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/resources/log4j2.xml new file mode 100755 index 0000000000..9849ed5a8a --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/support-version.list b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/support-version.list new file mode 100755 index 0000000000..730308cbca --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.0.x-0.9.1.x-scenario/support-version.list @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +0.9.0.4 +0.9.1.2 \ No newline at end of file diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/bin/startup.sh b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/bin/startup.sh new file mode 100755 index 0000000000..9ee496ba82 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/c3p0-0.9.2.x-0.10.x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/config/expectedData.yaml new file mode 100755 index 0000000000..35d6453291 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/config/expectedData.yaml @@ -0,0 +1,288 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: c3p0-0.9.2.x-0.10.x-scenario + segmentSize: nq 0 + segments: + - segmentId: not null + spans: + - operationName: C3P0/Connection/getConnection + parentSpanId: 0 + spanId: 1 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 2 + startTime: gt 0 + endTime: gt 0 + componentId: 33 + isError: false + spanType: Exit + peer: mysql-server:3306 + skipAnalysis: false + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - key: db.statement + value: "CREATE TABLE test_C3P0(\nid VARCHAR(1) PRIMARY KEY, \nvalue VARCHAR(1)\ + \ NOT NULL)" + - operationName: C3P0/Connection/close + parentSpanId: 0 + spanId: 3 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: C3P0/Connection/getConnection + parentSpanId: 0 + spanId: 4 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 5 + startTime: gt 0 + endTime: gt 0 + componentId: 33 + isError: false + spanType: Exit + peer: mysql-server:3306 + skipAnalysis: false + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - key: db.statement + value: 'INSERT INTO test_C3P0(id, value) VALUES(1,1)' + - operationName: C3P0/Connection/close + parentSpanId: 0 + spanId: 6 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: C3P0/Connection/getConnection + parentSpanId: 0 + spanId: 7 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 8 + startTime: gt 0 + endTime: gt 0 + componentId: 33 + isError: false + spanType: Exit + peer: mysql-server:3306 + skipAnalysis: false + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - key: db.statement + value: 'SELECT id, value FROM test_C3P0 WHERE id=1' + - operationName: C3P0/Connection/close + parentSpanId: 0 + spanId: 9 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: C3P0/Connection/getConnection + parentSpanId: 0 + spanId: 10 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 11 + startTime: gt 0 + endTime: gt 0 + componentId: 33 + isError: false + spanType: Exit + peer: mysql-server:3306 + skipAnalysis: false + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - key: db.statement + value: "DELETE FROM test_C3P0 WHERE id=1" + - operationName: C3P0/Connection/close + parentSpanId: 0 + spanId: 12 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: C3P0/Connection/getConnection + parentSpanId: 0 + spanId: 13 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 14 + startTime: gt 0 + endTime: gt 0 + componentId: 33 + isError: false + spanType: Exit + peer: mysql-server:3306 + skipAnalysis: false + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - key: db.statement + value: "DROP table test_C3P0" + - operationName: C3P0/Connection/close + parentSpanId: 0 + spanId: 15 + startTime: gt 0 + endTime: gt 0 + componentId: 152 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + - operationName: GET:/c3p0-0.9.2.x-0.10.x-scenario/case/c3p0-0.9.2.x-0.10.x-scenario + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/c3p0-0.9.2.x-0.10.x-scenario/case/c3p0-0.9.2.x-0.10.x-scenario'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} +meterItems: +- serviceName: c3p0-0.9.2.x-0.10.x-scenario + meterSize: ge 12 + meters: + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: initialPoolSize} + singleValue: ge 0 + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: maxIdleTime} + singleValue: ge 0 + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: maxPoolSize} + singleValue: ge 0 + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: minPoolSize} + singleValue: ge -1 + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: numBusyConnections} + singleValue: ge 0 + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: numIdleConnections} + singleValue: ge 0 + - meterId: + name: datasource + tags: + - {name: name, value: test_mysql-server:3306} + - {name: status, value: numTotalConnections} + singleValue: ge 0 + - meterId: + name: thread_pool + tags: + - {name: metric_type, value: core_pool_size} + - {name: pool_name, value: tomcat_execute_pool} + singleValue: ge 1 + - meterId: + name: thread_pool + tags: + - {name: metric_type, value: max_pool_size} + - {name: pool_name, value: tomcat_execute_pool} + singleValue: ge 1 + - meterId: + name: thread_pool + tags: + - {name: metric_type, value: pool_size} + - {name: pool_name, value: tomcat_execute_pool} + singleValue: ge 0 + - meterId: + name: thread_pool + tags: + - {name: metric_type, value: active_size} + - {name: pool_name, value: tomcat_execute_pool} + singleValue: ge 0 + - meterId: + name: thread_pool + tags: + - {name: metric_type, value: queue_size} + - {name: pool_name, value: tomcat_execute_pool} + singleValue: ge 0 diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/configuration.yml b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/configuration.yml new file mode 100755 index 0000000000..1fb61085e6 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/configuration.yml @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/c3p0-0.9.2.x-0.10.x-scenario/case/c3p0-0.9.2.x-0.10.x-scenario +healthCheck: http://localhost:8080/c3p0-0.9.2.x-0.10.x-scenario/case/healthCheck +startScript: ./bin/startup.sh +environment: +depends_on: + - mysql-server +dependencies: + mysql-server: + image: mysql:5.7 + hostname: mysql-server + expose: + - "3306" + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=test diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/pom.xml b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/pom.xml new file mode 100755 index 0000000000..dc4e7cf830 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/pom.xml @@ -0,0 +1,127 @@ + + + + + org.apache.skywalking.apm.testcase + c3p0-0.9.2.x-0.10.x-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 1.8 + 3.8.1 + 0.9.5.5 + ${test.framework.version} + 2.1.6.RELEASE + + + skywalking-c3p0-0.9.2.x-0.10.x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + mysql + mysql-connector-java + 5.1.25 + + + + com.mchange + c3p0 + ${test.framework.version} + + + + + c3p0-0.9.2.x-0.10.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/assembly/assembly.xml new file mode 100755 index 0000000000..724b0f4a36 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/c3p0-0.9.2.x-0.10.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/mchange/Application.java b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/mchange/Application.java new file mode 100755 index 0000000000..6f88c48cbc --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/mchange/Application.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.c3p0.mchange; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + try { + SpringApplication.run(Application.class, args); + } catch (Exception e) { + // Never do this + } + } +} diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/mchange/MysqlConfig.java b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/mchange/MysqlConfig.java new file mode 100755 index 0000000000..8762486f35 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/mchange/MysqlConfig.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.c3p0.mchange; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class MysqlConfig { + private static Logger LOGGER = LogManager.getLogger(MysqlConfig.class); + private static String URL; + private static String USER_NAME; + private static String PASSWORD; + + static { + InputStream inputStream = MysqlConfig.class.getClassLoader().getResourceAsStream("jdbc.properties"); + Properties properties = new Properties(); + try { + properties.load(inputStream); + } catch (IOException e) { + LOGGER.error("Failed to load config", e); + } + URL = properties.getProperty("mysql.url"); + USER_NAME = properties.getProperty("mysql.username"); + PASSWORD = properties.getProperty("mysql.password"); + } + + public static String getUrl() { + return URL; + } + + public static String getUserName() { + return USER_NAME; + } + + public static String getPassword() { + return PASSWORD; + } +} diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/mchange/controller/CaseController.java b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/mchange/controller/CaseController.java new file mode 100755 index 0000000000..c9d8432531 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/mchange/controller/CaseController.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.c3p0.mchange.controller; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.skywalking.apm.testcase.c3p0.mchange.service.CaseService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/case") +public class CaseController { + + @Autowired + CaseService caseService; + + private static final Logger LOGGER = LogManager.getLogger(CaseController.class); + + private static final String SUCCESS = "Success"; + + @RequestMapping("/c3p0-0.9.2.x-0.10.x-scenario") + @ResponseBody + public String testcase() throws Exception { + try { + caseService.testCase(); + } catch (Exception e) { + LOGGER.error("Failed to execute sql.", e); + throw e; + } + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() throws Exception { + return SUCCESS; + } + +} diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/mchange/service/CaseService.java b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/mchange/service/CaseService.java new file mode 100644 index 0000000000..3d493b2a27 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/c3p0/mchange/service/CaseService.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.c3p0.mchange.service; + +import com.mchange.v2.c3p0.ComboPooledDataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import org.apache.skywalking.apm.testcase.c3p0.mchange.MysqlConfig; +import org.springframework.stereotype.Service; + +@Service +public class CaseService { + + public static ComboPooledDataSource DS; + private static final String CREATE_TABLE_SQL = "CREATE TABLE test_C3P0(\n" + "id VARCHAR(1) PRIMARY KEY, \n" + "value VARCHAR(1) NOT NULL)"; + private static final String INSERT_DATA_SQL = "INSERT INTO test_C3P0(id, value) VALUES(1,1)"; + private static final String QUERY_DATA_SQL = "SELECT id, value FROM test_C3P0 WHERE id=1"; + private static final String DELETE_DATA_SQL = "DELETE FROM test_C3P0 WHERE id=1"; + private static final String DROP_TABLE_SQL = "DROP table test_C3P0"; + + static { + try { + DS = new ComboPooledDataSource(); + DS.setDriverClass("com.mysql.jdbc.Driver"); + DS.setJdbcUrl(MysqlConfig.getUrl()); + DS.setUser(MysqlConfig.getUserName()); + DS.setPassword(MysqlConfig.getPassword()); + DS.setAcquireIncrement(1); + DS.setInitialPoolSize(5); + DS.setMinPoolSize(1); + DS.setMaxIdleTime(10); + DS.setTestConnectionOnCheckin(false); + DS.setTestConnectionOnCheckout(false); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void testCase() { + sqlExecutor(DS, CREATE_TABLE_SQL); + sqlExecutor(DS, INSERT_DATA_SQL); + sqlExecutor(DS, QUERY_DATA_SQL); + sqlExecutor(DS, DELETE_DATA_SQL); + sqlExecutor(DS, DROP_TABLE_SQL); + } + + public void sqlExecutor(ComboPooledDataSource dataSource, String sql) { + try (Connection conn = dataSource.getConnection();) { + Statement statement = conn.createStatement(); + statement.execute(sql); + } catch (SQLException e) { + e.printStackTrace(); + } + } +} diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/resources/application.yaml new file mode 100755 index 0000000000..88dfae1b32 --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/resources/application.yaml @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server: + port: 8080 + servlet: + context-path: /c3p0-0.9.2.x-0.10.x-scenario +logging: + config: classpath:log4j2.xml \ No newline at end of file diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/resources/jdbc.properties b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/resources/jdbc.properties new file mode 100755 index 0000000000..8051777cce --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/resources/jdbc.properties @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +mysql.url=jdbc:mysql://mysql-server:3306/test?serverTimezone=CST&useLocalSessionState=true +mysql.username=root +mysql.password=root \ No newline at end of file diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/resources/log4j2.xml new file mode 100755 index 0000000000..9849ed5a8a --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/support-version.list b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/support-version.list new file mode 100755 index 0000000000..9405ba41cc --- /dev/null +++ b/test/plugin/scenarios/c3p0-0.9.2.x-0.10.x-scenario/support-version.list @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +0.10.0 +0.9.5.5 +0.9.2.1 \ No newline at end of file diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/bin/startup.sh b/test/plugin/scenarios/caffeine-3.x-scenario/bin/startup.sh new file mode 100755 index 0000000000..01883d186d --- /dev/null +++ b/test/plugin/scenarios/caffeine-3.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/caffeine-3.x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/caffeine-3.x-scenario/config/expectedData.yaml new file mode 100755 index 0000000000..133ede4755 --- /dev/null +++ b/test/plugin/scenarios/caffeine-3.x-scenario/config/expectedData.yaml @@ -0,0 +1,163 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +segmentItems: +- serviceName: caffeine-3.x-scenario + segmentSize: nq 0 + segments: + - segmentId: not null + spans: + - operationName: Caffeine/put + parentSpanId: 0 + spanId: 1 + spanLayer: Cache + startTime: nq 0 + endTime: nq 0 + componentId: 160 + isError: false + spanType: Local + peer: '' + tags: + - { key: cache.type, value: Caffeine } + - { key: cache.cmd, value: put } + - { key: cache.op, value: write } + - { key: cache.key, value: "1" } + skipAnalysis: 'false' + - operationName: Caffeine/putAll + parentSpanId: 0 + spanId: 2 + spanLayer: Cache + startTime: nq 0 + endTime: nq 0 + componentId: 160 + isError: false + spanType: Local + peer: '' + tags: + - { key: cache.type, value: Caffeine } + - { key: cache.cmd, value: putAll } + - { key: cache.op, value: write } + - { key: cache.key, value: "2" } + skipAnalysis: 'false' + - operationName: Caffeine/computeIfAbsent + parentSpanId: 0 + spanId: 3 + spanLayer: Cache + startTime: nq 0 + endTime: nq 0 + componentId: 160 + isError: false + spanType: Local + peer: '' + tags: + - { key: cache.type, value: Caffeine } + - { key: cache.cmd, value: computeIfAbsent } + - { key: cache.op, value: read } + - { key: cache.key, value: "1" } + - operationName: Caffeine/getIfPresent + parentSpanId: 0 + spanId: 4 + spanLayer: Cache + startTime: nq 0 + endTime: nq 0 + componentId: 160 + isError: false + spanType: Local + peer: '' + tags: + - { key: cache.type, value: Caffeine } + - { key: cache.cmd, value: getIfPresent } + - { key: cache.op, value: read } + - { key: cache.key, value: "1" } + skipAnalysis: 'false' + - operationName: Caffeine/getAllPresent + parentSpanId: 0 + spanId: 5 + spanLayer: Cache + startTime: nq 0 + endTime: nq 0 + componentId: 160 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + tags: + - { key: cache.type, value: Caffeine } + - { key: cache.cmd, value: getAllPresent } + - { key: cache.op, value: read } + - { key: cache.key, value: '2' } + - operationName: Caffeine/remove + parentSpanId: 0 + spanId: 6 + spanLayer: Cache + startTime: nq 0 + endTime: nq 0 + componentId: 160 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + tags: + - { key: cache.type, value: Caffeine } + - { key: cache.cmd, value: remove } + - { key: cache.op, value: write } + - { key: cache.key, value: '1' } + - operationName: Caffeine/remove + parentSpanId: 0 + spanId: 7 + spanLayer: Cache + startTime: nq 0 + endTime: nq 0 + componentId: 160 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + tags: + - { key: cache.type, value: Caffeine } + - { key: cache.cmd, value: remove } + - { key: cache.op, value: write } + - { key: cache.key, value: '2' } + - operationName: Caffeine/clear + parentSpanId: 0 + spanId: 8 + spanLayer: Cache + startTime: nq 0 + endTime: nq 0 + componentId: 160 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + tags: + - { key: cache.type, value: Caffeine } + - { key: cache.cmd, value: clear } + - { key: cache.op, value: write } + - operationName: GET:/caffeine-3.x-scenario/case/caffeine + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - { key: url, value: 'http://localhost:8080/caffeine-3.x-scenario/case/caffeine' } + - { key: http.method, value: GET } + - { key: http.status_code, value: '200' } + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/configuration.yml b/test/plugin/scenarios/caffeine-3.x-scenario/configuration.yml new file mode 100755 index 0000000000..00208eefa9 --- /dev/null +++ b/test/plugin/scenarios/caffeine-3.x-scenario/configuration.yml @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/caffeine-3.x-scenario/case/caffeine +healthCheck: http://localhost:8080/caffeine-3.x-scenario/case/healthCheck +startScript: ./bin/startup.sh +runningMode: with_optional +withPlugins: apm-caffeine-3.x-plugin-*.jar \ No newline at end of file diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/pom.xml b/test/plugin/scenarios/caffeine-3.x-scenario/pom.xml new file mode 100755 index 0000000000..a627109082 --- /dev/null +++ b/test/plugin/scenarios/caffeine-3.x-scenario/pom.xml @@ -0,0 +1,111 @@ + + + + + org.apache.skywalking.apm.testcase + caffeine-3.x-scenario + 1.0.0 + jar + + 4.0.0 + + + 17 + UTF-8 + 3.8.1 + 6.0.2 + ${test.framework.version} + 3.0.0 + 3.1.8 + + + skywalking-caffeine-3.x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + com.github.ben-manes.caffeine + caffeine + ${caffeine.version} + + + + + caffeine-3.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/assembly/assembly.xml new file mode 100755 index 0000000000..382a68db61 --- /dev/null +++ b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/caffeine-3.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/Application.java b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/Application.java new file mode 100644 index 0000000000..a4739cfbfe --- /dev/null +++ b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/Application.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.caffeine; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/controller/CaffeineController.java b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/controller/CaffeineController.java new file mode 100644 index 0000000000..f8bad1d288 --- /dev/null +++ b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/controller/CaffeineController.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.caffeine.controller; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import java.util.HashMap; +import java.util.Map; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class CaffeineController { + + Cache caffeine = Caffeine.newBuilder().build(); + + @GetMapping("/case/caffeine") + public void testCase() { + try { + Map data = new HashMap<>(); + data.put("2", "value-2"); + + // put operations + caffeine.put("1", "value-1"); + caffeine.putAll(data); + + // get operations + caffeine.get("1", o -> "default"); + caffeine.getIfPresent("1"); + caffeine.getAllPresent(data.keySet()); + + // delete operations + caffeine.invalidate("1"); + caffeine.invalidateAll(data.keySet()); + caffeine.invalidateAll(); + } catch (Exception e) { + throw e; + } + } +} diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/controller/HeartbeatController.java b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/controller/HeartbeatController.java new file mode 100644 index 0000000000..9dbc01dc77 --- /dev/null +++ b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/controller/HeartbeatController.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.caffeine.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HeartbeatController { + + @GetMapping("/case/healthCheck") + public String healthCheck() { + return "success"; + } +} diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/resources/application.yaml new file mode 100755 index 0000000000..ac1fb9a734 --- /dev/null +++ b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/resources/application.yaml @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server: + port: 8080 + servlet: + context-path: /caffeine-3.x-scenario \ No newline at end of file diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/support-version.list b/test/plugin/scenarios/caffeine-3.x-scenario/support-version.list old mode 100644 new mode 100755 similarity index 98% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/support-version.list rename to test/plugin/scenarios/caffeine-3.x-scenario/support-version.list index 9798fc44d8..0c44c26905 --- a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/support-version.list +++ b/test/plugin/scenarios/caffeine-3.x-scenario/support-version.list @@ -14,5 +14,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -2.15.6 -2.14.2 \ No newline at end of file +3.0.0 +3.1.8 \ No newline at end of file diff --git a/test/plugin/scenarios/clickhouse-0.3.1-scenario/config/expectedData.yaml b/test/plugin/scenarios/clickhouse-0.3.1-scenario/config/expectedData.yaml index 13111717cb..3702b46778 100644 --- a/test/plugin/scenarios/clickhouse-0.3.1-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/clickhouse-0.3.1-scenario/config/expectedData.yaml @@ -53,7 +53,6 @@ segmentItems: - {key: db.type, value: ClickHouse} - {key: db.instance, value: system} - {key: db.statement, value: 'select timezone(), version()'} - - {key: http.status_code, value: '200'} - operationName: ClickHouse/JDBC/Statement/executeQuery operationId: 0 parentSpanId: 0 @@ -70,7 +69,6 @@ segmentItems: - {key: db.type, value: ClickHouse} - {key: db.instance, value: system} - {key: db.statement, value: SELECT * FROM clusters} - - {key: http.status_code, value: '200'} - operationName: ClickHouse/JDBC/Statement/execute operationId: 0 parentSpanId: 0 @@ -87,7 +85,6 @@ segmentItems: - {key: db.type, value: ClickHouse} - {key: db.instance, value: system} - {key: db.statement, value: SELECT 1} - - {key: http.status_code, value: '200'} - operationName: ClickHouse/JDBC/Connection/close operationId: 0 parentSpanId: 0 @@ -120,7 +117,6 @@ segmentItems: - {key: db.type, value: ClickHouse} - {key: db.instance, value: system} - {key: db.statement, value: 'select timezone(), version()'} - - {key: http.status_code, value: '200'} - operationName: ClickHouse/JDBC/PreparedStatement/executeQuery operationId: 0 parentSpanId: 0 @@ -137,7 +133,6 @@ segmentItems: - {key: db.type, value: ClickHouse} - {key: db.instance, value: system} - {key: db.statement, value: SELECT * FROM clusters} - - {key: http.status_code, value: '200'} - operationName: ClickHouse/JDBC/Statement/execute operationId: 0 parentSpanId: 0 @@ -154,7 +149,6 @@ segmentItems: - {key: db.type, value: ClickHouse} - {key: db.instance, value: system} - {key: db.statement, value: SELECT 1} - - {key: http.status_code, value: '200'} - operationName: ClickHouse/JDBC/Connection/close operationId: 0 parentSpanId: 0 @@ -187,7 +181,6 @@ segmentItems: - {key: db.type, value: ClickHouse} - {key: db.instance, value: system} - {key: db.statement, value: 'select timezone(), version()'} - - {key: http.status_code, value: '200'} - operationName: ClickHouse/JDBC/PreparedStatement/executeQuery operationId: 0 parentSpanId: 0 @@ -205,7 +198,6 @@ segmentItems: - {key: db.instance, value: system} - {key: db.statement, value: 'SELECT * FROM clusters WHERE cluster = ?'} - {key: db.sql.parameters, value: '[1]'} - - {key: http.status_code, value: '200'} - operationName: ClickHouse/JDBC/Statement/execute operationId: 0 parentSpanId: 0 @@ -222,7 +214,6 @@ segmentItems: - {key: db.type, value: ClickHouse} - {key: db.instance, value: system} - {key: db.statement, value: SELECT 1} - - {key: http.status_code, value: '200'} - operationName: ClickHouse/JDBC/Connection/close operationId: 0 parentSpanId: 0 diff --git a/test/plugin/scenarios/clickhouse-0.3.1-scenario/configuration.yml b/test/plugin/scenarios/clickhouse-0.3.1-scenario/configuration.yml index d98a13101c..3cfee72d53 100644 --- a/test/plugin/scenarios/clickhouse-0.3.1-scenario/configuration.yml +++ b/test/plugin/scenarios/clickhouse-0.3.1-scenario/configuration.yml @@ -20,6 +20,7 @@ healthCheck: http://localhost:8080/clickhouse-scenario/case/healthCheck startScript: ./bin/startup.sh environment: - SW_JDBC_TRACE_SQL_PARAMETERS=true + - SW_PLUGIN_HTTPCLIENT_PROPAGATION_EXCLUDE_PORTS=8123 depends_on: - clickhouse-server dependencies: diff --git a/test/plugin/scenarios/dbcp-2.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/dbcp-2.x-scenario/config/expectedData.yaml index 7e21e55dd6..4e3cb7c03a 100755 --- a/test/plugin/scenarios/dbcp-2.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/dbcp-2.x-scenario/config/expectedData.yaml @@ -224,7 +224,7 @@ segmentItems: - {key: http.status_code, value: '200'} meterItems: - serviceName: dbcp-2.x-scenario - meterSize: 12 + meterSize: ge 12 meters: - meterId: name: datasource diff --git a/test/plugin/scenarios/druid-1.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/druid-1.x-scenario/config/expectedData.yaml index 79b64a8ab5..fb32950056 100644 --- a/test/plugin/scenarios/druid-1.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/druid-1.x-scenario/config/expectedData.yaml @@ -206,7 +206,7 @@ segmentItems: - {key: http.status_code, value: '200'} meterItems: - serviceName: druid-1.x-scenario - meterSize: 14 + meterSize: ge 13 meters: - meterId: name: datasource @@ -220,12 +220,6 @@ meterItems: - {name: name, value: test_mysql-server:3306} - {name: status, value: poolingCount} singleValue: ge 0 - - meterId: - name: datasource - tags: - - {name: name, value: test_mysql-server:3306} - - {name: status, value: idleCount} - singleValue: ge 0 - meterId: name: datasource tags: diff --git a/test/plugin/scenarios/elasticsearch-7.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/elasticsearch-7.x-scenario/config/expectedData.yaml index af47b1b27e..33f33e7573 100644 --- a/test/plugin/scenarios/elasticsearch-7.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/elasticsearch-7.x-scenario/config/expectedData.yaml @@ -15,7 +15,7 @@ # limitations under the License. segmentItems: - serviceName: elasticsearch-7.x-scenario - segmentSize: ge 1 + segmentSize: ge 10 segments: - segmentId: not null spans: @@ -23,303 +23,650 @@ segmentItems: parentSpanId: 0 spanId: 1 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 77 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - skipAnalysis: 'false' + - {key: db.type, value: Elasticsearch} + - operationName: HEAD:/elasticsearch-case/case/healthCheck + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 1 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/elasticsearch-case/case/healthCheck'} + - {key: http.method, value: HEAD} + - {key: http.status_code, value: '200'} + - segmentId: not null + spans: + - operationName: Elasticsearch/CreateRequest/onResponse + parentSpanId: -1 + spanId: 0 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: transmission.latency, value: not null} + refs: + - {parentEndpoint: 'GET:/elasticsearch-case/case/elasticsearch', networkAddress: '', + refType: CrossThread, parentSpanId: 5, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: elasticsearch-7.x-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: Elasticsearch/IndexRequest/onResponse + parentSpanId: -1 + spanId: 0 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: transmission.latency, value: not null} + refs: + - {parentEndpoint: 'GET:/elasticsearch-case/case/elasticsearch', networkAddress: '', + refType: CrossThread, parentSpanId: 8, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: elasticsearch-7.x-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: Elasticsearch/RefreshRequest/onResponse + parentSpanId: -1 + spanId: 0 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: transmission.latency, value: not null} + refs: + - {parentEndpoint: 'GET:/elasticsearch-case/case/elasticsearch', networkAddress: '', + refType: CrossThread, parentSpanId: 9, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: elasticsearch-7.x-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: Elasticsearch/GetRequest/onResponse + parentSpanId: -1 + spanId: 0 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: transmission.latency, value: not null} + refs: + - {parentEndpoint: 'GET:/elasticsearch-case/case/elasticsearch', networkAddress: '', + refType: CrossThread, parentSpanId: 13, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: elasticsearch-7.x-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: Elasticsearch/SearchRequest/onResponse + parentSpanId: -1 + spanId: 0 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: transmission.latency, value: not null} + refs: + - {parentEndpoint: 'GET:/elasticsearch-case/case/elasticsearch', networkAddress: '', + refType: CrossThread, parentSpanId: 15, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: elasticsearch-7.x-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: Elasticsearch/UpdateRequest/onResponse + parentSpanId: -1 + spanId: 0 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: transmission.latency, value: not null} + refs: + - {parentEndpoint: 'GET:/elasticsearch-case/case/elasticsearch', networkAddress: '', + refType: CrossThread, parentSpanId: 17, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: elasticsearch-7.x-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: Elasticsearch/AnalyzeRequest/onResponse + parentSpanId: -1 + spanId: 0 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: transmission.latency, value: not null} + refs: + - {parentEndpoint: 'GET:/elasticsearch-case/case/elasticsearch', networkAddress: '', + refType: CrossThread, parentSpanId: 19, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: elasticsearch-7.x-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: Elasticsearch/DeleteRequest/onResponse + parentSpanId: -1 + spanId: 0 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: transmission.latency, value: not null} + refs: + - {parentEndpoint: 'GET:/elasticsearch-case/case/elasticsearch', networkAddress: '', + refType: CrossThread, parentSpanId: 21, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: elasticsearch-7.x-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: Elasticsearch/Health + parentSpanId: 0 + spanId: 1 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} - operationName: Elasticsearch/GetSettings parentSpanId: 0 spanId: 2 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 77 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - skipAnalysis: 'false' + - {key: db.type, value: Elasticsearch} - operationName: Elasticsearch/PutSettings parentSpanId: 0 spanId: 3 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 77 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.statement, value: not null } - skipAnalysis: 'false' + - {key: db.type, value: Elasticsearch} + - {key: db.statement, value: not null} - operationName: Elasticsearch/CreateRequest parentSpanId: 0 spanId: 4 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 77 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - skipAnalysis: 'false' - - operationName: Elasticsearch/IndexRequest + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - operationName: Elasticsearch/CreateRequest parentSpanId: 0 spanId: 5 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 77 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - - { key: db.statement, value: not null } - skipAnalysis: 'false' - - operationName: Elasticsearch/RefreshRequest + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - operationName: Elasticsearch/IndexRequest parentSpanId: 0 spanId: 6 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 77 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - skipAnalysis: 'false' - - operationName: Elasticsearch/GetRequest + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/RefreshRequest parentSpanId: 0 spanId: 7 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 77 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - - { key: db.statement, value: not null } - skipAnalysis: 'false' - - operationName: Elasticsearch/SearchRequest + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - operationName: Elasticsearch/IndexRequest parentSpanId: 0 spanId: 8 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 77 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - - { key: db.statement, value: not null } - skipAnalysis: 'false' - - operationName: Elasticsearch/UpdateRequest + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/RefreshRequest parentSpanId: 0 spanId: 9 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 77 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - - { key: db.statement, value: not null } - skipAnalysis: 'false' - - operationName: Elasticsearch/AnalyzeRequest + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - operationName: Elasticsearch/RefreshRequest parentSpanId: 0 spanId: 10 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 77 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: analyzer, value: not null } - - { key: db.statement, value: not null } - skipAnalysis: 'false' - - operationName: Elasticsearch/DeleteRequest + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - operationName: Elasticsearch/RefreshRequest parentSpanId: 0 spanId: 11 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 77 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - skipAnalysis: 'false' - - operationName: Elasticsearch/IndexRequest + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - operationName: Elasticsearch/GetRequest parentSpanId: 0 spanId: 12 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/GetRequest + parentSpanId: 0 + spanId: 13 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/SearchRequest + parentSpanId: 0 + spanId: 14 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/SearchRequest + parentSpanId: 0 + spanId: 15 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/UpdateRequest + parentSpanId: 0 + spanId: 16 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/UpdateRequest + parentSpanId: 0 + spanId: 17 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/AnalyzeRequest + parentSpanId: 0 + spanId: 18 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: analyzer, value: not null} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/AnalyzeRequest + parentSpanId: 0 + spanId: 19 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: analyzer, value: not null} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/DeleteRequest + parentSpanId: 0 + spanId: 20 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - operationName: Elasticsearch/DeleteRequest + parentSpanId: 0 + spanId: 21 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - operationName: Elasticsearch/IndexRequest + parentSpanId: 0 + spanId: 22 + spanLayer: Database + startTime: not null + endTime: not null componentId: 48 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - - { key: node.address, value: not null } - - { key: es.indices, value: not null } - - { key: es.types, value: not null } - - { key: db.statement, value: not null } - skipAnalysis: 'false' + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: node.address, value: not null} + - {key: es.indices, value: not null} + - {key: es.types, value: not null} + - {key: db.statement, value: not null} - operationName: Elasticsearch/actionGet parentSpanId: 0 - spanId: 13 - startTime: nq 0 - endTime: nq 0 + spanId: 23 + spanLayer: Unknown + startTime: not null + endTime: not null componentId: 48 isError: false spanType: Local + peer: '' + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.statement, value: not null } - skipAnalysis: 'false' + - {key: db.type, value: Elasticsearch} + - {key: db.statement, value: not null} - operationName: Elasticsearch/GetRequest parentSpanId: 0 - spanId: 14 + spanId: 24 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 48 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - - { key: node.address, value: not null } - - { key: es.indices, value: not null } - - { key: es.types, value: not null } - - { key: db.statement, value: not null } - skipAnalysis: 'false' + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: node.address, value: not null} + - {key: es.indices, value: not null} + - {key: es.types, value: not null} + - {key: db.statement, value: not null} - operationName: Elasticsearch/SearchRequest parentSpanId: 0 - spanId: 15 + spanId: 25 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 48 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - - { key: node.address, value: not null } - - { key: es.indices, value: not null } - - { key: es.types, value: not null } - - { key: db.statement, value: not null } - skipAnalysis: 'false' + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: node.address, value: not null} + - {key: es.indices, value: not null} + - {key: es.types, value: not null} + - {key: db.statement, value: not null} - operationName: Elasticsearch/actionGet parentSpanId: 0 - spanId: 16 - startTime: nq 0 - endTime: nq 0 + spanId: 26 + spanLayer: Unknown + startTime: not null + endTime: not null componentId: 48 isError: false spanType: Local + peer: '' + skipAnalysis: false tags: - {key: db.type, value: Elasticsearch} - - {key: es.took_millis, value: not null } - - {key: es.total_hits, value: not null } - - {key: db.statement, value: not null } - skipAnalysis: 'false' + - {key: es.took_millis, value: not null} + - {key: es.total_hits, value: not null} + - {key: db.statement, value: not null} - operationName: Elasticsearch/UpdateRequest parentSpanId: 0 - spanId: 17 + spanId: 27 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 48 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - - { key: node.address, value: not null } - - { key: es.indices, value: not null } - - { key: es.types, value: not null } - - { key: db.statement, value: not null } - skipAnalysis: 'false' + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: node.address, value: not null} + - {key: es.indices, value: not null} + - {key: es.types, value: not null} + - {key: db.statement, value: not null} - operationName: Elasticsearch/DeleteRequest parentSpanId: 0 - spanId: 18 + spanId: 28 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 48 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - - { key: node.address, value: not null } - - { key: es.indices, value: not null } - - { key: es.types, value: not null } - - { key: db.statement, value: not null } - skipAnalysis: 'false' + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: node.address, value: not null} + - {key: es.indices, value: not null} + - {key: es.types, value: not null} + - {key: db.statement, value: not null} - operationName: Elasticsearch/DeleteIndexRequest parentSpanId: 0 - spanId: 19 + spanId: 29 spanLayer: Database - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 48 isError: false spanType: Exit peer: not null + skipAnalysis: false tags: - - { key: db.type, value: Elasticsearch } - - { key: db.instance, value: not null } - - { key: node.address, value: not null } - - { key: es.indices, value: not null } - skipAnalysis: 'false' + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: not null} + - {key: node.address, value: not null} + - {key: es.indices, value: not null} - operationName: GET:/elasticsearch-case/case/elasticsearch parentSpanId: -1 spanId: 0 spanLayer: Http - startTime: nq 0 - endTime: nq 0 + startTime: not null + endTime: not null componentId: 1 isError: false spanType: Entry peer: '' + skipAnalysis: false tags: - - { key: url, value: 'http://localhost:8080/elasticsearch-case/case/elasticsearch' } - - { key: http.method, value: GET } + - {key: url, value: 'http://localhost:8080/elasticsearch-case/case/elasticsearch'} + - {key: http.method, value: GET} - {key: http.status_code, value: '200'} - skipAnalysis: 'false' diff --git a/test/plugin/scenarios/elasticsearch-7.x-scenario/configuration.yml b/test/plugin/scenarios/elasticsearch-7.x-scenario/configuration.yml index 853e67498e..aa43501041 100644 --- a/test/plugin/scenarios/elasticsearch-7.x-scenario/configuration.yml +++ b/test/plugin/scenarios/elasticsearch-7.x-scenario/configuration.yml @@ -22,7 +22,7 @@ environment: - elasticsearch.server=elasticsearch-server-7.x:9200 dependencies: elasticsearch-server-7.x: - image: elasticsearch:${CASE_SERVER_IMAGE_VERSION} + image: docker.elastic.co/elasticsearch/elasticsearch:${CASE_SERVER_IMAGE_VERSION} hostname: elasticsearch-server-7.x removeOnExit: true expose: @@ -30,6 +30,5 @@ dependencies: environment: - cluster.name=docker-node - xpack.security.enabled=false - - bootstrap.memory_lock=true - - "ES_JAVA_OPTS=-Xms256m -Xmx256m" + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - discovery.type=single-node diff --git a/test/plugin/scenarios/elasticsearch-7.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/RestHighLevelClientCase.java b/test/plugin/scenarios/elasticsearch-7.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/RestHighLevelClientCase.java index ce44956346..a0314fae32 100644 --- a/test/plugin/scenarios/elasticsearch-7.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/RestHighLevelClientCase.java +++ b/test/plugin/scenarios/elasticsearch-7.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/RestHighLevelClientCase.java @@ -22,9 +22,11 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CountDownLatch; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.skywalking.apm.testcase.elasticsearch.controller.CaseController; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsRequest; @@ -33,6 +35,7 @@ import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; +import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.index.IndexRequest; @@ -85,6 +88,7 @@ public String healthCheck() throws Exception { public String elasticsearch() throws Exception { String indexName = UUID.randomUUID().toString(); + String indexName2 = UUID.randomUUID().toString(); try { // health health(); @@ -97,27 +101,35 @@ public String elasticsearch() throws Exception { // create createIndex(indexName); + createIndexAsync(indexName2); // index index(indexName); + indexAsync(indexName2); // refresh client.indices().refresh(new RefreshRequest(indexName), RequestOptions.DEFAULT); + client.indices().refresh(new RefreshRequest(indexName2), RequestOptions.DEFAULT); // get get(indexName); + getAsync(indexName2); // search search(indexName); + searchAsync(indexName2); // update update(indexName); + updateAsync(indexName2); // analyze analyze(indexName); + analyzeAsync(indexName2); // delete delete(indexName); + deleteAsync(indexName2); } finally { if (null != client) { client.close(); @@ -189,6 +201,41 @@ private void createIndex(String indexName) throws IOException { } } + private void createIndexAsync(String indexName) throws InterruptedException { + CreateIndexRequest request = new CreateIndexRequest(indexName); + Map mapping = new HashMap<>(); + Map mappingProperties = new HashMap<>(); + Map mappingPropertiesAuthor = new HashMap<>(); + mappingPropertiesAuthor.put("type", "keyword"); + mappingProperties.put("author", mappingPropertiesAuthor); + Map mappingPropertiesTitle = new HashMap<>(); + mappingPropertiesTitle.put("type", "keyword"); + mappingProperties.put("title", mappingPropertiesTitle); + mapping.put("properties", mappingProperties); + request.mapping(mapping); + + request.settings(Settings.builder().put("index.number_of_shards", 1).put("index.number_of_replicas", 0)); + + final CountDownLatch latch = new CountDownLatch(1); + client.indices().createAsync(request, RequestOptions.DEFAULT, new ActionListener() { + @Override + public void onResponse(final CreateIndexResponse createIndexResponse) { + latch.countDown(); + if (!createIndexResponse.isAcknowledged()) { + String message = "elasticsearch create index fail."; + LOGGER.error(message); + throw new RuntimeException(message); + } + } + + @Override + public void onFailure(final Exception e) { + latch.countDown(); + } + }); + latch.await(); + } + private void index(String indexName) throws IOException { Map source = new HashMap<>(); source.put("author", "Marker"); @@ -201,6 +248,49 @@ private void index(String indexName) throws IOException { LOGGER.error(message); throw new RuntimeException(message); } + client.indices().refresh(new RefreshRequest(indexName), RequestOptions.DEFAULT); + } + + private void indexAsync(String indexName) throws InterruptedException { + Map source = new HashMap<>(); + source.put("author", "Marker"); + source.put("title", "Java programing."); + IndexRequest indexRequest = new IndexRequest(indexName).id("1").source(source); + + final CountDownLatch latch = new CountDownLatch(2); + client.indexAsync(indexRequest, RequestOptions.DEFAULT, + new ActionListener() { + @Override + public void onResponse(final IndexResponse indexResponse) { + latch.countDown(); + if (indexResponse.status().getStatus() >= 400) { + String message = "elasticsearch index data fail."; + LOGGER.error(message); + throw new RuntimeException(message); + } + } + + @Override + public void onFailure(final Exception e) { + latch.countDown(); + } + } + ); + + client.indices().refreshAsync(new RefreshRequest(indexName), RequestOptions.DEFAULT, + new ActionListener() { + @Override + public void onResponse(final RefreshResponse refreshResponse) { + latch.countDown(); + } + + @Override + public void onFailure(final Exception e) { + latch.countDown(); + } + } + ); + latch.await(); } private void get(String indexName) throws IOException { @@ -214,6 +304,31 @@ private void get(String indexName) throws IOException { } } + private void getAsync(String indexName) throws InterruptedException { + GetRequest getRequest = new GetRequest(indexName, "1"); + final CountDownLatch latch = new CountDownLatch(1); + client.getAsync(getRequest, RequestOptions.DEFAULT, + new ActionListener() { + @Override + public void onResponse(final GetResponse getResponse) { + latch.countDown(); + if (!getResponse.isExists()) { + String message = "elasticsearch get data fail."; + LOGGER.error(message); + throw new RuntimeException(message); + } + } + + @Override + public void onFailure(final Exception e) { + latch.countDown(); + throw new RuntimeException(e); + } + } + ); + latch.await(); + } + private void update(String indexName) throws IOException { UpdateRequest request = new UpdateRequest(indexName, "1"); Map parameters = singletonMap("title", "c++ programing."); @@ -228,6 +343,33 @@ private void update(String indexName) throws IOException { } } + private void updateAsync(String indexName) throws InterruptedException { + UpdateRequest request = new UpdateRequest(indexName, "1"); + Map parameters = singletonMap("title", "c++ programing."); + Script inline = new Script(ScriptType.INLINE, "painless", "ctx._source.title = params.title", parameters); + request.script(inline); + + final CountDownLatch latch = new CountDownLatch(1); + client.updateAsync(request, RequestOptions.DEFAULT, new ActionListener() { + @Override + public void onResponse(final UpdateResponse updateResponse) { + latch.countDown(); + if (updateResponse.getVersion() != 2) { + String message = "elasticsearch update data fail."; + LOGGER.error(message); + throw new RuntimeException(message); + } + } + + @Override + public void onFailure(final Exception e) { + latch.countDown(); + throw new RuntimeException(e); + } + }); + latch.await(); + } + private void analyze(String indexName) throws IOException { AnalyzeRequest analyzeRequest = AnalyzeRequest.withIndexAnalyzer(indexName, null, "SkyWalking"); AnalyzeResponse analyzeResponse = client.indices().analyze(analyzeRequest, RequestOptions.DEFAULT); @@ -238,6 +380,30 @@ private void analyze(String indexName) throws IOException { } } + private void analyzeAsync(String indexName) throws InterruptedException { + AnalyzeRequest analyzeRequest = AnalyzeRequest.withIndexAnalyzer(indexName, null, "SkyWalking"); + final CountDownLatch latch = new CountDownLatch(1); + client.indices().analyzeAsync( + analyzeRequest, RequestOptions.DEFAULT, new ActionListener() { + @Override + public void onResponse(final AnalyzeResponse analyzeResponse) { + latch.countDown(); + if (null == analyzeResponse.getTokens() || analyzeResponse.getTokens().size() < 1) { + String message = "elasticsearch analyze index fail."; + LOGGER.error(message); + throw new RuntimeException(message); + } + } + + @Override + public void onFailure(final Exception e) { + latch.countDown(); + LOGGER.error(e); + } + }); + latch.await(); + } + private void delete(String indexName) throws IOException { DeleteIndexRequest request = new DeleteIndexRequest(indexName); AcknowledgedResponse deleteIndexResponse = client.indices().delete(request, RequestOptions.DEFAULT); @@ -248,6 +414,29 @@ private void delete(String indexName) throws IOException { } } + private void deleteAsync(String indexName) throws InterruptedException { + DeleteIndexRequest request = new DeleteIndexRequest(indexName); + final CountDownLatch latch = new CountDownLatch(1); + client.indices().deleteAsync(request, RequestOptions.DEFAULT, new ActionListener() { + @Override + public void onResponse(final AcknowledgedResponse acknowledgedResponse) { + latch.countDown(); + if (!acknowledgedResponse.isAcknowledged()) { + String message = "elasticsearch delete index fail."; + LOGGER.error(message); + throw new RuntimeException(message); + } + } + + @Override + public void onFailure(final Exception e) { + latch.countDown(); + throw new RuntimeException(e); + } + }); + latch.await(); + } + private void search(String indexName) throws IOException { SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); sourceBuilder.query(QueryBuilders.termQuery("author", "Marker")); @@ -266,4 +455,35 @@ private void search(String indexName) throws IOException { throw new RuntimeException(message); } } + + private void searchAsync(String indexName) throws InterruptedException { + SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); + sourceBuilder.query(QueryBuilders.termQuery("author", "Marker")); + sourceBuilder.from(0); + sourceBuilder.size(10); + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.indices(indexName); + searchRequest.source(sourceBuilder); + final CountDownLatch latch = new CountDownLatch(1); + client.searchAsync(searchRequest, RequestOptions.DEFAULT, new ActionListener() { + @Override + public void onResponse(final SearchResponse searchResponse) { + latch.countDown(); + int length = searchResponse.getHits().getHits().length; + if (!(length > 0)) { + String message = "elasticsearch search data fail."; + LOGGER.error(message); + throw new RuntimeException(message); + } + } + + @Override + public void onFailure(final Exception e) { + latch.countDown(); + LOGGER.error(e); + } + }); + latch.await(); + } } diff --git a/test/plugin/scenarios/elasticsearch-7.x-scenario/support-version.list b/test/plugin/scenarios/elasticsearch-7.x-scenario/support-version.list index 54e1c1de5b..e5b98548c6 100644 --- a/test/plugin/scenarios/elasticsearch-7.x-scenario/support-version.list +++ b/test/plugin/scenarios/elasticsearch-7.x-scenario/support-version.list @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +# 7.0.0 - 7.17.12 have been tested, and 7.0.0 - 7.2.1 not included in the test 7.3.2 7.4.2 7.5.2 @@ -26,5 +27,4 @@ 7.12.1 7.13.4 7.14.2 -7.16.3 -7.17.12 \ No newline at end of file +7.17.21 \ No newline at end of file diff --git a/test/plugin/scenarios/elasticsearch-java-9.x-scenario/bin/startup.sh b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..e054a8dc76 --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} -Dskywalking.plugin.elasticsearch.trace_dsl=true ${home}/../libs/elasticsearch-java-9.x-scenario.jar & diff --git a/test/plugin/scenarios/elasticsearch-java-9.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..b9af4b71cc --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/config/expectedData.yaml @@ -0,0 +1,125 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: elasticsearch-java-9.x-scenario + segmentSize: ge 2 + segments: + - segmentId: not null + spans: + - operationName: Elasticsearch/es/indices.create + parentSpanId: 0 + spanId: 1 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: test-index} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/es/index + parentSpanId: 0 + spanId: 2 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: test-index} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/es/get + parentSpanId: 0 + spanId: 3 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: test-index} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/es/search + parentSpanId: 0 + spanId: 4 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: test-index} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/es/delete + parentSpanId: 0 + spanId: 5 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: test-index} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/es/indices.delete + parentSpanId: 0 + spanId: 6 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: test-index} + - {key: db.statement, value: not null} + - operationName: GET:/elasticsearch-java-case/case/elasticsearch + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + spanLayer: Http + isError: false + spanType: Entry + componentId: 1 + tags: + - {key: url, value: not null} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/elasticsearch-java-9.x-scenario/configuration.yml b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/configuration.yml new file mode 100644 index 0000000000..f660224a0f --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/configuration.yml @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/elasticsearch-java-case/case/elasticsearch +healthCheck: http://localhost:8080/elasticsearch-java-case/case/healthCheck +startScript: ./bin/startup.sh +environment: +- elasticsearch.server=elasticsearch-server:9200 +dependencies: + elasticsearch-server: + image: docker.elastic.co/elasticsearch/elasticsearch:9.0.0 + hostname: elasticsearch-server + removeOnExit: true + expose: + - 9200 + environment: + - cluster.name=docker-node + - xpack.security.enabled=false + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - discovery.type=single-node diff --git a/test/plugin/scenarios/elasticsearch-java-9.x-scenario/pom.xml b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/pom.xml new file mode 100644 index 0000000000..8ca30a4aa0 --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/pom.xml @@ -0,0 +1,138 @@ + + + + + org.apache.skywalking.apm.testcase + elasticsearch-java-9.x-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 17 + 3.8.1 + + 9.0.0 + + 3.0.13 + + + skywalking-elasticsearch-java-9.x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + jul-to-slf4j + org.slf4j + + + + + + co.elastic.clients + elasticsearch-java + ${test.framework.version} + + + + org.elasticsearch.client + elasticsearch-rest-client + ${test.framework.version} + + + com.fasterxml.jackson.core + jackson-databind + + + + + elasticsearch-java-9.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + diff --git a/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..42a1323949 --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/elasticsearch-java-9.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/Application.java b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/Application.java new file mode 100644 index 0000000000..3ce49f987a --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/Application.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.elasticsearch; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; + +@SpringBootApplication(exclude = ElasticsearchClientAutoConfiguration.class) +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/controller/CaseController.java b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/controller/CaseController.java new file mode 100644 index 0000000000..3f2be16239 --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/controller/CaseController.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.elasticsearch.controller; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.core.IndexResponse; +import co.elastic.clients.elasticsearch.core.GetResponse; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.DeleteResponse; +import co.elastic.clients.elasticsearch.indices.CreateIndexResponse; +import co.elastic.clients.elasticsearch.indices.DeleteIndexResponse; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/elasticsearch-java-case/case") +public class CaseController { + + @Value("${elasticsearch.server}") + private String elasticsearchServer; + + @GetMapping("/healthCheck") + public String healthCheck() throws IOException { + RestClient restClient = RestClient.builder(HttpHost.create(elasticsearchServer)).build(); + RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); + ElasticsearchClient client = new ElasticsearchClient(transport); + try { + client.info(); + return "Success"; + } finally { + restClient.close(); + } + } + + @GetMapping("/elasticsearch") + public String elasticsearch() throws IOException { + RestClient restClient = RestClient.builder(HttpHost.create(elasticsearchServer)).build(); + RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); + ElasticsearchClient client = new ElasticsearchClient(transport); + try { + + // Create index + CreateIndexResponse createResp = client.indices().create(c -> c.index("test-index")); + + // Index document + Map doc = new HashMap<>(); + doc.put("name", "test"); + doc.put("value", "skywalking"); + IndexResponse indexResp = client.index(i -> i.index("test-index").id("1").document(doc)); + + // Get document + GetResponse getResp = client.get(g -> g.index("test-index").id("1"), Map.class); + + // Search + SearchResponse searchResp = client.search(s -> s + .index("test-index") + .query(q -> q.match(m -> m.field("name").query("test"))), Map.class); + + // Delete document + DeleteResponse deleteResp = client.delete(d -> d.index("test-index").id("1")); + + // Delete index + DeleteIndexResponse deleteIndexResp = client.indices().delete(d -> d.index("test-index")); + + return "Success"; + } finally { + restClient.close(); + } + } +} diff --git a/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000..ba18cc1c03 --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/resources/application.yaml @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server: + port: 8080 +logging: + config: classpath:log4j2.xml \ No newline at end of file diff --git a/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..9849ed5a8a --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/elasticsearch-java-9.x-scenario/support-version.list b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/support-version.list new file mode 100644 index 0000000000..2f8cf94b33 --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-9.x-scenario/support-version.list @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# co.elastic.clients:elasticsearch-java 9.x (requires JDK 17) +# Versions must match elasticsearch-rest-client available versions +9.0.8 +9.1.10 +9.2.8 +9.3.3 diff --git a/test/plugin/scenarios/elasticsearch-java-scenario/bin/startup.sh b/test/plugin/scenarios/elasticsearch-java-scenario/bin/startup.sh new file mode 100644 index 0000000000..eb5ce9db9e --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} -Dskywalking.plugin.elasticsearch.trace_dsl=true ${home}/../libs/elasticsearch-java-scenario.jar & diff --git a/test/plugin/scenarios/elasticsearch-java-scenario/config/expectedData.yaml b/test/plugin/scenarios/elasticsearch-java-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..0d79dbce45 --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-scenario/config/expectedData.yaml @@ -0,0 +1,125 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: elasticsearch-java-scenario + segmentSize: ge 2 + segments: + - segmentId: not null + spans: + - operationName: Elasticsearch/es/indices.create + parentSpanId: 0 + spanId: 1 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: test-index} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/es/index + parentSpanId: 0 + spanId: 2 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: test-index} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/es/get + parentSpanId: 0 + spanId: 3 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: test-index} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/es/search + parentSpanId: 0 + spanId: 4 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: test-index} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/es/delete + parentSpanId: 0 + spanId: 5 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: test-index} + - {key: db.statement, value: not null} + - operationName: Elasticsearch/es/indices.delete + parentSpanId: 0 + spanId: 6 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 77 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: db.type, value: Elasticsearch} + - {key: db.instance, value: test-index} + - {key: db.statement, value: not null} + - operationName: GET:/elasticsearch-java-case/case/elasticsearch + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + spanLayer: Http + isError: false + spanType: Entry + componentId: 1 + tags: + - {key: url, value: not null} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/elasticsearch-java-scenario/configuration.yml b/test/plugin/scenarios/elasticsearch-java-scenario/configuration.yml new file mode 100644 index 0000000000..a6b201e828 --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-scenario/configuration.yml @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/elasticsearch-java-case/case/elasticsearch +healthCheck: http://localhost:8080/elasticsearch-java-case/case/healthCheck +startScript: ./bin/startup.sh +environment: +- elasticsearch.server=elasticsearch-server:9200 +dependencies: + elasticsearch-server: + image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0 + hostname: elasticsearch-server + removeOnExit: true + expose: + - 9200 + environment: + - cluster.name=docker-node + - xpack.security.enabled=false + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - discovery.type=single-node diff --git a/test/plugin/scenarios/elasticsearch-java-scenario/pom.xml b/test/plugin/scenarios/elasticsearch-java-scenario/pom.xml new file mode 100644 index 0000000000..275b963cd2 --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-scenario/pom.xml @@ -0,0 +1,138 @@ + + + + + org.apache.skywalking.apm.testcase + elasticsearch-java-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 1.8 + 3.8.1 + + 8.12.0 + + 2.1.6.RELEASE + + + skywalking-elasticsearch-java-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + jul-to-slf4j + org.slf4j + + + + + + co.elastic.clients + elasticsearch-java + ${test.framework.version} + + + + org.elasticsearch.client + elasticsearch-rest-client + ${test.framework.version} + + + com.fasterxml.jackson.core + jackson-databind + + + + + elasticsearch-java-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + diff --git a/test/plugin/scenarios/elasticsearch-java-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/elasticsearch-java-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..20d778c34c --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/elasticsearch-java-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/elasticsearch-java-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/Application.java b/test/plugin/scenarios/elasticsearch-java-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/Application.java new file mode 100644 index 0000000000..2cf2b3e563 --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/Application.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.elasticsearch; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/elasticsearch-java-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/controller/CaseController.java b/test/plugin/scenarios/elasticsearch-java-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/controller/CaseController.java new file mode 100644 index 0000000000..66180bfcd9 --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-scenario/src/main/java/org/apache/skywalking/apm/testcase/elasticsearch/controller/CaseController.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.elasticsearch.controller; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.core.IndexResponse; +import co.elastic.clients.elasticsearch.core.GetResponse; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.DeleteResponse; +import co.elastic.clients.elasticsearch.indices.CreateIndexResponse; +import co.elastic.clients.elasticsearch.indices.DeleteIndexResponse; +import co.elastic.clients.elasticsearch._types.FieldValue; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/elasticsearch-java-case/case") +public class CaseController { + + @Value("${elasticsearch.server}") + private String elasticsearchServer; + + @GetMapping("/healthCheck") + public String healthCheck() throws IOException { + RestClient restClient = RestClient.builder(HttpHost.create(elasticsearchServer)).build(); + RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); + ElasticsearchClient client = new ElasticsearchClient(transport); + try { + client.info(); + return "Success"; + } finally { + restClient.close(); + } + } + + @GetMapping("/elasticsearch") + public String elasticsearch() throws IOException { + RestClient restClient = RestClient.builder(HttpHost.create(elasticsearchServer)).build(); + RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); + ElasticsearchClient client = new ElasticsearchClient(transport); + try { + + // Create index + CreateIndexResponse createResp = client.indices().create(c -> c.index("test-index")); + + // Index document + Map doc = new HashMap<>(); + doc.put("name", "test"); + doc.put("value", "skywalking"); + IndexResponse indexResp = client.index(i -> i.index("test-index").id("1").document(doc)); + + // Get document + GetResponse getResp = client.get(g -> g.index("test-index").id("1"), Map.class); + + // Search + SearchResponse searchResp = client.search(s -> s + .index("test-index") + .query(q -> q.match(m -> m.field("name").query(FieldValue.of("test")))), Map.class); + + // Delete document + DeleteResponse deleteResp = client.delete(d -> d.index("test-index").id("1")); + + // Delete index + DeleteIndexResponse deleteIndexResp = client.indices().delete(d -> d.index("test-index")); + + return "Success"; + } finally { + restClient.close(); + } + } +} diff --git a/test/plugin/scenarios/elasticsearch-java-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/elasticsearch-java-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000..ba18cc1c03 --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-scenario/src/main/resources/application.yaml @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server: + port: 8080 +logging: + config: classpath:log4j2.xml \ No newline at end of file diff --git a/test/plugin/scenarios/elasticsearch-java-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/elasticsearch-java-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..9849ed5a8a --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/elasticsearch-java-scenario/support-version.list b/test/plugin/scenarios/elasticsearch-java-scenario/support-version.list new file mode 100644 index 0000000000..114e16d1bf --- /dev/null +++ b/test/plugin/scenarios/elasticsearch-java-scenario/support-version.list @@ -0,0 +1,42 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# co.elastic.clients:elasticsearch-java +# ES server fixed at 8.17.0 (backward compatible with 7.x/8.x clients) +# 7.16+: co.elastic.clients.transport.rest_client.RestClientTransport +# 8.x: ElasticsearchTransportBase.performRequest +7.16.3 +7.17.29 +8.0.1 +8.1.3 +8.2.3 +8.3.3 +8.4.3 +8.5.3 +8.6.2 +8.7.1 +8.8.2 +8.9.2 +8.10.4 +8.11.4 +8.12.2 +8.13.4 +8.14.3 +8.15.5 +8.16.6 +8.17.10 +8.18.8 +8.19.14 diff --git a/test/plugin/scenarios/elasticsearch-rest-high-level-6.x-scenario/configuration.yml b/test/plugin/scenarios/elasticsearch-rest-high-level-6.x-scenario/configuration.yml index bab1c5a875..72c856d395 100644 --- a/test/plugin/scenarios/elasticsearch-rest-high-level-6.x-scenario/configuration.yml +++ b/test/plugin/scenarios/elasticsearch-rest-high-level-6.x-scenario/configuration.yml @@ -20,7 +20,7 @@ environment: - elasticsearch.server=elasticsearch-server-6.x:9200 dependencies: elasticsearch-server-6.x: - image: elasticsearch:${CASE_SERVER_IMAGE_VERSION} + image: docker.elastic.co/elasticsearch/elasticsearch:${CASE_SERVER_IMAGE_VERSION} hostname: elasticsearch-server-6.x removeOnExit: true expose: diff --git a/test/plugin/scenarios/elasticsearch-transport-6.x-scenario/configuration.yml b/test/plugin/scenarios/elasticsearch-transport-6.x-scenario/configuration.yml index cafc4a0571..a268b52e4c 100644 --- a/test/plugin/scenarios/elasticsearch-transport-6.x-scenario/configuration.yml +++ b/test/plugin/scenarios/elasticsearch-transport-6.x-scenario/configuration.yml @@ -20,7 +20,7 @@ environment: - elasticsearch.server=elasticsearch-server-6.x:9200 dependencies: elasticsearch-server-6.x: - image: bitnami/elasticsearch:${CASE_SERVER_IMAGE_VERSION} + image: bitnamilegacy/elasticsearch:${CASE_SERVER_IMAGE_VERSION} hostname: elasticsearch-server-6.x removeOnExit: true expose: diff --git a/test/plugin/scenarios/feign-scenario/support-version.list b/test/plugin/scenarios/feign-scenario/support-version.list index 54e90a79ca..60e6621427 100644 --- a/test/plugin/scenarios/feign-scenario/support-version.list +++ b/test/plugin/scenarios/feign-scenario/support-version.list @@ -20,3 +20,8 @@ 9.3.1 9.4.0 9.5.1 +10.12 +11.10 +12.1 +12.5 +13.5 diff --git a/test/plugin/scenarios/gateway-2.0.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-2.0.x-scenario/config/expectedData.yaml index 8373df690b..d20a2486bc 100644 --- a/test/plugin/scenarios/gateway-2.0.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/gateway-2.0.x-scenario/config/expectedData.yaml @@ -34,8 +34,8 @@ segmentItems: - {key: http.method, value: GET} - {key: http.status_code, value: '200'} refs: - - {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070', - refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + - {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' - serviceName: gateway-projectA-scenario @@ -61,8 +61,8 @@ segmentItems: - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -75,8 +75,8 @@ segmentItems: - {key: http.status_code, value: '200'} skipAnalysis: 'false' - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -88,3 +88,17 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/gateway-2.0.x-scenario/gateway-projectA-scenario/pom.xml b/test/plugin/scenarios/gateway-2.0.x-scenario/gateway-projectA-scenario/pom.xml index fc1fb5e6d6..446f5e9f28 100644 --- a/test/plugin/scenarios/gateway-2.0.x-scenario/gateway-projectA-scenario/pom.xml +++ b/test/plugin/scenarios/gateway-2.0.x-scenario/gateway-projectA-scenario/pom.xml @@ -34,6 +34,11 @@ spring-cloud-starter-gateway ${test.framework.version} + + io.projectreactor + reactor-core + 3.1.7.RELEASE + diff --git a/test/plugin/scenarios/gateway-2.0.x-scenario/support-version.list b/test/plugin/scenarios/gateway-2.0.x-scenario/support-version.list index 8e6ebcf1ab..bb0a110c46 100644 --- a/test/plugin/scenarios/gateway-2.0.x-scenario/support-version.list +++ b/test/plugin/scenarios/gateway-2.0.x-scenario/support-version.list @@ -14,4 +14,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -2.0.4.RELEASE \ No newline at end of file +2.0.4.RELEASE +2.0.0.RELEASE \ No newline at end of file diff --git a/test/plugin/scenarios/gateway-2.1.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-2.1.x-scenario/config/expectedData.yaml index c312b744ab..7eecbf2365 100644 --- a/test/plugin/scenarios/gateway-2.1.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/gateway-2.1.x-scenario/config/expectedData.yaml @@ -54,8 +54,8 @@ segmentItems: - {key: http.method, value: GET} - {key: http.status_code, value: '200'} refs: - - {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070', - refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + - {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' - serviceName: gateway-projectA-scenario @@ -76,8 +76,8 @@ segmentItems: - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -95,8 +95,8 @@ segmentItems: - { key: message, value: not null } - { key: stack, value: not null } - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -106,11 +106,23 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null } skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + spanType: Local + refs: + - { parentEndpoint: '/provider/timeout/error', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + skipAnalysis: 'false' - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -123,8 +135,8 @@ segmentItems: - {key: http.status_code, value: '200'} skipAnalysis: 'false' - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -136,6 +148,20 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' - segmentId: not null spans: - operationName: /provider/b/testcase diff --git a/test/plugin/scenarios/gateway-3.x-filter-context-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-3.x-filter-context-scenario/config/expectedData.yaml index 61852cba38..6a052d28fd 100644 --- a/test/plugin/scenarios/gateway-3.x-filter-context-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/gateway-3.x-filter-context-scenario/config/expectedData.yaml @@ -43,8 +43,8 @@ segmentItems: - {key: http.method, value: GET} - {key: http.status_code, value: '200'} refs: - - {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070', - refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + - {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' - serviceName: gateway-projectA-scenario diff --git a/test/plugin/scenarios/gateway-3.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-3.x-scenario/config/expectedData.yaml index 70df54e93f..7a883f6261 100644 --- a/test/plugin/scenarios/gateway-3.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/gateway-3.x-scenario/config/expectedData.yaml @@ -54,8 +54,8 @@ segmentItems: - {key: http.method, value: GET} - {key: http.status_code, value: '200'} refs: - - {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070', - refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + - {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' - serviceName: gateway-projectA-scenario @@ -76,8 +76,8 @@ segmentItems: - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -95,8 +95,8 @@ segmentItems: - { key: message, value: not null } - { key: stack, value: not null} - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -106,11 +106,23 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null } skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + spanType: Local + refs: + - { parentEndpoint: '/provider/timeout/error', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + skipAnalysis: 'false' - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -123,8 +135,8 @@ segmentItems: - {key: http.status_code, value: '200'} skipAnalysis: 'false' - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -136,6 +148,20 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' - segmentId: not null spans: - operationName: /provider/b/testcase diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-4.1.2.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..fe66b460d6 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/config/expectedData.yaml @@ -0,0 +1,155 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: gateway-projectB-scenario + segmentSize: nq 0 + segments: + - segmentId: not null + spans: + - operationName: /provider/timeout/error + parentSpanId: 0 + spanId: 1 + isError: true + spanType: Exit + tags: + - { key: url, value: not null } + - { key: http.method, value: GET } + - { key: http.status_code, value: '500' } + logs: + - logEvent: + - { key: event, value: error } + - { key: error.kind, value: not null } + - { key: message, value: not null } + - {key: stack, value: not null} + - logEvent: + - { key: event, value: error } + - { key: error.kind, value: not null } + - { key: message, value: not null } + - { key: stack, value: not null } + - operationName: GET:/provider/b/testcase + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: not null} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: SpringCloudGateway/send, networkAddress: 'localhost:18070', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' +- serviceName: gateway-projectA-scenario + segmentSize: nq 0 + segments: + - segmentId: not null + spans: + - operationName: /provider/timeout/error + parentSpanId: -1 + spanId: 0 + spanLayer: Http + isError: true + spanType: Entry + tags: + - {key: url, value: 'http://localhost:8080/provider/timeout/error' } + - {key: http.method, value: GET} + - {key: http.status_code, value: '500'} + - segmentId: not null + spans: + - operationName: SpringCloudGateway/sendRequest + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: true + spanType: Exit + peer: 1.2.3.4:18070 + skipAnalysis: false + tags: + - { key: url, value: not null } + logs: + - logEvent: + - { key: event, value: error } + - { key: error.kind, value: not null } + - { key: message, value: not null } + - { key: stack, value: not null} + - operationName: SpringCloudGateway/send + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + spanType: Local + refs: + - { parentEndpoint: 'SpringCloudGateway/GatewayFilter', networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: SpringCloudGateway/sendRequest + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + spanType: Exit + peer: 'localhost:18070' + tags: + - {key: url, value: not null} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: SpringCloudGateway/send + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: 'SpringCloudGateway/GatewayFilter', networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: /provider/b/testcase + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 67 + isError: false + spanType: Entry + peer: not null + tags: + - { key: url, value: 'http://localhost:8080/provider/b/testcase' } + - { key: http.method, value: GET } + - { key: http.status_code, value: '200' } + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/configuration.yml b/test/plugin/scenarios/gateway-4.1.2.x-scenario/configuration.yml new file mode 100644 index 0000000000..a958b8d680 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/configuration.yml @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/provider/b/testcase +healthCheck: http://localhost:8080/provider/b/healthCheck +startScript: ./bin/startup.sh +runningMode: with_optional +withPlugins: apm-spring-cloud-gateway-4.x-plugin-*.jar diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-dist/bin/startup.sh b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-dist/bin/startup.sh new file mode 100644 index 0000000000..0d28675632 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-dist/bin/startup.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} "-Dskywalking.agent.service_name=gateway-projectA-scenario" ${home}/../libs/gateway-projectA-scenario.jar & +sleep 1 + +java -jar ${agent_opts} "-Dskywalking.agent.service_name=gateway-projectB-scenario" ${home}/../libs/gateway-projectB-scenario.jar & diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-dist/pom.xml b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-dist/pom.xml new file mode 100644 index 0000000000..8e7d699cd7 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-dist/pom.xml @@ -0,0 +1,54 @@ + + + + + org.apache.skywalking + gateway-4.1.2.x-scenario + 5.0.0 + + 4.0.0 + + gateway-dist + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ../target/ + + + + + + + diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-dist/src/main/assembly/assembly.xml b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-dist/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..00d04e28e0 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-dist/src/main/assembly/assembly.xml @@ -0,0 +1,46 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ../gateway-projectA-scenario/target/gateway-projectA-scenario.jar + ./libs + 0775 + + + ../gateway-projectB-scenario/target/gateway-projectB-scenario.jar + ./libs + 0775 + + + \ No newline at end of file diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/pom.xml b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/pom.xml new file mode 100644 index 0000000000..ad7b0f6f9b --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/pom.xml @@ -0,0 +1,56 @@ + + + + + org.apache.skywalking + gateway-4.1.2.x-scenario + 5.0.0 + + 4.0.0 + + gateway-projectA-scenario + + + + org.springframework.cloud + spring-cloud-starter-gateway + ${test.framework.version} + + + + + gateway-projectA-scenario + + + org.springframework.boot + spring-boot-maven-plugin + 3.0.0 + + + + repackage + + + + + + + diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/ApiKeyResolver.java b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/ApiKeyResolver.java new file mode 100644 index 0000000000..40517d1d3f --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/ApiKeyResolver.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA; + +import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@Component +public class ApiKeyResolver implements KeyResolver { + + public Mono resolve(ServerWebExchange exchange) { + return Mono.just(exchange.getRequest().getPath().value()); + } +} diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Application.java b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Application.java new file mode 100644 index 0000000000..68eb95b0c6 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Application.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test1Filter.java b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test1Filter.java new file mode 100644 index 0000000000..af891eae20 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test1Filter.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +public class Test1Filter implements GlobalFilter, Ordered { + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest buildRequest = exchange.getRequest().mutate().build(); + return chain.filter(exchange.mutate().request(buildRequest).build()); + } + + @Override + public int getOrder() { + return 0; + } +} diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test2Filter.java b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test2Filter.java new file mode 100644 index 0000000000..d3a5846bd5 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test2Filter.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +public class Test2Filter implements GlobalFilter, Ordered { + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest buildRequest = exchange.getRequest().mutate().build(); + return chain.filter(exchange.mutate().request(buildRequest).build()); + } + + @Override + public int getOrder() { + return 1; + } +} diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/TestFilterConfig.java b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/TestFilterConfig.java new file mode 100644 index 0000000000..29f9627bdf --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/TestFilterConfig.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class TestFilterConfig { + + @Bean + public Test1Filter test1Filter() { + return new Test1Filter(); + } + + @Bean + public Test2Filter test2Filter() { + return new Test2Filter(); + } +} diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/resources/application.yml b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/resources/application.yml new file mode 100644 index 0000000000..7639e7ce8a --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectA-scenario/src/main/resources/application.yml @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +server: + port: 8080 +spring: + cloud: + gateway: + httpclient: + connect-timeout: 2000 + routes: + - id: provider_route + uri: http://localhost:18070 + predicates: + - Path=/provider/b/* + - id: provider_timeout + uri: http://1.2.3.4:18070 + predicates: + - Path=/provider/timeout/* diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectB-scenario/pom.xml b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectB-scenario/pom.xml new file mode 100644 index 0000000000..6669207a94 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectB-scenario/pom.xml @@ -0,0 +1,57 @@ + + + + + gateway-4.1.2.x-scenario + org.apache.skywalking + 5.0.0 + + 4.0.0 + + gateway-projectB-scenario + + + + org.springframework.boot + spring-boot-starter-web + 2.1.0.RELEASE + + + + + gateway-projectB-scenario + + + org.springframework.boot + spring-boot-maven-plugin + 1.5.9.RELEASE + + + + repackage + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/Application.java b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/Application.java new file mode 100644 index 0000000000..51c94655c7 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/Application.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectB; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan(value = {"test.apache.skywalking.apm.testcase.sc.gateway.projectB.controller"}) +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/controller/TestController.java b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/controller/TestController.java new file mode 100644 index 0000000000..923d7fd7ff --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/controller/TestController.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectB.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +@RestController +public class TestController { + + @RequestMapping("/provider/b/testcase") + public String testcase() { + try { + new RestTemplate().getForEntity("http://localhost:8080/provider/timeout/error", String.class); + } catch (Exception e) { + } + return "1"; + } + + @RequestMapping("/provider/b/healthCheck") + public String healthCheck() { + return "Success"; + } +} diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectB-scenario/src/main/resources/application.properties b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectB-scenario/src/main/resources/application.properties new file mode 100644 index 0000000000..cac2c4d5a1 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/gateway-projectB-scenario/src/main/resources/application.properties @@ -0,0 +1,17 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +server.port=18070 \ No newline at end of file diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/pom.xml b/test/plugin/scenarios/gateway-4.1.2.x-scenario/pom.xml new file mode 100644 index 0000000000..41b9e64c15 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/pom.xml @@ -0,0 +1,59 @@ + + + + 4.0.0 + + org.apache.skywalking + gateway-4.1.2.x-scenario + pom + 5.0.0 + + gateway-projectA-scenario + gateway-projectB-scenario + gateway-dist + + + skywalking-gateway-4.1.2.x-scenario + + + UTF-8 + 1.8 + 3.8.1 + 4.1.2 + ${test.framework.version} + + + + gateway-4.1.2.x-scenario + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + + + diff --git a/test/plugin/scenarios/gateway-4.1.2.x-scenario/support-version.list b/test/plugin/scenarios/gateway-4.1.2.x-scenario/support-version.list new file mode 100644 index 0000000000..347dda15d0 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.1.2.x-scenario/support-version.list @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +4.1.2 +4.1.6 +4.2.0 \ No newline at end of file diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-4.3.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..94488a65aa --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/config/expectedData.yaml @@ -0,0 +1,189 @@ + # Licensed to the Apache Software Foundation (ASF) under one + # or more contributor license agreements. See the NOTICE file + # distributed with this work for additional information + # regarding copyright ownership. The ASF licenses this file + # to you 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. + + ignoreSegmentNames: + - HEAD:/provider/b/healthCheck + - /provider/b/healthCheck + + segmentItems: + - serviceName: gateway-projectB-scenario + segmentSize: nq 0 + segments: + - segmentId: not null + spans: + - operationName: GET:/provider/b/testcase + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: nq 0 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: not null} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: not null, + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' + + - serviceName: gateway-projectA-scenario + segmentSize: nq 0 + segments: + # -------------------------------------------------------- + # Case 1: Timeout Error + # -------------------------------------------------------- + # A. Entry Span + - segmentId: not null + spans: + - operationName: /provider/timeout/error + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: nq 0 + isError: true + spanType: Entry + peer: '' + tags: + - {key: url, value: not null } + - {key: http.method, value: GET} + - {key: http.status_code, value: '500'} + skipAnalysis: 'false' + + # B. Exit Span (Contains Logs) + - segmentId: not null + spans: + - operationName: SpringCloudGateway/sendRequest + parentSpanId: 1 + spanId: 2 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: nq 0 + isError: true + spanType: Exit + peer: not null + skipAnalysis: 'false' + tags: + - { key: url, value: not null } + logs: + - logEvent: + - { key: event, value: error } + - { key: error.kind, value: not null } + - { key: message, value: not null } + - { key: stack, value: not null} + - operationName: SpringCloudGateway/RoutingFilter + parentSpanId: 0 + spanId: 1 + startTime: nq 0 + endTime: nq 0 + componentId: nq 0 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + refs: + - { parentEndpoint: '/provider/timeout/error', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: nq 0 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + refs: + - { parentEndpoint: '/provider/timeout/error', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + + # -------------------------------------------------------- + # Case 2: Success Case + # -------------------------------------------------------- + # A. Entry Span + - segmentId: not null + spans: + - operationName: /provider/b/testcase + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: nq 0 + isError: false + spanType: Entry + peer: '' + tags: + - { key: url, value: not null } + - { key: http.method, value: GET } + - { key: http.status_code, value: '200' } + skipAnalysis: 'false' + + # B. Exit Span + - segmentId: not null + spans: + - operationName: SpringCloudGateway/sendRequest + parentSpanId: 1 + spanId: 2 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: nq 0 + isError: false + spanType: Exit + peer: not null + tags: + - {key: url, value: not null} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: SpringCloudGateway/RoutingFilter + parentSpanId: 0 + spanId: 1 + startTime: nq 0 + endTime: nq 0 + componentId: nq 0 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + refs: + - {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: nq 0 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + refs: + - {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/configuration.yml b/test/plugin/scenarios/gateway-4.3.x-scenario/configuration.yml new file mode 100644 index 0000000000..49d08ed8ef --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/configuration.yml @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/provider/b/testcase +healthCheck: http://localhost:8080/provider/b/healthCheck +startScript: ./bin/startup.sh +runningMode: with_optional +withPlugins: apm-spring-cloud-gateway-4.x-plugin-*.jar + diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-dist/bin/startup.sh b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-dist/bin/startup.sh new file mode 100644 index 0000000000..0d28675632 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-dist/bin/startup.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} "-Dskywalking.agent.service_name=gateway-projectA-scenario" ${home}/../libs/gateway-projectA-scenario.jar & +sleep 1 + +java -jar ${agent_opts} "-Dskywalking.agent.service_name=gateway-projectB-scenario" ${home}/../libs/gateway-projectB-scenario.jar & diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-dist/pom.xml b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-dist/pom.xml new file mode 100644 index 0000000000..45b02e8800 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-dist/pom.xml @@ -0,0 +1,57 @@ + + + + + org.apache.skywalking + gateway-4.3.x-scenario + 5.0.0 + + 4.0.0 + + gateway-dist + + + gateway-4.3.x-scenario + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + gateway-4.3.x-scenario + false + + src/main/assembly/assembly.xml + + ../target/ + + + + + + + diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-dist/src/main/assembly/assembly.xml b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-dist/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..127200bdc8 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-dist/src/main/assembly/assembly.xml @@ -0,0 +1,47 @@ + + + + gateway-dist + + zip + + + + + ./bin + 0775 + + + + + + ../gateway-projectA-scenario/target/gateway-projectA-scenario.jar + ./libs + 0775 + + + ../gateway-projectB-scenario/target/gateway-projectB-scenario.jar + ./libs + 0775 + + + \ No newline at end of file diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/pom.xml b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/pom.xml new file mode 100644 index 0000000000..a3009bea02 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/pom.xml @@ -0,0 +1,56 @@ + + + + + org.apache.skywalking + gateway-4.3.x-scenario + 5.0.0 + + 4.0.0 + + gateway-projectA-scenario + + + + org.springframework.cloud + spring-cloud-starter-gateway + ${test.framework.version} + + + + + gateway-projectA-scenario + + + org.springframework.boot + spring-boot-maven-plugin + 3.0.0 + + + + repackage + + + + + + + diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/ApiKeyResolver.java b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/ApiKeyResolver.java new file mode 100644 index 0000000000..40517d1d3f --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/ApiKeyResolver.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA; + +import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@Component +public class ApiKeyResolver implements KeyResolver { + + public Mono resolve(ServerWebExchange exchange) { + return Mono.just(exchange.getRequest().getPath().value()); + } +} diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Application.java b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Application.java new file mode 100644 index 0000000000..68eb95b0c6 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Application.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test1Filter.java b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test1Filter.java new file mode 100644 index 0000000000..af891eae20 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test1Filter.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +public class Test1Filter implements GlobalFilter, Ordered { + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest buildRequest = exchange.getRequest().mutate().build(); + return chain.filter(exchange.mutate().request(buildRequest).build()); + } + + @Override + public int getOrder() { + return 0; + } +} diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test2Filter.java b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test2Filter.java new file mode 100644 index 0000000000..d3a5846bd5 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test2Filter.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +public class Test2Filter implements GlobalFilter, Ordered { + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest buildRequest = exchange.getRequest().mutate().build(); + return chain.filter(exchange.mutate().request(buildRequest).build()); + } + + @Override + public int getOrder() { + return 1; + } +} diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/TestFilterConfig.java b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/TestFilterConfig.java new file mode 100644 index 0000000000..29f9627bdf --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/TestFilterConfig.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class TestFilterConfig { + + @Bean + public Test1Filter test1Filter() { + return new Test1Filter(); + } + + @Bean + public Test2Filter test2Filter() { + return new Test2Filter(); + } +} diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/resources/application.yml b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/resources/application.yml new file mode 100644 index 0000000000..7639e7ce8a --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectA-scenario/src/main/resources/application.yml @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +server: + port: 8080 +spring: + cloud: + gateway: + httpclient: + connect-timeout: 2000 + routes: + - id: provider_route + uri: http://localhost:18070 + predicates: + - Path=/provider/b/* + - id: provider_timeout + uri: http://1.2.3.4:18070 + predicates: + - Path=/provider/timeout/* diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectB-scenario/pom.xml b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectB-scenario/pom.xml new file mode 100644 index 0000000000..2e1b7cb3d7 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectB-scenario/pom.xml @@ -0,0 +1,57 @@ + + + + + gateway-4.3.x-scenario + org.apache.skywalking + 5.0.0 + + 4.0.0 + + gateway-projectB-scenario + + + + org.springframework.boot + spring-boot-starter-web + 2.1.0.RELEASE + + + + + gateway-projectB-scenario + + + org.springframework.boot + spring-boot-maven-plugin + 1.5.9.RELEASE + + + + repackage + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/Application.java b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/Application.java new file mode 100644 index 0000000000..51c94655c7 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/Application.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectB; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan(value = {"test.apache.skywalking.apm.testcase.sc.gateway.projectB.controller"}) +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/controller/TestController.java b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/controller/TestController.java new file mode 100644 index 0000000000..923d7fd7ff --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/controller/TestController.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.gateway.projectB.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +@RestController +public class TestController { + + @RequestMapping("/provider/b/testcase") + public String testcase() { + try { + new RestTemplate().getForEntity("http://localhost:8080/provider/timeout/error", String.class); + } catch (Exception e) { + } + return "1"; + } + + @RequestMapping("/provider/b/healthCheck") + public String healthCheck() { + return "Success"; + } +} diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectB-scenario/src/main/resources/application.properties b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectB-scenario/src/main/resources/application.properties new file mode 100644 index 0000000000..cac2c4d5a1 --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/gateway-projectB-scenario/src/main/resources/application.properties @@ -0,0 +1,17 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +server.port=18070 \ No newline at end of file diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/pom.xml b/test/plugin/scenarios/gateway-4.3.x-scenario/pom.xml new file mode 100644 index 0000000000..a91ec5a97d --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/pom.xml @@ -0,0 +1,79 @@ + + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + + org.apache.skywalking + gateway-4.3.x-scenario + pom + 5.0.0 + + gateway-projectA-scenario + gateway-projectB-scenario + gateway-dist + + + skywalking-gateway-4.3.x-scenario + + + UTF-8 + 17 + 2023.0.0 + 3.11.0 + 4.3.3 + ${test.framework.version} + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + gateway-4.3.x-scenario + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + diff --git a/test/plugin/scenarios/gateway-4.3.x-scenario/support-version.list b/test/plugin/scenarios/gateway-4.3.x-scenario/support-version.list new file mode 100644 index 0000000000..ab039a217d --- /dev/null +++ b/test/plugin/scenarios/gateway-4.3.x-scenario/support-version.list @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +4.3.3 \ No newline at end of file diff --git a/test/plugin/scenarios/gateway-4.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-4.x-scenario/config/expectedData.yaml index 70df54e93f..7a883f6261 100644 --- a/test/plugin/scenarios/gateway-4.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/gateway-4.x-scenario/config/expectedData.yaml @@ -54,8 +54,8 @@ segmentItems: - {key: http.method, value: GET} - {key: http.status_code, value: '200'} refs: - - {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070', - refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + - {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' - serviceName: gateway-projectA-scenario @@ -76,8 +76,8 @@ segmentItems: - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -95,8 +95,8 @@ segmentItems: - { key: message, value: not null } - { key: stack, value: not null} - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -106,11 +106,23 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null } skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + spanType: Local + refs: + - { parentEndpoint: '/provider/timeout/error', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + skipAnalysis: 'false' - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -123,8 +135,8 @@ segmentItems: - {key: http.status_code, value: '200'} skipAnalysis: 'false' - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -136,6 +148,20 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' - segmentId: not null spans: - operationName: /provider/b/testcase diff --git a/test/plugin/scenarios/gateway-4.x-scenario/support-version.list b/test/plugin/scenarios/gateway-4.x-scenario/support-version.list index ff90e11c58..d69db104f9 100644 --- a/test/plugin/scenarios/gateway-4.x-scenario/support-version.list +++ b/test/plugin/scenarios/gateway-4.x-scenario/support-version.list @@ -16,4 +16,5 @@ 4.0.0 4.0.8 -4.1.0 \ No newline at end of file +4.1.0 +4.1.1 \ No newline at end of file diff --git a/test/plugin/scenarios/graphql-16plus-scenario/support-version.list b/test/plugin/scenarios/graphql-16plus-scenario/support-version.list index c8a4778deb..3d9f037eba 100644 --- a/test/plugin/scenarios/graphql-16plus-scenario/support-version.list +++ b/test/plugin/scenarios/graphql-16plus-scenario/support-version.list @@ -15,6 +15,9 @@ # limitations under the License. # lists your version here +# graphql-java 20+ requires Java 11+, so only 16-19 are tested here (Java 8 scenario) 16.0 17.0 +18.7 +19.11 diff --git a/test/plugin/scenarios/graphql-20plus-scenario/bin/startup.sh b/test/plugin/scenarios/graphql-20plus-scenario/bin/startup.sh new file mode 100644 index 0000000000..aba240987a --- /dev/null +++ b/test/plugin/scenarios/graphql-20plus-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/graphql-20plus-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/graphql-20plus-scenario/config/expectedData.yaml b/test/plugin/scenarios/graphql-20plus-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..df3b3315ec --- /dev/null +++ b/test/plugin/scenarios/graphql-20plus-scenario/config/expectedData.yaml @@ -0,0 +1,88 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: graphql-20plus-scenario + segmentSize: gt 1 + segments: + - segmentId: not null + spans: + - operationName: user + parentSpanId: 0 + spanId: 1 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 92 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: x-le, value: '{"logic-span":true}'} + - operationName: users + parentSpanId: 0 + spanId: 2 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 92 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: x-le, value: '{"logic-span":true}'} + - operationName: user + parentSpanId: 0 + spanId: 3 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 92 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: x-le, value: '{"logic-span":true}'} + - operationName: users + parentSpanId: 0 + spanId: 4 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 92 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: x-le, value: '{"logic-span":true}'} + - operationName: GET:/graphql-scenario/case/graphql + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/graphql-scenario/case/graphql'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/graphql-20plus-scenario/configuration.yml b/test/plugin/scenarios/graphql-20plus-scenario/configuration.yml new file mode 100644 index 0000000000..9032d2abff --- /dev/null +++ b/test/plugin/scenarios/graphql-20plus-scenario/configuration.yml @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/graphql-scenario/case/graphql +healthCheck: http://localhost:8080/graphql-scenario/case/healthCheck +startScript: ./bin/startup.sh diff --git a/test/plugin/scenarios/graphql-20plus-scenario/pom.xml b/test/plugin/scenarios/graphql-20plus-scenario/pom.xml new file mode 100644 index 0000000000..d1daa8b2a4 --- /dev/null +++ b/test/plugin/scenarios/graphql-20plus-scenario/pom.xml @@ -0,0 +1,117 @@ + + + + + org.apache.skywalking.apm.testcase + graphql-20plus-scenario + 1.0.0 + jar + + 4.0.0 + + skywalking-graphql-20plus-scenario + + + UTF-8 + 17 + 3.8.1 + 3.0.13 + 20.0 + 1.18.30 + graphql + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + com.graphql-java + graphql-java + ${test.framework.version} + + + org.projectlombok + lombok + ${lombok.version} + provided + + + org.springframework.boot + spring-boot-starter-web + + + + + graphql-20plus-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + diff --git a/test/plugin/scenarios/graphql-20plus-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/graphql-20plus-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..8366461a93 --- /dev/null +++ b/test/plugin/scenarios/graphql-20plus-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/graphql-20plus-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/graphql-20plus-scenario/src/main/java/org/apache/skywalking/apm/testcase/graphql/Application.java b/test/plugin/scenarios/graphql-20plus-scenario/src/main/java/org/apache/skywalking/apm/testcase/graphql/Application.java new file mode 100644 index 0000000000..657ca45983 --- /dev/null +++ b/test/plugin/scenarios/graphql-20plus-scenario/src/main/java/org/apache/skywalking/apm/testcase/graphql/Application.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.graphql; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/graphql-20plus-scenario/src/main/java/org/apache/skywalking/apm/testcase/graphql/configuration/GraphSchema.java b/test/plugin/scenarios/graphql-20plus-scenario/src/main/java/org/apache/skywalking/apm/testcase/graphql/configuration/GraphSchema.java new file mode 100644 index 0000000000..607fdf7222 --- /dev/null +++ b/test/plugin/scenarios/graphql-20plus-scenario/src/main/java/org/apache/skywalking/apm/testcase/graphql/configuration/GraphSchema.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.graphql.configuration; + +import graphql.GraphQL; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLOutputType; +import graphql.schema.GraphQLSchema; +import org.apache.skywalking.apm.testcase.graphql.data.User; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; + +import static graphql.Scalars.GraphQLInt; +import static graphql.Scalars.GraphQLString; +import static graphql.schema.GraphQLArgument.newArgument; +import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; +import static graphql.schema.GraphQLObjectType.newObject; + +@Component +public class GraphSchema { + private graphql.schema.GraphQLSchema schema; + private GraphQLOutputType userType; + + @PostConstruct + public void init() { + initOutputType(); + schema = graphql.schema.GraphQLSchema.newSchema().query(newObject() + .name("GraphQuery") + .field(createUsersField()) + .field(createUserField()) + .build()).build(); + } + + @Bean + public GraphQL graphQL() { + return new GraphQL.Builder(getSchema()).build(); + } + + private void initOutputType() { + + userType = newObject() + .name("User") + .field(newFieldDefinition().name("id").type(GraphQLInt).build()) + .field(newFieldDefinition().name("name").type(GraphQLString).build()) + .build(); + } + + private GraphQLFieldDefinition createUserField() { + return GraphQLFieldDefinition.newFieldDefinition() + .name("user") + .argument(newArgument().name("id").type(GraphQLInt).build()) + .type(userType) + .dataFetcher(environment -> { + int id = environment.getArgument("id"); + try { + Thread.sleep(300L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + User user = new User(); + user.setId(id); + user.setName("Name_" + id); + return user; + }) + .build(); + } + + private GraphQLFieldDefinition createUsersField() { + return GraphQLFieldDefinition.newFieldDefinition() + .name("users") + .argument(newArgument().name("page").type(GraphQLInt).build()) + .argument(newArgument().name("size").type(GraphQLInt).build()) + .argument(newArgument().name("name").type(GraphQLString).build()) + .type(new GraphQLList(userType)) + .dataFetcher(environment -> { + int page = environment.getArgument("page"); + int size = environment.getArgument("size"); + String name = environment.getArgument("name"); + + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + List list = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + User user = new User(); + user.setId(i); + user.setName(name + "_" + page + "_" + i); + list.add(user); + } + return list; + }) + .build(); + } + + public GraphQLSchema getSchema() { + return schema; + } +} \ No newline at end of file diff --git a/test/plugin/scenarios/graphql-20plus-scenario/src/main/java/org/apache/skywalking/apm/testcase/graphql/controller/CaseController.java b/test/plugin/scenarios/graphql-20plus-scenario/src/main/java/org/apache/skywalking/apm/testcase/graphql/controller/CaseController.java new file mode 100644 index 0000000000..a014af2ae4 --- /dev/null +++ b/test/plugin/scenarios/graphql-20plus-scenario/src/main/java/org/apache/skywalking/apm/testcase/graphql/controller/CaseController.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.graphql.controller; + +import graphql.ExecutionInput; +import graphql.GraphQL; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +@RestController +@RequestMapping("/case") +public class CaseController { + + private static final String SUCCESS = "Success"; + + @Resource + private GraphQL graphQL; + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() { + return SUCCESS; + } + + @RequestMapping("/graphql") + @ResponseBody + public String graphql() { + String query3 = "{user(id:6) {id,name},users(page:2,size:5,name:\"john\") {id,name}}"; + ExecutionInput executionInput = ExecutionInput.newExecutionInput().query(query3).build(); + graphQL.execute(executionInput); + graphQL.executeAsync(executionInput); + return SUCCESS; + } +} diff --git a/test/plugin/scenarios/graphql-20plus-scenario/src/main/java/org/apache/skywalking/apm/testcase/graphql/data/User.java b/test/plugin/scenarios/graphql-20plus-scenario/src/main/java/org/apache/skywalking/apm/testcase/graphql/data/User.java new file mode 100644 index 0000000000..c23a2baf3f --- /dev/null +++ b/test/plugin/scenarios/graphql-20plus-scenario/src/main/java/org/apache/skywalking/apm/testcase/graphql/data/User.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.graphql.data; + +import lombok.Data; + +@Data +public class User { + private int id; + private String name; +} \ No newline at end of file diff --git a/test/plugin/scenarios/graphql-20plus-scenario/src/main/resources/application.yml b/test/plugin/scenarios/graphql-20plus-scenario/src/main/resources/application.yml new file mode 100644 index 0000000000..3d9f580c4e --- /dev/null +++ b/test/plugin/scenarios/graphql-20plus-scenario/src/main/resources/application.yml @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +server: + port: 8080 + servlet: + context-path: /graphql-scenario \ No newline at end of file diff --git a/test/plugin/scenarios/graphql-20plus-scenario/support-version.list b/test/plugin/scenarios/graphql-20plus-scenario/support-version.list new file mode 100644 index 0000000000..0d28512b2e --- /dev/null +++ b/test/plugin/scenarios/graphql-20plus-scenario/support-version.list @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# graphql-java 20+ requires Java 11+, tested with JDK 17 + +20.9 +21.5 +22.4 +23.1 +24.1 diff --git a/test/plugin/scenarios/grizzly-2.3.x-4.x-workthreadpool-scenario/config/expectedData.yaml b/test/plugin/scenarios/grizzly-2.3.x-4.x-workthreadpool-scenario/config/expectedData.yaml index da0f47c6ab..a635342f8b 100644 --- a/test/plugin/scenarios/grizzly-2.3.x-4.x-workthreadpool-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/grizzly-2.3.x-4.x-workthreadpool-scenario/config/expectedData.yaml @@ -15,7 +15,7 @@ # limitations under the License. meterItems: - serviceName: grizzly-2.3.x-4.x-workthreadpool-scenario - meterSize: 4 + meterSize: ge 4 meters: - meterId: name: thread_pool diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/bin/startup.sh b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..aeac2a7b77 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/grpc-1.30.x-1.39.x-scenario.jar & diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..b4551acf3e --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/config/expectedData.yaml @@ -0,0 +1,458 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: + - serviceName: grpc-1.30.x-1.39.x-scenario + segmentSize: gt 10 + segments: + - segmentId: not null + spans: + - operationName: GreeterBlocking.sayHello + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Entry + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.30.x-1.39.x-scenario/case/grpc-1.30.x-1.39.x-scenario, networkAddress: '127.0.0.1:18080', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.30.x-1.39.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/client/Request/onMessage + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.30.x-1.39.x-scenario/case/grpc-1.30.x-1.39.x-scenario, networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/client/Request/onMessage + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.30.x-1.39.x-scenario/case/grpc-1.30.x-1.39.x-scenario, networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.30.x-1.39.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/client/Request/onComplete + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.30.x-1.39.x-scenario/case/grpc-1.30.x-1.39.x-scenario, networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.30.x-1.39.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Entry + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.30.x-1.39.x-scenario/case/grpc-1.30.x-1.39.x-scenario, networkAddress: '127.0.0.1:18080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.30.x-1.39.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/server/Response/onMessage + parentSpanId: 0 + spanId: 1 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: Greeter.sayHello/server/Request/onMessage + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: Greeter.sayHello, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/client/Response/onMessage + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.30.x-1.39.x-scenario/case/grpc-1.30.x-1.39.x-scenario, networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GreeterBlocking.sayHello/server/Response/onClose + parentSpanId: 0 + spanId: 1 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + tags: + - {key: rpc.status_code, value: OK} + skipAnalysis: 'false' + - operationName: GreeterBlocking.sayHello/server/Request/onHalfClose + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GreeterBlocking.sayHello, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.30.x-1.39.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/server/Response/onMessage + parentSpanId: 0 + spanId: 1 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: Greeter.sayHello/server/Request/onMessage + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: Greeter.sayHello, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.30.x-1.39.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/server/Response/onClose + parentSpanId: 0 + spanId: 1 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + tags: + - {key: rpc.status_code, value: OK} + skipAnalysis: 'false' + - operationName: Greeter.sayHello/server/Request/onHalfClose + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: Greeter.sayHello, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.30.x-1.39.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/client/Response/onMessage + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.30.x-1.39.x-scenario/case/grpc-1.30.x-1.39.x-scenario, networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.30.x-1.39.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/client/Response/onClose + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.30.x-1.39.x-scenario/case/grpc-1.30.x-1.39.x-scenario, networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.30.x-1.39.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GreeterBlockingError.sayHello + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Entry + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.30.x-1.39.x-scenario/case/grpc-1.30.x-1.39.x-scenario, networkAddress: '127.0.0.1:18080', + refType: CrossProcess, parentSpanId: 5, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.30.x-1.39.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GreeterBlockingError.sayHello/server/Response/onClose + parentSpanId: 0 + spanId: 1 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: true + spanType: Local + peer: '' + tags: + - {key: rpc.status_code, value: UNKNOWN} + logs: + - logEvent: + - {key: event, value: error} + - {key: error.kind, value: java.lang.Exception} + - {key: message, value: ''} + - {key: stack, value: not null} + skipAnalysis: 'false' + - operationName: GreeterBlockingError.sayHello/server/Request/onHalfClose + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GreeterBlockingError.sayHello, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.30.x-1.39.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello + parentSpanId: 0 + spanId: 1 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Exit + peer: '127.0.0.1:18080' + skipAnalysis: 'false' + - operationName: GreeterBlocking.sayHello/client/Request/onComplete + parentSpanId: 2 + spanId: 3 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: GreeterBlocking.sayHello/client/Response/onClose + parentSpanId: 2 + spanId: 4 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: GreeterBlocking.sayHello + parentSpanId: 0 + panId: 2 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Exit + peer: '127.0.0.1:18080' + skipAnalysis: 'false' + - operationName: GreeterBlockingError.sayHello/client/Request/onComplete + parentSpanId: 5 + spanId: 6 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: GreeterBlockingError.sayHello/client/Response/onClose + parentSpanId: 5 + spanId: 7 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: true + spanType: Local + peer: '' + tags: + - {key: rpc.status_code, value: UNKNOWN} + logs: + - logEvent: + - {key: event, value: error} + - {key: error.kind, value: io.grpc.StatusRuntimeException} + - {key: message, value: UNKNOWN} + - {key: stack, value: not null} + skipAnalysis: 'false' + - operationName: GreeterBlockingError.sayHello/client/Request/onCancel + parentSpanId: 5 + spanId: 8 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: true + spanType: Local + peer: '' + logs: + - logEvent: + - {key: event, value: error} + - {key: error.kind, value: io.grpc.StatusRuntimeException} + - {key: message, value: UNKNOWN} + - {key: stack, value: not null} + skipAnalysis: 'false' + - operationName: GreeterBlockingError.sayHello + parentSpanId: 0 + spanId: 5 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: true + spanType: Exit + peer: 127.0.0.1:18080 + logs: + - logEvent: + - {key: event, value: error} + - {key: error.kind, value: io.grpc.StatusRuntimeException} + - {key: message, value: UNKNOWN} + - {key: stack, value: not null} + skipAnalysis: 'false' + - operationName: GET:/grpc-1.30.x-1.39.x-scenario/case/grpc-1.30.x-1.39.x-scenario + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: true + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/grpc-1.30.x-1.39.x-scenario/case/grpc-1.30.x-1.39.x-scenario'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '500'} + logs: + - logEvent: + - {key: event, value: error} + - {key: error.kind, value: org.springframework.web.util.NestedServletException} + - {key: message, value: 'Request processing failed; nested exception is io.grpc.StatusRuntimeException: + UNKNOWN'} + - {key: stack, value: not null} + - logEvent: + - {key: forward-url, value: /grpc-1.30.x-1.39.x-scenario/error} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/configuration.yml b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/configuration.yml new file mode 100644 index 0000000000..932097e647 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/configuration.yml @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/grpc-1.30.x-1.39.x-scenario/case/grpc-1.30.x-1.39.x-scenario +healthCheck: http://localhost:8080/grpc-1.30.x-1.39.x-scenario/case/healthCheck +startScript: ./bin/startup.sh +environment: +dependencies: diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/pom.xml b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/pom.xml new file mode 100644 index 0000000000..de23b46190 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/pom.xml @@ -0,0 +1,170 @@ + + + + + org.apache.skywalking.apm.testcase + grpc-1.30.x-1.39.x-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 1.8 + 3.8.1 + 1.6.2 + + 1.39.0 + + 2.1.6.RELEASE + + + skywalking-grpc-1.30.x-1.39.x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + + io.grpc + grpc-all + ${test.framework.version} + + + io.grpc + grpc-rls + + + + + + + + + grpc-1.30.x-1.39.x-scenario + + + kr.motd.maven + os-maven-plugin + ${os-maven-plugin.version} + + + initialize + + detect + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + + com.google.protobuf:protoc:3.11.4:exe:${os.detected.classifier} + + grpc-java + io.grpc:protoc-gen-grpc-java:${test.framework.version}:exe:${os.detected.classifier} + + + + + + compile + compile-custom + + + + + + + diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/assembly/assembly.xml similarity index 96% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/assembly/assembly.xml rename to test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/assembly/assembly.xml index 098c888d81..9ba3b28c36 100644 --- a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/assembly/assembly.xml +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/assembly/assembly.xml @@ -33,7 +33,7 @@ - ${project.build.directory}/nats-2.14.x-2.15.x-scenario.jar + ${project.build.directory}/grpc-1.30.x-1.39.x-scenario.jar ./libs 0775 diff --git a/test/plugin/scenarios/oracle-scenario/src/main/java/org/apache/skywalking/apm/testcase/oracle/Application.java b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/Application.java similarity index 95% rename from test/plugin/scenarios/oracle-scenario/src/main/java/org/apache/skywalking/apm/testcase/oracle/Application.java rename to test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/Application.java index 31bce49f55..052e0f7e3b 100644 --- a/test/plugin/scenarios/oracle-scenario/src/main/java/org/apache/skywalking/apm/testcase/oracle/Application.java +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/Application.java @@ -16,7 +16,7 @@ * */ -package org.apache.skywalking.apm.testcase.oracle; +package org.apache.skywalking.apm.testcase.grpc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/consumr/ConsumerInterceptor.java b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/consumr/ConsumerInterceptor.java new file mode 100644 index 0000000000..e8a35d0359 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/consumr/ConsumerInterceptor.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.consumr; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.ForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import javax.annotation.Nullable; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class ConsumerInterceptor implements ClientInterceptor { + + private static final Logger LOGGER = LogManager.getLogger(ConsumerInterceptor.class); + + @Override + public ClientCall interceptCall(MethodDescriptor descriptor, + CallOptions options, Channel channel) { + LOGGER.info("start interceptor!"); + LOGGER.info("method type: {}", descriptor.getType()); + return new ForwardingClientCall.SimpleForwardingClientCall(channel.newCall(descriptor, options)) { + @Override + public void start(Listener responseListener, Metadata headers) { + LOGGER.info("Peer: {}", channel.authority()); + LOGGER.info("Operation Name : {}", descriptor.getFullMethodName()); + Interceptor tracingResponseListener = new Interceptor(responseListener); + tracingResponseListener.contextSnapshot = "contextSnapshot"; + delegate().start(tracingResponseListener, headers); + } + + @Override + public void cancel(@Nullable String message, @Nullable Throwable cause) { + LOGGER.info("cancel"); + super.cancel(message, cause); + } + + @Override + public void halfClose() { + LOGGER.info("halfClose"); + super.halfClose(); + } + + @Override + public void sendMessage(REQ_T message) { + LOGGER.info("sendMessage ...."); + super.sendMessage(message); + } + }; + } + + private static class Interceptor extends ForwardingClientCallListener.SimpleForwardingClientCallListener { + private static final Logger LOGGER = LogManager.getLogger(Interceptor.class); + + private Object contextSnapshot; + + protected Interceptor(ClientCall.Listener delegate) { + super(delegate); + } + + @Override + public void onHeaders(Metadata headers) { + LOGGER.info("on Headers"); + for (String key : headers.keys()) { + LOGGER.info("Receive key: {}", key); + } + delegate().onHeaders(headers); + } + + @Override + public void onMessage(RESP_T message) { + LOGGER.info("contextSnapshot: {}", contextSnapshot); + delegate().onMessage(message); + } + + @Override + public void onClose(Status status, Metadata trailers) { + LOGGER.info("on close"); + delegate().onClose(status, trailers); + } + + @Override + public void onReady() { + LOGGER.info("on Ready"); + super.onReady(); + } + } +} diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/controller/CaseController.java b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/controller/CaseController.java new file mode 100644 index 0000000000..c97804e90b --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/controller/CaseController.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.controller; + +import io.grpc.ClientInterceptors; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.stub.ClientCallStreamObserver; +import io.grpc.stub.ClientResponseObserver; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import javax.annotation.PostConstruct; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.skywalking.apm.testcase.grpc.consumr.ConsumerInterceptor; +import org.apache.skywalking.apm.testcase.grpc.proto.GreeterBlockingErrorGrpc; +import org.apache.skywalking.apm.testcase.grpc.proto.GreeterBlockingGrpc; +import org.apache.skywalking.apm.testcase.grpc.proto.GreeterGrpc; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloRequest; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloReply; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/case") +public class CaseController { + + private static final Logger LOGGER = LogManager.getLogger(CaseController.class); + + private static final String SUCCESS = "Success"; + + private final String grpcProviderHost = "127.0.0.1"; + private final int grpcProviderPort = 18080; + private ManagedChannel channel; + private GreeterGrpc.GreeterStub greeterStub; + private GreeterBlockingGrpc.GreeterBlockingBlockingStub greeterBlockingStub; + private GreeterBlockingErrorGrpc.GreeterBlockingErrorBlockingStub greeterBlockingErrorStub; + + @PostConstruct + public void up() { + channel = ManagedChannelBuilder.forAddress(grpcProviderHost, grpcProviderPort).usePlaintext().build(); + greeterStub = GreeterGrpc.newStub(ClientInterceptors.intercept(channel, new ConsumerInterceptor())); + greeterBlockingStub = GreeterBlockingGrpc.newBlockingStub(ClientInterceptors.intercept(channel, new ConsumerInterceptor())); + greeterBlockingErrorStub = GreeterBlockingErrorGrpc.newBlockingStub(ClientInterceptors.intercept(channel, new ConsumerInterceptor())); + } + + @RequestMapping("/grpc-1.30.x-1.39.x-scenario") + @ResponseBody + public String testcase() { + greetService(); + greetBlockingService(); + greetBlockingErrorService(); + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() { + // your codes + return SUCCESS; + } + + private static List names() { + return Arrays.asList("Sophia", "Jackson"); + } + + private void greetService() { + ClientResponseObserver helloReplyStreamObserver = new ClientResponseObserver() { + private ClientCallStreamObserver requestStream; + + @Override + public void beforeStart(ClientCallStreamObserver observer) { + this.requestStream = observer; + this.requestStream.setOnReadyHandler(new Runnable() { + Iterator iterator = names().iterator(); + + @Override + public void run() { + while (requestStream.isReady()) { + if (iterator.hasNext()) { + String name = iterator.next(); + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + requestStream.onNext(request); + } else { + requestStream.onCompleted(); + } + } + } + }); + } + + @Override + public void onNext(HelloReply reply) { + LOGGER.info("Receive an message from provider. message: {}", reply.getMessage()); + requestStream.request(1); + } + + public void onError(Throwable throwable) { + LOGGER.error("Failed to send data", throwable); + } + + public void onCompleted() { + LOGGER.info("All Done"); + } + }; + + greeterStub.sayHello(helloReplyStreamObserver); + } + + private void greetBlockingService() { + HelloRequest request = HelloRequest.newBuilder().setName("Sophia").build(); + greeterBlockingStub.sayHello(request); + } + + private void greetBlockingErrorService() { + HelloRequest request = HelloRequest.newBuilder().setName("Tony").build(); + greeterBlockingErrorStub.sayHello(request); + } +} diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/ProviderConfiguration.java b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/ProviderConfiguration.java new file mode 100644 index 0000000000..8541c403b1 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/ProviderConfiguration.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.provider; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.ServerInterceptors; +import org.apache.skywalking.apm.testcase.grpc.provider.interceptor.ProviderInterceptor; +import org.apache.skywalking.apm.testcase.grpc.provider.service.GreeterBlockingErrorServiceImpl; +import org.apache.skywalking.apm.testcase.grpc.provider.service.GreeterBlockingServiceImpl; +import org.apache.skywalking.apm.testcase.grpc.provider.service.GreeterServiceImpl; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +@Configurable +@Component +public class ProviderConfiguration { + + @Bean(initMethod = "start", destroyMethod = "shutdown") + public Server server() { + return ServerBuilder.forPort(18080) + .addService(ServerInterceptors.intercept(new GreeterServiceImpl(), new ProviderInterceptor())) + .addService(ServerInterceptors.intercept(new GreeterBlockingServiceImpl(), new ProviderInterceptor())) + .addService(ServerInterceptors.intercept(new GreeterBlockingErrorServiceImpl(), new ProviderInterceptor())) + .build(); + } +} diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/interceptor/ProviderInterceptor.java b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/interceptor/ProviderInterceptor.java new file mode 100644 index 0000000000..26647b41a8 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/interceptor/ProviderInterceptor.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.provider.interceptor; + +import io.grpc.ForwardingServerCall; +import io.grpc.ForwardingServerCallListener; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class ProviderInterceptor implements ServerInterceptor { + private static final Logger LOGGER = LogManager.getLogger(ProviderInterceptor.class); + + @Override + public ServerCall.Listener interceptCall(ServerCall call, Metadata metadata, + ServerCallHandler handler) { + Map headerMap = new HashMap(); + for (String key : metadata.keys()) { + LOGGER.info("Receive key: {}", key); + if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { + String value = metadata.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)); + + headerMap.put(key, value); + } + } + LOGGER.info("authority : {}", call.getAuthority()); + return new ForwardingServerCallListener.SimpleForwardingServerCallListener(handler.startCall(new ForwardingServerCall.SimpleForwardingServerCall(call) { + @Override + public void sendHeaders(Metadata responseHeaders) { + LOGGER.info("sendHeaders...."); + Metadata.Key headerKey = Metadata.Key.of("test-server", Metadata.ASCII_STRING_MARSHALLER); + responseHeaders.put(headerKey, "test-server"); + delegate().sendHeaders(responseHeaders); + } + + @Override + public void sendMessage(RESQ_T message) { + delegate().sendMessage(message); + } + + }, metadata)) { + @Override + public void onReady() { + LOGGER.info("onReady...."); + delegate().onReady(); + } + + @Override + public void onCancel() { + LOGGER.info("onCancel...."); + delegate().onCancel(); + } + + @Override + public void onComplete() { + LOGGER.info("onComplete...."); + delegate().onComplete(); + } + + @Override + public void onHalfClose() { + LOGGER.info("onHalfClose...."); + delegate().onHalfClose(); + } + + @Override + public void onMessage(REQ_T message) { + LOGGER.info("onMessage...."); + delegate().onMessage(message); + } + }; + } +} diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterBlockingErrorServiceImpl.java b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterBlockingErrorServiceImpl.java new file mode 100644 index 0000000000..3cd6394a48 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterBlockingErrorServiceImpl.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.provider.service; + +import io.grpc.stub.StreamObserver; +import org.apache.skywalking.apm.testcase.grpc.proto.GreeterBlockingErrorGrpc; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloReply; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloRequest; + +public class GreeterBlockingErrorServiceImpl extends GreeterBlockingErrorGrpc.GreeterBlockingErrorImplBase { + @Override + public void sayHello(HelloRequest request, StreamObserver responseObserver) { + responseObserver.onError(new Exception()); + } +} diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterBlockingServiceImpl.java b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterBlockingServiceImpl.java new file mode 100644 index 0000000000..2f156bc62f --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterBlockingServiceImpl.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.provider.service; + +import io.grpc.stub.StreamObserver; +import org.apache.skywalking.apm.testcase.grpc.proto.GreeterBlockingGrpc; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloReply; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloRequest; + +public class GreeterBlockingServiceImpl extends GreeterBlockingGrpc.GreeterBlockingImplBase { + @Override + public void sayHello(HelloRequest request, StreamObserver responseObserver) { + responseObserver.onNext(HelloReply.newBuilder().setMessage("Hi," + request.getName()).build()); + responseObserver.onCompleted(); + } +} diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterServiceImpl.java b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterServiceImpl.java new file mode 100644 index 0000000000..5235d09318 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterServiceImpl.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.provider.service; + +import io.grpc.stub.StreamObserver; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.skywalking.apm.testcase.grpc.proto.GreeterGrpc; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloReply; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloRequest; + +public class GreeterServiceImpl extends GreeterGrpc.GreeterImplBase { + + private static final Logger LOGGER = LogManager.getLogger(GreeterServiceImpl.class); + + @Override + public StreamObserver sayHello(final StreamObserver responseObserver) { + StreamObserver requestStreamObserver = new StreamObserver() { + + public void onNext(HelloRequest request) { + LOGGER.info("Receive an message from client. Message: {}", request.getName()); + responseObserver.onNext(HelloReply.newBuilder().setMessage("Hi," + request.getName()).build()); + } + + public void onError(Throwable throwable) { + responseObserver.onError(throwable); + } + + public void onCompleted() { + LOGGER.info("End the stream."); + responseObserver.onCompleted(); + } + }; + return requestStreamObserver; + } +} diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/proto/GreetService.proto b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/proto/GreetService.proto new file mode 100644 index 0000000000..fd5ba4a8f1 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/proto/GreetService.proto @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "org.apache.skywalking.apm.testcase.grpc.proto"; + +service Greeter { + rpc SayHello (stream HelloRequest) returns (stream HelloReply) { + } +} + +service GreeterBlocking { + rpc SayHello (HelloRequest) returns (HelloReply) { + } +} +service GreeterBlockingError { + rpc SayHello (HelloRequest) returns (HelloReply) { + } +} + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string message = 1; +} \ No newline at end of file diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000..5f9c909a1b --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/resources/application.yaml @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server: + port: 8080 + servlet: + context-path: /grpc-1.30.x-1.39.x-scenario +logging: + config: classpath:log4j2.xml diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..9849ed5a8a --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/support-version.list b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/support-version.list new file mode 100644 index 0000000000..5c01dfaf60 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.30.x-1.39.x-scenario/support-version.list @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# INTERNAL: HTTP/2 error code: INTERNAL_ERROR Received Goaway occur in test cases 1.0.0 to 1.5.0 +# So these versions were not included in support-version.list. if you know what caused it, please help us. + +# Contains only the last version number of each minor version + +1.39.0 +1.38.0 +1.37.0 +1.36.0 +1.35.0 +1.34.0 +1.33.0 +1.32.1 +1.31.0 +1.30.0 diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/bin/startup.sh b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..54d5003fe6 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/grpc-1.59.x-1.70.x-scenario.jar & diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..2cbf91a2ea --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/config/expectedData.yaml @@ -0,0 +1,458 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: + - serviceName: grpc-1.59.x-1.70.x-scenario + segmentSize: gt 10 + segments: + - segmentId: not null + spans: + - operationName: GreeterBlocking.sayHello + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Entry + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.59.x-1.70.x-scenario/case/grpc-1.59.x-1.70.x-scenario, networkAddress: '127.0.0.1:18080', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.59.x-1.70.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/client/Request/onMessage + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.59.x-1.70.x-scenario/case/grpc-1.59.x-1.70.x-scenario, networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/client/Request/onMessage + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.59.x-1.70.x-scenario/case/grpc-1.59.x-1.70.x-scenario, networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.59.x-1.70.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/client/Request/onComplete + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.59.x-1.70.x-scenario/case/grpc-1.59.x-1.70.x-scenario, networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.59.x-1.70.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Entry + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.59.x-1.70.x-scenario/case/grpc-1.59.x-1.70.x-scenario, networkAddress: '127.0.0.1:18080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.59.x-1.70.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/server/Response/onMessage + parentSpanId: 0 + spanId: 1 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: Greeter.sayHello/server/Request/onMessage + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: Greeter.sayHello, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/client/Response/onMessage + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.59.x-1.70.x-scenario/case/grpc-1.59.x-1.70.x-scenario, networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GreeterBlocking.sayHello/server/Response/onClose + parentSpanId: 0 + spanId: 1 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + tags: + - {key: rpc.status_code, value: OK} + skipAnalysis: 'false' + - operationName: GreeterBlocking.sayHello/server/Request/onHalfClose + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GreeterBlocking.sayHello, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.59.x-1.70.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/server/Response/onMessage + parentSpanId: 0 + spanId: 1 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: Greeter.sayHello/server/Request/onMessage + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: Greeter.sayHello, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.59.x-1.70.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/server/Response/onClose + parentSpanId: 0 + spanId: 1 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + tags: + - {key: rpc.status_code, value: OK} + skipAnalysis: 'false' + - operationName: Greeter.sayHello/server/Request/onHalfClose + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: Greeter.sayHello, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.59.x-1.70.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/client/Response/onMessage + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.59.x-1.70.x-scenario/case/grpc-1.59.x-1.70.x-scenario, networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.59.x-1.70.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello/client/Response/onClose + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.59.x-1.70.x-scenario/case/grpc-1.59.x-1.70.x-scenario, networkAddress: '', refType: CrossThread, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.59.x-1.70.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GreeterBlockingError.sayHello + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Entry + peer: '' + refs: + - {parentEndpoint: GET:/grpc-1.59.x-1.70.x-scenario/case/grpc-1.59.x-1.70.x-scenario, networkAddress: '127.0.0.1:18080', + refType: CrossProcess, parentSpanId: 5, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.59.x-1.70.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GreeterBlockingError.sayHello/server/Response/onClose + parentSpanId: 0 + spanId: 1 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: true + spanType: Local + peer: '' + tags: + - {key: rpc.status_code, value: UNKNOWN} + logs: + - logEvent: + - {key: event, value: error} + - {key: error.kind, value: java.lang.Exception} + - {key: message, value: ''} + - {key: stack, value: not null} + skipAnalysis: 'false' + - operationName: GreeterBlockingError.sayHello/server/Request/onHalfClose + parentSpanId: -1 + spanId: 0 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GreeterBlockingError.sayHello, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: grpc-1.59.x-1.70.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: Greeter.sayHello + parentSpanId: 0 + spanId: 1 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Exit + peer: '127.0.0.1:18080' + skipAnalysis: 'false' + - operationName: GreeterBlocking.sayHello/client/Request/onComplete + parentSpanId: 2 + spanId: 3 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: GreeterBlocking.sayHello/client/Response/onClose + parentSpanId: 2 + spanId: 4 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: GreeterBlocking.sayHello + parentSpanId: 0 + panId: 2 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Exit + peer: '127.0.0.1:18080' + skipAnalysis: 'false' + - operationName: GreeterBlockingError.sayHello/client/Request/onComplete + parentSpanId: 5 + spanId: 6 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: GreeterBlockingError.sayHello/client/Response/onClose + parentSpanId: 5 + spanId: 7 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: true + spanType: Local + peer: '' + tags: + - {key: rpc.status_code, value: UNKNOWN} + logs: + - logEvent: + - {key: event, value: error} + - {key: error.kind, value: io.grpc.StatusRuntimeException} + - {key: message, value: UNKNOWN} + - {key: stack, value: not null} + skipAnalysis: 'false' + - operationName: GreeterBlockingError.sayHello/client/Request/onCancel + parentSpanId: 5 + spanId: 8 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: true + spanType: Local + peer: '' + logs: + - logEvent: + - {key: event, value: error} + - {key: error.kind, value: io.grpc.StatusRuntimeException} + - {key: message, value: UNKNOWN} + - {key: stack, value: not null} + skipAnalysis: 'false' + - operationName: GreeterBlockingError.sayHello + parentSpanId: 0 + spanId: 5 + spanLayer: RPCFramework + startTime: nq 0 + endTime: nq 0 + componentId: 23 + isError: true + spanType: Exit + peer: 127.0.0.1:18080 + logs: + - logEvent: + - {key: event, value: error} + - {key: error.kind, value: io.grpc.StatusRuntimeException} + - {key: message, value: UNKNOWN} + - {key: stack, value: not null} + skipAnalysis: 'false' + - operationName: GET:/grpc-1.59.x-1.70.x-scenario/case/grpc-1.59.x-1.70.x-scenario + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: true + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/grpc-1.59.x-1.70.x-scenario/case/grpc-1.59.x-1.70.x-scenario'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '500'} + logs: + - logEvent: + - {key: event, value: error} + - {key: error.kind, value: org.springframework.web.util.NestedServletException} + - {key: message, value: 'Request processing failed; nested exception is io.grpc.StatusRuntimeException: + UNKNOWN'} + - {key: stack, value: not null} + - logEvent: + - {key: forward-url, value: /grpc-1.59.x-1.70.x-scenario/error} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/configuration.yml b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/configuration.yml new file mode 100644 index 0000000000..858c323d01 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/configuration.yml @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/grpc-1.59.x-1.70.x-scenario/case/grpc-1.59.x-1.70.x-scenario +healthCheck: http://localhost:8080/grpc-1.59.x-1.70.x-scenario/case/healthCheck +startScript: ./bin/startup.sh +environment: +dependencies: diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/pom.xml b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/pom.xml new file mode 100644 index 0000000000..0794a1cb21 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/pom.xml @@ -0,0 +1,163 @@ + + + + + org.apache.skywalking.apm.testcase + grpc-1.59.x-1.70.x-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 1.8 + 3.8.1 + 1.6.2 + + 1.59.0 + + 2.1.6.RELEASE + + + skywalking-grpc-1.59.x-1.70.x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + + io.grpc + grpc-all + ${test.framework.version} + + + + + + grpc-1.59.x-1.70.x-scenario + + + kr.motd.maven + os-maven-plugin + ${os-maven-plugin.version} + + + initialize + + detect + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + + com.google.protobuf:protoc:3.23.4:exe:${os.detected.classifier} + + grpc-java + io.grpc:protoc-gen-grpc-java:${test.framework.version}:exe:${os.detected.classifier} + + + + + + compile + compile-custom + + + + + + + diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..11d5b1f62c --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/grpc-1.59.x-1.70.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/Application.java b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/Application.java new file mode 100644 index 0000000000..052e0f7e3b --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/Application.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + try { + SpringApplication.run(Application.class, args); + } catch (Exception e) { + // Never do this + } + } +} diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/consumr/ConsumerInterceptor.java b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/consumr/ConsumerInterceptor.java new file mode 100644 index 0000000000..e8a35d0359 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/consumr/ConsumerInterceptor.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.consumr; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.ForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import javax.annotation.Nullable; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class ConsumerInterceptor implements ClientInterceptor { + + private static final Logger LOGGER = LogManager.getLogger(ConsumerInterceptor.class); + + @Override + public ClientCall interceptCall(MethodDescriptor descriptor, + CallOptions options, Channel channel) { + LOGGER.info("start interceptor!"); + LOGGER.info("method type: {}", descriptor.getType()); + return new ForwardingClientCall.SimpleForwardingClientCall(channel.newCall(descriptor, options)) { + @Override + public void start(Listener responseListener, Metadata headers) { + LOGGER.info("Peer: {}", channel.authority()); + LOGGER.info("Operation Name : {}", descriptor.getFullMethodName()); + Interceptor tracingResponseListener = new Interceptor(responseListener); + tracingResponseListener.contextSnapshot = "contextSnapshot"; + delegate().start(tracingResponseListener, headers); + } + + @Override + public void cancel(@Nullable String message, @Nullable Throwable cause) { + LOGGER.info("cancel"); + super.cancel(message, cause); + } + + @Override + public void halfClose() { + LOGGER.info("halfClose"); + super.halfClose(); + } + + @Override + public void sendMessage(REQ_T message) { + LOGGER.info("sendMessage ...."); + super.sendMessage(message); + } + }; + } + + private static class Interceptor extends ForwardingClientCallListener.SimpleForwardingClientCallListener { + private static final Logger LOGGER = LogManager.getLogger(Interceptor.class); + + private Object contextSnapshot; + + protected Interceptor(ClientCall.Listener delegate) { + super(delegate); + } + + @Override + public void onHeaders(Metadata headers) { + LOGGER.info("on Headers"); + for (String key : headers.keys()) { + LOGGER.info("Receive key: {}", key); + } + delegate().onHeaders(headers); + } + + @Override + public void onMessage(RESP_T message) { + LOGGER.info("contextSnapshot: {}", contextSnapshot); + delegate().onMessage(message); + } + + @Override + public void onClose(Status status, Metadata trailers) { + LOGGER.info("on close"); + delegate().onClose(status, trailers); + } + + @Override + public void onReady() { + LOGGER.info("on Ready"); + super.onReady(); + } + } +} diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/controller/CaseController.java b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/controller/CaseController.java new file mode 100644 index 0000000000..2a26b99e96 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/controller/CaseController.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.controller; + +import io.grpc.ClientInterceptors; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.stub.ClientCallStreamObserver; +import io.grpc.stub.ClientResponseObserver; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import javax.annotation.PostConstruct; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.skywalking.apm.testcase.grpc.consumr.ConsumerInterceptor; +import org.apache.skywalking.apm.testcase.grpc.proto.GreeterBlockingErrorGrpc; +import org.apache.skywalking.apm.testcase.grpc.proto.GreeterBlockingGrpc; +import org.apache.skywalking.apm.testcase.grpc.proto.GreeterGrpc; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloRequest; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloReply; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/case") +public class CaseController { + + private static final Logger LOGGER = LogManager.getLogger(CaseController.class); + + private static final String SUCCESS = "Success"; + + private final String grpcProviderHost = "127.0.0.1"; + private final int grpcProviderPort = 18080; + private ManagedChannel channel; + private GreeterGrpc.GreeterStub greeterStub; + private GreeterBlockingGrpc.GreeterBlockingBlockingStub greeterBlockingStub; + private GreeterBlockingErrorGrpc.GreeterBlockingErrorBlockingStub greeterBlockingErrorStub; + + @PostConstruct + public void up() { + channel = ManagedChannelBuilder.forAddress(grpcProviderHost, grpcProviderPort).usePlaintext().build(); + greeterStub = GreeterGrpc.newStub(ClientInterceptors.intercept(channel, new ConsumerInterceptor())); + greeterBlockingStub = GreeterBlockingGrpc.newBlockingStub(ClientInterceptors.intercept(channel, new ConsumerInterceptor())); + greeterBlockingErrorStub = GreeterBlockingErrorGrpc.newBlockingStub(ClientInterceptors.intercept(channel, new ConsumerInterceptor())); + } + + @RequestMapping("/grpc-1.59.x-1.70.x-scenario") + @ResponseBody + public String testcase() { + greetService(); + greetBlockingService(); + greetBlockingErrorService(); + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() { + // your codes + return SUCCESS; + } + + private static List names() { + return Arrays.asList("Sophia", "Jackson"); + } + + private void greetService() { + ClientResponseObserver helloReplyStreamObserver = new ClientResponseObserver() { + private ClientCallStreamObserver requestStream; + + @Override + public void beforeStart(ClientCallStreamObserver observer) { + this.requestStream = observer; + this.requestStream.setOnReadyHandler(new Runnable() { + Iterator iterator = names().iterator(); + + @Override + public void run() { + while (requestStream.isReady()) { + if (iterator.hasNext()) { + String name = iterator.next(); + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + requestStream.onNext(request); + } else { + requestStream.onCompleted(); + } + } + } + }); + } + + @Override + public void onNext(HelloReply reply) { + LOGGER.info("Receive an message from provider. message: {}", reply.getMessage()); + requestStream.request(1); + } + + public void onError(Throwable throwable) { + LOGGER.error("Failed to send data", throwable); + } + + public void onCompleted() { + LOGGER.info("All Done"); + } + }; + + greeterStub.sayHello(helloReplyStreamObserver); + } + + private void greetBlockingService() { + HelloRequest request = HelloRequest.newBuilder().setName("Sophia").build(); + greeterBlockingStub.sayHello(request); + } + + private void greetBlockingErrorService() { + HelloRequest request = HelloRequest.newBuilder().setName("Tony").build(); + greeterBlockingErrorStub.sayHello(request); + } +} diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/ProviderConfiguration.java b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/ProviderConfiguration.java new file mode 100644 index 0000000000..8541c403b1 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/ProviderConfiguration.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.provider; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.ServerInterceptors; +import org.apache.skywalking.apm.testcase.grpc.provider.interceptor.ProviderInterceptor; +import org.apache.skywalking.apm.testcase.grpc.provider.service.GreeterBlockingErrorServiceImpl; +import org.apache.skywalking.apm.testcase.grpc.provider.service.GreeterBlockingServiceImpl; +import org.apache.skywalking.apm.testcase.grpc.provider.service.GreeterServiceImpl; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +@Configurable +@Component +public class ProviderConfiguration { + + @Bean(initMethod = "start", destroyMethod = "shutdown") + public Server server() { + return ServerBuilder.forPort(18080) + .addService(ServerInterceptors.intercept(new GreeterServiceImpl(), new ProviderInterceptor())) + .addService(ServerInterceptors.intercept(new GreeterBlockingServiceImpl(), new ProviderInterceptor())) + .addService(ServerInterceptors.intercept(new GreeterBlockingErrorServiceImpl(), new ProviderInterceptor())) + .build(); + } +} diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/interceptor/ProviderInterceptor.java b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/interceptor/ProviderInterceptor.java new file mode 100644 index 0000000000..26647b41a8 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/interceptor/ProviderInterceptor.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.provider.interceptor; + +import io.grpc.ForwardingServerCall; +import io.grpc.ForwardingServerCallListener; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class ProviderInterceptor implements ServerInterceptor { + private static final Logger LOGGER = LogManager.getLogger(ProviderInterceptor.class); + + @Override + public ServerCall.Listener interceptCall(ServerCall call, Metadata metadata, + ServerCallHandler handler) { + Map headerMap = new HashMap(); + for (String key : metadata.keys()) { + LOGGER.info("Receive key: {}", key); + if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { + String value = metadata.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)); + + headerMap.put(key, value); + } + } + LOGGER.info("authority : {}", call.getAuthority()); + return new ForwardingServerCallListener.SimpleForwardingServerCallListener(handler.startCall(new ForwardingServerCall.SimpleForwardingServerCall(call) { + @Override + public void sendHeaders(Metadata responseHeaders) { + LOGGER.info("sendHeaders...."); + Metadata.Key headerKey = Metadata.Key.of("test-server", Metadata.ASCII_STRING_MARSHALLER); + responseHeaders.put(headerKey, "test-server"); + delegate().sendHeaders(responseHeaders); + } + + @Override + public void sendMessage(RESQ_T message) { + delegate().sendMessage(message); + } + + }, metadata)) { + @Override + public void onReady() { + LOGGER.info("onReady...."); + delegate().onReady(); + } + + @Override + public void onCancel() { + LOGGER.info("onCancel...."); + delegate().onCancel(); + } + + @Override + public void onComplete() { + LOGGER.info("onComplete...."); + delegate().onComplete(); + } + + @Override + public void onHalfClose() { + LOGGER.info("onHalfClose...."); + delegate().onHalfClose(); + } + + @Override + public void onMessage(REQ_T message) { + LOGGER.info("onMessage...."); + delegate().onMessage(message); + } + }; + } +} diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterBlockingErrorServiceImpl.java b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterBlockingErrorServiceImpl.java new file mode 100644 index 0000000000..3cd6394a48 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterBlockingErrorServiceImpl.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.provider.service; + +import io.grpc.stub.StreamObserver; +import org.apache.skywalking.apm.testcase.grpc.proto.GreeterBlockingErrorGrpc; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloReply; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloRequest; + +public class GreeterBlockingErrorServiceImpl extends GreeterBlockingErrorGrpc.GreeterBlockingErrorImplBase { + @Override + public void sayHello(HelloRequest request, StreamObserver responseObserver) { + responseObserver.onError(new Exception()); + } +} diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterBlockingServiceImpl.java b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterBlockingServiceImpl.java new file mode 100644 index 0000000000..2f156bc62f --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterBlockingServiceImpl.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.provider.service; + +import io.grpc.stub.StreamObserver; +import org.apache.skywalking.apm.testcase.grpc.proto.GreeterBlockingGrpc; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloReply; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloRequest; + +public class GreeterBlockingServiceImpl extends GreeterBlockingGrpc.GreeterBlockingImplBase { + @Override + public void sayHello(HelloRequest request, StreamObserver responseObserver) { + responseObserver.onNext(HelloReply.newBuilder().setMessage("Hi," + request.getName()).build()); + responseObserver.onCompleted(); + } +} diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterServiceImpl.java b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterServiceImpl.java new file mode 100644 index 0000000000..5235d09318 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/grpc/provider/service/GreeterServiceImpl.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.grpc.provider.service; + +import io.grpc.stub.StreamObserver; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.skywalking.apm.testcase.grpc.proto.GreeterGrpc; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloReply; +import org.apache.skywalking.apm.testcase.grpc.proto.HelloRequest; + +public class GreeterServiceImpl extends GreeterGrpc.GreeterImplBase { + + private static final Logger LOGGER = LogManager.getLogger(GreeterServiceImpl.class); + + @Override + public StreamObserver sayHello(final StreamObserver responseObserver) { + StreamObserver requestStreamObserver = new StreamObserver() { + + public void onNext(HelloRequest request) { + LOGGER.info("Receive an message from client. Message: {}", request.getName()); + responseObserver.onNext(HelloReply.newBuilder().setMessage("Hi," + request.getName()).build()); + } + + public void onError(Throwable throwable) { + responseObserver.onError(throwable); + } + + public void onCompleted() { + LOGGER.info("End the stream."); + responseObserver.onCompleted(); + } + }; + return requestStreamObserver; + } +} diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/proto/GreetService.proto b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/proto/GreetService.proto new file mode 100644 index 0000000000..fd5ba4a8f1 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/proto/GreetService.proto @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "org.apache.skywalking.apm.testcase.grpc.proto"; + +service Greeter { + rpc SayHello (stream HelloRequest) returns (stream HelloReply) { + } +} + +service GreeterBlocking { + rpc SayHello (HelloRequest) returns (HelloReply) { + } +} +service GreeterBlockingError { + rpc SayHello (HelloRequest) returns (HelloReply) { + } +} + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string message = 1; +} \ No newline at end of file diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000..37d6d01659 --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/resources/application.yaml @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server: + port: 8080 + servlet: + context-path: /grpc-1.59.x-1.70.x-scenario +logging: + config: classpath:log4j2.xml diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..9849ed5a8a --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/support-version.list b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/support-version.list new file mode 100644 index 0000000000..dd10211b8e --- /dev/null +++ b/test/plugin/scenarios/grpc-1.59.x-1.70.x-scenario/support-version.list @@ -0,0 +1,35 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# INTERNAL: HTTP/2 error code: INTERNAL_ERROR Received Goaway occur in test cases 1.0.0 to 1.5.0 +# So these versions were not included in support-version.list. if you know what caused it, please help us. + +# Contains only the last version number of each minor version + +1.73.0 +1.72.0 +1.71.0 +1.70.0 +1.69.0 +1.68.0 +1.67.1 +1.66.0 +1.65.0 +1.64.0 +1.63.0 +1.62.2 +1.61.0 +1.60.0 diff --git a/test/plugin/scenarios/h2-scenario/config/expectedData.yaml b/test/plugin/scenarios/h2-scenario/config/expectedData.yaml index d06c487e8a..c1420faf08 100644 --- a/test/plugin/scenarios/h2-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/h2-scenario/config/expectedData.yaml @@ -98,7 +98,7 @@ segmentItems: skipAnalysis: 'false' meterItems: - serviceName: h2-scenario - meterSize: 5 + meterSize: ge 5 meters: - meterId: name: thread_pool diff --git a/test/plugin/scenarios/hikaricp-scenario/config/expectedData.yaml b/test/plugin/scenarios/hikaricp-scenario/config/expectedData.yaml index 75106a2a57..fbecf97a0a 100644 --- a/test/plugin/scenarios/hikaricp-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/hikaricp-scenario/config/expectedData.yaml @@ -206,7 +206,7 @@ segmentItems: - {key: http.status_code, value: '200'} meterItems: - serviceName: hikaricp-scenario - meterSize: 15 + meterSize: ge 15 meters: - meterId: name: datasource diff --git a/test/plugin/scenarios/jdk-httpclient-scenario/bin/startup.sh b/test/plugin/scenarios/jdk-httpclient-scenario/bin/startup.sh new file mode 100644 index 0000000000..6c3e9e8b21 --- /dev/null +++ b/test/plugin/scenarios/jdk-httpclient-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/jdk-httpclient-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/jdk-httpclient-scenario/config/expectedData.yaml b/test/plugin/scenarios/jdk-httpclient-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..f009e03a46 --- /dev/null +++ b/test/plugin/scenarios/jdk-httpclient-scenario/config/expectedData.yaml @@ -0,0 +1,111 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: + - serviceName: jdk-httpclient-scenario + segmentSize: gt 0 + segments: + - segmentId: not null + spans: + - operationName: POST:/user/login + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 14 + isError: false + spanType: Entry + peer: '' + skipAnalysis: 'false' + tags: + - { key: url, value: not null } + - { key: http.method, value: POST } + - { key: http.status_code, value: '200' } + refs: + - { parentEndpoint: GET:/case/jdk-httpclient-scenario-case, networkAddress: 'localhost:8080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: jdk-httpclient-scenario, traceId: not null } + - segmentId: not null + spans: + - operationName: POST:/user/asyncLogin + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 14 + isError: false + spanType: Entry + peer: '' + skipAnalysis: 'false' + tags: + - { key: url, value: not null } + - { key: http.method, value: POST } + - { key: http.status_code, value: '200' } + refs: + - { parentEndpoint: GET:/case/jdk-httpclient-scenario-case, networkAddress: 'localhost:8080', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: jdk-httpclient-scenario, traceId: not null } + + - segmentId: not null + spans: + - operationName: POST:/jdk-httpclient-scenario/user/login + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 66 + isError: false + spanType: Exit + peer: not null + skipAnalysis: 'false' + tags: + - { key: http.method, value: POST } + - { key: url, value: http://localhost:8080/jdk-httpclient-scenario/user/login } + - { key: http.status_code, value: '200' } + + - operationName: POST:/jdk-httpclient-scenario/user/asyncLogin + parentSpanId: 0 + spanId: 2 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 66 + isError: false + spanType: Exit + peer: not null + skipAnalysis: 'false' + tags: + - { key: http.method, value: POST } + - { key: url, value: http://localhost:8080/jdk-httpclient-scenario/user/asyncLogin } + - { key: http.status_code, value: '200' } + + - operationName: GET:/case/jdk-httpclient-scenario-case + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - { key: url, value: http://localhost:8080/jdk-httpclient-scenario/case/jdk-httpclient-scenario-case } + - { key: http.method, value: GET } + - { key: http.status_code, value: '200' } + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/jdk-httpclient-scenario/configuration.yml b/test/plugin/scenarios/jdk-httpclient-scenario/configuration.yml new file mode 100644 index 0000000000..a5fe9c2cb1 --- /dev/null +++ b/test/plugin/scenarios/jdk-httpclient-scenario/configuration.yml @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/jdk-httpclient-scenario/case/jdk-httpclient-scenario-case +healthCheck: http://localhost:8080/jdk-httpclient-scenario/case/healthCheck +runningMode: with_bootstrap +withPlugins: apm-jdk-httpclient-plugin-*.jar +startScript: ./bin/startup.sh diff --git a/test/plugin/scenarios/jdk-httpclient-scenario/pom.xml b/test/plugin/scenarios/jdk-httpclient-scenario/pom.xml new file mode 100644 index 0000000000..406bb8f57d --- /dev/null +++ b/test/plugin/scenarios/jdk-httpclient-scenario/pom.xml @@ -0,0 +1,128 @@ + + + + 4.0.0 + + org.apache.skywalking + jdk-httpclient-scenario + 5.0.0 + + + UTF-8 + 11 + 3.8.1 + 2.7.15 + + + skywalking-jdk-httpclient-scenario + + + + org.springframework.boot + spring-boot-starter-web + 2.7.15 + + + + com.alibaba + fastjson + 1.2.83 + + + + + jdk-httpclient-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + false + + + + + org.apache.maven.plugins + maven-jar-plugin + + + empty-javadoc-jar + package + + jar + + + javadoc + ${basedir}/javadoc + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/jdk-httpclient-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/jdk-httpclient-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..3ab23573ab --- /dev/null +++ b/test/plugin/scenarios/jdk-httpclient-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ./target/jdk-httpclient-scenario.jar + ./libs + 0775 + + + \ No newline at end of file diff --git a/test/plugin/scenarios/jdk-httpclient-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/Application.java b/test/plugin/scenarios/jdk-httpclient-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/Application.java new file mode 100644 index 0000000000..5147a224ee --- /dev/null +++ b/test/plugin/scenarios/jdk-httpclient-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/Application.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.jdk.httpclient; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/test/plugin/scenarios/jdk-httpclient-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java b/test/plugin/scenarios/jdk-httpclient-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java new file mode 100644 index 0000000000..25d4f4e6b9 --- /dev/null +++ b/test/plugin/scenarios/jdk-httpclient-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.jdk.httpclient.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +@RestController +@RequestMapping("/case") +public class CaseController { + + @GetMapping("/healthCheck") + public String healthCheck() { + return "Success"; + } + + @GetMapping("/jdk-httpclient-scenario-case") + public String testCase() throws InterruptedException, IOException { + HttpClient client = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(10)) + .build(); + + String json = "{\n" + + " \"username\": \"Alice\",\n" + + " \"password\": 21231231231\n" + + "}"; + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/jdk-httpclient-scenario/user/login")) + .timeout(Duration.ofSeconds(10)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(json)) + .build(); + + client.send(request, HttpResponse.BodyHandlers.ofString()); + + HttpRequest asyncRequest = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:8080/jdk-httpclient-scenario/user/asyncLogin")) + .timeout(Duration.ofSeconds(10)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(json)) + .build(); + CompletableFuture> future = client.sendAsync(asyncRequest, HttpResponse.BodyHandlers.ofString()); + future.join(); + return "success"; + } +} diff --git a/test/plugin/scenarios/jdk-httpclient-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/UserController.java b/test/plugin/scenarios/jdk-httpclient-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/UserController.java new file mode 100644 index 0000000000..a9665ffcb6 --- /dev/null +++ b/test/plugin/scenarios/jdk-httpclient-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/UserController.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.jdk.httpclient.controller; + +import com.alibaba.fastjson.JSONObject; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import test.apache.skywalking.apm.testcase.jdk.httpclient.dto.UserLoginDTO; + +@RestController +@RequestMapping("/user") +public class UserController { + @PostMapping("/login") + public JSONObject login(@RequestBody UserLoginDTO userLoginDTO) { + JSONObject resultJson = new JSONObject(); + resultJson.put("code", 200); + resultJson.put("message", "success"); + return resultJson; + } + + @PostMapping("/asyncLogin") + public JSONObject asyncLogin(@RequestBody UserLoginDTO userLoginDTO) { + JSONObject resultJson = new JSONObject(); + resultJson.put("code", 200); + resultJson.put("message", "success"); + return resultJson; + } +} diff --git a/test/plugin/scenarios/jdk-httpclient-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/dto/UserLoginDTO.java b/test/plugin/scenarios/jdk-httpclient-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/dto/UserLoginDTO.java new file mode 100644 index 0000000000..1226f82d2d --- /dev/null +++ b/test/plugin/scenarios/jdk-httpclient-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/dto/UserLoginDTO.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.jdk.httpclient.dto; + +public class UserLoginDTO { + private String username; + + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/test/plugin/scenarios/jdk-httpclient-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/jdk-httpclient-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000..2bed23f8ca --- /dev/null +++ b/test/plugin/scenarios/jdk-httpclient-scenario/src/main/resources/application.yaml @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +server: + port: 8080 + servlet: + context-path: /jdk-httpclient-scenario + diff --git a/test/plugin/scenarios/jdk-httpclient-scenario/support-version.list b/test/plugin/scenarios/jdk-httpclient-scenario/support-version.list new file mode 100644 index 0000000000..feef03cdea --- /dev/null +++ b/test/plugin/scenarios/jdk-httpclient-scenario/support-version.list @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +all \ No newline at end of file diff --git a/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/bin/startup.sh b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/bin/startup.sh new file mode 100644 index 0000000000..ac52eb1f0c --- /dev/null +++ b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/jdk-virtual-thread-executor-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/config/expectedData.yaml b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..f2ccbf3ed1 --- /dev/null +++ b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/config/expectedData.yaml @@ -0,0 +1,101 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: + - serviceName: jdk-virtual-thread-executor-scenario + segmentSize: ge 0 + segments: + - segmentId: not null + spans: + - operationName: GET:/case + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 14 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/virtual-thread-executor-scenario/case'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + - segmentId: not null + spans: + - operationName: /apache/skywalking + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: github.com:443 + skipAnalysis: false + tags: + - {key: url, value: 'https://github.com/apache/skywalking'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + - operationName: java.util.concurrent.ThreadPerTaskExecutor$TaskRunner.run + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 161 + isError: false + spanType: Local + skipAnalysis: false + tags: + - { key: thread.carrier, value: not null } + refs: + - { parentEndpoint: 'GET:/case', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: jdk-virtual-thread-executor-scenario, traceId: not null } + + - segmentId: not null + spans: + - operationName: /apache/skywalking + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: github.com:443 + skipAnalysis: false + tags: + - { key: url, value: 'https://github.com/apache/skywalking' } + - { key: http.method, value: GET } + - { key: http.status_code, value: '200' } + - operationName: java.util.concurrent.ThreadPerTaskExecutor$ThreadBoundFuture.run + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 161 + isError: false + spanType: Local + skipAnalysis: false + tags: + - { key: thread.carrier, value: not null } + refs: + - { parentEndpoint: 'GET:/case', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: jdk-virtual-thread-executor-scenario, traceId: not null } diff --git a/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/configuration.yml b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/configuration.yml new file mode 100644 index 0000000000..f36c72f82e --- /dev/null +++ b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/configuration.yml @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/virtual-thread-executor-scenario/case +healthCheck: http://localhost:8080/virtual-thread-executor-scenario/healthCheck +runningMode: with_bootstrap +withPlugins: apm-jdk-virtual-thread-executor-plugin-*.jar +startScript: ./bin/startup.sh diff --git a/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/pom.xml b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/pom.xml new file mode 100644 index 0000000000..cc26ef8c91 --- /dev/null +++ b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/pom.xml @@ -0,0 +1,128 @@ + + + + 4.0.0 + + org.apache.skywalking + jdk-virtual-thread-executor-scenario + 5.0.0 + + + UTF-8 + 21 + 3.8.1 + 2.7.15 + + + skywalking-jdk-virtual-thread-executor-scenario + + + + org.springframework.boot + spring-boot-starter-web + 2.7.15 + + + + org.redisson + redisson + 3.33.0 + + + + + jdk-virtual-thread-executor-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + false + + + + + org.apache.maven.plugins + maven-jar-plugin + + + empty-javadoc-jar + package + + jar + + + javadoc + ${basedir}/javadoc + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..55f39086fb --- /dev/null +++ b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ./target/jdk-virtual-thread-executor-scenario.jar + ./libs + 0775 + + + \ No newline at end of file diff --git a/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/virtualThread/Application.java b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/virtualThread/Application.java new file mode 100644 index 0000000000..57aef5d5b6 --- /dev/null +++ b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/virtualThread/Application.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.jdk.virtualThread; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @RestController + static class TestController { + private final RestTemplate restTemplate; + private final ExecutorService executorService; + + public TestController(final RestTemplate restTemplate) { + this.restTemplate = restTemplate; + this.executorService = Executors.newVirtualThreadPerTaskExecutor(); + } + + @GetMapping("/healthCheck") + public String healthCheck() { + return "Success"; + } + + @GetMapping("/case") + public String testCase() throws ExecutionException, InterruptedException { + Runnable runnable = () -> restTemplate.getForEntity("https://github.com/apache/skywalking", String.class); + executorService.execute(runnable); + + Callable callable = () -> { + restTemplate.getForEntity("https://github.com/apache/skywalking", String.class); + return "success"; + }; + executorService.submit(callable).get(); + + return "success"; + } + } +} diff --git a/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000..0ddde72c7c --- /dev/null +++ b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/src/main/resources/application.yaml @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +server: + port: 8080 + servlet: + context-path: /virtual-thread-executor-scenario + diff --git a/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/support-version.list b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/support-version.list new file mode 100644 index 0000000000..feef03cdea --- /dev/null +++ b/test/plugin/scenarios/jdk-virtual-thread-executor-scenario/support-version.list @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +all \ No newline at end of file diff --git a/test/plugin/scenarios/jedis-2.x-3.x-cluster-scenario/configuration.yml b/test/plugin/scenarios/jedis-2.x-3.x-cluster-scenario/configuration.yml index e37ee9af2f..79a60472da 100644 --- a/test/plugin/scenarios/jedis-2.x-3.x-cluster-scenario/configuration.yml +++ b/test/plugin/scenarios/jedis-2.x-3.x-cluster-scenario/configuration.yml @@ -23,19 +23,19 @@ environment: - SW_PLUGIN_JEDIS_TRACE_REDIS_PARAMETERS=true dependencies: redis-server1: - image: bitnami/redis-cluster:7.0 + image: bitnamilegacy/redis-cluster:7.0 hostname: redis-server1 environment: - ALLOW_EMPTY_PASSWORD=true - REDIS_NODES=redis-server1 redis-server2 redis-server3 redis-server2: - image: bitnami/redis-cluster:7.0 + image: bitnamilegacy/redis-cluster:7.0 hostname: redis-server2 environment: - ALLOW_EMPTY_PASSWORD=true - REDIS_NODES=redis-server1 redis-server2 redis-server3 redis-server3: - image: bitnami/redis-cluster:7.0 + image: bitnamilegacy/redis-cluster:7.0 hostname: redis-server3 depends_on: - redis-server1 diff --git a/test/plugin/scenarios/jedis-4.x-scenario/support-version.list b/test/plugin/scenarios/jedis-4.x-scenario/support-version.list index 31258966c3..7545c1ec00 100644 --- a/test/plugin/scenarios/jedis-4.x-scenario/support-version.list +++ b/test/plugin/scenarios/jedis-4.x-scenario/support-version.list @@ -19,3 +19,4 @@ 4.2.3 4.1.1 4.0.1 +5.2.0 diff --git a/test/plugin/scenarios/jersey-3.x-scenario/support-version.list b/test/plugin/scenarios/jersey-3.x-scenario/support-version.list index bf93530913..ac6d65588a 100644 --- a/test/plugin/scenarios/jersey-3.x-scenario/support-version.list +++ b/test/plugin/scenarios/jersey-3.x-scenario/support-version.list @@ -15,4 +15,4 @@ # limitations under the License. 3.0.0 -3.1.1 +3.1.12 diff --git a/test/plugin/scenarios/jetty-11.x-scenario/support-version.list b/test/plugin/scenarios/jetty-11.x-scenario/support-version.list index 140bec1629..ad7671ecf9 100644 --- a/test/plugin/scenarios/jetty-11.x-scenario/support-version.list +++ b/test/plugin/scenarios/jetty-11.x-scenario/support-version.list @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -11.0.0 -11.0.7 -11.0.15 +11.0.26 diff --git a/test/plugin/scenarios/jetty-11.x-thread-pool-scenario/config/expectedData.yaml b/test/plugin/scenarios/jetty-11.x-thread-pool-scenario/config/expectedData.yaml index d911caaa10..ff04156c69 100644 --- a/test/plugin/scenarios/jetty-11.x-thread-pool-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/jetty-11.x-thread-pool-scenario/config/expectedData.yaml @@ -15,7 +15,7 @@ # limitations under the License. meterItems: - serviceName: jetty-11.x-thread-pool-scenario - meterSize: 5 + meterSize: ge 5 meters: - meterId: name: thread_pool diff --git a/test/plugin/scenarios/jetty-12.x-scenario/bin/startup.sh b/test/plugin/scenarios/jetty-12.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..ea5bec3c39 --- /dev/null +++ b/test/plugin/scenarios/jetty-12.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/jetty-12.x-scenario.jar & diff --git a/test/plugin/scenarios/jetty-12.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/jetty-12.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..b3e44c9df3 --- /dev/null +++ b/test/plugin/scenarios/jetty-12.x-scenario/config/expectedData.yaml @@ -0,0 +1,86 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: + - serviceName: jetty-12.x-scenario + segmentSize: gt 2 + segments: + - segmentId: not null + spans: + - operationName: /jetty-12.x-scenario/case/case1 + parentSpanId: '-1' + spanId: '0' + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: '19' + isError: 'false' + spanType: Entry + peer: '' + skipAnalysis: 'false' + tags: + - key: url + value: http://localhost:18080/jetty-12.x-scenario/case/case1 + - key: http.method + value: GET + - key: http.status_code + value: '200' + refs: + - parentEndpoint: /jetty-12.x-scenario/case/jetty-12.x-scenario + networkAddress: localhost:18080 + refType: CrossProcess + parentSpanId: '1' + parentTraceSegmentId: not null + parentServiceInstance: not null + parentService: jetty-12.x-scenario + traceId: not null + - segmentId: not null + spans: + - operationName: /jetty-12.x-scenario/case/case1 + parentSpanId: '0' + spanId: '1' + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: '2' + isError: 'false' + spanType: Exit + peer: localhost:18080 + skipAnalysis: 'false' + tags: + - key: url + value: http://localhost:18080/jetty-12.x-scenario/case/case1 + - key: http.method + value: GET + - key: http.status_code + value: '200' + - operationName: /jetty-12.x-scenario/case/jetty-12.x-scenario + parentSpanId: '-1' + spanId: '0' + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: '19' + isError: 'false' + spanType: Entry + peer: '' + skipAnalysis: 'false' + tags: + - key: url + value: http://localhost:18080/jetty-12.x-scenario/case/jetty-12.x-scenario + - key: http.method + value: GET + - key: http.status_code + value: '200' diff --git a/test/plugin/scenarios/jetty-12.x-scenario/configuration.yml b/test/plugin/scenarios/jetty-12.x-scenario/configuration.yml new file mode 100644 index 0000000000..2db90d0c99 --- /dev/null +++ b/test/plugin/scenarios/jetty-12.x-scenario/configuration.yml @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:18080/jetty-12.x-scenario/case/jetty-12.x-scenario +healthCheck: http://localhost:18080/jetty-12.x-scenario/case/healthCheck +startScript: ./bin/startup.sh +environment: +dependencies: diff --git a/test/plugin/scenarios/jetty-12.x-scenario/pom.xml b/test/plugin/scenarios/jetty-12.x-scenario/pom.xml new file mode 100644 index 0000000000..6a28e254bf --- /dev/null +++ b/test/plugin/scenarios/jetty-12.x-scenario/pom.xml @@ -0,0 +1,128 @@ + + + + + org.apache.skywalking.apm.testcase + jetty-12.x-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 17 + 3.8.1 + + 12.0.36 + + 2.6.2 + + + skywalking-jetty-12.x-scenario + + + + org.eclipse.jetty + jetty-server + ${test.framework.version} + + + + org.apache.httpcomponents + httpclient + 4.3 + + + + + + jetty-12.x-scenario + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + org.apache.skywalking.apm.testcase.jetty12xserver.Application + + + + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + diff --git a/test/plugin/scenarios/jetty-12.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/jetty-12.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..4be83c4cb7 --- /dev/null +++ b/test/plugin/scenarios/jetty-12.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/jetty-12.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/jetty-12.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/jetty12xserver/Application.java b/test/plugin/scenarios/jetty-12.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/jetty12xserver/Application.java new file mode 100644 index 0000000000..aecb666125 --- /dev/null +++ b/test/plugin/scenarios/jetty-12.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/jetty12xserver/Application.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.jetty12xserver; + +import org.apache.skywalking.apm.testcase.jetty12xserver.handler.CaseHandler; +import org.eclipse.jetty.server.Server; + +/** + * Embedded Jetty 12 server. Jetty 12 handling routes every request through + * {@code org.eclipse.jetty.server.Server#handle(Request, Response, Callback)} — the method the + * jetty-server-12.x plugin enhances — before delegating to the top-level {@link CaseHandler}. + */ +public class Application { + + public static void main(String[] args) throws Exception { + Server server = new Server(18080); + server.setHandler(new CaseHandler()); + server.start(); + server.join(); + } +} diff --git a/test/plugin/scenarios/jetty-12.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/jetty12xserver/handler/CaseHandler.java b/test/plugin/scenarios/jetty-12.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/jetty12xserver/handler/CaseHandler.java new file mode 100644 index 0000000000..201ab06792 --- /dev/null +++ b/test/plugin/scenarios/jetty-12.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/jetty12xserver/handler/CaseHandler.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.jetty12xserver.handler; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import org.apache.http.HttpEntity; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +/** + * Top-level Jetty 12 core handler. + * + * The entry service {@code /jetty-12.x-scenario/case/jetty-12.x-scenario} makes a nested internal + * call to {@code /jetty-12.x-scenario/case/case1} on the same server. This exercises both sides of + * the jetty-server-12.x plugin in one trace: the entry span for the inbound request, the HttpClient + * exit span for the outbound call (inject), and a second entry span on {@code case1} that extracts + * the propagated context into a cross-process ref (the receiving/extract side). The health check + * {@code /case/healthCheck} and the nested {@code /case/case1} endpoint simply return 200. + */ +public class CaseHandler extends Handler.Abstract { + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + String path = request.getHttpURI().getPath(); + if (path != null && path.endsWith("/case/jetty-12.x-scenario")) { + visit("http://localhost:18080/jetty-12.x-scenario/case/case1"); + } + response.setStatus(200); + response.write(true, ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8)), callback); + return true; + } + + private static void visit(String url) throws Exception { + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpGet httpget = new HttpGet(url); + ResponseHandler responseHandler = response -> { + HttpEntity entity = response.getEntity(); + return entity != null ? EntityUtils.toString(entity) : null; + }; + httpClient.execute(httpget, responseHandler); + } + } +} diff --git a/test/plugin/scenarios/jetty-12.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/jetty-12.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..b5cda5ae8a --- /dev/null +++ b/test/plugin/scenarios/jetty-12.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/test/plugin/scenarios/jetty-12.x-scenario/support-version.list b/test/plugin/scenarios/jetty-12.x-scenario/support-version.list new file mode 100644 index 0000000000..6c62f269a7 --- /dev/null +++ b/test/plugin/scenarios/jetty-12.x-scenario/support-version.list @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +12.0.36 +12.1.10 diff --git a/test/plugin/scenarios/jetty-scenario/jettyclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyclient/controller/CaseController.java b/test/plugin/scenarios/jetty-scenario/jettyclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyclient/controller/CaseController.java index d45d8adfed..4e31db9383 100644 --- a/test/plugin/scenarios/jetty-scenario/jettyclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyclient/controller/CaseController.java +++ b/test/plugin/scenarios/jetty-scenario/jettyclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyclient/controller/CaseController.java @@ -16,7 +16,7 @@ * */ -package org.apache.skywalking.apm.testcase.jettyclient.contr; +package org.apache.skywalking.apm.testcase.jettyclient.controller; import javax.annotation.PostConstruct; import java.io.IOException; @@ -26,6 +26,8 @@ import org.apache.http.impl.client.HttpClients; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.client.util.BufferingResponseListener; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Controller; @@ -51,13 +53,19 @@ public void init() throws Exception { @ResponseBody public String jettyClientScenario() throws Exception { client.newRequest("http://" + jettyServerHost + ":18080/jettyserver-case/case/receiveContext-0").send(); - Response.CompleteListener listener = result -> { - CloseableHttpClient httpclient = HttpClients.createDefault(); - HttpGet httpget = new HttpGet("http://" + jettyServerHost + ":18080/jettyserver-case/case/receiveContext-0"); - try { - httpclient.execute(httpget); - } catch (IOException e) { - throw new RuntimeException(e); + Response.Listener listener = new BufferingResponseListener() { + public void onComplete(Result result) { + byte[] bytes = this.getContent(); + if (bytes == null || bytes.length == 0) { + throw new RuntimeException("content cant be empty"); + } + CloseableHttpClient httpclient = HttpClients.createDefault(); + HttpGet httpget = new HttpGet("http://" + jettyServerHost + ":18080/jettyserver-case/case/receiveContext-0"); + try { + httpclient.execute(httpget); + } catch (IOException e) { + throw new RuntimeException(e); + } } }; client.newRequest("http://" + jettyServerHost + ":18080/jettyserver-case/case/receiveContext-1").send(listener); diff --git a/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/servlet/AsyncCaseServlet.java b/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/servlet/AsyncCaseServlet.java index 667fdafd16..4075fc0e34 100644 --- a/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/servlet/AsyncCaseServlet.java +++ b/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/servlet/AsyncCaseServlet.java @@ -19,6 +19,7 @@ package org.apache.skywalking.apm.testcase.jettyserver.servlet; import java.io.IOException; +import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -31,6 +32,9 @@ public class AsyncCaseServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { Thread.sleep(2000); + resp.setContentType("text/plain;charset=UTF-8"); + PrintWriter out = resp.getWriter(); + out.print("Success"); } catch (InterruptedException e) { } } diff --git a/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/servlet/CaseServlet.java b/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/servlet/CaseServlet.java index 9dfae15f64..22b6eeac90 100644 --- a/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/servlet/CaseServlet.java +++ b/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/servlet/CaseServlet.java @@ -19,6 +19,7 @@ package org.apache.skywalking.apm.testcase.jettyserver.servlet; import java.io.IOException; +import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -31,6 +32,9 @@ public class CaseServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { Thread.sleep(2000); + resp.setContentType("text/plain;charset=UTF-8"); + PrintWriter out = resp.getWriter(); + out.print("Success"); } catch (InterruptedException e) { } } diff --git a/test/plugin/scenarios/jetty-thread-pool-scenario/config/expectedData.yaml b/test/plugin/scenarios/jetty-thread-pool-scenario/config/expectedData.yaml index 00b0074a84..276fa97406 100644 --- a/test/plugin/scenarios/jetty-thread-pool-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/jetty-thread-pool-scenario/config/expectedData.yaml @@ -15,7 +15,7 @@ # limitations under the License. meterItems: - serviceName: jetty-thread-pool-scenario - meterSize: 5 + meterSize: ge 5 meters: - meterId: name: thread_pool diff --git a/test/plugin/scenarios/kafka-scenario/configuration.yml b/test/plugin/scenarios/kafka-scenario/configuration.yml index 5a74c2ba42..7db52f161f 100644 --- a/test/plugin/scenarios/kafka-scenario/configuration.yml +++ b/test/plugin/scenarios/kafka-scenario/configuration.yml @@ -28,7 +28,7 @@ dependencies: image: zookeeper:3.4 hostname: zookeeper-server kafka-server: - image: bitnami/kafka:2.1.1 + image: bitnamilegacy/kafka:2.4.1 hostname: kafka-server environment: - KAFKA_ZOOKEEPER_CONNECT=zookeeper-server:2181 diff --git a/test/plugin/scenarios/kafka-scenario/src/main/java/test/apache/skywalking/apm/testcase/kafka/controller/CaseController.java b/test/plugin/scenarios/kafka-scenario/src/main/java/test/apache/skywalking/apm/testcase/kafka/controller/CaseController.java index ea8bb6e3c1..ee6458bdc1 100644 --- a/test/plugin/scenarios/kafka-scenario/src/main/java/test/apache/skywalking/apm/testcase/kafka/controller/CaseController.java +++ b/test/plugin/scenarios/kafka-scenario/src/main/java/test/apache/skywalking/apm/testcase/kafka/controller/CaseController.java @@ -24,6 +24,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.Collection; import java.util.regex.Pattern; import java.util.List; import java.util.ArrayList; @@ -32,10 +33,10 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; +import org.apache.kafka.clients.consumer.ConsumerRebalanceListener; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; -import org.apache.kafka.clients.consumer.internals.NoOpConsumerRebalanceListener; import org.apache.kafka.clients.producer.Callback; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; @@ -270,7 +271,17 @@ public void run() { consumerProperties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); consumerProperties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); KafkaConsumer consumer = new KafkaConsumer<>(consumerProperties); - consumer.subscribe(topicPattern, new NoOpConsumerRebalanceListener()); + consumer.subscribe(topicPattern, new ConsumerRebalanceListener() { + @Override + public void onPartitionsRevoked(Collection collection) { + + } + + @Override + public void onPartitionsAssigned(Collection collection) { + + } + }); while (true) { if (pollAndInvoke(consumer)) break; } diff --git a/test/plugin/scenarios/kafka-scenario/support-version.list b/test/plugin/scenarios/kafka-scenario/support-version.list index 1ffc63cb11..f59368c253 100644 --- a/test/plugin/scenarios/kafka-scenario/support-version.list +++ b/test/plugin/scenarios/kafka-scenario/support-version.list @@ -28,3 +28,7 @@ 3.0.2 3.1.2 3.2.3 +3.6.0 +3.7.0 +3.7.1 +3.9.1 \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/resources/skywalking-plugin.def b/test/plugin/scenarios/lettuce-6.5.x-scenario/bin/startup.sh similarity index 79% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/resources/skywalking-plugin.def rename to test/plugin/scenarios/lettuce-6.5.x-scenario/bin/startup.sh index 915c014315..3ef5c866ff 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-9.x-plugin/src/main/resources/skywalking-plugin.def +++ b/test/plugin/scenarios/lettuce-6.5.x-scenario/bin/startup.sh @@ -1,3 +1,5 @@ +#!/bin/bash +# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information @@ -14,5 +16,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -jetty-server-9.x=org.apache.skywalking.apm.plugin.jetty.v9.server.define.JettyInstrumentation -jetty-server-9.x=org.apache.skywalking.apm.plugin.jetty.v9.server.define.DispatcherInstrumentation +home="$(cd "$(dirname $0)"; pwd)" + +java -Dredis.host=${REDIS_SERVERS} -jar -Dskywalking.plugin.lettuce.trace_redis_parameters=true ${agent_opts} ${home}/../libs/lettuce-6.5.x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-6.5.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/lettuce-6.5.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..dbeb6b6138 --- /dev/null +++ b/test/plugin/scenarios/lettuce-6.5.x-scenario/config/expectedData.yaml @@ -0,0 +1,206 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: + - serviceName: lettuce-6.5.x-scenario + segmentSize: nq 0 + segments: + - segmentId: not null + spans: + - operationName: HEAD:/lettuce-6.5.x-scenario/case/healthCheck + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 1 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/lettuce-6.5.x-scenario/case/healthCheck'} + - {key: http.method, value: HEAD} + - {key: http.status_code, value: '200'} + - segmentId: not null + spans: + - operationName: Lettuce/SET + parentSpanId: 0 + spanId: 1 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key0 } + - { key: cache.cmd, value: SET } + - { key: cache.op, value: write } + - operationName: RedisReactive/local + parentSpanId: -1 + spanId: 0 + isError: false + spanType: Local + refs: + - { parentEndpoint: GET:/lettuce-6.5.x-scenario/case/lettuce-case, networkAddress: '', refType: CrossThread, + parentSpanId: 5, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + - segmentId: not null + spans: + - operationName: Lettuce/SET + parentSpanId: 0 + spanId: 1 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key1 } + - { key: cache.cmd, value: SET } + - { key: cache.op, value: write } + - operationName: RedisReactive/local + parentSpanId: -1 + spanId: 0 + isError: false + spanType: Local + refs: + - { parentEndpoint: GET:/lettuce-6.5.x-scenario/case/lettuce-case, networkAddress: '', refType: CrossThread, + parentSpanId: 6, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + - segmentId: not null + spans: + - operationName: Lettuce/GET + parentSpanId: 0 + spanId: 1 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: cache.type, value: Redis} + - {key: cache.key, value: key} + - {key: cache.cmd, value: GET} + - {key: cache.op, value: read} + - operationName: Lettuce/SET + parentSpanId: 0 + spanId: 2 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key0 } + - { key: cache.cmd, value: SET } + - { key: cache.op, value: write } + - operationName: Lettuce/SET + parentSpanId: 0 + spanId: 3 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key1 } + - { key: cache.cmd, value: SET } + - { key: cache.op, value: write } + - operationName: Lettuce/Reactive/createMono + parentSpanId: 0 + spanId: 4 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Local + skipAnalysis: false + - operationName: Lettuce/Reactive/createMono + parentSpanId: 0 + spanId: 5 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Local + skipAnalysis: false + - operationName: Lettuce/Reactive/createMono + parentSpanId: 0 + spanId: 6 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Local + skipAnalysis: false + - operationName: Lettuce/GET + parentSpanId: 7 + spanId: 8 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key } + - { key: cache.cmd, value: GET } + - { key: cache.op, value: read } + - operationName: RedisReactive/local + parentSpanId: 0 + spanId: 7 + isError: false + spanType: Local + - operationName: GET:/lettuce-6.5.x-scenario/case/lettuce-case + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 1 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/lettuce-6.5.x-scenario/case/lettuce-case'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} diff --git a/test/plugin/scenarios/lettuce-6.5.x-scenario/configuration.yml b/test/plugin/scenarios/lettuce-6.5.x-scenario/configuration.yml new file mode 100644 index 0000000000..6ddf05ae02 --- /dev/null +++ b/test/plugin/scenarios/lettuce-6.5.x-scenario/configuration.yml @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/lettuce-6.5.x-scenario/case/lettuce-case +healthCheck: http://localhost:8080/lettuce-6.5.x-scenario/case/healthCheck +startScript: ./bin/startup.sh +environment: + - REDIS_SERVERS=redis-server:6379 +depends_on: + - redis-server +dependencies: + redis-server: + image: redis:3.2.9-alpine + hostname: redis-server diff --git a/test/plugin/scenarios/lettuce-6.5.x-scenario/pom.xml b/test/plugin/scenarios/lettuce-6.5.x-scenario/pom.xml new file mode 100644 index 0000000000..248aded74a --- /dev/null +++ b/test/plugin/scenarios/lettuce-6.5.x-scenario/pom.xml @@ -0,0 +1,136 @@ + + + + 4.0.0 + + org.apache.skywalking + lettuce-6.5.x-scenario + 1.0.0 + + + UTF-8 + 1.8 + 3.8.1 + 6.5.0.RELEASE + ${test.framework.version} + 2.6.2 + 4.3.8.RELEASE + 2.1.6.RELEASE + 3.4.2 + + + lettuce-6.5.x-scenario + + + + com.squareup.okhttp3 + okhttp + ${okhttp.version} + + + io.lettuce + lettuce-core + ${test.framework.version} + + + + org.springframework.boot + spring-boot-starter + ${spring.boot.version} + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j.version} + + + org.apache.logging.log4j + log4j-jcl + ${log4j.version} + + + org.springframework.boot + spring-boot-starter-tomcat + ${spring.boot.version} + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + + lettuce-6.5.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..a48d54c644 --- /dev/null +++ b/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/lettuce-6.5.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/Application.java b/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/Application.java new file mode 100644 index 0000000000..f96b624013 --- /dev/null +++ b/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/Application.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.lettuce; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceController.java b/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceController.java new file mode 100644 index 0000000000..d2cc201025 --- /dev/null +++ b/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceController.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.lettuce.controller; + +import io.lettuce.core.LettuceFutures; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisFuture; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.async.RedisAsyncCommands; +import io.lettuce.core.api.reactive.RedisReactiveCommands; +import io.lettuce.core.api.sync.RedisCommands; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Controller +@RequestMapping("/case") +@PropertySource("classpath:application.properties") +public class LettuceController { + + @Value("${redis.servers:127.0.0.1:6379}") + private String address; + + @RequestMapping("/lettuce-case") + @ResponseBody + public String lettuceCase() { + RedisClient redisClient = RedisClient.create("redis://" + address); + StatefulRedisConnection connection0 = redisClient.connect(); + RedisCommands syncCommand = connection0.sync(); + syncCommand.get("key"); + + StatefulRedisConnection connection1 = redisClient.connect(); + RedisAsyncCommands asyncCommands = connection1.async(); + asyncCommands.setAutoFlushCommands(false); + List> futures = new ArrayList<>(); + futures.add(asyncCommands.set("key0", "value0")); + futures.add(asyncCommands.set("key1", "value1")); + asyncCommands.flushCommands(); + LettuceFutures.awaitAll(5, TimeUnit.SECONDS, futures.toArray(new RedisFuture[futures.size()])); + + StatefulRedisConnection connection2 = redisClient.connect(); + RedisReactiveCommands reactiveCommands = connection2.reactive(); + + Mono result = reactiveCommands.get("key") + .then(Flux.concat( + reactiveCommands.set("key0", "value0"), + reactiveCommands.set("key1", "value1") + ).then()) + .thenReturn("Success"); + + result.block(); + + connection0.close(); + connection1.close(); + connection2.close(); + redisClient.shutdown(); + return "Success"; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() { + return "healthCheck"; + } +} diff --git a/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/resources/application.properties b/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/resources/application.properties new file mode 100644 index 0000000000..0b168c7fdd --- /dev/null +++ b/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/resources/application.properties @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server.port=8080 +server.servlet.context-path=/lettuce-6.5.x-scenario \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..9849ed5a8a --- /dev/null +++ b/test/plugin/scenarios/lettuce-6.5.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-6.5.x-scenario/support-version.list b/test/plugin/scenarios/lettuce-6.5.x-scenario/support-version.list new file mode 100644 index 0000000000..df1aa5eb09 --- /dev/null +++ b/test/plugin/scenarios/lettuce-6.5.x-scenario/support-version.list @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +6.5.0.RELEASE +6.5.5.RELEASE +6.6.0.RELEASE +6.7.1.RELEASE \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-scenario/config/expectedData.yaml b/test/plugin/scenarios/lettuce-scenario/config/expectedData.yaml index 43db792872..884191cfe3 100644 --- a/test/plugin/scenarios/lettuce-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/lettuce-scenario/config/expectedData.yaml @@ -34,6 +34,60 @@ segmentItems: - {key: url, value: 'http://localhost:8080/lettuce-scenario/case/healthCheck'} - {key: http.method, value: HEAD} - {key: http.status_code, value: '200'} + - segmentId: not null + spans: + - operationName: Lettuce/SET + parentSpanId: 0 + spanId: 1 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key0 } + - { key: cache.cmd, value: SET } + - { key: cache.op, value: write } + - operationName: RedisReactive/local + parentSpanId: -1 + spanId: 0 + isError: false + spanType: Local + refs: + - { parentEndpoint: GET:/lettuce-scenario/case/lettuce-case, networkAddress: '', refType: CrossThread, + parentSpanId: 5, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + - segmentId: not null + spans: + - operationName: Lettuce/SET + parentSpanId: 0 + spanId: 1 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key1 } + - { key: cache.cmd, value: SET } + - { key: cache.op, value: write } + - operationName: RedisReactive/local + parentSpanId: -1 + spanId: 0 + isError: false + spanType: Local + refs: + - { parentEndpoint: GET:/lettuce-scenario/case/lettuce-case, networkAddress: '', refType: CrossThread, + parentSpanId: 6, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } - segmentId: not null spans: - operationName: Lettuce/GET @@ -84,6 +138,57 @@ segmentItems: - { key: cache.key, value: key1 } - { key: cache.cmd, value: SET } - { key: cache.op, value: write } + - operationName: Lettuce/Reactive/createMono + parentSpanId: 0 + spanId: 4 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Local + skipAnalysis: false + - operationName: Lettuce/Reactive/createMono + parentSpanId: 0 + spanId: 5 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Local + skipAnalysis: false + - operationName: Lettuce/Reactive/createMono + parentSpanId: 0 + spanId: 6 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Local + skipAnalysis: false + - operationName: Lettuce/GET + parentSpanId: 7 + spanId: 8 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key } + - { key: cache.cmd, value: GET } + - { key: cache.op, value: read } + - operationName: RedisReactive/local + parentSpanId: 0 + spanId: 7 + isError: false + spanType: Local - operationName: GET:/lettuce-scenario/case/lettuce-case parentSpanId: -1 spanId: 0 diff --git a/test/plugin/scenarios/lettuce-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceController.java b/test/plugin/scenarios/lettuce-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceController.java index c2f391b07c..d2cc201025 100644 --- a/test/plugin/scenarios/lettuce-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceController.java +++ b/test/plugin/scenarios/lettuce-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceController.java @@ -23,6 +23,7 @@ import io.lettuce.core.RedisFuture; import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.async.RedisAsyncCommands; +import io.lettuce.core.api.reactive.RedisReactiveCommands; import io.lettuce.core.api.sync.RedisCommands; import java.util.ArrayList; import java.util.List; @@ -32,6 +33,8 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; @Controller @RequestMapping("/case") @@ -58,8 +61,21 @@ public String lettuceCase() { asyncCommands.flushCommands(); LettuceFutures.awaitAll(5, TimeUnit.SECONDS, futures.toArray(new RedisFuture[futures.size()])); + StatefulRedisConnection connection2 = redisClient.connect(); + RedisReactiveCommands reactiveCommands = connection2.reactive(); + + Mono result = reactiveCommands.get("key") + .then(Flux.concat( + reactiveCommands.set("key0", "value0"), + reactiveCommands.set("key1", "value1") + ).then()) + .thenReturn("Success"); + + result.block(); + connection0.close(); connection1.close(); + connection2.close(); redisClient.shutdown(); return "Success"; } diff --git a/test/plugin/scenarios/lettuce-scenario/support-version.list b/test/plugin/scenarios/lettuce-scenario/support-version.list index add9033ee2..88e41e9ab3 100644 --- a/test/plugin/scenarios/lettuce-scenario/support-version.list +++ b/test/plugin/scenarios/lettuce-scenario/support-version.list @@ -17,4 +17,5 @@ 5.2.1.RELEASE 5.1.8.RELEASE 5.0.5.RELEASE -6.1.4.RELEASE \ No newline at end of file +6.1.4.RELEASE +6.4.2.RELEASE \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/resources/skywalking-plugin.def b/test/plugin/scenarios/lettuce-webflux-5x-scenario/bin/startup.sh similarity index 79% rename from apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/resources/skywalking-plugin.def rename to test/plugin/scenarios/lettuce-webflux-5x-scenario/bin/startup.sh index a7828a8f4b..d719f14162 100644 --- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-server-11.x-plugin/src/main/resources/skywalking-plugin.def +++ b/test/plugin/scenarios/lettuce-webflux-5x-scenario/bin/startup.sh @@ -1,3 +1,5 @@ +#!/bin/bash +# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information @@ -14,5 +16,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -jetty-server-11.x=org.apache.skywalking.apm.plugin.jetty.v11.server.define.JettyInstrumentation -jetty-server-11.x=org.apache.skywalking.apm.plugin.jetty.v11.server.define.DispatcherInstrumentation +home="$(cd "$(dirname $0)"; pwd)" + +java -Dredis.host=${REDIS_SERVERS} -jar -Dskywalking.plugin.lettuce.trace_redis_parameters=true ${agent_opts} ${home}/../libs/lettuce-webflux-5x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-webflux-5x-scenario/config/expectedData.yaml b/test/plugin/scenarios/lettuce-webflux-5x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..cdc04a6242 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-5x-scenario/config/expectedData.yaml @@ -0,0 +1,134 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: + - serviceName: lettuce-webflux-5x-scenario + segmentSize: nq 0 + segments: + - segmentId: not null + spans: + - operationName: /case/healthCheck + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 67 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/case/healthCheck'} + - {key: http.method, value: HEAD} + - {key: http.status_code, value: '200'} + - segmentId: not null + spans: + - operationName: Lettuce/GET + parentSpanId: 0 + spanId: 1 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - {key: cache.type, value: Redis} + - {key: cache.key, value: key} + - {key: cache.cmd, value: GET} + - {key: cache.op, value: read} + - operationName: RedisReactive/local + parentSpanId: -1 + spanId: 0 + isError: false + spanType: Local + refs: + - { parentEndpoint: /case/lettuce-case, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + - segmentId: not null + spans: + - operationName: Lettuce/SET + parentSpanId: 0 + spanId: 1 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key0 } + - { key: cache.cmd, value: SET } + - { key: cache.op, value: write } + - operationName: RedisReactive/local + parentSpanId: -1 + spanId: 0 + isError: false + spanType: Local + refs: + - { parentEndpoint: /case/lettuce-case, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + - segmentId: not null + spans: + - operationName: Lettuce/SET + parentSpanId: 0 + spanId: 1 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key1 } + - { key: cache.cmd, value: SET } + - { key: cache.op, value: write } + - operationName: RedisReactive/local + parentSpanId: -1 + spanId: 0 + isError: false + spanType: Local + refs: + - { parentEndpoint: /case/lettuce-case, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + - segmentId: not null + spans: + - operationName: /case/lettuce-case + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 67 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/case/lettuce-case'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} diff --git a/test/plugin/scenarios/lettuce-webflux-5x-scenario/configuration.yml b/test/plugin/scenarios/lettuce-webflux-5x-scenario/configuration.yml new file mode 100644 index 0000000000..d016d55400 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-5x-scenario/configuration.yml @@ -0,0 +1,30 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/case/lettuce-case +healthCheck: http://localhost:8080/case/healthCheck +startScript: ./bin/startup.sh +withPlugins: apm-spring-webflux-5.x-*.jar +runningMode: with_optional +environment: + - REDIS_SERVERS=redis-server:6379 +depends_on: + - redis-server +dependencies: + redis-server: + image: redis:3.2.9-alpine + hostname: redis-server diff --git a/test/plugin/scenarios/lettuce-webflux-5x-scenario/pom.xml b/test/plugin/scenarios/lettuce-webflux-5x-scenario/pom.xml new file mode 100644 index 0000000000..bf393539a2 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-5x-scenario/pom.xml @@ -0,0 +1,98 @@ + + + + 4.0.0 + + org.apache.skywalking + lettuce-webflux-5x-scenario + 5.0.0 + + + UTF-8 + 1.8 + 3.8.1 + 5.1.8.RELEASE + ${test.framework.version} + 2.1.6.RELEASE + + + skywalking-lettuce-webflux-5x-scenario + + + + io.lettuce + lettuce-core + ${test.framework.version} + + + + org.springframework.boot + spring-boot-starter-webflux + ${spring.boot.version} + + + + + lettuce-webflux-5x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..ffc45011aa --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/lettuce-webflux-5x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/Application.java b/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/Application.java new file mode 100644 index 0000000000..dae1948397 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/Application.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.lettuce; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + try { + SpringApplication.run(Application.class, args); + } catch (Exception e) { + // Never do this + } + } +} diff --git a/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/RedisClientConfig.java b/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/RedisClientConfig.java new file mode 100644 index 0000000000..0898d08338 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/RedisClientConfig.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.lettuce; + +import io.lettuce.core.RedisClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * + */ +@Configuration +public class RedisClientConfig { + + @Bean(destroyMethod = "shutdown") + public RedisClient redisClient(@Value("${redis.servers:127.0.0.1:6379}") String address) { + + return RedisClient.create("redis://" + address); + } +} diff --git a/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceReactiveController.java b/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceReactiveController.java new file mode 100644 index 0000000000..2664bd2713 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceReactiveController.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.lettuce.controller; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.api.reactive.RedisReactiveCommands; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import javax.annotation.Resource; + +@RestController +@RequestMapping("/case") +@PropertySource("classpath:application.properties") +public class LettuceReactiveController { + + @Value("${redis.servers:127.0.0.1:6379}") + private String address; + + @Resource + private RedisClient redisClient; + + @GetMapping("/lettuce-case") + public Mono lettuceCase() { + + return Mono.usingWhen( + Mono.fromCallable(() -> redisClient.connect()), + connection -> { + RedisReactiveCommands cmd = connection.reactive(); + return cmd.get("key") + .then(Flux.concat( + cmd.set("key0", "value0"), + cmd.set("key1", "value1") + ).then()) + .thenReturn("Success"); + }, + connection -> Mono.fromFuture(connection.closeAsync()), + connection -> Mono.fromFuture(connection.closeAsync()), + connection -> Mono.fromFuture(connection.closeAsync()) + ); + } + + @GetMapping("/healthCheck") + public Mono healthCheck() { + return Mono.just("healthCheck"); + } +} diff --git a/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/resources/application.properties b/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/resources/application.properties new file mode 100644 index 0000000000..d3b193de12 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/resources/application.properties @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server.port=8080 diff --git a/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..9849ed5a8a --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-5x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-webflux-5x-scenario/support-version.list b/test/plugin/scenarios/lettuce-webflux-5x-scenario/support-version.list new file mode 100644 index 0000000000..a7e848bbbf --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-5x-scenario/support-version.list @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +5.1.8.RELEASE +5.2.1.RELEASE +6.1.4.RELEASE \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-webflux-6x-scenario/bin/startup.sh b/test/plugin/scenarios/lettuce-webflux-6x-scenario/bin/startup.sh new file mode 100644 index 0000000000..686b12f52a --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-6x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -Dredis.host=${REDIS_SERVERS} -jar -Dskywalking.plugin.lettuce.trace_redis_parameters=true ${agent_opts} ${home}/../libs/lettuce-webflux-6x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-webflux-6x-scenario/config/expectedData.yaml b/test/plugin/scenarios/lettuce-webflux-6x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..e5857aa054 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-6x-scenario/config/expectedData.yaml @@ -0,0 +1,135 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: + - serviceName: lettuce-webflux-6x-scenario + segmentSize: nq 0 + segments: + - segmentId: not null + spans: + - operationName: /case/healthCheck + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 67 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - { key: url, value: 'http://localhost:8080/case/healthCheck' } + - { key: http.method, value: HEAD } + - { key: http.status_code, value: '200' } + - segmentId: not null + spans: + - operationName: Lettuce/GET + parentSpanId: 0 + spanId: 1 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key } + - { key: cache.cmd, value: GET } + - { key: cache.op, value: read } + - operationName: RedisReactive/local + parentSpanId: -1 + spanId: 0 + isError: false + spanType: Local + refs: + - { parentEndpoint: /case/lettuce-case, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + - segmentId: not null + spans: + - operationName: Lettuce/SET + parentSpanId: 0 + spanId: 1 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key0 } + - { key: cache.cmd, value: SET } + - { key: cache.op, value: write } + - operationName: RedisReactive/local + parentSpanId: -1 + spanId: 0 + isError: false + spanType: Local + refs: + - { parentEndpoint: /case/lettuce-case, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + - segmentId: not null + spans: + - operationName: Lettuce/SET + parentSpanId: 0 + spanId: 1 + spanLayer: Cache + startTime: not null + endTime: not null + componentId: 57 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: cache.type, value: Redis } + - { key: cache.key, value: key1 } + - { key: cache.cmd, value: SET } + - { key: cache.op, value: write } + - operationName: RedisReactive/local + parentSpanId: -1 + spanId: 0 + isError: false + spanType: Local + refs: + - { parentEndpoint: /case/lettuce-case, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + - segmentId: not null + spans: + - operationName: /case/lettuce-case + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 67 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - { key: url, value: 'http://localhost:8080/case/lettuce-case' } + - { key: http.method, value: GET } + - { key: http.status_code, value: '200' } + \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-webflux-6x-scenario/configuration.yml b/test/plugin/scenarios/lettuce-webflux-6x-scenario/configuration.yml new file mode 100644 index 0000000000..a3eecec7bb --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-6x-scenario/configuration.yml @@ -0,0 +1,30 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/case/lettuce-case +healthCheck: http://localhost:8080/case/healthCheck +startScript: ./bin/startup.sh +withPlugins: apm-spring-webflux-6.x-*.jar +runningMode: with_optional +environment: + - REDIS_SERVERS=redis-server:6379 +depends_on: + - redis-server +dependencies: + redis-server: + image: redis:3.2.9-alpine + hostname: redis-server diff --git a/test/plugin/scenarios/lettuce-webflux-6x-scenario/pom.xml b/test/plugin/scenarios/lettuce-webflux-6x-scenario/pom.xml new file mode 100644 index 0000000000..2e05615440 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-6x-scenario/pom.xml @@ -0,0 +1,117 @@ + + + + 4.0.0 + + org.apache.skywalking + lettuce-webflux-6x-scenario + 5.0.0 + + + UTF-8 + 17 + 3.8.1 + 6.4.2.RELEASE + ${test.framework.version} + 3.0.13 + + + skywalking-lettuce-webflux-6x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + io.lettuce + lettuce-core + ${test.framework.version} + + + + org.springframework.boot + spring-boot-starter-webflux + ${spring.boot.version} + + + org.apache.logging.log4j + log4j-api + + + + + + + + lettuce-webflux-6x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..87c9882cab --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/lettuce-webflux-6x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/Application.java b/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/Application.java new file mode 100644 index 0000000000..f96b624013 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/Application.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.lettuce; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/RedisClientConfig.java b/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/RedisClientConfig.java new file mode 100644 index 0000000000..0898d08338 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/RedisClientConfig.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.lettuce; + +import io.lettuce.core.RedisClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * + */ +@Configuration +public class RedisClientConfig { + + @Bean(destroyMethod = "shutdown") + public RedisClient redisClient(@Value("${redis.servers:127.0.0.1:6379}") String address) { + + return RedisClient.create("redis://" + address); + } +} diff --git a/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceReactiveController.java b/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceReactiveController.java new file mode 100644 index 0000000000..697369fcf8 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/java/org/apache/skywalking/apm/testcase/lettuce/controller/LettuceReactiveController.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.lettuce.controller; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.api.reactive.RedisReactiveCommands; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/case") +public class LettuceReactiveController { + + @Value("${redis.servers:127.0.0.1:6379}") + private String address; + + @Autowired + private RedisClient redisClient; + + @GetMapping("/lettuce-case") + public Mono lettuceCase() { + + return Mono.usingWhen( + Mono.fromCallable(() -> redisClient.connect()), + connection -> { + RedisReactiveCommands cmd = connection.reactive(); + return cmd.get("key") + .then(Flux.concat( + cmd.set("key0", "value0"), + cmd.set("key1", "value1") + ).then()) + .thenReturn("Success"); + }, + connection -> Mono.fromFuture(connection.closeAsync()) + ); + } + + @GetMapping("/healthCheck") + public Mono healthCheck() { + return Mono.just("healthCheck"); + } +} diff --git a/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/resources/application.properties b/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/resources/application.properties new file mode 100644 index 0000000000..afdb52cef7 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-6x-scenario/src/main/resources/application.properties @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server.port=8080 \ No newline at end of file diff --git a/test/plugin/scenarios/lettuce-webflux-6x-scenario/support-version.list b/test/plugin/scenarios/lettuce-webflux-6x-scenario/support-version.list new file mode 100644 index 0000000000..2cba7f3294 --- /dev/null +++ b/test/plugin/scenarios/lettuce-webflux-6x-scenario/support-version.list @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +6.4.2.RELEASE +6.5.5.RELEASE +6.6.0.RELEASE +6.7.1.RELEASE \ No newline at end of file diff --git a/test/plugin/scenarios/mariadb-3.x-scenario/bin/startup.sh b/test/plugin/scenarios/mariadb-3.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..a7a67d27ab --- /dev/null +++ b/test/plugin/scenarios/mariadb-3.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/mariadb-3.x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/oracle-scenario/config/expectedData.yaml b/test/plugin/scenarios/mariadb-3.x-scenario/config/expectedData.yaml similarity index 59% rename from test/plugin/scenarios/oracle-scenario/config/expectedData.yaml rename to test/plugin/scenarios/mariadb-3.x-scenario/config/expectedData.yaml index 91df4be88d..9f2a81315d 100644 --- a/test/plugin/scenarios/oracle-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/mariadb-3.x-scenario/config/expectedData.yaml @@ -14,100 +14,101 @@ # See the License for the specific language governing permissions and # limitations under the License. segmentItems: -- serviceName: oracle-scenario +- serviceName: mariadb-3.x-scenario segmentSize: ge 1 segments: - segmentId: not null spans: - - operationName: Oracle/JDBC/Statement/execute + - operationName: Mariadb/JDBC/Statement/execute parentSpanId: 0 spanId: 1 spanLayer: Database startTime: nq 0 endTime: nq 0 - componentId: 34 + componentId: 87 isError: false spanType: Exit - peer: oracle-server:1521 + peer: mariadb-server:3306 + skipAnalysis: 'false' tags: - - {key: db.type, value: Oracle} - - {key: db.instance, value: xe} + - {key: db.type, value: Mariadb} + - {key: db.instance, value: test} - key: db.statement - value: "CREATE TABLE test_007(\nid VARCHAR(1) PRIMARY KEY, \nvalue VARCHAR(1)\ + value: "CREATE TABLE test_table(\nid VARCHAR(1) PRIMARY KEY, \nvalue VARCHAR(10)\ \ NOT NULL)" - skipAnalysis: 'false' - - operationName: Oracle/JDBC/PreparedStatement/execute + - operationName: Mariadb/JDBC/PreparedStatement/execute parentSpanId: 0 spanId: 2 spanLayer: Database startTime: nq 0 endTime: nq 0 - componentId: 34 + componentId: 87 isError: false spanType: Exit - peer: oracle-server:1521 - tags: - - {key: db.type, value: Oracle} - - {key: db.instance, value: xe} - - {key: db.statement, value: 'INSERT INTO test_007(id, value) VALUES(?,?)'} + peer: mariadb-server:3306 skipAnalysis: 'false' - - operationName: Oracle/JDBC/PreparedStatement/executeQuery + tags: + - {key: db.type, value: Mariadb} + - {key: db.instance, value: test} + - {key: db.statement, value: "INSERT INTO test_table(id, value) VALUES(?,?)"} + - operationName: Mariadb/JDBC/PreparedStatement/execute parentSpanId: 0 spanId: 3 spanLayer: Database startTime: nq 0 endTime: nq 0 - componentId: 34 + componentId: 87 isError: false spanType: Exit - peer: oracle-server:1521 - tags: - - {key: db.type, value: Oracle} - - {key: db.instance, value: xe} - - {key: db.statement, value: 'SELECT id, value FROM test_007 WHERE id=?'} + peer: mariadb-server:3306 skipAnalysis: 'false' - - operationName: Oracle/JDBC/Statement/execute + tags: + - {key: db.type, value: Mariadb} + - {key: db.instance, value: test} + - {key: db.statement, value: "SELECT id, value FROM test_table WHERE id = ?"} + - operationName: Mariadb/JDBC/Statement/execute parentSpanId: 0 spanId: 4 spanLayer: Database startTime: nq 0 endTime: nq 0 - componentId: 34 + componentId: 87 isError: false spanType: Exit - peer: oracle-server:1521 - tags: - - {key: db.type, value: Oracle} - - {key: db.instance, value: xe} - - {key: db.statement, value: DROP table test_007} + peer: mariadb-server:3306 skipAnalysis: 'false' - - operationName: Oracle/JDBC/Connection/close + tags: + - {key: db.type, value: Mariadb} + - {key: db.instance, value: test} + - {key: db.statement, value: "DROP table test_table"} + - operationName: Mariadb/JDBC/Connection/close parentSpanId: 0 spanId: 5 spanLayer: Database - tags: - - {key: db.type, value: Oracle} - - {key: db.instance, value: xe} - - {key: db.statement, value: ''} startTime: nq 0 endTime: nq 0 - componentId: 34 + componentId: 87 isError: false spanType: Exit - peer: oracle-server:1521 + peer: mariadb-server:3306 skipAnalysis: 'false' - - operationName: GET:/oracle-scenario/case/oracle + tags: + - {key: db.type, value: Mariadb} + - {key: db.instance, value: test} + - {key: db.statement, value: ''} + - operationName: GET:/mariadb-3.x-scenario/case/mariadb-scenario parentSpanId: -1 spanId: 0 - spanLayer: Http startTime: nq 0 endTime: nq 0 - componentId: 1 + spanLayer: Http isError: false spanType: Entry peer: '' + componentId: 1 tags: - - {key: url, value: 'http://localhost:8080/oracle-scenario/case/oracle'} + - {key: url, value: 'http://localhost:8080/mariadb-3.x-scenario/case/mariadb-scenario'} - {key: http.method, value: GET} - {key: http.status_code, value: '200'} + logs: [] skipAnalysis: 'false' diff --git a/test/plugin/scenarios/mariadb-3.x-scenario/configuration.yml b/test/plugin/scenarios/mariadb-3.x-scenario/configuration.yml new file mode 100644 index 0000000000..cebb701abb --- /dev/null +++ b/test/plugin/scenarios/mariadb-3.x-scenario/configuration.yml @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/mariadb-3.x-scenario/case/mariadb-scenario +healthCheck: http://localhost:8080/mariadb-3.x-scenario/case/healthCheck +startScript: ./bin/startup.sh +environment: +depends_on: + - mariadb-server +dependencies: + mariadb-server: + image: mariadb:10.5.2 + hostname: mariadb-server + expose: + - "3306" + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=test diff --git a/test/plugin/scenarios/mariadb-3.x-scenario/pom.xml b/test/plugin/scenarios/mariadb-3.x-scenario/pom.xml new file mode 100644 index 0000000000..b8c31cd066 --- /dev/null +++ b/test/plugin/scenarios/mariadb-3.x-scenario/pom.xml @@ -0,0 +1,123 @@ + + + + + org.apache.skywalking.apm.testcase + mariadb-3.x-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 1.8 + 3.8.1 + + 3.5.1 + ${test.framework.version} + + 2.1.6.RELEASE + + + skywalking-mariadb-3.x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + org.mariadb.jdbc + mariadb-java-client + ${test.framework.version} + + + + + mariadb-3.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/mariadb-3.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..80cf6eba69 --- /dev/null +++ b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/mariadb-3.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/mariadb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mariadb/Application.java b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mariadb/Application.java new file mode 100644 index 0000000000..ad8ed130c2 --- /dev/null +++ b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mariadb/Application.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.mariadb; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + private static final Logger LOGGER = LogManager.getLogger(Application.class); + + public static void main(String[] args) { + try { + SpringApplication.run(Application.class, args); + } catch (Exception ex) { + LOGGER.error("Application start error", ex); + throw ex; + } + } +} diff --git a/test/plugin/scenarios/mariadb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mariadb/MariadbConfig.java b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mariadb/MariadbConfig.java new file mode 100644 index 0000000000..7eb8a6b1cb --- /dev/null +++ b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mariadb/MariadbConfig.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.mariadb; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class MariadbConfig { + private static final Logger LOGGER = LogManager.getLogger(MariadbConfig.class); + private static String URL; + private static String USER_NAME; + private static String PASSWORD; + + static { + InputStream inputStream = MariadbConfig.class.getClassLoader().getResourceAsStream("/jdbc.properties"); + Properties properties = new Properties(); + try { + properties.load(inputStream); + } catch (IOException e) { + LOGGER.error("Failed to load config", e); + } + + URL = properties.getProperty("mariadb.url"); + USER_NAME = properties.getProperty("mariadb.username"); + PASSWORD = properties.getProperty("mariadb.password"); + } + + public static String getUrl() { + return URL; + } + + public static String getUserName() { + return USER_NAME; + } + + public static String getPassword() { + return PASSWORD; + } +} diff --git a/test/plugin/scenarios/mariadb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mariadb/SQLExecutor.java b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mariadb/SQLExecutor.java new file mode 100644 index 0000000000..74b7ffbad4 --- /dev/null +++ b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mariadb/SQLExecutor.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.mariadb; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +public class SQLExecutor implements AutoCloseable { + private static final Logger LOGGER = LogManager.getLogger(SQLExecutor.class); + private Connection connection; + + public SQLExecutor() throws SQLException { + try { + Class.forName("org.mariadb.jdbc.Driver"); + } catch (ClassNotFoundException ex) { + LOGGER.error(ex); + } + connection = DriverManager.getConnection(MariadbConfig.getUrl(), MariadbConfig.getUserName(), MariadbConfig.getPassword()); + } + + public void execute(String sql) throws SQLException { + Statement statement = connection.createStatement(); + statement.execute(sql); + } + + public void insertData(String sql, String id, String value) throws SQLException { + PreparedStatement preparedStatement = connection.prepareStatement(sql); + preparedStatement.setString(1, id); + preparedStatement.setString(2, value); + preparedStatement.execute(); + } + + public void queryData(String sql, String id) throws SQLException { + PreparedStatement preparedStatement = connection.prepareStatement(sql); + preparedStatement.setString(1, id); + preparedStatement.execute(); + } + + public void closeConnection() throws SQLException { + if (this.connection != null) { + this.connection.close(); + } + } + + @Override + public void close() throws Exception { + closeConnection(); + } +} diff --git a/test/plugin/scenarios/mariadb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mariadb/controller/CaseController.java b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mariadb/controller/CaseController.java new file mode 100644 index 0000000000..7f6638bd7d --- /dev/null +++ b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mariadb/controller/CaseController.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.mariadb.controller; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.skywalking.apm.testcase.mariadb.SQLExecutor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/case") +public class CaseController { + + private static final Logger LOGGER = LogManager.getLogger(CaseController.class); + + private static final String SUCCESS = "Success"; + + private static final String CREATE_TABLE_SQL = "CREATE TABLE test_table(\n" + "id VARCHAR(1) PRIMARY KEY, \n" + "value VARCHAR(10) NOT NULL)"; + private static final String INSERT_DATA_SQL = "INSERT INTO test_table(id, value) VALUES(?,?)"; + private static final String QUERY_DATA_SQL = "SELECT id, value FROM test_table WHERE id = ?"; + private static final String DELETE_DATA_SQL = "DELETE FROM test_table WHERE id=?"; + private static final String DROP_TABLE_SQL = "DROP table test_table"; + + @RequestMapping("/mariadb-scenario") + @ResponseBody + public String testcase() throws Exception { + try (SQLExecutor sqlExecute = new SQLExecutor()) { + sqlExecute.execute(CREATE_TABLE_SQL); + sqlExecute.insertData(INSERT_DATA_SQL, "1", "value"); + sqlExecute.queryData(QUERY_DATA_SQL, "1"); + sqlExecute.execute(DROP_TABLE_SQL); + } catch (Exception ex) { + LOGGER.error("Failed to execute sql.", ex); + throw ex; + } + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() throws Exception { + return SUCCESS; + } + +} diff --git a/test/plugin/scenarios/mariadb-3.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000..5045bd899a --- /dev/null +++ b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/resources/application.yaml @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server: + port: 8080 + servlet: + context-path: /mariadb-3.x-scenario +logging: + config: classpath:log4j2.xml diff --git a/test/plugin/scenarios/mariadb-3.x-scenario/src/main/resources/jdbc.properties b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/resources/jdbc.properties new file mode 100644 index 0000000000..4b7d7ad333 --- /dev/null +++ b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/resources/jdbc.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +mariadb.url=jdbc:mariadb://mariadb-server:3306/test +mariadb.username=root +mariadb.password=root diff --git a/test/plugin/scenarios/mariadb-3.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..b5cda5ae8a --- /dev/null +++ b/test/plugin/scenarios/mariadb-3.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/test/plugin/scenarios/mariadb-3.x-scenario/support-version.list b/test/plugin/scenarios/mariadb-3.x-scenario/support-version.list new file mode 100644 index 0000000000..de41a47025 --- /dev/null +++ b/test/plugin/scenarios/mariadb-3.x-scenario/support-version.list @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +3.0.11 +3.1.4 +3.2.0 +3.3.4 +3.4.1 +3.5.1 + diff --git a/test/plugin/scenarios/mariadb-scenario/support-version.list b/test/plugin/scenarios/mariadb-scenario/support-version.list index 8d642a99e3..09e354a134 100644 --- a/test/plugin/scenarios/mariadb-scenario/support-version.list +++ b/test/plugin/scenarios/mariadb-scenario/support-version.list @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +2.7.12 2.6.0 2.5.4 2.4.4 diff --git a/test/plugin/scenarios/micronaut-http-scenario/pom.xml b/test/plugin/scenarios/micronaut-http-scenario/pom.xml index f9706e1da6..f04874ebeb 100644 --- a/test/plugin/scenarios/micronaut-http-scenario/pom.xml +++ b/test/plugin/scenarios/micronaut-http-scenario/pom.xml @@ -29,13 +29,7 @@ micronaut-scenario jar - 1.8 - - - 1.8 netty - 1.8 - 1.8 org.apache.skywalking.apm.testcase.micronaut.Application @@ -134,7 +128,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + 3.14.0 diff --git a/test/plugin/scenarios/mongodb-3.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/mongodb-3.x-scenario/config/expectedData.yaml index e17d7162cf..a050100ce9 100644 --- a/test/plugin/scenarios/mongodb-3.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/mongodb-3.x-scenario/config/expectedData.yaml @@ -31,6 +31,7 @@ segmentItems: peer: mongodb-server:27017 tags: - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} - {key: db.bind_vars, value: not null} skipAnalysis: 'false' - operationName: MongoDB/MixedBulkWriteOperation @@ -45,6 +46,8 @@ segmentItems: peer: mongodb-server:27017 tags: - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} - {key: db.bind_vars, value: not null} skipAnalysis: 'false' - operationName: MongoDB/FindOperation @@ -59,9 +62,11 @@ segmentItems: peer: mongodb-server:27017 tags: - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} - {key: db.bind_vars, value: not null} skipAnalysis: 'false' - - operationName: MongoDB/MixedBulkWriteOperation + - operationName: MongoDB/AggregateOperation parentSpanId: 0 spanId: 4 spanLayer: Database @@ -73,9 +78,11 @@ segmentItems: peer: mongodb-server:27017 tags: - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} - {key: db.bind_vars, value: not null} skipAnalysis: 'false' - - operationName: MongoDB/FindOperation + - operationName: MongoDB/MixedBulkWriteOperation parentSpanId: 0 spanId: 5 spanLayer: Database @@ -87,9 +94,11 @@ segmentItems: peer: mongodb-server:27017 tags: - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} - {key: db.bind_vars, value: not null} skipAnalysis: 'false' - - operationName: MongoDB/MixedBulkWriteOperation + - operationName: MongoDB/FindOperation parentSpanId: 0 spanId: 6 spanLayer: Database @@ -101,9 +110,11 @@ segmentItems: peer: mongodb-server:27017 tags: - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} - {key: db.bind_vars, value: not null} skipAnalysis: 'false' - - operationName: MongoDB/DropDatabaseOperation + - operationName: MongoDB/MixedBulkWriteOperation parentSpanId: 0 spanId: 7 spanLayer: Database @@ -115,6 +126,23 @@ segmentItems: peer: mongodb-server:27017 tags: - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} + - {key: db.bind_vars, value: not null} + skipAnalysis: 'false' + - operationName: MongoDB/DropDatabaseOperation + parentSpanId: 0 + spanId: 8 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} - {key: db.bind_vars, value: not null} skipAnalysis: 'false' - operationName: GET:/mongodb-case/case/mongodb diff --git a/test/plugin/scenarios/mongodb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java b/test/plugin/scenarios/mongodb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java index dad835c02a..d26f7d092e 100644 --- a/test/plugin/scenarios/mongodb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java +++ b/test/plugin/scenarios/mongodb-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java @@ -19,16 +19,23 @@ package org.apache.skywalking.apm.testcase.mongodb.controller; import com.mongodb.MongoClient; +import com.mongodb.client.AggregateIterable; import com.mongodb.client.FindIterable; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Aggregates; +import com.mongodb.client.model.Filters; import org.bson.BsonDocument; import org.bson.Document; +import org.bson.conversions.Bson; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Arrays; +import java.util.List; + import static com.mongodb.client.model.Filters.eq; @RestController @@ -65,6 +72,13 @@ public String mongoDBCase() { FindIterable findIterable = collection.find(eq("name", "org")); findIterable.first(); + // AggregateOperation + List pipeline = Arrays.asList( + Aggregates.match(Filters.eq("name", "test")) + ); + AggregateIterable aggregateIterable = collection.aggregate(pipeline); + aggregateIterable.first(); + // MixedBulkWriteOperation collection.updateOne(eq("name", "org"), BsonDocument.parse("{ $set : { \"name\": \"testA\"} }")); diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/mongodb-4.x-scenario/config/expectedData.yaml index 299d8b45ea..7673d38268 100644 --- a/test/plugin/scenarios/mongodb-4.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/mongodb-4.x-scenario/config/expectedData.yaml @@ -47,6 +47,7 @@ segmentItems: tags: - {key: db.type, value: MongoDB} - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} - {key: db.bind_vars, value: not null} skipAnalysis: 'false' - operationName: MongoDB/FindOperation @@ -62,9 +63,10 @@ segmentItems: tags: - {key: db.type, value: MongoDB} - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} - {key: db.bind_vars, value: '{"name": "org"}'} skipAnalysis: 'false' - - operationName: MongoDB/MixedBulkWriteOperation + - operationName: MongoDB/AggregateOperation parentSpanId: 0 spanId: 4 spanLayer: Database @@ -77,11 +79,28 @@ segmentItems: tags: - {key: db.type, value: MongoDB} - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} + - {key: db.bind_vars, value: '{"$match": {"name": "test"}},'} + skipAnalysis: 'false' + - operationName: MongoDB/MixedBulkWriteOperation + parentSpanId: 0 + spanId: 5 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} - {key: db.bind_vars, value: '{"name": "org"},'} skipAnalysis: 'false' - operationName: MongoDB/FindOperation parentSpanId: 0 - spanId: 5 + spanId: 6 spanLayer: Database startTime: nq 0 endTime: nq 0 @@ -92,11 +111,12 @@ segmentItems: tags: - {key: db.type, value: MongoDB} - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} - {key: db.bind_vars, value: '{"name": "testA"}'} skipAnalysis: 'false' - operationName: MongoDB/MixedBulkWriteOperation parentSpanId: 0 - spanId: 6 + spanId: 7 spanLayer: Database startTime: nq 0 endTime: nq 0 @@ -107,11 +127,12 @@ segmentItems: tags: - {key: db.type, value: MongoDB} - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} - {key: db.bind_vars, value: '{"id": "1"},'} skipAnalysis: 'false' - operationName: MongoDB/DropDatabaseOperation parentSpanId: 0 - spanId: 7 + spanId: 8 spanLayer: Database startTime: nq 0 endTime: nq 0 diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java b/test/plugin/scenarios/mongodb-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java index 33dfea5768..e50909855c 100644 --- a/test/plugin/scenarios/mongodb-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java +++ b/test/plugin/scenarios/mongodb-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java @@ -18,18 +18,25 @@ package org.apache.skywalking.apm.testcase.mongodb.controller; +import com.mongodb.client.AggregateIterable; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import com.mongodb.client.FindIterable; +import com.mongodb.client.model.Aggregates; +import com.mongodb.client.model.Filters; import org.bson.BsonDocument; import org.bson.Document; +import org.bson.conversions.Bson; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Arrays; +import java.util.List; + import static com.mongodb.client.model.Filters.eq; @RestController @@ -63,6 +70,13 @@ public String mongoDBCase() { FindIterable findIterable = collection.find(eq("name", "org")); findIterable.first(); + // AggregateOperation + List pipeline = Arrays.asList( + Aggregates.match(Filters.eq("name", "test")) + ); + AggregateIterable aggregateIterable = collection.aggregate(pipeline); + aggregateIterable.first(); + // MixedBulkWriteOperation collection.updateOne(eq("name", "org"), BsonDocument.parse("{ $set : { \"name\": \"testA\"} }")); diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/support-version.list b/test/plugin/scenarios/mongodb-4.x-scenario/support-version.list index 68dfcd9852..ec4ce41a71 100644 --- a/test/plugin/scenarios/mongodb-4.x-scenario/support-version.list +++ b/test/plugin/scenarios/mongodb-4.x-scenario/support-version.list @@ -15,4 +15,17 @@ # limitations under the License. 4.0.5 -4.1.0 \ No newline at end of file +4.1.0 +4.2.3 +4.3.4 +4.4.2 +4.5.1 +4.6.1 +4.7.2 +4.8.2 +4.9.1 +4.10.2 +4.11.5 +# 5.0-5.1 still uses MongoClientDelegate (same as 4.x) +5.0.1 +5.1.4 \ No newline at end of file diff --git a/test/plugin/scenarios/mongodb-5.x-scenario/bin/startup.sh b/test/plugin/scenarios/mongodb-5.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..8d9acc35ea --- /dev/null +++ b/test/plugin/scenarios/mongodb-5.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} -Dskywalking.plugin.mongodb.trace_param=true ${home}/../libs/mongodb-5.x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/mongodb-5.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/mongodb-5.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..58d2556342 --- /dev/null +++ b/test/plugin/scenarios/mongodb-5.x-scenario/config/expectedData.yaml @@ -0,0 +1,162 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: + - serviceName: mongodb-5.x-scenario + segmentSize: ge 2 + segments: + - segmentId: not null + spans: + - operationName: MongoDB/CreateCollectionOperation + parentSpanId: 0 + spanId: 1 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.bind_vars, value: 'testCollection'} + skipAnalysis: 'false' + - operationName: MongoDB/MixedBulkWriteOperation + parentSpanId: 0 + spanId: 2 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} + - {key: db.bind_vars, value: not null} + skipAnalysis: 'false' + - operationName: MongoDB/FindOperation + parentSpanId: 0 + spanId: 3 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} + - {key: db.bind_vars, value: '{"name": "org"}'} + skipAnalysis: 'false' + - operationName: MongoDB/AggregateOperation + parentSpanId: 0 + spanId: 4 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} + - {key: db.bind_vars, value: '{"$match": {"name": "test"}},'} + skipAnalysis: 'false' + - operationName: MongoDB/MixedBulkWriteOperation + parentSpanId: 0 + spanId: 5 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} + - {key: db.bind_vars, value: '{"name": "org"},'} + skipAnalysis: 'false' + - operationName: MongoDB/FindOperation + parentSpanId: 0 + spanId: 6 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} + - {key: db.bind_vars, value: '{"name": "testA"}'} + skipAnalysis: 'false' + - operationName: MongoDB/MixedBulkWriteOperation + parentSpanId: 0 + spanId: 7 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.collection, value: testCollection} + - {key: db.bind_vars, value: '{"id": "1"},'} + skipAnalysis: 'false' + - operationName: MongoDB/DropDatabaseOperation + parentSpanId: 0 + spanId: 8 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.instance, value: test-database} + - {key: db.bind_vars, value: null} + skipAnalysis: 'false' + - operationName: GET:/mongodb-5.x-scenario/case/mongodb-5.x-scenario + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/mongodb-5.x-scenario/case/mongodb-5.x-scenario'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/mongodb-5.x-scenario/configuration.yml b/test/plugin/scenarios/mongodb-5.x-scenario/configuration.yml new file mode 100644 index 0000000000..187a8601a7 --- /dev/null +++ b/test/plugin/scenarios/mongodb-5.x-scenario/configuration.yml @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/mongodb-5.x-scenario/case/mongodb-5.x-scenario +healthCheck: http://localhost:8080/mongodb-5.x-scenario/case/healthCheck +startScript: ./bin/startup.sh +dependencies: + mongodb-server: + image: mongo:6.0 + hostname: mongodb-server diff --git a/test/plugin/scenarios/mongodb-5.x-scenario/pom.xml b/test/plugin/scenarios/mongodb-5.x-scenario/pom.xml new file mode 100644 index 0000000000..9a0985910c --- /dev/null +++ b/test/plugin/scenarios/mongodb-5.x-scenario/pom.xml @@ -0,0 +1,148 @@ + + + + + org.apache.skywalking.apm.testcase + mongodb-5.x-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 1.8 + 3.8.1 + 5.2.0 + 2.1.6.RELEASE + 1.18.20 + + + skywalking-mongodb-5.x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + org.mongodb + mongodb-driver-core + ${test.framework.version} + + + org.mongodb + mongodb-driver-sync + ${test.framework.version} + + + org.mongodb + bson + ${test.framework.version} + + + + + + + org.mongodb + mongodb-driver-core + + + org.mongodb + mongodb-driver-sync + + + org.mongodb + bson + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + + mongodb-5.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + diff --git a/test/plugin/scenarios/mongodb-5.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/mongodb-5.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..ff39fb806e --- /dev/null +++ b/test/plugin/scenarios/mongodb-5.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/mongodb-5.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/mongodb-5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/Application.java b/test/plugin/scenarios/mongodb-5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/Application.java new file mode 100644 index 0000000000..1dd56fec23 --- /dev/null +++ b/test/plugin/scenarios/mongodb-5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/Application.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.mongodb; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + try { + SpringApplication.run(Application.class, args); + } catch (Exception e) { + // Never do this + } + } +} diff --git a/test/plugin/scenarios/mongodb-5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java b/test/plugin/scenarios/mongodb-5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java new file mode 100644 index 0000000000..06a196e216 --- /dev/null +++ b/test/plugin/scenarios/mongodb-5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.mongodb.controller; + +import com.mongodb.client.AggregateIterable; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.FindIterable; +import com.mongodb.client.model.Aggregates; +import com.mongodb.client.model.Filters; +import org.bson.BsonDocument; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Arrays; +import java.util.List; + +import static com.mongodb.client.model.Filters.eq; + +@RestController +@RequestMapping("/case") +public class CaseController { + + @Value(value = "${mongodb.uri}") + private String connectionString; + + @GetMapping("/healthCheck") + public String health() { + // check connect to mongodb server + try (MongoClient mongoClient = MongoClients.create(connectionString)) { + return "success"; + } + } + + @RequestMapping("/mongodb-5.x-scenario") + public String mongoDBCase() { + try (MongoClient mongoClient = MongoClients.create(connectionString)) { + MongoDatabase db = mongoClient.getDatabase("test-database"); + // CreateCollectionOperation + db.createCollection("testCollection"); + + MongoCollection collection = db.getCollection("testCollection"); + Document document = Document.parse("{id: 1, name: \"test\"}"); + // MixedBulkWriteOperation + collection.insertOne(document); + + // FindOperation + FindIterable findIterable = collection.find(eq("name", "org")); + findIterable.first(); + + // AggregateOperation + List pipeline = Arrays.asList( + Aggregates.match(Filters.eq("name", "test")) + ); + AggregateIterable aggregateIterable = collection.aggregate(pipeline); + aggregateIterable.first(); + + // MixedBulkWriteOperation + collection.updateOne(eq("name", "org"), BsonDocument.parse("{ $set : { \"name\": \"testA\"} }")); + + // FindOperation + findIterable = collection.find(eq("name", "testA")); + findIterable.first(); + + // MixedBulkWriteOperation + collection.deleteOne(eq("id", "1")); + + // DropDatabaseOperation + mongoClient.getDatabase("test-database").drop(); + } + return "success"; + } +} diff --git a/test/plugin/scenarios/mongodb-5.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/mongodb-5.x-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000..533ff2e37f --- /dev/null +++ b/test/plugin/scenarios/mongodb-5.x-scenario/src/main/resources/application.yaml @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server: + port: 8080 + servlet: + context-path: /mongodb-5.x-scenario +logging: + config: classpath:log4j2.xml +mongodb: + uri: mongodb://mongodb-server/test \ No newline at end of file diff --git a/test/plugin/scenarios/mongodb-5.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/mongodb-5.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..9849ed5a8a --- /dev/null +++ b/test/plugin/scenarios/mongodb-5.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/mongodb-5.x-scenario/support-version.list b/test/plugin/scenarios/mongodb-5.x-scenario/support-version.list new file mode 100644 index 0000000000..89a4430b05 --- /dev/null +++ b/test/plugin/scenarios/mongodb-5.x-scenario/support-version.list @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# MongoDB 5.2+ (MongoClusterImpl replaces MongoClientDelegate) +5.2.0 +5.5.1 diff --git a/test/plugin/scenarios/oracle-scenario/bin/startup.sh b/test/plugin/scenarios/mysql-9.x-scenario/bin/startup.sh similarity index 92% rename from test/plugin/scenarios/oracle-scenario/bin/startup.sh rename to test/plugin/scenarios/mysql-9.x-scenario/bin/startup.sh index a8f932fa0d..7e4ac0fd1d 100644 --- a/test/plugin/scenarios/oracle-scenario/bin/startup.sh +++ b/test/plugin/scenarios/mysql-9.x-scenario/bin/startup.sh @@ -18,4 +18,4 @@ home="$(cd "$(dirname $0)"; pwd)" -java -jar ${agent_opts} ${home}/../libs/oracle-scenario.jar & \ No newline at end of file +java -jar ${agent_opts} ${home}/../libs/mysql-9.x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/mysql-9.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/mysql-9.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..17f823f911 --- /dev/null +++ b/test/plugin/scenarios/mysql-9.x-scenario/config/expectedData.yaml @@ -0,0 +1,151 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: mysql-9.x-scenario + segmentSize: ge 2 + segments: + - segmentId: not null + spans: + - operationName: Mysql/JDBC/PreparedStatement/execute + parentSpanId: 0 + spanId: 1 + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - key: db.statement + value: "CREATE TABLE test_007(\nid VARCHAR(1) PRIMARY KEY, \nvalue VARCHAR(1)\ + \ NOT NULL)" + logs: [] + startTime: nq 0 + endTime: nq 0 + isError: false + spanLayer: Database + spanType: Exit + componentId: 33 + peer: mysql-server:3306 + skipAnalysis: 'false' + - operationName: Mysql/JDBC/PreparedStatement/execute + parentSpanId: 0 + spanId: 2 + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - {key: db.statement, value: 'INSERT INTO test_007(id, value) VALUES(?,?)'} + logs: [] + startTime: nq 0 + endTime: nq 0 + isError: false + spanLayer: Database + spanType: Exit + componentId: 33 + peer: mysql-server:3306 + skipAnalysis: 'false' + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 3 + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - {key: db.statement, value: DROP table test_007} + logs: [] + startTime: nq 0 + endTime: nq 0 + isError: false + spanLayer: Database + spanType: Exit + componentId: 33 + peer: mysql-server:3306 + skipAnalysis: 'false' + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 4 + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - {key: db.statement, value: "create procedure testProcedure(IN id varchar(10)) \n begin \n select id; \n end"} + logs: [] + startTime: nq 0 + endTime: nq 0 + isError: false + spanLayer: Database + spanType: Exit + componentId: 33 + peer: mysql-server:3306 + skipAnalysis: 'false' + # MySQL Connector 8.4+ no longer issues internal SHOW CREATE PROCEDURE query + - operationName: Mysql/JDBC/CallableStatement/execute + parentSpanId: 0 + spanId: 5 + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - {key: db.statement, value: "call testProcedure( ? )"} + logs: [] + startTime: nq 0 + endTime: nq 0 + isError: false + spanLayer: Database + spanType: Exit + componentId: 33 + peer: mysql-server:3306 + skipAnalysis: 'false' + - operationName: Mysql/JDBC/Statement/execute + parentSpanId: 0 + spanId: 6 + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - {key: db.statement, value: "drop procedure testProcedure"} + logs: [] + startTime: nq 0 + endTime: nq 0 + isError: false + spanLayer: Database + spanType: Exit + componentId: 33 + peer: mysql-server:3306 + skipAnalysis: 'false' + - operationName: Mysql/JDBC/Connection/close + parentSpanId: 0 + spanId: 7 + tags: + - {key: db.type, value: Mysql} + - {key: db.instance, value: test} + - {key: db.statement, value: ''} + logs: [] + startTime: nq 0 + endTime: nq 0 + isError: false + spanLayer: Database + spanType: Exit + componentId: 33 + peer: mysql-server:3306 + skipAnalysis: 'false' + - operationName: GET:/mysql-scenario/case/mysql-scenario + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + spanLayer: Http + isError: false + spanType: Entry + componentId: 1 + tags: + - {key: url, value: 'http://localhost:8080/mysql-scenario/case/mysql-scenario'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + logs: [] + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/oracle-scenario/configuration.yml b/test/plugin/scenarios/mysql-9.x-scenario/configuration.yml similarity index 72% rename from test/plugin/scenarios/oracle-scenario/configuration.yml rename to test/plugin/scenarios/mysql-9.x-scenario/configuration.yml index 0b112b9e7b..0d486ac74b 100644 --- a/test/plugin/scenarios/oracle-scenario/configuration.yml +++ b/test/plugin/scenarios/mysql-9.x-scenario/configuration.yml @@ -15,16 +15,18 @@ # limitations under the License. type: jvm -entryService: http://localhost:8080/oracle-scenario/case/oracle -healthCheck: http://localhost:8080/oracle-scenario/case/healthCheck +entryService: http://localhost:8080/mysql-scenario/case/mysql-scenario +healthCheck: http://localhost:8080/mysql-scenario/case/healthCheck startScript: ./bin/startup.sh environment: - - oracle.address=oracle-server:1521 - - oracle.username=system - - oracle.password=oracle +depends_on: + - mysql-server dependencies: - oracle-server: - image: deepdiver/docker-oracle-xe-11g - hostname: oracle-server + mysql-server: + image: mysql:8.0 + hostname: mysql-server expose: - - "1521" + - "3306" + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=test diff --git a/test/plugin/scenarios/oracle-scenario/pom.xml b/test/plugin/scenarios/mysql-9.x-scenario/pom.xml similarity index 91% rename from test/plugin/scenarios/oracle-scenario/pom.xml rename to test/plugin/scenarios/mysql-9.x-scenario/pom.xml index ed087a9cca..c99900594a 100644 --- a/test/plugin/scenarios/oracle-scenario/pom.xml +++ b/test/plugin/scenarios/mysql-9.x-scenario/pom.xml @@ -21,7 +21,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> org.apache.skywalking.apm.testcase - oracle-scenario + mysql-9.x-scenario 1.0.0 jar @@ -32,12 +32,13 @@ 1.8 3.8.1 - 10.2.0.4.0 + 9.0.0 + ${test.framework.version} 2.1.6.RELEASE - skywalking-oracle-scenario + skywalking-mysql-9.x-scenario @@ -52,11 +53,6 @@ - - com.oracle - ojdbc14 - ${test.framework.version} - org.springframework.boot spring-boot-starter-web @@ -71,10 +67,16 @@ org.springframework.boot spring-boot-starter-log4j2 + + + com.mysql + mysql-connector-j + ${test.framework.version} + - oracle-scenario + mysql-9.x-scenario org.springframework.boot @@ -118,4 +120,4 @@ - + \ No newline at end of file diff --git a/test/plugin/scenarios/mysql-9.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/mysql-9.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..9aae4e672b --- /dev/null +++ b/test/plugin/scenarios/mysql-9.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/mysql-9.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/mysql-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/Application.java b/test/plugin/scenarios/mysql-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/Application.java new file mode 100644 index 0000000000..b7d42e6fc2 --- /dev/null +++ b/test/plugin/scenarios/mysql-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/Application.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.mysql; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + try { + SpringApplication.run(Application.class, args); + } catch (Exception e) { + // Never do this + } + } +} diff --git a/test/plugin/scenarios/mysql-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/MysqlConfig.java b/test/plugin/scenarios/mysql-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/MysqlConfig.java new file mode 100644 index 0000000000..f30a6ebc37 --- /dev/null +++ b/test/plugin/scenarios/mysql-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/MysqlConfig.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.mysql; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class MysqlConfig { + private static final Logger LOGGER = LogManager.getLogger(MysqlConfig.class); + private static String URL; + private static String USER_NAME; + private static String PASSWORD; + + static { + InputStream inputStream = MysqlConfig.class.getClassLoader().getResourceAsStream("/jdbc.properties"); + Properties properties = new Properties(); + try { + properties.load(inputStream); + } catch (IOException e) { + LOGGER.error("Failed to load config", e); + } + + URL = properties.getProperty("mysql.url"); + USER_NAME = properties.getProperty("mysql.username"); + PASSWORD = properties.getProperty("mysql.password"); + } + + public static String getUrl() { + return URL; + } + + public static String getUserName() { + return USER_NAME; + } + + public static String getPassword() { + return PASSWORD; + } +} diff --git a/test/plugin/scenarios/mysql-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/SQLExecutor.java b/test/plugin/scenarios/mysql-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/SQLExecutor.java new file mode 100644 index 0000000000..74e3d12d6c --- /dev/null +++ b/test/plugin/scenarios/mysql-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/SQLExecutor.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.mysql; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +public class SQLExecutor implements AutoCloseable { + private Connection connection; + + public SQLExecutor() throws SQLException { + try { + Class.forName("com.mysql.cj.jdbc.Driver"); + } catch (ClassNotFoundException e) { + // + } + connection = DriverManager.getConnection(MysqlConfig.getUrl(), MysqlConfig.getUserName(), MysqlConfig.getPassword()); + } + + public void createTable(String sql) throws SQLException { + PreparedStatement preparedStatement = connection.prepareStatement(sql); + preparedStatement.execute(); + preparedStatement.close(); + } + + public void insertData(String sql, String id, String value) throws SQLException { + PreparedStatement preparedStatement = connection.prepareStatement(sql); + preparedStatement.setString(1, id); + preparedStatement.setString(2, value); + preparedStatement.execute(); + preparedStatement.close(); + } + + public void dropTable(String sql) throws SQLException { + executeStatement(sql); + } + + public void createProcedure(String sql) throws SQLException { + executeStatement(sql); + } + + public void dropProcedure(String sql) throws SQLException { + executeStatement(sql); + } + + public void callProcedure(String sql, String id) throws SQLException { + PreparedStatement preparedStatement = connection.prepareCall(sql); + preparedStatement.setString(1, id); + preparedStatement.execute(); + preparedStatement.close(); + } + + public void executeStatement(String sql) throws SQLException { + Statement preparedStatement = connection.createStatement(); + preparedStatement.execute(sql); + preparedStatement.close(); + } + + public void closeConnection() throws SQLException { + if (this.connection != null) { + this.connection.close(); + } + } + + @Override + public void close() throws Exception { + closeConnection(); + } +} diff --git a/test/plugin/scenarios/mysql-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/controller/CaseController.java b/test/plugin/scenarios/mysql-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/controller/CaseController.java new file mode 100644 index 0000000000..626ef9a190 --- /dev/null +++ b/test/plugin/scenarios/mysql-9.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/controller/CaseController.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.mysql.controller; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.skywalking.apm.testcase.mysql.SQLExecutor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/case") +public class CaseController { + + private static final Logger LOGGER = LogManager.getLogger(CaseController.class); + + private static final String SUCCESS = "Success"; + + private static final String CREATE_TABLE_SQL = "CREATE TABLE test_007(\n" + "id VARCHAR(1) PRIMARY KEY, \n" + "value VARCHAR(1) NOT NULL)"; + private static final String INSERT_DATA_SQL = "INSERT INTO test_007(id, value) VALUES(?,?)"; + private static final String QUERY_DATA_SQL = "SELECT id, value FROM test_007 WHERE id=?"; + private static final String DELETE_DATA_SQL = "DELETE FROM test_007 WHERE id=?"; + private static final String DROP_TABLE_SQL = "DROP table test_007"; + private static final String CREATE_PROCEDURE_SQL = "create procedure testProcedure(IN id varchar(10)) \n begin \n select id; \n end"; + private static final String CALL_PROCEDURE_SQL = "call testProcedure( ? )"; + private static final String DROP_PROCEDURE_SQL = "drop procedure testProcedure"; + + @RequestMapping("/mysql-scenario") + @ResponseBody + public String testcase() throws Exception { + try (SQLExecutor sqlExecute = new SQLExecutor()) { + sqlExecute.createTable(CREATE_TABLE_SQL); + sqlExecute.insertData(INSERT_DATA_SQL, "1", "1"); + sqlExecute.dropTable(DROP_TABLE_SQL); + sqlExecute.createProcedure(CREATE_PROCEDURE_SQL); + sqlExecute.callProcedure(CALL_PROCEDURE_SQL, "nihao"); + sqlExecute.dropProcedure(DROP_PROCEDURE_SQL); + } catch (Exception e) { + LOGGER.error("Failed to execute sql.", e); + throw e; + } + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() throws Exception { + try (SQLExecutor sqlExecutor = new SQLExecutor()) { + // ignore + } + return SUCCESS; + } + +} diff --git a/test/plugin/scenarios/oracle-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/mysql-9.x-scenario/src/main/resources/application.yaml similarity index 96% rename from test/plugin/scenarios/oracle-scenario/src/main/resources/application.yaml rename to test/plugin/scenarios/mysql-9.x-scenario/src/main/resources/application.yaml index 3bf1f53f66..8dc5e28d15 100644 --- a/test/plugin/scenarios/oracle-scenario/src/main/resources/application.yaml +++ b/test/plugin/scenarios/mysql-9.x-scenario/src/main/resources/application.yaml @@ -18,6 +18,6 @@ server: port: 8080 servlet: - context-path: /oracle-scenario + context-path: /mysql-scenario logging: config: classpath:log4j2.xml \ No newline at end of file diff --git a/test/plugin/scenarios/mysql-9.x-scenario/src/main/resources/jdbc.properties b/test/plugin/scenarios/mysql-9.x-scenario/src/main/resources/jdbc.properties new file mode 100644 index 0000000000..7d999c28da --- /dev/null +++ b/test/plugin/scenarios/mysql-9.x-scenario/src/main/resources/jdbc.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +mysql.url=jdbc:mysql://mysql-server:3306/test?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true +mysql.username=root +mysql.password=root diff --git a/test/plugin/scenarios/mysql-9.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/mysql-9.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..9849ed5a8a --- /dev/null +++ b/test/plugin/scenarios/mysql-9.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/mysql-9.x-scenario/support-version.list b/test/plugin/scenarios/mysql-9.x-scenario/support-version.list new file mode 100644 index 0000000000..d0f41f5673 --- /dev/null +++ b/test/plugin/scenarios/mysql-9.x-scenario/support-version.list @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Uses com.mysql:mysql-connector-j artifact (8.0.31+ and 9.x) +8.4.0 +9.0.0 +9.1.0 +9.2.0 +9.3.0 +9.4.0 +9.5.0 +9.6.0 diff --git a/test/plugin/scenarios/mysql-scenario/support-version.list b/test/plugin/scenarios/mysql-scenario/support-version.list index 746fa92395..b0b1761ebb 100644 --- a/test/plugin/scenarios/mysql-scenario/support-version.list +++ b/test/plugin/scenarios/mysql-scenario/support-version.list @@ -14,6 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# 8.0.30+ requires allowPublicKeyRetrieval=true and serverTimezone=UTC +# Those versions are tested in mysql-9.x-scenario with the fixed JDBC URL 8.0.19 8.0.15 6.0.6 diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/bin/startup.sh b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/bin/startup.sh similarity index 94% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/bin/startup.sh rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/bin/startup.sh index d2237e1e87..ded2569231 100644 --- a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/bin/startup.sh +++ b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/bin/startup.sh @@ -21,4 +21,4 @@ home="$(cd "$(dirname $0)"; pwd)" java -jar ${agent_opts} -Dserver.port=8080 \ -Dskywalking.agent.service_name=scenario-8080- \ -Dnats.server=nats-server \ - ${home}/../libs/nats-2.14.x-2.15.x-scenario.jar & \ No newline at end of file + ${home}/../libs/nats-2.14.x-2.16.5-scenario.jar & diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/config/expectedData.yaml similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/config/expectedData.yaml rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/config/expectedData.yaml diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/configuration.yml b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/configuration.yml similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/configuration.yml rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/configuration.yml diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/pom.xml b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/pom.xml similarity index 96% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/pom.xml rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/pom.xml index 5234f29e9a..3fdcfa2857 100644 --- a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/pom.xml +++ b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/pom.xml @@ -21,7 +21,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> org.apache.skywalking.apm.testcase - nats-2.14.x-2.15.x-scenario + nats-2.14.x-2.16.5-scenario 1.0.0 jar @@ -75,7 +75,7 @@ - nats-2.14.x-2.15.x-scenario + nats-2.14.x-2.16.5-scenario org.springframework.boot @@ -112,4 +112,4 @@ - \ No newline at end of file + diff --git a/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..dd2cf1d4f5 --- /dev/null +++ b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/nats-2.14.x-2.16.5-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/Application.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/Application.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/Application.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/Application.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/controller/StartController.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/controller/StartController.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/controller/StartController.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/controller/StartController.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/JetStreamPublisher.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/JetStreamPublisher.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/JetStreamPublisher.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/JetStreamPublisher.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/JetStreamPublisherFetcher.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/JetStreamPublisherFetcher.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/JetStreamPublisherFetcher.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/JetStreamPublisherFetcher.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/NormalPublisher.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/NormalPublisher.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/NormalPublisher.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/NormalPublisher.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/Publisher.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/Publisher.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/Publisher.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/Publisher.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/ReqReplyPublisher.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/ReqReplyPublisher.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/ReqReplyPublisher.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/publisher/ReqReplyPublisher.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/Consumer.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/Consumer.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/Consumer.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/Consumer.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/JetStreamFetcherConsumer.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/JetStreamFetcherConsumer.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/JetStreamFetcherConsumer.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/JetStreamFetcherConsumer.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/JetStreamHandlerConsumer.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/JetStreamHandlerConsumer.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/JetStreamHandlerConsumer.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/JetStreamHandlerConsumer.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/NextMsgConsumer.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/NextMsgConsumer.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/NextMsgConsumer.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/NextMsgConsumer.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/ReqReplyConsumer.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/ReqReplyConsumer.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/ReqReplyConsumer.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/subscriber/ReqReplyConsumer.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/StopSignal.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/StopSignal.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/StopSignal.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/StopSignal.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/StreamUtil.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/StreamUtil.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/StreamUtil.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/StreamUtil.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/TrackedConnection.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/TrackedConnection.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/TrackedConnection.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/TrackedConnection.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/WorkBuilder.java b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/WorkBuilder.java similarity index 100% rename from test/plugin/scenarios/nats-2.14.x-2.15.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/WorkBuilder.java rename to test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/src/main/java/org/apache/skywalking/apm/testcase/nats/client/work/WorkBuilder.java diff --git a/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/support-version.list b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/support-version.list new file mode 100644 index 0000000000..25a0bd9d45 --- /dev/null +++ b/test/plugin/scenarios/nats-2.14.x-2.16.5-scenario/support-version.list @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +2.16.5 +2.15.6 +2.14.2 diff --git a/test/plugin/scenarios/oracle-scenario/src/main/java/org/apache/skywalking/apm/testcase/oracle/controller/CaseController.java b/test/plugin/scenarios/oracle-scenario/src/main/java/org/apache/skywalking/apm/testcase/oracle/controller/CaseController.java deleted file mode 100644 index c3bb2d2aa9..0000000000 --- a/test/plugin/scenarios/oracle-scenario/src/main/java/org/apache/skywalking/apm/testcase/oracle/controller/CaseController.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.oracle.controller; - -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 javax.annotation.PostConstruct; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/case") -public class CaseController { - - private static final Logger LOGGER = LogManager.getLogger(CaseController.class); - - @Value("${oracle.address}") - private String oracleHostAndPort; - - @Value("${oracle.username}") - private String oracleUsername; - - @Value("${oracle.password}") - private String oraclePassword; - - private String connectURL; - private static final String TEST_EXIST_SQL = "SELECT * FROM dual"; - private static final String CREATE_TABLE_SQL = "CREATE TABLE test_007(\n" + "id VARCHAR(1) PRIMARY KEY, \n" + "value VARCHAR(1) NOT NULL)"; - private static final String INSERT_DATA_SQL = "INSERT INTO test_007(id, value) VALUES(?,?)"; - private static final String QUERY_DATA_SQL = "SELECT id, value FROM test_007 WHERE id=?"; - private static final String DROP_TABLE_SQL = "DROP table test_007"; - - private static final String SUCCESS = "Success"; - - @PostConstruct - public void setUp() throws ClassNotFoundException { - Class.forName("oracle.jdbc.driver.OracleDriver"); - connectURL = "jdbc:oracle:thin:@" + oracleHostAndPort + ":xe"; - } - - @RequestMapping("/oracle") - @ResponseBody - public String testcase() { - Connection connection = null; - try { - // create table by using statement - connection = DriverManager.getConnection(connectURL, oracleUsername, oraclePassword); - Statement statement = connection.createStatement(); - statement.execute(CREATE_TABLE_SQL); - statement.close(); - - // insert table by using PreparedStatement - PreparedStatement insertDataPreparedStatement = connection.prepareStatement(INSERT_DATA_SQL); - insertDataPreparedStatement.setString(1, "1"); - insertDataPreparedStatement.setString(2, "1"); - insertDataPreparedStatement.execute(); - insertDataPreparedStatement.close(); - - // query data by using PreparedStatement - PreparedStatement queryDataPreparedStatement = connection.prepareStatement(QUERY_DATA_SQL); - queryDataPreparedStatement.setString(1, "1"); - ResultSet resultSet = queryDataPreparedStatement.executeQuery(); - resultSet.next(); - LOGGER.info("Query id[{}]: value={}", "1", resultSet.getString(2)); - queryDataPreparedStatement.close(); - - // drop table by using statement - Statement dropTableStatement = connection.createStatement(); - dropTableStatement.execute(DROP_TABLE_SQL); - dropTableStatement.close(); - } catch (SQLException e) { - String message = "Failed to execute sql"; - LOGGER.error(message); - throw new RuntimeException(message); - } finally { - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - String message = "Failed to close connection"; - LOGGER.error(message); - throw new RuntimeException(message); - } - } - } - return SUCCESS; - } - - @RequestMapping("/healthCheck") - @ResponseBody - public String healthCheck() { - Connection connection = null; - try { - connection = DriverManager.getConnection(connectURL, oracleUsername, oraclePassword); - PreparedStatement preparedStatement = connection.prepareStatement(TEST_EXIST_SQL); - preparedStatement.execute(); - preparedStatement.close(); - } catch (SQLException e) { - String message = "Failed to execute sql"; - LOGGER.error(message); - throw new RuntimeException(message); - } finally { - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - String message = "Failed to close connection"; - LOGGER.error(message); - throw new RuntimeException(message); - } - } - } - return SUCCESS; - } -} diff --git a/test/plugin/scenarios/postgresql-above9.4.1207-scenario/support-version.list b/test/plugin/scenarios/postgresql-above9.4.1207-scenario/support-version.list index 99dbebec49..3aa745581d 100644 --- a/test/plugin/scenarios/postgresql-above9.4.1207-scenario/support-version.list +++ b/test/plugin/scenarios/postgresql-above9.4.1207-scenario/support-version.list @@ -14,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -9.4.1212 42.0.0 42.1.4 42.2.8 diff --git a/test/plugin/scenarios/redisson-scenario/support-version.list b/test/plugin/scenarios/redisson-scenario/support-version.list index 78009656c1..aa0a5594df 100644 --- a/test/plugin/scenarios/redisson-scenario/support-version.list +++ b/test/plugin/scenarios/redisson-scenario/support-version.list @@ -14,6 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +# 3.5.0-3.12.4, 3.26.1-3.30.0 have been tested, and 3.12.5-3.26.0 are also supported but not included in the test +3.30.0 +3.29.0 +3.28.0 +3.27.2 +3.26.1 +3.12.4 3.11.5 3.10.7 3.9.1 diff --git a/test/plugin/scenarios/resteasy-6.x-scenario/support-version.list b/test/plugin/scenarios/resteasy-6.x-scenario/support-version.list index 49ada0d6d6..9b953ddf82 100644 --- a/test/plugin/scenarios/resteasy-6.x-scenario/support-version.list +++ b/test/plugin/scenarios/resteasy-6.x-scenario/support-version.list @@ -15,6 +15,6 @@ # limitations under the License. # test all 6.x version, with corresponding latest patch version -6.2.4.Final +6.2.16.Final 6.1.0.Final 6.0.3.Final \ No newline at end of file diff --git a/test/plugin/scenarios/resttemplate-6.x-scenario/bin/startup.sh b/test/plugin/scenarios/resttemplate-6.x-scenario/bin/startup.sh index 197feca45a..bd736d21e8 100644 --- a/test/plugin/scenarios/resttemplate-6.x-scenario/bin/startup.sh +++ b/test/plugin/scenarios/resttemplate-6.x-scenario/bin/startup.sh @@ -18,6 +18,6 @@ home="$(cd "$(dirname $0)"; pwd)" -export SW_EXCLUDE_PLUGINS=spring-mvc-annotation,spring-mvc-annotation-3.x,spring-mvc-annotation-4.x,spring-mvc-annotation-5.x,spring-resttemplate-4.x,tomcat-7.x/8.x,tomcat-10.x +export SW_EXCLUDE_PLUGINS=spring-mvc-annotation,spring-mvc-annotation-3.x,spring-mvc-annotation-4.x,spring-mvc-annotation-5.x,spring-resttemplate-4.x,tomcat java -jar ${agent_opts} ${home}/../libs/resttemplate-6.x-scenario.jar & diff --git a/test/plugin/scenarios/rocketmq-5-grpc-scenario/pom.xml b/test/plugin/scenarios/rocketmq-5-grpc-scenario/pom.xml index d27b6488b1..018ce5430e 100644 --- a/test/plugin/scenarios/rocketmq-5-grpc-scenario/pom.xml +++ b/test/plugin/scenarios/rocketmq-5-grpc-scenario/pom.xml @@ -34,7 +34,7 @@ 1.8 3.8.1 5.0.5 - 5.1.1 + 5.1.4 2.1.6.RELEASE 1.18.20 diff --git a/test/plugin/scenarios/rocketmq-5-grpc-scenario/src/main/java/test/apache/skywalking/apm/testcase/rocketmq/client/java/controller/CaseController.java b/test/plugin/scenarios/rocketmq-5-grpc-scenario/src/main/java/test/apache/skywalking/apm/testcase/rocketmq/client/java/controller/CaseController.java index 5219bc8db9..313389913e 100644 --- a/test/plugin/scenarios/rocketmq-5-grpc-scenario/src/main/java/test/apache/skywalking/apm/testcase/rocketmq/client/java/controller/CaseController.java +++ b/test/plugin/scenarios/rocketmq-5-grpc-scenario/src/main/java/test/apache/skywalking/apm/testcase/rocketmq/client/java/controller/CaseController.java @@ -48,35 +48,27 @@ public class CaseController { @Autowired private MessageService messageService; + private volatile boolean consumerStarted = false; + @RequestMapping("/rocketmq-5-grpc-scenario") @ResponseBody public String testcase() { try { messageService.sendNormalMessage(NORMAL_TOPIC, TAG_NOMARL, GROUP); - Thread t1 = new Thread(() -> messageService.pushConsumes( - Collections.singletonList(NORMAL_TOPIC), - Collections.singletonList(TAG_NOMARL), - GROUP - )); - t1.start(); - t1.join(); messageService.sendNormalMessageAsync(ASYNC_PRODUCER_TOPIC, TAG_ASYNC_PRODUCER, GROUP); messageService.sendNormalMessageAsync(ASYNC_PRODUCER_TOPIC, TAG_ASYNC_PRODUCER, GROUP); - Thread t2 = new Thread(() -> messageService.simpleConsumes(Collections.singletonList(ASYNC_PRODUCER_TOPIC), - Collections.singletonList(TAG_ASYNC_PRODUCER), GROUP, - 10, 10 - )); - t2.start(); - t2.join(); + new Thread(() -> messageService.simpleConsumes( + Collections.singletonList(ASYNC_PRODUCER_TOPIC), + Collections.singletonList(TAG_ASYNC_PRODUCER), GROUP, + 10, 10 + )).start(); messageService.sendNormalMessage(ASYNC_CONSUMER_TOPIC, TAG_ASYNC_CONSUMER, GROUP); messageService.sendNormalMessage(ASYNC_CONSUMER_TOPIC, TAG_ASYNC_CONSUMER, GROUP); - Thread t3 = new Thread(() -> messageService.simpleConsumeAsync(ASYNC_CONSUMER_TOPIC, TAG_ASYNC_CONSUMER, GROUP, 10, - 10 - )); - t3.start(); - t3.join(); + new Thread(() -> messageService.simpleConsumeAsync( + ASYNC_CONSUMER_TOPIC, TAG_ASYNC_CONSUMER, GROUP, 10, 10 + )).start(); } catch (Exception e) { log.error("testcase error", e); } @@ -86,11 +78,45 @@ public String testcase() { @RequestMapping("/healthCheck") @ResponseBody public String healthCheck() throws Exception { - System.setProperty(MixAll.ROCKETMQ_HOME_ENV, this.getClass().getResource("/").getPath()); - messageService.updateNormalTopic(NORMAL_TOPIC); - messageService.updateNormalTopic(ASYNC_PRODUCER_TOPIC); - messageService.updateNormalTopic(ASYNC_CONSUMER_TOPIC); - final Producer producer = ProducerSingleton.getInstance(endpoints, NORMAL_TOPIC); + if (!consumerStarted) { + // Set flag early to prevent re-entry from concurrent healthCheck + // requests (each curl has a 3s timeout, and initialization may + // take longer than that). + consumerStarted = true; + try { + System.setProperty(MixAll.ROCKETMQ_HOME_ENV, this.getClass().getResource("/").getPath()); + messageService.updateNormalTopic(NORMAL_TOPIC); + messageService.updateNormalTopic(ASYNC_PRODUCER_TOPIC); + messageService.updateNormalTopic(ASYNC_CONSUMER_TOPIC); + // Start push consumer early so it has time to receive messages + messageService.pushConsumes( + Collections.singletonList(NORMAL_TOPIC), + Collections.singletonList(TAG_NOMARL), + GROUP + ); + final Producer producer = ProducerSingleton.getInstance(endpoints, NORMAL_TOPIC); + // Send a probe message so the consumer has something to receive + messageService.sendNormalMessage(NORMAL_TOPIC, TAG_NOMARL, GROUP); + } catch (Exception e) { + consumerStarted = false; + throw e; + } + } + + // Wait until the consumer has actually received a probe message, + // confirming it can consume from the topic. + // Send a fresh probe on every retry so the consumer picks it up once + // rebalance finishes (messages sent before rebalance may never arrive). + if (!MessageService.CONSUMER_READY) { + try { + messageService.sendNormalMessage(NORMAL_TOPIC, TAG_NOMARL, GROUP); + System.out.printf("HealthCheck: sent probe message (consumer not ready yet).%n"); + } catch (Exception e) { + System.out.printf("HealthCheck: failed to send probe: %s%n", e.getMessage()); + } + throw new RuntimeException("Consumer has not received probe message yet"); + } + return SUCCESS; } } diff --git a/test/plugin/scenarios/rocketmq-5-grpc-scenario/src/main/java/test/apache/skywalking/apm/testcase/rocketmq/client/java/controller/MessageService.java b/test/plugin/scenarios/rocketmq-5-grpc-scenario/src/main/java/test/apache/skywalking/apm/testcase/rocketmq/client/java/controller/MessageService.java index 0c87f66981..33b9bad539 100644 --- a/test/plugin/scenarios/rocketmq-5-grpc-scenario/src/main/java/test/apache/skywalking/apm/testcase/rocketmq/client/java/controller/MessageService.java +++ b/test/plugin/scenarios/rocketmq-5-grpc-scenario/src/main/java/test/apache/skywalking/apm/testcase/rocketmq/client/java/controller/MessageService.java @@ -87,8 +87,8 @@ public void sendNormalMessageAsync(String topic, String tag, String group) throw .build(); try { CompletableFuture future = producer.sendAsync(message); - future.join(); - log.info("Send async message successfully"); + SendReceipt sendReceipt = future.join(); + log.info("Send async message successfully, messageId={}", sendReceipt.getMessageId()); } catch (Throwable t) { log.error("Failed to send message", t); } @@ -142,17 +142,34 @@ public void simpleConsumes(List topics, .build(); Duration invisibleDuration = Duration.ofSeconds(duration); - final List messages = consumer.receive(maxMessageNum, invisibleDuration); - messages.forEach(messageView -> { - log.info("Received message: {}", messageView); - }); - for (MessageView msg : messages) { - final MessageId messageId = msg.getMessageId(); - try { - consumer.ack(msg); - log.info("Message is acknowledged successfully, messageId={}", messageId); - } catch (Throwable t) { - log.error("Message is failed to be acknowledged, messageId={}", messageId, t); + int counter = 0; + int checkCounter = 0; + while (true) { + final List messages = consumer.receive(maxMessageNum, invisibleDuration); + messages.forEach(messageView -> { + log.info("Received message: {}", messageView); + }); + boolean finishFlag = false; + for (MessageView msg : messages) { + final MessageId messageId = msg.getMessageId(); + try { + consumer.ack(msg); + log.info("Message is acknowledged successfully, messageId={}", messageId); + counter++; + if (counter >= 2) { + finishFlag = true; + } + } catch (Throwable t) { + log.error("Message is failed to be acknowledged, messageId={}", messageId, t); + } + } + checkCounter++; + if (finishFlag) { + break; + } + if (checkCounter >= 3) { + log.error("Message is failed to receive after 3 attempts"); + break; } } } catch (Exception e) { @@ -186,26 +203,23 @@ public void simpleConsumeAsync(String topic, maxMessageNum, invisibleDuration ); - future0.whenCompleteAsync((messages, throwable) -> { - if (null != throwable) { - log.error("Failed to receive message from remote", throwable); - return; - } + future0.thenComposeAsync(messages -> { log.info("Received {} message(s)", messages.size()); final Map> map = messages.stream().collect(Collectors.toMap(message -> message, consumer::ackAsync)); - for (Map.Entry> entry : map.entrySet()) { + List> ackFutures = map.entrySet().stream().map(entry -> { final MessageId messageId = entry.getKey().getMessageId(); final CompletableFuture future = entry.getValue(); - future.whenCompleteAsync((v, t) -> { + return future.whenCompleteAsync((v, t) -> { if (null != t) { log.error("Message is failed to be acknowledged, messageId={}", messageId, t); return; } log.info("Message is acknowledged successfully, messageId={}", messageId); }, ackCallbackExecutor); - } - }, receiveCallbackExecutor); + }).collect(Collectors.toList()); + return CompletableFuture.allOf(ackFutures.toArray(new CompletableFuture[0])); + }, receiveCallbackExecutor).join(); } catch (Exception e) { log.error("consumer start error", e); } @@ -226,6 +240,8 @@ public void updateNormalTopic(String topic) { MQAdminStartup.main(subArgs); } + public static volatile boolean CONSUMER_READY = false; + public static class MyConsumer implements MessageListener { @Override @@ -233,6 +249,7 @@ public ConsumeResult consume(MessageView messageView) { log.info("Consume message successfully, messageId={},messageBody={}", messageView.getMessageId(), messageView.getBody().toString() ); + CONSUMER_READY = true; return ConsumeResult.SUCCESS; } } diff --git a/test/plugin/scenarios/rocketmq-5-grpc-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/rocketmq-5-grpc-scenario/src/main/resources/log4j2.xml index 9849ed5a8a..b16354bfa3 100644 --- a/test/plugin/scenarios/rocketmq-5-grpc-scenario/src/main/resources/log4j2.xml +++ b/test/plugin/scenarios/rocketmq-5-grpc-scenario/src/main/resources/log4j2.xml @@ -23,7 +23,7 @@ - + diff --git a/test/plugin/scenarios/rocketmq-5-grpc-scenario/support-version.list b/test/plugin/scenarios/rocketmq-5-grpc-scenario/support-version.list index 4f8dd365dc..c7b3c10e09 100644 --- a/test/plugin/scenarios/rocketmq-5-grpc-scenario/support-version.list +++ b/test/plugin/scenarios/rocketmq-5-grpc-scenario/support-version.list @@ -16,5 +16,4 @@ # lists your version here (Contains only the last version number of each minor version.) -5.1.1 -5.1.4 +5.4.0 diff --git a/test/plugin/scenarios/rocketmq-scenario/src/main/java/test/apache/skywalking/apm/testcase/rocketmq/controller/CaseController.java b/test/plugin/scenarios/rocketmq-scenario/src/main/java/test/apache/skywalking/apm/testcase/rocketmq/controller/CaseController.java index ab96fd6a7c..85d32069a5 100644 --- a/test/plugin/scenarios/rocketmq-scenario/src/main/java/test/apache/skywalking/apm/testcase/rocketmq/controller/CaseController.java +++ b/test/plugin/scenarios/rocketmq-scenario/src/main/java/test/apache/skywalking/apm/testcase/rocketmq/controller/CaseController.java @@ -25,6 +25,7 @@ import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.common.RemotingHelper; @@ -47,17 +48,19 @@ public class CaseController { @Value("${name.server}") private String namerServer; + private volatile boolean consumerStarted = false; + private volatile boolean consumerReady = false; + private DefaultMQProducer probeProducer; + @RequestMapping("/rocketmq-scenario") @ResponseBody public String testcase() { try { - // start producer DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); producer.setNamesrvAddr(namerServer); producer.start(); System.out.printf("Provider Started.%n"); - // send msg Message msg = new Message("TopicTest", ("Hello RocketMQ sendMsg " + new Date()).getBytes(RemotingHelper.DEFAULT_CHARSET) ); @@ -65,30 +68,6 @@ public String testcase() { msg.setKeys("KeyA"); SendResult sendResult = producer.send(msg); System.out.printf("%s send msg: %s%n", new Date(), sendResult); - - // start consumer - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - try { - DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name"); - consumer.setNamesrvAddr(namerServer); - consumer.subscribe("TopicTest", "*"); - consumer.registerMessageListener(new MessageListenerConcurrently() { - @Override - public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { - System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), new String(msgs.get(0).getBody(), StandardCharsets.UTF_8)); - return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; - } - }); - consumer.start(); - System.out.printf("Consumer Started.%n"); - } catch (Exception e) { - log.error("consumer start error", e); - } - } - }); - thread.start(); } catch (Exception e) { log.error("testcase error", e); } @@ -98,11 +77,60 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeCo @RequestMapping("/healthCheck") @ResponseBody public String healthCheck() throws Exception { - // start producer - DefaultMQProducer producer = new DefaultMQProducer("healthCheck_please_rename_unique_group_name"); - producer.setNamesrvAddr(namerServer); - producer.start(); - System.out.printf("HealthCheck Provider Started.%n"); + if (!consumerStarted) { + // Set flag early to prevent re-entry from concurrent healthCheck + // requests (each curl has a 3s timeout, and initialization may + // take longer than that). + consumerStarted = true; + try { + // Speed up client-side rebalance from default 20s to 2s so the + // consumer discovers topic queues faster after startup. + System.setProperty("rocketmq.client.rebalance.waitInterval", "2000"); + + // Start a producer that stays alive to send probe messages on each retry. + probeProducer = new DefaultMQProducer("healthCheck_please_rename_unique_group_name"); + probeProducer.setNamesrvAddr(namerServer); + probeProducer.start(); + Message probeMsg = new Message("TopicTest", "probe".getBytes(StandardCharsets.UTF_8)); + probeProducer.send(probeMsg); + System.out.printf("HealthCheck: Topic created via probe message.%n"); + + // Start consumer after topic exists so rebalance finds queues immediately. + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name"); + consumer.setNamesrvAddr(namerServer); + consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); + consumer.subscribe("TopicTest", "*"); + consumer.registerMessageListener(new MessageListenerConcurrently() { + @Override + public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { + System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), new String(msgs.get(0).getBody(), StandardCharsets.UTF_8)); + consumerReady = true; + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + } + }); + consumer.start(); + System.out.printf("Consumer Started.%n"); + } catch (Exception e) { + consumerStarted = false; + throw e; + } + } + + // Wait until the consumer has actually received a probe message, + // confirming rebalance is complete and it can consume from the topic. + // Send a fresh probe on every retry so the consumer picks it up once + // rebalance finishes (messages sent before rebalance may never arrive). + if (!consumerReady) { + try { + Message probeMsg = new Message("TopicTest", "probe".getBytes(StandardCharsets.UTF_8)); + probeProducer.send(probeMsg); + System.out.printf("HealthCheck: sent probe message (consumer not ready yet).%n"); + } catch (Exception e) { + System.out.printf("HealthCheck: failed to send probe: %s%n", e.getMessage()); + } + throw new RuntimeException("Consumer has not received probe message yet"); + } + return SUCCESS; } diff --git a/test/plugin/scenarios/rocketmq-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/rocketmq-scenario/src/main/resources/log4j2.xml index 9849ed5a8a..b16354bfa3 100644 --- a/test/plugin/scenarios/rocketmq-scenario/src/main/resources/log4j2.xml +++ b/test/plugin/scenarios/rocketmq-scenario/src/main/resources/log4j2.xml @@ -23,7 +23,7 @@ - + diff --git a/test/plugin/scenarios/rocketmq-scenario/support-version.list b/test/plugin/scenarios/rocketmq-scenario/support-version.list index b391ea9d2a..8dc0b9e562 100644 --- a/test/plugin/scenarios/rocketmq-scenario/support-version.list +++ b/test/plugin/scenarios/rocketmq-scenario/support-version.list @@ -16,11 +16,11 @@ # lists your version here (Contains only the last version number of each minor version.) -5.1.0 +5.4.0 4.9.4 4.8.0 4.7.1 4.6.0 4.5.2 4.4.0 -4.3.1 +4.3.1 \ No newline at end of file diff --git a/test/plugin/scenarios/solon-2.x-scenario/bin/startup.sh b/test/plugin/scenarios/solon-2.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..6cfc6ad702 --- /dev/null +++ b/test/plugin/scenarios/solon-2.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} "-Dskywalking.plugin.solon.http_params_length_threshold=1024" "-Dskywalking.plugin.solon.include_http_headers=content_type,host" ${home}/../libs/solon-2.x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/solon-2.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/solon-2.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..bca5273af9 --- /dev/null +++ b/test/plugin/scenarios/solon-2.x-scenario/config/expectedData.yaml @@ -0,0 +1,99 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +segmentItems: + - serviceName: solon-2.x-scenario + segmentSize: ge 2 + segments: + - segmentId: not null + spans: + - operationName: GET:/testcase/error + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 158 + isError: true + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: not null} + - {key: http.method, value: GET} + - {key: http.headers, value: not null} + - {key: http.params, value: not null} + - {key: http.status_code, value: '500'} + logs: + - logEvent: + - {key: event, value: error} + - {key: error.kind, value: java.lang.RuntimeException} + - {key: message, value: this is Error} + - {key: stack, value: not null} + refs: + - {parentEndpoint: 'GET:/testcase/test', networkAddress: 'localhost:8082', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: solon-2.x-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: H2/JDBC/PreparedStatement/executeQuery + parentSpanId: 0 + spanId: 1 + spanLayer: Database + startTime: not null + endTime: not null + componentId: 32 + isError: false + spanType: Exit + peer: localhost:-1 + skipAnalysis: false + tags: + - {key: db.type, value: H2} + - {key: db.instance, value: test} + - {key: db.statement, value: SELECT 1 = 1} + - operationName: /testcase/error + parentSpanId: 0 + spanId: 2 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 128 + isError: true + spanType: Exit + peer: localhost:8082 + skipAnalysis: false + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8082/testcase/error'} + - {key: http.status_code, value: '500'} + - operationName: GET:/testcase/test + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 158 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8082/testcase/test'} + - {key: http.method, value: GET} + - {key: http.headers, value: not null} + - {key: http.params, value: '{}'} + - {key: http.status_code, value: '200'} diff --git a/test/plugin/scenarios/solon-2.x-scenario/configuration.yml b/test/plugin/scenarios/solon-2.x-scenario/configuration.yml new file mode 100644 index 0000000000..33cd22a0dc --- /dev/null +++ b/test/plugin/scenarios/solon-2.x-scenario/configuration.yml @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8082/testcase/test +healthCheck: http://localhost:8082/testcase/healthCheck +startScript: ./bin/startup.sh diff --git a/test/plugin/scenarios/solon-2.x-scenario/pom.xml b/test/plugin/scenarios/solon-2.x-scenario/pom.xml new file mode 100644 index 0000000000..3b850fff6b --- /dev/null +++ b/test/plugin/scenarios/solon-2.x-scenario/pom.xml @@ -0,0 +1,105 @@ + + + + 4.0.0 + + org.apache.skywalking + solon-2.x-scenario + jar + 5.0.0 + + skywalking-solon-2.x-scenario + + + UTF-8 + 2.8.3 + ${test.solon.version} + + + + + org.noear + solon-api + ${test.solon.version} + + + cn.hutool + hutool-http + 5.8.25 + + + com.h2database + h2 + 1.4.200 + + + + + solon-2.x-scenario + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.0 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + org.noear + solon-maven-plugin + 2.8.3 + test.apache.skywalking.apm.testcase.sc.solon.App + + + + package + + repackage + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + + diff --git a/test/plugin/scenarios/solon-2.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/solon-2.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..c408decc9d --- /dev/null +++ b/test/plugin/scenarios/solon-2.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + ${project.build.directory} + ./libs + + solon-2.x-scenario.jar + + 0775 + + + diff --git a/test/plugin/scenarios/solon-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/solon/App.java b/test/plugin/scenarios/solon-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/solon/App.java new file mode 100644 index 0000000000..dafe2bf504 --- /dev/null +++ b/test/plugin/scenarios/solon-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/solon/App.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.solon; + +import org.noear.solon.Solon; +import org.noear.solon.annotation.SolonMain; + +@SolonMain +public class App { + + public static void main(String[] args) { + Solon.start(App.class, args); + } +} diff --git a/test/plugin/scenarios/solon-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/solon/controller/Controller.java b/test/plugin/scenarios/solon-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/solon/controller/Controller.java new file mode 100644 index 0000000000..dece4c9615 --- /dev/null +++ b/test/plugin/scenarios/solon-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/solon/controller/Controller.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.solon.controller; + +import cn.hutool.http.HttpRequest; +import org.noear.solon.annotation.Body; +import org.noear.solon.annotation.Get; +import org.noear.solon.annotation.Inject; +import org.noear.solon.annotation.Mapping; +import org.noear.solon.annotation.Path; +import test.apache.skywalking.apm.testcase.sc.solon.service.TestService; + +import java.sql.SQLException; + +@org.noear.solon.annotation.Controller +public class Controller { + + @Inject + private TestService testService; + + @Mapping("/testcase/healthCheck") + public String healthCheck() { + return "healthCheck"; + } + + @Get + @Mapping("/testcase/{test}") + public String hello(@Body String body, @Path("test") String test) throws SQLException { + testService.executeSQL(); + String body1 = HttpRequest.get("http://localhost:8082/testcase/error").execute().body(); + return "Hello World"; + } + + @Get + @Mapping("/testcase/error") + public String error() { + throw new RuntimeException("this is Error"); + } + +} diff --git a/test/plugin/scenarios/solon-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/solon/service/TestService.java b/test/plugin/scenarios/solon-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/solon/service/TestService.java new file mode 100644 index 0000000000..ed05be7667 --- /dev/null +++ b/test/plugin/scenarios/solon-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/solon/service/TestService.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.sc.solon.service; + +import org.noear.solon.annotation.Component; +import org.noear.solon.annotation.Init; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@Component +public class TestService { + + private Connection connection; + + @Init + private void setUp() throws SQLException { + connection = DriverManager.getConnection("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "sa", ""); + } + + public void executeSQL() throws SQLException { + PreparedStatement preparedStatement = connection.prepareStatement("SELECT 1 = 1"); + preparedStatement.executeQuery(); + } + +} diff --git a/test/plugin/scenarios/solon-2.x-scenario/src/main/resources/app.yml b/test/plugin/scenarios/solon-2.x-scenario/src/main/resources/app.yml new file mode 100644 index 0000000000..86f1a8ddc0 --- /dev/null +++ b/test/plugin/scenarios/solon-2.x-scenario/src/main/resources/app.yml @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +server.port: 8082 \ No newline at end of file diff --git a/test/plugin/scenarios/solon-2.x-scenario/support-version.list b/test/plugin/scenarios/solon-2.x-scenario/support-version.list new file mode 100644 index 0000000000..7e5c2fd224 --- /dev/null +++ b/test/plugin/scenarios/solon-2.x-scenario/support-version.list @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +2.7.2 +2.8.3 \ No newline at end of file diff --git a/test/plugin/scenarios/solrj-7.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/solrj-7.x-scenario/config/expectedData.yaml index 3ff59ca319..93dd81f044 100644 --- a/test/plugin/scenarios/solrj-7.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/solrj-7.x-scenario/config/expectedData.yaml @@ -32,7 +32,7 @@ segmentItems: tags: - {key: db.type, value: Solr} - {key: http.status_code, value: '200'} - - {key: QTime, value: gt 0} + - {key: QTime, value: ge 0} skipAnalysis: 'false' - operationName: solrJ/mycore/update/COMMIT parentSpanId: 0 @@ -47,7 +47,7 @@ segmentItems: tags: - {key: db.type, value: Solr} - {key: http.status_code, value: '200'} - - {key: QTime, value: gt 0} + - {key: QTime, value: ge 0} skipAnalysis: 'false' - operationName: solrJ/mycore/update/OPTIMIZE parentSpanId: 0 @@ -62,7 +62,7 @@ segmentItems: tags: - {key: db.type, value: Solr} - {key: http.status_code, value: '200'} - - {key: QTime, value: gt 0} + - {key: QTime, value: ge 0} skipAnalysis: 'false' - operationName: solrJ/mycore/select parentSpanId: 0 @@ -111,7 +111,7 @@ segmentItems: tags: - {key: db.type, value: Solr} - {key: http.status_code, value: '200'} - - {key: QTime, value: gt 0} + - {key: QTime, value: ge 0} skipAnalysis: 'false' - operationName: solrJ/mycore/update/DELETE_BY_QUERY parentSpanId: 0 @@ -126,7 +126,7 @@ segmentItems: tags: - {key: db.type, value: Solr} - {key: http.status_code, value: '200'} - - {key: QTime, value: gt 0} + - {key: QTime, value: ge 0} skipAnalysis: 'false' - operationName: GET:/solrj-scenario/case/solrj parentSpanId: -1 diff --git a/test/plugin/scenarios/spring-4.1.x-scenario/pom.xml b/test/plugin/scenarios/spring-4.1.x-scenario/pom.xml index 012ab866eb..b6a8d68bb0 100644 --- a/test/plugin/scenarios/spring-4.1.x-scenario/pom.xml +++ b/test/plugin/scenarios/spring-4.1.x-scenario/pom.xml @@ -33,6 +33,7 @@ 3.8.1 4.1.0.RELEASE spring + 9.4.0 skywalking-spring-4.1.x-scenario @@ -86,6 +87,26 @@ log4j-core 2.8.1 + + org.springframework + spring-aop + ${test.framework.version} + + + org.springframework + spring-aspects + ${test.framework.version} + + + org.springframework.retry + spring-retry + 1.2.5.RELEASE + + + org.apache.skywalking + apm-toolkit-trace + ${apm-toolkit-trace.version} + diff --git a/test/plugin/scenarios/spring-4.1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/config/RetryConfig.java b/test/plugin/scenarios/spring-4.1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/config/RetryConfig.java new file mode 100644 index 0000000000..4d7d40d153 --- /dev/null +++ b/test/plugin/scenarios/spring-4.1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/config/RetryConfig.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.retry.annotation.EnableRetry; + +@Configuration +@EnableRetry +public class RetryConfig { +} diff --git a/test/plugin/scenarios/spring-4.1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java b/test/plugin/scenarios/spring-4.1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java index c940d5425c..b99b6b38c3 100644 --- a/test/plugin/scenarios/spring-4.1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java +++ b/test/plugin/scenarios/spring-4.1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java @@ -18,7 +18,10 @@ package test.apache.skywalking.apm.testcase.spring3.service; +import org.apache.skywalking.apm.toolkit.trace.Trace; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; import test.apache.skywalking.apm.testcase.spring3.component.TestComponentBean; import test.apache.skywalking.apm.testcase.spring3.dao.TestRepositoryBean; @@ -35,4 +38,10 @@ public void doSomeBusiness(String name) { componentBean.componentMethod(name); repositoryBean.doSomeStuff(name); } + + // Test the class is enhanced by both SkyWalking and Spring AOP + @Retryable(value = Exception.class, backoff = @Backoff(delay = 1000, multiplier = 2)) + @Trace + private void doRetry() { + } } diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/config/expectedData.yaml b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..992a34dafd --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/config/expectedData.yaml @@ -0,0 +1,379 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: spring-6.x-mixed-servlet-scenario + segmentSize: ge 10 + segments: + - segmentId: not null + spans: + - operationName: HEAD:/healthCheck + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/healthCheck'} + - {key: http.method, value: HEAD} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: test.apache.skywalking.apm.testcase.spring3.component.TestComponentBean.componentMethod + parentSpanId: 1 + spanId: 2 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 93 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: test.apache.skywalking.apm.testcase.spring3.dao.TestRepositoryBean.doSomeStuff + parentSpanId: 1 + spanId: 3 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 93 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: test.apache.skywalking.apm.testcase.spring3.service.TestServiceBean.doSomeBusiness + parentSpanId: 0 + spanId: 1 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 93 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: GET:/case/spring3 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/case/spring3'} + - {key: http.method, value: GET} + - {key: http.headers, value: 'mock_header=[mock_value]'} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'POST:/create/' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/create/'} + - {key: http.method, value: POST} + - {key: http.status_code, value: '201'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'GET:/get/{id}' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/get/1'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 3, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'PUT:/update/{id}' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/update/1'} + - {key: http.method, value: PUT} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 4, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'DELETE:/delete/{id}' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/delete/1'} + - {key: http.method, value: DELETE} + - {key: http.status_code, value: '204'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 5, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GET:/inherit/child/test + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/inherit/child/test'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 6, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GET:/impl/requestmapping + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/impl/requestmapping'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 7, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'GET:/impl/restmapping' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/impl/restmapping'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 8, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: /spring-6.x-mixed-servlet-scenario/case/spring3 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/case/spring3'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/create/ + parentSpanId: 0 + spanId: 2 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/create/'} + - {key: http.method, value: POST} + - {key: http.status_code, value: '201'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/get/1 + parentSpanId: 0 + spanId: 3 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/get/1'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/update/1 + parentSpanId: 0 + spanId: 4 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/update/1'} + - {key: http.method, value: PUT} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/delete/1 + parentSpanId: 0 + spanId: 5 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/delete/1'} + - {key: http.method, value: DELETE} + - {key: http.status_code, value: '204'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/inherit/child/test + parentSpanId: 0 + spanId: 6 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/inherit/child/test'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/impl/requestmapping + parentSpanId: 0 + spanId: 7 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/impl/requestmapping'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/impl/restmapping + parentSpanId: 0 + spanId: 8 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/impl/restmapping'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: GET:/case/resttemplate + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/case/resttemplate'} + - {key: http.method, value: GET} + - key: http.params + value: |- + q1=[v1] + chinese=[中文] + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/configuration.yml b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/configuration.yml new file mode 100644 index 0000000000..8ca8d8b42b --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/configuration.yml @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: tomcat +entryService: '"http://localhost:8080/spring-6.x-mixed-servlet-scenario/case/resttemplate?q1=v1&chinese=%e4%b8%ad%e6%96%87"' +healthCheck: http://localhost:8080/spring-6.x-mixed-servlet-scenario/healthCheck +runningMode: with_optional +withPlugins: apm-spring-annotation-plugin-*.jar;apm-*-6.x-plugin-*.jar +environment: + - CATALINA_OPTS="-Dskywalking.plugin.http.include_http_headers=mock_header -Dskywalking.plugin.springmvc.collect_http_params=true" diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/pom.xml b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/pom.xml new file mode 100644 index 0000000000..4cd3b19779 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/pom.xml @@ -0,0 +1,143 @@ + + + + 4.0.0 + + org.apache.skywalking + spring-6.x-mixed-servlet-scenario + 5.0.0 + + war + + + UTF-8 + 17 + 3.11.0 + 6.0.0 + spring + + + skywalking-spring-6.x-mixed-servlet-scenario + + + + org.springframework + spring-context + ${test.framework.version} + + + org.springframework + spring-aop + ${test.framework.version} + + + org.springframework + spring-webmvc + ${test.framework.version} + + + org.springframework + spring-web + ${test.framework.version} + + + + com.fasterxml.jackson.core + jackson-databind + 2.14.3 + + + + com.google.code.gson + gson + 2.9.1 + + + + com.squareup.okhttp3 + okhttp + 4.10.0 + + + + org.apache.logging.log4j + log4j-api + 2.19.0 + + + org.apache.logging.log4j + log4j-core + 2.19.0 + + + + jakarta.servlet + jakarta.servlet-api + 6.0.0 + + + + javax.servlet + javax.servlet-api + 4.0.1 + + + + + spring-6.x-mixed-servlet-scenario + + + org.apache.maven.plugins + maven-war-plugin + 3.3.1 + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + true + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + + + + + spring-snapshots + https://repo.spring.io/snapshot + + + spring-milestones + https://repo.spring.io/milestone + + + \ No newline at end of file diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java new file mode 100644 index 0000000000..e7fb1871e5 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.entity; + +public class User { + + private int id; + private String userName; + + public User(int id) { + this.id = id; + } + + public User(int id, String userName) { + this.id = id; + this.userName = userName; + } + + public User() { + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseController.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseController.java new file mode 100644 index 0000000000..7a95a9318a --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseController.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.implinterface; + +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestCaseController implements TestCaseInterface { + + @Override + public String implRequestMappingAnnotationTestCase() { + return "implRequestMappingAnnotationTestCase"; + } + + @Override + public String implRestAnnotationTestCase() { + return "implRestAnnotationTestCase"; + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseInterface.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseInterface.java new file mode 100644 index 0000000000..4fea97b7b1 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseInterface.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.implinterface; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("/impl") +public interface TestCaseInterface { + @RequestMapping(path = "/requestmapping") + String implRequestMappingAnnotationTestCase(); + + @GetMapping("/restmapping") + String implRestAnnotationTestCase(); +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ChildController.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ChildController.java new file mode 100644 index 0000000000..27ddf54c76 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ChildController.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.inherit; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/inherit/child") +public class ChildController extends ParentController { + +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ParentController.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ParentController.java new file mode 100644 index 0000000000..16b928100d --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ParentController.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.inherit; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/inherit/parent") +public class ParentController { + + @RequestMapping("test") + public String test(Integer param) { + return "parent-a" + param; + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/restapi/RestCaseController.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/restapi/RestCaseController.java new file mode 100644 index 0000000000..6792ecb712 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/restapi/RestCaseController.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.restapi; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; +import test.apache.skywalking.apm.testcase.entity.User; + +@RestController +public class RestCaseController { + + private static final Map USERS = new ConcurrentHashMap(); + + @GetMapping(value = "/get/{id}") + @ResponseBody + private ResponseEntity getUser(@PathVariable("id") int id) throws InterruptedException { + User currentUser = new User(id, "a"); + return ResponseEntity.ok(currentUser); + } + + @PostMapping(value = "/create/") + @ResponseBody + public ResponseEntity createUser(@RequestBody User user, + UriComponentsBuilder ucBuilder) throws InterruptedException { + USERS.put(user.getId(), user); + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(ucBuilder.path("/get/{id}").buildAndExpand(user.getId()).toUri()); + return new ResponseEntity(headers, HttpStatus.CREATED); + } + + @PutMapping(value = "/update/{id}") + @ResponseBody + public ResponseEntity updateUser(@PathVariable("id") int id, + @RequestBody User user) throws InterruptedException { + User currentUser = new User(id, user.getUserName()); + return ResponseEntity.ok(currentUser); + } + + @DeleteMapping(value = "/delete/{id}") + @ResponseBody + public ResponseEntity deleteUser(@PathVariable("id") int id) throws InterruptedException { + User currentUser = USERS.get(id); + if (currentUser == null) { + return ResponseEntity.noContent().build(); + } + USERS.remove(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/resttemplate/RestTemplateController.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/resttemplate/RestTemplateController.java new file mode 100644 index 0000000000..62b93ac01a --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/resttemplate/RestTemplateController.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.resttemplate; + +import java.io.IOException; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.http.HttpEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import test.apache.skywalking.apm.testcase.entity.User; + +@RestController +public class RestTemplateController { + + private static final String SUCCESS = "Success"; + + private static final Logger LOGGER = LogManager.getLogger(RestTemplateController.class); + + private static final String URL = "http://localhost:8080/spring-6.x-mixed-servlet-scenario"; + + @RequestMapping("/case/resttemplate") + @ResponseBody + public String restTemplate() throws IOException { + Request request = new Request.Builder().header("mock_header", "mock_value").url(URL + "/case/spring3").build(); + Response response = new OkHttpClient().newCall(request).execute(); + LOGGER.info(response.toString()); + + // Create user + HttpEntity userEntity = new HttpEntity<>(new User(1, "a")); + new RestTemplate().postForEntity(URL + "/create/", userEntity, Void.class); + + // Find User + new RestTemplate().getForEntity(URL + "/get/{id}", User.class, 1); + + //Modify user + HttpEntity updateUserEntity = new HttpEntity<>(new User(1, "b")); + new RestTemplate().put(URL + "/update/{id}", updateUserEntity, userEntity.getBody().getId(), 1); + + //Delete user + new RestTemplate().delete(URL + "/delete/{id}", 1); + + Request inheritRequest = new Request.Builder().url(URL + "/inherit/child/test").build(); + response = new OkHttpClient().newCall(inheritRequest).execute(); + LOGGER.info(response.toString()); + + Request implRequestMappingRequest = new Request.Builder().url(URL + "/impl/requestmapping").build(); + response = new OkHttpClient().newCall(implRequestMappingRequest).execute(); + LOGGER.info(response.toString()); + + Request implRestMappingRequest = new Request.Builder().url(URL + "/impl/restmapping").build(); + response = new OkHttpClient().newCall(implRestMappingRequest).execute(); + LOGGER.info(response.toString()); + + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() { + return SUCCESS; + } + +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/CaseController.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/CaseController.java new file mode 100644 index 0000000000..501c691579 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/CaseController.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import test.apache.skywalking.apm.testcase.spring3.service.TestServiceBean; + +@Controller +public class CaseController { + + private static final String SUCCESS = "Success"; + + @Autowired + private TestServiceBean testServiceBean; + + @RequestMapping(value = "/case/spring3") + @ResponseBody + public String updateUser() { + testServiceBean.doSomeBusiness("test"); + return SUCCESS; + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/component/TestComponentBean.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/component/TestComponentBean.java new file mode 100644 index 0000000000..274aaf490e --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/component/TestComponentBean.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3.component; + +import org.springframework.stereotype.Component; + +@Component +public class TestComponentBean { + + public String componentMethod(String name) { + return name + "-" + "dealWith-component"; + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/dao/TestRepositoryBean.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/dao/TestRepositoryBean.java new file mode 100644 index 0000000000..84f871ef55 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/dao/TestRepositoryBean.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3.dao; + +import org.springframework.stereotype.Repository; + +@Repository +public class TestRepositoryBean { + + public String doSomeStuff(String name) { + return name + "-dealWithRepository"; + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java new file mode 100644 index 0000000000..811fe11648 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import test.apache.skywalking.apm.testcase.spring3.dao.TestRepositoryBean; +import test.apache.skywalking.apm.testcase.spring3.component.TestComponentBean; + +@Service +public class TestServiceBean { + @Autowired + private TestComponentBean componentBean; + + @Autowired + private TestRepositoryBean repositoryBean; + + public void doSomeBusiness(String name) { + componentBean.componentMethod(name); + repositoryBean.doSomeStuff(name); + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..b5cda5ae8a --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml new file mode 100644 index 0000000000..b8a2885d3e --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/webapp/WEB-INF/web.xml b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..e64a37771f --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,35 @@ + + + + skywalking-spring-6.x-mixed-servlet-scenario + + + spring-mvc + org.springframework.web.servlet.DispatcherServlet + 1 + + + spring-mvc + / + + diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/support-version.list b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/support-version.list new file mode 100644 index 0000000000..845ea5eb10 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/support-version.list @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Single version by design: this scenario tests the mixed javax+jakarta CLASSPATH +# condition (issue #13938), which is resolved by servlet-commons' runtime instanceof +# dispatch and is independent of the Spring version. Version compatibility across +# 6.0/6.1/6.2 is already covered by spring-6.x-scenario. Do NOT expand this list. +6.2.19 diff --git a/test/plugin/scenarios/spring-6.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/spring-6.x-scenario/config/expectedData.yaml index 926a3f2b4c..081211529f 100644 --- a/test/plugin/scenarios/spring-6.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/spring-6.x-scenario/config/expectedData.yaml @@ -371,5 +371,9 @@ segmentItems: tags: - {key: url, value: 'http://localhost:8080/spring-6.x-scenario/case/resttemplate'} - {key: http.method, value: GET} + - key: http.params + value: |- + q1=[v1] + chinese=[中文] - {key: http.status_code, value: '200'} skipAnalysis: 'false' diff --git a/test/plugin/scenarios/spring-6.x-scenario/configuration.yml b/test/plugin/scenarios/spring-6.x-scenario/configuration.yml index 4e81ad6a99..bb658f8656 100644 --- a/test/plugin/scenarios/spring-6.x-scenario/configuration.yml +++ b/test/plugin/scenarios/spring-6.x-scenario/configuration.yml @@ -15,9 +15,9 @@ # limitations under the License. type: tomcat -entryService: http://localhost:8080/spring-6.x-scenario/case/resttemplate +entryService: '"http://localhost:8080/spring-6.x-scenario/case/resttemplate?q1=v1&chinese=%e4%b8%ad%e6%96%87"' healthCheck: http://localhost:8080/spring-6.x-scenario/healthCheck runningMode: with_optional withPlugins: apm-spring-annotation-plugin-*.jar;apm-*-6.x-plugin-*.jar environment: - - CATALINA_OPTS="-Dskywalking.plugin.http.include_http_headers=mock_header" + - CATALINA_OPTS="-Dskywalking.plugin.http.include_http_headers=mock_header -Dskywalking.plugin.springmvc.collect_http_params=true" diff --git a/test/plugin/scenarios/spring-6.x-scenario/support-version.list b/test/plugin/scenarios/spring-6.x-scenario/support-version.list index c96f0b4304..947a86b2f5 100644 --- a/test/plugin/scenarios/spring-6.x-scenario/support-version.list +++ b/test/plugin/scenarios/spring-6.x-scenario/support-version.list @@ -15,5 +15,5 @@ # limitations under the License. 6.0.4 -6.1.0 6.1.1 +6.2.19 diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/bin/startup.sh b/test/plugin/scenarios/spring-ai-1.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..9950f6fed6 --- /dev/null +++ b/test/plugin/scenarios/spring-ai-1.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -Dskywalking.plugin.springai.collect_input_messages=true -Dskywalking.plugin.springai.collect_output_messages=true -Dskywalking.plugin.springai.collect_tool_input=true -Dskywalking.plugin.springai.collect_tool_output=true -Dskywalking.plugin.springai.collect_retrieval_query=true -Dskywalking.plugin.springai.collect_retrieval_documents=true -jar ${agent_opts} ${home}/../libs/spring-ai-1.x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/spring-ai-1.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..d714ae2542 --- /dev/null +++ b/test/plugin/scenarios/spring-ai-1.x-scenario/config/expectedData.yaml @@ -0,0 +1,199 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: + - serviceName: spring-ai-1.x-scenario + segmentSize: gt 0 + segments: + - segmentId: not null + spans: + - operationName: Spring-ai/tool/execute/get_weather + parentSpanId: 0 + spanId: 1 + spanLayer: Unknown + startTime: not null + endTime: not null + componentId: 178 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - { key: gen_ai.tool.name, value: get_weather } + - { key: gen_ai.operation.name, value: execute_tool } + - { key: gen_ai.tool.call.arguments, value: '{"arg0":"new york"}' } + - { key: gen_ai.tool.call.result, value: '"Sunny, 10°C"' } + + - operationName: Spring-ai/tool/call + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + startTime: not null + endTime: not null + componentId: 178 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + refs: + - { parentEndpoint: 'GET:/spring-ai-1.x-scenario/case/spring-ai-1.x-scenario-case', + networkAddress: '', refType: CrossThread, parentSpanId: 4, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: spring-ai-1.x-scenario, + traceId: not null } + + - segmentId: not null + spans: + - operationName: Spring-ai/tool/execute/get_weather + parentSpanId: 2 + spanId: 3 + spanLayer: Unknown + spanType: Local + startTime: not null + endTime: not null + tags: + - { key: gen_ai.tool.name, value: get_weather } + - { key: gen_ai.operation.name, value: execute_tool } + - { key: gen_ai.tool.call.arguments, value: '{"arg0":"new york"}' } + - { key: gen_ai.tool.call.result, value: '"Sunny, 10°C"' } + + - operationName: Spring-ai/tool/call + parentSpanId: 1 + spanId: 2 + spanLayer: Unknown + startTime: not null + endTime: not null + spanType: Local + + - operationName: Spring-ai/openai/call + parentSpanId: 0 + spanId: 1 + spanLayer: GenAI + startTime: not null + endTime: not null + componentId: 173 + spanType: Exit + peer: http://localhost:8080/spring-ai-1.x-scenario/llm/v1/chat/completions + tags: + - { key: gen_ai.operation.name, value: chat } + - { key: gen_ai.provider.name, value: openai } + - { key: gen_ai.request.model, value: gpt-4.1-2025-04-14 } + - { key: gen_ai.request.temperature, value: '0.7' } + - { key: gen_ai.request.top_p, value: '0.9' } + - { key: gen_ai.response.id, value: 'chatcmpl-CyJXJt7gxwDgz' } + - { key: gen_ai.response.model, value: gpt-4.1-2025-04-14 } + - { key: gen_ai.usage.input_tokens, value: '52' } + - { key: gen_ai.usage.output_tokens, value: '17' } + - { key: gen_ai.client.token.usage, value: '69' } + - { key: gen_ai.response.finish_reasons, value: STOP } + - { key: gen_ai.input.messages, value: not null } + - { key: gen_ai.output.messages, value: not null } + + - operationName: Spring-ai/openai/stream + parentSpanId: 0 + spanId: 4 + spanLayer: GenAI + startTime: not null + endTime: not null + componentId: 173 + spanType: Exit + peer: http://localhost:8080/spring-ai-1.x-scenario/llm/v1/chat/completions + tags: + - { key: gen_ai.operation.name, value: chat } + - { key: gen_ai.provider.name, value: openai } + - { key: gen_ai.request.model, value: gpt-4.1-2025-04-14 } + - { key: gen_ai.request.temperature, value: '0.7' } + - { key: gen_ai.request.top_k, value: null } + - { key: gen_ai.request.top_p, value: '0.9' } + - { key: gen_ai.server.time_to_first_token, value: not null } + - { key: gen_ai.response.id, value: 'chatcmpl-fc1b64d3' } + - { key: gen_ai.response.model, value: gpt-4.1-2025-04-14 } + - { key: gen_ai.response.finish_reasons, value: STOP } + - { key: gen_ai.usage.input_tokens, value: '104' } + - { key: gen_ai.usage.output_tokens, value: '34' } + - { key: gen_ai.client.token.usage, value: '138' } + - { key: gen_ai.input.messages, value: not null } + - { key: gen_ai.output.messages, value: not null } + + - operationName: /spring-ai-1.x-scenario/llm/v1/chat/completions + parentSpanId: 0 + spanId: 5 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 99 + isError: false + spanType: Exit + peer: localhost:8080 + skipAnalysis: false + tags: + - { key: url, value: 'http://localhost:8080/spring-ai-1.x-scenario/llm/v1/chat/completions' } + - { key: http.method, value: POST } + - { key: http.status_code, value: '200' } + + - operationName: retrieval/simple/in-memory-map + parentSpanId: 0 + spanId: 6 + spanLayer: GenAI + startTime: not null + endTime: not null + componentId: 178 + spanType: Exit + peer: simple/in-memory-map + tags: + - { key: gen_ai.operation.name, value: retrieval } + - { key: gen_ai.data_source.id, value: simple/in-memory-map } + - { key: gen_ai.request.model, value: text-embedding-3-small } + - { key: gen_ai.request.top_k, value: '2' } + - { key: gen_ai.retrieval.query.text, value: 'What is Apache SkyWalking?' } + - { key: gen_ai.retrieval.documents, value: not null } + + - operationName: Spring-ai/openai/call + parentSpanId: 0 + spanId: 7 + spanLayer: GenAI + startTime: not null + endTime: not null + componentId: 173 + isError: false + spanType: Exit + peer: http://localhost:8080/spring-ai-1.x-scenario/llm/v1/chat/completions + skipAnalysis: false + tags: + - { key: gen_ai.operation.name, value: chat } + - { key: gen_ai.provider.name, value: openai } + - { key: gen_ai.request.model, value: gpt-4.1-2025-04-14 } + - { key: gen_ai.request.temperature, value: '0.7' } + - { key: gen_ai.request.top_p, value: '0.9' } + - { key: gen_ai.response.id, value: chatcmpl-DknJunZ3tgcSkKiv } + - { key: gen_ai.response.model, value: gpt-4.1-2025-04-14 } + - { key: gen_ai.usage.input_tokens, value: '72' } + - { key: gen_ai.usage.output_tokens, value: '25' } + - { key: gen_ai.client.token.usage, value: '97' } + - { key: gen_ai.response.finish_reasons, value: STOP } + - { key: gen_ai.input.messages, value: not null } + - { key: gen_ai.output.messages, value: not null } + + - operationName: GET:/spring-ai-1.x-scenario/case/spring-ai-1.x-scenario-case + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + spanType: Entry + componentId: 1 + tags: + - { key: url, value: http://localhost:8080/spring-ai-1.x-scenario/case/spring-ai-1.x-scenario-case } + - { key: http.method, value: GET } + - { key: http.status_code, value: '200' } \ No newline at end of file diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/configuration.yml b/test/plugin/scenarios/spring-ai-1.x-scenario/configuration.yml new file mode 100644 index 0000000000..073d9271d5 --- /dev/null +++ b/test/plugin/scenarios/spring-ai-1.x-scenario/configuration.yml @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/spring-ai-1.x-scenario/case/spring-ai-1.x-scenario-case +healthCheck: http://localhost:8080/spring-ai-1.x-scenario/case/healthCheck +startScript: ./bin/startup.sh + diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/pom.xml b/test/plugin/scenarios/spring-ai-1.x-scenario/pom.xml new file mode 100644 index 0000000000..0d508db8b0 --- /dev/null +++ b/test/plugin/scenarios/spring-ai-1.x-scenario/pom.xml @@ -0,0 +1,161 @@ + + + + 4.0.0 + + org.apache.skywalking + spring-ai-1.x-scenario + 5.0.0 + + + UTF-8 + 17 + 3.8.1 + + + spring-ai-1.x-scenario + + + + org.springframework.boot + spring-boot-starter-web + 3.5.7 + + + + com.alibaba + fastjson + 1.2.83 + + + + org.springframework.ai + spring-ai-client-chat + + + + org.springframework.ai + spring-ai-starter-model-openai + + + + org.springframework.ai + spring-ai-vector-store + + + + org.projectlombok + lombok + 1.18.42 + + + + + + + + org.springframework.ai + spring-ai-bom + 1.1.0 + pom + import + + + + + + spring-ai-1.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + 3.5.7 + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + false + + + + + org.apache.maven.plugins + maven-jar-plugin + + + empty-javadoc-jar + package + + jar + + + javadoc + ${basedir}/javadoc + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..deed40fcfe --- /dev/null +++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ./target/spring-ai-1.x-scenario.jar + ./libs + 0775 + + + \ No newline at end of file diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/Application.java b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/Application.java new file mode 100644 index 0000000000..5147a224ee --- /dev/null +++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/Application.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.jdk.httpclient; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/config/ChatClientConfig.java b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/config/ChatClientConfig.java new file mode 100644 index 0000000000..02d0f3466b --- /dev/null +++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/config/ChatClientConfig.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.jdk.httpclient.config; + +import org.springframework.ai.document.Document; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.vectorstore.SimpleVectorStore; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; + +import java.util.ArrayList; +import java.util.List; + +@Configuration +public class ChatClientConfig { + + @Bean + public ChatClient openAIChatClient(OpenAiChatModel model) { + return ChatClient.create(model); + } + + @Bean + @Lazy + public VectorStore vectorStore(EmbeddingModel embeddingModel) { + SimpleVectorStore vectorStore = SimpleVectorStore.builder(embeddingModel).build(); + + List documentList = new ArrayList<>(); + documentList.add(new Document("The 2025 AI Summit is scheduled for October 10-12 in San Francisco. " + + "The event will focus on Generative AI and Autonomous Agents.")); + documentList.add(new Document("Apache SkyWalking is an open-source Application Performance Management system " + + "designed for microservices, cloud native, and container-based architectures.")); + documentList.add(new Document("Spring AI provides a unified interface for interacting with different " + + "AI Models, allowing developers to switch between providers with minimal code changes.")); + documentList.add(new Document("A new distributed tracing protocol, TraceContext v2, was proposed " + + "on August 25, 2025, to improve cross-cloud observability.")); + + vectorStore.add(documentList); + return vectorStore; + } +} diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java new file mode 100644 index 0000000000..73dc1b15bc --- /dev/null +++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.jdk.httpclient.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.document.Document; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import test.apache.skywalking.apm.testcase.jdk.httpclient.tool.WeatherTool; + +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/case") +@RequiredArgsConstructor +public class CaseController { + + private final WeatherTool weatherTool; + private final ChatClient chatClient; + private final ObjectProvider vectorStoreProvider; + + @GetMapping("/healthCheck") + public String healthCheck() { + return "Success"; + } + + @GetMapping("/spring-ai-1.x-scenario-case") + public String testCase() throws Exception { + + String systemPrompt = """ + You are a professional technical assistant. + Strictly use the provided context to answer questions. + If the information is not in the context, say: "I'm sorry, I don't have that information in my knowledge base." + Do not use outside knowledge. Be concise. + """; + + chatClient + .prompt("What's the weather in New York?") + .system(systemPrompt) + .tools(weatherTool) + .call() + .content(); + + chatClient + .prompt("What's the weather in New York?") + .system(systemPrompt) + .tools(weatherTool) + .stream() + .content() + .doOnNext(System.out::println) + .blockLast(); + + String question = "What is Apache SkyWalking?"; + VectorStore vectorStore = vectorStoreProvider.getObject(); + String context = vectorStore.similaritySearch(SearchRequest.builder() + .query(question) + .topK(2) + .build()) + .stream() + .map(Document::getText) + .collect(Collectors.joining("\n")); + + chatClient + .prompt(question) + .system("Answer using only the following context:\n" + context) + .call() + .content(); + + return "success"; + } +} diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/LLMMockController.java b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/LLMMockController.java new file mode 100644 index 0000000000..d63482c5fd --- /dev/null +++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/LLMMockController.java @@ -0,0 +1,367 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.jdk.httpclient.controller; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.io.PrintWriter; +import java.time.Instant; + +@RestController +@RequestMapping("/llm") +public class LLMMockController { + @RequestMapping("/v1/chat/completions") + public Object completions(@RequestBody JSONObject request, HttpServletResponse response) throws IOException { + Boolean isStream = request.getBoolean("stream"); + if (isStream == null) isStream = false; + + JSONArray messages = request.getJSONArray("messages"); + JSONObject lastMessage = messages.getJSONObject(messages.size() - 1); + String lastRole = lastMessage.getString("role"); + + if (isStream) { + response.setContentType("text/event-stream"); + response.setCharacterEncoding("UTF-8"); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Connection", "keep-alive"); + + PrintWriter writer = response.getWriter(); + String id = "chatcmpl-fc1b64d3"; + long created = Instant.now().getEpochSecond(); + String model = "gpt-4.1-2025-04-14"; + + try { + if ("tool".equals(lastRole)) { + String fullContent = "The weather in New York is currently sunny with a temperature of 10°C."; + writeStreamChunk(writer, id, created, model, "{\"role\":\"assistant\"}", "null"); + + int len = fullContent.length(); + String[] parts = {fullContent.substring(0, len / 3), fullContent.substring(len / 3, len * 2 / 3), fullContent.substring(len * 2 / 3)}; + + for (String part : parts) { + Thread.sleep(50); + writeStreamChunk(writer, id, created, model, "{\"content\":\"" + escapeJson(part) + "\"}", "null"); + } + + writeStreamChunk(writer, id, created, model, "{}", "\"stop\""); + } else { + writeStreamChunk(writer, id, created, model, "{\"role\":\"assistant\"}", "null"); + + String toolCallDelta = """ + { + "tool_calls": [ + { + "index": 0, + "id": "call_iV4bvFIZujbb", + "type": "function", + "function": { + "name": "get_weather", + "arguments": "" + } + } + ] + } + """; + writeStreamChunk(writer, id, created, model, toolCallDelta, "null"); + + String args = "{\\\"arg0\\\":\\\"new york\\\"}"; + String argsDelta = """ + { + "tool_calls": [ + { + "index": 0, + "function": { + "arguments": "%s" + } + } + ] + } + """.formatted(args); + Thread.sleep(50); + writeStreamChunk(writer, id, created, model, argsDelta, "null"); + + writeStreamChunk(writer, id, created, model, "{}", "\"tool_calls\""); + } + + writer.write("data: [DONE]\n\n"); + writer.flush(); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return null; + } + + String toolCallResponse = """ + { + "choices": [ + { + "finish_reason": "tool_calls", + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "function": { + "arguments": "{\\"arg0\\":\\"new york\\"}", + "name": "get_weather" + }, + "id": "call_iV4bvFIZujbb", + "type": "function" + } + ] + } + } + ], + "created": 1768490813, + "id": "chatcmpl-CyJXJt7gxwDgz", + "usage": { + "completion_tokens": 17, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0 + }, + "prompt_tokens": 52, + "prompt_tokens_details": { + "audio_tokens": 0, + "cached_tokens": 0 + }, + "total_tokens": 69 + }, + "model": "gpt-4.1-2025-04-14", + "object": "chat.completion" + } + """; + + String finalResponse = """ + { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": "The weather in New York is currently sunny with a temperature of 10°C.", + "role": "assistant" + } + } + ], + "created": 1768491057, + "id":"chatcmpl-CyJXJt7gxwDgz", + "model": "gpt-4.1-2025-04-14", + "object": "chat.completion" + } + """; + + String ragLlmResponse = """ + { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "logprobs": null, + "message": { + "annotations": [], + "content": "Apache SkyWalking is an open-source Application Performance Management system designed for microservices, cloud native, and container-based architectures.", + "refusal": null, + "role": "assistant" + } + } + ], + "created": 1780045046, + "id": "chatcmpl-DknJunZ3tgcSkKiv", + "model": "gpt-4.1-2025-04-14", + "object": "chat.completion", + "service_tier": "default", + "system_fingerprint": "fp_a7294185dc", + "usage": { + "completion_tokens": 25, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0 + }, + "latency_checkpoint": { + "engine_tbt_ms": 23, + "engine_ttft_ms": 672, + "engine_ttlt_ms": 1259, + "pre_inference_ms": 157, + "service_tbt_ms": 610, + "service_ttft_ms": 12302, + "service_ttlt_ms": 27518, + "total_duration_ms": 27381, + "user_visible_ttft_ms": 12145 + }, + "prompt_tokens": 72, + "prompt_tokens_details": { + "audio_tokens": 0, + "cached_tokens": 0 + }, + "total_tokens": 97 + } + } + """; + + if (isRagLlmRequest(messages)) { + return JSON.parseObject(ragLlmResponse); + } + + if ("tool".equals(lastRole)) { + return JSON.parseObject(finalResponse); + } + + return JSON.parseObject(toolCallResponse); + } + + private void writeStreamChunk(PrintWriter writer, String id, long created, String model, String delta, String finishReason) { + String json = """ + { + "choices": [ + { + "delta": %s, + "finish_reason": %s, + "index": 0, + "logprobs": null + } + ], + "object": "chat.completion.chunk", + "usage": { + "completion_tokens": 17, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0 + }, + "prompt_tokens": 52, + "prompt_tokens_details": { + "audio_tokens": 0, + "cached_tokens": 0 + }, + "total_tokens": 69 + }, + "created": %d, + "system_fingerprint": null, + "model": "%s", + "id": "%s" + } + """.formatted(delta, finishReason, created, model, id); + + String cleanJson = json.replace("\n", "").replace("\r", ""); + writer.write("data: " + cleanJson + "\n\n"); + writer.flush(); + } + + private String escapeJson(String input) { + if (input == null) return ""; + return input.replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r"); + } + + private boolean isRagLlmRequest(JSONArray messages) { + if (messages == null || messages.size() < 2) { + return false; + } + + JSONObject lastMessage = messages.getJSONObject(messages.size() - 1); + if (!"user".equals(lastMessage.getString("role")) + || !"What is Apache SkyWalking?".equals(lastMessage.getString("content"))) { + return false; + } + + for (int i = 0; i < messages.size() - 1; i++) { + JSONObject message = messages.getJSONObject(i); + if (!"system".equals(message.getString("role"))) { + continue; + } + String content = message.getString("content"); + if (content != null + && content.startsWith("Answer using only the following context:") + && content.contains("Apache SkyWalking is an open-source Application Performance Management system")) { + return true; + } + } + return false; + } + + @PostMapping("/v1/embeddings") + public Object embeddings(@RequestBody JSONObject request) { + Object input = request.get("input"); + JSONArray inputs = input instanceof JSONArray ? (JSONArray) input : new JSONArray(); + if (!(input instanceof JSONArray)) { + inputs.add(String.valueOf(input)); + } + + JSONArray data = new JSONArray(); + for (int i = 0; i < inputs.size(); i++) { + JSONObject item = new JSONObject(); + item.put("object", "embedding"); + item.put("index", i); + item.put("embedding", embeddingFor(inputs.getString(i))); + data.add(item); + } + + JSONObject usage = new JSONObject(); + usage.put("prompt_tokens", inputs.size()); + usage.put("total_tokens", inputs.size()); + + JSONObject response = new JSONObject(); + response.put("object", "list"); + response.put("model", "text-embedding-3-small"); + response.put("data", data); + response.put("usage", usage); + return response; + } + + private JSONArray embeddingFor(String input) { + String text = input == null ? "" : input.toLowerCase(); + double[] values = new double[]{ + score(text, "summit", "san francisco", "generative", "autonomous"), + score(text, "skywalking", "apm", "microservices", "cloud native"), + score(text, "spring ai", "models", "providers"), + score(text, "tracecontext", "tracing", "observability"), + 0.1 + }; + + JSONArray embedding = new JSONArray(); + for (double value : values) { + embedding.add(value); + } + return embedding; + } + + private double score(String text, String... keywords) { + double value = 0.0; + for (String keyword : keywords) { + if (text.contains(keyword)) { + value += 1.0; + } + } + return value; + } +} diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/tool/WeatherTool.java b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/tool/WeatherTool.java new file mode 100644 index 0000000000..1606b67436 --- /dev/null +++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/tool/WeatherTool.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.jdk.httpclient.tool; + +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.stereotype.Component; + +@Component +public class WeatherTool { + @Tool(name = "get_weather", description = "Get weather by city name") + public String getWeather(String city) { + return switch (city.toLowerCase()) { + case "new york" -> "Sunny, 10°C"; + case "london" -> "Cloudy, 12°C"; + default -> "Unknown city"; + }; + } +} diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000..fceabe447e --- /dev/null +++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/resources/application.yaml @@ -0,0 +1,38 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +server: + port: 8080 + servlet: + context-path: /spring-ai-1.x-scenario + +spring: + ai: + openai: + api-key: xxxxxxxxxxxxxxxxxxxxxxxxxxx + base-url: http://localhost:8080/spring-ai-1.x-scenario/llm + chat: + options: + model: gpt-4.1-2025-04-14 + temperature: 0.7 + max-tokens: 1000 + top-p: 0.9 + embedding: + options: + model: text-embedding-3-small + + + diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/support-version.list b/test/plugin/scenarios/spring-ai-1.x-scenario/support-version.list new file mode 100644 index 0000000000..9d9826957c --- /dev/null +++ b/test/plugin/scenarios/spring-ai-1.x-scenario/support-version.list @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +1.0.1 +1.1.1 \ No newline at end of file diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/bin/startup.sh b/test/plugin/scenarios/spring-boot-2.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..cc7c8bd115 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -Dskywalking.plugin.http.include_http_headers=mock_header -Dskywalking.plugin.springmvc.collect_http_params=true -jar ${agent_opts} ${home}/../libs/spring-boot-2.x-scenario.jar & diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/spring-boot-2.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..a0c9365bd2 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/config/expectedData.yaml @@ -0,0 +1,379 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: spring-boot-2.x-scenario + segmentSize: ge 10 + segments: + - segmentId: not null + spans: + - operationName: HEAD:/healthCheck + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/healthCheck'} + - {key: http.method, value: HEAD} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: test.apache.skywalking.apm.testcase.spring3.component.TestComponentBean.componentMethod + parentSpanId: 1 + spanId: 2 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 93 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: test.apache.skywalking.apm.testcase.spring3.dao.TestRepositoryBean.doSomeStuff + parentSpanId: 1 + spanId: 3 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 93 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: test.apache.skywalking.apm.testcase.spring3.service.TestServiceBean.doSomeBusiness + parentSpanId: 0 + spanId: 1 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 93 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: GET:/case/spring3 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/case/spring3'} + - {key: http.method, value: GET} + - {key: http.headers, value: 'mock_header=[mock_value]'} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-2.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'POST:/create/' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/create/'} + - {key: http.method, value: POST} + - {key: http.status_code, value: '201'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-2.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'GET:/get/{id}' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/get/1'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 3, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-2.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'PUT:/update/{id}' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/update/1'} + - {key: http.method, value: PUT} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 4, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-2.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'DELETE:/delete/{id}' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/delete/1'} + - {key: http.method, value: DELETE} + - {key: http.status_code, value: '204'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 5, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-2.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GET:/inherit/child/test + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/inherit/child/test'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 6, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-2.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GET:/impl/requestmapping + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/impl/requestmapping'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 7, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-2.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'GET:/impl/restmapping' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/impl/restmapping'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 8, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-2.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: /spring-boot-2.x-scenario/case/spring3 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/case/spring3'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-boot-2.x-scenario/create/ + parentSpanId: 0 + spanId: 2 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/create/'} + - {key: http.method, value: POST} + - {key: http.status_code, value: '201'} + skipAnalysis: 'false' + - operationName: /spring-boot-2.x-scenario/get/1 + parentSpanId: 0 + spanId: 3 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/get/1'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-boot-2.x-scenario/update/1 + parentSpanId: 0 + spanId: 4 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/update/1'} + - {key: http.method, value: PUT} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-boot-2.x-scenario/delete/1 + parentSpanId: 0 + spanId: 5 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/delete/1'} + - {key: http.method, value: DELETE} + - {key: http.status_code, value: '204'} + skipAnalysis: 'false' + - operationName: /spring-boot-2.x-scenario/inherit/child/test + parentSpanId: 0 + spanId: 6 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/inherit/child/test'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-boot-2.x-scenario/impl/requestmapping + parentSpanId: 0 + spanId: 7 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/impl/requestmapping'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-boot-2.x-scenario/impl/restmapping + parentSpanId: 0 + spanId: 8 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/impl/restmapping'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: GET:/case/resttemplate + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-2.x-scenario/case/resttemplate'} + - {key: http.method, value: GET} + - key: http.params + value: |- + q1=[v1] + chinese=[中文] + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/configuration.yml b/test/plugin/scenarios/spring-boot-2.x-scenario/configuration.yml new file mode 100644 index 0000000000..e698f0affb --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/configuration.yml @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: '"http://localhost:8080/spring-boot-2.x-scenario/case/resttemplate?q1=v1&chinese=%e4%b8%ad%e6%96%87"' +healthCheck: http://localhost:8080/spring-boot-2.x-scenario/healthCheck +startScript: ./bin/startup.sh +runningMode: with_optional +withPlugins: apm-spring-annotation-plugin-*.jar diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/pom.xml b/test/plugin/scenarios/spring-boot-2.x-scenario/pom.xml new file mode 100644 index 0000000000..e2b3b867a5 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/pom.xml @@ -0,0 +1,114 @@ + + + + 4.0.0 + + org.apache.skywalking + spring-boot-2.x-scenario + 5.0.0 + + jar + + + UTF-8 + UTF-8 + 8 + 3.11.0 + + 2.7.18 + spring-boot + + + skywalking-spring-boot-2.x-scenario + + + + + org.springframework.boot + spring-boot-starter-web + ${test.framework.version} + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + ${test.framework.version} + + + + com.squareup.okhttp3 + okhttp + 4.10.0 + + + + + spring-boot-2.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${test.framework.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + true + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + diff --git a/test/plugin/scenarios/oracle-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/assembly/assembly.xml similarity index 95% rename from test/plugin/scenarios/oracle-scenario/src/main/assembly/assembly.xml rename to test/plugin/scenarios/spring-boot-2.x-scenario/src/main/assembly/assembly.xml index 9e4db94d75..c255ce5f79 100644 --- a/test/plugin/scenarios/oracle-scenario/src/main/assembly/assembly.xml +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/assembly/assembly.xml @@ -33,7 +33,7 @@ - ${project.build.directory}/oracle-scenario.jar + ./target/spring-boot-2.x-scenario.jar ./libs 0775 diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/Application.java b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/Application.java new file mode 100644 index 0000000000..bdb91531cc --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/Application.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java new file mode 100644 index 0000000000..e7fb1871e5 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.entity; + +public class User { + + private int id; + private String userName; + + public User(int id) { + this.id = id; + } + + public User(int id, String userName) { + this.id = id; + this.userName = userName; + } + + public User() { + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseController.java b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseController.java new file mode 100644 index 0000000000..7a95a9318a --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseController.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.implinterface; + +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestCaseController implements TestCaseInterface { + + @Override + public String implRequestMappingAnnotationTestCase() { + return "implRequestMappingAnnotationTestCase"; + } + + @Override + public String implRestAnnotationTestCase() { + return "implRestAnnotationTestCase"; + } +} diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseInterface.java b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseInterface.java new file mode 100644 index 0000000000..4fea97b7b1 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseInterface.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.implinterface; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("/impl") +public interface TestCaseInterface { + @RequestMapping(path = "/requestmapping") + String implRequestMappingAnnotationTestCase(); + + @GetMapping("/restmapping") + String implRestAnnotationTestCase(); +} diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ChildController.java b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ChildController.java new file mode 100644 index 0000000000..27ddf54c76 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ChildController.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.inherit; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/inherit/child") +public class ChildController extends ParentController { + +} diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ParentController.java b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ParentController.java new file mode 100644 index 0000000000..16b928100d --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ParentController.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.inherit; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/inherit/parent") +public class ParentController { + + @RequestMapping("test") + public String test(Integer param) { + return "parent-a" + param; + } +} diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/restapi/RestCaseController.java b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/restapi/RestCaseController.java new file mode 100644 index 0000000000..6792ecb712 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/restapi/RestCaseController.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.restapi; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; +import test.apache.skywalking.apm.testcase.entity.User; + +@RestController +public class RestCaseController { + + private static final Map USERS = new ConcurrentHashMap(); + + @GetMapping(value = "/get/{id}") + @ResponseBody + private ResponseEntity getUser(@PathVariable("id") int id) throws InterruptedException { + User currentUser = new User(id, "a"); + return ResponseEntity.ok(currentUser); + } + + @PostMapping(value = "/create/") + @ResponseBody + public ResponseEntity createUser(@RequestBody User user, + UriComponentsBuilder ucBuilder) throws InterruptedException { + USERS.put(user.getId(), user); + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(ucBuilder.path("/get/{id}").buildAndExpand(user.getId()).toUri()); + return new ResponseEntity(headers, HttpStatus.CREATED); + } + + @PutMapping(value = "/update/{id}") + @ResponseBody + public ResponseEntity updateUser(@PathVariable("id") int id, + @RequestBody User user) throws InterruptedException { + User currentUser = new User(id, user.getUserName()); + return ResponseEntity.ok(currentUser); + } + + @DeleteMapping(value = "/delete/{id}") + @ResponseBody + public ResponseEntity deleteUser(@PathVariable("id") int id) throws InterruptedException { + User currentUser = USERS.get(id); + if (currentUser == null) { + return ResponseEntity.noContent().build(); + } + USERS.remove(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/resttemplate/RestTemplateController.java b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/resttemplate/RestTemplateController.java new file mode 100644 index 0000000000..ba80f261b0 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/resttemplate/RestTemplateController.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.resttemplate; + +import java.io.IOException; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.http.HttpEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import test.apache.skywalking.apm.testcase.entity.User; + +@RestController +public class RestTemplateController { + + private static final String SUCCESS = "Success"; + + private static final Logger LOGGER = LogManager.getLogger(RestTemplateController.class); + + private static final String URL = "http://localhost:8080/spring-boot-2.x-scenario"; + + @RequestMapping("/case/resttemplate") + @ResponseBody + public String restTemplate() throws IOException { + Request request = new Request.Builder().header("mock_header", "mock_value").url(URL + "/case/spring3").build(); + Response response = new OkHttpClient().newCall(request).execute(); + LOGGER.info(response.toString()); + + // Create user + HttpEntity userEntity = new HttpEntity<>(new User(1, "a")); + new RestTemplate().postForEntity(URL + "/create/", userEntity, Void.class); + + // Find User + new RestTemplate().getForEntity(URL + "/get/{id}", User.class, 1); + + //Modify user + HttpEntity updateUserEntity = new HttpEntity<>(new User(1, "b")); + new RestTemplate().put(URL + "/update/{id}", updateUserEntity, userEntity.getBody().getId(), 1); + + //Delete user + new RestTemplate().delete(URL + "/delete/{id}", 1); + + Request inheritRequest = new Request.Builder().url(URL + "/inherit/child/test").build(); + response = new OkHttpClient().newCall(inheritRequest).execute(); + LOGGER.info(response.toString()); + + Request implRequestMappingRequest = new Request.Builder().url(URL + "/impl/requestmapping").build(); + response = new OkHttpClient().newCall(implRequestMappingRequest).execute(); + LOGGER.info(response.toString()); + + Request implRestMappingRequest = new Request.Builder().url(URL + "/impl/restmapping").build(); + response = new OkHttpClient().newCall(implRestMappingRequest).execute(); + LOGGER.info(response.toString()); + + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() { + return SUCCESS; + } + +} diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/CaseController.java b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/CaseController.java new file mode 100644 index 0000000000..501c691579 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/CaseController.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import test.apache.skywalking.apm.testcase.spring3.service.TestServiceBean; + +@Controller +public class CaseController { + + private static final String SUCCESS = "Success"; + + @Autowired + private TestServiceBean testServiceBean; + + @RequestMapping(value = "/case/spring3") + @ResponseBody + public String updateUser() { + testServiceBean.doSomeBusiness("test"); + return SUCCESS; + } +} diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/component/TestComponentBean.java b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/component/TestComponentBean.java new file mode 100644 index 0000000000..274aaf490e --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/component/TestComponentBean.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3.component; + +import org.springframework.stereotype.Component; + +@Component +public class TestComponentBean { + + public String componentMethod(String name) { + return name + "-" + "dealWith-component"; + } +} diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/dao/TestRepositoryBean.java b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/dao/TestRepositoryBean.java new file mode 100644 index 0000000000..84f871ef55 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/dao/TestRepositoryBean.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3.dao; + +import org.springframework.stereotype.Repository; + +@Repository +public class TestRepositoryBean { + + public String doSomeStuff(String name) { + return name + "-dealWithRepository"; + } +} diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java new file mode 100644 index 0000000000..811fe11648 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import test.apache.skywalking.apm.testcase.spring3.dao.TestRepositoryBean; +import test.apache.skywalking.apm.testcase.spring3.component.TestComponentBean; + +@Service +public class TestServiceBean { + @Autowired + private TestComponentBean componentBean; + + @Autowired + private TestRepositoryBean repositoryBean; + + public void doSomeBusiness(String name) { + componentBean.componentMethod(name); + repositoryBean.doSomeStuff(name); + } +} diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/resources/application.yml b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/resources/application.yml new file mode 100644 index 0000000000..e92dacba1a --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/resources/application.yml @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +server: + port: 8080 + servlet: + context-path: /spring-boot-2.x-scenario diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..b5cda5ae8a --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/test/plugin/scenarios/spring-boot-2.x-scenario/support-version.list b/test/plugin/scenarios/spring-boot-2.x-scenario/support-version.list new file mode 100644 index 0000000000..a1e6762600 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-2.x-scenario/support-version.list @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Spring Boot 2.x -> embedded Tomcat 9 (javax) + Spring MVC 5, one per minor (latest patch) +2.0.9.RELEASE +2.1.18.RELEASE +2.2.13.RELEASE +2.3.12.RELEASE +2.4.13 +2.5.15 +2.6.15 +2.7.18 diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/bin/startup.sh b/test/plugin/scenarios/spring-boot-3.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..c72984e26c --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -Dskywalking.plugin.http.include_http_headers=mock_header -Dskywalking.plugin.springmvc.collect_http_params=true -jar ${agent_opts} ${home}/../libs/spring-boot-3.x-scenario.jar & diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/spring-boot-3.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..a80d126128 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/config/expectedData.yaml @@ -0,0 +1,379 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: spring-boot-3.x-scenario + segmentSize: ge 10 + segments: + - segmentId: not null + spans: + - operationName: HEAD:/healthCheck + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/healthCheck'} + - {key: http.method, value: HEAD} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: test.apache.skywalking.apm.testcase.spring3.component.TestComponentBean.componentMethod + parentSpanId: 1 + spanId: 2 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 93 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: test.apache.skywalking.apm.testcase.spring3.dao.TestRepositoryBean.doSomeStuff + parentSpanId: 1 + spanId: 3 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 93 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: test.apache.skywalking.apm.testcase.spring3.service.TestServiceBean.doSomeBusiness + parentSpanId: 0 + spanId: 1 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 93 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: GET:/case/spring3 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/case/spring3'} + - {key: http.method, value: GET} + - {key: http.headers, value: 'mock_header=[mock_value]'} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-3.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'POST:/create/' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/create/'} + - {key: http.method, value: POST} + - {key: http.status_code, value: '201'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-3.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'GET:/get/{id}' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/get/1'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 3, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-3.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'PUT:/update/{id}' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/update/1'} + - {key: http.method, value: PUT} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 4, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-3.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'DELETE:/delete/{id}' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/delete/1'} + - {key: http.method, value: DELETE} + - {key: http.status_code, value: '204'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 5, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-3.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GET:/inherit/child/test + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/inherit/child/test'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 6, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-3.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GET:/impl/requestmapping + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/impl/requestmapping'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 7, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-3.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'GET:/impl/restmapping' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/impl/restmapping'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 8, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-boot-3.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: /spring-boot-3.x-scenario/case/spring3 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/case/spring3'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-boot-3.x-scenario/create/ + parentSpanId: 0 + spanId: 2 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/create/'} + - {key: http.method, value: POST} + - {key: http.status_code, value: '201'} + skipAnalysis: 'false' + - operationName: /spring-boot-3.x-scenario/get/1 + parentSpanId: 0 + spanId: 3 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/get/1'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-boot-3.x-scenario/update/1 + parentSpanId: 0 + spanId: 4 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/update/1'} + - {key: http.method, value: PUT} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-boot-3.x-scenario/delete/1 + parentSpanId: 0 + spanId: 5 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/delete/1'} + - {key: http.method, value: DELETE} + - {key: http.status_code, value: '204'} + skipAnalysis: 'false' + - operationName: /spring-boot-3.x-scenario/inherit/child/test + parentSpanId: 0 + spanId: 6 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/inherit/child/test'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-boot-3.x-scenario/impl/requestmapping + parentSpanId: 0 + spanId: 7 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/impl/requestmapping'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-boot-3.x-scenario/impl/restmapping + parentSpanId: 0 + spanId: 8 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/impl/restmapping'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: GET:/case/resttemplate + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-boot-3.x-scenario/case/resttemplate'} + - {key: http.method, value: GET} + - key: http.params + value: |- + q1=[v1] + chinese=[中文] + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/configuration.yml b/test/plugin/scenarios/spring-boot-3.x-scenario/configuration.yml new file mode 100644 index 0000000000..0263142980 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/configuration.yml @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: '"http://localhost:8080/spring-boot-3.x-scenario/case/resttemplate?q1=v1&chinese=%e4%b8%ad%e6%96%87"' +healthCheck: http://localhost:8080/spring-boot-3.x-scenario/healthCheck +startScript: ./bin/startup.sh +runningMode: with_optional +withPlugins: apm-spring-annotation-plugin-*.jar;apm-*-6.x-plugin-*.jar diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/pom.xml b/test/plugin/scenarios/spring-boot-3.x-scenario/pom.xml new file mode 100644 index 0000000000..3f9bddf53d --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/pom.xml @@ -0,0 +1,125 @@ + + + + 4.0.0 + + org.apache.skywalking + spring-boot-3.x-scenario + 5.0.0 + + jar + + + UTF-8 + UTF-8 + 17 + 3.11.0 + + 3.5.16 + spring-boot + + + skywalking-spring-boot-3.x-scenario + + + + + org.springframework.boot + spring-boot-starter-web + ${test.framework.version} + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + ${test.framework.version} + + + + com.squareup.okhttp3 + okhttp + 4.10.0 + + + + + spring-boot-3.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${test.framework.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + true + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + + + + spring-snapshots + https://repo.spring.io/snapshot + + + spring-milestones + https://repo.spring.io/milestone + + + diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..fcd1eab0d8 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ./target/spring-boot-3.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/Application.java b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/Application.java new file mode 100644 index 0000000000..bdb91531cc --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/Application.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java new file mode 100644 index 0000000000..e7fb1871e5 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.entity; + +public class User { + + private int id; + private String userName; + + public User(int id) { + this.id = id; + } + + public User(int id, String userName) { + this.id = id; + this.userName = userName; + } + + public User() { + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseController.java b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseController.java new file mode 100644 index 0000000000..7a95a9318a --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseController.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.implinterface; + +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestCaseController implements TestCaseInterface { + + @Override + public String implRequestMappingAnnotationTestCase() { + return "implRequestMappingAnnotationTestCase"; + } + + @Override + public String implRestAnnotationTestCase() { + return "implRestAnnotationTestCase"; + } +} diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseInterface.java b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseInterface.java new file mode 100644 index 0000000000..4fea97b7b1 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseInterface.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.implinterface; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("/impl") +public interface TestCaseInterface { + @RequestMapping(path = "/requestmapping") + String implRequestMappingAnnotationTestCase(); + + @GetMapping("/restmapping") + String implRestAnnotationTestCase(); +} diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ChildController.java b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ChildController.java new file mode 100644 index 0000000000..27ddf54c76 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ChildController.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.inherit; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/inherit/child") +public class ChildController extends ParentController { + +} diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ParentController.java b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ParentController.java new file mode 100644 index 0000000000..16b928100d --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ParentController.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.inherit; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/inherit/parent") +public class ParentController { + + @RequestMapping("test") + public String test(Integer param) { + return "parent-a" + param; + } +} diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/restapi/RestCaseController.java b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/restapi/RestCaseController.java new file mode 100644 index 0000000000..6792ecb712 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/restapi/RestCaseController.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.restapi; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; +import test.apache.skywalking.apm.testcase.entity.User; + +@RestController +public class RestCaseController { + + private static final Map USERS = new ConcurrentHashMap(); + + @GetMapping(value = "/get/{id}") + @ResponseBody + private ResponseEntity getUser(@PathVariable("id") int id) throws InterruptedException { + User currentUser = new User(id, "a"); + return ResponseEntity.ok(currentUser); + } + + @PostMapping(value = "/create/") + @ResponseBody + public ResponseEntity createUser(@RequestBody User user, + UriComponentsBuilder ucBuilder) throws InterruptedException { + USERS.put(user.getId(), user); + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(ucBuilder.path("/get/{id}").buildAndExpand(user.getId()).toUri()); + return new ResponseEntity(headers, HttpStatus.CREATED); + } + + @PutMapping(value = "/update/{id}") + @ResponseBody + public ResponseEntity updateUser(@PathVariable("id") int id, + @RequestBody User user) throws InterruptedException { + User currentUser = new User(id, user.getUserName()); + return ResponseEntity.ok(currentUser); + } + + @DeleteMapping(value = "/delete/{id}") + @ResponseBody + public ResponseEntity deleteUser(@PathVariable("id") int id) throws InterruptedException { + User currentUser = USERS.get(id); + if (currentUser == null) { + return ResponseEntity.noContent().build(); + } + USERS.remove(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/resttemplate/RestTemplateController.java b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/resttemplate/RestTemplateController.java new file mode 100644 index 0000000000..89947c72d6 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/resttemplate/RestTemplateController.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.resttemplate; + +import java.io.IOException; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.http.HttpEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import test.apache.skywalking.apm.testcase.entity.User; + +@RestController +public class RestTemplateController { + + private static final String SUCCESS = "Success"; + + private static final Logger LOGGER = LogManager.getLogger(RestTemplateController.class); + + private static final String URL = "http://localhost:8080/spring-boot-3.x-scenario"; + + @RequestMapping("/case/resttemplate") + @ResponseBody + public String restTemplate() throws IOException { + Request request = new Request.Builder().header("mock_header", "mock_value").url(URL + "/case/spring3").build(); + Response response = new OkHttpClient().newCall(request).execute(); + LOGGER.info(response.toString()); + + // Create user + HttpEntity userEntity = new HttpEntity<>(new User(1, "a")); + new RestTemplate().postForEntity(URL + "/create/", userEntity, Void.class); + + // Find User + new RestTemplate().getForEntity(URL + "/get/{id}", User.class, 1); + + //Modify user + HttpEntity updateUserEntity = new HttpEntity<>(new User(1, "b")); + new RestTemplate().put(URL + "/update/{id}", updateUserEntity, userEntity.getBody().getId(), 1); + + //Delete user + new RestTemplate().delete(URL + "/delete/{id}", 1); + + Request inheritRequest = new Request.Builder().url(URL + "/inherit/child/test").build(); + response = new OkHttpClient().newCall(inheritRequest).execute(); + LOGGER.info(response.toString()); + + Request implRequestMappingRequest = new Request.Builder().url(URL + "/impl/requestmapping").build(); + response = new OkHttpClient().newCall(implRequestMappingRequest).execute(); + LOGGER.info(response.toString()); + + Request implRestMappingRequest = new Request.Builder().url(URL + "/impl/restmapping").build(); + response = new OkHttpClient().newCall(implRestMappingRequest).execute(); + LOGGER.info(response.toString()); + + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() { + return SUCCESS; + } + +} diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/CaseController.java b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/CaseController.java new file mode 100644 index 0000000000..501c691579 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/CaseController.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import test.apache.skywalking.apm.testcase.spring3.service.TestServiceBean; + +@Controller +public class CaseController { + + private static final String SUCCESS = "Success"; + + @Autowired + private TestServiceBean testServiceBean; + + @RequestMapping(value = "/case/spring3") + @ResponseBody + public String updateUser() { + testServiceBean.doSomeBusiness("test"); + return SUCCESS; + } +} diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/component/TestComponentBean.java b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/component/TestComponentBean.java new file mode 100644 index 0000000000..274aaf490e --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/component/TestComponentBean.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3.component; + +import org.springframework.stereotype.Component; + +@Component +public class TestComponentBean { + + public String componentMethod(String name) { + return name + "-" + "dealWith-component"; + } +} diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/dao/TestRepositoryBean.java b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/dao/TestRepositoryBean.java new file mode 100644 index 0000000000..84f871ef55 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/dao/TestRepositoryBean.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3.dao; + +import org.springframework.stereotype.Repository; + +@Repository +public class TestRepositoryBean { + + public String doSomeStuff(String name) { + return name + "-dealWithRepository"; + } +} diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java new file mode 100644 index 0000000000..811fe11648 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring3.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import test.apache.skywalking.apm.testcase.spring3.dao.TestRepositoryBean; +import test.apache.skywalking.apm.testcase.spring3.component.TestComponentBean; + +@Service +public class TestServiceBean { + @Autowired + private TestComponentBean componentBean; + + @Autowired + private TestRepositoryBean repositoryBean; + + public void doSomeBusiness(String name) { + componentBean.componentMethod(name); + repositoryBean.doSomeStuff(name); + } +} diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/resources/application.yml b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/resources/application.yml new file mode 100644 index 0000000000..e67ba613ef --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/resources/application.yml @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +server: + port: 8080 + servlet: + context-path: /spring-boot-3.x-scenario diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..b5cda5ae8a --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/test/plugin/scenarios/spring-boot-3.x-scenario/support-version.list b/test/plugin/scenarios/spring-boot-3.x-scenario/support-version.list new file mode 100644 index 0000000000..1f8df5c9b3 --- /dev/null +++ b/test/plugin/scenarios/spring-boot-3.x-scenario/support-version.list @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +3.0.13 +3.1.12 +3.2.12 +3.3.13 +3.4.13 +3.5.16 diff --git a/test/plugin/scenarios/spring-kafka-1.3.x-scenario/configuration.yml b/test/plugin/scenarios/spring-kafka-1.3.x-scenario/configuration.yml index f608847078..8ff824fa43 100644 --- a/test/plugin/scenarios/spring-kafka-1.3.x-scenario/configuration.yml +++ b/test/plugin/scenarios/spring-kafka-1.3.x-scenario/configuration.yml @@ -25,10 +25,10 @@ depends_on: - kafka-server dependencies: zookeeper-server: - image: zookeeper:3.4 + image: zookeeper:3.7 hostname: zookeeper-server kafka-server: - image: bitnami/kafka:2.1.1 + image: bitnamilegacy/kafka:2.4.1 hostname: kafka-server environment: - KAFKA_ZOOKEEPER_CONNECT=zookeeper-server:2181 diff --git a/test/plugin/scenarios/spring-kafka-2.2.x-scenario/configuration.yml b/test/plugin/scenarios/spring-kafka-2.2.x-scenario/configuration.yml index c8b94d2bee..3728f3a9cb 100644 --- a/test/plugin/scenarios/spring-kafka-2.2.x-scenario/configuration.yml +++ b/test/plugin/scenarios/spring-kafka-2.2.x-scenario/configuration.yml @@ -25,10 +25,10 @@ depends_on: - kafka-server dependencies: zookeeper-server: - image: zookeeper:3.4 + image: zookeeper:3.7 hostname: zookeeper-server kafka-server: - image: bitnami/kafka:2.1.1 + image: bitnamilegacy/kafka:2.4.1 hostname: kafka-server environment: - KAFKA_ZOOKEEPER_CONNECT=zookeeper-server:2181 diff --git a/test/plugin/scenarios/spring-kafka-2.3.x-scenario/configuration.yml b/test/plugin/scenarios/spring-kafka-2.3.x-scenario/configuration.yml index 3d40a52355..8948c60ab6 100644 --- a/test/plugin/scenarios/spring-kafka-2.3.x-scenario/configuration.yml +++ b/test/plugin/scenarios/spring-kafka-2.3.x-scenario/configuration.yml @@ -25,10 +25,10 @@ depends_on: - kafka-server dependencies: zookeeper-server: - image: zookeeper:3.4 + image: zookeeper:3.7 hostname: zookeeper-server kafka-server: - image: bitnami/kafka:2.1.1 + image: bitnamilegacy/kafka:2.4.1 hostname: kafka-server environment: - KAFKA_ZOOKEEPER_CONNECT=zookeeper-server:2181 diff --git a/test/plugin/scenarios/spring-kafka-2.3.x-scenario/pom.xml b/test/plugin/scenarios/spring-kafka-2.3.x-scenario/pom.xml index cb1b20edd5..822455c920 100644 --- a/test/plugin/scenarios/spring-kafka-2.3.x-scenario/pom.xml +++ b/test/plugin/scenarios/spring-kafka-2.3.x-scenario/pom.xml @@ -55,17 +55,8 @@ - - org.apache.kafka - kafka-clients - ${kafka-version} - - - slf4j-api - * - - - + com.squareup.okhttp3 okhttp diff --git a/test/plugin/scenarios/spring-kafka-2.3.x-scenario/support-version.list b/test/plugin/scenarios/spring-kafka-2.3.x-scenario/support-version.list index 6859d9a3bb..126ffe549e 100644 --- a/test/plugin/scenarios/spring-kafka-2.3.x-scenario/support-version.list +++ b/test/plugin/scenarios/spring-kafka-2.3.x-scenario/support-version.list @@ -14,4 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Format: spring-kafka-version[,key=value] — extra properties passed to Maven. +# Spring Boot autoconfigure must match the spring-kafka version's expected APIs. +# Mapping: https://spring.io/projects/spring-kafka#overview (compatibility matrix) 2.3.10.RELEASE +2.4.13.RELEASE +2.5.17.RELEASE +2.6.13 +2.7.14,spring.boot.version=2.5.15 +2.8.11,spring.boot.version=2.7.18 +2.9.13,spring.boot.version=2.7.18 diff --git a/test/plugin/scenarios/spring-kafka-3.x-scenario/bin/startup.sh b/test/plugin/scenarios/spring-kafka-3.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..5a10cdccba --- /dev/null +++ b/test/plugin/scenarios/spring-kafka-3.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -Dbootstrap.servers=${BOOTSTRAP_SERVERS} -jar ${agent_opts} "-Dskywalking.agent.service_name=spring-kafka-3.x-scenario" ${home}/../libs/spring-kafka-3.x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/spring-kafka-3.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/spring-kafka-3.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..f58e28b78d --- /dev/null +++ b/test/plugin/scenarios/spring-kafka-3.x-scenario/config/expectedData.yaml @@ -0,0 +1,123 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: + - serviceName: spring-kafka-3.x-scenario + segmentSize: nq 0 + segments: + - segmentId: not null + spans: + - operationName: Kafka/spring_test/Producer + parentSpanId: 0 + spanId: 1 + spanLayer: MQ + startTime: not null + endTime: not null + componentId: 40 + isError: false + spanType: Exit + peer: kafka-server:9092 + skipAnalysis: false + tags: + - {key: mq.broker, value: 'kafka-server:9092'} + - {key: mq.topic, value: spring_test} + - operationName: Kafka/spring_test/Producer + parentSpanId: 0 + spanId: 2 + spanLayer: MQ + startTime: not null + endTime: not null + componentId: 40 + isError: false + spanType: Exit + peer: kafka-server:9092 + skipAnalysis: false + tags: + - { key: mq.broker, value: 'kafka-server:9092' } + - { key: mq.topic, value: spring_test } + - operationName: GET:/spring-kafka-3.x-scenario/case/spring-kafka-case + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 1 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/spring-kafka-3.x-scenario/case/spring-kafka-case'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + - segmentId: not null + spans: + - operationName: GET:/spring-kafka-3.x-scenario/case/spring-kafka-consumer-ping + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 1 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/spring-kafka-3.x-scenario/case/spring-kafka-consumer-ping'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: 'Kafka/spring_test/Consumer/grop:spring_test', networkAddress: 'localhost:8080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: spring-kafka-3.x-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: /spring-kafka-3.x-scenario/case/spring-kafka-consumer-ping + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + skipAnalysis: false + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-kafka-3.x-scenario/case/spring-kafka-consumer-ping'} + - {key: http.status_code, value: '200'} + - operationName: Kafka/spring_test/Consumer/grop:spring_test + parentSpanId: -1 + spanId: 0 + spanLayer: MQ + startTime: not null + endTime: not null + componentId: 41 + isError: false + spanType: Entry + peer: kafka-server:9092 + skipAnalysis: false + tags: + - {key: mq.broker, value: 'kafka-server:9092'} + - {key: mq.topic, value: spring_test} + - {key: transmission.latency, value: not null} + refs: + - {parentEndpoint: 'GET:/spring-kafka-3.x-scenario/case/spring-kafka-case', networkAddress: 'kafka-server:9092', + refType: CrossProcess, parentSpanId: not null, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: spring-kafka-3.x-scenario, + traceId: not null} diff --git a/test/plugin/scenarios/spring-kafka-3.x-scenario/configuration.yml b/test/plugin/scenarios/spring-kafka-3.x-scenario/configuration.yml new file mode 100644 index 0000000000..1397670aff --- /dev/null +++ b/test/plugin/scenarios/spring-kafka-3.x-scenario/configuration.yml @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/spring-kafka-3.x-scenario/case/spring-kafka-case +healthCheck: http://localhost:8080/spring-kafka-3.x-scenario/case/healthCheck +startScript: ./bin/startup.sh +environment: + - BOOTSTRAP_SERVERS=kafka-server:9092 +depends_on: + - zookeeper-server + - kafka-server +dependencies: + zookeeper-server: + image: zookeeper:3.7 + hostname: zookeeper-server + kafka-server: + image: bitnamilegacy/kafka:2.4.1 + hostname: kafka-server + environment: + - KAFKA_ZOOKEEPER_CONNECT=zookeeper-server:2181 + - KAFKA_BROKER_ID=1 + - ALLOW_PLAINTEXT_LISTENER=yes + - KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 + depends_on: + - zookeeper-server diff --git a/test/plugin/scenarios/spring-kafka-3.x-scenario/pom.xml b/test/plugin/scenarios/spring-kafka-3.x-scenario/pom.xml new file mode 100644 index 0000000000..2e6a5200a4 --- /dev/null +++ b/test/plugin/scenarios/spring-kafka-3.x-scenario/pom.xml @@ -0,0 +1,134 @@ + + + + 4.0.0 + + org.apache.skywalking + spring-kafka-3.x-scenario + 5.0.0 + + + UTF-8 + 17 + 3.8.1 + 3.0.0 + 2.6.2 + 3.2.12 + 3.3.2 + 3.0.0 + + + skywalking-spring-kafka-3.x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + org.springframework.kafka + spring-kafka + ${test.framework.version} + + + org.slf4j + * + + + + + + com.squareup.okhttp3 + okhttp + ${okhttp-version} + + + + + spring-kafka-3.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + + + + spring-snapshots + https://repo.spring.io/snapshot + + + spring-milestones + https://repo.spring.io/milestone + + + diff --git a/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..567e5690ed --- /dev/null +++ b/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/spring-kafka-3.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring/kafka/Application.java b/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring/kafka/Application.java new file mode 100644 index 0000000000..56eb4f0b97 --- /dev/null +++ b/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring/kafka/Application.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring.kafka; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring/kafka/controller/CaseController.java b/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring/kafka/controller/CaseController.java new file mode 100644 index 0000000000..6044346ae9 --- /dev/null +++ b/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring/kafka/controller/CaseController.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.apache.skywalking.apm.testcase.spring.kafka.controller; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.Deserializer; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.listener.AcknowledgingMessageListener; +import org.springframework.kafka.listener.ContainerProperties; +import org.springframework.kafka.listener.KafkaMessageListenerContainer; +import org.springframework.kafka.support.Acknowledgment; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import jakarta.annotation.PostConstruct; +import java.util.Arrays; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +@Controller +@RequestMapping("/case") +@PropertySource("classpath:application.properties") +public class CaseController { + + private static final String SUCCESS = "Success"; + + @Value("${bootstrap.servers:127.0.0.1:9092}") + private String bootstrapServers; + private String topicName; + private KafkaTemplate kafkaTemplate; + private KafkaTemplate kafkaTemplate2; + + private CountDownLatch latch; + private String helloWorld = "helloWorld"; + + @PostConstruct + private void setUp() { + topicName = "spring_test"; + setUpProvider(); + setUpAnotherProvider(); + setUpConsumer(); + } + + private void setUpProvider() { + Map props = new HashMap<>(); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + kafkaTemplate = new KafkaTemplate(new DefaultKafkaProducerFactory<>(props)); + try { + kafkaTemplate.send(topicName, "key", "ping").get(); + kafkaTemplate.flush(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void setUpAnotherProvider() { + Map props = new HashMap<>(); + // use list type here + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, Arrays.asList(bootstrapServers.split(","))); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + kafkaTemplate2 = new KafkaTemplate(new DefaultKafkaProducerFactory<>(props)); + try { + kafkaTemplate2.send(topicName, "key", "ping").get(); + kafkaTemplate2.flush(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void setUpConsumer() { + Map configs = new HashMap<>(); + configs.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + configs.put(ConsumerConfig.GROUP_ID_CONFIG, "grop:" + topicName); + configs.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); + Deserializer stringDeserializer = new StringDeserializer(); + DefaultKafkaConsumerFactory factory = new DefaultKafkaConsumerFactory(configs, stringDeserializer, stringDeserializer); + ContainerProperties props = new ContainerProperties(topicName); + props.setMessageListener(new AcknowledgingMessageListener() { + @Override + public void onMessage(ConsumerRecord data, Acknowledgment acknowledgment) { + if (data.value().equals(helloWorld)) { + OkHttpClient client = new OkHttpClient.Builder().build(); + Request request = new Request.Builder().url("http://localhost:8080/spring-kafka-3.x-scenario/case/spring-kafka-consumer-ping").build(); + Response response = null; + try { + response = client.newCall(request).execute(); + } catch (IOException e) { + } + response.body().close(); + acknowledgment.acknowledge(); + latch.countDown(); + } + } + }); + KafkaMessageListenerContainer container = new KafkaMessageListenerContainer<>(factory, props); + container.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE); + container.start(); + } + + @RequestMapping("/spring-kafka-case") + @ResponseBody + public String springKafkaCase() throws Exception { + this.latch = new CountDownLatch(1); + kafkaTemplate.send(topicName, "key", helloWorld).get(); + this.latch.await(); + kafkaTemplate.flush(); + this.latch = new CountDownLatch(1); + kafkaTemplate2.send(topicName, "key", helloWorld).get(); + this.latch.await(); + kafkaTemplate2.flush(); + return SUCCESS; + } + + @RequestMapping("/spring-kafka-consumer-ping") + @ResponseBody + public String springKafkaConsumerPing() { + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() { + return SUCCESS; + } +} + diff --git a/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/resources/application.properties b/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/resources/application.properties new file mode 100644 index 0000000000..09a297f5b4 --- /dev/null +++ b/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/resources/application.properties @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server.port=8080 +server.servlet.context-path=/spring-kafka-3.x-scenario \ No newline at end of file diff --git a/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..b5cda5ae8a --- /dev/null +++ b/test/plugin/scenarios/spring-kafka-3.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/test/plugin/scenarios/spring-kafka-3.x-scenario/support-version.list b/test/plugin/scenarios/spring-kafka-3.x-scenario/support-version.list new file mode 100644 index 0000000000..3f547d82e4 --- /dev/null +++ b/test/plugin/scenarios/spring-kafka-3.x-scenario/support-version.list @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Spring Kafka 3.x requires Spring Boot 3.x / JDK 17+ +# This scenario uses Spring Boot 3.1.12 BOM (Spring Framework 6.1). +# spring-kafka 3.0.x not tested here (needs Spring Boot 3.0 — covered by separate scenario if needed). +3.1.4 +3.2.10 +3.3.7 diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/bin/startup.sh b/test/plugin/scenarios/spring-rabbitmq-scenario/bin/startup.sh new file mode 100644 index 0000000000..f98cd90a75 --- /dev/null +++ b/test/plugin/scenarios/spring-rabbitmq-scenario/bin/startup.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -Dspring.rabbitmq.host=${RABBITMQ_HOST:-rabbitmq-server} -jar ${agent_opts} ${home}/../libs/spring-rabbitmq-scenario.jar & diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/config/expectedData.yaml b/test/plugin/scenarios/spring-rabbitmq-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..a9c4a431a2 --- /dev/null +++ b/test/plugin/scenarios/spring-rabbitmq-scenario/config/expectedData.yaml @@ -0,0 +1,165 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: spring-rabbitmq-scenario + segmentSize: ge 4 + segments: + - segmentId: not null + spans: + - operationName: HEAD:/spring-rabbitmq-scenario/case/healthcheck + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-rabbitmq-scenario/case/healthcheck'} + - {key: http.method, value: HEAD} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: RabbitMQ/Topic/Queue/test/Producer + parentSpanId: 0 + spanId: 1 + spanLayer: MQ + startTime: nq 0 + endTime: nq 0 + componentId: 52 + isError: false + spanType: Exit + peer: not blank + tags: + - {key: mq.broker, value: not blank} + - {key: mq.queue, value: test} + - {key: mq.topic, value: ''} + skipAnalysis: 'false' + - operationName: RabbitMQ/Topic/Queue/test-batch/Producer + parentSpanId: 0 + spanId: 2 + spanLayer: MQ + startTime: nq 0 + endTime: nq 0 + componentId: 52 + isError: false + spanType: Exit + peer: not blank + tags: + - {key: mq.broker, value: not blank} + - {key: mq.queue, value: test-batch} + - {key: mq.topic, value: ''} + skipAnalysis: 'false' + - operationName: RabbitMQ/Topic/Queue/test-batch/Producer + parentSpanId: 0 + spanId: 3 + spanLayer: MQ + startTime: nq 0 + endTime: nq 0 + componentId: 52 + isError: false + spanType: Exit + peer: not blank + tags: + - {key: mq.broker, value: not blank} + - {key: mq.queue, value: test-batch} + - {key: mq.topic, value: ''} + skipAnalysis: 'false' + - operationName: RabbitMQ/Topic/Queue/test-batch/Producer + parentSpanId: 0 + spanId: 4 + spanLayer: MQ + startTime: nq 0 + endTime: nq 0 + componentId: 52 + isError: false + spanType: Exit + peer: not blank + tags: + - {key: mq.broker, value: not blank} + - {key: mq.queue, value: test-batch} + - {key: mq.topic, value: ''} + skipAnalysis: 'false' + - operationName: GET:/spring-rabbitmq-scenario/case/rabbitmq + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-rabbitmq-scenario/case/rabbitmq'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: RabbitMQ/Topic/Queue/test-batch/Consumer + parentSpanId: -1 + spanId: 0 + spanLayer: MQ + startTime: nq 0 + endTime: nq 0 + componentId: 53 + isError: false + spanType: Entry + peer: not blank + tags: + - {key: transmission.latency, value: ge 0} + - {key: mq.broker, value: not blank} + - {key: mq.topic, value: ''} + - {key: mq.queue, value: test-batch} + - {key: transmission.latency, value: ge 0} + - {key: transmission.latency, value: ge 0} + refs: + - {parentEndpoint: GET:/spring-rabbitmq-scenario/case/rabbitmq, networkAddress: not null, + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-rabbitmq-scenario, traceId: not null} + - {parentEndpoint: GET:/spring-rabbitmq-scenario/case/rabbitmq, networkAddress: not null, + refType: CrossProcess, parentSpanId: 3, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-rabbitmq-scenario, traceId: not null} + - {parentEndpoint: GET:/spring-rabbitmq-scenario/case/rabbitmq, networkAddress: not null, + refType: CrossProcess, parentSpanId: 4, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-rabbitmq-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: RabbitMQ/Topic/Queue/test/Consumer + parentSpanId: -1 + spanId: 0 + spanLayer: MQ + startTime: nq 0 + endTime: nq 0 + componentId: 53 + isError: false + spanType: Entry + peer: not blank + tags: + - {key: transmission.latency, value: ge 0} + - {key: mq.broker, value: not blank} + - {key: mq.topic, value: ''} + - {key: mq.queue, value: test} + refs: + - {parentEndpoint: GET:/spring-rabbitmq-scenario/case/rabbitmq, networkAddress: not null, + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-rabbitmq-scenario, traceId: not null} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/configuration.yml b/test/plugin/scenarios/spring-rabbitmq-scenario/configuration.yml new file mode 100644 index 0000000000..31cb305bd1 --- /dev/null +++ b/test/plugin/scenarios/spring-rabbitmq-scenario/configuration.yml @@ -0,0 +1,33 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +type: jvm +entryService: http://localhost:8080/spring-rabbitmq-scenario/case/rabbitmq +healthCheck: http://localhost:8080/spring-rabbitmq-scenario/case/healthcheck +startScript: ./bin/startup.sh +environment: + - RABBITMQ_HOST=rabbitmq-server +depends_on: + - rabbitmq-server +dependencies: + rabbitmq-server: + image: rabbitmq:3.8.18 + hostname: rabbitmq-server + expose: + - 5672 + - 15672 + environment: + - RABBITMQ_DEFAULT_PASS=admin + - RABBITMQ_DEFAULT_USER=admin + - RABBITMQ_DEFAULT_VHOST=/ diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/pom.xml b/test/plugin/scenarios/spring-rabbitmq-scenario/pom.xml new file mode 100644 index 0000000000..0bf98a4869 --- /dev/null +++ b/test/plugin/scenarios/spring-rabbitmq-scenario/pom.xml @@ -0,0 +1,117 @@ + + + + 4.0.0 + + org.apache.skywalking + spring-rabbitmq-scenario + 1.0-SNAPSHOT + + + UTF-8 + 17 + 3.8.1 + 3.0.0 + ${test.framework.version} + 2.19.0 + + + + + org.springframework.boot + spring-boot-starter-amqp + ${test.framework.version} + + + + org.springframework.boot + spring-boot-starter + ${test.framework.version} + + + spring-boot-starter-logging + org.springframework.boot + + + + + org.springframework.boot + spring-boot-starter-web + ${test.framework.version} + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + + + spring-rabbitmq-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${test.framework.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..0e51e5c531 --- /dev/null +++ b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/spring-rabbitmq-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/Application.java b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/Application.java new file mode 100644 index 0000000000..9001ed22a9 --- /dev/null +++ b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/Application.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.spring.rabbitmq; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/config/RabbitMqConfig.java b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/config/RabbitMqConfig.java new file mode 100644 index 0000000000..a0de5ee428 --- /dev/null +++ b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/config/RabbitMqConfig.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.spring.rabbitmq.config; + +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RabbitMqConfig { + + @Bean + public Queue testQueue() { + return new Queue("test", false); + } + + @Bean + public Queue testBatchQueue() { + return new Queue("test-batch", false); + } + + @Bean + public SimpleRabbitListenerContainerFactory batchRabbitListenerContainerFactory( + ConnectionFactory connectionFactory) { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + factory.setConnectionFactory(connectionFactory); + // Enable batch listening mode + factory.setBatchListener(true); + factory.setConsumerBatchEnabled(true); + // Set batch size + factory.setBatchSize(3); + factory.setPrefetchCount(3); + factory.setReceiveTimeout(3000L); + return factory; + } +} diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/controller/CaseController.java b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/controller/CaseController.java new file mode 100644 index 0000000000..e143c51bf9 --- /dev/null +++ b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/controller/CaseController.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.spring.rabbitmq.controller; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageBuilder; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/case") +public class CaseController { + + private static final Logger LOGGER = LogManager.getLogger(CaseController.class); + + private static final String QUEUE_NAME = "test"; + private static final String BATCH_QUEUE_NAME = "test-batch"; + + private static final String MESSAGE = "rabbitmq-testcase"; + + @Autowired + private RabbitTemplate rabbitTemplate; + + @RequestMapping("/rabbitmq") + @ResponseBody + public String send() { + LOGGER.info("Message being published -------------->" + MESSAGE); + rabbitTemplate.convertAndSend(QUEUE_NAME, MESSAGE); + LOGGER.info("Message has been published-------------->" + MESSAGE); + + // Also send batch messages to test batch consumption + LOGGER.info("Sending batch messages --------------"); + List messages = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Message message = MessageBuilder.withBody(("batch-message-" + i).getBytes()).build(); + messages.add(message); + } + for (Message message : messages) { + rabbitTemplate.send(BATCH_QUEUE_NAME, message); + } + LOGGER.info("Batch messages have been published--------------"); + + return "Success"; + } + + @RabbitListener(queues = QUEUE_NAME) + public void consumer(String message) { + LOGGER.info("Message Consumer received-------------->" + message); + } + + @RabbitListener(queues = BATCH_QUEUE_NAME, containerFactory = "batchRabbitListenerContainerFactory") + public void batchConsumer(List messages) { + LOGGER.info("Batch Consumer received " + messages.size() + " messages: " + messages); + } + + @RequestMapping("/healthcheck") + public String healthCheck() { + return "Success"; + } +} diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/resources/application.yml b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/resources/application.yml new file mode 100644 index 0000000000..5b93e6c91a --- /dev/null +++ b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/resources/application.yml @@ -0,0 +1,29 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# +server: + port: 8080 + servlet: + context-path: /spring-rabbitmq-scenario + +spring: + rabbitmq: + host: ${RABBITMQ_HOST:127.0.0.1} + port: 5672 + username: admin + password: admin + virtual-host: / diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..565fda00e0 --- /dev/null +++ b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/support-version.list b/test/plugin/scenarios/spring-rabbitmq-scenario/support-version.list new file mode 100644 index 0000000000..8c67d6f4a8 --- /dev/null +++ b/test/plugin/scenarios/spring-rabbitmq-scenario/support-version.list @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +3.0.0 +4.0.0 \ No newline at end of file diff --git a/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..285bb6bd3e --- /dev/null +++ b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/config/expectedData.yaml @@ -0,0 +1,68 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: spring-scheduled-3.x-5.x-scenario + segmentSize: ge 2 + segments: + - segmentId: not null + spans: + - operationName: GET:/spring-scheduled-3.x-5.x-scenario/case/call + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/spring-scheduled-3.x-5.x-scenario/case/call'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: SpringScheduled/org.apache.skywalking.apm.testcase.spring.scheduled.job.SchedulingJob.work, networkAddress: 'localhost:8080', refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} + - segmentId: not null + spans: + - operationName: /spring-scheduled-3.x-5.x-scenario/case/call + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: not null + endTime: not null + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + skipAnalysis: false + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-scheduled-3.x-5.x-scenario/case/call'} + - {key: http.status_code, value: '200'} + - operationName: SpringScheduled/org.apache.skywalking.apm.testcase.spring.scheduled.job.SchedulingJob.work + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + startTime: not null + endTime: not null + componentId: 96 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: x-le, value: '{"logic-span":true}'} diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/resources/skywalking-plugin.def b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/configuration.yml similarity index 80% rename from apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/resources/skywalking-plugin.def rename to test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/configuration.yml index baa002ba28..61cc0a3fb3 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-10x-plugin/src/main/resources/skywalking-plugin.def +++ b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/configuration.yml @@ -14,5 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -tomcat-10.x=org.apache.skywalking.apm.plugin.tomcat10x.define.TomcatInstrumentation -tomcat-10.x=org.apache.skywalking.apm.plugin.tomcat10x.define.ApplicationDispatcherInstrumentation +type: tomcat +entryService: http://localhost:8080/spring-scheduled-3.x-5.x-scenario/case/healthCheck +healthCheck: http://localhost:8080/spring-scheduled-3.x-5.x-scenario/case/healthCheck \ No newline at end of file diff --git a/test/plugin/scenarios/spring-scheduled-scenario/pom.xml b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/pom.xml similarity index 94% rename from test/plugin/scenarios/spring-scheduled-scenario/pom.xml rename to test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/pom.xml index 10f388f6ac..8dd8e29cbe 100644 --- a/test/plugin/scenarios/spring-scheduled-scenario/pom.xml +++ b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/pom.xml @@ -21,13 +21,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> org.apache.skywalking.apm.testcase - spring-scheduled-scenario + spring-scheduled-3.x-5.x-scenario 1.0.0 war 4.0.0 - skywalking-spring-scheduled-scenario + skywalking-spring-scheduled-3.x-5.x-scenario UTF-8 @@ -92,7 +92,7 @@ - spring-scheduled-scenario + spring-scheduled-3.x-5.x-scenario maven-compiler-plugin @@ -109,7 +109,7 @@ 2.1 8080 - /spring-scheduled-scenario + /spring-scheduled-3.x-5.x-scenario UTF-8 tomcat7 diff --git a/test/plugin/scenarios/spring-scheduled-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/controller/CaseController.java b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/controller/CaseController.java similarity index 100% rename from test/plugin/scenarios/spring-scheduled-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/controller/CaseController.java rename to test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/controller/CaseController.java diff --git a/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/job/SchedulingJob.java b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/job/SchedulingJob.java new file mode 100644 index 0000000000..c943af8a56 --- /dev/null +++ b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/job/SchedulingJob.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.spring.scheduled.job; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import java.io.IOException; + +@Configuration +@EnableScheduling +public class SchedulingJob { + + private static final Logger LOGGER = LogManager.getLogger(SchedulingJob.class); + + private static final OkHttpClient CLIENT = new OkHttpClient.Builder().build(); + + @Scheduled(fixedDelay = 5000) + public void work() throws IOException { + LOGGER.info("work job running!"); + + Request request = new Request.Builder().url("http://localhost:8080/spring-scheduled-3.x-5.x-scenario/case/call").build(); + Response response = CLIENT.newCall(request).execute(); + response.body().close(); + } +} diff --git a/test/plugin/scenarios/spring-scheduled-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/src/main/resources/log4j2.xml similarity index 100% rename from test/plugin/scenarios/spring-scheduled-scenario/src/main/resources/log4j2.xml rename to test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/src/main/resources/log4j2.xml diff --git a/test/plugin/scenarios/spring-scheduled-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml similarity index 100% rename from test/plugin/scenarios/spring-scheduled-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml rename to test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml diff --git a/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/src/main/webapp/WEB-INF/web.xml b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..041c8b4f29 --- /dev/null +++ b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,34 @@ + + + skywalking-spring-scheduled-3.x-5.x-scenario + + + spring-mvc + org.springframework.web.servlet.DispatcherServlet + 1 + + + spring-mvc + / + + diff --git a/test/plugin/scenarios/spring-scheduled-scenario/support-version.list b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/support-version.list similarity index 97% rename from test/plugin/scenarios/spring-scheduled-scenario/support-version.list rename to test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/support-version.list index 78ff603b37..9685fe8ec3 100644 --- a/test/plugin/scenarios/spring-scheduled-scenario/support-version.list +++ b/test/plugin/scenarios/spring-scheduled-3.x-5.x-scenario/support-version.list @@ -22,4 +22,5 @@ 4.3.28.RELEASE 5.0.18.RELEASE 5.1.17.RELEASE -5.2.8.RELEASE +5.2.25.RELEASE +5.3.34 \ No newline at end of file diff --git a/test/plugin/scenarios/spring-scheduled-scenario/config/expectedData.yaml b/test/plugin/scenarios/spring-scheduled-6.x-scenario/config/expectedData.yaml similarity index 91% rename from test/plugin/scenarios/spring-scheduled-scenario/config/expectedData.yaml rename to test/plugin/scenarios/spring-scheduled-6.x-scenario/config/expectedData.yaml index 1c5f45888c..f0a297781f 100644 --- a/test/plugin/scenarios/spring-scheduled-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/spring-scheduled-6.x-scenario/config/expectedData.yaml @@ -14,12 +14,12 @@ # See the License for the specific language governing permissions and # limitations under the License. segmentItems: -- serviceName: spring-scheduled-scenario +- serviceName: spring-scheduled-6.x-scenario segmentSize: ge 2 segments: - segmentId: not null spans: - - operationName: GET:/spring-scheduled-scenario/case/call + - operationName: GET:/spring-scheduled-6.x-scenario/case/call parentSpanId: -1 spanId: 0 spanLayer: Http @@ -31,14 +31,14 @@ segmentItems: peer: '' skipAnalysis: false tags: - - {key: url, value: 'http://localhost:8080/spring-scheduled-scenario/case/call'} + - {key: url, value: 'http://localhost:8080/spring-scheduled-6.x-scenario/case/call'} - {key: http.method, value: GET} - {key: http.status_code, value: '200'} refs: - {parentEndpoint: SpringScheduled/org.apache.skywalking.apm.testcase.spring.scheduled.job.SchedulingJob.work, networkAddress: 'localhost:8080', refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} - segmentId: not null spans: - - operationName: /spring-scheduled-scenario/case/call + - operationName: /spring-scheduled-6.x-scenario/case/call parentSpanId: 0 spanId: 1 spanLayer: Http @@ -51,7 +51,7 @@ segmentItems: skipAnalysis: false tags: - {key: http.method, value: GET} - - {key: url, value: 'http://localhost:8080/spring-scheduled-scenario/case/call'} + - {key: url, value: 'http://localhost:8080/spring-scheduled-6.x-scenario/case/call'} - {key: http.status_code, value: '200'} - operationName: SpringScheduled/org.apache.skywalking.apm.testcase.spring.scheduled.job.SchedulingJob.work parentSpanId: -1 diff --git a/test/plugin/scenarios/spring-scheduled-scenario/configuration.yml b/test/plugin/scenarios/spring-scheduled-6.x-scenario/configuration.yml similarity index 82% rename from test/plugin/scenarios/spring-scheduled-scenario/configuration.yml rename to test/plugin/scenarios/spring-scheduled-6.x-scenario/configuration.yml index 93a92b6b00..9fec8d6ef0 100644 --- a/test/plugin/scenarios/spring-scheduled-scenario/configuration.yml +++ b/test/plugin/scenarios/spring-scheduled-6.x-scenario/configuration.yml @@ -15,5 +15,5 @@ # limitations under the License. type: tomcat -entryService: http://localhost:8080/spring-scheduled-scenario/case/healthCheck -healthCheck: http://localhost:8080/spring-scheduled-scenario/case/healthCheck \ No newline at end of file +entryService: http://localhost:8080/spring-scheduled-6.x-scenario/case/healthCheck +healthCheck: http://localhost:8080/spring-scheduled-6.x-scenario/case/healthCheck \ No newline at end of file diff --git a/test/plugin/scenarios/spring-scheduled-6.x-scenario/pom.xml b/test/plugin/scenarios/spring-scheduled-6.x-scenario/pom.xml new file mode 100644 index 0000000000..273d32a00b --- /dev/null +++ b/test/plugin/scenarios/spring-scheduled-6.x-scenario/pom.xml @@ -0,0 +1,112 @@ + + + + + org.apache.skywalking.apm.testcase + spring-scheduled-6.x-scenario + 1.0.0 + war + + 4.0.0 + + skywalking-spring-scheduled-6.x-scenario + + + UTF-8 + 17 + 3.8.1 + 1.18.20 + 6.1.6 + spring-scheduled + + + + + jakarta.servlet + jakarta.servlet-api + 6.0.0 + provided + + + org.apache.logging.log4j + log4j-api + 2.8.1 + + + org.apache.logging.log4j + log4j-core + 2.8.1 + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + org.springframework + spring-context + ${test.framework.version} + + + org.springframework + spring-web + ${test.framework.version} + + + org.springframework + spring-webmvc + ${test.framework.version} + + + cglib + cglib + 2.2 + + + + com.squareup.okhttp3 + okhttp + 3.0.0 + + + + + spring-scheduled-6.x-scenario + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-war-plugin + 3.3.1 + + + + diff --git a/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/controller/CaseController.java b/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/controller/CaseController.java new file mode 100644 index 0000000000..41dd892c59 --- /dev/null +++ b/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/controller/CaseController.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.spring.scheduled.controller; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequestMapping("/case") +public class CaseController { + + private static final Logger LOGGER = LogManager.getLogger(CaseController.class); + + private static final String SUCCESS = "Success"; + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() { + return SUCCESS; + } + + @RequestMapping("/call") + @ResponseBody + public String call() { + return SUCCESS; + } +} diff --git a/test/plugin/scenarios/spring-scheduled-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/job/SchedulingJob.java b/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/job/SchedulingJob.java similarity index 96% rename from test/plugin/scenarios/spring-scheduled-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/job/SchedulingJob.java rename to test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/job/SchedulingJob.java index dc36b167e6..fd2e65ec45 100644 --- a/test/plugin/scenarios/spring-scheduled-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/job/SchedulingJob.java +++ b/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/scheduled/job/SchedulingJob.java @@ -41,7 +41,7 @@ public class SchedulingJob { public void work() throws IOException { LOGGER.info("work job running!"); - Request request = new Request.Builder().url("http://localhost:8080/spring-scheduled-scenario/case/call").build(); + Request request = new Request.Builder().url("http://localhost:8080/spring-scheduled-6.x-scenario/case/call").build(); Response response = CLIENT.newCall(request).execute(); response.body().close(); } diff --git a/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..b16354bfa3 --- /dev/null +++ b/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml b/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml new file mode 100644 index 0000000000..78ce64329d --- /dev/null +++ b/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml @@ -0,0 +1,38 @@ + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/spring-scheduled-scenario/src/main/webapp/WEB-INF/web.xml b/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/webapp/WEB-INF/web.xml similarity index 95% rename from test/plugin/scenarios/spring-scheduled-scenario/src/main/webapp/WEB-INF/web.xml rename to test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/webapp/WEB-INF/web.xml index 97967a024e..2bbdb36e96 100644 --- a/test/plugin/scenarios/spring-scheduled-scenario/src/main/webapp/WEB-INF/web.xml +++ b/test/plugin/scenarios/spring-scheduled-6.x-scenario/src/main/webapp/WEB-INF/web.xml @@ -20,7 +20,7 @@ xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> - skywalking-spring-scheduled-scenario + skywalking-spring-scheduled-6.x-scenario spring-mvc diff --git a/test/plugin/scenarios/spring-scheduled-6.x-scenario/support-version.list b/test/plugin/scenarios/spring-scheduled-6.x-scenario/support-version.list new file mode 100644 index 0000000000..44322d6089 --- /dev/null +++ b/test/plugin/scenarios/spring-scheduled-6.x-scenario/support-version.list @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +6.0.19 +6.1.6 \ No newline at end of file diff --git a/test/plugin/scenarios/struts2.7-scenario/config/expectedData.yaml b/test/plugin/scenarios/struts2.7-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..02d7206ab2 --- /dev/null +++ b/test/plugin/scenarios/struts2.7-scenario/config/expectedData.yaml @@ -0,0 +1,72 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: struts2.7-scenario + segmentSize: gt 2 + segments: + - segmentId: not null + spans: + - operationName: /struts2.7-scenario/case/case1.action + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 15 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/struts2.7-scenario/case/case1.action'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: /struts2.7-scenario/case/case.action, networkAddress: 'localhost:8080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: struts2.7-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: /struts2.7-scenario/case/case1.action + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 2 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/struts2.7-scenario/case/case1.action'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /struts2.7-scenario/case/case.action + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 15 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/struts2.7-scenario/case/case.action'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/struts2.7-scenario/configuration.yml b/test/plugin/scenarios/struts2.7-scenario/configuration.yml new file mode 100644 index 0000000000..b16e56768e --- /dev/null +++ b/test/plugin/scenarios/struts2.7-scenario/configuration.yml @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: tomcat +entryService: http://localhost:8080/struts2.7-scenario/case/case.action +healthCheck: http://localhost:8080/struts2.7-scenario/healthCheck +environment: +dependencies: diff --git a/test/plugin/scenarios/struts2.7-scenario/pom.xml b/test/plugin/scenarios/struts2.7-scenario/pom.xml new file mode 100644 index 0000000000..beede09526 --- /dev/null +++ b/test/plugin/scenarios/struts2.7-scenario/pom.xml @@ -0,0 +1,96 @@ + + + + + org.apache.skywalking.apm.testcase + struts2.7-scenario + 1.0.0 + war + + 4.0.0 + + skywalking-struts2.7-scenario + + + UTF-8 + + 17 + 3.8.1 + 7.2.1 + 6.0.0 + 1.18.42 + + + + + jakarta.servlet + jakarta.servlet-api + ${jakarta-servlet-api.version} + provided + + + org.apache.logging.log4j + log4j-api + 2.8.1 + + + org.apache.logging.log4j + log4j-core + 2.8.1 + + + org.projectlombok + lombok + ${lombok.version} + provided + + + org.apache.struts + struts2-core + ${test.framework.version} + + + org.apache.httpcomponents + httpclient + 4.3 + + + + + struts2.7-scenario + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-war-plugin + 3.3.1 + + + + diff --git a/test/plugin/scenarios/struts2.7-scenario/src/main/java/org/apache/skywalking/apm/testcase/struts/Case1Action.java b/test/plugin/scenarios/struts2.7-scenario/src/main/java/org/apache/skywalking/apm/testcase/struts/Case1Action.java new file mode 100644 index 0000000000..9f40d9c99f --- /dev/null +++ b/test/plugin/scenarios/struts2.7-scenario/src/main/java/org/apache/skywalking/apm/testcase/struts/Case1Action.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.struts; + +import java.io.IOException; + +public class Case1Action { + + public String execute() throws IOException { + return "SUCCESS"; + } +} diff --git a/test/plugin/scenarios/struts2.7-scenario/src/main/java/org/apache/skywalking/apm/testcase/struts/CaseAction.java b/test/plugin/scenarios/struts2.7-scenario/src/main/java/org/apache/skywalking/apm/testcase/struts/CaseAction.java new file mode 100644 index 0000000000..89a4c70e36 --- /dev/null +++ b/test/plugin/scenarios/struts2.7-scenario/src/main/java/org/apache/skywalking/apm/testcase/struts/CaseAction.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.struts; + +import org.apache.http.HttpEntity; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; + +public class CaseAction { + + public String execute() throws IOException { + visit(); + return "SUCCESS"; + } + + private static void visit() throws IOException { + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpGet httpget = new HttpGet("http://localhost:8080/struts2.7-scenario/case/case1.action"); + ResponseHandler responseHandler = response -> { + HttpEntity entity = response.getEntity(); + return entity != null ? EntityUtils.toString(entity) : null; + }; + httpClient.execute(httpget, responseHandler); + } + } +} diff --git a/test/plugin/scenarios/struts2.7-scenario/src/main/java/org/apache/skywalking/apm/testcase/struts/HealthCheckServlet.java b/test/plugin/scenarios/struts2.7-scenario/src/main/java/org/apache/skywalking/apm/testcase/struts/HealthCheckServlet.java new file mode 100644 index 0000000000..31458cea43 --- /dev/null +++ b/test/plugin/scenarios/struts2.7-scenario/src/main/java/org/apache/skywalking/apm/testcase/struts/HealthCheckServlet.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.struts; + +import java.io.IOException; +import java.io.PrintWriter; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class HealthCheckServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // your codes + PrintWriter writer = resp.getWriter(); + writer.write("Success"); + writer.flush(); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + doGet(req, resp); + } +} diff --git a/test/plugin/scenarios/struts2.7-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/struts2.7-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..b5cda5ae8a --- /dev/null +++ b/test/plugin/scenarios/struts2.7-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/test/plugin/scenarios/struts2.7-scenario/src/main/resources/struts.xml b/test/plugin/scenarios/struts2.7-scenario/src/main/resources/struts.xml new file mode 100644 index 0000000000..62faf006d4 --- /dev/null +++ b/test/plugin/scenarios/struts2.7-scenario/src/main/resources/struts.xml @@ -0,0 +1,46 @@ + + + + + + + + + + 200 + + + + + 200 + + + + diff --git a/test/plugin/scenarios/struts2.7-scenario/src/main/webapp/WEB-INF/web.xml b/test/plugin/scenarios/struts2.7-scenario/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..a43b33cc55 --- /dev/null +++ b/test/plugin/scenarios/struts2.7-scenario/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,44 @@ + + + skywalking-struts2.7-scenario + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /case/* + + + + healthCheck + org.apache.skywalking.apm.testcase.struts.HealthCheckServlet + + + + healthCheck + /healthCheck + + diff --git a/test/plugin/scenarios/struts2.7-scenario/support-version.list b/test/plugin/scenarios/struts2.7-scenario/support-version.list new file mode 100644 index 0000000000..5fcb5f103f --- /dev/null +++ b/test/plugin/scenarios/struts2.7-scenario/support-version.list @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +7.0.3 +7.1.1 +7.2.1 diff --git a/test/plugin/scenarios/tomcat-10x-scenario/config/expectedData.yaml b/test/plugin/scenarios/tomcat-10x-scenario/config/expectedData.yaml index dc03590cdd..030fc841ae 100644 --- a/test/plugin/scenarios/tomcat-10x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/tomcat-10x-scenario/config/expectedData.yaml @@ -34,6 +34,8 @@ segmentItems: tags: - {key: url, value: 'http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario'} - {key: http.method, value: POST} + - {key: http.params, value: 'q1=[v1]'} + - {key: http.headers, value: 'mock_header=[mock_value]'} - {key: http.status_code, value: "200"} refs: - {parentEndpoint: 'GET:/tomcat-10x-scenario/case/tomcat-10x-scenario', networkAddress: 'localhost:8080', @@ -55,7 +57,7 @@ segmentItems: peer: localhost:8080 skipAnalysis: false tags: - - {key: url, value: 'http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario'} + - {key: url, value: 'http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario?q1=v1'} - {key: http.method, value: POST} - {key: http.status_code, value: "200"} - operationName: GET:/tomcat-10x-scenario/case/tomcat-10x-scenario diff --git a/test/plugin/scenarios/tomcat-10x-scenario/configuration.yml b/test/plugin/scenarios/tomcat-10x-scenario/configuration.yml index fac58bdfd1..f28e57d9e7 100644 --- a/test/plugin/scenarios/tomcat-10x-scenario/configuration.yml +++ b/test/plugin/scenarios/tomcat-10x-scenario/configuration.yml @@ -17,3 +17,5 @@ type: tomcat entryService: http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario healthCheck: http://localhost:8080/tomcat-10x-scenario/case/healthCheck +environment: + - CATALINA_OPTS="-Dskywalking.plugin.http.include_http_headers=mock_header -Dskywalking.plugin.tomcat.collect_http_params=true" diff --git a/test/plugin/scenarios/tomcat-10x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat10x/CaseServlet.java b/test/plugin/scenarios/tomcat-10x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat10x/CaseServlet.java index 441f4e0e0b..78da65ec8a 100644 --- a/test/plugin/scenarios/tomcat-10x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat10x/CaseServlet.java +++ b/test/plugin/scenarios/tomcat-10x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat10x/CaseServlet.java @@ -37,7 +37,10 @@ public class CaseServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpPost httpPost = new HttpPost("http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario"); + // Carry a query param and a header so the receiving Tomcat entry span (doPost) exercises + // the tomcat-plugin's http.params + http.headers collection (see configuration.yml). + HttpPost httpPost = new HttpPost("http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario?q1=v1"); + httpPost.setHeader("mock_header", "mock_value"); ResponseHandler responseHandler = response -> { HttpEntity entity = response.getEntity(); return entity != null ? EntityUtils.toString(entity) : null; diff --git a/test/plugin/scenarios/tomcat-9x-scenario/config/expectedData.yaml b/test/plugin/scenarios/tomcat-9x-scenario/config/expectedData.yaml index 10bd8bee30..2737cc9ca5 100644 --- a/test/plugin/scenarios/tomcat-9x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/tomcat-9x-scenario/config/expectedData.yaml @@ -34,6 +34,8 @@ segmentItems: tags: - {key: url, value: 'http://localhost:8080/tomcat-9x-scenario/case/tomcat-9x-scenario'} - {key: http.method, value: POST} + - {key: http.params, value: 'q1=[v1]'} + - {key: http.headers, value: 'mock_header=[mock_value]'} - {key: http.status_code, value: "200"} refs: - {parentEndpoint: 'GET:/tomcat-9x-scenario/case/tomcat-9x-scenario', networkAddress: 'localhost:8080', @@ -55,7 +57,7 @@ segmentItems: peer: localhost:8080 skipAnalysis: false tags: - - {key: url, value: 'http://localhost:8080/tomcat-9x-scenario/case/tomcat-9x-scenario'} + - {key: url, value: 'http://localhost:8080/tomcat-9x-scenario/case/tomcat-9x-scenario?q1=v1'} - {key: http.method, value: POST} - {key: http.status_code, value: "200"} - operationName: GET:/tomcat-9x-scenario/case/tomcat-9x-scenario diff --git a/test/plugin/scenarios/tomcat-9x-scenario/configuration.yml b/test/plugin/scenarios/tomcat-9x-scenario/configuration.yml index 523a817486..b345aba8f7 100644 --- a/test/plugin/scenarios/tomcat-9x-scenario/configuration.yml +++ b/test/plugin/scenarios/tomcat-9x-scenario/configuration.yml @@ -17,3 +17,5 @@ type: tomcat entryService: http://localhost:8080/tomcat-9x-scenario/case/tomcat-9x-scenario healthCheck: http://localhost:8080/tomcat-9x-scenario/case/healthCheck +environment: + - CATALINA_OPTS="-Dskywalking.plugin.http.include_http_headers=mock_header -Dskywalking.plugin.tomcat.collect_http_params=true" diff --git a/test/plugin/scenarios/tomcat-9x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat9x/CaseServlet.java b/test/plugin/scenarios/tomcat-9x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat9x/CaseServlet.java index 9b47be8639..e9dc5969c9 100644 --- a/test/plugin/scenarios/tomcat-9x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat9x/CaseServlet.java +++ b/test/plugin/scenarios/tomcat-9x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat9x/CaseServlet.java @@ -37,7 +37,10 @@ public class CaseServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpPost httpPost = new HttpPost("http://localhost:8080/tomcat-9x-scenario/case/tomcat-9x-scenario"); + // Carry a query param and a header so the receiving Tomcat entry span (doPost) exercises + // the tomcat-plugin's http.params + http.headers collection (see configuration.yml). + HttpPost httpPost = new HttpPost("http://localhost:8080/tomcat-9x-scenario/case/tomcat-9x-scenario?q1=v1"); + httpPost.setHeader("mock_header", "mock_value"); ResponseHandler responseHandler = response -> { HttpEntity entity = response.getEntity(); return entity != null ? EntityUtils.toString(entity) : null; diff --git a/test/plugin/scenarios/tomcat-thread-pool-scenario/config/expectedData.yaml b/test/plugin/scenarios/tomcat-thread-pool-scenario/config/expectedData.yaml index 6a4cd68333..c4cc8f9ddd 100644 --- a/test/plugin/scenarios/tomcat-thread-pool-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/tomcat-thread-pool-scenario/config/expectedData.yaml @@ -15,7 +15,7 @@ # limitations under the License. meterItems: - serviceName: tomcat-thread-pool-scenario - meterSize: 5 + meterSize: ge 5 meters: - meterId: name: thread_pool diff --git a/test/plugin/scenarios/undertow-2.3.x-scenario/bin/startup.sh b/test/plugin/scenarios/undertow-2.3.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..e7e48dc3b2 --- /dev/null +++ b/test/plugin/scenarios/undertow-2.3.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/undertow-2.3.x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/undertow-2.3.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/undertow-2.3.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..b364557b9f --- /dev/null +++ b/test/plugin/scenarios/undertow-2.3.x-scenario/config/expectedData.yaml @@ -0,0 +1,142 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +segmentItems: +- serviceName: undertow-2.3.x-scenario + segmentSize: gt 5 + segments: + - segmentId: not null + spans: + - operationName: GET:/undertow-scenario/case/undertow + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 84 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/undertow-scenario/case/undertow'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GET:/undertow-routing-scenario/case/{context} + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 84 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8081/undertow-routing-scenario/case/undertow'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: UndertowDispatch, networkAddress: 'localhost:8081', refType: CrossProcess, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: undertow-2.3.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GET:/undertow-scenario/case/undertow1 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 84 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/undertow-scenario/case/undertow1'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: UndertowDispatch, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: undertow-2.3.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: /undertow-routing-scenario/case/undertow + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 2 + isError: false + spanType: Exit + peer: localhost:8081 + tags: + - {key: url, value: 'http://localhost:8081/undertow-routing-scenario/case/undertow?send=httpHandler'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: UndertowDispatch + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 84 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: GET:/undertow-scenario/case/undertow, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: /undertow-scenario/case/undertow1 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 2 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/undertow-scenario/case/undertow1?send=runnable'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: UndertowDispatch + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 84 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: 'GET:/undertow-routing-scenario/case/{context}', networkAddress: '', + refType: CrossThread, parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: undertow-2.3.x-scenario, traceId: not null} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/undertow-2.3.x-scenario/configuration.yml b/test/plugin/scenarios/undertow-2.3.x-scenario/configuration.yml new file mode 100644 index 0000000000..3994b092f8 --- /dev/null +++ b/test/plugin/scenarios/undertow-2.3.x-scenario/configuration.yml @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +type: jvm +entryService: http://localhost:8080/undertow-scenario/case/undertow +healthCheck: http://localhost:8080/undertow-scenario/case/healthCheck +startScript: ./bin/startup.sh diff --git a/test/plugin/scenarios/undertow-2.3.x-scenario/pom.xml b/test/plugin/scenarios/undertow-2.3.x-scenario/pom.xml new file mode 100644 index 0000000000..736443dea5 --- /dev/null +++ b/test/plugin/scenarios/undertow-2.3.x-scenario/pom.xml @@ -0,0 +1,118 @@ + + + + 4.0.0 + + org.apache.skywalking + undertow-2.3.x-scenario + jar + 5.0.0 + + + UTF-8 + 11 + 3.8.1 + org.apache.skywalking.amp.testcase.undertow.Application + + undertow + 2.3.0.Final + 2.1.6.RELEASE + + + skywalking-undertow-2.3.x-scenario + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + io.undertow + undertow-core + ${test.framework.version} + + + + org.apache.httpcomponents + httpclient + 4.3 + + + + + undertow-2.3.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + + + + spring-snapshots + https://repo.spring.io/snapshot + + + spring-milestones + https://repo.spring.io/milestone + + + diff --git a/test/plugin/scenarios/undertow-2.3.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/undertow-2.3.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..443828cfcf --- /dev/null +++ b/test/plugin/scenarios/undertow-2.3.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/undertow-2.3.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/undertow-2.3.x-scenario/src/main/java/org/apache/skywalking/amp/testcase/undertow/Application.java b/test/plugin/scenarios/undertow-2.3.x-scenario/src/main/java/org/apache/skywalking/amp/testcase/undertow/Application.java new file mode 100644 index 0000000000..fd45a07d01 --- /dev/null +++ b/test/plugin/scenarios/undertow-2.3.x-scenario/src/main/java/org/apache/skywalking/amp/testcase/undertow/Application.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.amp.testcase.undertow; + +import io.undertow.Undertow; +import io.undertow.server.HttpHandler; +import io.undertow.server.RoutingHandler; +import io.undertow.util.Headers; +import io.undertow.util.Methods; +import java.io.IOException; +import org.apache.http.HttpEntity; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; + +public class Application { + + private static final String CASE_URL = "/undertow-scenario/case/undertow"; + + private static final String TEMPLATE = "/undertow-routing-scenario/case/{context}"; + + private static final String ROUTING_CASE_URL = "/undertow-routing-scenario/case/undertow"; + + public static void main(String[] args) throws InterruptedException { + new Thread(Application::undertowRouting).start(); + undertow(); + } + + private static void undertow() { + Undertow server = Undertow.builder().addHttpListener(8080, "0.0.0.0").setHandler(exchange -> { + if (CASE_URL.equals(exchange.getRequestPath())) { + exchange.dispatch(() -> { + try { + visit("http://localhost:8081/undertow-routing-scenario/case/undertow?send=httpHandler"); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); + exchange.getResponseSender().send("Success"); + }).build(); + Runtime.getRuntime().addShutdownHook(new Thread(server::stop)); + server.start(); + } + + private static void undertowRouting() { + HttpHandler httpHandler = exchange -> { + if (ROUTING_CASE_URL.equals(exchange.getRequestPath())) { + exchange.dispatch(httpServerExchange -> visit("http://localhost:8080/undertow-scenario/case/undertow1?send=runnable")); + } + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); + exchange.getResponseSender().send("Success"); + }; + RoutingHandler handler = new RoutingHandler(); + handler.add(Methods.GET, TEMPLATE, httpHandler); + handler.add(Methods.HEAD, TEMPLATE, httpHandler); + Undertow server = Undertow.builder().addHttpListener(8081, "0.0.0.0").setHandler(handler).build(); + Runtime.getRuntime().addShutdownHook(new Thread(server::stop)); + server.start(); + } + + private static void visit(String url) throws IOException { + CloseableHttpClient httpClient = HttpClients.createDefault(); + try { + HttpGet httpget = new HttpGet(url); + ResponseHandler responseHandler = response -> { + HttpEntity entity = response.getEntity(); + return entity != null ? EntityUtils.toString(entity) : null; + }; + httpClient.execute(httpget, responseHandler); + } finally { + httpClient.close(); + } + } +} diff --git a/test/plugin/scenarios/undertow-2.3.x-scenario/support-version.list b/test/plugin/scenarios/undertow-2.3.x-scenario/support-version.list new file mode 100644 index 0000000000..1f4d4b5614 --- /dev/null +++ b/test/plugin/scenarios/undertow-2.3.x-scenario/support-version.list @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Undertow 2.3.x requires Java 11+; 2.4.x requires Java 17. Tested with JDK 17. +2.3.25.Final +2.4.2.Final diff --git a/test/plugin/scenarios/undertow-scenario/support-version.list b/test/plugin/scenarios/undertow-scenario/support-version.list index 420cc76010..a6d77253e2 100644 --- a/test/plugin/scenarios/undertow-scenario/support-version.list +++ b/test/plugin/scenarios/undertow-scenario/support-version.list @@ -19,6 +19,9 @@ # 1.3.x 1.4.x: select the first, middle, and last version # 2.0.x: when there are many releases in the same month, choose the last one +# Undertow 2.3.x requires Java 11+, tested in undertow-2.3.x-scenario (JDK 17 workflow) 1.3.33.Final 1.4.27.Final 2.0.27.Final +2.1.8.Final +2.2.37.Final diff --git a/test/plugin/scenarios/undertow-worker-thread-pool-scenario/config/expectedData.yaml b/test/plugin/scenarios/undertow-worker-thread-pool-scenario/config/expectedData.yaml index 32d7928752..ba30823ec1 100644 --- a/test/plugin/scenarios/undertow-worker-thread-pool-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/undertow-worker-thread-pool-scenario/config/expectedData.yaml @@ -15,36 +15,36 @@ # limitations under the License. meterItems: - serviceName: undertow-worker-thread-pool-scenario - meterSize: 5 + meterSize: ge 5 meters: - meterId: name: thread_pool tags: - {name: metric_type, value: core_pool_size} - {name: pool_name, value: undertow_worker_pool} - singleValue: ge 1 + singleValue: ge -1 - meterId: name: thread_pool tags: - {name: metric_type, value: max_pool_size} - {name: pool_name, value: undertow_worker_pool} - singleValue: ge 1 + singleValue: ge -1 - meterId: name: thread_pool tags: - {name: metric_type, value: pool_size} - {name: pool_name, value: undertow_worker_pool} - singleValue: ge 0 + singleValue: ge -1 - meterId: name: thread_pool tags: - {name: metric_type, value: active_size} - {name: pool_name, value: undertow_worker_pool} - singleValue: ge 0 + singleValue: ge -1 - meterId: name: thread_pool tags: - {name: metric_type, value: queue_size} - {name: pool_name, value: undertow_worker_pool} - singleValue: ge 0 + singleValue: ge -1 diff --git a/test/plugin/scenarios/undertow-worker-thread-pool-scenario/pom.xml b/test/plugin/scenarios/undertow-worker-thread-pool-scenario/pom.xml index 7babe86a4e..ff3f3f582e 100644 --- a/test/plugin/scenarios/undertow-worker-thread-pool-scenario/pom.xml +++ b/test/plugin/scenarios/undertow-worker-thread-pool-scenario/pom.xml @@ -41,7 +41,7 @@ org.springframework.boot spring-boot-dependencies - ${spring-boot-version} + ${test.framework.version} pom import diff --git a/tools/plugin/check-compiler-overrides.sh b/tools/plugin/check-compiler-overrides.sh new file mode 100755 index 0000000000..601bcfe4d8 --- /dev/null +++ b/tools/plugin/check-compiler-overrides.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +# Check that plugin pom.xml files do not set maven.compiler.release, +# maven.compiler.source, or maven.compiler.target unless they are on +# the allowlist. A provided-scope dependency targeting a higher JDK +# does NOT justify raising the compiler level — only plugin source code +# that uses JDK 9+ language features does. + +WORK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../.. && pwd)" + +# Plugins that override maven.compiler.release. Two categories: +# +# 1. JDK 11/17+ required: plugin source code uses higher-JDK APIs directly +# (e.g., java.net.http.HttpClient). Sets maven.compiler.release=11/17/etc. +# +# 2. API visibility only: plugin source code is JDK 8 compatible, but provided +# dependencies use java.lang.Record or other JDK 9+ types. Sets +# (blank) to cancel the parent's --release 8 +# restriction, so the compiler can resolve those types. Bytecode remains +# JDK 8 compatible. +ALLOWLIST=( + # Category 1: JDK 11+ required + "bootstrap-plugins/jdk-httpclient-plugin" + # Category 2: API visibility only + "bootstrap-plugins/jdk-http-plugin" + "spring-plugins/spring-ai-1.x-plugin" +) + +EXIT_CODE=0 + +while IFS= read -r pom; do + # Check if this pom is on the allowlist + allowed=false + for entry in "${ALLOWLIST[@]}"; do + if [[ "${pom}" == *"${entry}"* ]]; then + allowed=true + break + fi + done + + if [ "${allowed}" = true ]; then + continue + fi + + # Report the violation + echo "ERROR: ${pom} sets a compiler level override but is not on the allowlist." + echo " If the plugin source code uses JDK 9+ language features, add it to the" + echo " allowlist in tools/plugin/check-compiler-overrides.sh." + echo " Otherwise, remove the compiler override from the pom.xml." + echo "" + EXIT_CODE=1 +done < <(grep -rl "maven\.compiler\.\(release\|source\|target\)" \ + "${WORK_DIR}/apm-sniffer/apm-sdk-plugin" \ + "${WORK_DIR}/apm-sniffer/bootstrap-plugins" \ + "${WORK_DIR}/apm-sniffer/optional-plugins" \ + "${WORK_DIR}/apm-sniffer/optional-reporter-plugins" \ + --include="pom.xml") + +if [ ${EXIT_CODE} -eq 0 ]; then + echo "Compiler override check passed." +fi + +exit ${EXIT_CODE} diff --git a/tools/releasing/create_release.sh b/tools/releasing/create_release.sh deleted file mode 100755 index 98cedccc01..0000000000 --- a/tools/releasing/create_release.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env bash - -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You 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. -# - -# This script relies on few environment variables to determine source code package -# behavior, those variables are: -# RELEASE_VERSION -- The version of this source package. -# For example: RELEASE_VERSION=5.0.0-alpha - - -RELEASE_VERSION=${RELEASE_VERSION} -TAG_NAME=v${RELEASE_VERSION} -PRODUCT_NAME="apache-skywalking-java-agent" - -echo "Release version "${RELEASE_VERSION} -echo "Source tag "${TAG_NAME} - -if [ "$RELEASE_VERSION" == "" ]; then - echo "RELEASE_VERSION environment variable not found, Please setting the RELEASE_VERSION." - echo "For example: export RELEASE_VERSION=5.0.0-alpha" - exit 1 -fi - -echo "Creating source package" - -PRODUCT_NAME=${PRODUCT_NAME}-${RELEASE_VERSION} - -rm -rf ${PRODUCT_NAME} -mkdir ${PRODUCT_NAME} - -git clone https://github.com/apache/skywalking-java.git ./${PRODUCT_NAME} -cd ${PRODUCT_NAME} - -TAG_EXIST=`git tag -l ${TAG_NAME} | wc -l` - -if [ ${TAG_EXIST} -ne 1 ]; then - echo "Could not find the tag named" ${TAG_NAME} - exit 1 -fi - -git checkout ${TAG_NAME} - -# Init submodules -git submodule init -git submodule update - -# Generate a static skywalking-agent-version.properties and override the template when releasing source tar -# because after that there is no Git information anymore. -./mvnw -q -pl apm-sniffer/apm-agent-core initialize \ - -DgenerateGitPropertiesFilename="$(pwd)/apm-sniffer/apm-agent-core/src/main/resources/skywalking-agent-version.properties" - -cd .. -# Build source code tar -tar czf ${PRODUCT_NAME}-src.tgz \ - --exclude .git \ - --exclude .DS_Store \ - --exclude .github \ - --exclude .gitignore \ - --exclude .gitmodules \ - ${PRODUCT_NAME} - -gpg --armor --detach-sig ${PRODUCT_NAME}-src.tgz - -shasum -a 512 ${PRODUCT_NAME}-src.tgz > ${PRODUCT_NAME}-src.tgz.sha512 - -# Build binary tar -cd ${PRODUCT_NAME} -export TAG=${RELEASE_VERSION} -make dist diff --git a/tools/releasing/release.sh b/tools/releasing/release.sh new file mode 100755 index 0000000000..8338e60486 --- /dev/null +++ b/tools/releasing/release.sh @@ -0,0 +1,648 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +# Apache SkyWalking Java Agent Release Script +# +# Usage: +# ./release.sh preflight Check tools and environment +# ./release.sh prepare Prepare release (branch, maven release:prepare, tag) +# ./release.sh stage Stage release (maven release:perform, build tars) +# ./release.sh upload Upload to Apache SVN dist/dev +# ./release.sh prepare-vote Run prepare + stage + upload, then generate vote email +# ./release.sh email [vote|announce] Generate email content +# ./release.sh promote Move from dist/dev to dist/release in SVN +# ./release.sh docker Build and push Docker images +# ./release.sh vote-passed Run promote + docker, then generate announce email +# ./release.sh cleanup Remove old release from dist/release + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +PRODUCT_NAME="apache-skywalking-java-agent" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; } + +# ============================================================ +# preflight — check tools and environment +# ============================================================ +cmd_preflight() { + info "Running pre-flight checks..." + + local failed=0 + + # Required tools + for tool in git gpg svn shasum mvn java tar gh; do + if command -v "$tool" &>/dev/null; then + info " $tool: $(command -v $tool)" + else + error " $tool: NOT FOUND" + failed=1 + fi + done + + # Java version + local java_version + java_version=$(java -version 2>&1 | head -1) + info " Java: $java_version" + + # GPG key + local gpg_keys + gpg_keys=$(gpg --list-secret-keys --keyid-format SHORT 2>/dev/null | grep -c "sec" || true) + if [ "$gpg_keys" -eq 0 ]; then + error " GPG: No secret keys found. Import your GPG key first." + failed=1 + else + info " GPG: $gpg_keys secret key(s) found" + fi + + # GPG signing without password prompt + info " Testing GPG signing (should not ask for password)..." + local test_file + test_file=$(mktemp) + echo "test" > "$test_file" + if gpg --batch --yes --armor --detach-sig "$test_file" 2>/dev/null; then + info " GPG signing: OK (no password prompt)" + rm -f "$test_file" "${test_file}.asc" + else + rm -f "$test_file" "${test_file}.asc" + echo "" + echo " GPG signing FAILED. The agent must be configured to cache the passphrase." + echo "" + echo " Options to fix:" + echo " 1. Configure gpg-agent with a longer cache TTL in ~/.gnupg/gpg-agent.conf:" + echo " default-cache-ttl 86400" + echo " max-cache-ttl 86400" + echo " Then: gpgconf --kill gpg-agent" + echo "" + echo " 2. Or run 'gpg --sign /dev/null' manually first to cache the passphrase." + echo "" + failed=1 + fi + + # Maven settings (Apache credentials) + local settings_file="${HOME}/.m2/settings.xml" + if [ -f "$settings_file" ]; then + if grep -q "apache.releases.https" "$settings_file"; then + info " Maven settings: apache.releases.https server found" + else + warn " Maven settings: apache.releases.https server NOT found in $settings_file" + failed=1 + fi + else + warn " Maven settings: $settings_file not found" + failed=1 + fi + + # Git status + cd "$PROJECT_ROOT" + if [ -n "$(git status --porcelain)" ]; then + warn " Git: working tree is dirty" + else + info " Git: working tree is clean" + fi + + local branch + branch=$(git rev-parse --abbrev-ref HEAD) + info " Git branch: $branch" + + if [ "$failed" -ne 0 ]; then + echo "" + error "Pre-flight checks failed. Fix the issues above before releasing." + fi + + echo "" + info "All pre-flight checks passed." +} + +# ============================================================ +# prepare — prepare the release (CHANGES.md, maven) +# ============================================================ +cmd_prepare() { + local version="${1:-}" + local next_version="${2:-}" + if [ -z "$version" ]; then + error "Usage: $0 prepare [next_version] (e.g., 9.7.0 9.8.0)" + fi + + cd "$PROJECT_ROOT" + + if [ -z "$next_version" ]; then + next_version=$(echo "$version" | awk -F. '{printf "%s.%s.%s", $1, $2+1, 0}') + fi + local branch_name="release/${version}" + + info "Preparing release ${version}..." + echo " Release version: ${version}" + echo " Tag: v${version}" + echo " Next dev version: ${next_version}-SNAPSHOT" + echo " Branch: ${branch_name}" + echo "" + read -rp "Continue? [y/N] " confirm + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + info "Aborted." + exit 0 + fi + + # Step 1: Create release branch from main + info "Creating branch ${branch_name}..." + git checkout -b "${branch_name}" + + # Step 2: Maven release:prepare + # This creates two commits: + # 1. [maven-release-plugin] prepare release vx.y.z (pom versions set to x.y.z) + # 2. [maven-release-plugin] prepare for next development iteration (pom versions set to next-SNAPSHOT) + # And a tag vx.y.z pointing to commit 1. + # CHANGES.md is kept as-is so the tag includes the full changelog. + info "Running maven release:prepare..." + ./mvnw release:clean + ./mvnw release:prepare -DautoVersionSubmodules=true -Pall \ + -DreleaseVersion="${version}" \ + -DdevelopmentVersion="${next_version}-SNAPSHOT" \ + -Dtag="v${version}" \ + -DpushChanges=false + + # Step 3: After the tag is created, move CHANGES.md for next dev cycle + info "Moving changelog to changes/changes-${version}.md..." + local changes_file="changes/changes-${version}.md" + + # Extract current version section from CHANGES.md + sed -n "/^${version}$/,/^------------------$/p" CHANGES.md | head -n -1 > "$changes_file" + grep "All issues and pull requests" CHANGES.md >> "$changes_file" || true + info "Created $changes_file" + + # Reset CHANGES.md for next development version + cat > CHANGES.md << EOF +Changes by Version +================== +Release Notes. + +${next_version} +------------------ + + +All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/xxx?closed=1) + +------------------ +Find change logs of all versions [here](changes). +EOF + + git add CHANGES.md "$changes_file" + git commit -m "Move ${version} changelog and reset for ${next_version} development" + + # Step 4: Push branch and tag + info "Pushing branch and tag..." + git push -u origin "${branch_name}" + git push origin "v${version}" + + # Step 5: Create PR + info "Creating pull request..." + if command -v gh &>/dev/null; then + gh pr create --base main --head "${branch_name}" \ + --title "Release ${version}" \ + --body "Release Apache SkyWalking Java Agent ${version}. + +- Maven release:prepare completed (tag \`v${version}\` created) +- CHANGES.md archived to \`changes/changes-${version}.md\` +- Next development version: ${next_version}-SNAPSHOT" + info "PR created." + else + warn "GitHub CLI (gh) not found. Create PR manually for branch ${branch_name}." + fi + + info "Release ${version} prepared." + info " Tag v${version} is ready." + info " PR created for branch ${branch_name} → main." + info "Next step: $0 stage" +} + +# ============================================================ +# stage — stage the release (maven, build source & binary tars) +# ============================================================ +cmd_stage() { + cd "$PROJECT_ROOT" + + # Detect version from latest tag + local version + version=$(git describe --tags --abbrev=0 | sed 's/^v//') + local tag_name="v${version}" + + info "Staging release ${version}..." + + # Maven release:perform + info "Running maven release:perform..." + ./mvnw release:perform -DskipTests -Pall + + # Build source and binary packages (inlined from create_release.sh) + info "Building source and binary packages..." + + cd "${SCRIPT_DIR}" + local product_dir="${PRODUCT_NAME}-${version}" + + rm -rf "${product_dir}" + mkdir "${product_dir}" + + git clone https://github.com/apache/skywalking-java.git "./${product_dir}" + cd "${product_dir}" + + TAG_EXIST=$(git tag -l "${tag_name}" | wc -l) + if [ "${TAG_EXIST}" -ne 1 ]; then + error "Could not find the tag named ${tag_name}" + fi + + git checkout "${tag_name}" + git submodule init + git submodule update + + # Generate static version properties (no Git info in source tar) + ./mvnw -q -pl apm-sniffer/apm-agent-core initialize \ + -DgenerateGitPropertiesFilename="$(pwd)/apm-sniffer/apm-agent-core/src/main/resources/skywalking-agent-version.properties" + + cd "${SCRIPT_DIR}" + + # Source tar + info "Creating source tar..." + tar czf "${product_dir}-src.tgz" \ + --exclude .git \ + --exclude .DS_Store \ + --exclude .github \ + --exclude .gitignore \ + --exclude .gitmodules \ + "${product_dir}" + + gpg --armor --detach-sig "${product_dir}-src.tgz" + shasum -a 512 "${product_dir}-src.tgz" > "${product_dir}-src.tgz.sha512" + + # Binary tar + info "Creating binary tar..." + cd "${product_dir}" + export TAG="${version}" + make dist + + echo "" + info "Release ${version} staged." + info "Source tar: ${SCRIPT_DIR}/${product_dir}-src.tgz" + info "Binary tar: ${SCRIPT_DIR}/${product_dir}/${PRODUCT_NAME}-${version}.tgz" + info "Next step: $0 upload" +} + +# ============================================================ +# upload — upload to Apache SVN dist/dev +# ============================================================ +cmd_upload() { + cd "$PROJECT_ROOT" + + local version + version=$(git describe --tags --abbrev=0 | sed 's/^v//') + local svn_dev="https://dist.apache.org/repos/dist/dev/skywalking/java-agent" + + info "Uploading release ${version} to Apache SVN (dist/dev)..." + + local staging_dir="${SCRIPT_DIR}/${PRODUCT_NAME}-${version}" + + # Verify files exist + local src_tar="${SCRIPT_DIR}/${PRODUCT_NAME}-${version}-src.tgz" + local bin_tar="${staging_dir}/${PRODUCT_NAME}-${version}.tgz" + + for f in "$src_tar" "${src_tar}.asc" "${src_tar}.sha512" \ + "$bin_tar" "${bin_tar}.asc" "${bin_tar}.sha512"; do + if [ ! -f "$f" ]; then + error "Missing file: $f. Run '$0 stage' first." + fi + done + + # Create SVN directory and upload + read -rp "SVN username (Apache ID): " svn_user + + local tmp_svn + tmp_svn=$(mktemp -d) + info "Checking out SVN dist/dev..." + svn checkout --depth empty "$svn_dev" "$tmp_svn" --username "$svn_user" + + mkdir -p "${tmp_svn}/${version}" + + cp "$src_tar" "${src_tar}.asc" "${src_tar}.sha512" "${tmp_svn}/${version}/" + cp "$bin_tar" "${bin_tar}.asc" "${bin_tar}.sha512" "${tmp_svn}/${version}/" + + cd "$tmp_svn" + svn add "${version}" + svn commit -m "Stage Apache SkyWalking Java Agent ${version}" --username "$svn_user" + + rm -rf "$tmp_svn" + info "Uploaded to ${svn_dev}/${version}" + info "Next step: $0 email vote" +} + +# ============================================================ +# email — generate email templates +# ============================================================ +cmd_email() { + local type="${1:-}" + if [[ ! "$type" =~ ^(vote|announce)$ ]]; then + error "Usage: $0 email [vote|announce]" + fi + + cd "$PROJECT_ROOT" + + local version + version=$(git describe --tags --abbrev=0 | sed 's/^v//') + local tag="v${version}" + local commit_id + commit_id=$(git rev-list -n1 "$tag" 2>/dev/null || echo "") + local submodule_commit + submodule_commit=$(git ls-tree "$tag" apm-protocol/apm-network/src/main/proto 2>/dev/null | awk '{print $3}' || echo "") + + # Get sha512 checksums + local src_sha512="" + local bin_sha512="" + local src_sha_file="${SCRIPT_DIR}/${PRODUCT_NAME}-${version}-src.tgz.sha512" + local bin_sha_file="${SCRIPT_DIR}/${PRODUCT_NAME}-${version}/${PRODUCT_NAME}-${version}.tgz.sha512" + [ -f "$src_sha_file" ] && src_sha512=$(cat "$src_sha_file") + [ -f "$bin_sha_file" ] && bin_sha512=$(cat "$bin_sha_file") + + echo "" + echo "============================================================" + + case "$type" in + vote) + cat << EOF +Mail to: dev@skywalking.apache.org +Subject: [VOTE] Release Apache SkyWalking Java Agent version ${version} + +Hi All, +This is a call for vote to release Apache SkyWalking Java Agent version ${version}. + +Release notes: + + * https://github.com/apache/skywalking-java/blob/master/changes/changes-${version}.md + +Release Candidate: + + * https://dist.apache.org/repos/dist/dev/skywalking/java-agent/${version} + * sha512 checksums + - ${src_sha512} + - ${bin_sha512} + +Maven 2 staging repository: + + * https://repository.apache.org/content/repositories//org/apache/skywalking/ + +Release Tag : + + * (Git Tag) v${version} + +Release CommitID : + + * https://github.com/apache/skywalking-java/tree/${commit_id} + * Git submodule + * apm-protocol/apm-network/src/main/proto: https://github.com/apache/skywalking-data-collect-protocol/tree/${submodule_commit} + +Keys to verify the Release Candidate : + + * https://dist.apache.org/repos/dist/release/skywalking/KEYS + +Guide to build the release from source : + + > ./mvnw clean package + +Voting will start now ($(date '+%B %d, %Y')) and will remain open for at least 72 hours, Request all PMC members to give their vote. +[ ] +1 Release this package. +[ ] +0 No opinion. +[ ] -1 Do not release this package because.... +EOF + ;; + announce) + cat << EOF +Mail to: dev@skywalking.apache.org, announce@apache.org +Subject: [ANNOUNCE] Apache SkyWalking Java Agent ${version} released + +Hi all, + +Apache SkyWalking Team is glad to announce the release of Apache SkyWalking Java Agent ${version}. + +SkyWalking: APM (application performance monitor) tool for distributed systems, +especially designed for microservices, cloud native and container-based (Docker, Kubernetes, Mesos) architectures. + +The Java Agent for Apache SkyWalking, which provides the native tracing/metrics/logging abilities for Java projects. + +This release contains a number of new features, bug fixes and improvements compared to +the previous version. The notable changes include: + +(Highlight key changes from changes-${version}.md) +1. ... +2. ... +3. ... + +Please refer to the change log for the complete list of changes: +https://github.com/apache/skywalking-java/blob/master/changes/changes-${version}.md + +Apache SkyWalking website: +http://skywalking.apache.org/ + +Downloads: +http://skywalking.apache.org/downloads/ + +Twitter: +https://twitter.com/AsfSkyWalking + +SkyWalking Resources: +- GitHub: https://github.com/apache/skywalking-java +- Issue: https://github.com/apache/skywalking/issues +- Mailing list: dev@skywalking.apache.org + + +- Apache SkyWalking Team +EOF + ;; + esac + + echo "============================================================" + echo "" + warn "Replace with the actual Nexus staging repository ID." +} + +# ============================================================ +# docker — build and push Docker images +# ============================================================ +cmd_docker() { + cd "$PROJECT_ROOT" + + local version + version=$(git describe --tags --abbrev=0 | sed 's/^v//') + + info "Building and pushing Docker images for ${version}..." + + local dist_tar="${SCRIPT_DIR}/${PRODUCT_NAME}-${version}/${PRODUCT_NAME}-${version}.tgz" + + if [ ! -f "$dist_tar" ]; then + error "Binary tar not found: $dist_tar. Run '$0 stage' first." + fi + + # Extract agent package + tar -xzf "$dist_tar" -C "$PROJECT_ROOT" + + export NAME=skywalking-java-agent + export HUB=apache + export TAG="$version" + + make docker.push.alpine docker.push.java8 docker.push.java11 docker.push.java17 docker.push.java21 docker.push.java25 + + info "Docker images pushed for ${version}." +} + +# ============================================================ +# promote — move from dist/dev to dist/release +# ============================================================ +cmd_promote() { + cd "$PROJECT_ROOT" + + local version + version=$(git describe --tags --abbrev=0 | sed 's/^v//') + + info "Promoting release ${version} from dist/dev to dist/release..." + + read -rp "SVN username (Apache ID): " svn_user + + svn mv "https://dist.apache.org/repos/dist/dev/skywalking/java-agent/${version}" \ + "https://dist.apache.org/repos/dist/release/skywalking/java-agent/${version}" \ + -m "Release Apache SkyWalking Java Agent ${version}" \ + --username "$svn_user" + + info "Release ${version} promoted." + info "Next steps:" + info " 1. Release the Nexus staging repository" + info " 2. Update website download page" + info " 3. Run: $0 email announce" + info " 4. Run: $0 docker" +} + +# ============================================================ +# cleanup — remove old release from dist/release +# ============================================================ +cmd_cleanup() { + local old_version="${1:-}" + if [ -z "$old_version" ]; then + error "Usage: $0 cleanup (e.g., 9.5.0)" + fi + + info "Removing old release ${old_version} from dist/release..." + + read -rp "SVN username (Apache ID): " svn_user + + svn rm "https://dist.apache.org/repos/dist/release/skywalking/java-agent/${old_version}" \ + -m "Remove old Apache SkyWalking Java Agent ${old_version} release" \ + --username "$svn_user" + + info "Removed ${old_version} from dist/release." + warn "Remember to update download page links to point to archive.apache.org." +} + +# ============================================================ +# prepare-vote — run all steps before the vote +# ============================================================ +cmd_prepare_vote() { + local version="${1:-}" + local next_version="${2:-}" + if [ -z "$version" ]; then + error "Usage: $0 prepare-vote [next_version] (e.g., 9.7.0 9.8.0)" + fi + + cmd_preflight + echo "" + cmd_prepare "$version" "$next_version" + echo "" + cmd_stage + echo "" + cmd_upload + echo "" + cmd_email vote +} + +# ============================================================ +# vote-passed — run all steps after the vote passes +# ============================================================ +cmd_vote_passed() { + local old_version="${1:-}" + + cmd_promote + echo "" + cmd_docker + echo "" + cmd_email announce + + if [ -n "$old_version" ]; then + echo "" + cmd_cleanup "$old_version" + else + echo "" + warn "To clean up an old release, run: $0 cleanup " + fi +} + +# ============================================================ +# Main dispatcher +# ============================================================ +main() { + local cmd="${1:-}" + shift || true + + case "$cmd" in + preflight) cmd_preflight "$@" ;; + prepare) cmd_prepare "$@" ;; + stage) cmd_stage "$@" ;; + upload) cmd_upload "$@" ;; + email) cmd_email "$@" ;; + docker) cmd_docker "$@" ;; + promote) cmd_promote "$@" ;; + cleanup) cmd_cleanup "$@" ;; + prepare-vote) cmd_prepare_vote "$@" ;; + vote-passed) cmd_vote_passed "$@" ;; + *) + echo "Apache SkyWalking Java Agent Release Tool" + echo "" + echo "Usage: $0 [args]" + echo "" + echo "Quick start (two-step release):" + echo " $0 prepare-vote 9.7.0 [9.8.0] # before vote (next version auto-calculated if omitted)" + echo " (wait for 72h vote to pass)" + echo " $0 vote-passed [old_version] # after vote" + echo "" + echo "Individual commands:" + echo " preflight Check tools and environment" + echo " prepare [next_ver] Prepare release (branch, tag, PR)" + echo " stage Stage release (maven release:perform, build tars)" + echo " upload Upload to Apache SVN dist/dev" + echo " prepare-vote [next_ver] Run preflight + prepare + stage + upload + vote email" + echo " email [vote|announce] Generate email content" + echo " promote Move from dist/dev to dist/release in SVN" + echo " docker Build and push Docker images" + echo " vote-passed [old_ver] Run promote + docker + announce email [+ cleanup]" + echo " cleanup Remove old release from dist/release" + ;; + esac +} + +main "$@"