#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <stdio.h>
#include <ctype.h>
#include "latencytop.h"
typedef struct {
int lt_c_cause_id;
int lt_c_flags;
char *lt_c_name;
} lt_cause_t;
typedef struct {
int lt_mt_priority;
int lt_mt_cause_id;
} lt_match_t;
static GHashTable *cause_lookup = NULL;
static GPtrArray *causes_array = NULL;
static int causes_array_len = 0;
static GHashTable *symbol_lookup_table = NULL;
char *dtrans = NULL;
typedef struct {
int lt_dm_priority;
char *lt_dm_macro;
} lt_dmacro_t;
typedef struct {
GSequence *lt_pr_cmd_disable;
GHashTable *lt_pr_dmacro;
} lt_parser_t;
static void
free_cause(lt_cause_t *cause, void *user)
{
g_assert(cause != NULL && cause->lt_c_name != NULL);
free(cause->lt_c_name);
free(cause);
}
static void
free_dmacro(lt_dmacro_t *d)
{
g_assert(d->lt_dm_macro != NULL);
free(d->lt_dm_macro);
free(d);
}
static lt_cause_t *
new_cause(char *name, int flags)
{
lt_cause_t *entry;
g_assert(name != NULL);
entry = (lt_cause_t *)lt_malloc(sizeof (lt_cause_t));
entry->lt_c_flags = flags;
entry->lt_c_name = name;
entry->lt_c_cause_id = causes_array_len;
g_ptr_array_add(causes_array, entry);
++causes_array_len;
return (entry);
}
static void
disable_cause(char *cause_str, GHashTable *cause_table)
{
lt_cause_t *cause;
cause = (lt_cause_t *)g_hash_table_lookup(cause_table, cause_str);
if (cause != NULL) {
cause->lt_c_flags |= CAUSE_FLAG_DISABLED;
}
}
static int
read_line_from_mem(const char *mem, int mem_len, char *line, int line_len,
int *index)
{
g_assert(mem != NULL && line != NULL && index != NULL);
if (line_len <= 0 || mem_len <= 0) {
return (0);
}
if (*index >= mem_len) {
return (0);
}
while (line_len > 1 && *index < mem_len) {
*line = mem[(*index)++];
--line_len;
++line;
if (*(line-1) == '\r' || *(line-1) == '\n') {
break;
}
}
*line = '\0';
return (1);
}
static int
parse_config_cmd(char *begin, lt_parser_t *parser)
{
char *tmp;
char old_chr = 0;
if (*begin == '\0') {
return (0);
}
for (tmp = begin;
*tmp != '\0' && !isspace(*tmp);
++tmp) {
}
old_chr = *tmp;
*tmp = '\0';
if (strcmp("disable_cause", begin) == 0) {
if (old_chr == '\0') {
lt_display_error(
"Invalid command format: %s\n",
begin);
return (-1);
}
begin = tmp+1;
while (isspace(*begin)) {
++begin;
}
g_sequence_append(parser->lt_pr_cmd_disable,
lt_strdup(begin));
} else {
*tmp = old_chr;
lt_display_error(
"Unknown command: %s\n", begin);
return (-1);
}
return (0);
}
static int
parse_sym_trans(char *begin)
{
int priority = 0;
char *match;
char *match_dup;
char *cause_str;
lt_cause_t *cause;
lt_match_t *match_entry;
char *tmp;
priority = strtol(begin, &tmp, 10);
if (tmp == begin || priority == 0) {
return (-1);
}
begin = tmp;
if (!isspace(*begin)) {
return (-1);
}
while (isspace(*begin)) {
++begin;
}
if (*begin == 0) {
return (-1);
}
for (tmp = begin;
*tmp != '\0' && !isspace(*tmp);
++tmp) {
}
if (*tmp == '\0') {
return (-1);
}
*tmp = '\0';
match = begin;
match_entry = (lt_match_t *)
g_hash_table_lookup(symbol_lookup_table, match);
if (match_entry != NULL &&
HIGHER_PRIORITY(match_entry->lt_mt_priority, priority)) {
return (0);
}
begin = tmp + 1;
while (isspace(*begin)) {
++begin;
}
if (*begin == 0) {
return (-1);
}
cause_str = begin;
cause = (lt_cause_t *)
g_hash_table_lookup(cause_lookup, cause_str);
if (cause == NULL) {
char *cause_dup = lt_strdup(cause_str);
cause = new_cause(cause_dup, 0);
g_hash_table_insert(cause_lookup, cause_dup, cause);
}
match_entry = (lt_match_t *)lt_malloc(sizeof (lt_match_t));
match_entry->lt_mt_priority = priority;
match_entry->lt_mt_cause_id = cause->lt_c_cause_id;
match_dup = lt_strdup(match);
g_hash_table_insert(symbol_lookup_table, match_dup,
match_entry);
return (0);
}
static int
parse_dmacro(char *begin, lt_parser_t *parser)
{
int priority = 0;
char *entryprobe;
char *returnprobe;
char *cause_str;
char buf[512];
char probepair[512];
char *tmp = NULL;
lt_cause_t *cause;
lt_dmacro_t *dmacro;
priority = strtol(begin, &tmp, 10);
if (tmp == begin || priority == 0) {
return (-1);
}
begin = tmp;
while (isspace(*begin)) {
++begin;
}
if (*begin == 0) {
return (-1);
}
for (tmp = begin;
*tmp != '\0' && !isspace(*tmp);
++tmp) {
}
if (*tmp == '\0') {
return (-1);
}
*tmp = '\0';
entryprobe = begin;
begin = tmp + 1;
while (isspace(*begin)) {
++begin;
}
for (tmp = begin;
*tmp != '\0' && !isspace(*tmp);
++tmp) {
}
if (*tmp == '\0') {
return (-1);
}
*tmp = '\0';
returnprobe = begin;
begin = tmp + 1;
while (isspace(*begin)) {
++begin;
}
if (*begin == 0) {
return (-1);
}
cause_str = begin;
dmacro = NULL;
cause = (lt_cause_t *)
g_hash_table_lookup(cause_lookup, cause_str);
if (cause == NULL) {
char *cause_dup = lt_strdup(cause_str);
cause = new_cause(cause_dup, 0);
g_hash_table_insert(cause_lookup, cause_dup, cause);
}
(void) snprintf(buf, sizeof (buf), "\nTRANSLATE(%s, %s, \"%s\", %d)\n",
entryprobe, returnprobe, cause_str, priority);
(void) snprintf(probepair, sizeof (probepair), "%s %s", entryprobe,
returnprobe);
g_assert(cause != NULL);
g_assert(parser->lt_pr_dmacro != NULL);
dmacro = g_hash_table_lookup(parser->lt_pr_dmacro, probepair);
if (dmacro == NULL) {
dmacro = (lt_dmacro_t *)lt_malloc(sizeof (lt_dmacro_t));
dmacro->lt_dm_priority = priority;
dmacro->lt_dm_macro = lt_strdup(buf);
g_hash_table_insert(parser->lt_pr_dmacro, lt_strdup(probepair),
dmacro);
} else if (dmacro->lt_dm_priority < priority) {
free(dmacro->lt_dm_macro);
dmacro->lt_dm_priority = priority;
dmacro->lt_dm_macro = lt_strdup(buf);
}
return (0);
}
static void
genscript(void *key, lt_dmacro_t *dmacro, GString *str)
{
g_string_append(str, dmacro->lt_dm_macro);
}
static int
parse_config(const char *work, int work_len)
{
char line[256];
int len;
char *begin, *end;
int current = 0;
lt_parser_t parser;
int ret = 0;
char flag;
GString *script;
cause_lookup = g_hash_table_new(g_str_hash, g_str_equal);
lt_check_null(cause_lookup);
parser.lt_pr_cmd_disable = g_sequence_new((GDestroyNotify)free);
lt_check_null(parser.lt_pr_cmd_disable);
parser.lt_pr_dmacro = g_hash_table_new_full(g_str_hash,
g_str_equal, (GDestroyNotify)free, (GDestroyNotify)free_dmacro);
lt_check_null(parser.lt_pr_dmacro);
while (read_line_from_mem(work, work_len, line, sizeof (line),
¤t)) {
len = strlen(line);
if (line[len-1] != '\n' && line[len-1] != '\r' &&
current < work_len) {
lt_display_error("Configuration line too long.\n");
goto err;
}
begin = line;
while (isspace(*begin)) {
++begin;
}
if (*begin == '\0') {
continue;
}
end = begin + strlen(begin) - 1;
while (isspace(*end)) {
--end;
}
end[1] = '\0';
flag = *begin;
++begin;
switch (flag) {
case '#':
ret = 0;
break;
case ';':
ret = parse_config_cmd(begin, &parser);
break;
case 'D':
case 'd':
if (!isspace(*begin)) {
lt_display_error(
"No space after flag char: %s\n", line);
}
while (isspace(*begin)) {
++begin;
}
ret = parse_dmacro(begin, &parser);
break;
case 'S':
case 's':
if (!isspace(*begin)) {
lt_display_error(
"No space after flag char: %s\n", line);
}
while (isspace(*begin)) {
++begin;
}
ret = parse_sym_trans(begin);
break;
default:
ret = -1;
break;
}
if (ret != 0) {
lt_display_error(
"Invalid configuration line: %s\n", line);
goto err;
}
}
script = g_string_new(NULL);
g_hash_table_foreach(parser.lt_pr_dmacro, (GHFunc)genscript, script);
dtrans = g_string_free(script, FALSE);
if (dtrans != NULL && strlen(dtrans) == 0) {
free(dtrans);
dtrans = NULL;
}
g_sequence_foreach(parser.lt_pr_cmd_disable, (GFunc)disable_cause,
cause_lookup);
g_sequence_free(parser.lt_pr_cmd_disable);
return (0);
err:
g_sequence_free(parser.lt_pr_cmd_disable);
g_hash_table_destroy(parser.lt_pr_dmacro);
return (-1);
}
int
lt_table_init(void)
{
char *config_loaded = NULL;
int config_loaded_len = 0;
const char *work = NULL;
int work_len = 0;
lt_cause_t *cause;
#ifdef EMBED_CONFIGS
work = &latencytop_trans_start;
work_len = (int)(&latencytop_trans_end - &latencytop_trans_start);
#endif
if (g_config.lt_cfg_config_name != NULL) {
FILE *fp;
fp = fopen(g_config.lt_cfg_config_name, "r");
if (NULL == fp) {
lt_display_error(
"Unable to open configuration file.\n");
return (-1);
}
(void) fseek(fp, 0, SEEK_END);
config_loaded_len = (int)ftell(fp);
config_loaded = (char *)lt_malloc(config_loaded_len);
(void) fseek(fp, 0, SEEK_SET);
if (config_loaded_len != 0 &&
fread(config_loaded, config_loaded_len, 1, fp) == 0) {
lt_display_error(
"Unable to read configuration file.\n");
(void) fclose(fp);
free(config_loaded);
return (-1);
}
(void) fclose(fp);
(void) printf("Loaded configuration from %s\n",
g_config.lt_cfg_config_name);
work = config_loaded;
work_len = config_loaded_len;
}
lt_table_deinit();
causes_array = g_ptr_array_new();
lt_check_null(causes_array);
cause = new_cause(lt_strdup("Nothing"), CAUSE_FLAG_DISABLED);
g_assert(cause->lt_c_cause_id == INVALID_CAUSE);
symbol_lookup_table = g_hash_table_new_full(
g_str_hash, g_str_equal,
(GDestroyNotify)free, (GDestroyNotify)free);
lt_check_null(symbol_lookup_table);
if (work_len != 0 && parse_config(work, work_len) != 0) {
return (-1);
}
if (config_loaded != NULL) {
free(config_loaded);
}
return (0);
}
int
lt_table_cause_from_name(char *name, int auto_create, int flags)
{
lt_cause_t *cause = NULL;
if (cause_lookup == NULL) {
cause_lookup = g_hash_table_new(g_str_hash, g_str_equal);
lt_check_null(cause_lookup);
} else {
cause = (lt_cause_t *)
g_hash_table_lookup(cause_lookup, name);
}
if (cause == NULL && auto_create) {
char *cause_dup;
if (name[0] == '#') {
flags |= CAUSE_FLAG_HIDE_IN_SUMMARY;
}
cause_dup = lt_strdup(name);
cause = new_cause(cause_dup, flags);
g_hash_table_insert(cause_lookup, cause_dup, cause);
}
return (cause == NULL ? INVALID_CAUSE : cause->lt_c_cause_id);
}
int
lt_table_cause_from_stack(const char *module_func, int *cause_id, int *priority)
{
lt_match_t *match;
g_assert(module_func != NULL && cause_id != NULL && priority != NULL);
if (symbol_lookup_table == NULL) {
return (0);
}
match = (lt_match_t *)
g_hash_table_lookup(symbol_lookup_table, module_func);
if (match == NULL) {
char *func = strchr(module_func, '`');
if (func != NULL) {
match = (lt_match_t *)
g_hash_table_lookup(symbol_lookup_table, func);
}
}
if (match == NULL) {
return (0);
} else {
*cause_id = match->lt_mt_cause_id;
*priority = match->lt_mt_priority;
return (1);
}
}
const char *
lt_table_get_cause_name(int cause_id)
{
lt_cause_t *cause;
if (cause_id < 0 || cause_id >= causes_array_len) {
return (NULL);
}
cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id);
if (cause == NULL) {
return (NULL);
} else {
return (cause->lt_c_name);
}
}
int
lt_table_get_cause_flag(int cause_id, int flag)
{
lt_cause_t *cause;
if (cause_id < 0 || cause_id >= causes_array_len) {
return (0);
}
cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id);
if (cause == NULL) {
return (0);
} else {
return (cause->lt_c_flags & flag);
}
}
int
lt_table_append_trans(FILE *fp)
{
if (dtrans != NULL) {
if (fwrite(dtrans, strlen(dtrans), 1, fp) != 1) {
return (-1);
}
}
return (0);
}
void
lt_table_deinit(void)
{
if (symbol_lookup_table != NULL) {
g_hash_table_destroy(symbol_lookup_table);
symbol_lookup_table = NULL;
}
if (cause_lookup != NULL) {
g_hash_table_destroy(cause_lookup);
cause_lookup = NULL;
}
if (causes_array != NULL) {
g_ptr_array_foreach(causes_array, (GFunc)free_cause, NULL);
g_ptr_array_free(causes_array, TRUE);
causes_array = NULL;
causes_array_len = 0;
}
if (dtrans != NULL) {
g_free(dtrans);
dtrans = NULL;
}
}