diff --git a/cmd/config/config.go b/cmd/config/config.go index eba6442f..a95a74bc 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -20,72 +20,8 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) -var ( - flagCoreCount int - flagLLCSize float64 - flagAllCoreMaxFrequency float64 - flagUncoreMaxFrequency float64 - flagUncoreMinFrequency float64 - flagUncoreMaxComputeFrequency float64 - flagUncoreMinComputeFrequency float64 - flagUncoreMaxIOFrequency float64 - flagUncoreMinIOFrequency float64 - flagTDP int - flagEPB int - flagEPP int - flagGovernor string - flagELC string - flagPrefetcherL2HW string - flagPrefetcherL2Adj string - flagPrefetcherDCUHW string - flagPrefetcherDCUIP string - flagPrefetcherDCUNP string - flagPrefetcherAMP string - flagPrefetcherLLCPP string - flagPrefetcherAOP string - flagPrefetcherHomeless string - flagPrefetcherLLC string -) - -const ( - flagCoreCountName = "cores" - flagLLCSizeName = "llc" - flagAllCoreMaxFrequencyName = "core-max" - flagUncoreMaxFrequencyName = "uncore-max" - flagUncoreMinFrequencyName = "uncore-min" - flagUncoreMaxComputeFrequencyName = "uncore-max-compute" - flagUncoreMinComputeFrequencyName = "uncore-min-compute" - flagUncoreMaxIOFrequencyName = "uncore-max-io" - flagUncoreMinIOFrequencyName = "uncore-min-io" - flagTDPName = "tdp" - flagEPBName = "epb" - flagEPPName = "epp" - flagGovernorName = "gov" - flagELCName = "elc" - flagPrefetcherL2HWName = "pref-l2hw" - flagPrefetcherL2AdjName = "pref-l2adj" - flagPrefetcherDCUHWName = "pref-dcuhw" - flagPrefetcherDCUIPName = "pref-dcuip" - flagPrefetcherDCUNPName = "pref-dcunp" - flagPrefetcherAMPName = "pref-amp" - flagPrefetcherLLCPPName = "pref-llcpp" - flagPrefetcherAOPName = "pref-aop" - flagPrefetcherHomelessName = "pref-homeless" - flagPrefetcherLLCName = "pref-llc" -) - -// governorOptions - list of valid governor options -var governorOptions = []string{"performance", "powersave"} - -// elcOptions - list of valid elc options -var elcOptions = []string{"latency-optimized", "default"} - -// prefetcherOptions - list of valid prefetcher options -var prefetcherOptions = []string{"enable", "disable"} - const cmdName = "config" var examples = []string{ @@ -111,285 +47,7 @@ USE CAUTION! Target may become unstable. It is up to the user to ensure that the } func init() { - Cmd.Flags().IntVar(&flagCoreCount, flagCoreCountName, 0, "") - Cmd.Flags().Float64Var(&flagLLCSize, flagLLCSizeName, 0, "") - Cmd.Flags().Float64Var(&flagAllCoreMaxFrequency, flagAllCoreMaxFrequencyName, 0, "") - Cmd.Flags().Float64Var(&flagUncoreMaxFrequency, flagUncoreMaxFrequencyName, 0, "") - Cmd.Flags().Float64Var(&flagUncoreMinFrequency, flagUncoreMinFrequencyName, 0, "") - Cmd.Flags().Float64Var(&flagUncoreMaxComputeFrequency, flagUncoreMaxComputeFrequencyName, 0, "") - Cmd.Flags().Float64Var(&flagUncoreMinComputeFrequency, flagUncoreMinComputeFrequencyName, 0, "") - Cmd.Flags().Float64Var(&flagUncoreMaxIOFrequency, flagUncoreMaxIOFrequencyName, 0, "") - Cmd.Flags().Float64Var(&flagUncoreMinIOFrequency, flagUncoreMinIOFrequencyName, 0, "") - Cmd.Flags().IntVar(&flagTDP, flagTDPName, 0, "") - Cmd.Flags().IntVar(&flagEPB, flagEPBName, 0, "") - Cmd.Flags().IntVar(&flagEPP, flagEPPName, 0, "") - Cmd.Flags().StringVar(&flagGovernor, flagGovernorName, "", "") - Cmd.Flags().StringVar(&flagELC, flagELCName, "", "") - Cmd.Flags().StringVar(&flagPrefetcherL2HW, flagPrefetcherL2HWName, "", "") - Cmd.Flags().StringVar(&flagPrefetcherL2Adj, flagPrefetcherL2AdjName, "", "") - Cmd.Flags().StringVar(&flagPrefetcherDCUHW, flagPrefetcherDCUHWName, "", "") - Cmd.Flags().StringVar(&flagPrefetcherDCUIP, flagPrefetcherDCUIPName, "", "") - Cmd.Flags().StringVar(&flagPrefetcherDCUNP, flagPrefetcherDCUNPName, "", "") - Cmd.Flags().StringVar(&flagPrefetcherAMP, flagPrefetcherAMPName, "", "") - Cmd.Flags().StringVar(&flagPrefetcherLLCPP, flagPrefetcherLLCPPName, "", "") - Cmd.Flags().StringVar(&flagPrefetcherAOP, flagPrefetcherAOPName, "", "") - Cmd.Flags().StringVar(&flagPrefetcherHomeless, flagPrefetcherHomelessName, "", "") - Cmd.Flags().StringVar(&flagPrefetcherLLC, flagPrefetcherLLCName, "", "") - - common.AddTargetFlags(Cmd) - - Cmd.SetUsageFunc(usageFunc) -} - -func usageFunc(cmd *cobra.Command) error { - cmd.Printf("Usage: %s [flags]\n\n", cmd.CommandPath()) - cmd.Printf("Examples:\n%s\n\n", cmd.Example) - cmd.Println("Flags:") - for _, group := range getFlagGroups() { - cmd.Printf(" %s:\n", group.GroupName) - for _, flag := range group.Flags { - cmd.Printf(" --%-20s %s\n", flag.Name, flag.Help) - } - } - cmd.Println("\nGlobal Flags:") - cmd.Parent().PersistentFlags().VisitAll(func(pf *pflag.Flag) { - flagDefault := "" - if cmd.Parent().PersistentFlags().Lookup(pf.Name).DefValue != "" { - flagDefault = fmt.Sprintf(" (default: %s)", cmd.Flags().Lookup(pf.Name).DefValue) - } - cmd.Printf(" --%-20s %s%s\n", pf.Name, pf.Usage, flagDefault) - }) - return nil -} - -func getFlagGroups() []common.FlagGroup { - generalFlags := []common.Flag{ - { - Name: flagCoreCountName, - Help: "number of physical cores per processor", - }, - { - Name: flagLLCSizeName, - Help: "LLC size in MB", - }, - { - Name: flagAllCoreMaxFrequencyName, - Help: "all-core max frequency in GHz", - }, - { - Name: flagTDPName, - Help: "maximum power per processor in Watts", - }, - { - Name: flagEPBName, - Help: "energy perf bias from best performance (0) to most power savings (15)", - }, - { - Name: flagEPPName, - Help: "energy perf profile from best performance (0) to most power savings (255)", - }, - { - Name: flagGovernorName, - Help: "CPU scaling governor (" + strings.Join(governorOptions, ", ") + ")", - }, - { - Name: flagELCName, - Help: "Efficiency Latency Control (" + strings.Join(elcOptions, ", ") + ") [SRF+]", - }, - } - groups := []common.FlagGroup{} - groups = append(groups, common.FlagGroup{ - GroupName: "General Options", - Flags: generalFlags, - }) - uncoreFrequencyFlags := []common.Flag{ - { - Name: flagUncoreMaxFrequencyName, - Help: "maximum uncore frequency in GHz [EMR-]", - }, - { - Name: flagUncoreMinFrequencyName, - Help: "minimum uncore frequency in GHz [EMR-]", - }, - { - Name: flagUncoreMaxComputeFrequencyName, - Help: "maximum uncore compute die frequency in GHz [SRF+]", - }, - { - Name: flagUncoreMinComputeFrequencyName, - Help: "minimum uncore compute die frequency in GHz [SRF+]", - }, - { - Name: flagUncoreMaxIOFrequencyName, - Help: "maximum uncore IO die frequency in GHz [SRF+]", - }, - { - Name: flagUncoreMinIOFrequencyName, - Help: "minimum uncore IO die frequency in GHz [SRF+]", - }, - } - groups = append(groups, common.FlagGroup{ - GroupName: "Uncore Frequency Options", - Flags: uncoreFrequencyFlags, - }) - prefetcherFlags := []common.Flag{ - { - Name: flagPrefetcherL2HWName, - Help: "L2 hardware prefetcher (" + strings.Join(prefetcherOptions, ", ") + ")", - }, - { - Name: flagPrefetcherL2AdjName, - Help: "L2 adjacent cache line prefetcher (" + strings.Join(prefetcherOptions, ", ") + ")", - }, - { - Name: flagPrefetcherDCUHWName, - Help: "DCU hardware prefetcher (" + strings.Join(prefetcherOptions, ", ") + ")", - }, - { - Name: flagPrefetcherDCUIPName, - Help: "DCU instruction pointer prefetcher (" + strings.Join(prefetcherOptions, ", ") + ")", - }, - { - Name: flagPrefetcherDCUNPName, - Help: "DCU next page prefetcher (" + strings.Join(prefetcherOptions, ", ") + ")", - }, - { - Name: flagPrefetcherAMPName, - Help: "Adaptive multipath probability prefetcher (" + strings.Join(prefetcherOptions, ", ") + ") [SPR,EMR,GNR]", - }, - { - Name: flagPrefetcherLLCPPName, - Help: "LLC page prefetcher (" + strings.Join(prefetcherOptions, ", ") + ") [GNR]", - }, - { - Name: flagPrefetcherAOPName, - Help: "Array of pointers prefetcher (" + strings.Join(prefetcherOptions, ", ") + ") [GNR]", - }, - { - Name: flagPrefetcherHomelessName, - Help: "Homeless prefetcher (" + strings.Join(prefetcherOptions, ", ") + ") [SPR,EMR,GNR]", - }, - { - Name: flagPrefetcherLLCName, - Help: "Last level cache prefetcher (" + strings.Join(prefetcherOptions, ", ") + ") [SPR,EMR,GNR]", - }, - } - groups = append(groups, common.FlagGroup{ - GroupName: "Prefetcher Options", - Flags: prefetcherFlags, - }) - - groups = append(groups, common.GetTargetFlagGroup()) - return groups -} - -func validateFlags(cmd *cobra.Command, args []string) error { - if cmd.Flags().Lookup(flagCoreCountName).Changed && flagCoreCount < 1 { - err := fmt.Errorf("invalid core count: %d", flagCoreCount) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagLLCSizeName).Changed && flagLLCSize < 1 { - err := fmt.Errorf("invalid LLC size: %.2f MB", flagLLCSize) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagAllCoreMaxFrequencyName).Changed && flagAllCoreMaxFrequency < 0.1 { - err := fmt.Errorf("invalid core frequency: %.1f GHz", flagAllCoreMaxFrequency) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagUncoreMaxFrequencyName).Changed && flagUncoreMaxFrequency < 0.1 { - err := fmt.Errorf("invalid uncore max frequency: %.1f GHz", flagUncoreMaxFrequency) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagUncoreMinFrequencyName).Changed && flagUncoreMinFrequency < 0.1 { - err := fmt.Errorf("invalid uncore min frequency: %.1f GHz", flagUncoreMinFrequency) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagTDPName).Changed && flagTDP < 1 { - err := fmt.Errorf("invalid power: %d", flagTDP) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagEPBName).Changed && (flagEPB < 0 || flagEPB > 15) { - err := fmt.Errorf("invalid epb: %d, valid values are 0-15", flagEPB) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagEPPName).Changed && (flagEPP < 0 || flagEPP > 255) { - err := fmt.Errorf("invalid epp: %d, valid values are 0-255", flagEPP) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagGovernorName).Changed && !slices.Contains(governorOptions, flagGovernor) { - err := fmt.Errorf("invalid governor: %s, valid options are: %s", flagGovernor, strings.Join(governorOptions, ", ")) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagELCName).Changed && !slices.Contains(elcOptions, flagELC) { - err := fmt.Errorf("invalid elc mode: %s, valid options are: %s", flagELC, strings.Join(elcOptions, ", ")) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagPrefetcherL2HWName).Changed && !slices.Contains(prefetcherOptions, flagPrefetcherL2HW) { - err := fmt.Errorf("invalid prefetcher value: %s, valid options are: %s", flagPrefetcherL2HW, strings.Join(prefetcherOptions, ", ")) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagPrefetcherL2AdjName).Changed && !slices.Contains(prefetcherOptions, flagPrefetcherL2Adj) { - err := fmt.Errorf("invalid L2 Adj prefetcher: %s, valid options are: %s", flagPrefetcherL2Adj, strings.Join(prefetcherOptions, ", ")) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagPrefetcherDCUHWName).Changed && !slices.Contains(prefetcherOptions, flagPrefetcherDCUHW) { - err := fmt.Errorf("invalid DCU HW prefetcher: %s, valid options are: %s", flagPrefetcherDCUHW, strings.Join(prefetcherOptions, ", ")) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagPrefetcherDCUIPName).Changed && !slices.Contains(prefetcherOptions, flagPrefetcherDCUIP) { - err := fmt.Errorf("invalid DCU IP prefetcher: %s, valid options are: %s", flagPrefetcherDCUIP, strings.Join(prefetcherOptions, ", ")) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagPrefetcherDCUNPName).Changed && !slices.Contains(prefetcherOptions, flagPrefetcherDCUNP) { - err := fmt.Errorf("invalid DCU NP prefetcher: %s, valid options are: %s", flagPrefetcherDCUNP, strings.Join(prefetcherOptions, ", ")) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagPrefetcherAMPName).Changed && !slices.Contains(prefetcherOptions, flagPrefetcherAMP) { - err := fmt.Errorf("invalid AMP prefetcher: %s, valid options are: %s", flagPrefetcherAMP, strings.Join(prefetcherOptions, ", ")) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagPrefetcherLLCPPName).Changed && !slices.Contains(prefetcherOptions, flagPrefetcherLLCPP) { - err := fmt.Errorf("invalid LLCPP prefetcher: %s, valid options are: %s", flagPrefetcherLLCPP, strings.Join(prefetcherOptions, ", ")) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagPrefetcherAOPName).Changed && !slices.Contains(prefetcherOptions, flagPrefetcherAOP) { - err := fmt.Errorf("invalid AOP prefetcher: %s, valid options are: %s", flagPrefetcherAOP, strings.Join(prefetcherOptions, ", ")) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagPrefetcherHomelessName).Changed && !slices.Contains(prefetcherOptions, flagPrefetcherHomeless) { - err := fmt.Errorf("invalid homeless prefetcher: %s, valid options are: %s", flagPrefetcherHomeless, strings.Join(prefetcherOptions, ", ")) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - if cmd.Flags().Lookup(flagPrefetcherLLCName).Changed && !slices.Contains(prefetcherOptions, flagPrefetcherLLC) { - err := fmt.Errorf("invalid LLC prefetcher: %s, valid options are: %s", flagPrefetcherLLC, strings.Join(prefetcherOptions, ", ")) - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - // common target flags - if err := common.ValidateTargetFlags(cmd); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - return err - } - return nil + initializeFlags(Cmd) } func runCmd(cmd *cobra.Command, args []string) error { @@ -442,14 +100,37 @@ func runCmd(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true return err } - // were any changes requested? + // make requested changes, one target at a time changeRequested := false - flagGroups := getFlagGroups() - for i := range len(flagGroups) - 1 { - for _, flag := range flagGroups[i].Flags { - if cmd.Flags().Lookup(flag.Name).Changed { - changeRequested = true - break + for _, myTarget := range myTargets { + for _, group := range flagGroups { + for _, flag := range group.flags { + if cmd.Flags().Lookup(flag.GetName()).Changed { + changeRequested = true + fmt.Printf("%s setting %s to %s\n", myTarget.GetName(), flag.GetName(), flag.GetValueAsString()) + var err error + switch flag.GetType() { + case "int": + if flag.intSetFunc != nil { + value, _ := cmd.Flags().GetInt(flag.GetName()) + err = flag.intSetFunc(value, myTarget, localTempDir) + } + case "float64": + if flag.floatSetFunc != nil { + value, _ := cmd.Flags().GetFloat64(flag.GetName()) + err = flag.floatSetFunc(value, myTarget, localTempDir) + } + case "string": + if flag.stringSetFunc != nil { + value, _ := cmd.Flags().GetString(flag.GetName()) + err = flag.stringSetFunc(value, myTarget, localTempDir) + } + } + if err != nil { + fmt.Fprintf(os.Stderr, "%s Error: %v\n", myTarget.GetName(), err) + slog.Error(err.Error(), slog.String("target", myTarget.GetName())) + } + } } } } @@ -457,86 +138,6 @@ func runCmd(cmd *cobra.Command, args []string) error { fmt.Println("No changes requested.") return nil } - // make requested changes, one target at a time - for _, myTarget := range myTargets { - if cmd.Flags().Lookup(flagCoreCountName).Changed { - out, err := setCoreCount(flagCoreCount, myTarget, localTempDir) - if err != nil { - fmt.Printf("Error: %v, %s\n", err, out) - cmd.SilenceUsage = true - return err - } - } - if cmd.Flags().Lookup(flagLLCSizeName).Changed { - setLlcSize(flagLLCSize, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagAllCoreMaxFrequencyName).Changed { - setCoreFrequency(flagAllCoreMaxFrequency, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagUncoreMaxFrequencyName).Changed { - setUncoreFrequency(true, flagUncoreMaxFrequency, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagUncoreMinFrequencyName).Changed { - setUncoreFrequency(false, flagUncoreMinFrequency, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagUncoreMaxComputeFrequencyName).Changed { - setUncoreDieFrequency(true, true, flagUncoreMaxComputeFrequency, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagUncoreMinComputeFrequencyName).Changed { - setUncoreDieFrequency(false, true, flagUncoreMinComputeFrequency, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagUncoreMaxIOFrequencyName).Changed { - setUncoreDieFrequency(true, false, flagUncoreMaxIOFrequency, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagUncoreMinIOFrequencyName).Changed { - setUncoreDieFrequency(false, false, flagUncoreMinIOFrequency, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagTDPName).Changed { - setPower(flagTDP, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagEPBName).Changed { - setEpb(flagEPB, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagEPPName).Changed { - setEpp(flagEPP, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagGovernorName).Changed { - setGovernor(flagGovernor, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagELCName).Changed { - setElc(flagELC, myTarget, localTempDir) - } - if cmd.Flags().Lookup(flagPrefetcherL2HWName).Changed { - setPrefetcher(flagPrefetcherL2HW, myTarget, localTempDir, "L2 HW") // these prefetcher names must match the shortName in prefetcher_defs.go - } - if cmd.Flags().Lookup(flagPrefetcherL2AdjName).Changed { - setPrefetcher(flagPrefetcherL2Adj, myTarget, localTempDir, "L2 Adj") - } - if cmd.Flags().Lookup(flagPrefetcherDCUHWName).Changed { - setPrefetcher(flagPrefetcherDCUHW, myTarget, localTempDir, "DCU HW") - } - if cmd.Flags().Lookup(flagPrefetcherDCUIPName).Changed { - setPrefetcher(flagPrefetcherDCUIP, myTarget, localTempDir, "DCU IP") - } - if cmd.Flags().Lookup(flagPrefetcherDCUNPName).Changed { - setPrefetcher(flagPrefetcherDCUNP, myTarget, localTempDir, "DCU NP") - } - if cmd.Flags().Lookup(flagPrefetcherAMPName).Changed { - setPrefetcher(flagPrefetcherAMP, myTarget, localTempDir, "AMP") - } - if cmd.Flags().Lookup(flagPrefetcherLLCPPName).Changed { - setPrefetcher(flagPrefetcherLLCPP, myTarget, localTempDir, "LLCPP") - } - if cmd.Flags().Lookup(flagPrefetcherAOPName).Changed { - setPrefetcher(flagPrefetcherAOP, myTarget, localTempDir, "AOP") - } - if cmd.Flags().Lookup(flagPrefetcherHomelessName).Changed { - setPrefetcher(flagPrefetcherHomeless, myTarget, localTempDir, "Homeless") - } - if cmd.Flags().Lookup(flagPrefetcherLLCName).Changed { - setPrefetcher(flagPrefetcherLLC, myTarget, localTempDir, "LLC") - } - } // print config after making changes fmt.Println("") // blank line if err := printConfig(myTargets, localTempDir); err != nil { @@ -591,8 +192,7 @@ func printConfig(myTargets []target.Target, localTempDir string) (err error) { return } -func setCoreCount(cores int, myTarget target.Target, localTempDir string) (string, error) { - fmt.Printf("set core count per processor to %d on %s\n", cores, myTarget.GetName()) +func setCoreCount(cores int, myTarget target.Target, localTempDir string) error { setScript := script.ScriptDefinition{ Name: "set core count", ScriptTemplate: fmt.Sprintf(` @@ -681,11 +281,14 @@ done `, cores), Superuser: true, } - return runScript(myTarget, setScript, localTempDir) + _, err := runScript(myTarget, setScript, localTempDir) + if err != nil { + return fmt.Errorf("failed to set core count: %w", err) + } + return nil } -func setLlcSize(llcSize float64, myTarget target.Target, localTempDir string) { - fmt.Printf("set LLC size to %.2f MB on %s\n", llcSize, myTarget.GetName()) +func setLlcSize(llcSize float64, myTarget target.Target, localTempDir string) error { scripts := []script.ScriptDefinition{} scripts = append(scripts, script.GetScriptByName(script.LscpuScriptName)) scripts = append(scripts, script.GetScriptByName(script.LspciBitsScriptName)) @@ -694,42 +297,31 @@ func setLlcSize(llcSize float64, myTarget target.Target, localTempDir string) { outputs, err := script.RunScripts(myTarget, scripts, true, localTempDir) if err != nil { - fmt.Fprintln(os.Stderr, err) - slog.Error("failed to run scripts on target", slog.String("target", myTarget.GetName()), slog.String("error", err.Error())) - return + return fmt.Errorf("failed to run scripts on target: %w", err) } maximumLlcSize, _, err := report.GetL3LscpuMB(outputs) if err != nil { - fmt.Fprintln(os.Stderr, err) - slog.Error("failed to get maximum LLC size", slog.String("error", err.Error())) - return + return fmt.Errorf("failed to get maximum LLC size: %w", err) } // microarchitecture uarch := report.UarchFromOutput(outputs) cacheWays := report.GetCacheWays(uarch) if len(cacheWays) == 0 { - fmt.Fprintln(os.Stderr, "failed to get cache ways") - slog.Error("failed to get cache ways") - return + return fmt.Errorf("failed to get cache ways") } // current LLC size currentLlcSize, err := report.GetL3MSRMB(outputs) if err != nil { - fmt.Fprintln(os.Stderr, err) - slog.Error("failed to get LLC size", slog.String("error", err.Error())) - return + return fmt.Errorf("failed to get current LLC size: %w", err) } if currentLlcSize == llcSize { - fmt.Printf("LLC size is already set to %.2f MB\n", llcSize) - return + return fmt.Errorf("LLC size is already set to %.2f MB", llcSize) } // calculate the number of ways to set cachePerWay := maximumLlcSize / float64(len(cacheWays)) waysToSet := int(math.Ceil((llcSize / cachePerWay)) - 1) if waysToSet >= len(cacheWays) { - fmt.Fprintf(os.Stderr, "LLC size is too large, maximum is %.2f MB\n", maximumLlcSize) - slog.Error("LLC size is too large", slog.Float64("llc size", llcSize), slog.Float64("current llc size", currentLlcSize)) - return + return fmt.Errorf("LLC size is too large, maximum is %.2f MB", maximumLlcSize) } // set the LLC size setScript := script.ScriptDefinition{ @@ -743,35 +335,26 @@ func setLlcSize(llcSize float64, myTarget target.Target, localTempDir string) { } _, err = runScript(myTarget, setScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to set LLC size: %v\n", err) + return fmt.Errorf("failed to set LLC size: %w", err) } + return nil } -func setCoreFrequency(coreFrequency float64, myTarget target.Target, localTempDir string) { - fmt.Printf("set core frequency to %.1f GHz on %s\n", coreFrequency, myTarget.GetName()) +func setCoreFrequency(coreFrequency float64, myTarget target.Target, localTempDir string) error { targetFamily, err := myTarget.GetFamily() if err != nil { - fmt.Fprintf(os.Stderr, "error getting target family: %v\n", err) - slog.Error("failed to get target family", slog.String("error", err.Error())) - return + return fmt.Errorf("failed to get target family: %w", err) } targetModel, err := myTarget.GetModel() if err != nil { - fmt.Fprintf(os.Stderr, "error getting target model: %v\n", err) - slog.Error("failed to get target model", slog.String("error", err.Error())) - return + return fmt.Errorf("failed to get target model: %w", err) } targetVendor, err := myTarget.GetVendor() if err != nil { - fmt.Fprintf(os.Stderr, "error getting target vendor: %v\n", err) - slog.Error("failed to get target vendor", slog.String("error", err.Error())) - return + return fmt.Errorf("failed to get target vendor: %w", err) } if targetVendor != "GenuineIntel" { - err := fmt.Errorf("core frequency setting not supported on %s due to vendor mismatch", myTarget.GetName()) - slog.Error(err.Error()) - fmt.Fprintf(os.Stderr, "Error: failed to set core frequency: %v\n", err) - return + return fmt.Errorf("core frequency setting not supported on %s due to vendor mismatch", myTarget.GetName()) } var setScript script.ScriptDefinition freqInt := uint64(coreFrequency * 10) @@ -784,9 +367,7 @@ func setCoreFrequency(coreFrequency float64, myTarget target.Target, localTempDi } output, err := runScript(myTarget, getScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to get pstate driver: %v\n", err) - slog.Error("failed to get pstate driver", slog.String("error", err.Error())) - return + return fmt.Errorf("failed to get pstate driver: %w", err) } if strings.Contains(output, "intel_pstate") { var value uint64 @@ -825,40 +406,22 @@ func setCoreFrequency(coreFrequency float64, myTarget target.Target, localTempDi } _, err = runScript(myTarget, setScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to set core frequency: %v\n", err) + return fmt.Errorf("failed to set core frequency: %w", err) } + return nil } -func setUncoreDieFrequency(maxFreq bool, computeDie bool, uncoreFrequency float64, myTarget target.Target, localTempDir string) { - var minmax, dietype string - if maxFreq { - minmax = "max" - } else { - minmax = "min" - } - if computeDie { - dietype = "compute" - } else { - dietype = "I/O" - } - fmt.Printf("set uncore %s %s die frequency to %.1f GHz on %s\n", minmax, dietype, uncoreFrequency, myTarget.GetName()) +func setUncoreDieFrequency(maxFreq bool, computeDie bool, uncoreFrequency float64, myTarget target.Target, localTempDir string) error { targetFamily, err := myTarget.GetFamily() if err != nil { - fmt.Fprintf(os.Stderr, "error getting target family: %v\n", err) - slog.Error("failed to get target family", slog.String("error", err.Error())) - return + return fmt.Errorf("failed to get target family: %w", err) } targetModel, err := myTarget.GetModel() if err != nil { - fmt.Fprintf(os.Stderr, "error getting target model: %v\n", err) - slog.Error("failed to get target model", slog.String("error", err.Error())) - return + return fmt.Errorf("failed to get target model: %w", err) } if targetFamily != "6" || (targetFamily == "6" && targetModel != "173" && targetModel != "175" && targetModel != "221") { - err := fmt.Errorf("uncore frequency setting not supported on %s due to family/model mismatch", myTarget.GetName()) - slog.Error(err.Error()) - fmt.Fprintf(os.Stderr, "Error: failed to set uncore frequency: %v\n", err) - return + return fmt.Errorf("uncore frequency setting not supported on %s due to family/model mismatch", myTarget.GetName()) } type dieId struct { instance string @@ -870,9 +433,7 @@ func setUncoreDieFrequency(maxFreq bool, computeDie bool, uncoreFrequency float6 scripts = append(scripts, script.GetScriptByName(script.UncoreDieTypesFromTPMIScriptName)) outputs, err := script.RunScripts(myTarget, scripts, true, localTempDir) if err != nil { - fmt.Fprintln(os.Stderr, err) - slog.Error("failed to run scripts on target", slog.String("target", myTarget.GetName()), slog.String("error", err.Error())) - return + return fmt.Errorf("failed to get uncore die types: %w", err) } re := regexp.MustCompile(`Read bits \d+:\d+ value (\d+) from TPMI ID .* for entry (\d+) in instance (\d+)`) for line := range strings.SplitSeq(outputs[script.UncoreDieTypesFromTPMIScriptName].Stdout, "\n") { @@ -906,20 +467,13 @@ func setUncoreDieFrequency(maxFreq bool, computeDie bool, uncoreFrequency float6 } _, err = runScript(myTarget, setScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to set uncore frequency: %v\n", err) - return + return fmt.Errorf("failed to set uncore frequency: %w", err) } } + return nil } -func setUncoreFrequency(maxFreq bool, uncoreFrequency float64, myTarget target.Target, localTempDir string) { - var minmax string - if maxFreq { - minmax = "max" - } else { - minmax = "min" - } - fmt.Printf("set uncore %s frequency to %.1f GHz on %s\n", minmax, uncoreFrequency, myTarget.GetName()) +func setUncoreFrequency(maxFreq bool, uncoreFrequency float64, myTarget target.Target, localTempDir string) error { scripts := []script.ScriptDefinition{} scripts = append(scripts, script.ScriptDefinition{ Name: "get uncore frequency MSR", @@ -931,34 +485,23 @@ func setUncoreFrequency(maxFreq bool, uncoreFrequency float64, myTarget target.T }) outputs, err := script.RunScripts(myTarget, scripts, true, localTempDir) if err != nil { - fmt.Fprintln(os.Stderr, err) - slog.Error("failed to run scripts on target", slog.String("target", myTarget.GetName()), slog.String("error", err.Error())) - return + return fmt.Errorf("failed to read uncore frequency MSR: %w", err) } targetFamily, err := myTarget.GetFamily() if err != nil { - fmt.Fprintf(os.Stderr, "error getting target family: %v\n", err) - slog.Error("failed to get target family", slog.String("error", err.Error())) - return + return fmt.Errorf("failed to get target family: %w", err) } targetModel, err := myTarget.GetModel() if err != nil { - fmt.Fprintf(os.Stderr, "error getting target model: %v\n", err) - slog.Error("failed to get target model", slog.String("error", err.Error())) - return + return fmt.Errorf("failed to get target model: %w", err) } if targetFamily != "6" || (targetFamily == "6" && (targetModel == "173" || targetModel == "175" || targetModel == "221")) { // not Intel || not GNR, SRF, CWF - err := fmt.Errorf("uncore frequency setting not supported on %s due to family/model mismatch", myTarget.GetName()) - slog.Error(err.Error()) - fmt.Fprintf(os.Stderr, "Error: failed to set uncore frequency: %v\n", err) - return + return fmt.Errorf("uncore frequency setting not supported on %s due to family/model mismatch", myTarget.GetName()) } msrHex := strings.TrimSpace(outputs["get uncore frequency MSR"].Stdout) msrInt, err := strconv.ParseInt(msrHex, 16, 0) if err != nil { - fmt.Fprintln(os.Stderr, err) - slog.Error("failed to read or parse msr value", slog.String("msr", msrHex), slog.String("error", err.Error())) - return + return fmt.Errorf("failed to read uncore frequency MSR: %w", err) } newFreq := uint64((uncoreFrequency * 1000) / 100) var newVal uint64 @@ -983,12 +526,12 @@ func setUncoreFrequency(maxFreq bool, uncoreFrequency float64, myTarget target.T } _, err = runScript(myTarget, setScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to set uncore frequency: %v\n", err) + return fmt.Errorf("failed to set uncore frequency: %w", err) } + return nil } -func setPower(power int, myTarget target.Target, localTempDir string) { - fmt.Printf("set power to %d Watts on %s\n", power, myTarget.GetName()) +func setTDP(power int, myTarget target.Target, localTempDir string) error { readScript := script.ScriptDefinition{ Name: "get power MSR", ScriptTemplate: "rdmsr 0x610", @@ -999,14 +542,12 @@ func setPower(power int, myTarget target.Target, localTempDir string) { } readOutput, err := script.RunScript(myTarget, readScript, localTempDir) if err != nil { - fmt.Fprintln(os.Stderr, err) - slog.Error("failed to run script on target", slog.String("target", myTarget.GetName()), slog.String("error", err.Error())) + return fmt.Errorf("failed to read power MSR: %w", err) } else { msrHex := strings.TrimSpace(readOutput.Stdout) msrInt, err := strconv.ParseInt(msrHex, 16, 0) if err != nil { - fmt.Fprintln(os.Stderr, err) - slog.Error("failed to parse msr value", slog.String("msr", msrHex), slog.String("error", err.Error())) + return fmt.Errorf("failed to parse power MSR: %w", err) } else { // mask out lower 14 bits newVal := uint64(msrInt) & 0xFFFFFFFFFFFFC000 @@ -1022,26 +563,24 @@ func setPower(power int, myTarget target.Target, localTempDir string) { } _, err := runScript(myTarget, setScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to set power: %v\n", err) + return fmt.Errorf("failed to set power: %w", err) } } } + return nil } -func setEpb(epb int, myTarget target.Target, localTempDir string) { +func setEPB(epb int, myTarget target.Target, localTempDir string) error { epbSourceScript := script.GetScriptByName(script.EpbSourceScriptName) epbSourceOutput, err := runScript(myTarget, epbSourceScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to get EPB source: %v\n", err) - return + return fmt.Errorf("failed to get EPB source: %w", err) } epbSource := strings.TrimSpace(epbSourceOutput) source, err := strconv.ParseInt(epbSource, 16, 0) if err != nil { - fmt.Fprintln(os.Stderr, err) - return + return fmt.Errorf("failed to parse EPB source: %w", err) } - fmt.Printf("set energy performance bias (EPB) to %d on %s\n", epb, myTarget.GetName()) var msr string var bitOffset uint if source == 0 { // 0 means the EPB is controlled by the OS @@ -1061,13 +600,11 @@ func setEpb(epb int, myTarget target.Target, localTempDir string) { } readOutput, err := runScript(myTarget, readScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to read MSR %s: %v\n", msr, err) - return + return fmt.Errorf("failed to read EPB MSR %s: %w", msr, err) } msrValue, err := strconv.ParseUint(strings.TrimSpace(readOutput), 16, 64) if err != nil { - fmt.Fprintln(os.Stderr, err) - return + return fmt.Errorf("failed to parse EPB MSR %s: %w", msr, err) } // mask out 4 bits starting at bitOffset maskedValue := msrValue &^ (0xF << bitOffset) @@ -1084,12 +621,12 @@ func setEpb(epb int, myTarget target.Target, localTempDir string) { } _, err = runScript(myTarget, setScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to set EPB: %v\n", err) + return fmt.Errorf("failed to set EPB: %w", err) } + return nil } -func setEpp(epp int, myTarget target.Target, localTempDir string) { - fmt.Printf("set energy performance profile (EPP) to %d on %s\n", epp, myTarget.GetName()) +func setEPP(epp int, myTarget target.Target, localTempDir string) error { // Set both the per-core EPP value and the package EPP value // Reference: 15.4.4 Managing HWP in the Intel SDM @@ -1104,13 +641,11 @@ func setEpp(epp int, myTarget target.Target, localTempDir string) { } stdout, err := runScript(myTarget, getScript, localTempDir) if err != nil { - return + return fmt.Errorf("failed to read EPP MSR %s: %w", "0x774", err) } msrValue, err := strconv.ParseUint(strings.TrimSpace(stdout), 16, 64) if err != nil { - fmt.Fprintln(os.Stderr, err) - slog.Error("failed to parse msr value", slog.String("msr", stdout), slog.String("error", err.Error())) - return + return fmt.Errorf("failed to parse EPP MSR %s: %w", "0x774", err) } // mask out bits 24-31 IA32_HWP_REQUEST MSR value maskedValue := msrValue & 0xFFFFFFFF00FFFFFF @@ -1127,8 +662,7 @@ func setEpp(epp int, myTarget target.Target, localTempDir string) { } _, err = runScript(myTarget, setScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to set EPP: %v\n", err) - return + return fmt.Errorf("failed to set EPP: %w", err) } // get the current value of the IA32_HWP_REQUEST_PKG MSR that includes the current package EPP value @@ -1142,14 +676,11 @@ func setEpp(epp int, myTarget target.Target, localTempDir string) { } stdout, err = runScript(myTarget, getScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to get pkg EPP: %v\n", err) - return + return fmt.Errorf("failed to read EPP pkg MSR %s: %w", "0x772", err) } msrValue, err = strconv.ParseUint(strings.TrimSpace(stdout), 16, 64) if err != nil { - fmt.Fprintln(os.Stderr, err) - slog.Error("failed to parse msr value", slog.String("msr", stdout), slog.String("error", err.Error())) - return + return fmt.Errorf("failed to parse EPP pkg MSR %s: %w", "0x772", err) } // mask out bits 24-31 IA32_HWP_REQUEST_PKG MSR value maskedValue = msrValue & 0xFFFFFFFF00FFFFFF @@ -1166,12 +697,12 @@ func setEpp(epp int, myTarget target.Target, localTempDir string) { } _, err = runScript(myTarget, setScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to set pkg EPP: %v\n", err) + return fmt.Errorf("failed to set EPP pkg: %w", err) } + return nil } -func setGovernor(governor string, myTarget target.Target, localTempDir string) { - fmt.Printf("set governor to %s on %s\n", governor, myTarget.GetName()) +func setGovernor(governor string, myTarget target.Target, localTempDir string) error { setScript := script.ScriptDefinition{ Name: "set governor", ScriptTemplate: fmt.Sprintf("echo %s | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor", governor), @@ -1179,21 +710,19 @@ func setGovernor(governor string, myTarget target.Target, localTempDir string) { } _, err := runScript(myTarget, setScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to set governor: %v\n", err) + return fmt.Errorf("failed to set governor: %w", err) } + return nil } -func setElc(elc string, myTarget target.Target, localTempDir string) { - fmt.Printf("set efficiency latency control (ELC) mode to %s on %s\n", elc, myTarget.GetName()) +func setELC(elc string, myTarget target.Target, localTempDir string) error { var mode string if elc == elcOptions[0] { mode = "latency-optimized-mode" } else if elc == elcOptions[1] { mode = "default" } else { - fmt.Fprintf(os.Stderr, "invalid elc mode: %s\n", elc) - slog.Error("invalid elc mode", slog.String("elc", elc)) - return + return fmt.Errorf("invalid ELC mode: %s", elc) } setScript := script.ScriptDefinition{ Name: "set elc", @@ -1205,17 +734,15 @@ func setElc(elc string, myTarget target.Target, localTempDir string) { } _, err := runScript(myTarget, setScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to set ELC mode: %v\n", err) + return fmt.Errorf("failed to set ELC mode: %w", err) } + return nil } -func setPrefetcher(enableDisable string, myTarget target.Target, localTempDir string, prefetcherType string) { - fmt.Printf("set %s prefetcher to %sd on %s\n", prefetcherType, enableDisable, myTarget.GetName()) +func setPrefetcher(enableDisable string, myTarget target.Target, localTempDir string, prefetcherType string) error { pf, err := report.GetPrefetcherDefByName(prefetcherType) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to get prefetcher definition: %v\n", err) - slog.Error("failed to get prefetcher definition", slog.String("prefetcher", prefetcherType), slog.String("error", err.Error())) - return + return fmt.Errorf("failed to get prefetcher definition: %w", err) } // check if the prefetcher is supported on this target's architecture // get the uarch @@ -1225,21 +752,15 @@ func setPrefetcher(enableDisable string, myTarget target.Target, localTempDir st scripts = append(scripts, script.GetScriptByName(script.LspciDevicesScriptName)) outputs, err := script.RunScripts(myTarget, scripts, true, localTempDir) if err != nil { - fmt.Fprintln(os.Stderr, err) - slog.Error("failed to run scripts on target", slog.String("target", myTarget.GetName()), slog.String("error", err.Error())) - return + return fmt.Errorf("failed to run target identification scripts on target: %w", err) } uarch := report.UarchFromOutput(outputs) if uarch == "" { - fmt.Fprintln(os.Stderr, "failed to get microarchitecture") - slog.Error("failed to get microarchitecture") - return + return fmt.Errorf("failed to get microarchitecture") } // is the prefetcher supported on this uarch? if !slices.Contains(pf.Uarchs, "all") && !slices.Contains(pf.Uarchs, uarch[:3]) { - fmt.Fprintf(os.Stderr, "prefetcher %s is not supported on %s\n", prefetcherType, uarch) - slog.Error("prefetcher not supported on target", slog.String("prefetcher", prefetcherType), slog.String("uarch", uarch)) - return + return fmt.Errorf("prefetcher %s is not supported on %s", prefetcherType, uarch) } // get the current value of the prefetcher MSR getScript := script.ScriptDefinition{ @@ -1252,14 +773,11 @@ func setPrefetcher(enableDisable string, myTarget target.Target, localTempDir st } stdout, err := runScript(myTarget, getScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to get prefetcher MSR: %v\n", err) - return + return fmt.Errorf("failed to read prefetcher MSR: %w", err) } msrValue, err := strconv.ParseUint(strings.TrimSpace(stdout), 16, 64) if err != nil { - fmt.Fprintln(os.Stderr, err) - slog.Error("failed to parse msr value", slog.String("msr", stdout), slog.String("error", err.Error())) - return + return fmt.Errorf("failed to parse prefetcher MSR: %w", err) } // set the prefetcher bit to bitValue determined by the onOff value, note: 0 is enable, 1 is disable var bitVal int @@ -1268,9 +786,7 @@ func setPrefetcher(enableDisable string, myTarget target.Target, localTempDir st } else if enableDisable == prefetcherOptions[1] { bitVal = 1 } else { - fmt.Fprintf(os.Stderr, "invalid prefetcher setting: %s\n", enableDisable) - slog.Error("invalid prefetcher setting", slog.String("prefetcher", enableDisable)) - return + return fmt.Errorf("invalid prefetcher setting: %s", enableDisable) } // mask out the prefetcher bit maskedValue := msrValue &^ (1 << pf.Bit) @@ -1287,8 +803,9 @@ func setPrefetcher(enableDisable string, myTarget target.Target, localTempDir st } _, err = runScript(myTarget, setScript, localTempDir) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to set %s prefetcher: %v\n", prefetcherType, err) + return fmt.Errorf("failed to set %s prefetcher: %w", prefetcherType, err) } + return nil } func runScript(myTarget target.Target, myScript script.ScriptDefinition, localTempDir string) (string, error) { diff --git a/cmd/config/flag.go b/cmd/config/flag.go new file mode 100644 index 00000000..8c4ca109 --- /dev/null +++ b/cmd/config/flag.go @@ -0,0 +1,72 @@ +package config + +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "perfspect/internal/target" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// flagDefinition is a struct that defines a command line flag. +type flagDefinition struct { + pflag *pflag.Flag + intSetFunc func(int, target.Target, string) error + floatSetFunc func(float64, target.Target, string) error + stringSetFunc func(string, target.Target, string) error + validationFunc func(cmd *cobra.Command) bool + validationDescription string +} + +// GetName returns the name of the flag. +func (f *flagDefinition) GetName() string { + return f.pflag.Name +} + +// GetType returns the type of the flag. +func (f *flagDefinition) GetType() string { + return f.pflag.Value.Type() +} + +// GetValueAsString returns the value of the flag as a string. +func (f *flagDefinition) GetValueAsString() string { + return f.pflag.Value.String() +} + +// newIntFlag creates a new integer flag and adds it to the command. +func newIntFlag(cmd *cobra.Command, name string, defaultValue int, setFunc func(int, target.Target, string) error, help string, validationDescription string, validationFunc func(cmd *cobra.Command) bool) flagDefinition { + cmd.Flags().Int(name, defaultValue, help) + pFlag := cmd.Flags().Lookup(name) + return flagDefinition{ + pflag: pFlag, + intSetFunc: setFunc, + validationFunc: validationFunc, + validationDescription: validationDescription, + } +} + +// newInt64Flag creates a new int64 flag and adds it to the command. +func newFloat64Flag(cmd *cobra.Command, name string, defaultValue float64, setFunc func(float64, target.Target, string) error, help string, validationDescription string, validationFunc func(cmd *cobra.Command) bool) flagDefinition { + cmd.Flags().Float64(name, defaultValue, help) + pFlag := cmd.Flags().Lookup(name) + return flagDefinition{ + pflag: pFlag, + floatSetFunc: setFunc, + validationFunc: validationFunc, + validationDescription: validationDescription, + } +} + +// newStringFlag creates a new string flag and adds it to the command. +func newStringFlag(cmd *cobra.Command, name string, defaultValue string, setFunc func(string, target.Target, string) error, help string, validationDescription string, validationFunc func(cmd *cobra.Command) bool) flagDefinition { + cmd.Flags().String(name, defaultValue, help) + pFlag := cmd.Flags().Lookup(name) + return flagDefinition{ + pflag: pFlag, + stringSetFunc: setFunc, + validationFunc: validationFunc, + validationDescription: validationDescription, + } +} diff --git a/cmd/config/flag_groups.go b/cmd/config/flag_groups.go new file mode 100644 index 00000000..309c2e81 --- /dev/null +++ b/cmd/config/flag_groups.go @@ -0,0 +1,328 @@ +package config + +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "fmt" + "os" + "perfspect/internal/common" + "perfspect/internal/report" + "perfspect/internal/target" + "slices" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// flagGroup - structure to hold a group of flags +// groups are used to organize the flags for display in the help message +type flagGroup struct { + name string + flags []flagDefinition +} + +// flagGroups - list of flag groups +// initialized by initializeFlags +// and used by the config command +var flagGroups = []flagGroup{} + +// flag group names +const ( + flagGroupGeneralName = "General Options" + flagGroupUncoreFrequencyName = "Uncore Frequency Options" + flagGroupPrefetcherName = "Prefetcher Options" +) + +// general flag names +const ( + flagCoreCountName = "cores" + flagLLCSizeName = "llc" + flagAllCoreMaxFrequencyName = "core-max" + flagTDPName = "tdp" + flagEPBName = "epb" + flagEPPName = "epp" + flagGovernorName = "gov" + flagELCName = "elc" +) + +// uncore frequency flag names +const ( + flagUncoreMaxFrequencyName = "uncore-max" + flagUncoreMinFrequencyName = "uncore-min" + flagUncoreMaxComputeFrequencyName = "uncore-max-compute" + flagUncoreMinComputeFrequencyName = "uncore-min-compute" + flagUncoreMaxIOFrequencyName = "uncore-max-io" + flagUncoreMinIOFrequencyName = "uncore-min-io" +) + +// prefetcher flag names +const ( + flagPrefetcherL2HWName = "pref-l2hw" + flagPrefetcherL2AdjName = "pref-l2adj" + flagPrefetcherDCUHWName = "pref-dcuhw" + flagPrefetcherDCUIPName = "pref-dcuip" + flagPrefetcherDCUNPName = "pref-dcunp" + flagPrefetcherAMPName = "pref-amp" + flagPrefetcherLLCPPName = "pref-llcpp" + flagPrefetcherAOPName = "pref-aop" + flagPrefetcherHomelessName = "pref-homeless" + flagPrefetcherLLCName = "pref-llc" +) + +// governorOptions - list of valid governor options +var governorOptions = []string{"performance", "powersave"} + +// elcOptions - list of valid elc options +var elcOptions = []string{"latency-optimized", "default"} + +// prefetcherOptions - list of valid prefetcher options +var prefetcherOptions = []string{"enable", "disable"} + +// initializeFlags initializes the command line flags for the config command +// the global flagGroups variable is used to store the flags +func initializeFlags(cmd *cobra.Command) { + // general options + group := flagGroup{name: flagGroupGeneralName, flags: []flagDefinition{}} + group.flags = append(group.flags, + newIntFlag(cmd, flagCoreCountName, 0, setCoreCount, "number of physical cores per processor", "greater than 0", + func(cmd *cobra.Command) bool { value, _ := cmd.Flags().GetInt(flagCoreCountName); return value > 0 }), + newFloat64Flag(cmd, flagLLCSizeName, 0, setLlcSize, "LLC size in MB", "greater than 0", + func(cmd *cobra.Command) bool { value, _ := cmd.Flags().GetFloat64(flagLLCSizeName); return value > 0 }), + newFloat64Flag(cmd, flagAllCoreMaxFrequencyName, 0, setCoreFrequency, "all-core max frequency in GHz", "greater than 0.1", + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetFloat64(flagAllCoreMaxFrequencyName) + return value > 0.1 + }), + newIntFlag(cmd, flagTDPName, 0, setTDP, "maximum power per processor in Watts", "greater than 0", + func(cmd *cobra.Command) bool { value, _ := cmd.Flags().GetInt(flagTDPName); return value > 0 }), + newIntFlag(cmd, flagEPBName, 0, setEPB, "energy perf bias from best performance (0) to most power savings (15)", "0-15", + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetInt(flagEPBName) + return value >= 0 && value <= 15 + }), + newIntFlag(cmd, flagEPPName, 0, setEPP, "energy perf profile from best performance (0) to most power savings (255)", "0-255", + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetInt(flagEPPName) + return value >= 0 && value <= 255 + }), + newStringFlag(cmd, flagGovernorName, "", setGovernor, "CPU scaling governor ("+strings.Join(governorOptions, ", ")+")", strings.Join(governorOptions, ", "), + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString(flagGovernorName) + return slices.Contains(governorOptions, value) + }), + newStringFlag(cmd, flagELCName, "", setELC, "Efficiency Latency Control ("+strings.Join(elcOptions, ", ")+") [SRF+]", strings.Join(elcOptions, ", "), + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString(flagELCName) + return slices.Contains(elcOptions, value) + })) + flagGroups = append(flagGroups, group) + // uncore frequency options + group = flagGroup{name: flagGroupUncoreFrequencyName, flags: []flagDefinition{}} + group.flags = append(group.flags, + newFloat64Flag(cmd, flagUncoreMaxFrequencyName, 0, + func(value float64, myTarget target.Target, localTempDir string) error { + return setUncoreFrequency(true, value, myTarget, localTempDir) + }, + "maximum uncore frequency in GHz [EMR-]", "greater than 0.1", + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetFloat64(flagUncoreMaxFrequencyName) + return value > 0.1 + }), + newFloat64Flag(cmd, flagUncoreMinFrequencyName, 0, + func(value float64, myTarget target.Target, localTempDir string) error { + return setUncoreFrequency(false, value, myTarget, localTempDir) + }, + "minimum uncore frequency in GHz [EMR-]", "greater than 0.1", + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetFloat64(flagUncoreMinFrequencyName) + return value > 0.1 + }), + newFloat64Flag(cmd, flagUncoreMaxComputeFrequencyName, 0, + func(value float64, myTarget target.Target, localTempDir string) error { + return setUncoreDieFrequency(true, true, value, myTarget, localTempDir) + }, + "maximum uncore compute die frequency in GHz [SRF+]", "greater than 0.1", + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetFloat64(flagUncoreMaxComputeFrequencyName) + return value > 0.1 + }), + newFloat64Flag(cmd, flagUncoreMinComputeFrequencyName, 0, + func(value float64, myTarget target.Target, localTempDir string) error { + return setUncoreDieFrequency(false, true, value, myTarget, localTempDir) + }, + "minimum uncore compute die frequency in GHz [SRF+]", "greater than 0.1", + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetFloat64(flagUncoreMinComputeFrequencyName) + return value > 0.1 + }), + newFloat64Flag(cmd, flagUncoreMaxIOFrequencyName, 0, + func(value float64, myTarget target.Target, localTempDir string) error { + return setUncoreDieFrequency(true, false, value, myTarget, localTempDir) + }, + "maximum uncore IO die frequency in GHz [SRF+]", "greater than 0.1", + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetFloat64(flagUncoreMaxIOFrequencyName) + return value > 0.1 + }), + newFloat64Flag(cmd, flagUncoreMinIOFrequencyName, 0, + func(value float64, myTarget target.Target, localTempDir string) error { + return setUncoreDieFrequency(false, false, value, myTarget, localTempDir) + }, + "minimum uncore IO die frequency in GHz [SRF+]", "greater than 0.1", + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetFloat64(flagUncoreMinIOFrequencyName) + return value > 0.1 + })) + flagGroups = append(flagGroups, group) + // prefetcher options + group = flagGroup{name: flagGroupPrefetcherName, flags: []flagDefinition{}} + group.flags = append(group.flags, + newStringFlag(cmd, flagPrefetcherL2HWName, "", + func(value string, myTarget target.Target, localTempDir string) error { + return setPrefetcher(value, myTarget, localTempDir, report.PrefetcherL2HWName) + }, + "L2 hardware prefetcher ("+strings.Join(prefetcherOptions, ", ")+")", strings.Join(prefetcherOptions, ", "), + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString(flagPrefetcherL2HWName) + return slices.Contains(prefetcherOptions, value) + }), + newStringFlag(cmd, flagPrefetcherL2AdjName, "", + func(value string, myTarget target.Target, localTempDir string) error { + return setPrefetcher(value, myTarget, localTempDir, report.PrefetcherL2AdjName) + }, + "L2 adjacent cache line prefetcher ("+strings.Join(prefetcherOptions, ", ")+")", strings.Join(prefetcherOptions, ", "), + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString(flagPrefetcherL2AdjName) + return slices.Contains(prefetcherOptions, value) + }), + newStringFlag(cmd, flagPrefetcherDCUHWName, "", + func(value string, myTarget target.Target, localTempDir string) error { + return setPrefetcher(value, myTarget, localTempDir, report.PrefetcherDCUHWName) + }, + "DCU hardware prefetcher ("+strings.Join(prefetcherOptions, ", ")+")", strings.Join(prefetcherOptions, ", "), + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString(flagPrefetcherDCUHWName) + return slices.Contains(prefetcherOptions, value) + }), + newStringFlag(cmd, flagPrefetcherDCUIPName, "", + func(value string, myTarget target.Target, localTempDir string) error { + return setPrefetcher(value, myTarget, localTempDir, report.PrefetcherDCUIPName) + }, + "DCU instruction pointer prefetcher ("+strings.Join(prefetcherOptions, ", ")+")", strings.Join(prefetcherOptions, ", "), + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString(flagPrefetcherDCUIPName) + return slices.Contains(prefetcherOptions, value) + }), + newStringFlag(cmd, flagPrefetcherDCUNPName, "", + func(value string, myTarget target.Target, localTempDir string) error { + return setPrefetcher(value, myTarget, localTempDir, report.PrefetcherDCUNPName) + }, + "DCU next page prefetcher ("+strings.Join(prefetcherOptions, ", ")+")", strings.Join(prefetcherOptions, ", "), + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString(flagPrefetcherDCUNPName) + return slices.Contains(prefetcherOptions, value) + }), + newStringFlag(cmd, flagPrefetcherAMPName, "", + func(value string, myTarget target.Target, localTempDir string) error { + return setPrefetcher(value, myTarget, localTempDir, report.PrefetcherAMPName) + }, + "Adaptive multipath probability prefetcher ("+strings.Join(prefetcherOptions, ", ")+") [SPR,EMR,GNR]", strings.Join(prefetcherOptions, ", "), + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString(flagPrefetcherAMPName) + return slices.Contains(prefetcherOptions, value) + }), + newStringFlag(cmd, flagPrefetcherLLCPPName, "", + func(value string, myTarget target.Target, localTempDir string) error { + return setPrefetcher(value, myTarget, localTempDir, report.PrefetcherLLCPPName) + }, + "LLC page prefetcher ("+strings.Join(prefetcherOptions, ", ")+") [GNR]", strings.Join(prefetcherOptions, ", "), + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString(flagPrefetcherLLCPPName) + return slices.Contains(prefetcherOptions, value) + }), + newStringFlag(cmd, flagPrefetcherAOPName, "", + func(value string, myTarget target.Target, localTempDir string) error { + return setPrefetcher(value, myTarget, localTempDir, report.PrefetcherAOPName) + }, + "Array of pointers prefetcher ("+strings.Join(prefetcherOptions, ", ")+") [GNR]", strings.Join(prefetcherOptions, ", "), + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString(flagPrefetcherAOPName) + return slices.Contains(prefetcherOptions, value) + }), + newStringFlag(cmd, flagPrefetcherHomelessName, "", + func(value string, myTarget target.Target, localTempDir string) error { + return setPrefetcher(value, myTarget, localTempDir, report.PrefetcherHomelessName) + }, + "Homeless prefetcher ("+strings.Join(prefetcherOptions, ", ")+") [SPR,EMR,GNR]", strings.Join(prefetcherOptions, ", "), + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString(flagPrefetcherHomelessName) + return slices.Contains(prefetcherOptions, value) + }), + newStringFlag(cmd, flagPrefetcherLLCName, "", + func(value string, myTarget target.Target, localTempDir string) error { + return setPrefetcher(value, myTarget, localTempDir, report.PrefetcherLLCName) + }, + "Last level cache prefetcher ("+strings.Join(prefetcherOptions, ", ")+") [SPR,EMR,GNR]", strings.Join(prefetcherOptions, ", "), + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString(flagPrefetcherLLCName) + return slices.Contains(prefetcherOptions, value) + })) + flagGroups = append(flagGroups, group) + + common.AddTargetFlags(Cmd) + Cmd.SetUsageFunc(usageFunc) +} + +// usageFunc prints the usage information for the command +func usageFunc(cmd *cobra.Command) error { + cmd.Printf("Usage: %s [flags]\n\n", cmd.CommandPath()) + cmd.Printf("Examples:\n%s\n\n", cmd.Example) + cmd.Println("Flags:") + for _, group := range flagGroups { + cmd.Printf(" %s:\n", group.name) + for _, flag := range group.flags { + cmd.Printf(" --%-20s %s\n", flag.GetName(), flag.pflag.Usage) + } + } + + targetFlagGroup := common.GetTargetFlagGroup() + cmd.Printf(" %s:\n", targetFlagGroup.GroupName) + for _, flag := range targetFlagGroup.Flags { + cmd.Printf(" --%-20s %s\n", flag.Name, flag.Help) + } + + cmd.Println("\nGlobal Flags:") + cmd.Parent().PersistentFlags().VisitAll(func(pf *pflag.Flag) { + flagDefault := "" + if cmd.Parent().PersistentFlags().Lookup(pf.Name).DefValue != "" { + flagDefault = fmt.Sprintf(" (default: %s)", cmd.Flags().Lookup(pf.Name).DefValue) + } + cmd.Printf(" --%-20s %s%s\n", pf.Name, pf.Usage, flagDefault) + }) + return nil +} + +// validateFlags validates the command line flags for the config command +// operates on the global flagGroups variable +func validateFlags(cmd *cobra.Command, args []string) error { + for _, group := range flagGroups { + for _, flag := range group.flags { + if cmd.Flags().Lookup(flag.GetName()).Changed && flag.validationFunc != nil { + if !flag.validationFunc(cmd) { + err := fmt.Errorf("invalid flag value, --%s %s, valid values are %s", flag.GetName(), flag.GetValueAsString(), flag.validationDescription) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + cmd.SilenceUsage = true + return err + } + } + } + } + // common target flags + if err := common.ValidateTargetFlags(cmd); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + return nil +} diff --git a/cmd/config/flag_groups_test.go b/cmd/config/flag_groups_test.go new file mode 100644 index 00000000..e8bfb77f --- /dev/null +++ b/cmd/config/flag_groups_test.go @@ -0,0 +1,57 @@ +package config + +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "bytes" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" +) + +func TestValidateFlags(t *testing.T) { + // Create a mock command + cmd := &cobra.Command{ + Use: "test", + Run: func(cmd *cobra.Command, args []string) {}, + } + // Mock flag groups and flags + flagGroups = []flagGroup{} + group := flagGroup{ + name: "testGroup", + flags: []flagDefinition{}, + } + group.flags = append(group.flags, newStringFlag(cmd, + "testFlag", + "", + nil, + "A test flag", + "valid value", + func(cmd *cobra.Command) bool { + value, _ := cmd.Flags().GetString("testFlag") + return value == "validValue" + })) + flagGroups = append(flagGroups, group) + + // Test case: Invalid flag value + t.Run("InvalidFlagValue", func(t *testing.T) { + _ = cmd.Flags().Set("testFlag", "invalidValue") + var stderr bytes.Buffer + cmd.SetErr(&stderr) + + err := validateFlags(cmd, []string{}) + assert.Error(t, err) + }) + + // Test case: Valid flag value + t.Run("ValidFlagValue", func(t *testing.T) { + _ = cmd.Flags().Set("testFlag", "validValue") + var stderr bytes.Buffer + cmd.SetErr(&stderr) + + err := validateFlags(cmd, []string{}) + assert.NoError(t, err) + }) +} diff --git a/cmd/config/flag_test.go b/cmd/config/flag_test.go new file mode 100644 index 00000000..8e28b847 --- /dev/null +++ b/cmd/config/flag_test.go @@ -0,0 +1,60 @@ +package config + +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "testing" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" +) + +func TestFlagDefinition_GetName(t *testing.T) { + // Create a mock pflag.Flag + mockFlag := &pflag.Flag{ + Name: "test-flag", + } + + // Create a flagDefinition instance with the mock flag + flagDef := flagDefinition{ + pflag: mockFlag, + } + + // Call GetName and verify the result + result := flagDef.GetName() + assert.Equal(t, "test-flag", result, "GetName should return the correct flag name") +} +func TestFlagDefinition_GetType(t *testing.T) { + flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) + flagSet.String("test-flag", "default", "help") + // Lookup the flag to get the pflag.Flag instance + mockFlag := flagSet.Lookup("test-flag") + if mockFlag == nil { + t.Fatalf("Failed to create mock flag") + } + // Create a flagDefinition instance with the mock flag + flagDef := flagDefinition{ + pflag: mockFlag, + } + // Call GetType and verify the result + result := flagDef.GetType() + assert.Equal(t, "string", result, "GetType should return the correct flag type") +} + +func TestFlagDefinition_GetValueAsString(t *testing.T) { + flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) + flagSet.String("test-flag", "default", "help") + // Lookup the flag to get the pflag.Flag instance + mockFlag := flagSet.Lookup("test-flag") + if mockFlag == nil { + t.Fatalf("Failed to create mock flag") + } + // Create a flagDefinition instance with the mock flag + flagDef := flagDefinition{ + pflag: mockFlag, + } + // Call GetValueAsString and verify the result + result := flagDef.GetValueAsString() + assert.Equal(t, "default", result, "GetValueAsString should return the correct flag value as string") +} diff --git a/cmd/lock/lock.go b/cmd/lock/lock.go index e5ce4e54..78571656 100755 --- a/cmd/lock/lock.go +++ b/cmd/lock/lock.go @@ -137,12 +137,16 @@ func validateFlags(cmd *cobra.Command, args []string) error { return err } } - if flagDuration <= 0 { err := fmt.Errorf("duration must be greater than 0") fmt.Fprintf(os.Stderr, "Error: %v\n", err) return err } + if flagFrequency <= 0 { + err := fmt.Errorf("frequency must be greater than 0") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } // common target flags if err := common.ValidateTargetFlags(cmd); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) diff --git a/cmd/telemetry/telemetry.go b/cmd/telemetry/telemetry.go index 4cbd286d..979a5fe6 100644 --- a/cmd/telemetry/telemetry.go +++ b/cmd/telemetry/telemetry.go @@ -227,6 +227,11 @@ func validateFlags(cmd *cobra.Command, args []string) error { return err } } + if flagInterval < 1 { + err := fmt.Errorf("interval must be 1 or greater") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } if flagDuration < 0 { err := fmt.Errorf("duration must be 0 or greater") fmt.Fprintf(os.Stderr, "Error: %v\n", err) diff --git a/go.mod b/go.mod index da3fb3df..6cc2002b 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/deckarep/golang-set/v2 v2.8.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 + github.com/stretchr/testify v1.8.4 github.com/xuri/excelize/v2 v2.9.0 golang.org/x/term v0.31.0 golang.org/x/text v0.24.0 @@ -24,8 +25,10 @@ require ( ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.4 // indirect github.com/xuri/efp v0.0.0-20241211021726-c4e992084aa6 // indirect @@ -33,4 +36,5 @@ require ( golang.org/x/crypto v0.36.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sys v0.32.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/internal/common/common.go b/internal/common/common.go index 731dbc0b..e27e18d5 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -16,6 +16,7 @@ import ( "perfspect/internal/script" "perfspect/internal/target" "perfspect/internal/util" + "strings" "syscall" "slices" @@ -57,7 +58,7 @@ func (tso *TargetScriptOutputs) GetTableNames() []string { const ( TableNameInsights = "Insights" - TableNamePerfspect = "PerfSpect Version" + TableNamePerfspect = "PerfSpect" ) type Category struct { @@ -163,15 +164,7 @@ func (rc *ReportingCommand) Run() error { } } multiSpinner.Start() - // get the data we need to generate reports - orderedTargetScriptOutputs, err = outputsFromTargets(rc.Cmd, myTargets, rc.TableNames, rc.ScriptParams, multiSpinner.Status, localTempDir) - if err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - slog.Error(err.Error()) - rc.Cmd.SilenceUsage = true - return err - } - // Collect indices of targets to remove + // remove targets that had errors var indicesToRemove []int for i := range targetErrs { if targetErrs[i] != nil { @@ -179,13 +172,12 @@ func (rc *ReportingCommand) Run() error { indicesToRemove = append(indicesToRemove, i) } } - // Remove targets in reverse order of indices to avoid shifting issues for i := len(indicesToRemove) - 1; i >= 0; i-- { myTargets = slices.Delete(myTargets, indicesToRemove[i], indicesToRemove[i]+1) } - // check if we have any remaining targets to run the scripts on - if len(myTargets) == 0 { - err := fmt.Errorf("no targets remain") + // collect data from targets + orderedTargetScriptOutputs, err = outputsFromTargets(rc.Cmd, myTargets, rc.TableNames, rc.ScriptParams, multiSpinner.Status, localTempDir) + if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) slog.Error(err.Error()) rc.Cmd.SilenceUsage = true @@ -194,6 +186,13 @@ func (rc *ReportingCommand) Run() error { // stop the progress indicator multiSpinner.Finish() fmt.Println() + // exit with error if no targets remain + if len(myTargets) == 0 { + err := fmt.Errorf("no successful targets found") + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } } // we have output data so create the output directory err := CreateOutputDir(outputDir) @@ -355,6 +354,8 @@ func (rc *ReportingCommand) createReports(appContext AppContext, orderedTargetSc }, Fields: []report.Field{ {Name: "Version", Values: []string{appContext.Version}}, + {Name: "Args", Values: []string{strings.Join(os.Args, " ")}}, + {Name: "OutputDir", Values: []string{appContext.OutputDir}}, }, }) // create the report(s) diff --git a/internal/common/targets.go b/internal/common/targets.go index 40120bb6..5841cb16 100644 --- a/internal/common/targets.go +++ b/internal/common/targets.go @@ -18,6 +18,8 @@ import ( "strconv" "strings" + "slices" + "github.com/spf13/cobra" "golang.org/x/term" "gopkg.in/yaml.v2" @@ -132,26 +134,24 @@ func GetTargets(cmd *cobra.Command, needsElevatedPrivileges bool, failIfCantElev } // create a temp directory on each target for targetIdx, myTarget := range targets { + // if we already have an error for this target, skip it if targetErrs[targetIdx] != nil { continue } _, err := myTarget.CreateTempDirectory(targetTempDirRoot) if err != nil { - targetErrs[targetIdx] = fmt.Errorf("failed to create temp directory on target") + targetErrs[targetIdx] = fmt.Errorf("failed to create temp directory on target: %v", err) slog.Error(targetErrs[targetIdx].Error(), slog.String("target", myTarget.GetName()), slog.String("error", err.Error())) continue } - // confirm that the temp directory was not created on a file system mounted with noexec - noExec, err := isNoExec(myTarget, myTarget.GetTempDirectory()) + // confirm that the temp directory was created on a file system that was not mounted with noexec + noExec, err := isDirNoExec(myTarget, myTarget.GetTempDirectory()) if err != nil { // log the error but don't reject the target just in case our check is wrong - slog.Error("failed to check if temp directory is mounted on 'noexec' file system", slog.String("target", myTarget.GetName()), slog.String("error", err.Error())) - continue - } - if noExec { + slog.Warn("failed to check if temp directory is mounted on 'noexec' file system", slog.String("target", myTarget.GetName()), slog.String("error", err.Error())) + } else if noExec { targetErrs[targetIdx] = fmt.Errorf("target's temp directory must not be on a file system mounted with the 'noexec' option, override the default with --tempdir") slog.Error(targetErrs[targetIdx].Error(), slog.String("target", myTarget.GetName())) - continue } } return @@ -375,52 +375,118 @@ func getHostArchitecture() (string, error) { } } -// isNoExec checks if the temporary directory is on a file system that is mounted with noexec. -func isNoExec(t target.Target, tempDir string) (bool, error) { - dfCmd := exec.Command("df", "-P", tempDir) +// fieldFromDfpOutput parses the output of the `df -P