blob: 231b6f33c9c435442ea129f498cb92e68c4e3c44 [file] [log] [blame]
/*
* Copyright © 2024 Igalia S.L.
* SPDX-License-Identifier: MIT
*/
#include "freedreno_rd_output.h"
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "c11/threads.h"
#include "util/detect_os.h"
#include "util/log.h"
#include "util/u_atomic.h"
#include "util/u_debug.h"
#if DETECT_OS_ANDROID
static const char *fd_rd_output_base_path = "/data/local/tmp";
#else
static const char *fd_rd_output_base_path = "/tmp";
#endif
static const struct debug_control fd_rd_dump_options[] = {
{ "enable", FD_RD_DUMP_ENABLE },
{ "combine", FD_RD_DUMP_COMBINE },
{ "full", FD_RD_DUMP_FULL },
{ "trigger", FD_RD_DUMP_TRIGGER },
{ NULL, 0 }
};
struct fd_rd_dump_env fd_rd_dump_env;
static void
fd_rd_dump_env_init_once(void)
{
fd_rd_dump_env.flags = parse_debug_string(os_get_option("FD_RD_DUMP"),
fd_rd_dump_options);
/* If any of the more-detailed FD_RD_DUMP flags is enabled, the general
* FD_RD_DUMP_ENABLE flag should also implicitly be set.
*/
if (fd_rd_dump_env.flags & ~FD_RD_DUMP_ENABLE)
fd_rd_dump_env.flags |= FD_RD_DUMP_ENABLE;
}
void
fd_rd_dump_env_init(void)
{
static once_flag once = ONCE_FLAG_INIT;
call_once(&once, fd_rd_dump_env_init_once);
}
static void
fd_rd_output_sanitize_name(char *name)
{
/* The name string is null-terminated after being constructed via asprintf.
* Sanitize it by reducing to an underscore anything that's not a hyphen,
* underscore, dot or alphanumeric character.
*/
for (char *s = name; *s; ++s) {
if (isalnum(*s) || *s == '-' || *s == '_' || *s == '.')
continue;
*s = '_';
}
}
void
fd_rd_output_init(struct fd_rd_output *output, const char* output_name)
{
const char *test_name = os_get_option("FD_RD_DUMP_TESTNAME");
ASSERTED int name_len;
if (test_name)
name_len = asprintf(&output->name, "%s_%s", test_name, output_name);
else
name_len = asprintf(&output->name, "%s", output_name);
assert(name_len != -1);
fd_rd_output_sanitize_name(output->name);
output->combine = false;
output->file = NULL;
output->trigger_fd = -1;
output->trigger_count = 0;
if (FD_RD_DUMP(COMBINE)) {
output->combine = true;
char file_path[PATH_MAX];
snprintf(file_path, sizeof(file_path), "%s/%s_combined.rd.gz",
fd_rd_output_base_path, output->name);
output->file = gzopen(file_path, "w");
}
if (FD_RD_DUMP(TRIGGER)) {
char file_path[PATH_MAX];
snprintf(file_path, sizeof(file_path), "%s/%s_trigger",
fd_rd_output_base_path, output->name);
output->trigger_fd = open(file_path, O_RDWR | O_CREAT | O_TRUNC, 0600);
}
}
void
fd_rd_output_fini(struct fd_rd_output *output)
{
if (output->name != NULL)
free(output->name);
if (output->file != NULL) {
assert(output->combine);
gzclose(output->file);
}
if (output->trigger_fd >= 0) {
close(output->trigger_fd);
/* Remove the trigger file. The filename is reconstructed here
* instead of having to spend memory to store it in the struct.
*/
char file_path[PATH_MAX];
snprintf(file_path, sizeof(file_path), "%s/%s_trigger",
fd_rd_output_base_path, output->name);
unlink(file_path);
}
}
static void
fd_rd_output_update_trigger_count(struct fd_rd_output *output)
{
assert(FD_RD_DUMP(TRIGGER));
/* Retrieve the trigger file size, only attempt to update the trigger
* value if anything was actually written to that file.
*/
struct stat stat;
if (fstat(output->trigger_fd, &stat) != 0) {
mesa_loge("[fd_rd_output] failed to acccess the %s trigger file",
output->name);
return;
}
if (stat.st_size == 0)
return;
char trigger_data[32];
int ret = read(output->trigger_fd, trigger_data, sizeof(trigger_data));
if (ret < 0) {
mesa_loge("[fd_rd_output] failed to read from the %s trigger file",
output->name);
return;
}
int num_read = MIN2(ret, sizeof(trigger_data) - 1);
/* After reading from it, the trigger file should be reset, which means
* moving the file offset to the start of the file as well as truncating
* it to zero bytes.
*/
if (lseek(output->trigger_fd, 0, SEEK_SET) < 0) {
mesa_loge("[fd_rd_output] failed to reset the %s trigger file position",
output->name);
return;
}
if (ftruncate(output->trigger_fd, 0) < 0) {
mesa_loge("[fd_rd_output] failed to truncate the %s trigger file",
output->name);
return;
}
/* Try to decode the count value through strtol. -1 translates to UINT_MAX
* and keeps generating dumps until disabled. Any positive value will
* allow generating dumps for that many submits. Any other value will
* disable any further generation of RD dumps.
*/
trigger_data[num_read] = '\0';
int32_t value = strtol(trigger_data, NULL, 0);
if (value == -1) {
output->trigger_count = UINT_MAX;
mesa_logi("[fd_rd_output] %s trigger enabling RD dumps until disabled",
output->name);
} else if (value > 0) {
output->trigger_count = (uint32_t) value;
mesa_logi("[fd_rd_output] %s trigger enabling RD dumps for next %u submissions",
output->name, output->trigger_count);
} else {
output->trigger_count = 0;
mesa_logi("[fd_rd_output] %s trigger disabling RD dumps", output->name);
}
}
bool
fd_rd_output_begin(struct fd_rd_output *output, uint32_t submit_idx)
{
assert(output->combine ^ (output->file == NULL));
if (FD_RD_DUMP(TRIGGER)) {
fd_rd_output_update_trigger_count(output);
if (output->trigger_count == 0)
return false;
/* UINT_MAX corresponds to generating dumps until disabled. */
if (output->trigger_count != UINT_MAX)
--output->trigger_count;
}
if (output->combine)
return true;
char file_path[PATH_MAX];
snprintf(file_path, sizeof(file_path), "%s/%s_%.5d.rd",
fd_rd_output_base_path, output->name, submit_idx);
output->file = gzopen(file_path, "w");
return true;
}
static void
fd_rd_output_write(struct fd_rd_output *output, const void *buffer, int size)
{
const uint8_t *pos = (uint8_t *) buffer;
while (size > 0) {
int ret = gzwrite(output->file, pos, size);
if (ret < 0) {
mesa_loge("[fd_rd_output] failed to write to compressed output: %s",
gzerror(output->file, NULL));
return;
}
pos += ret;
size -= ret;
}
}
void
fd_rd_output_write_section(struct fd_rd_output *output, enum rd_sect_type type,
const void *buffer, int size)
{
fd_rd_output_write(output, &type, 4);
fd_rd_output_write(output, &size, 4);
fd_rd_output_write(output, buffer, size);
}
void
fd_rd_output_end(struct fd_rd_output *output)
{
assert(output->file != NULL);
/* When combining output, flush the gzip stream on each submit. This should
* store all the data before any problem during the submit itself occurs.
*/
if (output->combine) {
gzflush(output->file, Z_FINISH);
return;
}
gzclose(output->file);
output->file = NULL;
}