Documentation
¶
Overview ¶
Package jambo provides a Go implementation of JVM Dynamic Attach mechanism.
This package allows you to dynamically attach to running JVM processes and execute various commands such as loading agents, dumping threads, inspecting heap, and more. It supports both HotSpot and OpenJ9 JVMs on Linux and Windows platforms.
Basic Usage ¶
Simple attach example:
output, err := jambo.Attach(12345, "threaddump", nil, true)
if err != nil {
log.Fatal(err)
}
fmt.Println(output)
Advanced Usage ¶
Using Process for more control:
proc, err := jambo.NewProcess(12345)
if err != nil {
log.Fatal(err)
}
opts := &jambo.Options{
PrintOutput: true,
Timeout: 5000,
}
output, err := proc.Attach("jcmd", []string{"VM.version"}, opts)
if err != nil {
log.Fatal(err)
}
Supported Commands ¶
- load: Load a Java agent into the JVM
- properties: Print system properties
- agentProperties: Print agent properties
- threaddump: Dump all thread stack traces
- dumpheap: Dump heap to file
- inspectheap: Print heap histogram
- datadump: Show heap and thread summary
- jcmd: Execute arbitrary jcmd command
Platform Support ¶
- Linux: Full support (HotSpot + OpenJ9, with container namespace support)
- Windows: HotSpot support via remote thread injection
- Other platforms: Basic stub implementation
Linux-specific implementation of JVM attach mechanism. This file contains implementations for both HotSpot and OpenJ9 JVMs on Linux.
Example (AdvancedAttach) ¶
Example_advancedAttach demonstrates using Process for more control.
package main
import (
"fmt"
"log"
"github.com/cosmorse/jambo"
)
func main() {
// Create a Process instance
proc, err := jambo.NewProcess(12345)
if err != nil {
log.Fatal(err)
}
// Get JVM type information
fmt.Printf("JVM Type: %v\n", proc.JVM().Type())
fmt.Printf("Process PID: %d\n", proc.Pid())
fmt.Printf("Namespace PID: %d\n", proc.NsPid())
// Configure attach options
opts := &jambo.Options{
PrintOutput: true,
Timeout: 5000, // 5 seconds
}
// Execute jcmd command
output, err := proc.Attach("jcmd", []string{"VM.version"}, opts)
if err != nil {
log.Fatal(err)
}
fmt.Println(output)
}
Example (ContainerSupport) ¶
Example_containerSupport demonstrates attaching to JVMs in containers.
package main
import (
"fmt"
"log"
"github.com/cosmorse/jambo"
)
func main() {
// When attaching to a containerized JVM, jambo automatically
// handles namespace switching and uses the correct namespace PID
proc, err := jambo.NewProcess(12345) // Host PID
if err != nil {
log.Fatal(err)
}
// Check if process is in a container
if proc.NsPid() != proc.Pid() {
fmt.Printf("Container detected - Host PID: %d, NS PID: %d\n",
proc.Pid(), proc.NsPid())
}
// Attach works the same way for containerized processes
output, err := proc.Attach("threaddump", nil, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(output)
}
Example (ErrorHandling) ¶
Example_errorHandling demonstrates proper error handling.
package main
import (
"fmt"
"log"
"github.com/cosmorse/jambo"
)
func main() {
// Parse PID from string
parsedPID, err := jambo.ParsePID("12345")
if err != nil {
log.Fatal("Invalid PID:", err)
}
// Create process
proc, err := jambo.NewProcess(parsedPID)
if err != nil {
switch err {
case jambo.ErrInvalidPID:
log.Fatal("Invalid process ID")
case jambo.ErrProcessNotFound:
log.Fatal("Process not found or not accessible")
default:
log.Fatal("Error:", err)
}
}
// Execute command
output, err := proc.Attach("threaddump", nil, nil)
if err != nil {
switch {
case err == jambo.ErrPermission:
log.Fatal("Permission denied - try running with sudo")
case err == jambo.ErrCommandFailed:
log.Fatal("Command execution failed in JVM")
default:
log.Fatal("Error:", err)
}
}
fmt.Println(output)
}
Example (GetProperties) ¶
Example_getProperties demonstrates getting system properties.
package main
import (
"fmt"
"log"
"github.com/cosmorse/jambo"
)
func main() {
proc, err := jambo.NewProcess(12345)
if err != nil {
log.Fatal(err)
}
// Get all system properties
output, err := proc.Attach("properties", nil, &jambo.Options{
PrintOutput: false, // Don't print to stdout
})
if err != nil {
log.Fatal(err)
}
// Parse and use properties as needed
fmt.Println(output)
}
Example (InspectHeap) ¶
Example_inspectHeap demonstrates heap inspection.
package main
import (
"fmt"
"log"
"github.com/cosmorse/jambo"
)
func main() {
proc, err := jambo.NewProcess(12345)
if err != nil {
log.Fatal(err)
}
// Get heap histogram
output, err := proc.Attach("inspectheap", nil, &jambo.Options{
PrintOutput: true,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(output)
}
Example (Jcmd) ¶
Example_jcmd demonstrates executing various jcmd commands.
package main
import (
"fmt"
"log"
"github.com/cosmorse/jambo"
)
func main() {
proc, err := jambo.NewProcess(12345)
if err != nil {
log.Fatal(err)
}
// Example 1: Get VM version
output, _ := proc.Attach("jcmd", []string{"VM.version"}, nil)
fmt.Println("VM Version:", output)
// Example 2: Get VM flags
output, _ = proc.Attach("jcmd", []string{"VM.flags"}, nil)
fmt.Println("VM Flags:", output)
// Example 3: Run GC
output, _ = proc.Attach("jcmd", []string{"GC.run"}, nil)
fmt.Println("GC Result:", output)
// Example 4: Get thread info
output, _ = proc.Attach("jcmd", []string{"Thread.print"}, nil)
fmt.Println(output)
}
Example (LoadAgent) ¶
Example_loadAgent demonstrates how to load a Java agent.
package main
import (
"fmt"
"log"
"github.com/cosmorse/jambo"
)
func main() {
proc, err := jambo.NewProcess(12345)
if err != nil {
log.Fatal(err)
}
// Load agent with absolute path
// Args: [agentPath, isAbsolutePath, options]
output, err := proc.Attach("load", []string{
"/path/to/agent.jar",
"true", // absolute path
"key1=value1,key2=value2", // agent options
}, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("Agent loaded:", output)
}
Example (MultipleCommands) ¶
Example_multipleCommands demonstrates executing multiple commands.
package main
import (
"fmt"
"log"
"github.com/cosmorse/jambo"
)
func main() {
proc, err := jambo.NewProcess(12345)
if err != nil {
log.Fatal(err)
}
commands := []struct {
name string
cmd string
args []string
}{
{"Thread Dump", "threaddump", nil},
{"Heap Summary", "jcmd", []string{"GC.heap_info"}},
{"VM Info", "jcmd", []string{"VM.info"}},
}
for _, c := range commands {
fmt.Printf("\\n=== %s ===\\n", c.name)
output, err := proc.Attach(c.cmd, c.args, &jambo.Options{
PrintOutput: false,
})
if err != nil {
log.Printf("Warning: %s failed: %v", c.name, err)
continue
}
fmt.Println(output)
}
}
Example (SimpleAttach) ¶
Example_simpleAttach demonstrates the simplest way to attach to a JVM process.
package main
import (
"fmt"
"log"
"github.com/cosmorse/jambo"
)
func main() {
// Attach to JVM process 12345 and get a thread dump
output, err := jambo.Attach(12345, "threaddump", nil, true)
if err != nil {
log.Fatal(err)
}
fmt.Println(output)
}
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrProcessNotFound indicates the target process does not exist or cannot be accessed. ErrProcessNotFound = errors.New("process not found") // ErrInvalidPID indicates the provided process ID is invalid (zero or negative). ErrInvalidPID = errors.New("invalid process ID") // ErrPermission indicates insufficient permissions to attach to the target process. ErrPermission = errors.New("permission denied") // ErrCommandFailed indicates the attach command execution failed in the target JVM. ErrCommandFailed = errors.New("command execution failed") )
Functions ¶
func Attach ¶
Attach is a convenience function that creates a Process and performs an attach operation. This is the simplest way to attach to a JVM process.
Parameters:
- pid: Target process ID
- command: Attach command to execute
- args: Command arguments (can be nil)
- printOutput: Whether to print output to stdout
Example:
output, err := jambo.Attach(12345, "threaddump", nil, true)
if err != nil {
log.Fatal(err)
}
fmt.Println(output)
For more control, use NewProcess() and call Attach() on the Process instance.
Types ¶
type JVM ¶
type JVM interface {
// Attach performs the actual attach operation to the target JVM process.
// It sends the command with arguments and returns the output.
//
// Parameters:
// - pid: The process ID of the target JVM
// - nspid: The namespace PID (for container support, same as pid if not in container)
// - args: Command and its arguments
// - printOutput: Whether to print output to stdout
// - tmpPath: Temporary directory path for attach files
//
// Returns:
// - string: Command output from the JVM
// - error: Any error that occurred during attach
Attach(pid, nspid int, args []string, printOutput bool, tmpPath string) (string, error)
// Detect checks if the target process is running this JVM type.
// Returns true if this JVM implementation is detected.
Detect(nspid int) bool
// Type returns the JVM implementation type.
Type() JVMType
}
JVM defines the interface for JVM attach operations. Different JVM implementations (HotSpot, OpenJ9) provide their own implementations.
type Options ¶
type Options struct {
// PrintOutput determines whether command output should be printed to stdout.
PrintOutput bool
// Timeout specifies the maximum time in milliseconds to wait for command completion.
// A value of 0 means no timeout (not yet implemented).
Timeout int
}
Options configures the behavior of attach operations.
type Process ¶
type Process struct {
// contains filtered or unexported fields
}
Process represents a target JVM process that can be attached to. It encapsulates process information and provides methods for attach operations.
Process instances should be created using NewProcess() to ensure proper initialization.
func NewProcess ¶
func (*Process) Attach ¶
Attach performs an attach operation with the specified command and arguments. This is the main method for executing commands in the target JVM.
The method performs the following steps:
- Enters target process namespaces (for container support)
- Switches to target process credentials (if needed)
- Determines the appropriate temp path
- Delegates to the JVM-specific implementation
Parameters:
- command: The attach command to execute (e.g., "threaddump", "jcmd", "load")
- args: Additional arguments for the command
- options: Attach options (nil for defaults)
Example:
proc, _ := jambo.NewProcess(12345)
output, err := proc.Attach("threaddump", nil, &jambo.Options{
PrintOutput: true,
})
if err != nil {
log.Fatal(err)
}
Example with jcmd:
output, err := proc.Attach("jcmd", []string{"VM.version"}, nil)
Example loading an agent:
output, err := proc.Attach("load", []string{"/path/to/agent.so", "false", "options"}, nil)
Returns ErrCommandFailed if the command execution fails in the JVM.
func (*Process) Gid ¶
Gid returns the group ID of the process owner. This is used for credential switching when attaching.
func (*Process) JVM ¶
JVM returns the JVM implementation instance for this process. The instance type (HotSpot or OpenJ9) is automatically detected during Process creation.
func (*Process) NsPid ¶
NsPid returns the namespace process ID. In containers, this may differ from Pid(). Otherwise, it's the same as Pid().