Indexing time diagnostic: count statistic values (min/max/mean/median), use millis instead of nano in presentations.

GitOrigin-RevId: 46a659987dfebe648aac0bc2275230d6fcfc2017
This commit is contained in:
Sergey Patrikeev
2020-05-02 16:32:25 +00:00
committed by intellij-monorepo-bot
parent ea679a4e0a
commit 9be68d5046
3 changed files with 130 additions and 47 deletions
@@ -8,37 +8,70 @@ import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
data class JsonPerThreadTime(
val perThreadTimes: List<TimeNano>,
val totalCpuTime: TimeNano
private fun TimeNano.toMillis(): TimeMillis = this / 1_000_000
data class JsonPerThreadTimeStats(
val minTime: TimeMillis,
val maxTime: TimeMillis,
val meanTime: TimeMillis,
val medianTime: TimeMillis
)
fun PerThreadTime.toJsonPerThreadTime(): JsonPerThreadTime =
JsonPerThreadTime(
threadIdToTime.values.sortedDescending(),
threadIdToTime.values.sum()
fun PerThreadTime.toJsonPerThreadTime(): JsonPerThreadTimeStats {
val timeBuckets = threadIdToTimeBucket.values
if (timeBuckets.isEmpty()) {
return JsonPerThreadTimeStats(-1, -1, -1, -1)
}
val median = getMedianOfArray(timeBuckets.flatMap { it.maxNTimes })
val min = timeBuckets.map { it.minTime }.min()!!
val max = timeBuckets.map { it.maxTime }.max()!!
val mean = getMeanOfArray(timeBuckets.map { it.meanTime })
return JsonPerThreadTimeStats(
min.toMillis(),
max.toMillis(),
mean.toLong().toMillis(),
median.toLong().toMillis()
)
}
private fun <N: Number> getMeanOfArray(elements: Collection<N>): Double {
require(elements.isNotEmpty())
return elements.map { it.toDouble() }.sum() / elements.size
}
private fun <N: Number> getMedianOfArray(elements: Collection<N>): Double {
require(elements.isNotEmpty())
val sorted = elements.map { it.toDouble() }.sorted()
return if (sorted.size % 2 == 0) {
(sorted[sorted.size / 2] + sorted[sorted.size / 2 - 1]) / 2.0
}
else {
sorted[sorted.size / 2]
}
}
data class JsonFileProviderIndexStatistics(
val indexableFilesProviderDebugName: String,
val providerName: String,
// <total time> = <content loading time> + <indexing time> + <time spent on waiting for other indexing tasks to complete>
val totalTime: TimeNano,
val indexingTime: JsonPerThreadTime,
val contentLoadingTime: JsonPerThreadTime,
val totalTime: TimeMillis,
val indexingTimePerFile: JsonPerThreadTimeStats,
val contentLoadingTimePerFile: JsonPerThreadTimeStats,
val numberOfFilesPerFileType: List<FilesNumberPerFileType>,
val timesPerFileType: List<TimePerFileType>,
val timesPerIndexer: List<TimePerIndexer>
) {
data class TimePerIndexer(val indexId: String, val time: JsonPerThreadTime)
data class TimePerFileType(val fileType: String, val time: JsonPerThreadTime)
data class TimePerIndexer(val indexId: String, val time: JsonPerThreadTimeStats)
data class TimePerFileType(val fileType: String, val time: JsonPerThreadTimeStats)
data class FilesNumberPerFileType(val fileType: String, val filesNumber: Int)
}
fun FileProviderIndexStatistics.convertToJson(): JsonFileProviderIndexStatistics =
JsonFileProviderIndexStatistics(
providerDebugName,
totalTime,
totalTime.toMillis(),
indexingStatistics.indexingTime.toJsonPerThreadTime(),
indexingStatistics.contentLoadingTime.toJsonPerThreadTime(),
indexingStatistics.numberOfFilesPerFileType
@@ -47,50 +80,58 @@ fun FileProviderIndexStatistics.convertToJson(): JsonFileProviderIndexStatistics
,
indexingStatistics.timesPerFileType
.map { JsonFileProviderIndexStatistics.TimePerFileType(it.key, it.value.toJsonPerThreadTime()) }
.sortedByDescending { it.time.totalCpuTime }
.sortedByDescending { it.time.meanTime }
,
indexingStatistics.timesPerIndexer
.map { JsonFileProviderIndexStatistics.TimePerIndexer(it.key, it.value.toJsonPerThreadTime()) }
.sortedByDescending { it.time.totalCpuTime }
.sortedByDescending { it.time.meanTime }
)
typealias PresentableTime = String
private fun TimeMillis.toPresentableTime(): PresentableTime =
DateFormatUtil.getIso8601Format().format(this)
@Suppress("unused", "used for JSON")
data class JsonProjectIndexingHistoryTimes(
val startIndexing: TimeMillis,
val endIndexing: TimeMillis,
val startIndexing: PresentableTime,
val endIndexing: PresentableTime,
val indexingTime: TimeMillis,
val startPushProperties: TimeMillis,
val endPushProperties: TimeMillis,
val startPushProperties: PresentableTime,
val endPushProperties: PresentableTime,
val pushPropertiesTime: TimeMillis,
val startIndexExtensions: TimeMillis,
val endIndexExtensions: TimeMillis,
val startIndexExtensions: PresentableTime,
val endIndexExtensions: PresentableTime,
val indexExtensionsTime: TimeMillis,
val startScanFiles: TimeMillis,
val endScanFiles: TimeMillis
) {
val presentableStartIndexingTime: String get() = DateFormatUtil.formatTimeWithSeconds(startIndexing)
val indexingTime: TimeMillis get() = endIndexing - startIndexing
val pushPropertiesTime: TimeMillis get() = endPushProperties - startPushProperties
val indexExtensionsTime: TimeMillis get() = endIndexExtensions - startIndexExtensions
val scanFilesTime: TimeMillis get() = endScanFiles - startScanFiles
}
val startScanFiles: PresentableTime,
val endScanFiles: PresentableTime,
val scanFilesTime: TimeMillis
)
fun ProjectIndexingHistory.IndexingTimes.convertToJson(): JsonProjectIndexingHistoryTimes =
JsonProjectIndexingHistoryTimes(
startIndexing,
endIndexing,
startPushProperties,
endPushProperties,
startIndexExtensions,
endIndexExtensions,
startScanFiles,
endScanFiles
fun ProjectIndexingHistory.IndexingTimes.convertToJson(): JsonProjectIndexingHistoryTimes {
return JsonProjectIndexingHistoryTimes(
startIndexing.toPresentableTime(),
endIndexing.toPresentableTime(),
endIndexing - startIndexing,
startPushProperties.toPresentableTime(),
endPushProperties.toPresentableTime(),
endPushProperties - startPushProperties,
startIndexExtensions.toPresentableTime(),
endIndexExtensions.toPresentableTime(),
endIndexExtensions - startIndexExtensions,
startScanFiles.toPresentableTime(),
endScanFiles.toPresentableTime(),
endScanFiles - startScanFiles
)
}
data class JsonProjectIndexingHistory(
val projectName: String,
val indexingTimes: JsonProjectIndexingHistoryTimes,
val providerStatistics: List<JsonFileProviderIndexStatistics>
val fileProviderStatistics: List<JsonFileProviderIndexStatistics>
)
fun ProjectIndexingHistory.convertToJson(): JsonProjectIndexingHistory {
@@ -99,7 +140,7 @@ fun ProjectIndexingHistory.convertToJson(): JsonProjectIndexingHistory {
times.convertToJson(),
providerStatistics
.map { it.convertToJson() }
.sortedByDescending { it.indexingTime.totalCpuTime }
.sortedByDescending { it.indexingTimePerFile.meanTime }
)
}
@@ -0,0 +1,38 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.indexing.diagnostic
import java.util.*
/**
* Bucket of times with size of at most [sizeLimit], where the first add time is [firstTime]
* Maintains min, max, mean of all added times.
*/
class MaxNTimeBucket(private val sizeLimit: Int, firstTime: TimeNano) {
private val _maxNTimes: PriorityQueue<TimeNano> = PriorityQueue()
private var _count: Long = 1
private var _minTime: TimeNano = firstTime
private var _maxTime: TimeNano = firstTime
private var _sum: TimeNano = firstTime
@Synchronized
fun addTime(time: TimeNano) {
_maxNTimes += time
if (_maxTime < time) _maxTime = time
if (_minTime > time) _minTime = time
_sum += time
_count++
while (_maxNTimes.size > sizeLimit) {
_maxNTimes.poll()
}
}
val count: Long @Synchronized get() = _count
val totalTime: TimeNano @Synchronized get() = _sum
val minTime: TimeNano @Synchronized get() = _minTime
val maxTime: TimeNano @Synchronized get() = _maxTime
val meanTime: Double @Synchronized get() = _sum.toDouble() / _count
val maxNTimes: List<TimeNano> @Synchronized get() = _maxNTimes.toList()
}
@@ -5,13 +5,17 @@ import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
class PerThreadTime {
val threadIdToTime: Map<Long, TimeNano>
get() = HashMap(_threadIdToTime)
private val timeBucketSize = 1024
private val _threadIdToTime: ConcurrentMap<Long, TimeNano> = ConcurrentHashMap()
val threadIdToTimeBucket: Map<Long, MaxNTimeBucket>
get() = HashMap(_threadIdToTimeBucket)
private val _threadIdToTimeBucket: ConcurrentMap<Long, MaxNTimeBucket> = ConcurrentHashMap()
fun addTimeSpentInCurrentThread(nanoTime: TimeNano) {
val currentThread = Thread.currentThread()
_threadIdToTime.compute(currentThread.id) { _, currentTime -> (currentTime ?: 0L) + nanoTime }
_threadIdToTimeBucket
.computeIfAbsent(currentThread.id) { MaxNTimeBucket(timeBucketSize, nanoTime) }
.addTime(nanoTime)
}
}