1
0
Fork 0
mirror of https://github.com/Luzifer/mondash.git synced 2024-12-23 12:31:18 +00:00
mondash/structs.go

275 lines
6.3 KiB
Go
Raw Normal View History

2015-02-07 18:32:44 +00:00
package main
import (
"encoding/json"
"errors"
"log"
2015-02-23 01:16:40 +00:00
"sort"
"strconv"
2015-02-07 18:32:44 +00:00
"time"
2015-07-06 19:41:21 +00:00
"github.com/Luzifer/mondash/storage"
2015-02-07 18:32:44 +00:00
)
2015-02-20 18:41:36 +00:00
type dashboard struct {
2015-02-07 18:32:44 +00:00
DashboardID string `json:"-"`
APIKey string `json:"api_key"`
2015-02-20 18:41:36 +00:00
Metrics dashboardMetrics `json:"metrics"`
2015-07-06 19:41:21 +00:00
storage storage.Storage
2015-02-07 18:32:44 +00:00
}
2015-07-06 19:41:21 +00:00
func loadDashboard(dashid string, store storage.Storage) (*dashboard, error) {
data, err := store.Get(dashid)
2015-02-07 18:32:44 +00:00
if err != nil {
2015-02-20 18:41:36 +00:00
return &dashboard{}, errors.New("Dashboard not found")
2015-02-07 18:32:44 +00:00
}
2015-07-06 19:41:21 +00:00
tmp := &dashboard{
DashboardID: dashid,
storage: store,
}
2015-02-20 18:47:36 +00:00
_ = json.Unmarshal(data, tmp)
2015-02-07 18:32:44 +00:00
return tmp, nil
}
2015-02-20 18:41:36 +00:00
func (d *dashboard) Save() {
2015-02-07 18:32:44 +00:00
data, err := json.Marshal(d)
if err != nil {
log.Printf("Error while marshalling dashboard: %s", err)
return
2015-02-07 18:32:44 +00:00
}
2015-07-06 19:41:21 +00:00
err = d.storage.Put(d.DashboardID, data)
2015-02-07 18:32:44 +00:00
if err != nil {
log.Printf("Error while storing dashboard: %s", err)
2015-02-07 18:32:44 +00:00
}
}
2015-02-20 18:41:36 +00:00
type dashboardMetrics []*dashboardMetric
2015-02-07 18:32:44 +00:00
2015-02-20 18:41:36 +00:00
func (a dashboardMetrics) Len() int { return len(a) }
func (a dashboardMetrics) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a dashboardMetrics) Less(i, j int) bool {
2015-02-07 18:32:44 +00:00
return a[i].HistoricalData[0].Time.Before(a[j].HistoricalData[0].Time)
}
2015-02-20 18:41:36 +00:00
type dashboardMetric struct {
2015-02-07 18:32:44 +00:00
MetricID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Status string `json:"status"`
Value float64 `json:"value,omitifempty"`
2015-02-07 18:32:44 +00:00
Expires int64 `json:"expires,omitifempty"`
Freshness int64 `json:"freshness,omitifempty"`
IgnoreMAD bool `json:"ignore_mad"`
2016-03-27 19:13:36 +00:00
HideMAD bool `json:"hide_mad"`
2015-02-20 18:41:36 +00:00
HistoricalData dashboardMetricHistory `json:"history,omitifempty"`
Meta dashboardMetricMeta `json:"meta,omitifempty"`
2015-02-07 18:32:44 +00:00
}
2015-02-20 18:41:36 +00:00
type dashboardMetricStatus struct {
2015-02-07 18:32:44 +00:00
Time time.Time `json:"time"`
Status string `json:"status"`
Value float64 `json:"value"`
2015-02-07 18:32:44 +00:00
}
2015-02-20 18:41:36 +00:00
type dashboardMetricMeta struct {
2015-02-07 18:32:44 +00:00
LastUpdate time.Time
LastOK time.Time
PercOK float64
PercWarn float64
PercCrit float64
}
2015-02-20 18:41:36 +00:00
type dashboardMetricHistory []dashboardMetricStatus
2015-02-07 18:32:44 +00:00
2015-02-20 18:41:36 +00:00
func newDashboardMetric() *dashboardMetric {
return &dashboardMetric{
2015-02-07 18:32:44 +00:00
Status: "Unknown",
Expires: 604800,
Freshness: 3600,
2015-02-20 18:41:36 +00:00
HistoricalData: dashboardMetricHistory{},
Meta: dashboardMetricMeta{},
2015-02-07 18:32:44 +00:00
}
}
2015-02-23 01:16:40 +00:00
func median(values []float64) float64 {
sort.Float64s(values)
2015-02-23 02:20:14 +00:00
if len(values) == 1 {
return values[0]
}
2015-02-23 01:16:40 +00:00
// If even, take an average
if len(values)%2 == 0 {
return 0.5*values[len(values)/2] + 0.5*values[len(values)/2-1]
}
2015-02-23 02:20:14 +00:00
log.Printf("len(values)=%v, len(values)/2=%v\n", len(values), len(values)/2)
2015-02-23 01:16:40 +00:00
return values[len(values)/2-1]
}
func absoluteValue(value float64) float64 {
if value < 0 {
value = -value
}
return value
}
func absoluteDeviation(values []float64) []float64 {
medianValue := median(values)
deviation := make([]float64, len(values))
2015-07-06 19:44:13 +00:00
for i := range values {
2015-02-23 01:16:40 +00:00
deviation[i] = absoluteValue(values[i] - medianValue)
}
return deviation
}
func (dm *dashboardMetric) getValueArray() []float64 {
2015-07-06 19:44:13 +00:00
values := []float64{}
2015-02-23 01:16:40 +00:00
for _, v := range dm.HistoricalData {
values = append(values, v.Value)
}
return values
}
func (dm *dashboardMetric) Median() float64 {
return median(dm.getValueArray())
}
func (dm *dashboardMetric) MedianAbsoluteDeviation() (float64, float64) {
values := dm.getValueArray()
medianValue := dm.Median()
return medianValue, median(absoluteDeviation(values))
}
func (dm *dashboardMetric) MadMultiplier() float64 {
medianValue, MAD := dm.MedianAbsoluteDeviation()
return absoluteValue(dm.Value-medianValue) / MAD
}
func (dm *dashboardMetric) StatisticalStatus() string {
mult := dm.MadMultiplier()
if mult > 4 {
return "Critical"
} else if mult > 3 {
return "Warning"
}
return "OK"
}
func (dm *dashboardMetric) PreferredStatus() string {
2016-03-27 20:18:13 +00:00
if dm.Meta.LastUpdate.Before(time.Now().Add(-1 * time.Duration(dm.Freshness) * time.Second)) {
return "Unknown"
}
if dm.IgnoreMAD {
return dm.Status
}
return dm.StatisticalStatus()
}
func (dm *dashboardMetric) LabelHistory() []string {
s := []string{}
labelStart := len(dm.HistoricalData) - 60
if labelStart < 0 {
labelStart = 0
}
for _, v := range dm.HistoricalData[labelStart:] {
s = append(s, strconv.Itoa(int(v.Time.Unix())))
}
return s
}
func (dm *dashboardMetric) DataHistory() []string {
s := []string{}
dataStart := len(dm.HistoricalData) - 60
if dataStart < 0 {
dataStart = 0
}
for _, v := range dm.HistoricalData[dataStart:] {
s = append(s, strconv.FormatFloat(v.Value, 'g', 4, 64))
}
return s
}
2015-02-20 18:41:36 +00:00
func (dm *dashboardMetric) Update(m *dashboardMetric) {
2015-02-07 18:32:44 +00:00
dm.Title = m.Title
dm.Description = m.Description
dm.Status = m.Status
dm.Value = m.Value
dm.IgnoreMAD = m.IgnoreMAD
2016-03-27 19:13:36 +00:00
dm.HideMAD = m.HideMAD
2015-02-07 18:32:44 +00:00
if m.Expires != 0 {
dm.Expires = m.Expires
}
if m.Freshness != 0 {
dm.Freshness = m.Freshness
}
dm.HistoricalData = append(dm.HistoricalData, dashboardMetricStatus{
2015-02-07 18:32:44 +00:00
Time: time.Now(),
Status: m.Status,
Value: m.Value,
})
2015-02-07 18:32:44 +00:00
countStatus := make(map[string]float64)
expired := time.Now().Add(time.Duration(dm.Expires*-1) * time.Second)
2015-02-20 18:41:36 +00:00
tmp := dashboardMetricHistory{}
2015-02-07 18:32:44 +00:00
for _, s := range dm.HistoricalData {
if s.Time.After(expired) {
tmp = append(tmp, s)
countStatus[s.Status] = countStatus[s.Status] + 1
countStatus["Total"] = countStatus["Total"] + 1
if dm.Meta.LastOK.Before(s.Time) && s.Status == "OK" {
dm.Meta.LastOK = s.Time
}
}
}
dm.HistoricalData = tmp
dm.Meta.LastUpdate = time.Now()
if countStatus["Total"] > 0 {
dm.Meta.PercCrit = countStatus["Critical"] / countStatus["Total"] * 100
dm.Meta.PercWarn = countStatus["Warning"] / countStatus["Total"] * 100
dm.Meta.PercOK = countStatus["OK"] / countStatus["Total"] * 100
}
2015-02-07 18:32:44 +00:00
}
2015-02-20 18:41:36 +00:00
func (dm *dashboardMetric) IsValid() (bool, string) {
2015-02-07 18:32:44 +00:00
if dm.Expires > 604800 || dm.Expires < 0 {
return false, "Expires not in range 0 < x < 640800"
}
if dm.Freshness > 604800 || dm.Freshness < 0 {
return false, "Freshness not in range 0 < x < 640800"
}
if !stringInSlice(dm.Status, []string{"OK", "Warning", "Critical", "Unknowm"}) {
return false, "Status not allowed"
}
if len(dm.Title) > 512 || len(dm.Description) > 1024 {
return false, "Title or Description too long"
}
return true, ""
}