Skip to content

Commit 0862bdd

Browse files
committed
SONARPY-2451 Collect data for the Jupyter notebooks
1 parent 58fe5a9 commit 0862bdd

File tree

4 files changed

+93
-5
lines changed

4 files changed

+93
-5
lines changed

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public final class IPynbSensor implements Sensor {
4747
private final NoSonarFilter noSonarFilter;
4848
private final PythonIndexer indexer;
4949
private static final String FAIL_FAST_PROPERTY_NAME = "sonar.internal.analysis.failFast";
50+
private final SensorTelemetryStorage sensorTelemetryStorage;
5051

5152
public IPynbSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter) {
5253
this(fileLinesContextFactory, checkFactory, noSonarFilter, null);
@@ -58,6 +59,7 @@ public IPynbSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory
5859
this.fileLinesContextFactory = fileLinesContextFactory;
5960
this.noSonarFilter = noSonarFilter;
6061
this.indexer = indexer;
62+
this.sensorTelemetryStorage = new SensorTelemetryStorage();
6163
}
6264

6365
@Override
@@ -80,6 +82,7 @@ public void execute(SensorContext context) {
8082
} else {
8183
processNotebooksFiles(pythonFiles, context);
8284
}
85+
sensorTelemetryStorage.send(context);
8386
}
8487

