Viewing: ldlm_cache_policy.c

// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright (c) 2010, 2017, Intel Corporation.
 */

/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * Author: Peter Braam <braam@clusterfs.com>
 * Author: Phil Schwan <phil@clusterfs.com>
 *
 * Author: Keguang Xu <squalfof@gmail.com>
 */

 /*
  * This file implements client lock cache policies for LDLM.
  * Currently supported policies:
  * - LRU (Least Recently Used)
  * - LFRU (Least Frequently and Recently Used)
  */

 #define DEBUG_SUBSYSTEM S_LDLM

 #include <lustre_swab.h>
 #include <obd_class.h>

 #include "ldlm_internal.h"

/* ==================== Basic LRU Implementation ==================== */
static void ldlm_lru_add_lock(struct ldlm_namespace *ns,
			      struct ldlm_lock *lock)
{
	ns->ns_nr_unused++;
	list_add_tail(&lock->l_lru, &ns->ns_unused_normal_list);
	lock->l_lru_type = LRU_NORMAL_LIST;
}

static int ldlm_lru_remove_lock(struct ldlm_namespace *ns,
				struct ldlm_lock *lock)
{
	int rc = 0;

	if (!list_empty(&lock->l_lru)) {
		LASSERT(lock->l_resource->lr_type != LDLM_FLOCK);
		if (ns->ns_last_pos == &lock->l_lru)
			ns->ns_last_pos = lock->l_lru.prev;
		list_del_init(&lock->l_lru);
		LASSERT(ns->ns_nr_unused > 0);
		ns->ns_nr_unused--;
		rc = 1;
	}
	return rc;
}

struct ldlm_lock_cache_ops ldlm_lru_cache_ops = {
	.llco_add_lock = ldlm_lru_add_lock,
	.llco_remove_lock = ldlm_lru_remove_lock,
};

/* ==================== LFRU Implementation ==================== */

/*
 * The privileged threshold is updated under two conditions:
 * - A lock with a strictly higher score is encountered.
 * - The access count reaches the window size, using the maximum
 *   frequency (max_freq) within the window as a basis.
 *
 * About `max_freq`:
 * - It is used to gradually decrement the `priv_score` to adapting changing
 *   workflows. A large `priv_score` should not persist for too long, as this
 *   could prevent new valuable locks from being promoted.
 * - To pick the appropriate score, one reasonable choice is to collect the
 *   recent (within the time window) lock access counts, pick the largest
 *   value, take that as the `priv_score's next value.
 * Why largest? Too small threshold would incorrectly lead to less valuable
 * locks to be promoted.
 */
static void ldlm_check_and_adjust_lfru_thresh(struct ldlm_namespace *ns,
					      __u8 score)
{
	if (score > ns->ns_lfru_priv_score_threshold ||
	    score == LDLM_LFRU_PRIV_THRESH_CAP) {
		ns->ns_lfru_priv_score_threshold = score;
		ns->ns_lfru_max_freq = LDLM_LFRU_MIN_PRIV_THRESH;
		ns->ns_lfru_access_window_cnt = 0;
		return;
	}

	if (score > ns->ns_lfru_max_freq)
		ns->ns_lfru_max_freq = score;
	ns->ns_lfru_access_window_cnt++;
	/* Update priv thres based on max_freq */
	if (ns->ns_lfru_access_window_cnt == ns->ns_lfru_check_window_size) {
		ns->ns_lfru_priv_score_threshold = ns->ns_lfru_max_freq;
		ns->ns_lfru_max_freq = LDLM_LFRU_MIN_PRIV_THRESH;
		ns->ns_lfru_access_window_cnt = 0;
	} else if (ns->ns_lfru_access_window_cnt ==
		   ns->ns_lfru_check_window_size / 2) {
		/*
		 * Decay scores by a factor of 0.75 (arbitrary chosen) after
		 * a half-window to reduce the influence of older access
		 * patterns when calculating the new threshold.
		 */
		ns->ns_lfru_max_freq = ns->ns_lfru_max_freq * 3 / 4;
	}
}

