Skip to content

Commit

Permalink
dynamic chunking: cleanup, fixes, improvements
Browse files Browse the repository at this point in the history
* make target duration a client option instead of a capability
* simplify algorithm for determining chunk size significantly
* preserve chunk size for the whole propagation, not just per upload
* move options to SyncOptions to avoid depending on ConfigFile
  in the propagator
* move chunk-size adjustment to after a chunk finishes, not when
  a new chunk starts
  • Loading branch information
ckamm committed Mar 28, 2017
1 parent 88e0f97 commit e86499d
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 118 deletions.
47 changes: 43 additions & 4 deletions src/gui/folder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -645,19 +645,58 @@ void Folder::startSync(const QStringList &pathList)
}

setDirtyNetworkLimits();
setSyncOptions();

_engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles);

QMetaObject::invokeMethod(_engine.data(), "startSync", Qt::QueuedConnection);

emit syncStarted();
}

void Folder::setSyncOptions()
{
SyncOptions opt;
ConfigFile cfgFile;

auto newFolderLimit = cfgFile.newBigFolderSizeLimit();
opt._newBigFolderSizeLimit = newFolderLimit.first ? newFolderLimit.second * 1000LL * 1000LL : -1; // convert from MB to B
opt._confirmExternalStorage = cfgFile.confirmExternalStorage();
_engine->setSyncOptions(opt);

_engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles);
QByteArray chunkSizeEnv = qgetenv("OWNCLOUD_CHUNK_SIZE");
if (!chunkSizeEnv.isEmpty()) {
opt._initialChunkSize = chunkSizeEnv.toUInt();
} else {
opt._initialChunkSize = cfgFile.chunkSize();
}
QByteArray minChunkSizeEnv = qgetenv("OWNCLOUD_MIN_CHUNK_SIZE");
if (!minChunkSizeEnv.isEmpty()) {
opt._minChunkSize = minChunkSizeEnv.toUInt();
} else {
opt._minChunkSize = cfgFile.minChunkSize();
}
QByteArray maxChunkSizeEnv = qgetenv("OWNCLOUD_MAX_CHUNK_SIZE");
if (!maxChunkSizeEnv.isEmpty()) {
opt._maxChunkSize = maxChunkSizeEnv.toUInt();
} else {
opt._maxChunkSize = cfgFile.maxChunkSize();
}

QMetaObject::invokeMethod(_engine.data(), "startSync", Qt::QueuedConnection);
// Previously min/max chunk size values didn't exist, so users might
// have setups where the chunk size exceeds the new min/max default
// values. To cope with this, adjust min/max to always include the
// initial chunk size value.
opt._minChunkSize = qMin(opt._minChunkSize, opt._initialChunkSize);
opt._maxChunkSize = qMax(opt._maxChunkSize, opt._initialChunkSize);

emit syncStarted();
QByteArray targetChunkUploadDurationEnv = qgetenv("OWNCLOUD_TARGET_CHUNK_UPLOAD_DURATION");
if (!targetChunkUploadDurationEnv.isEmpty()) {
opt._targetChunkUploadDuration = targetChunkUploadDurationEnv.toUInt();
} else {
opt._targetChunkUploadDuration = cfgFile.targetChunkUploadDuration();
}

_engine->setSyncOptions(opt);
}

void Folder::setDirtyNetworkLimits()
Expand Down
2 changes: 2 additions & 0 deletions src/gui/folder.h
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ private slots:

void checkLocalPath();

void setSyncOptions();

enum LogStatus {
LogStatusRemove,
LogStatusRename,
Expand Down
8 changes: 0 additions & 8 deletions src/libsync/capabilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,4 @@ QList<int> Capabilities::httpErrorCodesThatResetFailingChunkedUploads() const
return list;
}

