github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/os-instrumented.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"os"
    22  	"strings"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"github.com/minio/madmin-go/v3"
    27  	"github.com/minio/minio/internal/disk"
    28  	ioutilx "github.com/minio/minio/internal/ioutil"
    29  )
    30  
    31  //go:generate stringer -type=osMetric -trimprefix=osMetric $GOFILE
    32  
    33  type osMetric uint8
    34  
    35  const (
    36  	osMetricRemoveAll osMetric = iota
    37  	osMetricMkdirAll
    38  	osMetricMkdir
    39  	osMetricRename
    40  	osMetricOpenFileW
    41  	osMetricOpenFileR
    42  	osMetricOpenFileWFd
    43  	osMetricOpenFileRFd
    44  	osMetricOpen
    45  	osMetricOpenFileDirectIO
    46  	osMetricLstat
    47  	osMetricRemove
    48  	osMetricStat
    49  	osMetricAccess
    50  	osMetricCreate
    51  	osMetricReadDirent
    52  	osMetricFdatasync
    53  	osMetricSync
    54  
    55  	// .... add more
    56  
    57  	osMetricLast
    58  )
    59  
    60  var globalOSMetrics osMetrics
    61  
    62  func init() {
    63  	// Inject metrics.
    64  	ioutilx.OsOpenFile = OpenFile
    65  	ioutilx.OpenFileDirectIO = OpenFileDirectIO
    66  	ioutilx.OsOpen = Open
    67  }
    68  
    69  type osMetrics struct {
    70  	// All fields must be accessed atomically and aligned.
    71  	operations [osMetricLast]uint64
    72  	latency    [osMetricLast]lockedLastMinuteLatency
    73  }
    74  
    75  // time an os action.
    76  func (o *osMetrics) time(s osMetric) func() {
    77  	startTime := time.Now()
    78  	return func() {
    79  		duration := time.Since(startTime)
    80  
    81  		atomic.AddUint64(&o.operations[s], 1)
    82  		o.latency[s].add(duration)
    83  	}
    84  }
    85  
    86  // incTime will increment time on metric s with a specific duration.
    87  func (o *osMetrics) incTime(s osMetric, d time.Duration) {
    88  	atomic.AddUint64(&o.operations[s], 1)
    89  	o.latency[s].add(d)
    90  }
    91  
    92  func osTrace(s osMetric, startTime time.Time, duration time.Duration, path string, err error) madmin.TraceInfo {
    93  	var errStr string
    94  	if err != nil {
    95  		errStr = err.Error()
    96  	}
    97  	return madmin.TraceInfo{
    98  		TraceType: madmin.TraceOS,
    99  		Time:      startTime,
   100  		NodeName:  globalLocalNodeName,
   101  		FuncName:  "os." + s.String(),
   102  		Duration:  duration,
   103  		Path:      path,
   104  		Error:     errStr,
   105  	}
   106  }
   107  
   108  func updateOSMetrics(s osMetric, paths ...string) func(err error) {
   109  	if globalTrace.NumSubscribers(madmin.TraceOS) == 0 {
   110  		osAction := globalOSMetrics.time(s)
   111  		return func(err error) { osAction() }
   112  	}
   113  
   114  	startTime := time.Now()
   115  	return func(err error) {
   116  		duration := time.Since(startTime)
   117  		globalOSMetrics.incTime(s, duration)
   118  		globalTrace.Publish(osTrace(s, startTime, duration, strings.Join(paths, " -> "), err))
   119  	}
   120  }
   121  
   122  // RemoveAll captures time taken to call the underlying os.RemoveAll
   123  func RemoveAll(dirPath string) (err error) {
   124  	defer updateOSMetrics(osMetricRemoveAll, dirPath)(err)
   125  	return os.RemoveAll(dirPath)
   126  }
   127  
   128  // Mkdir captures time taken to call os.Mkdir
   129  func Mkdir(dirPath string, mode os.FileMode) (err error) {
   130  	defer updateOSMetrics(osMetricMkdir, dirPath)(err)
   131  	return os.Mkdir(dirPath, mode)
   132  }
   133  
   134  // MkdirAll captures time taken to call os.MkdirAll
   135  func MkdirAll(dirPath string, mode os.FileMode, baseDir string) (err error) {
   136  	defer updateOSMetrics(osMetricMkdirAll, dirPath)(err)
   137  	return osMkdirAll(dirPath, mode, baseDir)
   138  }
   139  
   140  // Rename captures time taken to call os.Rename
   141  func Rename(src, dst string) (err error) {
   142  	defer updateOSMetrics(osMetricRename, src, dst)(err)
   143  	return RenameSys(src, dst)
   144  }
   145  
   146  // OpenFile captures time taken to call os.OpenFile
   147  func OpenFile(name string, flag int, perm os.FileMode) (f *os.File, err error) {
   148  	switch flag & writeMode {
   149  	case writeMode:
   150  		defer updateOSMetrics(osMetricOpenFileW, name)(err)
   151  	default:
   152  		defer updateOSMetrics(osMetricOpenFileR, name)(err)
   153  	}
   154  	return os.OpenFile(name, flag, perm)
   155  }
   156  
   157  // Access captures time taken to call syscall.Access()
   158  // on windows, plan9 and solaris syscall.Access uses
   159  // os.Lstat()
   160  func Access(name string) (err error) {
   161  	defer updateOSMetrics(osMetricAccess, name)(err)
   162  	return access(name)
   163  }
   164  
   165  // Open captures time taken to call os.Open
   166  func Open(name string) (f *os.File, err error) {
   167  	defer updateOSMetrics(osMetricOpen, name)(err)
   168  	return os.Open(name)
   169  }
   170  
   171  // OpenFileDirectIO captures time taken to call disk.OpenFileDirectIO
   172  func OpenFileDirectIO(name string, flag int, perm os.FileMode) (f *os.File, err error) {
   173  	defer updateOSMetrics(osMetricOpenFileDirectIO, name)(err)
   174  	return disk.OpenFileDirectIO(name, flag, perm)
   175  }
   176  
   177  // Lstat captures time taken to call os.Lstat
   178  func Lstat(name string) (info os.FileInfo, err error) {
   179  	defer updateOSMetrics(osMetricLstat, name)(err)
   180  	return os.Lstat(name)
   181  }
   182  
   183  // Remove captures time taken to call os.Remove
   184  func Remove(deletePath string) (err error) {
   185  	defer updateOSMetrics(osMetricRemove, deletePath)(err)
   186  	return os.Remove(deletePath)
   187  }
   188  
   189  // Stat captures time taken to call os.Stat
   190  func Stat(name string) (info os.FileInfo, err error) {
   191  	defer updateOSMetrics(osMetricStat, name)(err)
   192  	return os.Stat(name)
   193  }
   194  
   195  // Create captures time taken to call os.Create
   196  func Create(name string) (f *os.File, err error) {
   197  	defer updateOSMetrics(osMetricCreate, name)(err)
   198  	return os.Create(name)
   199  }
   200  
   201  // Fdatasync captures time taken to call Fdatasync
   202  func Fdatasync(f *os.File) (err error) {
   203  	fn := ""
   204  	if f != nil {
   205  		fn = f.Name()
   206  	}
   207  	defer updateOSMetrics(osMetricFdatasync, fn)(err)
   208  	return disk.Fdatasync(f)
   209  }
   210  
   211  // report returns all os metrics.
   212  func (o *osMetrics) report() madmin.OSMetrics {
   213  	var m madmin.OSMetrics
   214  	m.CollectedAt = time.Now()
   215  	m.LifeTimeOps = make(map[string]uint64, osMetricLast)
   216  	for i := osMetric(0); i < osMetricLast; i++ {
   217  		if n := atomic.LoadUint64(&o.operations[i]); n > 0 {
   218  			m.LifeTimeOps[i.String()] = n
   219  		}
   220  	}
   221  	if len(m.LifeTimeOps) == 0 {
   222  		m.LifeTimeOps = nil
   223  	}
   224  
   225  	m.LastMinute.Operations = make(map[string]madmin.TimedAction, osMetricLast)
   226  	for i := osMetric(0); i < osMetricLast; i++ {
   227  		lm := o.latency[i].total()
   228  		if lm.N > 0 {
   229  			m.LastMinute.Operations[i.String()] = lm.asTimedAction()
   230  		}
   231  	}
   232  	if len(m.LastMinute.Operations) == 0 {
   233  		m.LastMinute.Operations = nil
   234  	}
   235  
   236  	return m
   237  }