Skip to content

Commit

Permalink
Merge pull request #109 from xenit-eu/master
Browse files Browse the repository at this point in the history
Release 0.6.0
  • Loading branch information
kerkhofsd authored Jun 30, 2021
2 parents 15120a7 + b34ad7e commit 66cd31f
Show file tree
Hide file tree
Showing 15 changed files with 247 additions and 62 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ Version template:
-->

# Alfred Telemetry Changelog
## [0.6.0] - 2021-06-30

### Added
* Alfresco 7 support [#107]

### Fixed
* Prometheus scraping: max number of requests reached during Alfresco startup [#104]

[#107]: https://github.com/xenit-eu/alfred-telemetry/pull/107
[#104]: https://github.com/xenit-eu/alfred-telemetry/pull/104

## [0.5.2] - 2021-05-19

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package eu.xenit.alfred.telemetry.binder.solr;

import eu.xenit.alfred.telemetry.binder.solr.sharding.SolrShardingMetrics;
import eu.xenit.alfred.telemetry.binder.solr.sharding.SolrShardingMetricsFactory;
import eu.xenit.alfred.telemetry.binder.solr.tracking.SolrTrackingMetrics;
import io.micrometer.core.instrument.MeterRegistry;
import java.util.Properties;
import org.alfresco.service.transaction.TransactionService;
import org.quartz.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -20,6 +21,10 @@ public class SolrMetricsBeanPostProcessor implements BeanDefinitionRegistryPostP

private static final Logger LOGGER = LoggerFactory.getLogger(SolrMetricsBeanPostProcessor.class);

public static final String SEARCH_TRACKING_COMPONENT_BEAN_ID_BEFORE_7 = "search.solrTrackingComponent";
public static final String SEARCH_TRACKING_COMPONENT_BEAN_ID_AFTER_7 = "search.trackingComponent";
public static final String SOLR_TRACKING_METRICS_ENABLED_PROPERTY = "alfred.telemetry.binder.solr.tracking.enabled";
public static final String SOLR_TRACKING_METRICS_BEAN_ID = "eu.xenit.alfred.telemetry.binder.solr.tracking.SolrTrackingMetrics";
public static final String SOLR_SHARDING_REGISTRY_BEAN_ID = "search.SolrShardRegistry";
public static final String SOLR_SHARDING_METRICS_BEAN_ID = "eu.xenit.alfred.telemetry.binder.solr.sharding.SolrShardingMetrics";
public static final String SOLR_SHARDING_METRICS_CRON_PROPERTY = "alfred.telemetry.binder.solr.sharding.cronexpression";
Expand All @@ -30,15 +35,50 @@ public class SolrMetricsBeanPostProcessor implements BeanDefinitionRegistryPostP
private final Properties globalProperties;

private MeterRegistry meterRegistry;
private TransactionService transactionService;

public SolrMetricsBeanPostProcessor(Properties globalProperties, MeterRegistry meterRegistry, Scheduler scheduler) {
public SolrMetricsBeanPostProcessor(Properties globalProperties, MeterRegistry meterRegistry, Scheduler scheduler, TransactionService transactionService) {
this.globalProperties = globalProperties;
this.meterRegistry = meterRegistry;
this.scheduler = scheduler;
this.transactionService = transactionService;
}

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
createTrackingMetricsBean(beanDefinitionRegistry);
createShardingMetricsBean(beanDefinitionRegistry);
}

private void createTrackingMetricsBean(BeanDefinitionRegistry beanDefinitionRegistry) {
if (!Boolean.parseBoolean(globalProperties.getProperty(SOLR_TRACKING_METRICS_ENABLED_PROPERTY))) {
LOGGER.info("Solr tracking metrics are not enabled, skipping.");
return;
}

BeanDefinition solrTrackingComponentBean;
try {
//Fetching bean for Alfresco <7
solrTrackingComponentBean = beanDefinitionRegistry.getBeanDefinition(
SEARCH_TRACKING_COMPONENT_BEAN_ID_BEFORE_7);
} catch (NoSuchBeanDefinitionException e) {
LOGGER.info(String.format("%s not found, trying %s", SEARCH_TRACKING_COMPONENT_BEAN_ID_BEFORE_7,
SEARCH_TRACKING_COMPONENT_BEAN_ID_AFTER_7));
solrTrackingComponentBean = beanDefinitionRegistry.getBeanDefinition(
SEARCH_TRACKING_COMPONENT_BEAN_ID_AFTER_7);
}
GenericBeanDefinition solrTrackingMetricsBean = new GenericBeanDefinition();
solrTrackingMetricsBean.setBeanClass(SolrTrackingMetrics.class);
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addGenericArgumentValue(solrTrackingComponentBean);
constructorArgumentValues.addGenericArgumentValue(transactionService);
constructorArgumentValues.addGenericArgumentValue(meterRegistry);
solrTrackingMetricsBean.setConstructorArgumentValues(constructorArgumentValues);
beanDefinitionRegistry.registerBeanDefinition(SOLR_TRACKING_METRICS_BEAN_ID, solrTrackingMetricsBean);
LOGGER.info("Registered SolrTrackingMetrics bean");
}

private void createShardingMetricsBean(BeanDefinitionRegistry beanDefinitionRegistry) {
if (!Boolean.parseBoolean(globalProperties.getProperty(SOLR_SHARDING_METRICS_ENABLED_PROPERTY))) {
LOGGER.info("Solr sharding metrics are not enabled, skipping.");
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import java.util.function.ToDoubleFunction;
import org.alfresco.repo.solr.SOLRTrackingComponent;
import org.alfresco.repo.solr.SOLRTrackingComponentImpl;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.service.transaction.TransactionService;
import org.slf4j.Logger;
Expand All @@ -16,31 +16,28 @@ public class SolrTrackingMetrics {
public static final String SOLR_METRICS_PREFIX = "solr.tracking";
private TransactionService transactionService;

private SOLRTrackingComponent solrTrackingComponent;
private SOLRTrackingComponentImpl solrTrackingComponent;
private MeterRegistry registry;

public SolrTrackingMetrics(SOLRTrackingComponent solrTrackingComponent, TransactionService transactionService,
MeterRegistry registry, boolean enabled) {
public SolrTrackingMetrics(SOLRTrackingComponentImpl solrTrackingComponent, TransactionService transactionService,
MeterRegistry registry) {
this.solrTrackingComponent = solrTrackingComponent;
this.transactionService = transactionService;
this.registry = registry;
if (enabled) {
registerMetrics();
}
}

private void registerMetrics() {
LOGGER.info("Registering Solr metrics");
registerSolrTrackingMetric("maxTxnId", SOLRTrackingComponent::getMaxTxnId, "number");
registerSolrTrackingMetric("maxTxnCommitTime", SOLRTrackingComponent::getMaxTxnCommitTime, "timestamp");
registerSolrTrackingMetric("maxChangeSetId", SOLRTrackingComponent::getMaxChangeSetId, "number");
registerSolrTrackingMetric("maxChangeSetCommitTime", SOLRTrackingComponent::getMaxChangeSetCommitTime,
registerSolrTrackingMetric("maxTxnId", SOLRTrackingComponentImpl::getMaxTxnId, "number");
registerSolrTrackingMetric("maxTxnCommitTime", SOLRTrackingComponentImpl::getMaxTxnCommitTime, "timestamp");
registerSolrTrackingMetric("maxChangeSetId", SOLRTrackingComponentImpl::getMaxChangeSetId, "number");
registerSolrTrackingMetric("maxChangeSetCommitTime", SOLRTrackingComponentImpl::getMaxChangeSetCommitTime,
"timestamp");
}

private void registerSolrTrackingMetric(String name, ToDoubleFunction<SOLRTrackingComponent> function,
private void registerSolrTrackingMetric(String name, ToDoubleFunction<SOLRTrackingComponentImpl> function,
String baseUnit) {
ToDoubleFunction<SOLRTrackingComponent> wrappedFunction = solrTrackingComponent1 -> {
ToDoubleFunction<SOLRTrackingComponentImpl> wrappedFunction = solrTrackingComponent1 -> {
RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper();
return retryingTransactionHelper
.doInTransaction(() -> function.applyAsDouble(solrTrackingComponent1), true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
public class PrometheusConfig extends AbstractRegistryConfig {

private int maxRequests;
private int suppressMaxRequestsFailuresDuringUptimeMinutes;

public int getMaxRequests() {
return maxRequests;
Expand All @@ -13,4 +14,12 @@ public int getMaxRequests() {
public void setMaxRequests(int maxRequests) {
this.maxRequests = maxRequests;
}

public int getSuppressMaxRequestsFailuresDuringUptimeMinutes() {
return suppressMaxRequestsFailuresDuringUptimeMinutes;
}

public void setSuppressMaxRequestsFailuresDuringUptimeMinutes(int suppressMaxRequestsFailuresDuringUptimeMinutes) {
this.suppressMaxRequestsFailuresDuringUptimeMinutes = suppressMaxRequestsFailuresDuringUptimeMinutes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,62 +4,105 @@
import eu.xenit.alfred.telemetry.util.PrometheusRegistryUtil;
import io.micrometer.core.instrument.MeterRegistry;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Semaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.http.HttpStatus;
import org.springframework.util.ClassUtils;

public class PrometheusWebScript extends AbstractWebScript {

private static final Logger logger = LoggerFactory.getLogger(PrometheusWebScript.class);

private final int maxRequests;
private final int suppressMaxRequestsFailuresDuringUptimeMinutes;
private final RuntimeMXBean runtimeMXBean;
private final Semaphore semaphore;

private final MeterRegistry meterRegistry;

public PrometheusWebScript(MeterRegistry meterRegistry, PrometheusConfig prometheusConfig) {
this(meterRegistry, ManagementFactory.getRuntimeMXBean(), prometheusConfig);
}

public PrometheusWebScript(MeterRegistry meterRegistry, RuntimeMXBean runtimeMXBean,
PrometheusConfig prometheusConfig) {
this.meterRegistry = meterRegistry;
this.maxRequests = prometheusConfig.getMaxRequests();
this.suppressMaxRequestsFailuresDuringUptimeMinutes = prometheusConfig
.getSuppressMaxRequestsFailuresDuringUptimeMinutes();
this.runtimeMXBean = runtimeMXBean;
this.semaphore = new Semaphore(maxRequests);
}

private static boolean prometheusAvailableOnClasspath() {
return ClassUtils.isPresent("io.micrometer.prometheus.PrometheusMeterRegistry",
PrometheusWebScript.class.getClassLoader());
}

@Override
public void execute(WebScriptRequest webScriptRequest, WebScriptResponse webScriptResponse) throws IOException {
public void execute(WebScriptRequest request, WebScriptResponse response) throws IOException {

if (!prometheusAvailableOnClasspath()) {
throw new WebScriptException(404, "micrometer-prometheus-registry not available on the classpath");
setStatusCodeAndWriteResponse(response, HttpStatus.NOT_FOUND,
"micrometer-prometheus-registry not available on the classpath");
return;
}

if (!PrometheusRegistryUtil.isOrContainsPrometheusRegistry(meterRegistry)) {
throw new WebScriptException(404, "The global MeterRegistry doesn't contain a PrometheusMeterRegistry");
setStatusCodeAndWriteResponse(response, HttpStatus.NOT_FOUND,
"The global MeterRegistry doesn't contain a PrometheusMeterRegistry");
return;
}

if (!semaphore.tryAcquire()) {
throw new WebScriptException(503, "Max number of active requests (" + maxRequests + ") reached");
final String message = "Max number of active requests (" + maxRequests + ") reached";
logMaxRequestsViolation(message);
setStatusCodeAndWriteResponse(response, HttpStatus.SERVICE_UNAVAILABLE, message);
return;
}

try {
executeInternal(webScriptResponse);
executeInternal(response);
} finally {
semaphore.release();
}
}

private static boolean prometheusAvailableOnClasspath() {
return ClassUtils.isPresent("io.micrometer.prometheus.PrometheusMeterRegistry",
PrometheusWebScript.class.getClassLoader());
}

private void executeInternal(WebScriptResponse response) throws IOException {
response.setStatus(200);
writeTextToResponse(PrometheusRegistryUtil.extractPrometheusScrapeData(meterRegistry), response);
setStatusCodeAndWriteResponse(response, HttpStatus.OK,
PrometheusRegistryUtil.extractPrometheusScrapeData(meterRegistry));
}

private void setStatusCodeAndWriteResponse(WebScriptResponse response, HttpStatus status, String message)
throws IOException {
response.setStatus(status.value());
writeResponse(response, message);
}

private void writeTextToResponse(final String text, final WebScriptResponse response) throws IOException {
private void writeResponse(final WebScriptResponse response, final String text) throws IOException {
response.setContentType("text/plain");
response.setContentEncoding("UTF-8");
response.setHeader("length", String.valueOf(text.getBytes().length));
response.getOutputStream().write(text.getBytes());
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
response.setHeader("length", String.valueOf(bytes.length));
response.getOutputStream().write(bytes);
}

private void logMaxRequestsViolation(String message) {
if (uptimeAtLeast(suppressMaxRequestsFailuresDuringUptimeMinutes)) {
logger.error(message);
} else {
logger.debug(message);
}
}

private boolean uptimeAtLeast(int minutes) {
return runtimeMXBean.getUptime() > (1000L * 60 * minutes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ alfred.telemetry.export.jmx.enabled=true
## Micrometer Prometheus registry configuration
alfred.telemetry.export.prometheus.enabled=true
alfred.telemetry.export.prometheus.max-requests=1
alfred.telemetry.export.prometheus.suppress-max-request-failures-during-uptime-minutes=5


#### Alfresco integration configuration ####
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
class="eu.xenit.alfred.telemetry.registry.prometheus.PrometheusConfig">
<property name="enabled" value="${alfred.telemetry.export.prometheus.enabled}"/>
<property name="maxRequests" value="${alfred.telemetry.export.prometheus.max-requests}"/>
<property name="suppressMaxRequestsFailuresDuringUptimeMinutes"
value="${alfred.telemetry.export.prometheus.suppress-max-request-failures-during-uptime-minutes}"/>
</bean>

<bean id="alfred-telemetry.PrometheusRegistryFactoryWrapper"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,7 @@
<constructor-arg ref="global-properties"/>
<constructor-arg ref="meterRegistry"/>
<constructor-arg ref="schedulerFactory"/>
</bean>

<bean id="alfred-telemetry.solrMetrics" class="eu.xenit.alfred.telemetry.binder.solr.tracking.SolrTrackingMetrics">
<constructor-arg ref="search.solrTrackingComponent"/>
<constructor-arg ref="transactionService"/>
<constructor-arg ref="meterRegistry"/>
<constructor-arg value="${alfred.telemetry.binder.solr.tracking.enabled}"/>
</bean>

</beans>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package eu.xenit.alfred.telemetry.binder.solr;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import io.micrometer.core.instrument.MeterRegistry;
import java.util.Properties;
import org.alfresco.service.transaction.TransactionService;
import org.junit.jupiter.api.Test;
import org.quartz.Scheduler;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;

class SolrMetricsBeanPostProcessorTest {

@Test
public void testAllBeansCreated() {
SolrMetricsBeanPostProcessor solrMetricsBeanPostProcessor = getSolrMetricsBeanPostProcessor(true, true);
BeanDefinitionRegistry beanDefinitionRegistry = mock(BeanDefinitionRegistry.class);
solrMetricsBeanPostProcessor.postProcessBeanDefinitionRegistry(beanDefinitionRegistry);
verify(beanDefinitionRegistry)
.registerBeanDefinition(eq(SolrMetricsBeanPostProcessor.SOLR_TRACKING_METRICS_BEAN_ID), any());
verify(beanDefinitionRegistry)
.registerBeanDefinition(eq(SolrMetricsBeanPostProcessor.SOLR_SHARDING_METRICS_BEAN_ID), any());
}

private SolrMetricsBeanPostProcessor getSolrMetricsBeanPostProcessor(boolean tracking, boolean sharding) {
Properties properties = new Properties();
properties.setProperty(SolrMetricsBeanPostProcessor.SOLR_TRACKING_METRICS_ENABLED_PROPERTY,
Boolean.toString(tracking));
properties.setProperty(SolrMetricsBeanPostProcessor.SOLR_SHARDING_METRICS_ENABLED_PROPERTY,
Boolean.toString(sharding));
SolrMetricsBeanPostProcessor solrMetricsBeanPostProcessor = new SolrMetricsBeanPostProcessor(properties,
mock(MeterRegistry.class), mock(Scheduler.class), mock(TransactionService.class));
return solrMetricsBeanPostProcessor;
}

@Test
public void noBeansCreated() {
BeanDefinitionRegistry beanDefinitionRegistry = mock(BeanDefinitionRegistry.class);
getSolrMetricsBeanPostProcessor(false, false).postProcessBeanDefinitionRegistry(beanDefinitionRegistry);
verify(beanDefinitionRegistry, never())
.registerBeanDefinition(eq(SolrMetricsBeanPostProcessor.SOLR_TRACKING_METRICS_BEAN_ID), any());
verify(beanDefinitionRegistry, never())
.registerBeanDefinition(eq(SolrMetricsBeanPostProcessor.SOLR_SHARDING_METRICS_BEAN_ID), any());
}

@Test
public void checkAlfresco7BeanCreated() {
BeanDefinitionRegistry beanDefinitionRegistry = mock(BeanDefinitionRegistry.class);
when(beanDefinitionRegistry
.getBeanDefinition(eq(SolrMetricsBeanPostProcessor.SEARCH_TRACKING_COMPONENT_BEAN_ID_BEFORE_7)))
.thenThrow(
NoSuchBeanDefinitionException.class);
getSolrMetricsBeanPostProcessor(true, false).postProcessBeanDefinitionRegistry(beanDefinitionRegistry);
verify(beanDefinitionRegistry)
.registerBeanDefinition(eq(SolrMetricsBeanPostProcessor.SOLR_TRACKING_METRICS_BEAN_ID), any());
}

}
Loading

0 comments on commit 66cd31f

Please sign in to comment.