package main import ( "context" "log" "net" "net/http" _ "net/http/pprof" "os" "runtime" "runtime/pprof" "sync" "github.com/facebookincubator/go-belt" "github.com/facebookincubator/go-belt/tool/experimental/errmon" errmonsentry "github.com/facebookincubator/go-belt/tool/experimental/errmon/implementation/sentry" "github.com/facebookincubator/go-belt/tool/logger" "github.com/facebookincubator/go-belt/tool/logger/implementation/logrus" "github.com/getsentry/sentry-go" "github.com/spf13/pflag" "github.com/xaionaro-go/streamctl/cmd/streamd/ui" "github.com/xaionaro-go/streamctl/pkg/observability" "github.com/xaionaro-go/streamctl/pkg/streamcontrol" "github.com/xaionaro-go/streamctl/pkg/streamd" "github.com/xaionaro-go/streamctl/pkg/streamd/config" "github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc" "github.com/xaionaro-go/streamctl/pkg/streamd/server" uiiface "github.com/xaionaro-go/streamctl/pkg/streamd/ui" "github.com/xaionaro-go/streamctl/pkg/xpath" "google.golang.org/grpc" ) const forceNetPProfOnAndroid = true func main() { loggerLevel := logger.LevelWarning pflag.Var(&loggerLevel, "log-level", "Log level") listenAddr := pflag.String("listen-addr", ":3594", "the address to listen for incoming connections to") configPath := pflag.String("config-path", "/etc/streamd/streamd.yaml", "the path to the config file") netPprofAddr := pflag.String("go-net-pprof-addr", "", "address to listen to for net/pprof requests") cpuProfile := pflag.String("go-profile-cpu", "", "file to write cpu profile to") heapProfile := pflag.String("go-profile-heap", "", "file to write memory profile to") sentryDSN := pflag.String("sentry-dsn", "", "DSN of a Sentry instance to send error reports") pflag.Parse() l := logrus.Default().WithLevel(loggerLevel) if *cpuProfile != "" { f, err := os.Create(*cpuProfile) if err != nil { l.Fatalf("unable to create file '%s': %v", *cpuProfile, err) } defer f.Close() if err := pprof.StartCPUProfile(f); err != nil { l.Fatalf("unable to write to file '%s': %v", *cpuProfile, err) } defer pprof.StopCPUProfile() } if *heapProfile != "" { f, err := os.Create(*heapProfile) if err != nil { l.Fatalf("unable to create file '%s': %v", *heapProfile, err) } defer f.Close() runtime.GC() if err := pprof.WriteHeapProfile(f); err != nil { l.Fatalf("unable to write to file '%s': %v", *heapProfile, err) } } if *netPprofAddr != "" || (forceNetPProfOnAndroid && runtime.GOOS == "android") { go func() { if *netPprofAddr == "" { *netPprofAddr = "localhost:0" } l.Infof("starting to listen for net/pprof requests at '%s'", *netPprofAddr) l.Error(http.ListenAndServe(*netPprofAddr, nil)) }() } if oldValue := runtime.GOMAXPROCS(0); oldValue < 16 { l.Infof("increased GOMAXPROCS from %d to %d", oldValue, 16) runtime.GOMAXPROCS(16) } ctx := context.Background() ctx = logger.CtxWithLogger(ctx, l) if *sentryDSN != "" { l.Infof("setting up Sentry at DSN '%s'", *sentryDSN) sentryClient, err := sentry.NewClient(sentry.ClientOptions{ Dsn: *sentryDSN, }) if err != nil { l.Fatal(err) } sentryErrorMonitor := errmonsentry.New(sentryClient) ctx = errmon.CtxWithErrorMonitor(ctx, sentryErrorMonitor) l = l.WithPreHooks(observability.NewErrorMonitorLoggerHook( sentryErrorMonitor, )) ctx = logger.CtxWithLogger(ctx, l) } logger.Default = func() logger.Logger { return l } defer belt.Flush(ctx) configPathExpanded, err := xpath.Expand(*configPath) if err != nil { l.Fatalf("unable to get the path to the data file: %v", err) } var wg sync.WaitGroup var cancelFunc context.CancelFunc var _ui uiiface.UI var streamdGRPC *server.GRPCServer var streamdGRPCLocker sync.Mutex restart := func() { l.Debugf("restart()") if cancelFunc != nil { l.Infof("cancelling the old server") cancelFunc() wg.Wait() } wg.Add(1) defer wg.Done() l.Infof("starting a server") streamdGRPCLocker.Lock() defer streamdGRPCLocker.Unlock() var cfg config.Config err := config.ReadConfigFromPath(ctx, configPathExpanded, &cfg) if err != nil { l.Fatal(cfg) } ctx, _cancelFunc := context.WithCancel(ctx) cancelFunc = _cancelFunc streamD, err := streamd.New( cfg, _ui, func(ctx context.Context, c config.Config) error { return config.WriteConfigToPath(ctx, configPathExpanded, c) }, belt.CtxBelt(ctx), ) if err != nil { l.Fatalf("unable to initialize the streamd instance: %v", err) } go func() { if err = streamD.Run(ctx); err != nil { l.Errorf("streamd returned an error: %v", err) } }() listener, err := net.Listen("tcp", *listenAddr) if err != nil { log.Fatalf("failed to listen: %v", err) } go func() { <-ctx.Done() listener.Close() }() grpcServer := grpc.NewServer() streamdGRPC = server.NewGRPCServer(streamD) streamd_grpc.RegisterStreamDServer(grpcServer, streamdGRPC) l.Infof("started server at %s", *listenAddr) streamdGRPCLocker.Unlock() err = grpcServer.Serve(listener) streamdGRPCLocker.Lock() if err != nil { log.Fatal(err) } } _ui = ui.NewUI( ctx, func(listenPort uint16, platID streamcontrol.PlatformName, authURL string) bool { logger.Tracef(ctx, "streamd.UI.OpenOAuthURL(%d, %s, '%s')", listenPort, platID, authURL) defer logger.Tracef(ctx, "/streamd.UI.OpenOAuthURL(%d, %s, '%s')", listenPort, platID, authURL) streamdGRPCLocker.Lock() logger.Tracef(ctx, "streamdGRPCLocker.Lock()-ed") defer logger.Tracef(ctx, "streamdGRPCLocker.Lock()-ed") defer streamdGRPCLocker.Unlock() err := streamdGRPC.OpenOAuthURL(ctx, listenPort, platID, authURL) errmon.ObserveErrorCtx(ctx, err) return err == nil }, func(ctx context.Context, s string) { restart() }, ) restart() <-ctx.Done() }