Merge pull request #88 from siavashs/freebsd

Add support for FreeBSD
This commit is contained in:
Björn Rabenstein 2015-07-14 15:38:39 +02:00
commit 5e86b55c46
25 changed files with 704 additions and 19 deletions

72
collector/cpu_freebsd.go Normal file
View file

@ -0,0 +1,72 @@
// +build !nocpu
package collector
import (
"errors"
"os"
"strconv"
"unsafe"
"github.com/prometheus/client_golang/prometheus"
)
/*
#cgo LDFLAGS: -lkvm
#include <fcntl.h>
#include <kvm.h>
#include <sys/param.h>
#include <sys/pcpu.h>
#include <sys/resource.h>
*/
import "C"
type statCollector struct {
cpu *prometheus.CounterVec
}
func init() {
Factories["cpu"] = NewStatCollector
}
// Takes a prometheus registry and returns a new Collector exposing
// CPU stats.
func NewStatCollector() (Collector, error) {
return &statCollector{
cpu: prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: Namespace,
Name: "cpu_seconds_total",
Help: "Seconds the CPU spent in each mode.",
},
[]string{"cpu", "mode"},
),
}, nil
}
// Expose CPU stats using KVM.
func (c *statCollector) Update(ch chan<- prometheus.Metric) (err error) {
if os.Geteuid() != 0 && os.Getegid() != 2 {
return errors.New("caller should be either root user or kmem group to access /dev/mem")
}
var errbuf *C.char
kd := C.kvm_open(nil, nil, nil, C.O_RDONLY, errbuf)
if errbuf != nil {
return errors.New("failed to call kvm_open()")
}
defer C.kvm_close(kd)
ncpus := C.kvm_getncpus(kd)
for i := 0; i < int(ncpus); i++ {
pcpu := C.kvm_getpcpu(kd, C.int(i))
cp_time := ((*C.struct_pcpu)(unsafe.Pointer(pcpu))).pc_cp_time
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "user"}).Set(float64(cp_time[C.CP_USER]))
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "nice"}).Set(float64(cp_time[C.CP_NICE]))
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "system"}).Set(float64(cp_time[C.CP_SYS]))
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "interrupt"}).Set(float64(cp_time[C.CP_INTR]))
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "idle"}).Set(float64(cp_time[C.CP_IDLE]))
}
c.cpu.Collect(ch)
return err
}

View file

