Skip to content

Commit

Permalink
[feat]: [CI-13471]: Parse and Upload Savings (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
anurag-harness authored Aug 5, 2024
1 parent 0fe9572 commit d7e71d9
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 51 deletions.
15 changes: 8 additions & 7 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,14 @@ type (
}

PollStepResponse struct {
Exited bool `json:"exited,omitempty"`
ExitCode int `json:"exit_code,omitempty"`
Error string `json:"error,omitempty"`
OOMKilled bool `json:"oom_killed,omitempty"`
Outputs map[string]string `json:"outputs,omitempty"`
Artifact []byte `json:"artifact,omitempty"`
OutputV2 []*OutputV2 `json:"outputV2,omitempty"`
Exited bool `json:"exited,omitempty"`
ExitCode int `json:"exit_code,omitempty"`
Error string `json:"error,omitempty"`
OOMKilled bool `json:"oom_killed,omitempty"`
Outputs map[string]string `json:"outputs,omitempty"`
Artifact []byte `json:"artifact,omitempty"`
OutputV2 []*OutputV2 `json:"outputV2,omitempty"`
OptimizationState string `json:"optimization_state,omitempty"`
}

StreamOutputRequest struct {
Expand Down
9 changes: 9 additions & 0 deletions pipeline/runtime/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os"
"strings"

"github.com/drone/runner-go/pipeline/runtime"
"github.com/harness/godotenv/v3"
"github.com/harness/harness-docker-runner/api"
"github.com/harness/harness-docker-runner/engine"
Expand Down Expand Up @@ -209,3 +210,11 @@ func IsFeatureFlagEnabled(featureFlagName string, engine *engine.Engine, step *s
val, ok := step.Envs[featureFlagName]
return ok && val == trueValue
}

// checkStepSuccess checks if the step was successful based on the return values
func checkStepSuccess(state *runtime.State, err error) bool {
if err == nil && state != nil && state.ExitCode == 0 && state.Exited {
return true
}
return false
}
24 changes: 17 additions & 7 deletions pipeline/runtime/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,20 @@ import (
"github.com/harness/harness-docker-runner/pipeline"
tiCfg "github.com/harness/lite-engine/ti/config"
"github.com/harness/lite-engine/ti/report"
"github.com/harness/lite-engine/ti/savings"
"github.com/harness/ti-client/types"
)

func executeRunStep(ctx context.Context, engine *engine.Engine, r *api.StartStepRequest, out io.Writer, tiConfig *tiCfg.Cfg) (
*runtime.State, map[string]string, []byte, []*api.OutputV2, error) {
*runtime.State, map[string]string, []byte, []*api.OutputV2, string, error) {
start := time.Now()
step := toStep(r)
step.Command = r.Run.Command
step.Entrypoint = r.Run.Entrypoint

optimizationState := types.DISABLED
if (len(r.OutputVars) > 0 || len(r.Outputs) > 0) && (len(step.Entrypoint) == 0 || len(step.Command) == 0) {
return nil, nil, nil, nil, fmt.Errorf("output variable should not be set for unset entrypoint or command")
return nil, nil, nil, nil, string(optimizationState), fmt.Errorf("output variable should not be set for unset entrypoint or command")
}

enablePluginOutputSecrets := IsFeatureFlagEnabled(ciEnablePluginOutputSecrets, engine, step)
Expand Down Expand Up @@ -63,11 +67,17 @@ func executeRunStep(ctx context.Context, engine *engine.Engine, r *api.StartStep
step.Envs["PLUGIN_ARTIFACT_FILE"] = artifactFile

exited, err := engine.Run(ctx, step, out)
timeTakenMs := time.Since(start).Milliseconds()
logrus.WithField("step_id", r.ID).WithField("stage_id", r.StageRuntimeID).Traceln("completed step run")
if rerr := report.ParseAndUploadTests(ctx, r.TestReport, r.WorkingDir, step.Name, log, time.Now(), tiConfig, r.Envs); rerr != nil {
logrus.WithError(rerr).WithField("step", step.Name).Errorln("failed to upload report")
}

// Parse and upload savings to TI
if tiConfig.GetParseSavings() {
optimizationState = savings.ParseAndUploadSavings(ctx, r.WorkingDir, log, step.Name, checkStepSuccess(exited, err), timeTakenMs, tiConfig, r.Envs)
}

artifact, _ := fetchArtifactDataFromArtifactFile(artifactFile, out)
if exited != nil && exited.Exited && exited.ExitCode == 0 {
if enablePluginOutputSecrets {
Expand Down Expand Up @@ -127,12 +137,12 @@ func executeRunStep(ctx context.Context, engine *engine.Engine, r *api.StartStep
}
}

return exited, outputs, artifact, outputsV2, finalErr
return exited, outputs, artifact, outputsV2, string(optimizationState), finalErr

} else {
outputs, err := fetchOutputVariables(outputFile, out, false) // nolint:govet
if err != nil {
return exited, nil, nil, nil, err
return exited, nil, nil, nil, string(optimizationState), err
}
// Delete output variable file
if ferr := os.Remove(outputFile); ferr != nil {
Expand All @@ -149,11 +159,11 @@ func executeRunStep(ctx context.Context, engine *engine.Engine, r *api.StartStep
})
}
}
return exited, outputs, artifact, outputsV2, err
return exited, outputs, artifact, outputsV2, string(optimizationState), err
}
return exited, outputs, artifact, nil, err
return exited, outputs, artifact, nil, string(optimizationState), err
}
}

