/** * Copyright (C) Mellanox Technologies Ltd. 2001-2014. ALL RIGHTS RESERVED. * * See file LICENSE for terms. */ #include "libstats.h" #include #include #include #include #include #include #include #include /* Class table */ #define UCS_STATS_CLS_HASH_SIZE 127 #define UCS_STATS_CLSID_HASH(a) ( (uintptr_t)((a)->cls) ) #define UCS_STATS_CLSID_CMP(a, b) ( ((long)((a)->cls)) - ((long)((b)->cls)) ) #define UCS_STATS_CLSID_SENTINEL UINT8_MAX /* Encode counter size */ #define UCS_STATS_BITS_PER_COUNTER 2 #define UCS_STATS_COUNTER_ZERO 0 #define UCS_STATS_COUNTER_U16 1 #define UCS_STATS_COUNTER_U32 2 #define UCS_STATS_COUNTER_U64 3 /* Compression mode */ #define UCS_STATS_COMPRESSION_NONE 0 #define UCS_STATS_COMPRESSION_BZIP2 1 /* Statistics data header */ typedef struct ucs_stats_data_header { uint32_t version; uint32_t reserved; uint32_t compression; uint32_t num_classes; } ucs_stats_data_header_t; /* Class id record */ typedef struct ucs_stats_clsid ucs_stats_clsid_t; struct ucs_stats_clsid { uint8_t clsid; ucs_stats_class_t *cls; ucs_stats_clsid_t *next; }; /* Save pointer to class table near the root node */ typedef struct ucs_stats_root_storage { ucs_stats_class_t **classes; unsigned num_classes; ucs_stats_node_t node; } ucs_stats_root_storage_t; SGLIB_DEFINE_LIST_PROTOTYPES(ucs_stats_clsid_t, UCS_STATS_CLSID_CMP, next) SGLIB_DEFINE_LIST_FUNCTIONS(ucs_stats_clsid_t, UCS_STATS_CLSID_CMP, next) SGLIB_DEFINE_HASHED_CONTAINER_PROTOTYPES(ucs_stats_clsid_t, UCS_STATS_CLS_HASH_SIZE, UCS_STATS_CLSID_HASH) SGLIB_DEFINE_HASHED_CONTAINER_FUNCTIONS(ucs_stats_clsid_t, UCS_STATS_CLS_HASH_SIZE, UCS_STATS_CLSID_HASH) #define FREAD(_buf, _size, _stream) \ { \ size_t nread = fread(_buf, 1, _size, _stream); \ assert(nread == _size); \ } #define FWRITE(_buf, _size, _stream) \ { \ size_t nwrite = fwrite(_buf, 1, _size, _stream); \ assert(nwrite == _size); \ } #define FREAD_ONE(_ptr, _stream) \ FREAD(_ptr, sizeof(*(_ptr)), _stream) #define FWRITE_ONE(_ptr, _stream) \ FWRITE(_ptr, sizeof(*(_ptr)), _stream) static unsigned ucs_stats_get_all_classes_recurs(ucs_stats_node_t *node, ucs_stats_children_sel_t sel, ucs_stats_clsid_t **cls_hash) { ucs_stats_clsid_t *elem, search; ucs_stats_node_t *child; unsigned count; search.cls = node->cls; if (!sglib_hashed_ucs_stats_clsid_t_find_member(cls_hash, &search)) { elem = malloc(sizeof *elem); elem->cls = node->cls; sglib_hashed_ucs_stats_clsid_t_add(cls_hash, elem); count = 1; } else { count = 0; } ucs_list_for_each(child, &node->children[sel], list) { count += ucs_stats_get_all_classes_recurs(child, sel, cls_hash); } return count; } static char * ucs_stats_read_str(FILE *stream) { uint8_t tmp; char *str; FREAD_ONE(&tmp, stream); /* coverity[tainted_data] */ str = malloc(tmp + 1); FREAD(str, tmp, stream); str[tmp] = '\0'; return str; } static void ucs_stats_write_str(const char *str, FILE *stream) { uint8_t tmp = strlen(str); FWRITE_ONE(&tmp, stream); FWRITE(str, tmp, stream); } static void ucs_stats_read_counters(ucs_stats_counter_t *counters, unsigned num_counters, FILE *stream) { const unsigned counters_per_byte = 8 / UCS_STATS_BITS_PER_COUNTER; uint16_t value16; uint32_t value32; uint64_t value64; uint8_t *counter_desc, v; size_t counter_desc_size; unsigned i; counter_desc_size = ((num_counters + counters_per_byte - 1) / counters_per_byte); counter_desc = ucs_alloca(counter_desc_size); FREAD(counter_desc, counter_desc_size, stream); for (i = 0; i < num_counters; ++i) { v = (counter_desc[i / counters_per_byte] >> ((i % counters_per_byte) * UCS_STATS_BITS_PER_COUNTER)) & 0x3; switch (v) { case UCS_STATS_COUNTER_ZERO: counters[i] = 0; break; case UCS_STATS_COUNTER_U16: FREAD_ONE(&value16, stream); counters[i] = value16; break; case UCS_STATS_COUNTER_U32: FREAD_ONE(&value32, stream); counters[i] = value32; break; case UCS_STATS_COUNTER_U64: FREAD_ONE(&value64, stream); counters[i] = value64; break; } } } static void ucs_stats_write_counters(ucs_stats_counter_t *counters, unsigned num_counters, FILE *stream) { const unsigned counters_per_byte = 8 / UCS_STATS_BITS_PER_COUNTER; ucs_stats_counter_t value; uint8_t *counter_desc, v; char *counter_data, *pos; size_t counter_desc_size; unsigned i; UCS_STATIC_ASSERT((8 % UCS_STATS_BITS_PER_COUNTER) == 0); counter_desc_size = ((num_counters + counters_per_byte - 1) / counters_per_byte); counter_desc = ucs_alloca(counter_desc_size); counter_data = ucs_alloca(num_counters * sizeof(ucs_stats_counter_t)); memset(counter_desc, 0, counter_desc_size); pos = counter_data; /* * First, we have an array with 2 bits per counter describing its size: * (0 - empty, 1 - 16bit, 2 - 32bit, 3 - 64bit) * Then, an array of all counters, each one occupying the size listed before. */ for (i = 0; i < num_counters; ++i) { value = counters[i]; if (value == 0) { v = UCS_STATS_COUNTER_ZERO; } else if (value <= USHRT_MAX) { v = UCS_STATS_COUNTER_U16; *(uint16_t*)(pos) = value; pos += sizeof(uint16_t); } else if (value <= UINT_MAX) { v = UCS_STATS_COUNTER_U32; *(uint32_t*)(pos) = value; pos += sizeof(uint32_t); } else { v = UCS_STATS_COUNTER_U64; *(uint64_t*)(pos) = value; pos += sizeof(uint64_t); } counter_desc[i / counters_per_byte] |= v << ((i % counters_per_byte) * UCS_STATS_BITS_PER_COUNTER); } FWRITE(counter_desc, counter_desc_size, stream); FWRITE(counter_data, pos - counter_data, stream); } static void ucs_stats_serialize_binary_recurs(FILE *stream, ucs_stats_node_t *node, ucs_stats_children_sel_t sel, ucs_stats_clsid_t **cls_hash) { ucs_stats_class_t *cls = node->cls; ucs_stats_clsid_t *elem, search; ucs_stats_node_t *child; uint8_t sentinel; /* Search the class */ search.cls = cls; elem = sglib_hashed_ucs_stats_clsid_t_find_member(cls_hash, &search); assert(elem != NULL); /* Write class ID */ FWRITE_ONE(&elem->clsid, stream); /* Name */ ucs_stats_write_str(node->name, stream); /* Counters */ ucs_stats_write_counters(node->counters, cls->num_counters, stream); /* Children */ ucs_list_for_each(child, &node->children[sel], list) { ucs_stats_serialize_binary_recurs(stream, child, sel, cls_hash); } /* Write sentinel which is not valid class id to mark end of children */ sentinel = UCS_STATS_CLSID_SENTINEL; FWRITE_ONE(&sentinel, stream); } static ucs_status_t ucs_stats_serialize_binary(FILE *stream, ucs_stats_node_t *root, ucs_stats_children_sel_t sel) { ucs_stats_clsid_t* cls_hash[UCS_STATS_CLS_HASH_SIZE]; struct sglib_hashed_ucs_stats_clsid_t_iterator it; ucs_stats_class_t *cls; ucs_stats_clsid_t *elem; ucs_stats_data_header_t hdr; unsigned index, counter; sglib_hashed_ucs_stats_clsid_t_init(cls_hash); /* Write header */ hdr.version = 1; hdr.compression = UCS_STATS_COMPRESSION_NONE; hdr.reserved = 0; hdr.num_classes = ucs_stats_get_all_classes_recurs(root, sel, cls_hash); assert(hdr.num_classes < UINT8_MAX); FWRITE_ONE(&hdr, stream); /* Write stats node classes */ index = 0; for (elem = sglib_hashed_ucs_stats_clsid_t_it_init(&it, cls_hash); elem != NULL; elem = sglib_hashed_ucs_stats_clsid_t_it_next(&it)) { cls = elem->cls; ucs_stats_write_str(cls->name, stream); FWRITE_ONE(&cls->num_counters, stream); for (counter = 0; counter < cls->num_counters; ++counter) { ucs_stats_write_str(cls->counter_names[counter], stream); } elem->clsid = index++; } assert(index == hdr.num_classes); /* Write stats nodes */ ucs_stats_serialize_binary_recurs(stream, root, sel, cls_hash); /* Free classes */ for (elem = sglib_hashed_ucs_stats_clsid_t_it_init(&it, cls_hash); elem != NULL; elem = sglib_hashed_ucs_stats_clsid_t_it_next(&it)) { free(elem); } return UCS_OK; } static ucs_status_t ucs_stats_serialize_text_recurs_filtered(FILE *stream, ucs_stats_filter_node_t *filter_node, unsigned indent) { ucs_stats_filter_node_t *filter_child; ucs_stats_node_t *node; unsigned i; int is_sum = ucs_global_opts.stats_format == UCS_STATS_SUMMARY; char *nl = is_sum ? "" : "\n"; char *space = is_sum ? "" : " "; char *left_b = is_sum ? "{" : ""; char *rigth_b = is_sum ? "} " : ""; if (!filter_node->ref_count) { return UCS_OK; } if (ucs_list_is_empty(&filter_node->type_list_head)) { ucs_error("no node is associated with node filter"); return UCS_OK; } node = ucs_list_head(&filter_node->type_list_head, ucs_stats_node_t, type_list); if (filter_node->type_list_len > 1) { fprintf(stream, "%*s%s*:%s", UCS_STATS_INDENT(is_sum, indent), node->cls->name, nl); } else { if (ucs_global_opts.stats_format == UCS_STATS_SUMMARY) { fprintf(stream, "%*s%s:%s", UCS_STATS_INDENT(is_sum, indent), strlen(node->cls->name) ? node->cls->name : node->name, nl); } else { fprintf(stream, "%*s"UCS_STATS_NODE_FMT":%s", UCS_STATS_INDENT(is_sum, indent), UCS_STATS_NODE_ARG(node), nl); } } /* Root shouldn't be with brackets.*/ if (filter_node->parent) { fputs(left_b, stream); } for (i = 0; (i < node->cls->num_counters) && (i < 64); ++i) { ucs_stats_counter_t counters_acc = 0; if (filter_node->counters_bitmask & UCS_BIT(i)) { ucs_stats_node_t * temp_node; ucs_list_for_each(temp_node, &filter_node->type_list_head, type_list) { counters_acc += temp_node->counters[i]; } fprintf(stream, "%*s%s:%s%"PRIu64"%s", UCS_STATS_INDENT(is_sum, indent + 1), node->cls->counter_names[i], space, counters_acc, nl); /* Don't print space on last counter */ if (UCS_STATS_IS_LAST_COUNTER(filter_node->counters_bitmask, i) && is_sum) { fputs(" ", stream); } } } ucs_list_for_each(filter_child, &filter_node->children, list) { ucs_stats_serialize_text_recurs_filtered(stream, filter_child, indent + 1); } if (filter_node->parent) { /* Root shouldn't be with parent brackets.*/ fputs(rigth_b, stream); } else { /* End report with new line.*/ fputs("\n", stream); } return UCS_OK; } ucs_status_t ucs_stats_serialize(FILE *stream, ucs_stats_node_t *root, int options) { ucs_stats_children_sel_t sel = (options & UCS_STATS_SERIALIZE_INACTVIVE) ? UCS_STATS_INACTIVE_CHILDREN : UCS_STATS_ACTIVE_CHILDREN; if (options & UCS_STATS_SERIALIZE_BINARY) { return ucs_stats_serialize_binary(stream, root, sel); } else { return ucs_stats_serialize_text_recurs_filtered(stream, root->filter_node, 0); } } static ucs_status_t ucs_stats_deserialize_recurs(FILE *stream, ucs_stats_class_t **classes, unsigned num_classes, size_t headroom, ucs_stats_node_t **p_root) { ucs_stats_node_t *node, *child; ucs_stats_class_t *cls; uint8_t clsid, namelen; ucs_status_t status; void *ptr; if (headroom >= UINT_MAX) { return UCS_ERR_INVALID_PARAM; } if (feof(stream)) { ucs_error("Error parsing statistics - premature end of stream"); return UCS_ERR_MESSAGE_TRUNCATED; } FREAD_ONE(&clsid, stream); if (clsid == UCS_STATS_CLSID_SENTINEL) { return UCS_ERR_NO_MESSAGE; /* Sentinel */ } if (clsid >= num_classes) { ucs_error("Error parsing statistics - class id out of range"); return UCS_ERR_OUT_OF_RANGE; } FREAD_ONE(&namelen, stream); if (namelen >= UCS_STAT_NAME_MAX) { ucs_error("Error parsing statistics - node name too long"); return UCS_ERR_OUT_OF_RANGE; /* Name too long */ } cls = classes[clsid]; ptr = malloc(headroom + sizeof *node + sizeof(ucs_stats_counter_t) * cls->num_counters); if (ptr == NULL) { ucs_error("Failed to allocate statistics counters (headroom %zu, %u counters)", headroom, cls->num_counters); return UCS_ERR_NO_MEMORY; } node = UCS_PTR_BYTE_OFFSET(ptr, headroom); node->cls = cls; FREAD(node->name, namelen, stream); node->name[namelen] = '\0'; ucs_list_head_init(&node->children[UCS_STATS_INACTIVE_CHILDREN]); ucs_list_head_init(&node->children[UCS_STATS_ACTIVE_CHILDREN]); /* Read counters */ ucs_stats_read_counters(node->counters, cls->num_counters, stream); /* Read children */ do { status = ucs_stats_deserialize_recurs(stream, classes, num_classes, 0, &child); if (status == UCS_OK) { ucs_list_add_tail(&node->children[UCS_STATS_ACTIVE_CHILDREN], &child->list); } else if (status == UCS_ERR_NO_MESSAGE) { break; /* Sentinel */ } else { ucs_error("ucs_stats_deserialize_recurs returned %s", ucs_status_string(status)); free(ptr); /* Error TODO free previous children */ return status; } } while (1); *p_root = node; return UCS_OK; } static void ucs_stats_free_classes(ucs_stats_class_t **classes, unsigned num_classes) { unsigned i, j; for (i = 0; i < num_classes; ++i) { free((char*)classes[i]->name); for (j = 0; j < classes[i]->num_counters; ++j) { free((char*)classes[i]->counter_names[j]); } free(classes[i]); } free(classes); } ucs_status_t ucs_stats_deserialize(FILE *stream, ucs_stats_node_t **p_root) { ucs_stats_data_header_t hdr; ucs_stats_root_storage_t *s; ucs_stats_class_t **classes, *cls; unsigned i, j, num_counters; ucs_status_t status; size_t nread; char *name; nread = fread(&hdr, 1, sizeof(hdr), stream); if (nread == 0) { status = UCS_ERR_NO_ELEM; goto err; } if (hdr.version != 1) { ucs_error("invalid file version"); status = UCS_ERR_UNSUPPORTED; goto err; } if (!(hdr.num_classes < UINT8_MAX)) { ucs_error("invalid num classes"); status = UCS_ERR_OUT_OF_RANGE; goto err; } /* Read classes */ classes = malloc(hdr.num_classes * sizeof(*classes)); for (i = 0; i < hdr.num_classes; ++i) { name = ucs_stats_read_str(stream); FREAD_ONE(&num_counters, stream); /* coverity[tainted_data] */ cls = malloc(sizeof *cls + num_counters * sizeof(cls->counter_names[0])); cls->name = name; cls->num_counters = num_counters; /* coverity[tainted_data] */ for (j = 0; j < cls->num_counters; ++j) { cls->counter_names[j] = ucs_stats_read_str(stream); } classes[i] = cls; } /* Read nodes */ status = ucs_stats_deserialize_recurs(stream, classes, hdr.num_classes, sizeof(ucs_stats_root_storage_t) - sizeof(ucs_stats_node_t), p_root); if (status != UCS_OK) { if (status == UCS_ERR_NO_MESSAGE) { ucs_error("Error parsing statistics - misplaced sentinel"); } goto err_free; } s = ucs_container_of(*p_root, ucs_stats_root_storage_t, node); s->num_classes = hdr.num_classes; s->classes = classes; return UCS_OK; err_free: ucs_stats_free_classes(classes, hdr.num_classes); err: return status; } static void ucs_stats_free_recurs(ucs_stats_node_t *node) { ucs_stats_node_t *child, *tmp; ucs_list_for_each_safe(child, tmp, &node->children[UCS_STATS_ACTIVE_CHILDREN], list) { ucs_stats_free_recurs(child); free(child); } ucs_list_for_each_safe(child, tmp, &node->children[UCS_STATS_INACTIVE_CHILDREN], list) { ucs_stats_free_recurs(child); free(child); } } void ucs_stats_free(ucs_stats_node_t *root) { ucs_stats_root_storage_t *s; s = ucs_container_of(root, ucs_stats_root_storage_t, node); ucs_stats_free_recurs(&s->node); ucs_stats_free_classes(s->classes, s->num_classes); free(s); }