@ -0,0 +1,226 @@
// +build !nodevstat
package collector
import (
"errors"
"fmt"
"github.com/prometheus/client_golang/prometheus"
)
/*
#cgo LDFLAGS: -ldevstat -lkvm
#include <devstat.h>
#include <fcntl.h>
#include <libgeom.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
uint64_t read;
uint64_t write;
uint64_t free;
} Bytes;
typedef struct {
uint64_t other;
uint64_t read;
uint64_t write;
uint64_t free;
} Transfers;
typedef struct {
double other;
double read;
double write;
double free;
} Duration;
typedef struct {
char device[DEVSTAT_NAME_LEN];
int unit;
Bytes bytes;
Transfers transfers;
Duration duration;
long busyTime;
uint64_t blocks;
} Stats;
int _get_ndevs() {
struct statinfo current;
int num_devices;
current.dinfo = (struct devinfo *)calloc(1, sizeof(struct devinfo));
if (current.dinfo == NULL)
return -2;
devstat_checkversion(NULL);
if (devstat_getdevs(NULL, &current) == -1)
return -1;
return current.dinfo->numdevs;
}
Stats _get_stats(int i) {
struct statinfo current;
int num_devices;
current.dinfo = (struct devinfo *)calloc(1, sizeof(struct devinfo));
devstat_getdevs(NULL, &current);
num_devices = current.dinfo->numdevs;
Stats stats;
uint64_t bytes_read, bytes_write, bytes_free;
uint64_t transfers_other, transfers_read, transfers_write, transfers_free;
long double duration_other, duration_read, duration_write, duration_free;
long double busy_time;
uint64_t blocks;
strcpy(stats.device, current.dinfo->devices[i].device_name);
stats.unit = current.dinfo->devices[i].unit_number;
devstat_compute_statistics(&current.dinfo->devices[i],
NULL,
1.0,
DSM_TOTAL_BYTES_READ, &bytes_read,
DSM_TOTAL_BYTES_WRITE, &bytes_write,
DSM_TOTAL_BYTES_FREE, &bytes_free,
DSM_TOTAL_TRANSFERS_OTHER, &transfers_other,
DSM_TOTAL_TRANSFERS_READ, &transfers_read,
DSM_TOTAL_TRANSFERS_WRITE, &transfers_write,
DSM_TOTAL_TRANSFERS_FREE, &transfers_free,
DSM_TOTAL_DURATION_OTHER, &duration_other,
DSM_TOTAL_DURATION_READ, &duration_read,
DSM_TOTAL_DURATION_WRITE, &duration_write,
DSM_TOTAL_DURATION_FREE, &duration_free,
DSM_TOTAL_BUSY_TIME, &busy_time,
DSM_TOTAL_BLOCKS, &blocks,
DSM_NONE);
stats.bytes.read = bytes_read;
stats.bytes.write = bytes_write;
stats.bytes.free = bytes_free;
stats.transfers.other = transfers_other;
stats.transfers.read = transfers_read;
stats.transfers.write = transfers_write;
stats.transfers.free = transfers_free;
stats.duration.other = duration_other;
stats.duration.read = duration_read;
stats.duration.write = duration_write;
stats.duration.free = duration_free;
stats.busyTime = busy_time;
stats.blocks = blocks;
return stats;
}
*/
import "C"
const (
devstatSubsystem = "devstat"
)
type devstatCollector struct {
bytes *prometheus.CounterVec
bytes_total *prometheus.CounterVec
transfers *prometheus.CounterVec
duration *prometheus.CounterVec
busyTime *prometheus.CounterVec
blocks *prometheus.CounterVec
}
func init() {
Factories["devstat"] = NewDevstatCollector
}
// Takes a prometheus registry and returns a new Collector exposing
// Device stats.
func NewDevstatCollector() (Collector, error) {
return &devstatCollector{
bytes: prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: Namespace,
Subsystem: devstatSubsystem,
Name: "bytes_total",
Help: "The total number of bytes in transactions.",
},
[]string{"device", "type"},
),
transfers: prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: Namespace,
Subsystem: devstatSubsystem,
Name: "transfers_total",
Help: "The total number of transactions.",
},
[]string{"device", "type"},
),
duration: prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: Namespace,
Subsystem: devstatSubsystem,
Name: "duration_seconds_total",
Help: "The total duration of transactions in seconds.",
},
[]string{"device", "type"},
),
busyTime: prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: Namespace,
Subsystem: devstatSubsystem,
Name: "busy_time_seconds_total",
Help: "Total time the device had one or more transactions outstanding in seconds.",
},
[]string{"device"},
),
blocks: prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: Namespace,
Subsystem: devstatSubsystem,
Name: "blocks_transferred_total",
Help: "The total number of blocks transferred.",
},
[]string{"device"},
),
}, nil
}
func (c *devstatCollector) Update(ch chan<- prometheus.Metric) (err error) {
count := C._get_ndevs()
if count == -1 {
return errors.New("devstat_getdevs() failed")
}
if count == -2 {
return errors.New("calloc() failed")
}
for i := C.int(0); i < count; i++ {
stats := C._get_stats(i)
device := fmt.Sprintf("%s%d", C.GoString(&stats.device[0]), stats.unit)
// Free metrics are disabled for now, please see PR #88 for more details.
c.bytes.With(prometheus.Labels{"device": device, "type": "read"}).Set(float64(stats.bytes.read))
c.bytes.With(prometheus.Labels{"device": device, "type": "write"}).Set(float64(stats.bytes.write))
//c.bytes.With(prometheus.Labels{"device": device, "type": "free"}).Set(float64(stats.bytes.free))
c.transfers.With(prometheus.Labels{"device": device, "type": "other"}).Set(float64(stats.transfers.other))
c.transfers.With(prometheus.Labels{"device": device, "type": "read"}).Set(float64(stats.transfers.read))
c.transfers.With(prometheus.Labels{"device": device, "type": "write"}).Set(float64(stats.transfers.write))
//c.transfers.With(prometheus.Labels{"device": device, "type": "free"}).Set(float64(stats.transfers.free))
c.duration.With(prometheus.Labels{"device": device, "type": "other"}).Set(float64(stats.duration.other))
c.duration.With(prometheus.Labels{"device": device, "type": "read"}).Set(float64(stats.duration.read))
c.duration.With(prometheus.Labels{"device": device, "type": "write"}).Set(float64(stats.duration.write))
//c.duration.With(prometheus.Labels{"device": device, "type": "free"}).Set(float64(stats.duration.free))
c.busyTime.With(prometheus.Labels{"device": device}).Set(float64(stats.busyTime))
c.blocks.With(prometheus.Labels{"device": device}).Set(float64(stats.blocks))
}
c.bytes.Collect(ch)
c.transfers.Collect(ch)
c.duration.Collect(ch)
c.busyTime.Collect(ch)
c.blocks.Collect(ch)
return err
}

