How to measure performance/speed of Files.copy met

2019-09-19 16:42发布

问题:

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.

回答1:

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")
}