S3: add metrics

This commit is contained in:
Nicola Murino 2020-01-23 23:17:00 +01:00
parent d481294519
commit 5f4efc9148
6 changed files with 197 additions and 25 deletions

View file

@ -372,7 +372,7 @@ The program must write the questions on its standard output, in a single line, u
- `instruction`, string. A short description to show to the user that is trying to authenticate. Can be empty or omitted - `instruction`, string. A short description to show to the user that is trying to authenticate. Can be empty or omitted
- `questions`, list of questions to be asked to the user - `questions`, list of questions to be asked to the user
- `echos` list of boolean flags corresponding to the questions (so the lengths of both lists must be the same) and indicating whether user's reply for a particular question should be echoed on the screen while they are typing: true if it should be echoed, or false if it should be hidden. - `echos` list of boolean flags corresponding to the questions (so the lengths of both lists must be the same) and indicating whether user's reply for a particular question should be echoed on the screen while they are typing: true if it should be echoed, or false if it should be hidden.
- `auth_result`, integer. Set this field to 1 to indicate successfull authentication, 0 is ignored, any other value means authentication error. If this fields is found and it is different from 0 then SFTPGo does not read any other questions from the external program and finalize the authentication. - `auth_result`, integer. Set this field to 1 to indicate successful authentication, 0 is ignored, any other value means authentication error. If this fields is found and it is different from 0 then SFTPGo does not read any other questions from the external program and finalize the authentication.
SFTPGo writes the user answers to the program standard input, one per line, in the same order of the questions. SFTPGo writes the user answers to the program standard input, one per line, in the same order of the questions.
Please be sure that your program receive the answers for all the issued questions before asking for the next ones. Please be sure that your program receive the answers for all the issued questions before asking for the next ones.
@ -411,7 +411,7 @@ Actions will not be executed if an error is detected and so a partial file is up
The `command`, if defined, is invoked with the following arguments: The `command`, if defined, is invoked with the following arguments:
- `action`, string, possibile values are: `download`, `upload`, `delete`, `rename`, `ssh_cmd` - `action`, string, possible values are: `download`, `upload`, `delete`, `rename`, `ssh_cmd`
- `username` - `username`
- `path` is the full filesystem path, can be empty for some ssh commands - `path` is the full filesystem path, can be empty for some ssh commands
- `target_path`, non empty for `rename` action - `target_path`, non empty for `rename` action
@ -446,7 +446,7 @@ Actions will not be fired for internal updates such as the last login or the use
The `command`, if defined, is invoked with the following arguments: The `command`, if defined, is invoked with the following arguments:
- `action`, string, possibile values are: `add`, `update`, `delete` - `action`, string, possible values are: `add`, `update`, `delete`
- `username` - `username`
- `ID` - `ID`
- `status` - `status`

2
go.mod
View file

@ -27,4 +27,4 @@ require (
gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0
) )
replace github.com/eikenb/pipeat v0.0.0-20190316224601-fb1f3a9aa29f => github.com/drakkan/pipeat v0.0.0-20200122173221-ea03f92ba172 replace github.com/eikenb/pipeat v0.0.0-20190316224601-fb1f3a9aa29f => github.com/drakkan/pipeat v0.0.0-20200123131427-11c048cfc0ec

4
go.sum
View file

@ -32,8 +32,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/drakkan/pipeat v0.0.0-20200122173221-ea03f92ba172 h1:0mSDVf/0IPuuy3c5Qg+ceoxgbV7KUoLN3Ircswglf7A= github.com/drakkan/pipeat v0.0.0-20200123131427-11c048cfc0ec h1:DXfzg1NXoesnFzdCyyi2uU3o1o0XiWTN2ZcpWDE7MCk=
github.com/drakkan/pipeat v0.0.0-20200122173221-ea03f92ba172/go.mod h1:wNYvIpR5rIhoezOYcpxcXz4HbIEOu7A45EqlQCA+h+w= github.com/drakkan/pipeat v0.0.0-20200123131427-11c048cfc0ec/go.mod h1:wNYvIpR5rIhoezOYcpxcXz4HbIEOu7A45EqlQCA+h+w=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=

View file

