128 lines
3.2 KiB
Go
128 lines
3.2 KiB
Go
|
// Copyright (C) 2018 Storj Labs, Inc.
|
||
|
// See LICENSE for copying information.
|
||
|
|
||
|
package telemetry
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"net"
|
||
|
"os"
|
||
|
"time"
|
||
|
|
||
|
"github.com/zeebo/admission/admmonkit"
|
||
|
"github.com/zeebo/admission/admproto"
|
||
|
"go.uber.org/zap"
|
||
|
monkit "gopkg.in/spacemonkeygo/monkit.v2"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
DefaultInterval = time.Hour
|
||
|
|
||
|
// MTUs are often 1500, though a good argument could be made for 512
|
||
|
DefaultPacketSize = 1000
|
||
|
)
|
||
|
|
||
|
// ClientOpts allows you to set Client Options
|
||
|
type ClientOpts struct {
|
||
|
// Interval is how frequently stats from the provided Registry will be
|
||
|
// sent up. Note that this interval is "jittered", so the actual interval
|
||
|
// is taken from a normal distribution with a mean of Interval and a
|
||
|
// variance of Interval/4. Defaults to DefaultInterval
|
||
|
Interval time.Duration
|
||
|
|
||
|
// Application is the application name, usually prepended to metric names.
|
||
|
// By default it will be os.Args[0]
|
||
|
Application string
|
||
|
|
||
|
// Instance is a string that identifies this particular server. Could be a
|
||
|
// node id, but defaults to the first non-nil MAC address
|
||
|
Instance string
|
||
|
|
||
|
// PacketSize controls how we fragment the data as it goes out in UDP
|
||
|
// packets. Defaults to DefaultPacketSize
|
||
|
PacketSize int
|
||
|
|
||
|
// Registry is where to get stats from. Defaults to monkit.Default
|
||
|
Registry *monkit.Registry
|
||
|
|
||
|
// FloatEncoding is how floats should be encoded on the wire.
|
||
|
// Default is float16.
|
||
|
FloatEncoding admproto.FloatEncoding
|
||
|
}
|
||
|
|
||
|
// Client is a telemetry client for sending UDP packets at a regular interval
|
||
|
// from a monkit.Registry
|
||
|
type Client struct {
|
||
|
interval time.Duration
|
||
|
opts admmonkit.Options
|
||
|
}
|
||
|
|
||
|
// NewClient constructs a telemetry client that sends packets to remote_addr
|
||
|
// over UDP.
|
||
|
func NewClient(remote_addr string, opts ClientOpts) (rv *Client, err error) {
|
||
|
if opts.Interval == 0 {
|
||
|
opts.Interval = DefaultInterval
|
||
|
}
|
||
|
if opts.Application == "" {
|
||
|
if len(os.Args) > 0 {
|
||
|
opts.Application = os.Args[0]
|
||
|
} else {
|
||
|
// what the actual heck
|
||
|
opts.Application = "unknown"
|
||
|
}
|
||
|
}
|
||
|
if opts.Instance == "" {
|
||
|
// instance by default is the first non-nil mac address
|
||
|
ifaces, err := net.Interfaces()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
for _, iface := range ifaces {
|
||
|
if iface.HardwareAddr != nil {
|
||
|
opts.Instance = iface.HardwareAddr.String()
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if opts.Instance == "" {
|
||
|
opts.Instance = "unknown"
|
||
|
}
|
||
|
}
|
||
|
if opts.Registry == nil {
|
||
|
opts.Registry = monkit.Default
|
||
|
}
|
||
|
if opts.PacketSize == 0 {
|
||
|
opts.PacketSize = DefaultPacketSize
|
||
|
}
|
||
|
|
||
|
return &Client{
|
||
|
interval: opts.Interval,
|
||
|
opts: admmonkit.Options{
|
||
|
Application: opts.Application,
|
||
|
InstanceId: []byte(opts.Instance),
|
||
|
Address: remote_addr,
|
||
|
PacketSize: opts.PacketSize,
|
||
|
Registry: opts.Registry,
|
||
|
ProtoOpts: admproto.Options{FloatEncoding: opts.FloatEncoding},
|
||
|
},
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// Run calls Report roughly every Interval
|
||
|
func (c *Client) Run(ctx context.Context) {
|
||
|
for {
|
||
|
time.Sleep(jitter(c.interval))
|
||
|
if ctx.Err() != nil {
|
||
|
return
|
||
|
}
|
||
|
err := c.Report(ctx)
|
||
|
if err != nil {
|
||
|
zap.S().Errorf("failed sending telemetry report: %v", err)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Report bundles up all the current stats and writes them out as UDP packets
|
||
|
func (c *Client) Report(ctx context.Context) error {
|
||
|
return admmonkit.Send(ctx, c.opts)
|
||
|
}
|