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  }