Run Jobs on a schedule, supports fixed interval, timely, and cron-expression timers; Instrument your processes and expose metrics for each job.
A simple process manager that allows you to specify a Schedule that execute a Job based on a Timer. Schedule manage the state of this job allowing you to start/stop/restart in concurrent safe way. Schedule also instrument this Job and gather metrics and optionally expose them via uber-go/tally scope.
go get github.com/sherifabdlnaby/sched
import "github.com/sherifabdlnaby/sched"
Go 1.13 >=
Simply a func(){}
implementation that is the schedule goal to run, and instrument.
An Object that Implements the type Timer interface{}. A Timer is responsible for providing a schedule with the next time the job should run and if there will be subsequent runs.
Packaged Implementations:
- Fixed :- Infinitely Fires a job at a
Fixed Interval (
time.Duration
) - Cron :- Infinitely Fires a job based on a Cron Expression, all Expressions supported by gorhill/cronexpr are supported.
- Once :- A Timer that run ONCE after an optional specific delay or at a specified time, schedule will stop after it fires.
You can Implement your own Timer for your specific scheduling needs by implementing
type Timer interface {
// done indicated that there will be no more runs.
Next() (next time.Time, done bool)
}
A Schedule wraps a Job and fires it according to Timer.
fixedTimer30second, _ := sched.NewFixed(30 * time.Second)
job := func() {
log.Println("Doing some work...")
time.Sleep(1 * time.Second)
log.Println("Finished Work.")
}
// Create Schedule
schedule := sched.NewSchedule("every30s", fixedTimer30second, job)
// Start
schedule.Start()
Additional Options can be passed to Schedule to change its behavior.
// Create Schedule
schedule := sched.NewSchedule("every30s", fixedTimer30second, job,
sched.WithLogger(sched.DefaultLogger()),
opt2,
opt3,
....,
)
WithLogger( logger Logger)
-> Supply the Schedule the Logger it is going to use for logging.
- func DefaultLogger() Logger : Provide a Default Logging Interface to be used Instead of Implementing your own.
- func NopLogger() Logger : A nop Logger that will not output anything to stdout.
WithMetrics( scope tally.Scope)
-> Supply the Schedule with a metrics scope it can use to export metrics.
- Use any of
uber-go/tally
implementations (Prometheus, statsd, etc)
Use func WithConsoleMetrics(printEvery time.Duration) Option
Implementation to Output Metrics to stdout (good for debugging)
WithExpectedRunTime(d time.Duration)
-> Supply the Schedule with the expected duration for the job to run, schedule
will output corresponding logs and metrics if job run exceeded expected.
Scheduler manage one or more Schedule creating them using common options, enforcing unique IDs, and supply methods to Start / Stop all schedule(s).
Metric | Type | Desc |
---|---|---|
up | Gauge | If the schedule is Running / Stopped |
runs | Counter | Number of Runs Since Starting |
runs_overlapping | Counter | Number of times more than one job was running together. (Overlapped) |
run_actual_elapsed_time | Time | Elapsed Time between Starting and Ending of Job Execution |
run_total_elapsed_time | Time | Total Elapsed Time between Creating the Job and Ending of Job Execution, This differ from Actual Elapsed time when Overlapping blocking is Implemented |
run_errors | Counter | Count Number of Times a Job error'd(Panicked) during execution. |
run_exceed_expected_time | Counter | Count Number of Times a Job Execution Time exceeded the Expected Time |
# HELP sched_run_actual_elapsed_time sched_run_actual_elapsed_time summary
# TYPE sched_run_actual_elapsed_time summary
sched_run_actual_elapsed_time{id="every5s",quantile="0.5"} 0.203843151
sched_run_actual_elapsed_time{id="every5s",quantile="0.75"} 1.104031623
sched_run_actual_elapsed_time{id="every5s",quantile="0.95"} 1.104031623
sched_run_actual_elapsed_time{id="every5s",quantile="0.99"} 1.104031623
sched_run_actual_elapsed_time{id="every5s",quantile="0.999"} 1.104031623
sched_run_actual_elapsed_time_sum{id="every5s"} 1.307874774
sched_run_actual_elapsed_time_count{id="every5s"} 2
# HELP sched_run_errors sched_run_errors counter
# TYPE sched_run_errors counter
sched_run_errors{id="every5s"} 0
# HELP sched_run_exceed_expected_time sched_run_exceed_expected_time counter
# TYPE sched_run_exceed_expected_time counter
sched_run_exceed_expected_time{id="every5s"} 0
# HELP sched_run_total_elapsed_time sched_run_total_elapsed_time summary
# TYPE sched_run_total_elapsed_time summary
sched_run_total_elapsed_time{id="every5s",quantile="0.5"} 0.203880714
sched_run_total_elapsed_time{id="every5s",quantile="0.75"} 1.104065614
sched_run_total_elapsed_time{id="every5s",quantile="0.95"} 1.104065614
sched_run_total_elapsed_time{id="every5s",quantile="0.99"} 1.104065614
sched_run_total_elapsed_time{id="every5s",quantile="0.999"} 1.104065614
sched_run_total_elapsed_time_sum{id="every5s"} 1.307946328
sched_run_total_elapsed_time_count{id="every5s"} 2
# HELP sched_runs sched_runs counter
# TYPE sched_runs counter
sched_runs{id="every5s"} 2
# HELP sched_runs_overlapping sched_runs_overlapping counter
# TYPE sched_runs_overlapping counter
sched_runs_overlapping{id="every5s"} 0
# HELP sched_up sched_up gauge
# TYPE sched_up gauge
sched_up{id="every5s"} 1
- schedule-console-metrics
- schedule-cron
- schedule-fixed
- schedule-four-mixed-timers
- schedule-once
- schedule-overlapping
- schedule-panic
- schedule-prom-metrics
- schedule-warn-expected
- scheduler
- scheduler-extra-opts
package main
import (
"fmt"
"github.com/sherifabdlnaby/sched"
"log"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
cronTimer, err := sched.NewCron("* * * * *")
if err != nil {
panic(fmt.Sprintf("invalid cron expression: %s", err.Error()))
}
job := func() {
log.Println("Doing some work...")
time.Sleep(1 * time.Second)
log.Println("Finished Work.")
}
// Create Schedule
schedule := sched.NewSchedule("cron", cronTimer, job, sched.WithLogger(sched.DefaultLogger()))
// Start Schedule
schedule.Start()
// Stop schedule after 5 Minutes
time.AfterFunc(5*time.Minute, func() {
schedule.Stop()
})
// Listen to CTRL + C
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
_ = <-signalChan
// Stop before shutting down.
schedule.Stop()
return
}
2021-04-10T12:30: 13.132+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "cron"}
2021-04-10T12:30: 13.132+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "47s", "At": "2021-04-10T12:31:00+02:00"}
2021-04-10T12: 31: 00.000+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:32:00+02:00"}
2021-04-10T12: 31:00.000+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "8e1044ab-20b6-4acf-8a15-e06c0418522c"}
2021/04/10 12: 31: 00 Doing some work...
2021/04/10 12: 31: 01 Finished Work.
2021-04-10T12: 31: 01.001+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron", "Instance": "8e1044ab-20b6-4acf-8a15-e06c0418522c", "Duration": "1.001s", "State": "FINISHED"}
2021-04-10T12:32: 00.002+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:33:00+02:00"}
2021-04-10T12: 32: 00.002+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "baae94eb-f818-4b34-a1f4-45b521a360a1"}
2021/04/10 12: 32: 00 Doing some work...
2021/04/10 12: 32: 01 Finished Work.
2021-04-10T12:32: 01.005+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron", "Instance": "baae94eb-f818-4b34-a1f4-45b521a360a1", "Duration": "1.003s", "State": "FINISHED"}
2021-04-10T12: 33: 00.001+0200 INFO sched sched/schedule.go:168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:34:00+02:00"}
2021-04-10T12:33: 00.001+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "71c8f0bf-3624-4a92-909c-b4149f3c62a3"}
2021/04/10 12: 33: 00 Doing some work...
2021/04/10 12: 33: 01 Finished Work.
2021-04-10T12: 33: 01.004+0200 INFO sched sched/schedule.go:208 Job Finished {"id": "cron", "Instance": "71c8f0bf-3624-4a92-909c-b4149f3c62a3", "Duration": "1.003s", "State": "FINISHED"}
2021-04-10T12:28: 45.591+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "cron"}
2021-04-10T12:28: 45.592+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "14s", "At": "2021-04-10T12:29:00+02:00"}
2021-04-10T12: 29: 00.000+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:30:00+02:00"}
2021-04-10T12: 29:00.000+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "786540f1-594b-44a0-9a66-7181619e38a6"}
2021/04/10 12: 29: 00 Doing some work...
CTRL+C
2021-04-10T12: 29: 00.567+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "cron"}
2021-04-10T12: 29: 00.567+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "cron"}
2021-04-10T12: 29: 00.567+0200 INFO sched sched/schedule.go: 171 Job Next Run Canceled {"id": "cron", "At": "2021-04-10T12:30:00+02:00"}
2021/04/10 12: 29: 01 Finished Work.
2021-04-10T12: 29:01.000+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron", "Instance": "786540f1-594b-44a0-9a66-7181619e38a6", "Duration": "1s", "State": "FINISHED"}
2021-04-10T12: 29: 01.000+0200 INFO sched sched/schedule.go: 133 Job Schedule Stopped {"id": "cron" }
- Control Logging Verbosity
- Make Panic Recovery Optional
- Make Job a func() error and allow retry(s), backoff, and collect errors and their metrics
- Make Jobs context aware and support canceling Jobs Context.
- Make allow Overlapping Optional and Configure How Overlapping is handled/denied.
- Global Package-Level Metrics
MIT License Copyright (c) 2021 Sherif Abdel-Naby
PR(s) are Open and Welcomed. ❤️