static void ldlm_lfru_add_lock(struct ldlm_namespace *ns,
			       struct ldlm_lock *lock)
{
	bool was_priv = lock->l_lru_type == LRU_PRIV;
	bool new_priv = false;

	/* save the lock in privilege or normal list */
	ns->ns_nr_unused++;
	lock->l_lru_score = min_t(int, lock->l_lru_score + 1,
				  LDLM_LFRU_PRIV_THRESH_CAP);
	new_priv = !was_priv &&
		   (lock->l_lru_score > ns->ns_lfru_priv_score_threshold ||
		    lock->l_lru_score == LDLM_LFRU_PRIV_THRESH_CAP);
	/*
	 * Once a lock is promoted to privilege list, it should reside there
	 * until it's evicted by the LRU policy.
	 */
	if (was_priv || new_priv) {
		list_add_tail(&lock->l_lru, &ns->ns_unused_priv_list);
		ns->ns_nr_priv++;
		lock->l_lru_type = LRU_PRIV;
	} else {
		list_add_tail(&lock->l_lru, &ns->ns_unused_normal_list);
		lock->l_lru_type = LRU_NORMAL_LIST;
	}
	if (!was_priv)
		ldlm_check_and_adjust_lfru_thresh(ns, lock->l_lru_score);
}

static int ldlm_lfru_remove_lock(struct ldlm_namespace *ns,
				 struct ldlm_lock *lock)
{
	int rc = 0;

	rc = ldlm_lru_remove_lock(ns, lock);
	if (rc && lock->l_lru_type == LRU_PRIV) {
		LASSERT(ns->ns_nr_priv > 0);
		ns->ns_nr_priv--;
	}
	return rc;
}

static void ldlm_lfru_demote_lock(struct ldlm_namespace *ns,
				  struct ldlm_lock *lock)
{
	if ((lock->l_flags & LDLM_FL_NS_SRV)) {
		LASSERT(list_empty(&lock->l_lru));
		return;
	}
	/* remove from priv list first */
	LASSERT(lock->l_lru_type == LRU_PRIV);
	ldlm_lfru_remove_lock(ns, lock);
	/* add to normal list */
	ns->ns_nr_unused++;
	list_add_tail(&lock->l_lru, &ns->ns_unused_normal_list);
	lock->l_lru_type = LRU_NORMAL_LIST;
	lock->l_lru_score >>= 2;
}

/**
 * ldlm_lfru_priv_too_many: determine if priv lock count is too large
 *
 * TOO MANY criteria:
 * - priv count exceeds 1/8 of the default LRU size, and
 * - priv count exceeds `priv_ratio_limit` (defaults to 1/3)
 *   of the current cache size
 */
static inline bool ldlm_lfru_priv_too_many(struct ldlm_namespace *ns)
{
	return (ns->ns_nr_priv >= (LDLM_DEFAULT_LRU_SIZE >> 3)) &&
	       (ns->ns_nr_priv >=
		(u64)ns->ns_nr_unused * ns->ns_lfru_priv_ratio_limit_256 >> 8);
}

static int ldlm_lfru_try_batch_demote_locks(struct ldlm_namespace *ns,
					    int batch_size)
{
	struct ldlm_lock *lock, *temp;
	int target_evicts = batch_size;
	int evicts = 0;

	if (unlikely(list_empty(&ns->ns_unused_priv_list)))
		return 0;

	/* priv list has exceeded capacity threshold or not */
	if (target_evicts == LDLM_LFRU_PRIV_PER_ROUND_LIMIT &&
	    !ldlm_lfru_priv_too_many(ns))
		return 0;

	list_for_each_entry_safe(lock, temp, &ns->ns_unused_priv_list, l_lru) {
		if (evicts < target_evicts) {
			ldlm_lfru_demote_lock(ns, lock);
			evicts++;
		} else {
			break;
		}
	}

	return evicts;
}

struct ldlm_lock_cache_ops ldlm_lfru_cache_ops = {
	.llco_add_lock = ldlm_lfru_add_lock,
	.llco_remove_lock = ldlm_lfru_remove_lock,
	.llco_demote_lock = ldlm_lfru_demote_lock,
	.llco_try_batch_demote_locks = ldlm_lfru_try_batch_demote_locks,
};