#!/bin/bash

# Run this as root in a disposable VM as it messes up your realtime clock.
# You shouldn't need anything more than a default mariadb install and
# passwordless login for root enabled, which is the default on most distros.
#
# Make sure your VM has at least 4 cores. 8 cores should take around
# 10 mins to reproduce usually, 30 mins max.

# You might want to watch -n1 'mysql -e "show processlist"' on
# another terminal. The clock in the corner should jump back and forth.
#
#

# Disable anything that might interfere with our time shenanigans
if timedatectl set-ntp false; then
	timecmd="timedatectl set-time --adjust-system-clock"
else
	timecmd="date -s"
fi
systemctl stop ntp

query="INSERT INTO tbl (dateline, typeid, clientip, machineuuid) VALUES (UNIX_TIMESTAMP(), 'boot-system', '127.0.0.1', 'AB026A50-B459-CDEF-8040-9684B6681234');"

# This can probably be reduced, but it seems we need a couple indexes and AUTO_INCREMENT
mariadb -e 'CREATE DATABASE IF NOT EXISTS tdb; CREATE TABLE IF NOT EXISTS `tdb`.`tbl` (
  `logid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `dateline` int(10) unsigned NOT NULL,
  `typeid` varchar(30) NOT NULL,
  `clientip` varchar(40) NOT NULL,
  `machineuuid` varchar(36) CHARACTER SET ascii COLLATE ascii_general_ci DEFAULT NULL,
  PRIMARY KEY (`logid`),
  KEY `dateline` (`dateline`),
  KEY `logtypeid` (`typeid`,`dateline`),
  KEY `clientip` (`clientip`,`dateline`),
  KEY `machineuuid` (`machineuuid`,`dateline`)
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; TRUNCATE TABLE `tdb`.`tbl`' || exit 1

max=$( mariadb -N -s -r -e "SHOW VARIABLES LIKE 'max_connections'" | awk '{print $2-2}' )
(( max > 10 )) || max=149
echo "(Max: $max)"

# Start loop running many parallel INSERTs, put in background
while :; do
	for _ in $(seq 1 "$max"); do
	(
		sleep 0.000$RANDOM
		if mariadb -e "$query" tdb &> /dev/null; then
			printf _
		else
			printf X
		fi
	) &
	done
	wait
done &
mainpid=$!

# Start another background job that checks for hanging queries, assume deadlock if
# a query is taking more than 10 seconds and abort the whole script.
while :; do
	[ -e "/proc/$mainpid" ] || break
	#( timeout 2 mariadb -e 'show processlist' 2>/dev/null || printf 'TIMEOUT: ' >&2 ) | wc -l
	d=$( mariadb -N -s -r -e 'SHOW PROCESSLIST' | awk '$5 == "Query" {print $6; exit}' )
	# Any query running for longer than 10 seconds is taken as an indication that we broke it
	if [ -n "$d" ] && (( d > 10 )); then
		echo "Kaputt"
		kill "$$"
		exit
	fi
	echo " $d"
	[ -e "/proc/$mainpid" ] || break
	sleep 0.5
done &
watchpid=$!

trap "kill $mainpid $watchpid 2> /dev/null" EXIT

# Rapidly jump back and forth by 65 seconds
[ "$1" != "notime" ] && while true; do
	(( RANDOM % 10 == 2 )) && sleep 0.8
	sleep 0.1
	$timecmd "@$(($(date +%s) + 65))" > /dev/null
	sleep 0.1
	$timecmd "@$(($(date +%s) - 65))" > /dev/null
	[ -e "/proc/$mainpid" ] || break
done

wait
