github.com/observiq/carbon@v0.9.11-0.20200820160507-1b872e368a5e/commands/root.go (about) 1 package commands 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "net/http" 8 9 // This package registers its HTTP endpoints for profiling using an init hook 10 _ "net/http/pprof" 11 "os" 12 "runtime" 13 "runtime/pprof" 14 "sync" 15 "time" 16 17 agent "github.com/observiq/carbon/agent" 18 "github.com/spf13/cobra" 19 "go.uber.org/zap" 20 "go.uber.org/zap/zapcore" 21 ) 22 23 // RootFlags are the root level flags that be provided when invoking carbon from the command line 24 type RootFlags struct { 25 DatabaseFile string 26 ConfigFiles []string 27 PluginDir string 28 PprofPort int 29 CPUProfile string 30 CPUProfileDuration time.Duration 31 MemProfile string 32 MemProfileDelay time.Duration 33 34 LogFile string 35 Debug bool 36 } 37 38 // NewRootCmd will return a root level command 39 func NewRootCmd() *cobra.Command { 40 rootFlags := &RootFlags{} 41 42 root := &cobra.Command{ 43 Use: "carbon [-c ./config.yaml]", 44 Short: "A log parser and router", 45 Long: "A log parser and router", 46 Args: cobra.NoArgs, 47 Run: func(command *cobra.Command, args []string) { runRoot(command, args, rootFlags) }, 48 } 49 50 rootFlagSet := root.PersistentFlags() 51 rootFlagSet.StringVar(&rootFlags.LogFile, "log_file", "", "write logs to configured path rather than stderr") 52 rootFlagSet.StringSliceVarP(&rootFlags.ConfigFiles, "config", "c", []string{defaultConfig()}, "path to a config file") 53 rootFlagSet.StringVar(&rootFlags.PluginDir, "plugin_dir", defaultPluginDir(), "path to the plugin directory") 54 rootFlagSet.StringVar(&rootFlags.DatabaseFile, "database", "", "path to the carbon offset database") 55 rootFlagSet.BoolVar(&rootFlags.Debug, "debug", false, "debug logging") 56 57 // Profiling flags 58 rootFlagSet.IntVar(&rootFlags.PprofPort, "pprof_port", 0, "listen port for pprof profiling") 59 rootFlagSet.StringVar(&rootFlags.CPUProfile, "cpu_profile", "", "path to cpu profile output") 60 rootFlagSet.DurationVar(&rootFlags.CPUProfileDuration, "cpu_profile_duration", 60*time.Second, "duration to run the cpu profile") 61 rootFlagSet.StringVar(&rootFlags.MemProfile, "mem_profile", "", "path to memory profile output") 62 rootFlagSet.DurationVar(&rootFlags.MemProfileDelay, "mem_profile_delay", 10*time.Second, "time to wait before writing a memory profile") 63 64 // Set profiling flags to hidden 65 hiddenFlags := []string{"pprof_port", "cpu_profile", "cpu_profile_duration", "mem_profile", "mem_profile_delay"} 66 for _, flag := range hiddenFlags { 67 err := rootFlagSet.MarkHidden(flag) 68 if err != nil { 69 // MarkHidden only fails if the flag does not exist 70 panic(err) 71 } 72 } 73 74 root.AddCommand(NewGraphCommand(rootFlags)) 75 root.AddCommand(NewVersionCommand()) 76 root.AddCommand(NewOffsetsCmd(rootFlags)) 77 78 return root 79 } 80 81 func runRoot(command *cobra.Command, _ []string, flags *RootFlags) { 82 var logger *zap.SugaredLogger 83 if flags.Debug { 84 logger = newDefaultLoggerAt(zapcore.DebugLevel, flags.LogFile) 85 } else { 86 logger = newDefaultLoggerAt(zapcore.InfoLevel, flags.LogFile) 87 } 88 defer func() { 89 _ = logger.Sync() 90 }() 91 92 cfg, err := agent.NewConfigFromGlobs(flags.ConfigFiles) 93 if err != nil { 94 logger.Errorw("Failed to read configs from globs", zap.Any("error", err), zap.Any("globs", flags.ConfigFiles)) 95 os.Exit(1) 96 } 97 logger.Debugw("Parsed config", "config", cfg) 98 99 agent, err := agent.NewLogAgent(cfg, logger, flags.PluginDir, flags.DatabaseFile, nil) 100 if err != nil { 101 logger.Errorw("Failed to build agent", zap.Error(err)) 102 os.Exit(1) 103 } 104 105 ctx, cancel := context.WithCancel(command.Context()) 106 service, err := newAgentService(ctx, agent, cancel) 107 if err != nil { 108 logger.Errorf("Failed to create agent service", zap.Any("error", err)) 109 os.Exit(1) 110 } 111 112 profilingWg := startProfiling(ctx, flags, logger) 113 114 err = service.Run() 115 if err != nil { 116 logger.Errorw("Failed to run agent service", zap.Error(err)) 117 os.Exit(1) 118 } 119 120 profilingWg.Wait() 121 } 122 123 func startProfiling(ctx context.Context, flags *RootFlags, logger *zap.SugaredLogger) *sync.WaitGroup { 124 wg := &sync.WaitGroup{} 125 126 // Start pprof listening on port 127 if flags.PprofPort != 0 { 128 // pprof endpoints registered by importing net/pprof 129 var srv http.Server 130 srv.Addr = fmt.Sprintf(":%d", flags.PprofPort) 131 132 wg.Add(1) 133 go func() { 134 defer wg.Done() 135 logger.Info(srv.ListenAndServe()) 136 }() 137 138 wg.Add(1) 139 140 go func() { 141 defer wg.Done() 142 <-ctx.Done() 143 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 144 defer cancel() 145 err := srv.Shutdown(ctx) 146 if err != nil { 147 logger.Warnw("Errored shutting down pprof server", zap.Error(err)) 148 } 149 }() 150 } 151 152 // Start CPU profile for configured duration 153 if flags.CPUProfile != "" { 154 wg.Add(1) 155 go func() { 156 defer wg.Done() 157 158 f, err := os.Create(flags.CPUProfile) 159 if err != nil { 160 logger.Errorw("Failed to create CPU profile", zap.Error(err)) 161 } 162 defer f.Close() 163 164 if err := pprof.StartCPUProfile(f); err != nil { 165 log.Fatal("could not start CPU profile: ", err) 166 } 167 168 select { 169 case <-ctx.Done(): 170 case <-time.After(flags.CPUProfileDuration): 171 } 172 pprof.StopCPUProfile() 173 }() 174 } 175 176 // Start memory profile after configured delay 177 if flags.MemProfile != "" { 178 wg.Add(1) 179 go func() { 180 defer wg.Done() 181 182 select { 183 case <-ctx.Done(): 184 case <-time.After(flags.MemProfileDelay): 185 } 186 187 f, err := os.Create(flags.MemProfile) 188 if err != nil { 189 logger.Errorw("Failed to create memory profile", zap.Error(err)) 190 } 191 defer f.Close() // error handling omitted for example 192 193 runtime.GC() // get up-to-date statistics 194 if err := pprof.WriteHeapProfile(f); err != nil { 195 log.Fatal("could not write memory profile: ", err) 196 } 197 }() 198 } 199 200 return wg 201 }