1 /*
2 * Copyright © 2020 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included
12 * in all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 */
22
23 /**
24 * @file intel_measure.c
25 */
26
27 #include "intel_measure.h"
28
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/stat.h>
34 #include <sys/types.h>
35 #include <unistd.h>
36
37 #define __STDC_FORMAT_MACROS 1
38 #include <inttypes.h>
39
40 #include "dev/intel_device_info.h"
41 #include "util/debug.h"
42 #include "util/macros.h"
43 #include "util/u_debug.h"
44
45
46 static const struct debug_control debug_control[] = {
47 { "draw", INTEL_MEASURE_DRAW },
48 { "rt", INTEL_MEASURE_RENDERPASS },
49 { "shader", INTEL_MEASURE_SHADER },
50 { "batch", INTEL_MEASURE_BATCH },
51 { "frame", INTEL_MEASURE_FRAME },
52 { NULL, 0 }
53 };
54 static struct intel_measure_config config;
55
56 void
intel_measure_init(struct intel_measure_device * device)57 intel_measure_init(struct intel_measure_device *device)
58 {
59 static bool once = false;
60 const char *env = getenv("INTEL_MEASURE");
61 if (unlikely(!once)) {
62 once = true;
63 memset(&config, 0, sizeof(struct intel_measure_config));
64 if (!env)
65 return;
66
67 char env_copy[1024];
68 strncpy(env_copy, env, 1024);
69 env_copy[1023] = '\0';
70
71 config.file = stderr;
72 config.flags = parse_debug_string(env_copy, debug_control);
73 if (!config.flags)
74 config.flags = INTEL_MEASURE_DRAW;
75 config.enabled = true;
76 config.event_interval = 1;
77 config.control_fh = -1;
78
79 /* Overflows of the following defaults will drop data and generate a
80 * warning on the output filehandle.
81 */
82
83 /* default batch_size allows for 64k renders in a single batch */
84 const int DEFAULT_BATCH_SIZE = 64 * 1024;
85 config.batch_size = DEFAULT_BATCH_SIZE;
86
87 /* Default buffer_size allows for 64k batches per line of output in the
88 * csv. Overflow may occur for offscreen workloads or large 'interval'
89 * settings.
90 */
91 const int DEFAULT_BUFFER_SIZE = 64 * 1024;
92 config.buffer_size = DEFAULT_BUFFER_SIZE;
93
94 const char *filename = strstr(env_copy, "file=");
95 const char *start_frame_s = strstr(env_copy, "start=");
96 const char *count_frame_s = strstr(env_copy, "count=");
97 const char *control_path = strstr(env_copy, "control=");
98 const char *interval_s = strstr(env_copy, "interval=");
99 const char *batch_size_s = strstr(env_copy, "batch_size=");
100 const char *buffer_size_s = strstr(env_copy, "buffer_size=");
101 while (true) {
102 char *sep = strrchr(env_copy, ',');
103 if (sep == NULL)
104 break;
105 *sep = '\0';
106 }
107
108 if (filename && !__check_suid()) {
109 filename += 5;
110 config.file = fopen(filename, "w");
111 if (!config.file) {
112 fprintf(stderr, "INTEL_MEASURE failed to open output file %s: %s\n",
113 filename, strerror (errno));
114 abort();
115 }
116 }
117
118 if (start_frame_s) {
119 start_frame_s += 6;
120 const int start_frame = atoi(start_frame_s);
121 if (start_frame < 0) {
122 fprintf(stderr, "INTEL_MEASURE start frame may "
123 "not be negative: %d\n", start_frame);
124 abort();
125 }
126
127 config.start_frame = start_frame;
128 config.enabled = false;
129 }
130
131 if (count_frame_s) {
132 count_frame_s += 6;
133 const int count_frame = atoi(count_frame_s);
134 if (count_frame <= 0) {
135 fprintf(stderr, "INTEL_MEASURE count frame must be positive: %d\n",
136 count_frame);
137 abort();
138 }
139
140 config.end_frame = config.start_frame + count_frame;
141 }
142
143 if (control_path) {
144 control_path += 8;
145 if (mkfifoat(AT_FDCWD, control_path, O_CREAT | S_IRUSR | S_IWUSR)) {
146 if (errno != EEXIST) {
147 fprintf(stderr, "INTEL_MEASURE failed to create control "
148 "fifo %s: %s\n", control_path, strerror (errno));
149 abort();
150 }
151 }
152
153 config.control_fh = openat(AT_FDCWD, control_path,
154 O_RDONLY | O_NONBLOCK);
155 if (config.control_fh == -1) {
156 fprintf(stderr, "INTEL_MEASURE failed to open control fifo "
157 "%s: %s\n", control_path, strerror (errno));
158 abort();
159 }
160
161 /* when using a control fifo, do not start until the user triggers
162 * capture
163 */
164 config.enabled = false;
165 }
166
167 if (interval_s) {
168 interval_s += 9;
169 const int event_interval = atoi(interval_s);
170 if (event_interval < 1) {
171 fprintf(stderr, "INTEL_MEASURE event_interval must be positive: "
172 "%d\n", event_interval);
173 abort();
174 }
175 config.event_interval = event_interval;
176 }
177
178 if (batch_size_s) {
179 batch_size_s += 11;
180 const int batch_size = atoi(batch_size_s);
181 if (batch_size < DEFAULT_BATCH_SIZE) {
182 fprintf(stderr, "INTEL_MEASURE minimum batch_size is 4k: "
183 "%d\n", batch_size);
184 abort();
185 }
186 if (batch_size > DEFAULT_BATCH_SIZE * 1024) {
187 fprintf(stderr, "INTEL_MEASURE batch_size limited to 4M: "
188 "%d\n", batch_size);
189 abort();
190 }
191
192 config.batch_size = batch_size;
193 }
194
195 if (buffer_size_s) {
196 buffer_size_s += 12;
197 const int buffer_size = atoi(buffer_size_s);
198 if (buffer_size < DEFAULT_BUFFER_SIZE) {
199 fprintf(stderr, "INTEL_MEASURE minimum buffer_size is 1k: "
200 "%d\n", DEFAULT_BUFFER_SIZE);
201 }
202 if (buffer_size > DEFAULT_BUFFER_SIZE * 1024) {
203 fprintf(stderr, "INTEL_MEASURE buffer_size limited to 1M: "
204 "%d\n", buffer_size);
205 }
206
207 config.buffer_size = buffer_size;
208 }
209
210 fputs("draw_start,draw_end,frame,batch,"
211 "event_index,event_count,type,count,vs,tcs,tes,"
212 "gs,fs,cs,framebuffer,idle_us,time_us\n",
213 config.file);
214 }
215
216 device->config = NULL;
217 device->frame = 0;
218 device->release_batch = NULL;
219 pthread_mutex_init(&device->mutex, NULL);
220 list_inithead(&device->queued_snapshots);
221
222 if (env)
223 device->config = &config;
224 }
225
226 const char *
intel_measure_snapshot_string(enum intel_measure_snapshot_type type)227 intel_measure_snapshot_string(enum intel_measure_snapshot_type type)
228 {
229 const char *names[] = {
230 [INTEL_SNAPSHOT_UNDEFINED] = "undefined",
231 [INTEL_SNAPSHOT_BLIT] = "blit",
232 [INTEL_SNAPSHOT_CCS_AMBIGUATE] = "ccs ambiguate",
233 [INTEL_SNAPSHOT_CCS_COLOR_CLEAR] = "ccs color clear",
234 [INTEL_SNAPSHOT_CCS_PARTIAL_RESOLVE] = "ccs partial resolve",
235 [INTEL_SNAPSHOT_CCS_RESOLVE] = "ccs resolve",
236 [INTEL_SNAPSHOT_COMPUTE] = "compute",
237 [INTEL_SNAPSHOT_COPY] = "copy",
238 [INTEL_SNAPSHOT_DRAW] = "draw",
239 [INTEL_SNAPSHOT_HIZ_AMBIGUATE] = "hiz ambiguate",
240 [INTEL_SNAPSHOT_HIZ_CLEAR] = "hiz clear",
241 [INTEL_SNAPSHOT_HIZ_RESOLVE] = "hiz resolve",
242 [INTEL_SNAPSHOT_MCS_COLOR_CLEAR] = "mcs color clear",
243 [INTEL_SNAPSHOT_MCS_PARTIAL_RESOLVE] = "mcs partial resolve",
244 [INTEL_SNAPSHOT_SLOW_COLOR_CLEAR] = "slow color clear",
245 [INTEL_SNAPSHOT_SLOW_DEPTH_CLEAR] = "slow depth clear",
246 [INTEL_SNAPSHOT_SECONDARY_BATCH] = "secondary command buffer",
247 [INTEL_SNAPSHOT_END] = "end",
248 };
249 assert(type < ARRAY_SIZE(names));
250 assert(names[type] != NULL);
251 assert(type != INTEL_SNAPSHOT_UNDEFINED);
252 return names[type];
253 }
254
255 /**
256 * Indicate to the caller whether a new snapshot should be started.
257 *
258 * Callers provide rendering state to this method to determine whether the
259 * current start event should be skipped. Depending on the configuration
260 * flags, a new snapshot may start:
261 * - at every event
262 * - when the program changes
263 * - after a batch is submitted
264 * - at frame boundaries
265 *
266 * Returns true if a snapshot should be started.
267 */
268 bool
intel_measure_state_changed(const struct intel_measure_batch * batch,uintptr_t vs,uintptr_t tcs,uintptr_t tes,uintptr_t gs,uintptr_t fs,uintptr_t cs)269 intel_measure_state_changed(const struct intel_measure_batch *batch,
270 uintptr_t vs, uintptr_t tcs, uintptr_t tes,
271 uintptr_t gs, uintptr_t fs, uintptr_t cs)
272 {
273 if (batch->index == 0) {
274 /* always record the first event */
275 return true;
276 }
277
278 const struct intel_measure_snapshot *last_snap =
279 &batch->snapshots[batch->index - 1];
280
281 if (config.flags & INTEL_MEASURE_DRAW)
282 return true;
283
284 if (batch->index % 2 == 0) {
285 /* no snapshot is running, but we have a start event */
286 return true;
287 }
288
289 if (config.flags & (INTEL_MEASURE_FRAME | INTEL_MEASURE_BATCH)) {
290 /* only start collection when index == 0, at the beginning of a batch */
291 return false;
292 }
293
294 if (config.flags & INTEL_MEASURE_RENDERPASS) {
295 return ((last_snap->framebuffer != batch->framebuffer) ||
296 /* compute workloads are always in their own renderpass */
297 (cs != 0));
298 }
299
300 /* remaining comparisons check the state of the render pipeline for
301 * INTEL_MEASURE_PROGRAM
302 */
303 assert(config.flags & INTEL_MEASURE_SHADER);
304
305 if (!vs && !tcs && !tes && !gs && !fs && !cs) {
306 /* blorp always changes program */
307 return true;
308 }
309
310 return (last_snap->vs != (uintptr_t) vs ||
311 last_snap->tcs != (uintptr_t) tcs ||
312 last_snap->tes != (uintptr_t) tes ||
313 last_snap->gs != (uintptr_t) gs ||
314 last_snap->fs != (uintptr_t) fs ||
315 last_snap->cs != (uintptr_t) cs);
316 }
317
318 /**
319 * Notify intel_measure that a frame is about to begin.
320 *
321 * Configuration values and the control fifo may commence measurement at frame
322 * boundaries.
323 */
324 void
intel_measure_frame_transition(unsigned frame)325 intel_measure_frame_transition(unsigned frame)
326 {
327 if (frame == config.start_frame)
328 config.enabled = true;
329 else if (frame == config.end_frame)
330 config.enabled = false;
331
332 /* user commands to the control fifo will override any start/count
333 * environment settings
334 */
335 if (config.control_fh != -1) {
336 while (true) {
337 const unsigned BUF_SIZE = 128;
338 char buf[BUF_SIZE];
339 ssize_t bytes = read(config.control_fh, buf, BUF_SIZE - 1);
340 if (bytes == 0)
341 break;
342 if (bytes == -1) {
343 fprintf(stderr, "INTEL_MEASURE failed to read control fifo: %s\n",
344 strerror(errno));
345 abort();
346 }
347
348 buf[bytes] = '\0';
349 char *nptr = buf, *endptr = buf;
350 while (*nptr != '\0' && *endptr != '\0') {
351 long fcount = strtol(nptr, &endptr, 10);
352 if (nptr == endptr) {
353 config.enabled = false;
354 fprintf(stderr, "INTEL_MEASURE invalid frame count on "
355 "control fifo.\n");
356 lseek(config.control_fh, 0, SEEK_END);
357 break;
358 } else if (fcount == 0) {
359 config.enabled = false;
360 } else {
361 config.enabled = true;
362 config.end_frame = frame + fcount;
363 }
364
365 nptr = endptr + 1;
366 }
367 }
368 }
369 }
370
371 #define TIMESTAMP_BITS 36
372 static uint64_t
raw_timestamp_delta(uint64_t time0,uint64_t time1)373 raw_timestamp_delta(uint64_t time0, uint64_t time1)
374 {
375 if (time0 > time1) {
376 return (1ULL << TIMESTAMP_BITS) + time1 - time0;
377 } else {
378 return time1 - time0;
379 }
380 }
381
382 /**
383 * Verify that rendering has completed for the batch
384 *
385 * Rendering is complete when the last timestamp has been written.
386 */
387 bool
intel_measure_ready(struct intel_measure_batch * batch)388 intel_measure_ready(struct intel_measure_batch *batch)
389 {
390 assert(batch->timestamps);
391 assert(batch->index > 1);
392 return (batch->timestamps[batch->index - 1] != 0);
393 }
394
395 /**
396 * Submit completed snapshots for buffering.
397 *
398 * Snapshot data becomes available when asynchronous rendering completes.
399 * Depending on configuration, snapshot data may need to be collated before
400 * writing to the output file.
401 */
402 static void
intel_measure_push_result(struct intel_measure_device * device,struct intel_measure_batch * batch)403 intel_measure_push_result(struct intel_measure_device *device,
404 struct intel_measure_batch *batch)
405 {
406 struct intel_measure_ringbuffer *rb = device->ringbuffer;
407
408 uint64_t *timestamps = batch->timestamps;
409 assert(timestamps != NULL);
410 assert(batch->index == 0 || timestamps[0] != 0);
411
412 for (int i = 0; i < batch->index; i += 2) {
413 const struct intel_measure_snapshot *begin = &batch->snapshots[i];
414 const struct intel_measure_snapshot *end = &batch->snapshots[i+1];
415
416 assert (end->type == INTEL_SNAPSHOT_END);
417
418 if (begin->type == INTEL_SNAPSHOT_SECONDARY_BATCH) {
419 assert(begin->secondary != NULL);
420 begin->secondary->batch_count = batch->batch_count;
421 intel_measure_push_result(device, begin->secondary);
422 continue;
423 }
424
425 const uint64_t prev_end_ts = rb->results[rb->head].end_ts;
426
427 /* advance ring buffer */
428 if (++rb->head == config.buffer_size)
429 rb->head = 0;
430 if (rb->head == rb->tail) {
431 static bool warned = false;
432 if (unlikely(!warned)) {
433 fprintf(config.file,
434 "WARNING: Buffered data exceeds INTEL_MEASURE limit: %d. "
435 "Data has been dropped. "
436 "Increase setting with INTEL_MEASURE=buffer_size={count}\n",
437 config.buffer_size);
438 warned = true;
439 }
440 break;
441 }
442
443 struct intel_measure_buffered_result *buffered_result =
444 &rb->results[rb->head];
445
446 memset(buffered_result, 0, sizeof(*buffered_result));
447 memcpy(&buffered_result->snapshot, begin,
448 sizeof(struct intel_measure_snapshot));
449 buffered_result->start_ts = timestamps[i];
450 buffered_result->end_ts = timestamps[i+1];
451 buffered_result->idle_duration =
452 raw_timestamp_delta(prev_end_ts, buffered_result->start_ts);
453 buffered_result->frame = batch->frame;
454 buffered_result->batch_count = batch->batch_count;
455 buffered_result->event_index = i / 2;
456 buffered_result->snapshot.event_count = end->event_count;
457 }
458 }
459
460 static unsigned
ringbuffer_size(const struct intel_measure_ringbuffer * rb)461 ringbuffer_size(const struct intel_measure_ringbuffer *rb)
462 {
463 unsigned head = rb->head;
464 if (head < rb->tail)
465 head += config.buffer_size;
466 return head - rb->tail;
467 }
468
469 static const struct intel_measure_buffered_result *
ringbuffer_pop(struct intel_measure_ringbuffer * rb)470 ringbuffer_pop(struct intel_measure_ringbuffer *rb)
471 {
472 if (rb->tail == rb->head) {
473 /* encountered ringbuffer overflow while processing events */
474 return NULL;
475 }
476
477 if (++rb->tail == config.buffer_size)
478 rb->tail = 0;
479 return &rb->results[rb->tail];
480 }
481
482 static const struct intel_measure_buffered_result *
ringbuffer_peek(const struct intel_measure_ringbuffer * rb,unsigned index)483 ringbuffer_peek(const struct intel_measure_ringbuffer *rb, unsigned index)
484 {
485 int result_offset = rb->tail + index + 1;
486 if (result_offset >= config.buffer_size)
487 result_offset -= config.buffer_size;
488 return &rb->results[result_offset];
489 }
490
491
492 /**
493 * Determine the number of buffered events that must be combined for the next
494 * line of csv output. Returns 0 if more events are needed.
495 */
496 static unsigned
buffered_event_count(struct intel_measure_device * device)497 buffered_event_count(struct intel_measure_device *device)
498 {
499 const struct intel_measure_ringbuffer *rb = device->ringbuffer;
500 const unsigned buffered_event_count = ringbuffer_size(rb);
501 if (buffered_event_count == 0) {
502 /* no events to collect */
503 return 0;
504 }
505
506 /* count the number of buffered events required to meet the configuration */
507 if (config.flags & (INTEL_MEASURE_DRAW |
508 INTEL_MEASURE_RENDERPASS |
509 INTEL_MEASURE_SHADER)) {
510 /* For these flags, every buffered event represents a line in the
511 * output. None of these events span batches. If the event interval
512 * crosses a batch boundary, then the next interval starts with the new
513 * batch.
514 */
515 return 1;
516 }
517
518 const unsigned start_frame = ringbuffer_peek(rb, 0)->frame;
519 if (config.flags & INTEL_MEASURE_BATCH) {
520 /* each buffered event is a command buffer. The number of events to
521 * process is the same as the interval, unless the interval crosses a
522 * frame boundary
523 */
524 if (buffered_event_count < config.event_interval) {
525 /* not enough events */
526 return 0;
527 }
528
529 /* Imperfect frame tracking requires us to allow for *older* frames */
530 if (ringbuffer_peek(rb, config.event_interval - 1)->frame <= start_frame) {
531 /* No frame transition. The next {interval} events should be combined. */
532 return config.event_interval;
533 }
534
535 /* Else a frame transition occurs within the interval. Find the
536 * transition, so the following line of output begins with the batch
537 * that starts the new frame.
538 */
539 for (int event_index = 1;
540 event_index <= config.event_interval;
541 ++event_index) {
542 if (ringbuffer_peek(rb, event_index)->frame > start_frame)
543 return event_index;
544 }
545
546 assert(false);
547 }
548
549 /* Else we need to search buffered events to find the matching frame
550 * transition for our interval.
551 */
552 assert(config.flags & INTEL_MEASURE_FRAME);
553 for (int event_index = 1;
554 event_index < buffered_event_count;
555 ++event_index) {
556 const int latest_frame = ringbuffer_peek(rb, event_index)->frame;
557 if (latest_frame - start_frame >= config.event_interval)
558 return event_index;
559 }
560
561 return 0;
562 }
563
564 /**
565 * Take result_count events from the ringbuffer and output them as a single
566 * line.
567 */
568 static void
print_combined_results(struct intel_measure_device * measure_device,int result_count,struct intel_device_info * info)569 print_combined_results(struct intel_measure_device *measure_device,
570 int result_count,
571 struct intel_device_info *info)
572 {
573 if (result_count == 0)
574 return;
575
576 struct intel_measure_ringbuffer *result_rb = measure_device->ringbuffer;
577 assert(ringbuffer_size(result_rb) >= result_count);
578 const struct intel_measure_buffered_result* start_result =
579 ringbuffer_pop(result_rb);
580 const struct intel_measure_buffered_result* current_result = start_result;
581
582 if (start_result == NULL)
583 return;
584 --result_count;
585
586 uint64_t duration_ts = raw_timestamp_delta(start_result->start_ts,
587 current_result->end_ts);
588 unsigned event_count = start_result->snapshot.event_count;
589 while (result_count-- > 0) {
590 assert(ringbuffer_size(result_rb) > 0);
591 current_result = ringbuffer_pop(result_rb);
592 if (current_result == NULL)
593 return;
594 duration_ts += raw_timestamp_delta(current_result->start_ts,
595 current_result->end_ts);
596 event_count += current_result->snapshot.event_count;
597 }
598
599 uint64_t duration_idle_ns =
600 intel_device_info_timebase_scale(info, start_result->idle_duration);
601 uint64_t duration_time_ns =
602 intel_device_info_timebase_scale(info, duration_ts);
603 const struct intel_measure_snapshot *begin = &start_result->snapshot;
604 fprintf(config.file, "%"PRIu64",%"PRIu64",%u,%u,%u,%u,%s,%u,"
605 "0x%"PRIxPTR",0x%"PRIxPTR",0x%"PRIxPTR",0x%"PRIxPTR",0x%"PRIxPTR","
606 "0x%"PRIxPTR",0x%"PRIxPTR",%.3lf,%.3lf\n",
607 start_result->start_ts, current_result->end_ts,
608 start_result->frame, start_result->batch_count,
609 start_result->event_index, event_count,
610 begin->event_name, begin->count,
611 begin->vs, begin->tcs, begin->tes, begin->gs, begin->fs, begin->cs,
612 begin->framebuffer,
613 (double)duration_idle_ns / 1000.0,
614 (double)duration_time_ns / 1000.0);
615 }
616
617 /**
618 * Empty the ringbuffer of events that can be printed.
619 */
620 static void
intel_measure_print(struct intel_measure_device * device,struct intel_device_info * info)621 intel_measure_print(struct intel_measure_device *device,
622 struct intel_device_info *info)
623 {
624 while (true) {
625 const int events_to_combine = buffered_event_count(device);
626 if (events_to_combine == 0)
627 break;
628 print_combined_results(device, events_to_combine, info);
629 }
630 }
631
632 /**
633 * Collect snapshots from completed command buffers and submit them to
634 * intel_measure for printing.
635 */
636 void
intel_measure_gather(struct intel_measure_device * measure_device,struct intel_device_info * info)637 intel_measure_gather(struct intel_measure_device *measure_device,
638 struct intel_device_info *info)
639 {
640 pthread_mutex_lock(&measure_device->mutex);
641
642 /* Iterate snapshots and collect if ready. Each snapshot queue will be
643 * in-order, but we must determine which queue has the oldest batch.
644 */
645 /* iterate snapshots and collect if ready */
646 while (!list_is_empty(&measure_device->queued_snapshots)) {
647 struct intel_measure_batch *batch =
648 list_first_entry(&measure_device->queued_snapshots,
649 struct intel_measure_batch, link);
650
651 if (!intel_measure_ready(batch)) {
652 /* command buffer has begun execution on the gpu, but has not
653 * completed.
654 */
655 break;
656 }
657
658 list_del(&batch->link);
659 assert(batch->index % 2 == 0);
660
661 intel_measure_push_result(measure_device, batch);
662
663 batch->index = 0;
664 batch->frame = 0;
665 if (measure_device->release_batch)
666 measure_device->release_batch(batch);
667 }
668
669 intel_measure_print(measure_device, info);
670 pthread_mutex_unlock(&measure_device->mutex);
671 }
672
673