Skip to content

Commit 4e83135

Browse files
authored
Merge pull request #6 from zoni/fix-global-scope-scraping
Support scraping of "global" scope
2 parents 9bbdcdb + 3e8a4ef commit 4e83135

File tree

1 file changed

+92
-25
lines changed

1 file changed

+92
-25
lines changed

dovecot_exporter.go

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,89 @@ var dovecotUpDesc = prometheus.NewDesc(
3434
[]string{"scope"},
3535
nil)
3636

37-
// Converts the output of Dovecot's EXPORT command to metrics.
38-
func CollectFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
39-
scanner := bufio.NewScanner(file)
37+
// CollectFromReader converts the output of Dovecot's EXPORT command to metrics.
38+
func CollectFromReader(file io.Reader, scope string, ch chan<- prometheus.Metric) error {
39+
if scope == "global" {
40+
return collectGlobalMetricsFromReader(file, scope, ch)
41+
}
42+
return collectDetailMetricsFromReader(file, scope, ch)
43+
}
44+
45+
// CollectFromFile collects dovecot statistics from the given file
46+
func CollectFromFile(path string, scope string, ch chan<- prometheus.Metric) error {
47+
conn, err := os.Open(path)
48+
if err != nil {
49+
return err
50+
}
51+
return CollectFromReader(conn, scope, ch)
52+
}
53+
54+
// CollectFromSocket collects statistics from dovecot's stats socket.
55+
func CollectFromSocket(path string, scope string, ch chan<- prometheus.Metric) error {
56+
conn, err := net.Dial("unix", path)
57+
if err != nil {
58+
return err
59+
}
60+
_, err = conn.Write([]byte("EXPORT\t" + scope + "\n"))
61+
if err != nil {
62+
return err
63+
}
64+
return CollectFromReader(conn, scope, ch)
65+
}
66+
67+
// collectGlobalMetricsFromReader collects dovecot "global" scope metrics from
68+
// the supplied reader.
69+
func collectGlobalMetricsFromReader(reader io.Reader, scope string, ch chan<- prometheus.Metric) error {
70+
scanner := bufio.NewScanner(reader)
71+
scanner.Split(bufio.ScanLines)
72+
73+
// Read first line of input, containing the aggregation and column names.
74+
if !scanner.Scan() {
75+
return fmt.Errorf("Failed to extract columns from input")
76+
}
77+
columnNames := strings.Fields(scanner.Text())
78+
if len(columnNames) < 1 {
79+
return fmt.Errorf("Input does not provide any columns")
80+
}
81+
82+
columns := []*prometheus.Desc{}
83+
for _, columnName := range columnNames {
84+
columns = append(columns, prometheus.NewDesc(
85+
prometheus.BuildFQName("dovecot", scope, columnName),
86+
"Help text not provided by this exporter.",
87+
[]string{},
88+
nil))
89+
}
90+
91+
// Global metrics only have a single row containing values following the
92+
// line with column names
93+
if !scanner.Scan() {
94+
return scanner.Err()
95+
}
96+
values := strings.Fields(scanner.Text())
97+
98+
if len(values) != len(columns) {
99+
return fmt.Errorf("error while parsing row: value count does not match column count")
100+
}
101+
102+
for i, value := range values {
103+
f, err := strconv.ParseFloat(value, 64)
104+
if err != nil {
105+
return err
106+
}
107+
ch <- prometheus.MustNewConstMetric(
108+
columns[i],
109+
prometheus.UntypedValue,
110+
f,
111+
)
112+
}
113+
return scanner.Err()
114+
}
115+
116+
// collectGlobalMetricsFromReader collects dovecot "non-global" scope metrics
117+
// from the supplied reader.
118+
func collectDetailMetricsFromReader(reader io.Reader, scope string, ch chan<- prometheus.Metric) error {
119+
scanner := bufio.NewScanner(reader)
40120
scanner.Split(bufio.ScanLines)
41121

42122
// Read first line of input, containing the aggregation and column names.
@@ -47,6 +127,7 @@ func CollectFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
47127
if len(columnNames) < 2 {
48128
return fmt.Errorf("Input does not provide any columns")
49129
}
130+
50131
columns := []*prometheus.Desc{}
51132
for _, columnName := range columnNames[1:] {
52133
columns = append(columns, prometheus.NewDesc(
@@ -58,10 +139,16 @@ func CollectFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
58139

59140
// Read successive lines, containing the values.
60141
for scanner.Scan() {
61-
values := strings.Fields(scanner.Text())
62-
if len(values) != len(columns)+1 {
142+
row := scanner.Text()
143+
if strings.TrimSpace(row) == "" {
63144
break
64145
}
146+
147+
values := strings.Fields(row)
148+
if len(values) != len(columns)+1 {
149+
return fmt.Errorf("error while parsing rows: value count does not match column count")
150+
}
151+
65152
for i, value := range values[1:] {
66153
f, err := strconv.ParseFloat(value, 64)
67154
if err != nil {
@@ -77,26 +164,6 @@ func CollectFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
77164
return scanner.Err()
78165
}
79166

80-
func CollectFromFile(path string, ch chan<- prometheus.Metric) error {
81-
conn, err := os.Open(path)
82-
if err != nil {
83-
return err
84-
}
85-
return CollectFromReader(conn, ch)
86-
}
87-
88-
func CollectFromSocket(path string, scope string, ch chan<- prometheus.Metric) error {
89-
conn, err := net.Dial("unix", path)
90-
if err != nil {
91-
return err
92-
}
93-
_, err = conn.Write([]byte("EXPORT\t" + scope + "\n"))
94-
if err != nil {
95-
return err
96-
}
97-
return CollectFromReader(conn, ch)
98-
}
99-
100167
type DovecotExporter struct {
101168
scopes []string
102169
socketPath string

0 commit comments

Comments
 (0)