/*
 * (c) Copyright IBM Corp. 2005 All Rights Reserved
 *
 * Physical Memory Information Module
 *
 * 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 02111-1307 USA
 */

/**
 * @file lib_sum_map.c
 *
 * @brief Data analyzers for library information.
 *
 * This part provides the source for the analysis of the various
 * libraries. This includes library information across all processes
 * per VMA, library usage information across across all processes
 * and all VMA types, and which libraries have non-shared pages.
 *
 * @author International Business Machines
 * @author Paul Movall <movall@us.ibm.com>
 *
 * @version Current Version: 1.0
 *
 * @date Current Date: 01/2005
 *
 * @version 0.1, 11/2003: File created by Paul Movall <movall@us.ibm.com>
 * @version 1.0, 01/2005: Miscellaneous cleanup for publish
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/param.h>

#include "td.h"

#ifndef PAGE_SIZE
/** @def PAGE_SIZE Insure that we have a page size set.
 * Both Intel and PPC use 4k, so use that if not set. */
#define PAGE_SIZE 4096
#endif

/** @def FORMAT_MAP_VERBOSE Format of the mapping output in verbose,
 * or human readable, format. */
#define FORMAT_MAP_VERBOSE 	"%s is used %d times with %d virtual pages " \
	" and %d resident pages and flags %c%c%c%c%c%c " \
	"has the following map:\n"
/** @def FORMAT_MAP_TERSE Format of the mapping output in terse,
 * or CSV, format. */
#define FORMAT_MAP_TERSE	"%s, %d, %d, %d, %c%c%c%c%c%c, "

/** @def FORMAT_LIB_SIZE_TERSE Format of the library size usage output in terse,
 * or CSV, format. */
#define FORMAT_LIB_SIZE_TERSE	"%s, %d, %d, %d, %d, %s, %d\n"
/** @def FORMAT_LIB_SIZE_VERBOSE Format of the library size usage output in,
 * verbose, or human readable, format. */
#define FORMAT_LIB_SIZE_VERBOSE \
"Entity %s has %d uses of %d pages .text, %d pages .data, %d pages .bss.\n" \
"\t%s has %d pages total\n"

/**
 * @struct page_counts_struct
 * @brief Count the various attributes and users of a page.
 */
/**
 * @typedef page_count_t
 * @brief Typedef for the @c page_counts_struct.
 */
typedef struct page_counts_struct {
	int physical;	/**< The number of users of a physical page.	*/
} page_count_t;

/**
 * @struct vma_map_struct
 * @brief Structure used to create a list of each unique VMA in use.
 */
/**
 * @typedef vma_map_t
 * @brief Typedef for the @c vma_map_struct.
 */
typedef struct vma_map_struct {
	char name[PATH_MAX];		/**< Name of associated file.	*/
	uint32_t vflags;		/**< VMA Flags.			*/
	page_count_t *map_counts;	/**< Pointer to usage counts	*/
	int max_pgs;			/**< Number of virtual pages.
					 * That is also the maximum number
					 * of physical pages.		*/
	int count;			/**< Usage count of this VMA.	*/
	int pages;			/**< Actual number of physical
					 * pages resident.		*/
	struct vma_map_struct *next;	/**< Pointer to next in list.	*/
} vma_map_t;

/**
 * @struct vma_map_list_head_struct
 * @brief Global list head structure for both the head and tail.
 */
/**
 * @typedef vma_map_list_head_t
 * @brief Typedef for the @c vma_map_list_head_struct structure.
 */
typedef struct vma_map_list_head_struct {
	vma_map_t *head;	/**< The head of the global VMA list.	*/
	vma_map_t *tail;	/**< The tail of the global VMA list.	*/
} vma_map_list_head_t;

/** @brief Global list of VMAs that are in use.				*/
static vma_map_list_head_t head_map;

