commit 98247750157d809dee381bde1290e88fb1023502 Author: Fabrizio Furnari Date: Tue Dec 1 20:21:13 2020 +0100 first commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..63fce66 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +#GOFLAGS="" +LDFLAGS=-ldflags "-s -w" +BINARY=metrorama + +.DEFAULT_GOAL: $(BINARY) + +$(BINARY): clean + go build ${GOFLAGS} ${LDFLAGS} -o ${BINARY} + +install: + go install ${GOFLAGS} ${LDFLAGS} -o ${BINARY} + +clean: + if [ -f ${BINARY} ]; then rm ${BINARY} ; fi + +run: clean $(BINARY) + ./$(BINARY) + +.phony: clean install diff --git a/README.org b/README.org new file mode 100644 index 0000000..3d48c03 --- /dev/null +++ b/README.org @@ -0,0 +1 @@ +- =apt-get install libstatgrab-dev= diff --git a/main.go b/main.go new file mode 100644 index 0000000..974b057 --- /dev/null +++ b/main.go @@ -0,0 +1,164 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "strconv" + "time" +) + +var ( + listen = flag.String("l", "0.0.0.0:8080", "Listen address") + rate = flag.Int("r", 500, "Sample rate (ms)") + timeout = flag.Int("t", 10, "Total timeout (s)") + minRate = 500 + maxTimeout = 600 +) + +// TODO: bench test with pointer +func getMetrics() (Metric, error) { + log.Println("Started getMetrics") + var s = Stat{} + m := Metric{} + cpustats := s.CPUStats() + m.Load1m = cpustats.LoadMin1 + log.Printf("Metric: %v\n", m) + return m, nil +} + +func (t *Test) collectData() { + timeo := time.After(t.Timeout) + t.Status = "STARTED" + for { + select { + case tick := <-t.ticker.C: + t.Status = "ONGOING" + + m, err := getMetrics() + if err != nil { + log.Printf("Cannot collect metrics: %s\n", err) + return + } + t.Result = append(t.Result, m) + log.Printf("%v: %v\n", tick, m) + //t.Result += fmt.Sprintf("%v: Collecting data\n", tick) + case <-t.Done: + log.Printf("DONE\n") + t.Status = "COMPLETED" + t.Unlock() + return + case <-timeo: + log.Printf("TIMEOUT\n") + t.Status = "TIMEOUT" + t.Unlock() + return + } + } + return // really needed? +} + +func (t *Test) startTest(w http.ResponseWriter, req *http.Request) { + // Check if another collectData is running (better: check sync.Mutex) + if t.Status == "ONGOING" { + http.Error(w, "Another test is currently ongoing, stop or try again later", 409) //better error code + return + } + // Check parameters + qRate := req.URL.Query().Get("rate") + if qRate != "" { + qRate, err := strconv.Atoi(qRate) + if err != nil { + log.Printf("Error while converting rate: %s\n", err.Error()) + http.Error(w, "Error: error while converting rate", 400) + return + } + if qRate < minRate { + log.Printf("Requested rate is too low: %d (min: %d)\n", qRate, minRate) + http.Error(w, "Error: requested rate too low", 400) + return + } + t.Rate = qRate + } + + qTimeout := req.URL.Query().Get("timeout") + if qTimeout != "" { + qTimeout, err := strconv.Atoi(qTimeout) + if err != nil { + log.Printf("Error while converting timeout: %s\n", err.Error()) + http.Error(w, "Error: error while converting timeout", 400) + return + } + if qTimeout > maxTimeout { + log.Printf("Requested timeout is too high: %d (max: %d)\n", qTimeout, maxTimeout) + http.Error(w, "Error: requested timeout too high", 400) + return + } + t.Timeout = time.Duration(qTimeout) * time.Second + } + + qName := req.URL.Query().Get("name") + if qName == "" { + t.Name = fmt.Sprintf("test-%d", time.Now().Local().Unix()) + } else { + t.Name = qName + } + // Check if previous test is present + if len(t.Result) == 0 { + log.Println("Cleaning previous test data") + t.Result = []Metric{} + } + // Lock to avoid multiple start + t.Lock() + fmt.Fprintf(w, "Test name: %s, rate: %d (ms), timeout: %d (s)\n", t.Name, t.Rate, t.Timeout/1000000000) + endTime := time.Now().Local().Add(t.Timeout) + fmt.Fprintf(w, "Test will end @%v\n", endTime.Format(time.RFC1123Z)) + go t.collectData() +} + +func (t *Test) stopTest(w http.ResponseWriter, req *http.Request) { + if t.Status != "ONGOING" { + e := fmt.Sprintf("Cannot stop test in %s status: try start first", t.Status) + http.Error(w, e, 400) + return + } + + t.ticker.Stop() + t.Done <- true + fmt.Fprintf(w, "Done\n") + fmt.Fprintf(w, "%v\n", t.Result) +} + +func (t *Test) testStatus(w http.ResponseWriter, req *http.Request) { + fmt.Fprintf(w, "%v\n", t.Result) + fmt.Fprintf(w, "Test %s, status: %s\n", t.Name, t.Status) +} + +func newTest() *Test { + log.Println("Creating new test") + // Not checking if rate and timeout are in value range because are set on CLI + t := Test{ + ticker: time.NewTicker(time.Duration(*rate) * time.Millisecond), + Done: make(chan bool), + Rate: *rate, + Timeout: time.Duration(*timeout) * time.Second, + Status: "CREATED", + } + return &t +} + +func main() { + + flag.Parse() + + t := newTest() + log.Printf("%+v\n", t) + + http.HandleFunc("/start", t.startTest) + http.HandleFunc("/status", t.testStatus) + http.HandleFunc("/stop", t.stopTest) + + log.Printf("Listening on %s\n", *listen) + log.Fatal(http.ListenAndServe(*listen, nil)) +} diff --git a/metrorama b/metrorama new file mode 100755 index 0000000..d40f642 Binary files /dev/null and b/metrorama differ diff --git a/stats.go b/stats.go new file mode 100644 index 0000000..90a2576 --- /dev/null +++ b/stats.go @@ -0,0 +1,43 @@ +package main + +// #cgo LDFLAGS: -lstatgrab +// #include +import "C" +import "time" + +var mainfunc = make(chan func()) + +func do(f func()) { + done := make(chan bool, 1) + mainfunc <- func() { + f() + done <- true + } + <-done +} + +func (s *Stat) CPUStats() *CPUStats { + s.Lock() + defer s.Unlock() + + var cpu *CPUStats + + do(func() { + cpup := C.sg_get_cpu_percents_of(C.sg_new_diff_cpu_percent, nil) + loadStat := C.sg_get_load_stats(nil) + cpu = &CPUStats{ + User: float64(cpup.user), + Kernel: float64(cpup.kernel), + Idle: float64(cpup.idle), + IOWait: float64(cpup.iowait), + Swap: float64(cpup.swap), + Nice: float64(cpup.nice), + LoadMin1: float64(loadStat.min1), + LoadMin5: float64(loadStat.min5), + LoadMin15: float64(loadStat.min15), + Period: time.Duration(int(cpup.time_taken)) * time.Second, + TimeTaken: time.Now(), + } + }) + return cpu +} diff --git a/struct.go b/struct.go new file mode 100644 index 0000000..bb50153 --- /dev/null +++ b/struct.go @@ -0,0 +1,44 @@ +package main + +import ( + "sync" + "time" +) + +type Test struct { + sync.Mutex + ticker *time.Ticker + Status string + Done chan bool + Rate int + Name string + Result []Metric + Timeout time.Duration +} + +type Metric struct { + Timestamp time.Time `json:"timestamp"` + Load1m float64 + Load5m float64 + Load15m float64 + FreeMem int +} + +type CPUStats struct { + User float64 + Kernel float64 + Idle float64 + IOWait float64 + Swap float64 + Nice float64 + LoadMin1 float64 + LoadMin5 float64 + LoadMin15 float64 + Period time.Duration + TimeTaken time.Time +} + +type Stat struct { + sync.Mutex + exitMessage chan bool +}