@ -19,40 +19,40 @@ var (
Help: "Total number of logged in users", Help: "Total number of logged in users",
}) })
// totalUploads is the metric that reports the total number of uploads // totalUploads is the metric that reports the total number of successful SFTP/SCP uploads
totalUploads = promauto.NewCounter(prometheus.CounterOpts{ totalUploads = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_uploads_total", Name: "sftpgo_uploads_total",
Help: "The total number of uploads", Help: "The total number of successful SFTP/SCP uploads",
}) })
// totalDownloads is the metric that reports the total number of downloads // totalDownloads is the metric that reports the total number of successful SFTP/SCP downloads
totalDownloads = promauto.NewCounter(prometheus.CounterOpts{ totalDownloads = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_downloads_total", Name: "sftpgo_downloads_total",
Help: "The total number of downloads", Help: "The total number of successful SFTP/SCP downloads",
}) })
// totalUploadErrors is the metric that reports the total number of upload errors // totalUploadErrors is the metric that reports the total number of SFTP/SCP upload errors
totalUploadErrors = promauto.NewCounter(prometheus.CounterOpts{ totalUploadErrors = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_upload_errors_total", Name: "sftpgo_upload_errors_total",
Help: "The total number of upload errors", Help: "The total number of SFTP/SCP upload errors",
}) })
// totalDownloadErrors is the metric that reports the total number of download errors // totalDownloadErrors is the metric that reports the total number of SFTP/SCP download errors
totalDownloadErrors = promauto.NewCounter(prometheus.CounterOpts{ totalDownloadErrors = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_download_errors_total", Name: "sftpgo_download_errors_total",
Help: "The total number of download errors", Help: "The total number of SFTP/SCP download errors",
}) })
// totalUploadSize is the metric that reports the total uploads size as bytes // totalUploadSize is the metric that reports the total SFTP/SCP uploads size as bytes
totalUploadSize = promauto.NewCounter(prometheus.CounterOpts{ totalUploadSize = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_upload_size", Name: "sftpgo_upload_size",
Help: "The total upload size as bytes", Help: "The total SFTP/SCP upload size as bytes, partial uploads are included",
}) })
// totalDownloadSize is the metric that reports the total downloads size as bytes // totalDownloadSize is the metric that reports the total SFTP/SCP downloads size as bytes
totalDownloadSize = promauto.NewCounter(prometheus.CounterOpts{ totalDownloadSize = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_download_size", Name: "sftpgo_download_size",
Help: "The total download size as bytes", Help: "The total SFTP/SCP download size as bytes, partial downloads are included",
}) })
// totalSSHCommands is the metric that reports the total number of executed SSH commands // totalSSHCommands is the metric that reports the total number of executed SSH commands
@ -167,26 +167,188 @@ var (
Name: "sftpgo_http_server_errors_total", Name: "sftpgo_http_server_errors_total",
Help: "The total number of HTTP requests served with 5xx status code", Help: "The total number of HTTP requests served with 5xx status code",
}) })
// totalS3Uploads is the metric that reports the total number of successful S3 uploads
totalS3Uploads = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_uploads_total",
Help: "The total number of successful S3 uploads",
})
// totalS3Downloads is the metric that reports the total number of successful S3 downloads
totalS3Downloads = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_downloads_total",
Help: "The total number of successful S3 downloads",
})
// totalS3UploadErrors is the metric that reports the total number of S3 upload errors
totalS3UploadErrors = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_upload_errors_total",
Help: "The total number of S3 upload errors",
})
// totalS3DownloadErrors is the metric that reports the total number of S3 download errors
totalS3DownloadErrors = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_download_errors_total",
Help: "The total number of S3 download errors",
})
// totalS3UploadSize is the metric that reports the total S3 uploads size as bytes
totalS3UploadSize = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_upload_size",
Help: "The total S3 upload size as bytes, partial uploads are included",
})
// totalS3DownloadSize is the metric that reports the total S3 downloads size as bytes
totalS3DownloadSize = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_download_size",
Help: "The total S3 download size as bytes, partial downloads are included",
})
// totalS3ListObjects is the metric that reports the total successful S3 list objects requests
totalS3ListObjects = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_list_objects",
Help: "The total number of successful S3 list objects requests",
})
// totalS3CopyObject is the metric that reports the total successful S3 copy object requests
totalS3CopyObject = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_copy_object",
Help: "The total number of successful S3 copy object requests",
})
// totalS3DeleteObject is the metric that reports the total successful S3 delete object requests
totalS3DeleteObject = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_delete_object",
Help: "The total number of successful S3 delete object requests",
})
// totalS3ListObjectsError is the metric that reports the total S3 list objects errors
totalS3ListObjectsErrors = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_list_objects_errors",
Help: "The total number of S3 list objects errors",
})
// totalS3CopyObjectErrors is the metric that reports the total S3 copy object errors
totalS3CopyObjectErrors = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_copy_object_errors",
Help: "The total number of S3 copy object errors",
})
// totalS3DeleteObjectErrors is the metric that reports the total S3 delete object errors
totalS3DeleteObjectErrors = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_delete_object_errors",
Help: "The total number of S3 delete object errors",
})
// totalS3HeadBucket is the metric that reports the total successful S3 head bucket requests
totalS3HeadBucket = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_head_bucket",
Help: "The total number of successful S3 head bucket requests",
})
// totalS3CreateBucket is the metric that reports the total successful S3 create bucket requests
totalS3CreateBucket = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_create_bucket",
Help: "The total number of successful S3 create bucket requests",
})
// totalS3HeadBucketErrors is the metric that reports the total S3 head bucket errors
totalS3HeadBucketErrors = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_head_bucket_errors",
Help: "The total number of S3 head bucket errors",
})
// totalS3CreateBucketErrors is the metric that reports the total S3 create bucket errors
totalS3CreateBucketErrors = promauto.NewCounter(prometheus.CounterOpts{
Name: "sftpgo_s3_create_bucket_errors",
Help: "The total number of S3 create bucket errors",
})
) )
// TransferCompleted update metrics after an upload or a download // TransferCompleted updates metrics after an upload or a download
func TransferCompleted(bytesSent, bytesReceived int64, transferKind int, err error) { func TransferCompleted(bytesSent, bytesReceived int64, transferKind int, err error) {
if transferKind == 0 { if transferKind == 0 {
// upload // upload
if err == nil { if err == nil {
totalUploads.Inc() totalUploads.Inc()
totalUploadSize.Add(float64(bytesReceived))
} else { } else {
totalUploadErrors.Inc() totalUploadErrors.Inc()
} }
totalUploadSize.Add(float64(bytesReceived))
} else { } else {
// download // download
if err == nil { if err == nil {
totalDownloads.Inc() totalDownloads.Inc()
totalDownloadSize.Add(float64(bytesSent))
} else { } else {
totalDownloadErrors.Inc() totalDownloadErrors.Inc()
} }
totalDownloadSize.Add(float64(bytesSent))
}
}
// S3TransferCompleted updates metrics after an S3 upload or a download
func S3TransferCompleted(bytes int64, transferKind int, err error) {
if transferKind == 0 {
// upload
if err == nil {
totalS3Uploads.Inc()
} else {
totalS3UploadErrors.Inc()
}
totalS3UploadSize.Add(float64(bytes))
} else {
// download
if err == nil {
totalS3Downloads.Inc()
} else {
totalS3DownloadErrors.Inc()
}
totalS3DownloadSize.Add(float64(bytes))
}
}
// S3ListObjectsCompleted updates metrics after an S3 list objects request terminates
func S3ListObjectsCompleted(err error) {
if err == nil {
totalS3ListObjects.Inc()
} else {
totalS3ListObjectsErrors.Inc()
}
}
// S3CopyObjectCompleted updates metrics after an S3 copy object request terminates
func S3CopyObjectCompleted(err error) {
if err == nil {
totalS3CopyObject.Inc()
} else {
totalS3CopyObjectErrors.Inc()
}
}
// S3DeleteObjectCompleted updates metrics after an S3 delete object request terminates
func S3DeleteObjectCompleted(err error) {
if err == nil {
totalS3DeleteObject.Inc()
} else {
totalS3DeleteObjectErrors.Inc()
}
}
// S3HeadBucketCompleted updates metrics after an S3 head bucket request terminates
func S3HeadBucketCompleted(err error) {
if err == nil {
totalS3HeadBucket.Inc()
} else {
totalS3HeadBucketErrors.Inc()
}
}
// S3CreateBucketCompleted updates metrics after an S3 create bucket request terminates
func S3CreateBucketCompleted(err error) {
if err == nil {
totalS3CreateBucket.Inc()
} else {
totalS3CreateBucketErrors.Inc()
} }
} }

