A Decorator to Record  Execution Time

Photo by Djim Loic on Unsplash

A Decorator to Record Execution Time

A lightweight decorator to save a function's execution time into a database

·

2 min read

It can be useful to measure the execution time of functions to ensure your code is running well. An execution time way longer than average implies an issue. Here is a lightweight context manager, implemented as a decorator, which saves some metrics on your functions' runtime.

This simple Metric class gets implemented as a context manager, and saves the function name and execution time. All it requires is a database connection to save the metrics.

from contextlib import ContextDecorator,contextmanager
import pymysql.cursors
from time import time

class DatabaseFactory():
    def __init__(self, host: str, user: str, password: str, database: str, port: int):
        self.host: str = host
        self.user: str = user
        self.password = password
        self.database: str = database
        self.port: int = port

    @contextmanager
    def connection(self):
        connection = self._connect()
        try:
            yield connection
        except Exception as ex:
            raise ex
        finally:
            connection.close()


    def _connect(self):
        return pymysql.connect(
            host=self.host,
            user=self.user,
            password=self.password,
            db=self.database,
            charset="utf8mb4",
            cursorclass=pymysql.cursors.DictCursor,
            autocommit=True,
            port=self.port,
        )

db = DatabaseFactory(
    host="127.0.0.1", 
    user="root", 
    password="", 
    database="metric",
    port=3306,
)

class Metric():
    def __init__(self, function: object): 

        self.function: object = function

    def __call__(self):
        t_start = time()
        self.function()
        t_end = time()

        self._metrics(self.function.__name__, t_end-t_start)

    def _metrics(self, function_name: str, execution_time: float):
        with db.connection() as connection: 
            with connection.cursor() as curs: 
                curs.execute("INSERT INTO metrics (function_name, execution_time) VALUES (%s, %s)", (function_name, execution_time*1000000))

Most of the lines are dedicated to opening up a MySQL database connection and using a MySQL database called metric.

The class Metric will get implemented as a decorator and just invokes the function and saves the function name and execution time (in ms) into the database.

To implement this class as a decorator, all I need is the following small function.

from metric import Metric
import time

@Metric
def test(): 
    time.sleep(5)

Now every time the test function gets run I should see a new entry in my table. This is what it looks like:

select * from metrics;
+----+---------------+----------------+
| id | function_name | execution_time |
+----+---------------+----------------+
|  1 | test          |        5005049 |
+----+---------------+----------------+
1 row in set (0.01 sec)

Perfect! Though this is only one metric, there are a whole host of other useful metrics to track - the sky is the limit.