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