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  }