Skip to content

Commit 2e3fc3d

Browse files
committed
SONARPY-2457 Collect data for the Python version
1 parent bf32141 commit 2e3fc3d

File tree

3 files changed

+72
-7
lines changed

3 files changed

+72
-7
lines changed

sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonSensor.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,30 +68,32 @@ public final class PythonSensor implements Sensor {
6868
static final String UNSET_VERSION_WARNING = "Your code is analyzed as compatible with all Python 3 versions by default." +
6969
" You can get a more precise analysis by setting the exact Python version in your configuration via the parameter \"sonar.python.version\"";
7070

71+
private final SensorTelemetryStorage sensorTelemetryStorage;
72+
7173
/**
7274
* Constructor to be used by pico if neither PythonCustomRuleRepository nor PythonIndexer are to be found and injected.
7375
*/
7476
public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory,
7577
NoSonarFilter noSonarFilter, AnalysisWarningsWrapper analysisWarnings) {
76-
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, null, null, analysisWarnings);
78+
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, null, null, analysisWarnings, new SensorTelemetryStorage());
7779
}
7880

7981
public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter,
8082
PythonCustomRuleRepository[] customRuleRepositories, AnalysisWarningsWrapper analysisWarnings) {
81-
this(fileLinesContextFactory, checkFactory, noSonarFilter, customRuleRepositories, null, null, analysisWarnings);
83+
this(fileLinesContextFactory, checkFactory, noSonarFilter, customRuleRepositories, null, null, analysisWarnings, new SensorTelemetryStorage());
8284
}
8385

8486
public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter,
8587
PythonIndexer indexer, SonarLintCache sonarLintCache, AnalysisWarningsWrapper analysisWarnings) {
8688
// ^^ This constructor implicitly assumes that a PythonIndexer and a SonarLintCache are always available at the same time.
8789
// In practice, this is currently the case, since both are provided by PythonPlugin under the same conditions.
8890
// See also PythonPlugin::SonarLintPluginAPIManager::addSonarlintPythonIndexer.
89-
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, indexer, sonarLintCache, analysisWarnings);
91+
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, indexer, sonarLintCache, analysisWarnings, new SensorTelemetryStorage());
9092
}
9193

9294
public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter,
9395
@Nullable PythonCustomRuleRepository[] customRuleRepositories, @Nullable PythonIndexer indexer,
94-
@Nullable SonarLintCache sonarLintCache, AnalysisWarningsWrapper analysisWarnings) {
96+
@Nullable SonarLintCache sonarLintCache, AnalysisWarningsWrapper analysisWarnings, SensorTelemetryStorage sensorTelemetryStorage) {
9597
this.checks = new PythonChecks(checkFactory)
9698
.addChecks(CheckList.REPOSITORY_KEY, CheckList.getChecks())
9799
.addCustomChecks(customRuleRepositories);
@@ -100,6 +102,12 @@ public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactor
100102
this.indexer = indexer;
101103
this.sonarLintCache = sonarLintCache;
102104
this.analysisWarnings = analysisWarnings;
105+
this.sensorTelemetryStorage = sensorTelemetryStorage;
106+
}
107+
108+
public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter mock, PythonCustomRuleRepository[] customRuleRepositories,
109+
AnalysisWarningsWrapper analysisWarnings, SensorTelemetryStorage sensorTelemetryStorage) {
110+
this(fileLinesContextFactory, checkFactory, mock, customRuleRepositories, null, null, analysisWarnings, sensorTelemetryStorage);
103111
}
104112

105113
@Override
@@ -121,15 +129,25 @@ public void execute(SensorContext context) {
121129
if (pythonVersionParameter.length != 0){
122130
ProjectPythonVersion.setCurrentVersions(PythonVersionUtils.fromStringArray(pythonVersionParameter));
123131
}
132+
setTelemetry(context, pythonVersionParameter);
124133
CacheContext cacheContext = CacheContextImpl.of(context);
125134
PythonIndexer pythonIndexer = this.indexer != null ? this.indexer : new SonarQubePythonIndexer(pythonFiles, cacheContext, context);
126135
pythonIndexer.setSonarLintCache(sonarLintCache);
127136
TypeShed.setProjectLevelSymbolTable(pythonIndexer.projectLevelSymbolTable());
128137
PythonScanner scanner = new PythonScanner(context, checks, fileLinesContextFactory, noSonarFilter, PythonParser.create(), pythonIndexer);
129138
scanner.execute(pythonFiles, context);
139+
sensorTelemetryStorage.send(context);
130140
durationReport.stop();
131141
}
132142