typedef struct vma_map_sum_struct {
	struct vma_map_sum_struct *next;
	char name[PATH_MAX];
	int max;
	int text;
	int data;
	int bss;
	int vtext;
	int vdata;
	int vbss;
} vma_map_sum_t;

/**
 * @brief Add data to the existing map information structure.
 *
 * This function is used to add data to an existing list entry.
 * It increments the usage of the VMA and updates the usage count
 * for all of the resident pages.
 *
 * @param cur_vma The VMA data to add to the list entry.
 * @param vma_map The list entry to which to add the data.
 *
 * @return None
 *
 * @note This is also used on the first occurance once and empty one
 * has been added to the list.
 */
void add_map_data(vma_t *cur_vma, vma_map_t *vma_map) {
	map_t *cur_map;
	int cur_indx;

	vma_map->count++;

	for (cur_map = cur_vma->map_head;
	     cur_map != NULL;
	     cur_map = cur_map->next) {
		cur_indx = (cur_map->virt - cur_vma->start) / PAGE_SIZE;
		if (vma_map->map_counts[cur_indx].physical == 0) {
			vma_map->pages++;
		}
		vma_map->map_counts[cur_indx].physical++;
	}
}

/**
 * @brief Allocate, initialize, and add a new map entry to the global list.
 *
 * @param cur_vma The input VMA structure.
 *
 * @return A pointer to an initialized @c vma_map_t structure that has
 * been added to the list.
 *
 * @note This function will exit with a -1 if the allocation fails.
 */
vma_map_t *add_new_map(vma_t *cur_vma) {
	vma_map_t *vma_map;

	vma_map = (vma_map_t *)malloc(sizeof(vma_map_t));
	if (vma_map == NULL) {
		perror("Error allocating VMA MAP! Exiting...\n");
		exit(-1);
	}

	vma_map->next = NULL;

	if (head_map.tail == NULL) {
		head_map.tail = vma_map;
		head_map.head = vma_map;
	} else {
		head_map.tail->next = vma_map;
		head_map.tail = vma_map;
	}

	strcpy(vma_map->name, cur_vma->name);
	vma_map->count = 0;
	vma_map->pages = 0;
	vma_map->vflags = cur_vma->vma_flags;
	vma_map->max_pgs = (cur_vma->end - cur_vma->start + 1) / PAGE_SIZE;

	vma_map->map_counts = (page_count_t *)malloc(sizeof(page_count_t) *
						     vma_map->max_pgs);
	if (vma_map->map_counts == NULL) {
		perror("Error allocating VMA map buffer. Exiting!\n");
		exit(-1);
	}
	memset(vma_map->map_counts,
	       0,
	       sizeof(page_count_t) * vma_map->max_pgs);

	return(vma_map);
}

/**
 * @brief Find the current VMA in the global list.
 *
 * @param cur_vma The input VMA structure.
 *
 * @return A pointer to the @c vma_map_t structure that has been
 * found in the list. NULL if there is no match.
 *
 * @note This function uses the VMA flags, the VMA name, and
 * the VMA size to determine if a match has occurred.
 */
vma_map_t *find_vma_map(vma_t *cur_vma) {
	vma_map_t *vma_map;
	int done;

	for (vma_map = head_map.head, done = FALSE;
	     (vma_map != NULL) && (!done);
	     ) {
		if ( (vma_map->vflags == cur_vma->vma_flags) &&
		     (strncmp(vma_map->name, cur_vma->name, PATH_MAX) == 0) &&
		     (vma_map->max_pgs ==
		      ((cur_vma->end - cur_vma->start + 1) / PAGE_SIZE)) ) {
			done = TRUE;
		} else {
			vma_map = vma_map->next;
		}
	}

	return(vma_map);
}

/**
 * @brief Deallocate the map list so it can be rebuilt.
 *
 * @return None
 *
 * @todo Implement this function.
 * @bug This is a major memory leak if this function is left this way!
 */
void free_all(void) {
	/* memory leak... */
	head_map.head = head_map.tail = NULL;
}

