Skip to content

Commit

Permalink
Implement analytics for executor exit code.
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchell-as committed Oct 10, 2023
1 parent 7a54123 commit 00ab186
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 8 deletions.
6 changes: 3 additions & 3 deletions cmd/state-exec/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"os/exec"
)

func runCmd(meta *executorMeta) error {
func runCmd(meta *executorMeta) (int, error) {
userArgs := os.Args[1:]
cmd := exec.Command(meta.MatchingBin, userArgs...)
cmd.Stdin = os.Stdin
Expand All @@ -15,8 +15,8 @@ func runCmd(meta *executorMeta) error {
cmd.Env = meta.TransformedEnv

if err := cmd.Run(); err != nil {
return fmt.Errorf("command %q failed: %w", meta.MatchingBin, err)
return -1, fmt.Errorf("command %q failed: %w", meta.MatchingBin, err)
}

return nil
return cmd.ProcessState.ExitCode(), nil
}
4 changes: 2 additions & 2 deletions cmd/state-exec/comm.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ const (
msgWidth = 1024
)

func sendMsgToService(sockPath string, hb *svcmsg.Heartbeat) error {
func sendMsgToService(sockPath string, msg svcmsg.Messager) error {
conn, err := net.Dial(network, sockPath)
if err != nil {
return fmt.Errorf("dial failed: %w", err)
}
defer conn.Close()

_, err = conn.Write([]byte(hb.SvcMsg()))
_, err = conn.Write([]byte(msg.SvcMsg()))
if err != nil {
return fmt.Errorf("write to connection failed: %w", err)
}
Expand Down
17 changes: 17 additions & 0 deletions cmd/state-exec/exitcode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"fmt"
"os"
"strconv"

"github.com/ActiveState/cli/internal/svcctl/svcmsg"
)

func newExitCodeMessage(exitCode int) (*svcmsg.ExitCode, error) {
execPath, err := os.Executable()
if err != nil {
return nil, fmt.Errorf("cannot get executable info: %w", err)
}
return &svcmsg.ExitCode{execPath, strconv.Itoa(exitCode)}, nil
}
17 changes: 16 additions & 1 deletion cmd/state-exec/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,25 @@ func run() error {
}

logr.Debug("cmd - running: %s", meta.MatchingBin)
if err := runCmd(meta); err != nil {
exitCode, err := runCmd(meta)
if err != nil {
logr.Debug(" running - failed: bins (%v)", meta.ExecMeta.Bins)
return fmt.Errorf("cannot run command: %w", err)
}

msg, err := newExitCodeMessage(exitCode)
if err != nil {
return fmt.Errorf("cannot create new exit code message: %w", err)
}
logr.Debug("message data - exec: %s, exit code: %s", msg.ExecPath, msg.ExitCode)

if err := sendMsgToService(meta.SockPath, msg); err != nil {
logr.Debug(" sock - error: %v", err)

if onCI() { // halt control flow on CI only
return fmt.Errorf("cannot send message to service (this error is handled in CI only): %w", err)
}
}

return nil
}
1 change: 1 addition & 0 deletions cmd/state-svc/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func (s *service) Start() error {
svcctl.HTTPAddrHandler(portText(s.server)),
svcctl.LogFileHandler(s.logFile),
svcctl.HeartbeatHandler(s.cfg, s.server.Resolver(), s.an),
svcctl.ExitCodeHandler(s.cfg, s.server.Resolver(), s.an),
}
s.ipcSrv = ipc.NewServer(s.ctx, spath, reqHandlers...)
err = s.ipcSrv.Start()
Expand Down
3 changes: 3 additions & 0 deletions internal/analytics/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ const ActCommandError = "command-error"
// ActCommandInputError is the event action used for command input errors
const ActCommandInputError = "command-input-error"

// ActExecutorExit is the event action used for executor exit codes
const ActExecutorExit = "executor-exit"

// UpdateLabelSuccess is the sent if an auto-update was successful
const UpdateLabelSuccess = "success"

Expand Down
22 changes: 20 additions & 2 deletions internal/svcctl/comm.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var (
KeyHTTPAddr = "http-addr"
KeyLogFile = "log-file"
KeyHeartbeat = "heart<"
KeyExitCode = "exitcode<"
)

type Requester interface {
Expand Down Expand Up @@ -150,6 +151,23 @@ func HeartbeatHandler(cfg *config.Instance, resolver Resolver, analyticsReporter
}
}

func (c *Comm) SendHeartbeat(ctx context.Context, pid string) (string, error) {
return c.req.Request(ctx, KeyHeartbeat+pid)
func ExitCodeHandler(cfg *config.Instance, resolver Resolver, analyticsReporter AnalyticsReporter) ipc.RequestHandler {
return func(input string) (string, bool) {
defer func() { panics.HandlePanics(recover(), debug.Stack()) }()

if !strings.HasPrefix(input, KeyExitCode) {
return "", false
}

logging.Debug("Exit Code: Received exit code through ipc")

data := input[len(KeyExitCode):]
exitCode := svcmsg.NewExitCodeFromSvcMsg(data)

analyticsReporter.EventWithSource(constants.CatDebug, constants.ActExecutorExit, exitCode.ExitCode, &dimensions.Values{
Command: ptr.To(exitCode.ExecPath),
})

return "ok", true
}
}
42 changes: 42 additions & 0 deletions internal/svcctl/svcmsg/exitcode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Package svcmsg models the Exit Code data that the executor must communicate
// to the service.
//
// IMPORTANT: This package should have minimal dependencies as it will be
// imported by cmd/state-exec. The resulting compiled executable must remain as
// small as possible.
package svcmsg

import (
"fmt"
"strings"
)

type ExitCode struct {
ExecPath string
ExitCode string
}

func NewExitCodeFromSvcMsg(data string) *ExitCode {
var execPath, exitCode string

ss := strings.SplitN(data, "<", 2)
if len(ss) > 0 {
execPath = ss[0]
}
if len(ss) > 1 {
exitCode = ss[1]
}

return NewExitCode(execPath, exitCode)
}

func NewExitCode(execPath, exitCode string) *ExitCode {
return &ExitCode{
ExecPath: execPath,
ExitCode: exitCode,
}
}

func (e *ExitCode) SvcMsg() string {
return fmt.Sprintf("exitcode<%s<%s", e.ExecPath, e.ExitCode)
}
5 changes: 5 additions & 0 deletions internal/svcctl/svcmsg/svcmsg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package svcmsg

type Messager interface {
SvcMsg() string
}
2 changes: 2 additions & 0 deletions test/integration/analytics_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ func (suite *AnalyticsIntegrationTestSuite) TestHeartbeats() {
})
suite.Require().Equal(1, countEvents(executorEvents, anaConst.CatRuntimeUsage, anaConst.ActRuntimeAttempt, anaConst.SrcExecutor),
ts.DebugMessage("Should have a runtime attempt, events:\n"+suite.summarizeEvents(executorEvents)))
suite.Require().Equal(1, countEvents(executorEvents, anaConst.CatDebug, anaConst.ActExecutorExit, anaConst.SrcExecutor),
ts.DebugMessage("Should have an executor exit event, events:\n"+suite.summarizeEvents(executorEvents)))

// It's possible due to the timing of the heartbeats and the fact that they are async that we have gotten either
// one or two by this point. Technically more is possible, just very unlikely.
Expand Down

0 comments on commit 00ab186

Please sign in to comment.