Skip to content

Commit

Permalink
Adds support for counting agent jar classes in memory calculator
Browse files Browse the repository at this point in the history
  • Loading branch information
pivotal-david-osullivan committed May 5, 2022
1 parent c581db8 commit 416fd09
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
# limitations under the License.

bin/
.DS_Store
18 changes: 17 additions & 1 deletion count/count_classes.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"strings"
)

var ClassExtensions = []string{".class", ".clj", ".groovy", ".kts"}
var ClassExtensions = []string{".class", ".classdata", ".clj", ".groovy", ".kts"}

func Classes(path string) (int, error) {
file := filepath.Join(path, "lib", "modules")
Expand Down Expand Up @@ -114,3 +114,19 @@ func ModuleClasses(file string) (int, error) {

return count, nil
}

func JarClassesFrom(paths ...string) (int, int, error) {
var agentClassCount, skippedPaths int

for _, path := range paths {
if c, err := JarClasses(path); err == nil {
agentClassCount += c
} else if strings.Contains(err.Error(), "no such file or directory") {
skippedPaths++
continue
} else {
return 0, 0, fmt.Errorf("unable to count classes of jar at %s\n%w", path, err)
}
}
return agentClassCount, skippedPaths, nil
}
47 changes: 38 additions & 9 deletions helper/memory_calculator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package helper

import (
"fmt"
"github.com/mattn/go-shellwords"
"io/ioutil"
"os"
"regexp"
Expand Down Expand Up @@ -75,6 +76,12 @@ func (m MemoryCalculator) Execute() (map[string]string, error) {
}
}

var values []string
opts, ok := os.LookupEnv("JAVA_TOOL_OPTIONS")
if ok {
values = append(values, opts)
}

if s, ok := os.LookupEnv("BPL_JVM_LOADED_CLASS_COUNT"); ok {
if c.LoadedClassCount, err = strconv.Atoi(s); err != nil {
return nil, fmt.Errorf("unable to convert $BPL_JVM_LOADED_CLASS_COUNT=%s to integer\n%w", s, err)
Expand All @@ -94,6 +101,11 @@ func (m MemoryCalculator) Execute() (map[string]string, error) {
}
}

agentClassCount, err := m.CountAgentClasses(opts)
if err != nil {
return nil, err
}

staticAdjustment := 0
adjustmentFactor := uint64(100)
if adj, ok := os.LookupEnv("BPL_JVM_CLASS_ADJUSTMENT"); ok {
Expand All @@ -110,12 +122,12 @@ func (m MemoryCalculator) Execute() (map[string]string, error) {

appClassCount, err := count.Classes(appPath)

totalClasses := float64(jvmClassCount+appClassCount+staticAdjustment) * (float64(adjustmentFactor) / 100.0)
totalClasses := float64(jvmClassCount+appClassCount+agentClassCount+staticAdjustment) * (float64(adjustmentFactor) / 100.0)

if err != nil {
return nil, fmt.Errorf("unable to determine class count\n%w", err)
}
m.Logger.Debugf("Memory Calculation: (%d%% * (%d + %d + %d)) * %0.2f", adjustmentFactor, jvmClassCount, appClassCount, staticAdjustment, ClassLoadFactor)
m.Logger.Debugf("Memory Calculation: (%d%% * (%d + %d + %d + %d)) * %0.2f", adjustmentFactor, jvmClassCount, appClassCount, agentClassCount, staticAdjustment, ClassLoadFactor)
c.LoadedClassCount = int(totalClasses * ClassLoadFactor)
}

Expand Down Expand Up @@ -154,13 +166,7 @@ func (m MemoryCalculator) Execute() (map[string]string, error) {
c.TotalMemory = calc.Size{Value: totalMemory}
}

var values []string
s, ok := os.LookupEnv("JAVA_TOOL_OPTIONS")
if ok {
values = append(values, s)
}

r, err := c.Calculate(s)
r, err := c.Calculate(opts)
if err != nil {
return nil, fmt.Errorf("unable to calculate memory configuration\n%w", err)
}
Expand Down Expand Up @@ -220,3 +226,26 @@ func parseMemInfo(s string) (int64, error) {
}
return num * unit, nil
}

func (m MemoryCalculator) CountAgentClasses(opts string) (int, error) {
var agentClassCount, skippedAgents int
if p, err := shellwords.Parse(opts); err != nil {
return 0, fmt.Errorf("unable to parse $JAVA_TOOL_OPTIONS\n%w", err)
} else {
var agentPaths []string
for _, s := range p {
if strings.HasPrefix(s, "-javaagent:") {
agentPaths = append(agentPaths, strings.Split(s, ":")[1])
}
}
if len(agentPaths) > 0 {
agentClassCount, skippedAgents, err = count.JarClassesFrom(agentPaths...)
if err != nil {
return 0, fmt.Errorf("error counting agent jar classes \n%w", err)
} else if skippedAgents > 0 {
m.Logger.Infof(`WARNING: could not count classes from all agent jars (skipped %d), class count and metaspace may not be sized correctly %d`, skippedAgents)
}
}
}
return agentClassCount, nil
}
51 changes: 51 additions & 0 deletions helper/memory_calculator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package helper_test

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"testing"

Expand Down Expand Up @@ -361,6 +363,55 @@ func testMemoryCalculator(t *testing.T, context spec.G, it spec.S) {
})
})

