-
Notifications
You must be signed in to change notification settings - Fork 71
RUM-6171 use FileObserver to limit calls to filesystem #2718
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
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 |
---|---|---|
|
@@ -6,6 +6,11 @@ | |
|
||
package com.datadog.android.core.internal.persistence.file.batch | ||
|
||
import android.os.FileObserver | ||
import android.os.FileObserver.CREATE | ||
import android.os.FileObserver.DELETE | ||
import android.os.FileObserver.MOVED_TO | ||
import android.os.FileObserver.MOVED_FROM | ||
import androidx.annotation.WorkerThread | ||
import com.datadog.android.api.InternalLogger | ||
import com.datadog.android.core.internal.metrics.BatchClosedMetadata | ||
|
@@ -20,7 +25,6 @@ import com.datadog.android.core.internal.persistence.file.lengthSafe | |
import com.datadog.android.core.internal.persistence.file.listFilesSafe | ||
import com.datadog.android.core.internal.persistence.file.mkdirsSafe | ||
import java.io.File | ||
import java.io.FileFilter | ||
import java.util.Locale | ||
import java.util.concurrent.atomic.AtomicInteger | ||
import kotlin.math.roundToLong | ||
|
@@ -36,8 +40,6 @@ internal class BatchFileOrchestrator( | |
private val pendingFiles: AtomicInteger = AtomicInteger(0) | ||
) : FileOrchestrator { | ||
|
||
private val fileFilter = BatchFileFilter() | ||
|
||
// Offset the recent threshold for read and write to avoid conflicts | ||
// Arbitrary offset as ±5% of the threshold | ||
@Suppress("UnsafeThirdPartyFunctionCall") // rounded Double isn't NaN | ||
|
@@ -52,6 +54,37 @@ internal class BatchFileOrchestrator( | |
private var lastFileAccessTimestamp: Long = 0L | ||
private var lastCleanupTimestamp: Long = 0L | ||
|
||
private val knownFiles: MutableSet<File> = | ||
rootDir.listFilesSafe(internalLogger)?.toMutableSet() ?: mutableSetOf() | ||
|
||
@Suppress("DEPRECATION") // Recommended constructor only available in API 29 Q | ||
internal val fileObserver = object : FileObserver(rootDir.path, FILE_OBSERVER_MASK) { | ||
override fun onEvent(event: Int, name: String?) { | ||
if (!name.isNullOrEmpty() && name.isBatchFileName) { | ||
val file = File(rootDir, name) | ||
when (event) { | ||
MOVED_TO, CREATE -> { | ||
synchronized(knownFiles) { | ||
knownFiles.add(file) | ||
} | ||
|
||
} | ||
|
||
MOVED_FROM, DELETE -> { | ||
synchronized(knownFiles) { | ||
knownFiles.remove(file) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
init { | ||
fileObserver.startWatching() | ||
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. should we stop watching at some point ? not sure when though or if needed 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. In theory yes but not sure when either 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. We could use We probably need to still find a way to stop watching, because if 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. Apparently we should be good, because there is |
||
} | ||
|
||
// region FileOrchestrator | ||
|
||
@WorkerThread | ||
|
@@ -323,7 +356,9 @@ internal class BatchFileOrchestrator( | |
} | ||
|
||
private fun listBatchFiles(): List<File> { | ||
return rootDir.listFilesSafe(fileFilter, internalLogger).orEmpty().toList() | ||
return synchronized(knownFiles) { | ||
knownFiles.toList() | ||
} | ||
} | ||
|
||
private fun listSortedBatchFiles(): List<File> { | ||
|
@@ -340,28 +375,20 @@ internal class BatchFileOrchestrator( | |
get() = File("${this.path}_metadata") | ||
|
||
private val File.isBatchFile: Boolean | ||
get() = name.toLongOrNull() != null | ||
get() = name.isBatchFileName | ||
|
||
private val String.isBatchFileName: Boolean | ||
get() = toLongOrNull() != null | ||
|
||
private val List<File>.latestBatchFile: File? | ||
get() = maxOrNull() | ||
|
||
// endregion | ||
|
||
// region FileFilter | ||
|
||
internal inner class BatchFileFilter : FileFilter { | ||
@Suppress("ReturnCount") | ||
override fun accept(file: File?): Boolean { | ||
if (file == null) return false | ||
|
||
return file.isBatchFile | ||
} | ||
} | ||
|
||
// endregion | ||
|
||
companion object { | ||
|
||
private const val FILE_OBSERVER_MASK = CREATE or DELETE or MOVED_TO or MOVED_FROM | ||
|
||
const val DECREASE_PERCENT = 0.95 | ||
const val INCREASE_PERCENT = 1.05 | ||
|
||
|
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.
I realized the it may bring the following concern: this will be called on the user thread during SDK initialization and since it is I/O call, it may slow down SDK start (especially if there is a lot of files on the disk).
Instead, we maybe can to initial read in the
getWritableFile
/getReadableFile
methods (well, on any initial access of this property), this will shift it to the worker thread improving SDK initialization.Same may apply to the
root
folder creation.