github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/stats/historical.go (about) 1 package stats 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 8 "github.com/fastly/go-fastly/v9/fastly" 9 10 "github.com/fastly/cli/pkg/argparser" 11 "github.com/fastly/cli/pkg/global" 12 ) 13 14 const statusSuccess = "success" 15 16 // HistoricalCommand exposes the Historical Stats API. 17 type HistoricalCommand struct { 18 argparser.Base 19 20 Input fastly.GetStatsInput 21 formatFlag string 22 serviceName argparser.OptionalServiceNameID 23 } 24 25 // NewHistoricalCommand is the "stats historical" subcommand. 26 func NewHistoricalCommand(parent argparser.Registerer, g *global.Data) *HistoricalCommand { 27 var c HistoricalCommand 28 c.Globals = g 29 30 c.CmdClause = parent.Command("historical", "View historical stats for a Fastly service") 31 c.RegisterFlag(argparser.StringFlagOpts{ 32 Name: argparser.FlagServiceIDName, 33 Description: argparser.FlagServiceIDDesc, 34 Dst: &g.Manifest.Flag.ServiceID, 35 Short: 's', 36 }) 37 c.RegisterFlag(argparser.StringFlagOpts{ 38 Action: c.serviceName.Set, 39 Name: argparser.FlagServiceName, 40 Description: argparser.FlagServiceDesc, 41 Dst: &c.serviceName.Value, 42 }) 43 44 c.CmdClause.Flag("from", "From time, accepted formats at https://fastly.dev/reference/api/metrics-stats/historical-stats").StringVar(c.Input.From) 45 c.CmdClause.Flag("to", "To time").StringVar(c.Input.To) 46 c.CmdClause.Flag("by", "Aggregation period (minute/hour/day)").EnumVar(c.Input.By, "minute", "hour", "day") 47 c.CmdClause.Flag("region", "Filter by region ('stats regions' to list)").StringVar(c.Input.Region) 48 49 c.CmdClause.Flag("format", "Output format (json)").EnumVar(&c.formatFlag, "json") 50 51 return &c 52 } 53 54 // Exec implements the command interface. 55 func (c *HistoricalCommand) Exec(_ io.Reader, out io.Writer) error { 56 serviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog) 57 if err != nil { 58 return err 59 } 60 if c.Globals.Verbose() { 61 argparser.DisplayServiceID(serviceID, flag, source, out) 62 } 63 64 c.Input.Service = fastly.ToPointer(serviceID) 65 66 var envelope statsResponse 67 err = c.Globals.APIClient.GetStatsJSON(&c.Input, &envelope) 68 if err != nil { 69 c.Globals.ErrLog.AddWithContext(err, map[string]any{ 70 "Service ID": serviceID, 71 }) 72 return err 73 } 74 75 if envelope.Status != statusSuccess { 76 return fmt.Errorf("non-success response: %s", envelope.Msg) 77 } 78 79 switch c.formatFlag { 80 case "json": 81 err := writeBlocksJSON(out, serviceID, envelope.Data) 82 if err != nil { 83 c.Globals.ErrLog.AddWithContext(err, map[string]any{ 84 "Service ID": serviceID, 85 }) 86 } 87 88 default: 89 writeHeader(out, envelope.Meta) 90 err := writeBlocks(out, serviceID, envelope.Data) 91 if err != nil { 92 c.Globals.ErrLog.AddWithContext(err, map[string]any{ 93 "Service ID": serviceID, 94 }) 95 } 96 } 97 98 return nil 99 } 100 101 func writeHeader(out io.Writer, meta statsResponseMeta) { 102 fmt.Fprintf(out, "From: %s\n", meta.From) 103 fmt.Fprintf(out, "To: %s\n", meta.To) 104 fmt.Fprintf(out, "By: %s\n", meta.By) 105 fmt.Fprintf(out, "Region: %s\n", meta.Region) 106 fmt.Fprintf(out, "---\n") 107 } 108 109 func writeBlocks(out io.Writer, service string, blocks []statsResponseData) error { 110 for _, block := range blocks { 111 if err := fmtBlock(out, service, block); err != nil { 112 return err 113 } 114 } 115 116 return nil 117 } 118 119 func writeBlocksJSON(out io.Writer, _ string, blocks []statsResponseData) error { 120 for _, block := range blocks { 121 if err := json.NewEncoder(out).Encode(block); err != nil { 122 return err 123 } 124 } 125 126 return nil 127 }