root/usr.bin/systat/malloc.c
/*      $OpenBSD: malloc.c,v 1.5 2019/11/28 16:27:25 guenther Exp $     */
/*
 * Copyright (c) 2008 Can Erkin Acar <canacar@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/signal.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#include "systat.h"

void print_types(void);
void print_buckets(void);
int  read_types(void);
int  read_buckets(void);
void sort_types(void);
int  select_types(void);
int  select_buckets(void);
void showtype(int k);
void showbucket(int k);


/* qsort callbacks */
int sort_tname_callback(const void *s1, const void *s2);
int sort_treq_callback(const void *s1, const void *s2);
int sort_inuse_callback(const void *s1, const void *s2);
int sort_memuse_callback(const void *s1, const void *s2);

#define MAX_BUCKETS 16

struct type_info {
        const char *name;
        struct kmemstats stats;
        char buckets[MAX_BUCKETS];
};


struct type_info types[M_LAST];

struct kmembuckets buckets[MAX_BUCKETS];
int bucket_sizes[MAX_BUCKETS];

int num_types = 0;
int num_buckets = 0;

/*
 * These names are defined in <sys/malloc.h>.
 */
const char *kmemnames[] = INITKMEMNAMES;

field_def fields_malloc[] = {
        {"TYPE", 14, 32, 1, FLD_ALIGN_LEFT, -1, 0, 0, 0},
        {"INUSE", 6, 16, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"MEMUSE", 6, 16, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"HIGHUSE", 6, 16, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"LIMIT", 6, 16, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"REQUESTS", 8, 16, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"TYPE LIMIT", 5, 12, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"KERN LIMIT", 5, 12, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"BUCKETS", MAX_BUCKETS, MAX_BUCKETS, 1, FLD_ALIGN_LEFT, -1, 0, 0, 0},

        {"BUCKET", 8, 8, 1, FLD_ALIGN_LEFT, -1, 0, 0, 0},
        {"REQUESTS", 8, 24, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"INUSE", 8, 24, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"FREE", 8, 24, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"HIWAT", 8, 24, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"COULDFREE", 8, 24, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
};


#define FLD_TYPE_NAME           FIELD_ADDR(fields_malloc,0)
#define FLD_TYPE_INUSE          FIELD_ADDR(fields_malloc,1)
#define FLD_TYPE_MEMUSE         FIELD_ADDR(fields_malloc,2)
#define FLD_TYPE_HIGHUSE        FIELD_ADDR(fields_malloc,3)
#define FLD_TYPE_LIMIT          FIELD_ADDR(fields_malloc,4)
#define FLD_TYPE_REQUESTS       FIELD_ADDR(fields_malloc,5)
#define FLD_TYPE_TLIMIT         FIELD_ADDR(fields_malloc,6)
#define FLD_TYPE_KLIMIT         FIELD_ADDR(fields_malloc,7)
#define FLD_TYPE_SIZES          FIELD_ADDR(fields_malloc,8)

#define FLD_BUCKET_SIZE         FIELD_ADDR(fields_malloc,9)
#define FLD_BUCKET_REQUESTS     FIELD_ADDR(fields_malloc,10)
#define FLD_BUCKET_INUSE        FIELD_ADDR(fields_malloc,11)
#define FLD_BUCKET_FREE         FIELD_ADDR(fields_malloc,12)
#define FLD_BUCKET_HIWAT        FIELD_ADDR(fields_malloc,13)
#define FLD_BUCKET_COULDFREE    FIELD_ADDR(fields_malloc,14)



/* Define views */
field_def *view_malloc_0[] = {
        FLD_TYPE_NAME, FLD_TYPE_INUSE, FLD_TYPE_MEMUSE,
        FLD_TYPE_HIGHUSE, FLD_TYPE_LIMIT, FLD_TYPE_REQUESTS,
        FLD_TYPE_TLIMIT, FLD_TYPE_KLIMIT, FLD_TYPE_SIZES, NULL
};

field_def *view_malloc_1[] = {
        FLD_BUCKET_SIZE, FLD_BUCKET_REQUESTS, FLD_BUCKET_INUSE,
        FLD_BUCKET_FREE, FLD_BUCKET_HIWAT, FLD_BUCKET_COULDFREE, NULL
};

order_type type_order_list[] = {
        {"name", "name", 'N', sort_tname_callback},
        {"inuse", "in use", 'U', sort_inuse_callback},
        {"memuse", "mem use", 'S', sort_memuse_callback},
        {"requests", "requests", 'Q', sort_treq_callback},
        {NULL, NULL, 0, NULL}
};

/* Define view managers */
struct view_manager types_mgr = {
        "Types", select_types, read_types, sort_types, print_header,
        print_types, keyboard_callback, type_order_list, type_order_list
};

struct view_manager buckets_mgr = {
        "Buckets", select_buckets, read_buckets, NULL, print_header,
        print_buckets, keyboard_callback, NULL, NULL
};

field_view views_malloc[] = {
        {view_malloc_0, "malloc", '6', &types_mgr},
        {view_malloc_1, "buckets", '7', &buckets_mgr},
        {NULL, NULL, 0, NULL}
};


int
sort_tname_callback(const void *s1, const void *s2)
{
        struct type_info *t1, *t2;
        t1 = (struct type_info *)s1;
        t2 = (struct type_info *)s2;

        return strcmp(t1->name, t2->name) * sortdir;
}

int
sort_treq_callback(const void *s1, const void *s2)
{
        struct type_info *t1, *t2;
        t1 = (struct type_info *)s1;
        t2 = (struct type_info *)s2;

        if (t1->stats.ks_calls <  t2->stats.ks_calls)
                return sortdir;
        if (t1->stats.ks_calls >  t2->stats.ks_calls)
                return -sortdir;

        return sort_tname_callback(s1, s2);
}

int
sort_inuse_callback(const void *s1, const void *s2)
{
        struct type_info *t1, *t2;
        t1 = (struct type_info *)s1;
        t2 = (struct type_info *)s2;

        if (t1->stats.ks_inuse <  t2->stats.ks_inuse)
                return sortdir;
        if (t1->stats.ks_inuse >  t2->stats.ks_inuse)
                return -sortdir;

        return sort_tname_callback(s1, s2);
}

int
sort_memuse_callback(const void *s1, const void *s2)
{
        struct type_info *t1, *t2;
        t1 = (struct type_info *)s1;
        t2 = (struct type_info *)s2;

        if (t1->stats.ks_memuse <  t2->stats.ks_memuse)
                return sortdir;
        if (t1->stats.ks_memuse >  t2->stats.ks_memuse)
                return -sortdir;

        return sort_tname_callback(s1, s2);
}


void
sort_types(void)
{
        order_type *ordering;

        if (curr_mgr == NULL)
                return;

        ordering = curr_mgr->order_curr;

        if (ordering == NULL)
                return;
        if (ordering->func == NULL)
                return;
        if (num_types <= 0)
                return;

        mergesort(types, num_types, sizeof(struct type_info), ordering->func);
}

int
select_types(void)
{
        num_disp = num_types;
        return (0);
}

int
select_buckets(void)
{
        num_disp = num_buckets;
        return (0);
}

int
read_buckets(void)
{
        int mib[4];
        char buf[BUFSIZ], *bufp, *ap;
        const char *errstr;
        size_t siz;

        mib[0] = CTL_KERN;
        mib[1] = KERN_MALLOCSTATS;
        mib[2] = KERN_MALLOC_BUCKETS;

        siz = sizeof(buf);
        num_buckets = 0;

        if (sysctl(mib, 3, buf, &siz, NULL, 0) == -1) {
                error("sysctl(kern.malloc.buckets): %s", strerror(errno));
                return (-1);
        }

        bufp = buf;
        mib[2] = KERN_MALLOC_BUCKET;
        siz = sizeof(struct kmembuckets);

        while ((ap = strsep(&bufp, ",")) != NULL) {
                if (num_buckets >= MAX_BUCKETS)
                        break;
                bucket_sizes[num_buckets] = strtonum(ap, 0, INT_MAX, &errstr);
                if (errstr) {
                        error("strtonum(%s): %s", ap, errstr);
                        return (-1);
                }
                mib[3] = bucket_sizes[num_buckets];
                if (sysctl(mib, 4, &buckets[num_buckets], &siz,
                           NULL, 0) == -1) {
                        error("sysctl(kern.malloc.bucket.%d): %s",
                            mib[3], strerror(errno));
                        return (-1);
                }
                num_buckets++;
        }

        return (0);
}

int
read_types(void)
{
        struct type_info *ti;
        int i, j, k, mib[4];
        size_t siz;

        bzero(types, sizeof(types));
        ti = types;
        siz = sizeof(struct kmemstats);

        num_types = 0;
        
        for (i = 0; i < M_LAST; i++) {
                mib[0] = CTL_KERN;
                mib[1] = KERN_MALLOCSTATS;
                mib[2] = KERN_MALLOC_KMEMSTATS;
                mib[3] = i;
                
                /*
                 * Skip errors -- these are presumed to be unallocated
                 * entries.
                 */
                if (sysctl(mib, 4, &ti->stats, &siz, NULL, 0) == -1)
                        continue;

                if (ti->stats.ks_calls == 0)
                        continue;

                ti->name = kmemnames[i];
                j = 1 << MINBUCKET;

                for (k = 0; k < MAX_BUCKETS; k++, j <<= 1)
                        ti->buckets[k] = (ti->stats.ks_size & j) ? '|' : '.';

                ti++;
                num_types++;
        }

        return (0);
}


void
print_types(void)
{
        int n, count = 0;

        for (n = dispstart; n < num_disp; n++) {
                showtype(n);
                count++;
                if (maxprint > 0 && count >= maxprint)
                        break;  }
}

void
print_buckets(void)
{
        int n, count = 0;

        for (n = dispstart; n < num_disp; n++) {
                showbucket(n);
                count++;
                if (maxprint > 0 && count >= maxprint)
                        break;
        }
}

int
initmalloc(void)
{
        field_view *v;

        for (v = views_malloc; v->name != NULL; v++)
                add_view(v);

        read_buckets();
        read_types();

        return(0);
}

void
showbucket(int k)
{
        struct kmembuckets *kp = buckets + k;

        if (k < 0 || k >= num_buckets)
                return;

        print_fld_size(FLD_BUCKET_SIZE, bucket_sizes[k]);
        print_fld_size(FLD_BUCKET_INUSE, kp->kb_total - kp->kb_totalfree);
        print_fld_size(FLD_BUCKET_FREE, kp->kb_totalfree);
        print_fld_size(FLD_BUCKET_REQUESTS, kp->kb_calls);
        print_fld_size(FLD_BUCKET_HIWAT, kp->kb_highwat);
        print_fld_size(FLD_BUCKET_COULDFREE, kp->kb_couldfree);

        end_line();
}


void
showtype(int k)
{
        struct type_info *t = types + k;

        if (k < 0 || k >= num_types)
                return;


        print_fld_str(FLD_TYPE_NAME, t->name ? t->name : "undefined");
        print_fld_size(FLD_TYPE_INUSE, t->stats.ks_inuse);
        print_fld_size(FLD_TYPE_MEMUSE, t->stats.ks_memuse);
        print_fld_size(FLD_TYPE_HIGHUSE, t->stats.ks_maxused);
        print_fld_size(FLD_TYPE_LIMIT, t->stats.ks_limit);
        print_fld_size(FLD_TYPE_REQUESTS, t->stats.ks_calls);
        print_fld_size(FLD_TYPE_TLIMIT, t->stats.ks_limblocks);
        print_fld_str(FLD_TYPE_SIZES, t->buckets);

        end_line();
}