/**
 * @brief Build the global mapping list.
 *
 * @note This function will also copy the associated name into
 * the real name for the VMA so that the comparison on the
 * mapping list entries is consistent.
 *
 * @return None.
 */
void lib_sum_common(void) {
	task_t *cur_task;
	vma_t *cur_vma;
	vma_map_t *vma_map;
	int cur_indx;

	/* go delete the existing list */
	free_all();

	for (cur_task = head_task.head;
	     cur_task != NULL;
	     cur_task = cur_task->next) {
		for (cur_vma = cur_task->vma_head;
		     cur_vma != NULL;
		     cur_vma = cur_vma->next) {
			if ((strcmp(cur_vma->name, "NULL") == 0) &&
			    ((cur_vma->vma_flags & VMA_FLAGS_BSS) ==
			     VMA_FLAGS_BSS) ) {
				strcpy(cur_vma->name,
				       cur_vma->assoc_name);
			}

			if (strcmp(cur_vma->name, "NULL") != 0) {
				vma_map = find_vma_map(cur_vma);
				if (vma_map == NULL) {
					vma_map = add_new_map(cur_vma);
				}
				add_map_data(cur_vma, vma_map);
			}
		}
	}
}

/**
 * @brief This function will output the library information per VMA.
 *
 * @param outf The file to which to write the output.
 *
 * @return None.
 */
void lib_summary(FILE *outf) {
	int cur_indx;
	vma_map_t *vma_map;
	char *format;
	char *map_str;

	lib_sum_common();

	if (terse) {
		fprintf(outf, "%s, %s, %s, %s, %s, %s\n",
			"Name",
			"Lib Usage Count",
			"Virtual Pages",
			"Resident Pages",
			"Flags",
			"Physical Map");
	}

	for (vma_map = head_map.head;
	     vma_map != NULL;
	     vma_map = vma_map->next) {
		if (terse) {
			format = FORMAT_MAP_TERSE;
		} else {
			format = FORMAT_MAP_VERBOSE;
		}
		fprintf(outf,
			format,
			vma_map->name,
			vma_map->count,
			vma_map->max_pgs,
			vma_map->pages,
			(vma_map->vflags & VMA_FLAGS_STACK) ? 'S' : '.',
			(vma_map->vflags & VMA_FLAGS_EXECUTABLE) ? 'X' : '.',
			(vma_map->vflags & VMA_FLAGS_RD) ? 'r' : '.',
			(vma_map->vflags & VMA_FLAGS_WR) ? 'w' : '.',
			(vma_map->vflags & VMA_FLAGS_EXEC) ? 'x' : '.',
			(vma_map->vflags & VMA_FLAGS_BSS) ? 'B' : '.');
		
		map_str = malloc(sizeof(char) * (vma_map->max_pgs + 1));
		if (map_str == NULL) {
			perror("Error allocating VMA map buffer. Exiting!\n");
			exit(-1);
		}
		memset(map_str, '.', sizeof(char) * vma_map->max_pgs);
		map_str[sizeof(char) * vma_map->max_pgs] = '\0';

		for (cur_indx = 0;
		     cur_indx < vma_map->max_pgs;
		     cur_indx++) {
			if ((vma_map->map_counts[cur_indx].physical > 0) &&
			    (vma_map->map_counts[cur_indx].physical < 10)) {
				map_str[cur_indx] = '0' +
				  vma_map->map_counts[cur_indx].physical;
			} else if
				(vma_map->map_counts[cur_indx].physical > 10) {
				map_str[cur_indx] = '#';
			}
		}

		if (terse) {
			fprintf(outf,
				"[%s]\n",
				map_str);
		} else {
			cur_indx = 0;
			do {
				fprintf(outf,
					"[%-64.64s]\n",
					&(map_str[cur_indx]));
				cur_indx += 64;
			} while (cur_indx < vma_map->max_pgs);
			fprintf(outf, "\n");
		}

		free(map_str);
	}
}

