mirror of
https://github.com/octoleo/syncthing.git
synced 2025-02-02 11:58:28 +00:00
Show scan rate in web GUI
This commit is contained in:
parent
37f866b47f
commit
a8a2192cf9
@ -126,11 +126,12 @@ func (s *verboseSvc) formatEvent(ev events.Event) string {
|
|||||||
folder := data["folder"].(string)
|
folder := data["folder"].(string)
|
||||||
current := data["current"].(int64)
|
current := data["current"].(int64)
|
||||||
total := data["total"].(int64)
|
total := data["total"].(int64)
|
||||||
|
rate := data["rate"].(float64) / 1024 / 1024
|
||||||
var pct int64
|
var pct int64
|
||||||
if total > 0 {
|
if total > 0 {
|
||||||
pct = 100 * current / total
|
pct = 100 * current / total
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("Scanning folder %q, %d%% done", folder, pct)
|
return fmt.Sprintf("Scanning folder %q, %d%% done (%.01f MB/s)", folder, pct, rate)
|
||||||
|
|
||||||
case events.DevicePaused:
|
case events.DevicePaused:
|
||||||
data := ev.Data.(map[string]string)
|
data := ev.Data.(map[string]string)
|
||||||
|
@ -228,7 +228,7 @@
|
|||||||
<span ng-switch-when="scanning">
|
<span ng-switch-when="scanning">
|
||||||
<span class="hidden-xs" translate>Scanning</span>
|
<span class="hidden-xs" translate>Scanning</span>
|
||||||
<span class="hidden-xs" ng-if="scanPercentage(folder.id) != undefined">
|
<span class="hidden-xs" ng-if="scanPercentage(folder.id) != undefined">
|
||||||
({{scanPercentage(folder.id)}}%)
|
(<span class="hidden-xs" ng-if="scanRate(folder.id) > 0">{{scanRate(folder.id) | binary}}B/s, </span>{{scanPercentage(folder.id)}}%)
|
||||||
</span>
|
</span>
|
||||||
<span class="visible-xs">◼</span>
|
<span class="visible-xs">◼</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -321,7 +321,8 @@ angular.module('syncthing.core')
|
|||||||
var data = arg.data;
|
var data = arg.data;
|
||||||
$scope.scanProgress[data.folder] = {
|
$scope.scanProgress[data.folder] = {
|
||||||
current: data.current,
|
current: data.current,
|
||||||
total: data.total
|
total: data.total,
|
||||||
|
rate: data.rate,
|
||||||
};
|
};
|
||||||
console.log("FolderScanProgress", data);
|
console.log("FolderScanProgress", data);
|
||||||
});
|
});
|
||||||
@ -668,6 +669,13 @@ angular.module('syncthing.core')
|
|||||||
return Math.floor(pct);
|
return Math.floor(pct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.scanRate = function (folder) {
|
||||||
|
if (!$scope.scanProgress[folder]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return $scope.scanProgress[folder].rate;
|
||||||
|
}
|
||||||
|
|
||||||
$scope.deviceStatus = function (deviceCfg) {
|
$scope.deviceStatus = function (deviceCfg) {
|
||||||
if ($scope.deviceFolders(deviceCfg).length === 0) {
|
if ($scope.deviceFolders(deviceCfg).length === 0) {
|
||||||
return 'unused';
|
return 'unused';
|
||||||
|
File diff suppressed because one or more lines are too long
@ -19,7 +19,7 @@ import (
|
|||||||
// workers are used in parallel. The outbox will become closed when the inbox
|
// workers are used in parallel. The outbox will become closed when the inbox
|
||||||
// is closed and all items handled.
|
// is closed and all items handled.
|
||||||
|
|
||||||
func newParallelHasher(dir string, blockSize, workers int, outbox, inbox chan protocol.FileInfo, counter *int64, done, cancel chan struct{}) {
|
func newParallelHasher(dir string, blockSize, workers int, outbox, inbox chan protocol.FileInfo, counter Counter, done, cancel chan struct{}) {
|
||||||
wg := sync.NewWaitGroup()
|
wg := sync.NewWaitGroup()
|
||||||
wg.Add(workers)
|
wg.Add(workers)
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ func newParallelHasher(dir string, blockSize, workers int, outbox, inbox chan pr
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func HashFile(path string, blockSize int, sizeHint int64, counter *int64) ([]protocol.BlockInfo, error) {
|
func HashFile(path string, blockSize int, sizeHint int64, counter Counter) ([]protocol.BlockInfo, error) {
|
||||||
fd, err := os.Open(path)
|
fd, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugln("open:", err)
|
l.Debugln("open:", err)
|
||||||
@ -59,7 +59,7 @@ func HashFile(path string, blockSize int, sizeHint int64, counter *int64) ([]pro
|
|||||||
return Blocks(fd, blockSize, sizeHint, counter)
|
return Blocks(fd, blockSize, sizeHint, counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashFiles(dir string, blockSize int, outbox, inbox chan protocol.FileInfo, counter *int64, cancel chan struct{}) {
|
func hashFiles(dir string, blockSize int, outbox, inbox chan protocol.FileInfo, counter Counter, cancel chan struct{}) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case f, ok := <-inbox:
|
case f, ok := <-inbox:
|
||||||
|
@ -11,15 +11,18 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
var SHA256OfNothing = []uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}
|
var SHA256OfNothing = []uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}
|
||||||
|
|
||||||
|
type Counter interface {
|
||||||
|
Update(bytes int64)
|
||||||
|
}
|
||||||
|
|
||||||
// Blocks returns the blockwise hash of the reader.
|
// Blocks returns the blockwise hash of the reader.
|
||||||
func Blocks(r io.Reader, blocksize int, sizehint int64, counter *int64) ([]protocol.BlockInfo, error) {
|
func Blocks(r io.Reader, blocksize int, sizehint int64, counter Counter) ([]protocol.BlockInfo, error) {
|
||||||
hf := sha256.New()
|
hf := sha256.New()
|
||||||
hashLength := hf.Size()
|
hashLength := hf.Size()
|
||||||
|
|
||||||
@ -50,7 +53,7 @@ func Blocks(r io.Reader, blocksize int, sizehint int64, counter *int64) ([]proto
|
|||||||
}
|
}
|
||||||
|
|
||||||
if counter != nil {
|
if counter != nil {
|
||||||
atomic.AddInt64(counter, int64(n))
|
counter.Update(int64(n))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carve out a hash-sized chunk of "hashes" to store the hash for this
|
// Carve out a hash-sized chunk of "hashes" to store the hash for this
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/rcrowley/go-metrics"
|
||||||
"github.com/syncthing/syncthing/lib/db"
|
"github.com/syncthing/syncthing/lib/db"
|
||||||
"github.com/syncthing/syncthing/lib/events"
|
"github.com/syncthing/syncthing/lib/events"
|
||||||
"github.com/syncthing/syncthing/lib/osutil"
|
"github.com/syncthing/syncthing/lib/osutil"
|
||||||
@ -143,7 +144,11 @@ func (w *Walker) Walk() (chan protocol.FileInfo, error) {
|
|||||||
// which it receives the files we ask it to hash.
|
// which it receives the files we ask it to hash.
|
||||||
go func() {
|
go func() {
|
||||||
var filesToHash []protocol.FileInfo
|
var filesToHash []protocol.FileInfo
|
||||||
var total, progress int64 = 1, 0
|
var total int64 = 1
|
||||||
|
|
||||||
|
progress := newByteCounter()
|
||||||
|
defer progress.Close()
|
||||||
|
|
||||||
for file := range toHashChan {
|
for file := range toHashChan {
|
||||||
filesToHash = append(filesToHash, file)
|
filesToHash = append(filesToHash, file)
|
||||||
total += int64(file.CachedSize)
|
total += int64(file.CachedSize)
|
||||||
@ -151,7 +156,7 @@ func (w *Walker) Walk() (chan protocol.FileInfo, error) {
|
|||||||
|
|
||||||
realToHashChan := make(chan protocol.FileInfo)
|
realToHashChan := make(chan protocol.FileInfo)
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
newParallelHasher(w.Dir, w.BlockSize, w.Hashers, finishedChan, realToHashChan, &progress, done, w.Cancel)
|
newParallelHasher(w.Dir, w.BlockSize, w.Hashers, finishedChan, realToHashChan, progress, done, w.Cancel)
|
||||||
|
|
||||||
// A routine which actually emits the FolderScanProgress events
|
// A routine which actually emits the FolderScanProgress events
|
||||||
// every w.ProgressTicker ticks, until the hasher routines terminate.
|
// every w.ProgressTicker ticks, until the hasher routines terminate.
|
||||||
@ -163,12 +168,14 @@ func (w *Walker) Walk() (chan protocol.FileInfo, error) {
|
|||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
current := atomic.LoadInt64(&progress)
|
current := progress.Total()
|
||||||
l.Debugf("Walk %s %s current progress %d/%d (%d%%)", w.Dir, w.Subs, current, total, current*100/total)
|
rate := progress.Rate()
|
||||||
|
l.Debugf("Walk %s %s current progress %d/%d at %.01f MB/s (%d%%)", w.Dir, w.Subs, current, total, rate/1024/1024, current*100/total)
|
||||||
events.Default.Log(events.FolderScanProgress, map[string]interface{}{
|
events.Default.Log(events.FolderScanProgress, map[string]interface{}{
|
||||||
"folder": w.Folder,
|
"folder": w.Folder,
|
||||||
"current": current,
|
"current": current,
|
||||||
"total": total,
|
"total": total,
|
||||||
|
"rate": rate, // bytes per second
|
||||||
})
|
})
|
||||||
case <-w.Cancel:
|
case <-w.Cancel:
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
@ -492,3 +499,48 @@ func SymlinkFlags(t symlinks.TargetType) uint32 {
|
|||||||
}
|
}
|
||||||
panic("unknown symlink TargetType")
|
panic("unknown symlink TargetType")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A byteCounter gets bytes added to it via Update() and then provides the
|
||||||
|
// Total() and one minute moving average Rate() in bytes per second.
|
||||||
|
type byteCounter struct {
|
||||||
|
total int64
|
||||||
|
metrics.EWMA
|
||||||
|
stop chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newByteCounter() *byteCounter {
|
||||||
|
c := &byteCounter{
|
||||||
|
EWMA: metrics.NewEWMA1(), // a one minute exponentially weighted moving average
|
||||||
|
stop: make(chan struct{}),
|
||||||
|
}
|
||||||
|
go c.ticker()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *byteCounter) ticker() {
|
||||||
|
// The metrics.EWMA expects clock ticks every five seconds in order to
|
||||||
|
// decay the average properly.
|
||||||
|
t := time.NewTicker(5 * time.Second)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-t.C:
|
||||||
|
c.Tick()
|
||||||
|
case <-c.stop:
|
||||||
|
t.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *byteCounter) Update(bytes int64) {
|
||||||
|
atomic.AddInt64(&c.total, bytes)
|
||||||
|
c.EWMA.Update(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *byteCounter) Total() int64 {
|
||||||
|
return atomic.LoadInt64(&c.total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *byteCounter) Close() {
|
||||||
|
close(c.stop)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user