2018-08-16 06:22:20 +00:00
// Statup
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statup
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2018-06-30 00:57:05 +00:00
package core
2018-06-10 01:31:13 +00:00
import (
"encoding/json"
"fmt"
2018-09-18 22:02:27 +00:00
"github.com/ararog/timeago"
2018-09-12 04:14:22 +00:00
"github.com/hunterlong/statup/core/notifier"
2018-06-30 00:57:05 +00:00
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
2018-09-10 22:16:23 +00:00
"sort"
2018-06-11 00:20:42 +00:00
"strconv"
2018-06-10 01:31:13 +00:00
"time"
)
type Service struct {
2018-08-19 00:37:00 +00:00
* types . Service
2018-07-14 02:37:39 +00:00
}
2018-09-12 04:14:22 +00:00
func ( s * Service ) Select ( ) * types . Service {
return s . Service
}
2018-08-20 07:20:05 +00:00
func ReturnService ( s * types . Service ) * Service {
2018-09-05 10:54:57 +00:00
return & Service { s }
2018-06-22 04:02:57 +00:00
}
2018-09-10 22:16:23 +00:00
// SelectService returns a *core.Service from in memory
2018-06-23 00:10:37 +00:00
func SelectService ( id int64 ) * Service {
2018-09-08 22:16:26 +00:00
for _ , s := range CoreApp . Services {
2018-09-12 04:14:22 +00:00
if s . Select ( ) . Id == id {
2018-09-08 22:16:26 +00:00
return s . ( * Service )
2018-06-23 00:10:37 +00:00
}
}
return nil
2018-06-10 01:31:13 +00:00
}
2018-09-10 22:16:23 +00:00
// SelectAllServices returns a slice of *core.Service to be store on []*core.Services, should only be called once on startup.
2018-09-08 22:16:26 +00:00
func ( c * Core ) SelectAllServices ( ) ( [ ] * Service , error ) {
var services [ ] * Service
2018-09-06 05:28:35 +00:00
db := servicesDB ( ) . Find ( & services ) . Order ( "order_id desc" )
2018-09-05 10:54:57 +00:00
if db . Error != nil {
utils . Log ( 3 , fmt . Sprintf ( "service error: %v" , db . Error ) )
return nil , db . Error
2018-06-30 00:57:05 +00:00
}
2018-09-15 01:18:21 +00:00
CoreApp . Services = nil
2018-09-08 22:16:26 +00:00
for _ , service := range services {
service . Start ( )
service . AllCheckins ( )
service . AllFailures ( )
CoreApp . Services = append ( CoreApp . Services , service )
2018-06-22 06:56:44 +00:00
}
2018-09-12 04:14:22 +00:00
sort . Sort ( ServiceOrder ( CoreApp . Services ) )
2018-09-05 10:54:57 +00:00
return services , db . Error
2018-08-20 07:20:05 +00:00
}
2018-09-10 22:16:23 +00:00
// reorderServices will sort the services based on 'order_id'
func reorderServices ( ) {
sort . Sort ( ServiceOrder ( CoreApp . Services ) )
}
// ToJSON will convert a service to a JSON string
2018-08-20 07:20:05 +00:00
func ( s * Service ) ToJSON ( ) string {
data , _ := json . Marshal ( s )
return string ( data )
2018-06-10 01:31:13 +00:00
}
2018-09-10 22:16:23 +00:00
// AvgTime will return the average amount of time for a service to response back successfully
2018-06-10 01:31:13 +00:00
func ( s * Service ) AvgTime ( ) float64 {
2018-06-15 04:30:10 +00:00
total , _ := s . TotalHits ( )
if total == 0 {
return float64 ( 0 )
}
sum , _ := s . Sum ( )
2018-06-10 01:31:13 +00:00
avg := sum / float64 ( total ) * 100
2018-06-15 04:30:10 +00:00
amount := fmt . Sprintf ( "%0.0f" , avg * 10 )
val , _ := strconv . ParseFloat ( amount , 10 )
return val
2018-06-10 01:31:13 +00:00
}
2018-09-10 22:16:23 +00:00
// Online24 returns the service's uptime percent within last 24 hours
2018-08-19 00:37:00 +00:00
func ( s * Service ) Online24 ( ) float32 {
2018-09-05 10:54:57 +00:00
ago := time . Now ( ) . Add ( - 24 * time . Hour )
return s . OnlineSince ( ago )
}
2018-09-10 22:16:23 +00:00
// OnlineSince accepts a time since parameter to return the percent of a service's uptime.
2018-09-05 10:54:57 +00:00
func ( s * Service ) OnlineSince ( ago time . Time ) float32 {
failed , _ := s . TotalFailuresSince ( ago )
2018-06-10 03:44:47 +00:00
if failed == 0 {
s . Online24Hours = 100.00
return s . Online24Hours
}
2018-09-05 10:54:57 +00:00
total , _ := s . TotalHitsSince ( ago )
2018-06-10 04:21:12 +00:00
if total == 0 {
s . Online24Hours = 0
return s . Online24Hours
}
2018-06-10 03:44:47 +00:00
avg := float64 ( failed ) / float64 ( total ) * 100
2018-06-11 00:20:42 +00:00
avg = 100 - avg
if avg < 0 {
avg = 0
}
amount , _ := strconv . ParseFloat ( fmt . Sprintf ( "%0.2f" , avg ) , 10 )
s . Online24Hours = float32 ( amount )
return s . Online24Hours
2018-06-10 03:44:47 +00:00
}
2018-09-10 22:16:23 +00:00
// DateScan struct is for creating the charts.js graph JSON array
2018-06-24 11:51:07 +00:00
type DateScan struct {
CreatedAt time . Time ` json:"x" `
2018-06-24 20:56:58 +00:00
Value int64 ` json:"y" `
2018-06-24 11:51:07 +00:00
}
2018-09-18 22:02:27 +00:00
// DateScanObj struct is for creating the charts.js graph JSON array
type DateScanObj struct {
Array [ ] DateScan
}
2018-09-10 22:16:23 +00:00
// lastFailure returns the last failure a service had
2018-08-20 07:20:05 +00:00
func ( s * Service ) lastFailure ( ) * Failure {
limited := s . LimitedFailures ( )
2018-09-18 22:02:27 +00:00
if len ( limited ) == 0 {
return nil
}
2018-08-20 07:20:05 +00:00
last := limited [ len ( limited ) - 1 ]
return last
2018-07-14 02:37:39 +00:00
}
2018-09-10 22:16:23 +00:00
// SmallText returns a short description about a services status
2018-08-19 00:37:00 +00:00
func ( s * Service ) SmallText ( ) string {
last := s . LimitedFailures ( )
hits , _ := s . LimitedHits ( )
2018-09-16 07:48:34 +00:00
zone := CoreApp . Timezone
2018-08-19 08:48:02 +00:00
if s . Online {
2018-06-30 03:40:00 +00:00
if len ( last ) == 0 {
2018-09-16 07:48:34 +00:00
return fmt . Sprintf ( "Online since %v" , utils . Timezoner ( s . CreatedAt , zone ) . Format ( "Monday 3:04:05PM, Jan _2 2006" ) )
2018-06-30 03:40:00 +00:00
} else {
2018-09-16 07:48:34 +00:00
return fmt . Sprintf ( "Online, last failure was %v" , utils . Timezoner ( hits [ 0 ] . CreatedAt , zone ) . Format ( "Monday 3:04:05PM, Jan _2 2006" ) )
2018-06-30 03:40:00 +00:00
}
}
2018-08-19 08:48:02 +00:00
if len ( last ) > 0 {
2018-08-20 07:20:05 +00:00
lastFailure := s . lastFailure ( )
2018-09-18 22:02:27 +00:00
got , _ := timeago . TimeAgoWithTime ( time . Now ( ) . Add ( s . Downtime ( ) ) , time . Now ( ) )
return fmt . Sprintf ( "Reported offline %v, %v" , got , lastFailure . ParseError ( ) )
2018-08-19 08:48:02 +00:00
} else {
return fmt . Sprintf ( "%v is currently offline" , s . Name )
}
2018-06-30 03:40:00 +00:00
}
2018-09-18 22:02:27 +00:00
func ( s * Service ) DowntimeText ( ) string {
2018-09-19 06:12:42 +00:00
return fmt . Sprintf ( "%v has been offline for %v" , s . Name , utils . DurationReadable ( s . Downtime ( ) ) )
2018-09-18 22:02:27 +00:00
}
2018-09-10 22:16:23 +00:00
// GroupDataBy returns a SQL query as a string to group a column by a time
2018-09-18 22:02:27 +00:00
func GroupDataBy ( column string , id int64 , start , end time . Time , increment string ) string {
2018-07-04 09:00:16 +00:00
var sql string
switch CoreApp . DbConnection {
case "mysql" :
2018-09-18 22:02:27 +00:00
sql = fmt . Sprintf ( "SELECT CONCAT(date_format(created_at, '%%Y-%%m-%%dT%%H:%%i:00Z')) AS created_at, AVG(latency)*1000 AS value FROM %v WHERE service=%v AND DATE_FORMAT(created_at, '%%Y-%%m-%%dT%%TZ') BETWEEN DATE_FORMAT('%v', '%%Y-%%m-%%dT%%TZ') AND DATE_FORMAT('%v', '%%Y-%%m-%%dT%%TZ') GROUP BY 1 ORDER BY created_at ASC;" , column , id , start . UTC ( ) . Format ( time . RFC3339 ) , end . UTC ( ) . Format ( time . RFC3339 ) )
2018-07-04 09:00:16 +00:00
case "sqlite" :
2018-09-18 22:02:27 +00:00
sql = fmt . Sprintf ( "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:00Z', created_at), AVG(latency)*1000 as value FROM %v WHERE service=%v AND created_at >= '%v' AND created_at <= '%v' GROUP BY strftime('%%M:00', created_at) ORDER BY created_at ASC;" , column , id , start . UTC ( ) . Format ( time . RFC3339 ) , end . UTC ( ) . Format ( time . RFC3339 ) )
2018-07-04 09:00:16 +00:00
case "postgres" :
2018-09-18 22:02:27 +00:00
sql = fmt . Sprintf ( "SELECT date_trunc('%v', created_at), AVG(latency)*1000 AS value FROM %v WHERE service=%v AND created_at >= '%v' AND created_at <= '%v' GROUP BY 1 ORDER BY date_trunc ASC;" , increment , column , id , start . UTC ( ) . Format ( time . RFC3339 ) , end . UTC ( ) . Format ( time . RFC3339 ) )
2018-07-04 09:00:16 +00:00
}
return sql
}
2018-09-18 22:02:27 +00:00
// Downtime returns the amount of time of a offline service
2018-09-16 10:29:52 +00:00
func ( s * Service ) Downtime ( ) time . Duration {
hits , _ := s . Hits ( )
fails := s . LimitedFailures ( )
if len ( fails ) == 0 {
return time . Duration ( 0 )
}
2018-09-19 06:12:42 +00:00
if len ( hits ) == 0 {
return time . Now ( ) . UTC ( ) . Sub ( fails [ len ( fails ) - 1 ] . CreatedAt . UTC ( ) )
}
2018-09-18 22:02:27 +00:00
since := fails [ 0 ] . CreatedAt . UTC ( ) . Sub ( hits [ 0 ] . CreatedAt . UTC ( ) )
2018-09-16 10:29:52 +00:00
return since
}
2018-09-18 22:02:27 +00:00
func GraphDataRaw ( service types . ServiceInterface , start , end time . Time ) * DateScanObj {
var d [ ] DateScan
s := service . Select ( )
sql := GroupDataBy ( "hits" , s . Id , start , end , "minute" )
2018-09-05 10:54:57 +00:00
rows , err := DbSession . Raw ( sql ) . Rows ( )
2018-06-24 11:51:07 +00:00
if err != nil {
2018-06-30 00:57:05 +00:00
utils . Log ( 2 , err )
2018-09-15 01:18:21 +00:00
return nil
2018-06-10 01:31:13 +00:00
}
2018-09-05 10:54:57 +00:00
for rows . Next ( ) {
2018-09-18 22:02:27 +00:00
var gd DateScan
2018-07-03 21:39:56 +00:00
var tt string
2018-06-24 11:51:07 +00:00
var ff float64
2018-09-05 10:54:57 +00:00
err := rows . Scan ( & tt , & ff )
2018-07-03 21:39:56 +00:00
if err != nil {
utils . Log ( 2 , fmt . Sprintf ( "Issue loading chart data for service %v, %v" , s . Name , err ) )
}
gd . CreatedAt , err = time . Parse ( time . RFC3339 , tt )
if err != nil {
utils . Log ( 2 , fmt . Sprintf ( "Issue parsing time %v" , err ) )
}
2018-09-16 07:48:34 +00:00
gd . CreatedAt = utils . Timezoner ( gd . CreatedAt , CoreApp . Timezone )
2018-06-24 11:51:07 +00:00
gd . Value = int64 ( ff )
d = append ( d , gd )
}
2018-09-18 22:02:27 +00:00
return & DateScanObj { d }
}
func ( d * DateScanObj ) ToString ( ) string {
data , err := json . Marshal ( d . Array )
if err != nil {
utils . Log ( 2 , err )
return "{}"
}
return string ( data )
2018-09-15 01:18:21 +00:00
}
// GraphData returns the JSON object used by Charts.js to render the chart
func ( s * Service ) GraphData ( ) string {
2018-09-18 22:02:27 +00:00
start := time . Now ( ) . Add ( time . Hour * - 24 + time . Minute * 0 + time . Second * 0 )
end := time . Now ( )
obj := GraphDataRaw ( s , start , end )
2018-09-15 01:18:21 +00:00
data , err := json . Marshal ( obj )
2018-06-24 11:51:07 +00:00
if err != nil {
2018-06-30 00:57:05 +00:00
utils . Log ( 2 , err )
2018-06-25 06:21:18 +00:00
return ""
2018-06-24 11:51:07 +00:00
}
2018-06-19 04:48:25 +00:00
return string ( data )
2018-06-10 01:31:13 +00:00
}
2018-09-10 22:16:23 +00:00
// AvgUptime24 returns a service's average online status for last 24 hours
2018-09-05 10:54:57 +00:00
func ( s * Service ) AvgUptime24 ( ) string {
ago := time . Now ( ) . Add ( - 24 * time . Hour )
return s . AvgUptime ( ago )
}
2018-09-10 22:16:23 +00:00
// AvgUptime returns average online status for last 24 hours
2018-09-05 10:54:57 +00:00
func ( s * Service ) AvgUptime ( ago time . Time ) string {
failed , _ := s . TotalFailuresSince ( ago )
2018-06-10 01:31:13 +00:00
if failed == 0 {
2018-09-06 05:28:35 +00:00
return "100"
2018-06-10 01:31:13 +00:00
}
2018-09-05 10:54:57 +00:00
total , _ := s . TotalHitsSince ( ago )
2018-06-10 04:21:12 +00:00
if total == 0 {
2018-09-08 22:16:26 +00:00
return "0.00"
2018-06-10 04:21:12 +00:00
}
2018-06-10 01:31:13 +00:00
percent := float64 ( failed ) / float64 ( total ) * 100
2018-06-11 00:20:42 +00:00
percent = 100 - percent
if percent < 0 {
percent = 0
}
2018-09-06 05:28:35 +00:00
amount := fmt . Sprintf ( "%0.2f" , percent )
if amount == "100.00" {
amount = "100"
2018-06-22 04:02:57 +00:00
}
2018-09-06 05:28:35 +00:00
return amount
}
2018-09-10 22:16:23 +00:00
// TotalUptime returns the total uptime percent of a service
2018-09-06 05:28:35 +00:00
func ( s * Service ) TotalUptime ( ) string {
hits , _ := s . TotalHits ( )
failures , _ := s . TotalFailures ( )
percent := float64 ( failures ) / float64 ( hits ) * 100
percent = 100 - percent
if percent < 0 {
percent = 0
}
amount := fmt . Sprintf ( "%0.2f" , percent )
if amount == "100.00" {
amount = "100"
}
return amount
2018-06-10 01:31:13 +00:00
}
2018-09-10 22:16:23 +00:00
// index returns a services index int for updating the []*core.Services slice
2018-08-21 03:11:40 +00:00
func ( s * Service ) index ( ) int {
2018-09-08 22:16:26 +00:00
for k , service := range CoreApp . Services {
if s . Id == service . ( * Service ) . Id {
2018-08-21 03:11:40 +00:00
return k
2018-08-20 07:20:05 +00:00
}
}
2018-08-21 03:11:40 +00:00
return 0
}
2018-09-10 22:16:23 +00:00
// updateService will update a service in the []*core.Services slice
2018-08-21 03:11:40 +00:00
func updateService ( service * Service ) {
index := service . index ( )
2018-09-08 22:16:26 +00:00
CoreApp . Services [ index ] = service
2018-08-20 07:20:05 +00:00
}
2018-09-10 22:16:23 +00:00
// Delete will remove a service from the database, it will also end the service checking go routine
2018-08-20 07:20:05 +00:00
func ( u * Service ) Delete ( ) error {
2018-09-08 22:16:26 +00:00
i := u . index ( )
2018-09-05 10:54:57 +00:00
err := servicesDB ( ) . Delete ( u )
if err . Error != nil {
utils . Log ( 3 , fmt . Sprintf ( "Failed to delete service %v. %v" , u . Name , err . Error ) )
return err . Error
2018-06-30 00:57:05 +00:00
}
2018-08-19 00:37:00 +00:00
u . Close ( )
2018-09-08 22:16:26 +00:00
slice := CoreApp . Services
CoreApp . Services = append ( slice [ : i ] , slice [ i + 1 : ] ... )
2018-09-10 22:16:23 +00:00
reorderServices ( )
2018-09-12 04:14:22 +00:00
notifier . OnDeletedService ( u . Service )
2018-09-05 10:54:57 +00:00
return err . Error
}
2018-09-10 22:16:23 +00:00
// UpdateSingle will update a single column for a service
2018-09-05 10:54:57 +00:00
func ( u * Service ) UpdateSingle ( attr ... interface { } ) error {
return servicesDB ( ) . Model ( u ) . Update ( attr ) . Error
2018-06-11 03:41:02 +00:00
}
2018-09-10 22:16:23 +00:00
// Update will update a service in the database, the service's checking routine can be restarted by passing true
2018-08-22 05:41:15 +00:00
func ( u * Service ) Update ( restart bool ) error {
2018-09-05 10:54:57 +00:00
err := servicesDB ( ) . Update ( u )
if err . Error != nil {
2018-08-20 07:20:05 +00:00
utils . Log ( 3 , fmt . Sprintf ( "Failed to update service %v. %v" , u . Name , err ) )
2018-09-05 10:54:57 +00:00
return err . Error
2018-07-01 03:54:28 +00:00
}
2018-08-22 05:41:15 +00:00
if restart {
u . Close ( )
2018-09-08 22:16:26 +00:00
u . Start ( )
u . SleepDuration = time . Duration ( u . Interval ) * time . Second
2018-08-22 05:41:15 +00:00
go u . CheckQueue ( true )
}
2018-09-10 22:16:23 +00:00
reorderServices ( )
2018-09-08 22:16:26 +00:00
updateService ( u )
2018-09-12 04:14:22 +00:00
notifier . OnUpdatedService ( u . Service )
2018-09-05 10:54:57 +00:00
return err . Error
2018-07-27 02:10:05 +00:00
}
2018-09-10 22:16:23 +00:00
// Create will create a service and insert it into the database
2018-09-15 01:18:21 +00:00
func ( u * Service ) Create ( check bool ) ( int64 , error ) {
2018-06-15 04:30:10 +00:00
u . CreatedAt = time . Now ( )
2018-09-05 10:54:57 +00:00
db := servicesDB ( ) . Create ( u )
if db . Error != nil {
utils . Log ( 3 , fmt . Sprintf ( "Failed to create service %v #%v: %v" , u . Name , u . Id , db . Error ) )
return 0 , db . Error
2018-06-10 03:44:47 +00:00
}
2018-08-19 00:37:00 +00:00
u . Start ( )
2018-09-15 01:18:21 +00:00
go u . CheckQueue ( check )
2018-09-08 22:16:26 +00:00
CoreApp . Services = append ( CoreApp . Services , u )
2018-09-10 22:16:23 +00:00
reorderServices ( )
2018-09-12 04:14:22 +00:00
notifier . OnNewService ( u . Service )
2018-09-05 10:54:57 +00:00
return u . Id , nil
2018-06-10 01:31:13 +00:00
}
2018-09-10 22:16:23 +00:00
// ServicesCount returns the amount of services inside the []*core.Services slice
2018-09-08 22:16:26 +00:00
func ( c * Core ) ServicesCount ( ) int {
return len ( c . Services )
}
2018-09-10 22:16:23 +00:00
// CountOnline
2018-09-08 22:16:26 +00:00
func ( c * Core ) CountOnline ( ) int {
2018-06-11 00:20:42 +00:00
amount := 0
2018-09-08 22:16:26 +00:00
for _ , s := range CoreApp . Services {
2018-09-12 04:14:22 +00:00
if s . Select ( ) . Online {
2018-06-11 00:20:42 +00:00
amount ++
}
}
return amount
2018-06-25 07:09:31 +00:00
}