Viewing: ofd_access_batch.c
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2020, DataDirect Networks Storage.
*
* This file is part of Lustre, http://www.lustre.org/
*
* Author: John L. Hammond <jhammond@whamcloud.com>
*
* lustre/utils/ofd_access_batch.c
*
* Access log entry batching for ofd_access_log_reader.
*/
#include <stdlib.h>
#include <stdbool.h>
#include <stddef.h>
#include <assert.h>
#include <malloc.h>
#include <unistd.h>
#include <pthread.h>
#include <linux/lustre/lustre_access_log.h>
#include <linux/lustre/lustre_fid.h>
#include <linux/lustre/lustre_idl.h>
#include <libcfs/util/hash.h>
#include <libcfs/util/list.h>
#include <lustre/lustreapi.h>
#include "lstddef.h"
#include "ofd_access_batch.h"
static time_t when_last_printed;
struct fid_hash_node {
struct list_head fhn_node;
struct lu_fid fhn_fid;
};
static inline bool fid_eq(const struct lu_fid *f1, const struct lu_fid *f2)
{
return f1->f_seq == f2->f_seq && f1->f_oid == f2->f_oid &&
f1->f_ver == f2->f_ver;
}
static void fhn_init(struct fid_hash_node *fhn, const struct lu_fid *fid)
{
INIT_LIST_HEAD(&fhn->fhn_node);
fhn->fhn_fid = *fid;
}
static bool fhn_is_hashed(struct fid_hash_node *fhn)
{
return !list_empty(&fhn->fhn_node);
}
static void fhn_del_init(struct fid_hash_node *fhn)
{
if (fhn_is_hashed(fhn))
list_del_init(&fhn->fhn_node);
}
static inline void fhn_replace_init(struct fid_hash_node *old_fhn,
struct fid_hash_node *new_fhn)
{
list_add(&new_fhn->fhn_node, &old_fhn->fhn_node);
list_del_init(&old_fhn->fhn_node);
}
static void fid_hash_add(struct list_head *head, unsigned int shift,
struct fid_hash_node *fhn)
{
assert(!fhn_is_hashed(fhn));
list_add(&fhn->fhn_node, &head[llapi_fid_hash(&fhn->fhn_fid, shift)]);
}
static struct fid_hash_node *
fid_hash_insert(struct list_head *head, unsigned int shift, struct fid_hash_node *new_fhn)
{
struct list_head *list;
struct fid_hash_node *old_fhn, *next;
list = &head[llapi_fid_hash(&new_fhn->fhn_fid, shift)];
list_for_each_entry_safe(old_fhn, next, list, fhn_node) {
assert(fhn_is_hashed(old_fhn));
if (fid_eq(&old_fhn->fhn_fid, &new_fhn->fhn_fid))
return old_fhn;
}
list_add(&new_fhn->fhn_node, list);
return new_fhn;
}
static int fid_hash_init(struct list_head **phead, unsigned int *pshift,
unsigned int shift)
{
struct list_head *new_head;
unsigned int i;
new_head = malloc(sizeof(*new_head) << shift);
if (new_head == NULL)
return -1;
for (i = 0; i < (1 << shift); i++)
INIT_LIST_HEAD(&new_head[i]);
*phead = new_head;
*pshift = shift;
return 0;
}
static int fid_hash_resize(struct list_head **phead, unsigned int *pshift,
unsigned int new_shift)
{
struct list_head *new_head;
unsigned int i;
int rc;
if (*pshift == new_shift)
return 0;
rc = fid_hash_init(&new_head, &new_shift, new_shift);
if (rc < 0)
return rc;
for (i = 0; i < (1 << *pshift); i++) {
struct list_head *list = &(*phead)[i];
struct fid_hash_node *fhn, *next;
list_for_each_entry_safe(fhn, next, list, fhn_node) {
fhn_del_init(fhn);
fid_hash_add(new_head, new_shift, fhn);
}
}
free(*phead);
*phead = new_head;
*pshift = new_shift;
return 0;
}
enum alr_rw {
ALR_READ = 0,
ALR_WRITE = 1,
ALR_RW_MAX
};
/* Entry in the batching hash. */
struct alr_entry {
struct fid_hash_node alre_fid_hash_node;
time_t alre_time[ALR_RW_MAX]; /* Not strictly needed. */
__u64 alre_begin[ALR_RW_MAX];
__u64 alre_end[ALR_RW_MAX];
__u64 alre_size[ALR_RW_MAX];
__u64 alre_segment_count[ALR_RW_MAX];
__u64 alre_count[ALR_RW_MAX];
char alre_obd_name[];
};
enum {
ALR_BATCH_HASH_SHIFT_DEFAULT = 10,
ALR_BATCH_HASH_SHIFT_MAX = 30,
};
struct alr_batch {
struct list_head *alrb_hash;
unsigned int alrb_hash_shift;
unsigned int alrb_count;
};
static void alre_del_init(struct alr_entry *alre)
{
fhn_del_init(&alre->alre_fid_hash_node);
}
static void alre_update(struct alr_entry *alre, time_t time, __u64 begin,
__u64 end, __u32 size, __u32 segment_count, __u32 flags)
{
enum alr_rw d = (flags & OFD_ACCESS_READ) ? ALR_READ : ALR_WRITE;
alre->alre_time[d] = max_t(time_t, alre->alre_time[d], time);
alre->alre_begin[d] = min_t(__u64, alre->alre_begin[d], begin);
alre->alre_end[d] = max_t(__u64, alre->alre_end[d], end);
alre->alre_size[d] += size;
alre->alre_segment_count[d] += segment_count;
alre->alre_count[d] += 1;
}
int alr_batch_add(struct alr_batch *alrb, const char *obd_name,
const struct lu_fid *pfid, time_t time, __u64 begin, __u64 end,
__u32 size, __u32 segment_count, __u32 flags)
{
struct fid_hash_node fhn, *p;
struct alr_entry *alre;
int rc;
if (alrb == NULL)
return 0;
static_assert(sizeof(time_t) == sizeof(__u64), "time_t size mismatch");
fhn_init(&fhn, pfid);
/* Find old or insert sentinel (fhn). Replace sentinel if returned. */
p = fid_hash_insert(alrb->alrb_hash, alrb->alrb_hash_shift, &fhn);
if (p == &fhn) {
size_t alre_size = sizeof(*alre) + strlen(obd_name) + 1;
alre = calloc(1, alre_size);
if (alre == NULL) {
rc = -1;
goto out;
}
fhn_init(&alre->alre_fid_hash_node, pfid);
strcpy(alre->alre_obd_name, obd_name);
fhn_replace_init(&fhn, &alre->alre_fid_hash_node);
alrb->alrb_count++;
} else {
alre = container_of(p, struct alr_entry, alre_fid_hash_node);
}
alre_update(alre, time, begin, end, size, segment_count, flags);
rc = 0;
out:
fhn_del_init(&fhn);
return rc;
}
static int sort_compare(const void *a1, const void *a2)
{
int l = *(const int*)a1;
int r = *(const int *)a2;
if (l > r) return -1;
if (l < r) return 1;
return 0;
}
static void alre_printf(FILE *f, struct alr_entry *alre, enum alr_rw d)
{
fprintf(f, "o=%s f="DFID" t=%lld b=%llu e=%llu s=%llu g=%llu n=%llu d=%c\n",
alre->alre_obd_name,
PFID(&alre->alre_fid_hash_node.fhn_fid),
(long long)alre->alre_time[d],
(unsigned long long)alre->alre_begin[d],
(unsigned long long)alre->alre_end[d],
(unsigned long long)alre->alre_size[d],
(unsigned long long)alre->alre_segment_count[d],
(unsigned long long)alre->alre_count[d],
(d == ALR_READ) ? 'r' : 'w');
when_last_printed = time(NULL);
}
struct alr_thread_arg {
struct list_head list;
int fraction;
FILE *file;
pthread_mutex_t *file_mutex;
};
static void alre_print_keepalive(struct alr_thread_arg *aa)
{
/* Do not print keepalive if disabled */
if (keepalive_interval == 0)
return;
/* If nothing printed during keepalive_interval - send keepalive */
if (time(NULL) < (when_last_printed + keepalive_interval))
return;
fprintf(aa->file, "# keepalive\n");
when_last_printed = time(NULL);
DEBUG("send keepalive\n");
}
static void alre_print_keepalive_locked(struct alr_thread_arg *aa)
{
int rc = pthread_mutex_lock(aa->file_mutex);
if (rc != 0)
FATAL("cannot lock batch file: %s\n", strerror(rc));
alre_print_keepalive(aa);
rc = pthread_mutex_unlock(aa->file_mutex);
if (rc != 0)
FATAL("cannot unlock batch file: %s\n", strerror(rc));
}
/* Fraction < 100 */
static void *alr_sort_and_print_thread(void *arg)
{
struct alr_entry *alre, *next;
struct alr_thread_arg *aa = arg;
struct list_head *tmp = &aa->list;
int *sa = NULL;
int rc, i, nr = 0;
enum alr_rw d;
unsigned long cut;
list_for_each_entry(alre, tmp, alre_fid_hash_node.fhn_node) {
if (alre->alre_count[ALR_READ] > 0)
nr++;
if (alre->alre_count[ALR_WRITE] > 0)
nr++;
}
if (nr == 0)
goto out;
sa = calloc(nr, sizeof(*sa));
if (!sa)
FATAL("cannot allocate memory for sorting\n");
i = 0;
list_for_each_entry(alre, tmp, alre_fid_hash_node.fhn_node) {
if (alre->alre_count[ALR_READ] > 0)
sa[i++] = alre->alre_count[ALR_READ];
if (alre->alre_count[ALR_WRITE] > 0)
sa[i++] = alre->alre_count[ALR_WRITE];
}
qsort(sa, nr, sizeof(*sa), sort_compare);
i = nr * aa->fraction / 100;
cut = sa[i];
if (cut < 1)
cut = 1;
free(sa);
/* Prevent jumbled output from multiple concurrent sort and
* print threads. */
rc = pthread_mutex_lock(aa->file_mutex);
if (rc != 0)
FATAL("cannot lock batch file: %s\n", strerror(rc));
/* there might be lots of items at @cut, but we want to limit total
* output. so the first loop dumps all items > @cut and the second
* loop dumps items=@cut so that total number (@i) is not exceeeded.
* XXX: possible optimization - move items=@cut to another list, so
* that 2nd pass takes < O(n) */
list_for_each_entry(alre, tmp, alre_fid_hash_node.fhn_node) {
for (d = 0; d < ALR_RW_MAX; d++) {
if (alre->alre_count[d] <= cut)
continue;
alre_printf(aa->file, alre, d);
i--;
}
}
list_for_each_entry(alre, tmp, alre_fid_hash_node.fhn_node) {
for (d = 0; d < ALR_RW_MAX && i > 0; d++) {
if (alre->alre_count[d] != cut)
continue;
alre_printf(aa->file, alre, d);
i--;
}
}
rc = pthread_mutex_unlock(aa->file_mutex);
if (rc != 0)
FATAL("cannot unlock batch file: %s\n", strerror(rc));
out:
/* send keepalive */
alre_print_keepalive_locked(aa);
fflush(aa->file);
list_for_each_entry_safe(alre, next, tmp, alre_fid_hash_node.fhn_node) {
alre_del_init(alre);
free(alre);
}
free(aa);
return NULL;
}
/* Fraction == 100 */
static void *alr_print_thread_fraction_100(void *arg)
{
struct alr_entry *alre, *next;
struct alr_thread_arg *aa = arg;
int rc;
/* Prevent jumbled output from multiple concurrent sort and
* print threads. */
rc = pthread_mutex_lock(aa->file_mutex);
if (rc != 0)
FATAL("cannot lock batch file: %s\n", strerror(rc));
list_for_each_entry(alre, &aa->list, alre_fid_hash_node.fhn_node) {
enum alr_rw d;
for (d = 0; d < ALR_RW_MAX; d++) {
if (alre->alre_count[d] != 0)
alre_printf(aa->file, alre, d);
}
}
/* send keepalive */
alre_print_keepalive(aa);
rc = pthread_mutex_unlock(aa->file_mutex);
if (rc != 0)
FATAL("cannot unlock batch file: %s\n", strerror(rc));
fflush(aa->file);
list_for_each_entry_safe(alre, next, &aa->list, alre_fid_hash_node.fhn_node) {
alre_del_init(alre);
free(alre);
}
free(aa);
return NULL;
}
/* Print, clear, and resize the batch. */
int alr_batch_print(struct alr_batch *alrb, FILE *file,
pthread_mutex_t *file_mutex, int fraction)
{
unsigned int new_hash_shift;
pthread_attr_t attr, *pattr = NULL;
struct alr_thread_arg *aa = NULL;
pthread_t pid;
int i, rc;
if (alrb == NULL)
return 0;
aa = calloc(1, sizeof(*aa));
if (aa == NULL)
return -ENOMEM;
/* move all collected items to the temp list */
INIT_LIST_HEAD(&aa->list);
for (i = 0; i < (1 << alrb->alrb_hash_shift); i++) {
if (list_empty(&alrb->alrb_hash[i]))
continue;
list_splice(&alrb->alrb_hash[i], &aa->list);
INIT_LIST_HEAD(&alrb->alrb_hash[i]);
}
aa->file = file;
aa->file_mutex = file_mutex;
aa->fraction = fraction;
rc = pthread_attr_init(&attr);
if (rc != 0)
goto out;
pattr = &attr;
rc = pthread_attr_setdetachstate(pattr, PTHREAD_CREATE_DETACHED);
if (rc != 0)
goto out;
/* as sorting may take time and we don't want to lose access
* records we better do sorting and printing in a different thread */
if (fraction >= 100) /* Print all 100% records */
rc = pthread_create(&pid, pattr, &alr_print_thread_fraction_100, aa);
else
rc = pthread_create(&pid, pattr, &alr_sort_and_print_thread, aa);
if (rc != 0)
goto out;
aa = NULL; /* Sort and print thread owns it now. */
out:
/* Resize hash based on previous count. */
new_hash_shift = alrb->alrb_hash_shift;
while (new_hash_shift < ALR_BATCH_HASH_SHIFT_MAX &&
(1 << new_hash_shift) < alrb->alrb_count)
new_hash_shift++;
fid_hash_resize(&alrb->alrb_hash, &alrb->alrb_hash_shift,
new_hash_shift);
alrb->alrb_count = 0;
if (pattr != NULL)
pthread_attr_destroy(pattr);
if (aa != NULL) {
struct alr_entry *alre, *next;
list_for_each_entry_safe(alre, next, &aa->list,
alre_fid_hash_node.fhn_node) {
alre_del_init(alre);
free(alre);
}
}
free(aa);
if (rc > 0)
rc = -rc; /* Fixup pthread return conventions. */
return rc;
}
struct alr_batch *alr_batch_create(unsigned int shift)
{
struct alr_batch *alrb;
int rc;
if (shift == -1U)
shift = ALR_BATCH_HASH_SHIFT_DEFAULT;
alrb = calloc(1, sizeof(*alrb));
if (alrb == NULL)
return NULL;
rc = fid_hash_init(&alrb->alrb_hash, &alrb->alrb_hash_shift, shift);
if (rc < 0) {
free(alrb);
return NULL;
}
return alrb;
}
void alr_batch_destroy(struct alr_batch *alrb)
{
unsigned int i;
if (alrb == NULL)
return;
for (i = 0; i < (1 << alrb->alrb_hash_shift); i++) {
struct list_head *list = &alrb->alrb_hash[i];
struct alr_entry *alre, *next;
list_for_each_entry_safe(alre, next, list, alre_fid_hash_node.fhn_node) {
alre_del_init(alre);
free(alre);
}
}
free(alrb->alrb_hash);
free(alrb);
}