Skip to content

Commit d794035

Browse files
Add performance measurement for project level symbol table (#925)
1 parent 3fef6bc commit d794035

File tree

8 files changed

+112
-16
lines changed

8 files changed

+112
-16
lines changed

docs/properties.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Internal analysis properties of the Python analyzer
2+
3+
``sonar.python.performance.measure``: Boolean; if set to true, will enable performance monitoring of the analyzer (default: `false`).
4+
5+
``sonar.python.performance.measure.path``: Path where the performance monitoring report will be saved, relative to the work dir (default: `sonar-python-performance-measure.json`).
6+
7+
``sonar.internal.analysis.failFast``: Boolean; if set to true, exceptions will fail the analysis (default: `false`).
8+
9+
``sonar.python.sonarlint.maxlines``: Maximum number of lines in a project above which the project symbol table won't be computed in SonarLint context (default: `150000`).

its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/SonarLintTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public void should_raise_issues() throws IOException {
104104
sonarlintEngine.declareModule(new ModuleInfo("myModule", (a, b) -> Stream.of(inputFile)));
105105
sonarlintEngine.analyze(configuration, issues::add, logOutput, null);
106106

107-
assertThat(logsByLevel.get(LogOutput.Level.WARN)).isNull();
107+
assertThat(logsByLevel.get(LogOutput.Level.WARN)).containsExactly("No workDir in SonarLint");
108108
assertThat(issues).extracting("ruleKey", "startLine", "inputFile.path", "severity").containsOnly(
109109
tuple("python:BackticksUsage", 2, inputFile.getPath(), "BLOCKER"),
110110
tuple("python:S1542", 1, inputFile.getPath(), "MAJOR"));

pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
<mockito.version>3.9.0</mockito.version>
9292
<sonar.version>8.9.0.43852</sonar.version>
9393
<sonar.orchestrator.version>3.35.1.2719</sonar.orchestrator.version>
94-
<sonar-analyzer-commons.version>1.14.1.690</sonar-analyzer-commons.version>
94+
<sonar-analyzer-commons.version>1.15.0.696</sonar-analyzer-commons.version>
9595
<sonarlint-core.version>5.4.0.31923</sonarlint-core.version>
9696
<sslr.version>1.23</sslr.version>
9797
</properties>
@@ -120,6 +120,11 @@
120120
<artifactId>sonar-xml-parsing</artifactId>
121121
<version>${sonar-analyzer-commons.version}</version>
122122
</dependency>
123+
<dependency>
124+
<groupId>org.sonarsource.analyzer-commons</groupId>
125+
<artifactId>sonar-performance-measure</artifactId>
126+
<version>${sonar-analyzer-commons.version}</version>
127+
</dependency>
123128
<dependency>
124129
<groupId>org.sonarsource.analyzer-commons</groupId>
125130
<artifactId>sonar-analyzer-test-commons</artifactId>

sonar-python-plugin/pom.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@
5656
<groupId>org.sonarsource.analyzer-commons</groupId>
5757
<artifactId>sonar-xml-parsing</artifactId>
5858
</dependency>
59+
<dependency>
60+
<groupId>org.sonarsource.analyzer-commons</groupId>
61+
<artifactId>sonar-performance-measure</artifactId>
62+
</dependency>
5963
<dependency>
6064
<groupId>org.sonarsource.sslr</groupId>
6165
<artifactId>sslr-testing-harness</artifactId>
@@ -146,8 +150,8 @@
146150
<configuration>
147151
<rules>
148152
<requireFilesSize>
149-
<maxsize>4810000</maxsize>
150-
<minsize>4310000</minsize>
153+
<maxsize>5000000</maxsize>
154+
<minsize>4500000</minsize>
151155
<files>
152156
<file>${project.build.directory}/${project.build.finalName}.jar</file>
153157
</files>

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121

2222
import com.sonar.sslr.api.AstNode;
2323
import com.sonar.sslr.api.RecognitionException;
24+
import java.io.File;
2425
import java.io.IOException;
2526
import java.util.ArrayList;
2627
import java.util.Collections;
2728
import java.util.List;
29+
import java.util.Optional;
2830
import javax.annotation.Nullable;
2931
import org.sonar.api.batch.fs.FilePredicates;
3032
import org.sonar.api.batch.fs.InputFile;
@@ -47,9 +49,14 @@
4749
import org.sonar.python.parser.PythonParser;
4850
import org.sonar.python.semantic.ProjectLevelSymbolTable;
4951
import org.sonar.python.tree.PythonTreeMaker;
52+
import org.sonarsource.performance.measure.PerformanceMeasure;
5053

5154
public final class PythonSensor implements Sensor {
5255

56+
private static final String PERFORMANCE_MEASURE_PROPERTY = "sonar.python.performance.measure";
57+
private static final String PERFORMANCE_MEASURE_FILE_PATH_PROPERTY = "sonar.python.performance.measure.path";
58+
private static final String PERFORMANCE_MEASURE_DESTINATION_FILE = "sonar-python-performance-measure.json";
59+
5360
private final PythonChecks checks;
5461
private final FileLinesContextFactory fileLinesContextFactory;
5562
private final NoSonarFilter noSonarFilter;
@@ -92,11 +99,13 @@ public void describe(SensorDescriptor descriptor) {
9299

93100
@Override
94101
public void execute(SensorContext context) {
102+
PerformanceMeasure.Duration durationReport = createPerformanceMeasureReport(context);
95103
List<InputFile> mainFiles = getInputFiles(Type.MAIN, context);
96104
List<InputFile> testFiles = getInputFiles(Type.TEST, context);
97105
PythonIndexer pythonIndexer = this.indexer != null ? this.indexer : new SonarQubePythonIndexer(mainFiles);
98106
PythonScanner scanner = new PythonScanner(context, checks, fileLinesContextFactory, noSonarFilter, pythonIndexer);
99107
scanner.execute(mainFiles, context);
108+
durationReport.stop();
100109
if (!testFiles.isEmpty()) {
101110
new TestHighlightingScanner(context).execute(testFiles, context);
102111
}
@@ -110,6 +119,19 @@ private static List<InputFile> getInputFiles(InputFile.Type type, SensorContext
110119
return Collections.unmodifiableList(list);
111120
}
112121

122+
private static PerformanceMeasure.Duration createPerformanceMeasureReport(SensorContext context) {
123+
return PerformanceMeasure.reportBuilder()
124+
.activate(context.config().getBoolean(PERFORMANCE_MEASURE_PROPERTY).orElse(Boolean.FALSE))
125+
.toFile(context.config().get(PERFORMANCE_MEASURE_FILE_PATH_PROPERTY)
126+
.filter(path -> !path.isEmpty())
127+
.orElseGet(() -> Optional.ofNullable(context.fileSystem().workDir())
128+
.filter(File::exists)
129+
.map(file -> file.toPath().resolve(PERFORMANCE_MEASURE_DESTINATION_FILE).toString())
130+
.orElse(null)))
131+
.appendMeasurementCost()
132+
.start("PythonSensor");
133+
}
134+
113135
private static class TestHighlightingScanner extends Scanner {
114136

115137
private static final Logger LOG = Loggers.get(TestHighlightingScanner.class);

sonar-python-plugin/src/main/java/org/sonar/plugins/python/indexer/SonarLintPythonIndexer.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,8 @@ public void buildOnce(SensorContext context) {
5353
List<InputFile> files = getInputFiles(moduleFileSystem);
5454
LOG.debug("Input files for indexing: " + files);
5555
// computes "globalSymbolsByModuleName"
56-
long startTime = System.currentTimeMillis();
5756
GlobalSymbolsScanner globalSymbolsStep = new GlobalSymbolsScanner(context);
5857
globalSymbolsStep.execute(files, context);
59-
long stopTime = System.currentTimeMillis() - startTime;
60-
LOG.debug("Time to build the project level symbol table: " + stopTime + "ms");
6158
shouldBuildProjectSymbolTable = false;
6259
}
6360

sonar-python-plugin/src/main/java/org/sonar/plugins/python/indexer/SonarQubePythonIndexer.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.sonar.api.batch.sensor.SensorContext;
2525
import org.sonar.api.utils.log.Logger;
2626
import org.sonar.api.utils.log.Loggers;
27+
import org.sonarsource.performance.measure.PerformanceMeasure;
2728

2829
public class SonarQubePythonIndexer extends PythonIndexer {
2930

@@ -37,12 +38,11 @@ public SonarQubePythonIndexer(List<InputFile> files) {
3738
@Override
3839
public void buildOnce(SensorContext context) {
3940
this.projectBaseDirAbsolutePath = context.fileSystem().baseDir().getAbsolutePath();
41+
PerformanceMeasure.Duration duration = PerformanceMeasure.start("ProjectLevelSymbolTable");
4042
LOG.debug("Input files for indexing: " + files);
4143
// computes "globalSymbolsByModuleName"
42-
long startTime = System.currentTimeMillis();
4344
GlobalSymbolsScanner globalSymbolsStep = new GlobalSymbolsScanner(context);
4445
globalSymbolsStep.execute(files, context);
45-
long stopTime = System.currentTimeMillis() - startTime;
46-
LOG.debug("Time to build the project level symbol table: " + stopTime + "ms");
46+
duration.stop();
4747
}
4848
}

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

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,65 @@ public void cancelled_analysis() {
447447
assertThat(context.allAnalysisErrors()).isEmpty();
448448
}
449449

450+
@Test
451+
public void saving_performance_measure_not_activated_by_default() throws IOException {
452+
activeRules = (new ActiveRulesBuilder()).build();
453+
454+
inputFile("main.py");
455+
sensor().execute(context);
456+
assertThat(context.allIssues()).isEmpty();
457+
assertThat(logTester.logs(LoggerLevel.INFO)).noneMatch(s -> s.matches(".*performance measures.*"));
458+
Path defaultPerformanceFile = workDir.resolve("sonar-python-performance-measure.json");
459+
assertThat(defaultPerformanceFile).doesNotExist();
460+
}
461+
462+
@Test
463+
public void saving_performance_measure() throws IOException {
464+
context.setSettings(new MapSettings().setProperty("sonar.python.performance.measure", "true"));
465+
activeRules = (new ActiveRulesBuilder()).build();
466+
467+
inputFile("main.py");
468+
sensor().execute(context);
469+
Path defaultPerformanceFile = workDir.resolve("sonar-python-performance-measure.json");
470+
assertThat(logTester.logs(LoggerLevel.INFO)).anyMatch(s -> s.matches(".*performance measures.*"));
471+
assertThat(defaultPerformanceFile).exists();
472+
assertThat(new String(Files.readAllBytes(defaultPerformanceFile), StandardCharsets.UTF_8)).contains("\"PythonSensor\"");
473+
}
474+
475+
@Test
476+
public void saving_performance_measure_custom_path() throws IOException {
477+
Path customPerformanceFile = workDir.resolve("custom.performance.measure.json");
478+
MapSettings mapSettings = new MapSettings();
479+
mapSettings.setProperty("sonar.python.performance.measure", "true");
480+
mapSettings.setProperty("sonar.python.performance.measure.path", customPerformanceFile.toString());
481+
context.setSettings(mapSettings);
482+
activeRules = (new ActiveRulesBuilder()).build();
483+
484+
inputFile("main.py");
485+
sensor().execute(context);
486+
assertThat(logTester.logs(LoggerLevel.INFO)).anyMatch(s -> s.matches(".*performance measures.*"));
487+
Path defaultPerformanceFile = workDir.resolve("sonar-python-performance-measure.json");
488+
assertThat(defaultPerformanceFile).doesNotExist();
489+
assertThat(customPerformanceFile).exists();
490+
assertThat(new String(Files.readAllBytes(customPerformanceFile), StandardCharsets.UTF_8)).contains("\"PythonSensor\"");
491+
}
492+
493+
@Test
494+
public void saving_performance_measure_empty_path() throws IOException {
495+
MapSettings mapSettings = new MapSettings();
496+
mapSettings.setProperty("sonar.python.performance.measure", "true");
497+
mapSettings.setProperty("sonar.python.performance.measure.path", "");
498+
context.setSettings(mapSettings);
499+
activeRules = (new ActiveRulesBuilder()).build();
500+
501+
inputFile("main.py");
502+
sensor().execute(context);
503+
assertThat(logTester.logs(LoggerLevel.INFO)).anyMatch(s -> s.matches(".*performance measures.*"));
504+
Path defaultPerformanceFile = workDir.resolve("sonar-python-performance-measure.json");
505+
assertThat(defaultPerformanceFile).exists();
506+
assertThat(new String(Files.readAllBytes(defaultPerformanceFile), StandardCharsets.UTF_8)).contains("\"PythonSensor\"");
507+
}
508+
450509
private PythonSensor sensor() {
451510
return sensor(CUSTOM_RULES, null);
452511
}
@@ -480,12 +539,12 @@ private InputFile inputFile(String name) {
480539

481540
private DefaultInputFile createInputFile(String name) {
482541
return TestInputFileBuilder.create("moduleKey", name)
483-
.setModuleBaseDir(baseDir.toPath())
484-
.setCharset(StandardCharsets.UTF_8)
485-
.setType(Type.MAIN)
486-
.setLanguage(Python.KEY)
487-
.initMetadata(TestUtils.fileContent(new File(baseDir, name), StandardCharsets.UTF_8))
488-
.build();
542+
.setModuleBaseDir(baseDir.toPath())
543+
.setCharset(StandardCharsets.UTF_8)
544+
.setType(Type.MAIN)
545+
.setLanguage(Python.KEY)
546+
.initMetadata(TestUtils.fileContent(new File(baseDir, name), StandardCharsets.UTF_8))
547+
.build();
489548
}
490549

491550
private void verifyUsages(String componentKey, int line, int offset, TextRange... trs) {

0 commit comments

Comments
 (0)