Projectize the project.
This commit is contained in:
parent
e45eaafd65
commit
c5a4a33791
|
|
@ -1 +1,5 @@
|
||||||
.env
|
.env
|
||||||
|
/unifi-poller
|
||||||
|
/*.1.gz
|
||||||
|
/*.1
|
||||||
|
/vendor
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,30 @@
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/dewski/unifi",
|
"ImportPath": "github.com/davidnewhall/unifi",
|
||||||
"GoVersion": "go1.6",
|
"GoVersion": "go1.10",
|
||||||
|
"GodepVersion": "v79",
|
||||||
"Packages": [
|
"Packages": [
|
||||||
"./..."
|
"./..."
|
||||||
],
|
],
|
||||||
"Deps": [
|
"Deps": [
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/influxdata/influxdb/client/v2",
|
"ImportPath": "github.com/influxdata/influxdb/client/v2",
|
||||||
"Comment": "v0.10.0-565-ga4b00ae",
|
"Comment": "v1.5.0-149-g14dcc5d",
|
||||||
"Rev": "a4b00aeeba630bda96db70bd9fc5eb2d4c0b7580"
|
"Rev": "14dcc5d6e7a6b15e17aba7b104b8ad0ca6c91ad2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/influxdata/influxdb/models",
|
"ImportPath": "github.com/influxdata/influxdb/models",
|
||||||
"Comment": "v0.10.0-565-ga4b00ae",
|
"Comment": "v1.5.0-149-g14dcc5d",
|
||||||
"Rev": "a4b00aeeba630bda96db70bd9fc5eb2d4c0b7580"
|
"Rev": "14dcc5d6e7a6b15e17aba7b104b8ad0ca6c91ad2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/influxdata/influxdb/pkg/escape",
|
"ImportPath": "github.com/influxdata/influxdb/pkg/escape",
|
||||||
"Comment": "v0.10.0-565-ga4b00ae",
|
"Comment": "v1.5.0-149-g14dcc5d",
|
||||||
"Rev": "a4b00aeeba630bda96db70bd9fc5eb2d4c0b7580"
|
"Rev": "14dcc5d6e7a6b15e17aba7b104b8ad0ca6c91ad2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/pkg/errors",
|
||||||
|
"Comment": "v0.8.0-6-g2b3a18b",
|
||||||
|
"Rev": "2b3a18b5f0fb6b4f9190549597d3f962c02bc5eb"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
/pkg
|
|
||||||
/bin
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2013-2016 Errplane Inc.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
|
||||||
the Software without restriction, including without limitation the rights to
|
|
||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
||||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
||||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
# List
|
|
||||||
- bootstrap 3.3.5 [MIT LICENSE](https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
||||||
- collectd.org [ISC LICENSE](https://github.com/collectd/go-collectd/blob/master/LICENSE)
|
|
||||||
- github.com/armon/go-metrics [MIT LICENSE](https://github.com/armon/go-metrics/blob/master/LICENSE)
|
|
||||||
- github.com/BurntSushi/toml [WTFPL LICENSE](https://github.com/BurntSushi/toml/blob/master/COPYING)
|
|
||||||
- github.com/bmizerany/pat [MIT LICENSE](https://github.com/bmizerany/pat#license)
|
|
||||||
- github.com/boltdb/bolt [MIT LICENSE](https://github.com/boltdb/bolt/blob/master/LICENSE)
|
|
||||||
- github.com/dgryski/go-bits [MIT LICENSE](https://github.com/dgryski/go-bits/blob/master/LICENSE)
|
|
||||||
- github.com/dgryski/go-bitstream [MIT LICENSE](https://github.com/dgryski/go-bitstream/blob/master/LICENSE)
|
|
||||||
- github.com/gogo/protobuf/proto [BSD LICENSE](https://github.com/gogo/protobuf/blob/master/LICENSE)
|
|
||||||
- github.com/davecgh/go-spew/spew [ISC LICENSE](https://github.com/davecgh/go-spew/blob/master/LICENSE)
|
|
||||||
- github.com/golang/snappy [BSD LICENSE](https://github.com/golang/snappy/blob/master/LICENSE)
|
|
||||||
- github.com/hashicorp/go-msgpack [BSD LICENSE](https://github.com/hashicorp/go-msgpack/blob/master/LICENSE)
|
|
||||||
- github.com/hashicorp/raft [MPL LICENSE](https://github.com/hashicorp/raft/blob/master/LICENSE)
|
|
||||||
- github.com/hashicorp/raft-boltdb [MOZILLA PUBLIC LICENSE](https://github.com/hashicorp/raft-boltdb/blob/master/LICENSE)
|
|
||||||
- github.com/influxdata/usage-client [MIT LICENSE](https://github.com/influxdata/usage-client/blob/master/LICENSE.txt)
|
|
||||||
- github.com/jwilder/encoding [MIT LICENSE](https://github.com/jwilder/encoding/blob/master/LICENSE)
|
|
||||||
- github.com/kimor79/gollectd [BSD LICENSE](https://github.com/kimor79/gollectd/blob/master/LICENSE)
|
|
||||||
- github.com/paulbellamy/ratecounter [MIT LICENSE](https://github.com/paulbellamy/ratecounter/blob/master/LICENSE)
|
|
||||||
- github.com/peterh/liner [MIT LICENSE](https://github.com/peterh/liner/blob/master/COPYING)
|
|
||||||
- github.com/rakyll/statik [APACHE LICENSE](https://github.com/rakyll/statik/blob/master/LICENSE)
|
|
||||||
- glyphicons [LICENSE](http://glyphicons.com/license/)
|
|
||||||
- golang.org/x/crypto [BSD LICENSE](https://github.com/golang/crypto/blob/master/LICENSE)
|
|
||||||
- golang.org/x/tools [BSD LICENSE](https://github.com/golang/tools/blob/master/LICENSE)
|
|
||||||
- gopkg.in/fatih/pool.v2 [MIT LICENSE](https://github.com/fatih/pool/blob/v2.0.0/LICENSE)
|
|
||||||
- jquery 2.1.4 [MIT LICENSE](https://github.com/jquery/jquery/blob/master/LICENSE.txt)
|
|
||||||
- react 0.13.3 [BSD LICENSE](https://github.com/facebook/react/blob/master/LICENSE)
|
|
||||||
573
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/client/v2/client.go
generated
vendored
573
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/client/v2/client.go
generated
vendored
|
|
@ -1,573 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/influxdb/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UDPPayloadSize is a reasonable default payload size for UDP packets that
|
|
||||||
// could be travelling over the internet.
|
|
||||||
const (
|
|
||||||
UDPPayloadSize = 512
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTTPConfig is the config data needed to create an HTTP Client
|
|
||||||
type HTTPConfig struct {
|
|
||||||
// Addr should be of the form "http://host:port"
|
|
||||||
// or "http://[ipv6-host%zone]:port".
|
|
||||||
Addr string
|
|
||||||
|
|
||||||
// Username is the influxdb username, optional
|
|
||||||
Username string
|
|
||||||
|
|
||||||
// Password is the influxdb password, optional
|
|
||||||
Password string
|
|
||||||
|
|
||||||
// UserAgent is the http User Agent, defaults to "InfluxDBClient"
|
|
||||||
UserAgent string
|
|
||||||
|
|
||||||
// Timeout for influxdb writes, defaults to no timeout
|
|
||||||
Timeout time.Duration
|
|
||||||
|
|
||||||
// InsecureSkipVerify gets passed to the http client, if true, it will
|
|
||||||
// skip https certificate verification. Defaults to false
|
|
||||||
InsecureSkipVerify bool
|
|
||||||
|
|
||||||
// TLSConfig allows the user to set their own TLS config for the HTTP
|
|
||||||
// Client. If set, this option overrides InsecureSkipVerify.
|
|
||||||
TLSConfig *tls.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// UDPConfig is the config data needed to create a UDP Client
|
|
||||||
type UDPConfig struct {
|
|
||||||
// Addr should be of the form "host:port"
|
|
||||||
// or "[ipv6-host%zone]:port".
|
|
||||||
Addr string
|
|
||||||
|
|
||||||
// PayloadSize is the maximum size of a UDP client message, optional
|
|
||||||
// Tune this based on your network. Defaults to UDPBufferSize.
|
|
||||||
PayloadSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchPointsConfig is the config data needed to create an instance of the BatchPoints struct
|
|
||||||
type BatchPointsConfig struct {
|
|
||||||
// Precision is the write precision of the points, defaults to "ns"
|
|
||||||
Precision string
|
|
||||||
|
|
||||||
// Database is the database to write points to
|
|
||||||
Database string
|
|
||||||
|
|
||||||
// RetentionPolicy is the retention policy of the points
|
|
||||||
RetentionPolicy string
|
|
||||||
|
|
||||||
// Write consistency is the number of servers required to confirm write
|
|
||||||
WriteConsistency string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client is a client interface for writing & querying the database
|
|
||||||
type Client interface {
|
|
||||||
// Ping checks that status of cluster
|
|
||||||
Ping(timeout time.Duration) (time.Duration, string, error)
|
|
||||||
|
|
||||||
// Write takes a BatchPoints object and writes all Points to InfluxDB.
|
|
||||||
Write(bp BatchPoints) error
|
|
||||||
|
|
||||||
// Query makes an InfluxDB Query on the database. This will fail if using
|
|
||||||
// the UDP client.
|
|
||||||
Query(q Query) (*Response, error)
|
|
||||||
|
|
||||||
// Close releases any resources a Client may be using.
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHTTPClient creates a client interface from the given config.
|
|
||||||
func NewHTTPClient(conf HTTPConfig) (Client, error) {
|
|
||||||
if conf.UserAgent == "" {
|
|
||||||
conf.UserAgent = "InfluxDBClient"
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(conf.Addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if u.Scheme != "http" && u.Scheme != "https" {
|
|
||||||
m := fmt.Sprintf("Unsupported protocol scheme: %s, your address"+
|
|
||||||
" must start with http:// or https://", u.Scheme)
|
|
||||||
return nil, errors.New(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: conf.InsecureSkipVerify,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if conf.TLSConfig != nil {
|
|
||||||
tr.TLSClientConfig = conf.TLSConfig
|
|
||||||
}
|
|
||||||
return &client{
|
|
||||||
url: u,
|
|
||||||
username: conf.Username,
|
|
||||||
password: conf.Password,
|
|
||||||
useragent: conf.UserAgent,
|
|
||||||
httpClient: &http.Client{
|
|
||||||
Timeout: conf.Timeout,
|
|
||||||
Transport: tr,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping will check to see if the server is up with an optional timeout on waiting for leader.
|
|
||||||
// Ping returns how long the request took, the version of the server it connected to, and an error if one occurred.
|
|
||||||
func (c *client) Ping(timeout time.Duration) (time.Duration, string, error) {
|
|
||||||
now := time.Now()
|
|
||||||
u := c.url
|
|
||||||
u.Path = "ping"
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", u.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("User-Agent", c.useragent)
|
|
||||||
|
|
||||||
if c.username != "" {
|
|
||||||
req.SetBasicAuth(c.username, c.password)
|
|
||||||
}
|
|
||||||
|
|
||||||
if timeout > 0 {
|
|
||||||
params := req.URL.Query()
|
|
||||||
params.Set("wait_for_leader", fmt.Sprintf("%.0fs", timeout.Seconds()))
|
|
||||||
req.URL.RawQuery = params.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusNoContent {
|
|
||||||
var err = fmt.Errorf(string(body))
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
version := resp.Header.Get("X-Influxdb-Version")
|
|
||||||
return time.Since(now), version, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close releases the client's resources.
|
|
||||||
func (c *client) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUDPClient returns a client interface for writing to an InfluxDB UDP
|
|
||||||
// service from the given config.
|
|
||||||
func NewUDPClient(conf UDPConfig) (Client, error) {
|
|
||||||
var udpAddr *net.UDPAddr
|
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", conf.Addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := net.DialUDP("udp", nil, udpAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
payloadSize := conf.PayloadSize
|
|
||||||
if payloadSize == 0 {
|
|
||||||
payloadSize = UDPPayloadSize
|
|
||||||
}
|
|
||||||
|
|
||||||
return &udpclient{
|
|
||||||
conn: conn,
|
|
||||||
payloadSize: payloadSize,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping will check to see if the server is up with an optional timeout on waiting for leader.
|
|
||||||
// Ping returns how long the request took, the version of the server it connected to, and an error if one occurred.
|
|
||||||
func (uc *udpclient) Ping(timeout time.Duration) (time.Duration, string, error) {
|
|
||||||
return 0, "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close releases the udpclient's resources.
|
|
||||||
func (uc *udpclient) Close() error {
|
|
||||||
return uc.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type client struct {
|
|
||||||
url *url.URL
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
useragent string
|
|
||||||
httpClient *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
type udpclient struct {
|
|
||||||
conn *net.UDPConn
|
|
||||||
payloadSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchPoints is an interface into a batched grouping of points to write into
|
|
||||||
// InfluxDB together. BatchPoints is NOT thread-safe, you must create a separate
|
|
||||||
// batch for each goroutine.
|
|
||||||
type BatchPoints interface {
|
|
||||||
// AddPoint adds the given point to the Batch of points
|
|
||||||
AddPoint(p *Point)
|
|
||||||
// AddPoints adds the given points to the Batch of points
|
|
||||||
AddPoints(ps []*Point)
|
|
||||||
// Points lists the points in the Batch
|
|
||||||
Points() []*Point
|
|
||||||
|
|
||||||
// Precision returns the currently set precision of this Batch
|
|
||||||
Precision() string
|
|
||||||
// SetPrecision sets the precision of this batch.
|
|
||||||
SetPrecision(s string) error
|
|
||||||
|
|
||||||
// Database returns the currently set database of this Batch
|
|
||||||
Database() string
|
|
||||||
// SetDatabase sets the database of this Batch
|
|
||||||
SetDatabase(s string)
|
|
||||||
|
|
||||||
// WriteConsistency returns the currently set write consistency of this Batch
|
|
||||||
WriteConsistency() string
|
|
||||||
// SetWriteConsistency sets the write consistency of this Batch
|
|
||||||
SetWriteConsistency(s string)
|
|
||||||
|
|
||||||
// RetentionPolicy returns the currently set retention policy of this Batch
|
|
||||||
RetentionPolicy() string
|
|
||||||
// SetRetentionPolicy sets the retention policy of this Batch
|
|
||||||
SetRetentionPolicy(s string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBatchPoints returns a BatchPoints interface based on the given config.
|
|
||||||
func NewBatchPoints(conf BatchPointsConfig) (BatchPoints, error) {
|
|
||||||
if conf.Precision == "" {
|
|
||||||
conf.Precision = "ns"
|
|
||||||
}
|
|
||||||
if _, err := time.ParseDuration("1" + conf.Precision); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
bp := &batchpoints{
|
|
||||||
database: conf.Database,
|
|
||||||
precision: conf.Precision,
|
|
||||||
retentionPolicy: conf.RetentionPolicy,
|
|
||||||
writeConsistency: conf.WriteConsistency,
|
|
||||||
}
|
|
||||||
return bp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type batchpoints struct {
|
|
||||||
points []*Point
|
|
||||||
database string
|
|
||||||
precision string
|
|
||||||
retentionPolicy string
|
|
||||||
writeConsistency string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *batchpoints) AddPoint(p *Point) {
|
|
||||||
bp.points = append(bp.points, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *batchpoints) AddPoints(ps []*Point) {
|
|
||||||
bp.points = append(bp.points, ps...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *batchpoints) Points() []*Point {
|
|
||||||
return bp.points
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *batchpoints) Precision() string {
|
|
||||||
return bp.precision
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *batchpoints) Database() string {
|
|
||||||
return bp.database
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *batchpoints) WriteConsistency() string {
|
|
||||||
return bp.writeConsistency
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *batchpoints) RetentionPolicy() string {
|
|
||||||
return bp.retentionPolicy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *batchpoints) SetPrecision(p string) error {
|
|
||||||
if _, err := time.ParseDuration("1" + p); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
bp.precision = p
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *batchpoints) SetDatabase(db string) {
|
|
||||||
bp.database = db
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *batchpoints) SetWriteConsistency(wc string) {
|
|
||||||
bp.writeConsistency = wc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *batchpoints) SetRetentionPolicy(rp string) {
|
|
||||||
bp.retentionPolicy = rp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Point represents a single data point
|
|
||||||
type Point struct {
|
|
||||||
pt models.Point
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPoint returns a point with the given timestamp. If a timestamp is not
|
|
||||||
// given, then data is sent to the database without a timestamp, in which case
|
|
||||||
// the server will assign local time upon reception. NOTE: it is recommended to
|
|
||||||
// send data with a timestamp.
|
|
||||||
func NewPoint(
|
|
||||||
name string,
|
|
||||||
tags map[string]string,
|
|
||||||
fields map[string]interface{},
|
|
||||||
t ...time.Time,
|
|
||||||
) (*Point, error) {
|
|
||||||
var T time.Time
|
|
||||||
if len(t) > 0 {
|
|
||||||
T = t[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
pt, err := models.NewPoint(name, tags, fields, T)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Point{
|
|
||||||
pt: pt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a line-protocol string of the Point
|
|
||||||
func (p *Point) String() string {
|
|
||||||
return p.pt.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrecisionString returns a line-protocol string of the Point, at precision
|
|
||||||
func (p *Point) PrecisionString(precison string) string {
|
|
||||||
return p.pt.PrecisionString(precison)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the measurement name of the point
|
|
||||||
func (p *Point) Name() string {
|
|
||||||
return p.pt.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tags returns the tags associated with the point
|
|
||||||
func (p *Point) Tags() map[string]string {
|
|
||||||
return p.pt.Tags()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time return the timestamp for the point
|
|
||||||
func (p *Point) Time() time.Time {
|
|
||||||
return p.pt.Time()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnixNano returns the unix nano time of the point
|
|
||||||
func (p *Point) UnixNano() int64 {
|
|
||||||
return p.pt.UnixNano()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fields returns the fields for the point
|
|
||||||
func (p *Point) Fields() map[string]interface{} {
|
|
||||||
return p.pt.Fields()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPointFrom returns a point from the provided models.Point.
|
|
||||||
func NewPointFrom(pt models.Point) *Point {
|
|
||||||
return &Point{pt: pt}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uc *udpclient) Write(bp BatchPoints) error {
|
|
||||||
var b bytes.Buffer
|
|
||||||
var d time.Duration
|
|
||||||
d, _ = time.ParseDuration("1" + bp.Precision())
|
|
||||||
|
|
||||||
for _, p := range bp.Points() {
|
|
||||||
pointstring := p.pt.RoundedString(d) + "\n"
|
|
||||||
|
|
||||||
// Write and reset the buffer if we reach the max size
|
|
||||||
if b.Len()+len(pointstring) >= uc.payloadSize {
|
|
||||||
if _, err := uc.conn.Write(b.Bytes()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := b.WriteString(pointstring); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := uc.conn.Write(b.Bytes())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) Write(bp BatchPoints) error {
|
|
||||||
var b bytes.Buffer
|
|
||||||
|
|
||||||
for _, p := range bp.Points() {
|
|
||||||
if _, err := b.WriteString(p.pt.PrecisionString(bp.Precision())); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.WriteByte('\n'); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u := c.url
|
|
||||||
u.Path = "write"
|
|
||||||
req, err := http.NewRequest("POST", u.String(), &b)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "")
|
|
||||||
req.Header.Set("User-Agent", c.useragent)
|
|
||||||
if c.username != "" {
|
|
||||||
req.SetBasicAuth(c.username, c.password)
|
|
||||||
}
|
|
||||||
|
|
||||||
params := req.URL.Query()
|
|
||||||
params.Set("db", bp.Database())
|
|
||||||
params.Set("rp", bp.RetentionPolicy())
|
|
||||||
params.Set("precision", bp.Precision())
|
|
||||||
params.Set("consistency", bp.WriteConsistency())
|
|
||||||
req.URL.RawQuery = params.Encode()
|
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
|
|
||||||
var err = fmt.Errorf(string(body))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query defines a query to send to the server
|
|
||||||
type Query struct {
|
|
||||||
Command string
|
|
||||||
Database string
|
|
||||||
Precision string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewQuery returns a query object
|
|
||||||
// database and precision strings can be empty strings if they are not needed
|
|
||||||
// for the query.
|
|
||||||
func NewQuery(command, database, precision string) Query {
|
|
||||||
return Query{
|
|
||||||
Command: command,
|
|
||||||
Database: database,
|
|
||||||
Precision: precision,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response represents a list of statement results.
|
|
||||||
type Response struct {
|
|
||||||
Results []Result
|
|
||||||
Err string `json:"error,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the first error from any statement.
|
|
||||||
// Returns nil if no errors occurred on any statements.
|
|
||||||
func (r *Response) Error() error {
|
|
||||||
if r.Err != "" {
|
|
||||||
return fmt.Errorf(r.Err)
|
|
||||||
}
|
|
||||||
for _, result := range r.Results {
|
|
||||||
if result.Err != "" {
|
|
||||||
return fmt.Errorf(result.Err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Result represents a resultset returned from a single statement.
|
|
||||||
type Result struct {
|
|
||||||
Series []models.Row
|
|
||||||
Err string `json:"error,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uc *udpclient) Query(q Query) (*Response, error) {
|
|
||||||
return nil, fmt.Errorf("Querying via UDP is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query sends a command to the server and returns the Response
|
|
||||||
func (c *client) Query(q Query) (*Response, error) {
|
|
||||||
u := c.url
|
|
||||||
u.Path = "query"
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", u.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "")
|
|
||||||
req.Header.Set("User-Agent", c.useragent)
|
|
||||||
if c.username != "" {
|
|
||||||
req.SetBasicAuth(c.username, c.password)
|
|
||||||
}
|
|
||||||
|
|
||||||
params := req.URL.Query()
|
|
||||||
params.Set("q", q.Command)
|
|
||||||
params.Set("db", q.Database)
|
|
||||||
if q.Precision != "" {
|
|
||||||
params.Set("epoch", q.Precision)
|
|
||||||
}
|
|
||||||
req.URL.RawQuery = params.Encode()
|
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var response Response
|
|
||||||
dec := json.NewDecoder(resp.Body)
|
|
||||||
dec.UseNumber()
|
|
||||||
decErr := dec.Decode(&response)
|
|
||||||
|
|
||||||
// ignore this error if we got an invalid status code
|
|
||||||
if decErr != nil && decErr.Error() == "EOF" && resp.StatusCode != http.StatusOK {
|
|
||||||
decErr = nil
|
|
||||||
}
|
|
||||||
// If we got a valid decode error, send that back
|
|
||||||
if decErr != nil {
|
|
||||||
return nil, decErr
|
|
||||||
}
|
|
||||||
// If we don't have an error in our json response, and didn't get statusOK
|
|
||||||
// then send back an error
|
|
||||||
if resp.StatusCode != http.StatusOK && response.Error() == nil {
|
|
||||||
return &response, fmt.Errorf("received status code %d from server",
|
|
||||||
resp.StatusCode)
|
|
||||||
}
|
|
||||||
return &response, nil
|
|
||||||
}
|
|
||||||
46
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/models/consistency.go
generated
vendored
46
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/models/consistency.go
generated
vendored
|
|
@ -1,46 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConsistencyLevel represent a required replication criteria before a write can
|
|
||||||
// be returned as successful
|
|
||||||
type ConsistencyLevel int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ConsistencyLevelAny allows for hinted hand off, potentially no write happened yet
|
|
||||||
ConsistencyLevelAny ConsistencyLevel = iota
|
|
||||||
|
|
||||||
// ConsistencyLevelOne requires at least one data node acknowledged a write
|
|
||||||
ConsistencyLevelOne
|
|
||||||
|
|
||||||
// ConsistencyLevelQuorum requires a quorum of data nodes to acknowledge a write
|
|
||||||
ConsistencyLevelQuorum
|
|
||||||
|
|
||||||
// ConsistencyLevelAll requires all data nodes to acknowledge a write
|
|
||||||
ConsistencyLevelAll
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrInvalidConsistencyLevel is returned when parsing the string version
|
|
||||||
// of a consistency level.
|
|
||||||
ErrInvalidConsistencyLevel = errors.New("invalid consistency level")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseConsistencyLevel converts a consistency level string to the corresponding ConsistencyLevel const
|
|
||||||
func ParseConsistencyLevel(level string) (ConsistencyLevel, error) {
|
|
||||||
switch strings.ToLower(level) {
|
|
||||||
case "any":
|
|
||||||
return ConsistencyLevelAny, nil
|
|
||||||
case "one":
|
|
||||||
return ConsistencyLevelOne, nil
|
|
||||||
case "quorum":
|
|
||||||
return ConsistencyLevelQuorum, nil
|
|
||||||
case "all":
|
|
||||||
return ConsistencyLevelAll, nil
|
|
||||||
default:
|
|
||||||
return 0, ErrInvalidConsistencyLevel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1553
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/models/points.go
generated
vendored
1553
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/models/points.go
generated
vendored
File diff suppressed because it is too large
Load Diff
60
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/models/rows.go
generated
vendored
60
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/models/rows.go
generated
vendored
|
|
@ -1,60 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash/fnv"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Row represents a single row returned from the execution of a statement.
|
|
||||||
type Row struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Tags map[string]string `json:"tags,omitempty"`
|
|
||||||
Columns []string `json:"columns,omitempty"`
|
|
||||||
Values [][]interface{} `json:"values,omitempty"`
|
|
||||||
Err error `json:"err,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SameSeries returns true if r contains values for the same series as o.
|
|
||||||
func (r *Row) SameSeries(o *Row) bool {
|
|
||||||
return r.tagsHash() == o.tagsHash() && r.Name == o.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// tagsHash returns a hash of tag key/value pairs.
|
|
||||||
func (r *Row) tagsHash() uint64 {
|
|
||||||
h := fnv.New64a()
|
|
||||||
keys := r.tagsKeys()
|
|
||||||
for _, k := range keys {
|
|
||||||
h.Write([]byte(k))
|
|
||||||
h.Write([]byte(r.Tags[k]))
|
|
||||||
}
|
|
||||||
return h.Sum64()
|
|
||||||
}
|
|
||||||
|
|
||||||
// tagKeys returns a sorted list of tag keys.
|
|
||||||
func (r *Row) tagsKeys() []string {
|
|
||||||
a := make([]string, 0, len(r.Tags))
|
|
||||||
for k := range r.Tags {
|
|
||||||
a = append(a, k)
|
|
||||||
}
|
|
||||||
sort.Strings(a)
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rows represents a collection of rows. Rows implements sort.Interface.
|
|
||||||
type Rows []*Row
|
|
||||||
|
|
||||||
func (p Rows) Len() int { return len(p) }
|
|
||||||
|
|
||||||
func (p Rows) Less(i, j int) bool {
|
|
||||||
// Sort by name first.
|
|
||||||
if p[i].Name != p[j].Name {
|
|
||||||
return p[i].Name < p[j].Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by tag set hash. Tags don't have a meaningful sort order so we
|
|
||||||
// just compute a hash and sort by that instead. This allows the tests
|
|
||||||
// to receive rows in a predictable order every time.
|
|
||||||
return p[i].tagsHash() < p[j].tagsHash()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Rows) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
||||||
51
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/models/time.go
generated
vendored
51
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/models/time.go
generated
vendored
|
|
@ -1,51 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
// Helper time methods since parsing time can easily overflow and we only support a
|
|
||||||
// specific time range.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// MaxNanoTime is the maximum time that can be represented via int64 nanoseconds since the epoch.
|
|
||||||
MaxNanoTime = time.Unix(0, math.MaxInt64).UTC()
|
|
||||||
// MinNanoTime is the minumum time that can be represented via int64 nanoseconds since the epoch.
|
|
||||||
MinNanoTime = time.Unix(0, math.MinInt64).UTC()
|
|
||||||
|
|
||||||
// ErrTimeOutOfRange gets returned when time is out of the representable range using int64 nanoseconds since the epoch.
|
|
||||||
ErrTimeOutOfRange = fmt.Errorf("time outside range %s - %s", MinNanoTime, MaxNanoTime)
|
|
||||||
)
|
|
||||||
|
|
||||||
// SafeCalcTime safely calculates the time given. Will return error if the time is outside the
|
|
||||||
// supported range.
|
|
||||||
func SafeCalcTime(timestamp int64, precision string) (time.Time, error) {
|
|
||||||
mult := GetPrecisionMultiplier(precision)
|
|
||||||
if t, ok := safeSignedMult(timestamp, mult); ok {
|
|
||||||
return time.Unix(0, t).UTC(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Time{}, ErrTimeOutOfRange
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckTime checks that a time is within the safe range.
|
|
||||||
func CheckTime(t time.Time) error {
|
|
||||||
if t.Before(MinNanoTime) || t.After(MaxNanoTime) {
|
|
||||||
return ErrTimeOutOfRange
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the multiplication and check to make sure it didn't overflow.
|
|
||||||
func safeSignedMult(a, b int64) (int64, bool) {
|
|
||||||
if a == 0 || b == 0 || a == 1 || b == 1 {
|
|
||||||
return a * b, true
|
|
||||||
}
|
|
||||||
if a == math.MinInt64 || b == math.MaxInt64 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
c := a * b
|
|
||||||
return c, c/b == a
|
|
||||||
}
|
|
||||||
45
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/pkg/escape/bytes.go
generated
vendored
45
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/pkg/escape/bytes.go
generated
vendored
|
|
@ -1,45 +0,0 @@
|
||||||
package escape
|
|
||||||
|
|
||||||
import "bytes"
|
|
||||||
|
|
||||||
func Bytes(in []byte) []byte {
|
|
||||||
for b, esc := range Codes {
|
|
||||||
in = bytes.Replace(in, []byte{b}, esc, -1)
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
func Unescape(in []byte) []byte {
|
|
||||||
i := 0
|
|
||||||
inLen := len(in)
|
|
||||||
var out []byte
|
|
||||||
|
|
||||||
for {
|
|
||||||
if i >= inLen {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if in[i] == '\\' && i+1 < inLen {
|
|
||||||
switch in[i+1] {
|
|
||||||
case ',':
|
|
||||||
out = append(out, ',')
|
|
||||||
i += 2
|
|
||||||
continue
|
|
||||||
case '"':
|
|
||||||
out = append(out, '"')
|
|
||||||
i += 2
|
|
||||||
continue
|
|
||||||
case ' ':
|
|
||||||
out = append(out, ' ')
|
|
||||||
i += 2
|
|
||||||
continue
|
|
||||||
case '=':
|
|
||||||
out = append(out, '=')
|
|
||||||
i += 2
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out = append(out, in[i])
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
34
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/pkg/escape/strings.go
generated
vendored
34
core/poller/Godeps/_workspace/src/github.com/influxdata/influxdb/pkg/escape/strings.go
generated
vendored
|
|
@ -1,34 +0,0 @@
|
||||||
package escape
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
var (
|
|
||||||
Codes = map[byte][]byte{
|
|
||||||
',': []byte(`\,`),
|
|
||||||
'"': []byte(`\"`),
|
|
||||||
' ': []byte(`\ `),
|
|
||||||
'=': []byte(`\=`),
|
|
||||||
}
|
|
||||||
|
|
||||||
codesStr = map[string]string{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
for k, v := range Codes {
|
|
||||||
codesStr[string(k)] = string(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnescapeString(in string) string {
|
|
||||||
for b, esc := range codesStr {
|
|
||||||
in = strings.Replace(in, esc, b, -1)
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
func String(in string) string {
|
|
||||||
for b, esc := range codesStr {
|
|
||||||
in = strings.Replace(in, b, esc, -1)
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +1,34 @@
|
||||||
all:
|
PACKAGES=`find ./cmd -mindepth 1 -maxdepth 1 -type d`
|
||||||
go build ./...
|
LIBRARYS=
|
||||||
|
|
||||||
|
all: clean man build
|
||||||
|
|
||||||
|
clean:
|
||||||
|
for p in $(PACKAGES); do rm -f `echo $${p}|cut -d/ -f3`{,.1,.1.gz}; done
|
||||||
|
|
||||||
|
build:
|
||||||
|
for p in $(PACKAGES); do go build -ldflags "-w -s" $${p}; done
|
||||||
|
|
||||||
|
linux:
|
||||||
|
for p in $(PACKAGES); do GOOS=linux go build -ldflags "-w -s" $${p}; done
|
||||||
|
|
||||||
|
install:
|
||||||
|
go install -ldflags "-w -s" ./...
|
||||||
|
|
||||||
|
test: lint
|
||||||
|
for p in $(PACKAGES) $(LIBRARYS); do go test -race -covermode=atomic $${p}; done
|
||||||
|
|
||||||
|
lint:
|
||||||
|
goimports -l $(PACKAGES) $(LIBRARYS)
|
||||||
|
gofmt -l $(PACKAGES) $(LIBRARYS)
|
||||||
|
errcheck $(PACKAGES) $(LIBRARYS)
|
||||||
|
golint $(PACKAGES) $(LIBRARYS)
|
||||||
|
go vet $(PACKAGES) $(LIBRARYS)
|
||||||
|
|
||||||
|
man:
|
||||||
|
script/build_manpages.sh ./
|
||||||
|
|
||||||
|
deps:
|
||||||
|
rm -rf Godeps vendor
|
||||||
|
godep save ./...
|
||||||
|
godep update ./...
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
worker: unifi
|
|
||||||
|
|
@ -1,32 +1,20 @@
|
||||||
# Unifi
|
# Unifi
|
||||||
|
|
||||||
Collect your Unifi client data every 15 seconds and send it to an InfluxDB instance.
|
Collect your Unifi Controller Client data and send it to an InfluxDB instance.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Deploying
|
## Deploying
|
||||||
|
|
||||||
The repository is ready for deployment on Heroku. Steps to deploy:
|
|
||||||
|
|
||||||
Clone the repository and using `.env.example` create your own `.env` file with your Unifi GUI and InfluxDB credentials.
|
Clone the repository and using `.env.example` create your own `.env` file with your Unifi GUI and InfluxDB credentials.
|
||||||
|
|
||||||
Create your heroku application:
|
|
||||||
|
Set your environment variables before running:
|
||||||
|
|
||||||
```
|
```
|
||||||
heroku create [name]
|
source .env ; ./unifi-poller
|
||||||
```
|
```
|
||||||
|
|
||||||
Set your environment variables before deploying:
|
## Copyright & License
|
||||||
|
|
||||||
```
|
|
||||||
heroku config:set $(cat .env | grep -v ^# | xargs)
|
|
||||||
```
|
|
||||||
|
|
||||||
Push to heroku:
|
|
||||||
|
|
||||||
```
|
|
||||||
git push heroku master
|
|
||||||
```
|
|
||||||
|
|
||||||
## Copyright
|
|
||||||
Copyright © 2016 Garrett Bjerkhoel. See [MIT-LICENSE](http://github.com/dewski/unifi/blob/master/MIT-LICENSE) for details.
|
Copyright © 2016 Garrett Bjerkhoel. See [MIT-LICENSE](http://github.com/dewski/unifi/blob/master/MIT-LICENSE) for details.
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"name": "unifi",
|
|
||||||
"description": "Data logger for Unifi controllers",
|
|
||||||
"keywords": [
|
|
||||||
"go",
|
|
||||||
],
|
|
||||||
"image": "heroku/go:1.6",
|
|
||||||
"mount_dir": "src/github.com/dewski/unifi",
|
|
||||||
"website": "https://github.com/dewski/unifi",
|
|
||||||
"repository": "https://github.com/dewski/unifi"
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
unifi-poller(1) -- Utility to poll Unifi Metrics and drop them into InfluxDB
|
||||||
|
===
|
||||||
|
|
||||||
|
## SYNOPSIS
|
||||||
|
|
||||||
|
`unifi-poller -c /usr/local/etc/unifi-poller.conf`
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
|
||||||
|
* This application polls a Unifi Controller API for Client and Device Metrics.
|
||||||
|
* The metrics are then stored in an InfluxDB instance.
|
||||||
|
|
||||||
|
## OPTIONS
|
||||||
|
|
||||||
|
`unifi-poller [-c <config file>] [-h] [-v]`
|
||||||
|
|
||||||
|
-c, --config <file_path>
|
||||||
|
Provide a configuration file (instead of the default).
|
||||||
|
|
||||||
|
-v, --version
|
||||||
|
Display version and exit.
|
||||||
|
|
||||||
|
-h, --help
|
||||||
|
Display usage and exit.
|
||||||
|
|
||||||
|
|
||||||
|
## GO DURATION
|
||||||
|
|
||||||
|
This application uses the Go Time Durations for a polling interval.
|
||||||
|
The format is an integer followed by a time unit. You may append multiple time units
|
||||||
|
to add them together. Some valid time units are:
|
||||||
|
|
||||||
|
`us` (microsecond)
|
||||||
|
`ns` (nanosecond)
|
||||||
|
`ms` (millisecond)
|
||||||
|
`s` (second)
|
||||||
|
`m` (minute)
|
||||||
|
`h` (hour)
|
||||||
|
|
||||||
|
Example Use: `1m`, `5h`, `100ms`, `17s`, `1s45ms`, `1m3s`
|
||||||
|
|
||||||
|
## AUTHOR
|
||||||
|
|
||||||
|
* Garrett Bjerkhoel (original code) ~ 2016
|
||||||
|
* David Newhall II (rewritten) ~ 4/20/2018
|
||||||
|
|
||||||
|
## LOCATION
|
||||||
|
|
||||||
|
* https://github.com/davidnewhall/unifi-poller
|
||||||
|
* /usr/local/bin/unifi-poller
|
||||||
|
* previously: https://github.com/dewski/unifi
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -46,6 +45,9 @@ type Client struct {
|
||||||
BytesR int64 `json:"bytes-r"`
|
BytesR int64 `json:"bytes-r"`
|
||||||
Ccq int64 `json:"ccq"`
|
Ccq int64 `json:"ccq"`
|
||||||
Channel int `json:"channel"`
|
Channel int `json:"channel"`
|
||||||
|
DevCat int `json:"dev_cat"`
|
||||||
|
DevFamily int `json:"dev_family"`
|
||||||
|
DevID int `json:"dev_id"`
|
||||||
DpiStats []DpiStat `json:"dpi_stats"`
|
DpiStats []DpiStat `json:"dpi_stats"`
|
||||||
DpiStatsLastUpdated int64 `json:"dpi_stats_last_updated"`
|
DpiStatsLastUpdated int64 `json:"dpi_stats_last_updated"`
|
||||||
Essid string `json:"essid"`
|
Essid string `json:"essid"`
|
||||||
|
|
@ -67,6 +69,8 @@ type Client struct {
|
||||||
Noise int64 `json:"noise"`
|
Noise int64 `json:"noise"`
|
||||||
Note string `json:"note"`
|
Note string `json:"note"`
|
||||||
Noted bool `json:"noted"`
|
Noted bool `json:"noted"`
|
||||||
|
OsClass int `json:"os_class"`
|
||||||
|
OsName int `json:"os_name"`
|
||||||
Oui string `json:"oui"`
|
Oui string `json:"oui"`
|
||||||
PowersaveEnabled bool `json:"powersave_enabled"`
|
PowersaveEnabled bool `json:"powersave_enabled"`
|
||||||
QosPolicyApplied bool `json:"qos_policy_applied"`
|
QosPolicyApplied bool `json:"qos_policy_applied"`
|
||||||
|
|
@ -94,10 +98,16 @@ type Client struct {
|
||||||
UserGroupID string `json:"usergroup_id"`
|
UserGroupID string `json:"usergroup_id"`
|
||||||
UseFixedIP bool `json:"use_fixedip"`
|
UseFixedIP bool `json:"use_fixedip"`
|
||||||
Vlan int `json:"vlan"`
|
Vlan int `json:"vlan"`
|
||||||
|
WiredRxBytes int64 `json:"wired-rx_bytes"`
|
||||||
|
WiredRxBytesR int64 `json:"wired-rx_bytes-r"`
|
||||||
|
WiredRxPackets int64 `json:"wired-rx_packets"`
|
||||||
|
WiredTxBytes int64 `json:"wired-tx_bytes"`
|
||||||
|
WiredTxBytesR int64 `json:"wired-tx_bytes-r"`
|
||||||
|
WiredTxPackets int64 `json:"wired-tx_packets"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Point generates a client's datapoint for InfluxDB.
|
// Point generates a client's datapoint for InfluxDB.
|
||||||
func (c Client) Point() *influx.Point {
|
func (c Client) Point() (*influx.Point, error) {
|
||||||
if c.Name == "" && c.Hostname != "" {
|
if c.Name == "" && c.Hostname != "" {
|
||||||
c.Name = c.Hostname
|
c.Name = c.Hostname
|
||||||
} else if c.Hostname == "" && c.Name != "" {
|
} else if c.Hostname == "" && c.Name != "" {
|
||||||
|
|
@ -111,23 +121,23 @@ func (c Client) Point() *influx.Point {
|
||||||
"mac": c.Mac,
|
"mac": c.Mac,
|
||||||
"user_id": c.UserID,
|
"user_id": c.UserID,
|
||||||
"site_id": c.SiteID,
|
"site_id": c.SiteID,
|
||||||
"ip": c.IP,
|
|
||||||
"fixed_ip": c.FixedIP,
|
|
||||||
"essid": c.Essid,
|
|
||||||
"bssid": c.Bssid,
|
|
||||||
"network": c.Network,
|
|
||||||
"network_id": c.NetworkID,
|
"network_id": c.NetworkID,
|
||||||
"usergroup_id": c.UserGroupID,
|
"usergroup_id": c.UserGroupID,
|
||||||
"ap_mac": c.ApMac,
|
"ap_mac": c.ApMac,
|
||||||
"gw_mac": c.GwMac,
|
"gw_mac": c.GwMac,
|
||||||
"sw_mac": c.SwMac,
|
"sw_mac": c.SwMac,
|
||||||
"sw_port": strconv.Itoa(c.SwPort),
|
|
||||||
"oui": c.Oui,
|
"oui": c.Oui,
|
||||||
"name": c.Name,
|
|
||||||
"hostname": c.Hostname,
|
|
||||||
"radio_name": c.RadioName,
|
"radio_name": c.RadioName,
|
||||||
"radio": c.Radio,
|
"radio": c.Radio,
|
||||||
"radio_proto": c.RadioProto,
|
"radio_proto": c.RadioProto,
|
||||||
|
"name": c.Name,
|
||||||
|
"fixed_ip": c.FixedIP,
|
||||||
|
"sw_port": strconv.Itoa(c.SwPort),
|
||||||
|
"os_class": strconv.Itoa(c.OsClass),
|
||||||
|
"os_name": strconv.Itoa(c.OsName),
|
||||||
|
"dev_cat": strconv.Itoa(c.DevCat),
|
||||||
|
"dev_id": strconv.Itoa(c.DevID),
|
||||||
|
"dev_family": strconv.Itoa(c.DevFamily),
|
||||||
"authorized": strconv.FormatBool(c.Authorized),
|
"authorized": strconv.FormatBool(c.Authorized),
|
||||||
"is_11r": strconv.FormatBool(c.Is11R),
|
"is_11r": strconv.FormatBool(c.Is11R),
|
||||||
"is_wired": strconv.FormatBool(c.IsWired),
|
"is_wired": strconv.FormatBool(c.IsWired),
|
||||||
|
|
@ -143,6 +153,10 @@ func (c Client) Point() *influx.Point {
|
||||||
"vlan": strconv.Itoa(c.Vlan),
|
"vlan": strconv.Itoa(c.Vlan),
|
||||||
}
|
}
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
|
"ip": c.IP,
|
||||||
|
"essid": c.Essid,
|
||||||
|
"bssid": c.Bssid,
|
||||||
|
"hostname": c.Hostname,
|
||||||
"dpi_stats_last_updated": c.DpiStatsLastUpdated,
|
"dpi_stats_last_updated": c.DpiStatsLastUpdated,
|
||||||
"last_seen_by_uap": c.LastSeenByUAP,
|
"last_seen_by_uap": c.LastSeenByUAP,
|
||||||
"last_seen_by_ugw": c.LastSeenByUGW,
|
"last_seen_by_ugw": c.LastSeenByUGW,
|
||||||
|
|
@ -157,6 +171,7 @@ func (c Client) Point() *influx.Point {
|
||||||
"idle_time": c.IdleTime,
|
"idle_time": c.IdleTime,
|
||||||
"last_seen": c.LastSeen,
|
"last_seen": c.LastSeen,
|
||||||
"latest_assoc_time": c.LatestAssocTime,
|
"latest_assoc_time": c.LatestAssocTime,
|
||||||
|
"network": c.Network,
|
||||||
"noise": c.Noise,
|
"noise": c.Noise,
|
||||||
"note": c.Note,
|
"note": c.Note,
|
||||||
"roam_count": c.RoamCount,
|
"roam_count": c.RoamCount,
|
||||||
|
|
@ -172,12 +187,13 @@ func (c Client) Point() *influx.Point {
|
||||||
"tx_power": c.TxPower,
|
"tx_power": c.TxPower,
|
||||||
"tx_rate": c.TxRate,
|
"tx_rate": c.TxRate,
|
||||||
"uptime": c.Uptime,
|
"uptime": c.Uptime,
|
||||||
|
"wired-rx_bytes": c.WiredRxBytes,
|
||||||
|
"wired-rx_bytes-r": c.WiredRxBytesR,
|
||||||
|
"wired-rx_packets": c.WiredRxPackets,
|
||||||
|
"wired-tx_bytes": c.WiredTxBytes,
|
||||||
|
"wired-tx_bytes-r": c.WiredTxBytesR,
|
||||||
|
"wired-tx_packets": c.WiredTxPackets,
|
||||||
}
|
}
|
||||||
|
|
||||||
pt, err := influx.NewPoint("clients", tags, fields, time.Now())
|
return influx.NewPoint("clients", tags, fields, time.Now())
|
||||||
if err != nil {
|
|
||||||
log.Println("Error creating point:", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return pt
|
|
||||||
}
|
}
|
||||||
|
|
@ -16,12 +16,20 @@ const (
|
||||||
NetworkPath = "/api/s/default/rest/networkconf"
|
NetworkPath = "/api/s/default/rest/networkconf"
|
||||||
// UserGroupPath contains usergroup configurations.
|
// UserGroupPath contains usergroup configurations.
|
||||||
UserGroupPath = "/api/s/default/rest/usergroup"
|
UserGroupPath = "/api/s/default/rest/usergroup"
|
||||||
|
// App defaults in case they're missing from the config.
|
||||||
|
defaultInterval = 30 * time.Second
|
||||||
|
defaultInfxDb = "unifi"
|
||||||
|
defaultInfxUser = "unifi"
|
||||||
|
defaultInfxPass = "unifi"
|
||||||
|
defaultInfxURL = "http://127.0.0.1:8086"
|
||||||
|
defaultUnifUser = "influx"
|
||||||
|
defaultUnifURL = "https://127.0.0.1:8443"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config represents the data needed to poll a controller and report to influxdb.
|
// Config represents the data needed to poll a controller and report to influxdb.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Interval time.Duration `json:"interval",toml:"interval",yaml:"interval"`
|
Interval time.Duration `json:"interval",toml:"interval",yaml:"interval"`
|
||||||
InfluxAddr string `json:"influx_addr",toml:"influx_addr",yaml:"influx_addr"`
|
InfluxURL string `json:"influx_url",toml:"influx_addr",yaml:"influx_addr"`
|
||||||
InfluxUser string `json:"influx_user",toml:"influx_user",yaml:"influx_user"`
|
InfluxUser string `json:"influx_user",toml:"influx_user",yaml:"influx_user"`
|
||||||
InfluxPass string `json:"influx_pass",toml:"influx_pass",yaml:"influx_pass"`
|
InfluxPass string `json:"influx_pass",toml:"influx_pass",yaml:"influx_pass"`
|
||||||
InfluxDB string `json:"influx_db",toml:"influx_db",yaml:"influx_db"`
|
InfluxDB string `json:"influx_db",toml:"influx_db",yaml:"influx_db"`
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package main
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -13,6 +12,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
influx "github.com/influxdata/influxdb/client/v2"
|
influx "github.com/influxdata/influxdb/client/v2"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -20,16 +20,17 @@ func main() {
|
||||||
if err := config.AuthController(); err != nil {
|
if err := config.AuthController(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Println("Successfully authenticated to Unifi Controller!")
|
log.Println("Authenticated to Unifi Controller", config.UnifiBase, "as user", config.UnifiUser)
|
||||||
|
|
||||||
infdb, err := influx.NewHTTPClient(influx.HTTPConfig{
|
infdb, err := influx.NewHTTPClient(influx.HTTPConfig{
|
||||||
Addr: config.InfluxAddr,
|
Addr: config.InfluxURL,
|
||||||
Username: config.InfluxUser,
|
Username: config.InfluxUser,
|
||||||
Password: config.InfluxPass,
|
Password: config.InfluxPass,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
log.Println("Logging Unifi Metrics to InfluXDB @", config.InfluxURL, "as user", config.InfluxUser)
|
||||||
log.Println("Polling Unifi Controller, interval:", config.Interval)
|
log.Println("Polling Unifi Controller, interval:", config.Interval)
|
||||||
config.PollUnifiController(infdb)
|
config.PollUnifiController(infdb)
|
||||||
}
|
}
|
||||||
|
|
@ -37,21 +38,21 @@ func main() {
|
||||||
// GetConfig parses and returns our configuration data.
|
// GetConfig parses and returns our configuration data.
|
||||||
func GetConfig() Config {
|
func GetConfig() Config {
|
||||||
// TODO: A real config file.
|
// TODO: A real config file.
|
||||||
var err error
|
interval, err := time.ParseDuration(os.Getenv("INTERVAL"))
|
||||||
config := Config{
|
if err != nil {
|
||||||
InfluxAddr: os.Getenv("INFLUXDB_ADDR"),
|
log.Println("Invalid Interval, defaulting to", defaultInterval)
|
||||||
|
interval = time.Duration(defaultInterval)
|
||||||
|
}
|
||||||
|
return Config{
|
||||||
|
InfluxURL: os.Getenv("INFLUXDB_URL"),
|
||||||
InfluxUser: os.Getenv("INFLUXDB_USERNAME"),
|
InfluxUser: os.Getenv("INFLUXDB_USERNAME"),
|
||||||
InfluxPass: os.Getenv("INFLUXDB_PASSWORD"),
|
InfluxPass: os.Getenv("INFLUXDB_PASSWORD"),
|
||||||
InfluxDB: os.Getenv("INFLUXDB_DATABASE"),
|
InfluxDB: os.Getenv("INFLUXDB_DATABASE"),
|
||||||
UnifiUser: os.Getenv("UNIFI_USERNAME"),
|
UnifiUser: os.Getenv("UNIFI_USERNAME"),
|
||||||
UnifiPass: os.Getenv("UNIFI_PASSWORD"),
|
UnifiPass: os.Getenv("UNIFI_PASSWORD"),
|
||||||
UnifiBase: "https://" + os.Getenv("UNIFI_ADDR") + ":" + os.Getenv("UNIFI_PORT"),
|
UnifiBase: "https://" + os.Getenv("UNIFI_ADDR") + ":" + os.Getenv("UNIFI_PORT"),
|
||||||
|
Interval: interval,
|
||||||
}
|
}
|
||||||
if config.Interval, err = time.ParseDuration(os.Getenv("INTERVAL")); err != nil {
|
|
||||||
log.Println("Invalid Interval, defaulting to 15 seconds.")
|
|
||||||
config.Interval = time.Duration(time.Second * 15)
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthController creates a http.Client with authenticated cookies.
|
// AuthController creates a http.Client with authenticated cookies.
|
||||||
|
|
@ -60,18 +61,19 @@ func (c *Config) AuthController() error {
|
||||||
json := `{"username": "` + c.UnifiUser + `","password": "` + c.UnifiPass + `"}`
|
json := `{"username": "` + c.UnifiUser + `","password": "` + c.UnifiPass + `"}`
|
||||||
jar, err := cookiejar.New(nil)
|
jar, err := cookiejar.New(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "cookiejar.New(nil)")
|
||||||
}
|
}
|
||||||
c.uniClient = &http.Client{
|
c.uniClient = &http.Client{
|
||||||
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
|
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
|
||||||
Jar: jar,
|
Jar: jar,
|
||||||
}
|
}
|
||||||
if req, err := c.uniRequest(LoginPath, json); err != nil {
|
if req, err := c.uniRequest(LoginPath, json); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "c.uniRequest(LoginPath, json)")
|
||||||
} else if resp, err := c.uniClient.Do(req); err != nil {
|
} else if resp, err := c.uniClient.Do(req); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "c.uniClient.Do(req)")
|
||||||
} else if resp.StatusCode != http.StatusOK {
|
} else if resp.StatusCode != http.StatusOK {
|
||||||
return errors.New("Error Authenticating with Unifi Controller")
|
return errors.Errorf("authentication failed (%v): %v (status: %v/%v)",
|
||||||
|
c.UnifiUser, c.UnifiBase+LoginPath, resp.StatusCode, resp.Status)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +96,11 @@ func (c *Config) PollUnifiController(infdb influx.Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
bp.AddPoint(client.Point())
|
if pt, errr := client.Point(); errr != nil {
|
||||||
|
log.Println("client.Point():", errr)
|
||||||
|
} else {
|
||||||
|
bp.AddPoint(pt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err = infdb.Write(bp); err != nil {
|
if err = infdb.Write(bp); err != nil {
|
||||||
log.Println("infdb.Write(bp):", err)
|
log.Println("infdb.Write(bp):", err)
|
||||||
|
Before Width: | Height: | Size: 909 KiB After Width: | Height: | Size: 909 KiB |
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
OUTPUT=$1
|
||||||
|
|
||||||
|
# This requires the installation of `ronn`: sudo gem install ronn
|
||||||
|
for f in cmd/*/README.md;do
|
||||||
|
# Strtip off cmd/ then strip off README to get the man-file name.
|
||||||
|
PKGNOCMD="${f#cmd/}"
|
||||||
|
PKG="${PKGNOCMD%/README.md}"
|
||||||
|
echo "Creating Man Page: ${f} -> ${OUTPUT}${PKG}.1.gz"
|
||||||
|
ronn < "$f" | gzip -9 > "${OUTPUT}${PKG}.1.gz"
|
||||||
|
done
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Load the environment variables needed for testing
|
|
||||||
export $(cat .env | grep -v ^# | xargs)
|
|
||||||
|
|
||||||
go clean
|
|
||||||
go build -o unifi
|
|
||||||
./unifi
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue