From b18135e57954048ed01c796877f5d1f29dad6d6d Mon Sep 17 00:00:00 2001 From: Luther Wen Xu Date: Tue, 21 Apr 2020 20:16:28 +0800 Subject: [PATCH] Initial Commit This is a code dump of Status without the HTTP server yet. --- .gitignore | 4 ++++ check/check.go | 34 ++++++++++++++++++++++++++++++ check/http.go | 48 +++++++++++++++++++++++++++++++++++++++++++ check/ping.go | 19 +++++++++++++++++ checkLoop.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ config.go | 20 ++++++++++++++++++ db/db.go | 17 +++++++++++++++ db/ping.go | 31 ++++++++++++++++++++++++++++ go.mod | 9 ++++++++ go.sum | 35 +++++++++++++++++++++++++++++++ main.go | 9 ++++++++ 11 files changed, 282 insertions(+) create mode 100644 .gitignore create mode 100644 check/check.go create mode 100644 check/http.go create mode 100644 check/ping.go create mode 100644 checkLoop.go create mode 100644 config.go create mode 100644 db/db.go create mode 100644 db/ping.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e6f5d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Config File +config.json +# Generated Data +data.db diff --git a/check/check.go b/check/check.go new file mode 100644 index 0000000..f1482ba --- /dev/null +++ b/check/check.go @@ -0,0 +1,34 @@ +package check + +import ( + "time" +) + +//Result denotes the status from a status check. +type Result uint8 + +const ( + //Online means all requests were fulfilled by the remote end. + Online Result = iota + //Unstable means some requests were fulfilled by the remote end. + Unstable + //Offline means every requests to the remote end has failed. + Offline +) + +//TryCount is the amount of times requests will be made to the remote end. +const TryCount = 3 + +func statusFromSuccessCount(count int) Result { + if count == 0 { + return Offline + } + if count < TryCount { + return Unstable + } + return Online +} + +func divide(t time.Duration, dividend int) time.Duration { + return time.Duration(int(t) / dividend) +} diff --git a/check/http.go b/check/http.go new file mode 100644 index 0000000..2c43a4b --- /dev/null +++ b/check/http.go @@ -0,0 +1,48 @@ +package check + +import ( + "net/http" + "net/http/httptrace" + "time" +) + +//HTTPPing returns the response time to fetch a page. +func HTTPPing(target string) (time.Duration, Result, error) { + var totalTime time.Duration + var err error + successCount := 0 + okCount := 0 + for i := 0; i < TryCount; i++ { + duration, successful, getErr := timeGet(target) + totalTime += duration + if getErr != nil { + err = getErr + } + if successful { + okCount++ + } + successCount++ + } + return divide(totalTime, successCount), statusFromSuccessCount(okCount), err +} + +func timeGet(url string) (time.Duration, bool, error) { + req, _ := http.NewRequest("GET", url, nil) + + var connect time.Time + var duration time.Duration + + trace := &httptrace.ClientTrace{ + ConnectStart: func(network, addr string) { connect = time.Now() }, + GotFirstResponseByte: func() { + duration = time.Since(connect) + }, + } + + req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace)) + res, err := http.DefaultTransport.RoundTrip(req) + if err != nil { + return 0, false, err + } + return duration, res.StatusCode < 400, nil +} diff --git a/check/ping.go b/check/ping.go new file mode 100644 index 0000000..78bffdb --- /dev/null +++ b/check/ping.go @@ -0,0 +1,19 @@ +package check + +import ( + "time" + + "github.com/sparrc/go-ping" +) + +//ICMPPing returns the ping to the target host. +func ICMPPing(targetHost string) (time.Duration, Result, error) { + pinger, err := ping.NewPinger(targetHost) + if err != nil { + return 0, Offline, err + } + pinger.Count = TryCount + pinger.Run() + stats := pinger.Statistics() + return stats.AvgRtt, statusFromSuccessCount(stats.PacketsRecv), nil +} diff --git a/checkLoop.go b/checkLoop.go new file mode 100644 index 0000000..4eaa468 --- /dev/null +++ b/checkLoop.go @@ -0,0 +1,56 @@ +package main + +import ( + "errors" + "fmt" + "time" + + "status/check" + "status/db" +) + +type checkTarget struct { + CheckType string `json:"type"` + ServiceName string `json:"name"` + Target string `json:"target"` +} + +func checkLoop(targets []checkTarget) { + lastTime := time.Now() + for { + if time.Since(lastTime) < time.Minute { + secondsUntilNextMinute := 60 - time.Now().Second() + time.Sleep(time.Duration(secondsUntilNextMinute) * time.Second) + lastTime = time.Now() + } + + for _, t := range targets { + currentTime := time.Now() + var duration time.Duration + var result check.Result + var err error + + switch t.CheckType { + case "http": + duration, result, err = check.HTTPPing(t.Target) + case "icmp": + duration, result, err = check.ICMPPing(t.Target) + default: + result = check.Offline + err = errors.New("unknown check type") + } + + if err != nil { + fmt.Printf("[ERROR] Failed checking `%s` with `%s` with error: `%s`", t.Target, t.CheckType, err) + } + + entry := db.PingEntry{ + ServiceName: t.ServiceName, + Ping: duration.Milliseconds(), + Status: result, + Time: currentTime, + } + entry.AddToDB() + } + } +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..d2a8d39 --- /dev/null +++ b/config.go @@ -0,0 +1,20 @@ +package main + +import ( + "encoding/json" + "io/ioutil" +) + +type Config struct { + Targets []checkTarget +} + +func ReadConfig(file string) (Config, error) { + bytes, err := ioutil.ReadFile(file) + if err != nil { + return Config{}, err + } + var result Config + json.Unmarshal(bytes, &result) + return result, nil +} diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..77256b7 --- /dev/null +++ b/db/db.go @@ -0,0 +1,17 @@ +package db + +import ( + "github.com/jinzhu/gorm" + _ "github.com/jinzhu/gorm/dialects/sqlite" +) + +var db *gorm.DB + +func init() { + var err error + db, err = gorm.Open("sqlite3", "data.db") + if err != nil { + panic("failed to connect database") + } + db.AutoMigrate(&PingEntry{}) +} diff --git a/db/ping.go b/db/ping.go new file mode 100644 index 0000000..ffc9f79 --- /dev/null +++ b/db/ping.go @@ -0,0 +1,31 @@ +package db + +import ( + "time" + + "github.com/jinzhu/gorm" + + "status/check" +) + +//PingEntry represents an entry of Ping that is persisted in the DB. +type PingEntry struct { + gorm.Model + ServiceName string + Time time.Time + Ping int64 + Status check.Result +} + +func (p PingEntry) AddToDB() { + if db == nil { + panic("DB is nil") + } + db.Create(&p) +} + +func GetFromDB(serviceName string, from, to time.Time) []*PingEntry { + entries := make([]*PingEntry, 0) + db.Where("service_name = ? AND time BETWEEN ? AND ?", serviceName, from, to).Find(&entries) + return entries +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9d84230 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module status + +go 1.14 + +require ( + github.com/jinzhu/gorm v1.9.12 + github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c + golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..adaf3a3 --- /dev/null +++ b/go.sum @@ -0,0 +1,35 @@ +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q= +github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= +github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c h1:gqEdF4VwBu3lTKGHS9rXE9x1/pEaSwCXRLOZRF6qtlw= +github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/main.go b/main.go new file mode 100644 index 0000000..c586cd7 --- /dev/null +++ b/main.go @@ -0,0 +1,9 @@ +package main + +func main() { + cfg, err := ReadConfig("config.json") + if err != nil { + panic(err) + } + checkLoop(cfg.Targets) +}