View file

@ -0,0 +1,124 @@
// +build !nofilesystem
package collector
import (
"errors"
"flag"
"regexp"
"unsafe"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/log"
)
/*
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
#include <stdio.h>
*/
import "C"
const (
filesystemSubsystem = "filesystem"
)
var (
ignoredMountPoints = flag.String("collector.filesystem.ignored-mount-points", "^/(dev)($|/)", "Regexp of mount points to ignore for filesystem collector.")
)
type filesystemCollector struct {
ignoredMountPointsPattern *regexp.Regexp
size, free, avail, files, filesFree *prometheus.GaugeVec
}
func init() {
Factories["filesystem"] = NewFilesystemCollector
}
// Takes a prometheus registry and returns a new Collector exposing
// Filesystems stats.
func NewFilesystemCollector() (Collector, error) {
var filesystemLabelNames = []string{"filesystem"}
return &filesystemCollector{
ignoredMountPointsPattern: regexp.MustCompile(*ignoredMountPoints),
size: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: Namespace,
Subsystem: filesystemSubsystem,
Name: "size_bytes",
Help: "Filesystem size in bytes.",
},
filesystemLabelNames,
),
free: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: Namespace,
Subsystem: filesystemSubsystem,
Name: "free_bytes",
Help: "Filesystem free space in bytes.",
},
filesystemLabelNames,
),
avail: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: Namespace,
Subsystem: filesystemSubsystem,
Name: "avail_bytes",
Help: "Filesystem space available to non-root users in bytes.",
},
filesystemLabelNames,
),
files: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: Namespace,
Subsystem: filesystemSubsystem,
Name: "file_nodes",
Help: "Filesystem total file nodes.",
},
filesystemLabelNames,
),
filesFree: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: Namespace,
Subsystem: filesystemSubsystem,
Name: "file_free_nodes",
Help: "Filesystem total free file nodes.",
},
filesystemLabelNames,
),
}, nil
}
// Expose filesystem fullness.
func (c *filesystemCollector) Update(ch chan<- prometheus.Metric) (err error) {
var mntbuf *C.struct_statfs
count := C.getmntinfo(&mntbuf, C.MNT_NOWAIT)
if count == 0 {
return errors.New("getmntinfo() failed")
}
mnt := (*[1 << 30]C.struct_statfs)(unsafe.Pointer(mntbuf))
for i := 0; i < int(count); i++ {
name := C.GoString(&mnt[i].f_mntonname[0])
if c.ignoredMountPointsPattern.MatchString(name) {
log.Debugf("Ignoring mount point: %s", name)
continue
}
c.size.WithLabelValues(name).Set(float64(mnt[i].f_blocks) * float64(mnt[i].f_bsize))
c.free.WithLabelValues(name).Set(float64(mnt[i].f_bfree) * float64(mnt[i].f_bsize))
c.avail.WithLabelValues(name).Set(float64(mnt[i].f_bavail) * float64(mnt[i].f_bsize))
c.files.WithLabelValues(name).Set(float64(mnt[i].f_files))
c.filesFree.WithLabelValues(name).Set(float64(mnt[i].f_ffree))
}
c.size.Collect(ch)
c.free.Collect(ch)
c.avail.Collect(ch)
c.files.Collect(ch)
c.filesFree.Collect(ch)
return err
}

View file

