This commit is contained in:
davidnewhall2 2019-12-15 03:27:34 -08:00
parent 1ace3e9619
commit 4b5072fd6d
6 changed files with 56 additions and 51 deletions

View File

@ -10,11 +10,12 @@ import (
"github.com/davidnewhall/unifi-poller/pkg/poller"
influx "github.com/influxdata/influxdb1-client/v2"
conf "golift.io/config"
"golift.io/config"
)
const (
defaultInterval = 30 * time.Second
minimumInterval = 10 * time.Second
defaultInfluxDB = "unifi"
defaultInfluxUser = "unifi"
defaultInfluxURL = "http://127.0.0.1:8086"
@ -22,13 +23,13 @@ const (
// Config defines the data needed to store metrics in InfluxDB
type Config struct {
Interval conf.Duration `json:"interval,omitempty" toml:"interval,omitempty" xml:"interval" yaml:"interval"`
Disable bool `json:"disable" toml:"disable" xml:"disable" yaml:"disable"`
VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"`
URL string `json:"url,omitempty" toml:"url,omitempty" xml:"url" yaml:"url"`
User string `json:"user,omitempty" toml:"user,omitempty" xml:"user" yaml:"user"`
Pass string `json:"pass,omitempty" toml:"pass,omitempty" xml:"pass" yaml:"pass"`
DB string `json:"db,omitempty" toml:"db,omitempty" xml:"db" yaml:"db"`
Interval config.Duration `json:"interval,omitempty" toml:"interval,omitempty" xml:"interval" yaml:"interval"`
Disable bool `json:"disable" toml:"disable" xml:"disable" yaml:"disable"`
VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"`
URL string `json:"url,omitempty" toml:"url,omitempty" xml:"url" yaml:"url"`
User string `json:"user,omitempty" toml:"user,omitempty" xml:"user" yaml:"user"`
Pass string `json:"pass,omitempty" toml:"pass,omitempty" xml:"pass" yaml:"pass"`
DB string `json:"db,omitempty" toml:"db,omitempty" xml:"db" yaml:"db"`
}
// InfluxDB allows the data to be nested in the config file.
@ -63,9 +64,9 @@ func init() {
// This is started by Run() or RunBoth() after everything checks out.
func (u *InfluxUnifi) PollController() {
interval := u.Config.Interval.Round(time.Second)
ticker := time.NewTicker(interval)
log.Printf("[INFO] Everything checks out! Poller started, InfluxDB interval: %v", interval)
ticker := time.NewTicker(interval)
for u.LastCheck = range ticker.C {
metrics, err := u.Collector.Metrics()
if err != nil {
@ -128,12 +129,12 @@ func (u *InfluxUnifi) setConfigDefaults() {
}
if u.Config.Interval.Duration == 0 {
u.Config.Interval = conf.Duration{Duration: defaultInterval}
} else if u.Config.Interval.Duration < defaultInterval/2 {
u.Config.Interval = conf.Duration{Duration: defaultInterval / 2}
u.Config.Interval = config.Duration{Duration: defaultInterval}
} else if u.Config.Interval.Duration < minimumInterval {
u.Config.Interval = config.Duration{Duration: minimumInterval}
}
u.Config.Interval = conf.Duration{Duration: u.Config.Interval.Duration.Round(time.Second)}
u.Config.Interval = config.Duration{Duration: u.Config.Interval.Duration.Round(time.Second)}
}
// ReportMetrics batches all device and client data into influxdb data points.
@ -179,6 +180,7 @@ func (u *InfluxUnifi) collect(r report, ch chan *metric) {
// to the collect routine through the metric channel.
func (u *InfluxUnifi) loopPoints(r report) {
m := r.metrics()
r.add()
go func() {
defer r.done()
@ -186,6 +188,7 @@ func (u *InfluxUnifi) loopPoints(r report) {
u.batchSite(r, s)
}
}()
r.add()
go func() {
defer r.done()
@ -193,6 +196,7 @@ func (u *InfluxUnifi) loopPoints(r report) {
u.batchClient(r, s)
}
}()
r.add()
go func() {
defer r.done()
@ -200,6 +204,7 @@ func (u *InfluxUnifi) loopPoints(r report) {
u.batchIDS(r, s)
}
}()
if m.Devices == nil {
return
}
@ -211,6 +216,7 @@ func (u *InfluxUnifi) loopPoints(r report) {
u.batchUAP(r, s)
}
}()
r.add()
go func() {
defer r.done()
@ -218,6 +224,7 @@ func (u *InfluxUnifi) loopPoints(r report) {
u.batchUSG(r, s)
}
}()
r.add()
go func() {
defer r.done()
@ -225,6 +232,7 @@ func (u *InfluxUnifi) loopPoints(r report) {
u.batchUSW(r, s)
}
}()
r.add()
go func() {
defer r.done()

View File

@ -31,13 +31,13 @@ const ENVConfigPrefix = "UP"
// UnifiPoller contains the application startup data, and auth info for UniFi & Influx.
type UnifiPoller struct {
Flag *Flag
Flags *Flags
Config *Config
sync.Mutex // locks the Unifi struct member when re-authing to unifi.
}
// Flag represents the CLI args available and their settings.
type Flag struct {
// Flags represents the CLI args available and their settings.
type Flags struct {
ConfigFile string
DumpJSON string
ShowVer bool
@ -84,8 +84,8 @@ type Poller struct {
// ParseConfigs parses the poller config and the config for each registered output plugin.
func (u *UnifiPoller) ParseConfigs() error {
// Parse config file.
if err := config.ParseFile(u.Config, u.Flag.ConfigFile); err != nil {
u.Flag.Usage()
if err := config.ParseFile(u.Config, u.Flags.ConfigFile); err != nil {
u.Flags.Usage()
return err
}
@ -99,7 +99,7 @@ func (u *UnifiPoller) ParseConfigs() error {
for _, o := range outputs {
// Parse config file for each output plugin.
if err := config.ParseFile(o.Config, u.Flag.ConfigFile); err != nil {
if err := config.ParseFile(o.Config, u.Flags.ConfigFile); err != nil {
return err
}

View File

@ -37,12 +37,12 @@ func (u *UnifiPoller) DumpJSONPayload() (err error) {
switch sites, err := u.GetFilteredSites(config); {
case err != nil:
return err
case StringInSlice(u.Flag.DumpJSON, []string{"d", "device", "devices"}):
case StringInSlice(u.Flags.DumpJSON, []string{"d", "device", "devices"}):
return u.dumpSitesJSON(config, unifi.APIDevicePath, "Devices", sites)
case StringInSlice(u.Flag.DumpJSON, []string{"client", "clients", "c"}):
case StringInSlice(u.Flags.DumpJSON, []string{"client", "clients", "c"}):
return u.dumpSitesJSON(config, unifi.APIClientPath, "Clients", sites)
case strings.HasPrefix(u.Flag.DumpJSON, "other "):
apiPath := strings.SplitN(u.Flag.DumpJSON, " ", 2)[1]
case strings.HasPrefix(u.Flags.DumpJSON, "other "):
apiPath := strings.SplitN(u.Flags.DumpJSON, " ", 2)[1]
_, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping Path '%s':\n", apiPath)
return u.PrintRawAPIJSON(config, apiPath)
default:

View File

@ -55,7 +55,7 @@ func (u *UnifiPoller) InitializeOutputs() error {
}
if count < 1 {
return fmt.Errorf("no output plugins configured")
return fmt.Errorf("no output plugins imported")
}
for err := range v {
@ -64,7 +64,7 @@ func (u *UnifiPoller) InitializeOutputs() error {
}
if count--; count < 1 {
return fmt.Errorf("all output plugins have stopped")
return fmt.Errorf("all output plugins have stopped, or none enabled")
}
}

View File

@ -10,15 +10,9 @@ import (
"github.com/spf13/pflag"
)
// New returns a new poller struct preloaded with default values.
// No need to call this if you call Start.c
// New returns a new poller struct.
func New() *UnifiPoller {
return &UnifiPoller{
Config: &Config{},
Flag: &Flag{
ConfigFile: DefaultConfFile,
},
}
return &UnifiPoller{Config: &Config{}, Flags: &Flags{}}
}
// Start begins the application from a CLI.
@ -27,15 +21,15 @@ func New() *UnifiPoller {
func (u *UnifiPoller) Start() error {
log.SetOutput(os.Stdout)
log.SetFlags(log.LstdFlags)
u.Flag.Parse(os.Args[1:])
u.Flags.Parse(os.Args[1:])
if u.Flag.ShowVer {
if u.Flags.ShowVer {
fmt.Printf("%s v%s\n", AppName, version.Version)
return nil // don't run anything else w/ version request.
}
if u.Flag.DumpJSON == "" { // do not print this when dumping JSON.
u.Logf("Loading Configuration File: %s", u.Flag.ConfigFile)
if u.Flags.DumpJSON == "" { // do not print this when dumping JSON.
u.Logf("Loading Configuration File: %s", u.Flags.ConfigFile)
}
// Parse config file and ENV variables.
@ -53,7 +47,7 @@ func (u *UnifiPoller) Start() error {
}}
}
if u.Flag.DumpJSON != "" {
if u.Flags.DumpJSON != "" {
return u.DumpJSONPayload()
}
@ -66,7 +60,7 @@ func (u *UnifiPoller) Start() error {
}
// Parse turns CLI arguments into data structures. Called by Start() on startup.
func (f *Flag) Parse(args []string) {
func (f *Flags) Parse(args []string) {
f.FlagSet = pflag.NewFlagSet(AppName, pflag.ExitOnError)
f.Usage = func() {
fmt.Printf("Usage: %s [--config=/path/to/up.conf] [--version]", AppName)

View File

@ -65,6 +65,7 @@ type metric struct {
// Report accumulates counters that are printed to a log line.
type Report struct {
Config
Total int // Total count of metrics recorded.
Errors int // Total count of errors recording metrics.
Zeros int // Total count of metrics equal to zero.
@ -74,7 +75,6 @@ type Report struct {
Start time.Time // Time collection began.
ch chan []*metric
wg sync.WaitGroup
Config
}
func init() {
@ -97,27 +97,28 @@ func (u *promUnifi) Run(c poller.Collect) error {
u.Config.Namespace = strings.Replace(poller.AppName, "-", "", -1)
}
u.Config.Namespace = strings.Replace(u.Config.Namespace, "-", "_", -1)
if u.Config.HTTPListen == "" {
u.Config.HTTPListen = defaultHTTPListen
}
name := strings.Replace(u.Config.Namespace, "-", "_", -1)
prometheus.MustRegister(version.NewCollector(u.Config.Namespace))
ns := name
if ns = strings.Trim(ns, "_") + "_"; ns == "_" {
ns = ""
if u.Config.Namespace = strings.Trim(u.Config.Namespace, "_") + "_"; u.Config.Namespace == "_" {
u.Config.Namespace = ""
}
prometheus.MustRegister(version.NewCollector(name))
prometheus.MustRegister(&promUnifi{
Collector: c,
Client: descClient(ns + "client_"),
Device: descDevice(ns + "device_"), // stats for all device types.
UAP: descUAP(ns + "device_"),
USG: descUSG(ns + "device_"),
USW: descUSW(ns + "device_"),
Site: descSite(ns + "site_"),
Client: descClient(u.Config.Namespace + "client_"),
Device: descDevice(u.Config.Namespace + "device_"), // stats for all device types.
UAP: descUAP(u.Config.Namespace + "device_"),
USG: descUSG(u.Config.Namespace + "device_"),
USW: descUSW(u.Config.Namespace + "device_"),
Site: descSite(u.Config.Namespace + "site_"),
})
c.Logf("Exporting Measurements for Prometheus at https://%s/metrics, namespace: %s", u.Config.HTTPListen, u.Config.Namespace)
return http.ListenAndServe(u.Config.HTTPListen, nil)
@ -185,12 +186,14 @@ func (u *promUnifi) exportMetrics(r report, ch chan<- prometheus.Metric, ourChan
r.error(ch, m.Desc, fmt.Sprintf("not a number: %v", m.Value))
}
}
r.done()
}
}
func (u *promUnifi) loopExports(r report) {
m := r.metrics()
r.add()
go func() {
defer r.done()