Skip to content

Commit 8a6fc85

Browse files
benjaminlegrand-seagatencw
authored andcommitted
accounting: fix global error acounting
fs.CountError is called when an error is encountered. The method was calling GlobalStats().Error(err) which incremented the error at the global stats level. This led to calls to core/stats with group= filter returning an error count of 0 even if errors actually occured. This change requires the context to be provided when calling fs.CountError. Doing so, we can retrieve the correct StatsInfo to increment the errors from. Fixes rclone#5865
1 parent c053429 commit 8a6fc85

File tree

19 files changed

+139
-86
lines changed

19 files changed

+139
-86
lines changed

Diff for: cmd/cmd.go

+26-20
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,13 @@ func ShowVersion() {
8383
// It returns a string with the file name if points to a file
8484
// otherwise "".
8585
func NewFsFile(remote string) (fs.Fs, string) {
86+
ctx := context.Background()
8687
_, fsPath, err := fspath.SplitFs(remote)
8788
if err != nil {
88-
err = fs.CountError(err)
89+
err = fs.CountError(ctx, err)
8990
fs.Fatalf(nil, "Failed to create file system for %q: %v", remote, err)
9091
}
91-
f, err := cache.Get(context.Background(), remote)
92+
f, err := cache.Get(ctx, remote)
9293
switch err {
9394
case fs.ErrorIsFile:
9495
cache.Pin(f) // pin indefinitely since it was on the CLI
@@ -97,7 +98,7 @@ func NewFsFile(remote string) (fs.Fs, string) {
9798
cache.Pin(f) // pin indefinitely since it was on the CLI
9899
return f, ""
99100
default:
100-
err = fs.CountError(err)
101+
err = fs.CountError(ctx, err)
101102
fs.Fatalf(nil, "Failed to create file system for %q: %v", remote, err)
102103
}
103104
return nil, ""
@@ -108,18 +109,19 @@ func NewFsFile(remote string) (fs.Fs, string) {
108109
// This works the same as NewFsFile however it adds filters to the Fs
109110
// to limit it to a single file if the remote pointed to a file.
110111
func newFsFileAddFilter(remote string) (fs.Fs, string) {
111-
fi := filter.GetConfig(context.Background())
112+
ctx := context.Background()
113+
fi := filter.GetConfig(ctx)
112114
f, fileName := NewFsFile(remote)
113115
if fileName != "" {
114116
if !fi.InActive() {
115117
err := fmt.Errorf("can't limit to single files when using filters: %v", remote)
116-
err = fs.CountError(err)
118+
err = fs.CountError(ctx, err)
117119
fs.Fatal(nil, err.Error())
118120
}
119121
// Limit transfers to this file
120122
err := fi.AddFile(fileName)
121123
if err != nil {
122-
err = fs.CountError(err)
124+
err = fs.CountError(ctx, err)
123125
fs.Fatalf(nil, "Failed to limit to single file %q: %v", remote, err)
124126
}
125127
}
@@ -139,9 +141,10 @@ func NewFsSrc(args []string) fs.Fs {
139141
//
140142
// This must point to a directory
141143
func newFsDir(remote string) fs.Fs {
142-
f, err := cache.Get(context.Background(), remote)
144+
ctx := context.Background()
145+
f, err := cache.Get(ctx, remote)
143146
if err != nil {
144-
err = fs.CountError(err)
147+
err = fs.CountError(ctx, err)
145148
fs.Fatalf(nil, "Failed to create file system for %q: %v", remote, err)
146149
}
147150
cache.Pin(f) // pin indefinitely since it was on the CLI
@@ -175,6 +178,7 @@ func NewFsSrcFileDst(args []string) (fsrc fs.Fs, srcFileName string, fdst fs.Fs)
175178
// NewFsSrcDstFiles creates a new src and dst fs from the arguments
176179
// If src is a file then srcFileName and dstFileName will be non-empty
177180
func NewFsSrcDstFiles(args []string) (fsrc fs.Fs, srcFileName string, fdst fs.Fs, dstFileName string) {
181+
ctx := context.Background()
178182
fsrc, srcFileName = newFsFileAddFilter(args[0])
179183
// If copying a file...
180184
dstRemote := args[1]
@@ -193,14 +197,14 @@ func NewFsSrcDstFiles(args []string) (fsrc fs.Fs, srcFileName string, fdst fs.Fs
193197
fs.Fatalf(nil, "%q is a directory", args[1])
194198
}
195199
}
196-
fdst, err := cache.Get(context.Background(), dstRemote)
200+
fdst, err := cache.Get(ctx, dstRemote)
197201
switch err {
198202
case fs.ErrorIsFile:
199-
_ = fs.CountError(err)
203+
_ = fs.CountError(ctx, err)
200204
fs.Fatalf(nil, "Source doesn't exist or is a directory and destination is a file")
201205
case nil:
202206
default:
203-
_ = fs.CountError(err)
207+
_ = fs.CountError(ctx, err)
204208
fs.Fatalf(nil, "Failed to create file system for destination %q: %v", dstRemote, err)
205209
}
206210
cache.Pin(fdst) // pin indefinitely since it was on the CLI
@@ -234,7 +238,8 @@ func ShowStats() bool {
234238

235239
// Run the function with stats and retries if required
236240
func Run(Retry bool, showStats bool, cmd *cobra.Command, f func() error) {
237-
ci := fs.GetConfig(context.Background())
241+
ctx := context.Background()
242+
ci := fs.GetConfig(ctx)
238243
var cmdErr error
239244
stopStats := func() {}
240245
if !showStats && ShowStats() {
@@ -248,7 +253,7 @@ func Run(Retry bool, showStats bool, cmd *cobra.Command, f func() error) {
248253
SigInfoHandler()
249254
for try := 1; try <= ci.Retries; try++ {
250255
cmdErr = f()
251-
cmdErr = fs.CountError(cmdErr)
256+
cmdErr = fs.CountError(ctx, cmdErr)
252257
lastErr := accounting.GlobalStats().GetLastError()
253258
if cmdErr == nil {
254259
cmdErr = lastErr
@@ -436,19 +441,19 @@ func initConfig() {
436441
fs.Infof(nil, "Creating CPU profile %q\n", *cpuProfile)
437442
f, err := os.Create(*cpuProfile)
438443
if err != nil {
439-
err = fs.CountError(err)
444+
err = fs.CountError(ctx, err)
440445
fs.Fatal(nil, fmt.Sprint(err))
441446
}
442447
err = pprof.StartCPUProfile(f)
443448
if err != nil {
444-
err = fs.CountError(err)
449+
err = fs.CountError(ctx, err)
445450
fs.Fatal(nil, fmt.Sprint(err))
446451
}
447452
atexit.Register(func() {
448453
pprof.StopCPUProfile()
449454
err := f.Close()
450455
if err != nil {
451-
err = fs.CountError(err)
456+
err = fs.CountError(ctx, err)
452457
fs.Fatal(nil, fmt.Sprint(err))
453458
}
454459
})
@@ -460,25 +465,26 @@ func initConfig() {
460465
fs.Infof(nil, "Saving Memory profile %q\n", *memProfile)
461466
f, err := os.Create(*memProfile)
462467
if err != nil {
463-
err = fs.CountError(err)
468+
err = fs.CountError(ctx, err)
464469
fs.Fatal(nil, fmt.Sprint(err))
465470
}
466471
err = pprof.WriteHeapProfile(f)
467472
if err != nil {
468-
err = fs.CountError(err)
473+
err = fs.CountError(ctx, err)
469474
fs.Fatal(nil, fmt.Sprint(err))
470475
}
471476
err = f.Close()
472477
if err != nil {
473-
err = fs.CountError(err)
478+
err = fs.CountError(ctx, err)
474479
fs.Fatal(nil, fmt.Sprint(err))
475480
}
476481
})
477482
}
478483
}
479484

480485
func resolveExitCode(err error) {
481-
ci := fs.GetConfig(context.Background())
486+
ctx := context.Background()
487+
ci := fs.GetConfig(ctx)
482488
atexit.Run()
483489
if err == nil {
484490
if ci.ErrorOnNoTransfer {

Diff for: cmd/serve/dlna/dlna.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -190,16 +190,17 @@ func (s *server) ModelNumber() string {
190190

191191
// Renders the root device descriptor.
192192
func (s *server) rootDescHandler(w http.ResponseWriter, r *http.Request) {
193+
ctx := r.Context()
193194
tmpl, err := data.GetTemplate()
194195
if err != nil {
195-
serveError(s, w, "Failed to load root descriptor template", err)
196+
serveError(ctx, s, w, "Failed to load root descriptor template", err)
196197
return
197198
}
198199

199200
buffer := new(bytes.Buffer)
200201
err = tmpl.Execute(buffer, s)
201202
if err != nil {
202-
serveError(s, w, "Failed to render root descriptor XML", err)
203+
serveError(ctx, s, w, "Failed to render root descriptor XML", err)
203204
return
204205
}
205206

@@ -215,15 +216,16 @@ func (s *server) rootDescHandler(w http.ResponseWriter, r *http.Request) {
215216

216217
// Handle a service control HTTP request.
217218
func (s *server) serviceControlHandler(w http.ResponseWriter, r *http.Request) {
219+
ctx := r.Context()
218220
soapActionString := r.Header.Get("SOAPACTION")
219221
soapAction, err := upnp.ParseActionHTTPHeader(soapActionString)
220222
if err != nil {
221-
serveError(s, w, "Could not parse SOAPACTION header", err)
223+
serveError(ctx, s, w, "Could not parse SOAPACTION header", err)
222224
return
223225
}
224226
var env soap.Envelope
225227
if err := xml.NewDecoder(r.Body).Decode(&env); err != nil {
226-
serveError(s, w, "Could not parse SOAP request body", err)
228+
serveError(ctx, s, w, "Could not parse SOAP request body", err)
227229
return
228230
}
229231

@@ -257,6 +259,7 @@ func (s *server) soapActionResponse(sa upnp.SoapAction, actionRequestXML []byte,
257259

258260
// Serves actual resources (media files).
259261
func (s *server) resourceHandler(w http.ResponseWriter, r *http.Request) {
262+
ctx := r.Context()
260263
remotePath := r.URL.Path
261264
node, err := s.vfs.Stat(r.URL.Path)
262265
if err != nil {
@@ -277,7 +280,7 @@ func (s *server) resourceHandler(w http.ResponseWriter, r *http.Request) {
277280
file := node.(*vfs.File)
278281
in, err := file.Open(os.O_RDONLY)
279282
if err != nil {
280-
serveError(node, w, "Could not open resource", err)
283+
serveError(ctx, node, w, "Could not open resource", err)
281284
return
282285
}
283286
defer fs.CheckClose(in, &err)

Diff for: cmd/serve/dlna/dlna_util.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dlna
22

33
import (
4+
"context"
45
"crypto/md5"
56
"encoding/xml"
67
"fmt"
@@ -142,9 +143,10 @@ func logging(next http.Handler) http.Handler {
142143
// Error recovery and general request logging are left to logging().
143144
func traceLogging(next http.Handler) http.Handler {
144145
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
146+
ctx := r.Context()
145147
dump, err := httputil.DumpRequest(r, true)
146148
if err != nil {
147-
serveError(nil, w, "error dumping request", err)
149+
serveError(ctx, nil, w, "error dumping request", err)
148150
return
149151
}
150152
fs.Debugf(nil, "%s", dump)
@@ -182,8 +184,8 @@ func withHeader(name string, value string, next http.Handler) http.Handler {
182184
}
183185

184186
// serveError returns an http.StatusInternalServerError and logs the error
185-
func serveError(what interface{}, w http.ResponseWriter, text string, err error) {
186-
err = fs.CountError(err)
187+
func serveError(ctx context.Context, what interface{}, w http.ResponseWriter, text string, err error) {
188+
err = fs.CountError(ctx, err)
187189
fs.Errorf(what, "%s: %v", text, err)
188190
http.Error(w, text+".", http.StatusInternalServerError)
189191
}

Diff for: cmd/serve/http/http.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ func (s *HTTP) handler(w http.ResponseWriter, r *http.Request) {
186186

187187
// serveDir serves a directory index at dirRemote
188188
func (s *HTTP) serveDir(w http.ResponseWriter, r *http.Request, dirRemote string) {
189+
ctx := r.Context()
189190
VFS, err := s.getVFS(r.Context())
190191
if err != nil {
191192
http.Error(w, "Root directory not found", http.StatusNotFound)
@@ -198,7 +199,7 @@ func (s *HTTP) serveDir(w http.ResponseWriter, r *http.Request, dirRemote string
198199
http.Error(w, "Directory not found", http.StatusNotFound)
199200
return
200201
} else if err != nil {
201-
serve.Error(dirRemote, w, "Failed to list directory", err)
202+
serve.Error(ctx, dirRemote, w, "Failed to list directory", err)
202203
return
203204
}
204205
if !node.IsDir() {
@@ -208,7 +209,7 @@ func (s *HTTP) serveDir(w http.ResponseWriter, r *http.Request, dirRemote string
208209
dir := node.(*vfs.Dir)
209210
dirEntries, err := dir.ReadDirAll()
210211
if err != nil {
211-
serve.Error(dirRemote, w, "Failed to list directory", err)
212+
serve.Error(ctx, dirRemote, w, "Failed to list directory", err)
212213
return
213214
}
214215

@@ -234,6 +235,7 @@ func (s *HTTP) serveDir(w http.ResponseWriter, r *http.Request, dirRemote string
234235

235236
// serveFile serves a file object at remote
236237
func (s *HTTP) serveFile(w http.ResponseWriter, r *http.Request, remote string) {
238+
ctx := r.Context()
237239
VFS, err := s.getVFS(r.Context())
238240
if err != nil {
239241
http.Error(w, "File not found", http.StatusNotFound)
@@ -247,7 +249,7 @@ func (s *HTTP) serveFile(w http.ResponseWriter, r *http.Request, remote string)
247249
http.Error(w, "File not found", http.StatusNotFound)
248250
return
249251
} else if err != nil {
250-
serve.Error(remote, w, "Failed to find file", err)
252+
serve.Error(ctx, remote, w, "Failed to find file", err)
251253
return
252254
}
253255
if !node.IsFile() {
@@ -287,7 +289,7 @@ func (s *HTTP) serveFile(w http.ResponseWriter, r *http.Request, remote string)
287289
// open the object
288290
in, err := file.Open(os.O_RDONLY)
289291
if err != nil {
290-
serve.Error(remote, w, "Failed to open file", err)
292+
serve.Error(ctx, remote, w, "Failed to open file", err)
291293
return
292294
}
293295
defer func() {

Diff for: cmd/serve/webdav/webdav.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ func (w *WebDAV) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
349349
// serveDir serves a directory index at dirRemote
350350
// This is similar to serveDir in serve http.
351351
func (w *WebDAV) serveDir(rw http.ResponseWriter, r *http.Request, dirRemote string) {
352+
ctx := r.Context()
352353
VFS, err := w.getVFS(r.Context())
353354
if err != nil {
354355
http.Error(rw, "Root directory not found", http.StatusNotFound)
@@ -361,7 +362,7 @@ func (w *WebDAV) serveDir(rw http.ResponseWriter, r *http.Request, dirRemote str
361362
http.Error(rw, "Directory not found", http.StatusNotFound)
362363
return
363364
} else if err != nil {
364-
serve.Error(dirRemote, rw, "Failed to list directory", err)
365+
serve.Error(ctx, dirRemote, rw, "Failed to list directory", err)
365366
return
366367
}
367368
if !node.IsDir() {
@@ -372,7 +373,7 @@ func (w *WebDAV) serveDir(rw http.ResponseWriter, r *http.Request, dirRemote str
372373
dirEntries, err := dir.ReadDirAll()
373374

374375
if err != nil {
375-
serve.Error(dirRemote, rw, "Failed to list directory", err)
376+
serve.Error(ctx, dirRemote, rw, "Failed to list directory", err)
376377
return
377378
}
378379

Diff for: fs/accounting/accounting.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ func Start(ctx context.Context) {
4444
//
4545
// We can't do this in an init() method as it uses fs.Config
4646
// and that isn't set up then.
47-
fs.CountError = GlobalStats().Error
47+
fs.CountError = func(ctx context.Context, err error) error {
48+
return Stats(ctx).Error(err)
49+
}
4850
}
4951

5052
// Account limits and accounts for one transfer

Diff for: fs/accounting/stats_groups_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"testing"
88
"time"
99

10+
"github.com/rclone/rclone/fs"
11+
"github.com/rclone/rclone/fs/fserrors"
1012
"github.com/rclone/rclone/fs/rc"
1113
"github.com/rclone/rclone/fstest/testy"
1214
"github.com/stretchr/testify/assert"
@@ -206,6 +208,34 @@ func TestStatsGroupOperations(t *testing.T) {
206208
})
207209
}
208210

211+
func TestCountError(t *testing.T) {
212+
ctx := context.Background()
213+
Start(ctx)
214+
defer func() {
215+
groups = newStatsGroups()
216+
}()
217+
t.Run("global stats", func(t *testing.T) {
218+
GlobalStats().ResetCounters()
219+
err := fs.CountError(ctx, fmt.Errorf("global err"))
220+
assert.Equal(t, int64(1), GlobalStats().errors)
221+
222+
assert.True(t, fserrors.IsCounted(err))
223+
})
224+
t.Run("group stats", func(t *testing.T) {
225+
statGroupName := fmt.Sprintf("%s-error_group", t.Name())
226+
GlobalStats().ResetCounters()
227+
stCtx := WithStatsGroup(ctx, statGroupName)
228+
st := StatsGroup(stCtx, statGroupName)
229+
230+
err := fs.CountError(stCtx, fmt.Errorf("group err"))
231+
232+
assert.Equal(t, int64(0), GlobalStats().errors)
233+
assert.Equal(t, int64(1), st.errors)
234+
assert.True(t, fserrors.IsCounted(err))
235+
})
236+
237+
}
238+
209239
func percentDiff(start, end uint64) uint64 {
210240
return (start - end) * 100 / start
211241
}

Diff for: fs/cache/cache.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func createOnFirstUse() {
2929
c.SetExpireInterval(ci.FsCacheExpireInterval)
3030
c.SetFinalizer(func(value interface{}) {
3131
if s, ok := value.(fs.Shutdowner); ok {
32-
_ = fs.CountError(s.Shutdown(context.Background()))
32+
_ = fs.CountError(context.Background(), s.Shutdown(context.Background()))
3333
}
3434
})
3535
})

Diff for: fs/config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ var (
4141
//
4242
// This is a function pointer to decouple the config
4343
// implementation from the fs
44-
CountError = func(err error) error { return err }
44+
CountError = func(ctx context.Context, err error) error { return err }
4545

4646
// ConfigProvider is the config key used for provider options
4747
ConfigProvider = "provider"

0 commit comments

Comments
 (0)