View file

@ -132,6 +132,7 @@ func (t *Transfer) Close() error {
numFiles = 1 numFiles = 1
} }
t.checkDownloadSize() t.checkDownloadSize()
metrics.TransferCompleted(t.bytesSent, t.bytesReceived, t.transferType, t.transferError)
if t.transferType == transferUpload && t.file != nil && t.file.Name() != t.path { if t.transferType == transferUpload && t.file != nil && t.file.Name() != t.path {
if t.transferError == nil || uploadMode == uploadModeAtomicWithResume { if t.transferError == nil || uploadMode == uploadModeAtomicWithResume {
err = os.Rename(t.file.Name(), t.path) err = os.Rename(t.file.Name(), t.path)
@ -162,7 +163,6 @@ func (t *Transfer) Close() error {
err = t.transferError err = t.transferError
} }
} }
metrics.TransferCompleted(t.bytesSent, t.bytesReceived, t.transferType, t.transferError)
removeTransfer(t) removeTransfer(t)
t.updateQuota(numFiles) t.updateQuota(numFiles)
return err return err

View file

@ -16,6 +16,7 @@ import (
"github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/metrics"
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
"github.com/eikenb/pipeat" "github.com/eikenb/pipeat"
) )
@ -138,6 +139,7 @@ func (fs S3Fs) Stat(name string) (os.FileInfo, error) {
} }
return true return true
}) })
metrics.S3ListObjectsCompleted(err)
if err == nil && len(result.Name()) == 0 { if err == nil && len(result.Name()) == 0 {
err = errors.New("404 no such file or directory") err = errors.New("404 no such file or directory")
} }
@ -164,8 +166,9 @@ func (fs S3Fs) Open(name string) (*os.File, *pipeat.PipeReaderAt, func(), error)
Bucket: aws.String(fs.config.Bucket), Bucket: aws.String(fs.config.Bucket),
Key: aws.String(key), Key: aws.String(key),
}) })
fsLog(fs, logger.LevelDebug, "download completed, path: %#v size: %v, err: %v", name, n, err)
w.CloseWithError(err) w.CloseWithError(err)
fsLog(fs, logger.LevelDebug, "download completed, path: %#v size: %v, err: %v", name, n, err)
metrics.S3TransferCompleted(n, 1, err)
}() }()
return nil, r, cancelFn, nil return nil, r, cancelFn, nil
} }
@ -187,8 +190,10 @@ func (fs S3Fs) Create(name string, flag int) (*os.File, *pipeat.PipeWriterAt, fu
Body: r, Body: r,
StorageClass: utils.NilIfEmpty(fs.config.StorageClass), StorageClass: utils.NilIfEmpty(fs.config.StorageClass),
}) })
fsLog(fs, logger.LevelDebug, "upload completed, path: %#v, response: %v, err: %v", name, response, err)
r.CloseWithError(err) r.CloseWithError(err)
fsLog(fs, logger.LevelDebug, "upload completed, path: %#v, response: %v, readed bytes: %v, err: %v",
name, response, r.GetReadedBytes(), err)
metrics.S3TransferCompleted(r.GetReadedBytes(), 0, err)
}() }()
return nil, w, cancelFn, nil return nil, w, cancelFn, nil
} }
@ -229,6 +234,7 @@ func (fs S3Fs) Rename(source, target string) error {
CopySource: aws.String(copySource), CopySource: aws.String(copySource),
Key: aws.String(target), Key: aws.String(target),
}) })
metrics.S3CopyObjectCompleted(err)
if err != nil { if err != nil {
return err return err
} }
@ -255,6 +261,7 @@ func (fs S3Fs) Remove(name string, isDir bool) error {
Bucket: aws.String(fs.config.Bucket), Bucket: aws.String(fs.config.Bucket),
Key: aws.String(name), Key: aws.String(name),
}) })
metrics.S3DeleteObjectCompleted(err)
return err return err
} }
@ -331,6 +338,7 @@ func (fs S3Fs) ReadDir(dirname string) ([]os.FileInfo, error) {
} }
return true return true
}) })
metrics.S3ListObjectsCompleted(err)
return result, err return result, err
} }
@ -401,6 +409,7 @@ func (fs S3Fs) CheckRootPath(username string, uid int, gid int) bool {
_, err = fs.svc.CreateBucketWithContext(ctx, input) _, err = fs.svc.CreateBucketWithContext(ctx, input)
fsLog(fs, logger.LevelDebug, "bucket %#v for user %#v does not exists, try to create, error: %v", fsLog(fs, logger.LevelDebug, "bucket %#v for user %#v does not exists, try to create, error: %v",
fs.config.Bucket, username, err) fs.config.Bucket, username, err)
metrics.S3CreateBucketCompleted(err)
return err == nil return err == nil
} }
@ -421,7 +430,7 @@ func (fs S3Fs) ScanRootDirContents() (int, int64, error) {
} }
return true return true
}) })
metrics.S3ListObjectsCompleted(err)
return numFiles, size, err return numFiles, size, err
} }
@ -496,6 +505,7 @@ func (fs *S3Fs) checkIfBucketExists() error {
_, err := fs.svc.HeadBucketWithContext(ctx, &s3.HeadBucketInput{ _, err := fs.svc.HeadBucketWithContext(ctx, &s3.HeadBucketInput{
Bucket: aws.String(fs.config.Bucket), Bucket: aws.String(fs.config.Bucket),
}) })
metrics.S3HeadBucketCompleted(err)
return err return err
} }