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  }