/**
 * @brief This function will output the library information as one entity.
 *
 * This function will calculate the total number of pages used by each
 * library (or executable) entity across all users. It takes into account
 * shared vs. non-shared pages so that the summation is accurate. It breaks
 * the total number of consumed pages into text (or code), data, and bss.
 *
 * @param outf The file to which to write the output.
 *
 * @return None.
 */
void lib_sizes(FILE *outf) {
	int cur_indx;
	vma_map_t *vma_map;
	vma_map_t *sum_map;
	int i;
	int text;
	int bss;
	int data;
	int vtext;
	int vbss;
	int vdata;
	char *format;
	vma_map_sum_t *map_sum_list = NULL;
	vma_map_sum_t *map_summary;
	int num_maps = 0;
	int max_map_sz = 0;
	FILE *postf;
	int max_lines;
	char *scur, *sprev;
	vma_map_sum_t *cur, *prev;

	lib_sum_common();

	if (terse) {
		format = FORMAT_LIB_SIZE_TERSE;
		fprintf(outf, "%s, %s, %s, %s, %s, %s, %s\n",
			"Name",
			"Usage",
			"Lib Text",
			"Lib Data",
			"Lib BSS",
			"Name",
			"Total");
	} else {
		format = FORMAT_LIB_SIZE_VERBOSE;
	}

	for (vma_map = head_map.head;
	     vma_map != NULL;
	     vma_map = vma_map->next) {
		if ( ((vma_map->vflags & VMA_FLAGS_RPT) == 0) &&
		     ((vma_map->vflags & VMA_FLAGS_EXECUTABLE) == 0) ) {
			text = data = bss = 0;
			vtext = vdata = vbss = 0;
			for (sum_map = vma_map;
			     sum_map != NULL;
			     sum_map = sum_map->next) {
				if ( (strcmp(sum_map->name,
					     vma_map->name) == 0) &&
				     ((sum_map->vflags & VMA_FLAGS_RPT) ==
				      0) ) {
					/* mark that this one is reported */
					sum_map->vflags |= VMA_FLAGS_RPT;

					if ((sum_map->vflags & 
					     VMA_FLAGS_WR) == 0) {
						/*
						 * not writable, must be
						 * .text...
						 * also assume that there is
						 * only one copy...
						 */
						vtext += sum_map->max_pgs;
						text += sum_map->pages;
					} else if ( (sum_map->vflags &
						     VMA_FLAGS_BSS) ==
						   VMA_FLAGS_BSS) {
						/* bss area */
						vbss += (sum_map->count *
							 sum_map->max_pgs);
						for (i = 0;
						     i <
						       sum_map->max_pgs;
						     i++) {
							bss +=
							  sum_map->
							  map_counts[i].
							  physical;
						}
					} else {
						/* must be .data */
						vdata += (sum_map->count *
							  sum_map->max_pgs);
						for (i = 0;
						     i <
						       sum_map->max_pgs;
						     i++) {
							data +=
							  sum_map->
							  map_counts[i].
							  physical;
						}
					}
				} else {
					/* different lib or already reported */
				}
			}

			fprintf(outf,
				format,
				vma_map->name,
				vma_map->count,
				virtual ? vtext : text,
				virtual ? vdata : data,
				virtual ? vbss : bss,
				vma_map->name,
				virtual ? vtext + vdata + vbss :
				text + data + bss);

			if (strcmp(vma_map->name, "NULL") != 0) {
				num_maps++;

				if (virtual) {
					if ((vtext + vdata + vbss) >
					    max_map_sz) {
						max_map_sz =
						  vtext + vdata + vbss;
					}
				} else {
					if ((text + data + bss) >
					    max_map_sz) {
						max_map_sz =
						  text + data + bss;
					}
				}

				map_summary =
				  (vma_map_sum_t *)
				  malloc(sizeof(vma_map_sum_t));
				memset(map_summary, 0, sizeof(vma_map_sum_t));

				map_summary->text = text;
				map_summary->data = data;
				map_summary->bss = bss;
				map_summary->vtext = vtext;
				map_summary->vdata = vdata;
				map_summary->vbss = vbss;

				for (sprev = scur =
				     strtok(vma_map->name, "/");
				     scur != NULL;
				     sprev = scur,
				     scur = strtok(NULL, "/"))
					;
				strncpy(map_summary->name,
					sprev,
					16);

				if (virtual) {
					map_summary->max =
					  vtext + vdata + vbss;
				} else {
					map_summary->max =
					  text + data + bss;
				}

				if ((map_sum_list == NULL) ||
				    (map_sum_list->max < map_summary->max)) {
					map_summary->next = map_sum_list;
					map_sum_list = map_summary;
				} else {
					for (prev = cur = map_sum_list;
					     ((cur != NULL) &&
					      (cur->max > map_summary->max));
					     prev = cur, cur = cur->next)
						;
					     
					map_summary->next = cur;
					prev->next = map_summary;
				}
			}
		} else {
			/* since reported just skip it */
		}
	}

	if (post_parm) {
		if ((postf = fopen(post_file, "w")) == NULL) {
			perror("Error opening PostScript file!");
			return;
		}
		
		max_lines =
		  WritePostScriptPreface(postf, num_maps, max_map_sz, FALSE);
		WritePostScriptStartPage(postf, FALSE);

		i = 0;

		while (map_sum_list != NULL) {
			fprintf(postf,
				"(%s) 0 ypos FH FHdelta add taskname\n",
				map_sum_list->name);

			fprintf(postf,
				"%d %d %d %f libpgs\n",
				map_sum_list->bss,
				map_sum_list->data,
				map_sum_list->text,
				virtual ? 0.6 : 0.35);

			if (virtual) {
				fprintf(postf,
					"%d %d %d %f libpgs\n",
					map_sum_list->vbss,
					map_sum_list->vdata,
					map_sum_list->vtext,
					0.1);
			}

			fprintf(postf,
				"/ypos ypos FH FHdelta add sub store\n\n");

			map_summary = map_sum_list->next;
			free(map_sum_list);
			map_sum_list = map_summary;

			if ((++i > max_lines) && (map_sum_list != NULL)) {
				WritePostScriptEndPage(postf, FALSE);
				i = 0;
				WritePostScriptStartPage(postf, FALSE);
			}
		}
		WritePostScriptEndPage(postf, FALSE);
		WritePostScriptEpilog(postf, num_maps, max_map_sz, FALSE);
		fclose(postf);
	}
}

/**
 * @brief This function will look for libraries with sharing anomolies.
 *
 * This function examines each VMA looking for instances where a single
 * VMA has both shared and non-shared pages. This is an indication that
 * the text segment of the library was compiled without the -fpic
 * (or similar) compiler directive. This forces copies of the executable
 * pages instead of sharing of those pages, resulting in a lot of
 * wasted memory!
 *
 * @param outf The file to which to write the output.
 *
 * @return None.
 */
void nonshared_libs(FILE *outf) {
	task_t *cur_task;
	vma_t *cur_vma;
	pte_t *cur_pte;

	for (cur_task = head_task.head;
	     cur_task != NULL;
	     cur_task = cur_task->next) {
		for (cur_vma = cur_task->vma_head;
		     cur_vma != NULL;
		     cur_vma = cur_vma->next) {

			cur_pte = cur_vma->pte_info;

			if ( (cur_pte->single_use_count != 0) &&
			     (cur_pte->shared_count != 0) ) {
				fprintf(outf, "Alert! VMA [%s] in \"%s\" has "
					"%d single usage and %d shared usage, "
					"flags = %08lX\n",
					cur_vma->name,
					cur_task->name,
					cur_pte->single_use_count,
					cur_pte->shared_count,
					cur_vma->vma_flags);
			}
		}
	}
}

