diff --git a/Documentation/dev-tools/kunit/running_tips.rst b/Documentation/dev-tools/kunit/running_tips.rst
index bd689db6fdd2..a528d92e5d8f 100644
--- a/Documentation/dev-tools/kunit/running_tips.rst
+++ b/Documentation/dev-tools/kunit/running_tips.rst
@@ -446,3 +446,10 @@ This attribute indicates whether the test uses init data or functions.
 
 This attribute is automatically saved as a boolean and tests can also be
 filtered using this attribute.
+
+``duration``
+
+This attribute indicates the length of time in seconds of the test execution.
+
+This attribute is automatically saved as a timespec64 and printed for each test
+(excluding parameterized tests).
diff --git a/include/kunit/attributes.h b/include/kunit/attributes.h
index bc76a0b786d2..89ca54ef380d 100644
--- a/include/kunit/attributes.h
+++ b/include/kunit/attributes.h
@@ -18,6 +18,11 @@ struct kunit_attr_filter {
 	char *input;
 };
 
+/*
+ * Frees all of a test's allocated attributes.
+ */
+void kunit_free_attr(void *test_or_suite, bool is_test);
+
 /*
  * Returns the name of the filter's attribute.
  */
diff --git a/include/kunit/test.h b/include/kunit/test.h
index ec61cad6b71d..dca78d9bd3f6 100644
--- a/include/kunit/test.h
+++ b/include/kunit/test.h
@@ -82,6 +82,7 @@ enum kunit_speed {
 /* Holds attributes for each test case and suite */
 struct kunit_attributes {
 	enum kunit_speed speed;
+	struct timespec64 *duration;
 };
 
 /**
diff --git a/lib/kunit/attributes.c b/lib/kunit/attributes.c
index 2cf04cc09372..b9fd6c686d65 100644
--- a/lib/kunit/attributes.c
+++ b/lib/kunit/attributes.c
@@ -40,6 +40,7 @@ struct kunit_attr {
 	int (*filter)(void *attr, const char *input, int *err);
 	void *attr_default;
 	enum print_ops print;
+	bool to_free;
 };
 
 /* String Lists for enum Attributes */
@@ -79,8 +80,29 @@ static const char *attr_string_to_string(void *attr, bool *to_free)
 	return (char *) attr;
 }
 
+static const char *attr_duration_to_string(void *attr, bool *to_free)
+{
+	int string_max_len = 20;
+	char *str = kmalloc(string_max_len, GFP_KERNEL);
+	struct timespec64 *val = (struct timespec64 *)attr;
+
+	*to_free = true;
+
+	/* Check if duration will overflow string */
+	if (val->tv_sec >= 1000000)
+		str = "Greater than or equal to 1000000s";
+	else
+		sprintf(str, "%lld.%09lds", val->tv_sec, val->tv_nsec);
+	return str;
+}
+
 /* Filter Methods */
 
+static int attr_default_filter(void *attr, const char *input, int *err)
+{
+	return false;
+}
+
 static const char op_list[] = "<>!=";
 
 /*
@@ -246,8 +268,20 @@ static void *attr_is_init_get(void *test_or_suite, bool is_test)
 		return ((void *) suite->is_init);
 }
 
+static void *attr_duration_get(void *test_or_suite, bool is_test)
+{
+	struct kunit_case *test = is_test ? test_or_suite : NULL;
+
+	if (test && !test->generate_params)
+		return ((void *) test->attr.duration);
+	else
+		return ((void *) NULL);
+}
+
 /* List of all Test Attributes */
 
