Skip to content

Commit

Permalink
Add jvm-mon Go version
Browse files Browse the repository at this point in the history
  • Loading branch information
ajermakovics committed Apr 30, 2020
1 parent e39ec2f commit 3edbecd
Show file tree
Hide file tree
Showing 27 changed files with 1,484 additions and 0 deletions.
18 changes: 18 additions & 0 deletions jvm-mon-go/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
*.ipr
*.iml
*.jar
*.class
*.iws
rice-box.go
# output and ide dirs
build/
target/
out/
.gradle/
.vscode/
vendor/
.idea/

# bin
jvm-mon-go
log
39 changes: 39 additions & 0 deletions jvm-mon-go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# jvm-mon

JVM monitoring from the terminal (cpu, memory, threads).
Single executable written in Go and Java.
Supports monitoring application running on Java 8 and newer.

# Build

Prerequisites:
- Go (at least 1.13)
- https://github.com/Masterminds/glide for dependency management.
- https://github.com/GeertJohan/go.rice for embedded files
- JDK 8+ for building java agent

1. Build java agent: `./gradlew jar`
2. Install Go dependencies:
```
glide update
glide install
```
3. `go build`

# Usage

To monitor JVMs started with your username:

`./jvm-mon-go`

# How it works

jvm-mon attaches to a running JVM you select and loads an agent.jar into the process.
They communicate via a socket to send/receive JVM metrics in json format.

# Development

Run jvm-mon from Go sources: `./run.sh`
Run a java process: `./agent.sh`

See `log` file for debugging output
23 changes: 23 additions & 0 deletions jvm-mon-go/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
apply plugin: 'java'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
mavenCentral()
}

jar {
manifest {
attributes "Main-Class": "jvmmon.Agent"
attributes "Agent-Class": "jvmmon.Agent"
attributes "Premain-Class": "jvmmon.Agent"
attributes "Can-Redefine-Classes": 'false'
attributes "Can-Retransform-Classes": 'false'
attributes 'Can-Set-Native-Method-Prefix': 'false'
}
}

dependencies {
testCompile 'junit:junit:4.12'
}
24 changes: 24 additions & 0 deletions jvm-mon-go/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

./make-agent.sh

echo "Building"
go build -o build/

export GOOS=linux
export GOARCH=amd64

