-
Notifications
You must be signed in to change notification settings - Fork 320
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented CIFS client support for procfs
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 <[email protected]>
- Loading branch information
Christian Rebischke
committed
Jan 15, 2019
1 parent
b1a0a9a
commit 1f1c397
Showing
4 changed files
with
583 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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`), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, ¤tSMBBlock.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 | ||
} |
Oops, something went wrong.