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