diff --git a/cmd/servicechecker/main.go b/cmd/servicechecker/main.go index bd24f0b..670873d 100644 --- a/cmd/servicechecker/main.go +++ b/cmd/servicechecker/main.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "os/signal" "time" grpccli "go.unistack.org/micro-client-grpc/v3" @@ -15,6 +16,7 @@ import ( "go.unistack.org/micro/v3/semconv" "go.unistack.org/servicechecker/pkg/config" "go.unistack.org/servicechecker/pkg/grpcconn" + "go.unistack.org/servicechecker/pkg/scheduler" "google.golang.org/protobuf/reflect/protodesc" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/descriptorpb" @@ -22,7 +24,9 @@ import ( ) func main() { - ctx := context.Background() + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) + defer stop() + l := slog.NewLogger() l.Init() m := victoriametrics.NewMeter() @@ -36,6 +40,15 @@ func main() { l.Fatal(ctx, "failed to open config", err) } + s, err := scheduler.NewScheduler() + if err != nil { + l.Fatal(ctx, "failed to create scheduler", err) + } + + if err = s.Start(); err != nil { + l.Fatal(ctx, "failed to start scheduler", err) + } + clients := make(map[string]client.Client) gcli := grpccli.NewClient( client.Codec("application/json", jsonpbcodec.NewCodec()), @@ -110,5 +123,6 @@ func main() { } } } - m.Write(os.Stdout) + + <-ctx.Done() } diff --git a/go.mod b/go.mod index 5ca05ca..7202c9a 100644 --- a/go.mod +++ b/go.mod @@ -3,23 +3,27 @@ module go.unistack.org/servicechecker go 1.23.3 require ( + github.com/go-co-op/gocron/v2 v2.12.3 github.com/google/uuid v1.6.0 - github.com/jhump/protoreflect/v2 v2.0.0-beta.2 go.unistack.org/micro-client-grpc/v3 v3.11.10 go.unistack.org/micro-codec-jsonpb/v3 v3.10.3 go.unistack.org/micro-codec-proto/v3 v3.10.2 go.unistack.org/micro-meter-victoriametrics/v3 v3.8.9 - go.unistack.org/micro-proto/v3 v3.4.1 go.unistack.org/micro/v3 v3.10.100 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/kr/text v0.2.0 // indirect + github.com/jonboulle/clockwork v0.4.0 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/valyala/fastrand v1.1.0 // indirect github.com/valyala/histogram v1.2.0 // indirect go.unistack.org/metrics v0.0.1 // indirect + go.unistack.org/micro-proto/v3 v3.4.1 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.18.0 // indirect diff --git a/go.sum b/go.sum index 97722a9..0847a67 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,26 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-co-op/gocron/v2 v2.12.3 h1:3JkKjkFoAPp/i0YE+sonlF5gi+xnBChwYh75nX16MaE= +github.com/go-co-op/gocron/v2 v2.12.3/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jhump/protoreflect/v2 v2.0.0-beta.2 h1:qZU+rEZUOYTz1Bnhi3xbwn+VxdXkLVeEpAeZzVXLY88= -github.com/jhump/protoreflect/v2 v2.0.0-beta.2/go.mod h1:4tnOYkB/mq7QTyS3YKtVtNrJv4Psqout8HA1U+hZtgM= +github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= @@ -21,6 +29,8 @@ github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ= github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.unistack.org/metrics v0.0.1 h1:sCnGO059ZccGC/D34iRH121eSk+7ci5+OY9cl5K7GKY= go.unistack.org/metrics v0.0.1/go.mod h1:1FY4R7EKJa9Oz2D6wlGScNerpl6igRs9Cx/3et4Rgs4= go.unistack.org/micro-client-grpc/v3 v3.11.10 h1:hbqCL4JLbTtPN1ee16EK6LqTbeIr6HynJJjCRWAu2kk= @@ -35,6 +45,8 @@ go.unistack.org/micro-proto/v3 v3.4.1 h1:UTjLSRz2YZuaHk9iSlVqqsA50JQNAEK2ZFboGqt go.unistack.org/micro-proto/v3 v3.4.1/go.mod h1:okx/cnOhzuCX0ggl/vToatbCupi0O44diiiLLsZ93Zo= go.unistack.org/micro/v3 v3.10.100 h1:yWOaU0ImCGm5k5MUzlIobJUOr+KLfrR/BoDZvcHyKxM= go.unistack.org/micro/v3 v3.10.100/go.mod h1:YzMldzHN9Ei+zy5t/Psu7RUWDZwUfrNYiStSQtTz90g= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= @@ -48,7 +60,9 @@ google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFN google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/config/config.go b/pkg/config/config.go index 3f7c442..6375259 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -22,9 +22,10 @@ type Service struct { } type Check struct { - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Data string `json:"data,omitempty" yaml:"data,omitempty"` - Timeout mtime.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Data string `json:"data,omitempty" yaml:"data,omitempty"` + Timeout mtime.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"` + Interval mtime.Duration `json:"interval,omitempty" yaml:"interval,omitempty"` } func (cfg *Config) Parse(r io.Reader) error { diff --git a/pkg/scheduler/scheduler.go b/pkg/scheduler/scheduler.go new file mode 100644 index 0000000..6819bc7 --- /dev/null +++ b/pkg/scheduler/scheduler.go @@ -0,0 +1,93 @@ +package scheduler + +import ( + "time" + + cron "github.com/go-co-op/gocron/v2" +) + +type Task func() + +type Scheduler interface { + Start() error + Stop() error + Jobs() []Job + NewJob(td time.Duration, fn any, args ...any) (Job, error) +} + +type Job interface { + ID() string + LastRun() (time.Time, error) + Name() string + NextRun() (time.Time, error) + NextRuns(int) ([]time.Time, error) +} + +type Options struct{} + +type Option func(*Options) error + +type scheduler struct { + scheduler cron.Scheduler +} + +func NewScheduler(opts ...Option) (Scheduler, error) { + s, err := cron.NewScheduler() + if err != nil { + return nil, err + } + return &scheduler{scheduler: s}, nil +} + +func (s *scheduler) Start() error { + s.scheduler.Start() + return nil +} + +func (s *scheduler) Stop() error { + if err := s.scheduler.Shutdown(); err != nil { + return err + } + return nil +} + +func (s *scheduler) NewJob(td time.Duration, fn any, args ...any) (Job, error) { + j, err := s.scheduler.NewJob(cron.DurationJob(td), cron.NewTask(fn, args...)) + if err != nil { + return nil, err + } + return &job{job: j}, nil +} + +func (s *scheduler) Jobs() []Job { + jobs := s.scheduler.Jobs() + ret := make([]Job, len(jobs)) + for idx := range jobs { + ret[idx] = &job{job: jobs[idx]} + } + return ret +} + +type job struct { + job cron.Job +} + +func (j *job) ID() string { + return j.job.ID().String() +} + +func (j *job) LastRun() (time.Time, error) { + return j.job.LastRun() +} + +func (j *job) Name() string { + return j.job.Name() +} + +func (j *job) NextRun() (time.Time, error) { + return j.job.NextRun() +} + +func (j *job) NextRuns(n int) ([]time.Time, error) { + return j.job.NextRuns(n) +}