[PATCH] Multi-process perf test

Douglas Bagnall douglas.bagnall at catalyst.net.nz
Thu Jun 29 00:00:26 UTC 2017


hi Andrew,

I see a few minor things.

On 29/06/17 10:27, Andrew Bartlett via samba-technical wrote:
> G'Day Douglas,
> 
> Can you please take a look at the attached multi-process perf test,
> which has been kicking around since we started on the multi-process
> LDAP server work?
> 
> It would be nice to get it into master, as all our current tests are
> single-threaded. 
> 
> The test uses the python messaging code we landed a few months ago for
> coordination between the workers.
> 
> Thanks,
> 
> Andrew Bartlett
> -- Andrew Bartlett http://samba.org/~abartlet/ Authentication Developer,
> Samba Team http://samba.org Samba Developer, Catalyst IT
> http://catalyst.net.nz/services/samba
> 
> 
> 0001-perf-tests-Add-tests-running-a-bind-in-parallel.patch
> 
> 
> From 6da73736483d246715047365b19d6608294c180c Mon Sep 17 00:00:00 2001
> From: Andrew Bartlett <abartlet at samba.org>
> Date: Wed, 29 Mar 2017 15:43:44 +1300
> Subject: [PATCH] perf-tests: Add tests running a bind in parallel
> 
> We hope to show that with multiple clients, that multiple servers make sense
> 
> Signed-off-by: Andrew Bartlett <abartlet at samba.org>
> ---
>  selftest/perf_tests.py                          |  14 ++
>  source4/dsdb/tests/python/ad_dc_multi_bind.py   |  48 +++++--
>  source4/dsdb/tests/python/ad_dc_multi_ops.py    | 115 +++++++++++++++++
>  source4/dsdb/tests/python/ad_dc_multi_search.py | 164 ++++++++++++++++++++++++
>  4 files changed, 329 insertions(+), 12 deletions(-)
>  create mode 100644 source4/dsdb/tests/python/ad_dc_multi_ops.py
>  create mode 100644 source4/dsdb/tests/python/ad_dc_multi_search.py
> 
> diff --git a/selftest/perf_tests.py b/selftest/perf_tests.py
> index 9b4289b..31fc631 100644
> --- a/selftest/perf_tests.py
> +++ b/selftest/perf_tests.py
> @@ -73,6 +73,20 @@ plantestsuite_loadlist("samba4.ldb.multi_connect.python(ad_dc_ntvfs)",
>                          'tdb://$PREFIX_ABS/ad_dc_ntvfs/private/sam.ldb'
>                          '$LOADLIST', '$LISTOPT'])
>  
> +plantestsuite_loadlist("samba4.ldap.ad_dc_multi_search.krb5.python(ad_dc_ntvfs)",
> +                       "ad_dc_ntvfs",
> +                       [python, os.path.join(samba4srcdir,
> +                                             "dsdb/tests/python/ad_dc_multi_search.py"),
> +                        '$SERVER', '-U"$USERNAME%$PASSWORD"', '-k yes',
> +                        '--realm=$REALM',
> +                        '$LOADLIST', '$LISTOPT'])
> +
> +plantestsuite_loadlist("samba4.ldb.multi_search.python(ad_dc_ntvfs)",
> +                       "ad_dc_ntvfs",
> +                       [python, os.path.join(samba4srcdir,
> +                                             "dsdb/tests/python/ad_dc_multi_search.py"),
> +                        'tdb://$PREFIX_ABS/ad_dc_ntvfs/private/sam.ldb'
> +                        '$LOADLIST', '$LISTOPT'])
>  
>  # this one doesn't tidy itself up fully, so leave it as last unless
>  # you want a messy database.
> diff --git a/source4/dsdb/tests/python/ad_dc_multi_bind.py b/source4/dsdb/tests/python/ad_dc_multi_bind.py
> index 10daed5..04762db 100644
> --- a/source4/dsdb/tests/python/ad_dc_multi_bind.py
> +++ b/source4/dsdb/tests/python/ad_dc_multi_bind.py
> @@ -32,10 +32,7 @@ except ImportError:
>  
>  from samba.samdb import SamDB
>  from samba.auth import system_session
> -from ldb import Message, MessageElement, Dn, LdbError
> -from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE
> -from ldb import SCOPE_BASE, SCOPE_SUBTREE, SCOPE_ONELEVEL
> -
> +from ldb import SCOPE_BASE, SCOPE_SUBTREE
>  parser = optparse.OptionParser("ad_dc_mulit_bind.py [options] <host>")
>  sambaopts = options.SambaOptions(parser)
>  parser.add_option_group(sambaopts)
> @@ -50,7 +47,6 @@ credopts = options.CredentialsOptions(parser)
>  parser.add_option_group(credopts)
>  opts, args = parser.parse_args()
>  
> -
>  if len(args) < 1:
>      parser.print_usage()
>      sys.exit(1)
> @@ -60,23 +56,51 @@ host = args[0]
>  lp = sambaopts.get_loadparm()
>  creds = credopts.get_credentials(lp)
>  

