[SATYR PATCHv3 5/6] python: distance computation between pair of threads

Martin Milata mmilata at redhat.com
Mon Jun 24 09:47:33 UTC 2013


The distance computation function is now exposed as a
BaseThread.distance(otherthread, [dist_type]) method.

Related to #63.

Signed-off-by: Martin Milata <mmilata at redhat.com>
---
 include/distance.h      |  5 ++++-
 python/py_base_thread.c | 39 +++++++++++++++++++++++++++++++++++++
 python/py_base_thread.h |  3 +++
 python/py_module.c      |  9 +++++++++
 tests/python/metrics.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 106 insertions(+), 2 deletions(-)

diff --git a/include/distance.h b/include/distance.h
index 53be6d7..bb7140b 100644
--- a/include/distance.h
+++ b/include/distance.h
@@ -78,7 +78,10 @@ enum sr_distance_type
      * Like the Levenshtein distance, but with transpositions. NOTE:
      * The triangle inequality does not hold.
      */
-    SR_DISTANCE_DAMERAU_LEVENSHTEIN
+    SR_DISTANCE_DAMERAU_LEVENSHTEIN,
+
+    /* Sentinel, keep it the last entry. */
+    SR_DISTANCE_NUM
 };
 
 float
diff --git a/python/py_base_thread.c b/python/py_base_thread.c
index 6536c52..8053878 100644
--- a/python/py_base_thread.c
+++ b/python/py_base_thread.c
@@ -23,14 +23,22 @@
 
 #include "thread.h"
 #include "frame.h"
+#include "distance.h"
 
 #define thread_doc "satyr.BaseThread - base class for threads"
 #define frames_doc "A list containing objects representing frames in a thread."
 
+#define distance_doc "Usage: thread.distance(other, dist_type=DISTANCE_LEVENSHTEIN)\n"\
+                     "other: other thread\n"\
+                     "dist_type (optional): one of DISTANCE_LEVENSHTEIN, DISTANCE_JARO_WINKLER,\n"\
+                     "                      DISTANCE_JACCARD or DISTANCE_DAMERAU_LEVENSHTEIN\n"\
+                     "Returns: positive float - distance between the two threads"
+
 static PyMethodDef
 thread_methods[] =
 {
     /* methods */
+    { "distance", (PyCFunction)sr_py_base_thread_distance, METH_VARARGS|METH_KEYWORDS, distance_doc },
     { NULL },
 };
 
@@ -203,3 +211,34 @@ sr_py_base_thread_cmp(struct sr_py_base_thread *self, struct sr_py_base_thread *
 
     return normalize_cmp(sr_thread_cmp(self->thread, other->thread));
 }
