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  }