Add site metrics to influxdb.
This commit is contained in:
		
							parent
							
								
									faf64d940e
								
							
						
					
					
						commit
						dca41ce6a3
					
				|  | @ -2,12 +2,12 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| [[projects]] | [[projects]] | ||||||
|   digest = "1:fda9365965d38b80007d47efbf516adbc65d266515f263e9c43336ab47ef0f69" |   digest = "1:f42822e830b569d8527ad6e57585e6ccc094296bc9d648ccd256f95249453ce1" | ||||||
|   name = "github.com/golift/unifi" |   name = "github.com/golift/unifi" | ||||||
|   packages = ["."] |   packages = ["."] | ||||||
|   pruneopts = "UT" |   pruneopts = "UT" | ||||||
|   revision = "1d74eaae61aad0558126b6ade64753ebcc5982ef" |   revision = "fc5a69d27d7527038ae55ee5112bc755db380879" | ||||||
|   version = "v2.0.4" |   version = "v2.1.0" | ||||||
| 
 | 
 | ||||||
| [[projects]] | [[projects]] | ||||||
|   branch = "master" |   branch = "master" | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| package unifipoller | package unifipoller | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/golift/unifi" | 	"github.com/golift/unifi" | ||||||
|  | @ -39,6 +40,14 @@ type UnifiPoller struct { | ||||||
| 	*Config | 	*Config | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Metrics contains all the data from the controller.
 | ||||||
|  | type Metrics struct { | ||||||
|  | 	unifi.Sites | ||||||
|  | 	unifi.Clients | ||||||
|  | 	*unifi.Devices | ||||||
|  | 	influx.BatchPoints | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // 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   Dur      `json:"interval,_omitempty" toml:"interval,_omitempty" xml:"interval" yaml:"interval"` | 	Interval   Dur      `json:"interval,_omitempty" toml:"interval,_omitempty" xml:"interval" yaml:"interval"` | ||||||
|  | @ -60,7 +69,7 @@ type Dur struct{ value time.Duration } | ||||||
| 
 | 
 | ||||||
| // UnmarshalTOML parses a duration type from a config file.
 | // UnmarshalTOML parses a duration type from a config file.
 | ||||||
| func (v *Dur) UnmarshalTOML(data []byte) error { | func (v *Dur) UnmarshalTOML(data []byte) error { | ||||||
| 	unquoted := string(data[1 : len(data)-1]) | 	unquoted := strings.Trim(string(data), `"`) | ||||||
| 	dur, err := time.ParseDuration(unquoted) | 	dur, err := time.ParseDuration(unquoted) | ||||||
| 	if err == nil { | 	if err == nil { | ||||||
| 		v.value = dur | 		v.value = dur | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/golift/unifi" | 	"github.com/golift/unifi" | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
|  | @ -24,13 +25,15 @@ func (u *UnifiPoller) DumpJSONPayload() (err error) { | ||||||
| 		fmt.Fprintf(os.Stderr, "[ERROR] "+m, v...) | 		fmt.Fprintf(os.Stderr, "[ERROR] "+m, v...) | ||||||
| 	} // Log all errors to stderr.
 | 	} // Log all errors to stderr.
 | ||||||
| 
 | 
 | ||||||
| 	switch sites, err := u.filterSites(u.Sites); { | 	switch sites, err := u.GetFilteredSites(); { | ||||||
| 	case err != nil: | 	case err != nil: | ||||||
| 		return err | 		return err | ||||||
| 	case StringInSlice(u.DumpJSON, []string{"d", "device", "devices"}): | 	case StringInSlice(u.DumpJSON, []string{"d", "device", "devices"}): | ||||||
| 		return u.DumpDeviceJSON(sites) | 		return u.DumpDeviceJSON(sites) | ||||||
| 	case StringInSlice(u.DumpJSON, []string{"client", "clients", "c"}): | 	case StringInSlice(u.DumpJSON, []string{"client", "clients", "c"}): | ||||||
| 		return u.DumpClientsJSON(sites) | 		return u.DumpClientsJSON(sites) | ||||||
|  | 	case strings.HasPrefix(u.DumpJSON, "other "): | ||||||
|  | 		return u.DumpOtherJSON(sites) | ||||||
| 	default: | 	default: | ||||||
| 		return errors.New("must provide filter: devices, clients") | 		return errors.New("must provide filter: devices, clients") | ||||||
| 	} | 	} | ||||||
|  | @ -58,6 +61,20 @@ func (u *UnifiPoller) DumpDeviceJSON(sites []unifi.Site) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // DumpOtherJSON prints the raw json for a user-provided path in a Unifi Controller.
 | ||||||
|  | func (u *UnifiPoller) DumpOtherJSON(sites []unifi.Site) error { | ||||||
|  | 	for _, s := range sites { | ||||||
|  | 		path := strings.SplitN(u.DumpJSON, " ", 2)[1] | ||||||
|  | 		if strings.Contains(path, "%s") { | ||||||
|  | 			path = fmt.Sprintf(path, s.Name) | ||||||
|  | 		} | ||||||
|  | 		if err := u.dumpJSON(path, "Other", s); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (u *UnifiPoller) dumpJSON(path, what string, site unifi.Site) error { | func (u *UnifiPoller) dumpJSON(path, what string, site unifi.Site) error { | ||||||
| 	req, err := u.UniReq(path, "") | 	req, err := u.UniReq(path, "") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -40,95 +40,100 @@ FIRST: | ||||||
| func (u *UnifiPoller) PollController() { | func (u *UnifiPoller) PollController() { | ||||||
| 	log.Println("[INFO] Everything checks out! Poller started, interval:", u.Interval.value) | 	log.Println("[INFO] Everything checks out! Poller started, interval:", u.Interval.value) | ||||||
| 	ticker := time.NewTicker(u.Interval.value) | 	ticker := time.NewTicker(u.Interval.value) | ||||||
| 
 | 	var err error | ||||||
| 	for range ticker.C { | 	for range ticker.C { | ||||||
|  | 		m := &Metrics{} | ||||||
| 		// Get the sites we care about.
 | 		// Get the sites we care about.
 | ||||||
| 		sites, err := u.filterSites(u.Sites) | 		if m.Sites, err = u.GetFilteredSites(); err != nil { | ||||||
| 		if err != nil { |  | ||||||
| 			logErrors([]error{err}, "uni.GetSites()") | 			logErrors([]error{err}, "uni.GetSites()") | ||||||
| 		} | 		} | ||||||
| 		// Get all the points.
 | 		// Get all the points.
 | ||||||
| 		clients, err := u.GetClients(sites) | 		if m.Clients, err = u.GetClients(m.Sites); err != nil { | ||||||
| 		if err != nil { |  | ||||||
| 			logErrors([]error{err}, "uni.GetClients()") | 			logErrors([]error{err}, "uni.GetClients()") | ||||||
| 		} | 		} | ||||||
| 		devices, err := u.GetDevices(sites) | 		if m.Devices, err = u.GetDevices(m.Sites); err != nil { | ||||||
| 		if err != nil { |  | ||||||
| 			logErrors([]error{err}, "uni.GetDevices()") | 			logErrors([]error{err}, "uni.GetDevices()") | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
| 		// Make a new Points Batcher.
 | 		// Make a new Points Batcher.
 | ||||||
| 		bp, err := influx.NewBatchPoints(influx.BatchPointsConfig{Database: u.InfluxDB}) | 		m.BatchPoints, err = influx.NewBatchPoints(influx.BatchPointsConfig{Database: u.InfluxDB}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logErrors([]error{err}, "influx.NewBatchPoints") | 			logErrors([]error{err}, "influx.NewBatchPoints") | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		// Batch (and send) all the points.
 | 		// Batch (and send) all the points.
 | ||||||
| 		if errs := batchPoints(devices, clients, bp); errs != nil && hasErr(errs) { | 		if errs := m.SendPoints(); errs != nil && hasErr(errs) { | ||||||
| 			logErrors(errs, "asset.Points()") | 			logErrors(errs, "asset.Points()") | ||||||
| 		} | 		} | ||||||
| 		if err := u.Write(bp); err != nil { | 		if err := u.Write(m.BatchPoints); err != nil { | ||||||
| 			logErrors([]error{err}, "infdb.Write(bp)") | 			logErrors([]error{err}, "infdb.Write(bp)") | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Talk about the data.
 | 		// Talk about the data.
 | ||||||
| 		var fieldcount, pointcount int | 		var fieldcount, pointcount int | ||||||
| 		for _, p := range bp.Points() { | 		for _, p := range m.Points() { | ||||||
| 			pointcount++ | 			pointcount++ | ||||||
| 			i, _ := p.Fields() | 			i, _ := p.Fields() | ||||||
| 			fieldcount += len(i) | 			fieldcount += len(i) | ||||||
| 		} | 		} | ||||||
| 		u.Logf("Unifi Measurements Recorded. Sites: %d Clients: %d, "+ | 		u.Logf("Unifi Measurements Recorded. Sites: %d, Clients: %d, "+ | ||||||
| 			"Wireless APs: %d, Gateways: %d, Switches: %d, Points: %d, Fields: %d", | 			"Wireless APs: %d, Gateways: %d, Switches: %d, Points: %d, Fields: %d", | ||||||
| 			len(sites), len(clients.UCLs), | 			len(m.Sites), len(m.Clients), len(m.UAPs), len(m.USGs), len(m.USWs), pointcount, fieldcount) | ||||||
| 			len(devices.UAPs), len(devices.USGs), len(devices.USWs), pointcount, fieldcount) |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // batchPoints combines all device and client data into influxdb data points.
 | // SendPoints combines all device and client data into influxdb data points.
 | ||||||
| func batchPoints(devices *unifi.Devices, clients *unifi.Clients, bp influx.BatchPoints) (errs []error) { | // Call this after you've collected all the data you care about.
 | ||||||
| 	process := func(asset Asset) error { | // This sends all the batched points to InfluxDB.
 | ||||||
| 		if asset == nil { | func (m *Metrics) SendPoints() (errs []error) { | ||||||
| 			return nil | 	for _, asset := range m.Sites { | ||||||
| 		} | 		errs = append(errs, m.processPoints(asset)) | ||||||
| 		influxPoints, err := asset.Points() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		bp.AddPoints(influxPoints) |  | ||||||
| 		return nil |  | ||||||
| 	} | 	} | ||||||
| 	if devices != nil { | 	for _, asset := range m.Clients { | ||||||
| 		for _, asset := range devices.UAPs { | 		errs = append(errs, m.processPoints(asset)) | ||||||
| 			errs = append(errs, process(asset)) |  | ||||||
| 		} |  | ||||||
| 		for _, asset := range devices.USGs { |  | ||||||
| 			errs = append(errs, process(asset)) |  | ||||||
| 		} |  | ||||||
| 		for _, asset := range devices.USWs { |  | ||||||
| 			errs = append(errs, process(asset)) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	if clients != nil { | 	if m.Devices == nil { | ||||||
| 		for _, asset := range clients.UCLs { | 		return | ||||||
| 			errs = append(errs, process(asset)) | 	} | ||||||
| 		} | 	for _, asset := range m.UAPs { | ||||||
|  | 		errs = append(errs, m.processPoints(asset)) | ||||||
|  | 	} | ||||||
|  | 	for _, asset := range m.USGs { | ||||||
|  | 		errs = append(errs, m.processPoints(asset)) | ||||||
|  | 	} | ||||||
|  | 	for _, asset := range m.USWs { | ||||||
|  | 		errs = append(errs, m.processPoints(asset)) | ||||||
| 	} | 	} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // filterSites returns a list of sites to fetch data for.
 | // processPoints is helper function for SendPoints.
 | ||||||
| // Omits requested but unconfigured sites.
 | func (m *Metrics) processPoints(asset Asset) error { | ||||||
| func (u *UnifiPoller) filterSites(filter []string) ([]unifi.Site, error) { | 	if asset == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	influxPoints, err := asset.Points() | ||||||
|  | 	if err != nil || influxPoints == nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	m.BatchPoints.AddPoints(influxPoints) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetFilteredSites returns a list of sites to fetch data for.
 | ||||||
|  | // Omits requested but unconfigured sites. Grabs the full list from the
 | ||||||
|  | // controller and filters the sites provided in the config file.
 | ||||||
|  | func (u *UnifiPoller) GetFilteredSites() (unifi.Sites, error) { | ||||||
| 	sites, err := u.GetSites() | 	sites, err := u.GetSites() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} else if len(filter) < 1 || StringInSlice("all", filter) { | 	} else if len(u.Sites) < 1 || StringInSlice("all", u.Sites) { | ||||||
| 		return sites, nil | 		return sites, nil | ||||||
| 	} | 	} | ||||||
| 	var i int | 	var i int | ||||||
| 	for _, s := range sites { | 	for _, s := range sites { | ||||||
| 		// Only include valid sites in the request filter.
 | 		// Only include valid sites in the request filter.
 | ||||||
| 		if StringInSlice(s.Name, filter) { | 		if StringInSlice(s.Name, u.Sites) { | ||||||
| 			sites[i] = s | 			sites[i] = s | ||||||
| 			i++ | 			i++ | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -101,7 +101,11 @@ func (u *UnifiPoller) GetUnifi() (err error) { | ||||||
| 	if u.Debug && !u.Quiet { | 	if u.Debug && !u.Quiet { | ||||||
| 		u.Unifi.DebugLog = log.Printf // Log debug messages.
 | 		u.Unifi.DebugLog = log.Printf // Log debug messages.
 | ||||||
| 	} | 	} | ||||||
| 	u.Logf("Authenticated to Unifi Controller at %s as user %s", u.UnifiBase, u.UnifiUser) | 	v, err := u.GetServer() | ||||||
|  | 	if err != nil { | ||||||
|  | 		v.ServerVersion = "unknown" | ||||||
|  | 	} | ||||||
|  | 	u.Logf("Authenticated to Unifi Controller at %s version %s as user %s", u.UnifiBase, v.ServerVersion, u.UnifiUser) | ||||||
| 	if err = u.CheckSites(); err != nil { | 	if err = u.CheckSites(); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue