/*
 * ocfsproc.c
 *
 * ocfs proc interface
 *
 * Copyright (C) 2002 Oracle Corporation.  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 recieved 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.
 *
 * Authors: Neeraj Goyal, Suchit Kaura, Kurt Hackel, Sunil Mushran,
 *          Manish Singh, Wim Coekaerts
 */

#define OCFSPROC_PRIVATE_DECLS

#include <ocfs.h>

/* Tracing */
#define OCFS_DEBUG_CONTEXT      OCFS_DEBUG_CONTEXT_PROC

/*
 * ocfs_proc_init()
 *
 */
int ocfs_proc_init (void)
{
	static struct
	{
		char *name;
		char *data;
		int (*read_proc) (char *, char **, off_t, int, int *, void *);
	}
	*p, ProcList[] =
	{
		{ "ocfs/version", NULL, ocfs_proc_version },
		{ "ocfs/nodename", NULL, ocfs_proc_nodename },
		{ "ocfs/globalctxt", NULL, ocfs_proc_globalctxt },
#ifdef OCFS_LINUX_MEM_DEBUG
		{ "ocfs/memallocs", NULL, ocfs_proc_memallocs },
#endif
		{ NULL, }
	};

	LOG_ENTRY ();

	proc_mkdir ("ocfs", 0);

	for (p = ProcList; p->name; p++)
		create_proc_read_entry (p->name, 0, NULL, p->read_proc,
					p->data);

	LOG_EXIT_LONG (0);
	return 0;
}				/* ocfs_proc_init */

/*
 * ocfs_proc_deinit()
 *
 */
void ocfs_proc_deinit (void)
{
	LOG_ENTRY ();

	remove_proc_entry ("ocfs/version", NULL);
	remove_proc_entry ("ocfs/nodename", NULL);
	remove_proc_entry ("ocfs/memallocs", NULL);
	remove_proc_entry ("ocfs", NULL);

	LOG_EXIT ();
	return;
}				/* ocfs_proc_deinit */

/*
 * ocfs_proc_calc_metrics()
 *
 */
static int ocfs_proc_calc_metrics (char *page, char **start, off_t off,
				   int count, int *eof, int len)
{
	LOG_ENTRY ();

	if (len <= off + count)
		*eof = 1;

	*start = page + off;

	len -= off;

	if (len > count)
		len = count;

	if (len < 0)
		len = 0;

	LOG_EXIT ();
	return len;
}				/* ocfs_proc_calc_metrics */

#ifdef OCFS_LINUX_MEM_DEBUG
/*
 * ocfs_proc_memallocs()
 *
 */
static int ocfs_proc_memallocs (char *page, char **start, off_t off,
				int count, int *eof, void *data)
{
	int ret = 0;
	int proc_overflow = 0;
	struct list_head *iter;
	struct list_head *temp_iter;
	alloc_item *item;
	int len = 0;
        char *slabname;
	char *tmpstr = NULL;

	LOG_ENTRY ();

#define	MEMDBG_LEN		255
	tmpstr = ocfs_malloc(MEMDBG_LEN);
	if (!tmpstr) {
		LOG_ERROR_STATUS (-ENOMEM);
		goto bail;
	}

	sprintf (tmpstr, "%-8s  %-9s  %s\n", "Pointer", "Size/Slab", "Line:File");
	printk("%s", tmpstr);
	ret = sprintf ((char *) (page + len), "%s", tmpstr);
	len += ret;

	list_for_each_safe (iter, temp_iter, &OcfsGlobalCtxt.item_list) {
		if (len >= 4096)
                        proc_overflow = 1;
		item = list_entry (iter, alloc_item, list);
		switch (item->type) {
		    case SLAB_ITEM:
			if (item->u.slab == OcfsGlobalCtxt.oin_cache)
				slabname = "oin";
			else if (item->u.slab == OcfsGlobalCtxt.ofile_cache)
				slabname = "ofile";
			else if (item->u.slab == OcfsGlobalCtxt.lockres_cache)
				slabname = "lockres";
			else if (item->u.slab == OcfsGlobalCtxt.fe_cache)
				slabname = "fe";
			else
				slabname = "unknown";

			if (item->u.slab == OcfsGlobalCtxt.lockres_cache) {
				ocfs_lock_res *p = item->address;
				sprintf(tmpstr,
				       	"%08x  %9s  %-40s  %5d  %u.%u\n",
				       	item->address, slabname, item->tag,
				       	atomic_read(&p->lr_ref_cnt),
				       	HILO(p->sector_num));
			} else
				sprintf(tmpstr, "%08x  %9s  %-40s\n", item->address,
				       	slabname, item->tag);

			printk("%s", tmpstr);
			if (!proc_overflow) {
				ret = snprintf ((char *) (page + len),
					       	(4096 - len), "%s", tmpstr);
				len += ret;
			}
			break;
		    case KMALLOC_ITEM: 
		    case VMALLOC_ITEM:
                    default:
			sprintf(tmpstr, "%08x  %9d  %s\n", item->address,
			       	item->u.length, item->tag);
			printk("%s", tmpstr);
			if (!proc_overflow) {
				ret = snprintf ((char *) (page + len),
					       	(4096 - len), "%s", tmpstr);
				len += ret;
			}

			break;
		}
		if (ret < 0)
			proc_overflow = 1;
	}


	if (proc_overflow)
		LOG_ERROR_STR ("proc output truncated");

	ret = ocfs_proc_calc_metrics (page, start, off, count, eof, len);

bail:
	ocfs_safefree (tmpstr);
	LOG_EXIT_LONG (ret);
	return ret;
}				/* ocfs_proc_memallocs */
#endif

/*
 * ocfs_proc_globalctxt()
 *
 */
static int ocfs_proc_globalctxt(char *page, char **start, off_t off,
				int count, int *eof, void *data)
{
	int len;
	int ret;

	LOG_ENTRY ();

	sprintf (page, "lockres count: %d\n", atomic_read (&OcfsGlobalCtxt.cnt_lockres));
	len = strlen (page);

	ret = ocfs_proc_calc_metrics (page, start, off, count, eof, len);

	LOG_EXIT_LONG (ret);
	return ret;
}				/* ocfs_proc_version */


/*
 * ocfs_proc_version()
 *
 */
static int ocfs_proc_version (char *page, char **start, off_t off,
			      int count, int *eof, void *data)
{
	extern char *ocfs_version;
	int len;
	int ret;

	LOG_ENTRY ();

	strcpy (page, ocfs_version);
	strcat (page, "\n");
	len = strlen (page);

	ret = ocfs_proc_calc_metrics (page, start, off, count, eof, len);

	LOG_EXIT_LONG (ret);
	return ret;
}				/* ocfs_proc_version */

/*
 * ocfs_proc_nodenum()
 *
 */
static int ocfs_proc_nodenum (char *page, char **start, off_t off,
			      int count, int *eof, void *data)
{
	int len;
	int ret;
	ocfs_super *osb;

	LOG_ENTRY ();

	osb = (ocfs_super *) data;
	sprintf (page, "%d\n", osb->node_num);
	len = strlen (page);

	ret = ocfs_proc_calc_metrics (page, start, off, count, eof, len);

	LOG_EXIT_LONG (ret);
	return ret;
}				/* ocfs_proc_nodenum */

/*
 * ocfs_proc_nodename()
 *
 */
static int ocfs_proc_nodename (char *page, char **start, off_t off,
			       int count, int *eof, void *data)
{
	int len;
	int ret;

	LOG_ENTRY ();

	strcpy (page, OcfsGlobalCtxt.node_name);
	strcat (page, "\n");
	len = strlen (page);

	ret = ocfs_proc_calc_metrics (page, start, off, count, eof, len);

	LOG_EXIT_LONG (ret);
	return ret;
}				/* ocfs_proc_nodename */

/*
 * ocfs_proc_add_volume()
 *
 */
void ocfs_proc_add_volume (ocfs_super * osb)
{
	char newdir[20];
	char tmp[50];
	static struct
	{
		char *name;
		char *data;
		int (*read_proc) (char *, char **, off_t, int, int *, void *);
	}
	*p, ProcList[] =
	{
		{ "nodenum", NULL, ocfs_proc_nodenum },
		{ "mountpoint", NULL, ocfs_proc_mountpoint },
		{ "statistics", NULL, ocfs_proc_statistics },
		{ "hashstat", NULL, ocfs_proc_hash_stats },
		{ NULL, }
	};

	LOG_ENTRY ();

	ProcList[0].data = (char *) osb;
	ProcList[1].data = osb->vol_layout.mount_point;
	ProcList[2].data = (char *) osb;
	ProcList[3].data = (char *) osb;

	sprintf (newdir, "ocfs/%-d", osb->osb_id);
	proc_mkdir (newdir, 0);

	for (p = ProcList; p->name; p++) {
		sprintf (tmp, "%s/%s", newdir, p->name);
		create_proc_read_entry (tmp, 0, NULL, p->read_proc, p->data);
	}

	LOG_EXIT ();
	return;
}				/* ocfs_proc_add_volume */

