252 lines
6.6 KiB
Go
252 lines
6.6 KiB
Go
// Copyright 2019 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 btrfs
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/prometheus/procfs/internal/fs"
|
|
"github.com/prometheus/procfs/internal/util"
|
|
)
|
|
|
|
// SectorSize contains the Linux sector size.
|
|
// > Linux always considers sectors to be 512 bytes long independently
|
|
// > of the devices real block size.
|
|
const SectorSize = 512
|
|
|
|
// FS represents the pseudo-filesystem sys, which provides an interface to
|
|
// kernel data structures.
|
|
type FS struct {
|
|
sys *fs.FS
|
|
}
|
|
|
|
// NewDefaultFS returns a new Bcache using the default sys fs mount point. It will error
|
|
// if the mount point can't be read.
|
|
func NewDefaultFS() (FS, error) {
|
|
return NewFS(fs.DefaultSysMountPoint)
|
|
}
|
|
|
|
// NewFS returns a new Btrfs filesystem using the given sys fs mount point. It will error
|
|
// if the mount point can't be read.
|
|
func NewFS(mountPoint string) (FS, error) {
|
|
if strings.TrimSpace(mountPoint) == "" {
|
|
mountPoint = fs.DefaultSysMountPoint
|
|
}
|
|
sys, err := fs.NewFS(mountPoint)
|
|
if err != nil {
|
|
return FS{}, err
|
|
}
|
|
return FS{&sys}, nil
|
|
}
|
|
|
|
// Stats retrieves Btrfs filesystem runtime statistics for each mounted Btrfs filesystem.
|
|
func (fs FS) Stats() ([]*Stats, error) {
|
|
matches, err := filepath.Glob(fs.sys.Path("fs/btrfs/*-*"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
stats := make([]*Stats, 0, len(matches))
|
|
for _, uuidPath := range matches {
|
|
s, err := GetStats(uuidPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Set the UUID from the path when it could not be retrieved from the filesystem.
|
|
if s.UUID == "" {
|
|
s.UUID = filepath.Base(uuidPath)
|
|
}
|
|
|
|
stats = append(stats, s)
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// GetStats collects all Btrfs statistics from sysfs
|
|
func GetStats(uuidPath string) (*Stats, error) {
|
|
r := &reader{path: uuidPath}
|
|
s := r.readFilesystemStats()
|
|
if r.err != nil {
|
|
return nil, r.err
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
type reader struct {
|
|
path string
|
|
err error
|
|
devCount int
|
|
}
|
|
|
|
// readFile reads a file relative to the path of the reader.
|
|
// Non-existing files are ignored.
|
|
func (r *reader) readFile(n string) string {
|
|
b, err := util.SysReadFile(path.Join(r.path, n))
|
|
if err != nil && !os.IsNotExist(err) {
|
|
r.err = err
|
|
}
|
|
return strings.TrimSpace(string(b))
|
|
}
|
|
|
|
// readValues reads a number of numerical values into an uint64 slice.
|
|
func (r *reader) readValue(n string) (v uint64) {
|
|
// Read value from file
|
|
s := r.readFile(n)
|
|
if r.err != nil {
|
|
return
|
|
}
|
|
|
|
// Convert number
|
|
v, _ = strconv.ParseUint(s, 10, 64)
|
|
return
|
|
}
|
|
|
|
// listFiles returns a list of files for a directory of the reader.
|
|
func (r *reader) listFiles(p string) []string {
|
|
files, err := ioutil.ReadDir(path.Join(r.path, p))
|
|
if err != nil {
|
|
r.err = err
|
|
return nil
|
|
}
|
|
|
|
names := make([]string, len(files))
|
|
for i, f := range files {
|
|
names[i] = f.Name()
|
|
}
|
|
return names
|
|
}
|
|
|
|
// readAllocationStats reads Btrfs allocation data for the current path.
|
|
func (r *reader) readAllocationStats(d string) (a *AllocationStats) {
|
|
// Create a reader for this subdirectory
|
|
sr := &reader{path: path.Join(r.path, d), devCount: r.devCount}
|
|
|
|
// Get the stats
|
|
a = &AllocationStats{
|
|
// Read basic allocation stats
|
|
MayUseBytes: sr.readValue("bytes_may_use"),
|
|
PinnedBytes: sr.readValue("bytes_pinned"),
|
|
ReadOnlyBytes: sr.readValue("bytes_readonly"),
|
|
ReservedBytes: sr.readValue("bytes_reserved"),
|
|
UsedBytes: sr.readValue("bytes_used"),
|
|
DiskUsedBytes: sr.readValue("disk_used"),
|
|
DiskTotalBytes: sr.readValue("disk_total"),
|
|
Flags: sr.readValue("flags"),
|
|
TotalBytes: sr.readValue("total_bytes"),
|
|
TotalPinnedBytes: sr.readValue("total_bytes_pinned"),
|
|
Layouts: sr.readLayouts(),
|
|
}
|
|
|
|
// Pass any error back
|
|
r.err = sr.err
|
|
|
|
return
|
|
}
|
|
|
|
// readLayouts reads all Btrfs layout statistics for the current path
|
|
func (r *reader) readLayouts() map[string]*LayoutUsage {
|
|
files, err := ioutil.ReadDir(r.path)
|
|
if err != nil {
|
|
r.err = err
|
|
return nil
|
|
}
|
|
|
|
m := make(map[string]*LayoutUsage)
|
|
for _, f := range files {
|
|
if f.IsDir() {
|
|
m[f.Name()] = r.readLayout(f.Name())
|
|
}
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
// readLayout reads the Btrfs layout statistics for an allocation layout.
|
|
func (r *reader) readLayout(p string) (l *LayoutUsage) {
|
|
l = new(LayoutUsage)
|
|
l.TotalBytes = r.readValue(path.Join(p, "total_bytes"))
|
|
l.UsedBytes = r.readValue(path.Join(p, "used_bytes"))
|
|
l.Ratio = r.calcRatio(p)
|
|
|
|
return
|
|
}
|
|
|
|
// calcRatio returns the calculated ratio for a layout mode.
|
|
func (r *reader) calcRatio(p string) float64 {
|
|
switch p {
|
|
case "single", "raid0":
|
|
return 1
|
|
case "dup", "raid1", "raid10":
|
|
return 2
|
|
case "raid5":
|
|
return float64(r.devCount) / (float64(r.devCount) - 1)
|
|
case "raid6":
|
|
return float64(r.devCount) / (float64(r.devCount) - 2)
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// readDeviceInfo returns the information for all devices associated with this filesystem.
|
|
func (r *reader) readDeviceInfo(d string) map[string]*Device {
|
|
devs := r.listFiles("devices")
|
|
info := make(map[string]*Device, len(devs))
|
|
for _, n := range devs {
|
|
info[n] = &Device{
|
|
Size: SectorSize * r.readValue("devices/"+n+"/size"),
|
|
}
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
// readFilesystemStats reads Btrfs statistics for a filesystem.
|
|
func (r *reader) readFilesystemStats() (s *Stats) {
|
|
// First get disk info, and add it to reader
|
|
devices := r.readDeviceInfo("devices")
|
|
r.devCount = len(devices)
|
|
|
|
s = &Stats{
|
|
// Read basic filesystem information
|
|
Label: r.readFile("label"),
|
|
UUID: r.readFile("metadata_uuid"),
|
|
Features: r.listFiles("features"),
|
|
CloneAlignment: r.readValue("clone_alignment"),
|
|
NodeSize: r.readValue("nodesize"),
|
|
QuotaOverride: r.readValue("quota_override"),
|
|
SectorSize: r.readValue("sectorsize"),
|
|
|
|
// Device info
|
|
Devices: devices,
|
|
|
|
// Read allocation data
|
|
Allocation: Allocation{
|
|
GlobalRsvReserved: r.readValue("allocation/global_rsv_reserved"),
|
|
GlobalRsvSize: r.readValue("allocation/global_rsv_size"),
|
|
Data: r.readAllocationStats("allocation/data"),
|
|
Metadata: r.readAllocationStats("allocation/metadata"),
|
|
System: r.readAllocationStats("allocation/system"),
|
|
},
|
|
}
|
|
return
|
|
}
|