Skip to content
This repository has been archived by the owner on Jul 9, 2024. It is now read-only.

change code structure #23

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions collector/exporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package collector

import (
"time"
"strings"

"github.com/prometheus/common/log"
"github.com/prometheus/client_golang/prometheus"
)

// Metrics name parts.
const (
// Default all Volumes
allVolumes = "_all"

// Namespace
namespace = "gluster"
// Subsystem(s).
exporter = "exporter"
)

// Metric descriptors.
var (
scrapeDurationDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exporter, "collector_duration_seconds"),
"Collector time duration.",
[]string{"collector"}, nil,
)
)

// Collect defines which metrics we should collect
type Collect struct {
Base bool
Profile bool
Quota bool
Mount bool
Peer bool
}

type Exporter struct {
hostname string
glusterPath string
volumes []string
collect Collect
error prometheus.Gauge
totalScrapes prometheus.Counter
scrapeErrors *prometheus.CounterVec
glusterUp prometheus.Gauge
}

// returns a new GlusterFS exporter
func New(hostname string, glusterPath string, volumeString string, collect Collect) *Exporter {

gfsPath, err := getGlusterBinary(glusterPath)
if err != nil {
log.Errorf("Given Gluster path %v has err: %v", glusterPath, err)
}

volumes := strings.Split(volumeString, ",")
if len(volumes) < 1 {
log.Infof("No volumes given. Proceeding without volume information. Volumes: %v", volumeString)
}

return &Exporter{
hostname: hostname,
glusterPath: gfsPath,
volumes: volumes,
collect: collect,
totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: exporter,
Name: "scrapes_total",
Help: "Total number of times GlusterFS was scraped for metrics.",
}),
scrapeErrors: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: exporter,
Name: "scrape_errors_total",
Help: "Total number of times an error occurred scraping a GlusterFS.",
}, []string{"collector"}),
error: prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: exporter,
Name: "last_scrape_error",
Help: "Whether the last scrape of metrics from GlusterFS resulted in an error (1 for error, 0 for success).",
}),
glusterUp: prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Name: "up",
Help: "Whether the GlusterFS server is up.",
}),
}
}

// Describe implements prometheus.Collector.
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {

metricCh := make(chan prometheus.Metric)
doneCh := make(chan struct{})

go func() {
for m := range metricCh {
ch <- m.Desc()
}
close(doneCh)
}()

e.Collect(metricCh)
close(metricCh)
<-doneCh
}

// Collect implements prometheus.Collector.
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
e.scrape(ch)

ch <- e.totalScrapes
ch <- e.error
e.scrapeErrors.Collect(ch)
ch <- e.glusterUp
}

func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
e.totalScrapes.Inc()
var err error

scrapeTime := time.Now()

// if can get volume info, glusterFS is UP(1), or Down(0)
_, err = ExecVolumeInfo()
if err != nil {
e.glusterUp.Set(0)
}
e.glusterUp.Set(1)

// default collect volume info as Base Metrics
e.collect.Base = true

