You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2021/10/26 11:10:04 UTC

[skywalking-cli] 01/01: Allow setting `start` `end` with relative time

This is an automated email from the ASF dual-hosted git repository.

kezhenxu94 pushed a commit to branch duration/relative
in repository https://gitbox.apache.org/repos/asf/skywalking-cli.git

commit 50ea406eb635736f51eeb1153468924ac2a35eea
Author: kezhenxu94 <ke...@apache.org>
AuthorDate: Tue Oct 26 19:09:56 2021 +0800

    Allow setting `start` `end` with relative time
---
 internal/commands/interceptor/duration.go | 48 +++++++++++++++++--------------
 internal/flags/duration.go                | 18 ++++++++----
 pkg/display/graph/dashboard/global.go     | 14 ++++++---
 3 files changed, 50 insertions(+), 30 deletions(-)

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 @@ 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)
@@ -39,7 +41,11 @@ 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,
@@ -47,9 +53,20 @@ func TryParseTime(unparsed string) (api.Step, time.Time, error) {
 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
@@ -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
@@ -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
diff --git a/internal/flags/duration.go b/internal/flags/duration.go
index f4a3f67..9f4b758 100644
--- a/internal/flags/duration.go
+++ b/internal/flags/duration.go
@@ -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 
@@ -39,7 +40,14 @@ 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"`
 
 // 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 +62,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,
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 @@ 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"
@@ -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
@@ -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
@@ -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
 	}
@@ -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
 	}