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 }