-
Notifications
You must be signed in to change notification settings - Fork 319
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit adds support for CIFS to procfs
Supported are any client statistics in SMB1 and SMB2
- Loading branch information
Christian Rebischke
authored and
Christian Rebischke
committed
Jul 16, 2018
1 parent
ae68e2d
commit 2f5136d
Showing
4 changed files
with
762 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,78 @@ | ||
// 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 SMB1 statistics | ||
type SMB1Stats struct { | ||
SessionIDs SessionIDs | ||
Stats map[string]uint64 | ||
} | ||
|
||
// model for SMB2 statistics | ||
type SMB2Stats struct { | ||
SessionIDs SessionIDs | ||
Stats map[string]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 | ||
SMB1Stats []*SMB1Stats | ||
SMB2Stats []*SMB2Stats | ||
} | ||
|
||
// 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 fixed regex for parsing SMB1 | ||
var regexpSMB1s = [...]*regexp.Regexp{ | ||
regexp.MustCompile(`(?P<sessionID>\d+)\) \\\\(?P<server>[A-Za-z1-9-.]+)(?P<share>.+)`), | ||
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+)`), | ||
} | ||
|
||
// Array with fixed regex for parsing SMB2 | ||
var regexpSMB2s = [...]*regexp.Regexp{ | ||
regexp.MustCompile(`(?P<sessionID>\d+)\) \\\\(?P<server>[A-Za-z1-9-.]+)(?P<share>.+)`), | ||
regexp.MustCompile(`SMBs: (?P<smbs>\d+)`), | ||
regexp.MustCompile(`(?P<keyword>.*): (?P<sent>\d+) sent (?P<failed>\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,228 @@ | ||
// 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" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
// 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) | ||
// Parse header | ||
for scanner.Scan() { | ||
line := scanner.Text() | ||
for _, regexpHeader := range regexpHeaders { | ||
match := regexpHeader.FindStringSubmatch(line) | ||
if 0 == len(match) { | ||
continue | ||
} | ||
for index, name := range regexpHeader.SubexpNames() { | ||
if 0 == index || "" == name { | ||
continue | ||
} | ||
value, err := strconv.ParseUint(match[index], 10, 64) | ||
if nil != err { | ||
continue | ||
} | ||
stats.Header[name] = value | ||
} | ||
break | ||
} | ||
if strings.HasPrefix(line, "Total vfs") { | ||
break | ||
} | ||
} | ||
// Parse Shares | ||
var tmpSMB1Stats *SMB1Stats | ||
var tmpSMB2Stats *SMB2Stats | ||
var tmpSessionIDs *SessionIDs | ||
// The legacy variable sets the current context. True for SMB1, False for SMB2 | ||
legacy := true | ||
for scanner.Scan() { | ||
line := scanner.Text() | ||
if legacy { | ||
// This part manages the parsing of SMB1 | ||
for _, regexpSMB1 := range regexpSMB1s { | ||
match := regexpSMB1.FindStringSubmatch(line) | ||
if 0 == len(match) { | ||
// Check for SMB1 Line: "SMBs: 9 Oplocks breaks: 0" | ||
// If this Check fails we change to SMB2 Statistics | ||
if strings.HasPrefix(line, "SMBs:") && !(strings.Contains(line, "breaks")) { | ||
legacy = false | ||
tmpSMB2Stats = &SMB2Stats{ | ||
Stats: make(map[string]map[string]uint64), | ||
} | ||
stats.SMB2Stats = append(stats.SMB2Stats, tmpSMB2Stats) | ||
re := regexp.MustCompile("[0-9]+") | ||
find_smb := re.FindAllString(line, 1) | ||
tmpSMB2Stats.Stats["smbs"] = make(map[string]uint64) | ||
value, err := strconv.ParseUint(find_smb[0], 10, 64) | ||
if nil != err { | ||
continue | ||
} | ||
tmpSMB2Stats.Stats["smbs"]["smbs"] = value | ||
break | ||
} | ||
continue | ||
} | ||
for index, name := range regexpSMB1.SubexpNames() { | ||
if 0 == index || "" == name { | ||
continue | ||
} | ||
switch name { | ||
case "sessionID": | ||
value, err := strconv.ParseUint(match[index], 10, 64) | ||
if nil != err { | ||
continue | ||
} | ||
tmpSessionIDs = &SessionIDs{ | ||
SessionID: value, | ||
} | ||
case "server": | ||
if "" != match[index] { | ||
tmpSessionIDs.Server = match[index] | ||
} | ||
case "share": | ||
if "" != match[index] { | ||
tmpSessionIDs.Share = match[index] | ||
} | ||
case "smbs": | ||
tmpSMB1Stats = &SMB1Stats{ | ||
Stats: make(map[string]uint64), | ||
} | ||
stats.SMB1Stats = append(stats.SMB1Stats, tmpSMB1Stats) | ||
value, err := strconv.ParseUint(match[index], 10, 64) | ||
if nil != err { | ||
continue | ||
} | ||
tmpSMB1Stats.Stats[name] = value | ||
default: | ||
value, err := strconv.ParseUint(match[index], 10, 64) | ||
if nil != err { | ||
continue | ||
} | ||
if 0 == tmpSMB1Stats.SessionIDs.SessionID { | ||
tmpSMB1Stats.SessionIDs.SessionID = tmpSessionIDs.SessionID | ||
tmpSMB1Stats.SessionIDs.Server = tmpSessionIDs.Server | ||
tmpSMB1Stats.SessionIDs.Share = tmpSessionIDs.Share | ||
|
||
} | ||
tmpSMB1Stats.Stats[name] = value | ||
} | ||
} | ||
break | ||
} | ||
} else { | ||
// This part manages the parsing of SMB2 Shares | ||
var keyword string | ||
for _, regexpSMB2 := range regexpSMB2s { | ||
match := regexpSMB2.FindStringSubmatch(line) | ||
if 0 == len(match) { | ||
// Check for SMB2 Line: "SMBs: 9" | ||
// If this Check fails we change to SMB1 Statistics | ||
if strings.HasPrefix(line, "SMBs:") && strings.Contains(line, "breaks") { | ||
legacy = true | ||
tmpSMB1Stats = &SMB1Stats{ | ||
Stats: make(map[string]uint64), | ||
} | ||
stats.SMB1Stats = append(stats.SMB1Stats, tmpSMB1Stats) | ||
re := regexp.MustCompile("[0-9]+") | ||
find_smb := re.FindAllString(line, 2) | ||
smbs, err := strconv.ParseUint(find_smb[0], 10, 64) | ||
if nil != err { | ||
continue | ||
} | ||
breaks, err := strconv.ParseUint(find_smb[1], 10, 64) | ||
if nil != err { | ||
continue | ||
} | ||
tmpSMB1Stats.Stats["smbs"] = smbs | ||
tmpSMB1Stats.Stats["breaks"] = breaks | ||
|
||
break | ||
} | ||
continue | ||
} | ||
for index, name := range regexpSMB2.SubexpNames() { | ||
if 0 == index || "" == name { | ||
continue | ||
} | ||
switch name { | ||
case "sessionID": | ||
value, err := strconv.ParseUint(match[index], 10, 64) | ||
if nil != err { | ||
continue | ||
} | ||
tmpSessionIDs = &SessionIDs{ | ||
SessionID: value, | ||
} | ||
case "server": | ||
if "" != match[index] { | ||
tmpSessionIDs.Server = match[index] | ||
} | ||
case "share": | ||
if "" != match[index] { | ||
tmpSessionIDs.Share = match[index] | ||
} | ||
case "smbs": | ||
tmpSMB2Stats = &SMB2Stats{ | ||
Stats: make(map[string]map[string]uint64), | ||
} | ||
stats.SMB2Stats = append(stats.SMB2Stats, tmpSMB2Stats) | ||
value, err := strconv.ParseUint(match[index], 10, 64) | ||
if nil != err { | ||
continue | ||
} | ||
tmpSMB2Stats.Stats[name] = make(map[string]uint64) | ||
tmpSMB2Stats.Stats[name][name] = value | ||
|
||
default: | ||
value, err := strconv.ParseUint(match[index], 10, 64) | ||
if nil != err { | ||
keyword = match[index] | ||
tmpSMB2Stats.Stats[keyword] = make(map[string]uint64) | ||
continue | ||
} | ||
if 0 == tmpSMB2Stats.SessionIDs.SessionID { | ||
tmpSMB2Stats.SessionIDs.SessionID = tmpSessionIDs.SessionID | ||
tmpSMB2Stats.SessionIDs.Server = tmpSessionIDs.Server | ||
tmpSMB2Stats.SessionIDs.Share = tmpSessionIDs.Share | ||
|
||
} | ||
tmpSMB2Stats.Stats[keyword][name] = value | ||
} | ||
} | ||
break | ||
} | ||
} | ||
} | ||
|
||
if err := scanner.Err(); err != nil { | ||
return nil, fmt.Errorf("error scanning SMB file: %s", err) | ||
} | ||
|
||
if 0 == len(stats.Header) { | ||
// 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.