[folks] Add a performance test for contacts in e-d-s



commit c8c1642adb85c7bbe2baf3c0a15f4994bcdb270d
Author: Simon McVittie <simon mcvittie collabora co uk>
Date:   Mon Mar 18 20:28:20 2013 +0000

    Add a performance test for contacts in e-d-s

 tests/eds/Makefile.am |    5 +
 tests/eds/perf.vala   |  406 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 411 insertions(+), 0 deletions(-)
---
diff --git a/tests/eds/Makefile.am b/tests/eds/Makefile.am
index ef0b130..642e1f3 100644
--- a/tests/eds/Makefile.am
+++ b/tests/eds/Makefile.am
@@ -78,6 +78,7 @@ TESTS = \
        anti-linking \
        enable-disable-stores \
        set-is-favourite \
+       perf \
        $(NULL)
 
 RUN_WITH_PRIVATE_BUS = $(top_srcdir)/tests/tools/with-session-bus-eds.sh
@@ -227,6 +228,10 @@ store_removed_SOURCES = \
        store-removed.vala \
        $(NULL)
 
+perf_SOURCES = \
+       perf.vala \
+       $(NULL)
+
 helper_create_many_contacts_SOURCES = \
        helper-create-many-contacts.vala \
        $(NULL)
diff --git a/tests/eds/perf.vala b/tests/eds/perf.vala
new file mode 100644
index 0000000..1609bf7
--- /dev/null
+++ b/tests/eds/perf.vala
@@ -0,0 +1,406 @@
+/*
+ * Copyright © 2011 Collabora Ltd.
+ * Copyright © 2013 Intel Corporation
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *    Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *    Simon McVittie <simon mcvittie collabora co uk>
+ */
+
+using Folks;
+
+public class PerfTests : EdsTest.TestCase
+{
+  public PerfTests ()
+    {
+      base ("PerfTests");
+
+      this.add_test ("unprepared", this._test);
+      this.add_test ("again", this._test);
+      this.add_test ("twice", this._test_twice);
+      this.add_test ("pre-prepared", this._test_preprepared);
+
+      this.add_test ("pre-prepared-one-individual", this._test_preprepared_one);
+      this.add_test ("pre-prepared-one-persona",
+          this._test_preprepared_one_persona);
+
+      if (Test.perf () ||
+          Environment.get_variable ("FOLKS_TESTS_SLOW") != null)
+        {
+          this.add_test ("clones", this._test_clones);
+          this.add_test ("clones-one-individual", this._test_clones_one);
+          this.add_test ("clones-one-persona", this._test_clones_one_persona);
+        }
+      else
+        {
+          message ("skipping slow tests, run with -m perf or " +
+              "FOLKS_TESTS_SLOW=1 to enable");
+        }
+    }
+
+  /* Add a bunch of contacts, going behind Folks' back. Do it in a
+   * subprocess, so that we ignore it when profiling this process. */
+  private void _add_500 ()
+    {
+      Test.timer_start ();
+
+      try
+        {
+          Folks.TestUtils.run_test_helper_sync (
+              { "eds/helper-create-many-contacts",
+                  "-n", "500",
+                  "-u", ((!) this.eds_backend).address_book_uid });
+        }
+      catch (Error e)
+        {
+          error (e.message);
+        }
+
+      message ("%.6f Finished adding contacts", Test.timer_elapsed ());
+    }
+
+  public override void set_up ()
+    {
+      base.set_up ();
+      this._add_500 ();
+    }
+
+  private uint _test_num = 0;
+
+  public override void create_backend ()
+    {
+      /* Use a unique e-d-s book for each test. */
+      this.eds_backend = new EdsTest.Backend ();
+      ((!) this.eds_backend).set_up (true, "test%u".printf (this._test_num++));
+    }
+
+  /* Prepare the individual aggregator and wait for it to quiesce. */
+  private async IndividualAggregator _aggregate () throws Error
+    {
+      Test.timer_start ();
+
+      message ("%.6f Preparing backend store", Test.timer_elapsed ());
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+
+      message ("%.6f Loading backends", Test.timer_elapsed ());
+      yield store.load_backends ();
+
+      var eds = store.dup_backend_by_name ("eds");
+      assert (eds != null);
+
+      message ("%.6f Waiting for EDS backend", Test.timer_elapsed ());
+      yield TestUtils.backend_prepare_and_wait_for_quiescence ((!) eds);
+
+      message ("%.6f Preparing aggregator", Test.timer_elapsed ());
+      var aggregator = new IndividualAggregator ();
+      yield aggregator.prepare ();
+
+      message ("%.6f Waiting for aggregator", Test.timer_elapsed ());
+      yield TestUtils.aggregator_prepare_and_wait_for_quiescence (aggregator);
+
+      return aggregator;
+    }
+
+  private IndividualAggregator _run_aggregate ()
+    {
+      AsyncResult? result = null;
+      var loop = new MainLoop (null, false);
+
+      this._aggregate.begin ((obj, res) =>
+        {
+          result = res;
+          loop.quit ();
+        });
+
+      TestUtils.loop_run_with_timeout (loop, 60);
+      assert (result != null);
+
+      try
+        {
+          return this._aggregate.end ((!) result);
+        }
+      catch (Error e)
+        {
+          error ("%s #%d: %s", e.domain.to_string (), e.code, e.message);
+        }
+    }
+
+  /* Basic test: prepare the aggregator, then iterate through individuals.
+   * Also part of some subsequent tests.
+   *
+   * This represents a Folks client like gnome-contacts or the Empathy
+   * contact list, whose use case is: give me all my contacts as
+   * Individuals. */
+  private void _test ()
+    {
+      var aggregator = this._run_aggregate ();
+
+      var map = aggregator.individuals;
+      var iter = aggregator.individuals.map_iterator ();
+
+      message ("%.6f Aggregated into %d individuals", Test.timer_elapsed (),
+          map.size);
+
+      while (iter.next ())
+        debug ("%s → %s", iter.get_key (), iter.get_value ().full_name);
+
+      var elapsed = Test.timer_elapsed ();
+      Test.minimized_result (elapsed,
+          "%.6f Total time to iterate %d individuals", elapsed, map.size);
+    }
+
+  /* Basis for tests that look for one individual.
+   *
+   * This represents a Folks client that has a stored individual ID
+   * and wants to display that one Individiual. */
+  private async void _test_one_individual (string id) throws Error
+    {
+      var aggregator = yield this._aggregate ();
+      var map = aggregator.individuals;
+      Individual? individual = yield aggregator.look_up_individual (id);
+
+      assert (individual != null);
+
+      message ("%s => %s", id, ((!) individual).full_name);
+
+      var elapsed = Test.timer_elapsed ();
+      Test.minimized_result (elapsed,
+          "%.6f Total time to find one of %d individuals", elapsed, map.size);
+    }
+
+  private void _run_test_one_individual (string id) throws Error
+    {
+      AsyncResult? result = null;
+      var loop = new MainLoop (null, false);
+
+      this._test_one_individual.begin (id, (obj, res) =>
+        {
+          result = res;
+          loop.quit ();
+        });
+
+      TestUtils.loop_run_with_timeout (loop, 60);
+      assert (result != null);
+
+      this._test_one_individual.end ((!) result);
+    }
+
+  /* Basis for tests that look for one Persona.
+   *
+   * This represents a Folks client that has a stored persona UID,
+   * or can derive one from information known to it (e.g. a Telepathy
+   * identifier), and wants to display that Persona and its
+   * parent Individual. */
+  private async void _test_one_persona (string uid) throws Error
+    {
+      var aggregator = yield this._aggregate ();
+      var map = aggregator.individuals;
+
+      message ("%.6f Aggregated into %d individuals", Test.timer_elapsed (),
+          map.size);
+
+      /* FIXME: #696215: there really ought to be convenience API for
+       * some of this. */
+      string backend_name;
+      string persona_store_id;
+      string persona_id;
+      Persona.split_uid (uid, out backend_name, out persona_store_id,
+          out persona_id);
+      var backend_store = BackendStore.dup ();
+      var backend = backend_store.dup_backend_by_name (backend_name);
+      assert (backend != null);
+      var persona_stores = ((!) backend).persona_stores;
+      var persona_store = persona_stores[persona_store_id];
+      assert (persona_store != null);
+      var personas = ((!) persona_store).personas;
+
+      /* personas is keyed by Persona.iid, which is not the same thing as
+       * either Individual.id or Persona.uid.
+       *
+       * FIXME: surely there ought to be a generic way to do this without
+       * iteration? */
+      Persona? persona = null;
+      var iter = personas.map_iterator ();
+      while (iter.next ())
+        {
+          persona = iter.get_value ();
+
+          if (persona.uid == uid)
+            break;
+        }
+
+      assert (persona != null);
+      var individual = persona.individual;
+      assert (individual != null);
+      message ("%s is part of individual %s", uid, persona.individual.id);
+
+      var elapsed = Test.timer_elapsed ();
+      Test.minimized_result (elapsed,
+          "%.6f Total time to find one of %d individuals by persona uid",
+          elapsed, map.size);
+    }
+
+  private void _run_test_one_persona (string id) throws Error
+    {
+      AsyncResult? result = null;
+      var loop = new MainLoop (null, false);
+
+      this._test_one_persona.begin (id, (obj, res) =>
+        {
+          result = res;
+          loop.quit ();
+        });
+
+      TestUtils.loop_run_with_timeout (loop, 60);
+      assert (result != null);
+
+      this._test_one_persona.end ((!) result);
+    }
+
+  /**
+   * Benchmark whether we go any faster when test() is repeated.
+   */
+  private void _test_twice ()
+    {
+      this._test ();
+      this._test ();
+    }
+
+  /**
+   * Benchmark how much slower we go when we have many similar (or in this
+   * case, identical except for unique identifier) contacts in a backend.
+   *
+   * This is really pretty slow, so it's only run if either the test
+   * was run with -m perf, or FOLKS_TESTS_SLOW is set (because setting
+   * environment variables is easier than passing command-line arguments
+   * while using Automake).
+   */
+  private void _test_clones ()
+    {
+      /* Work with 3 copies of basically the same 500 contacts. */
+      this._add_500 ();
+      this._add_500 ();
+      this._test ();
+    }
+
+  /**
+   * Benchmark how much faster we go when another process has already prepared
+   * our cache for us (assuming we ever implement such a cache).
+   */
+  private void _test_preprepared ()
+    {
+      try
+        {
+          Test.timer_start ();
+          Folks.TestUtils.run_test_helper_sync (
+              { "eds/helper-prepare-aggregator" });
+          message ("%.6f Finished pre-preparation", Test.timer_elapsed ());
+        }
+      catch (Error e)
+        {
+          error (e.message);
+        }
+
+      this._test ();
+    }
+
+  /* test_preprepared() x test_one_individual() */
+  private void _test_preprepared_one ()
+    {
+      try
+        {
+          string capture;
+          Test.timer_start ();
+          Folks.TestUtils.run_test_helper_sync (
+              { "eds/helper-prepare-aggregator", "--print-an-individual-id" },
+              out capture);
+          message ("%.6f Finished pre-preparation", Test.timer_elapsed ());
+
+          capture = capture.chomp ();
+          this._run_test_one_individual (capture);
+        }
+      catch (Error e)
+        {
+          error (e.message);
+        }
+    }
+
+  /* test_preprepared_one() x test_clones() */
+  private void _test_clones_one ()
+    {
+      this._add_500 ();
+      this._add_500 ();
+      this._test_preprepared_one ();
+    }
+
+  /* test_preprepared() x test_one_persona() */
+  private void _test_preprepared_one_persona ()
+    {
+      try
+        {
+          string capture;
+          Test.timer_start ();
+          Folks.TestUtils.run_test_helper_sync (
+              { "eds/helper-prepare-aggregator", "--print-a-persona-uid" },
+              out capture);
+          message ("%.6f Finished pre-preparation", Test.timer_elapsed ());
+
+          capture = capture.chomp ();
+          this._run_test_one_persona (capture);
+        }
+      catch (Error e)
+        {
+          error (e.message);
+        }
+    }
+
+  /* test_preprepared_one_persona() x test_clones() */
+  private void _test_clones_one_persona ()
+    {
+      this._add_500 ();
+      this._add_500 ();
+      this._test_preprepared_one_persona ();
+    }
+
+  public override void tear_down ()
+    {
+      try
+        {
+          Folks.TestUtils.run_test_helper_sync (
+              { "eds/helper-delete-contacts",
+                  "-u", ((!) this.eds_backend).address_book_uid });
+        }
+      catch (Error e)
+        {
+          error (e.message);
+        }
+
+      base.tear_down ();
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  var tests = new PerfTests ();
+  tests.register ();
+  Test.run ();
+  tests.final_tear_down ();
+
+  return 0;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]