DIR="${GOOS}_${GOARCH}"
rm build/${DIR}/*
mkdir -p build/${DIR}

echo "Building $DIR"
go build -o build/${DIR}

rm build/*.tgz
tar cvzf build/jvm-mon-osx.tgz -C build jvm-mon-go
tar cvzf build/jvm-mon-linux64.tgz -C build/linux_amd64 jvm-mon-go

#Mac:
#GOOS=darwin
#GOARCH=amd64
27 changes: 27 additions & 0 deletions jvm-mon-go/glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions jvm-mon-go/glide.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package: .
import:
- package: github.com/gizak/termui
version: v2.2.0
- package: github.com/maruel/panicparse
version: v1.0.2
- package: github.com/nsf/termbox-go
- package: github.com/mattn/go-runewidth
- package: github.com/GeertJohan/go.rice
version: v1.0.0
13 changes: 13 additions & 0 deletions jvm-mon-go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/ajermakovics/jvm-mon-go

go 1.13

require (
github.com/GeertJohan/go.rice v1.0.0
github.com/gizak/termui v2.2.0+incompatible
github.com/maruel/panicparse v1.0.2
github.com/mattn/go-runewidth v0.0.3-0.20180304235428-a9d6d1e4dc51
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7
github.com/nsf/termbox-go v0.0.0-20180303152453-e2050e41c884
github.com/tokuhirom/go-hsperfdata v1.0.4-0.20161127080129-b58598ac84ee
)
25 changes: 25 additions & 0 deletions jvm-mon-go/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gizak/termui v2.2.0+incompatible h1:qvZU9Xll/Xd/Xr/YO+HfBKXhy8a8/94ao6vV9DSXzUE=
github.com/gizak/termui v2.2.0+incompatible/go.mod h1:PkJoWUt/zacQKysNfQtcw1RW+eK2SxkieVBtl+4ovLA=
github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE=
github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/maruel/panicparse v1.0.2 h1:dTqB9K2WSvu6JoCQSqSJhNgnir3AA64OHX+jNp4kFNg=
github.com/maruel/panicparse v1.0.2/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI=
github.com/mattn/go-runewidth v0.0.3-0.20180304235428-a9d6d1e4dc51 h1:73DyaxlfqHF9FQireGHkut6r7OuuA0jTVjS9N0vwktI=
github.com/mattn/go-runewidth v0.0.3-0.20180304235428-a9d6d1e4dc51/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/nsf/termbox-go v0.0.0-20180303152453-e2050e41c884 h1:fcs71SMqqDhUD+PbpIv9xf3EH9F9s6HfiLwr6jKm1VA=
github.com/nsf/termbox-go v0.0.0-20180303152453-e2050e41c884/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/tokuhirom/go-hsperfdata v1.0.4-0.20161127080129-b58598ac84ee h1:0MtzuMhW7HpwcXokCdJ0Z9OU0Y7JAPMBYbgG4jfpxxw=
github.com/tokuhirom/go-hsperfdata v1.0.4-0.20161127080129-b58598ac84ee/go.mod h1:1qHqOCR9yTTU8uUZxhV7TSaCYszgo85Vb4IU9TqfH6M=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
176 changes: 176 additions & 0 deletions jvm-mon-go/jvm-mon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package main

import (
"encoding/json"
"fmt"
"github.com/GeertJohan/go.rice"
"github.com/GeertJohan/go.rice/embedded"
. "github.com/ajermakovics/jvm-mon-go/jvmmon"
ui "github.com/gizak/termui"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
)

var pid, jar, port string
var jvms map[string]JVM
var server *Server
var version = "1.0-ea1"
var logFile *os.File

func init() {
user := GetCurUser()
var logErr error
logPath := os.TempDir() + string(os.PathSeparator) + "jvm-mon_" + user + ".log"
logFile, logErr := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
if logErr != nil {
log.Fatalf("Error opening %v file: %v", logPath, logErr)
panic(logErr)
}
log.SetOutput(logFile)
println("jvm-mon v:", version, " user:", user, " log:", logPath)

jvms = GetJVMs()
log.Println("jvm-mon v", version)
log.Println("Found JVMs: ", len(jvms))

err := ui.Init()
if err != nil {
log.Fatal("Cannot initialize UI", err)
panic(err)
}

server, err = NewServer()
if err != nil {
panic(err)
}
port = strconv.Itoa((*server).Port)
go receiveMetrics()
go checkConnections()
}

func main() {
jar = loadJar()

// Create UI
jvmTable := NewNavTable(jvms, "JVMs (v" + version + ")", 9)
memChart := NewMemChart()
cpuChart := NewCpuChart()
threadTable := NewThreadTable(9)

ui.Body.AddRows(
ui.NewRow(
ui.NewCol(6, 0, jvmTable),
ui.NewCol(6, 0, memChart)),
ui.NewRow(
ui.NewCol(6, 0, threadTable),
ui.NewCol(6, 0, cpuChart)))

ui.Body.Align()
ui.Render(ui.Body)

ui.Handle("/sys/kbd/C-c", func(ui.Event) { ui.StopLoop() })
ui.Handle("/sys/kbd/q", func(ui.Event) { ui.StopLoop() })
ui.Handle("/nav-table/selected", monitor)

ui.Loop()
ui.Close()
cleanUp()
}

func cleanUp() {
os.Remove(jar) // from temp
}

func loadJar() string {
for boxName, _ := range embedded.EmbeddedBoxes {
log.Println("Embedded dir: ", boxName)
}

box := rice.MustFindBox(`build/libs`)
jarFile, err := box.Open(`jvm-mon-go.jar`)
if err != nil {
panic(err)
}
stat, _ := jarFile.Stat()
log.Println("Found embeded jar file: ", stat.Size())
jarBytes, err := box.Bytes("jvm-mon-go.jar")
if err != nil {
panic(err)
}
tmpJarFile, err := ioutil.TempFile(os.TempDir(), "jvm-mon-go.jar")
if _, err = tmpJarFile.Write(jarBytes); err != nil {
fmt.Println("Failed to write to temporary file", err)
}
var tmpJarPath = tmpJarFile.Name()
log.Println("Created temp file ", tmpJarPath)

if err := tmpJarFile.Close(); err != nil {
fmt.Println(err)
}

err = os.Chmod(tmpJarPath, 0644)
if err != nil {
log.Println("Cannot chmod ", tmpJarPath, " ", err)
}

return tmpJarPath
}

func checkConnections() {
for {
addr := <-(*server).Connections
log.Println("JVM Connected ", addr)
}
}

func receiveMetrics() {
for {
msg := <-(*server).Messages
var metrics Metrics
msgBytes := []byte(msg)
err := json.Unmarshal(msgBytes, &metrics)
if err != nil {
log.Fatal("Cannot unmarshal: ", msg, "err: ", err)
continue
}

ui.SendCustomEvt("/metrics/mem", metrics)
ui.SendCustomEvt("/metrics/cpu", metrics)
ui.SendCustomEvt("/metrics/threads", metrics.Threads)
}
}

func monitor(e ui.Event) {
pid = e.Data.(string)
jvm := jvms[pid]
ui.SendCustomEvt("/metrics/mem/clear", pid)
ui.SendCustomEvt("/metrics/cpu/clear", pid)
go attachAgent(jvm, jar, port)
}

func attachAgent(jvm JVM, jar string, port string) {
err := jvm.AttachAndLoadAgent(jar, port)
if err != nil {
log.Println("Cannot attach to pid ", pid)
}
}

func findJar() string {
//workDir, _ := os.Getwd()
self, _ := os.Executable()
selfDir := filepath.Dir(self)

jar = filepath.Join(selfDir, "libs", "jvm-mon-go.jar") // during dev
if _, err := os.Stat(jar); os.IsNotExist(err) {
jar = filepath.Join(selfDir, "jvm-mon-go.jar")
}
if _, err := os.Stat(jar); os.IsNotExist(err) {
log.Fatal("Agent jar not found: ", jar, "Error: ", err)
panic(err)
}
log.Println("Agent jar file: ", jar)
return jar
}
Loading

0 comments on commit 3edbecd

Please sign in to comment.