github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/cmd/usage/server/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "log" 9 "net/http" 10 "os" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/golang/protobuf/proto" 16 "github.com/gorilla/handlers" 17 pb "github.com/tickoalcantara12/micro/v3/cmd/usage/proto" 18 bolt "go.etcd.io/bbolt" 19 ) 20 21 var ( 22 db *bolt.DB 23 fd = "usage.db" 24 25 mtx sync.RWMutex 26 seen = map[string]uint64{} 27 ) 28 29 func setup() { 30 // setup db 31 d, err := bolt.Open(fd, 0600, &bolt.Options{Timeout: 1 * time.Second}) 32 if err != nil { 33 log.Fatal(err) 34 } 35 db = d 36 37 if err := db.Update(func(tx *bolt.Tx) error { 38 for _, b := range []string{"usage", "metrics"} { 39 if _, err := tx.CreateBucketIfNotExists([]byte(b)); err != nil { 40 return err 41 } 42 } 43 return nil 44 }); err != nil { 45 log.Fatal(err) 46 } 47 48 go flush() 49 } 50 51 func flush() { 52 for { 53 time.Sleep(time.Hour) 54 now := time.Now().UnixNano() 55 mtx.Lock() 56 for k, v := range seen { 57 d := uint64(now) - v 58 // 48 hours 59 if d > 1.728e14 { 60 delete(seen, k) 61 } 62 } 63 seen = make(map[string]uint64) 64 mtx.Unlock() 65 } 66 } 67 68 func process(w http.ResponseWriter, r *http.Request, u *pb.Usage) { 69 today := time.Now().Format("20060102") 70 key := fmt.Sprintf("%s-%s", u.Service, u.Id) 71 now := uint64(time.Now().UnixNano()) 72 73 mtx.Lock() 74 last := seen[key] 75 lastSeen := now - last 76 seen[key] = now 77 mtx.Unlock() 78 79 db.Update(func(tx *bolt.Tx) error { 80 b := tx.Bucket([]byte(`usage`)) 81 buf, err := proto.Marshal(u) 82 if err != nil { 83 return err 84 } 85 k := fmt.Sprintf("%d-%s", u.Timestamp, key) 86 // save this usage 87 if err := b.Put([]byte(k), buf); err != nil { 88 return err 89 } 90 91 // save daily usage 92 b = tx.Bucket([]byte(`metrics`)) 93 dailyKey := fmt.Sprintf("%s-%s", today, u.Service) 94 95 // get usage 96 v := b.Get([]byte(dailyKey)) 97 if v == nil { 98 // todo: don't overwrite this 99 u.Metrics.Count["services"] = uint64(1) 100 m, _ := proto.Marshal(u.Metrics) 101 return b.Put([]byte(dailyKey), m) 102 } 103 104 m := new(pb.Metrics) 105 if err := proto.Unmarshal(v, m); err != nil { 106 return err 107 } 108 109 // update request count 110 m.Count["requests"] += u.Metrics.Count["requests"] 111 m.Count["services"] += u.Metrics.Count["services"] 112 113 // not seen today add it 114 if lastSeen == 0 || lastSeen > 7.2e13 { 115 c := m.Count["instances"] 116 c++ 117 m.Count["instances"] = c 118 } 119 120 buf, err = proto.Marshal(m) 121 if err != nil { 122 return err 123 } 124 125 // store today-micro.api/new/cli/proxy 126 return b.Put([]byte(dailyKey), buf) 127 }) 128 } 129 130 func metrics(w http.ResponseWriter, r *http.Request) { 131 r.ParseForm() 132 prefix := time.Now().Add(time.Hour * -24).Format("20060102") 133 metrics := map[string]interface{}{} 134 135 if date := r.Form.Get("date"); len(date) >= 4 && len(date) <= 8 { 136 prefix = date 137 } 138 139 db.View(func(tx *bolt.Tx) error { 140 c := tx.Bucket([]byte(`metrics`)).Cursor() 141 142 for k, v := c.Seek([]byte(prefix)); k != nil && bytes.HasPrefix(k, []byte(prefix)); k, v = c.Next() { 143 m := new(pb.Metrics) 144 proto.Unmarshal(v, m) 145 key := strings.TrimPrefix(string(k), prefix+"-") 146 metrics[key] = m 147 } 148 return nil 149 }) 150 151 var buf []byte 152 ct := r.Header.Get("Content-Type") 153 154 if v := r.Form.Get("pretty"); len(v) > 0 || ct != "application/json" { 155 buf, _ = json.MarshalIndent(metrics, "", "\t") 156 } else { 157 buf, _ = json.Marshal(metrics) 158 } 159 160 if len(buf) == 0 { 161 buf = []byte(`{}`) 162 } 163 164 w.Header().Set("Content-Type", "application/json") 165 w.Write(buf) 166 } 167 168 func handler(w http.ResponseWriter, r *http.Request) { 169 r.ParseForm() 170 171 // return metrics 172 if r.Method == "GET" { 173 metrics(w, r) 174 return 175 } 176 177 // require post for updates 178 if r.Method != "POST" { 179 return 180 } 181 if r.Header.Get("Content-Type") != "application/protobuf" { 182 return 183 } 184 185 if r.UserAgent() != "micro/usage" { 186 return 187 } 188 189 b, err := ioutil.ReadAll(r.Body) 190 if err != nil { 191 http.Error(w, err.Error(), 500) 192 return 193 } 194 u := new(pb.Usage) 195 if err := proto.Unmarshal(b, u); err != nil { 196 http.Error(w, err.Error(), 500) 197 return 198 } 199 go process(w, r, u) 200 } 201 202 func main() { 203 setup() 204 http.HandleFunc("/", handler) 205 206 lh := handlers.LoggingHandler(os.Stdout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 207 if strings.HasPrefix(r.URL.Path, "/usage") { 208 r.URL.Path = strings.TrimPrefix(r.URL.Path, "/usage") 209 } 210 http.DefaultServeMux.ServeHTTP(w, r) 211 })) 212 213 if err := http.ListenAndServe(":8091", lh); err != nil { 214 log.Fatal(err) 215 } 216 }