Java tutorial
package com.cloudhopper.smpp.demo; /* * #%L * ch-smpp * %% * Copyright (C) 2009 - 2012 Cloudhopper by Twitter * %% * 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. * #L% */ import com.cloudhopper.commons.charset.CharsetUtil; import com.cloudhopper.commons.util.DecimalUtil; import com.cloudhopper.smpp.PduAsyncResponse; import com.cloudhopper.smpp.SmppBindType; import com.cloudhopper.smpp.SmppSession; import com.cloudhopper.smpp.SmppSessionConfiguration; import com.cloudhopper.smpp.impl.DefaultSmppClient; import com.cloudhopper.smpp.impl.DefaultSmppSessionHandler; import com.cloudhopper.smpp.pdu.SubmitSm; import com.cloudhopper.smpp.type.Address; import io.netty.channel.nio.NioEventLoopGroup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** * * @author joelauer (twitter: @jjlauer or <a href="http://twitter.com/jjlauer" target=window>http://twitter.com/jjlauer</a>) */ public class PerformanceClientMain { private static final Logger logger = LoggerFactory.getLogger(PerformanceClientMain.class); // // performance testing options (just for this sample) // // total number of sessions (conns) to create static public final int SESSION_COUNT = 10; // size of window per session static public final int WINDOW_SIZE = 50; // total number of submit to send total across all sessions static public final int SUBMIT_TO_SEND = 2000; // total number of submit sent static public final AtomicInteger SUBMIT_SENT = new AtomicInteger(0); static public void main(String[] args) throws Exception { // // setup 3 things required for any session we plan on creating // // create and assign the NioEventLoopGroup instances to handle event processing, // such as accepting new connections, receiving data, writing data, and so on. NioEventLoopGroup group = new NioEventLoopGroup(1); // to enable automatic expiration of requests, a second scheduled executor // is required which is what a monitor task will be executed with - this // is probably a thread pool that can be shared with between all client bootstraps ScheduledThreadPoolExecutor monitorExecutor = (ScheduledThreadPoolExecutor) Executors .newScheduledThreadPool(1, new ThreadFactory() { private AtomicInteger sequence = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setName("SmppClientSessionWindowMonitorPool-" + sequence.getAndIncrement()); return t; } }); // a single instance of a client bootstrap can technically be shared // between any sessions that are created (a session can go to any different // number of SMSCs) - each session created under // a client bootstrap will use the executor and monitorExecutor set // in its constructor - just be *very* careful with the "expectedSessions" // value to make sure it matches the actual number of total concurrent // open sessions you plan on handling - the underlying netty library // used for NIO sockets essentially uses this value as the max number of // threads it will ever use, despite the "max pool size", etc. set on // the executor passed in here DefaultSmppClient clientBootstrap = new DefaultSmppClient(group, monitorExecutor); // same configuration for each client runner SmppSessionConfiguration config = new SmppSessionConfiguration(); config.setWindowSize(WINDOW_SIZE); config.setName("Tester.Session.0"); config.setType(SmppBindType.TRANSCEIVER); config.setHost("127.0.0.1"); config.setPort(2776); config.setConnectTimeout(10000); config.setSystemId("1234567890"); config.setPassword("password"); config.getLoggingOptions().setLogBytes(false); // to enable monitoring (request expiration) config.setRequestExpiryTimeout(30000); config.setWindowMonitorInterval(15000); config.setCountersEnabled(true); // various latches used to signal when things are ready CountDownLatch allSessionsBoundSignal = new CountDownLatch(SESSION_COUNT); CountDownLatch startSendingSignal = new CountDownLatch(1); // create all session runners and executors to run them ThreadPoolExecutor taskExecutor = (ThreadPoolExecutor) Executors.newCachedThreadPool(); ClientSessionTask[] tasks = new ClientSessionTask[SESSION_COUNT]; for (int i = 0; i < SESSION_COUNT; i++) { tasks[i] = new ClientSessionTask(allSessionsBoundSignal, startSendingSignal, clientBootstrap, config); taskExecutor.submit(tasks[i]); } // wait for all sessions to bind logger.info("Waiting up to 7 seconds for all sessions to bind..."); if (!allSessionsBoundSignal.await(7000, TimeUnit.MILLISECONDS)) { throw new Exception("One or more sessions were unable to bind, cancelling test"); } logger.info("Sending signal to start test..."); long startTimeMillis = System.currentTimeMillis(); startSendingSignal.countDown(); // wait for all tasks to finish taskExecutor.shutdown(); taskExecutor.awaitTermination(3, TimeUnit.DAYS); long stopTimeMillis = System.currentTimeMillis(); // did everything succeed? int actualSubmitSent = 0; int sessionFailures = 0; for (int i = 0; i < SESSION_COUNT; i++) { if (tasks[i].getCause() != null) { sessionFailures++; logger.error("Task #" + i + " failed with exception: " + tasks[i].getCause()); } else { actualSubmitSent += tasks[i].getSubmitRequestSent(); } } logger.info("Performance client finished:"); logger.info(" Sessions: " + SESSION_COUNT); logger.info(" Window Size: " + WINDOW_SIZE); logger.info("Sessions Failed: " + sessionFailures); logger.info(" Time: " + (stopTimeMillis - startTimeMillis) + " ms"); logger.info(" Target Submit: " + SUBMIT_TO_SEND); logger.info(" Actual Submit: " + actualSubmitSent); double throughput = (double) actualSubmitSent / ((double) (stopTimeMillis - startTimeMillis) / (double) 1000); logger.info(" Throughput: " + DecimalUtil.toString(throughput, 3) + " per sec"); for (int i = 0; i < SESSION_COUNT; i++) { if (tasks[i].session != null && tasks[i].session.hasCounters()) { logger.info(" Session " + i + ": submitSM {}", tasks[i].session.getCounters().getTxSubmitSM()); } } // this is required to not causing server to hang from non-daemon threads // this also makes sure all open Channels are closed to I *think* logger.info("Shutting down client bootstrap and executors..."); clientBootstrap.destroy(); monitorExecutor.shutdownNow(); logger.info("Done. Exiting"); } public static class ClientSessionTask implements Runnable { private SmppSession session; private CountDownLatch allSessionsBoundSignal; private CountDownLatch startSendingSignal; private DefaultSmppClient clientBootstrap; private SmppSessionConfiguration config; private int submitRequestSent; private int submitResponseReceived; private AtomicBoolean sendingDone; private Exception cause; public ClientSessionTask(CountDownLatch allSessionsBoundSignal, CountDownLatch startSendingSignal, DefaultSmppClient clientBootstrap, SmppSessionConfiguration config) { this.allSessionsBoundSignal = allSessionsBoundSignal; this.startSendingSignal = startSendingSignal; this.clientBootstrap = clientBootstrap; this.config = config; this.submitRequestSent = 0; this.submitResponseReceived = 0; this.sendingDone = new AtomicBoolean(false); } public Exception getCause() { return this.cause; } public int getSubmitRequestSent() { return this.submitRequestSent; } @Override public void run() { // a countdownlatch will be used to eventually wait for all responses // to be received by this thread since we don't want to exit too early CountDownLatch allSubmitResponseReceivedSignal = new CountDownLatch(1); DefaultSmppSessionHandler sessionHandler = new ClientSmppSessionHandler( allSubmitResponseReceivedSignal); String text160 = "\u20AC Lorem [ipsum] dolor sit amet, consectetur adipiscing elit. Proin feugiat, leo id commodo tincidunt, nibh diam ornare est, vitae accumsan risus lacus sed sem metus."; byte[] textBytes = CharsetUtil.encode(text160, CharsetUtil.CHARSET_GSM); try { // create session a session by having the bootstrap connect a // socket, send the bind request, and wait for a bind response session = clientBootstrap.bind(config, sessionHandler); // don't start sending until signalled allSessionsBoundSignal.countDown(); startSendingSignal.await(); // all threads compete for processing while (SUBMIT_SENT.getAndIncrement() < SUBMIT_TO_SEND) { SubmitSm submit = new SubmitSm(); submit.setSourceAddress(new Address((byte) 0x03, (byte) 0x00, "40404")); submit.setDestAddress(new Address((byte) 0x01, (byte) 0x01, "44555519205")); submit.setShortMessage(textBytes); // asynchronous send this.submitRequestSent++; sendingDone.set(true); session.sendRequestPdu(submit, 30000, false); } // all threads have sent all submit, we do need to wait for // an acknowledgement for all "inflight" though (synchronize // against the window) logger.info("before waiting sendWindow.size: {}", session.getSendWindow().getSize()); allSubmitResponseReceivedSignal.await(); logger.info("after waiting sendWindow.size: {}", session.getSendWindow().getSize()); session.unbind(5000); } catch (Exception e) { logger.error("", e); this.cause = e; } } class ClientSmppSessionHandler extends DefaultSmppSessionHandler { private CountDownLatch allSubmitResponseReceivedSignal; public ClientSmppSessionHandler(CountDownLatch allSubmitResponseReceivedSignal) { super(logger); this.allSubmitResponseReceivedSignal = allSubmitResponseReceivedSignal; } @Override public void fireChannelUnexpectedlyClosed() { // this is an error we didn't really expect for perf testing // its best to at least countDown the latch so we're not waiting forever logger.error("Unexpected close occurred..."); this.allSubmitResponseReceivedSignal.countDown(); } @Override public void fireExpectedPduResponseReceived(PduAsyncResponse pduAsyncResponse) { submitResponseReceived++; // if the sending thread is finished, check if we're done if (sendingDone.get()) { if (submitResponseReceived >= submitRequestSent) { this.allSubmitResponseReceivedSignal.countDown(); } } } } } }