diff --git a/kurtosis-devnet/cmd/main.go b/kurtosis-devnet/cmd/main.go index a65cb114a7745..ab083af0446c7 100644 --- a/kurtosis-devnet/cmd/main.go +++ b/kurtosis-devnet/cmd/main.go @@ -7,9 +7,11 @@ import ( "os" "path/filepath" + "github.com/ethereum-optimism/optimism/devnet-sdk/telemetry" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/deploy" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" autofixTypes "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/types" + "github.com/honeycombio/otel-config-go/otelconfig" "github.com/urfave/cli/v2" ) @@ -80,6 +82,18 @@ func printWelcomeMessage() { } func mainAction(c *cli.Context) error { + ctx := c.Context + + ctx, shutdown, err := telemetry.SetupOpenTelemetry( + ctx, + otelconfig.WithServiceName(c.App.Name), + otelconfig.WithServiceVersion(c.App.Version), + ) + if err != nil { + return fmt.Errorf("error setting up OpenTelemetry: %w", err) + } + defer shutdown() + // Only show welcome message if not showing help or version if !c.Bool("help") && !c.Bool("version") && c.NArg() == 0 { printWelcomeMessage() @@ -115,7 +129,7 @@ func mainAction(c *cli.Context) error { return fmt.Errorf("error creating deployer: %w", err) } - env, err := deployer.Deploy(c.Context, nil) + env, err := deployer.Deploy(ctx, nil) if err != nil { if autofixMode == autofixTypes.AutofixModeDisabled { printAutofixMessage() diff --git a/kurtosis-devnet/pkg/build/contracts.go b/kurtosis-devnet/pkg/build/contracts.go index 461ba71d09231..9e80e1ff466c1 100644 --- a/kurtosis-devnet/pkg/build/contracts.go +++ b/kurtosis-devnet/pkg/build/contracts.go @@ -16,6 +16,7 @@ import ( ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/enclave" "github.com/spf13/afero" + "go.opentelemetry.io/otel" ) // ContractBuilder handles building smart contracts using just commands @@ -107,7 +108,10 @@ func NewContractBuilder(opts ...ContractBuilderOptions) *ContractBuilder { } // Build executes the contract build command -func (b *ContractBuilder) Build(_ string) (string, error) { +func (b *ContractBuilder) Build(ctx context.Context, _ string) (string, error) { + _, span := otel.Tracer("contract-builder").Start(ctx, "build contracts") + defer span.End() + // since we ignore layer for now, we can skip the build if the file already // exists: it'll be the same file! if url, ok := b.builtContracts[""]; ok { diff --git a/kurtosis-devnet/pkg/build/contracts_test.go b/kurtosis-devnet/pkg/build/contracts_test.go index d52a078e61b81..a4cf312297c8c 100644 --- a/kurtosis-devnet/pkg/build/contracts_test.go +++ b/kurtosis-devnet/pkg/build/contracts_test.go @@ -244,7 +244,7 @@ func TestContractBuilder_Build(t *testing.T) { builder.enclaveManager = enclaveManager // Execute build - output, err := builder.Build("") + output, err := builder.Build(context.Background(), "") // Verify results if tt.expectError { diff --git a/kurtosis-devnet/pkg/build/docker.go b/kurtosis-devnet/pkg/build/docker.go index 6763cd698fae3..c9142c574481c 100644 --- a/kurtosis-devnet/pkg/build/docker.go +++ b/kurtosis-devnet/pkg/build/docker.go @@ -16,6 +16,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/client" + "go.opentelemetry.io/otel" "golang.org/x/sync/semaphore" ) @@ -228,7 +229,7 @@ type templateData struct { // Build ensures the docker image for the given project is built, respecting concurrency limits. // It blocks until the specific requested build is complete. Other builds may run concurrently. -func (b *DockerBuilder) Build(projectName, imageTag string) (string, error) { +func (b *DockerBuilder) Build(ctx context.Context, projectName, imageTag string) (string, error) { b.mu.Lock() state, exists := b.buildStates[projectName] if !exists { @@ -241,7 +242,7 @@ func (b *DockerBuilder) Build(projectName, imageTag string) (string, error) { if !exists { state.once.Do(func() { - err := b.executeBuild(projectName, imageTag, state) + err := b.executeBuild(ctx, projectName, imageTag, state) if err != nil { state.err = err state.result = "" @@ -255,8 +256,9 @@ func (b *DockerBuilder) Build(projectName, imageTag string) (string, error) { return state.result, state.err } -func (b *DockerBuilder) executeBuild(projectName, initialImageTag string, state *buildState) error { - ctx := context.Background() +func (b *DockerBuilder) executeBuild(ctx context.Context, projectName, initialImageTag string, state *buildState) error { + ctx, span := otel.Tracer("docker-builder").Start(ctx, fmt.Sprintf("build %s", projectName)) + defer span.End() log.Printf("Build started for project: %s (tag: %s)", projectName, initialImageTag) diff --git a/kurtosis-devnet/pkg/build/docker_test.go b/kurtosis-devnet/pkg/build/docker_test.go index faa71d6c9f840..ea96e8c2ba30e 100644 --- a/kurtosis-devnet/pkg/build/docker_test.go +++ b/kurtosis-devnet/pkg/build/docker_test.go @@ -2,6 +2,7 @@ package build import ( "bytes" + "context" "fmt" "log" "sync" @@ -39,7 +40,7 @@ func TestDockerBuilder_Build_Success(t *testing.T) { ) // Execute build - resultTag, err := builder.Build(projectName, initialTag) + resultTag, err := builder.Build(context.Background(), projectName, initialTag) // Verify results require.NoError(t, err) @@ -59,7 +60,7 @@ func TestDockerBuilder_Build_CommandFailure(t *testing.T) { ) // Try to build a project - result, err := builder.Build("test-project", "test-tag") + result, err := builder.Build(context.Background(), "test-project", "test-tag") // Verify the result require.NoError(t, err) @@ -89,7 +90,7 @@ func TestDockerBuilder_Build_ConcurrencyLimit(t *testing.T) { defer wg.Done() projectName := fmt.Sprintf("concurrent-project-%d", idx) initialTag := fmt.Sprintf("%s:enclave1", projectName) - _, err := builder.Build(projectName, initialTag) + _, err := builder.Build(context.Background(), projectName, initialTag) assert.NoError(t, err, "Build %d failed", idx) }(i) } @@ -123,7 +124,7 @@ func TestDockerBuilder_Build_DryRun(t *testing.T) { ) // Execute build - resultTag, err := builder.Build(projectName, initialTag) + resultTag, err := builder.Build(context.Background(), projectName, initialTag) // Verify results require.NoError(t, err) @@ -161,7 +162,7 @@ func TestDockerBuilder_Build_DuplicateCalls(t *testing.T) { for i := 0; i < numCalls; i++ { go func(idx int) { defer wg.Done() - results[idx], errors[idx] = builder.Build(projectName, initialTag) + results[idx], errors[idx] = builder.Build(context.Background(), projectName, initialTag) }(i) } diff --git a/kurtosis-devnet/pkg/build/prestate.go b/kurtosis-devnet/pkg/build/prestate.go index 1e1a7a20dc3c5..38472d5cc35f9 100644 --- a/kurtosis-devnet/pkg/build/prestate.go +++ b/kurtosis-devnet/pkg/build/prestate.go @@ -2,10 +2,13 @@ package build import ( "bytes" + "context" "fmt" "log" "os/exec" "text/template" + + "go.opentelemetry.io/otel" ) // PrestateBuilder handles building prestates using just commands @@ -69,7 +72,10 @@ type prestateTemplateData struct { } // Build executes the prestate build command -func (b *PrestateBuilder) Build(path string) error { +func (b *PrestateBuilder) Build(ctx context.Context, path string) error { + _, span := otel.Tracer("prestate-builder").Start(ctx, "build prestate") + defer span.End() + if _, ok := b.builtPrestates[path]; ok { return nil } diff --git a/kurtosis-devnet/pkg/deploy/deploy.go b/kurtosis-devnet/pkg/deploy/deploy.go index 5653aad56f54a..32768902da874 100644 --- a/kurtosis-devnet/pkg/deploy/deploy.go +++ b/kurtosis-devnet/pkg/deploy/deploy.go @@ -14,6 +14,8 @@ import ( "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/engine" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" autofixTypes "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/types" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" ) type EngineManager interface { @@ -43,6 +45,7 @@ type Deployer struct { newEnclaveFS func(ctx context.Context, enclave string, opts ...ktfs.EnclaveFSOption) (*ktfs.EnclaveFS, error) enclaveManager *enclave.KurtosisEnclaveManager autofixMode autofixTypes.AutofixMode + tracer trace.Tracer } func WithKurtosisDeployer(ktDeployer DeployerFunc) DeployerOption { @@ -118,6 +121,7 @@ func NewDeployer(opts ...DeployerOption) (*Deployer, error) { return kurtosis.NewKurtosisDeployer(opts...) }, newEnclaveFS: ktfs.NewEnclaveFS, + tracer: otel.Tracer("deployer"), } for _, opt := range opts { opt(d) @@ -160,6 +164,9 @@ func NewDeployer(opts ...DeployerOption) (*Deployer, error) { } func (d *Deployer) deployEnvironment(ctx context.Context, r io.Reader) (*kurtosis.KurtosisEnvironment, error) { + ctx, span := d.tracer.Start(ctx, "deploy environment") + defer span.End() + // Create a multi reader to output deployment input to stdout buf := bytes.NewBuffer(nil) tee := io.TeeReader(r, buf) @@ -206,7 +213,10 @@ func (d *Deployer) deployEnvironment(ctx context.Context, r io.Reader) (*kurtosi return info, nil } -func (d *Deployer) renderTemplate(buildDir string, urlBuilder func(path ...string) string) (*bytes.Buffer, error) { +func (d *Deployer) renderTemplate(ctx context.Context, buildDir string, urlBuilder func(path ...string) string) (*bytes.Buffer, error) { + ctx, span := d.tracer.Start(ctx, "render template") + defer span.End() + t := &Templater{ baseDir: d.baseDir, dryRun: d.dryRun, @@ -218,10 +228,12 @@ func (d *Deployer) renderTemplate(buildDir string, urlBuilder func(path ...strin urlBuilder: urlBuilder, } - return t.Render() + return t.Render(ctx) } func (d *Deployer) Deploy(ctx context.Context, r io.Reader) (*kurtosis.KurtosisEnvironment, error) { + ctx, span := d.tracer.Start(ctx, "deploy devnet") + defer span.End() // Clean up the enclave before deploying if d.autofixMode == autofixTypes.AutofixModeNuke { @@ -263,7 +275,7 @@ func (d *Deployer) Deploy(ctx context.Context, r io.Reader) (*kurtosis.KurtosisE ch := srv.getState(ctx) - buf, err := d.renderTemplate(tmpDir, srv.URL) + buf, err := d.renderTemplate(ctx, tmpDir, srv.URL) if err != nil { return nil, fmt.Errorf("error rendering template: %w", err) } diff --git a/kurtosis-devnet/pkg/deploy/fileserver.go b/kurtosis-devnet/pkg/deploy/fileserver.go index 2590fdfbd7531..53b0802442cf3 100644 --- a/kurtosis-devnet/pkg/deploy/fileserver.go +++ b/kurtosis-devnet/pkg/deploy/fileserver.go @@ -17,6 +17,7 @@ import ( "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/util" "github.com/spf13/afero" + "go.opentelemetry.io/otel" ) const FILESERVER_PACKAGE = "fileserver" @@ -34,6 +35,9 @@ func (f *FileServer) URL(path ...string) string { } func (f *FileServer) Deploy(ctx context.Context, sourceDir string, stateCh <-chan *fileserverState) (retErr error) { + ctx, span := otel.Tracer("fileserver").Start(ctx, "deploy fileserver") + defer span.End() + if f.fs == nil { f.fs = afero.NewOsFs() } diff --git a/kurtosis-devnet/pkg/deploy/prestate.go b/kurtosis-devnet/pkg/deploy/prestate.go index 3789a99e22987..d6c7a823a6c98 100644 --- a/kurtosis-devnet/pkg/deploy/prestate.go +++ b/kurtosis-devnet/pkg/deploy/prestate.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "encoding/json" "fmt" "log" @@ -25,7 +26,7 @@ type localPrestateHolder struct { urlBuilder func(path ...string) string } -func (h *localPrestateHolder) GetPrestateInfo() (*PrestateInfo, error) { +func (h *localPrestateHolder) GetPrestateInfo(ctx context.Context) (*PrestateInfo, error) { if h.info != nil { return h.info, nil } @@ -59,7 +60,7 @@ func (h *localPrestateHolder) GetPrestateInfo() (*PrestateInfo, error) { } // Build all prestate files directly in the target directory - if err := h.builder.Build(buildDir); err != nil { + if err := h.builder.Build(ctx, buildDir); err != nil { return nil, fmt.Errorf("failed to build prestates: %w", err) } diff --git a/kurtosis-devnet/pkg/deploy/prestate_test.go b/kurtosis-devnet/pkg/deploy/prestate_test.go index 0a25da55e4b9a..3525531547aa4 100644 --- a/kurtosis-devnet/pkg/deploy/prestate_test.go +++ b/kurtosis-devnet/pkg/deploy/prestate_test.go @@ -2,6 +2,7 @@ package deploy import ( "bytes" + "context" "os" "path/filepath" "strings" @@ -56,7 +57,7 @@ _prestate-build target: buildWg := &sync.WaitGroup{} // Create template context with just the prestate function - tmplCtx := tmpl.NewTemplateContext(templater.localPrestateOption(buildWg)) + tmplCtx := tmpl.NewTemplateContext(templater.localPrestateOption(context.Background(), buildWg)) // Test template with multiple calls to localPrestate template := `first: diff --git a/kurtosis-devnet/pkg/deploy/template.go b/kurtosis-devnet/pkg/deploy/template.go index 692e7699b4bd9..278b0993fb9cd 100644 --- a/kurtosis-devnet/pkg/deploy/template.go +++ b/kurtosis-devnet/pkg/deploy/template.go @@ -2,6 +2,7 @@ package deploy import ( "bytes" + "context" "encoding/json" "fmt" "log" @@ -59,7 +60,7 @@ type dockerBuildJob struct { done chan struct{} } -func (f *Templater) localDockerImageOption() tmpl.TemplateContextOptions { +func (f *Templater) localDockerImageOption(_ context.Context) tmpl.TemplateContextOptions { // Initialize the build jobs map if it's nil if f.buildJobs == nil { f.buildJobs = make(map[string]*dockerBuildJob) @@ -99,7 +100,7 @@ func (f *Templater) localDockerImageOption() tmpl.TemplateContextOptions { }) } -func (f *Templater) localContractArtifactsOption(buildWg *sync.WaitGroup) tmpl.TemplateContextOptions { +func (f *Templater) localContractArtifactsOption(ctx context.Context, buildWg *sync.WaitGroup) tmpl.TemplateContextOptions { contractBuilder := build.NewContractBuilder( build.WithContractBaseDir(f.baseDir), build.WithContractDryRun(f.dryRun), @@ -115,7 +116,7 @@ func (f *Templater) localContractArtifactsOption(buildWg *sync.WaitGroup) tmpl.T f.contracts.started = true buildWg.Add(1) go func() { - url, err := contractBuilder.Build("") + url, err := contractBuilder.Build(ctx, "") f.contracts.url = url f.contracts.err = err buildWg.Done() @@ -126,7 +127,7 @@ func (f *Templater) localContractArtifactsOption(buildWg *sync.WaitGroup) tmpl.T }) } -func (f *Templater) localPrestateOption(buildWg *sync.WaitGroup) tmpl.TemplateContextOptions { +func (f *Templater) localPrestateOption(ctx context.Context, buildWg *sync.WaitGroup) tmpl.TemplateContextOptions { holder := &localPrestateHolder{ baseDir: f.baseDir, buildDir: f.buildDir, @@ -143,7 +144,7 @@ func (f *Templater) localPrestateOption(buildWg *sync.WaitGroup) tmpl.TemplateCo f.prestate.started = true buildWg.Add(1) go func() { - info, err := holder.GetPrestateInfo() + info, err := holder.GetPrestateInfo(ctx) f.prestate.info = info f.prestate.err = err buildWg.Done() @@ -163,7 +164,7 @@ func (f *Templater) localPrestateOption(buildWg *sync.WaitGroup) tmpl.TemplateCo }) } -func (f *Templater) Render() (*bytes.Buffer, error) { +func (f *Templater) Render(ctx context.Context) (*bytes.Buffer, error) { // Initialize the build jobs map if it's nil if f.buildJobs == nil { f.buildJobs = make(map[string]*dockerBuildJob) @@ -172,9 +173,9 @@ func (f *Templater) Render() (*bytes.Buffer, error) { buildWg := &sync.WaitGroup{} opts := []tmpl.TemplateContextOptions{ - f.localDockerImageOption(), - f.localContractArtifactsOption(buildWg), - f.localPrestateOption(buildWg), + f.localDockerImageOption(ctx), + f.localContractArtifactsOption(ctx, buildWg), + f.localPrestateOption(ctx, buildWg), tmpl.WithBaseDir(f.baseDir), } @@ -231,7 +232,7 @@ func (f *Templater) Render() (*bytes.Buffer, error) { go func(j *dockerBuildJob) { defer buildWg.Done() log.Printf("Starting build for %s (tag: %s)", j.projectName, j.imageTag) - j.result, j.err = dockerBuilder.Build(j.projectName, j.imageTag) + j.result, j.err = dockerBuilder.Build(ctx, j.projectName, j.imageTag) close(j.done) // Mark this job as done }(job) } diff --git a/kurtosis-devnet/pkg/deploy/template_test.go b/kurtosis-devnet/pkg/deploy/template_test.go index 2b3281da61f3b..04df915bf06a6 100644 --- a/kurtosis-devnet/pkg/deploy/template_test.go +++ b/kurtosis-devnet/pkg/deploy/template_test.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "os" "path/filepath" "strings" @@ -48,7 +49,7 @@ artifacts: {{localContractArtifacts "l1"}}` }, } - buf, err := templater.Render() + buf, err := templater.Render(context.Background()) require.NoError(t, err) // Verify template rendering @@ -103,7 +104,7 @@ prestateHash: {{ (localPrestate).Hashes.prestate_mt64 }}` }, } - buf, err := templater.Render() + buf, err := templater.Render(context.Background()) require.NoError(t, err) // --- Assertions --- @@ -164,7 +165,7 @@ func TestLocalPrestateOption(t *testing.T) { buildWg := &sync.WaitGroup{} // Get the localPrestate option - option := templater.localPrestateOption(buildWg) + option := templater.localPrestateOption(context.Background(), buildWg) // Create a template context with the option ctx := tmpl.NewTemplateContext(option) @@ -221,7 +222,7 @@ func TestLocalContractArtifactsOption(t *testing.T) { } buildWg := &sync.WaitGroup{} // Get the localContractArtifacts option - option := templater.localContractArtifactsOption(buildWg) + option := templater.localContractArtifactsOption(context.Background(), buildWg) // Create a template context with the option ctx := tmpl.NewTemplateContext(option) diff --git a/kurtosis-devnet/pkg/kurtosis/api/enclave/enclave.go b/kurtosis-devnet/pkg/kurtosis/api/enclave/enclave.go index 40c1f4572361a..92df86a43b3c8 100644 --- a/kurtosis-devnet/pkg/kurtosis/api/enclave/enclave.go +++ b/kurtosis-devnet/pkg/kurtosis/api/enclave/enclave.go @@ -7,6 +7,8 @@ import ( "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/wrappers" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/util" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" ) // DockerManager defines the interface for Docker operations @@ -24,6 +26,7 @@ func (d *DefaultDockerManager) DestroyDockerResources(ctx context.Context, encla type KurtosisEnclaveManager struct { kurtosisCtx interfaces.KurtosisContextInterface dockerMgr DockerManager + tracer trace.Tracer } type KurtosisEnclaveManagerOptions func(*KurtosisEnclaveManager) @@ -41,7 +44,9 @@ func WithDockerManager(dockerMgr DockerManager) KurtosisEnclaveManagerOptions { } func NewKurtosisEnclaveManager(opts ...KurtosisEnclaveManagerOptions) (*KurtosisEnclaveManager, error) { - manager := &KurtosisEnclaveManager{} + manager := &KurtosisEnclaveManager{ + tracer: otel.Tracer("enclave-manager"), + } for _, opt := range opts { opt(manager) @@ -58,6 +63,9 @@ func NewKurtosisEnclaveManager(opts ...KurtosisEnclaveManagerOptions) (*Kurtosis } func (mgr *KurtosisEnclaveManager) GetEnclave(ctx context.Context, enclave string) (interfaces.EnclaveContext, error) { + ctx, span := mgr.tracer.Start(ctx, "get enclave") + defer span.End() + // Try to get existing enclave first enclaveCtx, err := mgr.kurtosisCtx.GetEnclave(ctx, enclave) if err != nil { @@ -77,6 +85,9 @@ func (mgr *KurtosisEnclaveManager) GetEnclave(ctx context.Context, enclave strin // cleanupEnclave handles the common cleanup logic for both stopped and empty enclaves func (mgr *KurtosisEnclaveManager) cleanupEnclave(ctx context.Context, enclave string) error { + ctx, span := mgr.tracer.Start(ctx, "cleanup enclave") + defer span.End() + // Remove the enclave err := mgr.kurtosisCtx.DestroyEnclave(ctx, enclave) if err != nil { @@ -103,6 +114,9 @@ func (mgr *KurtosisEnclaveManager) cleanupEnclave(ctx context.Context, enclave s } func (mgr *KurtosisEnclaveManager) Autofix(ctx context.Context, enclave string) error { + ctx, span := mgr.tracer.Start(ctx, "autofix enclave") + defer span.End() + fmt.Printf("Autofixing enclave '%s'\n", enclave) status, err := mgr.kurtosisCtx.GetEnclaveStatus(ctx, enclave) if err != nil { @@ -125,6 +139,9 @@ func (mgr *KurtosisEnclaveManager) Autofix(ctx context.Context, enclave string) } func (mgr *KurtosisEnclaveManager) Nuke(ctx context.Context) error { + ctx, span := mgr.tracer.Start(ctx, "nuke enclaves") + defer span.End() + enclaves, err := mgr.kurtosisCtx.Clean(ctx, true) if err != nil { fmt.Printf("failed to clean enclaves: %v", err) diff --git a/kurtosis-devnet/pkg/kurtosis/api/run/handlers.go b/kurtosis-devnet/pkg/kurtosis/api/run/handlers.go index 7ed3dbbbba0d5..3c4c719063e57 100644 --- a/kurtosis-devnet/pkg/kurtosis/api/run/handlers.go +++ b/kurtosis-devnet/pkg/kurtosis/api/run/handlers.go @@ -6,6 +6,8 @@ import ( "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" "github.com/fatih/color" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" ) // Color printers @@ -63,17 +65,34 @@ func AllHandlers(handlers ...MessageHandler) MessageHandler { } // defaultHandler is the default message handler that provides standard Kurtosis output -var defaultHandler = FirstMatchHandler( - MessageHandlerFunc(handleProgress), - MessageHandlerFunc(handleInstruction), - MessageHandlerFunc(handleWarning), - MessageHandlerFunc(handleInfo), - MessageHandlerFunc(handleResult), - MessageHandlerFunc(handleError), -) +type defaultHandler struct { + tracer trace.Tracer + span trace.Span +} + +func newDefaultHandler() *defaultHandler { + return &defaultHandler{ + tracer: otel.Tracer("kurtosis-run"), + } +} + +var _ MessageHandler = (*defaultHandler)(nil) + +func (h *defaultHandler) Handle(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { + hdlr := FirstMatchHandler( + MessageHandlerFunc(h.handleProgress), + MessageHandlerFunc(h.handleInstruction), + MessageHandlerFunc(h.handleWarning), + MessageHandlerFunc(h.handleInfo), + MessageHandlerFunc(h.handleResult), + MessageHandlerFunc(h.handleError), + ) + + return hdlr.Handle(ctx, resp) +} // handleProgress handles progress info messages -func handleProgress(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { +func (h *defaultHandler) handleProgress(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { if progressInfo := resp.GetProgressInfo(); progressInfo != nil { // ignore progress messages, same as kurtosis run does return true, nil @@ -82,9 +101,12 @@ func handleProgress(ctx context.Context, resp interfaces.StarlarkResponse) (bool } // handleInstruction handles instruction messages -func handleInstruction(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { +func (h *defaultHandler) handleInstruction(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { if instruction := resp.GetInstruction(); instruction != nil { desc := instruction.GetDescription() + _, span := h.tracer.Start(ctx, desc) + h.span = span + fmt.Println(printCyan(desc)) return true, nil } @@ -92,7 +114,7 @@ func handleInstruction(ctx context.Context, resp interfaces.StarlarkResponse) (b } // handleWarning handles warning messages -func handleWarning(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { +func (h *defaultHandler) handleWarning(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { if warning := resp.GetWarning(); warning != nil { fmt.Println(printYellow(warning.GetMessage())) return true, nil @@ -101,7 +123,7 @@ func handleWarning(ctx context.Context, resp interfaces.StarlarkResponse) (bool, } // handleInfo handles info messages -func handleInfo(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { +func (h *defaultHandler) handleInfo(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { if info := resp.GetInfo(); info != nil { fmt.Println(printBlue(info.GetMessage())) return true, nil @@ -110,18 +132,21 @@ func handleInfo(ctx context.Context, resp interfaces.StarlarkResponse) (bool, er } // handleResult handles instruction result messages -func handleResult(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { +func (h *defaultHandler) handleResult(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { if result := resp.GetInstructionResult(); result != nil { if result.GetSerializedInstructionResult() != "" { fmt.Printf("%s\n\n", result.GetSerializedInstructionResult()) } + if h.span != nil { + h.span.End() + } return true, nil } return false, nil } // handleError handles error messages -func handleError(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { +func (h *defaultHandler) handleError(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { if err := resp.GetError(); err != nil { if interpretErr := err.GetInterpretationError(); interpretErr != nil { return true, fmt.Errorf(printRed("interpretation error: %v"), interpretErr) diff --git a/kurtosis-devnet/pkg/kurtosis/api/run/handlers_test.go b/kurtosis-devnet/pkg/kurtosis/api/run/handlers_test.go index 1e9af79d6bbdf..580642d921061 100644 --- a/kurtosis-devnet/pkg/kurtosis/api/run/handlers_test.go +++ b/kurtosis-devnet/pkg/kurtosis/api/run/handlers_test.go @@ -12,6 +12,7 @@ import ( func TestHandleProgress(t *testing.T) { ctx := context.Background() + d := newDefaultHandler() tests := []struct { name string response interfaces.StarlarkResponse @@ -33,7 +34,7 @@ func TestHandleProgress(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - handled, err := handleProgress(ctx, tt.response) + handled, err := d.handleProgress(ctx, tt.response) assert.NoError(t, err) assert.Equal(t, tt.want, handled) }) @@ -42,6 +43,7 @@ func TestHandleProgress(t *testing.T) { func TestHandleInstruction(t *testing.T) { ctx := context.Background() + d := newDefaultHandler() tests := []struct { name string response interfaces.StarlarkResponse @@ -63,7 +65,7 @@ func TestHandleInstruction(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - handled, err := handleInstruction(ctx, tt.response) + handled, err := d.handleInstruction(ctx, tt.response) assert.NoError(t, err) assert.Equal(t, tt.want, handled) }) @@ -72,6 +74,7 @@ func TestHandleInstruction(t *testing.T) { func TestHandleWarning(t *testing.T) { ctx := context.Background() + d := newDefaultHandler() tests := []struct { name string response interfaces.StarlarkResponse @@ -93,7 +96,7 @@ func TestHandleWarning(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - handled, err := handleWarning(ctx, tt.response) + handled, err := d.handleWarning(ctx, tt.response) assert.NoError(t, err) assert.Equal(t, tt.want, handled) }) @@ -102,6 +105,7 @@ func TestHandleWarning(t *testing.T) { func TestHandleInfo(t *testing.T) { ctx := context.Background() + d := newDefaultHandler() tests := []struct { name string response interfaces.StarlarkResponse @@ -123,7 +127,7 @@ func TestHandleInfo(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - handled, err := handleInfo(ctx, tt.response) + handled, err := d.handleInfo(ctx, tt.response) assert.NoError(t, err) assert.Equal(t, tt.want, handled) }) @@ -132,6 +136,7 @@ func TestHandleInfo(t *testing.T) { func TestHandleResult(t *testing.T) { ctx := context.Background() + d := newDefaultHandler() tests := []struct { name string response interfaces.StarlarkResponse @@ -162,7 +167,7 @@ func TestHandleResult(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - handled, err := handleResult(ctx, tt.response) + handled, err := d.handleResult(ctx, tt.response) assert.NoError(t, err) assert.Equal(t, tt.want, handled) }) @@ -171,6 +176,7 @@ func TestHandleResult(t *testing.T) { func TestHandleError(t *testing.T) { ctx := context.Background() + d := newDefaultHandler() testErr := fmt.Errorf("test error") tests := []struct { name string @@ -211,7 +217,7 @@ func TestHandleError(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - handled, err := handleError(ctx, tt.response) + handled, err := d.handleError(ctx, tt.response) if tt.wantError { assert.Error(t, err) } else { @@ -224,6 +230,7 @@ func TestHandleError(t *testing.T) { func TestFirstMatchHandler(t *testing.T) { ctx := context.Background() + d := newDefaultHandler() testErr := fmt.Errorf("test error") tests := []struct { name string @@ -235,8 +242,8 @@ func TestFirstMatchHandler(t *testing.T) { { name: "first handler matches", handlers: []MessageHandler{ - MessageHandlerFunc(handleInfo), - MessageHandlerFunc(handleWarning), + MessageHandlerFunc(d.handleInfo), + MessageHandlerFunc(d.handleWarning), }, response: &fake.StarlarkResponse{ Info: "test info", @@ -246,8 +253,8 @@ func TestFirstMatchHandler(t *testing.T) { { name: "second handler matches", handlers: []MessageHandler{ - MessageHandlerFunc(handleInfo), - MessageHandlerFunc(handleWarning), + MessageHandlerFunc(d.handleInfo), + MessageHandlerFunc(d.handleWarning), }, response: &fake.StarlarkResponse{ Warning: "test warning", @@ -257,8 +264,8 @@ func TestFirstMatchHandler(t *testing.T) { { name: "no handlers match", handlers: []MessageHandler{ - MessageHandlerFunc(handleInfo), - MessageHandlerFunc(handleWarning), + MessageHandlerFunc(d.handleInfo), + MessageHandlerFunc(d.handleWarning), }, response: &fake.StarlarkResponse{ Result: "test result", HasResult: true, @@ -268,7 +275,7 @@ func TestFirstMatchHandler(t *testing.T) { { name: "handler returns error", handlers: []MessageHandler{ - MessageHandlerFunc(handleError), + MessageHandlerFunc(d.handleError), }, response: &fake.StarlarkResponse{ Err: &fake.StarlarkError{InterpretationErr: testErr}, @@ -294,6 +301,7 @@ func TestFirstMatchHandler(t *testing.T) { func TestAllHandlers(t *testing.T) { ctx := context.Background() + d := newDefaultHandler() testErr := fmt.Errorf("test error") tests := []struct { name string @@ -318,8 +326,8 @@ func TestAllHandlers(t *testing.T) { { name: "some handlers match", handlers: []MessageHandler{ - MessageHandlerFunc(handleInfo), - MessageHandlerFunc(handleWarning), + MessageHandlerFunc(d.handleInfo), + MessageHandlerFunc(d.handleWarning), }, response: &fake.StarlarkResponse{ Info: "test info", @@ -329,8 +337,8 @@ func TestAllHandlers(t *testing.T) { { name: "no handlers match", handlers: []MessageHandler{ - MessageHandlerFunc(handleInfo), - MessageHandlerFunc(handleWarning), + MessageHandlerFunc(d.handleInfo), + MessageHandlerFunc(d.handleWarning), }, response: &fake.StarlarkResponse{ Result: "test result", HasResult: true, @@ -340,8 +348,8 @@ func TestAllHandlers(t *testing.T) { { name: "handler returns error", handlers: []MessageHandler{ - MessageHandlerFunc(handleInfo), - MessageHandlerFunc(handleError), + MessageHandlerFunc(d.handleInfo), + MessageHandlerFunc(d.handleError), }, response: &fake.StarlarkResponse{ Err: &fake.StarlarkError{InterpretationErr: testErr}, diff --git a/kurtosis-devnet/pkg/kurtosis/api/run/kurtosis_run.go b/kurtosis-devnet/pkg/kurtosis/api/run/kurtosis_run.go index a1361a34a79c1..bd317e9b6f7ad 100644 --- a/kurtosis-devnet/pkg/kurtosis/api/run/kurtosis_run.go +++ b/kurtosis-devnet/pkg/kurtosis/api/run/kurtosis_run.go @@ -11,6 +11,8 @@ import ( "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/wrappers" "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/starlark_run_config" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" ) type KurtosisRunner struct { @@ -18,6 +20,7 @@ type KurtosisRunner struct { enclave string kurtosisCtx interfaces.KurtosisContextInterface runHandlers []MessageHandler + tracer trace.Tracer } type KurtosisRunnerOptions func(*KurtosisRunner) @@ -47,7 +50,9 @@ func WithKurtosisRunnerRunHandlers(runHandlers ...MessageHandler) KurtosisRunner } func NewKurtosisRunner(opts ...KurtosisRunnerOptions) (*KurtosisRunner, error) { - r := &KurtosisRunner{} + r := &KurtosisRunner{ + tracer: otel.Tracer("kurtosis-run"), + } for _, opt := range opts { opt(r) } @@ -63,6 +68,9 @@ func NewKurtosisRunner(opts ...KurtosisRunnerOptions) (*KurtosisRunner, error) { } func (r *KurtosisRunner) Run(ctx context.Context, packageName string, args io.Reader) error { + ctx, span := r.tracer.Start(ctx, fmt.Sprintf("run package %s", packageName)) + defer span.End() + if r.dryRun { fmt.Printf("Dry run mode enabled, would run kurtosis package %s in enclave %s\n", packageName, r.enclave) @@ -112,7 +120,7 @@ func (r *KurtosisRunner) Run(ctx context.Context, packageName string, args io.Re runFinishedHandler := makeRunFinishedHandler(&isRunSuccessful) // Combine custom handlers with default handler and run finished handler - handler := AllHandlers(append(r.runHandlers, defaultHandler, runFinishedHandler)...) + handler := AllHandlers(append(r.runHandlers, newDefaultHandler(), runFinishedHandler)...) // Process the output stream for responseLine := range stream {