synapsecns/sanguine

View on GitHub
core/metrics/localmetrics/pyroscope.go

Summary

Maintainability
B
4 hrs
Test Coverage
package localmetrics

import (
    "context"

    "github.com/Flaque/filet"

    // embeds the pyroscope config file.
    _ "embed"
    "fmt"
    "os"
    "time"

    "github.com/ory/dockertest/v3"
    "github.com/ory/dockertest/v3/docker"
    "github.com/ory/dockertest/v3/docker/types/mount"
    "github.com/stretchr/testify/assert"
    "github.com/synapsecns/sanguine/core"
    "github.com/synapsecns/sanguine/core/dockerutil"
    "github.com/synapsecns/sanguine/core/metrics/internal"
    "github.com/synapsecns/sanguine/core/processlog"
    "github.com/synapsecns/sanguine/core/retry"
)

//go:embed pyroscope.yaml
var pyroscopeConfig string

// pyroscopePath is the local path to the pyroscope config file.
const pyroscopePath = "/pyroscope.yaml"

// StartPyroscopeServer starts a new pyroscope instance.
// See:  https://github.com/grafana/pyroscope/blob/main/examples/tracing/jaeger/docker-compose.yml
// for details.
// nolint: cyclop
func (j *testJaeger) StartPyroscopeServer(ctx context.Context) *uiResource {
    if !j.cfg.enablePyroscope {
        return &uiResource{
            uiURL: core.GetEnv(internal.PyroscopeEndpoint, fmt.Sprintf("%s not found", internal.PyroscopeEndpoint)),
        }
    }
    if core.HasEnv(internal.PyroscopeEndpoint) {
        return &uiResource{
            uiURL: os.Getenv(internal.PyroscopeEndpoint),
        }
    }

    runOptions := &dockertest.RunOptions{
        Hostname:   "pyroscope",
        Repository: "pyroscope/pyroscope",
        Env: []string{
            fmt.Sprintf("PYROSCOPE_CONFIG=%s", pyroscopePath),
        },
        Tag:          "latest",
        Cmd:          []string{"server"},
        ExposedPorts: []string{"4040"},
        Networks:     j.getNetworks(),
        Labels: map[string]string{
            appLabel:   "pyroscope",
            runIDLabel: j.runID,
        },
    }

    // github actions functions on a bridge so the host mount happens on the machine host
    // rather than in the isolated container environment. This causes the pyroscope config
    // to be inaccessible. To get around this we remove the environment variables from the
    // container.
    // See:  https://stackoverflow.com/a/60202672 for details.
    if core.HasEnv("CI") {
        runOptions.Env = []string{}
    }

    resource, err := j.pool.RunWithOptions(runOptions, func(config *docker.HostConfig) {
        config.Mounts = []docker.HostMount{
            {
                Type:     string(mount.TypeBind),
                Target:   pyroscopePath,
                Source:   filet.TmpFile(j.tb, "", pyroscopeConfig).Name(),
                ReadOnly: true,
            },
        }
        config.VolumesFrom = []string{}
        config.AutoRemove = true
        config.RestartPolicy = docker.RestartPolicy{Name: "no"}
    })
    assert.Nil(j.tb, err)

    j.tb.Setenv(internal.PyroscopeEndpoint, fmt.Sprintf("http://localhost:%s", dockerutil.GetPort(resource, "4040/tcp")))

    if !j.cfg.keepContainers {
        err = resource.Expire(uint(keepAliveOnFailure.Seconds()))
        assert.Nil(j.tb, err)
    }

    // make sure client is alive
    err = retry.WithBackoff(ctx, checkURL(os.Getenv(internal.PyroscopeEndpoint)), retry.WithMax(time.Millisecond*10), retry.WithMax(time.Minute), retry.WithMaxAttempts(100))
    if err != nil {
        return nil
    }

    logResourceChan := make(chan *uiResource, 1)
    go func() {
        _ = dockerutil.TailContainerLogs(dockerutil.WithContext(ctx), dockerutil.WithPool(j.pool), dockerutil.WithProcessLogOptions(processlog.WithLogDir(j.logDir), processlog.WithLogFileName("pyroscope")), dockerutil.WithFollow(true),
            dockerutil.WithResource(resource), dockerutil.WithCallback(func(ctx context.Context, metadata processlog.LogMetadata) {
                select {
                case <-ctx.Done():
                    return
                case logResourceChan <- &uiResource{
                    Resource: resource,
                    uiURL:    os.Getenv(internal.PyroscopeEndpoint),
                }:
                    return
                }
            }))
    }()
    // make sure client is alive
    err = retry.WithBackoff(ctx, checkURL(os.Getenv(internal.PyroscopeEndpoint)), retry.WithMax(time.Millisecond*10), retry.WithMax(time.Minute))
    if err != nil {
        return nil
    }

    select {
    case <-ctx.Done():
        return nil
    case logResource := <-logResourceChan:
        return logResource
    }
}