mesa/src/util/mesa_cache_db.c
Dmitry Osipenko fd9f7b748e util/mesa-db: Introduce multipart mesa-db cache
Whenever a single file mesa-db cache hits max size limit, a half of cache
is evicted and the cache file is defragmented. The downside of this eviction
strategy is that it causes high disk IO usage during eviction if mesa-db
cache file size is large.

In order to mitigate this downside, we will split mesa-db into multiple
part such that only one part will be evicted at a time. Each part will be
an individual single file mesa-db cache, like a DB shard. The new multipart
mesa-db cache will merge the parts into a single virtual cache.

This patch introduces two new environment variables:

1. MESA_DISK_CACHE_DATABASE_NUM_PARTS:
Controls number of mesa-db cache file parts. By default 50 parts will be
created. The old pre-multipart mesa-db cache files will be auto-removed
if they exist, i.e. Mesa will switch to the new DB version automatically.

2. MESA_DISK_CACHE_DATABASE_EVICTION_SCORE_2X_PERIOD:
Controls the eviction score doubling time period. The evicted DB part
selection is based on cache entries size weighted by 'last_access_time' of
the entries. By default the cache eviction score is doubled for each month
of cache entry age, i.e. for two equally sized entries where one entry is
older by one month than the other, the older entry will have x2 eviction
score than the other entry. Database part with a highest total eviction
score is selected for eviction.

This patch brings x40 performance improvement of cache eviction time using
multipart cache vs a single file cache due to a smaller eviction portions
and more optimized eviction algorithm.

Acked-by: Timothy Arceri <tarceri@itsqueeze.com>
Signed-off-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/20256>
2023-02-01 02:47:30 +00:00

999 lines
25 KiB
C

