├── .arcconfig ├── .gitignore ├── README.md ├── runner ├── stress ├── __init__.py ├── database.py ├── plancache.py ├── query_table.py ├── server.py ├── static │ ├── css │ │ ├── bootstrap-responsive.css │ │ ├── bootstrap-responsive.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.min.css │ │ ├── codemirror.css │ │ ├── stress.css │ │ └── stress.scss │ ├── img │ │ ├── ajax-loader.gif │ │ ├── background.png │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ ├── js │ │ ├── bootstrap-dropdown.js │ │ ├── bootstrap-modal.js │ │ ├── bootstrap-popover.js │ │ ├── bootstrap-tab.js │ │ ├── bootstrap-tooltip.js │ │ ├── bootstrap.min.js │ │ ├── codemirror.js │ │ ├── common.js │ │ ├── highcharts.js │ │ ├── jqconsole-2.7.min.js │ │ ├── jquery-1.7.2.min.js │ │ ├── jquery.knob-1.1.1.js │ │ ├── live.js │ │ ├── metrics_graphs.js │ │ ├── mysql.js │ │ ├── query_dial.js │ │ ├── query_graphs.js │ │ └── stress.js │ └── lang │ │ └── sh_sql.min.js ├── templates │ └── index.html ├── test_worker.py ├── utils.py └── worker.py └── workloads ├── key_value ├── key_value.json └── key_value.sql └── video ├── video.json └── video.sql /.arcconfig: -------------------------------------------------------------------------------- 1 | { 2 | "project_id" : "workloadsimulator", 3 | "conduit_uri" : "http:\/\/grizzly.memsql.com\/api\/" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | *.data 4 | .sass-cache 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [MemSQL Workload Simulator](http://developers.memsql.com/) 2 | ==================== 3 | 4 | The MemSQL Workload Simulator lets you simulate running thousands of queries per second against the database with a custom workload. It also supports 5 | live mode, which lets you monitor the performance of a running MemSQL instance. 6 | 7 | This project is under active, continuous development. Expect updates to come frequently as the feature set is built out. If you have suggestions for features, feel free to post them to the issues section of this repository or 8 | implement them and submit them as a pull request. If you build an interesting workload, we would love to include it in the workloads directory. 9 | 10 | 11 | Requirements 12 | ------------ 13 | 14 | This guide is known to work on Ubuntu 12.04. Since it uses standard python packages and tools, it should work on other Linux distributions as well. 15 | It assumes that you already have a MemSQL server set up and running on 127.0.0.1:3306 -- refer to [developers.memsql.com](http://developers.memsql.com) 16 | for more info about running MemSQL. 17 | 18 | All command-line instructions assume that your working directory is the original location of this README file. 19 | 20 | 21 | Installing the workload simulator 22 | --------------------------------- 23 | 24 | + **Make sure you have python dev tools and pip installed** 25 | 26 | ``` 27 | sudo apt-get install python-dev python-setuptools libmysqlclient-dev 28 | sudo easy_install pip 29 | ``` 30 | 31 | + **Install dependencies** 32 | 33 | ``` 34 | sudo pip install flask sqlalchemy MySQL-python simplejson 35 | ``` 36 | 37 | Running the workload simulator 38 | -------------------- 39 | 40 | + **Start the server** 41 | 42 | ``` 43 | python runner 44 | ``` 45 | 46 | + **You can stop it by sending SIGTERM to the parent python process. The easiest way to do this is to type `Ctrl-\` (control backslash) on your terminal.** 47 | 48 | 49 | + **Open http://localhost:9000 in a browser** 50 | 51 | This is the "active mode" which allows you to run queries against the MemSQL server. Let's start with a simple example. 52 | 53 | + **Install the key-value example database and table** 54 | 55 | ``` 56 | mysql -h 127.0.0.1 -u root -P 3306 -vv < workloads/key_value/key_value.sql 57 | ``` 58 | 59 | + **Load the key-value workload** 60 | 61 | On http://localhost:9000, click Load Workload and choose the file `workloads/key_value/key_value.json` 62 | 63 | You will see three types of queries appear in a grid. Next to each there is a dial indicating how many times per second we will try to execute a query of that type. 64 | 65 | + **Hit PLAY** 66 | 67 | The simulator starts executing queries against the database. The dials start showing the actual number of queries per second for each type of query. On the right you can see a real-time graph of the total number of queries per second being processed by MemSQL. 68 | 69 | + **Console** 70 | 71 | At any time, you can use the console on the right to run individual queries. Running your own queries may be useful for setting up schemas, inspecting the tables, and checking syntax. 72 | 73 | + **Running with MySQL** 74 | 75 | This mode does not use any MemSQL specific query constructs, so you can use it to run heavy workloads against MySQL as well. 76 | 77 | 78 | Live mode 79 | ---------------------- 80 | 81 | + **Navigate to http://localhost:9000/live** 82 | 83 | This is a real-time monitor of all queries processed by MemSQL. It is refreshed every second. On the bottom right there is a pie chart detailing the memory used by MemSQL. This mode can only be used with MemSQL. 84 | 85 | 86 | Getting advanced 87 | ------------------------ 88 | 89 | + **Try creating your own workload in active mode.** 90 | 91 | Generate random numbers and strings with @ and ^, respectively. For example, to insert a random integer value in the key-value example, use the query `INSERT INTO t (k, v) VALUES (@, @);` 92 | 93 | + **Check out the sample workload from the video (https://vimeo.com/44087431)** 94 | 95 | The sql schema is in `workloads/video/video.sql`. The workload is in `workloads/video/video.json` 96 | 97 | 98 | Troubleshooting 99 | ------------------- 100 | 101 | If a message pops up saying, "something went wrong...", you've run into an unhandled error. If the problem persists, restart the server from the command line by interrupting with Ctrl-\ and running "python runner". 102 | -------------------------------------------------------------------------------- /runner: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import stress.server 4 | stress.server.main() 5 | -------------------------------------------------------------------------------- /stress/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memsql/workload-simulator/8dcdc345661d91c597461db307ea047556d88e82/stress/__init__.py -------------------------------------------------------------------------------- /stress/database.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2009 Facebook 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | """A lightweight wrapper around MySQLdb.""" 18 | 19 | import copy 20 | import datetime 21 | import MySQLdb.constants 22 | import MySQLdb.converters 23 | import MySQLdb.cursors 24 | import itertools 25 | import logging 26 | import optparse 27 | import os 28 | import random 29 | import time 30 | import sys 31 | 32 | def connect(*args, **kwargs): 33 | return Connection(*args, **kwargs) 34 | 35 | class Connection(object): 36 | """A lightweight wrapper around MySQLdb DB-API connections. 37 | 38 | The main value we provide is wrapping rows in a dict/object so that 39 | columns can be accessed by name. Typical usage:: 40 | 41 | db = database.Connection("localhost", "mydatabase") 42 | for article in db.query("SELECT * FROM articles"): 43 | print article.title 44 | 45 | Cursors are hidden by the implementation, but other than that, the methods 46 | are very similar to the DB-API. 47 | 48 | We explicitly set the timezone to UTC and the character encoding to 49 | UTF-8 on all connections to avoid time zone and encoding errors. 50 | """ 51 | def __init__(self, host, database, user=None, password=None, 52 | max_idle_time=7*3600): 53 | self.host = host 54 | self.database = database 55 | self.max_idle_time = max_idle_time 56 | self.print_queries = False 57 | 58 | args = dict(conv=CONVERSIONS, use_unicode=True, charset="utf8", 59 | db=database, init_command='SET time_zone = "+0:00"', 60 | sql_mode="ANSI") 61 | if user is not None: 62 | args["user"] = user 63 | if password is not None: 64 | args["passwd"] = password 65 | 66 | # We accept a path to a MySQL socket file or a host(:port) string 67 | if "/" in host: 68 | args["unix_socket"] = host 69 | else: 70 | self.socket = None 71 | pair = host.split(":") 72 | if len(pair) == 2: 73 | args["host"] = pair[0] 74 | args["port"] = int(pair[1]) 75 | else: 76 | args["host"] = host 77 | args["port"] = 3306 78 | 79 | self._db = None 80 | self._db_args = args 81 | self._last_use_time = time.time() 82 | self.reconnect() 83 | 84 | def __del__(self): 85 | self.close() 86 | 87 | def set_print_queries(self, print_queries): 88 | self.print_queries = print_queries 89 | 90 | def close(self): 91 | """Closes this database connection.""" 92 | if getattr(self, "_db", None) is not None: 93 | self._db.close() 94 | self._db = None 95 | 96 | def reconnect(self): 97 | """Closes the existing database connection and re-opens it.""" 98 | self.close() 99 | self._db = MySQLdb.connect(**self._db_args) 100 | self._db.autocommit(True) 101 | 102 | def iter(self, query, *parameters): 103 | """Returns an iterator for the given query and parameters.""" 104 | self._ensure_connected() 105 | cursor = MySQLdb.cursors.SSCursor(self._db) 106 | try: 107 | self._execute(cursor, query, parameters) 108 | column_names = [d[0] for d in cursor.description] 109 | for row in cursor: 110 | yield Row(zip(column_names, row)) 111 | finally: 112 | cursor.close() 113 | 114 | def query(self, query, *parameters): 115 | """Returns a row list for the given query and parameters.""" 116 | cursor = self._cursor() 117 | try: 118 | self._execute(cursor, query, parameters) 119 | column_names = [d[0] for d in cursor.description] 120 | return [Row(itertools.izip(column_names, row)) for row in cursor] 121 | except TypeError as e: 122 | return cursor.lastrowid 123 | finally: 124 | cursor.close() 125 | 126 | def get(self, query, *parameters): 127 | """Returns the first row returned for the given query.""" 128 | rows = self.query(query, *parameters) 129 | if not rows: 130 | return None 131 | elif len(rows) > 1: 132 | raise Exception("Multiple rows returned for Database.get() query") 133 | else: 134 | return rows[0] 135 | 136 | # rowcount is a more reasonable default return value than lastrowid, 137 | # but for historical compatibility execute() must return lastrowid. 138 | def execute(self, query, *parameters): 139 | """Executes the given query, returning the lastrowid from the query.""" 140 | return self.execute_lastrowid(query, *parameters) 141 | 142 | def execute_lastrowid(self, query, *parameters): 143 | """Executes the given query, returning the lastrowid from the query.""" 144 | cursor = self._cursor() 145 | try: 146 | self._execute(cursor, query, parameters) 147 | return cursor.lastrowid 148 | finally: 149 | cursor.close() 150 | 151 | def execute_rowcount(self, query, *parameters): 152 | """Executes the given query, returning the rowcount from the query.""" 153 | cursor = self._cursor() 154 | try: 155 | self._execute(cursor, query, parameters) 156 | return cursor.rowcount 157 | finally: 158 | cursor.close() 159 | 160 | def executemany(self, query, parameters): 161 | """Executes the given query against all the given param sequences. 162 | 163 | We return the lastrowid from the query. 164 | """ 165 | return self.executemany_lastrowid(query, parameters) 166 | 167 | def executemany_lastrowid(self, query, parameters): 168 | """Executes the given query against all the given param sequences. 169 | 170 | We return the lastrowid from the query. 171 | """ 172 | cursor = self._cursor() 173 | try: 174 | cursor.executemany(query, parameters) 175 | return cursor.lastrowid 176 | finally: 177 | cursor.close() 178 | 179 | def executemany_rowcount(self, query, parameters): 180 | """Executes the given query against all the given param sequences. 181 | 182 | We return the rowcount from the query. 183 | """ 184 | cursor = self._cursor() 185 | try: 186 | cursor.executemany(query, parameters) 187 | return cursor.rowcount 188 | finally: 189 | cursor.close() 190 | 191 | def _ensure_connected(self): 192 | # Mysql by default closes client connections that are idle for 193 | # 8 hours, but the client library does not report this fact until 194 | # you try to perform a query and it fails. Protect against this 195 | # case by preemptively closing and reopening the connection 196 | # if it has been idle for too long (7 hours by default). 197 | if (self._db is None or 198 | (time.time() - self._last_use_time > self.max_idle_time)): 199 | self.reconnect() 200 | self._last_use_time = time.time() 201 | 202 | def _cursor(self): 203 | self._ensure_connected() 204 | return self._db.cursor() 205 | 206 | def _execute(self, cursor, query, parameters): 207 | if self.print_queries: 208 | print "%s;" % query 209 | 210 | try: 211 | return cursor.execute(query, parameters) 212 | except OperationalError: 213 | self.close() 214 | raise 215 | 216 | 217 | class Row(dict): 218 | """A dict that allows for object-like property access syntax.""" 219 | def __getattr__(self, name): 220 | try: 221 | return self[name] 222 | except KeyError: 223 | raise AttributeError(name) 224 | 225 | 226 | # Fix the access conversions to properly recognize unicode/binary 227 | FIELD_TYPE = MySQLdb.constants.FIELD_TYPE 228 | FLAG = MySQLdb.constants.FLAG 229 | CONVERSIONS = copy.copy(MySQLdb.converters.conversions) 230 | 231 | field_types = [FIELD_TYPE.BLOB, FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING] 232 | if 'VARCHAR' in vars(FIELD_TYPE): 233 | field_types.append(FIELD_TYPE.VARCHAR) 234 | 235 | for field_type in field_types: 236 | CONVERSIONS[field_type] = [(FLAG.BINARY, str)] + CONVERSIONS[field_type] 237 | 238 | 239 | # Alias some common MySQL exceptions 240 | IntegrityError = MySQLdb.IntegrityError 241 | OperationalError = MySQLdb.OperationalError 242 | MySQLError = MySQLdb.MySQLError 243 | -------------------------------------------------------------------------------- /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/query_table.py: -------------------------------------------------------------------------------- 1 | from bisect import bisect_right 2 | import datetime 3 | import logging 4 | import os 5 | import random 6 | import re 7 | 8 | logger = logging.getLogger('query_table') 9 | logger.setLevel(logging.INFO) 10 | 11 | STRING_TOKEN = "^" 12 | NUMBER_TOKEN = "@" 13 | 14 | # Can use anything after G for arbitrary tokens 15 | TIME_TOKEN = "@T" 16 | MARK_TOKEN = "@M" 17 | 18 | # These are ordered very specifically so that 19 | # if i < j, SPLIT_TOKENS[i] is not a prefix of 20 | # SPLIT_TOKENS[j] 21 | ALL_PARAM_TOKENS = ["@M", "@T", "@", "^"] 22 | 23 | # Final Types 24 | OLD_STRING = 0 25 | NEW_STRING = 1 26 | OLD_NUMBER = 2 27 | NEW_NUMBER = 3 28 | 29 | def split_and_fold(s, c): 30 | build = [] 31 | spl = re.split(re.escape(c), s, flags=re.IGNORECASE) 32 | for i, piece in enumerate(spl): 33 | if i > 0: 34 | build.append(c) 35 | build.append(piece) 36 | 37 | return build 38 | 39 | def tokenize_query(query): 40 | string_list = [query] 41 | for i, c in enumerate(ALL_PARAM_TOKENS): 42 | build = [] 43 | for j, s in enumerate(string_list): 44 | if s in ALL_PARAM_TOKENS[:i]: 45 | build.append(s) 46 | else: 47 | build.extend(split_and_fold(s, c)) 48 | string_list = build 49 | return string_list 50 | 51 | # Very simple heuristics. Uses old numbers with select queries 52 | def parse_query(qlist): 53 | qtype = qlist[0].lstrip().split()[0].lower() 54 | ret = [] 55 | if qtype == "select": 56 | for q in qlist: 57 | if q == STRING_TOKEN: 58 | ret.append(OLD_STRING) 59 | elif q == NUMBER_TOKEN: 60 | ret.append(OLD_NUMBER) 61 | else: 62 | ret.append(q) 63 | else: 64 | for q in qlist: 65 | if q == STRING_TOKEN: 66 | ret.append(NEW_STRING) 67 | elif q == NUMBER_TOKEN: 68 | ret.append(NEW_NUMBER) 69 | else: 70 | ret.append(q) 71 | 72 | return ret 73 | 74 | def find_gt(a, x): 75 | 'Find leftmost value greater than x' 76 | i = bisect_right(a, x) 77 | return i 78 | 79 | counter = 0 80 | def get_new_random_number(prefix): 81 | global counter 82 | ret = counter 83 | counter += 1 84 | return prefix + str(ret) 85 | 86 | def get_old_random_number(prefix): 87 | ret = random.randint(1, max(counter, 2)) 88 | return prefix + str(ret) 89 | 90 | def get_random_string(prefix, n): 91 | global counter 92 | ret = counter 93 | counter += 1 94 | s = prefix + "|" + str(ret) 95 | return s[:n] 96 | 97 | def get_global_marker(prefix): 98 | return prefix 99 | 100 | global_time = datetime.datetime.now() 101 | def get_random_time(): 102 | global global_time 103 | current_time = int("%.4d%.2d%.2d%.2d%.2d%.2d" % global_time.timetuple()[:6]) 104 | global_time += datetime.timedelta(seconds = 1) 105 | return str(current_time) 106 | 107 | def generate_query_function(query): 108 | qlist = tokenize_query(query) 109 | qlist = parse_query(qlist) 110 | build = [] 111 | for q in qlist: 112 | if q == OLD_STRING or q == NEW_STRING: 113 | build.append(repr("'")) 114 | build.append("get_random_string(prefix, 32)") 115 | build.append(repr("'")) 116 | elif q == OLD_NUMBER: 117 | build.append("get_old_random_number(prefix)") 118 | elif q == NEW_NUMBER: 119 | build.append("get_new_random_number(prefix)") 120 | elif q == TIME_TOKEN: 121 | build.append("get_random_time()") 122 | elif q == MARK_TOKEN: 123 | build.append("get_global_marker(prefix)") 124 | else: 125 | build.append(repr(q)) 126 | ret = ','.join(build) 127 | query_f_s = 'lambda prefix: "".join([%s])' % ret 128 | query_f = eval(query_f_s) 129 | return query_f_s, query_f 130 | 131 | class QueryGen(object): 132 | def __init__(self, query_id, query, stats): 133 | self.query_id = query_id 134 | self.queryf_str, self.query_f = generate_query_function(query) 135 | self.stats = stats 136 | 137 | class QueryTable(object): 138 | def __init__(self, workload, qps_array, qps_query_table): 139 | self.probabilities = [] 140 | self.query_objects = [] 141 | 142 | self.total_qps = sum([int(info['qps']) for info in workload.values()]) 143 | self.max_query_id = max([int(k) for k in workload.keys()]) 144 | 145 | workload_l = workload.items() 146 | workload_l.sort(key=lambda (q, i) : i['qps']) 147 | 148 | qps_prob = 0.0 149 | for query_id, info in workload_l: 150 | if info['qps'] == 0: 151 | qps_prob = 0 152 | else: 153 | qps_prob += (info['qps']*1.0)/self.total_qps 154 | 155 | stats = qps_query_table[query_id] 156 | 157 | self.probabilities.append(qps_prob) 158 | self.query_objects.append(QueryGen(query_id, info['query'], stats)) 159 | 160 | self.qps = qps_array 161 | 162 | def is_empty(self): 163 | return self.total_qps == 0 164 | 165 | def get_random_query(self): 166 | i = find_gt(self.probabilities, random.random()) 167 | return self.query_objects[i] 168 | -------------------------------------------------------------------------------- /stress/server.py: -------------------------------------------------------------------------------- 1 | """Add a description here 2 | """ 3 | 4 | from flask import Flask, request, session, g, redirect, url_for, \ 5 | abort, render_template, flash, make_response 6 | 7 | from cStringIO import StringIO 8 | import logging 9 | import multiprocessing 10 | import re 11 | import subprocess 12 | import sys 13 | import _mysql 14 | import string 15 | 16 | import database 17 | import plancache 18 | import query_table 19 | import utils 20 | import worker 21 | 22 | import random 23 | from datetime import datetime, timedelta 24 | 25 | try: 26 | import simplejson as json 27 | except ImportError as e: 28 | import json 29 | 30 | logger = logging.getLogger('server') 31 | logger.setLevel(logging.DEBUG) 32 | 33 | DEBUG = True 34 | SECRET_KEY = 'foobar' 35 | PING_TIMEOUT = 10 # in seconds 36 | VERY_LONG_AGO = datetime(1990, 03, 02) # send me a bday card 37 | 38 | app = Flask(__name__) 39 | app.config.from_object(__name__) 40 | 41 | g_workload = None 42 | g_workers = None 43 | g_last_ping = VERY_LONG_AGO 44 | g_settings = None 45 | 46 | # immutable, at least in theory 47 | class Settings: 48 | def __init__(self, d): 49 | self.memsql_host = None 50 | self.memsql_port = None 51 | self.memsql_user = None 52 | self.memsql_pass = None 53 | self.memsql_db = None 54 | self.workers = None 55 | change = False 56 | try: 57 | for key, setting in d.items(): 58 | if not hasattr(self, key): 59 | continue 60 | if isinstance(setting, str): 61 | setting = setting.lstrip().rstrip() 62 | if key in ['memsql_port', 'workers']: 63 | setting = int(setting) 64 | if getattr(self, key) != setting: 65 | change = True 66 | setattr(self, key, setting) 67 | except ValueError as e: 68 | raise ServerException('Invalid settings. Please verify port number and workers processes', ServerException.ER_SETTINGS) 69 | 70 | def __eq__(self, another): 71 | return hasattr(another, 'memsql_host') and self.memsql_host == another.memsql_host \ 72 | and hasattr(another, 'memsql_port') and self.memsql_port == another.memsql_port \ 73 | and hasattr(another, 'memsql_user') and self.memsql_user == another.memsql_user \ 74 | and hasattr(another, 'memsql_pass') and self.memsql_pass == another.memsql_pass \ 75 | and hasattr(another, 'memsql_db') and self.memsql_db == another.memsql_db \ 76 | and hasattr(another, 'workers') and self.workers == another.workers 77 | 78 | def __hash__(self): 79 | return hash(frozenset(self.get_dict().items())); 80 | 81 | def get_db_conn(self): 82 | return utils.get_db_conn(self.memsql_host, self.memsql_port, \ 83 | self.memsql_user, self.memsql_pass, self.memsql_db) 84 | 85 | def get_dict(self): 86 | return { 87 | 'memsql_host' : self.memsql_host, 88 | 'memsql_port' : self.memsql_port, 89 | 'memsql_user' : self.memsql_user, 90 | 'memsql_pass' : self.memsql_pass, 91 | 'memsql_db' : self.memsql_db, 92 | 'workers' : self.workers 93 | } 94 | 95 | def get_client_arguments(self): 96 | return { 97 | 'host' : self.memsql_host, 98 | 'port' : self.memsql_port, 99 | 'user' : self.memsql_user, 100 | 'passwd' : self.memsql_pass, 101 | 'db' : self.memsql_db, 102 | 'unix_socket' : '/tmp/memsql.sock' 103 | } 104 | 105 | class ServerException(Exception): 106 | ER_UNKNOWN = 0 107 | ER_DBCONN = 1 108 | ER_DBNAME = 2 109 | ER_JS = 3 110 | ER_QUERY = 4 111 | ER_SETTINGS = 5 112 | 113 | def __init__(self, message, n=ER_UNKNOWN): 114 | self.message = message 115 | self.n = n 116 | 117 | def to_dict(self): 118 | return {'errno' : self.n, 'message' : self.message} 119 | 120 | def __str__(self): 121 | return "[%d] %s" % self.message 122 | 123 | class WorkloadException(ServerException): 124 | def __init__(self, id_to_error): 125 | self.id_to_error = id_to_error 126 | 127 | def to_dict(self): 128 | ret = { 129 | 'errno' : ServerException.ER_QUERY, 130 | 'message' : 'Invalid queries in workload', 131 | 'query_map' : self.id_to_error 132 | } 133 | return ret 134 | 135 | def format_response(running, error=None, **kwargs): 136 | ret = {'running' : running} 137 | if error: 138 | assert isinstance(error, ServerException) 139 | ret['error'] = error.to_dict() 140 | 141 | ret.update(kwargs) 142 | return json.dumps(ret) 143 | 144 | def validate_workload(): 145 | global g_workload 146 | global g_workers 147 | global g_settings 148 | 149 | try: 150 | conn = _mysql.connect(**g_settings.get_client_arguments()) 151 | 152 | try: 153 | qt = query_table.QueryTable(g_workload, g_workers.qps_array, g_workers.qps_query_table) 154 | failed_queries = {} 155 | for q in qt.query_objects: 156 | query = q.query_f('0') 157 | logger.debug("Validating [%s]" % query) 158 | try: 159 | conn.query(query) 160 | conn.store_result() 161 | except _mysql.MySQLError as e: 162 | n,m = e 163 | if n in worker.uncaught_errors: 164 | failed_queries[q.query_id] = str(e) 165 | else: 166 | logger.debug("Uncaught [%d] : %s" % (n, m)) 167 | 168 | if len(failed_queries) > 0: 169 | raise WorkloadException(failed_queries) 170 | 171 | if qt.is_empty(): 172 | raise ServerException("Workload is empty. Adjust the dial to indicate how many times to run the query per-second.", ServerException.ER_JS) 173 | 174 | logger.debug("VALID WORKLOAD!") 175 | finally: 176 | conn.close() 177 | except _mysql.MySQLError as e: 178 | check_connection_settings(g_settings) 179 | raise ServerException('Unable to validate workload. Could not connect to database.', ServerException.ER_UNKNOWN) 180 | 181 | def check_connection_settings(settings): 182 | try: 183 | conn = settings.get_db_conn() 184 | except database.MySQLError as e: 185 | n,m = e 186 | if n == worker.ER_DB_DNE: 187 | raise ServerException(str(e), ServerException.ER_DBNAME) 188 | else: 189 | raise ServerException(str(e), ServerException.ER_DBCONN) 190 | 191 | try: 192 | conn.query('show tables') 193 | except database.MySQLError as e: 194 | raise ServerException(str(e), ServerException.ER_DBNAME) 195 | finally: 196 | conn.close() 197 | 198 | # Raises a ServerException with a human readable error if the workers 199 | # aren't in tip-top shape. 200 | def check_workers(): 201 | global g_workers 202 | global g_settings 203 | 204 | if not g_workers.is_alive(): 205 | check_connection_settings(g_settings) 206 | raise ServerException("Unable to initialize workers.") 207 | 208 | logger.debug("Checked workers PASSED") 209 | 210 | def reset_workers(): 211 | global g_workers 212 | global g_settings 213 | 214 | if g_workers is not None: 215 | g_workers.clear() 216 | logger.info("Creating %d workers", g_settings.workers) 217 | g_workers = worker.WorkerPool(g_settings) 218 | 219 | @app.route('/', methods=['GET']) 220 | def render_index(): 221 | global g_last_ping 222 | global g_settings 223 | 224 | if (datetime.now() - g_last_ping).seconds <= PING_TIMEOUT: 225 | return redirect(url_for('get_live_page', redirected=True)); 226 | else: 227 | g_last_ping = datetime.now(); 228 | return render_template('index.html', settings=g_settings, live=False) 229 | 230 | @app.route('/workload', methods=['POST']) 231 | def submit_workload(): 232 | global g_workers 233 | global g_workload 234 | global g_settings 235 | 236 | try: 237 | g_workload = {} 238 | 239 | for query_id, info in json.loads(request.values.get('workload')).items(): 240 | try: 241 | info['qps'] = int(info['qps']) 242 | except ValueError as e: 243 | info['qps'] = int(float(info['qps'])) 244 | g_workload[int(query_id)] = info 245 | if len(g_workload) == 0: 246 | raise ServerException("The workload table is empty. Use the workspace below to add new queries.", \ 247 | ServerException.ER_JS) 248 | 249 | g_settings = Settings(json.loads(request.values.get('settings'))) 250 | if g_workers is None \ 251 | or not g_workers.is_alive() \ 252 | or g_workers.settings_dict != g_settings.get_dict(): 253 | reset_workers() 254 | 255 | check_workers() 256 | 257 | validate_workload() 258 | 259 | if not g_workers.pause(): 260 | raise ServerException("Unknown Error- workers failed") 261 | 262 | g_workers.send_workload(g_workload) 263 | return format_response(True) 264 | except ServerException as e: 265 | return format_response(False, e) 266 | 267 | @app.route('/ping', methods=['POST']) 268 | def ping(): 269 | global g_last_ping 270 | 271 | if (datetime.now() - g_last_ping).seconds <= PING_TIMEOUT: 272 | g_last_ping = datetime.now() 273 | return 'OK' 274 | else: 275 | return 'Timeout' 276 | 277 | @app.route('/unload', methods=['PUT']) 278 | def unload(): 279 | global g_last_ping 280 | g_last_ping = VERY_LONG_AGO 281 | return '' 282 | 283 | @app.route('/pause', methods=['POST']) 284 | def pause(): 285 | global g_workers 286 | 287 | try: 288 | if g_workers is None or g_workers.paused: 289 | return format_response(False) 290 | 291 | check_workers() 292 | 293 | g_workers.pause() 294 | assert g_workers.paused 295 | return format_response(False) 296 | except ServerException as e: 297 | return format_response(False, e) 298 | 299 | stat_check_count = 0 300 | full_check_period = 10 # should work out to once per second 301 | @app.route('/stats', methods=['get']) 302 | def get_stats(): 303 | global g_workers 304 | global g_workload 305 | global stat_check_count 306 | global full_check_period 307 | 308 | try: 309 | if g_workers is None: 310 | return format_response(False) 311 | 312 | if stat_check_count % full_check_period == 0: 313 | check_workers() 314 | 315 | stat_check_count += 1 316 | 317 | ret = {} 318 | for query_id, info in g_workload.items(): 319 | ret[query_id] = max(0, g_workers.qps_array[query_id]) 320 | 321 | return format_response(True, None, stats=ret) 322 | except ServerException as e: 323 | return format_response(False, e) 324 | 325 | 326 | @app.route('/status', methods=['GET']) 327 | def get_status(): 328 | global g_workers 329 | global g_workload 330 | 331 | if g_workers is None: 332 | return format_response(running=False, error=None) 333 | 334 | try: 335 | check_workers() 336 | running = not g_workers.paused 337 | return format_response(running=running, error=None) 338 | except ServerException as e: 339 | return format_response(False, e) 340 | 341 | @app.route('/live', methods=['GET']) 342 | def get_live_page(): 343 | global g_settings 344 | redirected = request.values.get('redirected') 345 | return render_template('index.html', settings=g_settings, live=True, redirected=redirected) 346 | 347 | 348 | @app.route('/live/stats', methods=['GET']) 349 | def get_live_stats(): 350 | global g_plancaches 351 | 352 | settings = Settings(json.loads(request.values.get('settings', '{}'))) 353 | my_plancache = plancache.plancacheFactory(settings) 354 | 355 | try: 356 | return format_response(True, None, plancache=json.dumps(my_plancache.get_stats()), metrics=json.dumps(my_plancache.get_metrics())) 357 | except plancache.PlancacheBroken: 358 | # broken pipe to worker process- something's up 359 | try: 360 | check_connection_settings(settings) 361 | raise ServerException("Unable to access plancache") 362 | except ServerException as e: 363 | return format_response(False, e) 364 | 365 | def get_use_db(cmd): 366 | cmd = cmd.lstrip().rstrip() 367 | match = re.search(r'^use\s+([^\s]*)\s*;$', cmd) 368 | if match: 369 | return match.group(1) 370 | else: 371 | None 372 | 373 | @app.route('/sql', methods=['GET']) 374 | def run_sql_command(): 375 | print 'SQL COMMAND' 376 | print request.values.get('settings') 377 | 378 | try: 379 | settings = Settings(json.loads(request.values.get('settings'))) 380 | 381 | sql_command = re.sub(r'"', r'\\"', request.values.get('command')) 382 | 383 | args = settings.get_client_arguments() 384 | 385 | while True: 386 | command = 'mysql -h %(host)s --socket=%(unix_socket)s --port=%(port)d -u %(user)s --password=%(passwd)s %(db)s --table -vvv;' % args 387 | p = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 388 | stdout, stderr = p.communicate(sql_command) 389 | exit_code = p.returncode 390 | if (string.count(stdout, '\n') <= 100): 391 | print stdout, stderr 392 | else: 393 | print 'Output too long to print', stderr 394 | 395 | ret = {} 396 | if exit_code == 0: 397 | output = stdout 398 | try: 399 | output = re.split(r'^--------------$', output, maxsplit=3, flags=re.MULTILINE)[2] 400 | except IndexError as e: 401 | pass 402 | output = output.split('Bye')[0] 403 | output = output.lstrip().rstrip() 404 | if output != '': 405 | output = output + "\n" 406 | db_name = get_use_db(sql_command) 407 | if db_name: 408 | output = "Database Changed" 409 | ret['db'] = db_name 410 | break 411 | else: 412 | output = stderr 413 | if re.search(r'^ERROR 1049', output) and args['db'] != '': 414 | args['db'] = '' 415 | else: 416 | if re.search(r'^ERROR 2003', output): # can't connect to mysql 417 | raise ServerException(output, ServerException.ER_DBCONN) 418 | else: 419 | break 420 | 421 | ret['output'] = output 422 | return json.dumps(ret) 423 | except ServerException as e: 424 | return format_response(False, e); 425 | 426 | @app.route('/save', methods=['GET']) 427 | def save_session(): 428 | settings = request.values.get('settings', None) 429 | if settings: 430 | save_settings = json.loads(settings) 431 | else: 432 | save_session = Settings(json.loads(settings)).get_dict() 433 | 434 | workload = request.values.get('workload', None) 435 | if workload: 436 | save_workload = json.loads(workload) 437 | else: 438 | save_workload = g_workload 439 | 440 | contents = json.dumps({'settings' : save_settings, 'workload' : save_workload}) 441 | headers = {'Content-Type' : 'application/x-download', 'Content-Disposition' : 'attachment;filename=workload.json'} 442 | print contents 443 | return make_response((contents, 200, headers)) 444 | 445 | @app.route('/kill', methods=['GET']) 446 | def kill(): 447 | global g_workers 448 | global g_plancaches 449 | 450 | if g_workers: 451 | g_workers.clear() 452 | if g_plancaches: 453 | del g_plancaches 454 | sys.exit() 455 | 456 | def main(): 457 | global g_settings 458 | 459 | options = utils.get_options() 460 | 461 | g_settings = Settings({}) 462 | g_settings.memsql_host = options.memsql_host 463 | g_settings.memsql_port = options.memsql_port 464 | g_settings.memsql_user = options.memsql_user 465 | g_settings.memsql_pass = options.memsql_pass 466 | g_settings.memsql_db = options.memsql_db 467 | g_settings.workers = options.workers 468 | 469 | app.run(debug=True, port=options.server_port, host='0.0.0.0') 470 | 471 | if __name__ == '__main__': 472 | main() 473 | -------------------------------------------------------------------------------- /stress/static/css/bootstrap-responsive.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.0.3 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | 11 | .clearfix { 12 | *zoom: 1; 13 | } 14 | 15 | .clearfix:before, 16 | .clearfix:after { 17 | display: table; 18 | content: ""; 19 | } 20 | 21 | .clearfix:after { 22 | clear: both; 23 | } 24 | 25 | .hide-text { 26 | font: 0/0 a; 27 | color: transparent; 28 | text-shadow: none; 29 | background-color: transparent; 30 | border: 0; 31 | } 32 | 33 | .input-block-level { 34 | display: block; 35 | width: 100%; 36 | min-height: 28px; 37 | -webkit-box-sizing: border-box; 38 | -moz-box-sizing: border-box; 39 | -ms-box-sizing: border-box; 40 | box-sizing: border-box; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | visibility: hidden; 46 | } 47 | 48 | .visible-phone { 49 | display: none !important; 50 | } 51 | 52 | .visible-tablet { 53 | display: none !important; 54 | } 55 | 56 | .hidden-desktop { 57 | display: none !important; 58 | } 59 | 60 | @media (max-width: 767px) { 61 | .visible-phone { 62 | display: inherit !important; 63 | } 64 | .hidden-phone { 65 | display: none !important; 66 | } 67 | .hidden-desktop { 68 | display: inherit !important; 69 | } 70 | .visible-desktop { 71 | display: none !important; 72 | } 73 | } 74 | 75 | @media (min-width: 768px) and (max-width: 979px) { 76 | .visible-tablet { 77 | display: inherit !important; 78 | } 79 | .hidden-tablet { 80 | display: none !important; 81 | } 82 | .hidden-desktop { 83 | display: inherit !important; 84 | } 85 | .visible-desktop { 86 | display: none !important ; 87 | } 88 | } 89 | 90 | @media (max-width: 480px) { 91 | .nav-collapse { 92 | -webkit-transform: translate3d(0, 0, 0); 93 | } 94 | .page-header h1 small { 95 | display: block; 96 | line-height: 18px; 97 | } 98 | input[type="checkbox"], 99 | input[type="radio"] { 100 | border: 1px solid #ccc; 101 | } 102 | .form-horizontal .control-group > label { 103 | float: none; 104 | width: auto; 105 | padding-top: 0; 106 | text-align: left; 107 | } 108 | .form-horizontal .controls { 109 | margin-left: 0; 110 | } 111 | .form-horizontal .control-list { 112 | padding-top: 0; 113 | } 114 | .form-horizontal .form-actions { 115 | padding-right: 10px; 116 | padding-left: 10px; 117 | } 118 | .modal { 119 | position: absolute; 120 | top: 10px; 121 | right: 10px; 122 | left: 10px; 123 | width: auto; 124 | margin: 0; 125 | } 126 | .modal.fade.in { 127 | top: auto; 128 | } 129 | .modal-header .close { 130 | padding: 10px; 131 | margin: -10px; 132 | } 133 | .carousel-caption { 134 | position: static; 135 | } 136 | } 137 | 138 | @media (max-width: 767px) { 139 | body { 140 | padding-right: 20px; 141 | padding-left: 20px; 142 | } 143 | .navbar-fixed-top, 144 | .navbar-fixed-bottom { 145 | margin-right: -20px; 146 | margin-left: -20px; 147 | } 148 | .container-fluid { 149 | padding: 0; 150 | } 151 | .dl-horizontal dt { 152 | float: none; 153 | width: auto; 154 | clear: none; 155 | text-align: left; 156 | } 157 | .dl-horizontal dd { 158 | margin-left: 0; 159 | } 160 | .container { 161 | width: auto; 162 | } 163 | .row-fluid { 164 | width: 100%; 165 | } 166 | .row, 167 | .thumbnails { 168 | margin-left: 0; 169 | } 170 | [class*="span"], 171 | .row-fluid [class*="span"] { 172 | display: block; 173 | float: none; 174 | width: auto; 175 | margin-left: 0; 176 | } 177 | .input-large, 178 | .input-xlarge, 179 | .input-xxlarge, 180 | input[class*="span"], 181 | select[class*="span"], 182 | textarea[class*="span"], 183 | .uneditable-input { 184 | display: block; 185 | width: 100%; 186 | min-height: 28px; 187 | -webkit-box-sizing: border-box; 188 | -moz-box-sizing: border-box; 189 | -ms-box-sizing: border-box; 190 | box-sizing: border-box; 191 | } 192 | .input-prepend input, 193 | .input-append input, 194 | .input-prepend input[class*="span"], 195 | .input-append input[class*="span"] { 196 | display: inline-block; 197 | width: auto; 198 | } 199 | } 200 | 201 | @media (min-width: 768px) and (max-width: 979px) { 202 | .row { 203 | margin-left: -20px; 204 | *zoom: 1; 205 | } 206 | .row:before, 207 | .row:after { 208 | display: table; 209 | content: ""; 210 | } 211 | .row:after { 212 | clear: both; 213 | } 214 | [class*="span"] { 215 | float: left; 216 | margin-left: 20px; 217 | } 218 | .container, 219 | .navbar-fixed-top .container, 220 | .navbar-fixed-bottom .container { 221 | width: 724px; 222 | } 223 | .span12 { 224 | width: 724px; 225 | } 226 | .span11 { 227 | width: 662px; 228 | } 229 | .span10 { 230 | width: 600px; 231 | } 232 | .span9 { 233 | width: 538px; 234 | } 235 | .span8 { 236 | width: 476px; 237 | } 238 | .span7 { 239 | width: 414px; 240 | } 241 | .span6 { 242 | width: 352px; 243 | } 244 | .span5 { 245 | width: 290px; 246 | } 247 | .span4 { 248 | width: 228px; 249 | } 250 | .span3 { 251 | width: 166px; 252 | } 253 | .span2 { 254 | width: 104px; 255 | } 256 | .span1 { 257 | width: 42px; 258 | } 259 | .offset12 { 260 | margin-left: 764px; 261 | } 262 | .offset11 { 263 | margin-left: 702px; 264 | } 265 | .offset10 { 266 | margin-left: 640px; 267 | } 268 | .offset9 { 269 | margin-left: 578px; 270 | } 271 | .offset8 { 272 | margin-left: 516px; 273 | } 274 | .offset7 { 275 | margin-left: 454px; 276 | } 277 | .offset6 { 278 | margin-left: 392px; 279 | } 280 | .offset5 { 281 | margin-left: 330px; 282 | } 283 | .offset4 { 284 | margin-left: 268px; 285 | } 286 | .offset3 { 287 | margin-left: 206px; 288 | } 289 | .offset2 { 290 | margin-left: 144px; 291 | } 292 | .offset1 { 293 | margin-left: 82px; 294 | } 295 | .row-fluid { 296 | width: 100%; 297 | *zoom: 1; 298 | } 299 | .row-fluid:before, 300 | .row-fluid:after { 301 | display: table; 302 | content: ""; 303 | } 304 | .row-fluid:after { 305 | clear: both; 306 | } 307 | .row-fluid [class*="span"] { 308 | display: block; 309 | float: left; 310 | width: 100%; 311 | min-height: 28px; 312 | margin-left: 2.762430939%; 313 | *margin-left: 2.709239449638298%; 314 | -webkit-box-sizing: border-box; 315 | -moz-box-sizing: border-box; 316 | -ms-box-sizing: border-box; 317 | box-sizing: border-box; 318 | } 319 | .row-fluid [class*="span"]:first-child { 320 | margin-left: 0; 321 | } 322 | .row-fluid .span12 { 323 | width: 99.999999993%; 324 | *width: 99.9468085036383%; 325 | } 326 | .row-fluid .span11 { 327 | width: 91.436464082%; 328 | *width: 91.38327259263829%; 329 | } 330 | .row-fluid .span10 { 331 | width: 82.87292817100001%; 332 | *width: 82.8197366816383%; 333 | } 334 | .row-fluid .span9 { 335 | width: 74.30939226%; 336 | *width: 74.25620077063829%; 337 | } 338 | .row-fluid .span8 { 339 | width: 65.74585634900001%; 340 | *width: 65.6926648596383%; 341 | } 342 | .row-fluid .span7 { 343 | width: 57.182320438000005%; 344 | *width: 57.129128948638304%; 345 | } 346 | .row-fluid .span6 { 347 | width: 48.618784527%; 348 | *width: 48.5655930376383%; 349 | } 350 | .row-fluid .span5 { 351 | width: 40.055248616%; 352 | *width: 40.0020571266383%; 353 | } 354 | .row-fluid .span4 { 355 | width: 31.491712705%; 356 | *width: 31.4385212156383%; 357 | } 358 | .row-fluid .span3 { 359 | width: 22.928176794%; 360 | *width: 22.874985304638297%; 361 | } 362 | .row-fluid .span2 { 363 | width: 14.364640883%; 364 | *width: 14.311449393638298%; 365 | } 366 | .row-fluid .span1 { 367 | width: 5.801104972%; 368 | *width: 5.747913482638298%; 369 | } 370 | input, 371 | textarea, 372 | .uneditable-input { 373 | margin-left: 0; 374 | } 375 | input.span12, 376 | textarea.span12, 377 | .uneditable-input.span12 { 378 | width: 714px; 379 | } 380 | input.span11, 381 | textarea.span11, 382 | .uneditable-input.span11 { 383 | width: 652px; 384 | } 385 | input.span10, 386 | textarea.span10, 387 | .uneditable-input.span10 { 388 | width: 590px; 389 | } 390 | input.span9, 391 | textarea.span9, 392 | .uneditable-input.span9 { 393 | width: 528px; 394 | } 395 | input.span8, 396 | textarea.span8, 397 | .uneditable-input.span8 { 398 | width: 466px; 399 | } 400 | input.span7, 401 | textarea.span7, 402 | .uneditable-input.span7 { 403 | width: 404px; 404 | } 405 | input.span6, 406 | textarea.span6, 407 | .uneditable-input.span6 { 408 | width: 342px; 409 | } 410 | input.span5, 411 | textarea.span5, 412 | .uneditable-input.span5 { 413 | width: 280px; 414 | } 415 | input.span4, 416 | textarea.span4, 417 | .uneditable-input.span4 { 418 | width: 218px; 419 | } 420 | input.span3, 421 | textarea.span3, 422 | .uneditable-input.span3 { 423 | width: 156px; 424 | } 425 | input.span2, 426 | textarea.span2, 427 | .uneditable-input.span2 { 428 | width: 94px; 429 | } 430 | input.span1, 431 | textarea.span1, 432 | .uneditable-input.span1 { 433 | width: 32px; 434 | } 435 | } 436 | 437 | @media (min-width: 1200px) { 438 | .row { 439 | margin-left: -30px; 440 | *zoom: 1; 441 | } 442 | .row:before, 443 | .row:after { 444 | display: table; 445 | content: ""; 446 | } 447 | .row:after { 448 | clear: both; 449 | } 450 | [class*="span"] { 451 | float: left; 452 | margin-left: 30px; 453 | } 454 | .container, 455 | .navbar-fixed-top .container, 456 | .navbar-fixed-bottom .container { 457 | width: 1170px; 458 | } 459 | .span12 { 460 | width: 1170px; 461 | } 462 | .span11 { 463 | width: 1070px; 464 | } 465 | .span10 { 466 | width: 970px; 467 | } 468 | .span9 { 469 | width: 870px; 470 | } 471 | .span8 { 472 | width: 770px; 473 | } 474 | .span7 { 475 | width: 670px; 476 | } 477 | .span6 { 478 | width: 570px; 479 | } 480 | .span5 { 481 | width: 470px; 482 | } 483 | .span4 { 484 | width: 370px; 485 | } 486 | .span3 { 487 | width: 270px; 488 | } 489 | .span2 { 490 | width: 170px; 491 | } 492 | .span1 { 493 | width: 70px; 494 | } 495 | .offset12 { 496 | margin-left: 1230px; 497 | } 498 | .offset11 { 499 | margin-left: 1130px; 500 | } 501 | .offset10 { 502 | margin-left: 1030px; 503 | } 504 | .offset9 { 505 | margin-left: 930px; 506 | } 507 | .offset8 { 508 | margin-left: 830px; 509 | } 510 | .offset7 { 511 | margin-left: 730px; 512 | } 513 | .offset6 { 514 | margin-left: 630px; 515 | } 516 | .offset5 { 517 | margin-left: 530px; 518 | } 519 | .offset4 { 520 | margin-left: 430px; 521 | } 522 | .offset3 { 523 | margin-left: 330px; 524 | } 525 | .offset2 { 526 | margin-left: 230px; 527 | } 528 | .offset1 { 529 | margin-left: 130px; 530 | } 531 | .row-fluid { 532 | width: 100%; 533 | *zoom: 1; 534 | } 535 | .row-fluid:before, 536 | .row-fluid:after { 537 | display: table; 538 | content: ""; 539 | } 540 | .row-fluid:after { 541 | clear: both; 542 | } 543 | .row-fluid [class*="span"] { 544 | display: block; 545 | float: left; 546 | width: 100%; 547 | min-height: 28px; 548 | margin-left: 2.564102564%; 549 | *margin-left: 2.510911074638298%; 550 | -webkit-box-sizing: border-box; 551 | -moz-box-sizing: border-box; 552 | -ms-box-sizing: border-box; 553 | box-sizing: border-box; 554 | } 555 | .row-fluid [class*="span"]:first-child { 556 | margin-left: 0; 557 | } 558 | .row-fluid .span12 { 559 | width: 100%; 560 | *width: 99.94680851063829%; 561 | } 562 | .row-fluid .span11 { 563 | width: 91.45299145300001%; 564 | *width: 91.3997999636383%; 565 | } 566 | .row-fluid .span10 { 567 | width: 82.905982906%; 568 | *width: 82.8527914166383%; 569 | } 570 | .row-fluid .span9 { 571 | width: 74.358974359%; 572 | *width: 74.30578286963829%; 573 | } 574 | .row-fluid .span8 { 575 | width: 65.81196581200001%; 576 | *width: 65.7587743226383%; 577 | } 578 | .row-fluid .span7 { 579 | width: 57.264957265%; 580 | *width: 57.2117657756383%; 581 | } 582 | .row-fluid .span6 { 583 | width: 48.717948718%; 584 | *width: 48.6647572286383%; 585 | } 586 | .row-fluid .span5 { 587 | width: 40.170940171000005%; 588 | *width: 40.117748681638304%; 589 | } 590 | .row-fluid .span4 { 591 | width: 31.623931624%; 592 | *width: 31.5707401346383%; 593 | } 594 | .row-fluid .span3 { 595 | width: 23.076923077%; 596 | *width: 23.0237315876383%; 597 | } 598 | .row-fluid .span2 { 599 | width: 14.529914530000001%; 600 | *width: 14.4767230406383%; 601 | } 602 | .row-fluid .span1 { 603 | width: 5.982905983%; 604 | *width: 5.929714493638298%; 605 | } 606 | input, 607 | textarea, 608 | .uneditable-input { 609 | margin-left: 0; 610 | } 611 | input.span12, 612 | textarea.span12, 613 | .uneditable-input.span12 { 614 | width: 1160px; 615 | } 616 | input.span11, 617 | textarea.span11, 618 | .uneditable-input.span11 { 619 | width: 1060px; 620 | } 621 | input.span10, 622 | textarea.span10, 623 | .uneditable-input.span10 { 624 | width: 960px; 625 | } 626 | input.span9, 627 | textarea.span9, 628 | .uneditable-input.span9 { 629 | width: 860px; 630 | } 631 | input.span8, 632 | textarea.span8, 633 | .uneditable-input.span8 { 634 | width: 760px; 635 | } 636 | input.span7, 637 | textarea.span7, 638 | .uneditable-input.span7 { 639 | width: 660px; 640 | } 641 | input.span6, 642 | textarea.span6, 643 | .uneditable-input.span6 { 644 | width: 560px; 645 | } 646 | input.span5, 647 | textarea.span5, 648 | .uneditable-input.span5 { 649 | width: 460px; 650 | } 651 | input.span4, 652 | textarea.span4, 653 | .uneditable-input.span4 { 654 | width: 360px; 655 | } 656 | input.span3, 657 | textarea.span3, 658 | .uneditable-input.span3 { 659 | width: 260px; 660 | } 661 | input.span2, 662 | textarea.span2, 663 | .uneditable-input.span2 { 664 | width: 160px; 665 | } 666 | input.span1, 667 | textarea.span1, 668 | .uneditable-input.span1 { 669 | width: 60px; 670 | } 671 | .thumbnails { 672 | margin-left: -30px; 673 | } 674 | .thumbnails > li { 675 | margin-left: 30px; 676 | } 677 | .row-fluid .thumbnails { 678 | margin-left: 0; 679 | } 680 | } 681 | 682 | @media (max-width: 979px) { 683 | body { 684 | padding-top: 0; 685 | } 686 | .navbar-fixed-top { 687 | position: static; 688 | margin-bottom: 18px; 689 | } 690 | .navbar-fixed-top .navbar-inner { 691 | padding: 5px; 692 | } 693 | .navbar .container { 694 | width: auto; 695 | padding: 0; 696 | } 697 | .navbar .brand { 698 | padding-right: 10px; 699 | padding-left: 10px; 700 | margin: 0 0 0 -5px; 701 | } 702 | .nav-collapse { 703 | clear: both; 704 | } 705 | .nav-collapse .nav { 706 | float: none; 707 | margin: 0 0 9px; 708 | } 709 | .nav-collapse .nav > li { 710 | float: none; 711 | } 712 | .nav-collapse .nav > li > a { 713 | margin-bottom: 2px; 714 | } 715 | .nav-collapse .nav > .divider-vertical { 716 | display: none; 717 | } 718 | .nav-collapse .nav .nav-header { 719 | color: #999999; 720 | text-shadow: none; 721 | } 722 | .nav-collapse .nav > li > a, 723 | .nav-collapse .dropdown-menu a { 724 | padding: 6px 15px; 725 | font-weight: bold; 726 | color: #999999; 727 | -webkit-border-radius: 3px; 728 | -moz-border-radius: 3px; 729 | border-radius: 3px; 730 | } 731 | .nav-collapse .btn { 732 | padding: 4px 10px 4px; 733 | font-weight: normal; 734 | -webkit-border-radius: 4px; 735 | -moz-border-radius: 4px; 736 | border-radius: 4px; 737 | } 738 | .nav-collapse .dropdown-menu li + li a { 739 | margin-bottom: 2px; 740 | } 741 | .nav-collapse .nav > li > a:hover, 742 | .nav-collapse .dropdown-menu a:hover { 743 | background-color: #222222; 744 | } 745 | .nav-collapse.in .btn-group { 746 | padding: 0; 747 | margin-top: 5px; 748 | } 749 | .nav-collapse .dropdown-menu { 750 | position: static; 751 | top: auto; 752 | left: auto; 753 | display: block; 754 | float: none; 755 | max-width: none; 756 | padding: 0; 757 | margin: 0 15px; 758 | background-color: transparent; 759 | border: none; 760 | -webkit-border-radius: 0; 761 | -moz-border-radius: 0; 762 | border-radius: 0; 763 | -webkit-box-shadow: none; 764 | -moz-box-shadow: none; 765 | box-shadow: none; 766 | } 767 | .nav-collapse .dropdown-menu:before, 768 | .nav-collapse .dropdown-menu:after { 769 | display: none; 770 | } 771 | .nav-collapse .dropdown-menu .divider { 772 | display: none; 773 | } 774 | .nav-collapse .navbar-form, 775 | .nav-collapse .navbar-search { 776 | float: none; 777 | padding: 9px 15px; 778 | margin: 9px 0; 779 | border-top: 1px solid #222222; 780 | border-bottom: 1px solid #222222; 781 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 782 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 783 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 784 | } 785 | .navbar .nav-collapse .nav.pull-right { 786 | float: none; 787 | margin-left: 0; 788 | } 789 | .nav-collapse, 790 | .nav-collapse.collapse { 791 | height: 0; 792 | overflow: hidden; 793 | } 794 | .navbar .btn-navbar { 795 | display: block; 796 | } 797 | .navbar-static .navbar-inner { 798 | padding-right: 10px; 799 | padding-left: 10px; 800 | } 801 | } 802 | 803 | @media (min-width: 980px) { 804 | .nav-collapse.collapse { 805 | height: auto !important; 806 | overflow: visible !important; 807 | } 808 | } 809 | -------------------------------------------------------------------------------- /stress/static/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.0.3 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}@media(max-width:767px){.visible-phone{display:inherit!important}.hidden-phone{display:none!important}.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}}@media(min-width:768px) and (max-width:979px){.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:18px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-group>label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.modal{position:absolute;top:10px;right:10px;left:10px;width:auto;margin:0}.modal.fade.in{top:auto}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:auto;margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:20px}.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.762430939%;*margin-left:2.709239449638298%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:99.999999993%;*width:99.9468085036383%}.row-fluid .span11{width:91.436464082%;*width:91.38327259263829%}.row-fluid .span10{width:82.87292817100001%;*width:82.8197366816383%}.row-fluid .span9{width:74.30939226%;*width:74.25620077063829%}.row-fluid .span8{width:65.74585634900001%;*width:65.6926648596383%}.row-fluid .span7{width:57.182320438000005%;*width:57.129128948638304%}.row-fluid .span6{width:48.618784527%;*width:48.5655930376383%}.row-fluid .span5{width:40.055248616%;*width:40.0020571266383%}.row-fluid .span4{width:31.491712705%;*width:31.4385212156383%}.row-fluid .span3{width:22.928176794%;*width:22.874985304638297%}.row-fluid .span2{width:14.364640883%;*width:14.311449393638298%}.row-fluid .span1{width:5.801104972%;*width:5.747913482638298%}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:714px}input.span11,textarea.span11,.uneditable-input.span11{width:652px}input.span10,textarea.span10,.uneditable-input.span10{width:590px}input.span9,textarea.span9,.uneditable-input.span9{width:528px}input.span8,textarea.span8,.uneditable-input.span8{width:466px}input.span7,textarea.span7,.uneditable-input.span7{width:404px}input.span6,textarea.span6,.uneditable-input.span6{width:342px}input.span5,textarea.span5,.uneditable-input.span5{width:280px}input.span4,textarea.span4,.uneditable-input.span4{width:218px}input.span3,textarea.span3,.uneditable-input.span3{width:156px}input.span2,textarea.span2,.uneditable-input.span2{width:94px}input.span1,textarea.span1,.uneditable-input.span1{width:32px}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:30px}.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.564102564%;*margin-left:2.510911074638298%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145300001%;*width:91.3997999636383%}.row-fluid .span10{width:82.905982906%;*width:82.8527914166383%}.row-fluid .span9{width:74.358974359%;*width:74.30578286963829%}.row-fluid .span8{width:65.81196581200001%;*width:65.7587743226383%}.row-fluid .span7{width:57.264957265%;*width:57.2117657756383%}.row-fluid .span6{width:48.717948718%;*width:48.6647572286383%}.row-fluid .span5{width:40.170940171000005%;*width:40.117748681638304%}.row-fluid .span4{width:31.623931624%;*width:31.5707401346383%}.row-fluid .span3{width:23.076923077%;*width:23.0237315876383%}.row-fluid .span2{width:14.529914530000001%;*width:14.4767230406383%}.row-fluid .span1{width:5.982905983%;*width:5.929714493638298%}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:1160px}input.span11,textarea.span11,.uneditable-input.span11{width:1060px}input.span10,textarea.span10,.uneditable-input.span10{width:960px}input.span9,textarea.span9,.uneditable-input.span9{width:860px}input.span8,textarea.span8,.uneditable-input.span8{width:760px}input.span7,textarea.span7,.uneditable-input.span7{width:660px}input.span6,textarea.span6,.uneditable-input.span6{width:560px}input.span5,textarea.span5,.uneditable-input.span5{width:460px}input.span4,textarea.span4,.uneditable-input.span4{width:360px}input.span3,textarea.span3,.uneditable-input.span3{width:260px}input.span2,textarea.span2,.uneditable-input.span2{width:160px}input.span1,textarea.span1,.uneditable-input.span1{width:60px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top{position:static;margin-bottom:18px}.navbar-fixed-top .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 9px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#999;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:6px 15px;font-weight:bold;color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#222}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:block;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:9px 15px;margin:9px 0;border-top:1px solid #222;border-bottom:1px solid #222;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /stress/static/css/codemirror.css: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | line-height: 1em; 3 | font-family: monospace; 4 | } 5 | 6 | .CodeMirror-scroll { 7 | overflow: auto; 8 | height: 300px; 9 | /* This is needed to prevent an IE[67] bug where the scrolled content 10 | is visible outside of the scrolling box. */ 11 | position: relative; 12 | outline: none; 13 | } 14 | 15 | .CodeMirror-gutter { 16 | position: absolute; left: 0; top: 0; 17 | z-index: 10; 18 | background-color: #f7f7f7; 19 | border-right: 1px solid #eee; 20 | min-width: 2em; 21 | height: 100%; 22 | } 23 | .CodeMirror-gutter-text { 24 | color: #aaa; 25 | text-align: right; 26 | padding: .4em .2em .4em .4em; 27 | white-space: pre !important; 28 | } 29 | .CodeMirror-lines { 30 | padding: .4em; 31 | white-space: pre; 32 | } 33 | 34 | .CodeMirror pre { 35 | -moz-border-radius: 0; 36 | -webkit-border-radius: 0; 37 | -o-border-radius: 0; 38 | border-radius: 0; 39 | border-width: 0; margin: 0; padding: 0; background: transparent; 40 | font-family: inherit; 41 | font-size: inherit; 42 | padding: 0; margin: 0; 43 | white-space: pre; 44 | word-wrap: normal; 45 | line-height: inherit; 46 | } 47 | 48 | .CodeMirror-wrap pre { 49 | word-wrap: break-word; 50 | white-space: pre-wrap; 51 | word-break: normal; 52 | } 53 | .CodeMirror-wrap .CodeMirror-scroll { 54 | overflow-x: hidden; 55 | } 56 | 57 | .CodeMirror textarea { 58 | outline: none !important; 59 | } 60 | 61 | .CodeMirror pre.CodeMirror-cursor { 62 | z-index: 10; 63 | position: absolute; 64 | visibility: hidden; 65 | border-left: 1px solid black; 66 | border-right:none; 67 | width:0; 68 | } 69 | .CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {} 70 | .CodeMirror-focused pre.CodeMirror-cursor { 71 | visibility: visible; 72 | } 73 | 74 | div.CodeMirror-selected { background: #d9d9d9; } 75 | .CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; } 76 | 77 | .CodeMirror-searching { 78 | background: #ffa; 79 | background: rgba(255, 255, 0, .4); 80 | } 81 | 82 | /* Default theme */ 83 | 84 | .cm-s-default span.cm-keyword {color: #708;} 85 | .cm-s-default span.cm-atom {color: #219;} 86 | .cm-s-default span.cm-number {color: #164;} 87 | .cm-s-default span.cm-def {color: #00f;} 88 | .cm-s-default span.cm-variable {color: black;} 89 | .cm-s-default span.cm-variable-2 {color: #05a;} 90 | .cm-s-default span.cm-variable-3 {color: #085;} 91 | .cm-s-default span.cm-property {color: black;} 92 | .cm-s-default span.cm-operator {color: black;} 93 | .cm-s-default span.cm-comment {color: #a50;} 94 | .cm-s-default span.cm-string {color: #a11;} 95 | .cm-s-default span.cm-string-2 {color: #f50;} 96 | .cm-s-default span.cm-meta {color: #555;} 97 | .cm-s-default span.cm-error {color: #f00;} 98 | .cm-s-default span.cm-qualifier {color: #555;} 99 | .cm-s-default span.cm-builtin {color: #30a;} 100 | .cm-s-default span.cm-bracket {color: #cc7;} 101 | .cm-s-default span.cm-tag {color: #170;} 102 | .cm-s-default span.cm-attribute {color: #00c;} 103 | .cm-s-default span.cm-header {color: #a0a;} 104 | .cm-s-default span.cm-quote {color: #090;} 105 | .cm-s-default span.cm-hr {color: #999;} 106 | .cm-s-default span.cm-link {color: #00c;} 107 | 108 | span.cm-header, span.cm-strong {font-weight: bold;} 109 | span.cm-em {font-style: italic;} 110 | span.cm-emstrong {font-style: italic; font-weight: bold;} 111 | span.cm-link {text-decoration: underline;} 112 | 113 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 114 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 115 | -------------------------------------------------------------------------------- /stress/static/css/stress.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: scroll; } 3 | 4 | .topcontainer { 5 | margin-top: 2%; } 6 | 7 | .bigtitle { 8 | text-align: center; } 9 | .bigtitle .bigtitle-text { 10 | font-size: 50px; 11 | line-height: 60px; } 12 | .bigtitle .subtitle { 13 | margin-top: 30px; } 14 | 15 | .content-container { 16 | margin-top: 2%; } 17 | 18 | #load-workload-input { 19 | position: absolute; 20 | width: 100%; 21 | top: 0; 22 | left: 0; 23 | opacity: 0; } 24 | 25 | #settings-form { 26 | z-index: 13; 27 | margin-top: 5px; } 28 | #settings-form .control-label[rel=tooltip] { 29 | cursor: help; } 30 | 31 | #qps-button-container .qps-button { 32 | cursor: pointer; } 33 | 34 | #notifications .notification { 35 | max-width: 200px; 36 | pointer-events: auto; } 37 | 38 | #console { 39 | position: relative; 40 | top: 0; 41 | left: 0; 42 | width: 100%; 43 | height: 200px; 44 | background-color: black; 45 | /* The inner console element. */ 46 | /* The cursor. */ 47 | /* The cursor color when the console looses focus. */ 48 | /* The current prompt text color */ 49 | /* The command history */ 50 | /* The text color when in input mode. */ 51 | /* Previously entered input. */ 52 | /* The text color of the output. */ } 53 | #console .jqconsole { 54 | padding: 10px; } 55 | #console .jqconsole-cursor { 56 | background-color: gray; } 57 | #console .jqconsole-blurred .jqconsole-cursor { 58 | background-color: #666; } 59 | #console .jqconsole-prompt { 60 | color: #111; } 61 | #console .jqconsole-old-prompt { 62 | color: #555; 63 | font-weight: normal; } 64 | #console .jqconsole-input { 65 | color: #dd0; } 66 | #console .jqconsole-old-input { 67 | color: #bb0; 68 | font-weight: normal; } 69 | #console .jqconsole-output { 70 | color: #555; } 71 | 72 | .widget { 73 | position: relative; 74 | height: 150px; 75 | padding-top: 10px; 76 | padding-bottom: 10px; 77 | width: 390px; 78 | display: block; 79 | float: left; 80 | border: 1px dashed #ddd; } 81 | .widget .left-piece { 82 | width: 140px; } 83 | .widget .right-piece { 84 | width: 250px; } 85 | .widget .left-piece, .widget .right-piece { 86 | display: block; 87 | float: left; 88 | border: none; 89 | padding: 0; } 90 | .widget .dial-container { 91 | width: 120px; 92 | padding-top: 15px; 93 | padding-left: 10px; } 94 | .widget .dial { 95 | height: auto !important; 96 | font-size: 16px !important; 97 | border: none !important; 98 | box-shadow: none !important; 99 | color: #26ADE4 !important; } 100 | .widget .query-text { 101 | display: block; 102 | float: right; 103 | padding-top: 20px; 104 | padding-right: 20px; 105 | padding-left: 0px; } 106 | .widget .query-text .CodeMirror { 107 | width: 230px; 108 | max-width: 230px; 109 | height: 110px; 110 | max-height: 110px; 111 | border: 1px solid #ddd; } 112 | .widget .query-text .CodeMirror-scroll { 113 | overflow-y: auto; 114 | height: 110px; } 115 | .widget .icon-remove { 116 | position: absolute; 117 | top: 5px; 118 | right: 5px; 119 | z-index: 5; 120 | opacity: 0.5; } 121 | .widget .icon-remove:active { 122 | opacity: 1.0; } 123 | 124 | #new-widget { 125 | cursor: pointer; } 126 | #new-widget .plus-button { 127 | position: absolute; 128 | height: 75px; 129 | width: 195px; 130 | margin-top: 37.5px; 131 | margin-left: 97.5px; 132 | line-height: 75px; 133 | font-size: 110px; 134 | text-align: center; 135 | color: #ddd; } 136 | 137 | .mets-container { 138 | padding-top: 30px; } 139 | 140 | #mets-graph { 141 | position: relative; 142 | right: 140px; 143 | width: 125%;} 144 | 145 | -------------------------------------------------------------------------------- /stress/static/css/stress.scss: -------------------------------------------------------------------------------- 1 | body { 2 | overflow:scroll; 3 | } 4 | .topcontainer { 5 | margin-top: 2%; 6 | } 7 | 8 | .bigtitle { 9 | text-align: center; 10 | 11 | .bigtitle-text { 12 | font-size: 50px; 13 | line-height: 60px; 14 | } 15 | 16 | .subtitle { 17 | margin-top: 30px; 18 | } 19 | } 20 | 21 | .content-container { 22 | margin-top: 2%; 23 | } 24 | 25 | #load-workload-input { 26 | position: absolute; 27 | width: 100%; 28 | top: 0; 29 | left: 0; 30 | opacity: 0; 31 | } 32 | 33 | #settings-form { 34 | z-index:13; 35 | margin-top: 5px; 36 | 37 | .control-label[rel=tooltip] { 38 | cursor: help; 39 | } 40 | } 41 | 42 | #qps-button-container { 43 | .qps-button { 44 | cursor: pointer; 45 | } 46 | } 47 | 48 | #notifications { 49 | .notification { 50 | max-width: 200px; 51 | pointer-events: auto; 52 | } 53 | } 54 | 55 | #console { 56 | position:relative; 57 | top:0; 58 | left:0; 59 | width:100%; 60 | height:200px; 61 | background-color:black; 62 | 63 | /* The inner console element. */ 64 | .jqconsole { 65 | padding: 10px; 66 | } 67 | /* The cursor. */ 68 | .jqconsole-cursor { 69 | background-color: gray; 70 | } 71 | /* The cursor color when the console looses focus. */ 72 | .jqconsole-blurred .jqconsole-cursor { 73 | background-color: #666; 74 | } 75 | /* The current prompt text color */ 76 | .jqconsole-prompt { 77 | color: #111; 78 | } 79 | /* The command history */ 80 | .jqconsole-old-prompt { 81 | color: #555; 82 | font-weight: normal; 83 | } 84 | /* The text color when in input mode. */ 85 | .jqconsole-input { 86 | color: #dd0; 87 | } 88 | /* Previously entered input. */ 89 | .jqconsole-old-input { 90 | color: #bb0; 91 | font-weight: normal; 92 | } 93 | /* The text color of the output. */ 94 | .jqconsole-output { 95 | color: #555; 96 | } 97 | } 98 | 99 | $widget-width: 390px; 100 | $widget-height: 170px; 101 | $widget-padding: 10px; 102 | 103 | $dial-width: 120px; 104 | $dial-height: 120px; 105 | $dial-container-width: 140px; 106 | 107 | $effective-widget-height: $widget-height - $widget-padding*2; 108 | 109 | .widget { 110 | position: relative; 111 | height: $effective-widget-height; 112 | padding-top: $widget-padding; 113 | padding-bottom: $widget-padding; 114 | width: $widget-width; 115 | display:block; 116 | float:left; 117 | border: 1px dashed #ddd; 118 | .left-piece { 119 | width: $dial-container-width; 120 | } 121 | .right-piece { 122 | width: $widget-width - $dial-container-width; 123 | } 124 | .left-piece, .right-piece { 125 | display: block; 126 | float: left; 127 | border: none; 128 | padding: 0; 129 | } 130 | 131 | .dial-container { 132 | width: $dial-width; 133 | padding-top: ($effective-widget-height - $dial-height)/2; 134 | padding-left: ($dial-container-width - $dial-width)/2; 135 | } 136 | .dial { 137 | height: auto !important; 138 | font-size: 16px !important; 139 | border: none !important; 140 | box-shadow: none !important; 141 | color: #26ADE4 !important; 142 | } 143 | 144 | .query-text { 145 | $right-padding: 20px; 146 | $top-padding: 20px; 147 | 148 | display:block; 149 | float: right; 150 | padding-top: $right-padding; 151 | padding-right: $top-padding; 152 | padding-left: 0px; 153 | 154 | .CodeMirror { 155 | width: $widget-width - $dial-container-width - $right-padding; 156 | max-width: $widget-width - $dial-container-width - $right-padding; 157 | height: $effective-widget-height - 2*$top-padding; 158 | max-height: $effective-widget-height - 2*$top-padding; 159 | border: 1px solid #ddd; 160 | } 161 | .CodeMirror-scroll { 162 | overflow-y: auto; 163 | height: $effective-widget-height - 2*$top-padding; 164 | } 165 | } 166 | 167 | .icon-remove { 168 | position: absolute; 169 | top: 5px; 170 | right: 5px; 171 | z-index: 5; 172 | opacity: 0.5; 173 | } 174 | .icon-remove:active { 175 | opacity: 1.0; 176 | } 177 | } 178 | 179 | #new-widget { 180 | cursor: pointer; 181 | .plus-button { 182 | position: absolute; 183 | height: $effective-widget-height/2; 184 | width: $widget-width/2; 185 | margin-top: $effective-widget-height/4; 186 | margin-left: $widget-width/4; 187 | line-height: $effective-widget-height/2; 188 | font-size: 110px; 189 | text-align: center; 190 | color: #ddd; 191 | } 192 | } 193 | 194 | .mets-container { 195 | padding-top: 30px; 196 | } 197 | 198 | #mets-graph { 199 | position: relative; 200 | right: 140px; 201 | width: 125%; 202 | } 203 | 204 | -------------------------------------------------------------------------------- /stress/static/img/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memsql/workload-simulator/8dcdc345661d91c597461db307ea047556d88e82/stress/static/img/ajax-loader.gif -------------------------------------------------------------------------------- /stress/static/img/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memsql/workload-simulator/8dcdc345661d91c597461db307ea047556d88e82/stress/static/img/background.png -------------------------------------------------------------------------------- /stress/static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memsql/workload-simulator/8dcdc345661d91c597461db307ea047556d88e82/stress/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /stress/static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memsql/workload-simulator/8dcdc345661d91c597461db307ea047556d88e82/stress/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /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/static/js/bootstrap-modal.js: -------------------------------------------------------------------------------- 1 | /* ========================================================= 2 | * bootstrap-modal.js v2.0.3 3 | * http://twitter.github.com/bootstrap/javascript.html#modals 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 | /* MODAL CLASS DEFINITION 27 | * ====================== */ 28 | 29 | var Modal = function (content, options) { 30 | this.options = options 31 | this.$element = $(content) 32 | .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) 33 | } 34 | 35 | Modal.prototype = { 36 | 37 | constructor: Modal 38 | 39 | , toggle: function () { 40 | return this[!this.isShown ? 'show' : 'hide']() 41 | } 42 | 43 | , show: function () { 44 | var that = this 45 | , e = $.Event('show') 46 | 47 | this.$element.trigger(e) 48 | 49 | if (this.isShown || e.isDefaultPrevented()) return 50 | 51 | $('body').addClass('modal-open') 52 | 53 | this.isShown = true 54 | 55 | escape.call(this) 56 | backdrop.call(this, function () { 57 | var transition = $.support.transition && that.$element.hasClass('fade') 58 | 59 | if (!that.$element.parent().length) { 60 | that.$element.appendTo(document.body) //don't move modals dom position 61 | } 62 | 63 | that.$element 64 | .show() 65 | 66 | if (transition) { 67 | that.$element[0].offsetWidth // force reflow 68 | } 69 | 70 | that.$element.addClass('in') 71 | 72 | transition ? 73 | that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : 74 | that.$element.trigger('shown') 75 | 76 | }) 77 | } 78 | 79 | , hide: function (e) { 80 | e && e.preventDefault() 81 | 82 | var that = this 83 | 84 | e = $.Event('hide') 85 | 86 | this.$element.trigger(e) 87 | 88 | if (!this.isShown || e.isDefaultPrevented()) return 89 | 90 | this.isShown = false 91 | 92 | $('body').removeClass('modal-open') 93 | 94 | escape.call(this) 95 | 96 | this.$element.removeClass('in') 97 | 98 | $.support.transition && this.$element.hasClass('fade') ? 99 | hideWithTransition.call(this) : 100 | hideModal.call(this) 101 | } 102 | 103 | } 104 | 105 | 106 | /* MODAL PRIVATE METHODS 107 | * ===================== */ 108 | 109 | function hideWithTransition() { 110 | var that = this 111 | , timeout = setTimeout(function () { 112 | that.$element.off($.support.transition.end) 113 | hideModal.call(that) 114 | }, 500) 115 | 116 | this.$element.one($.support.transition.end, function () { 117 | clearTimeout(timeout) 118 | hideModal.call(that) 119 | }) 120 | } 121 | 122 | function hideModal(that) { 123 | this.$element 124 | .hide() 125 | .trigger('hidden') 126 | 127 | backdrop.call(this) 128 | } 129 | 130 | function backdrop(callback) { 131 | var that = this 132 | , animate = this.$element.hasClass('fade') ? 'fade' : '' 133 | 134 | if (this.isShown && this.options.backdrop) { 135 | var doAnimate = $.support.transition && animate 136 | 137 | this.$backdrop = $('
') 138 | .appendTo(document.body) 139 | 140 | if (this.options.backdrop != 'static') { 141 | this.$backdrop.click($.proxy(this.hide, this)) 142 | } 143 | 144 | if (doAnimate) this.$backdrop[0].offsetWidth // force reflow 145 | 146 | this.$backdrop.addClass('in') 147 | 148 | doAnimate ? 149 | this.$backdrop.one($.support.transition.end, callback) : 150 | callback() 151 | 152 | } else if (!this.isShown && this.$backdrop) { 153 | this.$backdrop.removeClass('in') 154 | 155 | $.support.transition && this.$element.hasClass('fade')? 156 | this.$backdrop.one($.support.transition.end, $.proxy(removeBackdrop, this)) : 157 | removeBackdrop.call(this) 158 | 159 | } else if (callback) { 160 | callback() 161 | } 162 | } 163 | 164 | function removeBackdrop() { 165 | this.$backdrop.remove() 166 | this.$backdrop = null 167 | } 168 | 169 | function escape() { 170 | var that = this 171 | if (this.isShown && this.options.keyboard) { 172 | $(document).on('keyup.dismiss.modal', function ( e ) { 173 | e.which == 27 && that.hide() 174 | }) 175 | } else if (!this.isShown) { 176 | $(document).off('keyup.dismiss.modal') 177 | } 178 | } 179 | 180 | 181 | /* MODAL PLUGIN DEFINITION 182 | * ======================= */ 183 | 184 | $.fn.modal = function (option) { 185 | return this.each(function () { 186 | var $this = $(this) 187 | , data = $this.data('modal') 188 | , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option) 189 | if (!data) $this.data('modal', (data = new Modal(this, options))) 190 | if (typeof option == 'string') data[option]() 191 | else if (options.show) data.show() 192 | }) 193 | } 194 | 195 | $.fn.modal.defaults = { 196 | backdrop: true 197 | , keyboard: true 198 | , show: true 199 | } 200 | 201 | $.fn.modal.Constructor = Modal 202 | 203 | 204 | /* MODAL DATA-API 205 | * ============== */ 206 | 207 | $(function () { 208 | $('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) { 209 | var $this = $(this), href 210 | , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 211 | , option = $target.data('modal') ? 'toggle' : $.extend({}, $target.data(), $this.data()) 212 | 213 | e.preventDefault() 214 | $target.modal(option) 215 | }) 216 | }) 217 | 218 | }(window.jQuery); -------------------------------------------------------------------------------- /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 |