diff --git a/.gitignore b/.gitignore index 2c109640c6..d437846758 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build/ +!micrometer-core/src/*/*/io/micrometer/core/instrument/binder/build .gradle/ out/ diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/MeterBinder.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/MeterBinder.java index 667570e996..b8fcc6517a 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/MeterBinder.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/MeterBinder.java @@ -27,6 +27,8 @@ */ public interface MeterBinder { + String DEFAULT_TAG_VALUE = "unknown"; + void bindTo(@NonNull MeterRegistry registry); } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/build/BuildInfo.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/build/BuildInfo.java new file mode 100644 index 0000000000..443e2ba3b9 --- /dev/null +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/build/BuildInfo.java @@ -0,0 +1,112 @@ +/* + * Copyright 2023 VMware, 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 + * + * https://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 io.micrometer.core.instrument.binder.build; + +import java.time.Instant; +import java.util.Optional; + +public class BuildInfo { + + private final String group; + + private final String artifact; + + private final String name; + + private final String version; + + private final Instant timestamp; + + private BuildInfo(String group, String artifact, String name, String version, Instant timestamp) { + this.group = group; + this.artifact = artifact; + this.name = name; + this.version = version; + this.timestamp = timestamp; + } + + public static BuildInfo.Builder builder() { + return new BuildInfo.Builder(); + } + + public Optional getGroup() { + return Optional.ofNullable(group); + } + + public Optional getArtifact() { + return Optional.ofNullable(artifact); + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public Optional getVersion() { + return Optional.ofNullable(version); + } + + public Optional getTimestamp() { + return Optional.ofNullable(timestamp); + } + + public static class Builder { + + private String group; + + private String artifact; + + private String name; + + private String version; + + private Instant timestamp; + + private Builder() { + } + + public BuildInfo build() { + return new BuildInfo(group, artifact, name, version, timestamp); + } + + public Builder group(String group) { + this.group = group; + return this; + } + + public Builder artifact(String artifact) { + this.artifact = artifact; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder version(String version) { + this.version = version; + return this; + } + + public Builder timestamp(Instant timestamp) { + this.timestamp = timestamp; + return this; + } + + } + +} diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/build/BuildInfoMetrics.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/build/BuildInfoMetrics.java new file mode 100644 index 0000000000..90495e5501 --- /dev/null +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/build/BuildInfoMetrics.java @@ -0,0 +1,61 @@ +/* + * Copyright 2023 VMware, 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 + * + * https://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 io.micrometer.core.instrument.binder.build; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.binder.MeterBinder; + +import java.time.Instant; + +import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNull; + +public class BuildInfoMetrics implements MeterBinder { + + private final BuildInfo buildInfo; + + private final Iterable tags; + + public BuildInfoMetrics(BuildInfo buildInfo) { + this(buildInfo, emptyList()); + } + + public BuildInfoMetrics(BuildInfo buildInfo, Iterable tags) { + requireNonNull(buildInfo, "buildInfo"); + requireNonNull(tags, "tags"); + this.buildInfo = buildInfo; + this.tags = tags; + } + + @Override + public void bindTo(MeterRegistry registry) { + requireNonNull(registry, "registry"); + Gauge.builder("build.info", () -> 1L) + .description("Build information") + .strongReference(true) + .tag("group", buildInfo.getGroup().orElse(DEFAULT_TAG_VALUE)) + .tag("artifact", buildInfo.getArtifact().orElse(DEFAULT_TAG_VALUE)) + .tag("name", buildInfo.getName().orElse(DEFAULT_TAG_VALUE)) + .tag("version", buildInfo.getVersion().orElse(DEFAULT_TAG_VALUE)) + .tag("timestamp", buildInfo.getTimestamp().map(Instant::toString).orElse(DEFAULT_TAG_VALUE)) + .tags(tags) + .register(registry); + } + +} diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/build/package-info.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/build/package-info.java new file mode 100644 index 0000000000..c56def0d73 --- /dev/null +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/build/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2023 VMware, 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 + * + * https://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. + */ + +/** + * Meter binders for build info. + */ +package io.micrometer.core.instrument.binder.build; diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/git/GitCommitInfo.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/git/GitCommitInfo.java new file mode 100644 index 0000000000..43f98e33a5 --- /dev/null +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/git/GitCommitInfo.java @@ -0,0 +1,101 @@ +/* + * Copyright 2023 VMware, 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 + * + * https://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 io.micrometer.core.instrument.binder.git; + +import java.time.Instant; +import java.util.Optional; + +/** + * Information from a Git commit. + */ +public class GitCommitInfo { + + private final String branch; + + private final String commitId; + + private final String commitIdShort; + + private final Instant commitTime; + + private GitCommitInfo(String branch, String commitId, String commitIdShort, Instant commitTime) { + this.branch = branch; + this.commitId = commitId; + this.commitIdShort = commitIdShort; + this.commitTime = commitTime; + } + + public static GitCommitInfo.Builder builder() { + return new GitCommitInfo.Builder(); + } + + public Optional getBranch() { + return Optional.ofNullable(branch); + } + + public Optional getCommitId() { + return Optional.ofNullable(commitId); + } + + public Optional getShortCommitId() { + return Optional.ofNullable(commitIdShort); + } + + public Optional getCommitTime() { + return Optional.ofNullable(commitTime); + } + + public static class Builder { + + private String branch; + + private String commitId; + + private String commitIdShort; + + private Instant commitTime; + + private Builder() { + } + + public GitCommitInfo build() { + return new GitCommitInfo(branch, commitId, commitIdShort, commitTime); + } + + public Builder branch(String branch) { + this.branch = branch; + return this; + } + + public Builder commitId(String commitId) { + this.commitId = commitId; + return this; + } + + public Builder commitIdShort(String commitIdShort) { + this.commitIdShort = commitIdShort; + return this; + } + + public Builder commitTime(Instant commitTime) { + this.commitTime = commitTime; + return this; + } + + } + +} diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/git/GitCommitInfoMetrics.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/git/GitCommitInfoMetrics.java new file mode 100644 index 0000000000..a41d0059f2 --- /dev/null +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/git/GitCommitInfoMetrics.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 VMware, 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 + * + * https://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 io.micrometer.core.instrument.binder.git; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.binder.MeterBinder; + +import java.time.Instant; + +import static java.util.Objects.requireNonNull; + +public class GitCommitInfoMetrics implements MeterBinder { + + private final GitCommitInfo gitCommitInfo; + + public GitCommitInfoMetrics(GitCommitInfo gitCommitInfo) { + requireNonNull(gitCommitInfo, "gitCommitInfo"); + this.gitCommitInfo = gitCommitInfo; + } + + @Override + public void bindTo(MeterRegistry registry) { + requireNonNull(registry, "registry"); + Gauge.builder("git.commit.info", () -> 1L) + .description("Git commit information") + .strongReference(true) + .tag("branch", gitCommitInfo.getBranch().orElse(DEFAULT_TAG_VALUE)) + .tag("commit.id", gitCommitInfo.getCommitId().orElse(DEFAULT_TAG_VALUE)) + .tag("commit.id.short", gitCommitInfo.getShortCommitId().orElse(DEFAULT_TAG_VALUE)) + .tag("commit.timestamp", gitCommitInfo.getCommitTime().map(Instant::toString).orElse(DEFAULT_TAG_VALUE)) + .register(registry); + } + +} diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/git/package-info.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/git/package-info.java new file mode 100644 index 0000000000..a0fa202c94 --- /dev/null +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/git/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2023 VMware, 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 + * + * https://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. + */ + +/** + * Meter binders for git info. + */ +package io.micrometer.core.instrument.binder.git; diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/build/BuildInfoMetricsTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/build/BuildInfoMetricsTest.java new file mode 100644 index 0000000000..3624fd4f61 --- /dev/null +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/build/BuildInfoMetricsTest.java @@ -0,0 +1,158 @@ +/* + * Copyright 2023 VMware, 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 + * + * https://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 io.micrometer.core.instrument.binder.build; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.search.Search; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +import static java.time.ZoneOffset.UTC; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BuildInfoMetricsTest { + + @Test + void shouldTagBuildInfoWithValuesWhenPresent() { + MeterRegistry registry = new SimpleMeterRegistry(); + + BuildInfo buildInfo = BuildInfo.builder() + .group("my-group") + .artifact("my-artifact") + .name("my-name") + .version("0.0.0-version") + .timestamp(LocalDateTime.of(2023, 1, 2, 3, 4, 5).toInstant(UTC)) + .build(); + + BuildInfoMetrics buildInfoMetrics = new BuildInfoMetrics(buildInfo); + + buildInfoMetrics.bindTo(registry); + + Collection gauges = Search.in(registry).name("build.info").gauges(); + + assertThat(gauges).hasSize(1); + + Gauge buildInfoGauge = gauges.iterator().next(); + + assertThat(buildInfoGauge.value()).isEqualTo(1); + + Meter.Id id = buildInfoGauge.getId(); + + assertThat(id.getName()).isEqualTo("build.info"); + assertThat(id.getTags()).containsExactlyInAnyOrder(Tag.of("group", "my-group"), + Tag.of("artifact", "my-artifact"), Tag.of("name", "my-name"), Tag.of("version", "0.0.0-version"), + Tag.of("timestamp", "2023-01-02T03:04:05Z")); + } + + @Test + void shouldTagBuildInfoWithValuesAndExtraTagsWhenPresent() { + MeterRegistry registry = new SimpleMeterRegistry(); + + BuildInfo buildInfo = BuildInfo.builder() + .group("my-group") + .artifact("my-artifact") + .name("my-name") + .version("0.0.0-version") + .timestamp(LocalDateTime.of(2023, 1, 2, 3, 4, 5).toInstant(UTC)) + .build(); + List tags = List.of(Tag.of("custom-tag", "custom-tag-value")); + + BuildInfoMetrics buildInfoMetrics = new BuildInfoMetrics(buildInfo, tags); + + buildInfoMetrics.bindTo(registry); + + Collection gauges = Search.in(registry).name("build.info").gauges(); + + assertThat(gauges).hasSize(1); + + Gauge buildInfoGauge = gauges.iterator().next(); + + assertThat(buildInfoGauge.value()).isEqualTo(1); + + Meter.Id id = buildInfoGauge.getId(); + + assertThat(id.getName()).isEqualTo("build.info"); + assertThat(id.getTags()).containsExactlyInAnyOrder(Tag.of("group", "my-group"), + Tag.of("artifact", "my-artifact"), Tag.of("name", "my-name"), Tag.of("version", "0.0.0-version"), + Tag.of("timestamp", "2023-01-02T03:04:05Z"), Tag.of("custom-tag", "custom-tag-value")); + } + + @Test + void shouldTagAllBuildInfoWithUnknownWhenNotPresent() { + MeterRegistry registry = new SimpleMeterRegistry(); + + BuildInfo buildInfo = BuildInfo.builder().build(); + + BuildInfoMetrics buildInfoMetrics = new BuildInfoMetrics(buildInfo); + + buildInfoMetrics.bindTo(registry); + + Collection gauges = Search.in(registry).name("build.info").gauges(); + + assertThat(gauges).hasSize(1); + + Gauge buildInfoGauge = gauges.iterator().next(); + + assertThat(buildInfoGauge.value()).isEqualTo(1); + + Meter.Id id = buildInfoGauge.getId(); + + assertThat(id.getName()).isEqualTo("build.info"); + assertThat(id.getTags()).containsExactlyInAnyOrder(Tag.of("group", "unknown"), Tag.of("artifact", "unknown"), + Tag.of("name", "unknown"), Tag.of("version", "unknown"), Tag.of("timestamp", "unknown")); + } + + @Test + void throwsWhenRegistryIsNull() { + BuildInfo buildInfo = BuildInfo.builder().build(); + + BuildInfoMetrics buildInfoMetrics = new BuildInfoMetrics(buildInfo); + + assertThatThrownBy(() -> buildInfoMetrics.bindTo(null)).isInstanceOf(NullPointerException.class) + .hasMessageContaining("registry"); + } + + @Test + void throwsWhenBuildInfoIsNullForBuildInfoConstructor() { + assertThatThrownBy(() -> new BuildInfoMetrics(null)).isInstanceOf(NullPointerException.class) + .hasMessageContaining("buildInfo"); + } + + @Test + void throwsWhenBuildInfoIsNullForBuildInfoAndTagsConstructor() { + assertThatThrownBy(() -> new BuildInfoMetrics(null, emptyList())).isInstanceOf(NullPointerException.class) + .hasMessageContaining("buildInfo"); + } + + @Test + void throwsWhenTagsIsNullForBuildInfoAndTagsConstructor() { + BuildInfo buildInfo = BuildInfo.builder().build(); + + assertThatThrownBy(() -> new BuildInfoMetrics(buildInfo, null)).isInstanceOf(NullPointerException.class) + .hasMessageContaining("tags"); + } + +} diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/git/GitCommitInfoMetricsMetricsTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/git/GitCommitInfoMetricsMetricsTest.java new file mode 100644 index 0000000000..c6634117ee --- /dev/null +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/git/GitCommitInfoMetricsMetricsTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2023 VMware, 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 + * + * https://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 io.micrometer.core.instrument.binder.git; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.search.Search; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.Collection; + +import static java.time.ZoneOffset.UTC; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class GitCommitInfoMetricsTest { + + @Test + void shouldTagAllGitCommitInfoWithValuesWhenPresent() { + MeterRegistry registry = new SimpleMeterRegistry(); + + GitCommitInfo gitCommitInfo = GitCommitInfo.builder() + .branch("my-branch") + .commitId("1234567890123456789012345678901234567890") + .commitIdShort("12345678") + .commitTime(LocalDateTime.of(2023, 1, 2, 3, 4, 5).toInstant(UTC)) + .build(); + + GitCommitInfoMetrics gitCommitInfoMetrics = new GitCommitInfoMetrics(gitCommitInfo); + + gitCommitInfoMetrics.bindTo(registry); + + Collection gauges = Search.in(registry).name("git.commit.info").gauges(); + + assertThat(gauges).hasSize(1); + + Gauge gitCommitInfoGauge = gauges.iterator().next(); + + assertThat(gitCommitInfoGauge.value()).isEqualTo(1); + + Meter.Id id = gitCommitInfoGauge.getId(); + + assertThat(id.getName()).isEqualTo("git.commit.info"); + assertThat(id.getTags()).containsExactlyInAnyOrder(Tag.of("branch", "my-branch"), + Tag.of("commit.id", "1234567890123456789012345678901234567890"), Tag.of("commit.id.short", "12345678"), + Tag.of("commit.timestamp", "2023-01-02T03:04:05Z")); + } + + @Test + void shouldTagAllGitCommitInfoWithUnknownWhenNotPresent() { + MeterRegistry registry = new SimpleMeterRegistry(); + + GitCommitInfo gitCommitInfo = GitCommitInfo.builder().build(); + + GitCommitInfoMetrics gitCommitInfoMetrics = new GitCommitInfoMetrics(gitCommitInfo); + + gitCommitInfoMetrics.bindTo(registry); + + Collection gauges = Search.in(registry).name("git.commit.info").gauges(); + + assertThat(gauges).hasSize(1); + + Gauge gitCommitInfoGauge = gauges.iterator().next(); + + assertThat(gitCommitInfoGauge.value()).isEqualTo(1); + + Meter.Id id = gitCommitInfoGauge.getId(); + + assertThat(id.getName()).isEqualTo("git.commit.info"); + assertThat(id.getTags()).containsExactlyInAnyOrder(Tag.of("branch", "unknown"), Tag.of("commit.id", "unknown"), + Tag.of("commit.id.short", "unknown"), Tag.of("commit.timestamp", "unknown")); + } + + @Test + void throwsWhenGitCommitInfoIsNull() { + assertThatThrownBy(() -> new GitCommitInfoMetrics(null)).isInstanceOf(NullPointerException.class) + .hasMessageContaining("gitCommitInfo"); + } + + @Test + void throwsWhenRegistryIsNull() { + GitCommitInfo gitCommitInfo = GitCommitInfo.builder().build(); + + GitCommitInfoMetrics gitCommitInfoMetrics = new GitCommitInfoMetrics(gitCommitInfo); + + assertThatThrownBy(() -> gitCommitInfoMetrics.bindTo(null)).isInstanceOf(NullPointerException.class) + .hasMessageContaining("registry"); + } + +}