/*
 * ocfs_proc_remove_volume()
 *
 */
void ocfs_proc_remove_volume (ocfs_super * osb)
{
	char tmp[50];

	LOG_ENTRY ();

	sprintf (tmp, "ocfs/%-d/nodenum", osb->osb_id);
	remove_proc_entry (tmp, NULL);

	sprintf (tmp, "ocfs/%-d/mountpoint", osb->osb_id);
	remove_proc_entry (tmp, NULL);

	sprintf (tmp, "ocfs/%-d/statistics", osb->osb_id);
	remove_proc_entry (tmp, NULL);

	sprintf (tmp, "ocfs/%-d", osb->osb_id);
	remove_proc_entry (tmp, NULL);

	LOG_EXIT ();
	return;
}				/* ocfs_proc_remove_volume */

/*
 * ocfs_proc_mountpoint()
 *
 */
static int ocfs_proc_mountpoint (char *page, char **start, off_t off,
				 int count, int *eof, void *data)
{
	int len;
	int ret;

	LOG_ENTRY ();

	strcpy (page, data);
	strcat (page, "\n");
	len = strlen (page);

	ret = ocfs_proc_calc_metrics (page, start, off, count, eof, len);

	LOG_EXIT_LONG (ret);
	return ret;
}				/* ocfs_proc_mountpoint */

/*
 * ocfs_proc_statistics()
 *
 */
static int ocfs_proc_statistics (char *page, char **start, off_t off,
				 int count, int *eof, void *data)
{
	int len;
	char tmp[1024];
	char pubmap[100];
	ocfs_super *osb;
	ocfs_vol_layout *VolLayout;
	int ret, i;
	char *ptr;

	LOG_ENTRY ();

	osb = (ocfs_super *) data;
	VolLayout = &(osb->vol_layout);

	ptr = pubmap;
	for (i = 0; i < 32; i++) {
		if (osb->publ_map & (1 << i))
			ptr += sprintf (ptr, "%d ", i);
	}
	if (pubmap != ptr)
		*(ptr - 1) = '\0';

#define PROC_STATS                             \
  "File open count          : %d.%u\n"         \
  "Publish map              : %s\n"            \
  "Number of nodes          : %u\n"            \
  "Cluster size             : %u\n"            \
  "Volume size              : %u.%u\n"         \
  "Dir node size            : %u.%u\n"         \
  "File node size           : %u.%u\n"         \
  "Failed Large Allocs      : %u\n"            \
  "Retry Large Allocs       : %u\n"		\
  "Lockres count	    : %d\n"  

	sprintf (tmp, PROC_STATS, HI (osb->file_open_cnt),
		 LO (osb->file_open_cnt), pubmap, VolLayout->num_nodes,
		 VolLayout->cluster_size, HI (VolLayout->size),
		 LO (VolLayout->size), HI (VolLayout->dir_node_size),
		 LO (VolLayout->dir_node_size), HI (VolLayout->file_node_size),
		 LO (VolLayout->file_node_size), osb->cluster_bitmap.failed,
		 osb->cluster_bitmap.ok_retries, atomic_read (&OcfsGlobalCtxt.cnt_lockres));

	strcpy (page, tmp);
	len = strlen (page);

	ret = ocfs_proc_calc_metrics (page, start, off, count, eof, len);

	LOG_EXIT_LONG (ret);
	return ret;
}				/* ocfs_proc_statistics */

/*
 * ocfs_proc_hash_stats()
 *
 */
static int ocfs_proc_hash_stats (char *page, char **start, off_t off,
				 int count, int *eof, void *data)
{
	int len;
	int ret;
	ocfs_super *osb;
	char tmp[HASHSTAT_BUFLEN];

	LOG_ENTRY ();

	osb = (ocfs_super *) data;

	ocfs_hash_stat (&(osb->root_sect_node), tmp, HASHSTAT_BUFLEN);

	strcpy (page, tmp);
	len = strlen (page);

	ret = ocfs_proc_calc_metrics (page, start, off, count, eof, len);

	LOG_EXIT_LONG (ret);
	return ret;
}				/* ocfs_proc_hash_stats */
