Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for counting agent jar classes in memory calculator #166

Merged
merged 3 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
20 changes: 19 additions & 1 deletion count/count_classes.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ package count

import (
"archive/zip"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"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 +116,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 errors.Is(err, fs.ErrNotExist) {
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`, 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",
}))
})
})

dmikusa marked this conversation as resolved.
Show resolved Hide resolved
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