@@ -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-
100167type DovecotExporter struct {
101168 scopes []string
102169 socketPath string
0 commit comments