/* * Copyright (c) 2011-2015 Spotify AB * * 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. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spotify.google.cloud.pubsub.client.integration; import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.services.pubsub.PubsubScopes; import com.spotify.google.cloud.pubsub.client.Message; import com.spotify.google.cloud.pubsub.client.Publisher; import com.spotify.google.cloud.pubsub.client.Pubsub; import com.spotify.google.cloud.pubsub.client.ReceivedMessage; import com.spotify.logging.LoggingConfigurator; import java.io.FileInputStream; import java.io.IOException; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ThreadLocalRandom; import java.util.function.Supplier; import java.util.stream.IntStream; import java.util.zip.Deflater; import static com.spotify.google.cloud.pubsub.client.integration.Util.nonGcmCiphers; import static com.spotify.logging.LoggingConfigurator.Level.WARN; import static java.util.stream.Collectors.toList; public class EndToEndBenchmark { private static final int PUBLISHER_CONCURRENCY = 128; private static final int PULLER_CONCURRENCY = 128; private static final int MESSAGE_SIZE = 512; public static void main(final String... args) throws IOException, ExecutionException, InterruptedException { final String project = Util.defaultProject(); GoogleCredential credential; // Use credentials from file if available try { credential = GoogleCredential .fromStream(new FileInputStream("credentials.json")) .createScoped(PubsubScopes.all()); } catch (IOException e) { credential = GoogleCredential.getApplicationDefault() .createScoped(PubsubScopes.all()); } final Pubsub pubsub = Pubsub.builder() .credential(credential) .compressionLevel(Deflater.BEST_SPEED) .enabledCipherSuites(nonGcmCiphers()) .build(); final Publisher publisher = Publisher.builder() .pubsub(pubsub) .concurrency(PUBLISHER_CONCURRENCY) .project(project) .build(); LoggingConfigurator.configureDefaults("benchmark", WARN); final String topic = "test-" + Long.toHexString(ThreadLocalRandom.current().nextLong()); final String subscription = "test-" + Long.toHexString(ThreadLocalRandom.current().nextLong()); pubsub.createTopic(project, topic).get(); pubsub.createSubscription(project, subscription, topic).get(); final List<String> payloads = IntStream.range(0, 1024) .mapToObj(i -> { final StringBuilder s = new StringBuilder(); while (s.length() < MESSAGE_SIZE) { s.append(ThreadLocalRandom.current().nextInt()); } return Message.encode(s.toString()); }) .collect(toList()); final int payloadIxMask = 1024 - 1; final Supplier<Message> generator = () -> Message.builder() .data(payloads.get(ThreadLocalRandom.current().nextInt() & payloadIxMask)) .putAttribute("ts", Long.toHexString(System.nanoTime())) .build(); final ProgressMeter meter = new ProgressMeter(); final ProgressMeter.Metric publishes = meter.group("operations").metric("publishes", "messages"); final ProgressMeter.Metric receives = meter.group("operations").metric("receives", "messages"); for (int i = 0; i < 100000; i++) { publish(publisher, generator, topic, publishes); } // Pull concurrently and (asynchronously) publish a new message for every message received for (int i = 0; i < PULLER_CONCURRENCY; i++) { pull(project, pubsub, subscription, receives, () -> publish(publisher, generator, topic, publishes)); } } private static void publish(final Publisher publisher, final Supplier<Message> generator, final String topic, final ProgressMeter.Metric publishes) { final Message message = generator.get(); final CompletableFuture<String> future = publisher.publish(topic, message); final long start = System.nanoTime(); future.whenComplete((s, ex) -> { if (ex != null) { ex.printStackTrace(); return; } final long end = System.nanoTime(); final long latency = end - start; publishes.inc(latency); }); } private static void pull(final String project, final Pubsub pubsub, final String subscription, final ProgressMeter.Metric receives, final Runnable callback) { pubsub.pull(project, subscription, false, 1000) .whenComplete((messages, ex) -> { if (ex != null) { ex.printStackTrace(); return; } // Immediately kick off another pull pull(project, pubsub, subscription, receives, callback); // Ack received messages final String[] ackIds = messages.stream().map(ReceivedMessage::ackId).toArray(String[]::new); pubsub.acknowledge(project, subscription, ackIds); // Account for and call callback for each received message for (final ReceivedMessage message : messages) { final String tsHex = message.message().attributes().get("ts"); final long tsNanos = Long.valueOf(tsHex, 16); final long latencyNanos = System.nanoTime() - tsNanos; receives.inc(latencyNanos); callback.run(); } }); } }