1 /*
2  * Copyright (c) 2019 Peter Bigot Consulting, LLC
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /* littlefs performance testing */
8 
9 #include <string.h>
10 #include <stdlib.h>
11 #include <zephyr/kernel.h>
12 #include <zephyr/ztest.h>
13 #include "testfs_tests.h"
14 #include "testfs_lfs.h"
15 #include <lfs.h>
16 
17 #include <zephyr/fs/littlefs.h>
18 
19 #define HELLO "hello"
20 #define GOODBYE "goodbye"
21 
write_read(const char * tag,struct fs_mount_t * mp,size_t buf_size,size_t nbuf)22 static int write_read(const char *tag,
23 		      struct fs_mount_t *mp,
24 		      size_t buf_size,
25 		      size_t nbuf)
26 {
27 	const struct lfs_config *lcp = &((const struct fs_littlefs *)mp->fs_data)->cfg;
28 	struct testfs_path path;
29 	struct fs_statvfs vfs;
30 	struct fs_dirent stat;
31 	struct fs_file_t file;
32 	size_t total = nbuf * buf_size;
33 	uint32_t t0;
34 	uint32_t t1;
35 	uint8_t *buf;
36 	int rc;
37 	int rv = TC_FAIL;
38 
39 	fs_file_t_init(&file);
40 	TC_PRINT("clearing %s for %s write/read test\n",
41 		 mp->mnt_point, tag);
42 	if (testfs_lfs_wipe_partition(mp) != TC_PASS) {
43 		return TC_FAIL;
44 	}
45 
46 	rc = fs_mount(mp);
47 	if (rc != 0) {
48 		TC_PRINT("Mount %s failed: %d\n", mp->mnt_point, rc);
49 		return TC_FAIL;
50 	}
51 
52 	rc = fs_statvfs(mp->mnt_point, &vfs);
53 	if (rc != 0) {
54 		TC_PRINT("statvfs %s failed: %d\n", mp->mnt_point, rc);
55 		goto out_mnt;
56 	}
57 
58 	TC_PRINT("%s: bsize %lu ; frsize %lu ; blocks %lu ; bfree %lu\n",
59 		 mp->mnt_point,
60 		 vfs.f_bsize, vfs.f_frsize, vfs.f_blocks, vfs.f_bfree);
61 	TC_PRINT("read_size %u ; prog_size %u ; cache_size %u ; lookahead_size %u\n",
62 		 lcp->read_size, lcp->prog_size, lcp->cache_size, lcp->lookahead_size);
63 
64 	testfs_path_init(&path, mp,
65 			 "data",
66 			 TESTFS_PATH_END);
67 
68 	buf = calloc(buf_size, sizeof(uint8_t));
69 	if (buf == NULL) {
70 		TC_PRINT("Failed to allocate %zu-byte buffer\n", buf_size);
71 		goto out_mnt;
72 	}
73 
74 	for (size_t i = 0; i < buf_size; ++i) {
75 		buf[i] = i;
76 	}
77 
78 	TC_PRINT("creating and writing %zu %zu-byte blocks\n",
79 		 nbuf, buf_size);
80 
81 	rc = fs_open(&file, path.path, FS_O_CREATE | FS_O_RDWR);
82 	if (rc != 0) {
83 		TC_PRINT("Failed to open %s for write: %d\n", path.path, rc);
84 		goto out_buf;
85 	}
86 
87 	t0 = k_uptime_get_32();
88 	for (size_t i = 0; i < nbuf; ++i) {
89 		rc = fs_write(&file, buf, buf_size);
90 		if (buf_size != rc) {
91 			TC_PRINT("Failed to write buf %zu: %d\n", i, rc);
92 			goto out_file;
93 		}
94 	}
95 	t1 = k_uptime_get_32();
96 
97 	if (t1 == t0) {
98 		t1++;
99 	}
100 
101 	(void)fs_close(&file);
102 
103 	rc = fs_stat(path.path, &stat);
104 	if (rc != 0) {
105 		TC_PRINT("Failed to stat %s: %d\n", path.path, rc);
106 		goto out_buf;
107 	}
108 
109 	if (stat.size != total) {
110 		TC_PRINT("File size %zu not %zu\n", stat.size, total);
111 		goto out_buf;
112 	}
113 
114 	TC_PRINT("%s write %zu * %zu = %zu bytes in %u ms: "
115 		 "%u By/s, %u KiBy/s\n",
116 		 tag, nbuf, buf_size, total, (t1 - t0),
117 		 (uint32_t)(total * 1000U / (t1 - t0)),
118 		 (uint32_t)(total * 1000U / (t1 - t0) / 1024U));
119 
120 	rc = fs_open(&file, path.path, FS_O_CREATE | FS_O_RDWR);
121 	if (rc != 0) {
122 		TC_PRINT("Failed to open %s for write: %d\n", path.path, rc);
123 		goto out_buf;
124 	}
125 
126 	t0 = k_uptime_get_32();
127 	for (size_t i = 0; i < nbuf; ++i) {
128 		rc = fs_read(&file, buf, buf_size);
129 		if (buf_size != rc) {
130 			TC_PRINT("Failed to read buf %zu: %d\n", i, rc);
131 			goto out_file;
132 		}
133 	}
134 	t1 = k_uptime_get_32();
135 
136 	if (t1 == t0) {
137 		t1++;
138 	}
139 
140 	TC_PRINT("%s read %zu * %zu = %zu bytes in %u ms: "
141 		 "%u By/s, %u KiBy/s\n",
142 		 tag, nbuf, buf_size, total, (t1 - t0),
143 		 (uint32_t)(total * 1000U / (t1 - t0)),
144 		 (uint32_t)(total * 1000U / (t1 - t0) / 1024U));
145 
146 	rv = TC_PASS;
147 
148 out_file:
149 	(void)fs_close(&file);
150 
151 out_buf:
152 	free(buf);
153 
154 out_mnt:
155 	(void)fs_unmount(mp);
156 
157 	return rv;
158 }
159 
custom_write_test(const char * tag,const struct fs_mount_t * mp,const struct lfs_config * cfgp,size_t buf_size,size_t nbuf)160 static int custom_write_test(const char *tag,
161 			     const struct fs_mount_t *mp,
162 			     const struct lfs_config *cfgp,
163 			     size_t buf_size,
164 			     size_t nbuf)
165 {
166 	struct fs_littlefs data = {
167 		.cfg = *cfgp,
168 	};
169 	struct fs_mount_t lfs_mnt = {
170 		.type = FS_LITTLEFS,
171 		.fs_data = &data,
172 		.storage_dev = mp->storage_dev,
173 		.mnt_point = mp->mnt_point,
174 	};
175 	struct lfs_config *lcp = &data.cfg;
176 	int rv = TC_FAIL;
177 
178 	if (lcp->cache_size == 0) {
179 		lcp->cache_size = CONFIG_FS_LITTLEFS_CACHE_SIZE;
180 	}
181 	if (lcp->lookahead_size == 0) {
182 		lcp->lookahead_size = CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE;
183 	}
184 
185 	lcp->read_buffer = malloc(lcp->cache_size);
186 	lcp->prog_buffer = malloc(lcp->cache_size);
187 	lcp->lookahead_buffer = malloc(lcp->lookahead_size);
188 
189 	TC_PRINT("bufs %p %p %p\n", lcp->read_buffer, lcp->prog_buffer, lcp->lookahead_buffer);
190 
191 	if ((lcp->read_buffer == NULL)
192 	    || (lcp->prog_buffer == NULL)
193 	    || (lcp->lookahead_buffer == NULL)) {
194 		TC_PRINT("%s buffer allocation failed\n", tag);
195 		goto out_free;
196 	}
197 
198 	rv = write_read(tag, &lfs_mnt, buf_size, nbuf);
199 
200 out_free:
201 	if (lcp->read_buffer) {
202 		free(lcp->read_buffer);
203 	}
204 	if (lcp->prog_buffer) {
205 		free(lcp->prog_buffer);
206 	}
207 	if (lcp->lookahead_buffer) {
208 		free(lcp->lookahead_buffer);
209 	}
210 
211 	return rv;
212 }
213 
small_8_1K_cust(void)214 static int small_8_1K_cust(void)
215 {
216 	struct lfs_config cfg = {
217 		.read_size = LARGE_IO_SIZE,
218 		.prog_size = LARGE_IO_SIZE,
219 		.cache_size = LARGE_CACHE_SIZE,
220 		.lookahead_size = LARGE_LOOKAHEAD_SIZE
221 	};
222 
223 	return custom_write_test("small 8x1K bigfile", &testfs_small_mnt, &cfg, 1024, 8);
224 }
225 
ZTEST(littlefs,test_lfs_perf)226 ZTEST(littlefs, test_lfs_perf)
227 {
228 	k_sleep(K_MSEC(100));   /* flush log messages */
229 	zassert_equal(write_read("small 8x1K dflt",
230 				 &testfs_small_mnt,
231 				 1024, 8),
232 		      TC_PASS,
233 		      "failed");
234 
235 	if (IS_ENABLED(CONFIG_APP_TEST_CUSTOM)) {
236 		k_sleep(K_MSEC(100));   /* flush log messages */
237 		zassert_equal(small_8_1K_cust(), TC_PASS,
238 			      "failed");
239 
240 		k_sleep(K_MSEC(100));   /* flush log messages */
241 		zassert_equal(write_read("medium 32x2K dflt",
242 					 &testfs_medium_mnt,
243 					 2048, 32),
244 			      TC_PASS,
245 			      "failed");
246 
247 		k_sleep(K_MSEC(100));   /* flush log messages */
248 		zassert_equal(write_read("large 64x4K dflt",
249 					 &testfs_large_mnt,
250 					 4096, 64),
251 			      TC_PASS,
252 			      "failed");
253 	}
254 }
255