internal/commands/interceptor/duration.go (97 lines of code) (raw):

// 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 ( "context" "fmt" "strconv" "time" api "skywalking.apache.org/repo/goapi/query" "github.com/apache/skywalking-cli/pkg/contextkey" "github.com/apache/skywalking-cli/pkg/graphql/utils" "github.com/urfave/cli/v2" "github.com/apache/skywalking-cli/internal/model" "github.com/apache/skywalking-cli/pkg/logger" ) 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) if err == nil { return step, t, nil } possibleError = err } 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(cliCtx *cli.Context) error { start := cliCtx.String("start") end := cliCtx.String("end") userStep := cliCtx.Generic("step") if timezone := cliCtx.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, s) if err := cliCtx.Set("start", startTime.Format(utils.StepFormats[step])); err != nil { return err } else if err := cliCtx.Set("end", endTime.Format(utils.StepFormats[step])); err != nil { return err } else if err := cliCtx.Set("step", step.String()); err != nil { return err } else if err := cliCtx.Set("duration-type", dt.String()); err != nil { return err } ctx := cliCtx.Context ctx = context.WithValue(ctx, contextkey.DurationStart{}, startTime.Format(utils.StepFormats[step])) ctx = context.WithValue(ctx, contextkey.DurationEnd{}, endTime.Format(utils.StepFormats[step])) ctx = context.WithValue(ctx, contextkey.DurationStep{}, step) ctx = context.WithValue(ctx, contextkey.DurationType{}, utils.DurationType(dt.String())) cliCtx.Context = ctx return nil } // IsSetDurationFlags checks if the duration flags are set func IsSetDurationFlags(ctx *cli.Context) bool { return ctx.IsSet("start") || ctx.IsSet("end") || ctx.IsSet("step") } // ParseDuration parses the `start` and `end` to a triplet, (startTime, endTime, step), // based on the given `timezone`, however, if the given `timezone` is empty, UTC becomes the default timezone. // if --start and --end are both absent, // then: start := now - 30min; end := now // if --start is given, --end is absent, // 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 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() // both are absent if start == "" && end == "" { return now.Add(-30 * time.Minute), now, api.StepMinute, utils.BothAbsent } var err error // both are present if start != "" && end != "" { if userStep, startTime, err = TryParseTime(start, userStep); err != nil { logger.Log.Fatalln("Unsupported time format:", start, err) } 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, userStep); err != nil { logger.Log.Fatalln("Unsupported time format:", start, err) } return startTime, startTime.Add(30 * utils.StepDuration[step]), step, utils.EndAbsent } // start is absent 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 } // AlignPrecision aligns the two time strings to same precision // by truncating the more precise one func AlignPrecision(start, end string) (_, _ string) { if len(start) < len(end) { return start, end[0:len(start)] } if len(start) > len(end) { return start[0:len(end)], end } return start, end }