if e.collect.Base {
// Base Gluster Info Scrape
scrapeTime := time.Now()
if err = ScrapeGlobalVolumeStatus(e.volumes, allVolumes, ch); err != nil {
log.Errorln("Error scraping for collect.global_status:", err)
e.scrapeErrors.WithLabelValues("collect.global_status").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.global_status")
}

// Peer Info Scrape
if e.collect.Peer {
scrapeTime = time.Now()
if err = ScrapePeerStatus(ch); err != nil {
log.Errorln("Error scraping for collect.peer_status: ", err)
e.scrapeErrors.WithLabelValues("collect.peer_status").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.peer_status")
}

// Mount Scrape
if e.collect.Mount {
scrapeTime = time.Now()
if err = ScrapeVolumeMountStatus(e.scrapeErrors, ch); err != nil {
log.Errorln("Error scraping for collect.mount_status:", err)
e.scrapeErrors.WithLabelValues("collect.mount_status").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.mount_status")
}

// Profile Scrape
if e.collect.Profile {
scrapeTime = time.Now()
if err = ScrapeProfileStatus(e.volumes, allVolumes, e.hostname, e.scrapeErrors, ch); err != nil {
log.Errorln("Error scraping for collect.profile_status:", err)
e.scrapeErrors.WithLabelValues("collect.profile_status").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.profile_status")
}

// Quota Scrape
if e.collect.Quota {
scrapeTime = time.Now()
if err = ScrapeQuotaStatus(e.volumes, allVolumes, e.scrapeErrors, ch); err != nil {
log.Errorln("Error scraping for collect.quota_status:", err)
e.scrapeErrors.WithLabelValues("collect.quota_status").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.quota_status")
}

}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
125 changes: 125 additions & 0 deletions collector/mount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package collector

import (
"bytes"
"os/exec"
"strings"
"fmt"
"time"
"os"

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


// TODO: Need Test
const (
// Subsystem(s).
mount = "mount"
)

type mountV struct {
mountPoint string
volume string
}

var (
volumeWriteable = prometheus.NewDesc(
prometheus.BuildFQName(namespace, mount, "mount_writable"),
"Writes and deletes file in Volume and checks if it is writable",
[]string{"volume", "mountpoint"}, nil)

mountSuccessful = prometheus.NewDesc(
prometheus.BuildFQName(namespace, mount, "mount_successful"),
"Checks if mountpoint exists, returns a bool value 0 or 1",
[]string{"volume", "mountpoint"}, nil)
)

func ScrapeVolumeMountStatus(scrapeError *prometheus.CounterVec, ch chan<- prometheus.Metric) error {
mountBuffer, execMountCheckErr := execMountCheck()
if execMountCheckErr != nil {
return execMountCheckErr
} else {
mounts, err := ParseMountOutput(mountBuffer.String())
testMountResult := testMount(mounts, err)

for _, mount := range mounts {
ch <- prometheus.MustNewConstMetric(
mountSuccessful, prometheus.GaugeValue, float64(testMountResult), mount.volume, mount.mountPoint,
)
}

if err != nil {
return err
} else {
for _, mount := range mounts {
isWritable, err := execTouchOnVolumes(mount.mountPoint)
if err != nil {
scrapeError.WithLabelValues("collect.mount_status").Inc()
}
testWriteResult := testWritable(isWritable)
ch <- prometheus.MustNewConstMetric(
volumeWriteable, prometheus.GaugeValue, float64(testWriteResult), mount.volume, mount.mountPoint,
)
}
}
}

return nil
}

func execMountCheck() (*bytes.Buffer, error) {
stdoutBuffer := &bytes.Buffer{}
mountCmd := exec.Command("mount", "-t", "fuse.glusterfs")

mountCmd.Stdout = stdoutBuffer
err := mountCmd.Run()

if err != nil {
return stdoutBuffer, err
}
return stdoutBuffer, nil
}

// ParseMountOutput pares output of system execution 'mount'
func ParseMountOutput(mountBuffer string) ([]mountV, error) {
mounts := make([]mountV, 0, 2)
mountRows := strings.Split(mountBuffer, "\n")
for _, row := range mountRows {
trimmedRow := strings.TrimSpace(row)
if len(row) > 3 {
mountColumns := strings.Split(trimmedRow, " ")
mounts = append(mounts, mountV{mountPoint: mountColumns[2], volume: mountColumns[0]})
}
}
return mounts, nil
}

// Test is mount Successful or not
func testMount(mounts []mountV, err error) int {
if mounts != nil && len(mounts) > 0 {
return 1
}
return 0
}

// Test if mount Writable or not
func testWritable(isWritable bool) int {
if isWritable {
return 1
}
return 0
}

func execTouchOnVolumes(mountpoint string) (bool, error) {
testFileName := fmt.Sprintf("%v/%v_%v", mountpoint, "gluster_mount.fixtures", time.Now())
_, createErr := os.Create(testFileName)
if createErr != nil {
return false, createErr
}
removeErr := os.Remove(testFileName)
if removeErr != nil {
return false, removeErr
}
return true, nil
}
4 changes: 2 additions & 2 deletions main_test.go → collector/mount_test.go
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package collector

import "testing"

Expand Down Expand Up @@ -29,7 +29,7 @@ func TestParseMountOutput(t *testing.T) {
},
}
for _, c := range tests {
mounts, err := parseMountOutput(c.mountOutput)
mounts, err := ParseMountOutput(c.mountOutput)
if err != nil {
t.Error(err)
}
Expand Down
Loading