This import should really have happened up there somewhere ^

> +import ad_dc_multi_ops

and you change the name of the class here, but not in the "if
ANCIENT_SAMBA" block at the bottom, meaning it won't work before 4.3.

> -class UserTests(samba.tests.TestCase):
> +class MultiBindTests(ad_dc_multi_ops.MultiOpsTests):


>      def setUp(self):
> -        super(UserTests, self).setUp()
> +        super(MultiBindTests, self).setUp()
>          self.lp = lp
>  
> -    def tearDown(self):
> -        super(UserTests, self).tearDown()
> +    def test_500_concurrent_binds_1_worker(self):
> +        def bind(self):
> +            samdb = SamDB(host, credentials=creds,
> +                          session_info=system_session(self.lp), lp=self.lp)
> +            samdb.search(base=samdb.domain_dn(),
> +                         scope=SCOPE_BASE, attrs=["*"])

I like this self._test_concurrent_things().

> -    def test_1000_binds(self):
> +        self._test_concurrent_things(bind, 1)
>  
> -        for x in range(1, 1000):
> +
> +    def test_500_concurrent_binds_2_workers(self):
> +        def bind(self):
>              samdb = SamDB(host, credentials=creds,
> -                         session_info=system_session(self.lp), lp=self.lp)
> +                          session_info=system_session(self.lp), lp=self.lp)
>              samdb.search(base=samdb.domain_dn(),
>                           scope=SCOPE_BASE, attrs=["*"])
>  
> +        self._test_concurrent_things(bind, 2)
> +
> +
> +    def test_500_concurrent_binds_5_workers(self):
> +        def bind(self):
> +            samdb = SamDB(host, credentials=creds,
> +                          session_info=system_session(self.lp), lp=self.lp)
> +            samdb.search(base=samdb.domain_dn(),
> +                         scope=SCOPE_BASE, attrs=["*"])
> +
> +        self._test_concurrent_things(bind, 5)
> +
> +
> +    def test_500_concurrent_binds_10_workers(self):
> +        def subtree_search(self):
> +            samdb = SamDB(host, credentials=creds,
> +                          session_info=system_session(self.lp), lp=self.lp)
> +            samdb.search(base=samdb.domain_dn(),
> +                         scope=SCOPE_SUBTREE, attrs=["*"])
> +
> +        self._test_concurrent_things(subtree_search, 10)
>  
>  if "://" not in host:
>      if os.path.isfile(host):
> diff --git a/source4/dsdb/tests/python/ad_dc_multi_ops.py b/source4/dsdb/tests/python/ad_dc_multi_ops.py
> new file mode 100644
> index 0000000..0bbc1d0
> --- /dev/null
> +++ b/source4/dsdb/tests/python/ad_dc_multi_ops.py
> @@ -0,0 +1,115 @@
> +#!/usr/bin/env python
> +# -*- coding: utf-8 -*-
> +import optparse
> +import sys
> +sys.path.insert(0, 'bin/python')
> +
> +import os
> +import samba

