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