quint64 Capabilities::requestMaxDurationDC() const
{
QByteArray requestMaxDurationDC = _capabilities["dav"].toMap()["max_single_upload_request_duration_msec"].toByteArray();
if (!requestMaxDurationDC.isEmpty())
return requestMaxDurationDC.toLongLong();
return 0;
}

}
1 change: 0 additions & 1 deletion src/libsync/capabilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ class OWNCLOUDSYNC_EXPORT Capabilities {
int sharePublicLinkExpireDateDays() const;
bool shareResharing() const;
bool chunkingNg() const;
quint64 requestMaxDurationDC() const;

/// disable parallel upload in chunking
bool chunkingParallelUploadDisabled() const;
Expand Down
14 changes: 11 additions & 3 deletions src/libsync/configfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ static const char updateCheckIntervalC[] = "updateCheckInterval";
static const char geometryC[] = "geometry";
static const char timeoutC[] = "timeout";
static const char chunkSizeC[] = "chunkSize";
static const char maxChunkSizeC[] = "maxChunkSizeC";
static const char minChunkSizeC[] = "minChunkSize";
static const char maxChunkSizeC[] = "maxChunkSize";
static const char targetChunkUploadDurationC[] = "targetChunkUploadDuration";

static const char proxyHostC[] = "Proxy/host";
static const char proxyTypeC[] = "Proxy/type";
Expand Down Expand Up @@ -134,13 +136,19 @@ quint64 ConfigFile::chunkSize() const
quint64 ConfigFile::maxChunkSize() const
{
QSettings settings(configFile(), QSettings::IniFormat);
return settings.value(QLatin1String(maxChunkSizeC), 50*1000*1000).toLongLong(); // default to 50 MB
return settings.value(QLatin1String(maxChunkSizeC), 100*1000*1000).toLongLong(); // default to 100 MB
}

quint64 ConfigFile::minChunkSize() const
{
QSettings settings(configFile(), QSettings::IniFormat);
return settings.value(QLatin1String(maxChunkSizeC), 1000*1000).toLongLong(); // default to 1 MB
return settings.value(QLatin1String(minChunkSizeC), 1000*1000).toLongLong(); // default to 1 MB
}

quint64 ConfigFile::targetChunkUploadDuration() const
{
QSettings settings(configFile(), QSettings::IniFormat);
return settings.value(QLatin1String(targetChunkUploadDurationC), 60*1000).toLongLong(); // default to 1 minute
}

void ConfigFile::setOptionalDesktopNotifications(bool show)
Expand Down
1 change: 1 addition & 0 deletions src/libsync/configfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class OWNCLOUDSYNC_EXPORT ConfigFile
quint64 chunkSize() const;
quint64 maxChunkSize() const;
quint64 minChunkSize() const;
quint64 targetChunkUploadDuration() const;

void saveGeometry(QWidget *w);
void restoreGeometry(QWidget *w);
Expand Down
31 changes: 30 additions & 1 deletion src/libsync/discoveryphase.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,41 @@ class Account;
*/

struct SyncOptions {
SyncOptions() : _newBigFolderSizeLimit(-1), _confirmExternalStorage(false) {}
SyncOptions()
: _newBigFolderSizeLimit(-1)
, _confirmExternalStorage(false)
, _initialChunkSize(10 * 1000 * 1000) // 10 MB
, _minChunkSize(1 * 1000 * 1000) // 1 MB
, _maxChunkSize(100 * 1000 * 1000) // 100 MB
, _targetChunkUploadDuration(60 * 1000) // 1 minute
{}

/** Maximum size (in Bytes) a folder can have without asking for confirmation.
* -1 means infinite */
qint64 _newBigFolderSizeLimit;

/** If a confirmation should be asked for external storages */
bool _confirmExternalStorage;

/** The initial un-adjusted chunk size in bytes for chunked uploads
*
* When dynamic chunk size adjustments are done, this is the
* starting value and is then gradually adjusted within the
* minChunkSize / maxChunkSize bounds.
*/
quint64 _initialChunkSize;

/** The minimum chunk size in bytes for chunked uploads */
quint64 _minChunkSize;

/** The maximum chunk size in bytes for chunked uploads */
quint64 _maxChunkSize;

/** The target duration of chunk uploads for dynamic chunk sizing.
*
* Set to 0 it will disable dynamic chunk sizing.
*/
quint64 _targetChunkUploadDuration;
};


Expand Down
43 changes: 14 additions & 29 deletions src/libsync/owncloudpropagator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,8 @@ PropagateItemJob* OwncloudPropagator::createJob(const SyncFileItemPtr &item) {
return job;
} else {
PropagateUploadFileCommon *job = 0;
if (item->_size > chunkSize() && account()->capabilities().chunkingNg()) {
job = new PropagateUploadFileNG(this, item, account()->capabilities().requestMaxDurationDC());
if (item->_size > _chunkSize && account()->capabilities().chunkingNg()) {
job = new PropagateUploadFileNG(this, item);
} else {
job = new PropagateUploadFileV1(this, item);
}
Expand Down Expand Up @@ -503,6 +503,17 @@ void OwncloudPropagator::start(const SyncFileItemVector& items)
scheduleNextJob();
}

const SyncOptions& OwncloudPropagator::syncOptions() const
{
return _syncOptions;
}

void OwncloudPropagator::setSyncOptions(const SyncOptions& syncOptions)
{
_syncOptions = syncOptions;
_chunkSize = syncOptions._initialChunkSize;
}

// ownCloud server < 7.0 did not had permissions so we need some other euristics
// to detect wrong doing in a Shared directory
bool OwncloudPropagator::isInSharedDirectory(const QString& file)
Expand All @@ -522,7 +533,7 @@ bool OwncloudPropagator::isInSharedDirectory(const QString& file)

int OwncloudPropagator::httpTimeout()
{
static int timeout;
static int timeout = 0;
if (!timeout) {
timeout = qgetenv("OWNCLOUD_TIMEOUT").toUInt();
if (timeout == 0) {
Expand All @@ -534,32 +545,6 @@ int OwncloudPropagator::httpTimeout()
return timeout;
}

quint64 OwncloudPropagator::chunkSize()
{
static uint chunkSize;
if (!chunkSize) {
chunkSize = qgetenv("OWNCLOUD_CHUNK_SIZE").toUInt();
if (chunkSize == 0) {
ConfigFile cfg;
chunkSize = cfg.chunkSize();
}
}
return chunkSize;
}

quint64 OwncloudPropagator::maxChunkSize()
{
static uint chunkSize;
if (!chunkSize) {
chunkSize = qgetenv("OWNCLOUD_MAX_CHUNK_SIZE").toUInt();
if (chunkSize == 0) {
ConfigFile cfg;
chunkSize = cfg.maxChunkSize();
}
}
return chunkSize;
}

bool OwncloudPropagator::localFileNameClash( const QString& relFile )
{
bool re = false;
Expand Down
19 changes: 15 additions & 4 deletions src/libsync/owncloudpropagator.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "syncjournaldb.h"
#include "bandwidthmanager.h"
#include "accountfwd.h"
#include "discoveryphase.h"

namespace OCC {

Expand Down Expand Up @@ -288,13 +289,17 @@ class OwncloudPropagator : public QObject {
, _finishedEmited(false)
, _bandwidthManager(this)
, _anotherSyncNeeded(false)
, _chunkSize(10 * 1000 * 1000) // 10 MB, overridden in setSyncOptions
, _account(account)
{ }

~OwncloudPropagator();

void start(const SyncFileItemVector &_syncedItems);

const SyncOptions& syncOptions() const;
void setSyncOptions(const SyncOptions& syncOptions);

QAtomicInt _downloadLimit;
QAtomicInt _uploadLimit;
BandwidthManager _bandwidthManager;
Expand All @@ -314,6 +319,15 @@ class OwncloudPropagator : public QObject {

/* the maximum number of jobs using bandwidth (uploads or downloads, in parallel) */
int maximumActiveTransferJob();

/** The size to use for upload chunks.
*
* Will be dynamically adjusted after each chunk upload finishes
* if Capabilities::desiredChunkUploadDuration has a target
* chunk-upload duration set.
*/
quint64 _chunkSize;

/* The maximum number of active jobs in parallel */
int hardMaximumActiveJob();

Expand Down Expand Up @@ -354,10 +368,6 @@ class OwncloudPropagator : public QObject {
// timeout in seconds
static int httpTimeout();

/** returns the size of chunks in bytes */
static quint64 chunkSize();
static quint64 maxChunkSize();

AccountPtr account() const;

enum DiskSpaceResult
Expand Down Expand Up @@ -404,6 +414,7 @@ private slots:

AccountPtr _account;
QScopedPointer<PropagateDirectory> _rootJob;
SyncOptions _syncOptions;

#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
// access to signals which are protected in Qt4
Expand Down
1 change: 1 addition & 0 deletions src/libsync/propagateupload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ void PUTFileJob::start() {
connect(_device, SIGNAL(wasReset()), this, SLOT(slotSoftAbort()));
#endif

_requestTimer.start();
AbstractNetworkJob::start();
}

Expand Down

1 comment on commit e86499d

@olifre
Copy link

@olifre olifre commented on e86499d Mar 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit adds 4 new environment variables, shouldn't they also be documented in envvars.rst, or are they added temporaritly only for debugging purposes?

Please sign in to comment.