Skip to content

Commit

Permalink
Collect metrics for application instances (#4270)
Browse files Browse the repository at this point in the history
  • Loading branch information
heowc authored Apr 25, 2024
1 parent 2a8b7ed commit de6d506
Show file tree
Hide file tree
Showing 12 changed files with 649 additions and 128 deletions.
11 changes: 11 additions & 0 deletions docs/modules/ROOT/pages/spring-cloud-netflix.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,17 @@ when running a Eureka server you must include these dependencies in your POM or

Spring Cloud Netflix Eureka Server does not support Spring AOT transformations or native images.

=== Metrics

`EurekaInstanceMonitor` listens to events related to Eureka instance registration and creates/updates `Gauge`s for Eureka instance information in Micrometer's `MeterRegistry`. By default, this behavior is disabled. If you want to enable it, you need to set `eureka.server.metrics.enabled` to `true`.

By default, the `Gauge`s are named `eureka.server.instances` and have the following tags:

- `application`: application name
- `status`: instance status (`UP`, `DOWN`, `STARTING`, `OUT_OF_SERVICE`, `UNKNOWN`, see: `com.netflix.appinfo.InstanceInfo.InstanceStatus`)

You can add additional tags by injecting your own implementation of `EurekaInstanceTagsProvider`.

== Configuration properties

To see the list of all Spring Cloud Netflix related configuration properties please check link:appendix.html[the Appendix page].
Expand Down
211 changes: 106 additions & 105 deletions docs/modules/ROOT/partials/_configprops.adoc

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2013-2024 the original author or authors.
*
* 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 org.springframework.cloud.netflix.eureka.server.metrics;

import com.netflix.appinfo.InstanceInfo;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;

/**
* Default implementation for {@link EurekaInstanceTagsProvider}.
*
* @author Wonchul Heo
* @since 4.1.2
*/
class DefaultEurekaInstanceTagsProvider implements EurekaInstanceTagsProvider {

@Override
public Tags eurekaInstanceTags(InstanceInfo instanceInfo) {
return Tags.of(Tag.of("application", instanceInfo.getAppName()),
Tag.of("status", instanceInfo.getStatus().name()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2013-2024 the original author or authors.
*
* 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 org.springframework.cloud.netflix.eureka.server.metrics;

import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
import io.micrometer.core.instrument.MeterRegistry;

import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration;
import org.springframework.context.annotation.Bean;

/**
* Auto-configuration for Eureka Instance metrics.
*
* @author Wonchul Heo
* @since 4.1.2
*/
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnBean(MeterRegistry.class)
@AutoConfigureAfter({ MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
EurekaServerAutoConfiguration.class })
@ConditionalOnProperty(name = "eureka.server.metrics.enabled", havingValue = "true")
class EurekaInstanceMetricsAutoConfiguration {

@ConditionalOnMissingBean
@Bean
public EurekaInstanceTagsProvider eurekaInstanceTagProvider() {
return new DefaultEurekaInstanceTagsProvider();
}

@ConditionalOnMissingBean
@Bean
public EurekaInstanceMonitor eurekaInstanceMeterBinder(MeterRegistry meterRegistry,
PeerAwareInstanceRegistry instanceRegistry, EurekaInstanceTagsProvider tagProvider) {
return new EurekaInstanceMonitor(meterRegistry, instanceRegistry, tagProvider);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2013-2024 the original author or authors.
*
* 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 org.springframework.cloud.netflix.eureka.server.metrics;

import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MultiGauge;
import io.micrometer.core.instrument.Tags;

import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRenewedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;

/**
* {@link SmartApplicationListener} for collecting event metrics from
* {@link PeerAwareInstanceRegistry}.
*
* @author Wonchul Heo
* @since 4.1.2
*/
public class EurekaInstanceMonitor implements SmartApplicationListener {

private final MultiGauge eurekaInstances;

private final PeerAwareInstanceRegistry instanceRegistry;

private final EurekaInstanceTagsProvider tagProvider;

EurekaInstanceMonitor(MeterRegistry meterRegistry, PeerAwareInstanceRegistry instanceRegistry,
EurekaInstanceTagsProvider tagProvider) {
Objects.requireNonNull(meterRegistry);
this.instanceRegistry = Objects.requireNonNull(instanceRegistry);
this.tagProvider = Objects.requireNonNull(tagProvider);
this.eurekaInstances = MultiGauge.builder("eureka.server.instances")
.description("Number of application instances registered with the Eureka server.")
.register(meterRegistry);
}

@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
// If events that change state are added, an event class must be added.
return EurekaInstanceCanceledEvent.class.isAssignableFrom(eventType)
|| EurekaInstanceRegisteredEvent.class.isAssignableFrom(eventType)
|| EurekaInstanceRenewedEvent.class.isAssignableFrom(eventType);
}

@Override
public void onApplicationEvent(ApplicationEvent event) {
final Map<Tags, Long> aggregatedCounts = instanceRegistry.getApplications().getRegisteredApplications().stream()
.flatMap(application -> application.getInstances().stream())
.collect(Collectors.groupingBy(tagProvider::eurekaInstanceTags, Collectors.counting()));
eurekaInstances.register(aggregatedCounts.entrySet().stream()
.map(entry -> MultiGauge.Row.of(entry.getKey(), entry.getValue())).collect(Collectors.toList()), true);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2013-2024 the original author or authors.
*
* 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 org.springframework.cloud.netflix.eureka.server.metrics;

import com.netflix.appinfo.InstanceInfo;
import io.micrometer.core.instrument.Tags;

/**
* Provides {@link Tags} for Eureka instance metrics.
*
* @author Wonchul Heo
* @since 4.1.2
*/
public interface EurekaInstanceTagsProvider {

Tags eurekaInstanceTags(InstanceInfo instanceInfo);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"properties": [
{
"name": "eureka.server.metrics.enabled",
"type": "java.lang.Boolean",
"defaultValue": "false",
"description": "Indicates whether the metrics should be enabled for eureka instances."
}
]
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
org.springframework.cloud.netflix.eureka.server.metrics.EurekaInstanceMetricsAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2013-2024 the original author or authors.
*
* 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 org.springframework.cloud.netflix.eureka.server;

import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.LeaseInfo;

public final class EurekaInstanceFixture {

private EurekaInstanceFixture() {
}

public static LeaseInfo getLeaseInfo() {
LeaseInfo.Builder leaseBuilder = LeaseInfo.Builder.newBuilder();
leaseBuilder.setRenewalIntervalInSecs(10);
leaseBuilder.setDurationInSecs(15);
return leaseBuilder.build();
}

public static InstanceInfo getInstanceInfo(String appName, String hostName, String instanceId, int port,
LeaseInfo leaseInfo) {
InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();
builder.setAppName(appName);
builder.setHostName(hostName);
builder.setInstanceId(instanceId);
builder.setPort(port);
builder.setLeaseInfo(leaseInfo);
return builder.build();
}

}
Loading

0 comments on commit de6d506

Please sign in to comment.