Skip to content

Commit

Permalink
Allow setting start end with relative time (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
kezhenxu94 authored Oct 26, 2021
1 parent 48338d3 commit 45a5f98
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 30 deletions.
48 changes: 27 additions & 21 deletions internal/commands/interceptor/duration.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package interceptor

import (
"fmt"
"strconv"
"time"

Expand All @@ -28,9 +29,10 @@ import (
"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)
Expand All @@ -39,17 +41,32 @@ func TryParseTime(unparsed string) (api.Step, time.Time, error) {
}
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,
// see ParseDuration
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)
}
}

var s api.Step
if userStep != nil {
s = userStep.(*model.StepEnumValue).Selected
}

startTime, endTime, step, dt := ParseDuration(start, end, timezone)
startTime, endTime, step, dt := ParseDuration(start, end, s)

if err := ctx.Set("start", startTime.Format(utils.StepFormats[step])); err != nil {
return err
Expand All @@ -71,20 +88,11 @@ func DurationInterceptor(ctx *cli.Context) error {
// 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
Expand All @@ -94,23 +102,21 @@ func ParseDuration(start, end, timezone string) (startTime, endTime time.Time, s

// 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
Expand Down
20 changes: 15 additions & 5 deletions internal/flags/duration.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import (
"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
Expand All @@ -39,7 +40,16 @@ var startEndUsage = `"start" and "end" specify a time range during which the que
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
Expand All @@ -54,8 +64,8 @@ var DurationFlags = []cli.Flag{
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,
Expand Down
14 changes: 10 additions & 4 deletions pkg/display/graph/dashboard/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"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"
Expand Down Expand Up @@ -88,6 +89,7 @@ var template *dashboard.GlobalTemplate
var allWidgets *widgets

var initStartStr string
var initStep = api.StepMinute
var initEndStr string

var curStartTime time.Time
Expand Down Expand Up @@ -131,7 +133,7 @@ func newLayoutButtons(c *container.Container) ([]*button.Button, error) {
return nil, err
}

buttons[int(lt)] = b
buttons[lt] = b
}

return buttons, nil
Expand Down Expand Up @@ -317,11 +319,15 @@ func refresh(con context.Context, ctx *cli.Context, interval time.Duration) {
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
}
Expand Down Expand Up @@ -355,7 +361,7 @@ func refresh(con context.Context, ctx *cli.Context, interval time.Duration) {
// 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
}
Expand Down

0 comments on commit 45a5f98

Please sign in to comment.