[mpact][profiler] Add utils for profiling Python programs and torch ops (#59)

diff --git a/benchmark/README.md b/benchmark/README.md
index 4749547..e870ae9 100644
--- a/benchmark/README.md
+++ b/benchmark/README.md
@@ -24,3 +24,14 @@
 ```shell
 python path/to/the/kernels_benchmark.py --benchmark-filter=add
 ```
+
+### Profiler
+
+Utils for profiling python scripts and pytorch models could be found in
+`benchmark/python/utils/profiler.py`.
+
+To run the profiling example, use the following command:
+
+```shell
+python benchmark/python/utils/profiler.py
+```
diff --git a/benchmark/python/utils/profiler.py b/benchmark/python/utils/profiler.py
new file mode 100644
index 0000000..8bca04b
--- /dev/null
+++ b/benchmark/python/utils/profiler.py
@@ -0,0 +1,66 @@
+import torch
+import cProfile
+from pstats import Stats
+
+
+def profile_torch(
+    func, args, row_limit=10, save_output=False, func_name=None, file_name="trace"
+):
+    """Use PyTorch's profiler to profile torch ops.
+
+    To see the graph: upload trace.json to chrome://tracing
+
+    More details about PyTorch profiler:
+    https://pytorch.org/tutorials/recipes/recipes/profiler_recipe.html
+    """
+    func_name = func_name if func_name else func.__name__
+    with torch.profiler.profile() as prof:
+        with torch.profiler.record_function(func_name):
+            func(*args)
+    print(prof.key_averages().table(sort_by="cpu_time_total", row_limit=row_limit))
+    if save_output:
+        prof.export_chrome_trace(f"{file_name}.json")
+
+
+def profile_python(func, args, row_limit=10, save_output=False, file_name="stats"):
+    """Use cProfile to profile python function calls.
+
+    To see the graph, run the following commands:
+    1. python -m pip install snakeviz
+    2. snakeviz stats.prof
+    """
+    pr = cProfile.Profile()
+    pr.enable()
+    func(*args)
+    pr.disable()
+    stats = Stats(pr)
+    stats.sort_stats("tottime").print_stats(row_limit)
+    if save_output:
+        pr.dump_stats(f"{file_name}.prof")
+
+
+if __name__ == "__main__":
+    # Example usage of the profiler.
+    from mpact.models.kernels import MMNet
+    from mpact_benchmark.utils.tensor_generator import generate_tensor
+    from mpact.mpactbackend import mpact_jit
+
+    # Generate input tensors.
+    dense_tensor1 = generate_tensor(seed=0, shape=(32, 32), sparsity=0.8)
+    dense_tensor2 = generate_tensor(seed=1, shape=(32, 32), sparsity=0.8)
+    sparse_tensor1 = dense_tensor1.to_sparse_csr()
+    sparse_tensor2 = dense_tensor2.to_sparse_csr()
+
+    # Profile with PyTorch profiler for torch operators.
+    # MPACT sparse.
+    profile_torch(mpact_jit, (MMNet(), sparse_tensor1, sparse_tensor2))
+    # Torch sparse.
+    profile_torch(
+        MMNet(), (sparse_tensor1, sparse_tensor2), func_name="sparsexsparse matmul"
+    )
+
+    # Profile with cProfile for Python function calls.
+    # MPACT sparse.
+    profile_python(mpact_jit, (MMNet(), sparse_tensor1, sparse_tensor2))
+    # Torch sparse.
+    profile_python(MMNet(), (sparse_tensor1, sparse_tensor2))