/**
* Copyright 2016 LinkedIn Corp. 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.
*/
package com.github.ambry.rest;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link AsyncRequestResponseHandler} specific implementation of {@link RestRequestHandlerFactory} and
* {@link RestResponseHandlerFactory}.
* <p/>
* Sets up all the supporting cast required for {@link AsyncRequestResponseHandler}. Maintains a single instance of
* {@link AsyncRequestResponseHandler} and returns the same instance on any call to {@link #getRestRequestHandler()} or
* {@link #getRestResponseHandler()}.
*/
public class AsyncRequestResponseHandlerFactory implements RestRequestHandlerFactory, RestResponseHandlerFactory {
private static final ReentrantLock lock = new ReentrantLock();
private static AsyncRequestResponseHandler instance;
private static RequestResponseHandlerMetrics requestResponseHandlerMetrics;
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* Constructor for {@link RestResponseHandlerFactory}.
* @param handlerCount the number of response scaling units required.
* @param metricRegistry the {@link MetricRegistry} instance that should be used for metrics.
* @throws IllegalArgumentException if {@code handlerCount} <= 0 or if {@code metricRegistry} is null.
*/
public AsyncRequestResponseHandlerFactory(Integer handlerCount, MetricRegistry metricRegistry) {
if (metricRegistry == null) {
throw new IllegalArgumentException("MetricRegistry instance provided is null");
} else if (handlerCount <= 0) {
throw new IllegalArgumentException("Response handler scaling unit count has to be > 0. Is " + handlerCount);
}
buildInstance(metricRegistry);
logger.trace("Instantiated AsyncRequestResponseHandlerFactory as RestResponseHandler");
}
/**
* Constructor for {@link RestRequestHandlerFactory}.
* @param handlerCount the number of request scaling units required.
* @param metricRegistry the {@link MetricRegistry} instance that should be used for metrics.
* @param blobStorageService the {@link BlobStorageService} to use for handling requests.
* @throws IllegalArgumentException if {@code handlerCount} <= 0 or if {@code metricRegistry} or
* {@code blobStorageService} is null.
*/
public AsyncRequestResponseHandlerFactory(Integer handlerCount, MetricRegistry metricRegistry,
BlobStorageService blobStorageService) {
if (metricRegistry == null || blobStorageService == null) {
throw new IllegalArgumentException("One or more arguments received is null");
} else if (handlerCount <= 0) {
throw new IllegalArgumentException("Request handler scaling unit count has to be > 0. Is " + handlerCount);
} else {
buildInstance(metricRegistry);
instance.setupRequestHandling(handlerCount, blobStorageService);
}
logger.trace("Instantiated AsyncRequestResponseHandlerFactory as RestRequestHandler");
}
/**
* Returns an instance of {@link AsyncRequestResponseHandler}.
* @return an instance of {@link AsyncRequestResponseHandler}.
*/
@Override
public RestRequestHandler getRestRequestHandler() {
return instance;
}
/**
* Returns an instance of {@link AsyncRequestResponseHandler}.
* @return an instance of {@link AsyncRequestResponseHandler}.
*/
@Override
public RestResponseHandler getRestResponseHandler() {
return instance;
}
/**
* Returns the singleton {@link AsyncRequestResponseHandler} instance being maintained. Creates it if it hasn't been
* created already.
* @param metricRegistry the {@link MetricRegistry} instance that should be used for metrics.
*/
private static void buildInstance(MetricRegistry metricRegistry) {
lock.lock();
try {
if (instance == null) {
AsyncRequestResponseHandlerFactory.requestResponseHandlerMetrics =
new RequestResponseHandlerMetrics(metricRegistry);
instance = new AsyncRequestResponseHandler(requestResponseHandlerMetrics);
}
// check if same instance of MetricRegistry - otherwise it is a problem.
if (AsyncRequestResponseHandlerFactory.requestResponseHandlerMetrics.metricRegistry != metricRegistry) {
throw new IllegalStateException("MetricRegistry instance provided during construction of "
+ "AsyncRequestResponseHandler differs from the one currently received");
}
} finally {
lock.unlock();
}
}
}
/**
* {@link AsyncRequestResponseHandler} specific metrics tracking.
*/
class RequestResponseHandlerMetrics {
final MetricRegistry metricRegistry;
private final AtomicInteger asyncRequestWorkerIndex = new AtomicInteger(0);
// Rates
// AsyncRequestWorker
public final Meter requestArrivalRate;
public final Meter requestDequeuingRate;
public final Meter requestQueuingRate;
// AsyncResponseHandler
public final Meter responseArrivalRate;
public final Meter responseCompletionRate;
// Latencies
// AsyncRequestWorker
public final Histogram requestPreProcessingTimeInMs;
// AsyncResponseHandler
public final Histogram responseCallbackProcessingTimeInMs;
public final Histogram responseCallbackWaitTimeInMs;
public final Histogram responsePreProcessingTimeInMs;
// AsyncRequestResponseHandler
public final Histogram requestWorkerSelectionTimeInMs;
// Errors
// AsyncRequestWorker
public final Counter requestProcessingError;
public final Counter requestQueueAddError;
public final Counter unknownRestMethodError;
// AsyncResponseHandler
public final Counter resourceReleaseError;
public final Counter responseAlreadyInFlightError;
public final Counter responseCompleteTasksError;
// AsyncRequestResponseHandler
public final Counter requestResponseHandlerShutdownError;
public final Counter requestResponseHandlerUnavailableError;
// Others
// AsyncResponseHandler
public final Counter responseExceptionCount;
public final Histogram responseHandlerCloseTimeInMs;
// AsyncRequestResponseHandler
public final Histogram requestWorkerShutdownTimeInMs;
public final Histogram requestWorkerStartTimeInMs;
public final Histogram requestResponseHandlerShutdownTimeInMs;
public final Histogram requestResponseHandlerStartTimeInMs;
public final Counter residualRequestQueueSize;
public final Counter residualResponseSetSize;
/**
* Creates an instance of RequestResponseHandlerMetrics using the given {@code metricRegistry}.
* @param metricRegistry the {@link MetricRegistry} to use for the metrics.
*/
public RequestResponseHandlerMetrics(MetricRegistry metricRegistry) {
this.metricRegistry = metricRegistry;
// Rates
// AsyncRequestWorker
requestArrivalRate = metricRegistry.meter(MetricRegistry.name(AsyncRequestWorker.class, "RequestArrivalRate"));
requestDequeuingRate = metricRegistry.meter(MetricRegistry.name(AsyncRequestWorker.class, "RequestDequeuingRate"));
requestQueuingRate = metricRegistry.meter(MetricRegistry.name(AsyncRequestWorker.class, "RequestQueuingRate"));
// AsyncResponseHandler
responseArrivalRate = metricRegistry.meter(MetricRegistry.name(AsyncResponseHandler.class, "ResponseArrivalRate"));
responseCompletionRate =
metricRegistry.meter(MetricRegistry.name(AsyncResponseHandler.class, "ResponseCompletionRate"));
// Latencies
// AsyncRequestWorker
requestPreProcessingTimeInMs =
metricRegistry.histogram(MetricRegistry.name(AsyncRequestWorker.class, "RequestPreProcessingTimeInMs"));
// AsyncResponseHandler
responseCallbackProcessingTimeInMs =
metricRegistry.histogram(MetricRegistry.name(AsyncResponseHandler.class, "ResponseCallbackProcessingTimeInMs"));
responseCallbackWaitTimeInMs =
metricRegistry.histogram(MetricRegistry.name(AsyncResponseHandler.class, "ResponseCallbackWaitTimeInMs"));
responsePreProcessingTimeInMs =
metricRegistry.histogram(MetricRegistry.name(AsyncResponseHandler.class, "ResponsePreProcessingTimeInMs"));
// AsyncRequestResponseHandler
requestWorkerSelectionTimeInMs = metricRegistry.histogram(
MetricRegistry.name(AsyncRequestResponseHandler.class, "RequestWorkerSelectionTimeInMs"));
// Errors
// AsyncRequestWorker
requestProcessingError =
metricRegistry.counter(MetricRegistry.name(AsyncRequestWorker.class, "RequestProcessingError"));
requestQueueAddError =
metricRegistry.counter(MetricRegistry.name(AsyncRequestWorker.class, "RequestQueueAddError"));
unknownRestMethodError =
metricRegistry.counter(MetricRegistry.name(AsyncRequestWorker.class, "UnknownRestMethodError"));
// AsyncResponseHandler
resourceReleaseError =
metricRegistry.counter(MetricRegistry.name(AsyncResponseHandler.class, "ResourceReleaseError"));
responseAlreadyInFlightError =
metricRegistry.counter(MetricRegistry.name(AsyncResponseHandler.class, "ResponseAlreadyInFlightError"));
responseCompleteTasksError =
metricRegistry.counter(MetricRegistry.name(AsyncResponseHandler.class, "ResponseCompleteTasksError"));
// AsyncRequestResponseHandler
requestResponseHandlerShutdownError =
metricRegistry.counter(MetricRegistry.name(AsyncRequestResponseHandler.class, "ShutdownError"));
requestResponseHandlerUnavailableError =
metricRegistry.counter(MetricRegistry.name(AsyncRequestResponseHandler.class, "UnavailableError"));
// Others
// AsyncResponseHandler
responseExceptionCount =
metricRegistry.counter(MetricRegistry.name(AsyncResponseHandler.class, "ResponseExceptionCount"));
responseHandlerCloseTimeInMs =
metricRegistry.histogram(MetricRegistry.name(AsyncResponseHandler.class, "CloseTimeInMs"));
// AsyncRequestResponseHandler
requestWorkerShutdownTimeInMs = metricRegistry.histogram(
MetricRegistry.name(AsyncRequestResponseHandler.class, "RequestWorkerShutdownTimeInMs"));
requestWorkerStartTimeInMs =
metricRegistry.histogram(MetricRegistry.name(AsyncRequestResponseHandler.class, "RequestWorkerStartTimeInMs"));
requestResponseHandlerShutdownTimeInMs =
metricRegistry.histogram(MetricRegistry.name(AsyncRequestResponseHandler.class, "ShutdownTimeInMs"));
requestResponseHandlerStartTimeInMs =
metricRegistry.histogram(MetricRegistry.name(AsyncRequestResponseHandler.class, "StartTimeInMs"));
// AsyncRequestWorker
residualRequestQueueSize =
metricRegistry.counter(MetricRegistry.name(AsyncRequestWorker.class, "ResidualRequestQueueSize"));
residualResponseSetSize =
metricRegistry.counter(MetricRegistry.name(AsyncRequestWorker.class, "ResidualResponseSetSize"));
}
/**
* Registers the {@code asyncRequestWorker} so that its metrics (request queue size) can be tracked.
* @param asyncRequestWorker the {@link AsyncRequestWorker} whose metrics need to be tracked.
*/
public void registerRequestWorker(final AsyncRequestWorker asyncRequestWorker) {
int pos = asyncRequestWorkerIndex.getAndIncrement();
Gauge<Integer> gauge = new Gauge<Integer>() {
@Override
public Integer getValue() {
return asyncRequestWorker.getRequestQueueSize();
}
};
metricRegistry.register(MetricRegistry.name(AsyncRequestWorker.class, pos + "-RequestQueueSize"), gauge);
}
/**
* Periodically reports key metrics of the {@code asyncRequestResponseHandler}.
* @param asyncRequestResponseHandler the {@link AsyncRequestResponseHandler} whose key metrics have to be tracked.
*/
public void trackAsyncRequestResponseHandler(final AsyncRequestResponseHandler asyncRequestResponseHandler) {
Gauge<Integer> totalRequestQueueSize = new Gauge<Integer>() {
@Override
public Integer getValue() {
return asyncRequestResponseHandler.getRequestQueueSize();
}
};
metricRegistry.register(MetricRegistry.name(AsyncRequestResponseHandler.class, "TotalRequestQueueSize"),
totalRequestQueueSize);
Gauge<Integer> totalResponseSetSize = new Gauge<Integer>() {
@Override
public Integer getValue() {
return asyncRequestResponseHandler.getResponseSetSize();
}
};
metricRegistry.register(MetricRegistry.name(AsyncRequestResponseHandler.class, "TotalResponseSetSize"),
totalResponseSetSize);
Gauge<Integer> asyncHandlerWorkersAlive = new Gauge<Integer>() {
@Override
public Integer getValue() {
return asyncRequestResponseHandler.getWorkersAlive();
}
};
metricRegistry.register(MetricRegistry.name(AsyncRequestResponseHandler.class, "AsyncHandlerWorkersAlive"),
asyncHandlerWorkersAlive);
}
}