internal/fs/wrappers/monitoring.go (308 lines of code) (raw):
// Copyright 2020 Google LLC
//
// 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 wrappers
import (
"context"
"errors"
"syscall"
"time"
"github.com/googlecloudplatform/gcsfuse/v2/common"
"github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil"
)
const name = "cloud.google.com/gcsfuse"
// Error categories
const (
errDevice = "DEVICE_ERROR"
errDirNotEmpty = "DIR_NOT_EMPTY"
errFileExists = "FILE_EXISTS"
errFileDir = "FILE_DIR_ERROR"
errNotImplemented = "NOT_IMPLEMENTED"
errIO = "IO_ERROR"
errInterrupt = "INTERRUPT_ERROR"
errInvalidArg = "INVALID_ARGUMENT"
errInvalidOp = "INVALID_OPERATION"
errMisc = "MISC_ERROR"
errNetwork = "NETWORK_ERROR"
errNoFileOrDir = "NO_FILE_OR_DIR"
errNotADir = "NOT_A_DIR"
errPerm = "PERM_ERROR"
errProcessMgmt = "PROCESS_RESOURCE_MGMT_ERROR"
errTooManyFiles = "TOO_MANY_OPEN_FILES"
)
// categorize maps an error to an error-category.
// This helps reduce the cardinality of the labels to less than 30.
// This lower number of errors allows the various errors to get piped to Cloud metrics without getting dropped.
func categorize(err error) string {
if err == nil {
return ""
}
var errno syscall.Errno
if !errors.As(err, &errno) {
errno = DefaultFSError
}
switch errno {
case syscall.ELNRNG,
syscall.ENODEV,
syscall.ENONET,
syscall.ENOSTR,
syscall.ENOTSOCK,
syscall.ENXIO,
syscall.EPROTO,
syscall.ERFKILL,
syscall.EXDEV:
return errDevice
case syscall.ENOTEMPTY:
return errDirNotEmpty
case syscall.EEXIST:
return errFileExists
case syscall.EBADF,
syscall.EBADFD,
syscall.EFBIG,
syscall.EISDIR,
syscall.EISNAM,
syscall.ENOTBLK:
return errFileDir
case syscall.ENOSYS:
return errNotImplemented
case syscall.EIO:
return errIO
case syscall.ECANCELED,
syscall.EINTR:
return errInterrupt
case syscall.EINVAL:
return errInvalidArg
case syscall.E2BIG,
syscall.EALREADY,
syscall.EBADE,
syscall.EBADR,
syscall.EDOM,
syscall.EINPROGRESS,
syscall.ENOEXEC,
syscall.ENOTSUP,
syscall.ENOTTY,
syscall.ERANGE,
syscall.ESPIPE:
return errInvalidOp
case syscall.EADV,
syscall.EBADSLT,
syscall.EBFONT,
syscall.ECHRNG,
syscall.EDOTDOT,
syscall.EIDRM,
syscall.EILSEQ,
syscall.ELIBACC,
syscall.ELIBBAD,
syscall.ELIBEXEC,
syscall.ELIBMAX,
syscall.ELIBSCN,
syscall.EMEDIUMTYPE,
syscall.ENAVAIL,
syscall.ENOANO,
syscall.ENOCSI,
syscall.ENODATA,
syscall.ENOMEDIUM,
syscall.ENOMSG,
syscall.ENOPKG,
syscall.ENOSR,
syscall.ENOTNAM,
syscall.ENOTRECOVERABLE,
syscall.EOVERFLOW,
syscall.ERESTART,
syscall.ESRMNT,
syscall.ESTALE,
syscall.ETIME,
syscall.ETOOMANYREFS,
syscall.EUCLEAN,
syscall.EUNATCH,
syscall.EXFULL:
return errMisc
case syscall.EADDRINUSE,
syscall.EADDRNOTAVAIL,
syscall.EAFNOSUPPORT,
syscall.EBADMSG,
syscall.EBADRQC,
syscall.ECOMM,
syscall.ECONNABORTED,
syscall.ECONNREFUSED,
syscall.ECONNRESET,
syscall.EDESTADDRREQ,
syscall.EFAULT,
syscall.EHOSTDOWN,
syscall.EHOSTUNREACH,
syscall.EISCONN,
syscall.EL2HLT,
syscall.EL2NSYNC,
syscall.EL3HLT,
syscall.EL3RST,
syscall.EMSGSIZE,
syscall.EMULTIHOP,
syscall.ENETDOWN,
syscall.ENETRESET,
syscall.ENETUNREACH,
syscall.ENOLINK,
syscall.ENOPROTOOPT,
syscall.ENOTCONN,
syscall.ENOTUNIQ,
syscall.EPFNOSUPPORT,
syscall.EPIPE,
syscall.EPROTONOSUPPORT,
syscall.EPROTOTYPE,
syscall.EREMCHG,
syscall.EREMOTE,
syscall.EREMOTEIO,
syscall.ESHUTDOWN,
syscall.ESOCKTNOSUPPORT,
syscall.ESTRPIPE,
syscall.ETIMEDOUT:
return errNetwork
case syscall.ENOENT:
return errNoFileOrDir
case syscall.ENOTDIR:
return errNotADir
case syscall.EACCES,
syscall.EKEYEXPIRED,
syscall.EKEYREJECTED,
syscall.EKEYREVOKED,
syscall.ENOKEY,
syscall.EPERM,
syscall.EROFS,
syscall.ETXTBSY:
return errPerm
case syscall.EAGAIN,
syscall.EBUSY,
syscall.ECHILD,
syscall.EDEADLK,
syscall.EDQUOT,
syscall.ELOOP,
syscall.EMLINK,
syscall.ENAMETOOLONG,
syscall.ENOBUFS,
syscall.ENOLCK,
syscall.ENOMEM,
syscall.ENOSPC,
syscall.EOWNERDEAD,
syscall.ESRCH,
syscall.EUSERS:
return errProcessMgmt
case syscall.EMFILE,
syscall.ENFILE:
return errTooManyFiles
}
return errMisc
}
// Records file system operation count, failed operation count and the operation latency.
func recordOp(ctx context.Context, metricHandle common.MetricHandle, method string, start time.Time, fsErr error) {
metricHandle.OpsCount(ctx, 1, []common.MetricAttr{{Key: common.FSOp, Value: method}})
// Recording opErrorCount.
if fsErr != nil {
errCategory := categorize(fsErr)
metricHandle.OpsErrorCount(ctx, 1, []common.MetricAttr{
{Key: common.FSOp, Value: method},
{Key: common.FSErrCategory, Value: errCategory}},
)
}
metricHandle.OpsLatency(ctx, float64(time.Since(start).Microseconds()), []common.MetricAttr{{Key: common.FSOp, Value: method}})
}
// WithMonitoring takes a FileSystem, returns a FileSystem with monitoring
// on the counts of requests per API.
func WithMonitoring(fs fuseutil.FileSystem, metricHandle common.MetricHandle) fuseutil.FileSystem {
return &monitoring{
wrapped: fs,
metricHandle: metricHandle,
}
}
type monitoring struct {
wrapped fuseutil.FileSystem
metricHandle common.MetricHandle
}
func (fs *monitoring) Destroy() {
fs.wrapped.Destroy()
}
type wrappedCall func(ctx context.Context) error
func (fs *monitoring) invokeWrapped(ctx context.Context, opName string, w wrappedCall) error {
startTime := time.Now()
err := w(ctx)
recordOp(ctx, fs.metricHandle, opName, startTime, err)
return err
}
func (fs *monitoring) StatFS(ctx context.Context, op *fuseops.StatFSOp) error {
return fs.invokeWrapped(ctx, "StatFS", func(ctx context.Context) error { return fs.wrapped.StatFS(ctx, op) })
}
func (fs *monitoring) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) error {
return fs.invokeWrapped(ctx, "LookUpInode", func(ctx context.Context) error { return fs.wrapped.LookUpInode(ctx, op) })
}
func (fs *monitoring) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) error {
return fs.invokeWrapped(ctx, "GetInodeAttributes", func(ctx context.Context) error { return fs.wrapped.GetInodeAttributes(ctx, op) })
}
func (fs *monitoring) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) error {
return fs.invokeWrapped(ctx, "SetInodeAttributes", func(ctx context.Context) error { return fs.wrapped.SetInodeAttributes(ctx, op) })
}
func (fs *monitoring) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) error {
return fs.invokeWrapped(ctx, "ForgetInode", func(ctx context.Context) error { return fs.wrapped.ForgetInode(ctx, op) })
}
func (fs *monitoring) BatchForget(ctx context.Context, op *fuseops.BatchForgetOp) error {
return fs.invokeWrapped(ctx, "BatchForget", func(ctx context.Context) error { return fs.wrapped.BatchForget(ctx, op) })
}
func (fs *monitoring) MkDir(ctx context.Context, op *fuseops.MkDirOp) error {
return fs.invokeWrapped(ctx, "MkDir", func(ctx context.Context) error { return fs.wrapped.MkDir(ctx, op) })
}
func (fs *monitoring) MkNode(ctx context.Context, op *fuseops.MkNodeOp) error {
return fs.invokeWrapped(ctx, "MkNode", func(ctx context.Context) error { return fs.wrapped.MkNode(ctx, op) })
}
func (fs *monitoring) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) error {
return fs.invokeWrapped(ctx, "CreateFile", func(ctx context.Context) error { return fs.wrapped.CreateFile(ctx, op) })
}
func (fs *monitoring) CreateLink(ctx context.Context, op *fuseops.CreateLinkOp) error {
return fs.invokeWrapped(ctx, "CreateLink", func(ctx context.Context) error { return fs.wrapped.CreateLink(ctx, op) })
}
func (fs *monitoring) CreateSymlink(ctx context.Context, op *fuseops.CreateSymlinkOp) error {
return fs.invokeWrapped(ctx, "CreateSymlink", func(ctx context.Context) error { return fs.wrapped.CreateSymlink(ctx, op) })
}
func (fs *monitoring) Rename(ctx context.Context, op *fuseops.RenameOp) error {
return fs.invokeWrapped(ctx, "Rename", func(ctx context.Context) error { return fs.wrapped.Rename(ctx, op) })
}
func (fs *monitoring) RmDir(ctx context.Context, op *fuseops.RmDirOp) error {
return fs.invokeWrapped(ctx, "RmDir", func(ctx context.Context) error { return fs.wrapped.RmDir(ctx, op) })
}
func (fs *monitoring) Unlink(ctx context.Context, op *fuseops.UnlinkOp) error {
return fs.invokeWrapped(ctx, "Unlink", func(ctx context.Context) error { return fs.wrapped.Unlink(ctx, op) })
}
func (fs *monitoring) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) error {
return fs.invokeWrapped(ctx, "OpenDir", func(ctx context.Context) error { return fs.wrapped.OpenDir(ctx, op) })
}
func (fs *monitoring) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) error {
return fs.invokeWrapped(ctx, "ReadDir", func(ctx context.Context) error { return fs.wrapped.ReadDir(ctx, op) })
}
func (fs *monitoring) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) error {
return fs.invokeWrapped(ctx, "ReleaseDirHandle", func(ctx context.Context) error { return fs.wrapped.ReleaseDirHandle(ctx, op) })
}
func (fs *monitoring) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) error {
return fs.invokeWrapped(ctx, "OpenFile", func(ctx context.Context) error { return fs.wrapped.OpenFile(ctx, op) })
}
func (fs *monitoring) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) error {
return fs.invokeWrapped(ctx, "ReadFile", func(ctx context.Context) error { return fs.wrapped.ReadFile(ctx, op) })
}
func (fs *monitoring) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) error {
return fs.invokeWrapped(ctx, "WriteFile", func(ctx context.Context) error { return fs.wrapped.WriteFile(ctx, op) })
}
func (fs *monitoring) SyncFile(ctx context.Context, op *fuseops.SyncFileOp) error {
return fs.invokeWrapped(ctx, "SyncFile", func(ctx context.Context) error { return fs.wrapped.SyncFile(ctx, op) })
}
func (fs *monitoring) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) error {
return fs.invokeWrapped(ctx, "FlushFile", func(ctx context.Context) error { return fs.wrapped.FlushFile(ctx, op) })
}
func (fs *monitoring) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) error {
return fs.invokeWrapped(ctx, "ReleaseFileHandle", func(ctx context.Context) error { return fs.wrapped.ReleaseFileHandle(ctx, op) })
}
func (fs *monitoring) ReadSymlink(ctx context.Context, op *fuseops.ReadSymlinkOp) error {
return fs.invokeWrapped(ctx, "ReadSymlink", func(ctx context.Context) error { return fs.wrapped.ReadSymlink(ctx, op) })
}
func (fs *monitoring) RemoveXattr(ctx context.Context, op *fuseops.RemoveXattrOp) error {
return fs.invokeWrapped(ctx, "RemoveXattr", func(ctx context.Context) error { return fs.wrapped.RemoveXattr(ctx, op) })
}
func (fs *monitoring) GetXattr(ctx context.Context, op *fuseops.GetXattrOp) error {
return fs.invokeWrapped(ctx, "GetXattr", func(ctx context.Context) error { return fs.wrapped.GetXattr(ctx, op) })
}
func (fs *monitoring) ListXattr(ctx context.Context, op *fuseops.ListXattrOp) error {
return fs.invokeWrapped(ctx, "ListXattr", func(ctx context.Context) error { return fs.wrapped.ListXattr(ctx, op) })
}
func (fs *monitoring) SetXattr(ctx context.Context, op *fuseops.SetXattrOp) error {
return fs.invokeWrapped(ctx, "SetXattr", func(ctx context.Context) error { return fs.wrapped.SetXattr(ctx, op) })
}
func (fs *monitoring) Fallocate(ctx context.Context, op *fuseops.FallocateOp) error {
return fs.invokeWrapped(ctx, "Fallocate", func(ctx context.Context) error { return fs.wrapped.Fallocate(ctx, op) })
}
func (fs *monitoring) SyncFS(ctx context.Context, op *fuseops.SyncFSOp) error {
return fs.invokeWrapped(ctx, "SyncFS", func(ctx context.Context) error { return fs.wrapped.SyncFS(ctx, op) })
}