From 84297ff0a664e465ee6a578a8eb09862bfa590d2 Mon Sep 17 00:00:00 2001 From: Zain Hoda Date: Sun, 22 Feb 2015 14:29:48 -0500 Subject: [PATCH 1/6] Change hard-coded EUWest to USEast In the future this should be read as an environment variable --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index b8a4a87..d297301 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,7 @@ func main() { if err != nil { log.Fatal(err) } - s3Conn := s3.New(awsAuth, aws.EUWest) + s3Conn := s3.New(awsAuth, aws.USEast) s3Storage = s3Conn.Bucket(os.Getenv("S3Bucket")) m := martini.Classic() From 740ac28ce699b085716ef96cc885453715e00f5f Mon Sep 17 00:00:00 2001 From: zainhoda Date: Sun, 22 Feb 2015 15:58:58 -0500 Subject: [PATCH 2/6] Add "value" as an option and basic graphing capabilities --- structs.go | 34 +++++++++++++++++++++++++++++++--- templates/dashboard.html | 18 ++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/structs.go b/structs.go index 8327a6d..ea9d295 100644 --- a/structs.go +++ b/structs.go @@ -3,10 +3,10 @@ package main import ( "encoding/json" "errors" - "log" - "time" - "launchpad.net/goamz/s3" + "log" + "strconv" + "time" ) type dashboard struct { @@ -52,6 +52,7 @@ type dashboardMetric struct { Title string `json:"title"` Description string `json:"description"` Status string `json:"status"` + Value float64 `json:"value,omitifempty"` Expires int64 `json:"expires,omitifempty"` Freshness int64 `json:"freshness,omitifempty"` HistoricalData dashboardMetricHistory `json:"history,omitifempty"` @@ -61,6 +62,7 @@ type dashboardMetric struct { type dashboardMetricStatus struct { Time time.Time `json:"time"` Status string `json:"status"` + Value float64 `json:"value"` } type dashboardMetricMeta struct { @@ -83,10 +85,35 @@ func newDashboardMetric() *dashboardMetric { } } +func (dm *dashboardMetric) LabelHistory() string { + s := "[" + for i, v := range dm.HistoricalData { + if i != 0 { + s = s + ", " + } + s = s + "" + strconv.Itoa(int(v.Time.Unix())) + "" + } + s = s + "]" + return s +} + +func (dm *dashboardMetric) DataHistory() string { + s := "[" + for i, v := range dm.HistoricalData { + if i != 0 { + s = s + ", " + } + s = s + strconv.FormatFloat(v.Value, 'g', 4, 64) + } + s = s + "]" + return s +} + func (dm *dashboardMetric) Update(m *dashboardMetric) { dm.Title = m.Title dm.Description = m.Description dm.Status = m.Status + dm.Value = m.Value if m.Expires != 0 { dm.Expires = m.Expires } @@ -96,6 +123,7 @@ func (dm *dashboardMetric) Update(m *dashboardMetric) { dm.HistoricalData = append(dashboardMetricHistory{dashboardMetricStatus{ Time: time.Now(), Status: m.Status, + Value: m.Value, }}, dm.HistoricalData...) countStatus := make(map[string]float64) diff --git a/templates/dashboard.html b/templates/dashboard.html index 4ab3952..f6c51c2 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -10,6 +10,10 @@ + + + + + @@ -91,6 +105,10 @@ ] }; + for (var i = 0; i < data.labels.length; i++) { + data.labels[i] = timeConverter(data.labels[i]); + } + new Chartist.Line('.{{metric.MetricID}}', data); From 525db95b083250c3ecc39ea015de2ce20d5e5e12 Mon Sep 17 00:00:00 2001 From: zainhoda Date: Sun, 22 Feb 2015 20:16:40 -0500 Subject: [PATCH 4/6] Add statistical monitoring --- structs.go | 69 ++++++++++++++++++++++++++++++++++++++++ templates/dashboard.html | 8 ++--- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/structs.go b/structs.go index df78bbd..fae2e16 100644 --- a/structs.go +++ b/structs.go @@ -5,6 +5,7 @@ import ( "errors" "launchpad.net/goamz/s3" "log" + "sort" "strconv" "time" ) @@ -85,6 +86,74 @@ func newDashboardMetric() *dashboardMetric { } } +func median(values []float64) float64 { + sort.Float64s(values) + + // If even, take an average + if len(values)%2 == 0 { + return 0.5*values[len(values)/2] + 0.5*values[len(values)/2-1] + } + 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)) + + for i, _ := range values { + deviation[i] = absoluteValue(values[i] - medianValue) + } + + return deviation +} + +func (dm *dashboardMetric) getValueArray() []float64 { + values := make([]float64, 0) + + 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) LabelHistory() string { s := "[" for i, v := range dm.HistoricalData { diff --git a/templates/dashboard.html b/templates/dashboard.html index 90414e5..50c84a4 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -81,11 +81,11 @@ {% else %} {% for metric in metrics %} - {% if metric.Status == "OK" %} + {% if metric.StatisticalStatus == "OK" %}
- {% elif metric.Status == "Warning" %} + {% elif metric.StatisticalStatus == "Warning" %}
- {% elif metric.Status == "Critical" %} + {% elif metric.StatisticalStatus == "Critical" %}
{% else %}
@@ -93,7 +93,7 @@

{{ metric.Title }}

{{ metric.Description }}

- Current Value: {{ metric.Value }} + Current Value: {{ metric.Value }} {{ metric.MadMultiplier }} MAD above the Median ({{ metric.Median }})