statping/database/service.go

214 lines
5.4 KiB
Go

package database
import (
"fmt"
"github.com/hunterlong/statping/types"
"strconv"
"strings"
"time"
)
type ServiceObj struct {
*types.Service
o *Object
Servicer
}
type Servicer interface {
Failures() *FailureObj
Checkins() []*CheckinObj
DowntimeText() string
UpdateStats()
Model() *ServiceObj
Hittable
}
type Hittable interface {
Hits() *HitObj
CreateHit(hit *types.Hit) *HitObj
}
func (s *ServiceObj) Model() *ServiceObj {
return s
}
func Service(id int64) (*ServiceObj, error) {
var service types.Service
query := database.Services().Where("id = ?", id)
finer := query.Find(&service)
return &ServiceObj{Service: &service, o: wrapObject(id, &service, query)}, finer.Error()
}
func wrapServices(all []*types.Service, db Database) []*ServiceObj {
var arr []*ServiceObj
for _, v := range all {
arr = append(arr, &ServiceObj{Service: v, o: wrapObject(v.Id, v, db)})
}
return arr
}
func Services() []*ServiceObj {
var services []*types.Service
db := database.Services().Order("order_id desc")
db.Find(&services)
return wrapServices(services, db)
}
func (s *ServiceObj) Checkins() []*CheckinObj {
var checkins []*types.Checkin
query := database.Checkins().Where("service = ?", s.Id)
query.Find(&checkins)
return wrapCheckins(checkins, query)
}
func (s *ServiceObj) DowntimeText() string {
last := s.Failures().Last(1)
if len(last) == 0 {
return ""
}
return parseError(last[0])
}
// ParseError returns a human readable error for a Failure
func parseError(f *types.Failure) string {
if f.Method == "checkin" {
return fmt.Sprintf("Checkin is Offline")
}
err := strings.Contains(f.Issue, "connection reset by peer")
if err {
return fmt.Sprintf("Connection Reset")
}
err = strings.Contains(f.Issue, "operation timed out")
if err {
return fmt.Sprintf("HTTP Request Timed Out")
}
err = strings.Contains(f.Issue, "x509: certificate is valid")
if err {
return fmt.Sprintf("SSL Certificate invalid")
}
err = strings.Contains(f.Issue, "Client.Timeout exceeded while awaiting headers")
if err {
return fmt.Sprintf("Connection Timed Out")
}
err = strings.Contains(f.Issue, "no such host")
if err {
return fmt.Sprintf("Domain is offline or not found")
}
err = strings.Contains(f.Issue, "HTTP Status Code")
if err {
return fmt.Sprintf("Incorrect HTTP Status Code")
}
err = strings.Contains(f.Issue, "connection refused")
if err {
return fmt.Sprintf("Connection Failed")
}
err = strings.Contains(f.Issue, "can't assign requested address")
if err {
return fmt.Sprintf("Unable to Request Address")
}
err = strings.Contains(f.Issue, "no route to host")
if err {
return fmt.Sprintf("Domain is offline or not found")
}
err = strings.Contains(f.Issue, "i/o timeout")
if err {
return fmt.Sprintf("Connection Timed Out")
}
err = strings.Contains(f.Issue, "Client.Timeout exceeded while reading body")
if err {
return fmt.Sprintf("Timed Out on Response Body")
}
return f.Issue
}
func (s *ServiceObj) Hits() *HitObj {
query := database.Hits().Where("service = ?", s.Id)
return &HitObj{wrapObject(s.Id, nil, query)}
}
func (s *ServiceObj) Failures() *FailureObj {
q := database.Failures().Where("method != 'checkin' AND service = ?", s.Id)
return &FailureObj{wrapObject(s.Id, nil, q)}
}
func (s *ServiceObj) Group() *GroupObj {
var group types.Group
q := database.Groups().Where("id = ?", s.GroupId)
finder := q.Find(&group)
if finder.Error() != nil {
return nil
}
return &GroupObj{Group: &group, o: wrapObject(group.Id, &group, q)}
}
func (s *ServiceObj) object() *Object {
return s.o
}
func (s *ServiceObj) UpdateStats() {
s.Online24Hours = s.OnlineDaysPercent(1)
s.Online7Days = s.OnlineDaysPercent(7)
s.AvgResponse = s.AvgTime()
s.FailuresLast24Hours = len(s.Failures().Since(time.Now().UTC().Add(-time.Hour * 24)))
s.Stats = &types.Stats{
Failures: s.Failures().Count(),
Hits: s.Hits().Count(),
}
}
// AvgTime will return the average amount of time for a service to response back successfully
func (s *ServiceObj) AvgTime() float64 {
sum := s.Hits().Sum()
return sum
}
// AvgUptime will return the average amount of time for a service to response back successfully
func (s *ServiceObj) AvgUptime(since time.Time) float64 {
sum := s.Hits().Sum()
return sum
}
// OnlineDaysPercent returns the service's uptime percent within last 24 hours
func (s *ServiceObj) OnlineDaysPercent(days int) float32 {
ago := time.Now().UTC().Add((-24 * time.Duration(days)) * time.Hour)
return s.OnlineSince(ago)
}
// OnlineSince accepts a time since parameter to return the percent of a service's uptime.
func (s *ServiceObj) OnlineSince(ago time.Time) float32 {
failed := s.Failures().Since(ago)
if len(failed) == 0 {
s.Online24Hours = 100.00
return s.Online24Hours
}
total := s.Hits().Since(ago)
if len(total) == 0 {
s.Online24Hours = 0
return s.Online24Hours
}
avg := float64(len(failed)) / float64(len(total)) * 100
avg = 100 - avg
if avg < 0 {
avg = 0
}
amount, _ := strconv.ParseFloat(fmt.Sprintf("%0.2f", avg), 10)
s.Online24Hours = float32(amount)
return s.Online24Hours
}
// Downtime returns the amount of time of a offline service
func (s *ServiceObj) Downtime() time.Duration {
hits := s.Hits().Last(1)
fail := s.Failures().Last(1)
if len(fail) == 0 {
return time.Duration(0)
}
if len(fail) == 0 {
return time.Now().UTC().Sub(fail[0].CreatedAt.UTC())
}
since := fail[0].CreatedAt.UTC().Sub(hits[0].CreatedAt.UTC())
return since
}