diff --git a/python/pluginResources/intellij.python.community.impl.xml b/python/pluginResources/intellij.python.community.impl.xml
index 960d33d9813f..87b2c43ca45d 100644
--- a/python/pluginResources/intellij.python.community.impl.xml
+++ b/python/pluginResources/intellij.python.community.impl.xml
@@ -78,6 +78,8 @@
+
+
diff --git a/python/src/com/jetbrains/python/run/PythonCommandLineState.java b/python/src/com/jetbrains/python/run/PythonCommandLineState.java
index c9cf0f2246a4..8dd3893a8e91 100644
--- a/python/src/com/jetbrains/python/run/PythonCommandLineState.java
+++ b/python/src/com/jetbrains/python/run/PythonCommandLineState.java
@@ -67,8 +67,10 @@ import com.jetbrains.python.library.PythonLibraryType;
import com.jetbrains.python.packaging.PyExecutionException;
import com.jetbrains.python.remote.PyRemotePathMapper;
import com.jetbrains.python.remote.PyRemoteSdkAdditionalData;
+import com.jetbrains.python.run.features.PyRunToolIds;
import com.jetbrains.python.run.features.PyRunToolParameters;
import com.jetbrains.python.run.features.PyRunToolProvider;
+import com.jetbrains.python.run.features.PyRunToolUsageCollector;
import com.jetbrains.python.run.target.HelpersAwareTargetEnvironmentRequest;
import com.jetbrains.python.run.target.PySdkTargetPaths;
import com.jetbrains.python.run.target.PythonCommandLineTargetEnvironmentProvider;
@@ -354,6 +356,7 @@ public abstract class PythonCommandLineState extends CommandLineState {
PyRunToolProvider runToolProvider = PyRunToolProvider.forSdk(sdk);
if (runToolProvider != null && useRunTool(myConfig, sdk)) {
runToolParameters = runToolProvider.getRunToolParameters();
+ PyRunToolUsageCollector.logRun(myConfig.getProject(), PyRunToolIds.idOf(runToolProvider));
}
}
diff --git a/python/src/com/jetbrains/python/run/features/PyRunToolProvider.kt b/python/src/com/jetbrains/python/run/features/PyRunToolProvider.kt
index b990d0c81b37..05af09be6645 100644
--- a/python/src/com/jetbrains/python/run/features/PyRunToolProvider.kt
+++ b/python/src/com/jetbrains/python/run/features/PyRunToolProvider.kt
@@ -11,18 +11,25 @@ import org.jetbrains.annotations.NonNls
@ApiStatus.Internal
value class PyRunToolId(@param:NonNls val value: String)
+internal object PyRunToolIds {
+ @JvmStatic
+ fun idOf(provider: PyRunToolProvider): String = provider.runToolData.id.value
+}
+
/**
* Metadata describing a concrete Python "Run with …" tool option.
*
* - id: a unique identifier of the tool.
* - name: user‑visible action/configuration name (localized).
* - group: user‑visible group name under which the action is shown in UI (localized).
+ * - idForStatistics: identifier for FUS statistics (should not change to avoid breaking statistics).
*/
@ApiStatus.Internal
data class PyRunToolData(
@param:NonNls val id: PyRunToolId,
@param:Nls val name: String,
- @param:Nls val group: String
+ @param:Nls val group: String,
+ @param:NonNls val idForStatistics: String = id.value
)
/**
diff --git a/python/src/com/jetbrains/python/run/features/PyRunToolUsageCollector.kt b/python/src/com/jetbrains/python/run/features/PyRunToolUsageCollector.kt
new file mode 100644
index 000000000000..db384ad3810d
--- /dev/null
+++ b/python/src/com/jetbrains/python/run/features/PyRunToolUsageCollector.kt
@@ -0,0 +1,49 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.run.features
+
+import com.intellij.internal.statistic.eventLog.EventLogGroup
+import com.intellij.internal.statistic.eventLog.events.EventFields
+import com.intellij.internal.statistic.eventLog.events.EventId1
+import com.intellij.internal.statistic.eventLog.validator.ValidationResultType
+import com.intellij.internal.statistic.eventLog.validator.rules.EventContext
+import com.intellij.internal.statistic.eventLog.validator.rules.impl.CustomValidationRule
+import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
+import com.intellij.internal.statistic.utils.getPluginInfo
+import com.intellij.openapi.project.Project
+
+/**
+ * FUS collector for "Run with …" Python tools exposed via [PyRunToolProvider].
+ * Logs how many runs are started via a run tool and the tool id.
+ */
+internal object PyRunToolUsageCollector : CounterUsagesCollector() {
+ private val GROUP = EventLogGroup("python.run.tool", 1, "FUS", "Tracks Python run configurations executed with specific run tools (e.g., uv run)")
+
+ private val TOOL_ID = EventFields.StringValidatedByCustomRule("run_tool_id", PyRunToolIdValidator::class.java)
+
+ private val RUN_USED: EventId1 = GROUP.registerEvent("run.used", TOOL_ID, "Execution of Python run configurations with specific run tool")
+
+ @JvmStatic
+ fun logRun(project: Project, runToolId: String) {
+ RUN_USED.log(project, runToolId)
+ }
+
+ override fun getGroup(): EventLogGroup = GROUP
+}
+
+/**
+ * Custom validator for PyRunTool IDs in FUS statistics.
+ * Validates that the ID corresponds to a known PyRunToolProvider.
+ */
+internal class PyRunToolIdValidator : CustomValidationRule() {
+ override fun getRuleId(): String = "python_run_tool"
+
+ override fun doValidate(data: String, context: EventContext): ValidationResultType {
+ val validIds = PyRunToolProvider.EP.extensionList
+ .asSequence()
+ .filter { getPluginInfo(it.javaClass).isDevelopedByJetBrains() }
+ .map { it.runToolData.idForStatistics }
+ .toSet()
+
+ return if (data in validIds) ValidationResultType.ACCEPTED else ValidationResultType.REJECTED
+ }
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/uv/run/UvRunToolProvider.kt b/python/src/com/jetbrains/python/sdk/uv/run/UvRunToolProvider.kt
index 4d6f8bd8b1db..10b0614206a3 100644
--- a/python/src/com/jetbrains/python/sdk/uv/run/UvRunToolProvider.kt
+++ b/python/src/com/jetbrains/python/sdk/uv/run/UvRunToolProvider.kt
@@ -15,7 +15,7 @@ import com.jetbrains.python.sdk.uv.isUv
private class UvRunToolProvider : PyRunToolProvider {
override val runToolData: PyRunToolData = PyRunToolData(
- PyRunToolId("py.run.with.uv"),
+ PyRunToolId("uv.run"),
PyBundle.message("uv.run.configuration.type.display.name"),
PyBundle.message("python.run.configuration.fragments.python.group"),
)