None of the next 4 imports are used, so it would be better not to
import them (unless you intend to use command-line options?).

> +import samba.getopt as options
> +import random
> +import tempfile
> +import shutil

> +import time

And none of this next stuff is used. If you want the test to work
before Samba 4.3, there is a corresponding chunk of boilerplate to add
at the bottom. Otherwise leave it all out.

> +
> +# We try to use the test infrastructure of Samba 4.3+, but if it
> +# doesn't work, we are probably in a back-ported patch and trying to
> +# run on 4.1 or something.
> +#
> +# Don't copy this horror into ordinary tests -- it is special for
> +# performance tests that want to apply to old versions.
> +try:
> +    from samba.tests.subunitrun import SubunitOptions, TestProgram
> +    ANCIENT_SAMBA = False
> +except ImportError:
> +    ANCIENT_SAMBA = True
> +    samba.ensure_external_module("testtools", "testtools")
> +    samba.ensure_external_module("subunit", "subunit/python")
> +    from subunit.run import SubunitTestRunner
> +    import unittest
> +

You do use Messaging:

> +from samba.messaging import Messaging

.. but not messaging:

> +from samba.dcerpc import messaging

os and time are already imported.

> +import os, signal
> +import time


> +class MultiOpsTests(samba.tests.TestCase):
> +
> +    def get_context(self, *args, **kwargs):
> +        kwargs['lp_ctx'] = samba.tests.env_loadparm()
> +        return Messaging(*args, **kwargs)
> +
> +    def setUp(self):
> +        super(MultiOpsTests, self).setUp()
> +
> +    def tearDown(self):
> +        super(MultiOpsTests, self).tearDown()
>
> +    def _test_concurrent_things(self, task, workers, num_ops=500):
> +
> +        # Run the task once to ensure we catch silly errors early
> +        task(self)

The next two variables are unused:

> +        msg_id_send = messaging.MSG_TMP_BASE + 100
> +        msg_id_recv = 0


> +        got_work_done = {"count": 0, "errors": 0}
> +
> +        def work_done_callback(got_work_done, msg_type, src, failure):
> +            got_work_done["count"] += 1
> +            if failure != "":
> +                got_work_done["errors"] += 1
> +                print failure + "\n"
> +            os.kill(src.pid, signal.SIGTERM)
> +
> +        master_ctx = self.get_context((1,))
> +        msg_id_done = master_ctx.register((work_done_callback, got_work_done))
> +
> +        pids = []
> +
> +        for y in range(workers):
> +
> +            pid = os.fork()
> +            if pid == 0:
> +                worker_ctx = self.get_context((1,))
> +
> +                failed = False
> +                for x in range(num_ops / workers):
> +                    try:
> +                        task(self)
> +                    except Exception as err:
> +                        failed = True
> +                        worker_ctx.send(master_ctx.server_id, msg_id_done, str(err))
> +                        break

> +                if failed == False:
                   ^^^^^^^^^^^^^^^^^^
I would prefer  "if not failed:"

