#include "lookup3.h"
#include "compress.h"
-#define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*16ULL)
-#define DEFAULT_FIELD_HASH_TABLE_SIZE (2047ULL*16ULL)
+#define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*sizeof(HashItem))
+#define DEFAULT_FIELD_HASH_TABLE_SIZE (333ULL*sizeof(HashItem))
#define DEFAULT_WINDOW_SIZE (8ULL*1024ULL*1024ULL)
* size */
#define DEFAULT_KEEP_FREE (1024ULL*1024ULL) /* 1 MB */
-static const char signature[] = { 'L', 'P', 'K', 'S', 'H', 'H', 'R', 'H' };
+/* n_data was the first entry we added after the initial file format design */
+#define HEADER_SIZE_MIN ALIGN64(offsetof(Header, n_data))
#define ALIGN64(x) (((x) + 7ULL) & ~7ULL)
+#define JOURNAL_HEADER_CONTAINS(h, field) \
+ (le64toh((h)->header_size) >= offsetof(Header, field) + sizeof((h)->field))
+
+static const char signature[] = { 'L', 'P', 'K', 'S', 'H', 'H', 'R', 'H' };
+
void journal_file_close(JournalFile *f) {
int t;
assert(f);
if (f->header) {
- if (f->writable)
+ /* Mark the file offline. Don't override the archived state if it already is set */
+ if (f->writable && f->header->state == STATE_ONLINE)
f->header->state = STATE_OFFLINE;
munmap(f->header, PAGE_ALIGN(sizeof(Header)));
if (template) {
h.seqnum_id = template->header->seqnum_id;
- h.seqnum = template->header->seqnum;
+ h.tail_seqnum = template->header->tail_seqnum;
} else
h.seqnum_id = h.file_id;
return -EPROTONOSUPPORT;
#endif
- if (f->header->header_size != htole64(ALIGN64(sizeof(*(f->header)))))
+ /* The first addition was n_data, so check that we are at least this large */
+ if (le64toh(f->header->header_size) < HEADER_SIZE_MIN)
return -EBADMSG;
if ((uint64_t) f->last_stat.st_size < (le64toh(f->header->header_size) + le64toh(f->header->arena_size)))
state = f->header->state;
- if (state == STATE_ONLINE)
- log_debug("Journal file %s is already online. Assuming unclean closing. Ignoring.", f->path);
- /* FIXME: immediately rotate */
- else if (state == STATE_ARCHIVED)
+ if (state == STATE_ONLINE) {
+ log_debug("Journal file %s is already online. Assuming unclean closing.", f->path);
+ return -EBUSY;
+ } else if (state == STATE_ARCHIVED)
return -ESHUTDOWN;
- else if (state != STATE_OFFLINE)
- log_debug("Journal file %s has unknown state %u. Ignoring.", f->path, state);
+ else if (state != STATE_OFFLINE) {
+ log_debug("Journal file %s has unknown state %u.", f->path, state);
+ return -EBUSY;
+ }
}
return 0;
assert(f);
- r = le64toh(f->header->seqnum) + 1;
+ r = le64toh(f->header->tail_seqnum) + 1;
if (seqnum) {
/* If an external seqnum counter was passed, we update
*seqnum = r;
}
- f->header->seqnum = htole64(r);
+ f->header->tail_seqnum = htole64(r);
- if (f->header->first_seqnum == 0)
- f->header->first_seqnum = htole64(r);
+ if (f->header->head_seqnum == 0)
+ f->header->head_seqnum = htole64(r);
return r;
}
assert(f);
- s = DEFAULT_DATA_HASH_TABLE_SIZE;
+ /* We estimate that we need 1 hash table entry per 768 of
+ journal file and we want to make sure we never get beyond
+ 75% fill level. Calculate the hash table size for the
+ maximum file size based on these metrics. */
+
+ s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem);
+ if (s < DEFAULT_DATA_HASH_TABLE_SIZE)
+ s = DEFAULT_DATA_HASH_TABLE_SIZE;
+
+ log_info("Reserving %llu entries in hash table.", (unsigned long long) (s / sizeof(HashItem)));
+
r = journal_file_append_object(f,
OBJECT_DATA_HASH_TABLE,
offsetof(Object, hash_table.items) + s,
f->data_hash_table[h].tail_hash_offset = htole64(offset);
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
+ f->header->n_data = htole64(le64toh(f->header->n_data) + 1);
+
return 0;
}
}
void journal_file_dump(JournalFile *f) {
- char a[33], b[33], c[33];
Object *o;
int r;
uint64_t p;
assert(f);
- printf("File Path: %s\n"
- "File ID: %s\n"
- "Machine ID: %s\n"
- "Boot ID: %s\n"
- "Arena size: %llu\n"
- "Objects: %lu\n"
- "Entries: %lu\n",
- f->path,
- sd_id128_to_string(f->header->file_id, a),
- sd_id128_to_string(f->header->machine_id, b),
- sd_id128_to_string(f->header->boot_id, c),
- (unsigned long long) le64toh(f->header->arena_size),
- (unsigned long) le64toh(f->header->n_objects),
- (unsigned long) le64toh(f->header->n_entries));
+ journal_file_print_header(f);
p = le64toh(f->header->header_size);
while (p != 0) {
log_error("File corrupt");
}
+void journal_file_print_header(JournalFile *f) {
+ char a[33], b[33], c[33];
+ char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX];
+
+ assert(f);
+
+ printf("File Path: %s\n"
+ "File ID: %s\n"
+ "Machine ID: %s\n"
+ "Boot ID: %s\n"
+ "Sequential Number ID: %s\n"
+ "State: %s\n"
+ "Compatible Flags:%s%s\n"
+ "Incompatible Flags:%s%s\n"
+ "Header size: %llu\n"
+ "Arena size: %llu\n"
+ "Data Hash Table Size: %llu\n"
+ "Field Hash Table Size: %llu\n"
+ "Objects: %llu\n"
+ "Entry Objects: %llu\n"
+ "Rotate Suggested: %s\n"
+ "Head Sequential Number: %llu\n"
+ "Tail Sequential Number: %llu\n"
+ "Head Realtime Timestamp: %s\n"
+ "Tail Realtime Timestamp: %s\n",
+ f->path,
+ sd_id128_to_string(f->header->file_id, a),
+ sd_id128_to_string(f->header->machine_id, b),
+ sd_id128_to_string(f->header->boot_id, c),
+ sd_id128_to_string(f->header->seqnum_id, c),
+ f->header->state == STATE_OFFLINE ? "offline" :
+ f->header->state == STATE_ONLINE ? "online" :
+ f->header->state == STATE_ARCHIVED ? "archived" : "unknown",
+ (f->header->compatible_flags & HEADER_COMPATIBLE_SIGNED) ? " SIGNED" : "",
+ (f->header->compatible_flags & ~HEADER_COMPATIBLE_SIGNED) ? " ???" : "",
+ (f->header->incompatible_flags & HEADER_INCOMPATIBLE_COMPRESSED) ? " COMPRESSED" : "",
+ (f->header->incompatible_flags & ~HEADER_INCOMPATIBLE_COMPRESSED) ? " ???" : "",
+ (unsigned long long) le64toh(f->header->header_size),
+ (unsigned long long) le64toh(f->header->arena_size),
+ (unsigned long long) le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
+ (unsigned long long) le64toh(f->header->field_hash_table_size) / sizeof(HashItem),
+ (unsigned long long) le64toh(f->header->n_objects),
+ (unsigned long long) le64toh(f->header->n_entries),
+ yes_no(journal_file_rotate_suggested(f)),
+ (unsigned long long) le64toh(f->header->head_seqnum),
+ (unsigned long long) le64toh(f->header->tail_seqnum),
+ format_timestamp(x, sizeof(x), le64toh(f->header->head_entry_realtime)),
+ format_timestamp(y, sizeof(y), le64toh(f->header->tail_entry_realtime)));
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
+ printf("Data Objects: %llu\n"
+ "Data Hash Table Fill: %.1f%%\n",
+ (unsigned long long) le64toh(f->header->n_data),
+ 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
+ printf("Field Objects: %llu\n"
+ "Field Hash Table Fill: %.1f%%\n",
+ (unsigned long long) le64toh(f->header->n_fields),
+ 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
+}
+
int journal_file_open(
const char *fname,
int flags,
mode_t mode,
+ JournalMetrics *metrics,
JournalFile *template,
JournalFile **ret) {
f->writable = (flags & O_ACCMODE) != O_RDONLY;
f->prot = prot_from_flags(flags);
- if (template) {
- f->metrics = template->metrics;
+ if (template)
f->compress = template->compress;
- }
f->path = strdup(fname);
if (!f->path) {
}
}
- if (f->last_stat.st_size < (off_t) sizeof(Header)) {
+ if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
r = -EIO;
goto fail;
}
}
if (f->writable) {
+ if (metrics) {
+ journal_default_metrics(metrics, f->fd);
+ f->metrics = *metrics;
+ } else if (template)
+ f->metrics = template->metrics;
+
r = journal_file_refresh_header(f);
if (r < 0)
goto fail;
sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1);
snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1,
"-%016llx-%016llx.journal",
- (unsigned long long) le64toh((*f)->header->seqnum),
+ (unsigned long long) le64toh((*f)->header->tail_seqnum),
(unsigned long long) le64toh((*f)->header->tail_entry_realtime));
r = rename(old_file->path, p);
old_file->header->state = STATE_ARCHIVED;
- r = journal_file_open(old_file->path, old_file->flags, old_file->mode, old_file, &new_file);
+ r = journal_file_open(old_file->path, old_file->flags, old_file->mode, NULL, old_file, &new_file);
journal_file_close(old_file);
*f = new_file;
const char *fname,
int flags,
mode_t mode,
+ JournalMetrics *metrics,
JournalFile *template,
JournalFile **ret) {
size_t l;
char *p;
- r = journal_file_open(fname, flags, mode, template, ret);
+ r = journal_file_open(fname, flags, mode, metrics, template, ret);
if (r != -EBADMSG && /* corrupted */
r != -ENODATA && /* truncated */
r != -EHOSTDOWN && /* other machine */
- r != -EPROTONOSUPPORT) /* incompatible feature */
+ r != -EPROTONOSUPPORT && /* incompatible feature */
+ r != -EBUSY && /* unclean shutdown */
+ r != -ESHUTDOWN /* already archived */)
return r;
if ((flags & O_ACCMODE) == O_RDONLY)
if (r < 0)
return -errno;
- log_warning("File %s corrupted, renaming and replacing.", fname);
+ log_warning("File %s corrupted or uncleanly shut down, renaming and replacing.", fname);
- return journal_file_open(fname, flags, mode, template, ret);
+ return journal_file_open(fname, flags, mode, metrics, template, ret);
}
struct vacuum_info {
return 1;
}
+
+bool journal_file_rotate_suggested(JournalFile *f) {
+ assert(f);
+
+ /* If we gained new header fields we gained new features,
+ * hence suggest a rotation */
+ if (le64toh(f->header->header_size) < sizeof(Header)) {
+ log_debug("%s uses an outdated header, suggesting rotation.", f->path);
+ return true;
+ }
+
+ /* Let's check if the hash tables grew over a certain fill
+ * level (75%, borrowing this value from Java's hash table
+ * implementation), and if so suggest a rotation. To calculate
+ * the fill level we need the n_data field, which only exists
+ * in newer versions. */
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
+ if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) {
+ log_debug("Data hash table of %s has a fill level at %.1f (%llu of %llu items, %llu file size, %llu bytes per hash table item), suggesting rotation.",
+ f->path,
+ 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))),
+ (unsigned long long) le64toh(f->header->n_data),
+ (unsigned long long) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)),
+ (unsigned long long) (f->last_stat.st_size),
+ (unsigned long long) (f->last_stat.st_size / le64toh(f->header->n_data)));
+ return true;
+ }
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
+ if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) {
+ log_debug("Field hash table of %s has a fill level at %.1f (%llu of %llu items), suggesting rotation.",
+ f->path,
+ 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))),
+ (unsigned long long) le64toh(f->header->n_fields),
+ (unsigned long long) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)));
+ return true;
+ }
+
+ return false;
+}