• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2024 Igalia S.L.
3  * SPDX-License-Identifier: MIT
4  */
5 
6 #include "freedreno_rd_output.h"
7 
8 #include <assert.h>
9 #include <ctype.h>
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15 
16 #include "c11/threads.h"
17 #include "util/log.h"
18 #include "util/u_atomic.h"
19 #include "util/u_debug.h"
20 
21 #ifdef ANDROID
22 static const char *fd_rd_output_base_path = "/data/local/tmp";
23 #else
24 static const char *fd_rd_output_base_path = "/tmp";
25 #endif
26 
27 static const struct debug_control fd_rd_dump_options[] = {
28    { "enable", FD_RD_DUMP_ENABLE },
29    { "combine", FD_RD_DUMP_COMBINE },
30    { "full", FD_RD_DUMP_FULL },
31    { "trigger", FD_RD_DUMP_TRIGGER },
32    { NULL, 0 }
33 };
34 
35 struct fd_rd_dump_env fd_rd_dump_env;
36 
37 static void
fd_rd_dump_env_init_once(void)38 fd_rd_dump_env_init_once(void)
39 {
40    fd_rd_dump_env.flags = parse_debug_string(os_get_option("FD_RD_DUMP"),
41                                              fd_rd_dump_options);
42 
43    /* If any of the more-detailed FD_RD_DUMP flags is enabled, the general
44     * FD_RD_DUMP_ENABLE flag should also implicitly be set.
45     */
46    if (fd_rd_dump_env.flags & ~FD_RD_DUMP_ENABLE)
47       fd_rd_dump_env.flags |= FD_RD_DUMP_ENABLE;
48 }
49 
50 void
fd_rd_dump_env_init(void)51 fd_rd_dump_env_init(void)
52 {
53    static once_flag once = ONCE_FLAG_INIT;
54    call_once(&once, fd_rd_dump_env_init_once);
55 }
56 
57 static void
fd_rd_output_sanitize_name(char * name)58 fd_rd_output_sanitize_name(char *name)
59 {
60    /* The name string is null-terminated after being constructed via snprintf.
61     * Sanitize it by reducing to an underscore anything that's not a hyphen,
62     * underscore, dot or alphanumeric character.
63     */
64    for (char *s = name; *s; ++s) {
65       if (isalnum(*s) || *s == '-' || *s == '_' || *s == '.')
66          continue;
67       *s = '_';
68    }
69 }
70 
71 void
fd_rd_output_init(struct fd_rd_output * output,char * output_name)72 fd_rd_output_init(struct fd_rd_output *output, char* output_name)
73 {
74    snprintf(output->name, sizeof(output->name), "%s", output_name);
75    fd_rd_output_sanitize_name(output->name);
76 
77    output->combine = false;
78    output->file = NULL;
79    output->trigger_fd = -1;
80    output->trigger_count = 0;
81 
82    if (FD_RD_DUMP(COMBINE)) {
83       output->combine = true;
84 
85       char file_path[256];
86       snprintf(file_path, sizeof(file_path), "%s/%s_combined.rd",
87                fd_rd_output_base_path, output->name);
88       output->file = gzopen(file_path, "w");
89    }
90 
91    if (FD_RD_DUMP(TRIGGER)) {
92       char file_path[256];
93       snprintf(file_path, sizeof(file_path), "%s/%s_trigger",
94                fd_rd_output_base_path, output->name);
95       output->trigger_fd = open(file_path, O_RDWR | O_CREAT | O_TRUNC, 0600);
96    }
97 }
98 
99 void
fd_rd_output_fini(struct fd_rd_output * output)100 fd_rd_output_fini(struct fd_rd_output *output)
101 {
102    if (output->file != NULL) {
103       assert(output->combine);
104       gzclose(output->file);
105    }
106 
107    if (output->trigger_fd >= 0) {
108       close(output->trigger_fd);
109 
110       /* Remove the trigger file. The filename is reconstructed here
111        * instead of having to spend memory to store it in the struct.
112        */
113       char file_path[256];
114       snprintf(file_path, sizeof(file_path), "%s/%s_trigger",
115                fd_rd_output_base_path, output->name);
116       unlink(file_path);
117    }
118 }
119 
120 static void
fd_rd_output_update_trigger_count(struct fd_rd_output * output)121 fd_rd_output_update_trigger_count(struct fd_rd_output *output)
122 {
123    assert(FD_RD_DUMP(TRIGGER));
124 
125    /* Retrieve the trigger file size, only attempt to update the trigger
126     * value if anything was actually written to that file.
127     */
128    struct stat stat;
129    if (fstat(output->trigger_fd, &stat) != 0) {
130       mesa_loge("[fd_rd_output] failed to acccess the %s trigger file",
131                 output->name);
132       return;
133    }
134 
135    if (stat.st_size == 0)
136       return;
137 
138    char trigger_data[32];
139    int ret = read(output->trigger_fd, trigger_data, sizeof(trigger_data));
140    if (ret < 0) {
141       mesa_loge("[fd_rd_output] failed to read from the %s trigger file",
142                 output->name);
143       return;
144    }
145    int num_read = MIN2(ret, sizeof(trigger_data) - 1);
146 
147    /* After reading from it, the trigger file should be reset, which means
148     * moving the file offset to the start of the file as well as truncating
149     * it to zero bytes.
150     */
151    if (lseek(output->trigger_fd, 0, SEEK_SET) < 0) {
152       mesa_loge("[fd_rd_output] failed to reset the %s trigger file position",
153                 output->name);
154       return;
155    }
156 
157    if (ftruncate(output->trigger_fd, 0) < 0) {
158       mesa_loge("[fd_rd_output] failed to truncate the %s trigger file",
159                 output->name);
160       return;
161    }
162 
163    /* Try to decode the count value through strtol. -1 translates to UINT_MAX
164     * and keeps generating dumps until disabled. Any positive value will
165     * allow generating dumps for that many submits. Any other value will
166     * disable any further generation of RD dumps.
167     */
168    trigger_data[num_read] = '\0';
169    int32_t value = strtol(trigger_data, NULL, 0);
170 
171    if (value == -1) {
172       output->trigger_count = UINT_MAX;
173       mesa_logi("[fd_rd_output] %s trigger enabling RD dumps until disabled",
174                 output->name);
175    } else if (value > 0) {
176       output->trigger_count = (uint32_t) value;
177       mesa_logi("[fd_rd_output] %s trigger enabling RD dumps for next %u submissions",
178                 output->name, output->trigger_count);
179    } else {
180       output->trigger_count = 0;
181       mesa_logi("[fd_rd_output] %s trigger disabling RD dumps", output->name);
182    }
183 }
184 
185 bool
fd_rd_output_begin(struct fd_rd_output * output,uint32_t submit_idx)186 fd_rd_output_begin(struct fd_rd_output *output, uint32_t submit_idx)
187 {
188    assert(output->combine ^ (output->file == NULL));
189 
190    if (FD_RD_DUMP(TRIGGER)) {
191       fd_rd_output_update_trigger_count(output);
192 
193       if (output->trigger_count == 0)
194          return false;
195       /* UINT_MAX corresponds to generating dumps until disabled. */
196       if (output->trigger_count != UINT_MAX)
197           --output->trigger_count;
198    }
199 
200    if (output->combine)
201       return true;
202 
203    char file_path[256];
204    snprintf(file_path, sizeof(file_path), "%s/%s_%.5d.rd",
205             fd_rd_output_base_path, output->name, submit_idx);
206    output->file = gzopen(file_path, "w");
207    return true;
208 }
209 
210 static void
fd_rd_output_write(struct fd_rd_output * output,const void * buffer,int size)211 fd_rd_output_write(struct fd_rd_output *output, const void *buffer, int size)
212 {
213    const uint8_t *pos = (uint8_t *) buffer;
214    while (size > 0) {
215       int ret = gzwrite(output->file, pos, size);
216       if (ret < 0) {
217          mesa_loge("[fd_rd_output] failed to write to compressed output: %s",
218                    gzerror(output->file, NULL));
219          return;
220       }
221       pos += ret;
222       size -= ret;
223    }
224 }
225 
226 void
fd_rd_output_write_section(struct fd_rd_output * output,enum rd_sect_type type,const void * buffer,int size)227 fd_rd_output_write_section(struct fd_rd_output *output, enum rd_sect_type type,
228                            const void *buffer, int size)
229 {
230    fd_rd_output_write(output, &type, 4);
231    fd_rd_output_write(output, &size, 4);
232    fd_rd_output_write(output, buffer, size);
233 }
234 
235 void
fd_rd_output_end(struct fd_rd_output * output)236 fd_rd_output_end(struct fd_rd_output *output)
237 {
238    assert(output->file != NULL);
239 
240    /* When combining output, flush the gzip stream on each submit. This should
241     * store all the data before any problem during the submit itself occurs.
242     */
243    if (output->combine) {
244       gzflush(output->file, Z_FINISH);
245       return;
246    }
247 
248    gzclose(output->file);
249    output->file = NULL;
250 }
251