Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add interface for stats and realtime analytics #48

Merged
merged 2 commits into from
Jul 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions fastly/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const APIKeyHeader = "Fastly-Key"
// support an on-premise solution, this is likely to always be the default.
const DefaultEndpoint = "https://api.fastly.com"

// RealtimeStatsEndpoint is the realtime stats endpoint for Fastly.
const RealtimeStatsEndpoint = "https://rt.fastly.com"

// ProjectURL is the url for this library.
var ProjectURL = "github.com/sethvargo/go-fastly"

Expand All @@ -53,6 +56,11 @@ type Client struct {
url *url.URL
}

// RTSClient is the entrypoint to the Fastly's Realtime Stats API
type RTSClient struct {
*Client
}

// DefaultClient instantiates a new Fastly API client. This function requires
// the environment variable `FASTLY_API_KEY` is set and contains a valid API key
// to authenticate with Fastly.
Expand Down Expand Up @@ -81,6 +89,17 @@ func NewClientForEndpoint(key string, endpoint string) (*Client, error) {
return client.init()
}

// NewRealtimeStatsClient instantiates a new Fastly API client for the realtime stats.
// This function requires the environment variable `FASTLY_API_KEY` is set and contains
// a valid API key to authenticate with Fastly.
func NewRealtimeStatsClient() *RTSClient {
c, err := NewClientForEndpoint(os.Getenv(APIKeyEnvVar), RealtimeStatsEndpoint)
if err != nil {
panic(err)
}
return &RTSClient{Client: c}
}

func (c *Client) init() (*Client, error) {
u, err := url.Parse(c.Address)
if err != nil {
Expand Down
20 changes: 20 additions & 0 deletions fastly/fastly_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
// testClient is the test client.
var testClient = DefaultClient()

// testStatsClient is the test client for realtime stats.
var testStatsClient = NewRealtimeStatsClient()

// testServiceID is the ID of the testing service.
var testServiceID = "7i6HN3TK9wS159v2gPAZ8A"

Expand Down Expand Up @@ -47,3 +50,20 @@ func record(t *testing.T, fixture string, f func(*Client)) {

f(client)
}

func recordRealtimeStats(t *testing.T, fixture string, f func(*RTSClient)) {
r, err := recorder.New("fixtures/" + fixture)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := r.Stop(); err != nil {
t.Fatal(err)
}
}()

client := NewRealtimeStatsClient()
client.HTTPClient.Transport = r

f(client)
}
53 changes: 53 additions & 0 deletions fastly/realtime_stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package fastly

import "fmt"

// RealtimeStats is a response from Fastly's real-time analytics endpoint
type RealtimeStatsResponse struct {
Timestamp uint64 `mapstructure:"Timestamp"`
Data []*RealtimeData `mapstructure:"Data"`
Error string `mapstructure:"Error"`
AggregateDelay uint32 `mapstructure:"AggregateDelay"`
}

// RealtimeData represents combined stats for all Fastly's POPs and aggregate of them.
// It also includes a timestamp of when the stats were recorded
type RealtimeData struct {
Datacenter map[string]*Stats `mapstructure:"datacenter"`
Aggregated *Stats `mapstructure:"aggregated"`
Recorded uint64 `mapstructure:"recorded"`
}

// GetRealtimeStatsInput is an input parameter to GetRealtimeStats function
type GetRealtimeStatsInput struct {
Service string
Timestamp uint64
Limit uint32
}

// GetRealtimeStats returns realtime stats for a service based on the GetRealtimeStatsInput
// parameter. The realtime stats work in a rolling fasion where first request will return
// a timestamp which should be passed to consequentive call and so on.
// More details at https://docs.fastly.com/api/analytics
func (c *RTSClient) GetRealtimeStats(i *GetRealtimeStatsInput) (*RealtimeStatsResponse, error) {
if i.Service == "" {
return nil, ErrMissingService
}

path := fmt.Sprintf("/v1/channel/%s/ts/%d", i.Service, i.Timestamp)

if i.Limit != 0 {
path = fmt.Sprintf("%s/limit/%d", path, i.Limit)
}

resp, err := c.Get(path, nil)
if err != nil {
return nil, err
}

var s *RealtimeStatsResponse
if err := decodeJSON(&s, resp.Body); err != nil {
return nil, err
}
return s, nil
}
32 changes: 32 additions & 0 deletions fastly/realtime_stats_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package fastly

import "testing"

func TestClient_GetRealtimeStats_validation(t *testing.T) {
var err error
_, err = testStatsClient.GetRealtimeStats(&GetRealtimeStatsInput{
Service: "",
})
if err != ErrMissingService {
t.Errorf("bad error: %s", err)
}
}

func TestStatsClient_GetRealtimeStats(t *testing.T) {
t.Parallel()

var err error

// Get
var s *RealtimeStatsResponse
recordRealtimeStats(t, "realtime_stats/get", func(c *RTSClient) {
s, err = c.GetRealtimeStats(&GetRealtimeStatsInput{
Service: testServiceID,
Timestamp: 0,
Limit: 3,
})
})
if err != nil {
t.Fatal(err)
}
}
Loading