I have a general abstract method which gets an input stream (can be from a network socket, or from a file on the local storage) and saves the data on the disk.
Below a small snippet of the function:
fun saveToFile(data: InputStream, fileDestination: File) {
val bytesWritten = Files.copy(data, fileDestination.toPath(), StandardCopyOption.REPLACE_EXISTING)
println("$bytesWritten bytes were saved at ${fileDestination.absolutePath}")
}
Is it possible to measure the speed/rate that the data are being saved on the disk while the process/method is on-going? For example, is there any possibility of invoking a function which returns the rate/speed or updates an object which holds that data?
If I was doing the implementation by myself with InputStream/OutputStream, I could have for example something like below:
fun saveData(data: InputStream, fileDestination: File, measureSpeed : (Statistics) -> Unit = { }) {
val outputStream = fileDestination.outputStream()
val maxBufferSize = 1024
val totalAmountData = data.available()
var totalBytesWritten = 0
var bytesWriteNextIteration: Int // amount of bytes that will be sent in only one write call
val statistics = Statistics(amountSent = 0, lastWriteBytes = 0, lastWriteTime = 1)
while (totalBytesWritten < totalAmountData) {
bytesWriteNextIteration = totalAmountData - totalBytesWritten
if (bytesWriteNextIteration > maxBufferSize) {
bytesWriteNextIteration = maxBufferSize
}
val bytes = ByteArray(bytesWriteNextIteration)
val nano = measureNanoTime {
outputStream.write(bytes)
}
statistics.amountSent = totalBytesWritten.toLong()
statistics.lastWriteBytes = bytesWriteNextIteration.toLong()
statistics.lastWriteTime = nano
measureSpeed(statistics)
totalBytesWritten += bytesWriteNextIteration
}
outputStream.flush()
outputStream.close()
}
data class Statistics(var amountSent: Long, var lastWriteBytes: Long, var lastWriteTime: Long)
and with measureSpeed
method to calculate the copy/transfer rate.
Since I didn't find anything in-built, the easiest way to do what's being asked is to "overload" the desired Files.copy
method and call that function instead.
The overloading method could be similar to the below:
private val BUFFER_SIZE = 8192
@Throws(IOException::class)
private fun copy(source: InputStream, sink: OutputStream, networkStatistics: NetworkStatistics, measureSpeed : (NetworkStatistics) -> Unit = { }): Long {
var nread = 0L
val buf = ByteArray(BUFFER_SIZE)
var n: Int
n = source.read(buf)
while (n > 0) {
val nano = measureNanoTime {
sink.write(buf, 0, n)
nread += n.toLong()
n = source.read(buf)
}
networkStatistics.amountSent = nread
networkStatistics.lastPacketBytes = n.toLong()
networkStatistics.lastPacketTime = nano
measureSpeed(networkStatistics)
}
return nread
}
@Throws(IOException::class)
fun copy(`in`: InputStream, target: Path, networkStatistics: NetworkStatistics, measureSpeed : (NetworkStatistics) -> Unit = { }, vararg options: CopyOption ): Long {
// ensure not null before opening file
Objects.requireNonNull(`in`)
// check for REPLACE_EXISTING
var replaceExisting = false
for (opt in options) {
if (opt === StandardCopyOption.REPLACE_EXISTING) {
replaceExisting = true
} else {
if (opt == null) {
throw NullPointerException("options contains 'null'")
} else {
throw UnsupportedOperationException(opt.toString() + " not supported")
}
}
}
// attempt to delete an existing file
var se: SecurityException? = null
if (replaceExisting) {
try {
Files.deleteIfExists(target)
} catch (x: SecurityException) {
se = x
}
}
// attempt to create target file. If it fails with
// FileAlreadyExistsException then it may be because the security
// manager prevented us from deleting the file, in which case we just
// throw the SecurityException.
val ostream: OutputStream
try {
ostream = Files.newOutputStream(target, StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE)
} catch (x: FileAlreadyExistsException) {
if (se != null)
throw se
// someone else won the race and created the file
throw x
}
// do the copy
ostream.use { out -> return copy(`in`, out, networkStatistics, measureSpeed = { networkStatistics -> measureSpeed(networkStatistics) }) }
}
and it would be called as:
val statistics = NetworkStatistics(responseShouldBe, 0, 0, 1)
copy(inputStream, file.toPath(), statistics, { it: NetworkStatistics -> measureSpeed(it) }, StandardCopyOption.REPLACE_EXISTING)
private fun measureSpeed(stats: NetworkStatistics) {
val a = stats.lastPacketBytes
val b = stats.lastPacketTime
val miliseconds = b.toDouble() / 1000
val seconds = miliseconds / 1000
println("$a per ($seconds seconds) or ($miliseconds milisecs) or ($b nanosecs) -- ${(a.toDouble()/(1024*1024))/seconds} MB/seconds")
}