Line data Source code
1 : /*
2 : File lookup rate test.
3 :
4 : Copyright (C) James Peach 2006
5 :
6 : This program is free software; you can redistribute it and/or modify
7 : it under the terms of the GNU General Public License as published by
8 : the Free Software Foundation; either version 3 of the License, or
9 : (at your option) any later version.
10 :
11 : This program is distributed in the hope that it will be useful,
12 : but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : GNU General Public License for more details.
15 :
16 : You should have received a copy of the GNU General Public License
17 : along with this program. If not, see <http://www.gnu.org/licenses/>.
18 : */
19 :
20 : #include "includes.h"
21 : #include "system/filesys.h"
22 : #include "torture/smbtorture.h"
23 : #include "libcli/libcli.h"
24 : #include "torture/util.h"
25 : #include "torture/raw/proto.h"
26 :
27 : #define BASEDIR "\\lookuprate"
28 : #define MISSINGNAME BASEDIR "\\foo"
29 :
30 : #define FUZZ_PERCENT 10
31 :
32 : #define usec_to_sec(s) ((s) / 1000000)
33 : #define sec_to_usec(s) ((s) * 1000000)
34 :
35 : struct rate_record
36 : {
37 : unsigned dirent_count;
38 : unsigned querypath_persec;
39 : unsigned findfirst_persec;
40 : };
41 :
42 : static struct rate_record records[] =
43 : {
44 : { 0, 0, 0 }, /* Base (optimal) lookup rate. */
45 : { 100, 0, 0},
46 : { 1000, 0, 0},
47 : { 10000, 0, 0},
48 : { 100000, 0, 0}
49 : };
50 :
51 : typedef NTSTATUS lookup_function(struct smbcli_tree *tree, const char * path);
52 :
53 : /* Test whether rhs is within fuzz% of lhs. */
54 0 : static bool fuzzily_equal(unsigned lhs, unsigned rhs, int percent)
55 : {
56 0 : double fuzz = (double)lhs * (double)percent/100.0;
57 :
58 0 : if (((double)rhs >= ((double)lhs - fuzz)) &&
59 0 : ((double)rhs <= ((double)lhs + fuzz))) {
60 0 : return true;
61 : }
62 :
63 0 : return false;
64 :
65 : }
66 :
67 0 : static NTSTATUS fill_directory(struct smbcli_tree *tree,
68 : const char * path, unsigned count)
69 : {
70 0 : NTSTATUS status;
71 0 : char *fname = NULL;
72 0 : unsigned i;
73 0 : unsigned current;
74 :
75 0 : struct timeval start;
76 0 : struct timeval now;
77 :
78 0 : status = smbcli_mkdir(tree, path);
79 0 : if (!NT_STATUS_IS_OK(status)) {
80 0 : return status;
81 : }
82 :
83 0 : printf("filling directory %s with %u files... ", path, count);
84 0 : fflush(stdout);
85 :
86 0 : current = random();
87 0 : start = timeval_current();
88 :
89 0 : for (i = 0; i < count; ++i) {
90 0 : int fnum;
91 :
92 0 : ++current;
93 0 : fname = talloc_asprintf(NULL, "%s\\fill%u",
94 : path, current);
95 :
96 0 : fnum = smbcli_open(tree, fname, O_RDONLY|O_CREAT,
97 : DENY_NONE);
98 0 : if (fnum < 0) {
99 0 : talloc_free(fname);
100 0 : return smbcli_nt_error(tree);
101 : }
102 :
103 0 : smbcli_close(tree, fnum);
104 0 : talloc_free(fname);
105 : }
106 :
107 0 : if (count) {
108 0 : double rate;
109 0 : now = timeval_current();
110 0 : rate = (double)count / usec_to_sec((double)usec_time_diff(&now, &start));
111 0 : printf("%u/sec\n", (unsigned)rate);
112 : } else {
113 0 : printf("done\n");
114 : }
115 :
116 0 : return NT_STATUS_OK;
117 : }
118 :
119 0 : static NTSTATUS squash_lookup_error(NTSTATUS status)
120 : {
121 0 : if (NT_STATUS_IS_OK(status)) {
122 0 : return NT_STATUS_OK;
123 : }
124 :
125 : /* We don't care if the file isn't there. */
126 0 : if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
127 0 : return NT_STATUS_OK;
128 : }
129 :
130 0 : if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
131 0 : return NT_STATUS_OK;
132 : }
133 :
134 0 : if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_FILE)) {
135 0 : return NT_STATUS_OK;
136 : }
137 :
138 0 : return status;
139 : }
140 :
141 : /* Look up a pathname using TRANS2_QUERY_PATH_INFORMATION. */
142 0 : static NTSTATUS querypath_lookup(struct smbcli_tree *tree, const char * path)
143 : {
144 0 : NTSTATUS status;
145 0 : time_t ftimes[3];
146 0 : size_t fsize;
147 0 : uint16_t fmode;
148 :
149 0 : status = smbcli_qpathinfo(tree, path, &ftimes[0], &ftimes[1], &ftimes[2],
150 : &fsize, &fmode);
151 :
152 0 : return squash_lookup_error(status);
153 : }
154 :
155 : /* Look up a pathname using TRANS2_FIND_FIRST2. */
156 0 : static NTSTATUS findfirst_lookup(struct smbcli_tree *tree, const char * path)
157 : {
158 0 : NTSTATUS status = NT_STATUS_OK;
159 :
160 0 : if (smbcli_list(tree, path, 0, NULL, NULL) < 0) {
161 0 : status = smbcli_nt_error(tree);
162 : }
163 :
164 0 : return squash_lookup_error(status);
165 : }
166 :
167 0 : static NTSTATUS lookup_rate_convert(struct smbcli_tree *tree,
168 : lookup_function lookup, const char * path, unsigned * rate)
169 : {
170 0 : NTSTATUS status;
171 :
172 0 : struct timeval start;
173 0 : struct timeval now;
174 0 : unsigned count = 0;
175 0 : int64_t elapsed = 0;
176 :
177 : #define LOOKUP_PERIOD_SEC (2)
178 :
179 0 : start = timeval_current();
180 0 : while (elapsed < sec_to_usec(LOOKUP_PERIOD_SEC)) {
181 :
182 0 : status = lookup(tree, path);
183 0 : if (!NT_STATUS_IS_OK(status)) {
184 0 : return status;
185 : }
186 :
187 0 : ++count;
188 0 : now = timeval_current();
189 0 : elapsed = usec_time_diff(&now, &start);
190 : }
191 :
192 : #undef LOOKUP_PERIOD_SEC
193 :
194 0 : *rate = (unsigned)((double)count / (double)usec_to_sec(elapsed));
195 0 : return NT_STATUS_OK;
196 : }
197 :
198 0 : static bool remove_working_directory(struct smbcli_tree *tree,
199 : const char * path)
200 : {
201 0 : int tries;
202 :
203 : /* Using smbcli_deltree to delete a very large number of files
204 : * doesn't work against all servers. Work around this by
205 : * retrying.
206 : */
207 0 : for (tries = 0; tries < 5; ) {
208 0 : int ret;
209 :
210 0 : ret = smbcli_deltree(tree, BASEDIR);
211 0 : if (ret == -1) {
212 0 : tries++;
213 0 : printf("(%s) failed to deltree %s: %s\n",
214 : __location__, BASEDIR,
215 : smbcli_errstr(tree));
216 0 : continue;
217 : }
218 :
219 0 : return true;
220 : }
221 :
222 0 : return false;
223 :
224 : }
225 :
226 : /* Verify that looking up a file name takes constant time.
227 : *
228 : * This test samples the lookup rate for a non-existent filename in a
229 : * directory, while varying the number of files in the directory. The
230 : * lookup rate should continue to approximate the lookup rate for the
231 : * empty directory case.
232 : */
233 0 : bool torture_bench_lookup(struct torture_context *torture)
234 : {
235 0 : NTSTATUS status;
236 0 : bool result = false;
237 :
238 0 : int i;
239 0 : struct smbcli_state *cli = NULL;
240 :
241 0 : if (!torture_open_connection(&cli, torture, 0)) {
242 0 : goto done;
243 : }
244 :
245 0 : remove_working_directory(cli->tree, BASEDIR);
246 :
247 0 : for (i = 0; i < ARRAY_SIZE(records); ++i) {
248 0 : printf("Testing lookup rate with %u directory entries\n",
249 : records[i].dirent_count);
250 :
251 0 : status = fill_directory(cli->tree, BASEDIR,
252 : records[i].dirent_count);
253 0 : if (!NT_STATUS_IS_OK(status)) {
254 0 : printf("failed to fill directory: %s\n", nt_errstr(status));
255 0 : goto done;
256 : }
257 :
258 0 : status = lookup_rate_convert(cli->tree, querypath_lookup,
259 : MISSINGNAME, &records[i].querypath_persec);
260 0 : if (!NT_STATUS_IS_OK(status)) {
261 0 : printf("querypathinfo of %s failed: %s\n",
262 : MISSINGNAME, nt_errstr(status));
263 0 : goto done;
264 : }
265 :
266 0 : status = lookup_rate_convert(cli->tree, findfirst_lookup,
267 : MISSINGNAME, &records[i].findfirst_persec);
268 0 : if (!NT_STATUS_IS_OK(status)) {
269 0 : printf("findfirst of %s failed: %s\n",
270 : MISSINGNAME, nt_errstr(status));
271 0 : goto done;
272 : }
273 :
274 0 : printf("entries = %u, querypath = %u/sec, findfirst = %u/sec\n",
275 : records[i].dirent_count,
276 : records[i].querypath_persec,
277 : records[i].findfirst_persec);
278 :
279 0 : if (!remove_working_directory(cli->tree, BASEDIR)) {
280 0 : goto done;
281 : }
282 : }
283 :
284 : /* Ok. We have run all our tests. Walk through the records we
285 : * accumulated and figure out whether the lookups took constant
286 : * time or not.
287 : */
288 0 : result = true;
289 0 : for (i = 0; i < ARRAY_SIZE(records); ++i) {
290 0 : if (!fuzzily_equal(records[0].querypath_persec,
291 : records[i].querypath_persec,
292 : FUZZ_PERCENT)) {
293 0 : printf("querypath rate for %d entries differed by "
294 : "more than %d%% from base rate\n",
295 : records[i].dirent_count, FUZZ_PERCENT);
296 0 : result = false;
297 : }
298 :
299 0 : if (!fuzzily_equal(records[0].findfirst_persec,
300 : records[i].findfirst_persec,
301 : FUZZ_PERCENT)) {
302 0 : printf("findfirst rate for %d entries differed by "
303 : "more than %d%% from base rate\n",
304 : records[i].dirent_count, FUZZ_PERCENT);
305 0 : result = false;
306 : }
307 : }
308 :
309 0 : done:
310 0 : if (cli) {
311 0 : remove_working_directory(cli->tree, BASEDIR);
312 0 : talloc_free(cli);
313 : }
314 :
315 0 : return result;
316 : }
317 :
318 : /* vim: set sts=8 sw=8 : */
|