From 3ecc099589be6d25f7a55b4be81f88426871db15 Mon Sep 17 00:00:00 2001 From: Mark Fasheh Date: Thu, 16 Nov 2017 15:19:47 -0600 Subject: [PATCH] scoutfs-utils: add command to print locking state This command takes a device and dumps all dlmglue locks and their state to the console. It also computes some average lock wait times. We provide a couple of options: --lvbs=[yes|no] turns on or off printing of lvb data (default is off) --oneline provides a more concise per-lock printout. Signed-off-by: Mark Fasheh --- utils/src/dlmglue.h | 101 +++++++++++ utils/src/locks.c | 420 ++++++++++++++++++++++++++++++++++++++++++++ utils/src/print.c | 2 +- utils/src/util.h | 6 + 4 files changed, 528 insertions(+), 1 deletion(-) create mode 100644 utils/src/dlmglue.h create mode 100644 utils/src/locks.c diff --git a/utils/src/dlmglue.h b/utils/src/dlmglue.h new file mode 100644 index 00000000..985d65f9 --- /dev/null +++ b/utils/src/dlmglue.h @@ -0,0 +1,101 @@ +/* -*- mode: c; c-basic-offset: 8; -*- + * vim: noexpandtab sw=8 ts=8 sts=0: + * + * dlmglue.h + * + * dlmglue constants for userspace decoding + * + * Copyright (C) 2002, 2004 Oracle. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + + +#ifndef DLMGLUE_H +#define DLMGLUE_H + +/* Max length of lockid name */ +#define OCFS2_LOCK_ID_MAX_LEN 32 + +#define DLM_LVB_LEN 64 + +enum ocfs2_ast_action { + OCFS2_AST_INVALID = 0, + OCFS2_AST_ATTACH, + OCFS2_AST_CONVERT, + OCFS2_AST_DOWNCONVERT, +}; + +/* actions for an unlockast function to take. */ +enum ocfs2_unlock_action { + OCFS2_UNLOCK_INVALID = 0, + OCFS2_UNLOCK_CANCEL_CONVERT, + OCFS2_UNLOCK_DROP_LOCK, +}; + +/* ocfs2_lock_res->l_flags flags. */ +#define OCFS2_LOCK_ATTACHED (0x00000001) /* we have initialized + * the lvb */ +#define OCFS2_LOCK_BUSY (0x00000002) /* we are currently in + * dlm_lock */ +#define OCFS2_LOCK_BLOCKED (0x00000004) /* blocked waiting to + * downconvert*/ +#define OCFS2_LOCK_LOCAL (0x00000008) /* newly created inode */ +#define OCFS2_LOCK_NEEDS_REFRESH (0x00000010) +#define OCFS2_LOCK_REFRESHING (0x00000020) +#define OCFS2_LOCK_INITIALIZED (0x00000040) /* track initialization + * for shutdown paths */ +#define OCFS2_LOCK_FREEING (0x00000080) /* help dlmglue track + * when to skip queueing + * a lock because it's + * about to be + * dropped. */ +#define OCFS2_LOCK_QUEUED (0x00000100) /* queued for downconvert */ +#define OCFS2_LOCK_NOCACHE (0x00000200) /* don't use a holder count */ +#define OCFS2_LOCK_PENDING (0x00000400) /* This lockres is pending a + call to dlm_lock. Only + exists with BUSY set. */ +#define OCFS2_LOCK_UPCONVERT_FINISHING (0x00000800) /* blocks the dc thread + * from downconverting + * before the upconvert + * has completed */ + +#define OCFS2_LOCK_NONBLOCK_FINISHED (0x00001000) /* NONBLOCK cluster + * lock has already + * returned, do not block + * dc thread from + * downconverting */ + +/* The cluster stack fields */ +#define OCFS2_STACK_LABEL_LEN 4 +#define OCFS2_CLUSTER_NAME_LEN 16 + +/* + * Return value from ->downconvert_worker functions. + * + * These control the precise actions of ocfs2_unblock_lock() + * and ocfs2_process_blocked_lock() + * + */ +enum ocfs2_unblock_action { + UNBLOCK_CONTINUE = 0, /* Continue downconvert */ + UNBLOCK_CONTINUE_POST = 1, /* Continue downconvert, fire + * ->post_unlock callback */ + UNBLOCK_STOP_POST = 2, /* Do not downconvert, fire + * ->post_unlock() callback. */ +}; + +#endif /* DLMGLUE_H */ diff --git a/utils/src/locks.c b/utils/src/locks.c new file mode 100644 index 00000000..b1f0113b --- /dev/null +++ b/utils/src/locks.c @@ -0,0 +1,420 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sparse.h" +#include "cmd.h" +#include "util.h" +#include "format.h" +#include "list.h" +#include "dlmglue.h" + +static int print_lvbs = 0; +static int oneline = 0; + +static char *level_str(int level) +{ + char *s; + + switch (level) { + case DLM_LOCK_IV: + s = "IV"; + break; + case DLM_LOCK_NL: + s = "NL"; + break; + case DLM_LOCK_CR: + s = "CR"; + break; + case DLM_LOCK_CW: + s = "CW"; + break; + case DLM_LOCK_PR: + s = "PR"; + break; + case DLM_LOCK_PW: + s = "PW"; + break; + case DLM_LOCK_EX: + s = "EX"; + break; + default: + s = "Unknown"; + } + + return s; +} + +static void print_flags(unsigned long flags, FILE *out) +{ + if (flags & OCFS2_LOCK_INITIALIZED ) + fprintf(out, " Initialized"); + + if (flags & OCFS2_LOCK_ATTACHED) + fprintf(out, " Attached"); + + if (flags & OCFS2_LOCK_BUSY) + fprintf(out, " Busy"); + + if (flags & OCFS2_LOCK_BLOCKED) + fprintf(out, " Blocked"); + + if (flags & OCFS2_LOCK_LOCAL) + fprintf(out, " Local"); + + if (flags & OCFS2_LOCK_NEEDS_REFRESH) + fprintf(out, " Needs Refresh"); + + if (flags & OCFS2_LOCK_REFRESHING) + fprintf(out, " Refreshing"); + + if (flags & OCFS2_LOCK_FREEING) + fprintf(out, " Freeing"); + + if (flags & OCFS2_LOCK_QUEUED) + fprintf(out, " Queued"); +} + +static char *action_str(unsigned int action) +{ + char *s; + + switch (action) { + case OCFS2_AST_INVALID: + s = "None"; + break; + case OCFS2_AST_ATTACH: + s = "Attach"; + break; + case OCFS2_AST_CONVERT: + s = "Convert"; + break; + case OCFS2_AST_DOWNCONVERT: + s = "Downconvert"; + break; + default: + s = "Unknown"; + } + + return s; +} + +static char *unlock_action_str(unsigned int unlock_action) +{ + char *s; + switch (unlock_action) { + case OCFS2_UNLOCK_INVALID: + s = "None"; + break; + case OCFS2_UNLOCK_CANCEL_CONVERT: + s = "Cancel Convert"; + break; + case OCFS2_UNLOCK_DROP_LOCK: + s = "Drop Lock"; + break; + default: + s = "Unknown"; + } + + return s; +} + +static void dump_raw_lvb(const char *lvb, FILE *out) +{ + int i; + + fprintf(out, "Raw LVB:\t"); + + for(i = 0; i < DLM_LVB_LEN; i++) { + fprintf(out, "%02hhx ", lvb[i]); + if (!((i+1) % 16) && i != (DLM_LVB_LEN-1)) + fprintf(out, "\n\t\t"); + } + fprintf(out, "\n"); +} + +static int end_line(FILE *f) +{ + int ret; + + do { + ret = fgetc(f); + if (ret == EOF) + return 1; + } while (ret != '\n'); + + return 0; +} + +/* the printing/scanning code here was modified from ocfs2-tools */ +static int print_fields(FILE *file, FILE *out) +{ + char id[OCFS2_LOCK_ID_MAX_LEN + 1]; + char lvb[DLM_LVB_LEN]; + int ret, i, level, requested, blocking; + unsigned long flags; + unsigned int action, unlock_action, cw, ro, ex, dummy; + const char *format; + unsigned long long num_prmode, num_exmode, num_cwmode; + unsigned int num_prmode_failed, num_exmode_failed, num_cwmode_failed; + unsigned long long total_prmode, total_exmode, total_cwmode; + unsigned long long avg_prmode = 0, avg_exmode = 0, avg_cwmode = 0; + unsigned int max_prmode, max_exmode, max_cwmode, num_refresh; + + ret = fscanf(file, "%s\t" + "%d\t" + "0x%lx\t" + "0x%x\t" + "0x%x\t" + "%u\t" + "%u\t" + "%d\t" + "%d\t", + id, + &level, + &flags, + &action, + &unlock_action, + &ro, + &ex, + &requested, + &blocking); + if (ret != 9) { + ret = -EINVAL; + goto out; + } + + format = "0x%x\t"; + for (i = 0; i < DLM_LVB_LEN; i++) { + ret = fscanf(file, format, &dummy); + if (ret != 1) { + ret = -EINVAL; + goto out; + } + + lvb[i] = (char) dummy; + } + + ret = fscanf(file, "%llu\t" + "%llu\t" + "%u\t" + "%u\t" + "%llu\t" + "%llu\t" + "%u\t" + "%u\t" + "%u\t" + "%u\t" + "%llu\t" + "%u\t" + "%llu\t" + "%u", + &num_prmode, + &num_exmode, + &num_prmode_failed, + &num_exmode_failed, + &total_prmode, + &total_exmode, + &max_prmode, + &max_exmode, + &num_refresh, + &cw, + &num_cwmode, + &num_cwmode_failed, + &total_cwmode, + &max_cwmode); + if (ret != 14) { + ret = -EINVAL; + goto out; + } + + if (oneline) { + fprintf(out, "%s mode %s flags", id, level_str(level)); + print_flags(flags, out); + fprintf(out, " cw/ro/ex %u/%u/%u act %s unlock %s req %s " + "block %s\n", cw, ro, ex, action_str(action), + unlock_action_str(unlock_action), level_str(requested), + level_str(blocking)); + ret = 1; + goto out; + } + + fprintf(out, "Lockres: %s Mode: %s\nFlags:", id, level_str(level)); + print_flags(flags, out); + fprintf(out, "\nCW Holders: %u RO Holders: %u EX Holders: %u\n", cw, + ro, ex); + fprintf(out, "Pending Action: %s Pending Unlock Action: %s\n", + action_str(action), unlock_action_str(unlock_action)); + fprintf(out, "Requested Mode: %s Blocking Mode: %s\n", + level_str(requested), level_str(blocking)); + + if (print_lvbs) + dump_raw_lvb(lvb, out); +#define NSEC_PER_USEC 1000 + + if (num_prmode) + avg_prmode = total_prmode/num_prmode; + + if (num_exmode) + avg_exmode = total_exmode/num_exmode; + + if (num_cwmode) + avg_cwmode = total_cwmode/num_cwmode; + + fprintf(out, "CW > Gets: %llu Fails: %u Waits Total: %lluus " + "Max: %uus Avg: %lluns\n", + num_cwmode, num_cwmode_failed, total_cwmode/NSEC_PER_USEC, + max_cwmode, avg_cwmode); + fprintf(out, "PR > Gets: %llu Fails: %u Waits Total: %lluus " + "Max: %uus Avg: %lluns\n", + num_prmode, num_prmode_failed, total_prmode/NSEC_PER_USEC, + max_prmode, avg_prmode); + fprintf(out, "EX > Gets: %llu Fails: %u Waits Total: %lluus " + "Max: %uus Avg: %lluns\n", + num_exmode, num_exmode_failed, total_exmode/NSEC_PER_USEC, + max_exmode, avg_exmode); + fprintf(out, "Disk Refreshes: %u\n", num_refresh); + + ret = 1; +out: + return ret; +} + +#define CURRENT_PROTO 4 +static void print_locks(int fd) +{ + FILE *file = fdopen(fd, "r"); + unsigned int version; + int ret; + + if (!file) + return; + + do { + /* + * Version is printed on every line (silly but easy to + * implement) + */ + ret = fscanf(file, "%x\t", &version); + if (ret != 1) + goto out; + + if (version > CURRENT_PROTO) { + fprintf(stdout, + "Lock debug proto is %u, but %u is the " + "highest I understand.\n", version, + CURRENT_PROTO); + goto out; + } + + ret = print_fields(file, stdout); + + /* Read to the end of the record here. Any new fields tagged + * onto the current format will be silently ignored. */ + } while (!end_line(file)); + +out: + fclose(file); +} + +static int get_fsid(int fd, u64 *fsid) +{ + struct scoutfs_super_block *super; + + super = read_block(fd, SCOUTFS_SUPER_BLKNO); + if (!super) + return -ENOMEM; + + *fsid = le64_to_cpu(super->hdr.fsid); + + return 0; +} + +static int locks_func(int argc, char *argv[]) +{ + char sysfs[PATH_MAX]; + char *path; + u64 fsid; + int ret; + int fd; + int c = 10000; + + static struct option long_ops[] = { + { "oneline", 0, NULL, 'o' }, + { "lvbs=", 1, NULL, 'L'}, + { NULL, 0, NULL, 0} + }; + + if (argc < 1) { + printf("scoutfs: locks: a device argument is required\n"); + return -EINVAL; + } + + while ((c = getopt_long(argc, argv, "l:", long_ops, NULL)) + != -1) { + switch (c) { + case 'o': + oneline = 1; + break; + case 'l': + case 'L': + if (strcasecmp(optarg, "yes") == 0) + print_lvbs = 1; + else if (strcasecmp(optarg, "no") == 0) + print_lvbs = 0; + break; + default: + return -EINVAL; + } + } + path = argv[optind]; + + /* XXX: Take mountpoint argument instead and turn that into a + * device for below */ + + fd = open(path, O_RDONLY); + if (fd < 0) { + ret = -errno; + fprintf(stderr, "failed to open '%s': %s (%d)\n", + path, strerror(errno), errno); + return ret; + } + + ret = get_fsid(fd, &fsid); + close(fd); + if (ret) + return ret; + + /* open sysfs file, print now */ + snprintf(sysfs, PATH_MAX, + "/sys/kernel/debug/scoutfs/%llx/locking_state", fsid); + + fd = open(sysfs, O_RDONLY); + if (fd < 0) { + ret = -errno; + fprintf(stderr, "failed to open '%s': %s (%d)\n", sysfs, + strerror(errno), errno); + return ret; + } + + print_locks(fd); + + close(fd); + + return 0; +} + +static void __attribute__((constructor)) locks_ctor(void) +{ + cmd_register("locks", "--lvbs=[yes|no] --oneline ", + "show file system locking state", locks_func); +} diff --git a/utils/src/print.c b/utils/src/print.c index 3007c063..6595c20c 100644 --- a/utils/src/print.c +++ b/utils/src/print.c @@ -18,7 +18,7 @@ #include "crc.h" #include "key.h" -static void *read_block(int fd, u64 blkno) +void *read_block(int fd, u64 blkno) { ssize_t ret; void *buf; diff --git a/utils/src/util.h b/utils/src/util.h index 89ec4b4f..1988d40b 100644 --- a/utils/src/util.h +++ b/utils/src/util.h @@ -5,6 +5,9 @@ #include #include +#include "sparse.h" + + /* * Generate build warnings if the condition is false but generate no * code at run time if it's true. @@ -75,4 +78,7 @@ static inline int memcmp_lens(const void *a, int a_len, return memcmp(a, b, len) ?: a_len - b_len; } +/* exported from print.c, we should probably just move it into a util.c */ +void *read_block(int fd, u64 blkno); + #endif