{"id":40279938,"url":"https://github.com/grafana/obi-java-agent","last_synced_at":"2026-01-20T04:01:08.968Z","repository":{"id":309591575,"uuid":"1021049222","full_name":"grafana/obi-java-agent","owner":"grafana","description":null,"archived":false,"fork":false,"pushed_at":"2026-01-18T23:10:47.000Z","size":165,"stargazers_count":5,"open_issues_count":10,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-01-19T08:44:17.619Z","etag":null,"topics":["keep"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/grafana.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-16T19:52:12.000Z","updated_at":"2025-11-30T18:26:26.000Z","dependencies_parsed_at":null,"dependency_job_id":"4e872034-65cb-492d-9948-2a20f9361aa9","html_url":"https://github.com/grafana/obi-java-agent","commit_stats":null,"previous_names":["grafana/obi-java-agent"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/grafana/obi-java-agent","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grafana%2Fobi-java-agent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grafana%2Fobi-java-agent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grafana%2Fobi-java-agent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grafana%2Fobi-java-agent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/grafana","download_url":"https://codeload.github.com/grafana/obi-java-agent/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grafana%2Fobi-java-agent/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28595314,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T02:08:49.799Z","status":"ssl_error","status_checked_at":"2026-01-20T02:08:44.148Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["keep"],"created_at":"2026-01-20T04:00:44.438Z","updated_at":"2026-01-20T04:01:08.957Z","avatar_url":"https://github.com/grafana.png","language":"Java","readme":"# OpenTelemetry eBPF Instrumentation (OBI) Java Agent\n\nA Java instrumentation agent for Java TLS observability using eBPF integration. \nThis agent intercepts sync and async TLS network I/O operations in Java applications and \ncommunicates with eBPF programs for distributed tracing and monitoring.\n\n## 🚀 Features\n\n- **Dynamic attach** - Attach to running JVMs without code changes\n- **Socket-level tracing** - Instruments `javax.net.ssl.SSLSocket` and `java.nio.channels.SocketChannel` operations\n- **SSL/TLS support** - Intercepts `javax.net.ssl.SSLEngine` for encrypted traffic\n- **Netty support** - Instruments Netty channels for reactive applications\n\n## 📋 Basic concepts\n\nThere are two main ways Java will create TLS traffic:\n\n1. Synchronous by using `SSLSocket`.\n2. Asynchronous by using `SSLEngine` to encrypt/decrypt and some mechanism to send the data, \nwhich is typically done though socket channels. We support the native JDK `SocketChannel`\nimplementations and `Netty's` socket channels.\n\nWith this bytecode instrumentation, we intercept the TLS traffic and we ship the data to\nOBI along with the connection information. We communicate with OBI via making a native C\nlibrary call to `ioctl`, which in turn makes a syscall. OBI attaches a kprobe to \n`do_vfs_ioctl` and intercepts the data sent from the Java agent.\n\nOBI cares about two main pieces of information to be able to correctly report and nest the\nTLS Java calls:\n1. The unencrypted TLS buffers.\n2. The connection information.\n\nWhen dealing with synchronous TLS traffic (e.g. SSLSocket), the encryption and socket\ncommunication is all done on the same thread and by the same Java class. In this case,\nwe simply inject a wrapper around the `SSLSocket` and capture the required buffers and\nconnection information.\n\nAsynchronous traffic is more complex. Typically, the encryption, decryption and the \ncommunication are not done on the same thread, and definitely not done by the same class. \nIn order to match the connection information to the unencrypted buffers, the agent injects \ncode to do the following:\n1. At the time of encryption/decryption (via SSLEngine) we create keys from the \nencrypted text (which should be random with enough length) and map that to the \nunencrypted buffer.\n2. At the time of socket communication we have the connection information, and the\nencrypted buffer. We consult the map of decrypted buffers, based on the encrypted \nbuffer keys and join that with the connection information. Once we have both parts\nwe make the same C library call to `ioctl`.\n\n## 📋 Table of Contents\n\n- [Architecture](#architecture)\n- [Building](#building)\n- [Usage](#usage)\n- [Instrumented Components](#instrumented-components)\n- [Benchmarking](#benchmarking)\n- [Testing](#testing)\n\n## 🏗️ Architecture\n\nThe project consists of two main modules:\n\n### 1. Agent Module (`agent/`)\nThe core instrumentation logic using ByteBuddy for bytecode manipulation:\n- **Instrumentations**: Socket, SocketChannel, SSLEngine, Netty\n- **eBPF Communication**: Via JNA and `ioctl` syscalls for minimal kernel impact\n- **Data Structures**: Connection tracking, SSL session management\n- **Utilities**: Optimized ByteBuffer extraction and manipulation\n\n### 2. Loader Module (`loader/`)\nA lightweight loader that:\n- Extracts the agent JAR from resources\n- Loads the agent using a separate classloader to avoid conflicts with the \n  target application\n- Ensures JNA is available in the bootstrap classloader\n- Handles agent attachment (both premain and agentmain)\n\n```\n┌───────────────────┐\n│  Java App         │\n│                   │\n│  ┌───────────┐    │\n│  │ OBI Agent │◄───┼─── Attaches via -javaagent\n│  └─────┬─────┘    │\n│        │          │\n│  ┌─────▼──────┐   │\n│  │Instrumented│   │\n│  │   Code     │   │\n│  └─────┬──────┘   │\n└────────┼──────────┘\n         │ ioctl\n         ▼\n   ┌──────────┐\n   │   OBI    │\n   └──────────┘\n```\n\n## 🔨 Building\n\n### Prerequisites\n- JDK 8 or higher\n- Gradle 7.0+\n\n### Build Commands\n\n```bash\n# Build all modules and distribution\n./gradlew build\n\n# Build only the agent\n./gradlew :agent:build\n\n# Build only the loader\n./gradlew :loader:build\n\n```\n\nThe final agent JAR will be located at:\n```\nbuild/obi-java-agent.jar\n```\n\n## 📦 Usage\n\n### Attach at Startup\n\n```bash\njava -javaagent:/path/to/obi-java-agent.jar -jar your-application.jar\n```\n\n### Attach to Running JVM\n\nUsing [jattach](https://github.com/jattach/jattach).\n\n```bash\njattach \u003cPID of Java program\u003e load instrument false \"/path/to/obi-java-agent.jar\"\n```\n\n### Enable Debug Mode (stdout)\n\n```bash\njava -javaagent:/path/to/obi-java-agent.jar=debug=true \\\n     -jar your-application.jar\n```\n\nor for dynamic attach\n\n```bash\njattach \u003cPID of Java program\u003e load instrument false \"/path/to/obi-java-agent.jar=debug=true\"\n```\n\n### Enable Debug for ByteBuddy instrumentation (stdout)\n\n```bash\njava -javaagent:/path/to/obi-java-agent.jar=debugBB=true \\\n     -jar your-application.jar\n```\n\nor for dynamic attach\n\n```bash\njattach \u003cPID of Java program\u003e load instrument false \"/path/to/obi-java-agent.jar=debugBB=true\"\n```\n\n\n## 🔍 Instrumented Components\n\n### 1. **javax.net.ssl.SSLSocket** for synchronous TLS\n- `getInputStream()` - Returns wrapped InputStream\n- `getOutputStream()` - Returns wrapped OutputStream\n- Tracks connection metadata (local/remote address, ports)\n\n### 2. **java.nio.channels.SocketChannel** for asynchronous TLS\n- `read(ByteBuffer)` - Single buffer reads\n- `read(ByteBuffer[])` - Scatter reads\n- `write(ByteBuffer)` - Single buffer writes\n- `write(ByteBuffer[])` - Gather writes\n- `shutdownInput` - clean-up\n- `shutdownOutput` - clean-up\n- `kill` - clean-up\n- `tryClose` - clean-up\n\n### 3. **javax.net.ssl.SSLEngine** for asynchronous TLS\n- `wrap(ByteBuffer)` - Encrypting outbound data\n- `wrap(ByteBuffer[])` - Encrypting outbound data\n- `unwrap(ByteBuffer)` - Decrypting inbound data\n- `unwrap(ByteBuffer[])` - Decrypting inbound data\n- SSL session to connection mapping\n\n### 4. **io.netty.handler.ssl.SslHandler** for Netty channels (which don't use JDK SocketChannel)\n- `wrap()` - Extracts connection info\n- `unwrap()` - Extracts connection info\n\n## 💻 Development\n\n### Key Technologies\n\n- **ByteBuddy** - Bytecode manipulation and agent building\n- **JNA (Java Native Access)** - Native library calls (ioctl)\n- **Caffeine** - High-performance LRU for keeping track of existing connections\n\n### Adding New Instrumentations\n\n1. Create a new class in `instrumentations/`\n2. Implement ByteBuddy `AgentBuilder.Transformer`\n3. Register in `Agent.java`\n4. Add to re-transform list in `Agent.java` for dynamic attach\n5. Add tests in `src/test/java/`\n\n## 📊 Benchmarking\n\n### Running Benchmarks\n\n```bash\n# Run all benchmarks\n./gradlew :agent:jmh\n\n# Run specific benchmark\n./gradlew :agent:jmh -Pjmh.includes=benchmarkFlattenDstByteBufferArray\n\n# Run with GC profiling\n./gradlew :agent:jmh -Pjmh.profilers=gc\n\n# Run with memory allocation profiling\n./gradlew :agent:jmh -Pjmh.profilers=gc,stack\n```\n\n### Benchmark Results\n\nSee `agent/src/jmh/java/io/opentelemetry/obi/java/instrumentations/util/BENCHMARK_README.md` for detailed benchmarking documentation.\n\nExample results (ns/op, lower is better):\n```\nBenchmark                              (bufferType)  (bufferSize)   Score\nflattenDstByteBufferArray                    heap           64    245.3\nflattenDstByteBufferArray                  direct           64    523.1\n```\n\n## 🧪 Testing\n\n### Unit Tests\n\n```bash\n# Run all tests\n./gradlew test\n\n# Run tests for specific module\n./gradlew :agent:test\n\n# Run specific test class\n./gradlew :agent:test --tests ByteBufferExtractorTest\n```\n\n## 📝 License\n\nApache 2.0 License \n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgrafana%2Fobi-java-agent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgrafana%2Fobi-java-agent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgrafana%2Fobi-java-agent/lists"}