return exited, nil, artifact, nil, err
return exited, nil, artifact, nil, string(optimizationState), err
}
24 changes: 16 additions & 8 deletions pipeline/runtime/runtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
tiCfg "github.com/harness/lite-engine/ti/config"
"github.com/harness/lite-engine/ti/instrumentation"
"github.com/harness/lite-engine/ti/report"
"github.com/harness/lite-engine/ti/savings"
"github.com/harness/ti-client/types"
"github.com/sirupsen/logrus"
)

Expand All @@ -26,22 +28,22 @@ const (
)

func executeRunTestStep(ctx context.Context, engine *engine.Engine, r *api.StartStepRequest, out io.Writer, tiConfig *tiCfg.Cfg) (
*runtime.State, map[string]string, []byte, []*api.OutputV2, error) {
*runtime.State, map[string]string, []byte, []*api.OutputV2, string, error) {
start := time.Now()
log := logrus.New()
log.Out = out

start := time.Now()
optimizationState := types.DISABLED
cmd, err := instrumentation.GetCmd(ctx, &r.RunTest, r.Name, r.WorkingDir, log, r.Envs, tiConfig)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, string(optimizationState), err
}

step := toStep(r)
step.Command = []string{cmd}
step.Entrypoint = r.RunTest.Entrypoint

if (len(r.OutputVars) > 0 || len(r.Outputs) > 0) && len(step.Entrypoint) == 0 || len(step.Command) == 0 {
return nil, nil, nil, nil, fmt.Errorf("output variable should not be set for unset entrypoint or command")
return nil, nil, nil, nil, string(optimizationState), fmt.Errorf("output variable should not be set for unset entrypoint or command")
}

enablePluginOutputSecrets := IsFeatureFlagEnabled(ciEnablePluginOutputSecrets, engine, step)
Expand All @@ -63,6 +65,7 @@ func executeRunTestStep(ctx context.Context, engine *engine.Engine, r *api.Start
step.Envs["PLUGIN_ARTIFACT_FILE"] = artifactFile

exited, err := engine.Run(ctx, step, out)
timeTakenMs := time.Since(start).Milliseconds()
if rerr := report.ParseAndUploadTests(ctx, r.TestReport, r.WorkingDir, step.Name, log, time.Now(), tiConfig, r.Envs); rerr != nil {
log.WithError(rerr).Errorln("failed to upload report")
}
Expand All @@ -71,6 +74,11 @@ func executeRunTestStep(ctx context.Context, engine *engine.Engine, r *api.Start
log.WithError(uerr).Errorln("unable to collect callgraph")
}

// Parse and upload savings to TI
if tiConfig.GetParseSavings() {
optimizationState = savings.ParseAndUploadSavings(ctx, r.WorkingDir, log, step.Name, checkStepSuccess(exited, err), timeTakenMs, tiConfig, r.Envs)
}

artifact, _ := fetchArtifactDataFromArtifactFile(artifactFile, out)
var outputs map[string]string
var outputErr error
Expand All @@ -92,7 +100,7 @@ func executeRunTestStep(ctx context.Context, engine *engine.Engine, r *api.Start
})
}
}
return exited, outputs, artifact, outputsV2, outputErr
return exited, outputs, artifact, outputsV2, string(optimizationState), outputErr
}
} else if len(r.OutputVars) > 0 {
if exited != nil && exited.Exited && exited.ExitCode == 0 {
Expand All @@ -101,8 +109,8 @@ func executeRunTestStep(ctx context.Context, engine *engine.Engine, r *api.Start
} else {
outputs, outputErr = fetchOutputVariables(outputFile, out, false) // nolint:govet
}
return exited, outputs, artifact, nil, outputErr
return exited, outputs, artifact, nil, string(optimizationState), outputErr
}
}
return exited, nil, artifact, nil, err
return exited, nil, artifact, nil, string(optimizationState), err
}
27 changes: 18 additions & 9 deletions pipeline/runtime/runtestsV2.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,25 @@ import (
"github.com/harness/lite-engine/ti/callgraph"
tiCfg "github.com/harness/lite-engine/ti/config"
"github.com/harness/lite-engine/ti/report"
"github.com/harness/lite-engine/ti/savings"
"github.com/harness/ti-client/types"
)

