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 }