> +                    worker_ctx.send(master_ctx.server_id, msg_id_done, "")
> +
> +                worker_ctx.loop_once(60)
> +                os._exit(0)
> +            else:
> +                pids.append(pid)
> +
> +            # Wait a little after each fork and run the event loop
> +            timeout = False
> +            start_time = time.time()
> +            while (got_work_done["count"] < workers) and not timeout:
> +                master_ctx.loop_once(0.1)
> +                if time.time() - start_time > 0.5:
> +                    timeout = True
> +
> +        timeout = False
> +        start_time = time.time()
> +
> +        while (got_work_done["count"] < workers) and not timeout:
> +            master_ctx.loop_once(0.1)
> +            if time.time() - start_time > num_ops:
> +                timeout = True
> +
> +        for pid in pids:
> +            if timeout:
> +                os.kill(pid, signal.SIGTERM)
> +            os.waitpid(pid, 0)
> +
> +        self.assertFalse(timeout, "Timeout waiting for child tasks to exit")
> +        self.assertEquals(got_work_done["errors"], 0)
> diff --git a/source4/dsdb/tests/python/ad_dc_multi_search.py b/source4/dsdb/tests/python/ad_dc_multi_search.py
> new file mode 100644
> index 0000000..14cac0f
> --- /dev/null
> +++ b/source4/dsdb/tests/python/ad_dc_multi_search.py
> @@ -0,0 +1,164 @@
> +#!/usr/bin/env python
> +# -*- coding: utf-8 -*-
> +import optparse
> +import sys
> +sys.path.insert(0, 'bin/python')
> +
> +import os
> +import samba
> +import samba.getopt as options

Four more unused imports:

