Path: blob/main/sys/contrib/openzfs/module/os/linux/spl/spl-kstat.c
48775 views
// SPDX-License-Identifier: GPL-2.0-or-later1/*2* Copyright (C) 2007-2010 Lawrence Livermore National Security, LLC.3* Copyright (C) 2007 The Regents of the University of California.4* Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).5* Written by Brian Behlendorf <[email protected]>.6* UCRL-CODE-2351977*8* This file is part of the SPL, Solaris Porting Layer.9*10* The SPL is free software; you can redistribute it and/or modify it11* under the terms of the GNU General Public License as published by the12* Free Software Foundation; either version 2 of the License, or (at your13* option) any later version.14*15* The SPL is distributed in the hope that it will be useful, but WITHOUT16* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or17* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License18* for more details.19*20* You should have received a copy of the GNU General Public License along21* with the SPL. If not, see <http://www.gnu.org/licenses/>.22*23* Solaris Porting Layer (SPL) Kstat Implementation.24*25* Links to Illumos.org for more information on kstat function:26* [1] https://illumos.org/man/1M/kstat27* [2] https://illumos.org/man/9f/kstat_create28*/29/*30* Copyright (c) 2024-2025, Klara, Inc.31* Copyright (c) 2024-2025, Syneto32*/3334#include <linux/seq_file.h>35#include <sys/kstat.h>36#include <sys/vmem.h>37#include <sys/cmn_err.h>38#include <sys/sysmacros.h>39#include <sys/string.h>4041static kmutex_t kstat_module_lock;42static struct list_head kstat_module_list;43static kid_t kstat_id;4445static int46kstat_resize_raw(kstat_t *ksp)47{48if (ksp->ks_raw_bufsize == KSTAT_RAW_MAX)49return (ENOMEM);5051vmem_free(ksp->ks_raw_buf, ksp->ks_raw_bufsize);52ksp->ks_raw_bufsize = MIN(ksp->ks_raw_bufsize * 2, KSTAT_RAW_MAX);53ksp->ks_raw_buf = vmem_alloc(ksp->ks_raw_bufsize, KM_SLEEP);5455return (0);56}5758static int59kstat_seq_show_headers(struct seq_file *f)60{61kstat_t *ksp = (kstat_t *)f->private;62int rc = 0;6364ASSERT(ksp->ks_magic == KS_MAGIC);6566seq_printf(f, "%d %d 0x%02x %d %d %lld %lld\n",67ksp->ks_kid, ksp->ks_type, ksp->ks_flags,68ksp->ks_ndata, (int)ksp->ks_data_size,69ksp->ks_crtime, ksp->ks_snaptime);7071switch (ksp->ks_type) {72case KSTAT_TYPE_RAW:73restart:74if (ksp->ks_raw_ops.headers) {75rc = ksp->ks_raw_ops.headers(76ksp->ks_raw_buf, ksp->ks_raw_bufsize);77if (rc == ENOMEM && !kstat_resize_raw(ksp))78goto restart;79if (!rc)80seq_puts(f, ksp->ks_raw_buf);81} else {82seq_printf(f, "raw data\n");83}84break;85case KSTAT_TYPE_NAMED:86seq_printf(f, "%-31s %-4s %s\n",87"name", "type", "data");88break;89case KSTAT_TYPE_INTR:90seq_printf(f, "%-8s %-8s %-8s %-8s %-8s\n",91"hard", "soft", "watchdog",92"spurious", "multsvc");93break;94case KSTAT_TYPE_IO:95seq_printf(f,96"%-8s %-8s %-8s %-8s %-8s %-8s "97"%-8s %-8s %-8s %-8s %-8s %-8s\n",98"nread", "nwritten", "reads", "writes",99"wtime", "wlentime", "wupdate",100"rtime", "rlentime", "rupdate",101"wcnt", "rcnt");102break;103case KSTAT_TYPE_TIMER:104seq_printf(f,105"%-31s %-8s "106"%-8s %-8s %-8s %-8s %-8s\n",107"name", "events", "elapsed",108"min", "max", "start", "stop");109break;110default:111PANIC("Undefined kstat type %d\n", ksp->ks_type);112}113114return (-rc);115}116117static int118kstat_seq_show_raw(struct seq_file *f, unsigned char *p, int l)119{120int i, j;121122for (i = 0; ; i++) {123seq_printf(f, "%03x:", i);124125for (j = 0; j < 16; j++) {126if (i * 16 + j >= l) {127seq_printf(f, "\n");128goto out;129}130131seq_printf(f, " %02x", (unsigned char)p[i * 16 + j]);132}133seq_printf(f, "\n");134}135out:136return (0);137}138139static int140kstat_seq_show_named(struct seq_file *f, kstat_named_t *knp)141{142seq_printf(f, "%-31s %-4d ", knp->name, knp->data_type);143144switch (knp->data_type) {145case KSTAT_DATA_CHAR:146knp->value.c[15] = '\0'; /* NULL terminate */147seq_printf(f, "%-16s", knp->value.c);148break;149/*150* NOTE - We need to be more careful able what tokens are151* used for each arch, for now this is correct for x86_64.152*/153case KSTAT_DATA_INT32:154seq_printf(f, "%d", knp->value.i32);155break;156case KSTAT_DATA_UINT32:157seq_printf(f, "%u", knp->value.ui32);158break;159case KSTAT_DATA_INT64:160seq_printf(f, "%lld", (signed long long)knp->value.i64);161break;162case KSTAT_DATA_UINT64:163seq_printf(f, "%llu",164(unsigned long long)knp->value.ui64);165break;166case KSTAT_DATA_LONG:167seq_printf(f, "%ld", knp->value.l);168break;169case KSTAT_DATA_ULONG:170seq_printf(f, "%lu", knp->value.ul);171break;172case KSTAT_DATA_STRING:173KSTAT_NAMED_STR_PTR(knp)174[KSTAT_NAMED_STR_BUFLEN(knp)-1] = '\0';175seq_printf(f, "%s", KSTAT_NAMED_STR_PTR(knp));176break;177default:178PANIC("Undefined kstat data type %d\n", knp->data_type);179}180181seq_printf(f, "\n");182183return (0);184}185186static int187kstat_seq_show_intr(struct seq_file *f, kstat_intr_t *kip)188{189seq_printf(f, "%-8u %-8u %-8u %-8u %-8u\n",190kip->intrs[KSTAT_INTR_HARD],191kip->intrs[KSTAT_INTR_SOFT],192kip->intrs[KSTAT_INTR_WATCHDOG],193kip->intrs[KSTAT_INTR_SPURIOUS],194kip->intrs[KSTAT_INTR_MULTSVC]);195196return (0);197}198199static int200kstat_seq_show_io(struct seq_file *f, kstat_io_t *kip)201{202/* though wlentime & friends are signed, they will never be negative */203seq_printf(f,204"%-8llu %-8llu %-8u %-8u %-8llu %-8llu "205"%-8llu %-8llu %-8llu %-8llu %-8u %-8u\n",206kip->nread, kip->nwritten,207kip->reads, kip->writes,208kip->wtime, kip->wlentime, kip->wlastupdate,209kip->rtime, kip->rlentime, kip->rlastupdate,210kip->wcnt, kip->rcnt);211212return (0);213}214215static int216kstat_seq_show_timer(struct seq_file *f, kstat_timer_t *ktp)217{218seq_printf(f,219"%-31s %-8llu %-8llu %-8llu %-8llu %-8llu %-8llu\n",220ktp->name, ktp->num_events, ktp->elapsed_time,221ktp->min_time, ktp->max_time,222ktp->start_time, ktp->stop_time);223224return (0);225}226227static int228kstat_seq_show(struct seq_file *f, void *p)229{230kstat_t *ksp = (kstat_t *)f->private;231int rc = 0;232233ASSERT(ksp->ks_magic == KS_MAGIC);234235switch (ksp->ks_type) {236case KSTAT_TYPE_RAW:237restart:238if (ksp->ks_raw_ops.data) {239rc = ksp->ks_raw_ops.data(240ksp->ks_raw_buf, ksp->ks_raw_bufsize, p);241if (rc == ENOMEM && !kstat_resize_raw(ksp))242goto restart;243if (!rc)244seq_puts(f, ksp->ks_raw_buf);245} else {246ASSERT(ksp->ks_ndata == 1);247rc = kstat_seq_show_raw(f, ksp->ks_data,248ksp->ks_data_size);249}250break;251case KSTAT_TYPE_NAMED:252rc = kstat_seq_show_named(f, (kstat_named_t *)p);253break;254case KSTAT_TYPE_INTR:255rc = kstat_seq_show_intr(f, (kstat_intr_t *)p);256break;257case KSTAT_TYPE_IO:258rc = kstat_seq_show_io(f, (kstat_io_t *)p);259break;260case KSTAT_TYPE_TIMER:261rc = kstat_seq_show_timer(f, (kstat_timer_t *)p);262break;263default:264PANIC("Undefined kstat type %d\n", ksp->ks_type);265}266267return (-rc);268}269270static int271kstat_default_update(kstat_t *ksp, int rw)272{273ASSERT(ksp != NULL);274275if (rw == KSTAT_WRITE)276return (EACCES);277278return (0);279}280281static void *282kstat_seq_data_addr(kstat_t *ksp, loff_t n)283{284void *rc = NULL;285286switch (ksp->ks_type) {287case KSTAT_TYPE_RAW:288if (ksp->ks_raw_ops.addr)289rc = ksp->ks_raw_ops.addr(ksp, n);290else291rc = ksp->ks_data;292break;293case KSTAT_TYPE_NAMED:294rc = ksp->ks_data + n * sizeof (kstat_named_t);295break;296case KSTAT_TYPE_INTR:297rc = ksp->ks_data + n * sizeof (kstat_intr_t);298break;299case KSTAT_TYPE_IO:300rc = ksp->ks_data + n * sizeof (kstat_io_t);301break;302case KSTAT_TYPE_TIMER:303rc = ksp->ks_data + n * sizeof (kstat_timer_t);304break;305default:306PANIC("Undefined kstat type %d\n", ksp->ks_type);307}308309return (rc);310}311312static void *313kstat_seq_start(struct seq_file *f, loff_t *pos)314{315loff_t n = *pos;316kstat_t *ksp = (kstat_t *)f->private;317ASSERT(ksp->ks_magic == KS_MAGIC);318319mutex_enter(ksp->ks_lock);320321if (ksp->ks_type == KSTAT_TYPE_RAW) {322ksp->ks_raw_bufsize = PAGE_SIZE;323ksp->ks_raw_buf = vmem_alloc(ksp->ks_raw_bufsize, KM_SLEEP);324}325326/* Dynamically update kstat, on error existing kstats are used */327(void) ksp->ks_update(ksp, KSTAT_READ);328329ksp->ks_snaptime = gethrtime();330331if (!(ksp->ks_flags & KSTAT_FLAG_NO_HEADERS) && !n &&332kstat_seq_show_headers(f))333return (NULL);334335if (n >= ksp->ks_ndata)336return (NULL);337338return (kstat_seq_data_addr(ksp, n));339}340341static void *342kstat_seq_next(struct seq_file *f, void *p, loff_t *pos)343{344kstat_t *ksp = (kstat_t *)f->private;345ASSERT(ksp->ks_magic == KS_MAGIC);346347++*pos;348if (*pos >= ksp->ks_ndata)349return (NULL);350351return (kstat_seq_data_addr(ksp, *pos));352}353354static void355kstat_seq_stop(struct seq_file *f, void *v)356{357kstat_t *ksp = (kstat_t *)f->private;358ASSERT(ksp->ks_magic == KS_MAGIC);359360if (ksp->ks_type == KSTAT_TYPE_RAW)361vmem_free(ksp->ks_raw_buf, ksp->ks_raw_bufsize);362363mutex_exit(ksp->ks_lock);364}365366static const struct seq_operations kstat_seq_ops = {367.show = kstat_seq_show,368.start = kstat_seq_start,369.next = kstat_seq_next,370.stop = kstat_seq_stop,371};372373static kstat_module_t *374kstat_find_module(char *name)375{376ASSERT(MUTEX_HELD(&kstat_module_lock));377378kstat_module_t *module = NULL;379380list_for_each_entry(module, &kstat_module_list, ksm_module_list) {381if (strncmp(name, module->ksm_name, KSTAT_STRLEN) == 0)382return (module);383}384385return (NULL);386}387388static void389kstat_delete_module(kstat_module_t *module)390{391ASSERT(MUTEX_HELD(&kstat_module_lock));392ASSERT(list_empty(&module->ksm_kstat_list));393ASSERT0(module->ksm_nchildren);394395kstat_module_t *parent = module->ksm_parent;396397char *p = module->ksm_name, *frag = NULL;398while (p != NULL && (frag = strsep(&p, "/"))) {}399400remove_proc_entry(frag, parent ? parent->ksm_proc : proc_spl_kstat);401list_del(&module->ksm_module_list);402kmem_free(module, sizeof (kstat_module_t));403404if (parent) {405parent->ksm_nchildren--;406if (parent->ksm_nchildren == 0 &&407list_empty(&parent->ksm_kstat_list))408kstat_delete_module(parent);409}410}411412static kstat_module_t *413kstat_create_module(char *name)414{415ASSERT(MUTEX_HELD(&kstat_module_lock));416417char buf[KSTAT_STRLEN];418kstat_module_t *module, *parent;419420(void) strlcpy(buf, name, KSTAT_STRLEN);421422module = parent = NULL;423char *p = buf, *frag;424while ((frag = strsep(&p, "/")) != NULL) {425module = kstat_find_module(buf);426if (module == NULL) {427struct proc_dir_entry *pde = proc_mkdir(frag,428parent ? parent->ksm_proc : proc_spl_kstat);429if (pde == NULL) {430cmn_err(CE_WARN, "kstat_create('%s'): "431"module dir create failed", buf);432if (parent)433kstat_delete_module(parent);434return (NULL);435}436437module = kmem_zalloc(sizeof (kstat_module_t), KM_SLEEP);438module->ksm_proc = pde;439strlcpy(module->ksm_name, buf, KSTAT_STRLEN);440INIT_LIST_HEAD(&module->ksm_kstat_list);441list_add_tail(&module->ksm_module_list,442&kstat_module_list);443444if (parent != NULL) {445module->ksm_parent = parent;446parent->ksm_nchildren++;447}448}449450parent = module;451if (p != NULL)452p[-1] = '/';453}454455return (module);456}457458static int459proc_kstat_open(struct inode *inode, struct file *filp)460{461struct seq_file *f;462int rc;463464rc = seq_open(filp, &kstat_seq_ops);465if (rc)466return (rc);467468f = filp->private_data;469f->private = SPL_PDE_DATA(inode);470471return (0);472}473474static ssize_t475proc_kstat_write(struct file *filp, const char __user *buf, size_t len,476loff_t *ppos)477{478struct seq_file *f = filp->private_data;479kstat_t *ksp = f->private;480int rc;481482ASSERT(ksp->ks_magic == KS_MAGIC);483484mutex_enter(ksp->ks_lock);485rc = ksp->ks_update(ksp, KSTAT_WRITE);486mutex_exit(ksp->ks_lock);487488if (rc)489return (-rc);490491*ppos += len;492return (len);493}494495static const kstat_proc_op_t proc_kstat_operations = {496#ifdef HAVE_PROC_OPS_STRUCT497.proc_open = proc_kstat_open,498.proc_write = proc_kstat_write,499.proc_read = seq_read,500.proc_lseek = seq_lseek,501.proc_release = seq_release,502#else503.open = proc_kstat_open,504.write = proc_kstat_write,505.read = seq_read,506.llseek = seq_lseek,507.release = seq_release,508#endif509};510511void512__kstat_set_raw_ops(kstat_t *ksp,513int (*headers)(char *buf, size_t size),514int (*data)(char *buf, size_t size, void *data),515void *(*addr)(kstat_t *ksp, loff_t index))516{517ksp->ks_raw_ops.headers = headers;518ksp->ks_raw_ops.data = data;519ksp->ks_raw_ops.addr = addr;520}521EXPORT_SYMBOL(__kstat_set_raw_ops);522523void524kstat_proc_entry_init(kstat_proc_entry_t *kpep, const char *module,525const char *name)526{527kpep->kpe_owner = NULL;528kpep->kpe_proc = NULL;529INIT_LIST_HEAD(&kpep->kpe_list);530strlcpy(kpep->kpe_module, module, sizeof (kpep->kpe_module));531strlcpy(kpep->kpe_name, name, sizeof (kpep->kpe_name));532}533EXPORT_SYMBOL(kstat_proc_entry_init);534535kstat_t *536__kstat_create(const char *ks_module, int ks_instance, const char *ks_name,537const char *ks_class, uchar_t ks_type, uint_t ks_ndata,538uchar_t ks_flags)539{540kstat_t *ksp;541542ASSERT(ks_module);543ASSERT0(ks_instance);544ASSERT(ks_name);545546if ((ks_type == KSTAT_TYPE_INTR) || (ks_type == KSTAT_TYPE_IO))547ASSERT(ks_ndata == 1);548549ksp = kmem_zalloc(sizeof (*ksp), KM_SLEEP);550if (ksp == NULL)551return (ksp);552553mutex_enter(&kstat_module_lock);554ksp->ks_kid = kstat_id;555kstat_id++;556mutex_exit(&kstat_module_lock);557558ksp->ks_magic = KS_MAGIC;559mutex_init(&ksp->ks_private_lock, NULL, MUTEX_DEFAULT, NULL);560ksp->ks_lock = &ksp->ks_private_lock;561562ksp->ks_crtime = gethrtime();563ksp->ks_snaptime = ksp->ks_crtime;564ksp->ks_instance = ks_instance;565strlcpy(ksp->ks_class, ks_class, sizeof (ksp->ks_class));566ksp->ks_type = ks_type;567ksp->ks_flags = ks_flags;568ksp->ks_update = kstat_default_update;569ksp->ks_private = NULL;570ksp->ks_raw_ops.headers = NULL;571ksp->ks_raw_ops.data = NULL;572ksp->ks_raw_ops.addr = NULL;573ksp->ks_raw_buf = NULL;574ksp->ks_raw_bufsize = 0;575kstat_proc_entry_init(&ksp->ks_proc, ks_module, ks_name);576577switch (ksp->ks_type) {578case KSTAT_TYPE_RAW:579ksp->ks_ndata = 1;580ksp->ks_data_size = ks_ndata;581break;582case KSTAT_TYPE_NAMED:583ksp->ks_ndata = ks_ndata;584ksp->ks_data_size = ks_ndata * sizeof (kstat_named_t);585break;586case KSTAT_TYPE_INTR:587ksp->ks_ndata = ks_ndata;588ksp->ks_data_size = ks_ndata * sizeof (kstat_intr_t);589break;590case KSTAT_TYPE_IO:591ksp->ks_ndata = ks_ndata;592ksp->ks_data_size = ks_ndata * sizeof (kstat_io_t);593break;594case KSTAT_TYPE_TIMER:595ksp->ks_ndata = ks_ndata;596ksp->ks_data_size = ks_ndata * sizeof (kstat_timer_t);597break;598default:599PANIC("Undefined kstat type %d\n", ksp->ks_type);600}601602if (ksp->ks_flags & KSTAT_FLAG_VIRTUAL) {603ksp->ks_data = NULL;604} else {605ksp->ks_data = kmem_zalloc(ksp->ks_data_size, KM_SLEEP);606if (ksp->ks_data == NULL) {607kmem_free(ksp, sizeof (*ksp));608ksp = NULL;609}610}611612return (ksp);613}614EXPORT_SYMBOL(__kstat_create);615616static int617kstat_detect_collision(kstat_proc_entry_t *kpep)618{619kstat_module_t *module;620kstat_proc_entry_t *tmp = NULL;621char *parent;622char *cp;623624parent = kmem_asprintf("%s", kpep->kpe_module);625626if ((cp = strrchr(parent, '/')) == NULL) {627kmem_strfree(parent);628return (0);629}630631cp[0] = '\0';632if ((module = kstat_find_module(parent)) != NULL) {633list_for_each_entry(tmp, &module->ksm_kstat_list, kpe_list) {634if (strncmp(tmp->kpe_name, cp+1, KSTAT_STRLEN) == 0) {635kmem_strfree(parent);636return (EEXIST);637}638}639}640641kmem_strfree(parent);642return (0);643}644645/*646* Add a file to the proc filesystem under the kstat namespace (i.e.647* /proc/spl/kstat/). The file need not necessarily be implemented as a648* kstat.649*/650void651kstat_proc_entry_install(kstat_proc_entry_t *kpep, mode_t mode,652const kstat_proc_op_t *proc_ops, void *data)653{654kstat_module_t *module;655kstat_proc_entry_t *tmp = NULL;656657ASSERT(kpep);658659mutex_enter(&kstat_module_lock);660661module = kstat_find_module(kpep->kpe_module);662if (module == NULL) {663if (kstat_detect_collision(kpep) != 0) {664cmn_err(CE_WARN, "kstat_create('%s', '%s'): namespace" \665" collision", kpep->kpe_module, kpep->kpe_name);666goto out;667}668module = kstat_create_module(kpep->kpe_module);669if (module == NULL)670goto out;671}672673/*674* We can only have one entry of this name per module. If one already675* exists, replace it by first removing the proc entry, then removing676* it from the list. The kstat itself lives on; it just can't be677* inspected through the filesystem.678*/679list_for_each_entry(tmp, &module->ksm_kstat_list, kpe_list) {680if (tmp->kpe_proc != NULL &&681strncmp(tmp->kpe_name, kpep->kpe_name, KSTAT_STRLEN) == 0) {682ASSERT3P(tmp->kpe_owner, ==, module);683remove_proc_entry(tmp->kpe_name, module->ksm_proc);684tmp->kpe_proc = NULL;685list_del_init(&tmp->kpe_list);686break;687}688}689690list_add_tail(&kpep->kpe_list, &module->ksm_kstat_list);691692kpep->kpe_owner = module;693kpep->kpe_proc = proc_create_data(kpep->kpe_name, mode,694module->ksm_proc, proc_ops, data);695if (kpep->kpe_proc == NULL) {696list_del_init(&kpep->kpe_list);697if (list_empty(&module->ksm_kstat_list))698kstat_delete_module(module);699}700out:701mutex_exit(&kstat_module_lock);702703}704EXPORT_SYMBOL(kstat_proc_entry_install);705706void707__kstat_install(kstat_t *ksp)708{709ASSERT(ksp);710mode_t mode;711/* Specify permission modes for different kstats */712if (strncmp(ksp->ks_proc.kpe_name, "dbufs", KSTAT_STRLEN) == 0) {713mode = 0600;714} else {715mode = 0644;716}717kstat_proc_entry_install(718&ksp->ks_proc, mode, &proc_kstat_operations, ksp);719}720EXPORT_SYMBOL(__kstat_install);721722void723kstat_proc_entry_delete(kstat_proc_entry_t *kpep)724{725kstat_module_t *module = kpep->kpe_owner;726if (kpep->kpe_proc)727remove_proc_entry(kpep->kpe_name, module->ksm_proc);728729mutex_enter(&kstat_module_lock);730list_del_init(&kpep->kpe_list);731732/*733* Remove top level module directory if it wasn't empty before, but now734* is.735*/736if (kpep->kpe_proc && list_empty(&module->ksm_kstat_list))737kstat_delete_module(module);738mutex_exit(&kstat_module_lock);739740}741EXPORT_SYMBOL(kstat_proc_entry_delete);742743void744__kstat_delete(kstat_t *ksp)745{746kstat_proc_entry_delete(&ksp->ks_proc);747748if (!(ksp->ks_flags & KSTAT_FLAG_VIRTUAL))749kmem_free(ksp->ks_data, ksp->ks_data_size);750751ksp->ks_lock = NULL;752mutex_destroy(&ksp->ks_private_lock);753kmem_free(ksp, sizeof (*ksp));754}755EXPORT_SYMBOL(__kstat_delete);756757int758spl_kstat_init(void)759{760mutex_init(&kstat_module_lock, NULL, MUTEX_DEFAULT, NULL);761INIT_LIST_HEAD(&kstat_module_list);762kstat_id = 0;763return (0);764}765766void767spl_kstat_fini(void)768{769ASSERT(list_empty(&kstat_module_list));770mutex_destroy(&kstat_module_lock);771}772773774