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  }