├── stress ├── __init__.py ├── static │ ├── img │ │ ├── ajax-loader.gif │ │ ├── background.png │ │ ├── glyphicons-halflings.png │ │ └── glyphicons-halflings-white.png │ ├── js │ │ ├── bootstrap-dropdown.js │ │ ├── bootstrap-popover.js │ │ ├── query_graphs.js │ │ ├── bootstrap-tab.js │ │ ├── metrics_graphs.js │ │ ├── live.js │ │ ├── query_dial.js │ │ ├── bootstrap-modal.js │ │ ├── stress.js │ │ ├── mysql.js │ │ ├── bootstrap-tooltip.js │ │ ├── common.js │ │ └── jquery.knob-1.1.1.js │ ├── lang │ │ └── sh_sql.min.js │ └── css │ │ ├── codemirror.css │ │ ├── stress.css │ │ ├── stress.scss │ │ ├── bootstrap-responsive.min.css │ │ └── bootstrap-responsive.css ├── test_worker.py ├── utils.py ├── plancache.py ├── query_table.py ├── worker.py ├── database.py ├── templates │ └── index.html └── server.py ├── .gitignore ├── runner ├── .arcconfig ├── workloads ├── key_value │ ├── key_value.sql │ └── key_value.json └── video │ ├── video.json │ └── video.sql └── README.md /stress/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | *.data 4 | .sass-cache 5 | -------------------------------------------------------------------------------- /runner: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import stress.server 4 | stress.server.main() 5 | -------------------------------------------------------------------------------- /.arcconfig: -------------------------------------------------------------------------------- 1 | { 2 | "project_id" : "workloadsimulator", 3 | "conduit_uri" : "http:\/\/grizzly.memsql.com\/api\/" 4 | } 5 | -------------------------------------------------------------------------------- /stress/static/img/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memsql/workload-simulator/HEAD/stress/static/img/ajax-loader.gif -------------------------------------------------------------------------------- /stress/static/img/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memsql/workload-simulator/HEAD/stress/static/img/background.png -------------------------------------------------------------------------------- /stress/static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memsql/workload-simulator/HEAD/stress/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /stress/static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memsql/workload-simulator/HEAD/stress/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /workloads/key_value/key_value.sql: -------------------------------------------------------------------------------- 1 | create database if not exists key_value; 2 | use key_value; 3 | 4 | DROP TABLE IF EXISTS `kv`; 5 | CREATE TABLE `kv` ( 6 | `k` bigint(20) NOT NULL, 7 | `v` varbinary(64) DEFAULT NULL, 8 | PRIMARY KEY (`k`) USING HASH 9 | ); 10 | -------------------------------------------------------------------------------- /workloads/key_value/key_value.json: -------------------------------------------------------------------------------- 1 | {"workload": {"1": {"query": "select v from kv \n\twhere k=@", "qps": "52728"}, "0": {"query": "insert into kv \n\tvalues (@, ^)", "qps": "40939"}, "3": {"query": "delete from kv \n\twhere k=@", "qps": "37701"}, "2": {"query": "update kv set v=^\n\twhere k=@", "qps": "42868"}}, "settings": {"memsql_host": "127.0.0.1", "memsql_db": "key_value", "memsql_port": "3306", "qps-number": "100000", "workers": "50", "memsql_pass": "", "memsql_user": "root", "dial_max_value": 100000}} -------------------------------------------------------------------------------- /stress/test_worker.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import worker 4 | 5 | client_arguments = dict(host = '127.0.0.1', port = 3306, user = 'root', passwd = '', db = 'test') 6 | 7 | workload = { 8 | 0 : { 9 | 'query' : 'select * from x where id > @', 10 | 'qps' : 1000 11 | } 12 | } 13 | 14 | 15 | if __name__ == '__main__': 16 | pool = worker.WorkerPool(10, client_arguments) 17 | assert pool.is_alive() 18 | pool.send_workload(workload) 19 | time.sleep(10) 20 | print "--> WOKE UP" 21 | assert pool.is_alive() 22 | print "--> ABOUT TO PAUSE" 23 | pool.pause() 24 | print "--> DONE PAUSING" 25 | -------------------------------------------------------------------------------- /workloads/video/video.json: -------------------------------------------------------------------------------- 1 | {"workload": {"1": {"query": "UPDATE order_line SET ol_delivery_d = @ WHERE ol_o_id = @ AND ol_d_id = @ AND ol_w_id = @", "qps": "15000"}, "0": {"query": "INSERT INTO orders (o_id, o_d_id, o_w_id, o_c_id, o_entry_d, o_ol_cnt, o_all_local) VALUES (@, @, @, @, @, @, @)", "qps": "15000"}, "3": {"query": "INSERT INTO customer values(@, @, @, @, @, @, @, @, @, @, @, @, @, @, @, @, @, @, @, @, @)", "qps": "15000"}, "2": {"query": "SELECT count(c_id) FROM customer WHERE c_w_id = @ AND c_d_id = @ AND c_last = @", "qps": "15000"}, "5": {"query": "select * from orders where o_w_id = @", "qps": "15000"}, "4": {"query": "UPDATE customer SET c_balance = @ WHERE c_w_id = @ AND c_d_id = @ AND c_id = @", "qps": "15000"}}, "settings": {"memsql_host": "127.0.0.1", "memsql_db": "video", "memsql_port": "3306", "qps-number": "15000", "workers": "50", "memsql_pass": "", "memsql_user": "root", "dial_max_value": 15000}} -------------------------------------------------------------------------------- /stress/utils.py: -------------------------------------------------------------------------------- 1 | """Helper functions. 2 | """ 3 | 4 | import logging 5 | import optparse 6 | import database 7 | from sqlalchemy import pool 8 | 9 | # Some basic configuration 10 | LOGGING_FORMAT = '%(levelname)s: %(asctime)-15s: %(message)s' 11 | 12 | # Uniform interface for parsing options, with some common built-in options. 13 | options = None 14 | largs = None 15 | 16 | parser = optparse.OptionParser(add_help_option=False) 17 | parser.add_option("--memsql-host", help="memsql hostname", default="127.0.0.1") 18 | parser.add_option("--memsql-port", help="memsql port", type="int", default=3306) 19 | parser.add_option("--memsql-user", help="memsql user", default="root") 20 | parser.add_option("--memsql-pass", help="memsql pass", default="") 21 | parser.add_option("--memsql-db", help="memsql database", default="") 22 | parser.add_option("-w", "--workers", help="default number of workers", type="int", default=50) 23 | parser.add_option("-p", "--server-port", type="int", default=9000, help="server port") 24 | 25 | parser.add_option("", "--help", action="help") 26 | 27 | def _parse_options(): 28 | global options 29 | global largs 30 | if not (options and largs): 31 | options, largs = parser.parse_args() 32 | 33 | def get_options(): 34 | """Return the parsed (named) options.""" 35 | global options 36 | _parse_options() 37 | return options 38 | 39 | def get_largs(): 40 | """Return the parsed (free) options.""" 41 | global largs 42 | _parse_options() 43 | return largs 44 | 45 | # Wraps SQLAlchemy's DB Pool into our own connection pool. 46 | db_pool = pool.manage(database) 47 | def get_db_conn(host=None, port=None, user=None, password=None, database=None): 48 | """Returns a database connection from the connection pool.""" 49 | global db_pool 50 | assert options 51 | 52 | if host is None: 53 | host = options.memsql_host 54 | if port is None: 55 | port = options.memsql_port 56 | if user is None: 57 | user = options.memsql_user 58 | if password is None: 59 | password = options.memsql_password 60 | if database is None: 61 | database = options.memsql_db 62 | 63 | return db_pool.connect( 64 | host='%s:%d' % (host, port), 65 | user=user, password=password, database=database) 66 | 67 | # Sets up the global logger. 68 | logger = logging.basicConfig(level=logging.ERROR, format=LOGGING_FORMAT) 69 | logger = logging 70 | -------------------------------------------------------------------------------- /stress/static/js/bootstrap-dropdown.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-dropdown.js v2.0.3 3 | * http://twitter.github.com/bootstrap/javascript.html#dropdowns 4 | * ============================================================ 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* DROPDOWN CLASS DEFINITION 27 | * ========================= */ 28 | 29 | var toggle = '[data-toggle="dropdown"]' 30 | , Dropdown = function (element) { 31 | var $el = $(element).on('click.dropdown.data-api', this.toggle) 32 | $('html').on('click.dropdown.data-api', function () { 33 | $el.parent().removeClass('open') 34 | }) 35 | } 36 | 37 | Dropdown.prototype = { 38 | 39 | constructor: Dropdown 40 | 41 | , toggle: function (e) { 42 | var $this = $(this) 43 | , $parent 44 | , selector 45 | , isActive 46 | 47 | if ($this.is('.disabled, :disabled')) return 48 | 49 | selector = $this.attr('data-target') 50 | 51 | if (!selector) { 52 | selector = $this.attr('href') 53 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 54 | } 55 | 56 | $parent = $(selector) 57 | $parent.length || ($parent = $this.parent()) 58 | 59 | isActive = $parent.hasClass('open') 60 | 61 | clearMenus() 62 | 63 | if (!isActive) $parent.toggleClass('open') 64 | 65 | return false 66 | } 67 | 68 | } 69 | 70 | function clearMenus() { 71 | $(toggle).parent().removeClass('open') 72 | } 73 | 74 | 75 | /* DROPDOWN PLUGIN DEFINITION 76 | * ========================== */ 77 | 78 | $.fn.dropdown = function (option) { 79 | return this.each(function () { 80 | var $this = $(this) 81 | , data = $this.data('dropdown') 82 | if (!data) $this.data('dropdown', (data = new Dropdown(this))) 83 | if (typeof option == 'string') data[option].call($this) 84 | }) 85 | } 86 | 87 | $.fn.dropdown.Constructor = Dropdown 88 | 89 | 90 | /* APPLY TO STANDARD DROPDOWN ELEMENTS 91 | * =================================== */ 92 | 93 | $(function () { 94 | $('html').on('click.dropdown.data-api', clearMenus) 95 | $('body') 96 | .on('click.dropdown', '.dropdown form', function (e) { e.stopPropagation() }) 97 | .on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) 98 | }) 99 | 100 | }(window.jQuery); -------------------------------------------------------------------------------- /stress/plancache.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | import logging 3 | import multiprocessing 4 | import time 5 | 6 | import worker 7 | import utils 8 | 9 | logger = logging.getLogger('plancache') 10 | logger.setLevel(logging.DEBUG) 11 | 12 | def save_plancache_loop(SettingsClass, stats, metrics, lock): 13 | POLLING_PERIOD = worker.PERIOD*10 14 | prev_time = 0 15 | prev_pc = {} 16 | 17 | pc_time = 0 18 | pc_counter = 0 19 | 20 | conn = SettingsClass.get_db_conn() 21 | 22 | while True: 23 | current_time = time.time() 24 | rows_pc = conn.query('show plancache') 25 | rows_mets = conn.query('show status extended') 26 | pc_time += time.time() - current_time 27 | pc_counter += 1 28 | with lock: 29 | for row in rows_pc: # fetch plancache 30 | query = row.QueryText 31 | db = row.Database 32 | 33 | if db != SettingsClass.memsql_db: 34 | continue 35 | 36 | commits = row.Commits if row.Commits is not None else 0 37 | rollbacks = row.Rollbacks if row.Rollbacks is not None else 0 38 | 39 | execs = commits + rollbacks 40 | 41 | if query in prev_pc: 42 | if query in stats or execs != prev_pc[query]: 43 | stats[query] = \ 44 | (execs - prev_pc[query]) * 1.0/(current_time - prev_time) 45 | 46 | prev_pc[query] = execs 47 | 48 | for row in rows_mets: # fetch db metrics 49 | var_name = row.Variable_name 50 | var_val = row.Value 51 | metrics[var_name] = var_val 52 | 53 | logger.debug("Total time spent [%g] Average Time Spent [%g]" % (pc_time, pc_time/pc_counter)) 54 | 55 | prev_time = current_time 56 | time.sleep(POLLING_PERIOD) 57 | 58 | 59 | g_plancaches = {}; 60 | 61 | def plancacheFactory(settings): 62 | if not g_plancaches.has_key(settings): 63 | g_plancaches[settings] = PlancacheStats(settings) 64 | return g_plancaches[settings]; 65 | 66 | class PlancacheBroken(Exception): 67 | pass 68 | 69 | class PlancacheStats(object): 70 | def __init__(self, SettingsClass): 71 | self.manager = multiprocessing.Manager() 72 | self.pc_dict = self.manager.dict() 73 | self.metrics = self.manager.dict() 74 | self.pc_lock = self.manager.Lock() 75 | self.pc_proc = multiprocessing.Process(target=save_plancache_loop, args=(SettingsClass, self.pc_dict, self.metrics, self.pc_lock)) 76 | self.pc_proc.start() 77 | 78 | def __del__(self): 79 | self.pc_proc.terminate() 80 | 81 | def get_metrics(self): 82 | if not self.pc_proc.is_alive(): 83 | raise PlancacheBroken 84 | try: 85 | with self.pc_lock: 86 | return self.metrics.copy() 87 | except IOError as e: 88 | raise PlancacheBroken 89 | 90 | def get_stats(self): 91 | if not self.pc_proc.is_alive(): 92 | raise PlancacheBroken 93 | try: 94 | with self.pc_lock: 95 | return self.pc_dict.copy() 96 | except IOError as e: 97 | raise PlancacheBroken 98 | -------------------------------------------------------------------------------- /stress/static/js/bootstrap-popover.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * bootstrap-popover.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#popovers 4 | * =========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * =========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* POPOVER PUBLIC CLASS DEFINITION 27 | * =============================== */ 28 | 29 | var Popover = function ( element, options ) { 30 | this.init('popover', element, options) 31 | } 32 | 33 | 34 | /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js 35 | ========================================== */ 36 | 37 | Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, { 38 | 39 | constructor: Popover 40 | 41 | , setContent: function () { 42 | var $tip = this.tip() 43 | , title = this.getTitle() 44 | , content = this.getContent() 45 | 46 | $tip.find('.popover-title')[this.isHTML(title) ? 'html' : 'text'](title) 47 | $tip.find('.popover-content > *')[this.isHTML(content) ? 'html' : 'text'](content) 48 | 49 | $tip.removeClass('fade top bottom left right in') 50 | } 51 | 52 | , hasContent: function () { 53 | return this.getTitle() || this.getContent() 54 | } 55 | 56 | , getContent: function () { 57 | var content 58 | , $e = this.$element 59 | , o = this.options 60 | 61 | content = $e.attr('data-content') 62 | || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) 63 | 64 | return content 65 | } 66 | 67 | , tip: function () { 68 | if (!this.$tip) { 69 | this.$tip = $(this.options.template) 70 | } 71 | return this.$tip 72 | } 73 | 74 | }) 75 | 76 | 77 | /* POPOVER PLUGIN DEFINITION 78 | * ======================= */ 79 | 80 | $.fn.popover = function (option) { 81 | return this.each(function () { 82 | var $this = $(this) 83 | , data = $this.data('popover') 84 | , options = typeof option == 'object' && option 85 | if (!data) $this.data('popover', (data = new Popover(this, options))) 86 | if (typeof option == 'string') data[option]() 87 | }) 88 | } 89 | 90 | $.fn.popover.Constructor = Popover 91 | 92 | $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, { 93 | placement: 'right' 94 | , content: '' 95 | , template: '
Instructions: Enter SQL queries in the boxes below. To generate random input, use @ for numbers and ^ for strings (no quotation marks necessary). Adjust the dial to set the number of queries per second sent to the server.
162 | {% endif %} 163 | 182 |This will clear all queries in the current workload. Are you sure you want to continue?
210 |Someone else is currently running a simulation with this [name of the thing] server instance. While you cannot run two simulations at once, you can view the other simulation by switching to live mode. 224 |
Looks like you lost connection with the server. Please reload the page to start a new. 246 |