...

Source file src/github.com/prometheus/procfs/btrfs/get.go

Documentation: github.com/prometheus/procfs/btrfs

     1  // Copyright 2019 The Prometheus Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package btrfs
    15  
    16  import (
    17  	"bufio"
    18  	"fmt"
    19  	"os"
    20  	"path"
    21  	"path/filepath"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"github.com/prometheus/procfs/internal/fs"
    26  	"github.com/prometheus/procfs/internal/util"
    27  )
    28  
    29  // SectorSize contains the Linux sector size.
    30  // > Linux always considers sectors to be 512 bytes long independently
    31  // > of the devices real block size.
    32  const SectorSize = 512
    33  
    34  // FS represents the pseudo-filesystem sys, which provides an interface to
    35  // kernel data structures.
    36  type FS struct {
    37  	sys *fs.FS
    38  }
    39  
    40  // NewDefaultFS returns a new Bcache using the default sys fs mount point. It will error
    41  // if the mount point can't be read.
    42  func NewDefaultFS() (FS, error) {
    43  	return NewFS(fs.DefaultSysMountPoint)
    44  }
    45  
    46  // NewFS returns a new Btrfs filesystem using the given sys fs mount point. It will error
    47  // if the mount point can't be read.
    48  func NewFS(mountPoint string) (FS, error) {
    49  	if strings.TrimSpace(mountPoint) == "" {
    50  		mountPoint = fs.DefaultSysMountPoint
    51  	}
    52  	sys, err := fs.NewFS(mountPoint)
    53  	if err != nil {
    54  		return FS{}, err
    55  	}
    56  	return FS{&sys}, nil
    57  }
    58  
    59  // Stats retrieves Btrfs filesystem runtime statistics for each mounted Btrfs filesystem.
    60  func (fs FS) Stats() ([]*Stats, error) {
    61  	matches, err := filepath.Glob(fs.sys.Path("fs/btrfs/*-*"))
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	stats := make([]*Stats, 0, len(matches))
    67  	for _, uuidPath := range matches {
    68  		s, err := GetStats(uuidPath)
    69  		if err != nil {
    70  			return nil, err
    71  		}
    72  
    73  		// Set the UUID from the path when it could not be retrieved from the filesystem.
    74  		if s.UUID == "" {
    75  			s.UUID = filepath.Base(uuidPath)
    76  		}
    77  
    78  		stats = append(stats, s)
    79  	}
    80  
    81  	return stats, nil
    82  }
    83  
    84  // GetStats collects all Btrfs statistics from sysfs.
    85  func GetStats(uuidPath string) (*Stats, error) {
    86  	r := &reader{path: uuidPath}
    87  	s := r.readFilesystemStats()
    88  	if r.err != nil {
    89  		return nil, r.err
    90  	}
    91  
    92  	return s, nil
    93  }
    94  
    95  type reader struct {
    96  	path     string
    97  	err      error
    98  	devCount int
    99  }
   100  
   101  // readFile reads a file relative to the path of the reader.
   102  // Non-existing files are ignored.
   103  func (r *reader) readFile(n string) string {
   104  	b, err := util.SysReadFile(path.Join(r.path, n))
   105  	if err != nil && !os.IsNotExist(err) {
   106  		r.err = err
   107  	}
   108  	return strings.TrimSpace(string(b))
   109  }
   110  
   111  // readValues reads a number of numerical values into an uint64 slice.
   112  func (r *reader) readValue(n string) (v uint64) {
   113  	// Read value from file
   114  	s := r.readFile(n)
   115  	if r.err != nil {
   116  		return
   117  	}
   118  
   119  	// Convert number
   120  	v, _ = strconv.ParseUint(s, 10, 64)
   121  	return
   122  }
   123  
   124  // listFiles returns a list of files for a directory of the reader.
   125  func (r *reader) listFiles(p string) []string {
   126  	files, err := os.ReadDir(path.Join(r.path, p))
   127  	if err != nil {
   128  		r.err = err
   129  		return nil
   130  	}
   131  
   132  	names := make([]string, len(files))
   133  	for i, f := range files {
   134  		names[i] = f.Name()
   135  	}
   136  	return names
   137  }
   138  
   139  // readAllocationStats reads Btrfs allocation data for the current path.
   140  func (r *reader) readAllocationStats(d string) (a *AllocationStats) {
   141  	// Create a reader for this subdirectory
   142  	sr := &reader{path: path.Join(r.path, d), devCount: r.devCount}
   143  
   144  	// Get the stats
   145  	a = &AllocationStats{
   146  		// Read basic allocation stats
   147  		MayUseBytes:      sr.readValue("bytes_may_use"),
   148  		PinnedBytes:      sr.readValue("bytes_pinned"),
   149  		ReadOnlyBytes:    sr.readValue("bytes_readonly"),
   150  		ReservedBytes:    sr.readValue("bytes_reserved"),
   151  		UsedBytes:        sr.readValue("bytes_used"),
   152  		DiskUsedBytes:    sr.readValue("disk_used"),
   153  		DiskTotalBytes:   sr.readValue("disk_total"),
   154  		Flags:            sr.readValue("flags"),
   155  		TotalBytes:       sr.readValue("total_bytes"),
   156  		TotalPinnedBytes: sr.readValue("total_bytes_pinned"),
   157  		Layouts:          sr.readLayouts(),
   158  	}
   159  
   160  	// Pass any error back
   161  	r.err = sr.err
   162  
   163  	return
   164  }
   165  
   166  // readLayouts reads all Btrfs layout statistics for the current path.
   167  func (r *reader) readLayouts() map[string]*LayoutUsage {
   168  	files, err := os.ReadDir(r.path)
   169  	if err != nil {
   170  		r.err = err
   171  		return nil
   172  	}
   173  
   174  	m := make(map[string]*LayoutUsage)
   175  	for _, f := range files {
   176  		if f.IsDir() {
   177  			m[f.Name()] = r.readLayout(f.Name())
   178  		}
   179  	}
   180  
   181  	return m
   182  }
   183  
   184  // readLayout reads the Btrfs layout statistics for an allocation layout.
   185  func (r *reader) readLayout(p string) (l *LayoutUsage) {
   186  	l = new(LayoutUsage)
   187  	l.TotalBytes = r.readValue(path.Join(p, "total_bytes"))
   188  	l.UsedBytes = r.readValue(path.Join(p, "used_bytes"))
   189  	l.Ratio = r.calcRatio(p)
   190  
   191  	return
   192  }
   193  
   194  // calcRatio returns the calculated ratio for a layout mode.
   195  func (r *reader) calcRatio(p string) float64 {
   196  	switch p {
   197  	case "single", "raid0":
   198  		return 1
   199  	case "dup", "raid1", "raid10":
   200  		return 2
   201  	case "raid5":
   202  		return float64(r.devCount) / (float64(r.devCount) - 1)
   203  	case "raid6":
   204  		return float64(r.devCount) / (float64(r.devCount) - 2)
   205  	default:
   206  		return 0
   207  	}
   208  }
   209  
   210  // readDeviceInfo returns the information for all devices associated with this filesystem.
   211  func (r *reader) readDeviceInfo(d string) map[string]*Device {
   212  	devs := r.listFiles(d)
   213  	info := make(map[string]*Device, len(devs))
   214  	for _, n := range devs {
   215  		info[n] = &Device{
   216  			Size: SectorSize * r.readValue("devices/"+n+"/size"),
   217  		}
   218  	}
   219  
   220  	return info
   221  }
   222  
   223  // readFilesystemStats reads Btrfs statistics for a filesystem.
   224  func (r *reader) readFilesystemStats() (s *Stats) {
   225  	// First get disk info, and add it to reader
   226  	devices := r.readDeviceInfo("devices")
   227  	r.devCount = len(devices)
   228  
   229  	s = &Stats{
   230  		// Read basic filesystem information
   231  		Label:          r.readFile("label"),
   232  		UUID:           r.readFile("metadata_uuid"),
   233  		Features:       r.listFiles("features"),
   234  		CloneAlignment: r.readValue("clone_alignment"),
   235  		NodeSize:       r.readValue("nodesize"),
   236  		QuotaOverride:  r.readValue("quota_override"),
   237  		SectorSize:     r.readValue("sectorsize"),
   238  
   239  		// Device info
   240  		Devices: devices,
   241  
   242  		// Read allocation data
   243  		Allocation: Allocation{
   244  			GlobalRsvReserved: r.readValue("allocation/global_rsv_reserved"),
   245  			GlobalRsvSize:     r.readValue("allocation/global_rsv_size"),
   246  			Data:              r.readAllocationStats("allocation/data"),
   247  			Metadata:          r.readAllocationStats("allocation/metadata"),
   248  			System:            r.readAllocationStats("allocation/system"),
   249  		},
   250  
   251  		// Read commit stats data
   252  		CommitStats: r.readCommitStats("commit_stats"),
   253  	}
   254  	return
   255  }
   256  
   257  // readCommitStats returns the commit_stats information for commit stats metrics.
   258  func (r *reader) readCommitStats(p string) CommitStats {
   259  	stats := CommitStats{}
   260  
   261  	f, err := os.Open(path.Join(r.path, p))
   262  	if err != nil {
   263  		// if commit_stats not found. maybe btrfs version < 6.0
   264  		if !os.IsNotExist(err) {
   265  			r.err = err
   266  		}
   267  		return stats
   268  	}
   269  	defer f.Close()
   270  
   271  	scanner := bufio.NewScanner(f)
   272  
   273  	for scanner.Scan() {
   274  		line := scanner.Text()
   275  		parts := strings.Fields(scanner.Text())
   276  		// require  <key> <value>
   277  		if len(parts) != 2 {
   278  			r.err = fmt.Errorf("invalid commit_stats line %q", line)
   279  			return stats
   280  		}
   281  
   282  		value, err := strconv.ParseUint(parts[1], 10, 64)
   283  		if err != nil {
   284  			r.err = fmt.Errorf("error parsing commit_stats line: %w", err)
   285  			return stats
   286  		}
   287  
   288  		switch metricName := parts[0]; metricName {
   289  		case "commits":
   290  			stats.Commits = value
   291  		case "last_commit_ms":
   292  			stats.LastCommitMs = value
   293  		case "max_commit_ms":
   294  			stats.MaxCommitMs = value
   295  		case "total_commit_ms":
   296  			stats.TotalCommitMs = value
   297  		default:
   298  			continue
   299  		}
   300  	}
   301  
   302  	if err := scanner.Err(); err != nil {
   303  		r.err = fmt.Errorf("error scanning commit_stats file: %w", err)
   304  		return stats
   305  	}
   306  
   307  	return stats
   308  }
   309  

View as plain text