Skip to content

Commit 4b5e001

Browse files
committed
Improve performance of file revision data streaming
1 parent 9d7d947 commit 4b5e001

17 files changed

Lines changed: 277 additions & 48 deletions

src/main/java/net/explorviz/code/analysis/export/DataExporter.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package net.explorviz.code.analysis.export;
22

3+
import java.util.ArrayList;
4+
import java.util.List;
35
import java.util.Map;
46
import java.util.concurrent.BlockingQueue;
57
import java.util.concurrent.CountDownLatch;
@@ -17,14 +19,61 @@
1719
*/
1820
public interface DataExporter {
1921

22+
/**
23+
* Number of files collected before a single {@code PersistFiles} streaming call
24+
* is made.
25+
*/
26+
int FILE_PERSIST_BATCH_SIZE = 250;
27+
2028
StateData getStateData(final String repositoryName, final String branchName, final String token,
2129
final Map<String, String> applicationPaths);
2230

2331
void persistFile(final FileData fileData);
2432

33+
/**
34+
* Persists a batch of files in a single operation. The default implementation
35+
* calls {@link #persistFile} for each file sequentially; exporters that support
36+
* client-streaming gRPC should override this to use {@code PersistFiles}.
37+
*/
38+
default void persistFilesBatch(final List<FileData> files) {
39+
files.forEach(this::persistFile);
40+
}
41+
42+
/**
43+
* Drains {@code completedFiles} as files arrive and dispatches them to
44+
* {@link #persistFilesBatch} in chunks of {@link #FILE_PERSIST_BATCH_SIZE}.
45+
* Runs
46+
* concurrently with the analysis pipeline; call
47+
* {@code analysisFinished.countDown()}
48+
* once all producers have finished to signal the final flush.
49+
*/
50+
default void persistFilesFromQueueInBatches(final BlockingQueue<FileData> completedFiles,
51+
final CountDownLatch analysisFinished) {
52+
final List<FileData> batch = new ArrayList<>(FILE_PERSIST_BATCH_SIZE);
53+
try {
54+
while (analysisFinished.getCount() > 0 || !completedFiles.isEmpty()) {
55+
final FileData fileData = completedFiles.poll(100, TimeUnit.MILLISECONDS);
56+
if (fileData == null) {
57+
continue;
58+
}
59+
batch.add(fileData);
60+
if (batch.size() >= FILE_PERSIST_BATCH_SIZE) {
61+
persistFilesBatch(List.copyOf(batch));
62+
batch.clear();
63+
}
64+
}
65+
} catch (InterruptedException e) {
66+
Thread.currentThread().interrupt();
67+
}
68+
if (!batch.isEmpty()) {
69+
persistFilesBatch(List.copyOf(batch));
70+
}
71+
}
72+
2573
/**
2674
* Persists files as they become available on the queue, while analysis is still
27-
* running. Uses {@link Runtime#availableProcessors()} minus one when parallelism
75+
* running. Uses {@link Runtime#availableProcessors()} minus one when
76+
* parallelism
2877
* is not positive.
2978
*/
3079
default void persistFilesFromQueue(final BlockingQueue<FileData> completedFiles,

src/main/java/net/explorviz/code/analysis/export/GrpcExporter.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package net.explorviz.code.analysis.export;
22

33
import io.quarkus.grpc.GrpcClient;
4+
import io.smallrye.mutiny.Multi;
45
import jakarta.enterprise.context.ApplicationScoped;
6+
import java.util.List;
57
import java.util.Map;
68
import net.explorviz.code.proto.CommitData;
79
import net.explorviz.code.proto.CommitServiceGrpc;
810
import net.explorviz.code.proto.ContributorServiceGrpc;
911
import net.explorviz.code.proto.FileData;
1012
import net.explorviz.code.proto.FileDataServiceGrpc;
13+
import net.explorviz.code.proto.MutinyFileDataServiceGrpc;
1114
import net.explorviz.code.proto.StateData;
1215
import net.explorviz.code.proto.StateDataRequest;
1316
import net.explorviz.code.proto.StateDataServiceGrpc;
@@ -30,6 +33,9 @@ public final class GrpcExporter implements DataExporter {
3033
@GrpcClient(GRPC_CLIENT_NAME)
3134
/* package */ FileDataServiceGrpc.FileDataServiceBlockingStub fileDataGrpcClient;
3235

36+
@GrpcClient(GRPC_CLIENT_NAME)
37+
/* package */ MutinyFileDataServiceGrpc.MutinyFileDataServiceStub fileDataMutinyGrpcClient;
38+
3339
@GrpcClient(GRPC_CLIENT_NAME)
3440
/* package */ CommitServiceGrpc.CommitServiceBlockingStub commitDataGrpcClient;
3541

@@ -90,6 +96,23 @@ public void persistFile(final FileData fileData) {
9096
}
9197
}
9298

99+
@Override
100+
public void persistFilesBatch(final List<FileData> files) {
101+
if (files.isEmpty()) {
102+
return;
103+
}
104+
LOGGER.info("Sending batch of {} files via streaming RPC", files.size());
105+
try {
106+
fileDataMutinyGrpcClient
107+
.persistFiles(Multi.createFrom().items(files.stream()))
108+
.await()
109+
.indefinitely();
110+
} catch (final Exception e) {
111+
LOGGER.error("Failed to send batch of {} files: {}", files.size(), e.getMessage());
112+
throw new RuntimeException("Failed to send file batch to landscape-service", e);
113+
}
114+
}
115+
93116
@Override
94117
public void persistCommit(final CommitData commitData) {
95118
LOGGER.info("Sending commit data on {}", commitData.getCommitId());

src/main/java/net/explorviz/code/analysis/parser/AntlrCParserService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.antlr.v4.runtime.CharStream;
1111
import org.antlr.v4.runtime.CharStreams;
1212
import org.antlr.v4.runtime.CommonTokenStream;
13+
import org.antlr.v4.runtime.tree.ParseTree;
1314
import org.antlr.v4.runtime.tree.ParseTreeWalker;
1415
import org.slf4j.Logger;
1516
import org.slf4j.LoggerFactory;
@@ -53,10 +54,12 @@ public CFileDataHandler parseFile(final String pathToFile, final String fileHash
5354
private CFileDataHandler parse(final CharStream charStream, final String fileName,
5455
final String fileHash) {
5556
final CLexer lexer = new CLexer(charStream);
57+
AntlrParserUtils.configureLexer(lexer);
5658
final CommonTokenStream tokens = new CommonTokenStream(lexer);
5759
final CParser parser = new CParser(tokens);
5860

59-
final CParser.CompilationUnitContext compilationUnit = parser.compilationUnit();
61+
final ParseTree compilationUnit =
62+
AntlrParserUtils.parseTwoStage(parser, tokens, LOGGER, fileName, parser::compilationUnit);
6063

6164
final CFileDataHandler fileDataHandler = new CFileDataHandler(fileName);
6265
fileDataHandler.setFileHash(fileHash);

src/main/java/net/explorviz/code/analysis/parser/AntlrCSharpParserService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.antlr.v4.runtime.CharStream;
1111
import org.antlr.v4.runtime.CharStreams;
1212
import org.antlr.v4.runtime.CommonTokenStream;
13+
import org.antlr.v4.runtime.tree.ParseTree;
1314
import org.antlr.v4.runtime.tree.ParseTreeWalker;
1415
import org.slf4j.Logger;
1516
import org.slf4j.LoggerFactory;
@@ -53,10 +54,12 @@ public CSharpFileDataHandler parseFile(final String pathToFile, final String fil
5354
private CSharpFileDataHandler parse(final CharStream charStream, final String fileName,
5455
final String fileHash) {
5556
final CSharpLexer lexer = new CSharpLexer(charStream);
57+
AntlrParserUtils.configureLexer(lexer);
5658
final CommonTokenStream tokens = new CommonTokenStream(lexer);
5759
final CSharpParser parser = new CSharpParser(tokens);
5860

59-
final CSharpParser.ProgContext prog = parser.prog();
61+
final ParseTree prog =
62+
AntlrParserUtils.parseTwoStage(parser, tokens, LOGGER, fileName, parser::prog);
6063

6164
final CSharpFileDataHandler fileDataHandler = new CSharpFileDataHandler(fileName);
6265
fileDataHandler.setFileHash(fileHash);

src/main/java/net/explorviz/code/analysis/parser/AntlrCppParserService.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.antlr.v4.runtime.CharStream;
1111
import org.antlr.v4.runtime.CharStreams;
1212
import org.antlr.v4.runtime.CommonTokenStream;
13+
import org.antlr.v4.runtime.tree.ParseTree;
1314
import org.antlr.v4.runtime.tree.ParseTreeWalker;
1415
import org.slf4j.Logger;
1516
import org.slf4j.LoggerFactory;
@@ -52,19 +53,17 @@ public CppFileDataHandler parseFile(final String pathToFile, final String fileHa
5253

5354
private CppFileDataHandler parse(final CharStream charStream, final String fileName,
5455
final String fileHash) {
55-
// Create lexer and parser
5656
final CPP14Lexer lexer = new CPP14Lexer(charStream);
57+
AntlrParserUtils.configureLexer(lexer);
5758
final CommonTokenStream tokens = new CommonTokenStream(lexer);
5859
final CPP14Parser parser = new CPP14Parser(tokens);
5960

60-
// Parse the translation unit (entry point for C/C++)
61-
final CPP14Parser.TranslationUnitContext translationUnit = parser.translationUnit();
61+
final ParseTree translationUnit =
62+
AntlrParserUtils.parseTwoStage(parser, tokens, LOGGER, fileName, parser::translationUnit);
6263

63-
// Create C/C++ file data handler
6464
final CppFileDataHandler fileDataHandler = new CppFileDataHandler(fileName);
6565
fileDataHandler.setFileHash(fileHash);
6666

67-
// Create and execute the listener
6867
final CppFileDataListener listener = new CppFileDataListener(fileDataHandler, tokens);
6968
final ParseTreeWalker walker = new ParseTreeWalker();
7069
walker.walk(listener, translationUnit);

src/main/java/net/explorviz/code/analysis/parser/AntlrGoParserService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.antlr.v4.runtime.CharStream;
1111
import org.antlr.v4.runtime.CharStreams;
1212
import org.antlr.v4.runtime.CommonTokenStream;
13+
import org.antlr.v4.runtime.tree.ParseTree;
1314
import org.antlr.v4.runtime.tree.ParseTreeWalker;
1415
import org.slf4j.Logger;
1516
import org.slf4j.LoggerFactory;
@@ -53,10 +54,12 @@ public GoFileDataHandler parseFile(final String pathToFile, final String fileHas
5354
private GoFileDataHandler parse(final CharStream charStream, final String fileName,
5455
final String fileHash) {
5556
final GoLexer lexer = new GoLexer(charStream);
57+
AntlrParserUtils.configureLexer(lexer);
5658
final CommonTokenStream tokens = new CommonTokenStream(lexer);
5759
final GoParser parser = new GoParser(tokens);
5860

59-
final GoParser.SourceFileContext sourceFile = parser.sourceFile();
61+
final ParseTree sourceFile =
62+
AntlrParserUtils.parseTwoStage(parser, tokens, LOGGER, fileName, parser::sourceFile);
6063

6164
final GoFileDataHandler fileDataHandler = new GoFileDataHandler(fileName);
6265
fileDataHandler.setFileHash(fileHash);

src/main/java/net/explorviz/code/analysis/parser/AntlrKotlinParserService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@ public KotlinFileDataHandler parseFile(final String pathToFile, final String fil
5454
private KotlinFileDataHandler parse(final CharStream charStream, final String fileName,
5555
final String fileHash) {
5656
final KotlinLexer lexer = new KotlinLexer(charStream);
57+
AntlrParserUtils.configureLexer(lexer);
5758
final CommonTokenStream tokens = new CommonTokenStream(lexer);
5859
final KotlinParser parser = new KotlinParser(tokens);
5960

6061
final boolean isScript = fileName.toLowerCase().endsWith(".kts");
61-
final ParseTree tree = isScript ? parser.script() : parser.kotlinFile();
62+
final ParseTree tree = AntlrParserUtils.parseTwoStage(parser, tokens, LOGGER, fileName,
63+
() -> isScript ? parser.script() : parser.kotlinFile());
6264

6365
final KotlinFileDataHandler fileDataHandler = new KotlinFileDataHandler(fileName);
6466
fileDataHandler.setFileHash(fileHash);

src/main/java/net/explorviz/code/analysis/parser/AntlrParserService.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.antlr.v4.runtime.CharStream;
1111
import org.antlr.v4.runtime.CharStreams;
1212
import org.antlr.v4.runtime.CommonTokenStream;
13+
import org.antlr.v4.runtime.tree.ParseTree;
1314
import org.antlr.v4.runtime.tree.ParseTreeWalker;
1415
import org.eclipse.microprofile.config.inject.ConfigProperty;
1516
import org.slf4j.Logger;
@@ -56,19 +57,17 @@ public JavaFileDataHandler parseFile(final String pathToFile, final String fileH
5657

5758
private JavaFileDataHandler parse(final CharStream charStream, final String fileName,
5859
final String fileHash) {
59-
// Create lexer and parser
6060
final JavaLexer lexer = new JavaLexer(charStream);
61+
AntlrParserUtils.configureLexer(lexer);
6162
final CommonTokenStream tokens = new CommonTokenStream(lexer);
6263
final JavaParser parser = new JavaParser(tokens);
6364

64-
// Parse the compilation unit
65-
final JavaParser.CompilationUnitContext compilationUnit = parser.compilationUnit();
65+
final ParseTree compilationUnit =
66+
AntlrParserUtils.parseTwoStage(parser, tokens, LOGGER, fileName, parser::compilationUnit);
6667

67-
// Create Java file data handler
6868
final JavaFileDataHandler fileDataHandler = new JavaFileDataHandler(fileName);
6969
fileDataHandler.setFileHash(fileHash);
7070

71-
// Create and execute the listener
7271
final JavaFileDataListener listener = new JavaFileDataListener(fileDataHandler,
7372
wildcardImportProperty, tokens);
7473
final ParseTreeWalker walker = new ParseTreeWalker();

0 commit comments

Comments
 (0)