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