-
Notifications
You must be signed in to change notification settings - Fork 433
pyroscope.java: add integration tests #4454
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
Changes from all commits
1f18dc2
2c7af13
585c9ef
81e6cd8
c2bd5df
9ab2aed
68f48b4
753e38e
ede51bd
312db85
20733fe
ac70cff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,4 +26,4 @@ jobs: | |
go-version-file: go.mod | ||
cache: false | ||
|
||
- run: make GO_TAGS="nodocker" test-pyroscope | ||
- run: sudo make test-pyroscope | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I've added a test that requires root. What do you suggest? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
//go:build linux && (amd64 || arm64) | ||
|
||
package integration | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"os" | ||
"strings" | ||
"sync" | ||
"testing" | ||
"time" | ||
|
||
"github.com/go-kit/log" | ||
"github.com/grafana/alloy/internal/component/discovery" | ||
"github.com/grafana/alloy/internal/component/pyroscope" | ||
"github.com/grafana/alloy/internal/component/pyroscope/java" | ||
"github.com/grafana/alloy/internal/component/pyroscope/testutil" | ||
"github.com/grafana/alloy/internal/component/pyroscope/util/test" | ||
pyroutil "github.com/grafana/alloy/internal/component/pyroscope/util/test/container" | ||
querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" | ||
"github.com/prometheus/client_golang/prometheus" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestPyroscopeJavaIntegration(t *testing.T) { | ||
if os.Getenv("GITHUB_ACTIONS") == "true" && os.Getenv("GITHUB_JOB") != "test_pyroscope" { | ||
t.Skip("Skipping Pyroscope Java integration test in GitHub Actions (job name is not test_pyroscope)") | ||
} | ||
wg := sync.WaitGroup{} | ||
defer func() { | ||
wg.Wait() | ||
}() | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
t.Cleanup(cancel) | ||
l := log.NewSyncLogger(log.NewLogfmtLogger(os.Stderr)) | ||
l = log.WithPrefix(l, | ||
"test", t.Name(), | ||
"ts", log.DefaultTimestampUTC, | ||
) | ||
|
||
_, pyroscopeEndpoint := pyroutil.StartPyroscopeContainer(t, ctx, l) | ||
|
||
_, javaEndpoint, pid := pyroutil.StartJavaApplicationContainer(t, ctx, l) | ||
|
||
t.Logf("Pyroscope endpoint: %s", pyroscopeEndpoint) | ||
t.Logf("Java application endpoint: %s", javaEndpoint) | ||
t.Logf("Java process PID in container: %d", pid) | ||
|
||
reg := prometheus.NewRegistry() | ||
|
||
writeComponent, err := testutil.CreateWriteComponent(l, reg, pyroscopeEndpoint) | ||
require.NoError(t, err, "Failed to create write component") | ||
|
||
args := java.DefaultArguments() | ||
args.ForwardTo = []pyroscope.Appendable{writeComponent} | ||
args.ProfilingConfig.Interval = time.Second | ||
args.Targets = []discovery.Target{ | ||
discovery.NewTargetFromMap(map[string]string{ | ||
java.LabelProcessID: fmt.Sprintf("%d", pid), | ||
"service_name": "spring-petclinic", | ||
}), | ||
} | ||
javaComponent, err := java.New( | ||
log.With(l, "component", "pyroscope.java"), | ||
reg, | ||
"test-java", | ||
args, | ||
) | ||
require.NoError(t, err, "Failed to create java component") | ||
|
||
wg.Add(2) | ||
go func() { | ||
defer wg.Done() | ||
_ = javaComponent.Run(ctx) | ||
}() | ||
go func() { | ||
defer wg.Done() | ||
for ctx.Err() == nil { | ||
burn(javaEndpoint) | ||
time.Sleep(100 * time.Millisecond) | ||
} | ||
}() | ||
|
||
require.Eventually(t, func() bool { | ||
req := &querierv1.SelectMergeProfileRequest{ | ||
ProfileTypeID: `process_cpu:cpu:nanoseconds:cpu:nanoseconds`, | ||
LabelSelector: `{service_name="spring-petclinic"}`, | ||
Start: time.Now().Add(-time.Hour).UnixMilli(), | ||
End: time.Now().UnixMilli(), | ||
} | ||
res, err := test.Query(pyroscopeEndpoint, req) | ||
if err != nil { | ||
t.Logf("Error querying endpoint: %v", err) | ||
return false | ||
} | ||
ss := res.String() | ||
if !strings.Contains(ss, `org/springframework/samples/petclinic/web/VetController.showVetList`) { | ||
return false | ||
} | ||
if !strings.Contains(ss, `libjvm.so.JavaThread::thread_main_inner`) { | ||
return false | ||
} | ||
return true | ||
}, 90*time.Second, 100*time.Millisecond) | ||
|
||
require.Eventually(t, func() bool { | ||
req := &querierv1.SelectMergeProfileRequest{ | ||
ProfileTypeID: `memory:alloc_in_new_tlab_bytes:bytes:space:bytes`, | ||
LabelSelector: `{service_name="spring-petclinic"}`, | ||
Start: time.Now().Add(-time.Hour).UnixMilli(), | ||
End: time.Now().UnixMilli(), | ||
} | ||
res, err := test.Query(pyroscopeEndpoint, req) | ||
if err != nil { | ||
t.Logf("Error querying endpoint: %v", err) | ||
return false | ||
} | ||
ss := res.String() | ||
if !strings.Contains(ss, `org/springframework/samples/petclinic/web/VetController.showVetList`) { | ||
return false | ||
} | ||
if strings.Contains(ss, `libjvm.so.JavaThread::thread_main_inner`) { | ||
return false | ||
} | ||
return true | ||
}, 90*time.Second, 100*time.Millisecond) | ||
cancel() | ||
} | ||
|
||
func burn(url string) { | ||
_, _ = http.DefaultClient.Get(url + "/") | ||
_, _ = http.DefaultClient.Get(url + "/owners/find") | ||
_, _ = http.DefaultClient.Get(url + "/vets") | ||
_, _ = http.DefaultClient.Get(url + "/oups") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
//go:build linux && (arm64 || amd64) | ||
|
||
package testutil | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/go-kit/log" | ||
"github.com/grafana/alloy/internal/component/pyroscope" | ||
"github.com/grafana/alloy/internal/component/pyroscope/write" | ||
"github.com/prometheus/client_golang/prometheus" | ||
"go.opentelemetry.io/otel/trace/noop" | ||
) | ||
|
||
// CreateWriteComponent creates a pyroscope.write component that forwards to the given endpoint | ||
func CreateWriteComponent(l log.Logger, reg prometheus.Registerer, endpoint string) (pyroscope.Appendable, error) { | ||
var receiver pyroscope.Appendable | ||
e := write.GetDefaultEndpointOptions() | ||
e.URL = endpoint | ||
|
||
_, err := write.New( | ||
log.With(l, "component", "pyroscope.write"), | ||
noop.Tracer{}, | ||
reg, | ||
func(exports write.Exports) { | ||
receiver = exports.Receiver | ||
}, | ||
"test", | ||
"", | ||
write.Arguments{Endpoints: []*write.EndpointOptions{&e}}, | ||
) | ||
if err != nil { | ||
return nil, fmt.Errorf("error creating write component: %w", err) | ||
} | ||
return receiver, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package container | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
stdlog "log" | ||
"testing" | ||
"time" | ||
|
||
"github.com/docker/docker/api/types/container" | ||
"github.com/docker/go-connections/nat" | ||
"github.com/go-kit/log" | ||
"github.com/stretchr/testify/require" | ||
"github.com/testcontainers/testcontainers-go" | ||
"github.com/testcontainers/testcontainers-go/wait" | ||
) | ||
|
||
func StartJavaApplicationContainer(t *testing.T, ctx context.Context, l log.Logger) (testcontainers.Container, string, int) { | ||
req := testcontainers.ContainerRequest{ | ||
Image: "springcommunity/spring-framework-petclinic:latest", | ||
ExposedPorts: []string{"8080/tcp"}, | ||
WaitingFor: wait.ForHTTP("/").WithPort("8080/tcp").WithStartupTimeout(3 * time.Minute), | ||
Env: map[string]string{ | ||
"JAVA_OPTS": "-Xmx512m -Xms256m", | ||
}, | ||
HostConfigModifier: func(hc *container.HostConfig) { | ||
hc.PidMode = "host" | ||
}, | ||
} | ||
|
||
c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ | ||
ContainerRequest: req, | ||
Started: true, | ||
Logger: stdlog.New(log.NewStdlibAdapter(l), "", 0), | ||
}) | ||
require.NoError(t, err) | ||
|
||
t.Cleanup(func() { | ||
err := testcontainers.TerminateContainer(c) | ||
require.NoError(t, err) | ||
}) | ||
|
||
mappedPort, err := c.MappedPort(ctx, nat.Port("8080/tcp")) | ||
require.NoError(t, err) | ||
|
||
host, err := c.Host(ctx) | ||
require.NoError(t, err) | ||
|
||
endpoint := fmt.Sprintf("http://%s:%s", host, mappedPort.Port()) | ||
inspected, err := c.Inspect(t.Context()) | ||
require.NoError(t, err) | ||
|
||
return c, endpoint, inspected.State.Pid | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package container | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
stdlog "log" | ||
"testing" | ||
|
||
"github.com/go-kit/log" | ||
"github.com/stretchr/testify/require" | ||
"github.com/testcontainers/testcontainers-go" | ||
"github.com/testcontainers/testcontainers-go/wait" | ||
) | ||
|
||
func StartPyroscopeContainer(t *testing.T, ctx context.Context, l log.Logger) (testcontainers.Container, string) { | ||
req := testcontainers.ContainerRequest{ | ||
Image: "grafana/pyroscope:latest", | ||
Cmd: []string{"--ingester.min-ready-duration=0s"}, | ||
ExposedPorts: []string{"4040/tcp"}, | ||
WaitingFor: wait.ForHTTP("/ready").WithPort("4040/tcp"), | ||
Env: map[string]string{ | ||
"PYROSCOPE_LOG_LEVEL": "debug", | ||
}, | ||
} | ||
|
||
c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ | ||
ContainerRequest: req, | ||
Started: true, | ||
Logger: stdlog.New(log.NewStdlibAdapter(l), "", 0), | ||
}) | ||
require.NoError(t, err) | ||
|
||
t.Cleanup(func() { | ||
err := testcontainers.TerminateContainer(c) | ||
require.NoError(t, err) | ||
}) | ||
|
||
mappedPort, err := c.MappedPort(ctx, "4040/tcp") | ||
require.NoError(t, err) | ||
|
||
host, err := c.Host(ctx) | ||
require.NoError(t, err) | ||
|
||
endpoint := fmt.Sprintf("http://%s:%s", host, mappedPort.Port()) | ||
return c, endpoint | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package test | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
|
||
"connectrpc.com/connect" | ||
"github.com/google/pprof/profile" | ||
querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" | ||
"github.com/grafana/pyroscope/api/gen/proto/go/querier/v1/querierv1connect" | ||
) | ||
|
||
func Query(url string, q *querierv1.SelectMergeProfileRequest) (*profile.Profile, error) { | ||
client := querierv1connect.NewQuerierServiceClient(http.DefaultClient, url) | ||
res, err := client.SelectMergeProfile(context.Background(), connect.NewRequest(q)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
bs, err := res.Msg.MarshalVT() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return profile.ParseData(bs) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only checked Dockerfile and Dockerfile.windows, which are not using it.