github.com/grahambrereton-form3/tilt@v0.10.18/integration/stats_server.go (about)

     1  package integration
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"net"
     9  	"net/http"
    10  	"time"
    11  
    12  	"github.com/gorilla/mux"
    13  	"github.com/windmilleng/wmclient/pkg/analytics"
    14  )
    15  
    16  type MemoryStatsServer struct {
    17  	ma       *analytics.MemoryAnalytics
    18  	ss       StatsServer
    19  	listener net.Listener
    20  }
    21  
    22  func StartMemoryStatsServer() (mss *MemoryStatsServer, port int, err error) {
    23  	mss = &MemoryStatsServer{}
    24  	mss.ma = analytics.NewMemoryAnalytics()
    25  	sr := &MemoryStatsReporter{mss.ma}
    26  	mss.ss = NewStatsServer(sr)
    27  
    28  	mux := http.NewServeMux()
    29  	mux.Handle("/", mss.ss.Router())
    30  
    31  	mss.listener, err = net.Listen("tcp", ":0")
    32  	if err != nil {
    33  		return nil, 0, err
    34  	}
    35  
    36  	go func() {
    37  		err := http.Serve(mss.listener, mux)
    38  		if err != nil {
    39  			fmt.Println(err)
    40  		}
    41  	}()
    42  
    43  	return mss, mss.listener.Addr().(*net.TCPAddr).Port, nil
    44  }
    45  
    46  func (mss *MemoryStatsServer) TearDown() error {
    47  	return mss.listener.Close()
    48  }
    49  
    50  type MemoryStatsReporter struct {
    51  	a *analytics.MemoryAnalytics
    52  }
    53  
    54  var _ StatsReporter = &MemoryStatsReporter{}
    55  
    56  func (sr *MemoryStatsReporter) Close() error {
    57  	return nil
    58  }
    59  
    60  func (sr *MemoryStatsReporter) Timing(name string, value time.Duration, tags map[string]string, rate float64) error {
    61  	sr.a.Timer(name, value, tags)
    62  	return nil
    63  }
    64  
    65  func (sr *MemoryStatsReporter) Count(name string, value int64, tags map[string]string, rate float64) error {
    66  	sr.a.Count(name, tags, int(value))
    67  	return nil
    68  }
    69  
    70  func (sr *MemoryStatsReporter) Incr(name string, tags map[string]string, rate float64) error {
    71  	sr.a.Incr(name, tags)
    72  	return nil
    73  }
    74  
    75  const keyTime = "time"
    76  
    77  type StatsReporter interface {
    78  	io.Closer
    79  	Timing(name string, value time.Duration, tags map[string]string, rate float64) error
    80  	Count(name string, value int64, tags map[string]string, rate float64) error
    81  	Incr(name string, tags map[string]string, rate float64) error
    82  }
    83  
    84  // A small http server that decodes json and sends it to our metrics services
    85  type StatsServer struct {
    86  	router   *mux.Router
    87  	reporter StatsReporter
    88  }
    89  
    90  func NewStatsServer(stats StatsReporter) StatsServer {
    91  	r := mux.NewRouter().UseEncodedPath()
    92  
    93  	s := StatsServer{router: r, reporter: stats}
    94  	r.HandleFunc("/report", s.Report).Methods("POST")
    95  	r.HandleFunc("/", s.Index).Methods("GET")
    96  	return s
    97  }
    98  
    99  func (s StatsServer) Router() *mux.Router {
   100  	return s.router
   101  }
   102  
   103  func (s StatsServer) Index(w http.ResponseWriter, r *http.Request) {
   104  	_, _ = w.Write([]byte("OK"))
   105  }
   106  
   107  func (s StatsServer) Report(w http.ResponseWriter, r *http.Request) {
   108  	now := time.Now()
   109  
   110  	events, err := parseJSON(r)
   111  	if err != nil {
   112  		http.Error(w, fmt.Sprintf("JSON decode: %v", err), http.StatusBadRequest)
   113  		return
   114  	}
   115  
   116  	for _, event := range events {
   117  		name := event.Name()
   118  		if name == "" {
   119  			http.Error(w, fmt.Sprintf("Missing name: %v", event), http.StatusBadRequest)
   120  			return
   121  		}
   122  
   123  		event[keyTime] = now.Format(time.RFC3339)
   124  
   125  		dur := event.Duration()
   126  		if dur == 0 {
   127  			// TODO: support count
   128  
   129  			err := s.reporter.Incr(name, event.Tags(), 1)
   130  			if err != nil {
   131  				log.Printf("Report error: %v", err)
   132  			}
   133  		} else {
   134  			err := s.reporter.Timing(name, dur, event.Tags(), 1)
   135  			if err != nil {
   136  				log.Printf("Report error: %v", err)
   137  			}
   138  		}
   139  
   140  	}
   141  }
   142  
   143  type Event map[string]interface{}
   144  
   145  func (e Event) Name() string {
   146  	name, ok := e["name"].(string)
   147  	if !ok {
   148  		return ""
   149  	}
   150  	return name
   151  }
   152  
   153  func (e Event) Duration() time.Duration {
   154  	// all json numbers are float64
   155  	dur, ok := e["duration"].(float64)
   156  	if !ok {
   157  		return 0
   158  	}
   159  	return time.Duration(dur)
   160  }
   161  
   162  func (e Event) Tags() map[string]string {
   163  	tags := make(map[string]string)
   164  	for k, v := range e {
   165  		if k == "name" || k == "duration" {
   166  			continue
   167  		}
   168  		if vStr, ok := v.(string); ok {
   169  			tags[k] = vStr
   170  		}
   171  	}
   172  	return tags
   173  }
   174  
   175  func parseJSON(r *http.Request) ([]Event, error) {
   176  	d := json.NewDecoder(r.Body)
   177  	result := make([]Event, 0)
   178  	for d.More() {
   179  		data := make(map[string]interface{})
   180  		err := d.Decode(&data)
   181  		if err != nil {
   182  			return nil, err
   183  		}
   184  
   185  		result = append(result, Event(data))
   186  	}
   187  	return result, nil
   188  }