/*
* Copyright © 2022 Collabora, Ltd.
*
* Based on Fossilize DB:
* Copyright © 2020 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#include "detect_os.h"
#if DETECT_OS_WINDOWS == 0
#include <fcntl.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <unistd.h>
#include "crc32.h"
#include "disk_cache.h"
#include "hash_table.h"
#include "mesa-sha1.h"
#include "mesa_cache_db.h"
#include "os_time.h"
#include "ralloc.h"
#include "u_debug.h"
#include "u_qsort.h"
#define MESA_CACHE_DB_VERSION 1
#define MESA_CACHE_DB_MAGIC "MESA_DB"
struct PACKED mesa_db_file_header {
char magic[8];
uint32_t version;
uint64_t uuid;
};
struct PACKED mesa_cache_db_file_entry {
cache_key key;
uint32_t crc;
uint32_t size;
};
struct PACKED mesa_index_db_file_entry {
uint64_t hash;
uint32_t size;
uint64_t last_access_time;
uint64_t cache_db_file_offset;
};
struct mesa_index_db_hash_entry {
uint64_t cache_db_file_offset;
uint64_t index_db_file_offset;
uint64_t last_access_time;
uint32_t size;
bool evicted;
};
static inline bool mesa_db_seek_end(FILE *file)
{
return !fseek(file, 0, SEEK_END);
}
static inline bool mesa_db_seek(FILE *file, long pos)
{
return !fseek(file, pos, SEEK_SET);
}
static inline bool mesa_db_seek_cur(FILE *file, long pos)
{
return !fseek(file, pos, SEEK_CUR);
}
static inline bool mesa_db_read_data(FILE *file, void *data, size_t size)
{
return fread(data, 1, size, file) == size;
}
#define mesa_db_read(file, var) mesa_db_read_data(file, var, sizeof(*(var)))
static inline bool mesa_db_write_data(FILE *file, const void *data, size_t size)
{
return fwrite(data, 1, size, file) == size;
}
#define mesa_db_write(file, var) mesa_db_write_data(file, var, sizeof(*(var)))
static inline bool mesa_db_truncate(FILE *file, long pos)
{
return !ftruncate(fileno(file), pos);
}
static bool
mesa_db_lock(struct mesa_cache_db *db)
{
simple_mtx_lock(&db->flock_mtx);
if (flock(fileno(db->cache.file), LOCK_EX) == -1)
goto unlock_mtx;
if (flock(fileno(db->index.file), LOCK_EX) == -1)
goto unlock_cache;
return true;
unlock_cache:
flock(fileno(db->cache.file), LOCK_UN);
unlock_mtx:
simple_mtx_unlock(&db->flock_mtx);
return false;
}
static void
mesa_db_unlock(struct mesa_cache_db *db)
{
flock(fileno(db->index.file), LOCK_UN);
flock(fileno(db->cache.file), LOCK_UN);
simple_mtx_unlock(&db->flock_mtx);
}
static uint64_t to_mesa_cache_db_hash(const uint8_t *cache_key_160bit)
{
uint64_t hash = 0;
for (unsigned i = 0; i < 8; i++)
hash |= ((uint64_t)cache_key_160bit[i]) << i * 8;
return hash;
}
static uint64_t
mesa_db_generate_uuid(void)
{
/* This simple UUID implementation is sufficient for our needs
* because UUID is updated rarely. It's nice to make UUID meaningful
* and incremental by adding the timestamp to it, which also prevents
* the potential collisions. */
return ((os_time_get() / 1000000) << 32) | rand();
}
static bool
mesa_db_read_header(FILE *file, struct mesa_db_file_header *header)
{
rewind(file);
fflush(file);
if (!mesa_db_read(file, header))
return false;
if (strncmp(header->magic, MESA_CACHE_DB_MAGIC, sizeof(header->magic)) ||
header->version != MESA_CACHE_DB_VERSION || !header->uuid)
return false;
return true;
}
static bool
mesa_db_load_header(struct mesa_cache_db_file *db_file)
{
struct mesa_db_file_header header;
if (!mesa_db_read_header(db_file->file, &header))
return false;
db_file->uuid = header.uuid;
return true;
}
static bool mesa_db_uuid_changed(struct mesa_cache_db *db)
{
struct mesa_db_file_header cache_header;
struct mesa_db_file_header index_header;
if (!mesa_db_read_header(db->cache.file, &cache_header) ||
!mesa_db_read_header(db->index.file, &index_header) ||
cache_header.uuid != index_header.uuid ||
cache_header.uuid != db->uuid)
return true;
return false;
}
static bool
mesa_db_write_header(struct mesa_cache_db_file *db_file,
uint64_t uuid, bool reset)
{
struct mesa_db_file_header header;
rewind(db_file->file);
sprintf(header.magic, "MESA_DB");
header.version = MESA_CACHE_DB_VERSION;
header.uuid = uuid;
if (!mesa_db_write(db_file->file, &header))
return false;
if (reset) {
if (!mesa_db_truncate(db_file->file, ftell(db_file->file)))
return false;
}
fflush(db_file->file);
return true;
}
/* Wipe out all database cache files.
*
* Whenever we get an unmanageable error on reading or writing to the
* database file, wipe out the whole database and start over. All the
* cached entries will be lost, but the broken cache will be auto-repaired
* reliably. Normally cache shall never get corrupted and losing cache
* entries is acceptable, hence it's more practical to repair DB using
* the simplest method.
*/
static bool
mesa_db_zap(struct mesa_cache_db *db)
{
/* Disable cache to prevent the recurring faults */
db->alive = false;
/* Zap corrupted database files to start over from a clean slate */
if (!mesa_db_truncate(db->cache.file, 0) ||
!mesa_db_truncate(db->index.file, 0))
return false;
fflush(db->cache.file);
fflush(db->index.file);
return true;
}
static bool
mesa_db_index_entry_valid(struct mesa_index_db_file_entry *entry)
{
return entry->size && entry->hash &&
(int64_t)entry->cache_db_file_offset >= sizeof(struct mesa_db_file_header);
}
static bool
mesa_db_cache_entry_valid(struct mesa_cache_db_file_entry *entry)
{
return entry->size && entry->crc;
}
static bool
mesa_db_update_index(struct mesa_cache_db *db)
{
struct mesa_index_db_hash_entry *hash_entry;
struct mesa_index_db_file_entry index_entry;
size_t file_length;
if (!mesa_db_seek_end(db->index.file))
return false;
file_length = ftell(db->index.file);
if (!mesa_db_seek(db->index.file, db->index.offset))
return false;
while (db->index.offset < file_length) {
if (!mesa_db_read(db->index.file, &index_entry))
break;
/* Check whether the index entry looks valid or we have a corrupted DB */
if (!mesa_db_index_entry_valid(&index_entry))
break;
hash_entry = ralloc(db->mem_ctx, struct mesa_index_db_hash_entry);
if (!hash_entry)
break;
hash_entry->cache_db_file_offset = index_entry.cache_db_file_offset;
hash_entry->index_db_file_offset = db->index.offset;
hash_entry->last_access_time = index_entry.last_access_time;
hash_entry->size = index_entry.size;
_mesa_hash_table_u64_insert(db->index_db, index_entry.hash, hash_entry);
db->index.offset += sizeof(index_entry);
}
if (!mesa_db_seek(db->index.file, db->index.offset))
return false;
return db->index.offset == file_length;
}
static void
mesa_db_hash_table_reset(struct mesa_cache_db *db)
{
_mesa_hash_table_u64_clear(db->index_db);
ralloc_free(db->mem_ctx);
db->mem_ctx = ralloc_context(NULL);
}
static bool
mesa_db_recreate_files(struct mesa_cache_db *db)
{
db->uuid = mesa_db_generate_uuid();
if (!mesa_db_write_header(&db->cache, db->uuid, true) ||
!mesa_db_write_header(&db->index, db->uuid, true))
return false;
return true;
}
static bool
mesa_db_load(struct mesa_cache_db *db, bool reload)
{
/* reloading must be done under the held lock */
if (!reload) {
if (!mesa_db_lock(db))
return false;
}
/* If file headers are invalid, then zap database files and start over */
if (!mesa_db_load_header(&db->cache) ||
!mesa_db_load_header(&db->index) ||
db->cache.uuid != db->index.uuid) {
/* This is unexpected to happen on reload, bail out */
if (reload)
goto fail;
if (!mesa_db_recreate_files(db))
goto fail;
} else {
db->uuid = db->cache.uuid;
}
db->index.offset = ftell(db->index.file);
if (reload)
mesa_db_hash_table_reset(db);
if (!mesa_db_update_index(db))
goto fail;
if (!reload)
mesa_db_unlock(db);
db->alive = true;
return true;
fail:
if (!reload)
mesa_db_unlock(db);
return false;
}
static bool
mesa_db_reload(struct mesa_cache_db *db)
{
fflush(db->cache.file);
fflush(db->index.file);
return mesa_db_load(db, true);
}
static void
touch_file(const char* path)
{
close(open(path, O_CREAT | O_CLOEXEC, 0644));
}
static bool
mesa_db_open_file(struct mesa_cache_db_file *db_file,
const char *cache_path,
const char *filename)
{
if (asprintf(&db_file->path, "%s/%s", cache_path, filename) == -1)
return false;
/* The fopen("r+b") mode doesn't auto-create new file, hence we need to
* explicitly create the file first.
*/
touch_file(db_file->path);
db_file->file = fopen(db_file->path, "r+b");
if (!db_file->file) {
free(db_file->path);
return false;
}
return true;
}
static void
mesa_db_close_file(struct mesa_cache_db_file *db_file)
{
fclose(db_file->file);
free(db_file->path);
}
static bool
mesa_db_remove_file(struct mesa_cache_db_file *db_file,
const char *cache_path,
const char *filename)
{
if (asprintf(&db_file->path, "%s/%s", cache_path, filename) == -1)
return false;
unlink(db_file->path);
return true;
}
static int
entry_sort_lru(const void *_a, const void *_b, void *arg)
{
const struct mesa_index_db_hash_entry *a = *((const struct mesa_index_db_hash_entry **)_a);
const struct mesa_index_db_hash_entry *b = *((const struct mesa_index_db_hash_entry **)_b);
/* In practice it's unlikely that we will get two entries with the
* same timestamp, but technically it's possible to happen if OS
* timer's resolution is low. */
if (a->last_access_time == b->last_access_time)
return 0;
return a->last_access_time > b->last_access_time ? 1 : -1;
}
static int
entry_sort_offset(const void *_a, const void *_b, void *arg)
{
const struct mesa_index_db_hash_entry *a = *((const struct mesa_index_db_hash_entry **)_a);
const struct mesa_index_db_hash_entry *b = *((const struct mesa_index_db_hash_entry **)_b);
struct mesa_cache_db *db = arg;
/* Two entries will never have the identical offset, otherwise DB is
* corrupted. */
if (a->cache_db_file_offset == b->cache_db_file_offset)
mesa_db_zap(db);
return a->cache_db_file_offset > b->cache_db_file_offset ? 1 : -1;
}
static uint32_t blob_file_size(uint32_t blob_size)
{
return sizeof(struct mesa_cache_db_file_entry) + blob_size;
}
static bool
mesa_db_compact(struct mesa_cache_db *db, int64_t blob_size,
struct mesa_index_db_hash_entry *remove_entry)
{
uint32_t num_entries, buffer_size = sizeof(struct mesa_index_db_file_entry);
struct mesa_db_file_header cache_header, index_header;
FILE *compacted_cache = NULL, *compacted_index = NULL;
struct mesa_index_db_file_entry index_entry;
struct mesa_index_db_hash_entry **entries;
bool success = false, compact = false;
void *buffer = NULL;
unsigned int i = 0;
/* reload index to sync the last access times */
if (!remove_entry && !mesa_db_reload(db))
return false;
num_entries = _mesa_hash_table_num_entries(db->index_db->table);
entries = calloc(num_entries, sizeof(*entries));
if (!entries)
return false;
compacted_cache = fopen(db->cache.path, "r+b");
compacted_index = fopen(db->index.path, "r+b");
if (!compacted_cache || !compacted_index)
goto cleanup;
/* The database file has been replaced if UUID changed. We opened
* some other cache, stop processing this database. */
if (!mesa_db_read_header(compacted_cache, &cache_header) ||
!mesa_db_read_header(compacted_index, &index_header) ||
cache_header.uuid != db->uuid ||
index_header.uuid != db->uuid)
goto cleanup;
hash_table_foreach(db->index_db->table, entry) {
entries[i] = entry->data;
entries[i]->evicted = (entries[i] == remove_entry);
buffer_size = MAX2(buffer_size, blob_file_size(entries[i]->size));
i++;
}
util_qsort_r(entries, num_entries, sizeof(*entries),
entry_sort_lru, db);
for (i = 0; blob_size > 0 && i < num_entries; i++) {
blob_size -= blob_file_size(entries[i]->size);
entries[i]->evicted = true;
}
util_qsort_r(entries, num_entries, sizeof(*entries),
entry_sort_offset, db);
/* entry_sort_offset() may zap the database */
if (!db->alive)
goto cleanup;
buffer = malloc(buffer_size);
if (!buffer)
goto cleanup;
/* Mark cache file invalid by writing zero-UUID header. If compaction will
* fail, then the file will remain to be invalid since we can't repair it. */
if (!mesa_db_write_header(&db->cache, 0, false) ||
!mesa_db_write_header(&db->index, 0, false))
goto cleanup;
/* Sync the file pointers */
if (!mesa_db_seek(compacted_cache, ftell(db->cache.file)) ||
!mesa_db_seek(compacted_index, ftell(db->index.file)))
goto cleanup;
/* Do the compaction */
for (i = 0; i < num_entries; i++) {
blob_size = blob_file_size(entries[i]->size);
/* Sanity-check the cache-read offset */
if (ftell(db->cache.file) != entries[i]->cache_db_file_offset)
goto cleanup;
if (entries[i]->evicted) {
/* Jump over the evicted entry */
if (!mesa_db_seek_cur(db->cache.file, blob_size) ||
!mesa_db_seek_cur(db->index.file, sizeof(index_entry)))
goto cleanup;
compact = true;
continue;
}
if (compact) {
/* Compact the cache file */
if (!mesa_db_read_data(db->cache.file, buffer, blob_size) ||
!mesa_db_cache_entry_valid(buffer) ||
!mesa_db_write_data(compacted_cache, buffer, blob_size))
goto cleanup;
/* Compact the index file */
if (!mesa_db_read(db->index.file, &index_entry) ||
!mesa_db_index_entry_valid(&index_entry) ||
index_entry.cache_db_file_offset != entries[i]->cache_db_file_offset ||
index_entry.size != entries[i]->size)
goto cleanup;
index_entry.cache_db_file_offset = ftell(compacted_cache) - blob_size;
if (!mesa_db_write(compacted_index, &index_entry))
goto cleanup;
} else {
/* Sanity-check the cache-write offset */
if (ftell(compacted_cache) != entries[i]->cache_db_file_offset)
goto cleanup;
/* Jump over the unchanged entry */
if (!mesa_db_seek_cur(db->index.file, sizeof(index_entry)) ||
!mesa_db_seek_cur(compacted_index, sizeof(index_entry)) ||
!mesa_db_seek_cur(db->cache.file, blob_size) ||
!mesa_db_seek_cur(compacted_cache, blob_size))
goto cleanup;
}
}
fflush(compacted_cache);
fflush(compacted_index);
/* Cut off the the freed space left after compaction */
if (!mesa_db_truncate(db->cache.file, ftell(compacted_cache)) ||
!mesa_db_truncate(db->index.file, ftell(compacted_index)))
goto cleanup;
/* Set the new UUID to let all cache readers know that the cache was changed */
db->uuid = mesa_db_generate_uuid();
if (!mesa_db_write_header(&db->cache, db->uuid, false) ||
!mesa_db_write_header(&db->index, db->uuid, false))
goto cleanup;
success = true;
cleanup:
free(buffer);
if (compacted_index)
fclose(compacted_index);
if (compacted_cache)
fclose(compacted_cache);
free(entries);
/* reload compacted index */
if (success && !mesa_db_reload(db))
success = false;
return success;
}
bool
mesa_cache_db_open(struct mesa_cache_db *db, const char *cache_path)
{
if (!mesa_db_open_file(&db->cache, cache_path, "mesa_cache.db"))
return false;
if (!mesa_db_open_file(&db->index, cache_path, "mesa_cache.idx"))
goto close_cache;
db->mem_ctx = ralloc_context(NULL);
if (!db->mem_ctx)
goto close_index;
simple_mtx_init(&db->flock_mtx, mtx_plain);
db->index_db = _mesa_hash_table_u64_create(NULL);
if (!db->index_db)
goto destroy_mtx;
if (!mesa_db_load(db, false))
goto destroy_hash;
return true;
destroy_hash:
_mesa_hash_table_u64_destroy(db->index_db);
destroy_mtx:
simple_mtx_destroy(&db->flock_mtx);
ralloc_free(db->mem_ctx);
close_index:
mesa_db_close_file(&db->index);
close_cache:
mesa_db_close_file(&db->cache);
return false;
}
bool
mesa_db_wipe_path(const char *cache_path)
{
struct mesa_cache_db db = {0};
bool success = true;
if (!mesa_db_remove_file(&db.cache, cache_path, "mesa_cache.db") ||
!mesa_db_remove_file(&db.index, cache_path, "mesa_cache.idx"))
success = false;
free(db.cache.path);
free(db.index.path);
return success;
}
void
mesa_cache_db_close(struct mesa_cache_db *db)
{
_mesa_hash_table_u64_destroy(db->index_db);
simple_mtx_destroy(&db->flock_mtx);
ralloc_free(db->mem_ctx);
mesa_db_close_file(&db->index);
mesa_db_close_file(&db->cache);
}
void
mesa_cache_db_set_size_limit(struct mesa_cache_db *db,
uint64_t max_cache_size)
{
db->max_cache_size = max_cache_size;
}
unsigned int
mesa_cache_db_file_entry_size(void)
{
return sizeof(struct mesa_cache_db_file_entry);
}
void *
mesa_cache_db_read_entry(struct mesa_cache_db *db,
const uint8_t *cache_key_160bit,
size_t *size)
{
uint64_t hash = to_mesa_cache_db_hash(cache_key_160bit);
struct mesa_cache_db_file_entry cache_entry;
struct mesa_index_db_file_entry index_entry;
struct mesa_index_db_hash_entry *hash_entry;
void *data = NULL;
if (!mesa_db_lock(db))
return NULL;
if (!db->alive)
goto fail;
if (mesa_db_uuid_changed(db) && !mesa_db_reload(db))
goto fail_fatal;
if (!mesa_db_update_index(db))
goto fail_fatal;
hash_entry = _mesa_hash_table_u64_search(db->index_db, hash);
if (!hash_entry)
goto fail;
if (!mesa_db_seek(db->cache.file, hash_entry->cache_db_file_offset) ||
!mesa_db_read(db->cache.file, &cache_entry) ||
!mesa_db_cache_entry_valid(&cache_entry))
goto fail_fatal;
if (memcmp(cache_entry.key, cache_key_160bit, sizeof(cache_entry.key)))
goto fail;
data = malloc(cache_entry.size);
if (!data)
goto fail;
if (!mesa_db_read_data(db->cache.file, data, cache_entry.size) ||
util_hash_crc32(data, cache_entry.size) != cache_entry.crc)
goto fail_fatal;
if (!mesa_db_seek(db->index.file, hash_entry->index_db_file_offset) ||
!mesa_db_read(db->index.file, &index_entry) ||
!mesa_db_index_entry_valid(&index_entry) ||
index_entry.cache_db_file_offset != hash_entry->cache_db_file_offset ||
index_entry.size != hash_entry->size)
goto fail_fatal;
index_entry.last_access_time = os_time_get_nano();
hash_entry->last_access_time = index_entry.last_access_time;
if (!mesa_db_seek(db->index.file, hash_entry->index_db_file_offset) ||
!mesa_db_write(db->index.file, &index_entry))
goto fail_fatal;
fflush(db->index.file);
mesa_db_unlock(db);
*size = cache_entry.size;
return data;
fail_fatal:
mesa_db_zap(db);
fail:
free(data);
mesa_db_unlock(db);
return NULL;
}
static bool
mesa_cache_db_has_space_locked(struct mesa_cache_db *db, size_t blob_size)
{
return ftell(db->cache.file) + blob_file_size(blob_size) -
sizeof(struct mesa_db_file_header) <= db->max_cache_size;
}
static size_t
mesa_cache_db_eviction_size(struct mesa_cache_db *db)
{
return db->max_cache_size / 2 - sizeof(struct mesa_db_file_header);
}
bool
mesa_cache_db_entry_write(struct mesa_cache_db *db,
const uint8_t *cache_key_160bit,
const void *blob, size_t blob_size)
{
uint64_t hash = to_mesa_cache_db_hash(cache_key_160bit);
struct mesa_index_db_hash_entry *hash_entry = NULL;
struct mesa_cache_db_file_entry cache_entry;
struct mesa_index_db_file_entry index_entry;
if (!mesa_db_lock(db))
return false;
if (!db->alive)
goto fail;
if (mesa_db_uuid_changed(db) && !mesa_db_reload(db))
goto fail_fatal;
if (!mesa_db_seek_end(db->cache.file))
goto fail_fatal;
if (!mesa_cache_db_has_space_locked(db, blob_size)) {
if (!mesa_db_compact(db, MAX2(blob_size, mesa_cache_db_eviction_size(db)),
NULL))
goto fail_fatal;
} else {
if (!mesa_db_update_index(db))
goto fail_fatal;
}
hash_entry = _mesa_hash_table_u64_search(db->index_db, hash);
if (hash_entry) {
hash_entry = NULL;
goto fail;
}
if (!mesa_db_seek_end(db->cache.file) ||
!mesa_db_seek_end(db->index.file))
goto fail_fatal;
memcpy(cache_entry.key, cache_key_160bit, sizeof(cache_entry.key));
cache_entry.crc = util_hash_crc32(blob, blob_size);
cache_entry.size = blob_size;
index_entry.hash = hash;
index_entry.size = blob_size;
index_entry.last_access_time = os_time_get_nano();
index_entry.cache_db_file_offset = ftell(db->cache.file);
hash_entry = ralloc(db->mem_ctx, struct mesa_index_db_hash_entry);
if (!hash_entry)
goto fail;
hash_entry->cache_db_file_offset = index_entry.cache_db_file_offset;
hash_entry->index_db_file_offset = ftell(db->index.file);
hash_entry->last_access_time = index_entry.last_access_time;
hash_entry->size = index_entry.size;
if (!mesa_db_write(db->cache.file, &cache_entry) ||
!mesa_db_write_data(db->cache.file, blob, blob_size) ||
!mesa_db_write(db->index.file, &index_entry))
goto fail_fatal;
fflush(db->cache.file);
fflush(db->index.file);
db->index.offset = ftell(db->index.file);
_mesa_hash_table_u64_insert(db->index_db, hash, hash_entry);
mesa_db_unlock(db);
return true;
fail_fatal:
mesa_db_zap(db);
fail:
mesa_db_unlock(db);
if (hash_entry)
ralloc_free(hash_entry);
return false;
}
bool
mesa_cache_db_entry_remove(struct mesa_cache_db *db,
const uint8_t *cache_key_160bit)
{
uint64_t hash = to_mesa_cache_db_hash(cache_key_160bit);
struct mesa_cache_db_file_entry cache_entry;
struct mesa_index_db_hash_entry *hash_entry;
if (!mesa_db_lock(db))
return NULL;
if (!db->alive)
goto fail;
if (mesa_db_uuid_changed(db) && !mesa_db_reload(db))
goto fail_fatal;
if (!mesa_db_update_index(db))
goto fail_fatal;
hash_entry = _mesa_hash_table_u64_search(db->index_db, hash);
if (!hash_entry)
goto fail;
if (!mesa_db_seek(db->cache.file, hash_entry->cache_db_file_offset) ||
!mesa_db_read(db->cache.file, &cache_entry) ||
!mesa_db_cache_entry_valid(&cache_entry))
goto fail_fatal;
if (memcmp(cache_entry.key, cache_key_160bit, sizeof(cache_entry.key)))
goto fail;
if (!mesa_db_compact(db, 0, hash_entry))
goto fail_fatal;
mesa_db_unlock(db);
return true;
fail_fatal:
mesa_db_zap(db);
fail:
mesa_db_unlock(db);
return false;
}
bool
mesa_cache_db_has_space(struct mesa_cache_db *db, size_t blob_size)
{
bool has_space;
if (!mesa_db_lock(db))
return false;
if (!mesa_db_seek_end(db->cache.file))
goto fail_fatal;
has_space = mesa_cache_db_has_space_locked(db, blob_size);
mesa_db_unlock(db);
return has_space;
fail_fatal:
mesa_db_zap(db);
mesa_db_unlock(db);
return false;
}
static uint64_t
mesa_cache_db_eviction_2x_score_period(void)
{
const uint64_t nsec_per_sec = 1000000000ull;
static uint64_t period = 0;
if (period)
return period;
period = debug_get_num_option("MESA_DISK_CACHE_DATABASE_EVICTION_SCORE_2X_PERIOD",
30 * 24 * 60 * 60) * nsec_per_sec;
return period;
}
double
mesa_cache_db_eviction_score(struct mesa_cache_db *db)
{
int64_t eviction_size = mesa_cache_db_eviction_size(db);
struct mesa_index_db_hash_entry **entries;
unsigned num_entries, i = 0;
double eviction_score = 0;
if (!mesa_db_lock(db))
return 0;
if (!db->alive)
goto fail;
if (!mesa_db_reload(db))
goto fail_fatal;
num_entries = _mesa_hash_table_num_entries(db->index_db->table);
entries = calloc(num_entries, sizeof(*entries));
if (!entries)
goto fail;
hash_table_foreach(db->index_db->table, entry)
entries[i++] = entry->data;
util_qsort_r(entries, num_entries, sizeof(*entries),
entry_sort_lru, db);
for (i = 0; eviction_size > 0 && i < num_entries; i++) {
uint64_t entry_age = os_time_get_nano() - entries[i]->last_access_time;
unsigned entry_size = blob_file_size(entries[i]->size);
/* Eviction score is a sum of weighted cache entry sizes,
* where weight doubles for each month of entry's age.
*/
uint64_t period = mesa_cache_db_eviction_2x_score_period();
double entry_scale = 1 + (double)entry_age / period;
double entry_score = entry_size * entry_scale;
eviction_score += entry_score;
eviction_size -= entry_size;
}
free(entries);
mesa_db_unlock(db);
return eviction_score;
fail_fatal:
mesa_db_zap(db);
fail:
mesa_db_unlock(db);
return 0;
}
#endif /* DETECT_OS_WINDOWS */