mirror of
https://github.com/Luzifer/mondash.git
synced 2024-11-10 00:20:02 +00:00
Merge remote-tracking branch 'alphahat/master'
This commit is contained in:
commit
55386a5eab
5 changed files with 168 additions and 23 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
gin-bin
|
gin-bin
|
||||||
mondash
|
mondash
|
||||||
Godeps/_workspace/
|
Godeps/_workspace/
|
||||||
|
*.sh
|
||||||
|
|
23
main.go
23
main.go
|
@ -5,13 +5,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
//"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"log"
|
//"log"
|
||||||
|
|
||||||
"launchpad.net/goamz/aws"
|
//"launchpad.net/goamz/aws"
|
||||||
"launchpad.net/goamz/s3"
|
//"launchpad.net/goamz/s3"
|
||||||
|
|
||||||
"github.com/flosch/pongo2"
|
"github.com/flosch/pongo2"
|
||||||
"github.com/go-martini/martini"
|
"github.com/go-martini/martini"
|
||||||
|
@ -20,18 +20,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var templates = make(map[string]*pongo2.Template)
|
var templates = make(map[string]*pongo2.Template)
|
||||||
var s3Storage *s3.Bucket
|
|
||||||
|
//var s3Storage *s3.Bucket
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
preloadTemplates()
|
preloadTemplates()
|
||||||
|
|
||||||
// Initialize S3 storage
|
// Initialize S3 storage
|
||||||
awsAuth, err := aws.EnvAuth()
|
//awsAuth, err := aws.EnvAuth()
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
log.Fatal(err)
|
// log.Fatal(err)
|
||||||
}
|
//}
|
||||||
s3Conn := s3.New(awsAuth, aws.EUWest)
|
//s3Conn := s3.New(awsAuth, aws.USEast)
|
||||||
s3Storage = s3Conn.Bucket(os.Getenv("S3Bucket"))
|
//s3Storage = s3Conn.Bucket(os.Getenv("S3Bucket"))
|
||||||
|
|
||||||
m := martini.Classic()
|
m := martini.Classic()
|
||||||
|
|
||||||
|
|
119
structs.go
119
structs.go
|
@ -3,10 +3,13 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
//"launchpad.net/goamz/s3"
|
||||||
"log"
|
"log"
|
||||||
|
//"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"launchpad.net/goamz/s3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type dashboard struct {
|
type dashboard struct {
|
||||||
|
@ -16,7 +19,7 @@ type dashboard struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDashboard(dashid string) (*dashboard, error) {
|
func loadDashboard(dashid string) (*dashboard, error) {
|
||||||
data, err := s3Storage.Get(dashid)
|
data, err := ioutil.ReadFile(dashid + ".txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &dashboard{}, errors.New("Dashboard not found")
|
return &dashboard{}, errors.New("Dashboard not found")
|
||||||
}
|
}
|
||||||
|
@ -33,7 +36,8 @@ func (d *dashboard) Save() {
|
||||||
log.Printf("Error while marshalling dashboard: %s", err)
|
log.Printf("Error while marshalling dashboard: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = s3Storage.Put(d.DashboardID, data, "application/json", s3.Private)
|
err = ioutil.WriteFile(d.DashboardID+".txt", data, 0600)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error while storing dashboard: %s", err)
|
log.Printf("Error while storing dashboard: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -52,6 +56,7 @@ type dashboardMetric struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Value float64 `json:"value,omitifempty"`
|
||||||
Expires int64 `json:"expires,omitifempty"`
|
Expires int64 `json:"expires,omitifempty"`
|
||||||
Freshness int64 `json:"freshness,omitifempty"`
|
Freshness int64 `json:"freshness,omitifempty"`
|
||||||
HistoricalData dashboardMetricHistory `json:"history,omitifempty"`
|
HistoricalData dashboardMetricHistory `json:"history,omitifempty"`
|
||||||
|
@ -61,6 +66,7 @@ type dashboardMetric struct {
|
||||||
type dashboardMetricStatus struct {
|
type dashboardMetricStatus struct {
|
||||||
Time time.Time `json:"time"`
|
Time time.Time `json:"time"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Value float64 `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type dashboardMetricMeta struct {
|
type dashboardMetricMeta struct {
|
||||||
|
@ -83,20 +89,121 @@ func newDashboardMetric() *dashboardMetric {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func median(values []float64) float64 {
|
||||||
|
sort.Float64s(values)
|
||||||
|
|
||||||
|
if len(values) == 1 {
|
||||||
|
return values[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If even, take an average
|
||||||
|
if len(values)%2 == 0 {
|
||||||
|
return 0.5*values[len(values)/2] + 0.5*values[len(values)/2-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("len(values)=%v, len(values)/2=%v\n", len(values), len(values)/2)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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) {
|
func (dm *dashboardMetric) Update(m *dashboardMetric) {
|
||||||
dm.Title = m.Title
|
dm.Title = m.Title
|
||||||
dm.Description = m.Description
|
dm.Description = m.Description
|
||||||
dm.Status = m.Status
|
dm.Status = m.Status
|
||||||
|
dm.Value = m.Value
|
||||||
if m.Expires != 0 {
|
if m.Expires != 0 {
|
||||||
dm.Expires = m.Expires
|
dm.Expires = m.Expires
|
||||||
}
|
}
|
||||||
if m.Freshness != 0 {
|
if m.Freshness != 0 {
|
||||||
dm.Freshness = m.Freshness
|
dm.Freshness = m.Freshness
|
||||||
}
|
}
|
||||||
dm.HistoricalData = append(dashboardMetricHistory{dashboardMetricStatus{
|
dm.HistoricalData = append(dm.HistoricalData, dashboardMetricStatus{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
Status: m.Status,
|
Status: m.Status,
|
||||||
}}, dm.HistoricalData...)
|
Value: m.Value,
|
||||||
|
})
|
||||||
|
|
||||||
countStatus := make(map[string]float64)
|
countStatus := make(map[string]float64)
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,24 @@
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
|
||||||
|
|
||||||
|
<!-- Chartist Library -->
|
||||||
|
<link rel="stylesheet" href="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.css">
|
||||||
|
<script src="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function timeConverter(UNIX_timestamp){
|
||||||
|
var a = new Date(UNIX_timestamp*1000);
|
||||||
|
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||||||
|
var year = a.getFullYear();
|
||||||
|
var month = months[a.getMonth()];
|
||||||
|
var date = a.getDate();
|
||||||
|
var hour = a.getHours();
|
||||||
|
var min = a.getMinutes();
|
||||||
|
var sec = a.getSeconds();
|
||||||
|
var time = date + ' ' + month + ' ' + year + ' ' + hour + ':' + min + ':' + sec ;
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
||||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
|
@ -63,11 +81,11 @@
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for metric in metrics %}
|
{% for metric in metrics %}
|
||||||
{% if metric.Status == "OK" %}
|
{% if metric.StatisticalStatus == "OK" %}
|
||||||
<div class="row alert alert-success">
|
<div class="row alert alert-success">
|
||||||
{% elif metric.Status == "Warning" %}
|
{% elif metric.StatisticalStatus == "Warning" %}
|
||||||
<div class="row alert alert-warning">
|
<div class="row alert alert-warning">
|
||||||
{% elif metric.Status == "Critical" %}
|
{% elif metric.StatisticalStatus == "Critical" %}
|
||||||
<div class="row alert alert-danger">
|
<div class="row alert alert-danger">
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="row alert alert-info">
|
<div class="row alert alert-info">
|
||||||
|
@ -75,6 +93,24 @@
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<h4>{{ metric.Title }}</h4>
|
<h4>{{ metric.Title }}</h4>
|
||||||
<p>{{ metric.Description }}</p>
|
<p>{{ metric.Description }}</p>
|
||||||
|
<span><span class="label label-info">Current Value: {{ metric.Value }}</span> {{ metric.MadMultiplier }} MAD above the Median ({{ metric.Median }})</span>
|
||||||
|
<div class="ct-chart {{metric.MetricID}} .ct-double-octave"></div>
|
||||||
|
<script>
|
||||||
|
var data = {
|
||||||
|
// A labels array that can contain any sort of values
|
||||||
|
labels: {{ metric.LabelHistory }},
|
||||||
|
// Our series array that contains series objects or in this case series data arrays
|
||||||
|
series: [
|
||||||
|
{{ metric.DataHistory }}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < data.labels.length; i++) {
|
||||||
|
data.labels[i] = timeConverter(data.labels[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
new Chartist.Line('.{{metric.MetricID}}', data);
|
||||||
|
</script>
|
||||||
<small>
|
<small>
|
||||||
Updated {{ metric.Meta.LastUpdate|naturaltime}}
|
Updated {{ metric.Meta.LastUpdate|naturaltime}}
|
||||||
{% if metric.Status != "OK" %}
|
{% if metric.Status != "OK" %}
|
||||||
|
|
|
@ -19,10 +19,10 @@ func handleRedirectWelcome(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
func handleCreateRandomDashboard(res http.ResponseWriter, req *http.Request) {
|
func handleCreateRandomDashboard(res http.ResponseWriter, req *http.Request) {
|
||||||
urlProposal := generateAPIKey()[0:20]
|
urlProposal := generateAPIKey()[0:20]
|
||||||
_, err := s3Storage.Get(urlProposal)
|
_, err := ioutil.ReadFile(urlProposal + ".txt")
|
||||||
for err == nil {
|
for err == nil {
|
||||||
urlProposal = generateAPIKey()[0:20]
|
urlProposal = generateAPIKey()[0:20]
|
||||||
_, err = s3Storage.Get(urlProposal)
|
_, err = ioutil.ReadFile(urlProposal + ".txt")
|
||||||
}
|
}
|
||||||
http.Redirect(res, req, fmt.Sprintf("/%s", urlProposal), http.StatusTemporaryRedirect)
|
http.Redirect(res, req, fmt.Sprintf("/%s", urlProposal), http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ func handleDeleteDashboard(params martini.Params, req *http.Request, res http.Re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = s3Storage.Del(params["dashid"])
|
//_ = s3Storage.Del(params["dashid"])
|
||||||
http.Error(res, "OK", http.StatusOK)
|
http.Error(res, "OK", http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue