mirror of https://github.com/statping/statping
217 lines
4.7 KiB
Go
217 lines
4.7 KiB
Go
package services
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"github.com/statping/statping/types"
|
|
"github.com/statping/statping/types/null"
|
|
"github.com/statping/statping/utils"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const limitedFailures = 25
|
|
|
|
func (s *Service) Duration() time.Duration {
|
|
return time.Duration(s.Interval) * time.Second
|
|
}
|
|
|
|
// Start will create a channel for the service checking go routine
|
|
func (s *Service) Start() {
|
|
if s.IsRunning() {
|
|
return
|
|
}
|
|
s.Running = make(chan bool)
|
|
}
|
|
|
|
// Close will stop the go routine that is checking if service is online or not
|
|
func (s *Service) Close() {
|
|
if s.IsRunning() {
|
|
close(s.Running)
|
|
}
|
|
}
|
|
|
|
func humanMicro(val int64) string {
|
|
if val < 10000 {
|
|
return fmt.Sprintf("%d μs", val)
|
|
}
|
|
return fmt.Sprintf("%0.0f ms", float64(val)*0.001)
|
|
}
|
|
|
|
// IsRunning returns true if the service go routine is running
|
|
func (s *Service) IsRunning() bool {
|
|
if s.Running == nil {
|
|
return false
|
|
}
|
|
select {
|
|
case <-s.Running:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (s Service) Hash() string {
|
|
format := fmt.Sprintf("name:%sdomain:%sport:%dtype:%smethod:%s", s.Name, s.Domain, s.Port, s.Type, s.Method)
|
|
h := sha1.New()
|
|
h.Write([]byte(format))
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
// SelectAllServices returns a slice of *core.Service to be store on []*core.Services
|
|
// should only be called once on startup.
|
|
func SelectAllServices(start bool) (map[int64]*Service, error) {
|
|
if len(allServices) > 0 {
|
|
return allServices, nil
|
|
}
|
|
|
|
for _, s := range all() {
|
|
|
|
if start {
|
|
CheckinProcess(s)
|
|
}
|
|
|
|
fails := s.AllFailures().LastAmount(limitedFailures)
|
|
s.Failures = fails
|
|
|
|
for _, c := range s.Checkins() {
|
|
s.AllCheckins = append(s.AllCheckins, c)
|
|
}
|
|
|
|
// collect initial service stats
|
|
s.UpdateStats()
|
|
|
|
allServices[s.Id] = s
|
|
}
|
|
|
|
return allServices, nil
|
|
}
|
|
|
|
func ValidateService(line string) (*Service, error) {
|
|
p, err := url.Parse(line)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newService := new(Service)
|
|
|
|
domain := p.Host
|
|
newService.Name = niceDomainName(domain, p.Path)
|
|
if p.Port() != "" {
|
|
newService.Port = int(utils.ToInt(p.Port()))
|
|
if p.Scheme != "http" && p.Scheme != "https" {
|
|
domain = strings.ReplaceAll(domain, ":"+p.Port(), "")
|
|
}
|
|
}
|
|
newService.Domain = domain
|
|
|
|
switch p.Scheme {
|
|
case "http", "https":
|
|
newService.Type = "http"
|
|
newService.Method = "get"
|
|
if p.Scheme == "https" {
|
|
newService.VerifySSL = null.NewNullBool(true)
|
|
}
|
|
default:
|
|
newService.Type = p.Scheme
|
|
}
|
|
return newService, nil
|
|
}
|
|
|
|
func niceDomainName(domain string, paths string) string {
|
|
domain = strings.ReplaceAll(domain, "www.", "")
|
|
splitPath := strings.Split(paths, "/")
|
|
if len(splitPath) == 1 {
|
|
return domain
|
|
}
|
|
var addedName []string
|
|
for k, p := range splitPath {
|
|
if k > 2 {
|
|
break
|
|
}
|
|
if len(p) > 16 {
|
|
addedName = append(addedName, p+"...")
|
|
break
|
|
} else {
|
|
addedName = append(addedName, p)
|
|
}
|
|
}
|
|
return domain + strings.Join(addedName, "/")
|
|
}
|
|
|
|
func (s *Service) UpdateStats() *Service {
|
|
s.Online24Hours = s.OnlineDaysPercent(1)
|
|
s.Online7Days = s.OnlineDaysPercent(7)
|
|
s.AvgResponse = s.AvgTime()
|
|
s.FailuresLast24Hours = s.FailuresSince(utils.Now().Add(-time.Hour * 24)).Count()
|
|
|
|
if s.LastOffline.IsZero() {
|
|
lastFail := s.AllFailures().Last()
|
|
if lastFail != nil {
|
|
s.LastOffline = lastFail.CreatedAt
|
|
}
|
|
}
|
|
|
|
s.Stats = &Stats{
|
|
Failures: s.AllFailures().Count(),
|
|
Hits: s.AllHits().Count(),
|
|
FirstHit: s.FirstHit().CreatedAt,
|
|
}
|
|
return s
|
|
}
|
|
|
|
// AvgTime will return the average amount of time for a service to response back successfully
|
|
func (s *Service) AvgTime() int64 {
|
|
return s.AllHits().Avg()
|
|
}
|
|
|
|
// OnlineDaysPercent returns the service's uptime percent within last 24 hours
|
|
func (s *Service) OnlineDaysPercent(days int) float32 {
|
|
ago := utils.Now().Add(-time.Duration(days) * types.Day)
|
|
return s.OnlineSince(ago)
|
|
}
|
|
|
|
// OnlineSince accepts a time since parameter to return the percent of a service's uptime.
|
|
func (s *Service) OnlineSince(ago time.Time) float32 {
|
|
failed := s.FailuresSince(ago)
|
|
failsList := failed.Count()
|
|
|
|
total := s.HitsSince(ago)
|
|
hitsList := total.Count()
|
|
|
|
if failsList == 0 {
|
|
s.Online24Hours = 100.00
|
|
return s.Online24Hours
|
|
}
|
|
|
|
if hitsList == 0 {
|
|
s.Online24Hours = 0
|
|
return s.Online24Hours
|
|
}
|
|
|
|
avg := (float64(failsList) / float64(hitsList)) * 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 *Service) Downtime() time.Duration {
|
|
hit := s.LastHit()
|
|
fail := s.AllFailures().Last()
|
|
if hit == nil {
|
|
return time.Duration(0)
|
|
}
|
|
if fail == nil {
|
|
return utils.Now().Sub(hit.CreatedAt)
|
|
}
|
|
|
|
return fail.CreatedAt.Sub(hit.CreatedAt)
|
|
}
|