$(uname -a)43 | EOF 44 | 45 | DATA=`lsb_release -sd 2>&1` 46 | if [ $? -eq 0 ] ; then 47 | echo "
${DATA}" >> "${REPORTS_HOME}/server-info.html" 48 | fi 49 | 50 | DATA=`free -m 2>&1` 51 | if [ $? -eq 0 ] ; then 52 | echo "
${DATA}" >> "${REPORTS_HOME}/server-info.html" 53 | fi 54 | 55 | DATA=`cat /proc/cpuinfo 2>&1` 56 | if [ $? -eq 0 ] ; then 57 | echo "
${DATA}" >> "${REPORTS_HOME}/server-info.html" 58 | fi 59 | 60 | DATA=`java -version 2>&1` 61 | if [ $? -eq 0 ] ; then 62 | echo "
${DATA}" >> "${REPORTS_HOME}/server-info.html" 63 | fi 64 | 65 | which erl > /dev/null 66 | if [ $? -eq 0 ] ; then 67 | DATA=`erl -version 2>&1` 68 | else 69 | DATA=`${WORKSPACE}/erlang/bin/erl -version 2>&1` 70 | fi 71 | if [ $? -eq 0 ] ; then 72 | echo "
" >> "${REPORTS_HOME}/server-info.html" 73 | echo "${DATA}" >> "${REPORTS_HOME}/server-info.html" 74 | echo "" >> "${REPORTS_HOME}/server-info.html" 75 | fi 76 | 77 | fi 78 | 79 | echo "==========================================================================" 80 | echo "Results stored under: ${REPORTS_HOME}" 81 | echo "==========================================================================" 82 | 83 | fi -------------------------------------------------------------------------------- /bin/benchmark-setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | true \ 4 | ${REPORTS_HOME:=$1} \ 5 | ${REPORTS_HOME:=`pwd`/reports/`hostname`} 6 | WORKSPACE="${BASEDIR}/workspace" 7 | 8 | mkdir -p ${WORKSPACE} 9 | mkdir -p ${REPORTS_HOME} 10 | cd "${REPORTS_HOME}" ; REPORTS_HOME=`pwd` ; cd - > /dev/null 11 | 12 | # 13 | # Install SBT 14 | # 15 | if [ ! -f "${WORKSPACE}/bin/sbt" ] ; then 16 | mkdir ~/.ivy2 2> /dev/null 17 | mkdir "${WORKSPACE}/bin" 2> /dev/null 18 | cd "${WORKSPACE}/bin" 19 | wget http://simple-build-tool.googlecode.com/files/sbt-launch-0.7.4.jar 20 | cat > ${WORKSPACE}/bin/sbt <
model name : Intel(R) Core(TM) i7-2600K CPU @ 3.40GHz5 |
Linux ubuntu-2600k 3.0.0-15-generic #26-Ubuntu SMP Fri Jan 20 17:23:00 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux10 |
java version "1.6.0_23" 13 | OpenJDK Runtime Environment (IcedTea6 1.11pre) (6b23~pre11-0ubuntu1.11.10) 14 | OpenJDK 64-Bit Server VM (build 20.0-b11, mixed mode)15 |
376 | This report was generated using the mqtt-benchmark tool. It 377 | provides a comparative benchmark of messaging servers that implement the MQTT 1.0 378 | specification. It covers a wide variety of common usage scenarios. Each scenario is warmed up for 3 seconds before the performance samples are taken. 379 |
380 | 381 |401 | A single publisher sending non-persistent messages to a topic that does not have an subscribers attached. 402 |
403 |
409 | With a 20 b Playload410 |![]() |
412 | <
413 | With a 1 k Playload414 |![]() |
416 | With a 256 k Playload417 |![]() |
419 |
426 | Loading427 |428 | A publisher on a clean session is sending messages with 20 byte payloads to 429 | with 1 subscribed non-clean session which is not currently connected. 430 | 431 |QoS 0 Publish432 |![]() QoS 1 Publish434 |![]() QoS 2 Publish436 |![]() |
438 | Unloading439 |440 | The non-clean session client then reconnects and starts receiving the messages 441 | previously sent to the subscription but now the publisher has stopped. 442 | 443 |QoS 0 Subscribe444 |![]() QoS 1 Subscribe446 |![]() QoS 2 Subscribe448 |![]() |
450 |
455 | These scenarios multiple subscribers and publishers communicate via one shared topic. 456 | All messages sent have a 20 byte payload. 457 |
458 |Clean | Non-Clean | |||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
QoS 0 |
461 |
|
501 |
| ||||||||||||||||||||||||||||||||
QoS 1 |
542 |
|
582 |
| ||||||||||||||||||||||||||||||||
QoS 2 |
623 |
|
663 |
|
707 | These scenarios sample performance as partitioned load is applied. 708 | Each topic only has 1 and only 1 publisher and subscriber attached. 709 |
710 |Clean | Non Clean | |||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
QoS 0 |
713 |
|
754 |
| ||||||||||||||||||||||||||||||||
QoS 1 |
795 |
|
836 |
| ||||||||||||||||||||||||||||||||
QoS 2 |
877 |
|
918 |
|
59 | * Simulates load on the a mqtt broker using non blocking io. 60 | *
61 | * 62 | * @author Hiram Chirino 63 | */ 64 | class NonBlockingScenario extends Scenario { 65 | 66 | def createProducer(i:Int) = { 67 | if(this.request_response) { 68 | new RequestingClient((i)) 69 | } else { 70 | new ProducerClient(i) 71 | } 72 | } 73 | def createConsumer(i:Int) = { 74 | if(this.request_response) { 75 | new RespondingClient(i) 76 | } else { 77 | new ConsumerClient(i) 78 | } 79 | } 80 | 81 | trait NonBlockingClient extends Client { 82 | 83 | protected var queue = createQueue(client_id) 84 | 85 | var message_counter=0L 86 | var reconnect_delay = 0L 87 | 88 | def client_id:String = null 89 | def clean = true 90 | 91 | sealed trait State 92 | 93 | case class INIT() extends State 94 | 95 | case class CONNECTING(host: String, port: Int, on_complete: ()=>Unit) extends State { 96 | 97 | def connect() = { 98 | val mqtt = new MQTT() 99 | mqtt.setDispatchQueue(queue) 100 | mqtt.setSslContext(ssl_context) 101 | mqtt.setHost(new URI(protocol+"://" + host + ":" + port)) 102 | mqtt.setClientId(client_id) 103 | mqtt.setCleanSession(clean) 104 | mqtt.setReconnectAttemptsMax(0) 105 | mqtt.setConnectAttemptsMax(0) 106 | 107 | user.foreach(mqtt.setUserName(_)) 108 | password.foreach(mqtt.setPassword(_)) 109 | val connection = mqtt.callbackConnection(); 110 | connection.connect(new Callback[Void](){ 111 | def onSuccess(na: Void) { 112 | state match { 113 | case x:CONNECTING => 114 | state = CONNECTED(connection) 115 | on_complete() 116 | case _ => 117 | connection.disconnect(null) 118 | } 119 | } 120 | def onFailure(value: Throwable) { 121 | on_failure(value) 122 | } 123 | }) 124 | } 125 | 126 | // We may need to delay the connection attempt. 127 | if( reconnect_delay==0 ) { 128 | connect 129 | } else { 130 | queue.after(5, TimeUnit.SECONDS) { 131 | if ( this == state ) { 132 | reconnect_delay=0 133 | connect 134 | } 135 | } 136 | } 137 | 138 | def close() = { 139 | state = DISCONNECTED() 140 | } 141 | 142 | def on_failure(e:Throwable) = { 143 | if( display_errors ) { 144 | e.printStackTrace 145 | } 146 | error_counter.incrementAndGet 147 | reconnect_delay = 1000 148 | close 149 | } 150 | 151 | } 152 | 153 | case class CONNECTED(val connection:CallbackConnection) extends State { 154 | 155 | connection.listener(new Listener { 156 | def onConnected() {} 157 | def onDisconnected() {} 158 | def onPublish(topic: UTF8Buffer, body: Buffer, ack: Runnable) { 159 | on_receive(topic, body, ack) 160 | } 161 | 162 | def onFailure(value: Throwable) { 163 | on_failure(value) 164 | } 165 | }) 166 | 167 | def close() = { 168 | state = CLOSING() 169 | connection.disconnect(new Callback[Void] { 170 | def onSuccess(value: Void) { 171 | state = DISCONNECTED() 172 | } 173 | def onFailure(value: Throwable) = onSuccess(null) 174 | }) 175 | } 176 | 177 | def on_failure(e:Throwable) = { 178 | if( display_errors ) { 179 | e.printStackTrace 180 | } 181 | error_counter.incrementAndGet 182 | reconnect_delay = 1000 183 | close 184 | } 185 | 186 | } 187 | case class CLOSING() extends State 188 | 189 | case class DISCONNECTED() extends State { 190 | queue { 191 | if( state==this ){ 192 | if( done.get ) { 193 | has_shutdown.countDown 194 | } else { 195 | reconnect_action 196 | } 197 | } 198 | } 199 | } 200 | 201 | var state:State = INIT() 202 | 203 | val has_shutdown = new CountDownLatch(1) 204 | def reconnect_action:Unit 205 | 206 | def on_failure(e:Throwable) = state match { 207 | case x:CONNECTING => x.on_failure(e) 208 | case x:CONNECTED => x.on_failure(e) 209 | case _ => 210 | } 211 | 212 | def start = queue { 213 | state = DISCONNECTED() 214 | } 215 | 216 | def queue_check = queue.assertExecuting() 217 | 218 | def open(host: String, port: Int)(on_complete: =>Unit) = { 219 | assert ( state.isInstanceOf[DISCONNECTED] ) 220 | queue_check 221 | state = CONNECTING(host, port, ()=>on_complete) 222 | } 223 | 224 | def close() = { 225 | queue_check 226 | state match { 227 | case x:CONNECTING => x.close 228 | case x:CONNECTED => x.close 229 | case _ => 230 | } 231 | } 232 | 233 | def shutdown = { 234 | assert(done.get) 235 | queue { 236 | close 237 | } 238 | has_shutdown.await() 239 | } 240 | 241 | def receive_suspend = { 242 | queue_check 243 | state match { 244 | case state:CONNECTED => state.connection.suspend() 245 | case _ => 246 | } 247 | } 248 | 249 | def receive_resume = { 250 | queue_check 251 | state match { 252 | case state:CONNECTED => state.connection.resume() 253 | case _ => 254 | } 255 | } 256 | 257 | def connection = { 258 | queue_check 259 | state match { 260 | case state:CONNECTED => Some(state.connection) 261 | case _ => None 262 | } 263 | } 264 | 265 | def on_receive(topic: UTF8Buffer, body: Buffer, ack: Runnable) = { 266 | ack.run() 267 | } 268 | 269 | def connect(proc: =>Unit) = { 270 | queue_check 271 | if( !done.get ) { 272 | open(host, port) { 273 | proc 274 | } 275 | } 276 | } 277 | 278 | } 279 | 280 | class ProducerClient(val id: Int) extends NonBlockingClient { 281 | 282 | override def client_id = "producer-"+id 283 | override def clean = producer_clean 284 | 285 | val message_cache = HashMap.empty[Int, AsciiBuffer] 286 | 287 | override def reconnect_action = { 288 | connect { 289 | write_action 290 | } 291 | } 292 | 293 | def write_action:Unit = { 294 | def retry:Unit = { 295 | if(done.get) { 296 | close 297 | } else { 298 | if(producer_sleep >= 0) { 299 | connection.foreach{ connection=> 300 | connection.publish(utf8(destination(id)), get_message(), QoS.values()(producer_qos), message_retain, new Callback[Void](){ 301 | def onSuccess(value: Void) = { 302 | producer_counter.incrementAndGet() 303 | message_counter += 1 304 | write_completed_action 305 | } 306 | def onFailure(value: Throwable) = { 307 | on_failure(value) 308 | } 309 | }) 310 | } 311 | } else { 312 | write_completed_action 313 | } 314 | } 315 | } 316 | retry 317 | } 318 | 319 | def write_completed_action:Unit = { 320 | def doit = { 321 | val m_p_connection = messages_per_connection.toLong 322 | if(m_p_connection > 0 && message_counter >= m_p_connection) { 323 | message_counter = 0 324 | close 325 | } else { 326 | write_action 327 | } 328 | } 329 | 330 | if(done.get) { 331 | close 332 | } else { 333 | if(producer_sleep != 0) { 334 | queue.after(math.abs(producer_sleep), TimeUnit.MILLISECONDS) { 335 | doit 336 | } 337 | } else { 338 | queue { doit } 339 | } 340 | } 341 | } 342 | 343 | def get_message() = { 344 | val m_s = message_size 345 | 346 | if(! message_cache.contains(m_s)) { 347 | message_cache(m_s) = message(client_id, m_s) 348 | } 349 | 350 | message_cache(m_s) 351 | } 352 | 353 | def message(name:String, size:Int) = { 354 | val buffer = new StringBuffer(size) 355 | buffer.append("Message from " + name + "\n") 356 | for( i <- buffer.length to size ) { 357 | buffer.append(('a'+(i%26)).toChar) 358 | } 359 | var rc = buffer.toString 360 | if( rc.length > size ) { 361 | rc.substring(0, size) 362 | } else { 363 | rc 364 | } 365 | ascii(rc) 366 | } 367 | 368 | } 369 | 370 | class ConsumerClient(val id: Int) extends NonBlockingClient { 371 | 372 | override def client_id = "consumer-"+id 373 | 374 | override def clean = consumer_clean 375 | 376 | override def reconnect_action = { 377 | connect { 378 | connection.foreach { connection => 379 | connection.subscribe(Array(new Topic(destination(id), QoS.values()(consumer_qos))), null) 380 | } 381 | } 382 | } 383 | 384 | def index_of(haystack:Array[Byte], needle:Array[Byte]):Int = { 385 | var i = 0 386 | while( haystack.length >= i+needle.length ) { 387 | if( haystack.startsWith(needle, i) ) { 388 | return i 389 | } 390 | i += 1 391 | } 392 | return -1 393 | } 394 | 395 | 396 | override def on_receive(topic: UTF8Buffer, body: Buffer, ack: Runnable) = { 397 | if( consumer_sleep != 0 && ((consumer_counter.get()%consumer_sleep_modulo) == 0)) { 398 | if( consumer_qos==0 ) { 399 | receive_suspend 400 | } 401 | queue.after(math.abs(consumer_sleep), TimeUnit.MILLISECONDS) { 402 | if( consumer_qos==0 ) { 403 | receive_resume 404 | } 405 | process_message(body, ack) 406 | } 407 | } else { 408 | process_message(body, ack) 409 | } 410 | } 411 | 412 | def process_message(body: Buffer, ack: Runnable):Unit = { 413 | ack.run() 414 | consumer_counter.incrementAndGet() 415 | } 416 | 417 | } 418 | 419 | class RequestingClient(id: Int) extends ProducerClient(id) { 420 | override def client_id = "requestor-"+id 421 | 422 | override def reconnect_action = { 423 | connect { 424 | connection.foreach { connection => 425 | connection.subscribe(Array(new Topic(response_destination(id), QoS.values()(consumer_qos))), null) 426 | } 427 | // give the response queue a chance to drain before doing new requests. 428 | queue.after(1000, TimeUnit.MILLISECONDS) { 429 | write_action 430 | } 431 | } 432 | } 433 | 434 | var request_start = 0L 435 | 436 | override def write_action:Unit = { 437 | def retry:Unit = { 438 | if(done.get) { 439 | close 440 | } else { 441 | if(producer_sleep >= 0) { 442 | connection.foreach{ connection=> 443 | var msg = get_message().deepCopy() // we have to copy since we are modifyiing.. 444 | msg.buffer.moveHead(msg.length-4).bigEndianEditor().writeInt(id) // write the last 4 bytes.. 445 | 446 | request_start = System.nanoTime() 447 | connection.publish(utf8(destination(id)), msg, QoS.values()(producer_qos), message_retain, new Callback[Void](){ 448 | def onSuccess(value: Void) = { 449 | // don't do anything.. we complete when 450 | // on_receive gets called. 451 | } 452 | def onFailure(value: Throwable) = { 453 | on_failure(value) 454 | } 455 | }) 456 | } 457 | } else { 458 | write_completed_action 459 | } 460 | } 461 | } 462 | retry 463 | } 464 | 465 | override def on_receive(topic: UTF8Buffer, body: Buffer, ack: Runnable) = { 466 | if(request_start != 0L) { 467 | request_times.add(System.nanoTime() - request_start) 468 | request_start = 0 469 | producer_counter.incrementAndGet() 470 | message_counter += 1 471 | write_completed_action 472 | } 473 | ack.run(); 474 | } 475 | 476 | } 477 | 478 | class RespondingClient(id: Int) extends ConsumerClient(id) { 479 | 480 | override def client_id = "responder-"+id 481 | 482 | val EMPTY = new Buffer(0); 483 | 484 | override def process_message(body: Buffer, ack: Runnable) = { 485 | connection.foreach{ connection=> 486 | body.moveHead(body.length-4) //lets read the last 4 bytes 487 | val rid = body.bigEndianEditor().readInt() 488 | connection.publish(utf8(response_destination(rid)), EMPTY, QoS.values()(producer_qos), message_retain, new Callback[Void](){ 489 | def onSuccess(value: Void) = { 490 | ack.run() 491 | consumer_counter.incrementAndGet() 492 | } 493 | def onFailure(value: Throwable) = { 494 | on_failure(value) 495 | } 496 | }) 497 | } 498 | } 499 | } 500 | } 501 | -------------------------------------------------------------------------------- /src/main/scala/com/github/mqtt/benchmark/Scenario.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2011 the original author or authors. 3 | * See the notice.md file distributed with this work for additional 4 | * information regarding copyright ownership. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.github.mqtt.benchmark 19 | 20 | import java.util.concurrent.atomic._ 21 | import java.util.concurrent.TimeUnit._ 22 | import scala.collection.mutable.ListBuffer 23 | import java.util.concurrent.ConcurrentLinkedQueue 24 | import java.security.KeyStore 25 | import java.io.FileInputStream 26 | import javax.net.ssl._ 27 | import org.fusesource.hawtdispatch.transport.{SslTransport, TcpTransport} 28 | 29 | object Scenario { 30 | val MESSAGE_ID:Array[Byte] = "message-id" 31 | val NEWLINE = '\n'.toByte 32 | val NANOS_PER_SECOND = NANOSECONDS.convert(1, SECONDS) 33 | val NANOS_PER_MS = NANOSECONDS.convert(1, MILLISECONDS) 34 | 35 | implicit def toBytes(value: String):Array[Byte] = value.getBytes("UTF-8") 36 | 37 | def o[T](value:T):Option[T] = value match { 38 | case null => None 39 | case x => Some(x) 40 | } 41 | 42 | def percentiles(percentiles:Array[Double], values:Array[Long]) = { 43 | if (values.length > 0) { 44 | java.util.Arrays.sort(values) 45 | percentiles.map { p => 46 | val pos = p * (values.length + 1); 47 | if (pos < 1) { 48 | values(0) 49 | } else if (pos >= values.length) { 50 | values(values.length - 1); 51 | } else { 52 | val lower = values((pos - 1).toInt); 53 | val upper = values(pos.toInt); 54 | lower + ((pos - pos.floor) * (upper - lower)).toLong; 55 | } 56 | } 57 | } else { 58 | percentiles.map(p => -1L) 59 | } 60 | } 61 | } 62 | 63 | trait Scenario { 64 | import Scenario._ 65 | 66 | var user: Option[String] = None 67 | var password: Option[String] = None 68 | var request_response = false 69 | 70 | private var _producer_sleep: { def apply(): Int; def init(time: Long) } = new { def apply() = 0; def init(time: Long) {} } 71 | def producer_sleep = _producer_sleep() 72 | def producer_sleep_= (new_value: Int) = _producer_sleep = new { def apply() = new_value; def init(time: Long) {} } 73 | def producer_sleep_= (new_func: { def apply(): Int; def init(time: Long) }) = _producer_sleep = new_func 74 | 75 | private var _consumer_sleep: { def apply(): Int; def init(time: Long) } = new { def apply() = 0; def init(time: Long) {} } 76 | def consumer_sleep = _consumer_sleep() 77 | def consumer_sleep_= (new_value: Int) = _consumer_sleep = new { def apply() = new_value; def init(time: Long) {} } 78 | def consumer_sleep_= (new_func: { def apply(): Int; def init(time: Long) }) = _consumer_sleep = new_func 79 | 80 | var producers = 1 81 | var producers_per_sample = 0 82 | 83 | var consumers = 1 84 | var consumers_per_sample = 0 85 | var sample_interval = 1000 86 | var protocol = "tcp" 87 | var host = "127.0.0.1" 88 | var port = 61613 89 | var buffer_size = 32*1024 90 | 91 | var consumer_clean = true 92 | var consumer_qos = 0 93 | var clear_subscriptions_when_finished = true 94 | 95 | var producer_clean = true 96 | var producer_qos = 0 97 | 98 | var message_retain = false 99 | private var _message_size: { def apply(): Int; def init(time: Long) } = new { def apply() = 1024; def init(time: Long) {} } 100 | def message_size = _message_size() 101 | def message_size_= (new_value: Int) = _message_size = new { def apply() = new_value; def init(time: Long) {} } 102 | def message_size_= (new_func: { def apply(): Int; def init(time: Long) }) = _message_size = new_func 103 | 104 | var consumer_sleep_modulo = 1 105 | var producer_sleep_modulo = 1 106 | private var _messages_per_connection: { def apply(): Int; def init(time: Long) } = new { def apply() = -1; def init(time: Long) {} } 107 | def messages_per_connection = _messages_per_connection() 108 | def messages_per_connection_= (new_value: Int) = _messages_per_connection = new { def apply() = new_value; def init(time: Long) {} } 109 | def messages_per_connection_= (new_func: { def apply(): Int; def init(time: Long) }) = _messages_per_connection = new_func 110 | 111 | var display_errors = false 112 | 113 | var destination_prefix = "" 114 | 115 | private var _destination_name: () => String = () => "" 116 | def destination_name = _destination_name() 117 | def destination_name_=(new_name: String) = _destination_name = () => new_name 118 | def destination_name_=(new_func: () => String) = _destination_name = new_func 119 | 120 | private var _response_destination_name: () => String = () => "response" 121 | def response_destination_name = _response_destination_name() 122 | def response_destination_name_=(new_name: String) = _response_destination_name = () => new_name 123 | def response_destination_name_=(new_func: () => String) = _response_destination_name = new_func 124 | 125 | var destination_count = 1 126 | 127 | val producer_counter = new AtomicLong() 128 | val consumer_counter = new AtomicLong() 129 | val request_times = new ConcurrentLinkedQueue[Long]() 130 | 131 | val error_counter = new AtomicLong() 132 | val done = new AtomicBoolean() 133 | 134 | var name = "custom" 135 | 136 | var drain_timeout = 2000L 137 | 138 | var key_store_file:Option[String] = None 139 | var key_store_password:Option[String] = None 140 | var key_password:Option[String] = None 141 | 142 | var key_store:KeyStore = _ 143 | var trust_managers:Array[TrustManager] = _ 144 | var key_managers:Array[KeyManager] = _ 145 | 146 | def ssl_context:SSLContext = { 147 | Option(SslTransport.protocol(protocol)).map { protocol => 148 | val rc = SSLContext.getInstance(protocol) 149 | rc.init(get_key_managers, get_trust_managers, null) 150 | rc 151 | }.getOrElse(null) 152 | } 153 | 154 | def get_key_store = { 155 | if( key_store==null && key_store_file.isDefined ) { 156 | key_store = { 157 | val store = KeyStore.getInstance("JKS") 158 | store.load(new FileInputStream(key_store_file.get), key_store_password.getOrElse("").toCharArray()) 159 | store 160 | } 161 | } 162 | key_store 163 | } 164 | 165 | def get_trust_managers = { 166 | val store = get_key_store 167 | if( trust_managers==null && store!=null ) { 168 | val factory = TrustManagerFactory.getInstance("SunX509") 169 | factory.init(store) 170 | trust_managers = factory.getTrustManagers 171 | } 172 | trust_managers 173 | } 174 | 175 | def get_key_managers = { 176 | val store = get_key_store 177 | if( key_managers==null && store!=null) { 178 | val factory = KeyManagerFactory.getInstance("SunX509") 179 | factory.init(store, key_password.getOrElse("").toCharArray()) 180 | key_managers = factory.getKeyManagers 181 | } 182 | key_managers 183 | } 184 | 185 | 186 | def run() = { 187 | print(toString) 188 | println("--------------------------------------") 189 | println(" Running: Press ENTER to stop") 190 | println("--------------------------------------") 191 | println("") 192 | 193 | with_load { 194 | 195 | // start a sampling client... 196 | val sample_thread = new Thread() { 197 | override def run() = { 198 | 199 | def print_rate(name: String, periodCount:Long, totalCount:Long, nanos: Long) = { 200 | val rate_per_second: java.lang.Float = ((1.0f * periodCount / nanos) * NANOS_PER_SECOND) 201 | println("%s total: %,d, rate: %,.3f per second".format(name, totalCount, rate_per_second)) 202 | } 203 | def print_percentil(name: String, value:Long, max:Long) = { 204 | println("%sth percentile response time: %,.3f ms, max: %,.3f ms".format(name, (1.0f * value / NANOS_PER_MS), (1.0f * max / NANOS_PER_MS))) 205 | } 206 | 207 | try { 208 | var start = System.nanoTime 209 | var total_producer_count = 0L 210 | var total_consumer_count = 0L 211 | var total_error_count = 0L 212 | var max_p90 = 0L 213 | var max_p99 = 0L 214 | var max_p999 = 0L 215 | 216 | collection_start 217 | while( !done.get ) { 218 | Thread.sleep(sample_interval) 219 | val end = System.nanoTime 220 | collection_sample 221 | val samples = collection_end 222 | samples.get("p_custom").foreach { case (_, count)::Nil => 223 | total_producer_count += count 224 | print_rate("Producer", count, total_producer_count, end - start) 225 | case _ => 226 | } 227 | samples.get("c_custom").foreach { case (_, count)::Nil => 228 | total_consumer_count += count 229 | print_rate("Consumer", count, total_consumer_count, end - start) 230 | case _ => 231 | } 232 | samples.get("p90_custom").foreach { case (_, value)::Nil => 233 | max_p90 = max_p90.max(value) 234 | print_percentil("90", value, max_p90) 235 | case _ => 236 | } 237 | samples.get("p99_custom").foreach { case (_, value)::Nil => 238 | max_p99 = max_p99.max(value) 239 | print_percentil("99", value, max_p99) 240 | case _ => 241 | } 242 | samples.get("p999_custom").foreach { case (_, value)::Nil => 243 | max_p999 = max_p999.max(value) 244 | print_percentil("99.9", value, max_p999) 245 | case _ => 246 | } 247 | samples.get("e_custom").foreach { case (_, count)::Nil => 248 | if( count!= 0 ) { 249 | total_error_count += count 250 | print_rate("Error", count, total_error_count, end - start) 251 | } 252 | case _ => 253 | } 254 | start = end 255 | } 256 | } catch { 257 | case e:InterruptedException => 258 | } 259 | } 260 | } 261 | sample_thread.start() 262 | 263 | System.in.read() 264 | done.set(true) 265 | 266 | sample_thread.interrupt 267 | sample_thread.join 268 | } 269 | 270 | } 271 | 272 | override def toString() = { 273 | "--------------------------------------\n"+ 274 | "Scenario Settings\n"+ 275 | "--------------------------------------\n"+ 276 | " host = "+host+"\n"+ 277 | " port = "+port+"\n"+ 278 | " destination_count = "+destination_count+"\n" + 279 | " destination_prefix = "+destination_prefix+"\n"+ 280 | " destination_name = "+destination_name+"\n" + 281 | " sample_interval (ms) = "+sample_interval+"\n" + 282 | " \n"+ 283 | " --- Producer Properties ---\n"+ 284 | " producers = "+producers+"\n"+ 285 | " message_size = "+message_size+"\n"+ 286 | " producer_sleep (ms) = "+producer_sleep+"\n"+ 287 | " producer_qos = "+producer_qos+"\n"+ 288 | " producer_retain = "+message_retain+"\n"+ 289 | " \n"+ 290 | " --- Consumer Properties ---\n"+ 291 | " consumers = "+consumers+"\n"+ 292 | " consumer_sleep (ms) = "+consumer_sleep+"\n"+ 293 | " consumer_qos = "+consumer_qos+"\n"+ 294 | " clean_session = "+producer_clean+"\n"+ 295 | "" 296 | 297 | } 298 | 299 | def settings(): List[(String, String)] = { 300 | var s: List[(String, String)] = Nil 301 | 302 | s :+= ("host", host) 303 | s :+= ("port", port.toString) 304 | s :+= ("destination_prefix", destination_prefix) 305 | s :+= ("destination_count", destination_count.toString) 306 | s :+= ("destination_name", destination_name) 307 | s :+= ("sample_interval", sample_interval.toString) 308 | s :+= ("producers", producers.toString) 309 | s :+= ("message_size", message_size.toString) 310 | s :+= ("producer_sleep", producer_sleep.toString) 311 | s :+= ("consumers", consumers.toString) 312 | s :+= ("consumer_sleep", consumer_sleep.toString) 313 | s :+= ("consumer_qos", consumer_qos.toString) 314 | s :+= ("producer_qos", producer_qos.toString) 315 | s :+= ("producer_retain", message_retain.toString) 316 | s :+= ("durable", producer_clean.toString) 317 | 318 | s 319 | } 320 | 321 | protected def destination(i:Int) = destination_prefix+destination_name+"-"+(i%destination_count) 322 | 323 | protected def response_destination(i:Int) = destination_prefix+response_destination_name+"-"+i 324 | 325 | var producer_samples:Option[ListBuffer[(Long,Long)]] = None 326 | var consumer_samples:Option[ListBuffer[(Long,Long)]] = None 327 | var error_samples = ListBuffer[(Long,Long)]() 328 | 329 | var request_time_p90_samples = ListBuffer[(Long,Long)]() 330 | var request_time_p99_samples = ListBuffer[(Long,Long)]() 331 | var request_time_p999_samples = ListBuffer[(Long,Long)]() 332 | 333 | def collection_start: Unit = { 334 | producer_counter.set(0) 335 | consumer_counter.set(0) 336 | error_counter.set(0) 337 | request_times.clear() 338 | 339 | producer_samples = if (producers > 0 || producers_per_sample>0 ) { 340 | Some(ListBuffer[(Long,Long)]()) 341 | } else { 342 | None 343 | } 344 | consumer_samples = if (consumers > 0 || consumers_per_sample>0 ) { 345 | Some(ListBuffer[(Long,Long)]()) 346 | } else { 347 | None 348 | } 349 | request_time_p90_samples = ListBuffer[(Long,Long)]() 350 | request_time_p99_samples = ListBuffer[(Long,Long)]() 351 | request_time_p999_samples = ListBuffer[(Long,Long)]() 352 | } 353 | 354 | def collection_end: Map[String, scala.List[(Long,Long)]] = { 355 | var rc = Map[String, List[(Long,Long)]]() 356 | producer_samples.foreach{ samples => 357 | rc += "p_"+name -> samples.toList 358 | samples.clear 359 | } 360 | consumer_samples.foreach{ samples => 361 | rc += "c_"+name -> samples.toList 362 | samples.clear 363 | } 364 | rc += "e_"+name -> error_samples.toList 365 | error_samples.clear 366 | if(request_response) { 367 | rc += "p90_"+name -> request_time_p90_samples.toList 368 | request_time_p90_samples.clear 369 | rc += "p99_"+name -> request_time_p99_samples.toList 370 | request_time_p99_samples.clear 371 | rc += "p999_"+name -> request_time_p999_samples.toList 372 | request_time_p999_samples.clear 373 | } 374 | 375 | rc 376 | } 377 | 378 | trait Client { 379 | def start():Unit 380 | def shutdown():Unit 381 | } 382 | 383 | var producer_clients = List[Client]() 384 | var consumer_clients = List[Client]() 385 | 386 | def with_load[T](func: =>T ):T = { 387 | done.set(false) 388 | 389 | val now = System.currentTimeMillis() 390 | _producer_sleep.init(now) 391 | _consumer_sleep.init(now) 392 | _message_size.init(now) 393 | _messages_per_connection.init(now) 394 | 395 | for (i <- 0 until producers) { 396 | val client = createProducer(i) 397 | producer_clients ::= client 398 | client.start() 399 | } 400 | 401 | for (i <- 0 until consumers) { 402 | val client = createConsumer(i) 403 | consumer_clients ::= client 404 | client.start() 405 | } 406 | 407 | try { 408 | func 409 | } finally { 410 | done.set(true) 411 | // wait for the threads to finish.. 412 | for( client <- consumer_clients ) { 413 | client.shutdown 414 | } 415 | consumer_clients = List() 416 | for( client <- producer_clients ) { 417 | client.shutdown 418 | } 419 | producer_clients = List() 420 | } 421 | } 422 | 423 | def drain = { 424 | done.set(false) 425 | if( clear_subscriptions_when_finished && !consumer_clean ) { 426 | consumer_clean=true 427 | print("clearing subscriptions") 428 | consumer_counter.set(0) 429 | var consumer_clients = List[Client]() 430 | for (i <- 0 until destination_count.max(consumers)) { 431 | val client = createConsumer(i) 432 | consumer_clients ::= client 433 | client.start() 434 | } 435 | 436 | // Keep sleeping until we stop draining messages. 437 | var drained = 0L 438 | try { 439 | Thread.sleep(drain_timeout); 440 | def done() = { 441 | val c = consumer_counter.getAndSet(0) 442 | drained += c 443 | c == 0 444 | } 445 | while( !done ) { 446 | print(".") 447 | Thread.sleep(drain_timeout); 448 | } 449 | } finally { 450 | done.set(true) 451 | for( client <- consumer_clients ) { 452 | client.shutdown 453 | } 454 | println(". (drained %d)".format(drained)) 455 | } 456 | consumer_clean=false 457 | } 458 | } 459 | 460 | 461 | def collection_sample: Unit = { 462 | 463 | val now = System.currentTimeMillis() 464 | producer_samples.foreach(_.append((now, producer_counter.getAndSet(0)))) 465 | consumer_samples.foreach(_.append((now, consumer_counter.getAndSet(0)))) 466 | 467 | if( request_response ) { 468 | 469 | var count = producer_samples.get.last._2.toInt 470 | val times = new Array[Long](count) 471 | while(count > 0 ) { 472 | count -= 1 473 | times(count) = request_times.poll() 474 | } 475 | val p = percentiles(Array(0.9, 0.99, 0.999), times) 476 | request_time_p90_samples.append((now, p(0))) 477 | request_time_p99_samples.append((now, p(1))) 478 | request_time_p999_samples.append((now, p(2))) 479 | } 480 | 481 | error_samples.append((now, error_counter.getAndSet(0))) 482 | 483 | // we might need to increment number the producers.. 484 | for (i <- 0 until producers_per_sample) { 485 | val client = createProducer(producer_clients.length) 486 | producer_clients ::= client 487 | client.start() 488 | } 489 | 490 | // we might need to increment number the consumers.. 491 | for (i <- 0 until consumers_per_sample) { 492 | val client = createConsumer(consumer_clients.length) 493 | consumer_clients ::= client 494 | client.start() 495 | } 496 | 497 | } 498 | 499 | def createProducer(i:Int):Client 500 | def createConsumer(i:Int):Client 501 | 502 | protected def ignore_failure(func: =>Unit):Unit = try { 503 | func 504 | } catch { case _ => 505 | } 506 | 507 | } 508 | 509 | 510 | --------------------------------------------------------------------------------