change admin to toolbox & support task
Showing
10 changed files
with
579 additions
and
92 deletions
| ... | @@ -2,7 +2,7 @@ package beego | ... | @@ -2,7 +2,7 @@ package beego |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "fmt" | 4 | "fmt" |
| 5 | "github.com/astaxie/beego/admin" | 5 | "github.com/astaxie/beego/toolbox" |
| 6 | "net/http" | 6 | "net/http" |
| 7 | "time" | 7 | "time" |
| 8 | ) | 8 | ) |
| ... | @@ -32,8 +32,9 @@ func init() { | ... | @@ -32,8 +32,9 @@ func init() { |
| 32 | BeeAdminApp.Route("/", AdminIndex) | 32 | BeeAdminApp.Route("/", AdminIndex) |
| 33 | BeeAdminApp.Route("/qps", QpsIndex) | 33 | BeeAdminApp.Route("/qps", QpsIndex) |
| 34 | BeeAdminApp.Route("/prof", ProfIndex) | 34 | BeeAdminApp.Route("/prof", ProfIndex) |
| 35 | BeeAdminApp.Route("/healthcheck", admin.Healthcheck) | 35 | BeeAdminApp.Route("/healthcheck", toolbox.Healthcheck) |
| 36 | BeeAdminApp.Route("/task", admin.TaskStatus) | 36 | BeeAdminApp.Route("/task", toolbox.TaskStatus) |
| 37 | BeeAdminApp.Route("/runtask", toolbox.RunTask) | ||
| 37 | FilterMonitorFunc = func(string, string, time.Duration) bool { return true } | 38 | FilterMonitorFunc = func(string, string, time.Duration) bool { return true } |
| 38 | } | 39 | } |
| 39 | 40 | ||
| ... | @@ -42,14 +43,14 @@ func AdminIndex(rw http.ResponseWriter, r *http.Request) { | ... | @@ -42,14 +43,14 @@ func AdminIndex(rw http.ResponseWriter, r *http.Request) { |
| 42 | } | 43 | } |
| 43 | 44 | ||
| 44 | func QpsIndex(rw http.ResponseWriter, r *http.Request) { | 45 | func QpsIndex(rw http.ResponseWriter, r *http.Request) { |
| 45 | admin.StatisticsMap.GetMap(rw) | 46 | toolbox.StatisticsMap.GetMap(rw) |
| 46 | } | 47 | } |
| 47 | 48 | ||
| 48 | func ProfIndex(rw http.ResponseWriter, r *http.Request) { | 49 | func ProfIndex(rw http.ResponseWriter, r *http.Request) { |
| 49 | r.ParseForm() | 50 | r.ParseForm() |
| 50 | command := r.Form.Get("command") | 51 | command := r.Form.Get("command") |
| 51 | if command != "" { | 52 | if command != "" { |
| 52 | admin.ProcessInput(command, rw) | 53 | toolbox.ProcessInput(command, rw) |
| 53 | } else { | 54 | } else { |
| 54 | rw.Write([]byte("request url like '/prof?command=lookup goroutine'")) | 55 | rw.Write([]byte("request url like '/prof?command=lookup goroutine'")) |
| 55 | } | 56 | } | ... | ... |
admin/task.go
deleted
100644 → 0
| 1 | package admin | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "fmt" | ||
| 5 | "net/http" | ||
| 6 | ) | ||
| 7 | |||
| 8 | var AdminTaskList map[string]Tasker | ||
| 9 | |||
| 10 | type Tasker interface { | ||
| 11 | GetStatus() string | ||
| 12 | Run() error | ||
| 13 | } | ||
| 14 | |||
| 15 | type Task struct { | ||
| 16 | Taskname string | ||
| 17 | Spec Schedule | ||
| 18 | Errlist []map[uint64]string //errtime:errinfo | ||
| 19 | ErrLimit int //max length for the errlist 0 stand for there' no limit | ||
| 20 | } | ||
| 21 | |||
| 22 | func (t *Task) GetStatus() string { | ||
| 23 | return "" | ||
| 24 | } | ||
| 25 | |||
| 26 | func (t *Task) Run() error { | ||
| 27 | return nil | ||
| 28 | } | ||
| 29 | |||
| 30 | func (t *Task) SetCron(spec string) { | ||
| 31 | |||
| 32 | } | ||
| 33 | |||
| 34 | type Schedule struct { | ||
| 35 | Second uint64 | ||
| 36 | Minute uint64 | ||
| 37 | Hour uint64 | ||
| 38 | DOM uint64 | ||
| 39 | Month uint64 | ||
| 40 | DOW uint64 | ||
| 41 | } | ||
| 42 | |||
| 43 | func StartTask() { | ||
| 44 | |||
| 45 | } | ||
| 46 | |||
| 47 | func StopTask() { | ||
| 48 | |||
| 49 | } | ||
| 50 | |||
| 51 | func AddTask(taskname string, t Tasker) { | ||
| 52 | AdminTaskList[taskname] = t | ||
| 53 | } | ||
| 54 | |||
| 55 | func TaskStatus(rw http.ResponseWriter, req *http.Request) { | ||
| 56 | for tname, t := range AdminTaskList { | ||
| 57 | fmt.Fprintf(rw, "%s:%s", tname, t.GetStatus()) | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | //to run a Task by http from the querystring taskname | ||
| 62 | //url like /task?taskname=sendmail | ||
| 63 | func RunTask(rw http.ResponseWriter, req *http.Request) { | ||
| 64 | req.ParseForm() | ||
| 65 | taskname := req.Form.Get("taskname") | ||
| 66 | if t, ok := AdminTaskList[taskname]; ok { | ||
| 67 | err := t.Run() | ||
| 68 | if err != nil { | ||
| 69 | fmt.Fprintf(rw, "%v", err) | ||
| 70 | } | ||
| 71 | fmt.Fprintf(rw, "%s run success,Now the Status is %s", t.GetStatus()) | ||
| 72 | } else { | ||
| 73 | fmt.Fprintf(rw, "there's no task which named:%s", taskname) | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | func init() { | ||
| 78 | AdminTaskList = make(map[string]Tasker) | ||
| 79 | } |
| ... | @@ -2,9 +2,9 @@ package beego | ... | @@ -2,9 +2,9 @@ package beego |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "fmt" | 4 | "fmt" |
| 5 | "github.com/astaxie/beego/admin" | ||
| 6 | beecontext "github.com/astaxie/beego/context" | 5 | beecontext "github.com/astaxie/beego/context" |
| 7 | "github.com/astaxie/beego/middleware" | 6 | "github.com/astaxie/beego/middleware" |
| 7 | "github.com/astaxie/beego/toolbox" | ||
| 8 | "net/http" | 8 | "net/http" |
| 9 | "net/url" | 9 | "net/url" |
| 10 | "os" | 10 | "os" |
| ... | @@ -861,9 +861,9 @@ Admin: | ... | @@ -861,9 +861,9 @@ Admin: |
| 861 | timeend := time.Since(starttime) | 861 | timeend := time.Since(starttime) |
| 862 | if FilterMonitorFunc(r.Method, requestPath, timeend) { | 862 | if FilterMonitorFunc(r.Method, requestPath, timeend) { |
| 863 | if runrouter != nil { | 863 | if runrouter != nil { |
| 864 | go admin.StatisticsMap.AddStatistics(r.Method, requestPath, runrouter.controllerType.Name(), timeend) | 864 | go toolbox.StatisticsMap.AddStatistics(r.Method, requestPath, runrouter.controllerType.Name(), timeend) |
| 865 | } else { | 865 | } else { |
| 866 | go admin.StatisticsMap.AddStatistics(r.Method, requestPath, "", timeend) | 866 | go toolbox.StatisticsMap.AddStatistics(r.Method, requestPath, "", timeend) |
| 867 | } | 867 | } |
| 868 | } | 868 | } |
| 869 | } | 869 | } | ... | ... |
toolbox/task.go
0 → 100644
| 1 | package toolbox | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "fmt" | ||
| 5 | "log" | ||
| 6 | "math" | ||
| 7 | "net/http" | ||
| 8 | "sort" | ||
| 9 | "strconv" | ||
| 10 | "strings" | ||
| 11 | "time" | ||
| 12 | ) | ||
| 13 | |||
| 14 | // bounds provides a range of acceptable values (plus a map of name to value). | ||
| 15 | type bounds struct { | ||
| 16 | min, max uint | ||
| 17 | names map[string]uint | ||
| 18 | } | ||
| 19 | |||
| 20 | // The bounds for each field. | ||
| 21 | var ( | ||
| 22 | AdminTaskList map[string]Tasker | ||
| 23 | stop chan bool | ||
| 24 | seconds = bounds{0, 59, nil} | ||
| 25 | minutes = bounds{0, 59, nil} | ||
| 26 | hours = bounds{0, 23, nil} | ||
| 27 | days = bounds{1, 31, nil} | ||
| 28 | months = bounds{1, 12, map[string]uint{ | ||
| 29 | "jan": 1, | ||
| 30 | "feb": 2, | ||
| 31 | "mar": 3, | ||
| 32 | "apr": 4, | ||
| 33 | "may": 5, | ||
| 34 | "jun": 6, | ||
| 35 | "jul": 7, | ||
| 36 | "aug": 8, | ||
| 37 | "sep": 9, | ||
| 38 | "oct": 10, | ||
| 39 | "nov": 11, | ||
| 40 | "dec": 12, | ||
| 41 | }} | ||
| 42 | weeks = bounds{0, 6, map[string]uint{ | ||
| 43 | "sun": 0, | ||
| 44 | "mon": 1, | ||
| 45 | "tue": 2, | ||
| 46 | "wed": 3, | ||
| 47 | "thu": 4, | ||
| 48 | "fri": 5, | ||
| 49 | "sat": 6, | ||
| 50 | }} | ||
| 51 | ) | ||
| 52 | |||
| 53 | const ( | ||
| 54 | // Set the top bit if a star was included in the expression. | ||
| 55 | starBit = 1 << 63 | ||
| 56 | ) | ||
| 57 | |||
| 58 | type Schedule struct { | ||
| 59 | Second uint64 | ||
| 60 | Minute uint64 | ||
| 61 | Hour uint64 | ||
| 62 | Day uint64 | ||
| 63 | Month uint64 | ||
| 64 | Week uint64 | ||
| 65 | } | ||
| 66 | |||
| 67 | type TaskFunc func() error | ||
| 68 | |||
| 69 | type Tasker interface { | ||
| 70 | GetStatus() string | ||
| 71 | Run() error | ||
| 72 | SetNext(time.Time) | ||
| 73 | GetNext() time.Time | ||
| 74 | SetPrev(time.Time) | ||
| 75 | GetPrev() time.Time | ||
| 76 | } | ||
| 77 | |||
| 78 | type taskerr struct { | ||
| 79 | t time.Time | ||
| 80 | errinfo string | ||
| 81 | } | ||
| 82 | |||
| 83 | type Task struct { | ||
| 84 | Taskname string | ||
| 85 | Spec *Schedule | ||
| 86 | DoFunc TaskFunc | ||
| 87 | Prev time.Time | ||
| 88 | Next time.Time | ||
| 89 | Errlist []*taskerr //errtime:errinfo | ||
| 90 | ErrLimit int //max length for the errlist 0 stand for there' no limit | ||
| 91 | } | ||
| 92 | |||
| 93 | func NewTask(tname string, spec string, f TaskFunc) *Task { | ||
| 94 | |||
| 95 | task := &Task{ | ||
| 96 | Taskname: tname, | ||
| 97 | DoFunc: f, | ||
| 98 | ErrLimit: 100, | ||
| 99 | } | ||
| 100 | task.SetCron(spec) | ||
| 101 | return task | ||
| 102 | } | ||
| 103 | |||
| 104 | func (tk *Task) GetStatus() string { | ||
| 105 | var str string | ||
| 106 | for _, v := range tk.Errlist { | ||
| 107 | str += v.t.String() + ":" + v.errinfo + "\n" | ||
| 108 | } | ||
| 109 | return str | ||
| 110 | } | ||
| 111 | |||
| 112 | func (tk *Task) Run() error { | ||
| 113 | err := tk.DoFunc() | ||
| 114 | if err != nil { | ||
| 115 | if tk.ErrLimit > 0 && tk.ErrLimit > len(tk.Errlist) { | ||
| 116 | tk.Errlist = append(tk.Errlist, &taskerr{t: tk.Next, errinfo: err.Error()}) | ||
| 117 | } | ||
| 118 | } | ||
| 119 | return err | ||
| 120 | } | ||
| 121 | |||
| 122 | func (tk *Task) SetNext(now time.Time) { | ||
| 123 | tk.Next = tk.Spec.Next(now) | ||
| 124 | } | ||
| 125 | |||
| 126 | func (tk *Task) GetNext() time.Time { | ||
| 127 | return tk.Next | ||
| 128 | } | ||
| 129 | |||
| 130 | //前6个字段分别表示: | ||
| 131 | // 秒钟:0-59 | ||
| 132 | // 分钟:0-59 | ||
| 133 | // 小时:1-23 | ||
| 134 | // 日期:1-31 | ||
| 135 | // 月份:1-12 | ||
| 136 | // 星期:0-6(0表示周日) | ||
| 137 | |||
| 138 | //还可以用一些特殊符号: | ||
| 139 | // *: 表示任何时刻 | ||
| 140 | // ,: 表示分割,如第三段里:2,4,表示2点和4点执行 | ||
| 141 | // -:表示一个段,如第三端里: 1-5,就表示1到5点 | ||
| 142 | // /n : 表示每个n的单位执行一次,如第三段里,*/1, 就表示每隔1个小时执行一次命令。也可以写成1-23/1. | ||
| 143 | ///////////////////////////////////////////////////////// | ||
| 144 | // */30 * * * * * 每30秒 执行 | ||
| 145 | // * 43 21 * * * 21:43 执行 | ||
| 146 | // * 15 05 * * * 05:15 执行 | ||
| 147 | // * 0 17 * * * 17:00 执行 | ||
| 148 | // * 0 17 * * 1 每周一的 17:00 执行 | ||
| 149 | // * 0,10 17 * * 0,2,3 每周日,周二,周三的 17:00和 17:10 执行 | ||
| 150 | // * 0-10 17 1 * * 毎月1日从 17:00到7:10 毎隔1分钟 执行 | ||
| 151 | // * 0 0 1,15 * 1 毎月1日和 15日和 一日的 0:00 执行 | ||
| 152 | // * 42 4 1 * * 毎月1日的 4:42分 执行 | ||
| 153 | // * 0 21 * * 1-6 周一到周六 21:00 执行 | ||
| 154 | // * 0,10,20,30,40,50 * * * * 每隔10分 执行 | ||
| 155 | // * */10 * * * * 每隔10分 执行 | ||
| 156 | // * * 1 * * * 从1:0到1:59 每隔1分钟 执行 | ||
| 157 | // * 0 1 * * * 1:00 执行 | ||
| 158 | // * 0 */1 * * * 毎时0分 每隔1小时 执行 | ||
| 159 | // * 0 * * * * 毎时0分 每隔1小时 执行 | ||
| 160 | // * 2 8-20/3 * * * 8:02,11:02,14:02,17:02,20:02 执行 | ||
| 161 | // * 30 5 1,15 * * 1日 和 15日的 5:30 执行 | ||
| 162 | func (t *Task) SetCron(spec string) { | ||
| 163 | t.Spec = t.parse(spec) | ||
| 164 | } | ||
| 165 | |||
| 166 | func (t *Task) parse(spec string) *Schedule { | ||
| 167 | if len(spec) > 0 && spec[0] == '@' { | ||
| 168 | return t.parseSpec(spec) | ||
| 169 | } | ||
| 170 | // Split on whitespace. We require 5 or 6 fields. | ||
| 171 | // (second) (minute) (hour) (day of month) (month) (day of week, optional) | ||
| 172 | fields := strings.Fields(spec) | ||
| 173 | if len(fields) != 5 && len(fields) != 6 { | ||
| 174 | log.Panicf("Expected 5 or 6 fields, found %d: %s", len(fields), spec) | ||
| 175 | } | ||
| 176 | |||
| 177 | // If a sixth field is not provided (DayOfWeek), then it is equivalent to star. | ||
| 178 | if len(fields) == 5 { | ||
| 179 | fields = append(fields, "*") | ||
| 180 | } | ||
| 181 | |||
| 182 | schedule := &Schedule{ | ||
| 183 | Second: getField(fields[0], seconds), | ||
| 184 | Minute: getField(fields[1], minutes), | ||
| 185 | Hour: getField(fields[2], hours), | ||
| 186 | Day: getField(fields[3], days), | ||
| 187 | Month: getField(fields[4], months), | ||
| 188 | Week: getField(fields[5], weeks), | ||
| 189 | } | ||
| 190 | |||
| 191 | return schedule | ||
| 192 | } | ||
| 193 | |||
| 194 | func (t *Task) parseSpec(spec string) *Schedule { | ||
| 195 | switch spec { | ||
| 196 | case "@yearly", "@annually": | ||
| 197 | return &Schedule{ | ||
| 198 | Second: 1 << seconds.min, | ||
| 199 | Minute: 1 << minutes.min, | ||
| 200 | Hour: 1 << hours.min, | ||
| 201 | Day: 1 << days.min, | ||
| 202 | Month: 1 << months.min, | ||
| 203 | Week: all(weeks), | ||
| 204 | } | ||
| 205 | |||
| 206 | case "@monthly": | ||
| 207 | return &Schedule{ | ||
| 208 | Second: 1 << seconds.min, | ||
| 209 | Minute: 1 << minutes.min, | ||
| 210 | Hour: 1 << hours.min, | ||
| 211 | Day: 1 << days.min, | ||
| 212 | Month: all(months), | ||
| 213 | Week: all(weeks), | ||
| 214 | } | ||
| 215 | |||
| 216 | case "@weekly": | ||
| 217 | return &Schedule{ | ||
| 218 | Second: 1 << seconds.min, | ||
| 219 | Minute: 1 << minutes.min, | ||
| 220 | Hour: 1 << hours.min, | ||
| 221 | Day: all(days), | ||
| 222 | Month: all(months), | ||
| 223 | Week: 1 << weeks.min, | ||
| 224 | } | ||
| 225 | |||
| 226 | case "@daily", "@midnight": | ||
| 227 | return &Schedule{ | ||
| 228 | Second: 1 << seconds.min, | ||
| 229 | Minute: 1 << minutes.min, | ||
| 230 | Hour: 1 << hours.min, | ||
| 231 | Day: all(days), | ||
| 232 | Month: all(months), | ||
| 233 | Week: all(weeks), | ||
| 234 | } | ||
| 235 | |||
| 236 | case "@hourly": | ||
| 237 | return &Schedule{ | ||
| 238 | Second: 1 << seconds.min, | ||
| 239 | Minute: 1 << minutes.min, | ||
| 240 | Hour: all(hours), | ||
| 241 | Day: all(days), | ||
| 242 | Month: all(months), | ||
| 243 | Week: all(weeks), | ||
| 244 | } | ||
| 245 | } | ||
| 246 | log.Panicf("Unrecognized descriptor: %s", spec) | ||
| 247 | return nil | ||
| 248 | } | ||
| 249 | |||
| 250 | func (s *Schedule) Next(t time.Time) time.Time { | ||
| 251 | |||
| 252 | // Start at the earliest possible time (the upcoming second). | ||
| 253 | t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond) | ||
| 254 | |||
| 255 | // This flag indicates whether a field has been incremented. | ||
| 256 | added := false | ||
| 257 | |||
| 258 | // If no time is found within five years, return zero. | ||
| 259 | yearLimit := t.Year() + 5 | ||
| 260 | |||
| 261 | WRAP: | ||
| 262 | if t.Year() > yearLimit { | ||
| 263 | return time.Time{} | ||
| 264 | } | ||
| 265 | |||
| 266 | // Find the first applicable month. | ||
| 267 | // If it's this month, then do nothing. | ||
| 268 | for 1<<uint(t.Month())&s.Month == 0 { | ||
| 269 | // If we have to add a month, reset the other parts to 0. | ||
| 270 | if !added { | ||
| 271 | added = true | ||
| 272 | // Otherwise, set the date at the beginning (since the current time is irrelevant). | ||
| 273 | t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()) | ||
| 274 | } | ||
| 275 | t = t.AddDate(0, 1, 0) | ||
| 276 | |||
| 277 | // Wrapped around. | ||
| 278 | if t.Month() == time.January { | ||
| 279 | goto WRAP | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | // Now get a day in that month. | ||
| 284 | for !dayMatches(s, t) { | ||
| 285 | if !added { | ||
| 286 | added = true | ||
| 287 | t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) | ||
| 288 | } | ||
| 289 | t = t.AddDate(0, 0, 1) | ||
| 290 | |||
| 291 | if t.Day() == 1 { | ||
| 292 | goto WRAP | ||
| 293 | } | ||
| 294 | } | ||
| 295 | |||
| 296 | for 1<<uint(t.Hour())&s.Hour == 0 { | ||
| 297 | if !added { | ||
| 298 | added = true | ||
| 299 | t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location()) | ||
| 300 | } | ||
| 301 | t = t.Add(1 * time.Hour) | ||
| 302 | |||
| 303 | if t.Hour() == 0 { | ||
| 304 | goto WRAP | ||
| 305 | } | ||
| 306 | } | ||
| 307 | |||
| 308 | for 1<<uint(t.Minute())&s.Minute == 0 { | ||
| 309 | if !added { | ||
| 310 | added = true | ||
| 311 | t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 0, 0, t.Location()) | ||
| 312 | } | ||
| 313 | t = t.Add(1 * time.Minute) | ||
| 314 | |||
| 315 | if t.Minute() == 0 { | ||
| 316 | goto WRAP | ||
| 317 | } | ||
| 318 | } | ||
| 319 | |||
| 320 | for 1<<uint(t.Second())&s.Second == 0 { | ||
| 321 | if !added { | ||
| 322 | added = true | ||
| 323 | t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, t.Location()) | ||
| 324 | } | ||
| 325 | t = t.Add(1 * time.Second) | ||
| 326 | |||
| 327 | if t.Second() == 0 { | ||
| 328 | goto WRAP | ||
| 329 | } | ||
| 330 | } | ||
| 331 | |||
| 332 | return t | ||
| 333 | } | ||
| 334 | |||
| 335 | func dayMatches(s *Schedule, t time.Time) bool { | ||
| 336 | var ( | ||
| 337 | domMatch bool = 1<<uint(t.Day())&s.Day > 0 | ||
| 338 | dowMatch bool = 1<<uint(t.Weekday())&s.Week > 0 | ||
| 339 | ) | ||
| 340 | |||
| 341 | if s.Day&starBit > 0 || s.Week&starBit > 0 { | ||
| 342 | return domMatch && dowMatch | ||
| 343 | } | ||
| 344 | return domMatch || dowMatch | ||
| 345 | } | ||
| 346 | |||
| 347 | func StartTask() { | ||
| 348 | go run() | ||
| 349 | } | ||
| 350 | |||
| 351 | func run() { | ||
| 352 | now := time.Now().Local() | ||
| 353 | for _, t := range AdminTaskList { | ||
| 354 | t.SetNext(now) | ||
| 355 | } | ||
| 356 | |||
| 357 | for { | ||
| 358 | sortList := NewMapSorter(AdminTaskList) | ||
| 359 | sortList.Sort() | ||
| 360 | var effective time.Time | ||
| 361 | if len(AdminTaskList) == 0 || sortList.Vals[0].GetNext().IsZero() { | ||
| 362 | // If there are no entries yet, just sleep - it still handles new entries | ||
| 363 | // and stop requests. | ||
| 364 | effective = now.AddDate(10, 0, 0) | ||
| 365 | } else { | ||
| 366 | effective = sortList.Vals[0].GetNext() | ||
| 367 | } | ||
| 368 | select { | ||
| 369 | case now = <-time.After(effective.Sub(now)): | ||
| 370 | // Run every entry whose next time was this effective time. | ||
| 371 | for _, e := range sortList.Vals { | ||
| 372 | if e.GetNext() != effective { | ||
| 373 | break | ||
| 374 | } | ||
| 375 | go e.Run() | ||
| 376 | e.SetPrev(e.GetNext()) | ||
| 377 | e.SetNext(effective) | ||
| 378 | } | ||
| 379 | continue | ||
| 380 | case <-stop: | ||
| 381 | return | ||
| 382 | } | ||
| 383 | } | ||
| 384 | } | ||
| 385 | |||
| 386 | func StopTask() { | ||
| 387 | stop <- true | ||
| 388 | } | ||
| 389 | |||
| 390 | func AddTask(taskname string, t Tasker) { | ||
| 391 | AdminTaskList[taskname] = t | ||
| 392 | } | ||
| 393 | |||
| 394 | func TaskStatus(rw http.ResponseWriter, req *http.Request) { | ||
| 395 | for tname, tk := range AdminTaskList { | ||
| 396 | fmt.Fprintf(rw, "%s:%s:%s", tname, tk.GetStatus(), tk.GetPrev().String()) | ||
| 397 | } | ||
| 398 | } | ||
| 399 | |||
| 400 | //to run a Task by http from the querystring taskname | ||
| 401 | //url like /task?taskname=sendmail | ||
| 402 | func RunTask(rw http.ResponseWriter, req *http.Request) { | ||
| 403 | req.ParseForm() | ||
| 404 | taskname := req.Form.Get("taskname") | ||
| 405 | if t, ok := AdminTaskList[taskname]; ok { | ||
| 406 | err := t.Run() | ||
| 407 | if err != nil { | ||
| 408 | fmt.Fprintf(rw, "%v", err) | ||
| 409 | } | ||
| 410 | fmt.Fprintf(rw, "%s run success,Now the Status is %s", t.GetStatus()) | ||
| 411 | } else { | ||
| 412 | fmt.Fprintf(rw, "there's no task which named:%s", taskname) | ||
| 413 | } | ||
| 414 | } | ||
| 415 | |||
| 416 | //sort map for tasker | ||
| 417 | type MapSorter struct { | ||
| 418 | Keys []string | ||
| 419 | Vals []Tasker | ||
| 420 | } | ||
| 421 | |||
| 422 | func NewMapSorter(m map[string]Tasker) *MapSorter { | ||
| 423 | ms := &MapSorter{ | ||
| 424 | Keys: make([]string, 0, len(m)), | ||
| 425 | Vals: make([]Tasker, 0, len(m)), | ||
| 426 | } | ||
| 427 | for k, v := range m { | ||
| 428 | ms.Keys = append(ms.Keys, k) | ||
| 429 | ms.Vals = append(ms.Vals, v) | ||
| 430 | } | ||
| 431 | return ms | ||
| 432 | } | ||
| 433 | |||
| 434 | func (ms *MapSorter) Sort() { | ||
| 435 | sort.Sort(ms) | ||
| 436 | } | ||
| 437 | |||
| 438 | func (ms *MapSorter) Len() int { return len(ms.Keys) } | ||
| 439 | func (ms *MapSorter) Less(i, j int) bool { | ||
| 440 | if ms.Vals[i].GetNext().IsZero() { | ||
| 441 | return false | ||
| 442 | } | ||
| 443 | if ms.Vals[j].GetNext().IsZero() { | ||
| 444 | return true | ||
| 445 | } | ||
| 446 | return ms.Vals[i].GetNext().Before(ms.Vals[j].GetNext()) | ||
| 447 | } | ||
| 448 | func (ms *MapSorter) Swap(i, j int) { | ||
| 449 | ms.Vals[i], ms.Vals[j] = ms.Vals[j], ms.Vals[i] | ||
| 450 | ms.Keys[i], ms.Keys[j] = ms.Keys[j], ms.Keys[i] | ||
| 451 | } | ||
| 452 | |||
| 453 | func getField(field string, r bounds) uint64 { | ||
| 454 | // list = range {"," range} | ||
| 455 | var bits uint64 | ||
| 456 | ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' }) | ||
| 457 | for _, expr := range ranges { | ||
| 458 | bits |= getRange(expr, r) | ||
| 459 | } | ||
| 460 | return bits | ||
| 461 | } | ||
| 462 | |||
| 463 | // getRange returns the bits indicated by the given expression: | ||
| 464 | // number | number "-" number [ "/" number ] | ||
| 465 | func getRange(expr string, r bounds) uint64 { | ||
| 466 | |||
| 467 | var ( | ||
| 468 | start, end, step uint | ||
| 469 | rangeAndStep = strings.Split(expr, "/") | ||
| 470 | lowAndHigh = strings.Split(rangeAndStep[0], "-") | ||
| 471 | singleDigit = len(lowAndHigh) == 1 | ||
| 472 | ) | ||
| 473 | |||
| 474 | var extra_star uint64 | ||
| 475 | if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" { | ||
| 476 | start = r.min | ||
| 477 | end = r.max | ||
| 478 | extra_star = starBit | ||
| 479 | } else { | ||
| 480 | start = parseIntOrName(lowAndHigh[0], r.names) | ||
| 481 | switch len(lowAndHigh) { | ||
| 482 | case 1: | ||
| 483 | end = start | ||
| 484 | case 2: | ||
| 485 | end = parseIntOrName(lowAndHigh[1], r.names) | ||
| 486 | default: | ||
| 487 | log.Panicf("Too many hyphens: %s", expr) | ||
| 488 | } | ||
| 489 | } | ||
| 490 | |||
| 491 | switch len(rangeAndStep) { | ||
| 492 | case 1: | ||
| 493 | step = 1 | ||
| 494 | case 2: | ||
| 495 | step = mustParseInt(rangeAndStep[1]) | ||
| 496 | |||
| 497 | // Special handling: "N/step" means "N-max/step". | ||
| 498 | if singleDigit { | ||
| 499 | end = r.max | ||
| 500 | } | ||
| 501 | default: | ||
| 502 | log.Panicf("Too many slashes: %s", expr) | ||
| 503 | } | ||
| 504 | |||
| 505 | if start < r.min { | ||
| 506 | log.Panicf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr) | ||
| 507 | } | ||
| 508 | if end > r.max { | ||
| 509 | log.Panicf("End of range (%d) above maximum (%d): %s", end, r.max, expr) | ||
| 510 | } | ||
| 511 | if start > end { | ||
| 512 | log.Panicf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr) | ||
| 513 | } | ||
| 514 | |||
| 515 | return getBits(start, end, step) | extra_star | ||
| 516 | } | ||
| 517 | |||
| 518 | // parseIntOrName returns the (possibly-named) integer contained in expr. | ||
| 519 | func parseIntOrName(expr string, names map[string]uint) uint { | ||
| 520 | if names != nil { | ||
| 521 | if namedInt, ok := names[strings.ToLower(expr)]; ok { | ||
| 522 | return namedInt | ||
| 523 | } | ||
| 524 | } | ||
| 525 | return mustParseInt(expr) | ||
| 526 | } | ||
| 527 | |||
| 528 | // mustParseInt parses the given expression as an int or panics. | ||
| 529 | func mustParseInt(expr string) uint { | ||
| 530 | num, err := strconv.Atoi(expr) | ||
| 531 | if err != nil { | ||
| 532 | log.Panicf("Failed to parse int from %s: %s", expr, err) | ||
| 533 | } | ||
| 534 | if num < 0 { | ||
| 535 | log.Panicf("Negative number (%d) not allowed: %s", num, expr) | ||
| 536 | } | ||
| 537 | |||
| 538 | return uint(num) | ||
| 539 | } | ||
| 540 | |||
| 541 | // getBits sets all bits in the range [min, max], modulo the given step size. | ||
| 542 | func getBits(min, max, step uint) uint64 { | ||
| 543 | var bits uint64 | ||
| 544 | |||
| 545 | // If step is 1, use shifts. | ||
| 546 | if step == 1 { | ||
| 547 | return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min) | ||
| 548 | } | ||
| 549 | |||
| 550 | // Else, use a simple loop. | ||
| 551 | for i := min; i <= max; i += step { | ||
| 552 | bits |= 1 << i | ||
| 553 | } | ||
| 554 | return bits | ||
| 555 | } | ||
| 556 | |||
| 557 | // all returns all bits within the given bounds. (plus the star bit) | ||
| 558 | func all(r bounds) uint64 { | ||
| 559 | return getBits(r.min, r.max, 1) | starBit | ||
| 560 | } | ||
| 561 | |||
| 562 | func init() { | ||
| 563 | AdminTaskList = make(map[string]Tasker) | ||
| 564 | } |
toolbox/task_test.go
0 → 100644
| 1 | package toolbox | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or sign in to post a comment