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

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

commit 729821ba34758389b5fec33ae5082afc0377c1d7
Author: kezhenxu94 <kezhenx...@163.com>
AuthorDate: Sat Dec 28 17:29:11 2019 +0800

    Make use of server timezone API when possible
---
 commands/interceptor/duration.go      | 17 ++++++++++---
 commands/interceptor/duration_test.go |  2 +-
 commands/interceptor/timezone.go      | 46 +++++++++++++++++++++++++++++++++++
 commands/service/list.go              |  1 +
 graphql/client/client.go              | 23 ++++++++++++------
 graphql/metadata/metadata.go          | 42 ++++++++++++++++++++++++++++++++
 swctl/main.go                         |  6 +++++
 7 files changed, 125 insertions(+), 12 deletions(-)

diff --git a/commands/interceptor/duration.go b/commands/interceptor/duration.go
index 186c3fe..33ea49f 100644
--- a/commands/interceptor/duration.go
+++ b/commands/interceptor/duration.go
@@ -18,6 +18,7 @@
 package interceptor
 
 import (
+       "strconv"
        "time"
 
        "github.com/urfave/cli"
@@ -43,8 +44,9 @@ func tryParseTime(unparsed string) (schema.Step, time.Time, 
error) {
 func DurationInterceptor(ctx *cli.Context) error {
        start := ctx.String("start")
        end := ctx.String("end")
+       timezone := ctx.GlobalString("timezone")
 
-       startTime, endTime, step := ParseDuration(start, end)
+       startTime, endTime, step := ParseDuration(start, end, timezone)
 
        if err := ctx.Set("start", startTime.Format(schema.StepFormats[step])); 
err != nil {
                return err
@@ -66,11 +68,20 @@ func DurationInterceptor(ctx *cli.Context) error {
 // NOTE that when either(both) `start` or `end` is(are) given, there is no 
timezone info
 // in the format, (e.g. 2019-11-09 1001), so they'll be considered as 
UTC-based,
 // and generate the missing `start`(`end`) based on the same timezone, UTC
-func ParseDuration(start, end string) (startTime, endTime time.Time, step 
schema.Step) {
-       logger.Log.Debugln("Start time:", start, "end time:", end)
+func ParseDuration(start, end, timezone string) (startTime, endTime time.Time, 
step schema.Step) {
+       logger.Log.Debugln("Start time:", start, "end time:", end, "timezone:", 
timezone)
 
        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, schema.StepMinute
diff --git a/commands/interceptor/duration_test.go 
b/commands/interceptor/duration_test.go
index e8720ae..b4f8c90 100644
--- a/commands/interceptor/duration_test.go
+++ b/commands/interceptor/duration_test.go
@@ -82,7 +82,7 @@ func TestParseDuration(t *testing.T) {
        }
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
-                       gotStartTime, gotEndTime, gotStep := 
ParseDuration(tt.args.start, tt.args.end)
+                       gotStartTime, gotEndTime, gotStep := 
ParseDuration(tt.args.start, tt.args.end, "")
                        current := 
gotStartTime.Truncate(time.Minute).Format(timeFormat)
                        spec := 
tt.wantedStartTime.Truncate(time.Minute).Format(timeFormat)
                        if !reflect.DeepEqual(current, spec) {
diff --git a/commands/interceptor/timezone.go b/commands/interceptor/timezone.go
new file mode 100644
index 0000000..a02cc26
--- /dev/null
+++ b/commands/interceptor/timezone.go
@@ -0,0 +1,46 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package interceptor
+
+import (
+       "strconv"
+
+       "github.com/urfave/cli"
+
+       "github.com/apache/skywalking-cli/graphql/metadata"
+       "github.com/apache/skywalking-cli/logger"
+)
+
+// TimezoneInterceptor sets the server timezone if the server supports the API,
+// otherwise, sets to local timezone
+func TimezoneInterceptor(ctx *cli.Context) error {
+       serverTimeInfo, err := metadata.ServerTimeInfo(ctx)
+
+       if err != nil {
+               logger.Log.Debugf("Failed to get server time info: %v\n", err)
+               return nil
+       }
+
+       if timezone := serverTimeInfo.Timezone; timezone != nil {
+               if _, err := strconv.Atoi(*timezone); err == nil {
+                       return ctx.GlobalSet("timezone", *timezone)
+               }
+       }
+
+       return nil
+}
diff --git a/commands/service/list.go b/commands/service/list.go
index feacb89..8400a47 100644
--- a/commands/service/list.go
+++ b/commands/service/list.go
@@ -36,6 +36,7 @@ var ListCommand = cli.Command{
        Description: "list all services if no <service name> is given, 
otherwise, only list the given service",
        Flags:       flags.DurationFlags,
        Before: interceptor.BeforeChain([]cli.BeforeFunc{
+               interceptor.TimezoneInterceptor,
                interceptor.DurationInterceptor,
        }),
        Action: func(ctx *cli.Context) error {
diff --git a/graphql/client/client.go b/graphql/client/client.go
index d33df92..a0766ef 100644
--- a/graphql/client/client.go
+++ b/graphql/client/client.go
@@ -37,7 +37,14 @@ func newClient(cliCtx *cli.Context) (client *graphql.Client) 
{
        return
 }
 
-func executeQuery(cliCtx *cli.Context, request *graphql.Request, response 
interface{}) {
+func ExecuteQuery(cliCtx *cli.Context, request *graphql.Request, response 
interface{}) error {
+       client := newClient(cliCtx)
+       ctx := context.Background()
+       err := client.Run(ctx, request, response)
+       return err
+}
+
+func ExecuteQueryOrFail(cliCtx *cli.Context, request *graphql.Request, 
response interface{}) {
        client := newClient(cliCtx)
        ctx := context.Background()
        if err := client.Run(ctx, request, response); err != nil {
@@ -56,7 +63,7 @@ func Services(cliCtx *cli.Context, duration schema.Duration) 
[]schema.Service {
        `)
        request.Var("duration", duration)
 
-       executeQuery(cliCtx, request, &response)
+       ExecuteQueryOrFail(cliCtx, request, &response)
        return response["services"]
 }
 
@@ -73,7 +80,7 @@ func SearchEndpoints(cliCtx *cli.Context, serviceID, keyword 
string, limit int)
        request.Var("keyword", keyword)
        request.Var("limit", limit)
 
-       executeQuery(cliCtx, request, &response)
+       ExecuteQueryOrFail(cliCtx, request, &response)
        return response["endpoints"]
 }
 
@@ -88,7 +95,7 @@ func GetEndpointInfo(cliCtx *cli.Context, endpointID string) 
schema.Endpoint {
        `)
        request.Var("endpointId", endpointID)
 
-       executeQuery(cliCtx, request, &response)
+       ExecuteQueryOrFail(cliCtx, request, &response)
        return response["endpoint"]
 }
 
@@ -111,7 +118,7 @@ func Instances(cliCtx *cli.Context, serviceID string, 
duration schema.Duration)
        request.Var("serviceId", serviceID)
        request.Var("duration", duration)
 
-       executeQuery(cliCtx, request, &response)
+       ExecuteQueryOrFail(cliCtx, request, &response)
        return response["instances"]
 }
 
@@ -126,7 +133,7 @@ func SearchService(cliCtx *cli.Context, serviceCode string) 
(service schema.Serv
        `)
        request.Var("serviceCode", serviceCode)
 
-       executeQuery(cliCtx, request, &response)
+       ExecuteQueryOrFail(cliCtx, request, &response)
        service = response["service"]
        if service.ID == "" {
                return service, fmt.Errorf("no such service [%s]", serviceCode)
@@ -147,7 +154,7 @@ func LinearIntValues(ctx *cli.Context, condition 
schema.MetricCondition, duratio
        request.Var("metric", condition)
        request.Var("duration", duration)
 
-       executeQuery(ctx, request, &response)
+       ExecuteQueryOrFail(ctx, request, &response)
 
        values := metricsToMap(duration, response["metrics"].Values)
 
@@ -167,7 +174,7 @@ func IntValues(ctx *cli.Context, condition 
schema.BatchMetricConditions, duratio
        request.Var("metric", condition)
        request.Var("duration", duration)
 
-       executeQuery(ctx, request, &response)
+       ExecuteQueryOrFail(ctx, request, &response)
 
        return response["metrics"].Values
 }
diff --git a/graphql/metadata/metadata.go b/graphql/metadata/metadata.go
new file mode 100644
index 0000000..55a1bc7
--- /dev/null
+++ b/graphql/metadata/metadata.go
@@ -0,0 +1,42 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package metadata
+
+import (
+       "github.com/machinebox/graphql"
+       "github.com/urfave/cli"
+
+       "github.com/apache/skywalking-cli/graphql/client"
+       "github.com/apache/skywalking-cli/graphql/schema"
+)
+
+func ServerTimeInfo(cliCtx *cli.Context) (schema.TimeInfo, error) {
+       request := graphql.NewRequest(`
+               query {
+                       timeInfo: getTimeInfo {
+                               timezone, currentTimestamp
+                       }
+               }
+       `)
+
+       var response map[string]schema.TimeInfo
+       if err := client.ExecuteQuery(cliCtx, request, &response); err != nil {
+               return schema.TimeInfo{}, err
+       }
+       return response["timeInfo"], nil
+}
diff --git a/swctl/main.go b/swctl/main.go
index 113c2bb..0cf3177 100644
--- a/swctl/main.go
+++ b/swctl/main.go
@@ -60,6 +60,12 @@ func main() {
                        Usage:    "base `url` of the OAP backend graphql",
                        Value:    "http://127.0.0.1:12800/graphql";,
                }),
+               altsrc.NewStringFlag(cli.StringFlag{
+                       Name:     "timezone",
+                       Required: false,
+                       Hidden:   true,
+                       Usage:    "the timezone of the server side",
+               }),
                altsrc.NewBoolFlag(cli.BoolFlag{
                        Name:     "debug",
                        Required: false,

Reply via email to