@ -1,20 +1,18 @@
// +build !noloadavg
// +build !linux
package collector
import (
"errors"
"fmt"
"io/ioutil"
"strconv"
"strings"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/log"
)
const (
procLoad = "/proc/loadavg"
)
// #include <stdlib.h>
import "C"
type loadavgCollector struct {
metric prometheus.Gauge
@ -25,7 +23,7 @@ func init() {
}
// Takes a prometheus registry and returns a new Collector exposing
// load, seconds since last login and a list of tags as specified by config.
// load1 stat.
func NewLoadavgCollector() (Collector, error) {
return &loadavgCollector{
metric: prometheus.NewGauge(prometheus.GaugeOpts{
@ -48,18 +46,12 @@ func (c *loadavgCollector) Update(ch chan<- prometheus.Metric) (err error) {
}
func getLoad1() (float64, error) {
data, err := ioutil.ReadFile(procLoad)
if err != nil {
return 0, err
var loadavg [1]C.double
samples := C.getloadavg(&loadavg[0], 1)
if samples > 0 {
return float64(loadavg[0]), nil
} else {
return 0, errors.New("failed to get load average")
}
return parseLoad(string(data))
}
func parseLoad(data string) (float64, error) {
parts := strings.Fields(data)
load, err := strconv.ParseFloat(parts[0], 64)
if err != nil {
return 0, fmt.Errorf("Could not parse load '%s': %s", parts[0], err)
}
return load, nil
}

View file

@ -0,0 +1,65 @@
// +build !noloadavg
package collector
import (
"fmt"
"io/ioutil"
"strconv"
"strings"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/log"
)
const (
procLoad = "/proc/loadavg"
)
type loadavgCollector struct {
metric prometheus.Gauge
}
func init() {
Factories["loadavg"] = NewLoadavgCollector
}
// Takes a prometheus registry and returns a new Collector exposing
// load, seconds since last login and a list of tags as specified by config.
func NewLoadavgCollector() (Collector, error) {
return &loadavgCollector{
metric: prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: Namespace,
Name: "load1",
Help: "1m load average.",
}),
}, nil
}
func (c *loadavgCollector) Update(ch chan<- prometheus.Metric) (err error) {
load, err := getLoad1()
if err != nil {
return fmt.Errorf("Couldn't get load: %s", err)
}
log.Debugf("Set node_load: %f", load)
c.metric.Set(load)
c.metric.Collect(ch)
return err
}
func getLoad1() (float64, error) {
data, err := ioutil.ReadFile(procLoad)
if err != nil {
return 0, err
}
return parseLoad(string(data))
}
func parseLoad(data string) (float64, error) {
parts := strings.Fields(data)
load, err := strconv.ParseFloat(parts[0], 64)
if err != nil {
return 0, fmt.Errorf("Could not parse load '%s': %s", parts[0], err)
}
return load, nil
}

View file

@ -0,0 +1,96 @@
// +build !nomeminfo
package collector
import (
"errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/log"
)
/*
#include <stddef.h>
#include <sys/sysctl.h>
int _sysctl(const char* name) {
int val;
size_t size = sizeof(val);
int res = sysctlbyname(name, &val, &size, NULL, 0);
if (res == -1) {
return -1;
}
if (size != sizeof(val)) {
return -2;
}
return val;
}
*/
import "C"
const (
memInfoSubsystem = "memory"
)
type meminfoCollector struct {
metrics map[string]prometheus.Gauge
}
func init() {
Factories["meminfo"] = NewMeminfoCollector
}
// Takes a prometheus registry and returns a new Collector exposing
// Memory stats.
func NewMeminfoCollector() (Collector, error) {
return &meminfoCollector{
metrics: map[string]prometheus.Gauge{},
}, nil
}
func (c *meminfoCollector) Update(ch chan<- prometheus.Metric) (err error) {
var pages map[string]C.int
pages = make(map[string]C.int)
size := C._sysctl(C.CString("vm.stats.vm.v_page_size"))
if size == -1 {
return errors.New("sysctl(vm.stats.vm.v_page_size) failed")
}
if size == -2 {
return errors.New("sysctl(vm.stats.vm.v_page_size) failed, wrong buffer size")
}
pages["active"] = C._sysctl(C.CString("vm.stats.vm.v_active_count"))
pages["inactive"] = C._sysctl(C.CString("vm.stats.vm.v_inactive_count"))
pages["wire"] = C._sysctl(C.CString("vm.stats.vm.v_wire_count"))
pages["cache"] = C._sysctl(C.CString("vm.stats.vm.v_cache_count"))
pages["free"] = C._sysctl(C.CString("vm.stats.vm.v_free_count"))
pages["swappgsin"] = C._sysctl(C.CString("vm.stats.vm.v_swappgsin"))
pages["swappgsout"] = C._sysctl(C.CString("vm.stats.vm.v_swappgsout"))
pages["total"] = C._sysctl(C.CString("vm.stats.vm.v_page_count"))
for key := range pages {
if pages[key] == -1 {
return errors.New("sysctl() failed for " + key)
}
if pages[key] == -2 {
return errors.New("sysctl() failed for " + key + ", wrong buffer size")
}
}
log.Debugf("Set node_mem: %#v", pages)
for k, v := range pages {
if _, ok := c.metrics[k]; !ok {
c.metrics[k] = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: Namespace,
Subsystem: memInfoSubsystem,
Name: k,
Help: k + " from sysctl()",
})
}
// Convert metrics to kB (same as Linux meminfo).
c.metrics[k].Set(float64(v) * float64(size))
c.metrics[k].Collect(ch)
}
return err
}

110
collector/netdev_freebsd.go Normal file
View file

@ -0,0 +1,110 @@
// +build !nonetdev
package collector
import (
"errors"
"fmt"
"strconv"
"github.com/prometheus/client_golang/prometheus"
)
/*
#cgo CFLAGS: -D_IFI_OQDROPS
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <net/if.h>
*/
import "C"
const (
netDevSubsystem = "network"
)
type netDevCollector struct {
metrics map[string]*prometheus.CounterVec
}
func init() {
Factories["netdev"] = NewNetDevCollector
}
// Takes a prometheus registry and returns a new Collector exposing
// Network device stats.
func NewNetDevCollector() (Collector, error) {
return &netDevCollector{
metrics: map[string]*prometheus.CounterVec{},
}, nil
}
func (c *netDevCollector) Update(ch chan<- prometheus.Metric) (err error) {
netDev, err := getNetDevStats()
if err != nil {
return fmt.Errorf("couldn't get netstats: %s", err)
}
for direction, devStats := range netDev {
for dev, stats := range devStats {
for t, value := range stats {
key := direction + "_" + t
if _, ok := c.metrics[key]; !ok {
c.metrics[key] = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: Namespace,
Subsystem: netDevSubsystem,
Name: key,
Help: fmt.Sprintf("%s %s from getifaddrs().", t, direction),
},
[]string{"device"},
)
}
v, err := strconv.ParseFloat(value, 64)
if err != nil {
return fmt.Errorf("invalid value %s in netstats: %s", value, err)
}
c.metrics[key].WithLabelValues(dev).Set(v)
}
}
}
for _, m := range c.metrics {
m.Collect(ch)
}
return err
}
func getNetDevStats() (map[string]map[string]map[string]string, error) {
netDev := map[string]map[string]map[string]string{}
netDev["transmit"] = map[string]map[string]string{}
netDev["receive"] = map[string]map[string]string{}
var ifap, ifa *C.struct_ifaddrs
if C.getifaddrs(&ifap) == -1 {
return nil, errors.New("getifaddrs() failed")
}
defer C.freeifaddrs(ifap)
for ifa = ifap; ifa != nil; ifa = ifa.ifa_next {
if ifa.ifa_addr.sa_family == C.AF_LINK {
receive := map[string]string{}
transmit := map[string]string{}
data := (*C.struct_if_data)(ifa.ifa_data)
receive["packets"] = strconv.Itoa(int(data.ifi_ipackets))
transmit["packets"] = strconv.Itoa(int(data.ifi_opackets))
receive["errs"] = strconv.Itoa(int(data.ifi_ierrors))
transmit["errs"] = strconv.Itoa(int(data.ifi_oerrors))
receive["bytes"] = strconv.Itoa(int(data.ifi_ibytes))
transmit["bytes"] = strconv.Itoa(int(data.ifi_obytes))
receive["multicast"] = strconv.Itoa(int(data.ifi_imcasts))
transmit["multicast"] = strconv.Itoa(int(data.ifi_omcasts))
receive["drop"] = strconv.Itoa(int(data.ifi_iqdrops))
transmit["drop"] = strconv.Itoa(int(data.ifi_oqdrops))
netDev["receive"][C.GoString(ifa.ifa_name)] = receive
netDev["transmit"][C.GoString(ifa.ifa_name)] = transmit
}
}
return netDev, nil
}