context("$JAVA_TOOL_OPTIONS with agents", func() {
it.Before(func() {
Expect(os.Setenv("JAVA_TOOL_OPTIONS", fmt.Sprintf("-javaagent:%s", filepath.Join("../count/testdata", "stub-dependency.jar")))).To(Succeed())
})

it.After(func() {
Expect(os.Unsetenv("JAVA_TOOL_OPTIONS")).To(Succeed())
})

it("counts classes of agent jars supplied via $JAVA_TOOL_OPTIONS", func() {
c, err := m.CountAgentClasses(os.Getenv("JAVA_TOOL_OPTIONS"))
Expect(err).NotTo(HaveOccurred())
Expect(c).To(Equal(2))
Expect(m.Execute()).To(Equal(map[string]string{
"JAVA_TOOL_OPTIONS": fmt.Sprintf("-javaagent:%s -XX:MaxDirectMemorySize=10M -Xmx522705K -XX:MaxMetaspaceSize=13870K -XX:ReservedCodeCacheSize=240M -Xss1M", filepath.Join("../count/testdata", "stub-dependency.jar")),
}))
})

it("skips counting classes if agent jar(s) supplied via $JAVA_TOOL_OPTIONS can't be found", func() {
Expect(os.Setenv("JAVA_TOOL_OPTIONS", fmt.Sprintf("-javaagent:!abc -javaagent:%s", filepath.Join("../count/testdata", "stub-dependency.jar")))).To(Succeed())
c, err := m.CountAgentClasses(os.Getenv("JAVA_TOOL_OPTIONS"))
Expect(err).NotTo(HaveOccurred())
Expect(c).To(Equal(2))
Expect(m.Execute()).To(Equal(map[string]string{
"JAVA_TOOL_OPTIONS": fmt.Sprintf("-javaagent:!abc -javaagent:%s -XX:MaxDirectMemorySize=10M -Xmx522705K -XX:MaxMetaspaceSize=13870K -XX:ReservedCodeCacheSize=240M -Xss1M", filepath.Join("../count/testdata", "stub-dependency.jar")),
}))
})

it("skips counting agent classes if no agent jar(s) are supplied", func() {
Expect(os.Setenv("JAVA_TOOL_OPTIONS", "")).To(Succeed())
c, err := m.CountAgentClasses(os.Getenv("JAVA_TOOL_OPTIONS"))
Expect(err).NotTo(HaveOccurred())
Expect(c).To(Equal(0))
Expect(m.Execute()).To(Equal(map[string]string{
"JAVA_TOOL_OPTIONS": " -XX:MaxDirectMemorySize=10M -Xmx522705K -XX:MaxMetaspaceSize=13870K -XX:ReservedCodeCacheSize=240M -Xss1M",
}))
})

it("does not change Metaspace if it has been user configured", func() {
Expect(os.Setenv("JAVA_TOOL_OPTIONS", fmt.Sprintf("-XX:MaxMetaspaceSize=20000K -javaagent:%s", filepath.Join("../count/testdata", "stub-dependency.jar")))).To(Succeed())
c, err := m.CountAgentClasses(os.Getenv("JAVA_TOOL_OPTIONS"))
Expect(err).NotTo(HaveOccurred())
Expect(c).To(Equal(2))
Expect(m.Execute()).To(Equal(map[string]string{
"JAVA_TOOL_OPTIONS": "-XX:MaxMetaspaceSize=20000K -javaagent:../count/testdata/stub-dependency.jar -XX:MaxDirectMemorySize=10M -Xmx516576K -XX:ReservedCodeCacheSize=240M -Xss1M",
}))
})
})

context("user configured", func() {
it.Before(func() {
Expect(os.Setenv("JAVA_TOOL_OPTIONS", "-XX:MaxDirectMemorySize=10M -Xmx522705K -XX:MaxMetaspaceSize=13870K -XX:ReservedCodeCacheSize=240M -Xss1M")).To(Succeed())
Expand Down

0 comments on commit 416fd09

Please sign in to comment.