IJPL-174608 platform: report linux distributive info in 'About'

(cherry picked from commit 85835ee6ad77b4ff6c708857ba69761068d2d16f)

GitOrigin-RevId: 0cb3f7b3451be0dc18bcac07e79ed10be0fd80b7
This commit is contained in:
Aleksey Pivovarov
2025-01-17 13:01:43 +01:00
committed by intellij-monorepo-bot
parent 98ef6fded6
commit d43fe4c2e4
7 changed files with 141 additions and 51 deletions

View File

@@ -2014,6 +2014,7 @@ c:com.intellij.help.impl.HelpManagerImpl
- invokeHelp(java.lang.String):V
com.intellij.ide.AboutPopupDescriptionProvider
- a:getDescription():java.lang.String
- getExtendedDescription():java.lang.String
f:com.intellij.ide.ChangeProjectIconPalette
- com.intellij.util.ui.ColorPalette
- <init>(I):V

View File

@@ -10,15 +10,12 @@ import com.intellij.internal.statistic.eventLog.events.EventFields.Version
import com.intellij.internal.statistic.eventLog.events.EventPair
import com.intellij.internal.statistic.service.fus.collectors.ApplicationUsagesCollector
import com.intellij.openapi.util.SystemInfo
import com.intellij.util.UnixUtil.getGlibcVersion
import com.intellij.util.UnixUtil
import org.jetbrains.annotations.ApiStatus
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import java.time.OffsetDateTime
import java.util.*
import kotlin.io.path.name
import kotlin.streams.asSequence
internal class OsDataCollector : ApplicationUsagesCollector() {
private val OS_NAMES = listOf("Windows", "Mac", "Linux", "FreeBSD", "Solaris", "Other")
@@ -64,12 +61,12 @@ internal class OsDataCollector : ApplicationUsagesCollector() {
TIMEZONE.metric(tz))
when {
SystemInfo.isLinux -> {
val (distro, release) = getReleaseData()
val isUnderWsl = detectIsUnderWsl()
val glibcVersion = getGlibcVersion()
val linuxMetrics = mutableListOf<EventPair<*>>(DISTRO.with(distro), RELEASE.with(release), UNDER_WSL.with(isUnderWsl))
if (glibcVersion != null) {
linuxMetrics.add(GLIBC.with(glibcVersion.toString()))
val distroInfo = UnixUtil.getOsInfo()
val linuxMetrics = mutableListOf<EventPair<*>>(DISTRO.with(DISTROS.coerce(distroInfo.distro)),
RELEASE.with(distroInfo.release),
UNDER_WSL.with(distroInfo.isUnderWsl))
if (distroInfo.glibcVersion != null) {
linuxMetrics.add(GLIBC.with(distroInfo.glibcVersion.toString()))
}
metrics += LINUX.metric(*linuxMetrics.toTypedArray())
}
@@ -98,32 +95,6 @@ internal class OsDataCollector : ApplicationUsagesCollector() {
if (SystemInfo.isWindows) null
else SHELLS.coerce(runCatching { System.getenv("SHELL")?.let { Path.of(it).name } }.getOrNull())
// https://www.freedesktop.org/software/systemd/man/os-release.html
private fun getReleaseData(): Pair<String, String?> =
try {
Files.lines(Path.of("/etc/os-release")).use { lines ->
val fields = setOf("ID", "VERSION_ID")
val values = lines.asSequence()
.map { it.split('=') }
.filter { it.size == 2 && it[0] in fields }
.associate { it[0] to it[1].trim('"') }
val distro = DISTROS.coerce(values["ID"])
distro to values["VERSION_ID"]
}
}
catch (ignored: IOException) {
"unknown" to null
}
private fun detectIsUnderWsl(): Boolean =
try {
@Suppress("SpellCheckingInspection") val kernel = Files.readString(Path.of("/proc/sys/kernel/osrelease"))
kernel.contains("-microsoft-")
}
catch(e: IOException) {
false
}
private fun List<String>.coerce(value: String?): String =
when (value) {
null -> "unknown"

View File

@@ -13,4 +13,9 @@ interface AboutPopupDescriptionProvider {
* Return additional info which should be shown in the "About" dialog.
*/
fun getDescription(): @DetailedDescription String?
/**
* Return additional info which should be copied into clipboard in the "About" dialog.
*/
fun getExtendedDescription(): @DetailedDescription String? = getDescription()
}

View File

@@ -0,0 +1,52 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.util.NlsContexts.DetailedDescription
import com.intellij.openapi.util.SystemInfo
import com.intellij.util.UnixUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@Service(Service.Level.APP)
private class OsDataLogger(val coroutineScope: CoroutineScope) {
internal var osInfoAboutString: String? = null
fun reportLinuxDistro() {
coroutineScope.launch {
if (SystemInfo.isLinux || SystemInfo.isFreeBSD) {
val osInfo = UnixUtil.getOsInfo()
val name = osInfo.prettyName
?: (osInfo.distro ?: "Unknown Distro").appendNotBlank(" ", osInfo.release)
val info = name
.appendNotBlank(" ", if (osInfo.isUnderWsl) "(in WSL)" else null)
.appendNotBlank("; glibc: ", osInfo.glibcVersion?.toString())
logger<OsDataLogger>().info(info)
osInfoAboutString = info
}
}
}
private fun String.appendNotBlank(delimiter: String?, value: String?): String {
if (value.isNullOrBlank()) return this
return this + delimiter + value
}
}
internal class OsDataLoggerApplicationInitializedListener : ApplicationInitializedListener {
override suspend fun execute() {
service<OsDataLogger>().reportLinuxDistro()
}
}
internal class OsDataLoggerAboutPopupDescriptionProvider : AboutPopupDescriptionProvider {
override fun getDescription(): @DetailedDescription String? = null
override fun getExtendedDescription(): @DetailedDescription String? {
return service<OsDataLogger>().osInfoAboutString
}
}

View File

@@ -194,8 +194,7 @@ public final class AboutDialog extends DialogWrapper {
myInfo.add(MessageFormat.format("VM: {0} by {1}", vmVersion, vmVendor));
//Print extra information from plugins
ExtensionPointName<AboutPopupDescriptionProvider> ep = new ExtensionPointName<>("com.intellij.aboutPopupDescriptionProvider");
for (AboutPopupDescriptionProvider aboutInfoProvider : ep.getExtensions()) {
for (AboutPopupDescriptionProvider aboutInfoProvider : EP_NAME.getExtensionList()) {
String description = aboutInfoProvider.getDescription();
if (description != null) {
lines.add(description);
@@ -287,7 +286,7 @@ public final class AboutDialog extends DialogWrapper {
text.append(SystemInfo.getOsNameAndVersion()).append('\n');
for (var aboutInfoProvider : EP_NAME.getExtensionList()) {
var description = aboutInfoProvider.getDescription();
var description = aboutInfoProvider.getExtendedDescription();
if (description != null) {
text.append(description).append('\n');
}

View File

@@ -1,11 +1,16 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.util
import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.util.ExecUtil
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.SystemInfo
import org.jetbrains.annotations.ApiStatus
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import kotlin.streams.asSequence
private const val COMMAND_LINE_TIMEOUT = 1000
private const val LIBC_LIBRARY_NAME = "LIBC"
@@ -14,21 +19,75 @@ private const val LIBC_LIBRARY_NAME = "LIBC"
object UnixUtil {
private val LOG = Logger.getInstance(UnixUtil::class.java.name)
fun getOsInfo(): OsInfo {
val osRelease = getReleaseData()
val isUnderWsl = detectIsUnderWsl()
val glibcVersion = getGlibcVersion()
return OsInfo(osRelease.distro, osRelease.release, osRelease.prettyName,
isUnderWsl, glibcVersion)
}
private fun detectIsUnderWsl(): Boolean {
try {
@Suppress("SpellCheckingInspection") val kernel = Files.readString(Path.of("/proc/sys/kernel/osrelease"))
return kernel.contains("-microsoft-")
}
catch (_: IOException) {
return false
}
}
// https://www.freedesktop.org/software/systemd/man/os-release.html
private fun getReleaseData(): OsRelease {
try {
Files.lines(Path.of("/etc/os-release")).use { lines ->
val fields = setOf("ID", "VERSION_ID", "PRETTY_NAME")
val values = lines.asSequence()
.map { it.split('=') }
.filter { it.size == 2 && it[0] in fields }
.associate { it[0] to it[1].trim('"') }
return OsRelease(values["ID"], values["VERSION_ID"], values["PRETTY_NAME"])
}
}
catch (_: IOException) {
return OsRelease(null, null, null)
}
}
@JvmStatic
fun getGlibcVersion(): Double? {
check(SystemInfo.isLinux) { "glibc version is only supported on Linux" }
val commandLine = GeneralCommandLine("ldd", "--version")
val output = ExecUtil.execAndGetOutput(commandLine, COMMAND_LINE_TIMEOUT)
if (output.exitCode != 0) {
LOG.debug("Failed to execute ${commandLine.commandLineString} exit code ${output.exitCode}")
try {
check(SystemInfo.isLinux) { "glibc version is only supported on Linux" }
val commandLine = GeneralCommandLine("ldd", "--version")
val output = ExecUtil.execAndGetOutput(commandLine, COMMAND_LINE_TIMEOUT)
if (output.exitCode != 0) {
LOG.debug("Failed to execute ${commandLine.commandLineString} exit code ${output.exitCode}")
return null
}
val outputStr = output.stdout.split("\n").firstOrNull { it.contains(LIBC_LIBRARY_NAME, true) }
if (outputStr == null) {
LOG.debug("Failed to find $LIBC_LIBRARY_NAME in ${output.stdout}")
return null
}
val version = outputStr.split(" ").lastOrNull { it.isNotEmpty() } ?: return null
return version.toDoubleOrNull()
}
catch (_: ExecutionException) {
return null
}
val outputStr = output.stdout.split("\n").firstOrNull { it.contains(LIBC_LIBRARY_NAME, true) }
if (outputStr == null) {
LOG.debug("Failed to find $LIBC_LIBRARY_NAME in ${output.stdout}")
return null
}
val version = outputStr.split(" ").lastOrNull { it.isNotEmpty() } ?: return null
return version.toDoubleOrNull()
}
data class OsInfo(
val distro: String?,
val release: String?,
val prettyName: String?,
val isUnderWsl: Boolean,
val glibcVersion: Double?,
)
private data class OsRelease(
val distro: String?,
val release: String?,
val prettyName: String?,
)
}

View File

@@ -1035,6 +1035,9 @@
<statistics.collectorExtension implementation="com.intellij.ide.actions.ToolwindowFusEventFields"/>
<statistics.collectorExtension implementation="com.intellij.ide.actions.DragEditorTabsFusEventFields"/>
<applicationInitializedListener implementation="com.intellij.ide.OsDataLoggerApplicationInitializedListener"/>
<aboutPopupDescriptionProvider implementation="com.intellij.ide.OsDataLoggerAboutPopupDescriptionProvider"/>
<statistics.applicationUsagesCollector implementation="com.intellij.featureStatistics.fusCollectors.EAPUsageCollector"
allowOnStartup="true"/>
<statistic.eventLog.eventLoggerProvider implementation="com.intellij.internal.statistic.eventLog.fus.FeatureUsageEventLoggerProvider"/>