// 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) }