Merge pull request #127 from lsy1990/main-nfs-client
add plugin nfs-client
This commit is contained in:
commit
18a55f4e87
|
@ -29,6 +29,7 @@ import (
|
|||
_ "flashcat.cloud/categraf/inputs/net"
|
||||
_ "flashcat.cloud/categraf/inputs/net_response"
|
||||
_ "flashcat.cloud/categraf/inputs/netstat"
|
||||
_ "flashcat.cloud/categraf/inputs/nfsclient"
|
||||
_ "flashcat.cloud/categraf/inputs/nginx_upstream_check"
|
||||
_ "flashcat.cloud/categraf/inputs/ntp"
|
||||
_ "flashcat.cloud/categraf/inputs/nvidia_smi"
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# # collect interval
|
||||
# interval = 15
|
||||
|
||||
|
||||
# # append some labels for series
|
||||
# labels = { region="cloud", product="n9e" }
|
||||
|
||||
# # interval = global.interval * interval_times
|
||||
# interval_times = 1
|
||||
|
||||
## Read more low-level metrics (optional, defaults to false)
|
||||
fullstat = false
|
||||
|
||||
## List of mounts to explictly include or exclude (optional)
|
||||
## The pattern (Go regexp) is matched against the mount point (not the
|
||||
## device being mounted). If include_mounts is set, all mounts are ignored
|
||||
## unless present in the list. If a mount is listed in both include_mounts
|
||||
## and exclude_mounts, it is excluded. Go regexp patterns can be used.
|
||||
|
||||
# include_mounts = []
|
||||
# exclude_mounts = []
|
||||
|
||||
## List of operations to include or exclude from collecting. This applies
|
||||
## only when fullstat=true. Symantics are similar to {include,exclude}_mounts:
|
||||
## the default is to collect everything; when include_operations is set, only
|
||||
## those OPs are collected; when exclude_operations is set, all are collected
|
||||
## except those listed. If include and exclude are set, the OP is excluded.
|
||||
## See /proc/self/mountstats for a list of valid operations; note that
|
||||
## NFSv3 and NFSv4 have different lists. While it is not possible to
|
||||
## have different include/exclude lists for NFSv3/4, unused elements
|
||||
## in the list should be okay. It is possible to have different lists
|
||||
## for different mountpoints: use mulitple [[input.nfsclient]] stanzas,
|
||||
## with their own lists. See "include_mounts" above, and be careful of
|
||||
## duplicate metrics.
|
||||
|
||||
#include_operations = ['READ','WRITE','ACCESS','GETATTR','READDIR','LOOKUP']
|
||||
# exclude_operations = []
|
|
@ -0,0 +1,501 @@
|
|||
package nfsclient
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"flashcat.cloud/categraf/config"
|
||||
"flashcat.cloud/categraf/inputs"
|
||||
"flashcat.cloud/categraf/types"
|
||||
)
|
||||
|
||||
const inputName = "nfsclient"
|
||||
|
||||
type NfsClient struct {
|
||||
config.PluginConfig
|
||||
|
||||
Fullstat bool `toml:"fullstat"`
|
||||
IncludeMounts []string `toml:"include_mounts"`
|
||||
ExcludeMounts []string `toml:"exclude_mounts"`
|
||||
IncludeOperations []string `toml:"include_operations"`
|
||||
ExcludeOperations []string `toml:"exclude_operations"`
|
||||
|
||||
nfs3Ops map[string]bool
|
||||
nfs4Ops map[string]bool
|
||||
mountstatsPath string
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add(inputName, func() inputs.Input {
|
||||
return &NfsClient{}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *NfsClient) Prefix() string {
|
||||
return inputName
|
||||
}
|
||||
|
||||
func (s *NfsClient) Init() error {
|
||||
var nfs3Fields = []string{
|
||||
"NULL",
|
||||
"GETATTR",
|
||||
"SETATTR",
|
||||
"LOOKUP",
|
||||
"ACCESS",
|
||||
"READLINK",
|
||||
"READ",
|
||||
"WRITE",
|
||||
"CREATE",
|
||||
"MKDIR",
|
||||
"SYMLINK",
|
||||
"MKNOD",
|
||||
"REMOVE",
|
||||
"RMDIR",
|
||||
"RENAME",
|
||||
"LINK",
|
||||
"READDIR",
|
||||
"READDIRPLUS",
|
||||
"FSSTAT",
|
||||
"FSINFO",
|
||||
"PATHCONF",
|
||||
"COMMIT",
|
||||
}
|
||||
|
||||
var nfs4Fields = []string{
|
||||
"NULL",
|
||||
"READ",
|
||||
"WRITE",
|
||||
"COMMIT",
|
||||
"OPEN",
|
||||
"OPEN_CONFIRM",
|
||||
"OPEN_NOATTR",
|
||||
"OPEN_DOWNGRADE",
|
||||
"CLOSE",
|
||||
"SETATTR",
|
||||
"FSINFO",
|
||||
"RENEW",
|
||||
"SETCLIENTID",
|
||||
"SETCLIENTID_CONFIRM",
|
||||
"LOCK",
|
||||
"LOCKT",
|
||||
"LOCKU",
|
||||
"ACCESS",
|
||||
"GETATTR",
|
||||
"LOOKUP",
|
||||
"LOOKUP_ROOT",
|
||||
"REMOVE",
|
||||
"RENAME",
|
||||
"LINK",
|
||||
"SYMLINK",
|
||||
"CREATE",
|
||||
"PATHCONF",
|
||||
"STATFS",
|
||||
"READLINK",
|
||||
"READDIR",
|
||||
"SERVER_CAPS",
|
||||
"DELEGRETURN",
|
||||
"GETACL",
|
||||
"SETACL",
|
||||
"FS_LOCATIONS",
|
||||
"RELEASE_LOCKOWNER",
|
||||
"SECINFO",
|
||||
"FSID_PRESENT",
|
||||
"EXCHANGE_ID",
|
||||
"CREATE_SESSION",
|
||||
"DESTROY_SESSION",
|
||||
"SEQUENCE",
|
||||
"GET_LEASE_TIME",
|
||||
"RECLAIM_COMPLETE",
|
||||
"LAYOUTGET",
|
||||
"GETDEVICEINFO",
|
||||
"LAYOUTCOMMIT",
|
||||
"LAYOUTRETURN",
|
||||
"SECINFO_NO_NAME",
|
||||
"TEST_STATEID",
|
||||
"FREE_STATEID",
|
||||
"GETDEVICELIST",
|
||||
"BIND_CONN_TO_SESSION",
|
||||
"DESTROY_CLIENTID",
|
||||
"SEEK",
|
||||
"ALLOCATE",
|
||||
"DEALLOCATE",
|
||||
"LAYOUTSTATS",
|
||||
"CLONE",
|
||||
"COPY",
|
||||
"OFFLOAD_CANCEL",
|
||||
"LOOKUPP",
|
||||
"LAYOUTERROR",
|
||||
"COPY_NOTIFY",
|
||||
"GETXATTR",
|
||||
"SETXATTR",
|
||||
"LISTXATTRS",
|
||||
"REMOVEXATTR",
|
||||
}
|
||||
|
||||
nfs3Ops := make(map[string]bool)
|
||||
nfs4Ops := make(map[string]bool)
|
||||
|
||||
s.mountstatsPath = s.getMountStatsPath()
|
||||
|
||||
if len(s.IncludeOperations) == 0 {
|
||||
for _, Op := range nfs3Fields {
|
||||
nfs3Ops[Op] = true
|
||||
}
|
||||
for _, Op := range nfs4Fields {
|
||||
nfs4Ops[Op] = true
|
||||
}
|
||||
} else {
|
||||
for _, Op := range s.IncludeOperations {
|
||||
nfs3Ops[Op] = true
|
||||
}
|
||||
for _, Op := range s.IncludeOperations {
|
||||
nfs4Ops[Op] = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.ExcludeOperations) > 0 {
|
||||
for _, Op := range s.ExcludeOperations {
|
||||
if nfs3Ops[Op] {
|
||||
delete(nfs3Ops, Op)
|
||||
}
|
||||
if nfs4Ops[Op] {
|
||||
delete(nfs4Ops, Op)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.nfs3Ops = nfs3Ops
|
||||
s.nfs4Ops = nfs4Ops
|
||||
|
||||
if len(s.IncludeMounts) > 0 {
|
||||
if config.Config.DebugMode {
|
||||
log.Println("D! Including these mount patterns:", s.IncludeMounts)
|
||||
}
|
||||
} else {
|
||||
log.Println("Including all mounts.")
|
||||
if config.Config.DebugMode {
|
||||
log.Println("D! Including all mounts.")
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.ExcludeMounts) > 0 {
|
||||
if config.Config.DebugMode {
|
||||
log.Println("D! Excluding these mount patterns:", s.ExcludeMounts)
|
||||
}
|
||||
} else {
|
||||
if config.Config.DebugMode {
|
||||
log.Println("D! Not excluding any mounts.")
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.IncludeOperations) > 0 {
|
||||
if config.Config.DebugMode {
|
||||
log.Println("D! Including these operations:", s.IncludeOperations)
|
||||
}
|
||||
} else {
|
||||
if config.Config.DebugMode {
|
||||
log.Println("D! Including all operations.")
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.ExcludeOperations) > 0 {
|
||||
if config.Config.DebugMode {
|
||||
log.Println("D! Excluding these mount patterns:", s.ExcludeOperations)
|
||||
}
|
||||
} else {
|
||||
if config.Config.DebugMode {
|
||||
log.Println("D! Not excluding any operations.")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *NfsClient) Drop() {}
|
||||
|
||||
func (s *NfsClient) Gather(slist *types.SampleList) {
|
||||
file, err := os.Open(s.mountstatsPath)
|
||||
if err != nil {
|
||||
log.Println("E! Failed opening the", file, "file:", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
if err := s.processText(scanner, slist); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Println("E!", err)
|
||||
}
|
||||
}
|
||||
func (s *NfsClient) GetInstances() []inputs.Instance {
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertToUint64(line []string) ([]uint64, error) {
|
||||
/* A "line" of input data (a pre-split array of strings) is
|
||||
processed one field at a time. Each field is converted to
|
||||
an uint64 value, and appened to an array of return values.
|
||||
On an error, check for ErrRange, and returns an error
|
||||
if found. This situation indicates a pretty major issue in
|
||||
the /proc/self/mountstats file, and returning faulty data
|
||||
is worse than no data. Other errors are ignored, and append
|
||||
whatever we got in the first place (probably 0).
|
||||
Yes, this is ugly. */
|
||||
|
||||
var nline []uint64
|
||||
|
||||
if len(line) < 2 {
|
||||
return nline, nil
|
||||
}
|
||||
|
||||
// Skip the first field; it's handled specially as the "first" variable
|
||||
for _, l := range line[1:] {
|
||||
val, err := strconv.ParseUint(l, 10, 64)
|
||||
if err != nil {
|
||||
if numError, ok := err.(*strconv.NumError); ok {
|
||||
if numError.Err == strconv.ErrRange {
|
||||
return nil, fmt.Errorf("errrange: line:[%v] raw:[%v] -> parsed:[%v]", line, l, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
nline = append(nline, val)
|
||||
}
|
||||
return nline, nil
|
||||
}
|
||||
|
||||
func (s *NfsClient) parseStat(mountpoint string, export string, version string, line []string, slist *types.SampleList) error {
|
||||
tags := map[string]string{"mountpoint": mountpoint, "serverexport": export}
|
||||
nline, err := convertToUint64(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(nline) == 0 {
|
||||
log.Println("W! Parsing Stat line with one field: %s\n", line)
|
||||
return nil
|
||||
}
|
||||
|
||||
first := strings.Replace(line[0], ":", "", 1)
|
||||
|
||||
var eventsFields = []string{
|
||||
"inoderevalidates",
|
||||
"dentryrevalidates",
|
||||
"datainvalidates",
|
||||
"attrinvalidates",
|
||||
"vfsopen",
|
||||
"vfslookup",
|
||||
"vfsaccess",
|
||||
"vfsupdatepage",
|
||||
"vfsreadpage",
|
||||
"vfsreadpages",
|
||||
"vfswritepage",
|
||||
"vfswritepages",
|
||||
"vfsgetdents",
|
||||
"vfssetattr",
|
||||
"vfsflush",
|
||||
"vfsfsync",
|
||||
"vfslock",
|
||||
"vfsrelease",
|
||||
"congestionwait",
|
||||
"setattrtrunc",
|
||||
"extendwrite",
|
||||
"sillyrenames",
|
||||
"shortreads",
|
||||
"shortwrites",
|
||||
"delay",
|
||||
"pnfsreads",
|
||||
"pnfswrites",
|
||||
}
|
||||
|
||||
var bytesFields = []string{
|
||||
"normalreadbytes",
|
||||
"normalwritebytes",
|
||||
"directreadbytes",
|
||||
"directwritebytes",
|
||||
"serverreadbytes",
|
||||
"serverwritebytes",
|
||||
"readpages",
|
||||
"writepages",
|
||||
}
|
||||
|
||||
var xprtudpFields = []string{
|
||||
"bind_count",
|
||||
"rpcsends",
|
||||
"rpcreceives",
|
||||
"badxids",
|
||||
"inflightsends",
|
||||
"backlogutil",
|
||||
}
|
||||
|
||||
var xprttcpFields = []string{
|
||||
"bind_count",
|
||||
"connect_count",
|
||||
"connect_time",
|
||||
"idle_time",
|
||||
"rpcsends",
|
||||
"rpcreceives",
|
||||
"badxids",
|
||||
"inflightsends",
|
||||
"backlogutil",
|
||||
}
|
||||
|
||||
var nfsopFields = []string{
|
||||
"ops",
|
||||
"trans",
|
||||
"timeouts",
|
||||
"bytes_sent",
|
||||
"bytes_recv",
|
||||
"queue_time",
|
||||
"response_time",
|
||||
"total_time",
|
||||
"errors",
|
||||
}
|
||||
|
||||
var fields = make(map[string]interface{})
|
||||
|
||||
switch first {
|
||||
case "READ", "WRITE":
|
||||
fields["nfsstat_ops"] = nline[0]
|
||||
fields["nfsstat_retrans"] = nline[1] - nline[0]
|
||||
fields["nfsstat_bytes"] = nline[3] + nline[4]
|
||||
fields["nfsstat_rtt"] = nline[6]
|
||||
fields["nfsstat_exe"] = nline[7]
|
||||
fields["nfsstat_rtt_per_op"] = 0.0
|
||||
if nline[0] > 0 {
|
||||
fields["nfsstat_rtt_per_op"] = float64(nline[6]) / float64(nline[0])
|
||||
}
|
||||
tags["nfsstat_operation"] = first
|
||||
slist.PushSamples(inputName, fields, tags)
|
||||
}
|
||||
|
||||
if s.Fullstat {
|
||||
switch first {
|
||||
case "events":
|
||||
if len(nline) >= len(eventsFields) {
|
||||
for i, t := range eventsFields {
|
||||
fields["nfs_events_"+t] = nline[i]
|
||||
}
|
||||
slist.PushSamples(inputName, fields, tags)
|
||||
}
|
||||
|
||||
case "bytes":
|
||||
if len(nline) >= len(bytesFields) {
|
||||
for i, t := range bytesFields {
|
||||
fields["nfs_bytes_"+t] = nline[i]
|
||||
}
|
||||
slist.PushSamples(inputName, fields, tags)
|
||||
}
|
||||
|
||||
case "xprt":
|
||||
if len(line) > 1 {
|
||||
switch line[1] {
|
||||
case "tcp":
|
||||
if len(nline)+2 >= len(xprttcpFields) {
|
||||
for i, t := range xprttcpFields {
|
||||
fields["nfs_xprt_tcp_"+t] = nline[i+2]
|
||||
}
|
||||
slist.PushSamples(inputName, fields, tags)
|
||||
}
|
||||
case "udp":
|
||||
if len(nline)+2 >= len(xprtudpFields) {
|
||||
for i, t := range xprtudpFields {
|
||||
fields["nfs_xprt_udp_"+t] = nline[i+2]
|
||||
}
|
||||
slist.PushSamples(inputName, fields, tags)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (version == "3" && s.nfs3Ops[first]) || (version == "4" && s.nfs4Ops[first]) {
|
||||
tags["operation"] = first
|
||||
if len(nline) <= len(nfsopFields) {
|
||||
for i, t := range nline {
|
||||
fields["nfs_ops_"+nfsopFields[i]] = t
|
||||
}
|
||||
slist.PushSamples(inputName, fields, tags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *NfsClient) processText(scanner *bufio.Scanner, slist *types.SampleList) error {
|
||||
var mount string
|
||||
var version string
|
||||
var export string
|
||||
var skip bool
|
||||
|
||||
for scanner.Scan() {
|
||||
lineString := scanner.Text()
|
||||
line := strings.Fields(lineString)
|
||||
lineLength := len(line)
|
||||
|
||||
if lineLength == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
skip = false
|
||||
|
||||
// This denotes a new mount has been found, so set
|
||||
// mount and export, and stop skipping (for now)
|
||||
if lineLength > 4 && strings.Contains(lineString, "fstype") && (strings.Contains(lineString, "nfs") || strings.Contains(lineString, "nfs4")) {
|
||||
mount = line[4]
|
||||
export = line[1]
|
||||
} else if lineLength > 5 && (strings.Contains(lineString, "(nfs)") || strings.Contains(lineString, "(nfs4)")) {
|
||||
version = strings.Split(strings.Split(lineString, "/")[2], " ")[0]
|
||||
}
|
||||
if mount == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(s.IncludeMounts) > 0 {
|
||||
skip = true
|
||||
for _, RE := range s.IncludeMounts {
|
||||
matched, _ := regexp.MatchString(RE, mount)
|
||||
if matched {
|
||||
skip = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !skip && len(s.ExcludeMounts) > 0 {
|
||||
for _, RE := range s.ExcludeMounts {
|
||||
matched, _ := regexp.MatchString(RE, mount)
|
||||
if matched {
|
||||
skip = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !skip {
|
||||
err := s.parseStat(mount, export, version, line, slist)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parseStat: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *NfsClient) getMountStatsPath() string {
|
||||
path := "/proc/self/mountstats"
|
||||
if os.Getenv("MOUNT_PROC") != "" {
|
||||
path = os.Getenv("MOUNT_PROC")
|
||||
}
|
||||
if config.Config.DebugMode {
|
||||
log.Println("D! using [", path, "] for mountstats")
|
||||
}
|
||||
return path
|
||||
}
|
Loading…
Reference in New Issue