2021-08-20 12:54:11 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2021-09-07 09:24:50 +02:00
|
|
|
"fmt"
|
2021-08-20 12:54:11 +02:00
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
)
|
|
|
|
|
2021-08-31 15:17:36 +02:00
|
|
|
// Example:
|
2021-09-01 08:48:35 +02:00
|
|
|
// {
|
|
|
|
// "metrics": ["flops_sp", "flops_dp"]
|
|
|
|
// "selectors": [["emmy", "host123", "cpu", "0"], ["emmy", "host123", "cpu", "1"]]
|
|
|
|
// }
|
|
|
|
type ApiRequestBody struct {
|
|
|
|
Metrics []string `json:"metrics"`
|
2021-09-08 12:17:10 +02:00
|
|
|
Selectors []Selector `json:"selectors"`
|
2021-08-20 12:54:11 +02:00
|
|
|
}
|
|
|
|
|
2021-08-31 15:17:36 +02:00
|
|
|
type ApiMetricData struct {
|
|
|
|
From int64 `json:"from"`
|
|
|
|
To int64 `json:"to"`
|
|
|
|
Data []Float `json:"data"`
|
2021-08-20 12:54:11 +02:00
|
|
|
}
|
|
|
|
|
2021-09-01 08:48:35 +02:00
|
|
|
type ApiStatsData struct {
|
|
|
|
From int64 `json:"from"`
|
|
|
|
To int64 `json:"to"`
|
|
|
|
Samples int `json:"samples"`
|
|
|
|
Avg Float `json:"avg"`
|
|
|
|
Min Float `json:"min"`
|
|
|
|
Max Float `json:"max"`
|
|
|
|
}
|
|
|
|
|
2021-08-24 10:41:30 +02:00
|
|
|
func handleTimeseries(rw http.ResponseWriter, r *http.Request) {
|
2021-08-20 12:54:11 +02:00
|
|
|
vars := mux.Vars(r)
|
|
|
|
from, err := strconv.ParseInt(vars["from"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
to, err := strconv.ParseInt(vars["to"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-08-31 15:17:36 +02:00
|
|
|
if r.Method != http.MethodPost {
|
|
|
|
http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed)
|
2021-08-20 12:54:11 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-08-31 15:17:36 +02:00
|
|
|
bodyDec := json.NewDecoder(r.Body)
|
|
|
|
var reqBody ApiRequestBody
|
|
|
|
err = bodyDec.Decode(&reqBody)
|
2021-08-24 10:41:30 +02:00
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-09-01 08:48:35 +02:00
|
|
|
res := make([]map[string]ApiMetricData, 0, len(reqBody.Selectors))
|
|
|
|
for _, selector := range reqBody.Selectors {
|
2021-08-31 15:17:36 +02:00
|
|
|
metrics := make(map[string]ApiMetricData)
|
2021-09-01 08:48:35 +02:00
|
|
|
for _, metric := range reqBody.Metrics {
|
|
|
|
data, f, t, err := memoryStore.Read(selector, metric, from, to)
|
2021-08-24 10:41:30 +02:00
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-08-31 15:17:36 +02:00
|
|
|
metrics[metric] = ApiMetricData{
|
|
|
|
From: f,
|
|
|
|
To: t,
|
|
|
|
Data: data,
|
|
|
|
}
|
2021-08-24 10:41:30 +02:00
|
|
|
}
|
2021-08-31 15:17:36 +02:00
|
|
|
res = append(res, metrics)
|
2021-08-24 10:41:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
rw.Header().Set("Content-Type", "application/json")
|
2021-08-31 15:17:36 +02:00
|
|
|
err = json.NewEncoder(rw).Encode(res)
|
2021-08-24 10:41:30 +02:00
|
|
|
if err != nil {
|
|
|
|
log.Println(err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-01 08:48:35 +02:00
|
|
|
func handleStats(rw http.ResponseWriter, r *http.Request) {
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
from, err := strconv.ParseInt(vars["from"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
to, err := strconv.ParseInt(vars["to"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Method != http.MethodPost {
|
|
|
|
http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
bodyDec := json.NewDecoder(r.Body)
|
|
|
|
var reqBody ApiRequestBody
|
|
|
|
err = bodyDec.Decode(&reqBody)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
res := make([]map[string]ApiStatsData, 0, len(reqBody.Selectors))
|
|
|
|
for _, selector := range reqBody.Selectors {
|
|
|
|
metrics := make(map[string]ApiStatsData)
|
|
|
|
for _, metric := range reqBody.Metrics {
|
|
|
|
stats, f, t, err := memoryStore.Stats(selector, metric, from, to)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
metrics[metric] = ApiStatsData{
|
|
|
|
From: f,
|
|
|
|
To: t,
|
|
|
|
Samples: stats.Samples,
|
|
|
|
Avg: stats.Avg,
|
|
|
|
Min: stats.Min,
|
|
|
|
Max: stats.Max,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
res = append(res, metrics)
|
|
|
|
}
|
|
|
|
|
|
|
|
rw.Header().Set("Content-Type", "application/json")
|
|
|
|
err = json.NewEncoder(rw).Encode(res)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-07 09:24:50 +02:00
|
|
|
func handleFree(rw http.ResponseWriter, r *http.Request) {
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
to, err := strconv.ParseInt(vars["to"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: lastCheckpoint might be modified by different go-routines.
|
|
|
|
// Load it using the sync/atomic package?
|
|
|
|
freeUpTo := lastCheckpoint.Unix()
|
|
|
|
if to < freeUpTo {
|
|
|
|
freeUpTo = to
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Method != http.MethodPost {
|
|
|
|
http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
bodyDec := json.NewDecoder(r.Body)
|
2021-09-08 12:17:10 +02:00
|
|
|
var selectors []Selector
|
2021-09-07 09:24:50 +02:00
|
|
|
err = bodyDec.Decode(&selectors)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
n := 0
|
|
|
|
for _, sel := range selectors {
|
|
|
|
bn, err := memoryStore.Free(sel, freeUpTo)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
n += bn
|
|
|
|
}
|
|
|
|
|
|
|
|
rw.WriteHeader(http.StatusOK)
|
|
|
|
rw.Write([]byte(fmt.Sprintf("buffers freed: %d\n", n)))
|
|
|
|
}
|
|
|
|
|
2021-09-08 09:08:51 +02:00
|
|
|
func StartApiServer(address string, ctx context.Context) error {
|
2021-08-20 12:54:11 +02:00
|
|
|
r := mux.NewRouter()
|
|
|
|
|
2021-08-31 15:17:36 +02:00
|
|
|
r.HandleFunc("/api/{from:[0-9]+}/{to:[0-9]+}/timeseries", handleTimeseries)
|
2021-09-01 08:48:35 +02:00
|
|
|
r.HandleFunc("/api/{from:[0-9]+}/{to:[0-9]+}/stats", handleStats)
|
2021-09-07 09:24:50 +02:00
|
|
|
r.HandleFunc("/api/{to:[0-9]+}/free", handleFree)
|
2021-08-20 12:54:11 +02:00
|
|
|
|
|
|
|
server := &http.Server{
|
|
|
|
Handler: r,
|
|
|
|
Addr: address,
|
|
|
|
WriteTimeout: 15 * time.Second,
|
|
|
|
ReadTimeout: 15 * time.Second,
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
log.Printf("API http endpoint listening on '%s'\n", address)
|
|
|
|
err := server.ListenAndServe()
|
|
|
|
if err != nil && err != http.ErrServerClosed {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
for {
|
2021-09-08 09:08:51 +02:00
|
|
|
_ = <-ctx.Done()
|
2021-08-20 12:54:11 +02:00
|
|
|
err := server.Shutdown(context.Background())
|
|
|
|
log.Println("API server shut down")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|