1 /* latency.c -- measure latency of OS */
2
3 #include "porttime.h"
4 #include "portmidi.h"
5 #include "stdlib.h"
6 #include "stdio.h"
7 #include "string.h"
8 #include "assert.h"
9
10 /* Latency is defined here to mean the time starting when a
11 process becomes ready to run, and ending when the process
12 actually runs. Latency is due to contention for the
13 processor, usually due to other processes, OS activity
14 including device drivers handling interrupts, and
15 waiting for the scheduler to suspend the currently running
16 process and activate the one that is waiting.
17
18 Latency can affect PortMidi applications: if a process fails
19 to wake up promptly, MIDI input may sit in the input buffer
20 waiting to be handled, and MIDI output may not be generated
21 with accurate timing. Using the latency parameter when
22 opening a MIDI output port allows the caller to defer timing
23 to PortMidi, which in most implementations will pass the
24 data on to the OS. By passing timestamps and data to the
25 OS kernel, device driver, or even hardware, there are fewer
26 sources of latency that can affect the ultimate timing of
27 the data. On the other hand, the application must generate
28 and deliver the data ahead of the timestamp. The amount by
29 which data is computed early must be at least as large as
30 the worst-case latency to avoid timing problems.
31
32 Latency is even more important in audio applications. If an
33 application lets an audio output buffer underflow, an audible
34 pop or click is produced. Audio input buffers can overflow,
35 causing data to be lost. In general the audio buffers must
36 be large enough to buffer the worst-case latency that the
37 application will encounter.
38
39 This program measures latency by recording the difference
40 between the scheduled callback time and the current real time.
41 We do not really know the scheduled callback time, so we will
42 record the differences between the real time of each callback
43 and the real time of the previous callback. Differences that
44 are larger than the scheduled difference are recorded. Smaller
45 differences indicate the system is recovering from an earlier
46 latency, so these are ignored.
47 Since printing by the callback process can cause all sorts of
48 delays, this program records latency observations in a
49 histogram. When the program is stopped, the histogram is
50 printed to the console.
51
52 Optionally the system can be tested under a load of MIDI input,
53 MIDI output, or both. If MIDI input is selected, the callback
54 thread will read any waiting MIDI events each iteration. You
55 must generate events on this interface for the test to actually
56 put any appreciable load on PortMidi. If MIDI output is
57 selected, alternating note on and note off events are sent each
58 X iterations, where you specify X. For example, with a timer
59 callback period of 2ms and X=1, a MIDI event is sent every 2ms.
60
61
62 INTERPRETING RESULTS: Time is quantized to 1ms, so there is
63 some uncertainty due to rounding. A microsecond latency that
64 spans the time when the clock is incremented will be reported
65 as a latency of 1. On the other hand, a latency of almost
66 1ms that falls between two clock ticks will be reported as
67 zero. In general, if the highest nonzero bin is numbered N,
68 then the maximum latency is N+1.
69
70 CHANGE LOG
71
72 18-Jul-03 Mark Nelson -- Added code to generate MIDI or receive
73 MIDI during test, and made period user-settable.
74 */
75
76 #define HIST_LEN 21 /* how many 1ms bins in the histogram */
77
78 #define STRING_MAX 80 /* used for console input */
79
80 #define INPUT_BUFFER_SIZE 100
81 #define OUTPUT_BUFFER_SIZE 0
82
83 #ifndef max
84 #define max(a, b) ((a) > (b) ? (a) : (b))
85 #endif
86 #ifndef min
87 #define min(a, b) ((a) <= (b) ? (a) : (b))
88 #endif
89
90 int get_number(char *prompt);
91
92 PtTimestamp previous_callback_time = 0;
93
94 int period; /* milliseconds per callback */
95
96 int histogram[HIST_LEN];
97 int max_latency = 0; /* worst latency observed */
98 int out_of_range = 0; /* how many points outside of HIST_LEN? */
99
100 int test_in, test_out; /* test MIDI in and/or out? */
101 int output_period; /* output MIDI every __ iterations if test_out true */
102 int iteration = 0;
103 PmStream *in, *out;
104 int note_on = 0; /* is the note currently on? */
105
106 /* callback function for PortTime -- computes histogram */
pt_callback(PtTimestamp timestamp,void * userData)107 void pt_callback(PtTimestamp timestamp, void *userData)
108 {
109 PtTimestamp difference = timestamp - previous_callback_time - period;
110 previous_callback_time = timestamp;
111
112 /* allow 5 seconds for the system to settle down */
113 if (timestamp < 5000) return;
114
115 iteration++;
116 /* send a note on/off if user requested it */
117 if (test_out && (iteration % output_period == 0)) {
118 PmEvent buffer[1];
119 buffer[0].timestamp = Pt_Time(NULL);
120 if (note_on) {
121 /* note off */
122 buffer[0].message = Pm_Message(0x90, 60, 0);
123 note_on = 0;
124 } else {
125 /* note on */
126 buffer[0].message = Pm_Message(0x90, 60, 100);
127 note_on = 1;
128 }
129 Pm_Write(out, buffer, 1);
130 iteration = 0;
131 }
132
133 /* read all waiting events (if user requested) */
134 if (test_in) {
135 PmError status;
136 PmEvent buffer[1];
137 do {
138 status = Pm_Poll(in);
139 if (status == TRUE) {
140 Pm_Read(in,buffer,1);
141 }
142 } while (status == TRUE);
143 }
144
145 if (difference < 0) return; /* ignore when system is "catching up" */
146
147 /* update the histogram */
148 if (difference < HIST_LEN) {
149 histogram[difference]++;
150 } else {
151 out_of_range++;
152 }
153
154 if (max_latency < difference) max_latency = difference;
155 }
156
157
main()158 int main()
159 {
160 char line[STRING_MAX];
161 int i;
162 int len;
163 int choice;
164 PtTimestamp stop;
165 printf("Latency histogram.\n");
166 period = 0;
167 while (period < 1) {
168 period = get_number("Choose timer period (in ms, >= 1): ");
169 }
170 printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n",
171 "1. No MIDI traffic",
172 "2. MIDI input",
173 "3. MIDI output",
174 "4. MIDI input and output");
175 choice = get_number("? ");
176 switch (choice) {
177 case 1: test_in = 0; test_out = 0; break;
178 case 2: test_in = 1; test_out = 0; break;
179 case 3: test_in = 0; test_out = 1; break;
180 case 4: test_in = 1; test_out = 1; break;
181 default: assert(0);
182 }
183 if (test_in || test_out) {
184 /* list device information */
185 for (i = 0; i < Pm_CountDevices(); i++) {
186 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
187 if ((test_in && info->input) ||
188 (test_out && info->output)) {
189 printf("%d: %s, %s", i, info->interf, info->name);
190 if (info->input) printf(" (input)");
191 if (info->output) printf(" (output)");
192 printf("\n");
193 }
194 }
195 /* open stream(s) */
196 if (test_in) {
197 int i = get_number("MIDI input device number: ");
198 Pm_OpenInput(&in,
199 i,
200 NULL,
201 INPUT_BUFFER_SIZE,
202 (PmTimestamp (*)(void *)) Pt_Time,
203 NULL);
204 /* turn on filtering; otherwise, input might overflow in the
205 5-second period before timer callback starts reading midi */
206 Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK);
207 }
208 if (test_out) {
209 int i = get_number("MIDI output device number: ");
210 PmEvent buffer[1];
211 Pm_OpenOutput(&out,
212 i,
213 NULL,
214 OUTPUT_BUFFER_SIZE,
215 (PmTimestamp (*)(void *)) Pt_Time,
216 NULL,
217 0); /* no latency scheduling */
218
219 /* send a program change to force a status byte -- this fixes
220 a problem with a buggy linux MidiSport driver, and shouldn't
221 hurt anything else
222 */
223 buffer[0].timestamp = 0;
224 buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */
225 Pm_Write(out, buffer, 1);
226
227 output_period = get_number(
228 "MIDI out should be sent every __ callback iterations: ");
229
230 assert(output_period >= 1);
231 }
232 }
233
234 printf("%s%s", "Latency measurements will start in 5 seconds. ",
235 "Type return to stop: ");
236 Pt_Start(period, &pt_callback, 0);
237 fgets(line, STRING_MAX, stdin);
238 stop = Pt_Time();
239 Pt_Stop();
240
241 /* courteously turn off the last note, if necessary */
242 if (note_on) {
243 PmEvent buffer[1];
244 buffer[0].timestamp = Pt_Time(NULL);
245 buffer[0].message = Pm_Message(0x90, 60, 0);
246 Pm_Write(out, buffer, 1);
247 }
248
249 /* print the histogram */
250 printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001);
251 printf("Latency(ms) Number of occurrences\n");
252 /* avoid printing beyond last non-zero histogram entry */
253 len = min(HIST_LEN, max_latency + 1);
254 for (i = 0; i < len; i++) {
255 printf("%2d %10d\n", i, histogram[i]);
256 }
257 printf("Number of points greater than %dms: %d\n",
258 HIST_LEN - 1, out_of_range);
259 printf("Maximum latency: %d milliseconds\n", max_latency);
260 printf("\nNote that due to rounding, actual latency can be 1ms higher\n");
261 printf("than the numbers reported here.\n");
262 printf("Type return to exit...");
263 fgets(line, STRING_MAX, stdin);
264
265 if(choice == 2)
266 Pm_Close(in);
267 else if(choice == 3)
268 Pm_Close(out);
269 else if(choice == 4)
270 {
271 Pm_Close(in);
272 Pm_Close(out);
273 }
274 return 0;
275 }
276
277
278 /* read a number from console */
get_number(char * prompt)279 int get_number(char *prompt)
280 {
281 char line[STRING_MAX];
282 int n = 0, i;
283 printf(prompt);
284 while (n != 1) {
285 n = scanf("%d", &i);
286 fgets(line, STRING_MAX, stdin);
287
288 }
289 return i;
290 }
291