| // 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 ( |
| "fmt" |
| "strconv" |
| "time" |
| |
| api "skywalking.apache.org/repo/goapi/query" |
| |
| "github.com/apache/skywalking-cli/pkg/graphql/utils" |
| |
| "github.com/urfave/cli/v2" |
| |
| "github.com/apache/skywalking-cli/internal/logger" |
| "github.com/apache/skywalking-cli/internal/model" |
| ) |
| |
| 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(ctx *cli.Context) error { |
| start := ctx.String("start") |
| end := ctx.String("end") |
| 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, s) |
| |
| if err := ctx.Set("start", startTime.Format(utils.StepFormats[step])); err != nil { |
| return err |
| } else if err := ctx.Set("end", endTime.Format(utils.StepFormats[step])); err != nil { |
| return err |
| } else if err := ctx.Set("step", step.String()); err != nil { |
| return err |
| } else if err := ctx.Set("duration-type", dt.String()); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // 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 len(start) > 0 && len(end) > 0 { |
| 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 |
| } else { // 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 |
| } |