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,