Allow setting `start` `end` with relative time (#128)
diff --git a/internal/commands/interceptor/duration.go b/internal/commands/interceptor/duration.go
index 9f8e627..ea9f291 100644
--- a/internal/commands/interceptor/duration.go
+++ b/internal/commands/interceptor/duration.go
@@ -18,6 +18,7 @@
package interceptor
import (
+ "fmt"
"strconv"
"time"
@@ -28,9 +29,10 @@
"github.com/urfave/cli/v2"
"github.com/apache/skywalking-cli/internal/logger"
+ "github.com/apache/skywalking-cli/internal/model"
)
-func TryParseTime(unparsed string) (api.Step, time.Time, error) {
+func TryParseTime(unparsed string, userStep api.Step) (api.Step, time.Time, error) {
var possibleError error
for step, layout := range utils.StepFormats {
t, err := time.Parse(layout, unparsed)
@@ -39,7 +41,11 @@
}
possibleError = err
}
- return api.StepSecond, time.Time{}, possibleError
+ duration, err := time.ParseDuration(unparsed)
+ if err == nil {
+ return userStep, time.Now().Add(duration), nil
+ }
+ return userStep, time.Time{}, fmt.Errorf("the given time %v is neither absolute time nor relative time: %+v %+v", unparsed, possibleError, err)
}
// DurationInterceptor sets the duration if absent, and formats it accordingly,
@@ -47,9 +53,20 @@
func DurationInterceptor(ctx *cli.Context) error {
start := ctx.String("start")
end := ctx.String("end")
- timezone := ctx.String("timezone")
+ userStep := ctx.Generic("step")
+ if timezone := ctx.String("timezone"); timezone != "" {
+ if offset, err := strconv.Atoi(timezone); err == nil {
+ // `offset` is in form of "+1300", while `time.FixedZone` takes offset in seconds
+ time.Local = time.FixedZone("", offset/100*60*60)
+ }
+ }
- startTime, endTime, step, dt := ParseDuration(start, end, timezone)
+ var s api.Step
+ if userStep != nil {
+ s = userStep.(*model.StepEnumValue).Selected
+ }
+
+ startTime, endTime, step, dt := ParseDuration(start, end, s)
if err := ctx.Set("start", startTime.Format(utils.StepFormats[step])); err != nil {
return err
@@ -71,20 +88,11 @@
// then: end := now + 30 units, where unit is the precision of `start`, (hours, minutes, etc.)
// if --start is absent, --end is given,
// then: start := end - 30 units, where unit is the precision of `end`, (hours, minutes, etc.)
-func ParseDuration(start, end, timezone string) (startTime, endTime time.Time, step api.Step, dt utils.DurationType) {
- logger.Log.Debugln("Start time:", start, "end time:", end, "timezone:", timezone)
+func ParseDuration(start, end string, userStep api.Step) (startTime, endTime time.Time, step api.Step, dt utils.DurationType) {
+ logger.Log.Debugln("Start time:", start, "end time:", end, "timezone:", time.Local)
now := time.Now()
- if timezone != "" {
- if offset, err := strconv.Atoi(timezone); err == nil {
- // `offset` is in form of "+1300", while `time.FixedZone` takes offset in seconds
- now = now.In(time.FixedZone("", offset/100*60*60))
-
- logger.Log.Debugln("Now:", now, "with server timezone:", timezone)
- }
- }
-
// both are absent
if start == "" && end == "" {
return now.Add(-30 * time.Minute), now, api.StepMinute, utils.BothAbsent
@@ -94,23 +102,21 @@
// both are present
if len(start) > 0 && len(end) > 0 {
- start, end = AlignPrecision(start, end)
-
- if _, startTime, err = TryParseTime(start); err != nil {
+ if userStep, startTime, err = TryParseTime(start, userStep); err != nil {
logger.Log.Fatalln("Unsupported time format:", start, err)
}
- if step, endTime, err = TryParseTime(end); err != nil {
+ if step, endTime, err = TryParseTime(end, userStep); err != nil {
logger.Log.Fatalln("Unsupported time format:", end, err)
}
return startTime, endTime, step, utils.BothPresent
} else if end == "" { // end is absent
- if step, startTime, err = TryParseTime(start); err != nil {
+ if step, startTime, err = TryParseTime(start, userStep); err != nil {
logger.Log.Fatalln("Unsupported time format:", start, err)
}
return startTime, startTime.Add(30 * utils.StepDuration[step]), step, utils.EndAbsent
} else { // start is absent
- if step, endTime, err = TryParseTime(end); err != nil {
+ if step, endTime, err = TryParseTime(end, userStep); err != nil {
logger.Log.Fatalln("Unsupported time format:", end, err)
}
return endTime.Add(-30 * utils.StepDuration[step]), endTime, step, utils.StartAbsent
diff --git a/internal/flags/duration.go b/internal/flags/duration.go
index f4a3f67..1e3793a 100644
--- a/internal/flags/duration.go
+++ b/internal/flags/duration.go
@@ -25,8 +25,9 @@
"github.com/apache/skywalking-cli/internal/model"
)
-var startEndUsage = `"start" and "end" specify a time range during which the query is preformed,
- they are both optional and their default values follow the rules below:
+var startEndUsage = `"start" and "end" specify a time range during which the query is preformed,
+ they can be absolute time like "2019-01-01 12", "2019-01-01 1213", or relative time (to the
+ current time) like "-30m", "30m". They are both optional and their default values follow the rules below:
1. when "start" and "end" are both absent, "start = now - 30 minutes" and "end = now",
namely past 30 minutes;
2. when "start" and "end" are both present, they are aligned to the same precision by
@@ -39,7 +40,16 @@
4. when "start" is present and "end" is absent, will determine the precision of "start"
and then use the precision to calculate "end" (plus 30 units), e.g. "start = 2019-11-09 1204",
the precision is "MINUTE", so "end = start + 30 minutes = 2019-11-09 1234",
- and if "start = 2019-11-08 06", the precision is "HOUR", so "end = start + 30HOUR = 2019-11-09 12".`
+ and if "start = 2019-11-08 06", the precision is "HOUR", so "end = start + 30HOUR = 2019-11-09 12".
+ Examples:
+ 1. Query the metrics from 20 minutes ago to 10 minutes ago
+ $ swctl metrics linear --name=service_resp_time --service-name business-zone::projectB --start "-20m" --end "-10m"
+ 2. Query the metrics from 1 hour ago to 10 minutes ago
+ $ swctl metrics linear --name=service_resp_time --service-name business-zone::projectB --start "-1h" --end "-10m"
+ 3. Query the metrics from 1 hour ago to now
+ $ swctl metrics linear --name=service_resp_time --service-name business-zone::projectB --start "-1h" --end "0m"
+ 4. Query the metrics from "2021-10-26 1047" to "2021-10-26 1127"
+ $ swctl metrics linear --name=service_resp_time --service-name business-zone::projectB --start "2021-10-26 1047" --end "2021-10-26 1127"`
// DurationFlags are healthcheck flags that involves a duration, composed
// by a start time, an end time, and a step, which is commonly used
@@ -54,8 +64,8 @@
Usage: `end time of the query duration. Check the usage of "start"`,
},
&cli.GenericFlag{
- Name: "step",
- Hidden: true,
+ Name: "step",
+ Usage: `time step between start time and end time, should be one of SECOND, MINUTE, HOUR, DAY`,
Value: &model.StepEnumValue{
Enum: api.AllStep,
Default: api.StepMinute,
diff --git a/pkg/display/graph/dashboard/global.go b/pkg/display/graph/dashboard/global.go
index 52a01c3..b7f8682 100644
--- a/pkg/display/graph/dashboard/global.go
+++ b/pkg/display/graph/dashboard/global.go
@@ -38,6 +38,7 @@
"github.com/mum4k/termdash/terminal/terminalapi"
"github.com/urfave/cli/v2"
+ "github.com/apache/skywalking-cli/internal/model"
"github.com/apache/skywalking-cli/pkg/display/graph/gauge"
"github.com/apache/skywalking-cli/pkg/display/graph/heatmap"
"github.com/apache/skywalking-cli/pkg/display/graph/linear"
@@ -88,6 +89,7 @@
var allWidgets *widgets
var initStartStr string
+var initStep = api.StepMinute
var initEndStr string
var curStartTime time.Time
@@ -131,7 +133,7 @@
return nil, err
}
- buttons[int(lt)] = b
+ buttons[lt] = b
}
return buttons, nil
@@ -317,11 +319,15 @@
initStartStr = ctx.String("start")
initEndStr = ctx.String("end")
- _, start, err := interceptor.TryParseTime(initStartStr)
+ if s := ctx.Generic("step"); s != nil {
+ initStep = s.(*model.StepEnumValue).Selected
+ }
+
+ _, start, err := interceptor.TryParseTime(initStartStr, initStep)
if err != nil {
return
}
- _, end, err := interceptor.TryParseTime(initEndStr)
+ _, end, err := interceptor.TryParseTime(initEndStr, initStep)
if err != nil {
return
}
@@ -355,7 +361,7 @@
// If the duration doesn't change, an error will be returned, and the dashboard will not refresh.
// Otherwise, a new duration will be returned, which is used to get the latest global data.
func updateDuration(interval time.Duration) (api.Duration, error) {
- step, _, err := interceptor.TryParseTime(initStartStr)
+ step, _, err := interceptor.TryParseTime(initStartStr, initStep)
if err != nil {
return api.Duration{}, err
}