Skip to content

Commit 2d457c9

Browse files
committed
feat(npm): Speed-up getting the remote package details
Obtaining the package details via `npm info` is a performance bottleneck of ORT's NPM package manager. Request the package details for all packages upfront, in parallel to reduce execution time. Experiments on a development machine show that execution of `NpmFunTest` now takes `1 min 13 sec` instead of `3 min 47 sec`. Fixes: #9950. Signed-off-by: Frank Viernau <[email protected]>
1 parent 42af1f5 commit 2d457c9

File tree

2 files changed

+39
-2
lines changed

2 files changed

+39
-2
lines changed

plugins/package-managers/node/src/main/kotlin/npm/Npm.kt

+37
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ package org.ossreviewtoolkit.plugins.packagemanagers.node.npm
2222
import java.io.File
2323
import java.util.LinkedList
2424

25+
import kotlinx.coroutines.Dispatchers
26+
import kotlinx.coroutines.async
27+
import kotlinx.coroutines.awaitAll
28+
import kotlinx.coroutines.withContext
29+
2530
import org.apache.logging.log4j.kotlin.logger
2631

2732
import org.ossreviewtoolkit.analyzer.PackageManagerFactory
@@ -45,6 +50,7 @@ import org.ossreviewtoolkit.utils.common.Os
4550
import org.ossreviewtoolkit.utils.common.ProcessCapture
4651
import org.ossreviewtoolkit.utils.common.collectMessages
4752
import org.ossreviewtoolkit.utils.common.withoutPrefix
53+
import org.ossreviewtoolkit.utils.ort.runBlocking
4854

4955
import org.semver4j.RangesList
5056
import org.semver4j.RangesListFactory
@@ -126,6 +132,9 @@ class Npm(override val descriptor: PluginDescriptor = NpmFactory.descriptor, pri
126132
val project = parseProject(definitionFile, analysisRoot)
127133
val projectModuleInfo = listModules(workingDir, issues).undoDeduplication()
128134

135+
// Warm-up the cache to speed-up processing:
136+
requestAllPackageDetails(projectModuleInfo)
137+
129138
val scopeNames = Scope.entries
130139
.filterNot { excludes.isScopeExcluded(it.descriptor) }
131140
.mapTo(mutableSetOf()) { scope ->
@@ -179,13 +188,41 @@ class Npm(override val descriptor: PluginDescriptor = NpmFactory.descriptor, pri
179188

180189
return process.extractNpmIssues()
181190
}
191+
192+
private fun requestAllPackageDetails(projectModuleInfo: ModuleInfo) {
193+
runBlocking {
194+
withContext(Dispatchers.IO.limitedParallelism(20)) {
195+
projectModuleInfo.getAllPackageNodeModuleIds().map { packageName ->
196+
async { getRemotePackageDetails(packageName) }
197+
}.awaitAll()
198+
}
199+
}
200+
}
182201
}
183202

184203
private enum class Scope(val descriptor: String) {
185204
DEPENDENCIES("dependencies"),
186205
DEV_DEPENDENCIES("devDependencies")
187206
}
188207

208+
private fun ModuleInfo.getAllPackageNodeModuleIds(): Set<String> {
209+
val queue = Scope.entries.flatMapTo(LinkedList()) { getScopeDependencies(it) }
210+
val result = mutableSetOf<String>()
211+
212+
while (queue.isNotEmpty()) {
213+
val info = queue.removeFirst()
214+
215+
@Suppress("ComplexCondition")
216+
if (!info.isProject && info.isInstalled && !info.name.isNullOrBlank() && !info.version.isNullOrBlank()) {
217+
result += "${info.name}@${info.version}"
218+
}
219+
220+
Scope.entries.flatMapTo(queue) { info.getScopeDependencies(it) }
221+
}
222+
223+
return result
224+
}
225+
189226
private fun ModuleInfo.getScopeDependencies(scope: Scope) =
190227
when (scope) {
191228
Scope.DEPENDENCIES -> dependencies.values.filter { !it.dev }

plugins/package-managers/node/src/main/kotlin/npm/NpmDependencyHandler.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ internal class NpmDependencyHandler(
6868
}
6969
}
7070

71-
private val ModuleInfo.isInstalled: Boolean get() = path != null
71+
internal val ModuleInfo.isInstalled: Boolean get() = path != null
7272

73-
private val ModuleInfo.isProject: Boolean get() = resolved == null
73+
internal val ModuleInfo.isProject: Boolean get() = resolved == null
7474

7575
private val ModuleInfo.packageJsonFile: File get() =
7676
File(

0 commit comments

Comments
 (0)