Skip to content

Commit

Permalink
[DataProvider] Extract Bazel version from profile, if present (#108)
Browse files Browse the repository at this point in the history
With Bazel release 6.1.0 the Bazel profile includes which version of
Bazel generated the profile. This PR adds a DataProvider that extracts
that information, if it is included.

Fixes #109

---------

Signed-off-by: Sara Adams <[email protected]>
  • Loading branch information
saraadams authored Nov 22, 2023
1 parent f1b0b13 commit 5fe8dcf
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,9 @@ public class BazelProfileConstants {

// InstantEvent names
public static final String INSTANT_FINISHING = "Finishing";

// otherData key names
// See
// https://github.com/bazelbuild/bazel/blob/aab19f75cd383c4b09a6ae720f9fa436bf89d271/src/main/java/com/google/devtools/build/lib/profiler/JsonTraceFileWriter.java#L179-L183
public static final String OTHER_DATA_BAZEL_VERSION = "bazel_version";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright 2023 EngFlow Inc.
*
* 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.engflow.bazel.invocation.analyzer.dataproviders;

import com.engflow.bazel.invocation.analyzer.core.Datum;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.Nullable;

/** The Bazel version used when the analyzed inputs were generated. */
public class BazelVersion implements Datum {
private final Optional<Integer> major;
private final Optional<Integer> minor;
private final Optional<Integer> patch;
private final Optional<String> preReleaseAnnotation;
@Nullable private final String emptyReason;

BazelVersion(int major, int minor, int patch, String preReleaseAnnotation) {
Preconditions.checkNotNull(preReleaseAnnotation);
this.major = Optional.of(major);
this.minor = Optional.of(minor);
this.patch = Optional.of(patch);
this.preReleaseAnnotation = Optional.of(preReleaseAnnotation);
this.emptyReason = null;
}

BazelVersion(String emptyReason) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(emptyReason));
this.major = Optional.empty();
this.minor = Optional.empty();
this.patch = Optional.empty();
this.preReleaseAnnotation = Optional.empty();
this.emptyReason = emptyReason;
}

@Override
public boolean isEmpty() {
return emptyReason != null;
}

@Override
public String getEmptyReason() {
return emptyReason;
}

public Optional<Integer> getMajor() {
return major;
}

public Optional<Integer> getMinor() {
return minor;
}

public Optional<Integer> getPatch() {
return patch;
}

public Optional<String> getPreReleaseAnnotation() {
return preReleaseAnnotation;
}

@Override
public String getDescription() {
return "The Bazel version used when the Bazel profile was generated. Extracted from the Bazel"
+ " profile.";
}

public String getSummary() {
return isEmpty()
? null
: String.format(
"release %d.%d.%d%s",
major.get(), minor.get(), patch.get(), preReleaseAnnotation.get());
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BazelVersion that = (BazelVersion) o;
return major.equals(that.major)
&& minor.equals(that.minor)
&& patch.equals(that.patch)
&& preReleaseAnnotation.equals(that.preReleaseAnnotation)
&& Objects.equals(emptyReason, that.emptyReason);
}

@Override
public int hashCode() {
return Objects.hash(major, minor, patch, preReleaseAnnotation, emptyReason);
}

@Override
public String toString() {
return isEmpty() ? emptyReason : getSummary();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2023 EngFlow Inc.
*
* 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.engflow.bazel.invocation.analyzer.dataproviders;

import static com.engflow.bazel.invocation.analyzer.core.DatumSupplier.memoized;

import com.engflow.bazel.invocation.analyzer.bazelprofile.BazelProfile;
import com.engflow.bazel.invocation.analyzer.bazelprofile.BazelProfileConstants;
import com.engflow.bazel.invocation.analyzer.core.DataProvider;
import com.engflow.bazel.invocation.analyzer.core.DatumSupplierSpecification;
import com.engflow.bazel.invocation.analyzer.core.InvalidProfileException;
import com.engflow.bazel.invocation.analyzer.core.MissingInputException;
import com.engflow.bazel.invocation.analyzer.core.NullDatumException;
import com.google.common.annotations.VisibleForTesting;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* A {@link DataProvider} that supplies data on the Bazel version used when the Bazel profile was
* generated.
*/
public class BazelVersionDataProvider extends DataProvider {
// See https://bazel.build/release#bazel-versioning
// See
// https://github.com/bazelbuild/bazel/blob/aab19f75cd383c4b09a6ae720f9fa436bf89d271/src/main/java/com/google/devtools/build/lib/profiler/JsonTraceFileWriter.java#L179
// See
// https://github.com/bazelbuild/bazel/blob/c637041ec145e0964982a2cbf8d5693f0d1d4be0/src/main/java/com/google/devtools/build/lib/analysis/BlazeVersionInfo.java#L119
private static final Pattern BAZEL_VERSION_PATTERN =
Pattern.compile("^release (\\d+)\\.(\\d+)\\.(\\d+)(|-.*)$");

@Override
public List<DatumSupplierSpecification<?>> getSuppliers() {
return List.of(
DatumSupplierSpecification.of(BazelVersion.class, memoized(this::getBazelVersion)));
}

public BazelVersion getBazelVersion()
throws InvalidProfileException, MissingInputException, NullDatumException {
BazelProfile bazelProfile = getDataManager().getDatum(BazelProfile.class);
String bazelVersion =
bazelProfile.getOtherData().get(BazelProfileConstants.OTHER_DATA_BAZEL_VERSION);
return parse(bazelVersion);
}

@VisibleForTesting
static BazelVersion parse(String version) {
if (version == null) {
// The version metadata was introduced in https://github.com/bazelbuild/bazel/pull/17562 and
// added to release 6.1.0.
return new BazelVersion(
"No Bazel version was found. Bazel versions before 6.1.0 did not report the version.");
}
Matcher m = BAZEL_VERSION_PATTERN.matcher(version);
if (m.matches()) {
return new BazelVersion(
Integer.valueOf(m.group(1)),
Integer.valueOf(m.group(2)),
Integer.valueOf(m.group(3)),
m.group(4));
} else {
return new BazelVersion(
String.format("The provided Bazel version could not be parsed: '%s'", version));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static List<DataProvider> getAllDataProviders() {
return List.of(
new ActionStatsDataProvider(),
new BazelPhasesDataProvider(),
new BazelVersionDataProvider(),
new CriticalPathDurationDataProvider(),
new EstimatedCoresDataProvider(),
new GarbageCollectionStatsDataProvider(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright 2023 EngFlow Inc.
*
* 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.engflow.bazel.invocation.analyzer.dataproviders;

import static com.engflow.bazel.invocation.analyzer.WriteBazelProfile.mainThread;
import static com.engflow.bazel.invocation.analyzer.WriteBazelProfile.metaData;
import static com.engflow.bazel.invocation.analyzer.WriteBazelProfile.trace;
import static com.google.common.truth.Truth.assertThat;

import com.engflow.bazel.invocation.analyzer.WriteBazelProfile;
import com.engflow.bazel.invocation.analyzer.bazelprofile.BazelProfileConstants;
import com.engflow.bazel.invocation.analyzer.core.DuplicateProviderException;
import com.engflow.bazel.invocation.analyzer.core.InvalidProfileException;
import com.engflow.bazel.invocation.analyzer.core.MissingInputException;
import com.engflow.bazel.invocation.analyzer.core.NullDatumException;
import org.junit.Before;
import org.junit.Test;

public class BazelVersionDataProviderTest extends DataProviderUnitTestBase {
private BazelVersionDataProvider provider;

@Before
public void setupTest() throws Exception {
provider = new BazelVersionDataProvider();
provider.register(dataManager);
super.dataProvider = provider;
}

@Test
public void shouldReturnEmptyOnMissingBazelVersion()
throws DuplicateProviderException,
InvalidProfileException,
MissingInputException,
NullDatumException {
useProfile(metaData(), trace(mainThread()));
assertThat(provider.getBazelVersion().isEmpty()).isTrue();
}

@Test
public void shouldReturnEmptyOnInvalidBazelVersion()
throws DuplicateProviderException,
InvalidProfileException,
MissingInputException,
NullDatumException {
useProfile(
metaData(
WriteBazelProfile.Property.put(
BazelProfileConstants.OTHER_DATA_BAZEL_VERSION, "invalid")),
trace(mainThread()));
assertThat(provider.getBazelVersion().isEmpty()).isTrue();
}

@Test
public void shouldReturnVersionWithoutPreRelease()
throws DuplicateProviderException,
InvalidProfileException,
MissingInputException,
NullDatumException {
String validBazelVersion = "release 6.1.0";
useProfile(
metaData(
WriteBazelProfile.Property.put(
BazelProfileConstants.OTHER_DATA_BAZEL_VERSION, validBazelVersion)),
trace(mainThread()));

BazelVersion version = provider.getBazelVersion();
assertThat(version.isEmpty()).isFalse();
assertThat(version.getSummary()).isEqualTo(validBazelVersion);
}

@Test
public void shouldReturnBazelVersionWithPreRelease()
throws DuplicateProviderException,
InvalidProfileException,
MissingInputException,
NullDatumException {
String validBazelVersion = "release 8.0.0-pre.20231030.2";
useProfile(
metaData(
WriteBazelProfile.Property.put(
BazelProfileConstants.OTHER_DATA_BAZEL_VERSION, validBazelVersion)),
trace(mainThread()));

BazelVersion version = provider.getBazelVersion();
assertThat(version.isEmpty()).isFalse();
assertThat(version.getSummary()).isEqualTo(validBazelVersion);
}

@Test
public void parseReturnsEmpty() {
assertThat(BazelVersionDataProvider.parse("").isEmpty()).isTrue();
assertThat(BazelVersionDataProvider.parse("1.2.3").isEmpty()).isTrue();
assertThat(BazelVersionDataProvider.parse("1.2.3-foo").isEmpty()).isTrue();
assertThat(BazelVersionDataProvider.parse("release 1").isEmpty()).isTrue();
assertThat(BazelVersionDataProvider.parse("release 1.2").isEmpty()).isTrue();
assertThat(BazelVersionDataProvider.parse("release 1.2.3foo").isEmpty()).isTrue();
}

@Test
public void parseReturnsNonempty() {
assertThat(BazelVersionDataProvider.parse("release 1.23.4"))
.isEqualTo(new BazelVersion(1, 23, 4, ""));
assertThat(BazelVersionDataProvider.parse("release 12.3.45-foo"))
.isEqualTo(new BazelVersion(12, 3, 45, "-foo"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
ActionStatsDataProviderTest.class,
BazelPhasesDataProviderTest.class,
BazelPhaseDescriptionsTest.class,
BazelVersionDataProviderTest.class,
BazelProfilePhaseTest.class,
CriticalPathDurationDataProviderTest.class,
EstimatedCoresDataProviderTest.class,
Expand Down

0 comments on commit 5fe8dcf

Please sign in to comment.