| /* Copyright (c) 2013, 2014 The Regents of the University of California | 
 |  * Barret Rhoden <brho@cs.berkeley.edu> | 
 |  * See LICENSE for details. | 
 |  * | 
 |  * lock_test: microbenchmark to measure different styles of spinlocks. | 
 |  * | 
 |  * to build on linux: (hacky) | 
 |  * $ gcc -O2 -std=gnu99 -fno-stack-protector -g tests/lock_test.c -lpthread \ | 
 |  *    -lm -o linux_lock_test */ | 
 |  | 
 | #define _GNU_SOURCE /* pthread_yield */ | 
 |  | 
 | #include <stdio.h> | 
 | #include <pthread.h> | 
 | #include <stdlib.h> | 
 | #include <unistd.h> | 
 | #include <sys/time.h> | 
 | #include <math.h> | 
 | #include <argp.h> | 
 | #include <sys/types.h> | 
 | #include <sys/stat.h> | 
 | #include <fcntl.h> | 
 | #include <assert.h> | 
 | #include <string.h> | 
 |  | 
 | /* OS dependent #incs */ | 
 | #ifdef __ros__ | 
 |  | 
 | #include <parlib/parlib.h> | 
 | #include <parlib/vcore.h> | 
 | #include <parlib/timing.h> | 
 | #include <parlib/spinlock.h> | 
 | #include <parlib/mcs.h> | 
 | #include <parlib/arch/arch.h> | 
 | #include <parlib/event.h> | 
 |  | 
 | #include <parlib/tsc-compat.h> | 
 | #include <benchutil/measure.h> | 
 |  | 
 | #else | 
 |  | 
 | #include "../user/parlib/include/tsc-compat.h" | 
 | #include "misc-compat.h" | 
 | #include "linux-lock-hacks.h" /* TODO: have a build system and lib / C file */ | 
 |  | 
 | #include "../user/benchutil/include/measure.h" | 
 | #include "../user/benchutil/measure.c" | 
 |  | 
 | static void os_prep_work(pthread_t *worker_threads, int nr_threads) | 
 | { | 
 | 	if (nr_threads > num_vcores()) | 
 | 		printf("WARNING: %d threads requested, but only %d cores available\n", | 
 | 		       nr_threads, num_vcores()); | 
 | } | 
 |  | 
 | static void os_post_work(pthread_t *worker_threads, int nr_threads) | 
 | { | 
 | 	if (nr_threads > num_vcores()) | 
 | 		return; | 
 | 	/* assuming we're taking cores 0..nr_threads, and we never move. */ | 
 | 	for (int i = 0; i < nr_threads; i++) { | 
 | 		cpu_set_t cpuset; | 
 | 		CPU_ZERO(&cpuset); | 
 | 		CPU_SET(i, &cpuset); | 
 | 		pthread_setaffinity_np(worker_threads[i], sizeof(cpu_set_t), &cpuset); | 
 | 	} | 
 | } | 
 |  | 
 | #define print_preempt_trace(args...) {} | 
 |  | 
 | __thread int __vcore_context = 0; | 
 |  | 
 | #endif | 
 |  | 
 | /* TODO: There's lot of work to do still, both on this program and on locking | 
 |  * and vcore code.  For some of the issues, I'll leave in the discussion / | 
 |  * answers, in case it comes up in the future (like when I read this in 8 | 
 |  * months). | 
 |  * | 
 |  * BUGS / COMMENTARY | 
 |  * 		Occasional deadlocks when preempting and not giving back! | 
 |  * 			- with the new PDRs style, though that doesn't mean the older styles | 
 |  * 			don't have this problem | 
 |  * 			- shouldn't be any weaker than PDR.  they all check pred_vc to see | 
 |  * 			if they are running, and if not, they make sure someone runs | 
 |  * 			- could be weaker if we have an old value for the lockholder, | 
 |  * 			someone outside the chain, and we made sure they ran, and they do | 
 |  * 			nothing (spin in the 2LS or something?) | 
 |  * 				no, they should have gotten a msg about us being preempted, | 
 |  * 				since whoever we turn into gets the message about us swapping. | 
 |  * 			- anyway, it's not clear if this is with MCSPDR, event delivery, | 
 |  * 			preemption handling, or just an artifact of the test (less likely) | 
 |  * 		why aren't MCS locks in uth_ctx getting dealt with? | 
 |  * 			- because the event is handled, but the lock holder isn't run.  the | 
 |  * 			preemption was dealt with, but nothing saved the lock holder | 
 |  * 			- any uthread_ctx lockholder that gets preempted will get | 
 |  * 			interrupted, and other cores will handle the preemption.  but that | 
 |  * 			uthread won't run again without 2LS support.  either all spinners | 
 |  * 			need to be aware of the 'lockholder' (PDR-style), or the 2LS needs | 
 |  * 			to know when a uthread becomes a 'lockholder' to make sure it runs | 
 |  * 			via user-level preempts.  If the latter, this needs to happen | 
 |  * 			atomically with grabbing the lock, or else be able to handle lots of | 
 |  * 			fake 'lockholders' (like round-robin among all of them) | 
 |  * 		why is the delay more than the expected delay? | 
 |  * 			because it takes ~2ms to spawn and run a process | 
 |  * 			could do this in a separate process, instead of a script | 
 |  * 				could also consider not using pth_test and changing prov, but | 
 |  * 				driving it by yields and requests.  would also test the | 
 |  * 				alarm/wakeup code (process sets alarm, goes to sleep, wakes up | 
 |  * 				and requests X cores) | 
 |  * 		why do we get occasional preempt-storms? (lots of change_tos) | 
 |  * 			due to the MCS-PDR chain, which i tried fixing by adjusting the | 
 |  * 			number of workers down to the number of vcores | 
 |  * 			why isn't the worker adaptation working? | 
 |  * 				- it actually was working, and nr_workers == nr_vcores.  that | 
 |  * 				just wasn't the root cause. | 
 |  * 				- was expecting it to cut down on PDR kernel traffic | 
 |  * 			- still get periods of low perf | 
 |  * 				like O(100) preempt msgs per big preempt/prov | 
 |  * 				does it really take that much to work out an MCS-PDR? | 
 |  * 			- one thing is that if we fake vc ctx, we never receive preemption | 
 |  * 			events.  might be a bad idea. | 
 |  * 				- in general, yeah.  faking VC and turning off events can really | 
 |  * 				muck with things | 
 |  * 				- these events aren't necessarily delivered to a VC who will | 
 |  * 				check events any time soon (might be the last one in the chain) | 
 |  * 				- the core of the issue is that we have the right amount of | 
 |  * 				workers and vcores, but that the system isn't given a chance to | 
 |  * 				stabilize itself.  also, if we have some VCs that are just | 
 |  * 				sitting around, spinning in the 2LS, if those get preempted, no | 
 |  * 				one notices or cares (when faking vc_ctx / getting no events) | 
 |  * 			- there is a slight race where we might make someone run who isn't a | 
 |  * 			lockholder.  logically, its okay.  worst case, it would act like an | 
 |  * 			extra preempt and different startcore, which shouldn't be too bad. | 
 |  * | 
 |  * 		sanity check: does throughput match latency? (2.5GHz TSC, MCS lock) | 
 |  * 			ex: 5000 locks/ms = 5 locks/us = 200ns/lock = 500 ticks / lock | 
 |  * 			500 ticks * 31 workers (queue) = 15000 ticks | 
 |  * 			avg acquire time was around 14K.  seems fine.. | 
 |  * 			when our MCSPDR throughput tanks (during preempts), it's around | 
 |  * 			400-500 locks/ms, which is around 2us/lock.   | 
 |  * 				when the locker on a preempted chain shows up, it needs to | 
 |  * 				change to the next one in line.  | 
 |  * 					- though that should be in parallel with the other | 
 |  * 					lockholders letting go.  shouldn't be that bad | 
 |  * 					- no, it is probably at the head of the chain very soon, | 
 |  * 					such that it is the bottleneck for the actual lock.  2us | 
 |  * 					seems possible | 
 |  * | 
 |  * 		what does it take to get out of a preemption with (old) MCS-PDR? | 
 |  * 			- these are now called pdro locks (old) | 
 |  * 			- for a single preempt, it will take 1..n-1 changes.  avg n/2 | 
 |  * 			- for multiple preempts, it's nr_pre * that (avg np/2, worst np) | 
 |  * 			- for every unlock/reacquire cycle (someone unlocks, then rejoins | 
 |  * 			the list), its nr_preempts (aka, nr_workers - nr_vcores) | 
 |  * 			- if we need to have a specific worker get out of the chain, on | 
 |  * 			average, it'd take n/2 cycles (p*n/2 changes)  worst: np | 
 |  * 			- if we want to get multiple workers out, the worst case is still | 
 |  * 			np, but as p increases, we're more likely to approach n cycles | 
 |  * 			- so the current model is np for the initial hit (to move the | 
 |  * 			offline VCs to the end of the chain) and another np to get our | 
 |  * 			specific workers out of the chain and yielding (2np) | 
 |  * | 
 |  * 			- but even with 1 preempt, we're getting 80-200 changes per | 
 |  * | 
 |  * 			- it shouldn't matter that the sys_change_to is really slow, should | 
 |  * 			be the same amount of changes.  however, the preempted ones are | 
 |  * 			never really at the tail end of the chain - they should end up right | 
 |  * 			before the lockholder often.  while the sys_change_tos are slowly | 
 |  * 			moving towards the back of the chain, the locking code is quickly | 
 |  * 			removing (online) nodes from the head and putting them on the back. | 
 |  * | 
 |  * 			- end result: based on lock hold time and lock delay time, a | 
 |  * 			preempted VC stays in the MCS chain (swaps btw VC/nodes), and when | 
 |  * 			it is inside the chain, someone is polling to make them run.  with | 
 |  * 			someone polling, it is extremely unlikely that someone outside the | 
 |  * 			chain will win the race and be able to change_to before the in-chain | 
 |  * 			poller.  to clarify: | 
 |  * 				- hold time and delay time matter, since the longer they are, | 
 |  * 				the greater the amount of time the change_to percolation has to | 
 |  * 				get the preempted VCs to the end of the chain (where no one | 
 |  * 				polls them). | 
 |  * 				- at least one vcore is getting the event to handle the | 
 |  * 				preemption of the in-chain, offline VC.  we could change it so | 
 |  * 				every VC polls the preempt_evq, or just wait til whoever is | 
 |  * 				getting the messages eventually checks their messages (VC0) | 
 |  * 				- if there is an in-chain poller, they will notice the instant | 
 |  * 				the VC map changes, and then immediately change_to (and spin on | 
 |  * 				the proclock in the kernel).  there's almost no chance of a | 
 |  * 				normal preempt event handler doing that faster.  (would require | 
 |  * 				some IRQ latency or something serious). | 
 |  * 			- adding in any hold time trashes our microbenchmark's perf, but a | 
 |  * 			little delay time actually helps: (all with no preempts going on) | 
 |  * 				- mcspdr, no delay: 4200-4400 (-w31 -l10000, no faking, etc) | 
 |  * 				- mcspdr, d = 1: 4400-4800 | 
 |  * 				- mcspdr, d = 2: 4200-5200 | 
 |  * 				- as you add delay, it cuts down on contention for the | 
 |  * 				lock->lock cacheline.  but if you add in too much, you'll tank | 
 |  * 				throughput (since there is no contention at all). | 
 |  * 				- as we increase the delay, we cut down on the chance of the | 
 |  * 				preempt storm / preempt-stuck-in-the-chain, though it can still | 
 |  * 				happen, even with a delay of 10us | 
 |  * 			- maybe add in the lockholder again? (removed in 73701d6bfb) | 
 |  * 				- massively cuts performance, like 2x throughput, without | 
 |  * 				preempts | 
 |  * 				- it's ability to help depends on impl: | 
 |  * 					in one version (old style), it didn't help much at all | 
 |  * 					- in another (optimized lockholder setting), i can't even | 
 |  * 					see the throughput hit, it recovered right away, with O(5) | 
 |  * 					messages | 
 |  * 					- the diff was having the lockholder assign the vcoreid | 
 |  * 					before passing off to the next in the chain, so that there | 
 |  * 					is less time with having "no lockholder".  (there's a brief | 
 |  * 					period where the lockholder says it is the next person, who | 
 |  * 					still spins.  they'll have to make sure their pred runs) | 
 |  * 			-adj workers doesn't matter either... | 
 |  * 				- the 2LS and preemption handling might be doing this | 
 |  * 				automatically, when handle_vc_preempt() does a | 
 |  * 				thread_paused() on its current_uthread. | 
 |  * 				- adj_workers isn't critical if we're using some locks | 
 |  * 				that check notif_pending.  eventually someone hears | 
 |  * 				about preempted VCs (assuming we can keep up) | 
 |  * | 
 |  * 			What about delays?  both hold and delay should make it easier to get | 
 |  * 			the preempted vcore to the end of the chain.  but do they have to be | 
 |  * 			too big to be reasonable? | 
 |  * 				- yes.  hold doesn't really help much until everything is | 
 |  * 				slower.  even with a hold of around 1.2us, we still have the | 
 |  * 				change_to-storms and lowered throughput. | 
 |  * 				- doing a combo helps too.  if you hold for 1ns (quite a bit | 
 |  * 				more actually, due to the overhead of ndelay, but sufficient to | 
 |  * 				be "doing work"), and delaying for around 7us before rejoining, | 
 |  * 				there's only about a 1/5 chance of a single preempt messing us | 
 |  * 				up | 
 |  * 					- though having multiple preempts outstanding make this less | 
 |  * 					likely to work. | 
 |  * 					- and it seems like if we get into the storm scenario, we | 
 |  * 					never really get out.  either we do quickly or never do. | 
 |  * 					depending on the workload, this could be a matter of luck | 
 |  * | 
 |  * 			So we could try tracking the lockholder, but only looking at it when | 
 |  * 			we know someone was preempted in the chain - specifically, when our | 
 |  * 			pred is offline.  when that happens, we don't change to them, we | 
 |  * 			make sure the lockholder is running. | 
 |  * 				- tracking takes us from 4200->2800 throughput or so for MCS | 
 |  * 				- 5200 -> 3700 or so for MCS in vc_ctx (__MCSPDR) | 
 |  * 				- main spike seems to be in the hold time.  bimodal distrib, | 
 |  * 				with most below 91 (the usual is everything packed below 70) and | 
 |  * 				a big spike around 320 | 
 |  * | 
 |  * 	Summary: | 
 |  * 		So we need to have someone outside the chain change_to the one in the | 
 |  * 		chain o/w, someone will always be in the chain.  Right now, it's always | 
 |  * 		the next in line who is doing the changing, so a preempted vcore is | 
 |  * 		always still in the chain.  | 
 |  * | 
 |  * 		If the locking workload has some delaying, such as while holding the | 
 |  * 		lock or before reacquiring, the "change_to" storm might not be a | 
 |  * 		problem.  If it is, the only alternative I have so far is to check the | 
 |  * 		lockholder (which prevents a chain member from always ensuring their | 
 |  * 		pred runs).  This hurts the lock's scalability/performance when we | 
 |  * 		aren't being preempted.  On the otherhand, based on what you're doing | 
 |  * 		with the lock, one more cache miss might not be as big of a deal as in | 
 |  * 		lock_test.  Especially if when you get stormed, your throughput could be | 
 |  * 		terrible and never recover. | 
 |  * | 
 |  * 		Similar point: you can use spinpdr locks.  They have the PDR-benefits, | 
 |  * 		and won't induce the storm of change_tos.  However, this isn't much | 
 |  * 		better for contended locks.  They perform 2-3x worse (on c89) without | 
 |  * 		preemption.  Arguably, if you were worried about the preempt storms and | 
 |  * 		want scalability, you might want to use mcspdr with lockholders. | 
 |  * | 
 |  * 		The MCSPDRS (now just callced MCSPDR, these are default) locks can avoid | 
 |  * 		the storm, but at the cost of a little more in performance.  mcspdrs | 
 |  * 		style is about the same when not getting preempted from uth ctx compared | 
 |  * 		to mcspdr (slight drop).  When in vc ctx, it's about 10-20% perf hit | 
 |  * 		(PDRS gains little from --vc_ctx).  | 
 |  * | 
 |  * 		Turns out there is a perf hit to PDRS (and any non-stack based qnode) | 
 |  * 		when running on c89.  The issue is that after shuffling the vcores | 
 |  * 		around, they are no longer mapped nicely to pcores (VC0->PC1, VC1->PC2). | 
 |  * 		This is due to some 'false sharing' of the cachelines, caused mostly by | 
 |  * 		aggressive prefetching (notably the intel adjacent cacheline prefetcher, | 
 |  * 		which grabs two CLs at a time!).  Basically, stack-based qnodes are | 
 |  * 		qnodes that are very far apart in memory.  Cranking up the padding in | 
 |  * 		qnodes in the "qnodes-in-locks" style replicates this. | 
 |  * | 
 |  * 		For some info on the prefetching: | 
 |  * 			http://software.intel.com/en-us/articles/optimizing-application-performance-on-intel-coret-microarchitecture-using-hardware-implemented-prefetchers/ | 
 |  * 			http://software.intel.com/en-us/forums/topic/341769 | 
 |  * | 
 |  * 		Here's some rough numbers of the different styles for qnodes on c89. | 
 |  * 		'in order' is VCn->PC(n+1) (0->1, 1->2).  Worst order is with even VCs | 
 |  * 		on one socket, odds on the other.  the number of CLs is the size of a | 
 |  * 		qnode.  mcspdr is the new style (called mcspdrs in some places in this | 
 |  * 		document), with lock-based qnodes.  mcspdr2 is the same, but with | 
 |  * 		stack-based qnodes.  mcspdro is the old style (bad a recovery), stack | 
 |  * 		based, sometimes just called mcs-pdr | 
 |  * | 
 |  * 		with prefetchers disabled (MCS and DCU) | 
 |  * 			mcspdr   1CL  4.8-5.4 in order, 3.8-4.2 worst order | 
 |  * 			mcspdr   2CL          in order,         worst order | 
 |  * 			mcspdr   4CL  5.2-6.0 in order, 4.7-5.3 worst order | 
 |  * 			mcspdr   8CL  5.4-6.7 in order, 5.2-6.2 worst order | 
 |  * 			mcspdr  16CL  5.1-5.8 in order, 5.2-6.8 worst order | 
 |  * 			mcspdr2 stck          in order,         worst order | 
 |  * 			mcspdro stck  4-3.4.3 in order, 4.2-4.5 worst order | 
 |  * 			mcspdro-vcctx 4.8-7.0 in order, 5.3-6.7 worst order | 
 |  * 			can we see the 2 humps?  | 
 |  * 				mcspdr 1CL yes but less, varied, etc | 
 |  * 				mcspdr2 no | 
 |  * | 
 |  * 		test again with worst order with prefetchers enabled | 
 |  * 			mcspdr   1CL  3.8-4.0 in order, 2.6-2.7 worst order | 
 |  * 			mcspdr   2CL  4.2-4.4 in order, 3.8-3.9 worst order | 
 |  * 			mcspdr   4CL  4.5-5.2 in order, 4.0-4.2 worst order | 
 |  * 			mcspdr   8CL  4.4-5.1 in order, 4.3-4.7 worst order | 
 |  * 			mcspdr  16CL  4.4-4.8 in order, 4.4-5.3 worst order | 
 |  * 			mcspdr2 stck  3.0-3.0 in order, 2.9-3.0 worst order | 
 |  * 			mcspdro stck  4.2-4.3 in order, 4.2-4.4 worst order | 
 |  * 			mcspdro-vcctx 5.2-6.4 in order, 5.0-5.9 worst order | 
 |  * 			can we see the 2 humps? | 
 |  * 				mcspdrs 1CL yes, clearly | 
 |  * 				mcspdr2 no | 
 |  * | 
 |  * PROGRAM FEATURES | 
 |  * 		- verbosity?  vcoremap, preempts, the throughput and latency histograms? | 
 |  * 		- have a max workers option (0?) == max vcores | 
 |  * 		- would like to randomize (within bounds) the hold/delay times | 
 |  * 			- help avoid convoys with MCS locks | 
 |  * | 
 |  * PERFORMANCE: | 
 |  * 		pcore control?  (hyperthreading, core 0, cross socket?) | 
 |  * 			want some options for controlling which threads run where, or which | 
 |  * 			vcores are even used (like turning off hyperthreading)? | 
 |  * 		implement ticket spinlocks?  (more fair, more effects of preempts) | 
 |  * 			no simple way to do PDR either, other than 'check everyone' | 
 |  * 		MCS vs MCSPDR vs __MCSPDR | 
 |  * 			MCS seems slightly better than __MCSPDR (and it should) | 
 |  * 			MCSPDR is a bit worse than __MCSPDR | 
 |  * 				- the uth_disable/enable code seems to make a difference. | 
 |  * 				- i see why the latencies are worse, since they have extra work | 
 |  * 				to do, but the internal part that contends with other cores | 
 |  * 				shouldn't be affected, unless there's some other thing going on. | 
 |  * 				Or perhaps there isn't always someone waiting for the lock? | 
 |  * 				- faking VC ctx mostly negates the cost of MCSPDR vs __MCSPDR | 
 |  * 			things that made a big diff: CL aligning the qnodes, putting qnodes | 
 |  * 			on stacks, reading in the vcoreid once before ensuring() | 
 |  * 		both MCS CAS unlocks could use some branch prediction work | 
 |  * 		spinpdr locks are 2-3x faster than spinlocks... | 
 |  * 			test, test&set  vs the existing test&set, plus lots of asserts | 
 |  * | 
 |  * 		some delay (like 10us) lowers latency while maintaining throughput | 
 |  * 			- makes sense esp with MCS.  if you join the queue at the last | 
 |  * 			second, you'll measure lower latency than attempting right away | 
 |  * 			- also true for spinlocks | 
 |  * 			- we can probably figure out the max throughput (TP = f(delay)) for | 
 |  * 			each lock type | 
 |  * | 
 |  * 		hard to get steady numbers with MCS - different runs of the same test | 
 |  * 		will vary in throughput by around 15-30% (e.g., MCS varying from 3k-4k | 
 |  * 		L/ms) | 
 |  * 			- happens on c89 (NUMA) and hossin (UMA) | 
 |  * 			- spinlocks seem a little steadier. | 
 |  * 			- for MCS locks, the order in which they line up across the pcores | 
 |  * 			will matter.  like if on one run, i regularly hand off between cores | 
 |  * 			in the same socket and only do one cross-socket step | 
 |  * 			- run a lot of shorter ones to get a trend, for now | 
 |  * 			- might be correllated with spikes in held times (last bin) | 
 |  * 			- can't turn off legacy USB on c89 (SMM) - interferes with PXE | 
 |  * | 
 |  * PREEMPTS: | 
 |  * 		better preempt record tracking? | 
 |  * 			i just hacked some event-intercept and timestamp code together | 
 |  * 			maybe put it in the event library? | 
 |  * 			the timestamps definitely helped debugging | 
 |  * | 
 |  * 		is it true that if uthread code never spins outside a PDR lock, then it | 
 |  * 		doesn't need preemption IPIs?  (just someone checks the event at some | 
 |  * 		point).  | 
 |  * 			think so: so long as you make progress and when you aren't, you | 
 |  * 			check events (like if a uthread blocks on something and enters VC | 
 |  * 			ctx) | 
 |  * 		adjusting the number of workers, whether vcores or uthreads | 
 |  * 		- if you have more lockers than cores: | 
 |  * 			- spinpdr a worker will get starved (akaros) (without 2LS support) | 
 |  * 				- running this from uth context will cause a handle_events | 
 |  * 			- mcspdr will require the kernel to switch (akaros) | 
 |  * 			- spin (akaros) might DL (o/w nothing), (linux) poor perf | 
 |  * 			- mcs (akaros) will DL, (linux) poor perf | 
 |  * 			- poor perf (latency spikes) comes from running the wrong thread | 
 |  * 			sometimes | 
 |  * 			- deadlock comes from the lack of kernel-level context switching | 
 |  * 		- if we scale workers down to the number of active vcores: | 
 |  * 			- two things: the initial hit, and the steady state.  during the | 
 |  * 			initial hit, we can still deadlock, since we have more lockers than | 
 |  * 			cores | 
 |  * 				- non-pdr (akaros) could deadlock in the initial hit | 
 |  * 				- (akaros) steady state, everything is normal (just fewer cores) | 
 |  * 			- how can we adjust this in linux? | 
 |  * 				- if know how many cores you have, then futex wait the others | 
 |  * 				- need some way to wake them back up | 
 |  * 				- if you do this in userspace, you might need something PDR-like | 
 |  * 				to handle when the "2LS" code gets preempted | 
 |  * 			- as mentioned above, the problem in akaros is that the lock/unlock | 
 |  * 			might be happening too fast to get into the steady-state and recover | 
 |  * 			from the initial preemption | 
 |  * 		- one of our benefits is that we can adapt in userspace, with userspace | 
 |  * 		knowledge, under any circumstance. | 
 |  * 			- we have the deadlock windows (forcing PDR). | 
 |  * 			- in return, we can do this adaptation in userspace | 
 |  * 			- and (arguably) anyone who does this in userspace will need PDR | 
 |  * | 
 |  * MEASUREMENT (user/parlib/measure.c) | 
 |  * 		extract into its own library, for linux apps | 
 |  * 		print out raw TSC times?  might help sync up diff timelines | 
 |  * 		Need more latency bins, spinlocks vary too much | 
 |  * 		maybe we need better high/low too, since this hist looks bad too | 
 |  * 			or not center on the average? | 
 |  * 			for printing, its hard to know without already binning. | 
 |  * 			maybe bin once (latency?), then use that to adjust the hist? | 
 |  * | 
 |  * 		Had this on a spinlock: | 
 |  * 		[      32 -    35656] 1565231: | 
 |  * 		(less than 200 intermediate) | 
 |  * 	    [  286557 - 20404788]   65298: * | 
 |  * | 
 |  *		Samples per dot: 34782 | 
 |  *		Total samples: 1640606 | 
 |  *		Avg time   : 96658 | 
 |  *		Stdev time : 604064.440882 | 
 |  *		Coef Var   : 6.249503 | 
 |  *			High coeff of var with serious outliers, adjusted bins | 
 |  *			50/75/90/99: 33079 / 33079 / 33079 / 290219 (-<860) | 
 |  *			Min / Max  : 32 / 20404788 | 
 |  *		was 50/75/90 really within 860 of each other? | 
 |  * | 
 |  * 		when we are preempted and don't even attempt anything, say for 10ms, it | 
 |  * 		actually doesn't hurt our 50/75/90/99 too much.  we have a ridiculous | 
 |  * 		stddev and max, and high average, but there aren't any additional | 
 |  * 		attempts at locking to mess with the attempt-latency.  Only nr_vcores | 
 |  * 		requests are in flight during the preemption, but we can spit out around | 
 |  * 		5000 per ms when we aren't preempted. | 
 |  * | 
 |  */ | 
 |  | 
 | const char *argp_program_version = "lock_test v0.1475263"; | 
 | const char *argp_program_bug_address = "<akaros+subscribe@googlegroups.com>"; | 
 |  | 
 | static char doc[] = "lock_test -- spinlock benchmarking"; | 
 | static char args_doc[] = "-w NUM -l NUM -t LOCK"; | 
 |  | 
 | #define OPT_VC_CTX 1 | 
 | #define OPT_ADJ_WORKERS 2 | 
 |  | 
 | static struct argp_option options[] = { | 
 | 	{"workers",		'w', "NUM",	OPTION_NO_USAGE, "Number of threads/cores"}, | 
 | 	{0, 0, 0, 0, ""}, | 
 | 	{"loops",		'l', "NUM",	OPTION_NO_USAGE, "Number of loops per worker"}, | 
 | 	{0, 0, 0, 0, ""}, | 
 | 	{"type",		't', "LOCK",OPTION_NO_USAGE, "Type of lock to use.  " | 
 | 	                                             "Options:\n" | 
 | 	                                             "\tmcs\n" | 
 | 	                                             "\tmcscas\n" | 
 | 	                                             "\tmcspdr\n" | 
 | 	                                             "\tmcspdro\n" | 
 | 	                                             "\t__mcspdro\n" | 
 | 	                                             "\tspin\n" | 
 | 	                                             "\tspinpdr"}, | 
 | 	{0, 0, 0, 0, "Other options (not mandatory):"}, | 
 | 	{"adj_workers",	OPT_ADJ_WORKERS, 0,	0, "Adjust workers such that the " | 
 | 	                                       "number of workers equals the " | 
 | 	                                       "number of vcores"}, | 
 | 	{"vc_ctx",		OPT_VC_CTX, 0,	0, "Run threads in mock-vcore context"}, | 
 | 	{0, 0, 0, 0, ""}, | 
 | 	{"hold",		'h', "NSEC",	0, "nsec to hold the lock"}, | 
 | 	{"delay",		'd', "NSEC",	0, "nsec to delay between grabs"}, | 
 | 	{"print",		'p', "ROWS",	0, "Print ROWS of optional measurements"}, | 
 | 	{"outfile",		'o', "FILE",	0, "Print ROWS of optional measurements"}, | 
 | 	{ 0 } | 
 | }; | 
 |  | 
 | struct prog_args { | 
 | 	int							nr_threads; | 
 | 	int							nr_loops; | 
 | 	int							hold_time; | 
 | 	int							delay_time; | 
 | 	int							nr_print_rows; | 
 | 	bool						fake_vc_ctx; | 
 | 	bool						adj_workers; | 
 | 	char						*outfile_path; | 
 | 	void *(*lock_type)(void *arg); | 
 | }; | 
 | struct prog_args pargs = {0}; | 
 |  | 
 | /* Globals */ | 
 | struct time_stamp { | 
 | 	uint64_t pre; | 
 | 	uint64_t acq; | 
 | 	uint64_t un; | 
 | 	bool valid; | 
 | }; | 
 | struct time_stamp **times; | 
 | bool run_locktest = TRUE; | 
 | pthread_barrier_t start_test; | 
 |  | 
 | /* Locking functions.  Define globals here, init them in main (if possible), and | 
 |  * use the lock_func() macro to make your thread func. */ | 
 |  | 
 | #define lock_func(lock_name, lock_cmd, unlock_cmd)                             \ | 
 | void *lock_name##_thread(void *arg)                                            \ | 
 | {                                                                              \ | 
 | 	long thread_id = (long)arg;                                                \ | 
 | 	int hold_time = ACCESS_ONCE(pargs.hold_time);                              \ | 
 | 	int delay_time = ACCESS_ONCE(pargs.delay_time);                            \ | 
 | 	int nr_loops = ACCESS_ONCE(pargs.nr_loops);                                \ | 
 | 	bool fake_vc_ctx = ACCESS_ONCE(pargs.fake_vc_ctx);                         \ | 
 | 	bool adj_workers = ACCESS_ONCE(pargs.adj_workers);                         \ | 
 | 	uint64_t pre_lock, acq_lock, un_lock;                                      \ | 
 | 	struct time_stamp *this_time;                                              \ | 
 | 	struct mcs_lock_qnode mcs_qnode = MCS_QNODE_INIT;                          \ | 
 | 	struct mcs_pdro_qnode pdro_qnode = MCSPDRO_QNODE_INIT;                     \ | 
 | 	int i;                                                                     \ | 
 | 	/* guessing a unique vcoreid for vcoreid for the __mcspdr test.  if the | 
 | 	 * program gets preempted for that test, things may go nuts */             \ | 
 | 	pdro_qnode.vcoreid = thread_id + 1 % pargs.nr_threads;                     \ | 
 | 	/* Wait til all threads are created.  Ideally, I'd like to busywait unless | 
 | 	 * absolutely critical to yield */                                         \ | 
 | 	pthread_barrier_wait(&start_test);                                         \ | 
 | 	if (fake_vc_ctx) {                                                         \ | 
 | 		/* tells the kernel / other vcores we're in vc ctx */                  \ | 
 | 		uth_disable_notifs();                                                  \ | 
 | 		/* tricks ourselves into believing we're in vc ctx */                  \ | 
 | 		__vcore_context = TRUE;                                                \ | 
 | 	}                                                                          \ | 
 | 	for (i = 0; i < nr_loops; i++) {                                           \ | 
 | 		if (!run_locktest)                                                     \ | 
 | 			break;                                                             \ | 
 | 		pre_lock = read_tsc_serialized();                                      \ | 
 |                                                                                \ | 
 | 		lock_cmd                                                               \ | 
 |                                                                                \ | 
 | 		acq_lock = read_tsc_serialized();                                      \ | 
 | 		if (hold_time)                                                         \ | 
 | 			ndelay(hold_time);                                                 \ | 
 |                                                                                \ | 
 | 		unlock_cmd                                                             \ | 
 |                                                                                \ | 
 | 		un_lock = read_tsc_serialized();                                       \ | 
 | 		this_time = ×[thread_id][i];                                      \ | 
 | 		this_time->pre = pre_lock;                                             \ | 
 | 		this_time->acq = acq_lock;                                             \ | 
 | 		this_time->un = un_lock;                                               \ | 
 | 		/* Can turn these on or off to control which samples we gather */      \ | 
 | 		this_time->valid = TRUE;                                               \ | 
 | 		/* this_time->valid = (num_vcores() == max_vcores());  */              \ | 
 |                                                                                \ | 
 | 		if (delay_time)                                                        \ | 
 | 			ndelay(delay_time);                                                \ | 
 | 		/* worker thread ids are 0..n-1.  if we're one of the threads that's | 
 | 		 * beyond the VC count, we yield. */                                   \ | 
 | 		if (adj_workers && num_vcores() < thread_id + 1) {                     \ | 
 | 			if (fake_vc_ctx) {                                                 \ | 
 | 				__vcore_context = FALSE;                                       \ | 
 | 				uth_enable_notifs();                                           \ | 
 | 			}                                                                  \ | 
 | 			/* we'll come back up once we have enough VCs running */           \ | 
 | 			pthread_yield();                                                   \ | 
 | 			if (fake_vc_ctx) {                                                 \ | 
 | 				uth_disable_notifs();                                          \ | 
 | 				__vcore_context = TRUE;                                        \ | 
 | 			}                                                                  \ | 
 | 		}                                                                      \ | 
 | 		cmb();                                                                 \ | 
 | 	}                                                                          \ | 
 | 	/* First thread to finish stops the test */                                \ | 
 | 	run_locktest = FALSE;                                                      \ | 
 | 	if (fake_vc_ctx) {                                                         \ | 
 | 		__vcore_context = FALSE;                                               \ | 
 | 		uth_enable_notifs();                                                   \ | 
 | 	}                                                                          \ | 
 | 	return (void*)(long)i;                                                     \ | 
 | } | 
 |  | 
 | #define fake_lock_func(lock_name, x1, x2)                                      \ | 
 | void *lock_name##_thread(void *arg)                                            \ | 
 | {                                                                              \ | 
 | 	printf("Lock " #lock_name " not supported!\n");                            \ | 
 | 	exit(-1);                                                                  \ | 
 | } | 
 |  | 
 | spinlock_t spin_lock = SPINLOCK_INITIALIZER; | 
 | struct mcs_lock mcs_lock = MCS_LOCK_INIT; | 
 |  | 
 | /* Defines locking funcs like "mcs_thread" */ | 
 | lock_func(mcs, | 
 |           mcs_lock_lock(&mcs_lock, &mcs_qnode);, | 
 |           mcs_lock_unlock(&mcs_lock, &mcs_qnode);) | 
 | lock_func(mcscas, | 
 |           mcs_lock_lock(&mcs_lock, &mcs_qnode);, | 
 |           mcs_lock_unlock_cas(&mcs_lock, &mcs_qnode);) | 
 | lock_func(spin, | 
 |           spinlock_lock(&spin_lock);, | 
 |           spinlock_unlock(&spin_lock);) | 
 |  | 
 | #ifdef __ros__ | 
 | struct spin_pdr_lock spdr_lock = SPINPDR_INITIALIZER; | 
 | struct mcs_pdr_lock mcspdr_lock; | 
 | struct mcs_pdro_lock mcspdro_lock = MCSPDRO_LOCK_INIT; | 
 |  | 
 | lock_func(mcspdr, | 
 |           mcs_pdr_lock(&mcspdr_lock);, | 
 |           mcs_pdr_unlock(&mcspdr_lock);) | 
 | lock_func(mcspdro, | 
 |           mcs_pdro_lock(&mcspdro_lock, &pdro_qnode);, | 
 |           mcs_pdro_unlock(&mcspdro_lock, &pdro_qnode);) | 
 | lock_func(__mcspdro, | 
 |           __mcs_pdro_lock(&mcspdro_lock, &pdro_qnode);, | 
 |           __mcs_pdro_unlock(&mcspdro_lock, &pdro_qnode);) | 
 | lock_func(spinpdr, | 
 |           spin_pdr_lock(&spdr_lock);, | 
 |           spin_pdr_unlock(&spdr_lock);) | 
 | #else | 
 |  | 
 | fake_lock_func(mcspdr, 0, 0); | 
 | fake_lock_func(mcspdro, 0, 0); | 
 | fake_lock_func(__mcspdro, 0, 0); | 
 | fake_lock_func(spinpdr, 0, 0); | 
 |  | 
 | #endif | 
 |  | 
 | static int get_acq_latency(void **data, int i, int j, uint64_t *sample) | 
 | { | 
 | 	struct time_stamp **times = (struct time_stamp**)data; | 
 | 	/* 0 for initial time means we didn't measure */ | 
 | 	if (times[i][j].pre == 0) | 
 | 		return -1; | 
 | 	/* can optionally throw out invalid times (keep this in sync with the | 
 | 	 * lock_test macro, based on what you want to meaasure. */ | 
 | 	#if 0 | 
 | 	if (!times[i][j].valid) | 
 | 		return -1; | 
 | 	#endif | 
 | 	*sample = times[i][j].acq - times[i][j].pre - get_tsc_overhead(); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int get_hld_latency(void **data, int i, int j, uint64_t *sample) | 
 | { | 
 | 	struct time_stamp **times = (struct time_stamp**)data; | 
 | 	/* 0 for initial time means we didn't measure */ | 
 | 	if (times[i][j].pre == 0) | 
 | 		return -1; | 
 | 	*sample = times[i][j].un - times[i][j].acq - get_tsc_overhead(); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int get_acq_timestamp(void **data, int i, int j, uint64_t *sample) | 
 | { | 
 | 	struct time_stamp **times = (struct time_stamp**)data; | 
 | 	/* 0 for initial time means we didn't measure */ | 
 | 	if (times[i][j].pre == 0) | 
 | 		return -1; | 
 | 	*sample = times[i][j].acq; | 
 | 	return 0; | 
 | } | 
 |  | 
 | #ifdef __ros__ | 
 |  | 
 | /* Lousy event intercept.  build something similar in the event library? */ | 
 | #define MAX_NR_EVENT_TRACES 1000 | 
 | uint64_t preempts[MAX_NR_EVENT_TRACES] = {0}; | 
 | uint64_t indirs[MAX_NR_EVENT_TRACES] = {0}; | 
 | atomic_t preempt_idx; | 
 | atomic_t indir_idx; | 
 | atomic_t preempt_cnt; | 
 | atomic_t indir_cnt; | 
 |  | 
 | static void trace_preempt(struct event_msg *ev_msg, unsigned int ev_type, | 
 |                           void *data) | 
 | { | 
 | 	unsigned long my_slot = atomic_fetch_and_add(&preempt_idx, 1); | 
 | 	if (my_slot < MAX_NR_EVENT_TRACES) | 
 | 		preempts[my_slot] = read_tsc(); | 
 | 	atomic_inc(&preempt_cnt); | 
 | } | 
 |  | 
 | static void trace_indir(struct event_msg *ev_msg, unsigned int ev_type, | 
 |                         void *data) | 
 | { | 
 | 	unsigned long my_slot = atomic_fetch_and_add(&indir_idx, 1); | 
 | 	if (my_slot < MAX_NR_EVENT_TRACES) | 
 | 		indirs[my_slot] = read_tsc(); | 
 | 	atomic_inc(&indir_cnt); | 
 | } | 
 |  | 
 | /* Helper, prints out the preempt trace */ | 
 | static void print_preempt_trace(uint64_t starttsc, int nr_print_rows) | 
 | { | 
 | 	/* reusing nr_print_rows for the nr preempt/indirs rows as well */ | 
 | 	int preempt_rows = MIN(MAX_NR_EVENT_TRACES, nr_print_rows); | 
 | 	if (pargs.fake_vc_ctx) { | 
 | 		printf("No preempt trace available when faking vc ctx\n"); | 
 | 		return; | 
 | 	} | 
 | 	printf("\n"); | 
 | 	printf("Nr Preempts: %d\n", atomic_read(&preempt_cnt)); | 
 | 	printf("Nr Indirs  : %d\n", atomic_read(&indir_cnt)); | 
 | 	if (preempt_rows) | 
 | 		printf("Preempt/Indir events:\n-----------------\n"); | 
 | 	for (int i = 0; i < preempt_rows; i++) { | 
 | 		if (preempts[i]) | 
 | 			printf("Preempt %3d at %6llu\n", i, tsc2msec(preempts[i] | 
 | 			                                             - starttsc)); | 
 | 	} | 
 | 	for (int i = 0; i < preempt_rows; i++) { | 
 | 		if (indirs[i]) | 
 | 			printf("Indir   %3d at %6llu\n", i, tsc2msec(indirs[i] | 
 | 			                                             - starttsc)); | 
 | 	} | 
 | } | 
 |  | 
 | /* Make sure we have enough VCs for nr_threads, pref 1:1 at the start */ | 
 | static void os_prep_work(pthread_t *worker_threads, int nr_threads) | 
 | { | 
 | 	if (nr_threads > max_vcores()) { | 
 | 		printf("Too many threads (%d) requested, can't get more than %d vc\n", | 
 | 		       nr_threads, max_vcores()); | 
 | 		exit(-1); | 
 | 	} | 
 | 	atomic_init(&preempt_idx, 0); | 
 | 	atomic_init(&indir_idx, 0); | 
 | 	atomic_init(&preempt_cnt, 0); | 
 | 	atomic_init(&indir_cnt, 0); | 
 | 	parlib_never_yield = TRUE; | 
 | 	pthread_need_tls(FALSE); | 
 | 	pthread_mcp_init();					/* gives us one vcore */ | 
 | 	register_ev_handler(EV_VCORE_PREEMPT, trace_preempt, 0); | 
 | 	register_ev_handler(EV_CHECK_MSGS, trace_indir, 0); | 
 | 	if (pargs.fake_vc_ctx) { | 
 | 		/* need to disable events when faking vc ctx.  since we're looping and | 
 | 		 * not handling events, we could run OOM */ | 
 | 		clear_kevent_q(EV_VCORE_PREEMPT); | 
 | 		clear_kevent_q(EV_CHECK_MSGS); | 
 | 	} | 
 | 	vcore_request_total(nr_threads); | 
 | 	parlib_never_vc_request = TRUE; | 
 | 	for (int i = 0; i < nr_threads; i++) { | 
 | 		printd("Vcore %d mapped to pcore %d\n", i, | 
 | 		       __procinfo.vcoremap[i].pcoreid); | 
 | 	} | 
 | } | 
 |  | 
 | static void os_post_work(pthread_t *worker_threads, int nr_threads) | 
 | { | 
 | } | 
 |  | 
 | #endif | 
 |  | 
 | /* Argument parsing */ | 
 | static error_t parse_opt (int key, char *arg, struct argp_state *state) | 
 | { | 
 | 	struct prog_args *pargs = state->input; | 
 | 	switch (key) { | 
 | 		case 'w': | 
 | 			pargs->nr_threads = atoi(arg); | 
 | 			if (pargs->nr_threads < 0) { | 
 | 				printf("Negative nr_threads...\n\n"); | 
 | 				argp_usage(state); | 
 | 			} | 
 | 			break; | 
 | 		case 'l': | 
 | 			pargs->nr_loops = atoi(arg); | 
 | 			if (pargs->nr_loops < 0) { | 
 | 				printf("Negative nr_loops...\n\n"); | 
 | 				argp_usage(state); | 
 | 			} | 
 | 			break; | 
 | 		case OPT_ADJ_WORKERS: | 
 | 			pargs->adj_workers = TRUE; | 
 | 			break; | 
 | 		case OPT_VC_CTX: | 
 | 			pargs->fake_vc_ctx = TRUE; | 
 | 			break; | 
 | 		case 'h': | 
 | 			pargs->hold_time = atoi(arg); | 
 | 			if (pargs->hold_time < 0) { | 
 | 				printf("Negative hold_time...\n\n"); | 
 | 				argp_usage(state); | 
 | 			} | 
 | 			break; | 
 | 		case 'd': | 
 | 			pargs->delay_time = atoi(arg); | 
 | 			if (pargs->delay_time < 0) { | 
 | 				printf("Negative delay_time...\n\n"); | 
 | 				argp_usage(state); | 
 | 			} | 
 | 			break; | 
 | 		case 'o': | 
 | 			pargs->outfile_path = arg; | 
 | 			break; | 
 | 		case 'p': | 
 | 			pargs->nr_print_rows = atoi(arg); | 
 | 			if (pargs->nr_print_rows < 0) { | 
 | 				printf("Negative print_rows...\n\n"); | 
 | 				argp_usage(state); | 
 | 			} | 
 | 			break; | 
 | 		case 't': | 
 | 			if (!strcmp("mcs", arg)) { | 
 | 				pargs->lock_type = mcs_thread; | 
 | 				break; | 
 | 			} | 
 | 			if (!strcmp("mcscas", arg)) { | 
 | 				pargs->lock_type = mcscas_thread; | 
 | 				break; | 
 | 			} | 
 | 			if (!strcmp("mcspdr", arg)) { | 
 | 				pargs->lock_type = mcspdr_thread; | 
 | 				break; | 
 | 			} | 
 | 			if (!strcmp("mcspdro", arg)) { | 
 | 				pargs->lock_type = mcspdro_thread; | 
 | 				break; | 
 | 			} | 
 | 			if (!strcmp("__mcspdro", arg)) { | 
 | 				pargs->lock_type = __mcspdro_thread; | 
 | 				break; | 
 | 			} | 
 | 			if (!strcmp("spin", arg)) { | 
 | 				pargs->lock_type = spin_thread; | 
 | 				break; | 
 | 			} | 
 | 			if (!strcmp("spinpdr", arg)) { | 
 | 				pargs->lock_type = spinpdr_thread; | 
 | 				break; | 
 | 			} | 
 | 			printf("Unknown locktype %s\n\n", arg); | 
 | 			argp_usage(state); | 
 | 			break; | 
 | 		case ARGP_KEY_ARG: | 
 | 			printf("Warning, extra argument %s ignored\n\n", arg); | 
 | 			break; | 
 | 		case ARGP_KEY_END: | 
 | 			if (!pargs->nr_threads) { | 
 | 				printf("Must select a number of threads.\n\n"); | 
 | 				argp_usage(state); | 
 | 				break; | 
 | 			} | 
 | 			if (!pargs->nr_loops) { | 
 | 				printf("Must select a number of loops.\n\n"); | 
 | 				argp_usage(state); | 
 | 				break; | 
 | 			} | 
 | 			if (!pargs->lock_type) { | 
 | 				printf("Must select a type of lock.\n\n"); | 
 | 				argp_usage(state); | 
 | 				break; | 
 | 			} | 
 | 			break; | 
 | 		default: | 
 | 			return ARGP_ERR_UNKNOWN; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct argp argp = {options, parse_opt, args_doc, doc}; | 
 |  | 
 | int main(int argc, char** argv) | 
 | { | 
 | 	pthread_t *worker_threads; | 
 | 	void **loops_done; | 
 | 	struct timeval start_tv = {0}; | 
 | 	struct timeval end_tv = {0}; | 
 | 	long usec_diff, total_loops = 0; | 
 | 	uint64_t starttsc; | 
 | 	int nr_threads, nr_loops; | 
 | 	FILE *outfile; | 
 | 	struct sample_stats acq_stats, hld_stats; | 
 |  | 
 | 	argp_parse(&argp, argc, argv, 0, 0, &pargs); | 
 | 	nr_threads = pargs.nr_threads; | 
 | 	nr_loops = pargs.nr_loops; | 
 | 	mcs_pdr_init(&mcspdr_lock); | 
 |  | 
 | 	if (pargs.outfile_path) { | 
 | 		/* RDWR, CREAT, TRUNC, O666 */ | 
 | 		outfile = fopen(pargs.outfile_path, "w+"); | 
 | 		if (!outfile) { | 
 | 			perror("outfile"); | 
 | 			exit(-1); | 
 | 		} | 
 | 	} | 
 | 	worker_threads = malloc(sizeof(pthread_t) * nr_threads); | 
 | 	if (!worker_threads) { | 
 | 		perror("pthread_t malloc failed:"); | 
 | 		exit(-1); | 
 | 	} | 
 | 	loops_done = malloc(sizeof(void*) * nr_threads); | 
 | 	if (!loops_done) { | 
 | 		perror("loops_done malloc failed"); | 
 | 		exit(-1); | 
 | 	} | 
 | 	printf("Making %d workers of %d loops each, %sadapting workers to vcores, " | 
 | 	       "and %sfaking vcore context\n", nr_threads, nr_loops, | 
 | 	       pargs.adj_workers ? "" : "not ", | 
 | 	       pargs.fake_vc_ctx ? "" : "not "); | 
 | 	pthread_barrier_init(&start_test, NULL, nr_threads); | 
 |  | 
 | 	times = malloc(sizeof(struct time_stamp *) * nr_threads); | 
 | 	assert(times); | 
 | 	for (int i = 0; i < nr_threads; i++) { | 
 | 		times[i] = malloc(sizeof(struct time_stamp) * nr_loops); | 
 | 		if (!times[i]) { | 
 | 			perror("Record keeping malloc"); | 
 | 			exit(-1); | 
 | 		} | 
 | 		memset(times[i], 0, sizeof(struct time_stamp) * nr_loops); | 
 | 	} | 
 | 	printf("Record tracking takes %ld bytes of memory\n", | 
 | 	       nr_threads * nr_loops * sizeof(struct time_stamp)); | 
 | 	os_prep_work(worker_threads, nr_threads);	/* ensure we have enough VCs */ | 
 | 	/* Doing this in MCP ctx, so we might have been getting a few preempts | 
 | 	 * already.  Want to read start before the threads pass their barrier */ | 
 | 	starttsc = read_tsc(); | 
 | 	/* create and join on yield */ | 
 | 	for (long i = 0; i < nr_threads; i++) { | 
 | 		if (pthread_create(&worker_threads[i], NULL, pargs.lock_type, | 
 | 		                   (void*)i)) | 
 | 			perror("pth_create failed"); | 
 | 	} | 
 | 	os_post_work(worker_threads, nr_threads); | 
 | 	if (gettimeofday(&start_tv, 0)) | 
 | 		perror("Start time error..."); | 
 | 	for (int i = 0; i < nr_threads; i++) { | 
 | 		pthread_join(worker_threads[i], &loops_done[i]); | 
 | 	} | 
 | 	if (gettimeofday(&end_tv, 0)) | 
 | 		perror("End time error..."); | 
 |  | 
 | 	printf("Acquire times (TSC Ticks)\n---------------------------\n"); | 
 | 	acq_stats.get_sample = get_acq_latency; | 
 | 	compute_stats((void**)times, nr_threads, nr_loops, &acq_stats); | 
 |  | 
 | 	printf("Held times (from acq til rel done) (TSC Ticks)\n------\n"); | 
 | 	hld_stats.get_sample = get_hld_latency; | 
 | 	compute_stats((void**)times, nr_threads, nr_loops, &hld_stats); | 
 |  | 
 | 	usec_diff = (end_tv.tv_sec - start_tv.tv_sec) * 1000000 + | 
 | 	            (end_tv.tv_usec - start_tv.tv_usec); | 
 | 	printf("Time to run: %ld usec\n", usec_diff); | 
 |  | 
 | 	printf("\nLock throughput:\n-----------------\n"); | 
 | 	/* throughput for the entire duration (in ms), 1ms steps.  print as many | 
 | 	 * steps as they ask for (up to the end of the run). */ | 
 | 	print_throughput((void**)times, usec_diff / 1000 + 1, msec2tsc(1), | 
 | 	                 pargs.nr_print_rows, | 
 | 	                 starttsc, nr_threads, | 
 | 	                 nr_loops, get_acq_timestamp); | 
 | 	print_preempt_trace(starttsc, pargs.nr_print_rows); | 
 |  | 
 | 	for (int i = 0; i < nr_threads; i++) { | 
 | 		total_loops += (long)loops_done[i]; | 
 | 		if (!loops_done[i]) | 
 | 			printf("WARNING: thread %d performed 0 loops!\n", i); | 
 | 	} | 
 | 	printf("Average number of loops done, per thread: %ld\n", | 
 | 	       total_loops / nr_threads); | 
 | 	for (int i = 0; i < nr_threads; i++) | 
 | 		printf("\tThread %d performed %lu loops\n", i, (long)loops_done[i]); | 
 |  | 
 | 	if (pargs.outfile_path) { | 
 | 		fprintf(outfile, "#"); | 
 | 		for (char **arg = argv; *arg; arg++) | 
 | 			fprintf(outfile, " %s", *arg); | 
 | 		fprintf(outfile, "\n"); | 
 | 		fprintf(outfile, "# thread_id attempt pre acq(uire) un(lock) " | 
 | 		                 "tsc_overhead\n"); | 
 | 		fprintf(outfile, "# acquire latency: acq - pre - tsc_overhead\n"); | 
 | 		fprintf(outfile, "# hold time: un - acq - tsc_overhead\n"); | 
 | 		fprintf(outfile, "# tsc_frequency %llu\n", get_tsc_freq()); | 
 | 		fprintf(outfile, "# tsc_overhead is 0 on linux, hard code it with a " | 
 | 		                 "value from akaros\n"); | 
 | 		for (int i = 0; i < nr_threads; i++) { | 
 | 			for (int j = 0; j < nr_loops; j++) { | 
 | 				struct time_stamp *ts = ×[i][j]; | 
 | 				if (!ts->pre) | 
 | 					break; /* empty record */ | 
 | 				fprintf(outfile, "%d %d %llu %llu %llu %llu\n", i, j, ts->pre, | 
 | 				        ts->acq, ts->un, get_tsc_overhead()); | 
 | 			} | 
 | 		} | 
 | 		fclose(outfile); | 
 | 	} | 
 | 	printf("Done, exiting\n"); | 
 | } |