exp.upspin.io@v0.0.0-20230625230448-5076e5b595ec/cmd/upspin-warden/main.go (about) 1 // Copyright 2017 The Upspin Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Command upspin-warden runs Upspin client daemons, such as upspinfs and 6 // cacheserver, and exports information about them to external programs. 7 package main 8 9 import ( 10 "bytes" 11 "flag" 12 "fmt" 13 "io" 14 "net/http" 15 "os" 16 "os/exec" 17 "sort" 18 "strings" 19 "sync" 20 "time" 21 22 "upspin.io/flags" 23 "upspin.io/log" 24 ) 25 26 func main() { 27 cmd := flag.String("cmd", "cacheserver,upspinfs,upspin-sharebot", "comma-separated list of `commands` to run") 28 flags.Parse(nil, "log", "config", "http") 29 w := NewWarden(strings.Split(*cmd, ",")) 30 log.Fatal(http.ListenAndServe(flags.HTTPAddr, w)) 31 } 32 33 // restartInterval specifies the time between daemon restarts. 34 const restartInterval = 10 * time.Second 35 36 // Warden implements the upspin-warden daemon. 37 type Warden struct { 38 log rollingLog 39 procs map[string]*Process 40 } 41 42 // NewWarden creates a Warden that runs the given commands. 43 // It implements a http.Handler that exports server state and logs. 44 // It redirects global Upspin log output to its internal rolling log. 45 func NewWarden(cmds []string) *Warden { 46 w := &Warden{procs: map[string]*Process{}} 47 for _, c := range cmds { 48 w.procs[c] = &Process{name: c} 49 } 50 log.SetOutput(io.MultiWriter(os.Stderr, &w.log)) 51 for _, p := range w.procs { 52 go p.Run() 53 } 54 return w 55 } 56 57 // ServeHTTP implements http.Handler. 58 func (w *Warden) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 59 switch name := r.URL.Path[1:]; name { 60 case "": // Root. 61 // Show truncated warden logs. 62 fmt.Fprintln(rw, "warden:") 63 fprintLastNLines(rw, w.log.Log(), 10, "\t") 64 // Show processes, their states, and truncated logs. 65 var names []string 66 for n := range w.procs { 67 names = append(names, n) 68 } 69 sort.Strings(names) 70 for _, n := range names { 71 p := w.procs[n] 72 fmt.Fprintf(rw, "\n%s: %s\n", n, p.State()) 73 fprintLastNLines(rw, p.log.Log(), 10, "\t") 74 } 75 case "warden": 76 // Show complete warden log. 77 rw.Write(w.log.Log()) 78 default: 79 // Show log for the given process. 80 p, ok := w.procs[name] 81 if !ok { 82 http.NotFound(rw, r) 83 return 84 } 85 rw.Write(p.log.Log()) 86 } 87 } 88 89 // fprintLastNLines writes the last n lines of buf to w, 90 // adding prefix to the start of each line. 91 func fprintLastNLines(w io.Writer, buf []byte, n int, prefix string) { 92 lines := make([][]byte, 0, n) 93 for i := 0; i <= n; i++ { 94 j := bytes.LastIndexByte(buf, '\n') 95 if j <= 0 { 96 if len(buf) > 0 { 97 lines = append(lines, buf) 98 } 99 break 100 } 101 lines = append(lines, buf[j+1:]) 102 buf = buf[:j] 103 } 104 for i := len(lines) - 1; i >= 0; i-- { 105 fmt.Fprintf(w, "%s%s\n", prefix, lines[i]) 106 } 107 } 108 109 // ProcessState describes the state of a Process. 110 type ProcessState int 111 112 //go:generate stringer -type ProcessState 113 114 const ( 115 NotStarted ProcessState = iota 116 Starting 117 Running 118 Error 119 ) 120 121 // Process manages the execution of a daemon process and captures its logs. 122 type Process struct { 123 name string 124 log rollingLog 125 126 mu sync.Mutex 127 state ProcessState 128 } 129 130 // State reports the state of the process. 131 func (p *Process) State() ProcessState { 132 p.mu.Lock() 133 defer p.mu.Unlock() 134 return p.state 135 } 136 137 // Run executes the process in a loop, restarting it after restartInterval 138 // since its last start. 139 func (p *Process) Run() { 140 for { 141 started := time.Now() 142 err := p.exec() 143 log.Error.Printf("%v: %v", p.name, err) 144 if d := time.Since(started); d < restartInterval { 145 i := restartInterval - d 146 log.Debug.Printf("%v: waiting %v before restarting", p.name, i) 147 time.Sleep(i) 148 } 149 } 150 } 151 152 // Exec starts the process and waits for it to return, 153 // updating the process's state field as necessary. 154 func (p *Process) exec() error { 155 cmd := exec.Command(p.name, 156 "-log="+flags.Log.String(), 157 "-config="+flags.Config) 158 cmd.Stdout = &p.log 159 cmd.Stderr = &p.log 160 p.setState(Starting) 161 if err := cmd.Start(); err != nil { 162 return err 163 } 164 p.setState(Running) 165 err := cmd.Wait() 166 p.setState(Error) 167 return err 168 } 169 170 func (p *Process) setState(s ProcessState) { 171 p.mu.Lock() 172 p.state = s 173 p.mu.Unlock() 174 log.Debug.Printf("%s: %s", p.name, s) 175 }