const (
outDir = "%s/ti/v2/callgraph/cg/"
)

func executeRunTestsV2Step(ctx context.Context, engine *engine.Engine, r *api.StartStepRequest, out io.Writer, tiConfig *tiCfg.Cfg) (
*runtime.State, map[string]string, []byte, []*api.OutputV2, error) {
*runtime.State, map[string]string, []byte, []*api.OutputV2, string, error) {
start := time.Now()
log := logrus.New()
log.Out = out
step := toStep(r)
start := time.Now()
optimizationState := types.DISABLED
step.Entrypoint = r.RunTestsV2.Entrypoint
preCmd, err := leRuntime.SetupRunTestV2(ctx, &r.RunTestsV2, step.Name, r.WorkingDir, log, r.Envs, tiConfig)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, string(optimizationState), err
}
command := r.RunTestsV2.Command[0]
if preCmd != "" {
Expand All @@ -45,7 +48,7 @@ func executeRunTestsV2Step(ctx context.Context, engine *engine.Engine, r *api.St
step.Command = []string{command}

if (len(r.OutputVars) > 0 || len(r.Outputs) > 0) && (len(step.Entrypoint) == 0 || len(step.Command) == 0) {
return nil, nil, nil, nil, fmt.Errorf("output variable should not be set for unset entrypoint or command")
return nil, nil, nil, nil, string(optimizationState), fmt.Errorf("output variable should not be set for unset entrypoint or command")
}

enablePluginOutputSecrets := IsFeatureFlagEnabled(ciEnablePluginOutputSecrets, engine, step)
Expand Down Expand Up @@ -77,6 +80,7 @@ func executeRunTestsV2Step(ctx context.Context, engine *engine.Engine, r *api.St
step.Envs["PLUGIN_ARTIFACT_FILE"] = artifactFile

exited, err := engine.Run(ctx, step, out)
timeTakenMs := time.Since(start).Milliseconds()
logrus.WithField("step_id", r.ID).WithField("stage_id", r.StageRuntimeID).Traceln("completed step runtestv2")

if len(r.TestReport.Junit.Paths) == 0 {
Expand All @@ -91,6 +95,11 @@ func executeRunTestsV2Step(ctx context.Context, engine *engine.Engine, r *api.St
log.WithError(uerr).Errorln("unable to collect callgraph")
}

// Parse and upload savings to TI
if tiConfig.GetParseSavings() {
optimizationState = savings.ParseAndUploadSavings(ctx, r.WorkingDir, log, step.Name, checkStepSuccess(exited, err), timeTakenMs, tiConfig, r.Envs)
}

artifact, _ := fetchArtifactDataFromArtifactFile(artifactFile, out)
if exited != nil && exited.Exited && exited.ExitCode == 0 {
if enablePluginOutputSecrets {
Expand Down Expand Up @@ -150,12 +159,12 @@ func executeRunTestsV2Step(ctx context.Context, engine *engine.Engine, r *api.St
}
}

return exited, outputs, artifact, outputsV2, finalErr
return exited, outputs, artifact, outputsV2, string(optimizationState), finalErr

} else {
outputs, err := fetchOutputVariables(outputFile, out, false) // nolint:govet
if err != nil {
return exited, nil, nil, nil, err
return exited, nil, nil, nil, string(optimizationState), err
}
// Delete output variable file
if ferr := os.Remove(outputFile); ferr != nil {
Expand All @@ -172,11 +181,11 @@ func executeRunTestsV2Step(ctx context.Context, engine *engine.Engine, r *api.St
})
}
}
return exited, outputs, artifact, outputsV2, err
return exited, outputs, artifact, outputsV2, string(optimizationState), err
}
return exited, outputs, artifact, nil, err
return exited, outputs, artifact, nil, string(optimizationState), err
}
}

