-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Client reports download status and download details #341
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package internal | ||
|
||
import ( | ||
"context" | ||
"sync/atomic" | ||
"time" | ||
) | ||
|
||
const downloadReporterDefaultInterval = time.Second * 10 | ||
|
||
type downloadReporter struct { | ||
start time.Time | ||
interval time.Duration | ||
packageLength float64 | ||
|
||
downloaded atomic.Uint64 | ||
|
||
done chan struct{} | ||
} | ||
|
||
func newDownloadReporter(interval time.Duration, length int) *downloadReporter { | ||
if interval <= 0 { | ||
interval = downloadReporterDefaultInterval | ||
} | ||
return &downloadReporter{ | ||
start: time.Now(), | ||
interval: interval, | ||
packageLength: float64(length), | ||
done: make(chan struct{}), | ||
} | ||
} | ||
|
||
// Write tracks the number of bytes downloaded. It will never return an error. | ||
func (p *downloadReporter) Write(b []byte) (int, error) { | ||
n := len(b) | ||
p.downloaded.Add(uint64(n)) | ||
return n, nil | ||
} | ||
|
||
// report periodically calls the passed function to with the download percent and rate to update the status of a package. | ||
func (p *downloadReporter) report(ctx context.Context, updateFn func(context.Context, float, float) error) { | ||
Check failure on line 41 in client/internal/package_download_details_reporter.go GitHub Actions / build-and-test
|
||
go func() { | ||
ticker := time.NewTicker(p.interval) | ||
defer ticker.Stop() | ||
for { | ||
select { | ||
case <-ctx.Done(): | ||
return | ||
case <-p.done: | ||
return | ||
case <-ticker.C: | ||
downloadTime := time.Now().Sub(p.start) | ||
downloaded := float64(p.downloaded.Load()) | ||
bps := downloaded / float64(downloadTime/time.Second) | ||
var downloadPercent float64 | ||
if p.packageLength > 0 { | ||
downloadPercent = downloaded / p.packageLength * 100 | ||
} | ||
_ = updateFn(ctx, downloadPercent, bps) | ||
} | ||
} | ||
}() | ||
} | ||
|
||
// stop the downloadReporter report goroutine | ||
func (p *downloadReporter) stop() { | ||
close(p.done) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,8 +5,11 @@ | |
"context" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"strconv" | ||
"sync" | ||
"time" | ||
|
||
"github.com/open-telemetry/opamp-go/client/types" | ||
"github.com/open-telemetry/opamp-go/protobufs" | ||
|
@@ -19,6 +22,7 @@ | |
clientSyncedState *ClientSyncedState | ||
localState types.PackagesStateProvider | ||
sender Sender | ||
reporterInterval time.Duration | ||
|
||
statuses *protobufs.PackageStatuses | ||
mux *sync.Mutex | ||
|
@@ -33,6 +37,7 @@ | |
clientSyncedState *ClientSyncedState, | ||
packagesStateProvider types.PackagesStateProvider, | ||
mux *sync.Mutex, | ||
reporterInterval time.Duration, | ||
) *packagesSyncer { | ||
return &packagesSyncer{ | ||
logger: logger, | ||
|
@@ -42,6 +47,7 @@ | |
localState: packagesStateProvider, | ||
doneCh: make(chan struct{}), | ||
mux: mux, | ||
reporterInterval: reporterInterval, | ||
} | ||
} | ||
|
||
|
@@ -273,6 +279,10 @@ | |
|
||
// downloadFile downloads the file from the server. | ||
func (s *packagesSyncer) downloadFile(ctx context.Context, pkgName string, file *protobufs.DownloadableFile) error { | ||
status := s.statuses.Packages[pkgName] | ||
status.Status = protobufs.PackageStatusEnum_PackageStatusEnum_Downloading | ||
_ = s.reportStatuses(ctx, true) | ||
|
||
s.logger.Debugf(ctx, "Downloading package %s file from %s", pkgName, file.DownloadUrl) | ||
|
||
req, err := http.NewRequestWithContext(ctx, "GET", file.DownloadUrl, nil) | ||
|
@@ -290,13 +300,37 @@ | |
return fmt.Errorf("cannot download file from %s, HTTP response=%v", file.DownloadUrl, resp.StatusCode) | ||
} | ||
|
||
err = s.localState.UpdateContent(ctx, pkgName, resp.Body, file.ContentHash, file.Signature) | ||
// Package length is required to be able to report download percent. | ||
packageLength := -1 | ||
if contentLength := resp.Header.Get("Content-Length"); contentLength != "" { | ||
if length, err := strconv.Atoi(contentLength); err == nil { | ||
packageLength = length | ||
} | ||
} | ||
// start the download reporter | ||
detailsReporter := newDownloadReporter(s.reporterInterval, packageLength) | ||
detailsReporter.report(ctx, s.getDownloadDetailsFn(pkgName)) | ||
defer detailsReporter.stop() | ||
|
||
tr := io.TeeReader(resp.Body, detailsReporter) | ||
err = s.localState.UpdateContent(ctx, pkgName, tr, file.ContentHash, file.Signature) | ||
if err != nil { | ||
return fmt.Errorf("failed to install/update the package %s downloaded from %s: %v", pkgName, file.DownloadUrl, err) | ||
} | ||
return nil | ||
} | ||
|
||
func (s *packagesSyncer) getDownloadDetailsFn(pkgName string) func(context.Context, float64, float64) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this func updates the download details, so perhaps a more fitting name is |
||
return func(ctx context.Context, percent, rate float64) error { | ||
status := s.statuses.Packages[pkgName] | ||
status.DownloadDetails = &protobufs.PackageDownloadDetails{ | ||
DownloadPercent: percent, | ||
DownloadBytesPerSecond: rate, | ||
Check failure on line 328 in client/internal/packagessyncer.go GitHub Actions / build-and-test
|
||
} | ||
return s.reportStatuses(ctx, true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will call Reading the current code, it appears we are NOT calling it concurrently. We call once at the start of Just to future-proof it I think it make sense to caution implementors of What do you think? |
||
} | ||
} | ||
|
||
// deleteUnneededLocalPackages deletes local packages that are not | ||
// needed anymore. This is done by comparing the local package state | ||
// with the server's package state. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can we change updateFn to take a
struct { DownloadPercent float, Bps float }
so that it is clear which of the 2 floats is which.