/** * 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.tools.perf.rest; import com.github.ambry.messageformat.BlobInfo; import com.github.ambry.messageformat.BlobProperties; import com.github.ambry.router.Callback; import com.github.ambry.router.FutureResult; import com.github.ambry.router.GetBlobOptions; import com.github.ambry.router.GetBlobResult; import com.github.ambry.router.ReadableStreamChannel; import com.github.ambry.router.Router; import com.github.ambry.router.RouterErrorCode; import com.github.ambry.router.RouterException; import java.util.Random; import java.util.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Perf specific implementation of {@link Router}. * * Get: Returns pre-populated repetitive data based on total size and chunk size configured. * GetBlobInfo: Returns pre-populated data. * PutBlob: Discards all bytes received. * DeleteBlob: No op. */ class PerfRouter implements Router { protected final static String BLOB_ID = "perf-blob-id"; private static final String CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final Random random = new Random(); private static RouterException ROUTER_CLOSED_EXCEPTION = new RouterException("Cannot accept operation because Router is closed", RouterErrorCode.RouterClosed); private final PerfRouterMetrics perfRouterMetrics; private final BlobProperties blobProperties; private final byte[] usermetadata; private final byte[] chunk; private volatile boolean routerOpen = true; private final Logger logger = LoggerFactory.getLogger(getClass()); /** * Creates an instance of PerfRouter with configuration as specified in {@code perfRouterConfig}. * @param perfConfig the {@link PerfConfig} to use that determines behavior. * @param perfRouterMetrics the {@link PerfRouterMetrics} instance to use to record metrics. */ public PerfRouter(PerfConfig perfConfig, PerfRouterMetrics perfRouterMetrics) { this.perfRouterMetrics = perfRouterMetrics; blobProperties = new BlobProperties(perfConfig.perfBlobSize, "PerfRouter"); usermetadata = getRandomString(perfConfig.perfUserMetadataSize).getBytes(); chunk = new byte[perfConfig.perfRouterChunkSize]; random.nextBytes(chunk); logger.trace("Instantiated PerfRouter"); } /** * Returns a stream of repeating data up to a pre-set size. {@code blobId} is ignored. * @param blobId The ID of the blob for which blob data is requested. * @param options The options associated with the request. This cannot be null. * @return a {@link Future} that will eventually contain a {@link GetBlobResult}. */ @Override public Future<GetBlobResult> getBlob(String blobId, GetBlobOptions options) { return getBlob(blobId, options, null); } @Override public Future<GetBlobResult> getBlob(String blobId, GetBlobOptions options, Callback<GetBlobResult> callback) { logger.trace("Received getBlob call"); FutureResult<GetBlobResult> futureResult = new FutureResult<>(); if (!routerOpen) { completeOperation(futureResult, callback, null, ROUTER_CLOSED_EXCEPTION); } else { GetBlobResult result = null; switch (options.getOperationType()) { case All: result = new GetBlobResult(new BlobInfo(blobProperties, usermetadata), new PerfRSC(chunk, blobProperties.getBlobSize())); break; case Data: result = new GetBlobResult(null, new PerfRSC(chunk, blobProperties.getBlobSize())); break; case BlobInfo: result = new GetBlobResult(new BlobInfo(blobProperties, usermetadata), null); break; } completeOperation(futureResult, callback, result, null); } return futureResult; } /** * Consumes the data in {@code channel} and simply throws it away. {@code blobProperties} and {@code usermetadata} are * ignored. * @param blobProperties The properties of the blob. * @param usermetadata Optional user metadata about the blob. This can be null. * @param channel The {@link ReadableStreamChannel} that contains the content of the blob. * @return a {@link Future} that will contain a (dummy) blob id. */ @Override public Future<String> putBlob(BlobProperties blobProperties, byte[] usermetadata, ReadableStreamChannel channel) { return putBlob(blobProperties, usermetadata, channel, null); } /** * Consumes the data in {@code channel} and simply throws it away. {@code blobProperties} and {@code usermetadata} are * ignored. * @param blobProperties The properties of the blob. * @param usermetadata Optional user metadata about the blob. This can be null. * @param channel The {@link ReadableStreamChannel} that contains the content of the blob. * @param callback the {@link Callback} to invoke on operation completion. * @return a {@link Future} that will contain a (dummy) blob id. */ @Override public Future<String> putBlob(BlobProperties blobProperties, byte[] usermetadata, final ReadableStreamChannel channel, final Callback<String> callback) { logger.trace("Received putBlob call"); final FutureResult<String> futureResult = new FutureResult<String>(); if (!routerOpen) { completeOperation(futureResult, callback, null, ROUTER_CLOSED_EXCEPTION); } else { final long putConsumeStartTime = System.currentTimeMillis(); channel.readInto(new NoOpAWC(), new Callback<Long>() { @Override public void onCompletion(Long result, Exception exception) { String operationResult = null; if (exception == null && (result == null || (channel.getSize() != -1 && result != channel.getSize()))) { exception = new IllegalStateException("The content was not completely read"); } else if (exception == null) { logger.debug("Total bytes read - {}", result); perfRouterMetrics.putSizeInBytes.update(result); operationResult = PerfRouter.BLOB_ID; } perfRouterMetrics.putContentConsumeTimeInMs.update(System.currentTimeMillis() - putConsumeStartTime); completeOperation(futureResult, callback, operationResult, exception); } }); } return futureResult; } /** * Does nothing. Simply indicates success immediately. * @param blobId (ignored). * @param serviceId (ignored). * @return a {@link FutureResult} that will eventually contain the result of the operation. */ @Override public Future<Void> deleteBlob(String blobId, String serviceId) { return deleteBlob(blobId, serviceId, null); } /** * Does nothing. Simply indicates success immediately. * @param blobId (ignored). * @param serviceId (ignored). * @param callback the {@link Callback} to invoke on operation completion. * @return a {@link FutureResult} that will eventually contain the result of the operation. */ @Override public Future<Void> deleteBlob(String blobId, String serviceId, Callback<Void> callback) { logger.trace("Received deleteBlob call"); FutureResult<Void> futureResult = new FutureResult<Void>(); if (!routerOpen) { completeOperation(futureResult, callback, null, ROUTER_CLOSED_EXCEPTION); } else { completeOperation(futureResult, callback, null, null); } return futureResult; } @Override public void close() { routerOpen = false; } /** * Completes a router operation by invoking the {@code callback} and setting the {@code futureResult} with * {@code operationResult} (if any) and {@code exception} (if any). * @param futureResult the {@link FutureResult} that needs to be set. * @param callback that {@link Callback} that needs to be invoked. Can be null. * @param operationResult the result of the operation (if any). * @param exception {@link Exception} encountered while performing the operation (if any). * @param <T> the type of {@code futureResult}, {@code callback} and {@code operationResult}. */ private <T> void completeOperation(FutureResult<T> futureResult, Callback<T> callback, T operationResult, Exception exception) { RuntimeException runtimeException = null; if (exception != null) { runtimeException = new RuntimeException(exception); } futureResult.done(operationResult, runtimeException); if (callback != null) { callback.onCompletion(operationResult, exception); } } /** * Gets a random string of size {@code length}. * @param length the size of the required random string. * @return a random string of size {@code length}. */ private String getRandomString(int length) { StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; i++) { sb.append(CHARACTERS.charAt(random.nextInt(CHARACTERS.length()))); } return sb.toString(); } }