fix: improve UI performance

This commit is contained in:
garethgeorge
2023-11-16 17:43:49 -08:00
parent 93b2120f74
commit 8488d461bd
3 changed files with 32 additions and 65 deletions

View File

@@ -117,7 +117,7 @@ func (o *Orchestrator) Run(mainCtx context.Context) error {
// runImmutable is a helper function for Run() that runs the orchestration loop with a single version of the config. // runImmutable is a helper function for Run() that runs the orchestration loop with a single version of the config.
func (o *Orchestrator) runVersion(mainCtx context.Context, config *v1.Config) bool { func (o *Orchestrator) runVersion(mainCtx context.Context, config *v1.Config) bool {
lock := sync.Mutex{} var lock sync.Mutex
ctx, cancel := context.WithCancel(mainCtx) ctx, cancel := context.WithCancel(mainCtx)
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -140,7 +140,9 @@ func (o *Orchestrator) runVersion(mainCtx context.Context, config *v1.Config) bo
defer wg.Done() defer wg.Done()
select { select {
case <-ctx.Done(): case <-ctx.Done():
timer.Stop() if !timer.Stop() {
<-timer.C
}
zap.L().Debug("cancelled scheduled (but not running) task, orchestrator context is cancelled.", zap.String("task", t.Name())) zap.L().Debug("cancelled scheduled (but not running) task, orchestrator context is cancelled.", zap.String("task", t.Name()))
return return
case <-timer.C: case <-timer.C:

View File

@@ -19,12 +19,6 @@ type RepoOrchestrator struct {
repoConfig *v1.Repo repoConfig *v1.Repo
repo *restic.Repo repo *restic.Repo
// TODO: decide if snapshot caching is a good idea. We gain performance but
// increase background memory use by a small amount at all times (probably on the order of 1MB).
snapshotsMu sync.Mutex // enable very fast snapshot access IF no update is required.
snapshotsResetTimer *time.Timer
snapshots []*restic.Snapshot
} }
func newRepoOrchestrator(repoConfig *v1.Repo, repo *restic.Repo) *RepoOrchestrator { func newRepoOrchestrator(repoConfig *v1.Repo, repo *restic.Repo) *RepoOrchestrator {
@@ -34,64 +28,26 @@ func newRepoOrchestrator(repoConfig *v1.Repo, repo *restic.Repo) *RepoOrchestrat
} }
} }
func (r *RepoOrchestrator) updateSnapshotsIfNeeded(ctx context.Context, force bool) error { func (r *RepoOrchestrator) Snapshots(ctx context.Context) ([]*restic.Snapshot, error) {
if r.snapshots != nil {
return nil
}
if r.snapshotsResetTimer != nil {
if !r.snapshotsResetTimer.Stop() {
<-r.snapshotsResetTimer.C
}
}
r.snapshotsResetTimer = time.AfterFunc(10 * time.Minute, func() {
r.snapshotsMu.Lock()
defer r.snapshotsMu.Unlock()
r.snapshots = nil
})
if r.snapshots != nil {
return nil
}
startTime := time.Now()
snapshots, err := r.repo.Snapshots(ctx, restic.WithPropagatedEnvVars(restic.EnvToPropagate...), restic.WithFlags("--latest", "1000")) snapshots, err := r.repo.Snapshots(ctx, restic.WithPropagatedEnvVars(restic.EnvToPropagate...), restic.WithFlags("--latest", "1000"))
if err != nil { if err != nil {
return fmt.Errorf("failed to update snapshots: %w", err) return nil, fmt.Errorf("restic.Snapshots: %w", err)
} }
sort.SliceStable(snapshots, func(i, j int) bool { sort.SliceStable(snapshots, func(i, j int) bool {
return snapshots[i].UnixTimeMs() < snapshots[j].UnixTimeMs() return snapshots[i].UnixTimeMs() < snapshots[j].UnixTimeMs()
}) })
r.snapshots = snapshots
zap.L().Debug("updated snapshots", zap.String("repo", r.repoConfig.Id), zap.Duration("duration", time.Since(startTime))) return snapshots, nil
return nil
}
func (r *RepoOrchestrator) Snapshots(ctx context.Context) ([]*restic.Snapshot, error) {
r.snapshotsMu.Lock()
defer r.snapshotsMu.Unlock()
if err := r.updateSnapshotsIfNeeded(ctx, false); err != nil {
return nil, err
}
return r.snapshots, nil
} }
func (r *RepoOrchestrator) SnapshotsForPlan(ctx context.Context, plan *v1.Plan) ([]*restic.Snapshot, error) { func (r *RepoOrchestrator) SnapshotsForPlan(ctx context.Context, plan *v1.Plan) ([]*restic.Snapshot, error) {
r.snapshotsMu.Lock() snapshots, err := r.Snapshots(ctx)
defer r.snapshotsMu.Unlock() if err != nil {
if err := r.updateSnapshotsIfNeeded(ctx, false); err != nil {
return nil, err return nil, err
} }
return filterSnapshotsForPlan(r.snapshots, plan), nil return filterSnapshotsForPlan(snapshots, plan), nil
} }
func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCallback func(event *restic.BackupProgressEntry)) (*restic.BackupProgressEntry, error) { func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCallback func(event *restic.BackupProgressEntry)) (*restic.BackupProgressEntry, error) {
@@ -123,14 +79,7 @@ func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCa
return nil, fmt.Errorf("failed to backup: %w", err) return nil, fmt.Errorf("failed to backup: %w", err)
} }
// Reset snapshots since a new backup has been added.
r.snapshotsMu.Lock()
r.snapshots = nil
r.snapshotsMu.Unlock()
zap.L().Debug("Backup completed", zap.String("repo", r.repoConfig.Id), zap.Duration("duration", time.Since(startTime))) zap.L().Debug("Backup completed", zap.String("repo", r.repoConfig.Id), zap.Duration("duration", time.Since(startTime)))
return summary, nil return summary, nil
} }