+
+/* methods */
+PyObject *
+sr_py_base_thread_distance(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    PyObject *other;
+    int dist_type = SR_DISTANCE_LEVENSHTEIN;
+    static const char *kwlist[] = { "other", "dist_type", NULL };
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|i", (char **)kwlist,
+                                     &sr_py_base_thread_type, &other, &dist_type))
+        return NULL;
+
+    struct sr_py_base_thread *t1 = (struct sr_py_base_thread *)self;
+    struct sr_py_base_thread *t2 = (struct sr_py_base_thread *)other;
+
+    if (self->ob_type != other->ob_type)
+    {
+        PyErr_SetString(PyExc_TypeError, "Both threads must have the same type");
+        return NULL;
+    }
+
+    if (dist_type < 0 || dist_type >= SR_DISTANCE_NUM)
+    {
+        PyErr_SetString(PyExc_ValueError, "Invalid distance type");
+        return NULL;
+    }
+
+    float dist = sr_distance(dist_type, t1->thread, t2->thread);
+    return PyFloat_FromDouble((double)dist);
+}
diff --git a/python/py_base_thread.h b/python/py_base_thread.h
index 69ae0de..663555c 100644
--- a/python/py_base_thread.h
+++ b/python/py_base_thread.h
@@ -49,6 +49,9 @@ PyObject *frames_to_python_list(struct sr_thread *thread, PyTypeObject *frame_ty
 
 int sr_py_base_thread_cmp(struct sr_py_base_thread *self, struct sr_py_base_thread *other);
 
+/* methods */
+PyObject *sr_py_base_thread_distance(PyObject *self, PyObject *args, PyObject *kwds);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/python/py_module.c b/python/py_module.c
index 29ab672..8fb77cc 100644
--- a/python/py_module.c
+++ b/python/py_module.c
@@ -16,6 +16,7 @@
 #include "py_java_stacktrace.h"
 #include "py_metrics.h"
 
+#include "distance.h"
 #include "gdb/sharedlib.h"
 
 static PyMethodDef
@@ -173,6 +174,14 @@ init_satyr()
     PyModule_AddObject(module, "Distances",
                        (PyObject *)&sr_py_distances_type);
 
+    PyModule_AddIntConstant(module, "DISTANCE_JARO_WINKLER",
+                            SR_DISTANCE_JARO_WINKLER);
+    PyModule_AddIntConstant(module, "DISTANCE_JACCARD", SR_DISTANCE_JACCARD);
+    PyModule_AddIntConstant(module, "DISTANCE_LEVENSHTEIN",
+                            SR_DISTANCE_LEVENSHTEIN);
+    PyModule_AddIntConstant(module, "DISTANCE_DAMERAU_LEVENSHTEIN",
+                            SR_DISTANCE_DAMERAU_LEVENSHTEIN);
+
     Py_INCREF(&sr_py_dendrogram_type);
     PyModule_AddObject(module, "Dendrogram",
                        (PyObject *)&sr_py_dendrogram_type);
diff --git a/tests/python/metrics.py b/tests/python/metrics.py
index a97e5eb..703bbdc 100755
--- a/tests/python/metrics.py
+++ b/tests/python/metrics.py
@@ -1,8 +1,9 @@
 #!/usr/bin/env python
 
-import os
 import unittest
 
+from test_helpers import load_input_contents
+
 try:
     import _satyr as satyr
 except ImportError:
@@ -43,5 +44,54 @@ class TestDistances(unittest.TestCase):
         distances = satyr.Distances("levenshtein", [thread1, thread2], 2)
         self.assertAlmostEqual(distances.get_distance(0, 1), 0.0)
 
+    def test_distance_gdb(self):
+        contents = load_input_contents('../gdb_stacktraces/rhbz-803600')
+        g = satyr.GdbStacktrace(contents)
+        thread1 = g.threads[0]
+        thread2 = g.threads[1]
+
+        # DISTANCE_LEVENSHTEIN is the default
+        self.assertAlmostEqual(thread1.distance(thread1), 0.0)
+        self.assertAlmostEqual(thread1.distance(thread2), 0.8827, places=3)
+        self.assertAlmostEqual(thread1.distance(thread2), thread2.distance(thread1))
+
+        # TODO are the same stacktraces supposed to compare at 0.98 ?
+        # I'll just leave the numbers here so we are assured they at least stay the same ...
+        self.assertAlmostEqual(
+            thread1.distance(thread1, dist_type=satyr.DISTANCE_JARO_WINKLER),
+            0.98,
+            places=3)
+        self.assertAlmostEqual(
+            thread1.distance(thread2, dist_type=satyr.DISTANCE_JARO_WINKLER),
+            0.3678,
+            places=3)
+        # WTF, not even symmetrical?
+        #self.assertAlmostEqual(
+        #    thread1.distance(thread2, dist_type=satyr.DISTANCE_JARO_WINKLER),
+        #    thread2.distance(thread1, dist_type=satyr.DISTANCE_JARO_WINKLER)
+        #)
+
+        self.assertAlmostEqual(
+            thread1.distance(thread1, dist_type=satyr.DISTANCE_JACCARD),
+            0.0)
+        self.assertAlmostEqual(
+            thread1.distance(thread2, dist_type=satyr.DISTANCE_JACCARD),
+            0.8904,
+            places=3)
+        self.assertAlmostEqual(
+            thread1.distance(thread2, dist_type=satyr.DISTANCE_JACCARD),
+            thread2.distance(thread1, dist_type=satyr.DISTANCE_JACCARD))
+
+        self.assertAlmostEqual(
+            thread1.distance(thread1, dist_type=satyr.DISTANCE_DAMERAU_LEVENSHTEIN),
+            0.0)
+        self.assertAlmostEqual(
+            thread1.distance(thread2, dist_type=satyr.DISTANCE_DAMERAU_LEVENSHTEIN),
+            0.8827,
+            places=3)
+        self.assertAlmostEqual(
+            thread1.distance(thread2, dist_type=satyr.DISTANCE_DAMERAU_LEVENSHTEIN),
+            thread2.distance(thread1, dist_type=satyr.DISTANCE_DAMERAU_LEVENSHTEIN))
+
 if __name__ == '__main__':
     unittest.main()
-- 
1.7.11.7



More information about the Crash-catcher mailing list