Skip to content

Commit

Permalink
Implemented CIFS client support for procfs
Browse files Browse the repository at this point in the history
This commit adds CIFS/SMB client support for procfs.
It support SMB version 1,2 and 3.
We parse /proc/fs/cifs/stats for CIFS and SMB statistics.

Signed-off-by: Christian Rebischke <chris@nullday.de>
  • Loading branch information
Christian Rebischke committed Feb 9, 2019
1 parent b1a0a9a commit b862ab7
Show file tree
Hide file tree
Showing 4 changed files with 583 additions and 0 deletions.
85 changes: 85 additions & 0 deletions cifs/cifs.go
@@ -0,0 +1,85 @@
// Copyright 2018 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.

// Package cifs implements parsing of /proc/fs/cifs/Stats
// Fields are documented in https://www.kernel.org/doc/readme/Documentation-filesystems-cifs-README

package cifs

import "regexp"

// model for the SMB statistics
type SMBStats struct {
SessionIDs SessionIDs
Stats map[string]uint64
}

// model for the Share sessionID "number) \\server\share"
type SessionIDs struct {
SessionID uint64
Server string
Share string
}

// model for the CIFS header statistics
type ClientStats struct {
Header map[string]uint64
SMBStatsList []*SMBStats
}

// Array with fixed regex for parsing the SMB stats header
var regexpHeaders = [...]*regexp.Regexp{
regexp.MustCompile(`CIFS Session: (?P<sessions>\d+)`),
regexp.MustCompile(`Share \(unique mount targets\): (?P<shares>\d+)`),
regexp.MustCompile(`SMB Request/Response Buffer: (?P<smbBuffer>\d+) Pool size: (?P<smbPoolSize>\d+)`),
regexp.MustCompile(`SMB Small Req/Resp Buffer: (?P<smbSmallBuffer>\d+) Pool size: (?P<smbSmallPoolSize>\d+)`),
regexp.MustCompile(`Operations \(MIDs\): (?P<operations>\d+)`),
regexp.MustCompile(`(?P<sessionCount>\d+) session (?P<shareReconnects>\d+) share reconnects`),
regexp.MustCompile(`Total vfs operations: (?P<totalOperations>\d+) maximum at one time: (?P<totalMaxOperations>\d+)`),
}

// Array with regex for parsing SMB
var regexpSMBs = [...]*regexp.Regexp{
regexp.MustCompile(`(?P<sessionID>\d+)\) \\\\(?P<server>[A-Za-z1-9-.]+)(?P<share>.+)`),
// Match SMB2 "flushes" line first. Otherwise we will get a mismatch.
regexp.MustCompile(`Flushes: (?P<flushesSent>\d+) sent (?P<flushesFailed>\d+) failed`),
regexp.MustCompile(`SMBs: (?P<smbs>\d+) Oplocks breaks: (?P<breaks>\d+)`),
regexp.MustCompile(`Reads: (?P<reads>\d+) Bytes: (?P<readsBytes>\d+)`),
regexp.MustCompile(`Writes: (?P<writes>\d+) Bytes: (?P<writesBytes>\d+)`),
regexp.MustCompile(`Flushes: (?P<flushes>\d+)`),
regexp.MustCompile(`Locks: (?P<locks>\d+) HardLinks: (?P<hardlinks>\d+) Symlinks: (?P<symlinks>\d+)`),
regexp.MustCompile(`Opens: (?P<opens>\d+) Closes: (?P<closes>\d+) Deletes: (?P<deletes>\d+)`),
regexp.MustCompile(`Posix Opens: (?P<posixOpens>\d+) Posix Mkdirs: (?P<posixMkdirs>\d+)`),
regexp.MustCompile(`Mkdirs: (?P<mkdirs>\d+) Rmdirs: (?P<rmdirs>\d+)`),
regexp.MustCompile(`Renames: (?P<renames>\d+) T2 Renames (?P<t2Renames>\d+)`),
regexp.MustCompile(`FindFirst: (?P<findFirst>\d+) FNext (?P<fNext>\d+) FClose (?P<fClose>\d+)`),
regexp.MustCompile(`SMBs: (?P<smbs>\d+)`),
regexp.MustCompile(`Negotiates: (?P<negotiatesSent>\d+) sent (?P<negotiatesFailed>\d+) failed`),
regexp.MustCompile(`SessionSetups: (?P<sessionSetupsSent>\d+) sent (?P<sessionSetupsFailed>\d+) failed`),
regexp.MustCompile(`Logoffs: (?P<logoffsSent>\d+) sent (?P<logoffsFailed>\d+) failed`),
regexp.MustCompile(`TreeConnects: (?P<treeConnectsSent>\d+) sent (?P<treeConnectsFailed>\d+) failed`),
regexp.MustCompile(`TreeDisconnects: (?P<treeDisconnectsSent>\d+) sent (?P<treeDisconnectsFailed>\d+) failed`),
regexp.MustCompile(`Creates: (?P<createsSent>\d+) sent (?P<createsFailed>\d+) failed`),
regexp.MustCompile(`Closes: (?P<closesSent>\d+) sent (?P<closesFailed>\d+) failed`),
regexp.MustCompile(`Reads: (?P<readsSent>\d+) sent (?P<readsFailed>\d+) failed`),
regexp.MustCompile(`Writes: (?P<writesSent>\d+) sent (?P<writesFailed>\d+) failed`),
regexp.MustCompile(`Locks: (?P<locksSent>\d+) sent (?P<locksFailed>\d+) failed`),
regexp.MustCompile(`IOCTLs: (?P<ioCTLsSent>\d+) sent (?P<ioCTLsFailed>\d+) failed`),
regexp.MustCompile(`Cancels: (?P<cancelsSent>\d+) sent (?P<cancelsFailed>\d+) failed`),
regexp.MustCompile(`Echos: (?P<echosSent>\d+) sent (?P<echosFailed>\d+) failed`),
regexp.MustCompile(`QueryDirectories: (?P<queryDirectoriesSent>\d+) sent (?P<queryDirectoriesFailed>\d+) failed`),
regexp.MustCompile(`ChangeNotifies: (?P<changeNotifiesSent>\d+) sent (?P<changeNotifiesFailed>\d+) failed`),
regexp.MustCompile(`QueryInfos: (?P<queryInfosSent>\d+) sent (?P<queryInfosFailed>\d+) failed`),
regexp.MustCompile(`SetInfos: (?P<setInfosSent>\d+) sent (?P<setInfosFailed>\d+) failed`),
regexp.MustCompile(`OplockBreaks: (?P<oplockBreaksSent>\d+) sent (?P<oplockBreaksFailed>\d+) failed`),
}
124 changes: 124 additions & 0 deletions cifs/parse_cifs.go
@@ -0,0 +1,124 @@
// Copyright 2018 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.

