2015-09-26 15:36:40 +00:00
// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2015-05-12 11:06:41 +00:00
// +build !nodiskstats
2014-06-04 11:12:34 +00:00
package collector
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"regexp"
"strconv"
"strings"
"github.com/prometheus/client_golang/prometheus"
2015-10-30 20:20:06 +00:00
"github.com/prometheus/common/log"
2014-06-04 11:12:34 +00:00
)
const (
2015-12-27 01:59:02 +00:00
diskSubsystem = "disk"
diskSectorSize uint64 = 512
2014-06-04 11:12:34 +00:00
)
var (
2016-07-09 11:23:01 +00:00
ignoredDevices = flag . String ( "collector.diskstats.ignored-devices" , "^(ram|loop|fd|(h|s|v|xv)d[a-z]|nvme\\d+n\\d+p)\\d+$" , "Regexp of devices to ignore for diskstats." )
2014-06-04 11:12:34 +00:00
)
type diskstatsCollector struct {
ignoredDevicesPattern * regexp . Regexp
2014-11-25 02:00:17 +00:00
metrics [ ] prometheus . Collector
2014-06-04 11:12:34 +00:00
}
func init ( ) {
Factories [ "diskstats" ] = NewDiskstatsCollector
}
2015-05-20 18:04:49 +00:00
// Takes a prometheus registry and returns a new Collector exposing
2014-06-04 11:12:34 +00:00
// disk device stats.
2015-05-20 18:04:49 +00:00
func NewDiskstatsCollector ( ) ( Collector , error ) {
2014-11-25 02:00:17 +00:00
var diskLabelNames = [ ] string { "device" }
return & diskstatsCollector {
2014-06-04 11:12:34 +00:00
ignoredDevicesPattern : regexp . MustCompile ( * ignoredDevices ) ,
2014-11-25 02:00:17 +00:00
// Docs from https://www.kernel.org/doc/Documentation/iostats.txt
metrics : [ ] prometheus . Collector {
prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "reads_completed" ,
Help : "The total number of reads completed successfully." ,
} ,
diskLabelNames ,
) ,
prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "reads_merged" ,
Help : "The number of reads merged. See https://www.kernel.org/doc/Documentation/iostats.txt." ,
} ,
diskLabelNames ,
) ,
prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "sectors_read" ,
Help : "The total number of sectors read successfully." ,
} ,
diskLabelNames ,
) ,
prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "read_time_ms" ,
Help : "The total number of milliseconds spent by all reads." ,
} ,
diskLabelNames ,
) ,
prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "writes_completed" ,
Help : "The total number of writes completed successfully." ,
} ,
diskLabelNames ,
) ,
prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "writes_merged" ,
Help : "The number of writes merged. See https://www.kernel.org/doc/Documentation/iostats.txt." ,
} ,
diskLabelNames ,
) ,
prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "sectors_written" ,
Help : "The total number of sectors written successfully." ,
} ,
diskLabelNames ,
) ,
prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "write_time_ms" ,
Help : "This is the total number of milliseconds spent by all writes." ,
} ,
diskLabelNames ,
) ,
prometheus . NewGaugeVec (
prometheus . GaugeOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "io_now" ,
Help : "The number of I/Os currently in progress." ,
} ,
diskLabelNames ,
) ,
prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "io_time_ms" ,
Help : "Milliseconds spent doing I/Os." ,
} ,
diskLabelNames ,
) ,
prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "io_time_weighted" ,
Help : "The weighted # of milliseconds spent doing I/Os. See https://www.kernel.org/doc/Documentation/iostats.txt." ,
} ,
diskLabelNames ,
) ,
2015-12-27 01:59:02 +00:00
prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "bytes_read" ,
Help : "The total number of bytes read successfully." ,
} ,
diskLabelNames ,
) ,
prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : Namespace ,
Subsystem : diskSubsystem ,
Name : "bytes_written" ,
Help : "The total number of bytes written successfully." ,
} ,
diskLabelNames ,
) ,
2014-11-25 02:00:17 +00:00
} ,
} , nil
2014-06-04 11:12:34 +00:00
}
2014-10-29 14:16:43 +00:00
func ( c * diskstatsCollector ) Update ( ch chan <- prometheus . Metric ) ( err error ) {
2015-09-26 12:53:46 +00:00
procDiskStats := procFilePath ( "diskstats" )
2014-06-04 11:12:34 +00:00
diskStats , err := getDiskStats ( )
if err != nil {
2014-11-25 02:00:17 +00:00
return fmt . Errorf ( "couldn't get diskstats: %s" , err )
2014-06-04 11:12:34 +00:00
}
2014-11-25 02:00:17 +00:00
2014-06-04 11:12:34 +00:00
for dev , stats := range diskStats {
if c . ignoredDevicesPattern . MatchString ( dev ) {
2015-05-28 19:21:44 +00:00
log . Debugf ( "Ignoring device: %s" , dev )
2014-06-04 11:12:34 +00:00
continue
}
2014-11-25 02:00:17 +00:00
if len ( stats ) != len ( c . metrics ) {
return fmt . Errorf ( "invalid line for %s for %s" , procDiskStats , dev )
}
2014-06-04 11:12:34 +00:00
for k , value := range stats {
v , err := strconv . ParseFloat ( value , 64 )
if err != nil {
2014-11-25 02:00:17 +00:00
return fmt . Errorf ( "invalid value %s in diskstats: %s" , value , err )
2014-06-04 11:12:34 +00:00
}
2014-11-25 02:00:17 +00:00
if counter , ok := c . metrics [ k ] . ( * prometheus . CounterVec ) ; ok {
2014-06-26 17:20:36 +00:00
counter . WithLabelValues ( dev ) . Set ( v )
2014-11-25 02:00:17 +00:00
} else if gauge , ok := c . metrics [ k ] . ( * prometheus . GaugeVec ) ; ok {
2014-06-26 17:20:36 +00:00
gauge . WithLabelValues ( dev ) . Set ( v )
2014-11-25 02:00:17 +00:00
} else {
return fmt . Errorf ( "unexpected collector %d" , k )
2014-06-04 11:12:34 +00:00
}
}
}
2014-11-25 02:00:17 +00:00
for _ , c := range c . metrics {
2014-10-29 14:16:43 +00:00
c . Collect ( ch )
}
return err
2014-06-04 11:12:34 +00:00
}
func getDiskStats ( ) ( map [ string ] map [ int ] string , error ) {
2015-09-26 12:53:46 +00:00
file , err := os . Open ( procFilePath ( "diskstats" ) )
2014-06-04 11:12:34 +00:00
if err != nil {
return nil , err
}
2014-11-24 23:30:07 +00:00
defer file . Close ( )
2014-06-04 11:12:34 +00:00
return parseDiskStats ( file )
}
2015-12-27 01:59:02 +00:00
func convertDiskSectorsToBytes ( sectorCount string ) ( string , error ) {
sectors , err := strconv . ParseUint ( sectorCount , 10 , 64 )
if err != nil {
return "" , err
}
return strconv . FormatUint ( sectors * diskSectorSize , 10 ) , nil
}
2014-11-24 23:30:07 +00:00
func parseDiskStats ( r io . Reader ) ( map [ string ] map [ int ] string , error ) {
var (
diskStats = map [ string ] map [ int ] string { }
scanner = bufio . NewScanner ( r )
)
2014-06-04 11:12:34 +00:00
for scanner . Scan ( ) {
parts := strings . Fields ( string ( scanner . Text ( ) ) )
2014-11-25 02:00:17 +00:00
if len ( parts ) < 4 { // we strip major, minor and dev
2015-09-26 12:53:46 +00:00
return nil , fmt . Errorf ( "invalid line in %s: %s" , procFilePath ( "diskstats" ) , scanner . Text ( ) )
2014-06-04 11:12:34 +00:00
}
dev := parts [ 2 ]
diskStats [ dev ] = map [ int ] string { }
for i , v := range parts [ 3 : ] {
diskStats [ dev ] [ i ] = v
}
2015-12-27 01:59:02 +00:00
bytesRead , err := convertDiskSectorsToBytes ( diskStats [ dev ] [ 2 ] )
if err != nil {
return nil , fmt . Errorf ( "invalid value for sectors read in %s: %s" , procFilePath ( "diskstats" ) , scanner . Text ( ) )
}
diskStats [ dev ] [ 11 ] = bytesRead
bytesWritten , err := convertDiskSectorsToBytes ( diskStats [ dev ] [ 6 ] )
if err != nil {
2016-02-10 04:22:29 +00:00
return nil , fmt . Errorf ( "invalid value for sectors written in %s: %s" , procFilePath ( "diskstats" ) , scanner . Text ( ) )
2015-12-27 01:59:02 +00:00
}
diskStats [ dev ] [ 12 ] = bytesWritten
2014-06-04 11:12:34 +00:00
}
2014-11-24 23:30:07 +00:00
2014-06-04 11:12:34 +00:00
return diskStats , nil
}