143+
private void setTelemetry(SensorContext context, String[] pythonVersionParameter) {
144+
var isVersionSet = pythonVersionParameter.length != 0 || context.runtime().getProduct() == SonarProduct.SONARLINT;
145+
if (pythonVersionParameter.length != 0) {
146+
sensorTelemetryStorage.updateMetric(TelemetryMetricKey.PYTHON_VERSION_KEY, String.join(",", pythonVersionParameter));
147+
}
148+
sensorTelemetryStorage.updateMetric(TelemetryMetricKey.PYTHON_VERSION_SET_KEY, isVersionSet ? "1" : "0");
149+
}
150+
133151
private static List<PythonInputFile> getInputFiles(SensorContext context) {
134152
FilePredicates p = context.fileSystem().predicates();
135153
Iterable<InputFile> it = context.fileSystem().inputFiles(p.and(p.hasLanguage(Python.KEY)));

sonar-python-plugin/src/main/java/org/sonar/plugins/python/TelemetryMetricKey.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ public enum TelemetryMetricKey {
2020
NOTEBOOK_PRESENT_KEY("python.notebook.present"),
2121
NOTEBOOK_TOTAL_KEY("python.notebook.total"),
2222
NOTEBOOK_RECOGNITION_ERROR_KEY("python.notebook.recognition_error"),
23-
NOTEBOOK_EXCEPTION_KEY("python.notebook.exceptions");
23+
NOTEBOOK_EXCEPTION_KEY("python.notebook.exceptions"),
24+
PYTHON_VERSION_SET_KEY("python.version.set"),
25+
PYTHON_VERSION_KEY("python.version");
2426

2527
private final String key;
2628

sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.HashSet;
3131
import java.util.Iterator;
3232
import java.util.List;
33+
import java.util.Map;
3334
import java.util.Set;
3435
import java.util.function.Function;
3536
import javax.annotation.Nullable;
@@ -1408,6 +1409,40 @@ void test_scanner_isNotebook() {
14081409
assertThat(PythonScanner.isNotebook(notebookPythonFile)).isTrue();
14091410
}
14101411

1412+
@Test
1413+
void send_telemetry_with_version() {
1414+
activeRules = new ActiveRulesBuilder()
1415+
.addRule(new NewActiveRule.Builder()
1416+
.setRuleKey(RuleKey.of(CheckList.REPOSITORY_KEY, "S930"))
1417+
.build())
1418+
.build();
1419+
1420+
context.setSettings(new MapSettings().setProperty("sonar.python.version", "3.10,3.13"));
1421+
1422+
SensorTelemetryStorage sensorTelemetryStorage = spy(new SensorTelemetryStorage());
1423+
PythonSensor sensor = sensor(sensorTelemetryStorage);
1424+
sensor.execute(context);
1425+
verify(sensorTelemetryStorage, times(1)).send(context);
1426+
assertThat(sensorTelemetryStorage.data()).containsExactlyInAnyOrderEntriesOf(Map.of(TelemetryMetricKey.PYTHON_VERSION_KEY, "3.10,3.13",
1427+
TelemetryMetricKey.PYTHON_VERSION_SET_KEY, "1"));
1428+
}
1429+
1430+
@Test
1431+
void send_telemetry_no_version() {
1432+
activeRules = new ActiveRulesBuilder()
1433+
.addRule(new NewActiveRule.Builder()
1434+
.setRuleKey(RuleKey.of(CheckList.REPOSITORY_KEY, "S930"))
1435+
.build())
1436+
.build();
1437+
1438+
SensorTelemetryStorage sensorTelemetryStorage = spy(new SensorTelemetryStorage());
1439+
PythonSensor sensor = sensor(sensorTelemetryStorage);
1440+
sensor.execute(context);
1441+
verify(sensorTelemetryStorage, times(1)).send(context);
1442+
assertThat(sensorTelemetryStorage.data()).containsExactlyInAnyOrderEntriesOf(Map.of(TelemetryMetricKey.PYTHON_VERSION_SET_KEY, "0"));
1443+
1444+
}
1445+
14111446
private com.sonar.sslr.api.Token passToken(URI uri) {
14121447
return com.sonar.sslr.api.Token.builder()
14131448
.setType(PythonKeyword.PASS)
@@ -1422,7 +1457,16 @@ private PythonSensor sensor() {
14221457
return sensor(CUSTOM_RULES, null, analysisWarning);
14231458
}
14241459

1460+
private PythonSensor sensor(SensorTelemetryStorage sensorTelemetryStorage) {
1461+
return sensor(CUSTOM_RULES, null, analysisWarning, sensorTelemetryStorage);
1462+
}
1463+
14251464
private PythonSensor sensor(@Nullable PythonCustomRuleRepository[] customRuleRepositories, @Nullable PythonIndexer indexer, AnalysisWarningsWrapper analysisWarnings) {
1465+
return sensor(customRuleRepositories, indexer, analysisWarnings, new SensorTelemetryStorage());
1466+
}
1467+
1468+
private PythonSensor sensor(@Nullable PythonCustomRuleRepository[] customRuleRepositories, @Nullable PythonIndexer indexer, AnalysisWarningsWrapper analysisWarnings,
1469+
SensorTelemetryStorage sensorTelemetryStorage) {
14261470
FileLinesContextFactory fileLinesContextFactory = mock(FileLinesContextFactory.class);
14271471
FileLinesContext fileLinesContext = mock(FileLinesContext.class);
14281472
when(fileLinesContextFactory.createFor(Mockito.any(InputFile.class))).thenReturn(fileLinesContext);
@@ -1431,12 +1475,13 @@ private PythonSensor sensor(@Nullable PythonCustomRuleRepository[] customRuleRep
14311475
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), analysisWarnings);
14321476
}
14331477
if (indexer == null) {
1434-
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), customRuleRepositories, analysisWarnings);
1478+
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), customRuleRepositories, analysisWarnings, sensorTelemetryStorage);
14351479
}
14361480
if (customRuleRepositories == null) {
14371481
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), indexer, new SonarLintCache(), analysisWarnings);
14381482
}
1439-
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), customRuleRepositories, indexer, new SonarLintCache(), analysisWarnings);
1483+
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), customRuleRepositories, indexer, new SonarLintCache(), analysisWarnings,
1484+
sensorTelemetryStorage);
14401485
}
14411486

14421487
private SonarLintPythonIndexer pythonIndexer(List<PythonInputFile> files) {

0 commit comments

Comments
 (0)