package cifs

import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)

// parseHeader parses our SMB header
func parseHeader(line string, header map[string]uint64) error {
for _, regexpHeader := range regexpHeaders {
match := regexpHeader.FindStringSubmatch(line)
if match == nil {
continue
}
for index, name := range regexpHeader.SubexpNames() {
if index == 0 || name == "" {
continue
}
value, err := strconv.ParseUint(match[index], 10, 64)
if nil != err {
return fmt.Errorf("invalid value in header")
}
header[name] = value
}
}
return nil
}

// parseSMBStats parses a SMB block
func parseSMBStats(line string, stats map[string]uint64, sessionIDs *SessionIDs) error {
for _, regexpSMB := range regexpSMBs {
match := regexpSMB.FindStringSubmatch(line)
if match == nil {
continue
}
for index, name := range regexpSMB.SubexpNames() {
if index == 0 || name == "" {
continue
}
switch name {
case "sessionID":
value, err := strconv.ParseUint(match[index], 10, 64)
if nil != err {
return fmt.Errorf("type mismatch for sessionID")
}
sessionIDs.SessionID = value
case "server":
if match[index] != "" {
sessionIDs.Server = match[index]
}
case "share":
if match[index] != "" {
sessionIDs.Share = match[index]
}
default:
value, err := strconv.ParseUint(match[index], 10, 64)
if nil != err {
return fmt.Errorf("invalid value in SMB Statistics")
}
stats[name] = value
}
}
return nil
}
return nil
}

// ParseClientStats returns stats read from /proc/fs/cifs/Stats
func ParseClientStats(r io.Reader) (*ClientStats, error) {
stats := &ClientStats{}
stats.Header = make(map[string]uint64)
scanner := bufio.NewScanner(r)
var currentSMBBlock *SMBStats
var currentSMBMetrics map[string]uint64
var currentSMBSessionIDs *SessionIDs
for scanner.Scan() {
line := scanner.Text()
// if line is empty we can go back to start
if line == "" {
continue
}
parseHeader(line, stats.Header)
// If we see a new SMB block we are initializing all necessary structs and hashmaps
if strings.Contains(line, ") \\") {
currentSMBMetrics = make(map[string]uint64)
currentSMBSessionIDs = &SessionIDs{}
currentSMBBlock = &SMBStats{
SessionIDs: *currentSMBSessionIDs,
Stats: currentSMBMetrics,
}
stats.SMBStatsList = append(stats.SMBStatsList, currentSMBBlock)
}
// Only parseSMBStats if we have a SMB block
if currentSMBSessionIDs != nil {
parseSMBStats(line, currentSMBMetrics, &currentSMBBlock.SessionIDs)
}
}

if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error scanning SMB file: %s", err)
}

if len(stats.Header) == 0 {
// We should never have an empty Header. Otherwise the file is invalid
return nil, fmt.Errorf("error scanning SMB file: header is empty")
}
return stats, nil
}

0 comments on commit b862ab7

Please sign in to comment.