mirror of https://github.com/statping/statping
				
				
				
			
		
			
				
	
	
		
			309 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			309 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
package services
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/sha1"
 | 
						|
	"crypto/tls"
 | 
						|
	"crypto/x509"
 | 
						|
	"encoding/hex"
 | 
						|
	"fmt"
 | 
						|
	"github.com/statping/statping/types"
 | 
						|
	"github.com/statping/statping/types/errors"
 | 
						|
	"github.com/statping/statping/types/failures"
 | 
						|
	"github.com/statping/statping/types/hits"
 | 
						|
	"github.com/statping/statping/utils"
 | 
						|
	"io/ioutil"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
const limitedFailures = 25
 | 
						|
 | 
						|
func (s *Service) LoadTLSCert() (*tls.Config, error) {
 | 
						|
	if s.TLSCert.String == "" || s.TLSCertKey.String == "" {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// load TLS cert and key from file path or PEM format
 | 
						|
	var cert tls.Certificate
 | 
						|
	var err error
 | 
						|
	tlsCertExtension := utils.FileExtension(s.TLSCert.String)
 | 
						|
	tlsCertKeyExtension := utils.FileExtension(s.TLSCertKey.String)
 | 
						|
	if tlsCertExtension == "" && tlsCertKeyExtension == "" {
 | 
						|
		cert, err = tls.X509KeyPair([]byte(s.TLSCert.String), []byte(s.TLSCertKey.String))
 | 
						|
	} else {
 | 
						|
		cert, err = tls.LoadX509KeyPair(s.TLSCert.String, s.TLSCertKey.String)
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		return nil, errors.Wrap(err, "issue loading X509KeyPair")
 | 
						|
	}
 | 
						|
 | 
						|
	config := &tls.Config{
 | 
						|
		Certificates:       []tls.Certificate{cert},
 | 
						|
		InsecureSkipVerify: s.TLSCertRoot.String == "",
 | 
						|
	}
 | 
						|
 | 
						|
	if s.TLSCertRoot.String == "" {
 | 
						|
		return config, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// create Root CA pool or use Root CA provided
 | 
						|
	rootCA := s.TLSCertRoot.String
 | 
						|
	caCert, err := ioutil.ReadFile(rootCA)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errors.Wrap(err, "issue reading root CA file: "+rootCA)
 | 
						|
	}
 | 
						|
	caCertPool := x509.NewCertPool()
 | 
						|
	caCertPool.AppendCertsFromPEM(caCert)
 | 
						|
 | 
						|
	config.RootCAs = caCertPool
 | 
						|
 | 
						|
	return config, nil
 | 
						|
}
 | 
						|
 | 
						|
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) UptimeData(hits []*hits.Hit, fails []*failures.Failure) (*UptimeSeries, error) {
 | 
						|
	if len(hits) == 0 {
 | 
						|
		return nil, errors.New("service does not have any successful hits")
 | 
						|
	}
 | 
						|
	// if theres no failures, then its been online 100%,
 | 
						|
	// return a series from created time, to current.
 | 
						|
	if len(fails) == 0 {
 | 
						|
		fistHit := hits[0]
 | 
						|
		duration := utils.Now().Sub(fistHit.CreatedAt).Milliseconds()
 | 
						|
		set := []series{
 | 
						|
			{
 | 
						|
				Start:    fistHit.CreatedAt,
 | 
						|
				End:      utils.Now(),
 | 
						|
				Duration: duration,
 | 
						|
				Online:   true,
 | 
						|
			},
 | 
						|
		}
 | 
						|
		out := &UptimeSeries{
 | 
						|
			Start:    fistHit.CreatedAt,
 | 
						|
			End:      utils.Now(),
 | 
						|
			Uptime:   duration,
 | 
						|
			Downtime: 0,
 | 
						|
			Series:   set,
 | 
						|
		}
 | 
						|
		return out, nil
 | 
						|
	}
 | 
						|
 | 
						|
	tMap := make(map[time.Time]bool)
 | 
						|
 | 
						|
	for _, v := range hits {
 | 
						|
		tMap[v.CreatedAt] = true
 | 
						|
	}
 | 
						|
	for _, v := range fails {
 | 
						|
		tMap[v.CreatedAt] = false
 | 
						|
	}
 | 
						|
 | 
						|
	var servs []ser
 | 
						|
	for t, v := range tMap {
 | 
						|
		s := ser{
 | 
						|
			Time:   t,
 | 
						|
			Online: v,
 | 
						|
		}
 | 
						|
		servs = append(servs, s)
 | 
						|
	}
 | 
						|
	if len(servs) == 0 {
 | 
						|
		return nil, errors.New("error generating uptime data structure")
 | 
						|
	}
 | 
						|
	sort.Sort(ByTime(servs))
 | 
						|
 | 
						|
	var allTimes []series
 | 
						|
	online := servs[0].Online
 | 
						|
	thisTime := servs[0].Time
 | 
						|
	for i := 0; i < len(servs); i++ {
 | 
						|
		v := servs[i]
 | 
						|
		if v.Online != online {
 | 
						|
			s := series{
 | 
						|
				Start:    thisTime,
 | 
						|
				End:      v.Time,
 | 
						|
				Duration: v.Time.Sub(thisTime).Milliseconds(),
 | 
						|
				Online:   online,
 | 
						|
			}
 | 
						|
			allTimes = append(allTimes, s)
 | 
						|
			thisTime = v.Time
 | 
						|
			online = v.Online
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if len(allTimes) == 0 {
 | 
						|
		return nil, errors.New("error generating uptime series structure")
 | 
						|
	}
 | 
						|
 | 
						|
	first := servs[0].Time
 | 
						|
	last := servs[len(servs)-1].Time
 | 
						|
	if !s.Online {
 | 
						|
		s := series{
 | 
						|
			Start:    allTimes[len(allTimes)-1].End,
 | 
						|
			End:      utils.Now(),
 | 
						|
			Duration: utils.Now().Sub(last).Milliseconds(),
 | 
						|
			Online:   s.Online,
 | 
						|
		}
 | 
						|
		allTimes = append(allTimes, s)
 | 
						|
	} else {
 | 
						|
		l := allTimes[len(allTimes)-1]
 | 
						|
		s := series{
 | 
						|
			Start:    l.Start,
 | 
						|
			End:      utils.Now(),
 | 
						|
			Duration: utils.Now().Sub(l.Start).Milliseconds(),
 | 
						|
			Online:   true,
 | 
						|
		}
 | 
						|
		allTimes = append(allTimes, s)
 | 
						|
	}
 | 
						|
 | 
						|
	response := &UptimeSeries{
 | 
						|
		Start:    first,
 | 
						|
		End:      last,
 | 
						|
		Uptime:   addDurations(allTimes, true),
 | 
						|
		Downtime: addDurations(allTimes, false),
 | 
						|
		Series:   allTimes,
 | 
						|
	}
 | 
						|
 | 
						|
	return response, nil
 | 
						|
}
 | 
						|
 | 
						|
func addDurations(s []series, on bool) int64 {
 | 
						|
	var dur int64
 | 
						|
	for _, v := range s {
 | 
						|
		if v.Online == on {
 | 
						|
			dur += v.Duration
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return dur
 | 
						|
}
 | 
						|
 | 
						|
// 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() {
 | 
						|
		s.Failures = s.AllFailures().LastAmount(limitedFailures)
 | 
						|
		s.prevOnline = true
 | 
						|
		// collect initial service stats
 | 
						|
		s.UpdateStats()
 | 
						|
		allServices[s.Id] = s
 | 
						|
		if start {
 | 
						|
			CheckinProcess(s)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return allServices, nil
 | 
						|
}
 | 
						|
 | 
						|
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()
 | 
						|
 | 
						|
	allFails := s.AllFailures()
 | 
						|
	if s.LastOffline.IsZero() {
 | 
						|
		lastFail := allFails.Last()
 | 
						|
		if lastFail != nil {
 | 
						|
			s.LastOffline = lastFail.CreatedAt
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	s.Stats = &Stats{
 | 
						|
		Failures: allFails.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 {
 | 
						|
	failsList := s.FailuresSince(ago).Count()
 | 
						|
	hitsList := s.HitsSince(ago).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
 | 
						|
}
 | 
						|
 | 
						|
// Uptime returns the duration of how long the service was online
 | 
						|
func (s Service) Uptime() utils.Duration {
 | 
						|
	return utils.Duration{Duration: utils.Now().Sub(s.LastOffline)}
 | 
						|
}
 | 
						|
 | 
						|
// Downtime returns the duration of how long the service has been offline
 | 
						|
func (s Service) Downtime() utils.Duration {
 | 
						|
	return utils.Duration{Duration: utils.Now().Sub(s.LastOnline)}
 | 
						|
}
 |