| // Package main is the entry point for the cos_node_profiler application that | |
| // imports the cloudlogger, profiler, and utils packages that respectively | |
| // write logs to Google Cloud Logging backend, fetch debugging information from | |
| // a Linux system and provide the interface between cloudlogger and profiler | |
| // packages. | |
| package main | |
| import ( | |
| "context" | |
| "encoding/json" | |
| "flag" | |
| "fmt" | |
| "os" | |
| "time" | |
| "cloud.google.com/go/logging" | |
| "cos.googlesource.com/cos/tools.git/src/pkg/nodeprofiler/cloudlogger" | |
| "cos.googlesource.com/cos/tools.git/src/pkg/nodeprofiler/profiler" | |
| log "github.com/sirupsen/logrus" | |
| ) | |
| const cloudLoggerName = "cos_node_profiler" | |
| var ( | |
| configFile = flag.String("config-file", "", "specifies the path of the configuration file. If path is not set, then it is assumed that command line flags will be passed to configure the Node Profiler.") | |
| projID = flag.String("project", "", "specifies the GCP project where logs will be added.") | |
| command = flag.String("cmd", "", "specifies raw commands for which to log output.") | |
| cmdCount = flag.Int("cmd-count", 0, "specifies the number of times to run an arbitrary shell command.") | |
| cmdInterval = flag.Int("cmd-interval", 0, "specifies the interval (in seconds) separating the number of times the user runs an arbitrary shell command.") | |
| cmdTimeOut = flag.Int("cmd-timeout", 300, "specifies the amount of time (in seconds) it will take for the a raw command to timeout and be killed.") | |
| profilerCount = flag.Int("profiler-count", 1, "specifies the number of times to collect USE Report.") | |
| profilerInterval = flag.Int("profiler-interval", 0, "specifies the interval (in seconds) separating the number of times the user collects USE Report.") | |
| ) | |
| func main() { | |
| var opts *cloudlogger.LoggerOpts | |
| var err error | |
| flag.Parse() | |
| if *configFile != "" { | |
| opts, err = loadConfig(*configFile) | |
| if err != nil { | |
| log.Fatalf("%v", err) | |
| } | |
| } else { | |
| opts = loadFlags() | |
| } | |
| // [START client setup] | |
| ctx := context.Background() | |
| client, err := logging.NewClient(ctx, opts.ProjID) | |
| if err != nil { | |
| log.Fatalf("failed to create logging client: %v", err) | |
| } | |
| defer client.Close() | |
| client.OnError = func(err error) { | |
| // Log an error to the local log if any function call failed. | |
| // For example, print an error if Flush() failed. | |
| log.Errorf("client.OnError: %v", err) | |
| } | |
| // [END client setup] | |
| log.Info("Begin logging profiler report...") | |
| logger := client.Logger(cloudLoggerName) | |
| if err = cloudlogger.LogProfilerReport(logger, opts); err != nil { | |
| log.Fatalf("%v", err) | |
| } | |
| log.Info("Successfully logged profiler report.") | |
| } | |
| // generateProfilerOpts is a helper function used to generate the components | |
| // array as well as the profiler options used to call the | |
| // profiler.GenerateUSEReport function from the profiler package. | |
| func generateProfilerOpts() ([]profiler.Component, []profiler.Command) { | |
| // [Begin generating ProfilerOpts from Profiler Package] | |
| // Getting Components | |
| cpu := profiler.NewCPU("CPU") | |
| memcap := profiler.NewMemCap("MemCap") | |
| sDevIO := profiler.NewStorageDevIO("StorageDevIO") | |
| sCap := profiler.NewStorageCap("StorageCap") | |
| components := []profiler.Component{cpu, memcap, sDevIO, sCap} | |
| // End Getting Components | |
| // Getting Commands | |
| vmstat := profiler.NewVMStat("vmstat", 1, 5, []string{"us", "sy", "st", "si", "so", "r"}) | |
| lscpu := profiler.NewLscpu("lscpu", []string{"CPU(s)"}) | |
| free := profiler.NewFree("free", []string{"Mem:used", "Mem:total", "Swap:used", "Swap:total"}) | |
| iostat := profiler.NewIOStat("iostat", "-xdz", 1, 5, []string{"aqu-sz", "%util"}) | |
| df := profiler.NewDF("df", "-k", []string{}) | |
| commands := []profiler.Command{vmstat, lscpu, free, iostat, df} | |
| // End Getting Commands | |
| // [End generating ProfilerOpts from Profiler Package] | |
| return components, commands | |
| } | |
| // loadflags helps to use command line flags as configuration to the Node | |
| // Profiler tool. | |
| func loadFlags() *cloudlogger.LoggerOpts { | |
| // Getting Profiler Options. | |
| components, commands := generateProfilerOpts() | |
| shCmds := []cloudlogger.ShellCmdOpts{ | |
| cloudlogger.ShellCmdOpts{ | |
| Command: *command, | |
| CmdCount: *cmdCount, | |
| CmdInterval: time.Duration(*cmdInterval) * time.Second, | |
| CmdTimeOut: time.Duration(*cmdTimeOut) * time.Second, | |
| }, | |
| } | |
| // populating LoggerOpts struct with configurations from user. | |
| opts := &cloudlogger.LoggerOpts{ | |
| ProjID: *projID, | |
| ShCmds: shCmds, | |
| ProfilerCount: *profilerCount, | |
| ProfilerInterval: time.Duration(*profilerInterval) * time.Second, | |
| Components: components, | |
| ProfilerCmds: commands, | |
| } | |
| return opts | |
| } | |
| // loadConfig helps to use a json configuration file in the current directory | |
| // as configuration to the Node Profiler Tool. | |
| func loadConfig(filename string) (*cloudlogger.LoggerOpts, error) { | |
| var logger cloudlogger.LoggerOpts | |
| configFile, err := os.Open(filename) | |
| defer configFile.Close() | |
| if err != nil { | |
| return &logger, fmt.Errorf("failed to open config file %v: %v", filename, err) | |
| } | |
| jsonParser := json.NewDecoder(configFile) | |
| if err = jsonParser.Decode(&logger); err != nil { | |
| return &logger, fmt.Errorf("failed to parse config file %v: %v", filename, err) | |
| } | |
| for i := 0; i < len(logger.ShCmds); i++ { | |
| logger.ShCmds[i].CmdInterval = logger.ShCmds[i].CmdInterval * time.Second | |
| logger.ShCmds[i].CmdTimeOut = logger.ShCmds[i].CmdTimeOut * time.Second | |
| } | |
| logger.ProfilerInterval = logger.ProfilerInterval * time.Second | |
| components, commands := generateProfilerOpts() | |
| logger.Components = components | |
| logger.ProfilerCmds = commands | |
| return &logger, err | |
| } |