+static struct timespec64 duration_default = {0, 0};
+
 static struct kunit_attr kunit_attr_list[] = {
 	{
 		.name = "speed",
@@ -256,6 +290,7 @@ static struct kunit_attr kunit_attr_list[] = {
 		.filter = attr_speed_filter,
 		.attr_default = (void *)KUNIT_SPEED_NORMAL,
 		.print = PRINT_ALWAYS,
+		.to_free = false,
 	},
 	{
 		.name = "module",
@@ -264,6 +299,7 @@ static struct kunit_attr kunit_attr_list[] = {
 		.filter = attr_string_filter,
 		.attr_default = (void *)"",
 		.print = PRINT_SUITE,
+		.to_free = false,
 	},
 	{
 		.name = "is_init",
@@ -272,10 +308,33 @@ static struct kunit_attr kunit_attr_list[] = {
 		.filter = attr_bool_filter,
 		.attr_default = (void *)false,
 		.print = PRINT_SUITE,
+		.to_free = false,
+	},
+	{
+		.name = "duration",
+		.get_attr = attr_duration_get,
+		.to_string = attr_duration_to_string,
+		.filter = attr_default_filter,
+		.attr_default = (void *)(&duration_default),
+		.print = PRINT_ALWAYS,
+		.to_free = true,
 	}
 };
 
-/* Helper Functions to Access Attributes */
+/* Helper Functions to Access/Free Attributes */
+
+void kunit_free_attr(void *test_or_suite, bool is_test)
+{
+	int i;
+	void *attr;
+
+	for (i = 0; i < ARRAY_SIZE(kunit_attr_list); i++) {
+		if (kunit_attr_list[i].to_free) {
+			attr = kunit_attr_list[i].get_attr(test_or_suite, is_test);
+			kfree(attr);
+		}
+	}
+}
 
 const char *kunit_attr_filter_name(struct kunit_attr_filter filter)
 {
diff --git a/lib/kunit/test.c b/lib/kunit/test.c
index e8b1b52a19ab..0d18e4969015 100644
--- a/lib/kunit/test.c
+++ b/lib/kunit/test.c
@@ -376,11 +376,11 @@ static void kunit_run_case_check_speed(struct kunit *test,
 /*
  * Initializes and runs test case. Does not clean up or do post validations.
  */
-static void kunit_run_case_internal(struct kunit *test,
+static struct timespec64 kunit_run_case_internal(struct kunit *test,
 				    struct kunit_suite *suite,
 				    struct kunit_case *test_case)
 {
-	struct timespec64 start, end;
+	struct timespec64 start, end, duration;
 
 	if (suite->init) {
 		int ret;
@@ -389,7 +389,9 @@ static void kunit_run_case_internal(struct kunit *test,
 		if (ret) {
 			kunit_err(test, "failed to initialize: %d\n", ret);
 			kunit_set_failure(test);
-			return;
+			duration.tv_sec = 0;
+			duration.tv_nsec = 0;
+			return duration;
 		}
 	}
 
@@ -399,7 +401,11 @@ static void kunit_run_case_internal(struct kunit *test,
 
 	ktime_get_ts64(&end);
 
-	kunit_run_case_check_speed(test, test_case, timespec64_sub(end, start));
+	duration = timespec64_sub(end, start);
+
+	kunit_run_case_check_speed(test, test_case, duration);
+
+	return duration;
 }
 
 static void kunit_case_internal_cleanup(struct kunit *test)
@@ -424,6 +430,7 @@ struct kunit_try_catch_context {
 	struct kunit *test;
 	struct kunit_suite *suite;
 	struct kunit_case *test_case;
+	struct timespec64 duration;
 };
 
 static void kunit_try_run_case(void *data)
@@ -440,7 +447,7 @@ static void kunit_try_run_case(void *data)
 	 * abort will be called, this thread will exit, and finally the parent
 	 * thread will resume control and handle any necessary clean up.
 	 */
-	kunit_run_case_internal(test, suite, test_case);
+	ctx->duration = kunit_run_case_internal(test, suite, test_case);
 }
 
 static void kunit_try_run_case_cleanup(void *data)
@@ -521,6 +528,7 @@ static void kunit_run_case_catch_errors(struct kunit_suite *suite,
 {
 	struct kunit_try_catch_context context;
 	struct kunit_try_catch *try_catch;
+	struct timespec64 *duration = kmalloc(sizeof(struct timespec64), GFP_KERNEL);
 
 	try_catch = &test->try_catch;
 
@@ -533,6 +541,10 @@ static void kunit_run_case_catch_errors(struct kunit_suite *suite,
 	context.test_case = test_case;
 	kunit_try_catch_run(try_catch, &context);
 
+	duration->tv_sec = context.duration.tv_sec;
+	duration->tv_nsec = context.duration.tv_nsec;
+	test_case->attr.duration = duration;
+
 	/* Now run the cleanup */
 	kunit_try_catch_init(try_catch,
 			     test,
@@ -670,6 +682,7 @@ int kunit_run_tests(struct kunit_suite *suite)
 		}
 
 		kunit_print_attr((void *)test_case, true, KUNIT_LEVEL_CASE);
+		kunit_free_attr((void *)test_case, true);
 
 		kunit_print_test_stats(&test, param_stats);
 
@@ -680,6 +693,7 @@ int kunit_run_tests(struct kunit_suite *suite)
 
 		kunit_update_stats(&suite_stats, test_case->status);
 		kunit_accumulate_stats(&total_stats, param_stats);
+
 	}
 
 	if (suite->suite_exit)
@@ -688,6 +702,7 @@ int kunit_run_tests(struct kunit_suite *suite)
 	kunit_print_suite_stats(suite, suite_stats, total_stats);
 suite_end:
 	kunit_print_suite_end(suite);
+	kunit_free_attr((void *)suite, false);
 
 	return 0;
 }
