add support for O_DIRECT/F_NOCACHE to check impact of buffer cache avoidance
[ardour.git] / tools / sftest.cc
1 /* g++ -o sftest sftest.cc `pkg-config --cflags --libs sndfile` `pkg-config --cflags --libs glibmm-2.4` */
2
3 #include <vector>
4 #include <iostream>
5 #include <iomanip>
6 #include <sstream>
7 #include <cstdlib>
8 #include <unistd.h>
9 #include <sndfile.h>
10 #include <stdint.h>
11 #include <string.h>
12 #include <getopt.h>
13 #include <fcntl.h>
14
15 #include <glibmm/miscutils.h>
16
17 using namespace std;
18
19 SF_INFO format_info;
20 float* data = 0;
21 bool with_sync = false;
22
23 int
24 write_one (SNDFILE* sf, uint32_t nframes)
25 {
26         if (sf_write_float (sf, (float*) data, nframes) != nframes) {
27                 return -1;
28         }
29
30         if (with_sync) {
31                 sf_write_sync (sf);
32         }
33
34         return 0;
35 }
36
37 void
38 usage ()
39 {
40         cout << "sftest [ -f HEADER-FORMAT ] [ -F DATA-FORMAT ] [ -r SAMPLERATE ] [ -n NFILES ] [ -b BLOCKSIZE ] [ -s ]" << endl;
41         cout << "\tHEADER-FORMAT is one of:" << endl
42              << "\t\tWAV" << endl
43              << "\t\tCAF" << endl
44              << "\t\tW64" << endl;
45         cout << "\tDATA-FORMAT is one of:" << endl
46              << "\t\tFLOAT" << endl
47              << "\t\t32" << endl
48              << "\t\t24" << endl
49              << "\t\t16" << endl;
50 }
51
52 int
53 main (int argc, char* argv[])
54 {
55         vector<SNDFILE*> sndfiles;
56         uint32_t sample_size;
57         char optstring[] = "f:r:F:n:c:b:sD";
58         int channels, samplerate;
59         char const *suffix = ".wav";
60         char const *header_format = "wav";
61         char const *data_format = "float";
62         uint32_t block_size = 64 * 1024;
63         uint32_t nfiles = 100;
64         bool direct = false;
65         const struct option longopts[] = {
66                 { "header-format", 1, 0, 'f' },
67                 { "data-format", 1, 0, 'F' },
68                 { "rate", 1, 0, 'r' },
69                 { "nfiles", 1, 0, 'n' },
70                 { "blocksize", 1, 0, 'b' },
71                 { "channels", 1, 0, 'c' },
72                 { "sync", 0, 0, 's' },
73                 { "direct", 0, 0, 'D' },
74                 { 0, 0, 0, 0 }
75         };
76
77         int option_index = 0;
78         int c = 0;
79         
80         while (1) {
81                 if ((c = getopt_long (argc, argv, optstring, longopts, &option_index)) == -1) {
82                         break;
83                 }
84
85                 switch (c) {
86                 case 'f':
87                         header_format = optarg;
88                         break;
89
90                 case 'F':
91                         data_format = optarg;
92                         break;
93
94                 case 'r':
95                         samplerate = atoi (optarg);
96                         break;
97
98                 case 'n':
99                         nfiles = atoi (optarg);
100                         break;
101
102                 case 'c':
103                         channels = atoi (optarg);
104
105                 case 'b':
106                         block_size = atoi (optarg);
107                         break;
108                 case 's':
109                         with_sync = true;
110                         break;
111                 case 'D':
112                         direct = true;
113                         break;
114                 default:
115                         usage ();
116                         return 0;
117                 }
118         }
119
120         /* setup file format */
121         memset (&format_info, 0, sizeof (format_info));
122
123         if (samplerate == 0 || nfiles == 0 || block_size == 0 || channels == 0) {
124                 usage ();
125                 return 1;
126         }
127
128         format_info.samplerate = samplerate;
129         format_info.channels = channels;
130         
131         if (strcasecmp (header_format, "wav") == 0) {
132                 format_info.format |= SF_FORMAT_WAV;
133                 suffix = ".wav";
134         } else if (strcasecmp (header_format, "caf") == 0) {
135                 format_info.format |= SF_FORMAT_CAF;
136                 suffix = ".caf";
137         } else if (strcasecmp (header_format, "w64") == 0) {
138                 format_info.format |= SF_FORMAT_W64;
139                 suffix = ".w64";
140         } else {
141                 usage ();
142                 return 0;
143         }
144
145         if (strcasecmp (data_format, "float") == 0) {
146                 format_info.format |= SF_FORMAT_FLOAT;
147                 sample_size = sizeof (float);
148         } else if (strcasecmp (data_format, "32") == 0) {
149                 format_info.format |= SF_FORMAT_PCM_32;
150                 sample_size = 4;
151         } else if (strcasecmp (data_format, "24") == 0) {
152                 format_info.format |= SF_FORMAT_PCM_24;
153                 sample_size = 3;
154         } else if (strcasecmp (data_format, "16") == 0) {
155                 format_info.format |= SF_FORMAT_PCM_16;
156                 sample_size = 2;
157         } else {
158                 usage ();
159                 return 0;
160         }
161         
162         char tmpdirname[] = "sftest-XXXXXX";
163         g_mkdtemp (tmpdirname);
164
165         for (uint32_t n = 0; n < nfiles; ++n) {
166                 SNDFILE* sf;
167                 string path;
168                 stringstream ss;
169
170                 ss << "sf-";
171                 ss << n;
172                 ss << suffix;
173                 
174                 path = Glib::build_filename (tmpdirname, ss.str());
175
176                 int flags = O_RDWR|O_CREAT|O_TRUNC;
177                 
178 #ifndef __APPLE__
179                 if (direct) {
180                         flags |= O_DIRECT;
181                 }
182 #endif
183
184                 int fd = open (path.c_str(), flags, 0644);
185
186                 if (fd < 0) {
187                         cerr << "Could not open file #" << n << " @ " << path << endl;
188                         return 1;
189                 }
190
191 #ifdef __APPLE__
192                 if (direct) {
193                         /* Apple man pages say only that it returns "a value other than -1 on success",
194                            which probably means zero, but you just can't be too careful with
195                            those guys.
196                         */
197                         if (fcntl (fd, F_NOCACHE, 1) == -1) {
198                                 cerr << "Cannot set F_NOCACHE on file # " << n << endl;
199                         }
200                 }
201 #endif
202                 if ((sf = sf_open_fd (fd, SFM_RDWR, &format_info, true)) == 0) {
203                         cerr << "Could not open file #" << n << " @ " << path << endl;
204                         return 1;
205                 }
206
207                 sndfiles.push_back (sf);
208         }
209
210         cout << nfiles << " files are in " << tmpdirname << " all used " << (direct ? "without" : "with") << " OS buffer cache" << endl;
211         cout << "Format is " << suffix << ' ' << channels << " channel" << (channels > 1 ? "s" : "") << " written in chunks of " << block_size << " frames, synced ? " << (with_sync ? "yes" : "no") << endl;
212                 
213         data = new float[block_size*channels];
214         uint64_t written = 0;
215         
216         while (true) {
217                 gint64 before;
218                 before = g_get_monotonic_time();
219                 for (vector<SNDFILE*>::iterator s = sndfiles.begin(); s != sndfiles.end(); ++s) {
220                         if (write_one (*s, block_size)) {
221                                 cerr << "Write failed for file #" << distance (sndfiles.begin(), s) << endl;
222                                 return 1;
223                         }
224                 }
225                 written += block_size;
226                 gint64 elapsed = g_get_monotonic_time() - before;
227                 double bandwidth = (sndfiles.size() * block_size * channels * sample_size) / (elapsed/1000000.0);
228                 double data_minutes = written / (double) (60.0 * 48000.0);
229                 const double data_rate = sndfiles.size() * channels * sample_size * samplerate;
230                 stringstream ds;
231                 ds << setprecision (1) << data_minutes;
232                 
233                 cout << "BW @ " << written << " frames (" << ds.str() << " minutes) = " << (bandwidth/1048576.0) <<  " MB/sec " << bandwidth / data_rate << " x faster than necessary " << endl;
234         }
235
236         return 0;
237 }
238
239