github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "sort" 12 13 "github.com/juju/errors" 14 "github.com/juju/loggo" 15 "github.com/prometheus/client_golang/prometheus" 16 "github.com/prometheus/client_golang/prometheus/promhttp" 17 "gopkg.in/juju/worker.v1" 18 "gopkg.in/tomb.v2" 19 "gopkg.in/yaml.v2" 20 21 "github.com/juju/juju/cmd/output" 22 "github.com/juju/juju/core/machinelock" 23 "github.com/juju/juju/core/presence" 24 "github.com/juju/juju/worker/introspection/pprof" 25 ) 26 27 var logger = loggo.GetLogger("juju.worker.introspection") 28 29 // DepEngineReporter provides insight into the running dependency engine of the agent. 30 type DepEngineReporter interface { 31 // Report returns a map describing the state of the receiver. It is expected 32 // to be goroutine-safe. 33 Report() map[string]interface{} 34 } 35 36 // IntrospectionReporter provides a simple method that the introspection 37 // worker will output for the entity. 38 type IntrospectionReporter interface { 39 IntrospectionReport() string 40 } 41 42 // Config describes the arguments required to create the introspection worker. 43 type Config struct { 44 SocketName string 45 DepEngine DepEngineReporter 46 StatePool IntrospectionReporter 47 PubSub IntrospectionReporter 48 MachineLock machinelock.Lock 49 PrometheusGatherer prometheus.Gatherer 50 Presence presence.Recorder 51 } 52 53 // Validate checks the config values to assert they are valid to create the worker. 54 func (c *Config) Validate() error { 55 if c.SocketName == "" { 56 return errors.NotValidf("empty SocketName") 57 } 58 if c.PrometheusGatherer == nil { 59 return errors.NotValidf("nil PrometheusGatherer") 60 } 61 return nil 62 } 63 64 // socketListener is a worker and constructed with NewWorker. 65 type socketListener struct { 66 tomb tomb.Tomb 67 listener *net.UnixListener 68 depEngine DepEngineReporter 69 statePool IntrospectionReporter 70 pubsub IntrospectionReporter 71 machineLock machinelock.Lock 72 prometheusGatherer prometheus.Gatherer 73 presence presence.Recorder 74 done chan struct{} 75 } 76 77 // NewWorker starts an http server listening on an abstract domain socket 78 // which will be created with the specified name. 79 func NewWorker(config Config) (worker.Worker, error) { 80 if err := config.Validate(); err != nil { 81 return nil, errors.Trace(err) 82 } 83 if runtime.GOOS != "linux" { 84 return nil, errors.NotSupportedf("os %q", runtime.GOOS) 85 } 86 87 path := "@" + config.SocketName 88 addr, err := net.ResolveUnixAddr("unix", path) 89 if err != nil { 90 return nil, errors.Annotate(err, "unable to resolve unix socket") 91 } 92 93 l, err := net.ListenUnix("unix", addr) 94 if err != nil { 95 return nil, errors.Annotate(err, "unable to listen on unix socket") 96 } 97 logger.Debugf("introspection worker listening on %q", path) 98 99 w := &socketListener{ 100 listener: l, 101 depEngine: config.DepEngine, 102 statePool: config.StatePool, 103 pubsub: config.PubSub, 104 machineLock: config.MachineLock, 105 prometheusGatherer: config.PrometheusGatherer, 106 presence: config.Presence, 107 done: make(chan struct{}), 108 } 109 go w.serve() 110 w.tomb.Go(w.run) 111 return w, nil 112 } 113 114 func (w *socketListener) serve() { 115 mux := http.NewServeMux() 116 RegisterHTTPHandlers( 117 ReportSources{ 118 DependencyEngine: w.depEngine, 119 StatePool: w.statePool, 120 PubSub: w.pubsub, 121 MachineLock: w.machineLock, 122 PrometheusGatherer: w.prometheusGatherer, 123 Presence: w.presence, 124 }, mux.Handle) 125 126 srv := http.Server{Handler: mux} 127 logger.Debugf("stats worker now serving") 128 defer logger.Debugf("stats worker serving finished") 129 defer close(w.done) 130 srv.Serve(w.listener) 131 } 132 133 func (w *socketListener) run() error { 134 defer logger.Debugf("stats worker finished") 135 <-w.tomb.Dying() 136 logger.Debugf("stats worker closing listener") 137 w.listener.Close() 138 // Don't mark the worker as done until the serve goroutine has finished. 139 <-w.done 140 return nil 141 } 142 143 // Kill implements worker.Worker. 144 func (w *socketListener) Kill() { 145 w.tomb.Kill(nil) 146 } 147 148 // Wait implements worker.Worker. 149 func (w *socketListener) Wait() error { 150 return w.tomb.Wait() 151 } 152 153 // ReportSources are the various information sources that are exposed 154 // through the introspection facility. 155 type ReportSources struct { 156 DependencyEngine DepEngineReporter 157 StatePool IntrospectionReporter 158 PubSub IntrospectionReporter 159 MachineLock machinelock.Lock 160 PrometheusGatherer prometheus.Gatherer 161 Presence presence.Recorder 162 } 163 164 // AddHandlers calls the given function with http.Handlers 165 // that serve agent introspection requests. The function will 166 // be called with a path; the function may alter the path 167 // as it sees fit. 168 func RegisterHTTPHandlers( 169 sources ReportSources, 170 handle func(path string, h http.Handler), 171 ) { 172 handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) 173 handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) 174 handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) 175 handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) 176 handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) 177 handle("/depengine", depengineHandler{sources.DependencyEngine}) 178 handle("/statepool", introspectionReporterHandler{ 179 name: "State Pool Report", 180 reporter: sources.StatePool, 181 }) 182 handle("/pubsub", introspectionReporterHandler{ 183 name: "PubSub Report", 184 reporter: sources.PubSub, 185 }) 186 handle("/metrics/", promhttp.HandlerFor(sources.PrometheusGatherer, promhttp.HandlerOpts{})) 187 // Unit agents don't have a presence recorder to pass in. 188 if sources.Presence != nil { 189 handle("/presence/", presenceHandler{sources.Presence}) 190 } 191 handle("/machinelock/", machineLockHandler{sources.MachineLock}) 192 } 193 194 type depengineHandler struct { 195 reporter DepEngineReporter 196 } 197 198 // ServeHTTP is part of the http.Handler interface. 199 func (h depengineHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 200 if h.reporter == nil { 201 w.WriteHeader(http.StatusNotFound) 202 fmt.Fprintln(w, "missing dependency engine reporter") 203 return 204 } 205 bytes, err := yaml.Marshal(h.reporter.Report()) 206 if err != nil { 207 w.WriteHeader(http.StatusInternalServerError) 208 fmt.Fprintf(w, "error: %v\n", err) 209 return 210 } 211 212 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 213 214 fmt.Fprint(w, "Dependency Engine Report\n\n") 215 w.Write(bytes) 216 } 217 218 type machineLockHandler struct { 219 lock machinelock.Lock 220 } 221 222 // ServeHTTP is part of the http.Handler interface. 223 func (h machineLockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 224 if h.lock == nil { 225 w.WriteHeader(http.StatusNotFound) 226 fmt.Fprintln(w, "missing machine lock reporter") 227 return 228 } 229 var args []machinelock.ReportOption 230 q := r.URL.Query() 231 if v := q.Get("yaml"); v != "" { 232 args = append(args, machinelock.ShowDetailsYAML) 233 } 234 if v := q.Get("history"); v != "" { 235 args = append(args, machinelock.ShowHistory) 236 } 237 if v := q.Get("stack"); v != "" { 238 args = append(args, machinelock.ShowStack) 239 } 240 241 content, err := h.lock.Report(args...) 242 if err != nil { 243 w.WriteHeader(http.StatusInternalServerError) 244 fmt.Fprintf(w, "error: %v\n", err) 245 return 246 } 247 248 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 249 fmt.Fprint(w, content) 250 } 251 252 type introspectionReporterHandler struct { 253 name string 254 reporter IntrospectionReporter 255 } 256 257 // ServeHTTP is part of the http.Handler interface. 258 func (h introspectionReporterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 259 if h.reporter == nil { 260 w.WriteHeader(http.StatusNotFound) 261 fmt.Fprintf(w, "%s: missing reporter\n", h.name) 262 return 263 } 264 265 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 266 267 fmt.Fprintf(w, "%s:\n\n", h.name) 268 fmt.Fprint(w, h.reporter.IntrospectionReport()) 269 } 270 271 type presenceHandler struct { 272 presence presence.Recorder 273 } 274 275 // ServeHTTP is part of the http.Handler interface. 276 func (h presenceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 277 if h.presence == nil || !h.presence.IsEnabled() { 278 w.WriteHeader(http.StatusNotFound) 279 w.Write([]byte("agent is not an apiserver\n")) 280 return 281 } 282 283 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 284 285 tw := output.TabWriter(w) 286 wrapper := output.Wrapper{tw} 287 288 // Could be smart here and switch on the request accept header. 289 connections := h.presence.Connections() 290 models := connections.Models() 291 sort.Strings(models) 292 293 for _, name := range models { 294 wrapper.Println("[" + name + "]") 295 wrapper.Println() 296 wrapper.Println("AGENT", "SERVER", "CONN ID", "STATUS") 297 values := connections.ForModel(name).Values() 298 sort.Sort(ValueSort(values)) 299 for _, value := range values { 300 agentName := value.Agent 301 if value.ControllerAgent { 302 agentName += " (controller)" 303 } 304 wrapper.Println(agentName, value.Server, value.ConnectionID, value.Status) 305 } 306 wrapper.Println() 307 } 308 tw.Flush() 309 } 310 311 type ValueSort []presence.Value 312 313 func (a ValueSort) Len() int { return len(a) } 314 func (a ValueSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 315 func (a ValueSort) Less(i, j int) bool { 316 // Sort by agent, then server, then connection id 317 if a[i].Agent != a[j].Agent { 318 return a[i].Agent < a[j].Agent 319 } 320 if a[i].Server != a[j].Server { 321 return a[i].Server < a[j].Server 322 } 323 return a[i].ConnectionID < a[j].ConnectionID 324 }