diff --git a/cmd/state-installer/cmd.go b/cmd/state-installer/cmd.go index a69b8b564b..462444eff5 100644 --- a/cmd/state-installer/cmd.go +++ b/cmd/state-installer/cmd.go @@ -12,6 +12,7 @@ import ( "github.com/ActiveState/cli/internal/analytics" "github.com/ActiveState/cli/internal/analytics/client/sync" + anaConst "github.com/ActiveState/cli/internal/analytics/constants" "github.com/ActiveState/cli/internal/captain" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/constants" @@ -37,9 +38,6 @@ import ( "golang.org/x/crypto/ssh/terminal" ) -const AnalyticsCat = "installer" -const AnalyticsFunnelCat = "installer-funnel" - type Params struct { sourceInstaller string path string @@ -125,8 +123,8 @@ func main() { logging.Debug("Original Args: %v", os.Args) logging.Debug("Processed Args: %v", processedArgs) - an = sync.New(cfg, nil, out) - an.Event(AnalyticsFunnelCat, "start") + an = sync.New(anaConst.SrcStateInstaller, cfg, nil, out) + an.Event(anaConst.CatInstallerFunnel, "start") params := newParams() cmd := captain.NewCommand( @@ -193,30 +191,30 @@ func main() { }, ) - an.Event(AnalyticsFunnelCat, "pre-exec") + an.Event(anaConst.CatInstallerFunnel, "pre-exec") err = cmd.Execute(processedArgs[1:]) if err != nil { errors.ReportError(err, cmd, an) if locale.IsInputError(err) { - an.EventWithLabel(AnalyticsCat, "input-error", errs.JoinMessage(err)) + an.EventWithLabel(anaConst.CatInstaller, "input-error", errs.JoinMessage(err)) logging.Debug("Installer input error: " + errs.JoinMessage(err)) } else { - an.EventWithLabel(AnalyticsCat, "error", errs.JoinMessage(err)) + an.EventWithLabel(anaConst.CatInstaller, "error", errs.JoinMessage(err)) multilog.Critical("Installer error: " + errs.JoinMessage(err)) } - an.EventWithLabel(AnalyticsFunnelCat, "fail", errs.JoinMessage(err)) + an.EventWithLabel(anaConst.CatInstallerFunnel, "fail", errs.JoinMessage(err)) exitCode, err = errors.ParseUserFacing(err) if err != nil { out.Error(err) } } else { - an.Event(AnalyticsFunnelCat, "success") + an.Event(anaConst.CatInstallerFunnel, "success") } } func execute(out output.Outputer, cfg *config.Instance, an analytics.Dispatcher, args []string, params *Params) error { - an.Event(AnalyticsFunnelCat, "exec") + an.Event(anaConst.CatInstallerFunnel, "exec") if params.path == "" { var err error @@ -274,13 +272,13 @@ func execute(out output.Outputer, cfg *config.Instance, an analytics.Dispatcher, if params.isUpdate { route = "update" } - an.Event(AnalyticsFunnelCat, route) + an.Event(anaConst.CatInstallerFunnel, route) // Check if state tool already installed if !params.isUpdate && !params.force && stateToolInstalled && !targetingSameBranch { logging.Debug("Cancelling out because State Tool is already installed") out.Print(fmt.Sprintf("State Tool Package Manager is already installed at [NOTICE]%s[/RESET]. To reinstall use the [ACTIONABLE]--force[/RESET] flag.", installPath)) - an.Event(AnalyticsFunnelCat, "already-installed") + an.Event(anaConst.CatInstallerFunnel, "already-installed") params.isUpdate = true return postInstallEvents(out, cfg, an, params) } @@ -295,7 +293,7 @@ func execute(out output.Outputer, cfg *config.Instance, an analytics.Dispatcher, // installOrUpdateFromLocalSource is invoked when we're performing an installation where the payload is already provided func installOrUpdateFromLocalSource(out output.Outputer, cfg *config.Instance, an analytics.Dispatcher, payloadPath string, params *Params) error { logging.Debug("Install from local source") - an.Event(AnalyticsFunnelCat, "local-source") + an.Event(anaConst.CatInstallerFunnel, "local-source") if !params.isUpdate { // install.sh or install.ps1 downloaded this installer and is running it. out.Print(output.Title("Installing State Tool Package Manager")) @@ -324,12 +322,12 @@ func installOrUpdateFromLocalSource(out output.Outputer, cfg *config.Instance, a } // Run installer - an.Event(AnalyticsFunnelCat, "pre-installer") + an.Event(anaConst.CatInstallerFunnel, "pre-installer") if err := installer.Install(); err != nil { out.Print("[ERROR]x Failed[/RESET]") return err } - an.Event(AnalyticsFunnelCat, "post-installer") + an.Event(anaConst.CatInstallerFunnel, "post-installer") out.Print("[SUCCESS]✔ Done[/RESET]") if !params.isUpdate { @@ -342,7 +340,7 @@ func installOrUpdateFromLocalSource(out output.Outputer, cfg *config.Instance, a } func postInstallEvents(out output.Outputer, cfg *config.Instance, an analytics.Dispatcher, params *Params) error { - an.Event(AnalyticsFunnelCat, "post-install-events") + an.Event(anaConst.CatInstallerFunnel, "post-install-events") installPath, err := resolveInstallPath(params.path) if err != nil { @@ -368,30 +366,30 @@ func postInstallEvents(out output.Outputer, cfg *config.Instance, an analytics.D switch { // Execute provided --command case params.command != "": - an.Event(AnalyticsFunnelCat, "forward-command") + an.Event(anaConst.CatInstallerFunnel, "forward-command") out.Print(fmt.Sprintf("\nRunning `[ACTIONABLE]%s[/RESET]`\n", params.command)) cmd, args := exeutils.DecodeCmd(params.command) if _, _, err := exeutils.ExecuteAndPipeStd(cmd, args, envSlice(binPath)); err != nil { - an.EventWithLabel(AnalyticsFunnelCat, "forward-command-err", err.Error()) + an.EventWithLabel(anaConst.CatInstallerFunnel, "forward-command-err", err.Error()) return errs.Silence(errs.Wrap(err, "Running provided command failed, error returned: %s", errs.JoinMessage(err))) } // Activate provided --activate Namespace case params.activate.IsValid(): - an.Event(AnalyticsFunnelCat, "forward-activate") + an.Event(anaConst.CatInstallerFunnel, "forward-activate") out.Print(fmt.Sprintf("\nRunning `[ACTIONABLE]state activate %s[/RESET]`\n", params.activate.String())) if _, _, err := exeutils.ExecuteAndPipeStd(stateExe, []string{"activate", params.activate.String()}, envSlice(binPath)); err != nil { - an.EventWithLabel(AnalyticsFunnelCat, "forward-activate-err", err.Error()) + an.EventWithLabel(anaConst.CatInstallerFunnel, "forward-activate-err", err.Error()) return errs.Silence(errs.Wrap(err, "Could not activate %s, error returned: %s", params.activate.String(), errs.JoinMessage(err))) } // Activate provided --activate-default Namespace case params.activateDefault.IsValid(): - an.Event(AnalyticsFunnelCat, "forward-activate-default") + an.Event(anaConst.CatInstallerFunnel, "forward-activate-default") out.Print(fmt.Sprintf("\nRunning `[ACTIONABLE]state activate --default %s[/RESET]`\n", params.activateDefault.String())) if _, _, err := exeutils.ExecuteAndPipeStd(stateExe, []string{"activate", params.activateDefault.String(), "--default"}, envSlice(binPath)); err != nil { - an.EventWithLabel(AnalyticsFunnelCat, "forward-activate-default-err", err.Error()) + an.EventWithLabel(anaConst.CatInstallerFunnel, "forward-activate-default-err", err.Error()) return errs.Silence(errs.Wrap(err, "Could not activate %s, error returned: %s", params.activateDefault.String(), errs.JoinMessage(err))) } case !params.isUpdate && terminal.IsTerminal(int(os.Stdin.Fd())) && os.Getenv(constants.InstallerNoSubshell) != "true" && os.Getenv("TERM") != "dumb": diff --git a/cmd/state-offline-installer/main.go b/cmd/state-offline-installer/main.go index 89dc821608..82b8cd86e7 100644 --- a/cmd/state-offline-installer/main.go +++ b/cmd/state-offline-installer/main.go @@ -8,6 +8,7 @@ import ( "github.com/ActiveState/cli/internal/analytics" "github.com/ActiveState/cli/internal/analytics/client/sync" + anaConst "github.com/ActiveState/cli/internal/analytics/constants" "github.com/ActiveState/cli/internal/captain" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/constants" @@ -78,7 +79,7 @@ func main() { return } - an = sync.New(cfg, nil, out) + an = sync.New(anaConst.SrcOfflineInstaller, cfg, nil, out) prime := primer.New( nil, out, nil, diff --git a/cmd/state-offline-uninstaller/main.go b/cmd/state-offline-uninstaller/main.go index 822e831dfb..0ba17527c5 100644 --- a/cmd/state-offline-uninstaller/main.go +++ b/cmd/state-offline-uninstaller/main.go @@ -8,6 +8,7 @@ import ( "github.com/ActiveState/cli/internal/analytics" "github.com/ActiveState/cli/internal/analytics/client/sync" + anaConst "github.com/ActiveState/cli/internal/analytics/constants" "github.com/ActiveState/cli/internal/captain" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/constants" @@ -78,7 +79,7 @@ func main() { return } - an = sync.New(cfg, nil, out) + an = sync.New(anaConst.SrcOfflineInstaller, cfg, nil, out) prime := primer.New( nil, out, nil, diff --git a/cmd/state-remote-installer/main.go b/cmd/state-remote-installer/main.go index 6320c67d0f..73ce0feaf2 100644 --- a/cmd/state-remote-installer/main.go +++ b/cmd/state-remote-installer/main.go @@ -10,6 +10,7 @@ import ( "github.com/ActiveState/cli/internal/analytics" "github.com/ActiveState/cli/internal/analytics/client/sync" + anaConst "github.com/ActiveState/cli/internal/analytics/constants" "github.com/ActiveState/cli/internal/captain" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/constants" @@ -94,7 +95,7 @@ func main() { return } - an = sync.New(cfg, nil, out) + an = sync.New(anaConst.SrcStateRemoteInstaller, cfg, nil, out) // Set up prompter prompter := prompt.New(true, an) diff --git a/cmd/state-svc/internal/resolver/resolver.go b/cmd/state-svc/internal/resolver/resolver.go index b644a7a008..ac3297a0e8 100644 --- a/cmd/state-svc/internal/resolver/resolver.go +++ b/cmd/state-svc/internal/resolver/resolver.go @@ -73,7 +73,9 @@ func New(cfg *config.Instance, an *sync.Client, auth *authentication.Auth) (*Res usageChecker := rtusage.NewChecker(cfg, auth) - anForClient := sync.New(cfg, auth, nil) + // Note: source does not matter here, as analytics sent via the resolver have a source + // (e.g. State Tool or Executor), and that source will be used. + anForClient := sync.New(anaConsts.SrcStateTool, cfg, auth, nil) return &Resolver{ cfg, msg, @@ -159,10 +161,10 @@ func (r *Resolver) Projects(ctx context.Context) ([]*graph.Project, error) { return projects, nil } -func (r *Resolver) AnalyticsEvent(_ context.Context, category, action string, _label *string, dimensionsJson string) (*graph.AnalyticsEventResponse, error) { +func (r *Resolver) AnalyticsEvent(_ context.Context, category, action, source string, _label *string, dimensionsJson string) (*graph.AnalyticsEventResponse, error) { defer func() { handlePanics(recover(), debug.Stack()) }() - logging.Debug("Analytics event resolver: %s - %s", category, action) + logging.Debug("Analytics event resolver: %s - %s (%s)", category, action, source) label := "" if _label != nil { @@ -188,12 +190,12 @@ func (r *Resolver) AnalyticsEvent(_ context.Context, category, action string, _l return nil }) - r.anForClient.EventWithLabel(category, action, label, dims) + r.anForClient.EventWithSourceAndLabel(category, action, source, label, dims) return &graph.AnalyticsEventResponse{Sent: true}, nil } -func (r *Resolver) ReportRuntimeUsage(_ context.Context, pid int, exec string, dimensionsJSON string) (*graph.ReportRuntimeUsageResponse, error) { +func (r *Resolver) ReportRuntimeUsage(_ context.Context, pid int, exec, source string, dimensionsJSON string) (*graph.ReportRuntimeUsageResponse, error) { defer func() { handlePanics(recover(), debug.Stack()) }() logging.Debug("Runtime usage resolver: %d - %s", pid, exec) @@ -202,7 +204,7 @@ func (r *Resolver) ReportRuntimeUsage(_ context.Context, pid int, exec string, d return &graph.ReportRuntimeUsageResponse{Received: false}, errs.Wrap(err, "Could not unmarshal") } - r.rtwatch.Watch(pid, exec, dims) + r.rtwatch.Watch(pid, exec, source, dims) return &graph.ReportRuntimeUsageResponse{Received: true}, nil } diff --git a/cmd/state-svc/internal/rtwatcher/entry.go b/cmd/state-svc/internal/rtwatcher/entry.go index 78275ad703..7626e57af4 100644 --- a/cmd/state-svc/internal/rtwatcher/entry.go +++ b/cmd/state-svc/internal/rtwatcher/entry.go @@ -11,9 +11,10 @@ import ( ) type entry struct { - PID int `json:"pid"` - Exec string `json:"exec"` - Dims *dimensions.Values `json:"dims"` + PID int `json:"pid"` + Exec string `json:"exec"` + Source string `json:"source"` + Dims *dimensions.Values `json:"dims"` } func (e entry) IsRunning() (bool, error) { diff --git a/cmd/state-svc/internal/rtwatcher/watcher.go b/cmd/state-svc/internal/rtwatcher/watcher.go index 60634c7040..dac20f12b2 100644 --- a/cmd/state-svc/internal/rtwatcher/watcher.go +++ b/cmd/state-svc/internal/rtwatcher/watcher.go @@ -30,7 +30,7 @@ type Watcher struct { } type analytics interface { - Event(category string, action string, dim ...*dimensions.Values) + EventWithSource(category, action, source string, dim ...*dimensions.Values) } func New(cfg *config.Instance, an analytics) *Watcher { @@ -97,7 +97,7 @@ func (w *Watcher) check() { func (w *Watcher) RecordUsage(e entry) { logging.Debug("Recording usage of %s (%d)", e.Exec, e.PID) - w.an.Event(anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat, e.Dims) + w.an.EventWithSource(anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat, e.Source, e.Dims) } func (w *Watcher) Close() error { @@ -116,10 +116,10 @@ func (w *Watcher) Close() error { return nil } -func (w *Watcher) Watch(pid int, exec string, dims *dimensions.Values) { +func (w *Watcher) Watch(pid int, exec, source string, dims *dimensions.Values) { logging.Debug("Watching %s (%d)", exec, pid) dims.Sequence = ptr.To(-1) // sequence is meaningless for heartbeat events - e := entry{pid, exec, dims} + e := entry{pid, exec, source, dims} w.watching = append(w.watching, e) go w.RecordUsage(e) // initial event } diff --git a/cmd/state-svc/internal/server/generated/generated.go b/cmd/state-svc/internal/server/generated/generated.go index c08a7f2084..78720300ff 100644 --- a/cmd/state-svc/internal/server/generated/generated.go +++ b/cmd/state-svc/internal/server/generated/generated.go @@ -78,14 +78,14 @@ type ComplexityRoot struct { } Query struct { - AnalyticsEvent func(childComplexity int, category string, action string, label *string, dimensionsJSON string) int + AnalyticsEvent func(childComplexity int, category string, action string, source string, label *string, dimensionsJSON string) int AvailableUpdate func(childComplexity int) int CheckMessages func(childComplexity int, command string, flags []string) int CheckRuntimeUsage func(childComplexity int, organizationName string) int ConfigChanged func(childComplexity int, key string) int FetchLogTail func(childComplexity int) int Projects func(childComplexity int) int - ReportRuntimeUsage func(childComplexity int, pid int, exec string, dimensionsJSON string) int + ReportRuntimeUsage func(childComplexity int, pid int, exec string, source string, dimensionsJSON string) int Version func(childComplexity int) int } @@ -110,8 +110,8 @@ type QueryResolver interface { Version(ctx context.Context) (*graph.Version, error) AvailableUpdate(ctx context.Context) (*graph.AvailableUpdate, error) Projects(ctx context.Context) ([]*graph.Project, error) - AnalyticsEvent(ctx context.Context, category string, action string, label *string, dimensionsJSON string) (*graph.AnalyticsEventResponse, error) - ReportRuntimeUsage(ctx context.Context, pid int, exec string, dimensionsJSON string) (*graph.ReportRuntimeUsageResponse, error) + AnalyticsEvent(ctx context.Context, category string, action string, source string, label *string, dimensionsJSON string) (*graph.AnalyticsEventResponse, error) + ReportRuntimeUsage(ctx context.Context, pid int, exec string, source string, dimensionsJSON string) (*graph.ReportRuntimeUsageResponse, error) CheckRuntimeUsage(ctx context.Context, organizationName string) (*graph.CheckRuntimeUsageResponse, error) CheckMessages(ctx context.Context, command string, flags []string) ([]*graph.MessageInfo, error) ConfigChanged(ctx context.Context, key string) (*graph.ConfigChangedResponse, error) @@ -262,7 +262,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.AnalyticsEvent(childComplexity, args["category"].(string), args["action"].(string), args["label"].(*string), args["dimensionsJson"].(string)), true + return e.complexity.Query.AnalyticsEvent(childComplexity, args["category"].(string), args["action"].(string), args["source"].(string), args["label"].(*string), args["dimensionsJson"].(string)), true case "Query.availableUpdate": if e.complexity.Query.AvailableUpdate == nil { @@ -331,7 +331,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.ReportRuntimeUsage(childComplexity, args["pid"].(int), args["exec"].(string), args["dimensionsJson"].(string)), true + return e.complexity.Query.ReportRuntimeUsage(childComplexity, args["pid"].(int), args["exec"].(string), args["source"].(string), args["dimensionsJson"].(string)), true case "Query.version": if e.complexity.Query.Version == nil { @@ -512,8 +512,8 @@ type Query { version: Version availableUpdate: AvailableUpdate projects: [Project]! - analyticsEvent(category: String!, action: String!, label: String, dimensionsJson: String!): AnalyticsEventResponse - reportRuntimeUsage(pid: Int!, exec: String!, dimensionsJson: String!): ReportRuntimeUsageResponse + analyticsEvent(category: String!, action: String!, source: String!, label: String, dimensionsJson: String!): AnalyticsEventResponse + reportRuntimeUsage(pid: Int!, exec: String!, source: String!, dimensionsJson: String!): ReportRuntimeUsageResponse checkRuntimeUsage(organizationName: String!): CheckRuntimeUsageResponse checkMessages(command: String!, flags: [String!]!): [MessageInfo!]! configChanged(key: String!): ConfigChangedResponse @@ -567,24 +567,33 @@ func (ec *executionContext) field_Query_analyticsEvent_args(ctx context.Context, } } args["action"] = arg1 - var arg2 *string + var arg2 string + if tmp, ok := rawArgs["source"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("source")) + arg2, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["source"] = arg2 + var arg3 *string if tmp, ok := rawArgs["label"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("label")) - arg2, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + arg3, err = ec.unmarshalOString2ᚖstring(ctx, tmp) if err != nil { return nil, err } } - args["label"] = arg2 - var arg3 string + args["label"] = arg3 + var arg4 string if tmp, ok := rawArgs["dimensionsJson"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("dimensionsJson")) - arg3, err = ec.unmarshalNString2string(ctx, tmp) + arg4, err = ec.unmarshalNString2string(ctx, tmp) if err != nil { return nil, err } } - args["dimensionsJson"] = arg3 + args["dimensionsJson"] = arg4 return args, nil } @@ -664,14 +673,23 @@ func (ec *executionContext) field_Query_reportRuntimeUsage_args(ctx context.Cont } args["exec"] = arg1 var arg2 string + if tmp, ok := rawArgs["source"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("source")) + arg2, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["source"] = arg2 + var arg3 string if tmp, ok := rawArgs["dimensionsJson"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("dimensionsJson")) - arg2, err = ec.unmarshalNString2string(ctx, tmp) + arg3, err = ec.unmarshalNString2string(ctx, tmp) if err != nil { return nil, err } } - args["dimensionsJson"] = arg2 + args["dimensionsJson"] = arg3 return args, nil } @@ -1620,7 +1638,7 @@ func (ec *executionContext) _Query_analyticsEvent(ctx context.Context, field gra }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().AnalyticsEvent(rctx, fc.Args["category"].(string), fc.Args["action"].(string), fc.Args["label"].(*string), fc.Args["dimensionsJson"].(string)) + return ec.resolvers.Query().AnalyticsEvent(rctx, fc.Args["category"].(string), fc.Args["action"].(string), fc.Args["source"].(string), fc.Args["label"].(*string), fc.Args["dimensionsJson"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -1675,7 +1693,7 @@ func (ec *executionContext) _Query_reportRuntimeUsage(ctx context.Context, field }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().ReportRuntimeUsage(rctx, fc.Args["pid"].(int), fc.Args["exec"].(string), fc.Args["dimensionsJson"].(string)) + return ec.resolvers.Query().ReportRuntimeUsage(rctx, fc.Args["pid"].(int), fc.Args["exec"].(string), fc.Args["source"].(string), fc.Args["dimensionsJson"].(string)) }) if err != nil { ec.Error(ctx, err) diff --git a/cmd/state-svc/main.go b/cmd/state-svc/main.go index 22faee2da8..0234ad634b 100644 --- a/cmd/state-svc/main.go +++ b/cmd/state-svc/main.go @@ -13,6 +13,7 @@ import ( "github.com/ActiveState/cli/cmd/state-svc/autostart" anaSync "github.com/ActiveState/cli/internal/analytics/client/sync" + anaConst "github.com/ActiveState/cli/internal/analytics/constants" "github.com/ActiveState/cli/internal/captain" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/constants" @@ -98,7 +99,7 @@ func run(cfg *config.Instance) error { } auth := authentication.New(cfg) - an := anaSync.New(cfg, auth, out) + an := anaSync.New(anaConst.SrcStateService, cfg, auth, out) defer an.Wait() if err := autostart.RegisterConfigListener(cfg); err != nil { diff --git a/cmd/state-svc/schema/schema.graphqls b/cmd/state-svc/schema/schema.graphqls index 0ab058c449..3b22d3c0b1 100644 --- a/cmd/state-svc/schema/schema.graphqls +++ b/cmd/state-svc/schema/schema.graphqls @@ -69,8 +69,8 @@ type Query { version: Version availableUpdate: AvailableUpdate projects: [Project]! - analyticsEvent(category: String!, action: String!, label: String, dimensionsJson: String!): AnalyticsEventResponse - reportRuntimeUsage(pid: Int!, exec: String!, dimensionsJson: String!): ReportRuntimeUsageResponse + analyticsEvent(category: String!, action: String!, source: String!, label: String, dimensionsJson: String!): AnalyticsEventResponse + reportRuntimeUsage(pid: Int!, exec: String!, source: String!, dimensionsJson: String!): ReportRuntimeUsageResponse checkRuntimeUsage(organizationName: String!): CheckRuntimeUsageResponse checkMessages(command: String!, flags: [String!]!): [MessageInfo!]! configChanged(key: String!): ConfigChangedResponse diff --git a/cmd/state/main.go b/cmd/state/main.go index c90ac8ad50..696e5b2df8 100644 --- a/cmd/state/main.go +++ b/cmd/state/main.go @@ -11,11 +11,11 @@ import ( "strings" "time" - "github.com/ActiveState/cli/cmd/state/internal/cmdtree/exechandlers/messenger" - "github.com/ActiveState/cli/internal/captain" - "github.com/ActiveState/cli/cmd/state/internal/cmdtree" + "github.com/ActiveState/cli/cmd/state/internal/cmdtree/exechandlers/messenger" anAsync "github.com/ActiveState/cli/internal/analytics/client/async" + anaConst "github.com/ActiveState/cli/internal/analytics/constants" + "github.com/ActiveState/cli/internal/captain" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/constraints" @@ -201,7 +201,7 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out logging.Warning("Could not sync authenticated state: %s", errs.JoinMessage(err)) } - an := anAsync.New(svcmodel, cfg, auth, out, pjNamespace) + an := anAsync.New(anaConst.SrcStateTool, svcmodel, cfg, auth, out, pjNamespace) defer func() { if err := events.WaitForEvents(time.Second, an.Wait); err != nil { logging.Warning("Failed waiting for events: %v", err) diff --git a/go.sum b/go.sum index dcf45175a8..14d761555c 100644 --- a/go.sum +++ b/go.sum @@ -341,10 +341,6 @@ github.com/99designs/gqlgen v0.17.19 h1:lO3PBSOv5Mw8RPt0Nwg7ovJ9tNfjGDEnU48AYqLz github.com/99designs/gqlgen v0.17.19/go.mod h1:tXjo2/o1L/9A8S/4nLK7Arx/677H8hxlD/iSTRx0BtE= github.com/ActiveState/go-ogle-analytics v0.0.0-20170510030904-9b3f14901527 h1:lW+qgVXf/iAnSs8SgagWxh8d6nsbpmwyhmeg9/fp0Os= github.com/ActiveState/go-ogle-analytics v0.0.0-20170510030904-9b3f14901527/go.mod h1:/9SyzKLlJSuIa7WAsLUUhHqTK9+PtZD8cKF8G4SLpa0= -github.com/ActiveState/graphql v0.0.0-20180524141115-05b17f315749 h1:6Uj/oFUEJ7UcQ721u2Smti9IIy9lPHEho50buO4ZYXE= -github.com/ActiveState/graphql v0.0.0-20180524141115-05b17f315749/go.mod h1:NhUbNQ8UpfnC6nZvZ8oThqYSCE/G8FQp9JUrK9jXJs0= -github.com/ActiveState/graphql v0.0.0-20230718220857-da7d147af6b4 h1:f2n4heMWzHlHUqQZuV2hTPO5bU95WEQrT76qyfS6qtE= -github.com/ActiveState/graphql v0.0.0-20230718220857-da7d147af6b4/go.mod h1:NhUbNQ8UpfnC6nZvZ8oThqYSCE/G8FQp9JUrK9jXJs0= github.com/ActiveState/graphql v0.0.0-20230719154233-6949037a6e48 h1:UCx/ObpVRgC4fp2OlJM2iNdMMu+K87/aPxKrQ1WRLj4= github.com/ActiveState/graphql v0.0.0-20230719154233-6949037a6e48/go.mod h1:NhUbNQ8UpfnC6nZvZ8oThqYSCE/G8FQp9JUrK9jXJs0= github.com/ActiveState/termtest v0.7.2 h1:vNPMpI2AyZnLZzZn/CRpMZzIuVL0XhaTLA4qyghIGF8= diff --git a/internal/analytics/analytics.go b/internal/analytics/analytics.go index 4edc267590..9292b954e9 100644 --- a/internal/analytics/analytics.go +++ b/internal/analytics/analytics.go @@ -8,8 +8,9 @@ import ( // Dispatcher describes a struct that can send analytics event in the background type Dispatcher interface { - Event(category string, action string, dim ...*dimensions.Values) - EventWithLabel(category string, action string, label string, dim ...*dimensions.Values) + Event(category, action string, dim ...*dimensions.Values) + EventWithLabel(category, action, label string, dim ...*dimensions.Values) + EventWithSource(category, action, source string, dim ...*dimensions.Values) Wait() Close() } diff --git a/internal/analytics/client/async/client.go b/internal/analytics/client/async/client.go index 9793a435c1..4aa4fa2452 100644 --- a/internal/analytics/client/async/client.go +++ b/internal/analytics/client/async/client.go @@ -38,13 +38,15 @@ type Client struct { ci bool interactive bool activestateCI bool + source string } var _ analytics.Dispatcher = &Client{} -func New(svcModel *model.SvcModel, cfg *config.Instance, auth *authentication.Auth, out output.Outputer, projectNameSpace string) *Client { +func New(source string, svcModel *model.SvcModel, cfg *config.Instance, auth *authentication.Auth, out output.Outputer, projectNameSpace string) *Client { a := &Client{ eventWaitGroup: &sync.WaitGroup{}, + source: source, } o := string(output.PlainFormatName) @@ -75,13 +77,24 @@ func New(svcModel *model.SvcModel, cfg *config.Instance, auth *authentication.Au } // Event logs an event to google analytics -func (a *Client) Event(category string, action string, dims ...*dimensions.Values) { +func (a *Client) Event(category, action string, dims ...*dimensions.Values) { a.EventWithLabel(category, action, "", dims...) } // EventWithLabel logs an event with a label to google analytics -func (a *Client) EventWithLabel(category string, action string, label string, dims ...*dimensions.Values) { - err := a.sendEvent(category, action, label, dims...) +func (a *Client) EventWithLabel(category, action, label string, dims ...*dimensions.Values) { + a.eventWithSourceAndLabel(category, action, a.source, label, dims...) +} + +// EventWithSource logs an event with another source to google analytics. +// For example, log runtime events triggered by executors as coming from an executor instead of from +// State Tool. +func (a *Client) EventWithSource(category, action, source string, dims ...*dimensions.Values) { + a.eventWithSourceAndLabel(category, action, source, "", dims...) +} + +func (a *Client) eventWithSourceAndLabel(category, action, source, label string, dims ...*dimensions.Values) { + err := a.sendEvent(category, action, source, label, dims...) if err != nil { multilog.Error("Error during analytics.sendEvent: %v", errs.JoinMessage(err)) } @@ -98,7 +111,7 @@ func (a *Client) Wait() { a.eventWaitGroup.Wait() } -func (a *Client) sendEvent(category, action, label string, dims ...*dimensions.Values) error { +func (a *Client) sendEvent(category, action, source, label string, dims ...*dimensions.Values) error { if a.svcModel == nil { // this is only true on CI return nil } @@ -133,7 +146,7 @@ func (a *Client) sendEvent(category, action, label string, dims ...*dimensions.V defer func() { handlePanics(recover(), debug.Stack()) }() defer a.eventWaitGroup.Done() - if err := a.svcModel.AnalyticsEvent(context.Background(), category, action, label, string(dimMarshalled)); err != nil { + if err := a.svcModel.AnalyticsEvent(context.Background(), category, action, source, label, string(dimMarshalled)); err != nil { logging.Debug("Failed to report analytics event via state-svc: %s", errs.JoinMessage(err)) } }() diff --git a/internal/analytics/client/blackhole/client.go b/internal/analytics/client/blackhole/client.go index c36cf79d1b..e533ac7203 100644 --- a/internal/analytics/client/blackhole/client.go +++ b/internal/analytics/client/blackhole/client.go @@ -13,10 +13,13 @@ func New() *Client { return &Client{} } -func (c Client) Event(category string, action string, dim ...*dimensions.Values) { +func (c Client) Event(category, action string, dim ...*dimensions.Values) { } -func (c Client) EventWithLabel(category string, action string, label string, dim ...*dimensions.Values) { +func (c Client) EventWithLabel(category, action, label string, dim ...*dimensions.Values) { +} + +func (c Client) EventWithSource(category, action, source string, dim ...*dimensions.Values) { } func (c Client) Wait() { diff --git a/internal/analytics/client/sync/client.go b/internal/analytics/client/sync/client.go index 36d725fb14..751c377149 100644 --- a/internal/analytics/client/sync/client.go +++ b/internal/analytics/client/sync/client.go @@ -32,7 +32,7 @@ import ( type Reporter interface { ID() string - Event(category, action, label string, dimensions *dimensions.Values) error + Event(category, action, source, label string, dimensions *dimensions.Values) error } // Client instances send analytics events to GA and S3 endpoints without delay. It is only supposed to be used inside the `state-svc`. All other processes should use the DefaultClient. @@ -45,16 +45,18 @@ type Client struct { reporters []Reporter sequence int auth *authentication.Auth + source string } var _ analytics.Dispatcher = &Client{} // New initializes the analytics instance with all custom dimensions known at this time -func New(cfg *config.Instance, auth *authentication.Auth, out output.Outputer) *Client { +func New(source string, cfg *config.Instance, auth *authentication.Auth, out output.Outputer) *Client { a := &Client{ eventWaitGroup: &sync.WaitGroup{}, sendReports: true, auth: auth, + source: source, } installSource, err := storage.InstallSource() @@ -150,13 +152,13 @@ func (a *Client) Wait() { } // Events returns a channel to feed eventData directly to the report loop -func (a *Client) report(category, action, label string, dimensions *dimensions.Values) { +func (a *Client) report(category, action, source, label string, dimensions *dimensions.Values) { if !a.sendReports { return } for _, reporter := range a.reporters { - if err := reporter.Event(category, action, label, dimensions); err != nil { + if err := reporter.Event(category, action, source, label, dimensions); err != nil { logging.Debug( "Reporter failed: %s, category: %s, action: %s, error: %s", reporter.ID(), category, action, errs.JoinMessage(err), @@ -165,7 +167,7 @@ func (a *Client) report(category, action, label string, dimensions *dimensions.V } } -func (a *Client) Event(category string, action string, dims ...*dimensions.Values) { +func (a *Client) Event(category, action string, dims ...*dimensions.Values) { a.EventWithLabel(category, action, "", dims...) } @@ -180,7 +182,20 @@ func mergeDimensions(target *dimensions.Values, dims ...*dimensions.Values) *dim return actualDims } -func (a *Client) EventWithLabel(category string, action, label string, dims ...*dimensions.Values) { +func (a *Client) EventWithLabel(category, action, label string, dims ...*dimensions.Values) { + a.EventWithSourceAndLabel(category, action, a.source, label, dims...) +} + +// EventWithSource should only be used by clients forwarding events on behalf of another source. +// Otherwise, use Event(). +func (a *Client) EventWithSource(category, action, source string, dims ...*dimensions.Values) { + a.EventWithSourceAndLabel(category, action, source, "", dims...) +} + +// EventWithSourceAndLabel should only be used by clients forwarding events on behalf of another +// source (for example, state-svc forwarding events on behalf of State Tool or an executor). +// Otherwise, use EventWithLabel(). +func (a *Client) EventWithSourceAndLabel(category, action, source, label string, dims ...*dimensions.Values) { if a.customDimensions == nil { if condition.InUnitTest() { return @@ -210,7 +225,7 @@ func (a *Client) EventWithLabel(category string, action, label string, dims ...* go func() { defer a.eventWaitGroup.Done() defer func() { handlePanics(recover(), debug.Stack()) }() - a.report(category, action, label, actualDims) + a.report(category, action, source, label, actualDims) }() } diff --git a/internal/analytics/client/sync/reporters/ga-state.go b/internal/analytics/client/sync/reporters/ga-state.go index 623b7470d8..afdec89b64 100644 --- a/internal/analytics/client/sync/reporters/ga-state.go +++ b/internal/analytics/client/sync/reporters/ga-state.go @@ -44,7 +44,7 @@ func (r *GaCLIReporter) AddOmitCategory(category string) { r.omit[category] = struct{}{} } -func (r *GaCLIReporter) Event(category, action, label string, d *dimensions.Values) error { +func (r *GaCLIReporter) Event(category, action, source, label string, d *dimensions.Values) error { if _, ok := r.omit[category]; ok { logging.Debug("Not sending event with category: %s to Google Analytics", category) return nil diff --git a/internal/analytics/client/sync/reporters/pixel.go b/internal/analytics/client/sync/reporters/pixel.go index 8a9fde09d7..d42588862b 100644 --- a/internal/analytics/client/sync/reporters/pixel.go +++ b/internal/analytics/client/sync/reporters/pixel.go @@ -29,7 +29,7 @@ func (r *PixelReporter) ID() string { return "PixelReporter" } -func (r *PixelReporter) Event(category, action, label string, d *dimensions.Values) error { +func (r *PixelReporter) Event(category, action, source, label string, d *dimensions.Values) error { pixelURL, err := url.Parse(r.url) if err != nil { return errs.Wrap(err, "Invalid pixel URL: %s", r.url) @@ -38,6 +38,7 @@ func (r *PixelReporter) Event(category, action, label string, d *dimensions.Valu query := &url.Values{} query.Add("x-category", category) query.Add("x-action", action) + query.Add("x-source", source) query.Add("x-label", label) for num, value := range legacyDimensionMap(d) { diff --git a/internal/analytics/client/sync/reporters/test.go b/internal/analytics/client/sync/reporters/test.go index b8c60dfe08..95ff0c426a 100644 --- a/internal/analytics/client/sync/reporters/test.go +++ b/internal/analytics/client/sync/reporters/test.go @@ -36,12 +36,13 @@ func (r *TestReporter) ID() string { type TestLogEntry struct { Category string Action string + Source string Label string Dimensions *dimensions.Values } -func (r *TestReporter) Event(category, action, label string, d *dimensions.Values) error { - b, err := json.Marshal(TestLogEntry{category, action, label, d}) +func (r *TestReporter) Event(category, action, source, label string, d *dimensions.Values) error { + b, err := json.Marshal(TestLogEntry{category, action, source, label, d}) if err != nil { return errs.Wrap(err, "Could not marshal test log entry") } diff --git a/internal/analytics/constants/constants.go b/internal/analytics/constants/constants.go index b2bf3dfa4f..268000d438 100644 --- a/internal/analytics/constants/constants.go +++ b/internal/analytics/constants/constants.go @@ -25,6 +25,30 @@ const CatConfig = "config" // CatUpdate is the event category used for all update events const CatUpdates = "updates" +// CatInstaller is the event category used for installer events. +const CatInstaller = "installer" + +// CatInstallerFunnel is the event category used for installer funnel events. +const CatInstallerFunnel = "installer-funnel" + +// SrcStateTool is the event source for events sent by state. +const SrcStateTool = "State Tool" + +// SrcStateService is the event source for events sent by state-svc. +const SrcStateService = "State Service" + +// SrcStateInstaller is the event source for events sent by state-installer. +const SrcStateInstaller = "State Installer" + +// SrcStateRemoteInstaller is the event source for events sent by state-remote-installer. +const SrcStateRemoteInstaller = "State Remote Installer" + +// SrcOfflineInstaller is the event source for events sent by offline installers. +const SrcOfflineInstaller = "Offline Installer" + +// SrcExecutor is the event source for events sent by executors. +const SrcExecutor = "Executor" + // ActRuntimeHeartbeat is the event action sent when a runtime is in use const ActRuntimeHeartbeat = "heartbeat" diff --git a/internal/graph/generated.go b/internal/graph/generated.go index b7102e0eff..62610a04aa 100644 --- a/internal/graph/generated.go +++ b/internal/graph/generated.go @@ -38,15 +38,6 @@ type MessageInfo struct { Placement MessagePlacementType `json:"placement"` } -/* -[ - { - "ID": "simple", - "Message": "This is a [NOTICE]simple[/RESET] message\nwith a line break", - } -] -*/ - type Project struct { Namespace string `json:"namespace"` Locations []string `json:"locations"` diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index ad5044acc8..6da18f055c 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -15,7 +15,7 @@ import ( ) type EventDispatcher interface { - EventWithLabel(category string, action string, label string, dim ...*dimensions.Values) + EventWithLabel(category, action string, label string, dim ...*dimensions.Values) } // Prompter is the interface used to run our prompt from, useful for mocking in tests diff --git a/internal/runners/tutorial/tutorial.go b/internal/runners/tutorial/tutorial.go index 2bc961ed35..c1f9fb00dc 100644 --- a/internal/runners/tutorial/tutorial.go +++ b/internal/runners/tutorial/tutorial.go @@ -12,8 +12,8 @@ import ( "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/language" "github.com/ActiveState/cli/internal/locale" - "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/osutils/user" + "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/prompt" "github.com/ActiveState/cli/internal/runbits" diff --git a/internal/svcctl/comm.go b/internal/svcctl/comm.go index eda636e675..7ada81cdcf 100644 --- a/internal/svcctl/comm.go +++ b/internal/svcctl/comm.go @@ -73,12 +73,12 @@ func (c *Comm) GetLogFileName(ctx context.Context) (string, error) { } type Resolver interface { - ReportRuntimeUsage(ctx context.Context, pid int, exec, dimensionsJSON string) (*graph.ReportRuntimeUsageResponse, error) + ReportRuntimeUsage(ctx context.Context, pid int, exec, source string, dimensionsJSON string) (*graph.ReportRuntimeUsageResponse, error) CheckRuntimeUsage(ctx context.Context, organizationName string) (*graph.CheckRuntimeUsageResponse, error) } type AnalyticsReporter interface { - Event(category string, action string, dims ...*dimensions.Values) + EventWithSource(category, action, source string, dims ...*dimensions.Values) } func HeartbeatHandler(cfg *config.Instance, resolver Resolver, analyticsReporter AnalyticsReporter) ipc.RequestHandler { @@ -138,8 +138,8 @@ func HeartbeatHandler(cfg *config.Instance, resolver Resolver, analyticsReporter } logging.Debug("Firing runtime usage events for %s", metaData.Namespace) - analyticsReporter.Event(constants.CatRuntimeUsage, constants.ActRuntimeAttempt, dims) - _, err = resolver.ReportRuntimeUsage(context.Background(), pidNum, hb.ExecPath, dimsJSON) + analyticsReporter.EventWithSource(constants.CatRuntimeUsage, constants.ActRuntimeAttempt, constants.SrcExecutor, dims) + _, err = resolver.ReportRuntimeUsage(context.Background(), pidNum, hb.ExecPath, constants.SrcExecutor, dimsJSON) if err != nil { multilog.Critical("Heartbeat Failure: Failed to report runtime usage in heartbeat handler: %s", errs.JoinMessage(err)) return diff --git a/pkg/platform/api/svc/request/analytics.go b/pkg/platform/api/svc/request/analytics.go index 386319d12a..713ebd2b87 100644 --- a/pkg/platform/api/svc/request/analytics.go +++ b/pkg/platform/api/svc/request/analytics.go @@ -1,26 +1,28 @@ package request type AnalyticsEvent struct { - category string - action string - label string - dimensionsJson string - outputType string - userID string + category string + action string + source string + label string + dimensionsJson string + outputType string + userID string } -func NewAnalyticsEvent(category, action, label, dimensionsJson string) *AnalyticsEvent { +func NewAnalyticsEvent(category, action, source, label, dimensionsJson string) *AnalyticsEvent { return &AnalyticsEvent{ - category: category, - action: action, - label: label, - dimensionsJson: dimensionsJson, + category: category, + action: action, + source: source, + label: label, + dimensionsJson: dimensionsJson, } } func (e *AnalyticsEvent) Query() string { - return `query($category: String!, $action: String!, $label: String, $dimensionsJson: String!) { - analyticsEvent(category: $category, action: $action, label: $label, dimensionsJson: $dimensionsJson) { + return `query($category: String!, $action: String!, $source: String!, $label: String, $dimensionsJson: String!) { + analyticsEvent(category: $category, action: $action, source: $source, label: $label, dimensionsJson: $dimensionsJson) { sent } }` @@ -28,9 +30,10 @@ func (e *AnalyticsEvent) Query() string { func (e *AnalyticsEvent) Vars() map[string]interface{} { return map[string]interface{}{ - "category": e.category, - "action": e.action, - "label": e.label, + "category": e.category, + "action": e.action, + "source": e.source, + "label": e.label, "dimensionsJson": e.dimensionsJson, } } diff --git a/pkg/platform/api/svc/request/reportrtusage.go b/pkg/platform/api/svc/request/reportrtusage.go index 48ff8b4da8..027c04b6ca 100644 --- a/pkg/platform/api/svc/request/reportrtusage.go +++ b/pkg/platform/api/svc/request/reportrtusage.go @@ -3,20 +3,22 @@ package request type ReportRuntimeUsage struct { pid int exec string + source string dimensionsJson string } -func NewReportRuntimeUsage(pid int, exec string, dimensionsJson string) *ReportRuntimeUsage { +func NewReportRuntimeUsage(pid int, exec, source string, dimensionsJson string) *ReportRuntimeUsage { return &ReportRuntimeUsage{ pid: pid, exec: exec, + source: source, dimensionsJson: dimensionsJson, } } func (e *ReportRuntimeUsage) Query() string { - return `query($pid: Int!, $exec: String!, $dimensionsJson: String!) { - reportRuntimeUsage(pid: $pid, exec: $exec, dimensionsJson: $dimensionsJson) { + return `query($pid: Int!, $exec: String!, $source: String!, $dimensionsJson: String!) { + reportRuntimeUsage(pid: $pid, exec: $exec, source: $source, dimensionsJson: $dimensionsJson) { received } }` @@ -26,6 +28,7 @@ func (e *ReportRuntimeUsage) Vars() map[string]interface{} { return map[string]interface{}{ "pid": e.pid, "exec": e.exec, + "source": e.source, "dimensionsJson": e.dimensionsJson, } } diff --git a/pkg/platform/model/svc.go b/pkg/platform/model/svc.go index b1de885514..ce074e398c 100644 --- a/pkg/platform/model/svc.go +++ b/pkg/platform/model/svc.go @@ -97,10 +97,10 @@ func (m *SvcModel) Ping() error { return err } -func (m *SvcModel) AnalyticsEvent(ctx context.Context, category, action, label string, dimJson string) error { +func (m *SvcModel) AnalyticsEvent(ctx context.Context, category, action, source, label string, dimJson string) error { defer profile.Measure("svc:analyticsEvent", time.Now()) - r := request.NewAnalyticsEvent(category, action, label, dimJson) + r := request.NewAnalyticsEvent(category, action, source, label, dimJson) u := graph.AnalyticsEventResponse{} if err := m.request(ctx, r, &u); err != nil { return errs.Wrap(err, "Error sending analytics event via state-svc") @@ -109,10 +109,10 @@ func (m *SvcModel) AnalyticsEvent(ctx context.Context, category, action, label s return nil } -func (m *SvcModel) ReportRuntimeUsage(ctx context.Context, pid int, exec string, dimJson string) error { +func (m *SvcModel) ReportRuntimeUsage(ctx context.Context, pid int, exec, source string, dimJson string) error { defer profile.Measure("svc:ReportRuntimeUsage", time.Now()) - r := request.NewReportRuntimeUsage(pid, exec, dimJson) + r := request.NewReportRuntimeUsage(pid, exec, source, dimJson) u := graph.ReportRuntimeUsageResponse{} if err := m.request(ctx, r, &u); err != nil { return errs.Wrap(err, "Error sending report runtime usage event via state-svc") diff --git a/pkg/platform/runtime/runtime.go b/pkg/platform/runtime/runtime.go index b5eb63d17d..4ccb330ed8 100644 --- a/pkg/platform/runtime/runtime.go +++ b/pkg/platform/runtime/runtime.go @@ -247,7 +247,7 @@ func (r *Runtime) recordUsage() { multilog.Critical("Could not marshal dimensions for runtime-usage: %s", errs.JoinMessage(err)) } if r.svcm != nil { - r.svcm.ReportRuntimeUsage(context.Background(), os.Getpid(), osutils.Executable(), dimsJson) + r.svcm.ReportRuntimeUsage(context.Background(), os.Getpid(), osutils.Executable(), anaConsts.SrcStateTool, dimsJson) } } diff --git a/pkg/platform/runtime/setup/setup.go b/pkg/platform/runtime/setup/setup.go index 51049739a2..b5307d8217 100644 --- a/pkg/platform/runtime/setup/setup.go +++ b/pkg/platform/runtime/setup/setup.go @@ -12,9 +12,6 @@ import ( "sync" "time" - bpModel "github.com/ActiveState/cli/pkg/platform/api/buildplanner/model" - "github.com/ActiveState/cli/pkg/platform/model" - "github.com/ActiveState/cli/internal/analytics" anaConsts "github.com/ActiveState/cli/internal/analytics/constants" "github.com/ActiveState/cli/internal/analytics/dimensions" @@ -31,8 +28,10 @@ import ( "github.com/ActiveState/cli/internal/rtutils/ptr" "github.com/ActiveState/cli/internal/svcctl" "github.com/ActiveState/cli/internal/unarchiver" + bpModel "github.com/ActiveState/cli/pkg/platform/api/buildplanner/model" "github.com/ActiveState/cli/pkg/platform/api/inventory/inventory_models" "github.com/ActiveState/cli/pkg/platform/authentication" + "github.com/ActiveState/cli/pkg/platform/model" apimodel "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/platform/runtime/artifact" "github.com/ActiveState/cli/pkg/platform/runtime/artifactcache" @@ -541,7 +540,7 @@ func (s *Setup) fetchAndInstallArtifactsFromBuildPlan(installFunc artifactInstal // only send the download analytics event, if we have to install artifacts that are not yet installed if len(artifactsToInstall) > 0 { - // if we get here, we dowload artifacts + // if we get here, we download artifacts s.analytics.Event(anaConsts.CatRuntime, anaConsts.ActRuntimeDownload, dimensions) } diff --git a/test/integration/analytics_int_test.go b/test/integration/analytics_int_test.go index 8bd885f5c8..0ae8d6196a 100644 --- a/test/integration/analytics_int_test.go +++ b/test/integration/analytics_int_test.go @@ -82,16 +82,16 @@ func (suite *AnalyticsIntegrationTestSuite) TestActivateEvents() { suite.Require().NotEmpty(events) // Runtime:start events - suite.assertNEvents(events, 1, anaConst.CatRuntime, anaConst.ActRuntimeStart, + suite.assertNEvents(events, 1, anaConst.CatRuntime, anaConst.ActRuntimeStart, anaConst.SrcStateTool, fmt.Sprintf("output:\n%s\n%s", cp.Snapshot(), ts.DebugLogs())) // Runtime:success events - suite.assertNEvents(events, 1, anaConst.CatRuntime, anaConst.ActRuntimeSuccess, + suite.assertNEvents(events, 1, anaConst.CatRuntime, anaConst.ActRuntimeSuccess, anaConst.SrcStateTool, fmt.Sprintf("output:\n%s\n%s", cp.Snapshot(), ts.DebugLogs())) - heartbeatInitialCount := countEvents(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat) + heartbeatInitialCount := countEvents(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat, anaConst.SrcStateTool) if heartbeatInitialCount < 2 { // 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. @@ -104,7 +104,7 @@ func (suite *AnalyticsIntegrationTestSuite) TestActivateEvents() { suite.Require().NotEmpty(events) // Runtime-use:heartbeat events - should now be at least +1 because we waited - suite.assertGtEvents(events, heartbeatInitialCount, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat, + suite.assertGtEvents(events, heartbeatInitialCount, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat, anaConst.SrcStateTool, fmt.Sprintf("output:\n%s\n%s", cp.Snapshot(), ts.DebugLogs())) @@ -121,11 +121,11 @@ func (suite *AnalyticsIntegrationTestSuite) TestActivateEvents() { } return (*e.Dimensions.Trigger) == target.TriggerExecutor.String() }) - suite.Require().Equal(1, countEvents(executorEvents, anaConst.CatRuntimeUsage, anaConst.ActRuntimeAttempt), + suite.Require().Equal(1, countEvents(executorEvents, anaConst.CatRuntimeUsage, anaConst.ActRuntimeAttempt, anaConst.SrcExecutor), ts.DebugMessage("Should have a runtime attempt, events:\n"+debugEvents(suite.T(), 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. - numHeartbeats := countEvents(executorEvents, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat) + numHeartbeats := countEvents(executorEvents, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat, anaConst.SrcExecutor) suite.Require().Greater(numHeartbeats, 0, "Should have a heartbeat") suite.Require().LessOrEqual(numHeartbeats, 2, "Should not have excessive heartbeats") var heartbeatEvent *reporters.TestLogEntry @@ -153,13 +153,13 @@ func (suite *AnalyticsIntegrationTestSuite) TestActivateEvents() { events = parseAnalyticsEvents(suite, ts) suite.Require().NotEmpty(events) - eventsAfterExit := countEvents(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat) + eventsAfterExit := countEvents(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat, anaConst.SrcExecutor) time.Sleep(sleepTime) eventsAfter := parseAnalyticsEvents(suite, ts) suite.Require().NotEmpty(eventsAfter) - eventsAfterWait := countEvents(eventsAfter, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat) + eventsAfterWait := countEvents(eventsAfter, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat, anaConst.SrcExecutor) suite.Equal(eventsAfterExit, eventsAfterWait, fmt.Sprintf("Heartbeats should stop ticking after exiting subshell.\n"+ @@ -177,9 +177,9 @@ func (suite *AnalyticsIntegrationTestSuite) TestActivateEvents() { suite.assertSequentialEvents(events) } -func countEvents(events []reporters.TestLogEntry, category, action string) int { +func countEvents(events []reporters.TestLogEntry, category, action, source string) int { filteredEvents := funk.Filter(events, func(e reporters.TestLogEntry) bool { - return e.Category == category && e.Action == action + return e.Category == category && e.Action == action && e.Source == source }).([]reporters.TestLogEntry) return len(filteredEvents) } @@ -203,15 +203,15 @@ func filterEvents(events []reporters.TestLogEntry, filters ...func(e reporters.T } func (suite *AnalyticsIntegrationTestSuite) assertNEvents(events []reporters.TestLogEntry, - expectedN int, category, action string, errMsg string) { - suite.Assert().Equal(expectedN, countEvents(events, category, action), + expectedN int, category, action, source string, errMsg string) { + suite.Assert().Equal(expectedN, countEvents(events, category, action, source), "Expected %d %s:%s events.\nFile location: %s\nEvents received:\n%s\nError:\n%s", expectedN, category, action, suite.eventsfile, suite.summarizeEvents(events), errMsg) } func (suite *AnalyticsIntegrationTestSuite) assertGtEvents(events []reporters.TestLogEntry, - greaterThanN int, category, action string, errMsg string) { - suite.Assert().Greater(countEvents(events, category, action), greaterThanN, + greaterThanN int, category, action, source string, errMsg string) { + suite.Assert().Greater(countEvents(events, category, action, source), greaterThanN, fmt.Sprintf("Expected more than %d %s:%s events.\nFile location: %s\nEvents received:\n%s\nError:\n%s", greaterThanN, category, action, suite.eventsfile, suite.summarizeEvents(events), errMsg)) } @@ -254,7 +254,7 @@ func (suite *AnalyticsIntegrationTestSuite) assertSequentialEvents(events []repo func (suite *AnalyticsIntegrationTestSuite) summarizeEvents(events []reporters.TestLogEntry) string { summary := []string{} for _, event := range events { - summary = append(summary, fmt.Sprintf("%s:%s:%s", event.Category, event.Action, event.Label)) + summary = append(summary, fmt.Sprintf("%s:%s:%s (%s)", event.Category, event.Action, event.Label, event.Source)) } return strings.Join(summary, "\n") } @@ -262,8 +262,8 @@ func (suite *AnalyticsIntegrationTestSuite) summarizeEvents(events []reporters.T func (suite *AnalyticsIntegrationTestSuite) summarizeEventSequence(events []reporters.TestLogEntry) string { summary := []string{} for _, event := range events { - summary = append(summary, fmt.Sprintf("%s:%s:%s (seq: %s:%s:%d)\n", - event.Category, event.Action, event.Label, + summary = append(summary, fmt.Sprintf("%s:%s:%s (%s seq: %s:%s:%d)\n", + event.Category, event.Action, event.Label, event.Source, *event.Dimensions.Command, (*event.Dimensions.InstanceID)[0:6], *event.Dimensions.Sequence)) } return strings.Join(summary, "\n") @@ -423,7 +423,7 @@ func (suite *AnalyticsIntegrationTestSuite) TestInputError() { events := parseAnalyticsEvents(suite, ts) suite.assertSequentialEvents(events) - suite.assertNEvents(events, 1, anaConst.CatDebug, anaConst.ActCommandInputError, + suite.assertNEvents(events, 1, anaConst.CatDebug, anaConst.ActCommandInputError, anaConst.SrcStateTool, fmt.Sprintf("output:\n%s\n%s", cp.Snapshot(), ts.DebugLogs())) @@ -466,7 +466,7 @@ func (suite *AnalyticsIntegrationTestSuite) TestAttempts() { for _, e := range events { if strings.Contains(e.Category, "runtime") && strings.Contains(e.Action, "attempt") { foundAttempts++ - if strings.Contains(*e.Dimensions.Trigger, "exec") { + if strings.Contains(*e.Dimensions.Trigger, "exec") && strings.Contains(e.Source, anaConst.SrcExecutor) { foundExecs++ } } @@ -564,8 +564,8 @@ func (suite *AnalyticsIntegrationTestSuite) TestConfigEvents() { suite.Fail("Should find multiple config events") } - suite.assertNEvents(events, 1, anaConst.CatConfig, anaConst.ActConfigSet, "Should be at one config set event") - suite.assertNEvents(events, 1, anaConst.CatConfig, anaConst.ActConfigUnset, "Should be at one config unset event") + suite.assertNEvents(events, 1, anaConst.CatConfig, anaConst.ActConfigSet, anaConst.SrcStateTool, "Should be at one config set event") + suite.assertNEvents(events, 1, anaConst.CatConfig, anaConst.ActConfigUnset, anaConst.SrcStateTool, "Should be at one config unset event") suite.assertSequentialEvents(events) } diff --git a/test/integration/offinstall_int_test.go b/test/integration/offinstall_int_test.go index ec06707b04..b79c7da7f5 100644 --- a/test/integration/offinstall_int_test.go +++ b/test/integration/offinstall_int_test.go @@ -97,7 +97,7 @@ func (suite *OffInstallIntegrationTestSuite) TestInstallAndUninstall() { heartbeat := suite.filterEvent(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat) suite.assertDimensions(heartbeat) - nDelete := countEvents(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeDelete) + nDelete := countEvents(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeDelete, anaConst.SrcOfflineInstaller) if nDelete != 0 { suite.FailNow(fmt.Sprintf("Expected 0 delete events, got %d, events:\n%#v", nDelete, events)) } @@ -147,7 +147,7 @@ func (suite *OffInstallIntegrationTestSuite) TestInstallAndUninstall() { // Verify that our analytics event was fired events := parseAnalyticsEvents(suite, ts) suite.Require().NotEmpty(events) - nHeartbeat := countEvents(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat) + nHeartbeat := countEvents(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat, anaConst.SrcExecutor) if nHeartbeat != 1 { suite.FailNow(fmt.Sprintf("Expected 1 heartbeat event, got %d, events:\n%#v", nHeartbeat, events)) } diff --git a/test/integration/performance_svc_int_test.go b/test/integration/performance_svc_int_test.go index 71b3d18df4..1ca2a467ff 100644 --- a/test/integration/performance_svc_int_test.go +++ b/test/integration/performance_svc_int_test.go @@ -75,7 +75,7 @@ func (suite *PerformanceIntegrationTestSuite) TestSvcPerformance() { suite.Run("Query Analytics", func() { t := time.Now() - err := svcmodel.AnalyticsEvent(context.Background(), "performance-test", "performance-test", "performance-test", "{}") + err := svcmodel.AnalyticsEvent(context.Background(), "performance-test", "performance-test", "performance-test", "performance-test", "{}") suite.Require().NoError(err, ts.DebugMessage(fmt.Sprintf("Error: %s\nLog Tail:\n%s", errs.JoinMessage(err), logging.ReadTail()))) duration := time.Since(t)