diff --git a/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java b/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java index c16701cce5c..fd9f49faa7b 100644 --- a/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java +++ b/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java @@ -26,6 +26,8 @@ import com.google.idea.blaze.base.command.buildresult.BuildResultHelper; import com.google.idea.blaze.base.command.buildresult.BuildResultHelper.GetArtifactsException; import com.google.idea.blaze.base.console.BlazeConsoleLineProcessorProvider; +import com.google.idea.blaze.base.logging.utils.querysync.BuildDepsStatsScope; +import com.google.idea.blaze.base.logging.utils.querysync.SyncQueryStatsScope; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.run.testlogs.BlazeTestResults; import com.google.idea.blaze.base.scope.BlazeContext; @@ -60,6 +62,8 @@ public BlazeBuildOutputs run( BuildResult buildResult = issueBuild(blazeCommandBuilder, WorkspaceRoot.fromProject(project), context); + BuildDepsStatsScope.fromContext(context) + .ifPresent(stats -> stats.setBazelExitCode(buildResult.exitCode)); if (buildResult.status == Status.FATAL_ERROR) { return BlazeBuildOutputs.noOutputs(buildResult); } @@ -132,6 +136,7 @@ public InputStream runQuery( .ignoreExitCode(true) .build() .run(); + SyncQueryStatsScope.fromContext(context).ifPresent(stats -> stats.setBazelExitCode(retVal)); BazelExitCodeException.throwIfFailed( blazeCommandBuilder, retVal, ThrowOption.ALLOW_PARTIAL_SUCCESS); return new BufferedInputStream( diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.java b/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.java index bd73dca6fd6..bbcdb5f25dc 100644 --- a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.java +++ b/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.java @@ -19,6 +19,7 @@ import com.google.common.collect.Interner; import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.run.testlogs.BlazeTestResults; +import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.exception.BuildException; import java.io.InputStream; import java.util.List; @@ -106,9 +107,12 @@ default void deleteTemporaryOutputFiles() {} * build should not use this since {@link ExternalTask} provide stdout handler. * * @param completedBuildId build id. + * @param stderrConsumer process stderr + * @param blazeContext blaze context may contains logging scope * @return a list of message on stdout. */ - default InputStream getStdout(String completedBuildId, Consumer stderrConsumer) + default InputStream getStdout( + String completedBuildId, Consumer stderrConsumer, BlazeContext blazeContext) throws BuildException { return InputStream.nullInputStream(); } diff --git a/base/src/com/google/idea/blaze/base/logging/utils/querysync/BuildDepsStatsScope.java b/base/src/com/google/idea/blaze/base/logging/utils/querysync/BuildDepsStatsScope.java new file mode 100644 index 00000000000..34f275b08a7 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/logging/utils/querysync/BuildDepsStatsScope.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.base.logging.utils.querysync; + +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.scope.BlazeScope; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; + +/** Stores @{BuildDepsStats} so that it can be logged by the BlazeContext creator owner. */ +public class BuildDepsStatsScope implements BlazeScope { + private Instant startTime; + private final BuildDepsStats.Builder builder; + + public BuildDepsStatsScope() { + builder = BuildDepsStats.builder(); + } + + public BuildDepsStats.Builder getBuilder() { + return builder; + } + + public static Optional fromContext(BlazeContext context) { + return Optional.ofNullable(context.getScope(BuildDepsStatsScope.class)) + .map(BuildDepsStatsScope::getBuilder); + } + + @Override + public void onScopeBegin(BlazeContext context) { + startTime = Instant.now(); + } + + /** Called when the context scope is ending. */ + @Override + public void onScopeEnd(BlazeContext context) { + QuerySyncActionStatsScope.fromContext(context) + .ifPresent( + stats -> + stats.addOperationStats( + builder.setTotalClockTime(Duration.between(startTime, Instant.now())).build())); + } +} diff --git a/base/src/com/google/idea/blaze/base/logging/utils/querysync/QuerySyncActionStatsScope.java b/base/src/com/google/idea/blaze/base/logging/utils/querysync/QuerySyncActionStatsScope.java new file mode 100644 index 00000000000..e60939cd7b7 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/logging/utils/querysync/QuerySyncActionStatsScope.java @@ -0,0 +1,118 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.base.logging.utils.querysync; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableSet; +import com.google.idea.blaze.base.logging.EventLoggingService; +import com.google.idea.blaze.base.logging.utils.querysync.QuerySyncActionStats.Result; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.scope.BlazeScope; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.vfs.VirtualFile; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import org.eclipse.sisu.Nullable; + +/** Stores @{QuerySyncActionStats} so that it can be logged by the BlazeContext creator owner. */ +public class QuerySyncActionStatsScope implements BlazeScope { + private final QuerySyncActionStats.Builder builder; + private final TimeSource timeSource; + + public QuerySyncActionStatsScope(Class actionClass, @Nullable AnActionEvent event) { + this(actionClass, event, ImmutableSet.of()); + } + + public QuerySyncActionStatsScope( + Class actionClass, @Nullable AnActionEvent event, VirtualFile virtualFile) { + this(actionClass, event, ImmutableSet.of(virtualFile)); + } + + public QuerySyncActionStatsScope( + Class actionClass, + @Nullable AnActionEvent event, + ImmutableCollection requestFiles) { + this(actionClass, event, requestFiles, () -> Instant.now()); + } + + @VisibleForTesting + public QuerySyncActionStatsScope( + Class actionClass, + @Nullable AnActionEvent event, + ImmutableCollection requestFiles, + TimeSource timeSource) { + builder = + QuerySyncActionStats.builder() + .handleActionClass(actionClass) + .handleActionEvent(event) + .setRequestedFiles( + requestFiles.stream().map(VirtualFile::toNioPath).collect(toImmutableSet())); + this.timeSource = timeSource; + } + + public QuerySyncActionStats.Builder getBuilder() { + return builder; + } + + public static Optional fromContext(BlazeContext context) { + return Optional.ofNullable(context.getScope(QuerySyncActionStatsScope.class)) + .map(QuerySyncActionStatsScope::getBuilder); + } + + @Override + public void onScopeBegin(BlazeContext context) { + builder.setStartTime(timeSource.now()); + } + + private Result getSyncResult(BlazeContext context) { + if (context.isCancelled()) { + return Result.CANCELLED; + } + if (context.hasErrors()) { + return Result.FAILURE; + } + if (context.hasWarnings()) { + return Result.SUCCESS_WITH_WARNING; + } + return Result.SUCCESS; + } + + /** Called when the context scope is ending. */ + @Override + public void onScopeEnd(BlazeContext context) { + fromContext(context) + .ifPresent( + builder -> + EventLoggingService.getInstance() + .log( + builder + .setTotalClockTime(Duration.between(builder.startTime(), Instant.now())) + .setResult(getSyncResult(context)) + .build())); + } + + /** + * Provider for the current value of "now" for users. This allows unit tests to set now + * specifically. + */ + public interface TimeSource { + Instant now(); + } +} diff --git a/base/src/com/google/idea/blaze/base/logging/utils/querysync/SyncQueryStatsScope.java b/base/src/com/google/idea/blaze/base/logging/utils/querysync/SyncQueryStatsScope.java new file mode 100644 index 00000000000..24da7979ad9 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/logging/utils/querysync/SyncQueryStatsScope.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.base.logging.utils.querysync; + +import com.google.common.annotations.VisibleForTesting; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.scope.BlazeScope; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; + +/** Stores @{SyncQueryStatsScope} so that it can be logged by the BlazeContext creator owner. */ +public class SyncQueryStatsScope implements BlazeScope { + private Instant startTime; + private final SyncQueryStats.Builder builder; + + public SyncQueryStatsScope() { + builder = SyncQueryStats.builder(); + } + + @VisibleForTesting + public SyncQueryStats.Builder getBuilder() { + return builder; + } + + public static Optional fromContext(BlazeContext context) { + return Optional.ofNullable(context.getScope(SyncQueryStatsScope.class)) + .map(SyncQueryStatsScope::getBuilder); + } + + @Override + public void onScopeBegin(BlazeContext context) { + startTime = Instant.now(); + } + + /** Called when the context scope is ending. */ + @Override + public void onScopeEnd(BlazeContext context) { + QuerySyncActionStatsScope.fromContext(context) + .ifPresent( + stats -> + stats.addOperationStats( + builder.setTotalClockTime(Duration.between(startTime, Instant.now())).build())); + } +} diff --git a/base/src/com/google/idea/blaze/base/qsync/ComposePreviewQuerySyncInspectionWidgetActionProvider.java b/base/src/com/google/idea/blaze/base/qsync/ComposePreviewQuerySyncInspectionWidgetActionProvider.java index e3143bcf8b5..52341f2dcdb 100644 --- a/base/src/com/google/idea/blaze/base/qsync/ComposePreviewQuerySyncInspectionWidgetActionProvider.java +++ b/base/src/com/google/idea/blaze/base/qsync/ComposePreviewQuerySyncInspectionWidgetActionProvider.java @@ -15,6 +15,7 @@ */ package com.google.idea.blaze.base.qsync; +import com.google.idea.blaze.base.logging.utils.querysync.QuerySyncActionStatsScope; import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.sync.status.BlazeSyncStatus; import com.intellij.icons.AllIcons.Actions; @@ -73,7 +74,9 @@ public BuildDependencies(@NotNull Editor editor) { public void actionPerformed(@NotNull AnActionEvent e) { Project project = editor.getProject(); PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); - QuerySyncManager.getInstance(project).generateRenderJar(psiFile); + QuerySyncManager.getInstance(project) + .generateRenderJar( + psiFile, new QuerySyncActionStatsScope(getClass(), e, psiFile.getVirtualFile())); } @Override diff --git a/base/src/com/google/idea/blaze/base/qsync/DependencyTracker.java b/base/src/com/google/idea/blaze/base/qsync/DependencyTracker.java index 9412a5ded0a..c535ef87ae0 100644 --- a/base/src/com/google/idea/blaze/base/qsync/DependencyTracker.java +++ b/base/src/com/google/idea/blaze/base/qsync/DependencyTracker.java @@ -29,6 +29,7 @@ import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.Uninterruptibles; import com.google.idea.blaze.base.bazel.BazelExitCode; +import com.google.idea.blaze.base.logging.utils.querysync.BuildDepsStatsScope; import com.google.idea.blaze.base.qsync.ArtifactTracker.UpdateResult; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.common.Label; @@ -147,6 +148,8 @@ private BlazeProjectSnapshot getCurrentSnapshot() { */ public boolean buildDependenciesForTargets(BlazeContext context, Set