Skip to content

Commit 2c7af13

Browse files
second integration test
1 parent 1f18dc2 commit 2c7af13

File tree

5 files changed

+275
-17
lines changed

5 files changed

+275
-17
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
//go:build !nodocker && linux && (amd64 || arm64)
2+
3+
package integration
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"net/http"
9+
"os"
10+
"strings"
11+
"sync"
12+
"testing"
13+
"time"
14+
15+
"github.com/go-kit/log"
16+
"github.com/grafana/alloy/internal/component/discovery"
17+
"github.com/grafana/alloy/internal/component/pyroscope"
18+
"github.com/grafana/alloy/internal/component/pyroscope/java"
19+
"github.com/grafana/alloy/internal/component/pyroscope/testutil"
20+
"github.com/grafana/alloy/internal/component/pyroscope/util/test"
21+
pyroutil "github.com/grafana/alloy/internal/component/pyroscope/util/test/container"
22+
querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1"
23+
"github.com/prometheus/client_golang/prometheus"
24+
"github.com/stretchr/testify/require"
25+
)
26+
27+
func TestPyroscopeJavaIntegration(t *testing.T) {
28+
wg := sync.WaitGroup{}
29+
defer func() {
30+
wg.Wait()
31+
}()
32+
ctx, cancel := context.WithCancel(context.Background())
33+
t.Cleanup(cancel)
34+
l := log.NewSyncLogger(log.NewLogfmtLogger(os.Stderr))
35+
l = log.WithPrefix(l,
36+
"test", t.Name(),
37+
"ts", log.DefaultTimestampUTC,
38+
)
39+
40+
_, pyroscopeEndpoint := pyroutil.StartPyroscopeContainer(t, ctx, l)
41+
42+
_, javaEndpoint, pid := pyroutil.StartJavaApplicationContainer(t, ctx, l)
43+
44+
t.Logf("Pyroscope endpoint: %s", pyroscopeEndpoint)
45+
t.Logf("Java application endpoint: %s", javaEndpoint)
46+
t.Logf("Java process PID in container: %d", pid)
47+
48+
reg := prometheus.NewRegistry()
49+
50+
writeComponent, err := testutil.CreateWriteComponent(l, reg, pyroscopeEndpoint)
51+
require.NoError(t, err, "Failed to create write component")
52+
53+
args := java.DefaultArguments()
54+
args.ForwardTo = []pyroscope.Appendable{writeComponent}
55+
args.ProfilingConfig.Interval = time.Second
56+
args.Targets = []discovery.Target{
57+
discovery.NewTargetFromMap(map[string]string{
58+
java.LabelProcessID: fmt.Sprintf("%d", pid),
59+
"service_name": "spring-petclinic",
60+
}),
61+
}
62+
javaComponent, err := java.New(
63+
log.With(l, "component", "pyroscope.java"),
64+
reg,
65+
"test-java",
66+
args,
67+
)
68+
require.NoError(t, err, "Failed to create java component")
69+
70+
wg.Add(2)
71+
go func() {
72+
defer wg.Done()
73+
_ = javaComponent.Run(ctx)
74+
}()
75+
go func() {
76+
defer wg.Done()
77+
for ctx.Err() == nil {
78+
burn(javaEndpoint)
79+
time.Sleep(100 * time.Millisecond)
80+
}
81+
}()
82+
83+
require.Eventually(t, func() bool {
84+
req := &querierv1.SelectMergeProfileRequest{
85+
ProfileTypeID: `process_cpu:cpu:nanoseconds:cpu:nanoseconds`,
86+
LabelSelector: `{service_name="spring-petclinic"}`,
87+
Start: time.Now().Add(-time.Hour).UnixMilli(),
88+
End: time.Now().UnixMilli(),
89+
}
90+
res, err := test.Query(pyroscopeEndpoint, req)
91+
if err != nil {
92+
t.Logf("Error querying endpoint: %v", err)
93+
return false
94+
}
95+
ss := res.String()
96+
if !strings.Contains(ss, `org/springframework/samples/petclinic/web/VetController.showVetList`) {
97+
return false
98+
}
99+
if !strings.Contains(ss, `libjvm.so.JavaThread::thread_main_inner`) {
100+
return false
101+
}
102+
return true
103+
}, 90*time.Second, 100*time.Millisecond)
104+
105+
require.Eventually(t, func() bool {
106+
req := &querierv1.SelectMergeProfileRequest{
107+
ProfileTypeID: `memory:alloc_in_new_tlab_bytes:bytes:space:bytes`,
108+
LabelSelector: `{service_name="spring-petclinic"}`,
109+
Start: time.Now().Add(-time.Hour).UnixMilli(),
110+
End: time.Now().UnixMilli(),
111+
}
112+
res, err := test.Query(pyroscopeEndpoint, req)
113+
if err != nil {
114+
t.Logf("Error querying endpoint: %v", err)
115+
return false
116+
}
117+
ss := res.String()
118+
if !strings.Contains(ss, `org/springframework/samples/petclinic/web/VetController.showVetList`) {
119+
return false
120+
}
121+
if strings.Contains(ss, `libjvm.so.JavaThread::thread_main_inner`) {
122+
return false
123+
}
124+
return true
125+
}, 90*time.Second, 100*time.Millisecond)
126+
cancel()
127+
}
128+
129+
func burn(url string) {
130+
_, _ = http.DefaultClient.Get(url + "/")
131+
_, _ = http.DefaultClient.Get(url + "/owners/find")
132+
_, _ = http.DefaultClient.Get(url + "/vets")
133+
_, _ = http.DefaultClient.Get(url + "/oups")
134+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//go:build linux && (arm64 || amd64)
2+
3+
package testutil
4+
5+
import (
6+
"fmt"
7+
8+
"github.com/go-kit/log"
9+
"github.com/grafana/alloy/internal/component/pyroscope"
10+
"github.com/grafana/alloy/internal/component/pyroscope/write"
11+
"github.com/prometheus/client_golang/prometheus"
12+
"go.opentelemetry.io/otel/trace/noop"
13+
)
14+
15+
// CreateWriteComponent creates a pyroscope.write component that forwards to the given endpoint
16+
func CreateWriteComponent(l log.Logger, reg prometheus.Registerer, endpoint string) (pyroscope.Appendable, error) {
17+
var receiver pyroscope.Appendable
18+
e := write.GetDefaultEndpointOptions()
19+
e.URL = endpoint
20+
21+
_, err := write.New(
22+
log.With(l, "component", "pyroscope.write"),
23+
noop.Tracer{},
24+
reg,
25+
func(exports write.Exports) {
26+
receiver = exports.Receiver
27+
},
28+
"test",
29+
"",
30+
write.Arguments{Endpoints: []*write.EndpointOptions{&e}},
31+
)
32+
if err != nil {
33+
return nil, fmt.Errorf("error creating write component: %w", err)
34+
}
35+
return receiver, nil
36+
}

internal/component/pyroscope/util/internal/cmd/playground/main.go

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ import (
1414
"github.com/grafana/alloy/internal/component/pyroscope"
1515
"github.com/grafana/alloy/internal/component/pyroscope/ebpf"
1616
"github.com/grafana/alloy/internal/component/pyroscope/java"
17-
"github.com/grafana/alloy/internal/component/pyroscope/write"
17+
"github.com/grafana/alloy/internal/component/pyroscope/testutil"
1818
"github.com/oklog/run"
1919
"github.com/prometheus/client_golang/prometheus"
20-
"go.opentelemetry.io/otel/trace/noop"
2120
)
2221

2322
var (
@@ -39,20 +38,7 @@ func parseConfig() *config {
3938
}
4039

4140
func newWrite() pyroscope.Appendable {
42-
var receiver pyroscope.Appendable
43-
e := write.GetDefaultEndpointOptions()
44-
e.URL = "http://localhost:4040"
45-
_, err := write.New(
46-
log.With(l, "component", "write"),
47-
noop.Tracer{},
48-
reg,
49-
func(exports write.Exports) {
50-
receiver = exports.Receiver
51-
},
52-
"playground",
53-
"",
54-
write.Arguments{Endpoints: []*write.EndpointOptions{&e}},
55-
)
41+
receiver, err := testutil.CreateWriteComponent(l, reg, "http://localhost:4040")
5642
if err != nil {
5743
_ = l.Log("msg", "error creating write component", "err", err)
5844
os.Exit(1)
@@ -109,7 +95,7 @@ func main() {
10995
}
11096
}
11197

112-
func newJava(ps pids, w pyroscope.Appendable) *java.Component {
98+
func newJava(ps pids, w pyroscope.Appendable) interface{ Run(context.Context) error } {
11399
args := java.DefaultArguments()
114100
args.ForwardTo = []pyroscope.Appendable{w}
115101
for _, pid := range ps {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package container
2+
3+
import (
4+
"context"
5+
"fmt"
6+
stdlog "log"
7+
"testing"
8+
"time"
9+
10+
"github.com/docker/docker/api/types/container"
11+
"github.com/docker/go-connections/nat"
12+
"github.com/go-kit/log"
13+
"github.com/stretchr/testify/require"
14+
"github.com/testcontainers/testcontainers-go"
15+
"github.com/testcontainers/testcontainers-go/wait"
16+
)
17+
18+
func StartJavaApplicationContainer(t *testing.T, ctx context.Context, l log.Logger) (testcontainers.Container, string, int) {
19+
req := testcontainers.ContainerRequest{
20+
Image: "springcommunity/spring-framework-petclinic:latest",
21+
ExposedPorts: []string{"8080/tcp"},
22+
WaitingFor: wait.ForHTTP("/").WithPort("8080/tcp").WithStartupTimeout(3 * time.Minute),
23+
Env: map[string]string{
24+
"JAVA_OPTS": "-Xmx512m -Xms256m",
25+
},
26+
HostConfigModifier: func(hc *container.HostConfig) {
27+
hc.PidMode = "host"
28+
},
29+
}
30+
31+
c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
32+
ContainerRequest: req,
33+
Started: true,
34+
Logger: stdlog.New(log.NewStdlibAdapter(l), "", 0),
35+
})
36+
require.NoError(t, err)
37+
38+
t.Cleanup(func() {
39+
err := testcontainers.TerminateContainer(c)
40+
require.NoError(t, err)
41+
})
42+
43+
// Get the endpoint
44+
mappedPort, err := c.MappedPort(ctx, nat.Port("8080/tcp"))
45+
require.NoError(t, err)
46+
47+
host, err := c.Host(ctx)
48+
require.NoError(t, err)
49+
50+
endpoint := fmt.Sprintf("http://%s:%s", host, mappedPort.Port())
51+
inspected, err := c.Inspect(t.Context())
52+
require.NoError(t, err)
53+
54+
return c, endpoint, inspected.State.Pid
55+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//go:build !nodocker
2+
3+
package container
4+
5+
import (
6+
"context"
7+
"fmt"
8+
stdlog "log"
9+
"testing"
10+
11+
"github.com/go-kit/log"
12+
"github.com/stretchr/testify/require"
13+
"github.com/testcontainers/testcontainers-go"
14+
"github.com/testcontainers/testcontainers-go/wait"
15+
)
16+
17+
func StartPyroscopeContainer(t *testing.T, ctx context.Context, l log.Logger) (testcontainers.Container, string) {
18+
req := testcontainers.ContainerRequest{
19+
Image: "grafana/pyroscope:latest",
20+
ExposedPorts: []string{"4040/tcp"},
21+
WaitingFor: wait.ForHTTP("/ready").WithPort("4040/tcp"),
22+
Env: map[string]string{
23+
"PYROSCOPE_LOG_LEVEL": "debug",
24+
},
25+
}
26+
27+
c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
28+
ContainerRequest: req,
29+
Started: true,
30+
Logger: stdlog.New(log.NewStdlibAdapter(l), "", 0),
31+
})
32+
require.NoError(t, err)
33+
34+
t.Cleanup(func() {
35+
err := testcontainers.TerminateContainer(c)
36+
require.NoError(t, err)
37+
})
38+
39+
mappedPort, err := c.MappedPort(ctx, "4040/tcp")
40+
require.NoError(t, err)
41+
42+
host, err := c.Host(ctx)
43+
require.NoError(t, err)
44+
45+
endpoint := fmt.Sprintf("http://%s:%s", host, mappedPort.Port())
46+
return c, endpoint
47+
}

0 commit comments

Comments
 (0)