8588
private void processNotebooksFiles(List<PythonInputFile> pythonFiles, SensorContext context) {
@@ -89,20 +92,29 @@ private void processNotebooksFiles(List<PythonInputFile> pythonFiles, SensorCont
8992
PythonIndexer pythonIndexer = new SonarQubePythonIndexer(pythonFiles, cacheContext, context);
9093
PythonScanner scanner = new PythonScanner(context, checks, fileLinesContextFactory, noSonarFilter, PythonParser.createIPythonParser(), pythonIndexer);
9194
scanner.execute(pythonFiles, context);
95+
sensorTelemetryStorage.updateMetric(SensorTelemetryStorage.MetricKey.NOTEBOOK_RECOGNITION_ERROR_KEY, String.valueOf(scanner.getRecognitionErrorCount()));
9296
}
9397

94-
private static List<PythonInputFile> parseNotebooks(List<PythonInputFile> pythonFiles, SensorContext context) {
98+
private List<PythonInputFile> parseNotebooks(List<PythonInputFile> pythonFiles, SensorContext context) {
9599
List<PythonInputFile> generatedIPythonFiles = new ArrayList<>();
100+
101+
sensorTelemetryStorage.updateMetric(SensorTelemetryStorage.MetricKey.NOTEBOOK_TOTAL_KEY, String.valueOf(pythonFiles.size()));
102+
var numberOfExceptions = 0;
103+
96104
for (PythonInputFile inputFile : pythonFiles) {
97105
try {
106+
sensorTelemetryStorage.updateMetric(SensorTelemetryStorage.MetricKey.NOTEBOOK_PRESENT_KEY, "1");
98107
var result = IpynbNotebookParser.parseNotebook(inputFile);
99108
result.ifPresent(generatedIPythonFiles::add);
100109
} catch (Exception e) {
110+
numberOfExceptions++;
101111
if (context.config().getBoolean(FAIL_FAST_PROPERTY_NAME).orElse(false) && !isErrorOnTestFile(inputFile)) {
102112
throw new IllegalStateException("Exception when parsing " + inputFile, e);
103113
}
104114
}
105115
}
116+
117+
sensorTelemetryStorage.updateMetric(SensorTelemetryStorage.MetricKey.NOTEBOOK_EXCEPTION_KEY, String.valueOf(numberOfExceptions));
106118
return generatedIPythonFiles;
107119
}
108120

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public class PythonScanner extends Scanner {
7777
private final PythonCpdAnalyzer cpdAnalyzer;
7878
private final PythonIndexer indexer;
7979
private final Map<PythonInputFile, Set<PythonCheck>> checksExecutedWithoutParsingByFiles = new HashMap<>();
80+
private int recognitionErrorCount = 0;
8081

8182
public PythonScanner(
8283
SensorContext context, PythonChecks checks,
@@ -123,6 +124,7 @@ protected void scanFile(PythonInputFile inputFile) throws IOException {
123124

124125
LOG.error("Unable to parse file: " + inputFile);
125126
LOG.error(newMessage);
127+
recognitionErrorCount++;
126128
context.newAnalysisError()
127129
.onFile(inputFile.wrappedFile())
128130
.at(inputFile.wrappedFile().newPointer(line, 0))
@@ -401,4 +403,8 @@ private static void addQuickFixes(InputFile inputFile, RuleKey ruleKey, Iterable
401403
private static TextRange rangeFromTextSpan(InputFile file, PythonTextEdit pythonTextEdit) {
402404
return file.newRange(pythonTextEdit.startLine(), pythonTextEdit.startLineOffset(), pythonTextEdit.endLine(), pythonTextEdit.endLineOffset());
403405
}
406+
407+
public int getRecognitionErrorCount() {
408+
return recognitionErrorCount;
409+
}
404410
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2024 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.plugins.python;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
import org.sonar.api.batch.sensor.SensorContext;
24+
25+
public class SensorTelemetryStorage {
26+
private static final Logger LOG = LoggerFactory.getLogger(SensorTelemetryStorage.class);
27+
28+
private final Map<String, String> data = new HashMap<>();
29+
30+
public void send(SensorContext sensorContext) {
31+
data.forEach((k, v) -> {
32+
LOG.info("Metrics property: {}={}", k, v);
33+
sensorContext.addTelemetryProperty(k, v);
34+
});
35+
}
36+
37+
public void updateMetric(MetricKey key, String value) {
38+
data.put(key.key(), value);
39+
}
40+
41+
public enum MetricKey {
42+
NOTEBOOK_PRESENT_KEY("python.notebook.present"),
43+
NOTEBOOK_TOTAL_KEY("python.notebook.total"),
44+
NOTEBOOK_RECOGNITION_ERROR_KEY("python.notebook.recognition_error"),
45+
NOTEBOOK_EXCEPTION_KEY("python.notebook.exceptions");
46+
47+
private final String key;
48+
49+
MetricKey(String key) {
50+
this.key = key;
51+
}
52+
53+
public String key() {
54+
return key;
55+
}
56+
}
57+
}

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
import static org.junit.Assert.assertThrows;
5858
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
5959
import static org.mockito.Mockito.mock;
60+
import static org.mockito.Mockito.spy;
61+
import static org.mockito.Mockito.verify;
6062
import static org.mockito.Mockito.when;
6163

6264
class IPynbSensorTest {
@@ -210,10 +212,16 @@ void test_notebook_sensor_cannot_parse_file() {
210212
}
211213

212214
@Test
213-
void test_notebook_sensor_is_excuted_on_json_file() {
215+
void test_notebook_sensor_is_executed_on_json_file() {
214216
inputFile(NOTEBOOK_FILE);
215217
activeRules = new ActiveRulesBuilder().build();
216-
assertDoesNotThrow(() -> notebookSensor().execute(context));
218+
var sensor = spy(notebookSensor());
219+
var contextSpy = spy(context);
220+
assertDoesNotThrow(() -> sensor.execute(contextSpy));
221+
verify(contextSpy, Mockito.times(1)).addTelemetryProperty(SensorTelemetryStorage.MetricKey.NOTEBOOK_PRESENT_KEY.key(), "1");
222+
verify(contextSpy, Mockito.times(1)).addTelemetryProperty(SensorTelemetryStorage.MetricKey.NOTEBOOK_RECOGNITION_ERROR_KEY.key(), "0");
223+
verify(contextSpy, Mockito.times(1)).addTelemetryProperty(SensorTelemetryStorage.MetricKey.NOTEBOOK_TOTAL_KEY.key(), "1");
224+
verify(contextSpy, Mockito.times(1)).addTelemetryProperty(SensorTelemetryStorage.MetricKey.NOTEBOOK_EXCEPTION_KEY.key(), "0");
217225
}
218226

219227
@Test
@@ -227,12 +235,17 @@ void test_notebook_sensor_does_not_execute_cpd_measures() {
227235
}
228236

229237
@Test
230-
void test_notebook_sensor_parse_error_on_valid_line(){
238+
void test_notebook_sensor_parse_error_on_valid_line() {
231239
inputFile("notebook_parse_error.ipynb");
232240
activeRules = new ActiveRulesBuilder().build();
233241
var sensor = notebookSensor();
234-
sensor.execute(context);
242+
var contextSpy = spy(context);
243+
sensor.execute(contextSpy);
235244
var logs = String.join("", logTester.logs());
236245
assertThat(logs).contains("Unable to parse file: notebook_parse_error.ipynbParse error at line 1");
246+
verify(contextSpy, Mockito.times(1)).addTelemetryProperty(SensorTelemetryStorage.MetricKey.NOTEBOOK_PRESENT_KEY.key(), "1");
247+
verify(contextSpy, Mockito.times(1)).addTelemetryProperty(SensorTelemetryStorage.MetricKey.NOTEBOOK_RECOGNITION_ERROR_KEY.key(), "1");
248+
verify(contextSpy, Mockito.times(1)).addTelemetryProperty(SensorTelemetryStorage.MetricKey.NOTEBOOK_TOTAL_KEY.key(), "1");
249+
verify(contextSpy, Mockito.times(1)).addTelemetryProperty(SensorTelemetryStorage.MetricKey.NOTEBOOK_EXCEPTION_KEY.key(), "0");
237250
}
238251
}

0 commit comments

Comments
 (0)