@@ -34,9 +34,89 @@ var dovecotUpDesc = prometheus.NewDesc(
34
34
[]string {"scope" },
35
35
nil )
36
36
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 )
40
120
scanner .Split (bufio .ScanLines )
41
121
42
122
// 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 {
47
127
if len (columnNames ) < 2 {
48
128
return fmt .Errorf ("Input does not provide any columns" )
49
129
}
130
+
50
131
columns := []* prometheus.Desc {}
51
132
for _ , columnName := range columnNames [1 :] {
52
133
columns = append (columns , prometheus .NewDesc (
@@ -58,10 +139,16 @@ func CollectFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
58
139
59
140
// Read successive lines, containing the values.
60
141
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 ) == "" {
63
144
break
64
145
}
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
+
65
152
for i , value := range values [1 :] {
66
153
f , err := strconv .ParseFloat (value , 64 )
67
154
if err != nil {
@@ -77,26 +164,6 @@ func CollectFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
77
164
return scanner .Err ()
78
165
}
79
166
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
-
100
167
type DovecotExporter struct {
101
168
scopes []string
102
169
socketPath string
0 commit comments