View File

@@ -17,10 +17,6 @@ export const OperationList = ({
}: React.PropsWithoutRef<{ operations: EOperation[] }>) => { }: React.PropsWithoutRef<{ operations: EOperation[] }>) => {
operations.sort((a, b) => b.parsedTime - a.parsedTime); operations.sort((a, b) => b.parsedTime - a.parsedTime);
const elems = operations.map((operation) => (
<OperationRow operation={operation} />
));
if (operations.length === 0) { if (operations.length === 0) {
return ( return (
<Empty <Empty
@@ -38,6 +34,11 @@ export const OperationList = ({
renderItem={(item, index) => ( renderItem={(item, index) => (
<OperationRow key={item.parsedId} operation={item} /> <OperationRow key={item.parsedId} operation={item} />
)} )}
pagination={
operations.length > 50
? { position: "both", align: "center", defaultPageSize: 50 }
: {}
}
/> />
); );
}; };
@@ -68,7 +69,10 @@ export const OperationRow = ({
const backupOp = operation.operationBackup; const backupOp = operation.operationBackup;
let desc = `Backup at ${formatTime(operation.unixTimeStartMs!)}`; let desc = `Backup at ${formatTime(operation.unixTimeStartMs!)}`;
if (operation.status !== OperationStatus.STATUS_INPROGRESS) { if (operation.status !== OperationStatus.STATUS_INPROGRESS) {
desc += ` and finished at ${formatTime(operation.unixTimeEndMs!)}`; desc += ` completed in ${formatDuration(
parseInt(operation.unixTimeEndMs!) -
parseInt(operation.unixTimeStartMs!)
)}`;
} else { } else {
desc += " and is still running."; desc += " and is still running.";
} }
@@ -273,5 +277,17 @@ const formatTime = (time: number | string) => {
} }
const d = new Date(); const d = new Date();
d.setTime(time); d.setTime(time);
return d.toLocaleString(); return d.toISOString();
}; };
const formatDuration = (ms: number) => {
const seconds = Math.floor(ms / 100) / 10;
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours === 0 && minutes === 0) {
return `${seconds % 60}s`;
} else if (hours === 0) {
return `${minutes}m${seconds % 60}s`;
}
return `${hours}h${minutes % 60}m${seconds % 60}s`;
};