github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/introspection/socket.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package introspection 5 6 import ( 7 "fmt" 8 "net" 9 "net/http" 10 "runtime" 11 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 "gopkg.in/tomb.v1" 15 "gopkg.in/yaml.v2" 16 17 "github.com/juju/juju/worker" 18 "github.com/juju/juju/worker/introspection/pprof" 19 ) 20 21 var logger = loggo.GetLogger("juju.worker.introspection") 22 23 // DepEngineReporter provides insight into the running dependency engine of the agent. 24 type DepEngineReporter interface { 25 // Report returns a map describing the state of the receiver. It is expected 26 // to be goroutine-safe. 27 Report() map[string]interface{} 28 } 29 30 // Config describes the arguments required to create the introspection worker. 31 type Config struct { 32 SocketName string 33 Reporter DepEngineReporter 34 } 35 36 // Validate checks the config values to assert they are valid to create the worker. 37 func (c *Config) Validate() error { 38 if c.SocketName == "" { 39 return errors.NotValidf("empty SocketName") 40 } 41 return nil 42 } 43 44 // socketListener is a worker and constructed with NewWorker. 45 type socketListener struct { 46 tomb tomb.Tomb 47 listener *net.UnixListener 48 reporter DepEngineReporter 49 done chan struct{} 50 } 51 52 // NewWorker starts an http server listening on an abstract domain socket 53 // which will be created with the specified name. 54 func NewWorker(config Config) (worker.Worker, error) { 55 if err := config.Validate(); err != nil { 56 return nil, errors.Trace(err) 57 } 58 if runtime.GOOS != "linux" { 59 return nil, errors.NotSupportedf("os %q", runtime.GOOS) 60 } 61 62 path := "@" + config.SocketName 63 addr, err := net.ResolveUnixAddr("unix", path) 64 if err != nil { 65 return nil, errors.Annotate(err, "unable to resolve unix socket") 66 } 67 68 l, err := net.ListenUnix("unix", addr) 69 if err != nil { 70 return nil, errors.Annotate(err, "unable to listen on unix socket") 71 } 72 logger.Debugf("introspection worker listening on %q", path) 73 74 w := &socketListener{ 75 listener: l, 76 reporter: config.Reporter, 77 done: make(chan struct{}), 78 } 79 go w.serve() 80 go w.run() 81 return w, nil 82 } 83 84 func (w *socketListener) serve() { 85 mux := http.NewServeMux() 86 mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) 87 mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) 88 mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) 89 mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) 90 mux.Handle("/depengine/", http.HandlerFunc(w.depengineReport)) 91 92 srv := http.Server{ 93 Handler: mux, 94 } 95 96 logger.Debugf("stats worker now servering") 97 defer logger.Debugf("stats worker servering finished") 98 defer close(w.done) 99 srv.Serve(w.listener) 100 } 101 102 func (w *socketListener) run() { 103 defer w.tomb.Done() 104 defer logger.Debugf("stats worker finished") 105 <-w.tomb.Dying() 106 logger.Debugf("stats worker closing listener") 107 w.listener.Close() 108 // Don't mark the worker as done until the serve goroutine has finished. 109 <-w.done 110 } 111 112 // Kill implements worker.Worker. 113 func (w *socketListener) Kill() { 114 w.tomb.Kill(nil) 115 } 116 117 // Wait implements worker.Worker. 118 func (w *socketListener) Wait() error { 119 return w.tomb.Wait() 120 } 121 122 func (s *socketListener) depengineReport(w http.ResponseWriter, r *http.Request) { 123 if s.reporter == nil { 124 w.WriteHeader(http.StatusNotFound) 125 fmt.Fprintln(w, "missing reporter") 126 return 127 } 128 bytes, err := yaml.Marshal(s.reporter.Report()) 129 if err != nil { 130 w.WriteHeader(http.StatusInternalServerError) 131 fmt.Fprintf(w, "error: %v\n", err) 132 return 133 } 134 135 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 136 137 fmt.Fprint(w, "Dependency Engine Report\n\n") 138 w.Write(bytes) 139 }