mirror of
https://github.com/hibiken/asynq.git
synced 2024-11-10 11:31:58 +08:00
Restructure CLI commands with subcommands
This commit is contained in:
parent
d6f389e63f
commit
f38f94b947
12
inspector.go
12
inspector.go
@ -32,10 +32,13 @@ func (i *Inspector) Queues() ([]string, error) {
|
|||||||
return i.rdb.AllQueues()
|
return i.rdb.AllQueues()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats represents a state of queues at a certain time.
|
// QueueStats represents a state of queues at a certain time.
|
||||||
type Stats struct {
|
type QueueStats struct {
|
||||||
// Name of the queue.
|
// Name of the queue.
|
||||||
Queue string
|
Queue string
|
||||||
|
// Size is the total number of tasks in the queue.
|
||||||
|
// The value is the sum of Enqueued, InProgress, Scheduled, Retry, and Dead.
|
||||||
|
Size int
|
||||||
// Number of enqueued tasks.
|
// Number of enqueued tasks.
|
||||||
Enqueued int
|
Enqueued int
|
||||||
// Number of in-progress tasks.
|
// Number of in-progress tasks.
|
||||||
@ -59,13 +62,14 @@ type Stats struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CurrentStats returns a current stats of the given queue.
|
// CurrentStats returns a current stats of the given queue.
|
||||||
func (i *Inspector) CurrentStats(qname string) (*Stats, error) {
|
func (i *Inspector) CurrentStats(qname string) (*QueueStats, error) {
|
||||||
stats, err := i.rdb.CurrentStats(qname)
|
stats, err := i.rdb.CurrentStats(qname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Stats{
|
return &QueueStats{
|
||||||
Queue: stats.Queue,
|
Queue: stats.Queue,
|
||||||
|
Size: stats.Size,
|
||||||
Enqueued: stats.Enqueued,
|
Enqueued: stats.Enqueued,
|
||||||
InProgress: stats.InProgress,
|
InProgress: stats.InProgress,
|
||||||
Scheduled: stats.Scheduled,
|
Scheduled: stats.Scheduled,
|
||||||
|
@ -77,7 +77,7 @@ func TestInspectorCurrentStats(t *testing.T) {
|
|||||||
processed map[string]int
|
processed map[string]int
|
||||||
failed map[string]int
|
failed map[string]int
|
||||||
qname string
|
qname string
|
||||||
want *Stats
|
want *QueueStats
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
enqueued: map[string][]*base.TaskMessage{
|
enqueued: map[string][]*base.TaskMessage{
|
||||||
@ -119,8 +119,9 @@ func TestInspectorCurrentStats(t *testing.T) {
|
|||||||
"low": 5,
|
"low": 5,
|
||||||
},
|
},
|
||||||
qname: "default",
|
qname: "default",
|
||||||
want: &Stats{
|
want: &QueueStats{
|
||||||
Queue: "default",
|
Queue: "default",
|
||||||
|
Size: 4,
|
||||||
Enqueued: 1,
|
Enqueued: 1,
|
||||||
InProgress: 1,
|
InProgress: 1,
|
||||||
Scheduled: 2,
|
Scheduled: 2,
|
||||||
@ -181,12 +182,13 @@ func TestInspectorHistory(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"default", 90},
|
{"default", 90},
|
||||||
{"custom", 7},
|
{"custom", 7},
|
||||||
{"default", 0},
|
{"default", 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
asynqtest.FlushDB(t, r)
|
asynqtest.FlushDB(t, r)
|
||||||
|
|
||||||
|
r.SAdd(base.AllQueues, tc.qname)
|
||||||
// populate last n days data
|
// populate last n days data
|
||||||
for i := 0; i < tc.n; i++ {
|
for i := 0; i < tc.n; i++ {
|
||||||
ts := now.Add(-time.Duration(i) * 24 * time.Hour)
|
ts := now.Add(-time.Duration(i) * 24 * time.Hour)
|
||||||
|
@ -162,30 +162,35 @@ func SeedEnqueuedQueue(tb testing.TB, r *redis.Client, msgs []*base.TaskMessage,
|
|||||||
// SeedInProgressQueue initializes the in-progress queue with the given messages.
|
// SeedInProgressQueue initializes the in-progress queue with the given messages.
|
||||||
func SeedInProgressQueue(tb testing.TB, r *redis.Client, msgs []*base.TaskMessage, qname string) {
|
func SeedInProgressQueue(tb testing.TB, r *redis.Client, msgs []*base.TaskMessage, qname string) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
r.SAdd(base.AllQueues, qname)
|
||||||
seedRedisList(tb, r, base.InProgressKey(qname), msgs)
|
seedRedisList(tb, r, base.InProgressKey(qname), msgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SeedScheduledQueue initializes the scheduled queue with the given messages.
|
// SeedScheduledQueue initializes the scheduled queue with the given messages.
|
||||||
func SeedScheduledQueue(tb testing.TB, r *redis.Client, entries []base.Z, qname string) {
|
func SeedScheduledQueue(tb testing.TB, r *redis.Client, entries []base.Z, qname string) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
r.SAdd(base.AllQueues, qname)
|
||||||
seedRedisZSet(tb, r, base.ScheduledKey(qname), entries)
|
seedRedisZSet(tb, r, base.ScheduledKey(qname), entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SeedRetryQueue initializes the retry queue with the given messages.
|
// SeedRetryQueue initializes the retry queue with the given messages.
|
||||||
func SeedRetryQueue(tb testing.TB, r *redis.Client, entries []base.Z, qname string) {
|
func SeedRetryQueue(tb testing.TB, r *redis.Client, entries []base.Z, qname string) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
r.SAdd(base.AllQueues, qname)
|
||||||
seedRedisZSet(tb, r, base.RetryKey(qname), entries)
|
seedRedisZSet(tb, r, base.RetryKey(qname), entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SeedDeadQueue initializes the dead queue with the given messages.
|
// SeedDeadQueue initializes the dead queue with the given messages.
|
||||||
func SeedDeadQueue(tb testing.TB, r *redis.Client, entries []base.Z, qname string) {
|
func SeedDeadQueue(tb testing.TB, r *redis.Client, entries []base.Z, qname string) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
r.SAdd(base.AllQueues, qname)
|
||||||
seedRedisZSet(tb, r, base.DeadKey(qname), entries)
|
seedRedisZSet(tb, r, base.DeadKey(qname), entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SeedDeadlines initializes the deadlines set with the given entries.
|
// SeedDeadlines initializes the deadlines set with the given entries.
|
||||||
func SeedDeadlines(tb testing.TB, r *redis.Client, entries []base.Z, qname string) {
|
func SeedDeadlines(tb testing.TB, r *redis.Client, entries []base.Z, qname string) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
r.SAdd(base.AllQueues, qname)
|
||||||
seedRedisZSet(tb, r, base.DeadlinesKey(qname), entries)
|
seedRedisZSet(tb, r, base.DeadlinesKey(qname), entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Version of asynq library and CLI.
|
// Version of asynq library and CLI.
|
||||||
const Version = "0.10.0"
|
const Version = "0.12.0"
|
||||||
|
|
||||||
// DefaultQueueName is the queue name used if none are specified by user.
|
// DefaultQueueName is the queue name used if none are specified by user.
|
||||||
const DefaultQueueName = "default"
|
const DefaultQueueName = "default"
|
||||||
|
@ -28,6 +28,8 @@ type Stats struct {
|
|||||||
// Paused indicates whether the queue is paused.
|
// Paused indicates whether the queue is paused.
|
||||||
// If true, tasks in the queue should not be processed.
|
// If true, tasks in the queue should not be processed.
|
||||||
Paused bool
|
Paused bool
|
||||||
|
// Size is the total number of tasks in the queue.
|
||||||
|
Size int
|
||||||
// Number of tasks in each state.
|
// Number of tasks in each state.
|
||||||
Enqueued int
|
Enqueued int
|
||||||
InProgress int
|
InProgress int
|
||||||
@ -125,20 +127,26 @@ func (r *RDB) CurrentStats(qname string) (*Stats, error) {
|
|||||||
Queue: qname,
|
Queue: qname,
|
||||||
Timestamp: now,
|
Timestamp: now,
|
||||||
}
|
}
|
||||||
|
size := 0
|
||||||
for i := 0; i < len(data); i += 2 {
|
for i := 0; i < len(data); i += 2 {
|
||||||
key := cast.ToString(data[i])
|
key := cast.ToString(data[i])
|
||||||
val := cast.ToInt(data[i+1])
|
val := cast.ToInt(data[i+1])
|
||||||
switch key {
|
switch key {
|
||||||
case base.QueueKey(qname):
|
case base.QueueKey(qname):
|
||||||
stats.Enqueued = val
|
stats.Enqueued = val
|
||||||
|
size += val
|
||||||
case base.InProgressKey(qname):
|
case base.InProgressKey(qname):
|
||||||
stats.InProgress = val
|
stats.InProgress = val
|
||||||
|
size += val
|
||||||
case base.ScheduledKey(qname):
|
case base.ScheduledKey(qname):
|
||||||
stats.Scheduled = val
|
stats.Scheduled = val
|
||||||
|
size += val
|
||||||
case base.RetryKey(qname):
|
case base.RetryKey(qname):
|
||||||
stats.Retry = val
|
stats.Retry = val
|
||||||
|
size += val
|
||||||
case base.DeadKey(qname):
|
case base.DeadKey(qname):
|
||||||
stats.Dead = val
|
stats.Dead = val
|
||||||
|
size += val
|
||||||
case base.ProcessedKey(qname, now):
|
case base.ProcessedKey(qname, now):
|
||||||
stats.Processed = val
|
stats.Processed = val
|
||||||
case base.FailedKey(qname, now):
|
case base.FailedKey(qname, now):
|
||||||
@ -151,6 +159,7 @@ func (r *RDB) CurrentStats(qname string) (*Stats, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stats.Size = size
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +177,14 @@ return res`)
|
|||||||
// HistoricalStats returns a list of stats from the last n days for the given queue.
|
// HistoricalStats returns a list of stats from the last n days for the given queue.
|
||||||
func (r *RDB) HistoricalStats(qname string, n int) ([]*DailyStats, error) {
|
func (r *RDB) HistoricalStats(qname string, n int) ([]*DailyStats, error) {
|
||||||
if n < 1 {
|
if n < 1 {
|
||||||
return []*DailyStats{}, nil
|
return nil, fmt.Errorf("the number of days must be positive")
|
||||||
|
}
|
||||||
|
exists, err := r.client.SIsMember(base.AllQueues, qname).Result()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil, &ErrQueueNotFound{qname}
|
||||||
}
|
}
|
||||||
const day = 24 * time.Hour
|
const day = 24 * time.Hour
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
@ -252,6 +268,9 @@ func (r *RDB) ListEnqueued(qname string, pgn Pagination) ([]*base.TaskMessage, e
|
|||||||
|
|
||||||
// ListInProgress returns all tasks that are currently being processed for the given queue.
|
// ListInProgress returns all tasks that are currently being processed for the given queue.
|
||||||
func (r *RDB) ListInProgress(qname string, pgn Pagination) ([]*base.TaskMessage, error) {
|
func (r *RDB) ListInProgress(qname string, pgn Pagination) ([]*base.TaskMessage, error) {
|
||||||
|
if !r.client.SIsMember(base.AllQueues, qname).Val() {
|
||||||
|
return nil, fmt.Errorf("queue %q does not exist", qname)
|
||||||
|
}
|
||||||
return r.listMessages(base.InProgressKey(qname), pgn)
|
return r.listMessages(base.InProgressKey(qname), pgn)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,17 +300,26 @@ func (r *RDB) listMessages(key string, pgn Pagination) ([]*base.TaskMessage, err
|
|||||||
// ListScheduled returns all tasks from the given queue that are scheduled
|
// ListScheduled returns all tasks from the given queue that are scheduled
|
||||||
// to be processed in the future.
|
// to be processed in the future.
|
||||||
func (r *RDB) ListScheduled(qname string, pgn Pagination) ([]base.Z, error) {
|
func (r *RDB) ListScheduled(qname string, pgn Pagination) ([]base.Z, error) {
|
||||||
|
if !r.client.SIsMember(base.AllQueues, qname).Val() {
|
||||||
|
return nil, fmt.Errorf("queue %q does not exist", qname)
|
||||||
|
}
|
||||||
return r.listZSetEntries(base.ScheduledKey(qname), pgn)
|
return r.listZSetEntries(base.ScheduledKey(qname), pgn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRetry returns all tasks from the given queue that have failed before
|
// ListRetry returns all tasks from the given queue that have failed before
|
||||||
// and willl be retried in the future.
|
// and willl be retried in the future.
|
||||||
func (r *RDB) ListRetry(qname string, pgn Pagination) ([]base.Z, error) {
|
func (r *RDB) ListRetry(qname string, pgn Pagination) ([]base.Z, error) {
|
||||||
|
if !r.client.SIsMember(base.AllQueues, qname).Val() {
|
||||||
|
return nil, fmt.Errorf("queue %q does not exist", qname)
|
||||||
|
}
|
||||||
return r.listZSetEntries(base.RetryKey(qname), pgn)
|
return r.listZSetEntries(base.RetryKey(qname), pgn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListDead returns all tasks from the given queue that have exhausted its retry limit.
|
// ListDead returns all tasks from the given queue that have exhausted its retry limit.
|
||||||
func (r *RDB) ListDead(qname string, pgn Pagination) ([]base.Z, error) {
|
func (r *RDB) ListDead(qname string, pgn Pagination) ([]base.Z, error) {
|
||||||
|
if !r.client.SIsMember(base.AllQueues, qname).Val() {
|
||||||
|
return nil, fmt.Errorf("queue %q does not exist", qname)
|
||||||
|
}
|
||||||
return r.listZSetEntries(base.DeadKey(qname), pgn)
|
return r.listZSetEntries(base.DeadKey(qname), pgn)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -615,6 +643,15 @@ func (e *ErrQueueNotFound) Error() string {
|
|||||||
return fmt.Sprintf("queue %q does not exist", e.qname)
|
return fmt.Sprintf("queue %q does not exist", e.qname)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrQueueNotEmpty indicates specified queue is not empty.
|
||||||
|
type ErrQueueNotEmpty struct {
|
||||||
|
qname string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrQueueNotEmpty) Error() string {
|
||||||
|
return fmt.Sprintf("queue %q is not empty", e.qname)
|
||||||
|
}
|
||||||
|
|
||||||
// Only check whether in-progress queue is empty before removing.
|
// Only check whether in-progress queue is empty before removing.
|
||||||
// KEYS[1] -> asynq:{<qname>}
|
// KEYS[1] -> asynq:{<qname>}
|
||||||
// KEYS[2] -> asynq:{<qname>}:in_progress
|
// KEYS[2] -> asynq:{<qname>}:in_progress
|
||||||
@ -650,7 +687,7 @@ local retry = redis.call("SCARD", KEYS[4])
|
|||||||
local dead = redis.call("SCARD", KEYS[5])
|
local dead = redis.call("SCARD", KEYS[5])
|
||||||
local total = enqueued + inprogress + scheduled + retry + dead
|
local total = enqueued + inprogress + scheduled + retry + dead
|
||||||
if total > 0 then
|
if total > 0 then
|
||||||
return redis.error_reply("Queue is not empty")
|
return redis.error_reply("QUEUE NOT EMPTY")
|
||||||
end
|
end
|
||||||
redis.call("DEL", KEYS[1])
|
redis.call("DEL", KEYS[1])
|
||||||
redis.call("DEL", KEYS[2])
|
redis.call("DEL", KEYS[2])
|
||||||
@ -689,8 +726,13 @@ func (r *RDB) RemoveQueue(qname string, force bool) error {
|
|||||||
base.DeadlinesKey(qname),
|
base.DeadlinesKey(qname),
|
||||||
}
|
}
|
||||||
if err := script.Run(r.client, keys).Err(); err != nil {
|
if err := script.Run(r.client, keys).Err(); err != nil {
|
||||||
return err
|
if err.Error() == "QUEUE NOT EMPTY" {
|
||||||
|
return &ErrQueueNotEmpty{qname}
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.client.SRem(base.AllQueues, qname).Err()
|
return r.client.SRem(base.AllQueues, qname).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +112,7 @@ func TestCurrentStats(t *testing.T) {
|
|||||||
want: &Stats{
|
want: &Stats{
|
||||||
Queue: "default",
|
Queue: "default",
|
||||||
Paused: false,
|
Paused: false,
|
||||||
|
Size: 4,
|
||||||
Enqueued: 1,
|
Enqueued: 1,
|
||||||
InProgress: 1,
|
InProgress: 1,
|
||||||
Scheduled: 2,
|
Scheduled: 2,
|
||||||
@ -166,6 +167,7 @@ func TestCurrentStats(t *testing.T) {
|
|||||||
want: &Stats{
|
want: &Stats{
|
||||||
Queue: "critical",
|
Queue: "critical",
|
||||||
Paused: true,
|
Paused: true,
|
||||||
|
Size: 1,
|
||||||
Enqueued: 1,
|
Enqueued: 1,
|
||||||
InProgress: 0,
|
InProgress: 0,
|
||||||
Scheduled: 0,
|
Scheduled: 0,
|
||||||
@ -232,12 +234,13 @@ func TestHistoricalStats(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"default", 90},
|
{"default", 90},
|
||||||
{"custom", 7},
|
{"custom", 7},
|
||||||
{"default", 0},
|
{"default", 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
h.FlushDB(t, r.client)
|
h.FlushDB(t, r.client)
|
||||||
|
|
||||||
|
r.client.SAdd(base.AllQueues, tc.qname)
|
||||||
// populate last n days data
|
// populate last n days data
|
||||||
for i := 0; i < tc.n; i++ {
|
for i := 0; i < tc.n; i++ {
|
||||||
ts := now.Add(-time.Duration(i) * 24 * time.Hour)
|
ts := now.Add(-time.Duration(i) * 24 * time.Hour)
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/v7"
|
|
||||||
"github.com/hibiken/asynq/internal/rdb"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// cancelCmd represents the cancel command
|
|
||||||
var cancelCmd = &cobra.Command{
|
|
||||||
Use: "cancel [task id]",
|
|
||||||
Short: "Sends a cancelation signal to the goroutine processing the specified task",
|
|
||||||
Long: `Cancel (asynq cancel) will send a cancelation signal to the goroutine processing
|
|
||||||
the specified task.
|
|
||||||
|
|
||||||
The command takes one argument which specifies the task to cancel.
|
|
||||||
The task should be in in-progress state.
|
|
||||||
Identifier for a task should be obtained by running "asynq ls" command.
|
|
||||||
|
|
||||||
Handler implementation needs to be context aware for cancelation signal to
|
|
||||||
actually cancel the processing.
|
|
||||||
|
|
||||||
Example: asynq cancel bnogo8gt6toe23vhef0g`,
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
Run: cancel,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(cancelCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cancel(cmd *cobra.Command, args []string) {
|
|
||||||
r := rdb.NewRDB(redis.NewClient(&redis.Options{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
}))
|
|
||||||
|
|
||||||
err := r.PublishCancelation(args[0])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("could not send cancelation signal: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("Successfully sent cancelation siganl for task %s\n", args[0])
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// delCmd represents the del command
|
|
||||||
var delCmd = &cobra.Command{
|
|
||||||
Use: "del [task key]",
|
|
||||||
Short: "Deletes a task given an identifier",
|
|
||||||
Long: `Del (asynq del) will delete a task given an identifier.
|
|
||||||
|
|
||||||
The command takes one argument which specifies the task to delete.
|
|
||||||
The task should be in either scheduled, retry or dead state.
|
|
||||||
Identifier for a task should be obtained by running "asynq ls" command.
|
|
||||||
|
|
||||||
Example: asynq enq d:1575732274:bnogo8gt6toe23vhef0g`,
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
Run: del,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(delCmd)
|
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
|
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
|
||||||
// and all subcommands, e.g.:
|
|
||||||
// delCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
|
||||||
// Cobra supports local flags which will only run when this command
|
|
||||||
// is called directly, e.g.:
|
|
||||||
// delCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
|
||||||
|
|
||||||
func del(cmd *cobra.Command, args []string) {
|
|
||||||
i := asynq.NewInspector(asynq.RedisClientOpt{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
})
|
|
||||||
err := i.DeleteTaskByKey(args[0])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("Successfully deleted %v\n", args[0])
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var delallValidArgs = []string{"scheduled", "retry", "dead"}
|
|
||||||
|
|
||||||
// delallCmd represents the delall command
|
|
||||||
var delallCmd = &cobra.Command{
|
|
||||||
Use: "delall [state]",
|
|
||||||
Short: "Deletes all tasks in the specified state",
|
|
||||||
Long: `Delall (asynq delall) will delete all tasks in the specified state.
|
|
||||||
|
|
||||||
The argument should be one of "scheduled", "retry", or "dead".
|
|
||||||
|
|
||||||
Example: asynq delall dead -> Deletes all dead tasks`,
|
|
||||||
ValidArgs: delallValidArgs,
|
|
||||||
Args: cobra.ExactValidArgs(1),
|
|
||||||
Run: delall,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(delallCmd)
|
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
|
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
|
||||||
// and all subcommands, e.g.:
|
|
||||||
// delallCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
|
||||||
// Cobra supports local flags which will only run when this command
|
|
||||||
// is called directly, e.g.:
|
|
||||||
// delallCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
|
||||||
|
|
||||||
func delall(cmd *cobra.Command, args []string) {
|
|
||||||
i := asynq.NewInspector(asynq.RedisClientOpt{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
})
|
|
||||||
var (
|
|
||||||
n int
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
switch args[0] {
|
|
||||||
case "scheduled":
|
|
||||||
n, err = i.DeleteAllScheduledTasks()
|
|
||||||
case "retry":
|
|
||||||
n, err = i.DeleteAllRetryTasks()
|
|
||||||
case "dead":
|
|
||||||
n, err = i.DeleteAllDeadTasks()
|
|
||||||
default:
|
|
||||||
fmt.Printf("error: `asynq delall [state]` only accepts %v as the argument.\n", delallValidArgs)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("Deleted all %d tasks in %q state\n", n, args[0])
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// enqCmd represents the enq command
|
|
||||||
var enqCmd = &cobra.Command{
|
|
||||||
Use: "enq [task key]",
|
|
||||||
Short: "Enqueues a task given an identifier",
|
|
||||||
Long: `Enq (asynq enq) will enqueue a task given an identifier.
|
|
||||||
|
|
||||||
The command takes one argument which specifies the task to enqueue.
|
|
||||||
The task should be in either scheduled, retry or dead state.
|
|
||||||
Identifier for a task should be obtained by running "asynq ls" command.
|
|
||||||
|
|
||||||
The task enqueued by this command will be processed as soon as the task
|
|
||||||
gets dequeued by a processor.
|
|
||||||
|
|
||||||
Example: asynq enq d:1575732274:bnogo8gt6toe23vhef0g`,
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
Run: enq,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(enqCmd)
|
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
|
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
|
||||||
// and all subcommands, e.g.:
|
|
||||||
// enqCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
|
||||||
// Cobra supports local flags which will only run when this command
|
|
||||||
// is called directly, e.g.:
|
|
||||||
// enqCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
|
||||||
|
|
||||||
func enq(cmd *cobra.Command, args []string) {
|
|
||||||
i := asynq.NewInspector(asynq.RedisClientOpt{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
})
|
|
||||||
err := i.EnqueueTaskByKey(args[0])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("Successfully enqueued %v\n", args[0])
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var enqallValidArgs = []string{"scheduled", "retry", "dead"}
|
|
||||||
|
|
||||||
// enqallCmd represents the enqall command
|
|
||||||
var enqallCmd = &cobra.Command{
|
|
||||||
Use: "enqall [state]",
|
|
||||||
Short: "Enqueues all tasks in the specified state",
|
|
||||||
Long: `Enqall (asynq enqall) will enqueue all tasks in the specified state.
|
|
||||||
|
|
||||||
The argument should be one of "scheduled", "retry", or "dead".
|
|
||||||
|
|
||||||
The tasks enqueued by this command will be processed as soon as it
|
|
||||||
gets dequeued by a processor.
|
|
||||||
|
|
||||||
Example: asynq enqall dead -> Enqueues all dead tasks`,
|
|
||||||
ValidArgs: enqallValidArgs,
|
|
||||||
Args: cobra.ExactValidArgs(1),
|
|
||||||
Run: enqall,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(enqallCmd)
|
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
|
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
|
||||||
// and all subcommands, e.g.:
|
|
||||||
// enqallCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
|
||||||
// Cobra supports local flags which will only run when this command
|
|
||||||
// is called directly, e.g.:
|
|
||||||
// enqallCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
|
||||||
|
|
||||||
func enqall(cmd *cobra.Command, args []string) {
|
|
||||||
i := asynq.NewInspector(asynq.RedisClientOpt{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
})
|
|
||||||
var (
|
|
||||||
n int
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
switch args[0] {
|
|
||||||
case "scheduled":
|
|
||||||
n, err = i.EnqueueAllScheduledTasks()
|
|
||||||
case "retry":
|
|
||||||
n, err = i.EnqueueAllRetryTasks()
|
|
||||||
case "dead":
|
|
||||||
n, err = i.EnqueueAllDeadTasks()
|
|
||||||
default:
|
|
||||||
fmt.Printf("error: `asynq enqall [state]` only accepts %v as the argument.\n", enqallValidArgs)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("Enqueued %d tasks in %q state\n", n, args[0])
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var days int
|
|
||||||
|
|
||||||
// historyCmd represents the history command
|
|
||||||
var historyCmd = &cobra.Command{
|
|
||||||
Use: "history",
|
|
||||||
Short: "Shows historical aggregate data",
|
|
||||||
Long: `History (asynq history) will show the number of processed and failed tasks
|
|
||||||
from the last x days.
|
|
||||||
|
|
||||||
By default, it will show the data from the last 10 days.
|
|
||||||
|
|
||||||
Example: asynq history -x=30 -> Shows stats from the last 30 days`,
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
Run: history,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(historyCmd)
|
|
||||||
historyCmd.Flags().IntVarP(&days, "days", "x", 10, "show data from last x days")
|
|
||||||
}
|
|
||||||
|
|
||||||
func history(cmd *cobra.Command, args []string) {
|
|
||||||
i := asynq.NewInspector(asynq.RedisClientOpt{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
})
|
|
||||||
|
|
||||||
stats, err := i.History(days)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
printDailyStats(stats)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printDailyStats(stats []*asynq.DailyStats) {
|
|
||||||
format := strings.Repeat("%v\t", 4) + "\n"
|
|
||||||
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
|
|
||||||
fmt.Fprintf(tw, format, "Date (UTC)", "Processed", "Failed", "Error Rate")
|
|
||||||
fmt.Fprintf(tw, format, "----------", "---------", "------", "----------")
|
|
||||||
for _, s := range stats {
|
|
||||||
var errrate string
|
|
||||||
if s.Processed == 0 {
|
|
||||||
errrate = "N/A"
|
|
||||||
} else {
|
|
||||||
errrate = fmt.Sprintf("%.2f%%", float64(s.Failed)/float64(s.Processed)*100)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(tw, format, s.Date.Format("2006-01-02"), s.Processed, s.Failed, errrate)
|
|
||||||
}
|
|
||||||
tw.Flush()
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// killCmd represents the kill command
|
|
||||||
var killCmd = &cobra.Command{
|
|
||||||
Use: "kill [task key]",
|
|
||||||
Short: "Kills a task given an identifier",
|
|
||||||
Long: `Kill (asynq kill) will put a task in dead state given an identifier.
|
|
||||||
|
|
||||||
The command takes one argument which specifies the task to kill.
|
|
||||||
The task should be in either scheduled or retry state.
|
|
||||||
Identifier for a task should be obtained by running "asynq ls" command.
|
|
||||||
|
|
||||||
Example: asynq kill r:1575732274:bnogo8gt6toe23vhef0g`,
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
Run: kill,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(killCmd)
|
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
|
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
|
||||||
// and all subcommands, e.g.:
|
|
||||||
// killCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
|
||||||
// Cobra supports local flags which will only run when this command
|
|
||||||
// is called directly, e.g.:
|
|
||||||
// killCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
|
||||||
|
|
||||||
func kill(cmd *cobra.Command, args []string) {
|
|
||||||
i := asynq.NewInspector(asynq.RedisClientOpt{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
})
|
|
||||||
err := i.KillTaskByKey(args[0])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("Successfully killed %v\n", args[0])
|
|
||||||
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var killallValidArgs = []string{"scheduled", "retry"}
|
|
||||||
|
|
||||||
// killallCmd represents the killall command
|
|
||||||
var killallCmd = &cobra.Command{
|
|
||||||
Use: "killall [state]",
|
|
||||||
Short: "Kills all tasks in the specified state",
|
|
||||||
Long: `Killall (asynq killall) will update all tasks from the specified state to dead state.
|
|
||||||
|
|
||||||
The argument should be either "scheduled" or "retry".
|
|
||||||
|
|
||||||
Example: asynq killall retry -> Update all retry tasks to dead tasks`,
|
|
||||||
ValidArgs: killallValidArgs,
|
|
||||||
Args: cobra.ExactValidArgs(1),
|
|
||||||
Run: killall,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(killallCmd)
|
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
|
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
|
||||||
// and all subcommands, e.g.:
|
|
||||||
// killallCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
|
||||||
// Cobra supports local flags which will only run when this command
|
|
||||||
// is called directly, e.g.:
|
|
||||||
// killallCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
|
||||||
|
|
||||||
func killall(cmd *cobra.Command, args []string) {
|
|
||||||
i := asynq.NewInspector(asynq.RedisClientOpt{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
})
|
|
||||||
var (
|
|
||||||
n int
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
switch args[0] {
|
|
||||||
case "scheduled":
|
|
||||||
n, err = i.KillAllScheduledTasks()
|
|
||||||
case "retry":
|
|
||||||
n, err = i.KillAllRetryTasks()
|
|
||||||
default:
|
|
||||||
fmt.Printf("error: `asynq killall [state]` only accepts %v as the argument.\n", killallValidArgs)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("Successfully updated %d tasks to \"dead\" state\n", n)
|
|
||||||
}
|
|
@ -1,190 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var lsValidArgs = []string{"enqueued", "inprogress", "scheduled", "retry", "dead"}
|
|
||||||
|
|
||||||
// lsCmd represents the ls command
|
|
||||||
var lsCmd = &cobra.Command{
|
|
||||||
Use: "ls [state]",
|
|
||||||
Short: "Lists tasks in the specified state",
|
|
||||||
Long: `Ls (asynq ls) will list all tasks in the specified state in a table format.
|
|
||||||
|
|
||||||
The command takes one argument which specifies the state of tasks.
|
|
||||||
The argument value should be one of "enqueued", "inprogress", "scheduled",
|
|
||||||
"retry", or "dead".
|
|
||||||
|
|
||||||
Example:
|
|
||||||
asynq ls dead -> Lists all tasks in dead state
|
|
||||||
|
|
||||||
Enqueued tasks requires a queue name after ":"
|
|
||||||
Example:
|
|
||||||
asynq ls enqueued:default -> List tasks from default queue
|
|
||||||
asynq ls enqueued:critical -> List tasks from critical queue
|
|
||||||
`,
|
|
||||||
Args: cobra.ExactValidArgs(1),
|
|
||||||
Run: ls,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flags
|
|
||||||
var pageSize int
|
|
||||||
var pageNum int
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(lsCmd)
|
|
||||||
lsCmd.Flags().IntVar(&pageSize, "size", 30, "page size")
|
|
||||||
lsCmd.Flags().IntVar(&pageNum, "page", 0, "page number - zero indexed (default 0)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ls(cmd *cobra.Command, args []string) {
|
|
||||||
if pageSize < 0 {
|
|
||||||
fmt.Println("page size cannot be negative.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if pageNum < 0 {
|
|
||||||
fmt.Println("page number cannot be negative.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
i := asynq.NewInspector(asynq.RedisClientOpt{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
})
|
|
||||||
parts := strings.Split(args[0], ":")
|
|
||||||
switch parts[0] {
|
|
||||||
case "enqueued":
|
|
||||||
if len(parts) != 2 {
|
|
||||||
fmt.Printf("error: Missing queue name\n`asynq ls enqueued:[queue name]`\n")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
listEnqueued(i, parts[1])
|
|
||||||
case "inprogress":
|
|
||||||
listInProgress(i)
|
|
||||||
case "scheduled":
|
|
||||||
listScheduled(i)
|
|
||||||
case "retry":
|
|
||||||
listRetry(i)
|
|
||||||
case "dead":
|
|
||||||
listDead(i)
|
|
||||||
default:
|
|
||||||
fmt.Printf("error: `asynq ls [state]`\nonly accepts %v as the argument.\n", lsValidArgs)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func listEnqueued(i *asynq.Inspector, qname string) {
|
|
||||||
tasks, err := i.ListEnqueuedTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if len(tasks) == 0 {
|
|
||||||
fmt.Printf("No enqueued tasks in %q queue\n", qname)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cols := []string{"ID", "Type", "Payload", "Queue"}
|
|
||||||
printTable(cols, func(w io.Writer, tmpl string) {
|
|
||||||
for _, t := range tasks {
|
|
||||||
fmt.Fprintf(w, tmpl, t.ID, t.Type, t.Payload, t.Queue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
fmt.Printf("\nShowing %d tasks from page %d\n", len(tasks), pageNum)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listInProgress(i *asynq.Inspector) {
|
|
||||||
tasks, err := i.ListInProgressTasks(asynq.PageSize(pageSize), asynq.Page(pageNum))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if len(tasks) == 0 {
|
|
||||||
fmt.Println("No in-progress tasks")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cols := []string{"ID", "Type", "Payload"}
|
|
||||||
printTable(cols, func(w io.Writer, tmpl string) {
|
|
||||||
for _, t := range tasks {
|
|
||||||
fmt.Fprintf(w, tmpl, t.ID, t.Type, t.Payload)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
fmt.Printf("\nShowing %d tasks from page %d\n", len(tasks), pageNum)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listScheduled(i *asynq.Inspector) {
|
|
||||||
tasks, err := i.ListScheduledTasks(asynq.PageSize(pageSize), asynq.Page(pageNum))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if len(tasks) == 0 {
|
|
||||||
fmt.Println("No scheduled tasks")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cols := []string{"Key", "Type", "Payload", "Process In", "Queue"}
|
|
||||||
printTable(cols, func(w io.Writer, tmpl string) {
|
|
||||||
for _, t := range tasks {
|
|
||||||
processIn := fmt.Sprintf("%.0f seconds",
|
|
||||||
t.NextEnqueueAt.Sub(time.Now()).Seconds())
|
|
||||||
fmt.Fprintf(w, tmpl, t.Key(), t.Type, t.Payload, processIn, t.Queue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
fmt.Printf("\nShowing %d tasks from page %d\n", len(tasks), pageNum)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listRetry(i *asynq.Inspector) {
|
|
||||||
tasks, err := i.ListRetryTasks(asynq.PageSize(pageSize), asynq.Page(pageNum))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if len(tasks) == 0 {
|
|
||||||
fmt.Println("No retry tasks")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cols := []string{"Key", "Type", "Payload", "Next Retry", "Last Error", "Retried", "Max Retry", "Queue"}
|
|
||||||
printTable(cols, func(w io.Writer, tmpl string) {
|
|
||||||
for _, t := range tasks {
|
|
||||||
var nextRetry string
|
|
||||||
if d := t.NextEnqueueAt.Sub(time.Now()); d > 0 {
|
|
||||||
nextRetry = fmt.Sprintf("in %v", d.Round(time.Second))
|
|
||||||
} else {
|
|
||||||
nextRetry = "right now"
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, tmpl, t.Key(), t.Type, t.Payload, nextRetry, t.ErrorMsg, t.Retried, t.MaxRetry, t.Queue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
fmt.Printf("\nShowing %d tasks from page %d\n", len(tasks), pageNum)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listDead(i *asynq.Inspector) {
|
|
||||||
tasks, err := i.ListDeadTasks(asynq.PageSize(pageSize), asynq.Page(pageNum))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if len(tasks) == 0 {
|
|
||||||
fmt.Println("No dead tasks")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cols := []string{"Key", "Type", "Payload", "Last Failed", "Last Error", "Queue"}
|
|
||||||
printTable(cols, func(w io.Writer, tmpl string) {
|
|
||||||
for _, t := range tasks {
|
|
||||||
fmt.Fprintf(w, tmpl, t.Key(), t.Type, t.Payload, t.LastFailedAt, t.ErrorMsg, t.Queue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
fmt.Printf("\nShowing %d tasks from page %d\n", len(tasks), pageNum)
|
|
||||||
}
|
|
@ -1,212 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/v7"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/hibiken/asynq/internal/base"
|
|
||||||
"github.com/spf13/cast"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// migrateCmd represents the migrate command
|
|
||||||
var migrateCmd = &cobra.Command{
|
|
||||||
Use: "migrate",
|
|
||||||
Short: fmt.Sprintf("Migrate all tasks to be compatible with asynq@%s", base.Version),
|
|
||||||
Long: fmt.Sprintf("Migrate (asynq migrate) will convert all tasks in redis to be compatible with asynq@%s.", base.Version),
|
|
||||||
Run: migrate,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(migrateCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrate(cmd *cobra.Command, args []string) {
|
|
||||||
c := redis.NewClient(&redis.Options{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
})
|
|
||||||
|
|
||||||
lists := []string{base.InProgressQueue}
|
|
||||||
allQueues, err := c.SMembers(base.AllQueues).Result()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error: could not read all queues: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
lists = append(lists, allQueues...)
|
|
||||||
for _, key := range lists {
|
|
||||||
if err := migrateList(c, key); err != nil {
|
|
||||||
fmt.Printf("error: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zsets := []string{base.ScheduledQueue, base.RetryQueue, base.DeadQueue}
|
|
||||||
for _, key := range zsets {
|
|
||||||
if err := migrateZSet(c, key); err != nil {
|
|
||||||
fmt.Printf("error: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type oldTaskMessage struct {
|
|
||||||
// Unchanged
|
|
||||||
Type string
|
|
||||||
Payload map[string]interface{}
|
|
||||||
ID uuid.UUID
|
|
||||||
Queue string
|
|
||||||
Retry int
|
|
||||||
Retried int
|
|
||||||
ErrorMsg string
|
|
||||||
UniqueKey string
|
|
||||||
|
|
||||||
// Following fields have changed.
|
|
||||||
|
|
||||||
// Deadline specifies the deadline for the task.
|
|
||||||
// Task won't be processed if it exceeded its deadline.
|
|
||||||
// The string shoulbe be in RFC3339 format.
|
|
||||||
//
|
|
||||||
// time.Time's zero value means no deadline.
|
|
||||||
Timeout string
|
|
||||||
|
|
||||||
// Deadline specifies the deadline for the task.
|
|
||||||
// Task won't be processed if it exceeded its deadline.
|
|
||||||
// The string shoulbe be in RFC3339 format.
|
|
||||||
//
|
|
||||||
// time.Time's zero value means no deadline.
|
|
||||||
Deadline string
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultTimeout = 30 * time.Minute
|
|
||||||
|
|
||||||
func convertMessage(old *oldTaskMessage) (*base.TaskMessage, error) {
|
|
||||||
timeout, err := time.ParseDuration(old.Timeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse Timeout field of %+v", old)
|
|
||||||
}
|
|
||||||
deadline, err := time.Parse(time.RFC3339, old.Deadline)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse Deadline field of %+v", old)
|
|
||||||
}
|
|
||||||
if timeout == 0 && deadline.IsZero() {
|
|
||||||
timeout = defaultTimeout
|
|
||||||
}
|
|
||||||
if deadline.IsZero() {
|
|
||||||
// Zero value used to be time.Time{},
|
|
||||||
// in the new schema zero value is represented by
|
|
||||||
// zero in Unix time.
|
|
||||||
deadline = time.Unix(0, 0)
|
|
||||||
}
|
|
||||||
return &base.TaskMessage{
|
|
||||||
Type: old.Type,
|
|
||||||
Payload: old.Payload,
|
|
||||||
ID: uuid.New(),
|
|
||||||
Queue: old.Queue,
|
|
||||||
Retry: old.Retry,
|
|
||||||
Retried: old.Retried,
|
|
||||||
ErrorMsg: old.ErrorMsg,
|
|
||||||
UniqueKey: old.UniqueKey,
|
|
||||||
Timeout: int64(timeout.Seconds()),
|
|
||||||
Deadline: deadline.Unix(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func deserialize(s string) (*base.TaskMessage, error) {
|
|
||||||
// Try deserializing as old message.
|
|
||||||
d := json.NewDecoder(strings.NewReader(s))
|
|
||||||
d.UseNumber()
|
|
||||||
var old *oldTaskMessage
|
|
||||||
if err := d.Decode(&old); err != nil {
|
|
||||||
// Try deserializing as new message.
|
|
||||||
d = json.NewDecoder(strings.NewReader(s))
|
|
||||||
d.UseNumber()
|
|
||||||
var msg *base.TaskMessage
|
|
||||||
if err := d.Decode(&msg); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not deserialize %s into task message: %v", s, err)
|
|
||||||
}
|
|
||||||
return msg, nil
|
|
||||||
}
|
|
||||||
return convertMessage(old)
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateZSet(c *redis.Client, key string) error {
|
|
||||||
if c.Exists(key).Val() == 0 {
|
|
||||||
// skip if key doesn't exist.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
res, err := c.ZRangeWithScores(key, 0, -1).Result()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var msgs []*redis.Z
|
|
||||||
for _, z := range res {
|
|
||||||
s, err := cast.ToStringE(z.Member)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not cast to string: %v", err)
|
|
||||||
}
|
|
||||||
msg, err := deserialize(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
encoded, err := base.EncodeMessage(msg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not encode message from %q: %v", key, err)
|
|
||||||
}
|
|
||||||
msgs = append(msgs, &redis.Z{Score: z.Score, Member: encoded})
|
|
||||||
}
|
|
||||||
if err := c.Rename(key, key+":backup").Err(); err != nil {
|
|
||||||
return fmt.Errorf("could not rename key %q: %v", key, err)
|
|
||||||
}
|
|
||||||
if err := c.ZAdd(key, msgs...).Err(); err != nil {
|
|
||||||
return fmt.Errorf("could not write new messages to %q: %v", key, err)
|
|
||||||
}
|
|
||||||
if err := c.Del(key + ":backup").Err(); err != nil {
|
|
||||||
return fmt.Errorf("could not delete back up key %q: %v", key+":backup", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateList(c *redis.Client, key string) error {
|
|
||||||
if c.Exists(key).Val() == 0 {
|
|
||||||
// skip if key doesn't exist.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
res, err := c.LRange(key, 0, -1).Result()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var msgs []interface{}
|
|
||||||
for _, s := range res {
|
|
||||||
msg, err := deserialize(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
encoded, err := base.EncodeMessage(msg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not encode message from %q: %v", key, err)
|
|
||||||
}
|
|
||||||
msgs = append(msgs, encoded)
|
|
||||||
}
|
|
||||||
if err := c.Rename(key, key+":backup").Err(); err != nil {
|
|
||||||
return fmt.Errorf("could not rename key %q: %v", key, err)
|
|
||||||
}
|
|
||||||
if err := c.LPush(key, msgs...).Err(); err != nil {
|
|
||||||
return fmt.Errorf("could not write new messages to %q: %v", key, err)
|
|
||||||
}
|
|
||||||
if err := c.Del(key + ":backup").Err(); err != nil {
|
|
||||||
return fmt.Errorf("could not delete back up key %q: %v", key+":backup", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/v7"
|
|
||||||
"github.com/hibiken/asynq/internal/rdb"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// pauseCmd represents the pause command
|
|
||||||
var pauseCmd = &cobra.Command{
|
|
||||||
Use: "pause [queue name]",
|
|
||||||
Short: "Pauses the specified queue",
|
|
||||||
Long: `Pause (asynq pause) will pause the specified queue.
|
|
||||||
Asynq servers will not process tasks from paused queues.
|
|
||||||
Use the "unpause" command to resume a paused queue.
|
|
||||||
|
|
||||||
Example: asynq pause default -> Pause the "default" queue`,
|
|
||||||
Args: cobra.ExactValidArgs(1),
|
|
||||||
Run: pause,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(pauseCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pause(cmd *cobra.Command, args []string) {
|
|
||||||
c := redis.NewClient(&redis.Options{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
})
|
|
||||||
r := rdb.NewRDB(c)
|
|
||||||
err := r.Pause(args[0])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("Successfully paused queue %q\n", args[0])
|
|
||||||
}
|
|
244
tools/asynq/cmd/queue.go
Normal file
244
tools/asynq/cmd/queue.go
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v7"
|
||||||
|
"github.com/hibiken/asynq"
|
||||||
|
"github.com/hibiken/asynq/internal/rdb"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
const separator = "================================================="
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(queueCmd)
|
||||||
|
queueCmd.AddCommand(queueListCmd)
|
||||||
|
queueCmd.AddCommand(queueInspectCmd)
|
||||||
|
queueCmd.AddCommand(queueHistoryCmd)
|
||||||
|
queueHistoryCmd.Flags().IntP("days", "x", 10, "show data from last x days")
|
||||||
|
|
||||||
|
queueCmd.AddCommand(queuePauseCmd)
|
||||||
|
queueCmd.AddCommand(queueUnpauseCmd)
|
||||||
|
queueCmd.AddCommand(queueRemoveCmd)
|
||||||
|
queueRemoveCmd.Flags().BoolP("force", "f", false, "remove the queue regardless of its size")
|
||||||
|
}
|
||||||
|
|
||||||
|
var queueCmd = &cobra.Command{
|
||||||
|
Use: "queue",
|
||||||
|
Short: "Manage queues",
|
||||||
|
}
|
||||||
|
|
||||||
|
var queueListCmd = &cobra.Command{
|
||||||
|
Use: "ls",
|
||||||
|
Short: "List queues",
|
||||||
|
// TODO: Use RunE instead?
|
||||||
|
Run: queueList,
|
||||||
|
}
|
||||||
|
|
||||||
|
var queueInspectCmd = &cobra.Command{
|
||||||
|
Use: "inspect QUEUE [QUEUE...]",
|
||||||
|
Short: "Display detailed information on one or more queues",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
// TODO: Use RunE instead?
|
||||||
|
Run: queueInspect,
|
||||||
|
}
|
||||||
|
|
||||||
|
var queueHistoryCmd = &cobra.Command{
|
||||||
|
Use: "history QUEUE [QUEUE...]",
|
||||||
|
Short: "Display historical aggregate data from one or more queues",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Run: queueHistory,
|
||||||
|
}
|
||||||
|
|
||||||
|
var queuePauseCmd = &cobra.Command{
|
||||||
|
Use: "pause QUEUE [QUEUE...]",
|
||||||
|
Short: "Pause one or more queues",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Run: queuePause,
|
||||||
|
}
|
||||||
|
|
||||||
|
var queueUnpauseCmd = &cobra.Command{
|
||||||
|
Use: "unpause QUEUE [QUEUE...]",
|
||||||
|
Short: "Unpause one or more queues",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Run: queueUnpause,
|
||||||
|
}
|
||||||
|
|
||||||
|
var queueRemoveCmd = &cobra.Command{
|
||||||
|
Use: "rm QUEUE [QUEUE...]",
|
||||||
|
Short: "Remove one or more queues",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Run: queueRemove,
|
||||||
|
}
|
||||||
|
|
||||||
|
func queueList(cmd *cobra.Command, args []string) {
|
||||||
|
i := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
queues, err := i.Queues()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: Could not fetch list of queues: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
for _, qname := range queues {
|
||||||
|
fmt.Println(qname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func queueInspect(cmd *cobra.Command, args []string) {
|
||||||
|
inspector := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
for i, qname := range args {
|
||||||
|
if i > 0 {
|
||||||
|
fmt.Printf("\n%s\n", separator)
|
||||||
|
}
|
||||||
|
fmt.Printf("\nQueue: %s\n\n", qname)
|
||||||
|
stats, err := inspector.CurrentStats(qname)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
printQueueStats(stats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printQueueStats(s *asynq.QueueStats) {
|
||||||
|
fmt.Printf("Size: %d\n", s.Size)
|
||||||
|
fmt.Printf("Paused: %t\n\n", s.Paused)
|
||||||
|
fmt.Println("Task Breakdown:")
|
||||||
|
printTable(
|
||||||
|
[]string{"InProgress", "Enqueued", "Scheduled", "Retry", "Dead"},
|
||||||
|
func(w io.Writer, tmpl string) {
|
||||||
|
fmt.Fprintf(w, tmpl, s.InProgress, s.Enqueued, s.Scheduled, s.Retry, s.Dead)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("%s Stats:\n", s.Timestamp.UTC().Format("2006-01-02"))
|
||||||
|
printTable(
|
||||||
|
[]string{"Processed", "Failed", "Error Rate"},
|
||||||
|
func(w io.Writer, tmpl string) {
|
||||||
|
var errRate string
|
||||||
|
if s.Processed == 0 {
|
||||||
|
errRate = "N/A"
|
||||||
|
} else {
|
||||||
|
errRate = fmt.Sprintf("%.2f%%", float64(s.Failed)/float64(s.Processed)*100)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, tmpl, s.Processed, s.Failed, errRate)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func queueHistory(cmd *cobra.Command, args []string) {
|
||||||
|
days, err := cmd.Flags().GetInt("days")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: Internal error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
inspector := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
for i, qname := range args {
|
||||||
|
if i > 0 {
|
||||||
|
fmt.Printf("\n%s\n", separator)
|
||||||
|
}
|
||||||
|
fmt.Printf("\nQueue: %s\n\n", qname)
|
||||||
|
stats, err := inspector.History(qname, days)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
printDailyStats(stats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printDailyStats(stats []*asynq.DailyStats) {
|
||||||
|
printTable(
|
||||||
|
[]string{"Date (UTC)", "Processed", "Failed", "Error Rate"},
|
||||||
|
func(w io.Writer, tmpl string) {
|
||||||
|
for _, s := range stats {
|
||||||
|
var errRate string
|
||||||
|
if s.Processed == 0 {
|
||||||
|
errRate = "N/A"
|
||||||
|
} else {
|
||||||
|
errRate = fmt.Sprintf("%.2f%%", float64(s.Failed)/float64(s.Processed)*100)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, tmpl, s.Date.Format("2006-01-02"), s.Processed, s.Failed, errRate)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func queuePause(cmd *cobra.Command, args []string) {
|
||||||
|
inspector := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
for _, qname := range args {
|
||||||
|
err := inspector.PauseQueue(qname)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("Successfully paused queue %q\n", qname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func queueUnpause(cmd *cobra.Command, args []string) {
|
||||||
|
inspector := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
for _, qname := range args {
|
||||||
|
err := inspector.UnpauseQueue(qname)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("Successfully unpaused queue %q\n", qname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func queueRemove(cmd *cobra.Command, args []string) {
|
||||||
|
// TODO: Use inspector once RemoveQueue become public API.
|
||||||
|
force, err := cmd.Flags().GetBool("force")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: Internal error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := redis.NewClient(&redis.Options{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
r := rdb.NewRDB(c)
|
||||||
|
for _, qname := range args {
|
||||||
|
err = r.RemoveQueue(qname, force)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*rdb.ErrQueueNotEmpty); ok {
|
||||||
|
fmt.Printf("error: %v\nIf you are sure you want to delete it, run 'asynq queue rm --force %s'\n", err, qname)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("Successfully removed queue %q\n", qname)
|
||||||
|
}
|
||||||
|
}
|
@ -1,54 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/v7"
|
|
||||||
"github.com/hibiken/asynq/internal/rdb"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// rmqCmd represents the rmq command
|
|
||||||
var rmqCmd = &cobra.Command{
|
|
||||||
Use: "rmq [queue name]",
|
|
||||||
Short: "Removes the specified queue",
|
|
||||||
Long: `Rmq (asynq rmq) will remove the specified queue.
|
|
||||||
By default, it will remove the queue only if it's empty.
|
|
||||||
Use --force option to override this behavior.
|
|
||||||
|
|
||||||
Example: asynq rmq low -> Removes "low" queue`,
|
|
||||||
Args: cobra.ExactValidArgs(1),
|
|
||||||
Run: rmq,
|
|
||||||
}
|
|
||||||
|
|
||||||
var rmqForce bool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(rmqCmd)
|
|
||||||
rmqCmd.Flags().BoolVarP(&rmqForce, "force", "f", false, "remove the queue regardless of its size")
|
|
||||||
}
|
|
||||||
|
|
||||||
func rmq(cmd *cobra.Command, args []string) {
|
|
||||||
c := redis.NewClient(&redis.Options{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
})
|
|
||||||
r := rdb.NewRDB(c)
|
|
||||||
err := r.RemoveQueue(args[0], rmqForce)
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(*rdb.ErrQueueNotEmpty); ok {
|
|
||||||
fmt.Printf("error: %v\nIf you are sure you want to delete it, run 'asynq rmq --force %s'\n", err, args[0])
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("error: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("Successfully removed queue %q\n", args[0])
|
|
||||||
}
|
|
@ -18,12 +18,21 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// serversCmd represents the servers command
|
func init() {
|
||||||
var serversCmd = &cobra.Command{
|
rootCmd.AddCommand(serverCmd)
|
||||||
Use: "servers",
|
serverCmd.AddCommand(serverListCmd)
|
||||||
Short: "Shows all running worker servers",
|
}
|
||||||
Long: `Servers (asynq servers) will show all running worker servers
|
|
||||||
pulling tasks from the specified redis instance.
|
var serverCmd = &cobra.Command{
|
||||||
|
Use: "server",
|
||||||
|
Short: "Manage servers",
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverListCmd = &cobra.Command{
|
||||||
|
Use: "ls",
|
||||||
|
Short: "List servers",
|
||||||
|
Long: `Server list (asynq server ls) shows all running worker servers
|
||||||
|
pulling tasks from the given redis instance.
|
||||||
|
|
||||||
The command shows the following for each server:
|
The command shows the following for each server:
|
||||||
* Host and PID of the process in which the server is running
|
* Host and PID of the process in which the server is running
|
||||||
@ -34,15 +43,10 @@ The command shows the following for each server:
|
|||||||
|
|
||||||
A "running" server is pulling tasks from queues and processing them.
|
A "running" server is pulling tasks from queues and processing them.
|
||||||
A "quiet" server is no longer pulling new tasks from queues`,
|
A "quiet" server is no longer pulling new tasks from queues`,
|
||||||
Args: cobra.NoArgs,
|
Run: serverList,
|
||||||
Run: servers,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func serverList(cmd *cobra.Command, args []string) {
|
||||||
rootCmd.AddCommand(serversCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func servers(cmd *cobra.Command, args []string) {
|
|
||||||
r := rdb.NewRDB(redis.NewClient(&redis.Options{
|
r := rdb.NewRDB(redis.NewClient(&redis.Options{
|
||||||
Addr: viper.GetString("uri"),
|
Addr: viper.GetString("uri"),
|
||||||
DB: viper.GetInt("db"),
|
DB: viper.GetInt("db"),
|
||||||
@ -81,12 +85,6 @@ func servers(cmd *cobra.Command, args []string) {
|
|||||||
printTable(cols, printRows)
|
printTable(cols, printRows)
|
||||||
}
|
}
|
||||||
|
|
||||||
// timeAgo takes a time and returns a string of the format "<duration> ago".
|
|
||||||
func timeAgo(since time.Time) string {
|
|
||||||
d := time.Since(since).Round(time.Second)
|
|
||||||
return fmt.Sprintf("%v ago", d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatQueues(qmap map[string]int) string {
|
func formatQueues(qmap map[string]int) string {
|
||||||
// sort queues by priority and name
|
// sort queues by priority and name
|
||||||
type queue struct {
|
type queue struct {
|
||||||
@ -116,3 +114,9 @@ func formatQueues(qmap map[string]int) string {
|
|||||||
}
|
}
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeAgo takes a time and returns a string of the format "<duration> ago".
|
||||||
|
func timeAgo(since time.Time) string {
|
||||||
|
d := time.Since(since).Round(time.Second)
|
||||||
|
return fmt.Sprintf("%v ago", d)
|
||||||
|
}
|
@ -10,6 +10,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v7"
|
"github.com/go-redis/redis/v7"
|
||||||
"github.com/hibiken/asynq/internal/rdb"
|
"github.com/hibiken/asynq/internal/rdb"
|
||||||
@ -51,6 +52,17 @@ func init() {
|
|||||||
// statsCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
// statsCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AggregateStats struct {
|
||||||
|
InProgress int
|
||||||
|
Enqueued int
|
||||||
|
Scheduled int
|
||||||
|
Retry int
|
||||||
|
Dead int
|
||||||
|
Processed int
|
||||||
|
Failed int
|
||||||
|
Timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
func stats(cmd *cobra.Command, args []string) {
|
func stats(cmd *cobra.Command, args []string) {
|
||||||
c := redis.NewClient(&redis.Options{
|
c := redis.NewClient(&redis.Options{
|
||||||
Addr: viper.GetString("uri"),
|
Addr: viper.GetString("uri"),
|
||||||
@ -59,26 +71,45 @@ func stats(cmd *cobra.Command, args []string) {
|
|||||||
})
|
})
|
||||||
r := rdb.NewRDB(c)
|
r := rdb.NewRDB(c)
|
||||||
|
|
||||||
stats, err := r.CurrentStats()
|
queues, err := r.AllQueues()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var aggStats AggregateStats
|
||||||
|
var stats []*rdb.Stats
|
||||||
|
for _, qname := range queues {
|
||||||
|
s, err := r.CurrentStats(qname)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
aggStats.InProgress += s.InProgress
|
||||||
|
aggStats.Enqueued += s.Enqueued
|
||||||
|
aggStats.Scheduled += s.Scheduled
|
||||||
|
aggStats.Retry += s.Retry
|
||||||
|
aggStats.Dead += s.Dead
|
||||||
|
aggStats.Processed += s.Processed
|
||||||
|
aggStats.Failed += s.Failed
|
||||||
|
aggStats.Timestamp = s.Timestamp
|
||||||
|
stats = append(stats, s)
|
||||||
|
}
|
||||||
info, err := r.RedisInfo()
|
info, err := r.RedisInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("STATES")
|
fmt.Println("BY STATES")
|
||||||
printStates(stats)
|
printStatsByState(&aggStats)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
fmt.Println("QUEUES")
|
fmt.Println("BY QUEUES")
|
||||||
printQueues(stats.Queues)
|
printStatsByQueue(stats)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
fmt.Printf("STATS FOR %s UTC\n", stats.Timestamp.UTC().Format("2006-01-02"))
|
fmt.Printf("STATS FOR %s UTC\n", aggStats.Timestamp.UTC().Format("2006-01-02"))
|
||||||
printStats(stats)
|
printSuccessFailureStats(&aggStats)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
fmt.Println("REDIS INFO")
|
fmt.Println("REDIS INFO")
|
||||||
@ -86,7 +117,7 @@ func stats(cmd *cobra.Command, args []string) {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
func printStates(s *rdb.Stats) {
|
func printStatsByState(s *AggregateStats) {
|
||||||
format := strings.Repeat("%v\t", 5) + "\n"
|
format := strings.Repeat("%v\t", 5) + "\n"
|
||||||
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
|
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
|
||||||
fmt.Fprintf(tw, format, "InProgress", "Enqueued", "Scheduled", "Retry", "Dead")
|
fmt.Fprintf(tw, format, "InProgress", "Enqueued", "Scheduled", "Retry", "Dead")
|
||||||
@ -95,13 +126,13 @@ func printStates(s *rdb.Stats) {
|
|||||||
tw.Flush()
|
tw.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func printQueues(queues []*rdb.Queue) {
|
func printStatsByQueue(stats []*rdb.Stats) {
|
||||||
var headers, seps, counts []string
|
var headers, seps, counts []string
|
||||||
for _, q := range queues {
|
for _, s := range stats {
|
||||||
title := queueTitle(q)
|
title := queueTitle(s)
|
||||||
headers = append(headers, title)
|
headers = append(headers, title)
|
||||||
seps = append(seps, strings.Repeat("-", len(title)))
|
seps = append(seps, strings.Repeat("-", len(title)))
|
||||||
counts = append(counts, strconv.Itoa(q.Size))
|
counts = append(counts, strconv.Itoa(s.Size))
|
||||||
}
|
}
|
||||||
format := strings.Repeat("%v\t", len(headers)) + "\n"
|
format := strings.Repeat("%v\t", len(headers)) + "\n"
|
||||||
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
|
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
|
||||||
@ -111,16 +142,16 @@ func printQueues(queues []*rdb.Queue) {
|
|||||||
tw.Flush()
|
tw.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func queueTitle(q *rdb.Queue) string {
|
func queueTitle(s *rdb.Stats) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString(strings.Title(q.Name))
|
b.WriteString(strings.Title(s.Queue))
|
||||||
if q.Paused {
|
if s.Paused {
|
||||||
b.WriteString(" (Paused)")
|
b.WriteString(" (Paused)")
|
||||||
}
|
}
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func printStats(s *rdb.Stats) {
|
func printSuccessFailureStats(s *AggregateStats) {
|
||||||
format := strings.Repeat("%v\t", 3) + "\n"
|
format := strings.Repeat("%v\t", 3) + "\n"
|
||||||
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
|
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
|
||||||
fmt.Fprintf(tw, format, "Processed", "Failed", "Error Rate")
|
fmt.Fprintf(tw, format, "Processed", "Failed", "Error Rate")
|
||||||
|
515
tools/asynq/cmd/task.go
Normal file
515
tools/asynq/cmd/task.go
Normal file
@ -0,0 +1,515 @@
|
|||||||
|
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v7"
|
||||||
|
"github.com/hibiken/asynq"
|
||||||
|
"github.com/hibiken/asynq/internal/rdb"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(taskCmd)
|
||||||
|
taskCmd.AddCommand(taskListCmd)
|
||||||
|
taskListCmd.Flags().StringP("queue", "q", "", "queue to inspect")
|
||||||
|
taskListCmd.Flags().StringP("state", "s", "", "state of the tasks to inspect")
|
||||||
|
taskListCmd.Flags().Int("page", 1, "page number")
|
||||||
|
taskListCmd.Flags().Int("size", 30, "page size")
|
||||||
|
taskListCmd.MarkFlagRequired("queue")
|
||||||
|
taskListCmd.MarkFlagRequired("state")
|
||||||
|
|
||||||
|
taskCmd.AddCommand(taskCancelCmd)
|
||||||
|
|
||||||
|
taskCmd.AddCommand(taskKillCmd)
|
||||||
|
taskKillCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs")
|
||||||
|
taskKillCmd.Flags().StringP("key", "k", "", "key of the task")
|
||||||
|
taskKillCmd.MarkFlagRequired("queue")
|
||||||
|
taskKillCmd.MarkFlagRequired("key")
|
||||||
|
|
||||||
|
taskCmd.AddCommand(taskDeleteCmd)
|
||||||
|
taskDeleteCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs")
|
||||||
|
taskDeleteCmd.Flags().StringP("key", "k", "", "key of the task")
|
||||||
|
taskDeleteCmd.MarkFlagRequired("queue")
|
||||||
|
taskDeleteCmd.MarkFlagRequired("key")
|
||||||
|
|
||||||
|
taskCmd.AddCommand(taskRunCmd)
|
||||||
|
taskRunCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs")
|
||||||
|
taskRunCmd.Flags().StringP("key", "k", "", "key of the task")
|
||||||
|
taskRunCmd.MarkFlagRequired("queue")
|
||||||
|
taskRunCmd.MarkFlagRequired("key")
|
||||||
|
|
||||||
|
taskCmd.AddCommand(taskKillAllCmd)
|
||||||
|
taskKillAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong")
|
||||||
|
taskKillAllCmd.Flags().StringP("state", "s", "", "state of the tasks")
|
||||||
|
taskKillAllCmd.MarkFlagRequired("queue")
|
||||||
|
taskKillAllCmd.MarkFlagRequired("state")
|
||||||
|
|
||||||
|
taskCmd.AddCommand(taskDeleteAllCmd)
|
||||||
|
taskDeleteAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong")
|
||||||
|
taskDeleteAllCmd.Flags().StringP("state", "s", "", "state of the tasks")
|
||||||
|
taskDeleteAllCmd.MarkFlagRequired("queue")
|
||||||
|
taskDeleteAllCmd.MarkFlagRequired("state")
|
||||||
|
|
||||||
|
taskCmd.AddCommand(taskRunAllCmd)
|
||||||
|
taskRunAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong")
|
||||||
|
taskRunAllCmd.Flags().StringP("state", "s", "", "state of the tasks")
|
||||||
|
taskRunAllCmd.MarkFlagRequired("queue")
|
||||||
|
taskRunAllCmd.MarkFlagRequired("state")
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskCmd = &cobra.Command{
|
||||||
|
Use: "task",
|
||||||
|
Short: "Manage tasks",
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskListCmd = &cobra.Command{
|
||||||
|
Use: "ls --queue=QUEUE --state=STATE",
|
||||||
|
Short: "List tasks",
|
||||||
|
Long: `List tasks of the given state from the specified queue.
|
||||||
|
|
||||||
|
The value for the state flag should be one of:
|
||||||
|
- in-progress
|
||||||
|
- enqueued
|
||||||
|
- scheduled
|
||||||
|
- retry
|
||||||
|
- dead
|
||||||
|
|
||||||
|
List opeartion paginates the result set.
|
||||||
|
By default, the command fetches the first 30 tasks.
|
||||||
|
Use --page and --size flags to specify the page number and size.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
To list enqueued tasks from "default" queue, run
|
||||||
|
asynq task ls --queue=default --state=enqueued
|
||||||
|
|
||||||
|
To list the tasks from the second page, run
|
||||||
|
asynq task ls --queue=default --state=enqueued --page=1`,
|
||||||
|
Run: taskList,
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskCancelCmd = &cobra.Command{
|
||||||
|
Use: "cancel TASK_ID [TASK_ID...]",
|
||||||
|
Short: "Cancel one or more in-progress tasks",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Run: taskCancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskKillCmd = &cobra.Command{
|
||||||
|
Use: "kill --queue=QUEUE --key=KEY",
|
||||||
|
Short: "Kill a task with the given key",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: taskKill,
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskDeleteCmd = &cobra.Command{
|
||||||
|
Use: "delete --queue=QUEUE --key=KEY",
|
||||||
|
Short: "Delete a task with the given key",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: taskDelete,
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskRunCmd = &cobra.Command{
|
||||||
|
Use: "run --queue=QUEUE --key=KEY",
|
||||||
|
Short: "Run a task with the given key",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: taskRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskKillAllCmd = &cobra.Command{
|
||||||
|
Use: "kill-all --queue=QUEUE --state=STATE",
|
||||||
|
Short: "Kill all tasks in the given state",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: taskKillAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskDeleteAllCmd = &cobra.Command{
|
||||||
|
Use: "delete-all --queue=QUEUE --key=KEY",
|
||||||
|
Short: "Delete all tasks in the given state",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: taskDeleteAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskRunAllCmd = &cobra.Command{
|
||||||
|
Use: "run-all --queue=QUEUE --key=KEY",
|
||||||
|
Short: "Run all tasks in the given state",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: taskRunAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
func taskList(cmd *cobra.Command, args []string) {
|
||||||
|
qname, err := cmd.Flags().GetString("queue")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
state, err := cmd.Flags().GetString("state")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
pageNum, err := cmd.Flags().GetInt("page")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
pageSize, err := cmd.Flags().GetInt("size")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch state {
|
||||||
|
case "in-progress":
|
||||||
|
listInProgressTasks(qname, pageNum, pageSize)
|
||||||
|
case "enqueued":
|
||||||
|
listEnqueuedTasks(qname, pageNum, pageSize)
|
||||||
|
case "scheduled":
|
||||||
|
listScheduledTasks(qname, pageNum, pageSize)
|
||||||
|
case "retry":
|
||||||
|
listRetryTasks(qname, pageNum, pageSize)
|
||||||
|
case "dead":
|
||||||
|
listDeadTasks(qname, pageNum, pageSize)
|
||||||
|
default:
|
||||||
|
fmt.Printf("error: state=%q is not supported\n", state)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listInProgressTasks(qname string, pageNum, pageSize int) {
|
||||||
|
i := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
tasks, err := i.ListInProgressTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if len(tasks) == 0 {
|
||||||
|
fmt.Printf("No in-progress tasks in %q queue\n", qname)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printTable(
|
||||||
|
[]string{"ID", "Type", "Payload"},
|
||||||
|
func(w io.Writer, tmpl string) {
|
||||||
|
for _, t := range tasks {
|
||||||
|
fmt.Fprintf(w, tmpl, t.ID, t.Type, t.Payload)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listEnqueuedTasks(qname string, pageNum, pageSize int) {
|
||||||
|
i := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
tasks, err := i.ListEnqueuedTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if len(tasks) == 0 {
|
||||||
|
fmt.Printf("No enqueued tasks in %q queue\n", qname)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printTable(
|
||||||
|
[]string{"ID", "Type", "Payload"},
|
||||||
|
func(w io.Writer, tmpl string) {
|
||||||
|
for _, t := range tasks {
|
||||||
|
fmt.Fprintf(w, tmpl, t.ID, t.Type, t.Payload)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listScheduledTasks(qname string, pageNum, pageSize int) {
|
||||||
|
i := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
tasks, err := i.ListScheduledTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if len(tasks) == 0 {
|
||||||
|
fmt.Printf("No scheduled tasks in %q queue\n", qname)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printTable(
|
||||||
|
[]string{"Key", "Type", "Payload", "Process In"},
|
||||||
|
func(w io.Writer, tmpl string) {
|
||||||
|
for _, t := range tasks {
|
||||||
|
processIn := fmt.Sprintf("%.0f seconds",
|
||||||
|
t.NextEnqueueAt.Sub(time.Now()).Seconds())
|
||||||
|
fmt.Fprintf(w, tmpl, t.Key(), t.Type, t.Payload, processIn)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listRetryTasks(qname string, pageNum, pageSize int) {
|
||||||
|
i := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
tasks, err := i.ListRetryTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if len(tasks) == 0 {
|
||||||
|
fmt.Printf("No retry tasks in %q queue\n", qname)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printTable(
|
||||||
|
[]string{"Key", "Type", "Payload", "Next Retry", "Last Error", "Retried", "Max Retry"},
|
||||||
|
func(w io.Writer, tmpl string) {
|
||||||
|
for _, t := range tasks {
|
||||||
|
var nextRetry string
|
||||||
|
if d := t.NextEnqueueAt.Sub(time.Now()); d > 0 {
|
||||||
|
nextRetry = fmt.Sprintf("in %v", d.Round(time.Second))
|
||||||
|
} else {
|
||||||
|
nextRetry = "right now"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, tmpl, t.Key(), t.Type, t.Payload, nextRetry, t.ErrorMsg, t.Retried, t.MaxRetry)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listDeadTasks(qname string, pageNum, pageSize int) {
|
||||||
|
i := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
tasks, err := i.ListDeadTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if len(tasks) == 0 {
|
||||||
|
fmt.Printf("No dead tasks in %q queue\n", qname)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printTable(
|
||||||
|
[]string{"Key", "Type", "Payload", "Last Failed", "Last Error"},
|
||||||
|
func(w io.Writer, tmpl string) {
|
||||||
|
for _, t := range tasks {
|
||||||
|
fmt.Fprintf(w, tmpl, t.Key(), t.Type, t.Payload, t.LastFailedAt, t.ErrorMsg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func taskCancel(cmd *cobra.Command, args []string) {
|
||||||
|
r := rdb.NewRDB(redis.NewClient(&redis.Options{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
}))
|
||||||
|
|
||||||
|
for _, id := range args {
|
||||||
|
err := r.PublishCancelation(id)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: could not send cancelation signal: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("Sent cancelation signal for task %s\n", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func taskKill(cmd *cobra.Command, args []string) {
|
||||||
|
qname, err := cmd.Flags().GetString("queue")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
key, err := cmd.Flags().GetString("key")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
err = i.KillTaskByKey(qname, key)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("task transitioned to dead state")
|
||||||
|
}
|
||||||
|
|
||||||
|
func taskDelete(cmd *cobra.Command, args []string) {
|
||||||
|
qname, err := cmd.Flags().GetString("queue")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
key, err := cmd.Flags().GetString("key")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
err = i.DeleteTaskByKey(qname, key)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("task deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
func taskRun(cmd *cobra.Command, args []string) {
|
||||||
|
qname, err := cmd.Flags().GetString("queue")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
key, err := cmd.Flags().GetString("key")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
err = i.EnqueueTaskByKey(qname, key)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("task transitioned to pending state")
|
||||||
|
}
|
||||||
|
|
||||||
|
func taskKillAll(cmd *cobra.Command, args []string) {
|
||||||
|
qname, err := cmd.Flags().GetString("queue")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
state, err := cmd.Flags().GetString("state")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
var n int
|
||||||
|
switch state {
|
||||||
|
case "scheduled":
|
||||||
|
n, err = i.KillAllScheduledTasks(qname)
|
||||||
|
case "retry":
|
||||||
|
n, err = i.KillAllRetryTasks(qname)
|
||||||
|
default:
|
||||||
|
fmt.Printf("error: unsupported state %q\n", state)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("%d tasks transitioned to dead state\n", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func taskDeleteAll(cmd *cobra.Command, args []string) {
|
||||||
|
qname, err := cmd.Flags().GetString("queue")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
state, err := cmd.Flags().GetString("state")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
var n int
|
||||||
|
switch state {
|
||||||
|
case "scheduled":
|
||||||
|
n, err = i.DeleteAllScheduledTasks(qname)
|
||||||
|
case "retry":
|
||||||
|
n, err = i.DeleteAllRetryTasks(qname)
|
||||||
|
case "dead":
|
||||||
|
n, err = i.DeleteAllDeadTasks(qname)
|
||||||
|
default:
|
||||||
|
fmt.Printf("error: unsupported state %q\n", state)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("%d tasks deleted\n", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func taskRunAll(cmd *cobra.Command, args []string) {
|
||||||
|
qname, err := cmd.Flags().GetString("queue")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
state, err := cmd.Flags().GetString("state")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := asynq.NewInspector(asynq.RedisClientOpt{
|
||||||
|
Addr: viper.GetString("uri"),
|
||||||
|
DB: viper.GetInt("db"),
|
||||||
|
Password: viper.GetString("password"),
|
||||||
|
})
|
||||||
|
var n int
|
||||||
|
switch state {
|
||||||
|
case "scheduled":
|
||||||
|
n, err = i.EnqueueAllScheduledTasks(qname)
|
||||||
|
case "retry":
|
||||||
|
n, err = i.EnqueueAllRetryTasks(qname)
|
||||||
|
case "dead":
|
||||||
|
n, err = i.EnqueueAllDeadTasks(qname)
|
||||||
|
default:
|
||||||
|
fmt.Printf("error: unsupported state %q\n", state)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("%d tasks transitioned to pending state\n", n)
|
||||||
|
}
|
@ -1,46 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/v7"
|
|
||||||
"github.com/hibiken/asynq/internal/rdb"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// unpauseCmd represents the unpause command
|
|
||||||
var unpauseCmd = &cobra.Command{
|
|
||||||
Use: "unpause [queue name]",
|
|
||||||
Short: "Unpauses the specified queue",
|
|
||||||
Long: `Unpause (asynq unpause) will unpause the specified queue.
|
|
||||||
Asynq servers will process tasks from unpaused/resumed queues.
|
|
||||||
|
|
||||||
Example: asynq unpause default -> Resume the "default" queue`,
|
|
||||||
Args: cobra.ExactValidArgs(1),
|
|
||||||
Run: unpause,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(unpauseCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func unpause(cmd *cobra.Command, args []string) {
|
|
||||||
c := redis.NewClient(&redis.Options{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
})
|
|
||||||
r := rdb.NewRDB(c)
|
|
||||||
err := r.Unpause(args[0])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("Successfully resumed queue %q\n", args[0])
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/v7"
|
|
||||||
"github.com/hibiken/asynq/internal/rdb"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// workersCmd represents the workers command
|
|
||||||
var workersCmd = &cobra.Command{
|
|
||||||
Use: "workers",
|
|
||||||
Short: "Shows all running workers information",
|
|
||||||
Long: `Workers (asynq workers) will show all running workers information.
|
|
||||||
|
|
||||||
The command shows the following for each worker:
|
|
||||||
* Process in which the worker is running
|
|
||||||
* ID of the task worker is processing
|
|
||||||
* Type of the task worker is processing
|
|
||||||
* Payload of the task worker is processing
|
|
||||||
* Queue that the task was pulled from.
|
|
||||||
* Time the worker started processing the task`,
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
Run: workers,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(workersCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func workers(cmd *cobra.Command, args []string) {
|
|
||||||
r := rdb.NewRDB(redis.NewClient(&redis.Options{
|
|
||||||
Addr: viper.GetString("uri"),
|
|
||||||
DB: viper.GetInt("db"),
|
|
||||||
Password: viper.GetString("password"),
|
|
||||||
}))
|
|
||||||
|
|
||||||
workers, err := r.ListWorkers()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(workers) == 0 {
|
|
||||||
fmt.Println("No workers")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort by started timestamp or ID.
|
|
||||||
sort.Slice(workers, func(i, j int) bool {
|
|
||||||
x, y := workers[i], workers[j]
|
|
||||||
if x.Started != y.Started {
|
|
||||||
return x.Started.Before(y.Started)
|
|
||||||
}
|
|
||||||
return x.ID < y.ID
|
|
||||||
})
|
|
||||||
|
|
||||||
cols := []string{"Process", "ID", "Type", "Payload", "Queue", "Started"}
|
|
||||||
printRows := func(w io.Writer, tmpl string) {
|
|
||||||
for _, wk := range workers {
|
|
||||||
fmt.Fprintf(w, tmpl,
|
|
||||||
fmt.Sprintf("%s:%d", wk.Host, wk.PID), wk.ID, wk.Type, wk.Payload, wk.Queue, timeAgo(wk.Started))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printTable(cols, printRows)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user