Skip to content

Commit

Permalink
server: add usage reporting
Browse files Browse the repository at this point in the history
Adds usage reporting that periodically reports anonymous usage
statistics to report.pikoproxy.com. Each report includes the servers
uptime, requests processed, upstreams registered and host
OS/architecture.

This can be disabled with '--usage.disable'.
  • Loading branch information
andydunstall committed May 29, 2024
1 parent 5bc2b55 commit a3e06b6
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 2 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/gin-gonic/gin v1.10.0
github.com/goccy/go-yaml v1.11.3
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.1.2
github.com/gorilla/websocket v1.5.1
github.com/hashicorp/go-sockaddr v1.0.6
github.com/oklog/run v1.1.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I=
Expand Down
18 changes: 18 additions & 0 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,19 @@ func (c *ClusterConfig) Validate() error {
return nil
}

type UsageConfig struct {
// Disable indicates whether to disable anonymous usage collection.
Disable bool `json:"disable" yaml:"disable"`
}

type Config struct {
Proxy ProxyConfig `json:"proxy" yaml:"proxy"`
Upstream UpstreamConfig `json:"upstream" yaml:"upstream"`
Admin AdminConfig `json:"admin" yaml:"admin"`
Gossip gossip.Config `json:"gossip" yaml:"gossip"`
Cluster ClusterConfig `json:"cluster" yaml:"cluster"`
Auth auth.Config `json:"auth" yaml:"auth"`
Usage UsageConfig `json:"usage" yaml:"usage"`
Log log.Config `json:"log" yaml:"log"`

// GracePeriod is the duration to gracefully shutdown the server. During
Expand Down Expand Up @@ -212,6 +218,18 @@ node to join (excluding itself) but fails to join any members.`,

c.Gossip.RegisterFlags(fs)

fs.BoolVar(
&c.Usage.Disable,
"usage.disable",
false,
`
Whether to disable anonymous usage tracking.
The Piko server periodically sends an anonymous report to help understand how
Piko is being used. This report includes the Piko version, host OS, host
architecture, requests processed and upstreams registered.`,
)

c.Log.RegisterFlags(fs)

fs.DurationVar(
Expand Down
24 changes: 22 additions & 2 deletions server/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,26 @@ import (

"github.com/andydunstall/piko/pkg/log"
"github.com/andydunstall/piko/server/cluster"
"go.uber.org/atomic"
"go.uber.org/zap"
)

var (
errEndpointNotFound = errors.New("not endpoint found")
)

type Usage struct {
Requests *atomic.Uint64
Upstreams *atomic.Uint64
}

// Proxy is responsible for forwarding requests to upstream endpoints.
type Proxy struct {
local *localProxy
remote *remoteProxy

usage *Usage

metrics *Metrics

logger log.Logger
Expand All @@ -38,8 +46,12 @@ func NewProxy(clusterState *cluster.State, opts ...Option) *Proxy {
metrics := NewMetrics()
logger := options.logger.WithSubsystem("proxy")
return &Proxy{
local: newLocalProxy(metrics, logger),
remote: newRemoteProxy(clusterState, options.forwarder, metrics, logger),
local: newLocalProxy(metrics, logger),
remote: newRemoteProxy(clusterState, options.forwarder, metrics, logger),
usage: &Usage{
Requests: atomic.NewUint64(0),
Upstreams: atomic.NewUint64(0),
},
metrics: metrics,
logger: logger,
}
Expand All @@ -65,6 +77,7 @@ func (p *Proxy) Request(
zap.String("path", r.URL.Path),
zap.Bool("forwarded", forwarded),
)
p.usage.Requests.Inc()

endpointID := endpointIDFromRequest(r)
if endpointID == "" {
Expand Down Expand Up @@ -152,6 +165,9 @@ func (p *Proxy) AddConn(conn Conn) {
zap.String("endpoint-id", conn.EndpointID()),
zap.String("addr", conn.Addr()),
)

p.usage.Upstreams.Inc()

p.local.AddConn(conn)
p.remote.AddConn(conn)
}
Expand All @@ -173,6 +189,10 @@ func (p *Proxy) ConnAddrs() map[string][]string {
return p.local.ConnAddrs()
}

func (p *Proxy) Usage() *Usage {
return p.usage
}

func (p *Proxy) Metrics() *Metrics {
return p.metrics
}
Expand Down
14 changes: 14 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
adminserver "github.com/andydunstall/piko/server/server/admin"
proxyserver "github.com/andydunstall/piko/server/server/proxy"
upstreamserver "github.com/andydunstall/piko/server/server/upstream"
"github.com/andydunstall/piko/server/usage"
"github.com/golang-jwt/jwt/v5"
"github.com/hashicorp/go-sockaddr"
rungroup "github.com/oklog/run"
Expand Down Expand Up @@ -221,6 +222,8 @@ func (s *Server) Run(ctx context.Context) error {
s.logger,
)

reporter := usage.NewReporter(p, s.logger)

var group rungroup.Group

// Termination handler.
Expand Down Expand Up @@ -335,6 +338,17 @@ func (s *Server) Run(ctx context.Context) error {
gossipCancel()
})

if !s.conf.Usage.Disable {
// Usage.
usageCtx, usageCancel := context.WithCancel(ctx)
group.Add(func() error {
reporter.Run(usageCtx)
return nil
}, func(error) {
usageCancel()
})
}

if err := group.Run(); err != nil {
return err
}
Expand Down
105 changes: 105 additions & 0 deletions server/usage/reporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package usage

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"runtime"
"time"

"github.com/andydunstall/piko/pkg/log"
"github.com/andydunstall/piko/server/proxy"
"github.com/google/uuid"
"go.uber.org/zap"
)

const (
reportInterval = time.Hour
)

type Report struct {
ID string `json:"id"`
OS string `json:"os"`
Arch string `json:"arch"`
Uptime int64 `json:"uptime"`
Requests uint64 `json:"requests"`
Upstreams uint64 `json:"upstreams"`
}

// Reporter sends a periodic usage report.
type Reporter struct {
id string
start time.Time
proxy *proxy.Proxy
logger log.Logger
}

func NewReporter(proxy *proxy.Proxy, logger log.Logger) *Reporter {
return &Reporter{
id: uuid.New().String(),
start: time.Now(),
proxy: proxy,
logger: logger.WithSubsystem("reporter"),
}
}

func (r *Reporter) Run(ctx context.Context) {
// Report on startup.
r.report()

ticker := time.NewTicker(reportInterval)
defer ticker.Stop()

for {
select {
case <-ctx.Done():
// Report on shutdown.
r.report()
return
case <-ticker.C:
// Report on interval.
r.report()
}
}
}

func (r *Reporter) report() {
report := &Report{
ID: r.id,
OS: runtime.GOOS,
Arch: runtime.GOARCH,
Uptime: int64(time.Since(r.start).Seconds()),
Requests: r.proxy.Usage().Requests.Load(),
Upstreams: r.proxy.Usage().Upstreams.Load(),
}
if err := r.send(report); err != nil {
// Debug only as theres no user impact.
r.logger.Debug("failed to send usage report", zap.Error(err))
}
}

func (r *Reporter) send(report *Report) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

body, err := json.Marshal(report)
if err != nil {
return fmt.Errorf("marshal: %w", err)
}
req, err := http.NewRequestWithContext(
ctx, http.MethodPost, "http://report.pikoproxy.com/v1", bytes.NewBuffer(body),
)
if err != nil {
return fmt.Errorf("request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("request: %w", err)
}
defer resp.Body.Close()

return nil
}

0 comments on commit a3e06b6

Please sign in to comment.