> +import random
> +import tempfile
> +import shutil
> +import time
> +
> +
> +# We try to use the test infrastructure of Samba 4.3+, but if it
> +# doesn't work, we are probably in a back-ported patch and trying to
> +# run on 4.1 or something.
> +#
> +# Don't copy this horror into ordinary tests -- it is special for
> +# performance tests that want to apply to old versions.
> +try:
> +    from samba.tests.subunitrun import SubunitOptions, TestProgram
> +    ANCIENT_SAMBA = False
> +except ImportError:
> +    ANCIENT_SAMBA = True
> +    samba.ensure_external_module("testtools", "testtools")
> +    samba.ensure_external_module("subunit", "subunit/python")
> +    from subunit.run import SubunitTestRunner
> +    import unittest
> +
> +from samba.samdb import SamDB
> +from samba.auth import system_session
> +from ldb import SCOPE_BASE, SCOPE_SUBTREE, SCOPE_ONELEVEL
> +import ad_dc_multi_ops
> +parser = optparse.OptionParser("ad_dc_mulit_bind.py [options] <host>")
> +sambaopts = options.SambaOptions(parser)
> +parser.add_option_group(sambaopts)
> +parser.add_option_group(options.VersionOptions(parser))
> +
> +if not ANCIENT_SAMBA:
> +    subunitopts = SubunitOptions(parser)
> +    parser.add_option_group(subunitopts)
> +
> +# use command line creds if available
> +credopts = options.CredentialsOptions(parser)
> +parser.add_option_group(credopts)
> +opts, args = parser.parse_args()
> +
> +if len(args) < 1:
> +    parser.print_usage()
> +    sys.exit(1)
> +
> +host = args[0]
> +
> +lp = sambaopts.get_loadparm()
> +creds = credopts.get_credentials(lp)
> +
> +from ldb import SCOPE_BASE, SCOPE_SUBTREE, SCOPE_ONELEVEL
> +
> +import ad_dc_multi_ops
> +class MultiSearchTests(ad_dc_multi_ops.MultiOpsTests):
> +    def setUp(self):
> +        super(MultiSearchTests, self).setUp()
> +        self.lp = lp
> +
> +    def test_250_concurrent_subtree_search_1_worker(self):
> +        def subtree_search(self):
> +            samdb = SamDB(host, credentials=creds,
> +                          session_info=system_session(self.lp), lp=self.lp)
> +            samdb.search(base=samdb.domain_dn(),
> +                         scope=SCOPE_SUBTREE, attrs=["*"])
> +
> +        self._test_concurrent_things(subtree_search, 1, num_ops=250)
> +
> +
> +    def test_500_concurrent_unindexed_search_1_workers(self):
> +        def unindexed_search(self):
> +            samdb = SamDB(host, credentials=creds,
> +                          session_info=system_session(self.lp), lp=self.lp)
> +            samdb.search(base=samdb.domain_dn(),
> +                         expression="samAccountName=administrator*",
> +                         scope=SCOPE_SUBTREE, attrs=["*"])
> +
> +        self._test_concurrent_things(unindexed_search, 1)
> +
> +
> +    def test_250_concurrent_subtree_search_2_workers(self):
> +        def subtree_search(self):
> +            samdb = SamDB(host, credentials=creds,
> +                          session_info=system_session(self.lp), lp=self.lp)
> +            samdb.search(base=samdb.domain_dn(),
> +                         scope=SCOPE_SUBTREE, attrs=["*"])
> +
> +        self._test_concurrent_things(subtree_search, 2, num_ops=250)
> +
> +
> +    def test_500_concurrent_unindexed_search_2_workers(self):
> +        def unindexed_search(self):
> +            samdb = SamDB(host, credentials=creds,
> +                          session_info=system_session(self.lp), lp=self.lp)
> +            samdb.search(base=samdb.domain_dn(),
> +                         expression="samAccountName=administrator*",
> +                         scope=SCOPE_SUBTREE, attrs=["*"])
> +
> +        self._test_concurrent_things(unindexed_search, 2)
> +
> +
> +    def test_250_concurrent_subtree_search_5_workers(self):
> +        def subtree_search(self):
> +            samdb = SamDB(host, credentials=creds,
> +                          session_info=system_session(self.lp), lp=self.lp)
> +            samdb.search(base=samdb.domain_dn(),
> +                         scope=SCOPE_SUBTREE, attrs=["*"])
> +
> +        self._test_concurrent_things(subtree_search, 5, num_ops=250)
> +
> +
> +    def test_500_concurrent_unindexed_search_5_workers(self):
> +        def unindexed_search(self):
> +            samdb = SamDB(host, credentials=creds,
> +                          session_info=system_session(self.lp), lp=self.lp)
> +            samdb.search(base=samdb.domain_dn(),
> +                         expression="samAccountName=administrator*",
> +                         scope=SCOPE_SUBTREE, attrs=["*"])
> +
> +        self._test_concurrent_things(unindexed_search, 5)
> +
> +
> +    def test_250_concurrent_subtree_search_10_workers(self):
> +        def subtree_search(self):
> +            samdb = SamDB(host, credentials=creds,
> +                          session_info=system_session(self.lp), lp=self.lp)
> +            samdb.search(base=samdb.domain_dn(),
> +                         scope=SCOPE_SUBTREE, attrs=["*"])
> +
> +        self._test_concurrent_things(subtree_search, 10, num_ops=250)
> +
> +
> +    def test_500_concurrent_unindexed_search_10_workers(self):
> +        def unindexed_search(self):
> +            samdb = SamDB(host, credentials=creds,
> +                          session_info=system_session(self.lp), lp=self.lp)
> +            samdb.search(base=samdb.domain_dn(),
> +                         expression="samAccountName=administrator*",
> +                         scope=SCOPE_SUBTREE, attrs=["*"])
> +
> +        self._test_concurrent_things(unindexed_search, 10)
> +
> +
> +if "://" not in host:
> +    if os.path.isfile(host):
> +        host = "tdb://%s" % host
> +    else:
> +        host = "ldap://%s" % host
> +
> +
> +if ANCIENT_SAMBA:
> +    runner = SubunitTestRunner()

Your class is called "MultiSearchTests", not "UserTests":

> +    if not runner.run(unittest.makeSuite(UserTests)).wasSuccessful():


> +        sys.exit(1)
> +    sys.exit(0)
> +else:
> +    TestProgram(module=__name__, opts=subunitopts)
> -- 2.9.4
> 

It seems the extra imports in the new files were cut-and-paste
inherited from ad_dc_multi_bind.py, so I have taken the liberty of
fixing them there and squashed it all into the attached patch. It
appears to run.

Also, line length and whitespace are close to perfect!

cheers,
Douglas
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0001-perf-tests-Add-tests-running-a-bind-in-parallel.patch
Type: text/x-patch
Size: 14716 bytes
Desc: not available
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20170629/8ea03462/0001-perf-tests-Add-tests-running-a-bind-in-parallel.bin>


More information about the samba-technical mailing list