// +build !nofilesystem

package collector

import (
	"bufio"
	"flag"
	"fmt"
	"os"
	"regexp"
	"strings"
	"syscall"

	"github.com/golang/glog"
	"github.com/prometheus/client_golang/prometheus"
)

const (
	procMounts          = "/proc/mounts"
	filesystemSubsystem = "filesystem"
)

var (
	ignoredMountPoints = flag.String("collector.filesystem.ignored-mount-points", "^/(sys|proc|dev)($|/)", "Regexp of mount points to ignore for filesystem collector.")
)

type filesystemCollector struct {
	config                    Config
	ignoredMountPointsPattern *regexp.Regexp

	size, free, avail, files, filesFree *prometheus.GaugeVec
}

func init() {
	Factories["filesystem"] = NewFilesystemCollector
}

// Takes a config struct and prometheus registry and returns a new Collector exposing
// network device filesystems.
func NewFilesystemCollector(config Config) (Collector, error) {
	var filesystemLabelNames = []string{"filesystem"}

	return &filesystemCollector{
		config: config,
		ignoredMountPointsPattern: regexp.MustCompile(*ignoredMountPoints),
		size: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: Namespace,
				Subsystem: filesystemSubsystem,
				Name:      "size",
				Help:      "Filesystem size in bytes.",
			},
			filesystemLabelNames,
		),
		free: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: Namespace,
				Subsystem: filesystemSubsystem,
				Name:      "free",
				Help:      "Filesystem free space in bytes.",
			},
			filesystemLabelNames,
		),
		avail: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: Namespace,
				Subsystem: filesystemSubsystem,
				Name:      "avail",
				Help:      "Filesystem space available to non-root users in bytes.",
			},
			filesystemLabelNames,
		),
		files: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: Namespace,
				Subsystem: filesystemSubsystem,
				Name:      "files",
				Help:      "Filesystem total file nodes.",
			},
			filesystemLabelNames,
		),
		filesFree: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: Namespace,
				Subsystem: filesystemSubsystem,
				Name:      "files_free",
				Help:      "Filesystem total free file nodes.",
			},
			filesystemLabelNames,
		),
	}, nil
}

// Expose filesystem fullness.
func (c *filesystemCollector) Update(ch chan<- prometheus.Metric) (err error) {
	mps, err := mountPoints()
	if err != nil {
		return err
	}
	for _, mp := range mps {
		if c.ignoredMountPointsPattern.MatchString(mp) {
			glog.V(1).Infof("Ignoring mount point: %s", mp)
			continue
		}
		buf := new(syscall.Statfs_t)
		err := syscall.Statfs(mp, buf)
		if err != nil {
			return fmt.Errorf("Statfs on %s returned %s", mp, err)
		}
		c.size.WithLabelValues(mp).Set(float64(buf.Blocks) * float64(buf.Bsize))
		c.free.WithLabelValues(mp).Set(float64(buf.Bfree) * float64(buf.Bsize))
		c.avail.WithLabelValues(mp).Set(float64(buf.Bavail) * float64(buf.Bsize))
		c.files.WithLabelValues(mp).Set(float64(buf.Files))
		c.filesFree.WithLabelValues(mp).Set(float64(buf.Ffree))
	}
	c.size.Collect(ch)
	c.free.Collect(ch)
	c.avail.Collect(ch)
	c.files.Collect(ch)
	c.filesFree.Collect(ch)
	return err
}

func mountPoints() ([]string, error) {
	file, err := os.Open(procMounts)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	mountPoints := []string{}
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		parts := strings.Fields(scanner.Text())
		mountPoints = append(mountPoints, parts[1])
	}
	return mountPoints, nil
}