first commit
commit
9824775015
@ -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
|
@ -0,0 +1 @@
|
|||||||
|
- =apt-get install libstatgrab-dev=
|
@ -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))
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// #cgo LDFLAGS: -lstatgrab
|
||||||
|
// #include <statgrab.h>
|
||||||
|
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
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue