Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2019 Peter Bigot Consulting, LLC
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/* littlefs performance testing */

#include <string.h>
#include <stdlib.h>
#include <kernel.h>
#include <ztest.h>
#include "testfs_tests.h"
#include "testfs_lfs.h"
#include <lfs.h>

#include <fs/littlefs.h>

#define HELLO "hello"
#define GOODBYE "goodbye"

static int write_read(const char *tag,
		      struct fs_mount_t *mp,
		      size_t buf_size,
		      size_t nbuf)
{
	const struct lfs_config *lcp = &((const struct fs_littlefs *)mp->fs_data)->cfg;
	struct testfs_path path;
	struct fs_statvfs vfs;
	struct fs_dirent stat;
	struct fs_file_t file;
	size_t total = nbuf * buf_size;
	u32_t t0;
	u32_t t1;
	u8_t *buf;
	int rc;
	int rv = TC_FAIL;

	TC_PRINT("clearing %s for %s write/read test\n",
		 mp->mnt_point, tag);
	if (testfs_lfs_wipe_partition(mp) != TC_PASS) {
		return TC_FAIL;
	}

	rc = fs_mount(mp);
	if (rc != 0) {
		TC_PRINT("Mount %s failed: %d\n", mp->mnt_point, rc);
		return TC_FAIL;
	}

	rc = fs_statvfs(mp->mnt_point, &vfs);
	if (rc != 0) {
		TC_PRINT("statvfs %s failed: %d\n", mp->mnt_point, rc);
		goto out_mnt;
	}

	TC_PRINT("%s: bsize %lu ; frsize %lu ; blocks %lu ; bfree %lu\n",
		 mp->mnt_point,
		 vfs.f_bsize, vfs.f_frsize, vfs.f_blocks, vfs.f_bfree);
	TC_PRINT("read_size %u ; prog_size %u ; cache_size %u ; lookahead_size %u\n",
		 lcp->read_size, lcp->prog_size, lcp->cache_size, lcp->lookahead_size);

	testfs_path_init(&path, mp,
			 "data",
			 TESTFS_PATH_END);

	buf = calloc(buf_size, sizeof(u8_t));
	if (buf == NULL) {
		TC_PRINT("Failed to allocate %zu-byte buffer\n", buf_size);
		goto out_mnt;
	}

	for (size_t i = 0; i < buf_size; ++i) {
		buf[i] = i;
	}

	TC_PRINT("creating and writing %zu %zu-byte blocks\n",
		 nbuf, buf_size);

	rc = fs_open(&file, path.path);
	if (rc != 0) {
		TC_PRINT("Failed to open %s for write: %d\n", path.path, rc);
		goto out_buf;
	}

	t0 = k_uptime_get_32();
	for (size_t i = 0; i < nbuf; ++i) {
		rc = fs_write(&file, buf, buf_size);
		if (buf_size != rc) {
			TC_PRINT("Failed to write buf %zu: %d\n", i, rc);
			goto out_file;
		}
	}
	t1 = k_uptime_get_32();

	if (t1 == t0) {
		t1++;
	}

	(void)fs_close(&file);

	rc = fs_stat(path.path, &stat);
	if (rc != 0) {
		TC_PRINT("Failed to stat %s: %d\n", path.path, rc);
		goto out_buf;
	}

	if (stat.size != total) {
		TC_PRINT("File size %zu not %zu\n", stat.size, total);
		goto out_buf;
	}

	TC_PRINT("%s write %zu * %zu = %zu bytes in %u ms: "
		 "%u By/s, %u KiBy/s\n",
		 tag, nbuf, buf_size, total, (t1 - t0),
		 (u32_t)(total * 1000U / (t1 - t0)),
		 (u32_t)(total * 1000U / (t1 - t0) / 1024U));

	rc = fs_open(&file, path.path);
	if (rc != 0) {
		TC_PRINT("Failed to open %s for write: %d\n", path.path, rc);
		goto out_buf;
	}

	t0 = k_uptime_get_32();
	for (size_t i = 0; i < nbuf; ++i) {
		rc = fs_read(&file, buf, buf_size);
		if (buf_size != rc) {
			TC_PRINT("Failed to read buf %zu: %d\n", i, rc);
			goto out_file;
		}
	}
	t1 = k_uptime_get_32();

	if (t1 == t0) {
		t1++;
	}

	TC_PRINT("%s read %zu * %zu = %zu bytes in %u ms: "
		 "%u By/s, %u KiBy/s\n",
		 tag, nbuf, buf_size, total, (t1 - t0),
		 (u32_t)(total * 1000U / (t1 - t0)),
		 (u32_t)(total * 1000U / (t1 - t0) / 1024U));

	rv = TC_PASS;

out_file:
	(void)fs_close(&file);

out_buf:
	free(buf);

out_mnt:
	(void)fs_unmount(mp);

	return rv;
}

static int custom_write_test(const char *tag,
			     const struct fs_mount_t *mp,
			     const struct lfs_config *cfgp,
			     size_t buf_size,
			     size_t nbuf)
{
	struct fs_littlefs data = {
		.cfg = *cfgp,
	};
	struct fs_mount_t lfs_mnt = {
		.type = FS_LITTLEFS,
		.fs_data = &data,
		.storage_dev = mp->storage_dev,
		.mnt_point = mp->mnt_point,
	};
	struct lfs_config *lcp = &data.cfg;
	int rv = TC_FAIL;

	if (lcp->cache_size == 0) {
		lcp->cache_size = CONFIG_FS_LITTLEFS_CACHE_SIZE;
	}
	if (lcp->lookahead_size == 0) {
		lcp->lookahead_size = CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE;
	}

	lcp->read_buffer = malloc(lcp->cache_size);
	lcp->prog_buffer = malloc(lcp->cache_size);
	lcp->lookahead_buffer = malloc(lcp->lookahead_size);

	TC_PRINT("bufs %p %p %p\n", lcp->read_buffer, lcp->prog_buffer, lcp->lookahead_buffer);

	if ((lcp->read_buffer == NULL)
	    || (lcp->prog_buffer == NULL)
	    || (lcp->lookahead_buffer == NULL)) {
		TC_PRINT("%s buffer allocation failed\n", tag);
		goto out_free;
	}

	rv = write_read(tag, &lfs_mnt, buf_size, nbuf);

out_free:
	if (lcp->read_buffer) {
		free(lcp->read_buffer);
	}
	if (lcp->prog_buffer) {
		free(lcp->prog_buffer);
	}
	if (lcp->lookahead_buffer) {
		free(lcp->lookahead_buffer);
	}

	return rv;
}

static int small_8_1K_cust(void)
{
	struct lfs_config cfg = {
		.read_size = LARGE_IO_SIZE,
		.prog_size = LARGE_IO_SIZE,
		.cache_size = LARGE_CACHE_SIZE,
		.lookahead_size = LARGE_LOOKAHEAD_SIZE
	};

	return custom_write_test("small 8x1K bigfile", &testfs_small_mnt, &cfg, 1024, 8);
}

void test_lfs_perf(void)
{
	k_sleep(K_MSEC(100));   /* flush log messages */
	zassert_equal(write_read("small 8x1K dflt",
				 &testfs_small_mnt,
				 1024, 8),
		      TC_PASS,
		      "failed");

	k_sleep(K_MSEC(100));   /* flush log messages */
	zassert_equal(small_8_1K_cust(), TC_PASS,
		      "failed");

	k_sleep(K_MSEC(100));   /* flush log messages */
	zassert_equal(write_read("medium 32x2K dflt",
				 &testfs_medium_mnt,
				 2048, 32),
		      TC_PASS,
		      "failed");

	k_sleep(K_MSEC(100));   /* flush log messages */
	zassert_equal(write_read("large 64x4K dflt",
				 &testfs_large_mnt,
				 4096, 64),
		      TC_PASS,
		      "failed");
}