From e4d452b4f896b0b7eb87dae696fdf0796e481525 Mon Sep 17 00:00:00 2001
From: Michael Hanselmann <hansmi@google.com>
Date: Thu, 22 Dec 2011 14:16:57 +0100
Subject: [PATCH] Add lock performance utility
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

I had an idea for improving locking performance. To see if it worked I
wrote this tool. Unfortunately the idea didn't quite work (broke
unittests left and right), but the tool is still handy for evaluating
future changes to the β€œSharedLock” class.

It is not installed or run at build/test time. In its current form it is
intended for manual use. Example output:

---
Total number of acquisitions: 32642
Per-thread acquisitions:
  Thread 0: 6536 (20.0%)
  Thread 1: 6488 (19.9%)
  Thread 2: 6536 (20.0%)
  Thread 3: 6529 (20.0%)
  Thread 4: 6553 (20.1%)
Benchmark CPU time: 5.010s
Average time per lock acquisition: 0.15348ms
Process:
  User time: 4.160s
  System time: 1.030s
  Total time: 5.190s
---

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>
---
 Makefile.am      |   1 +
 test/lockperf.py | 145 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 146 insertions(+)
 create mode 100755 test/lockperf.py

diff --git a/Makefile.am b/Makefile.am
index bccae3d0c..b645caba9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -587,6 +587,7 @@ EXTRA_DIST = \
 	doc/examples/gnt-debug/README \
 	doc/examples/gnt-debug/delay0.json \
 	doc/examples/gnt-debug/delay50.json \
+	test/lockperf.py \
 	test/testutils.py \
 	test/mocks.py \
 	$(dist_TESTS) \
diff --git a/test/lockperf.py b/test/lockperf.py
new file mode 100755
index 000000000..dddd019e2
--- /dev/null
+++ b/test/lockperf.py
@@ -0,0 +1,145 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2011 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for testing lock performance"""
+
+import os
+import sys
+import time
+import optparse
+import threading
+import resource
+
+from ganeti import locking
+
+
+def ParseOptions():
+  """Parses the command line options.
+
+  In case of command line errors, it will show the usage and exit the
+  program.
+
+  @return: the options in a tuple
+
+  """
+  parser = optparse.OptionParser()
+  parser.add_option("-t", dest="thread_count", default=1, type="int",
+                    help="Number of threads", metavar="NUM")
+  parser.add_option("-d", dest="duration", default=5, type="float",
+                    help="Duration", metavar="SECS")
+
+  (opts, args) = parser.parse_args()
+
+  if opts.thread_count < 1:
+    parser.error("Number of threads must be at least 1")
+
+  return (opts, args)
+
+
+class State:
+  def __init__(self, thread_count):
+    """Initializes this class.
+
+    """
+    self.verify = [False for _ in range(thread_count)]
+    self.counts = [0 for _ in range(thread_count)]
+    self.total_count = 0
+
+
+def _Counter(lock, state, me):
+  """Thread function for acquiring locks.
+
+  """
+  counts = state.counts
+  verify = state.verify
+
+  while True:
+    lock.acquire()
+    try:
+      verify[me] = 1
+
+      counts[me] += 1
+
+      state.total_count += 1
+
+      if state.total_count % 1000 == 0:
+        sys.stdout.write(" %8d\r" % state.total_count)
+        sys.stdout.flush()
+
+      if sum(verify) != 1:
+        print "Inconsistent state!"
+        os._exit(1) # pylint: disable=W0212
+
+      verify[me] = 0
+    finally:
+      lock.release()
+
+
+def main():
+  (opts, _) = ParseOptions()
+
+  lock = locking.SharedLock("TestLock")
+
+  state = State(opts.thread_count)
+
+  lock.acquire(shared=0)
+  try:
+    for i in range(opts.thread_count):
+      t = threading.Thread(target=_Counter, args=(lock, state, i))
+      t.setDaemon(True)
+      t.start()
+
+    start = time.clock()
+  finally:
+    lock.release()
+
+  while True:
+    if (time.clock() - start) > opts.duration:
+      break
+    time.sleep(0.1)
+
+  # Make sure we get a consistent view
+  lock.acquire(shared=0)
+
+  lock_cputime = time.clock() - start
+
+  res = resource.getrusage(resource.RUSAGE_SELF)
+
+  print "Total number of acquisitions: %s" % state.total_count
+  print "Per-thread acquisitions:"
+  for (i, count) in enumerate(state.counts):
+    print ("  Thread %s: %d (%0.1f%%)" %
+           (i, count, (100.0 * count / state.total_count)))
+
+  print "Benchmark CPU time: %0.3fs" % lock_cputime
+  print ("Average time per lock acquisition: %0.5fms" %
+         (1000.0 * lock_cputime / state.total_count))
+  print "Process:"
+  print "  User time: %0.3fs" % res.ru_utime
+  print "  System time: %0.3fs" % res.ru_stime
+  print "  Total time: %0.3fs" % (res.ru_utime + res.ru_stime)
+
+  # Exit directly without attempting to clean up threads
+  os._exit(0) # pylint: disable=W0212
+
+
+if __name__ == "__main__":
+  main()
-- 
GitLab