return exited, nil, artifact, nil, err
return exited, nil, artifact, nil, string(optimizationState), err
}
42 changes: 22 additions & 20 deletions pipeline/runtime/step_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ import (
type ExecutionStatus int

type StepStatus struct {
Status ExecutionStatus
State *runtime.State
StepErr error
Outputs map[string]string
Artifact []byte
OutputV2 []*api.OutputV2
Status ExecutionStatus
State *runtime.State
StepErr error
Outputs map[string]string
Artifact []byte
OutputV2 []*api.OutputV2
OptimizationState string
}

const (
Expand Down Expand Up @@ -74,8 +75,8 @@ func (e *StepExecutor) StartStep(ctx context.Context, r *api.StartStepRequest, s
e.mu.Unlock()

go func() {
state, outputs, artifact, outputV2, stepErr := e.executeStep(r, secrets, client, tiConfig)
status := StepStatus{Status: Complete, State: state, StepErr: stepErr, Outputs: outputs, Artifact: artifact, OutputV2: outputV2}
state, outputs, artifact, outputV2, optimizationState, stepErr := e.executeStep(r, secrets, client, tiConfig)
status := StepStatus{Status: Complete, State: state, StepErr: stepErr, Outputs: outputs, Artifact: artifact, OutputV2: outputV2, OptimizationState: optimizationState}
e.mu.Lock()
e.stepStatus[r.ID] = status
channels := e.stepWaitCh[r.ID]
Expand Down Expand Up @@ -199,7 +200,7 @@ func (e *StepExecutor) executeStepDrone(r *api.StartStepRequest, tiConfig *tiCfg

r.Kind = api.Run // only this kind is supported

exited, _, _, _, err := e.run(ctx, e.engine, r, stepLog, tiConfig)
exited, _, _, _, _, err := e.run(ctx, e.engine, r, stepLog, tiConfig)
if ctx.Err() == context.Canceled || ctx.Err() == context.DeadlineExceeded {
logr.WithError(err).Warnln("step execution canceled")
return nil, ctx.Err()
Expand Down Expand Up @@ -230,10 +231,10 @@ func (e *StepExecutor) executeStepDrone(r *api.StartStepRequest, tiConfig *tiCfg
return runStep()
}

func (e *StepExecutor) executeStep(r *api.StartStepRequest, secrets []string, client logstream.Client, tiConfig *tiCfg.Cfg) (*runtime.State, map[string]string, []byte, []*api.OutputV2, error) {
func (e *StepExecutor) executeStep(r *api.StartStepRequest, secrets []string, client logstream.Client, tiConfig *tiCfg.Cfg) (*runtime.State, map[string]string, []byte, []*api.OutputV2, string, error) {
if r.LogDrone {
state, err := e.executeStepDrone(r, tiConfig)
return state, nil, nil, nil, err
return state, nil, nil, nil, "", err
}

wc := livelog.New(client, r.LogKey, r.Name, getNudges())
Expand All @@ -256,7 +257,7 @@ func (e *StepExecutor) executeStep(r *api.StartStepRequest, secrets []string, cl
e.run(ctx, e.engine, r, wr, tiConfig) // nolint:errcheck
wc.Close()
}()
return &runtime.State{Exited: false}, nil, nil, nil, nil
return &runtime.State{Exited: false}, nil, nil, nil, "", nil
}

var result error
Expand All @@ -268,7 +269,7 @@ func (e *StepExecutor) executeStep(r *api.StartStepRequest, secrets []string, cl
defer cancel()
}

exited, outputs, artifact, outputV2, err := e.run(ctx, e.engine, r, wr, tiConfig)
exited, outputs, artifact, outputV2, optimizationState, err := e.run(ctx, e.engine, r, wr, tiConfig)
if err != nil {
result = multierror.Append(result, err)
}
Expand All @@ -283,7 +284,7 @@ func (e *StepExecutor) executeStep(r *api.StartStepRequest, secrets []string, cl
// DeadlineExceeded error this indicates the step was timed out.
switch ctx.Err() {
case context.Canceled, context.DeadlineExceeded:
return nil, nil, nil, nil, ctx.Err()
return nil, nil, nil, nil, "", ctx.Err()
}

if exited != nil {
Expand All @@ -299,11 +300,11 @@ func (e *StepExecutor) executeStep(r *api.StartStepRequest, secrets []string, cl
logrus.WithField("id", r.ID).Infof("received exit code %d\n", exited.ExitCode)
}
}
return exited, outputs, artifact, outputV2, result
return exited, outputs, artifact, outputV2, optimizationState, result
}

func (e *StepExecutor) run(ctx context.Context, engine *engine.Engine, r *api.StartStepRequest, out io.Writer, tiConfig *tiCfg.Cfg) (
*runtime.State, map[string]string, []byte, []*api.OutputV2, error) {
*runtime.State, map[string]string, []byte, []*api.OutputV2, string, error) {
if r.Kind == api.Run {
return executeRunStep(ctx, engine, r, out, tiConfig)
}
Expand All @@ -315,10 +316,11 @@ func (e *StepExecutor) run(ctx context.Context, engine *engine.Engine, r *api.St

func convertStatus(status StepStatus) *api.PollStepResponse {
r := &api.PollStepResponse{
Exited: true,
Outputs: status.Outputs,
Artifact: status.Artifact,
OutputV2: status.OutputV2,
Exited: true,
Outputs: status.Outputs,
Artifact: status.Artifact,
OutputV2: status.OutputV2,
OptimizationState: status.OptimizationState,
}

stepErr := status.StepErr
Expand Down

0 comments on commit d7e71d9

Please sign in to comment.