diff mbox series

[v2,2/5] histograms: Add traceeval initialize

Message ID 20230803225413.40697-3-stevie.6strings@gmail.com (mailing list archive)
State Superseded
Headers show
Series histograms: bug fixes and convention compliance | expand

Commit Message

Stevie Alvarez Aug. 3, 2023, 10:54 p.m. UTC
From: "Stevie Alvarez (Google)" <stevie.6strings@gmail.com>

traceeval_init() creates a new struct traceeval instance with regards
to the struct traceeval_type array arguments keys and vals. These arrays
define the structure of the histogram, with each describing the expected
structure of inserted arrays of union traceeval_data. The keys and vals
arguments are copied on the heap to ensure that the struct traceeval
instance has access to the definition regardless of how the user
initialized keys and vals.

Signed-off-by: Stevie Alvarez (Google) <stevie.6strings@gmail.com>
---
 Makefile                 |   2 +-
 include/traceeval-hist.h |   5 +
 src/Makefile             |   1 +
 src/histograms.c         | 223 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 230 insertions(+), 1 deletion(-)
 create mode 100644 src/histograms.c

Comments

Steven Rostedt Aug. 4, 2023, 2:40 p.m. UTC | #1
On Thu,  3 Aug 2023 18:54:00 -0400
Stevie Alvarez <stevie.6strings@gmail.com> wrote:

> From: "Stevie Alvarez (Google)" <stevie.6strings@gmail.com>
> 
> traceeval_init() creates a new struct traceeval instance with regards
> to the struct traceeval_type array arguments keys and vals. These arrays
> define the structure of the histogram, with each describing the expected
> structure of inserted arrays of union traceeval_data. The keys and vals
> arguments are copied on the heap to ensure that the struct traceeval
> instance has access to the definition regardless of how the user
> initialized keys and vals.
> 
> Signed-off-by: Stevie Alvarez (Google) <stevie.6strings@gmail.com>
> ---
>  Makefile                 |   2 +-
>  include/traceeval-hist.h |   5 +
>  src/Makefile             |   1 +
>  src/histograms.c         | 223 +++++++++++++++++++++++++++++++++++++++
>  4 files changed, 230 insertions(+), 1 deletion(-)
>  create mode 100644 src/histograms.c
> 
> diff --git a/Makefile b/Makefile
> index 4a24d5a..3ea051c 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -172,7 +172,7 @@ libs: $(LIBRARY_A) $(LIBRARY_SO)
>  
>  VALGRIND = $(shell which valgrind)
>  UTEST_DIR = utest
> -UTEST_BINARY = trace-utest
> +UTEST_BINARY = eval-utest

This seems like it doesn't belong in this patch.

>  
>  test: force $(LIBRARY_STATIC)
>  ifneq ($(CUNIT_INSTALLED),1)
> diff --git a/include/traceeval-hist.h b/include/traceeval-hist.h
> index 4664974..5bea025 100644
> --- a/include/traceeval-hist.h
> +++ b/include/traceeval-hist.h
> @@ -125,4 +125,9 @@ struct traceeval_iterator;
>  
>  struct traceeval;
>  
> +/* Histogram interfaces */
> +
> +struct traceeval *traceeval_init(const struct traceeval_type *keys,
> +		const struct traceeval_type *vals);
> +
>  #endif /* __LIBTRACEEVAL_HIST_H__ */
> diff --git a/src/Makefile b/src/Makefile
> index b4b6e52..b32a389 100644
> --- a/src/Makefile
> +++ b/src/Makefile
> @@ -4,6 +4,7 @@ include $(src)/scripts/utils.mk
>  
>  OBJS =
>  OBJS += trace-analysis.o
> +OBJS += histograms.o
>  
>  OBJS := $(OBJS:%.o=$(bdir)/%.o)
>  
> diff --git a/src/histograms.c b/src/histograms.c
> new file mode 100644
> index 0000000..be17b89
> --- /dev/null
> +++ b/src/histograms.c
> @@ -0,0 +1,223 @@
> +/* SPDX-License-Identifier: MIT */
> +/*
> + * libtraceeval histogram interface implementation.
> + *
> + * Copyright (C) 2023 Google Inc, Stevie Alvarez <stevie.6strings@gmail.com>
> + */
> +
> +#include <stdbool.h>
> +#include <string.h>
> +#include <stdarg.h>
> +#include <stdio.h>
> +
> +#include <traceeval-hist.h>
> +
> +/* A key-value pair */
> +struct entry {
> +	union traceeval_data	*keys;
> +	union traceeval_data	*vals;
> +};
> +
> +/* A table of key-value entries */
> +struct hist_table {
> +	struct entry	*map;
> +	size_t		nr_entries;
> +};
> +
> +/* Histogram */
> +struct traceeval {
> +	struct traceeval_type		*key_types;
> +	struct traceeval_type		*val_types;
> +	struct hist_table		*hist;
> +	size_t				nr_key_types;
> +	size_t				nr_val_types;
> +};
> +
> +/* Iterate over results of histogram */
> +struct traceeval_iterator {};

Add the traceeval_iterator struct when it is used.

It's not relevant to this patch.

> +
> +/*
> + * print_err() - print an error message
> + * @fmt: String format
> + * @...: (optional) Additional arguments to print in conjunction with @format
> + */
> +static void print_err(const char *fmt, ...)
> +{
> +	va_list ap;
> +
> +	va_start(ap, fmt);
> +	vfprintf(stderr, fmt, ap);
> +	va_end(ap);
> +	fprintf(stderr, "\n");
> +}
> +
> +/*
> + * type_release() - free a struct traceeval_type array
> + * @defs: The array to release
> + * @len: The length of @defs
> + *
> + * It is assumed that all elements of @defs, within the length of @len, have
> + * name fields initialized to valid pointers.
> + *
> + * This function was designed to be used on an array allocated by type_alloc().
> + * Note that type_alloc() initializes all name fields of elements within the
> + * returned size.
> + */
> +static void type_release(struct traceeval_type *defs, size_t len)

You added this but did not add traceeval_release().

traceeval_release() should be added in this patch to clean up everything
that traceeval_init() creates.

> +{
> +	if (!defs)
> +		return;
> +
> +	for (size_t i = 0; i < len; i++) {
> +		free(defs[i].name);
> +	}
> +
> +	free(defs);
> +}
> +
> +/*
> + * type_alloc() - clone a struct traceeval_type array
> + * @defs: The original array
> + * @copy: A pointer to where to place the @defs copy
> + *
> + * Clone traceeval_type array @defs to the heap, and place in @copy.
> + * @defs must be terminated with an instance of type TRACEEVAL_TYPE_NONE.
> + *
> + * The size of the copy pointed to by @copy is returned. It counts all elements
> + * in @defs excluding the terminating TRACEEVAL_TYPE_NONE traceeval_type.
> + * The copy contains everything from @defs excluding the TRACEEVAL_TYPE_NONE
> + * traceeval_type.
> + *
> + * The name field of each element of @defs (except for the terminating
> + * TRACEEVAL_TYPE_NONE) must be a NULL-terminated string. The validity of the
> + * name field is not checked, but errors are returned upon finding unset name
> + * fields and string duplication failures. It is guaranteed that all elements
> + * of the copy within the returned size have valid pointers in their name
> + * fields.
> + *
> + * Returns the size of the array pointed to by @copy, or, if an error occurs,
> + * the negative of the size of what's been allocated so far.
> + * If the return value is negative, the user is responsible for freeing
> + * -1 * return value number of elements from @copy.
> + */
> +static size_t type_alloc(const struct traceeval_type *defs,
> +		struct traceeval_type **copy)
> +{
> +	struct traceeval_type *new_defs = NULL;
> +	size_t size;
> +	size_t i;
> +
> +	if (!defs) {
> +		*copy = NULL;
> +		return 0;
> +	}
> +
> +	for (size = 0; defs && defs[size].type != TRACEEVAL_TYPE_NONE; size++);

For loops like the above, please do:

	for (size = 0; defs && defs[size].type != TRACEEVAL_TYPE_NONE; size++)
		;

This makes it obvious that it's an empty loop and we don't think it's
looping over the line below it.

> +
> +	if (size) {
> +		new_defs = calloc(size, sizeof(*new_defs));
> +	} else {
> +		*copy = NULL;
> +		return 0;
> +	}
> +
> +	for (i = 0; i < size; i++) {
> +		/* copy current def data to new_def */
> +		new_defs[i] = defs[i];
> +
> +		/* copy name to heap, ensures name copied */
> +		if (!defs[i].name)
> +			goto fail_type_name;

No need to be so verbose in the labels.

			goto fail;

is good enough ;-)

> +		new_defs[i].name = strdup(defs[i].name);
> +
> +		if (!new_defs[i].name)
> +			goto fail_type_name;
> +	}
> +
> +	*copy = new_defs;
> +	return size;
> +
> +fail_type_name:
> +	if (defs[size].name)
> +		print_err("failed to allocate name for traceeval_type %s", defs[size].name);
> +	print_err("failed to allocate name for traceeval_type index %zu", size);

I would change the above to:

	if (defs[i].name)
		print_err("Failed to allocate traceveal_type %zu", size);
	else
		print_err("traceeval_type list missing a name");

> +	*copy = new_defs;
> +	return i * -1;

Also, we need to clean up here and not pass that info back to the caller.

// need to make i: ssize_t i;

	for (; i >= 0; i--)
		free(new_defs[i].name);
	free(new_defs);
	*copy = NULL;
	return -1;


> +}
> +
> +/*
> + * traceeval_init - create a traceeval descriptor
> + * @keys: Defines the keys to differentiate traceeval entries
> + * @vals: Defines values attached to entries differentiated by @keys.
> + *
> + * The @keys and @vals define how the traceeval instance will be populated.
> + * The @keys will be used by traceeval_query() to find an instance within
> + * the "histogram". Note, both the @keys and @vals array must end with:
> + * { .type = TRACEEVAL_TYPE_NONE }.
> + *
> + * The @keys and @vals passed in are copied for internal use.
> + *
> + * For any member of @keys or @vals that isn't of type TRACEEVAL_TYPE_NONE,
> + * the name field must be a null-terminated string. For members of type
> + * TRACEEVAL_TYPE_NONE, the name is ignored.
> + *
> + * @vals can be NULL or start with its type field as TRACEEVAL_TYPE_NONE to
> + * define the values of the histogram to be empty.
> + * @keys must be populated with at least one element that is not
> + * TRACEEVAL_TYPE_NONE.
> + *
> + * Returns the descriptor on success, or NULL on error.
> + */
> +struct traceeval *traceeval_init(const struct traceeval_type *keys,
> +				 const struct traceeval_type *vals)
> +{
> +	struct traceeval *teval;
> +	char *err_msg;
> +
> +	if (!keys)
> +		return NULL;
> +
> +	if (keys->type == TRACEEVAL_TYPE_NONE) {
> +		err_msg = "keys cannot start with type TRACEEVAL_TYPE_NONE";

BTW, all the error messages should start with a capital letter.

			"Keys cannot .."

> +		goto fail_init_unalloced;

Just make this: goto fail;

> +	}
> +
> +	/* alloc teval */
> +	teval = calloc(1, sizeof(*teval));
> +	if (!teval) {
> +		err_msg = "failed to allocate memory for traceeval instance";
> +		goto fail_init_unalloced;
> +	}
> +
> +	/* alloc key types */
> +	teval->nr_key_types = type_alloc(keys, &teval->key_types);
> +	if (teval->nr_key_types <= 0) {
> +		teval->nr_key_types *= -1;

Get rid of the above setting.

> +		err_msg = "failed to allocate user defined keys";
> +		goto fail_init;

and this: goto fail_release;

> +	}
> +
> +	/* alloc val types */
> +	teval->nr_val_types = type_alloc(vals, &teval->val_types);
> +	if (teval->nr_val_types < 0) {
> +		teval->nr_val_types *= -1;

And git rid of the above too.

> +		err_msg = "failed to allocate user defined values";
> +		goto fail_init;
> +	}
> +
> +	/* alloc hist */
> +	teval->hist = calloc(1, sizeof(*teval->hist));
> +	if (!teval->hist) {
> +		err_msg = "failed to allocate memory for histogram";
> +		goto fail_init;
> +	}
> +
> +	return teval;
> +
> +fail_init:
> +	traceeval_release(teval);

The above isn't defined. If you reference a function in a patch, it must be
at least defined.

> +
> +fail_init_unalloced:
> +	print_err(err_msg);
> +	return NULL;
> +}

-- Steve
Stevie Alvarez Aug. 4, 2023, 6:23 p.m. UTC | #2
On Fri, Aug 04, 2023 at 10:40:39AM -0400, Steven Rostedt wrote:
> On Thu,  3 Aug 2023 18:54:00 -0400
> Stevie Alvarez <stevie.6strings@gmail.com> wrote:
> 
> > From: "Stevie Alvarez (Google)" <stevie.6strings@gmail.com>
> > 
> > traceeval_init() creates a new struct traceeval instance with regards
> > to the struct traceeval_type array arguments keys and vals. These arrays
> > define the structure of the histogram, with each describing the expected
> > structure of inserted arrays of union traceeval_data. The keys and vals
> > arguments are copied on the heap to ensure that the struct traceeval
> > instance has access to the definition regardless of how the user
> > initialized keys and vals.
> > 
> > Signed-off-by: Stevie Alvarez (Google) <stevie.6strings@gmail.com>
> > ---
> >  Makefile                 |   2 +-
> >  include/traceeval-hist.h |   5 +
> >  src/Makefile             |   1 +
> >  src/histograms.c         | 223 +++++++++++++++++++++++++++++++++++++++
> >  4 files changed, 230 insertions(+), 1 deletion(-)
> >  create mode 100644 src/histograms.c
> > 
> > diff --git a/Makefile b/Makefile
> > index 4a24d5a..3ea051c 100644
> > --- a/Makefile
> > +++ b/Makefile
> > @@ -172,7 +172,7 @@ libs: $(LIBRARY_A) $(LIBRARY_SO)
> >  
> >  VALGRIND = $(shell which valgrind)
> >  UTEST_DIR = utest
> > -UTEST_BINARY = trace-utest
> > +UTEST_BINARY = eval-utest
> 
> This seems like it doesn't belong in this patch.
> 
> >  
> >  test: force $(LIBRARY_STATIC)
> >  ifneq ($(CUNIT_INSTALLED),1)
> > diff --git a/include/traceeval-hist.h b/include/traceeval-hist.h
> > index 4664974..5bea025 100644
> > --- a/include/traceeval-hist.h
> > +++ b/include/traceeval-hist.h
> > @@ -125,4 +125,9 @@ struct traceeval_iterator;
> >  
> >  struct traceeval;
> >  
> > +/* Histogram interfaces */
> > +
> > +struct traceeval *traceeval_init(const struct traceeval_type *keys,
> > +		const struct traceeval_type *vals);
> > +
> >  #endif /* __LIBTRACEEVAL_HIST_H__ */
> > diff --git a/src/Makefile b/src/Makefile
> > index b4b6e52..b32a389 100644
> > --- a/src/Makefile
> > +++ b/src/Makefile
> > @@ -4,6 +4,7 @@ include $(src)/scripts/utils.mk
> >  
> >  OBJS =
> >  OBJS += trace-analysis.o
> > +OBJS += histograms.o
> >  
> >  OBJS := $(OBJS:%.o=$(bdir)/%.o)
> >  
> > diff --git a/src/histograms.c b/src/histograms.c
> > new file mode 100644
> > index 0000000..be17b89
> > --- /dev/null
> > +++ b/src/histograms.c
> > @@ -0,0 +1,223 @@
> > +/* SPDX-License-Identifier: MIT */
> > +/*
> > + * libtraceeval histogram interface implementation.
> > + *
> > + * Copyright (C) 2023 Google Inc, Stevie Alvarez <stevie.6strings@gmail.com>
> > + */
> > +
> > +#include <stdbool.h>
> > +#include <string.h>
> > +#include <stdarg.h>
> > +#include <stdio.h>
> > +
> > +#include <traceeval-hist.h>
> > +
> > +/* A key-value pair */
> > +struct entry {
> > +	union traceeval_data	*keys;
> > +	union traceeval_data	*vals;
> > +};
> > +
> > +/* A table of key-value entries */
> > +struct hist_table {
> > +	struct entry	*map;
> > +	size_t		nr_entries;
> > +};
> > +
> > +/* Histogram */
> > +struct traceeval {
> > +	struct traceeval_type		*key_types;
> > +	struct traceeval_type		*val_types;
> > +	struct hist_table		*hist;
> > +	size_t				nr_key_types;
> > +	size_t				nr_val_types;
> > +};
> > +
> > +/* Iterate over results of histogram */
> > +struct traceeval_iterator {};
> 
> Add the traceeval_iterator struct when it is used.
> 
> It's not relevant to this patch.
> 
> > +
> > +/*
> > + * print_err() - print an error message
> > + * @fmt: String format
> > + * @...: (optional) Additional arguments to print in conjunction with @format
> > + */
> > +static void print_err(const char *fmt, ...)
> > +{
> > +	va_list ap;
> > +
> > +	va_start(ap, fmt);
> > +	vfprintf(stderr, fmt, ap);
> > +	va_end(ap);
> > +	fprintf(stderr, "\n");
> > +}
> > +
> > +/*
> > + * type_release() - free a struct traceeval_type array
> > + * @defs: The array to release
> > + * @len: The length of @defs
> > + *
> > + * It is assumed that all elements of @defs, within the length of @len, have
> > + * name fields initialized to valid pointers.
> > + *
> > + * This function was designed to be used on an array allocated by type_alloc().
> > + * Note that type_alloc() initializes all name fields of elements within the
> > + * returned size.
> > + */
> > +static void type_release(struct traceeval_type *defs, size_t len)
> 
> You added this but did not add traceeval_release().
> 
> traceeval_release() should be added in this patch to clean up everything
> that traceeval_init() creates.
> 
> > +{
> > +	if (!defs)
> > +		return;
> > +
> > +	for (size_t i = 0; i < len; i++) {
> > +		free(defs[i].name);
> > +	}
> > +
> > +	free(defs);
> > +}
> > +
> > +/*
> > + * type_alloc() - clone a struct traceeval_type array
> > + * @defs: The original array
> > + * @copy: A pointer to where to place the @defs copy
> > + *
> > + * Clone traceeval_type array @defs to the heap, and place in @copy.
> > + * @defs must be terminated with an instance of type TRACEEVAL_TYPE_NONE.
> > + *
> > + * The size of the copy pointed to by @copy is returned. It counts all elements
> > + * in @defs excluding the terminating TRACEEVAL_TYPE_NONE traceeval_type.
> > + * The copy contains everything from @defs excluding the TRACEEVAL_TYPE_NONE
> > + * traceeval_type.
> > + *
> > + * The name field of each element of @defs (except for the terminating
> > + * TRACEEVAL_TYPE_NONE) must be a NULL-terminated string. The validity of the
> > + * name field is not checked, but errors are returned upon finding unset name
> > + * fields and string duplication failures. It is guaranteed that all elements
> > + * of the copy within the returned size have valid pointers in their name
> > + * fields.
> > + *
> > + * Returns the size of the array pointed to by @copy, or, if an error occurs,
> > + * the negative of the size of what's been allocated so far.
> > + * If the return value is negative, the user is responsible for freeing
> > + * -1 * return value number of elements from @copy.
> > + */
> > +static size_t type_alloc(const struct traceeval_type *defs,
> > +		struct traceeval_type **copy)
> > +{
> > +	struct traceeval_type *new_defs = NULL;
> > +	size_t size;
> > +	size_t i;
> > +
> > +	if (!defs) {
> > +		*copy = NULL;
> > +		return 0;
> > +	}
> > +
> > +	for (size = 0; defs && defs[size].type != TRACEEVAL_TYPE_NONE; size++);
> 
> For loops like the above, please do:
> 
> 	for (size = 0; defs && defs[size].type != TRACEEVAL_TYPE_NONE; size++)
> 		;
> 
> This makes it obvious that it's an empty loop and we don't think it's
> looping over the line below it.
> 
> > +
> > +	if (size) {
> > +		new_defs = calloc(size, sizeof(*new_defs));
> > +	} else {
> > +		*copy = NULL;
> > +		return 0;
> > +	}
> > +
> > +	for (i = 0; i < size; i++) {
> > +		/* copy current def data to new_def */
> > +		new_defs[i] = defs[i];
> > +
> > +		/* copy name to heap, ensures name copied */
> > +		if (!defs[i].name)
> > +			goto fail_type_name;
> 
> No need to be so verbose in the labels.
> 
> 			goto fail;
> 
> is good enough ;-)
> 
> > +		new_defs[i].name = strdup(defs[i].name);
> > +
> > +		if (!new_defs[i].name)
> > +			goto fail_type_name;
> > +	}
> > +
> > +	*copy = new_defs;
> > +	return size;
> > +
> > +fail_type_name:
> > +	if (defs[size].name)
> > +		print_err("failed to allocate name for traceeval_type %s", defs[size].name);
> > +	print_err("failed to allocate name for traceeval_type index %zu", size);
> 
> I would change the above to:
> 
> 	if (defs[i].name)
> 		print_err("Failed to allocate traceveal_type %zu", size);
> 	else
> 		print_err("traceeval_type list missing a name");
> 
> > +	*copy = new_defs;
> > +	return i * -1;
> 
> Also, we need to clean up here and not pass that info back to the caller.
> 
> // need to make i: ssize_t i;
> 
> 	for (; i >= 0; i--)
> 		free(new_defs[i].name);
> 	free(new_defs);
> 	*copy = NULL;
> 	return -1;
> 
> 
> > +}
> > +
> > +/*
> > + * traceeval_init - create a traceeval descriptor
> > + * @keys: Defines the keys to differentiate traceeval entries
> > + * @vals: Defines values attached to entries differentiated by @keys.
> > + *
> > + * The @keys and @vals define how the traceeval instance will be populated.
> > + * The @keys will be used by traceeval_query() to find an instance within
> > + * the "histogram". Note, both the @keys and @vals array must end with:
> > + * { .type = TRACEEVAL_TYPE_NONE }.
> > + *
> > + * The @keys and @vals passed in are copied for internal use.
> > + *
> > + * For any member of @keys or @vals that isn't of type TRACEEVAL_TYPE_NONE,
> > + * the name field must be a null-terminated string. For members of type
> > + * TRACEEVAL_TYPE_NONE, the name is ignored.
> > + *
> > + * @vals can be NULL or start with its type field as TRACEEVAL_TYPE_NONE to
> > + * define the values of the histogram to be empty.
> > + * @keys must be populated with at least one element that is not
> > + * TRACEEVAL_TYPE_NONE.
> > + *
> > + * Returns the descriptor on success, or NULL on error.
> > + */
> > +struct traceeval *traceeval_init(const struct traceeval_type *keys,
> > +				 const struct traceeval_type *vals)
> > +{
> > +	struct traceeval *teval;
> > +	char *err_msg;
> > +
> > +	if (!keys)
> > +		return NULL;
> > +
> > +	if (keys->type == TRACEEVAL_TYPE_NONE) {
> > +		err_msg = "keys cannot start with type TRACEEVAL_TYPE_NONE";
> 
> BTW, all the error messages should start with a capital letter.
> 
> 			"Keys cannot .."
> 
> > +		goto fail_init_unalloced;
> 
> Just make this: goto fail;

Having multiple of the same label segfaults my tests. I'm relatively new
to using C labels, is that behavior expected? And if so is it acceptable
for me to distingush the two fail labels by a single character?

-- Stevie

> 
> > +	}
> > +
> > +	/* alloc teval */
> > +	teval = calloc(1, sizeof(*teval));
> > +	if (!teval) {
> > +		err_msg = "failed to allocate memory for traceeval instance";
> > +		goto fail_init_unalloced;
> > +	}
> > +
> > +	/* alloc key types */
> > +	teval->nr_key_types = type_alloc(keys, &teval->key_types);
> > +	if (teval->nr_key_types <= 0) {
> > +		teval->nr_key_types *= -1;
> 
> Get rid of the above setting.
> 
> > +		err_msg = "failed to allocate user defined keys";
> > +		goto fail_init;
> 
> and this: goto fail_release;
> 
> > +	}
> > +
> > +	/* alloc val types */
> > +	teval->nr_val_types = type_alloc(vals, &teval->val_types);
> > +	if (teval->nr_val_types < 0) {
> > +		teval->nr_val_types *= -1;
> 
> And git rid of the above too.
> 
> > +		err_msg = "failed to allocate user defined values";
> > +		goto fail_init;
> > +	}
> > +
> > +	/* alloc hist */
> > +	teval->hist = calloc(1, sizeof(*teval->hist));
> > +	if (!teval->hist) {
> > +		err_msg = "failed to allocate memory for histogram";
> > +		goto fail_init;
> > +	}
> > +
> > +	return teval;
> > +
> > +fail_init:
> > +	traceeval_release(teval);
> 
> The above isn't defined. If you reference a function in a patch, it must be
> at least defined.
> 
> > +
> > +fail_init_unalloced:
> > +	print_err(err_msg);
> > +	return NULL;
> > +}
> 
> -- Steve
Steven Rostedt Aug. 4, 2023, 7:20 p.m. UTC | #3
On Fri, 4 Aug 2023 14:23:11 -0400
Stevie Alvarez <stevie.6strings@gmail.com> wrote:

> > > + * traceeval_init - create a traceeval descriptor
> > > + * @keys: Defines the keys to differentiate traceeval entries
> > > + * @vals: Defines values attached to entries differentiated by @keys.
> > > + *
> > > + * The @keys and @vals define how the traceeval instance will be populated.
> > > + * The @keys will be used by traceeval_query() to find an instance within
> > > + * the "histogram". Note, both the @keys and @vals array must end with:
> > > + * { .type = TRACEEVAL_TYPE_NONE }.
> > > + *
> > > + * The @keys and @vals passed in are copied for internal use.
> > > + *
> > > + * For any member of @keys or @vals that isn't of type TRACEEVAL_TYPE_NONE,
> > > + * the name field must be a null-terminated string. For members of type
> > > + * TRACEEVAL_TYPE_NONE, the name is ignored.
> > > + *
> > > + * @vals can be NULL or start with its type field as TRACEEVAL_TYPE_NONE to
> > > + * define the values of the histogram to be empty.
> > > + * @keys must be populated with at least one element that is not
> > > + * TRACEEVAL_TYPE_NONE.
> > > + *
> > > + * Returns the descriptor on success, or NULL on error.
> > > + */
> > > +struct traceeval *traceeval_init(const struct traceeval_type *keys,
> > > +				 const struct traceeval_type *vals)
> > > +{
> > > +	struct traceeval *teval;
> > > +	char *err_msg;
> > > +
> > > +	if (!keys)
> > > +		return NULL;
> > > +
> > > +	if (keys->type == TRACEEVAL_TYPE_NONE) {
> > > +		err_msg = "keys cannot start with type TRACEEVAL_TYPE_NONE";  
> > 
> > BTW, all the error messages should start with a capital letter.
> > 
> > 			"Keys cannot .."
> >   
> > > +		goto fail_init_unalloced;  
> > 
> > Just make this: goto fail;  
> 
> Having multiple of the same label segfaults my tests. I'm relatively new
> to using C labels, is that behavior expected? And if so is it acceptable
> for me to distingush the two fail labels by a single character?

labels must be unique per function. But I constantly use  the same label
multiple times per C file.

As I stated in the email, convert one to "fail:" and the other one to "fail_release:"

> 
> -- Stevie
> 
> >   
> > > +	}
> > > +
> > > +	/* alloc teval */
> > > +	teval = calloc(1, sizeof(*teval));
> > > +	if (!teval) {
> > > +		err_msg = "failed to allocate memory for traceeval instance";
> > > +		goto fail_init_unalloced;
> > > +	}
> > > +
> > > +	/* alloc key types */
> > > +	teval->nr_key_types = type_alloc(keys, &teval->key_types);
> > > +	if (teval->nr_key_types <= 0) {
> > > +		teval->nr_key_types *= -1;  
> > 
> > Get rid of the above setting.
> >   
> > > +		err_msg = "failed to allocate user defined keys";
> > > +		goto fail_init;  
> > 
> > and this: goto fail_release;
> >   
> > > +	}
> > > +
> > > +	/* alloc val types */
> > > +	teval->nr_val_types = type_alloc(vals, &teval->val_types);
> > > +	if (teval->nr_val_types < 0) {
> > > +		teval->nr_val_types *= -1;  
> > 
> > And git rid of the above too.
> >   
> > > +		err_msg = "failed to allocate user defined values";
> > > +		goto fail_init;
> > > +	}
> > > +
> > > +	/* alloc hist */
> > > +	teval->hist = calloc(1, sizeof(*teval->hist));
> > > +	if (!teval->hist) {
> > > +		err_msg = "failed to allocate memory for histogram";
> > > +		goto fail_init;
> > > +	}
> > > +
> > > +	return teval;
> > > +
> > > +fail_init:

This would be

 fail_release:

as it releases the teval.

> > > +	traceeval_release(teval);  
> > 
> > The above isn't defined. If you reference a function in a patch, it must be
> > at least defined.
> >   
> > > +
> > > +fail_init_unalloced:

and this one be

 fail:

As it is part of the fail path for both.

-- Steve


> > > +	print_err(err_msg);
> > > +	return NULL;
> > > +}  
> >
diff mbox series

Patch

diff --git a/Makefile b/Makefile
index 4a24d5a..3ea051c 100644
--- a/Makefile
+++ b/Makefile
@@ -172,7 +172,7 @@  libs: $(LIBRARY_A) $(LIBRARY_SO)
 
 VALGRIND = $(shell which valgrind)
 UTEST_DIR = utest
-UTEST_BINARY = trace-utest
+UTEST_BINARY = eval-utest
 
 test: force $(LIBRARY_STATIC)
 ifneq ($(CUNIT_INSTALLED),1)
diff --git a/include/traceeval-hist.h b/include/traceeval-hist.h
index 4664974..5bea025 100644
--- a/include/traceeval-hist.h
+++ b/include/traceeval-hist.h
@@ -125,4 +125,9 @@  struct traceeval_iterator;
 
 struct traceeval;
 
+/* Histogram interfaces */
+
+struct traceeval *traceeval_init(const struct traceeval_type *keys,
+		const struct traceeval_type *vals);
+
 #endif /* __LIBTRACEEVAL_HIST_H__ */
diff --git a/src/Makefile b/src/Makefile
index b4b6e52..b32a389 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -4,6 +4,7 @@  include $(src)/scripts/utils.mk
 
 OBJS =
 OBJS += trace-analysis.o
+OBJS += histograms.o
 
 OBJS := $(OBJS:%.o=$(bdir)/%.o)
 
diff --git a/src/histograms.c b/src/histograms.c
new file mode 100644
index 0000000..be17b89
--- /dev/null
+++ b/src/histograms.c
@@ -0,0 +1,223 @@ 
+/* SPDX-License-Identifier: MIT */
+/*
+ * libtraceeval histogram interface implementation.
+ *
+ * Copyright (C) 2023 Google Inc, Stevie Alvarez <stevie.6strings@gmail.com>
+ */
+
+#include <stdbool.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <traceeval-hist.h>
+
+/* A key-value pair */
+struct entry {
+	union traceeval_data	*keys;
+	union traceeval_data	*vals;
+};
+
+/* A table of key-value entries */
+struct hist_table {
+	struct entry	*map;
+	size_t		nr_entries;
+};
+
+/* Histogram */
+struct traceeval {
+	struct traceeval_type		*key_types;
+	struct traceeval_type		*val_types;
+	struct hist_table		*hist;
+	size_t				nr_key_types;
+	size_t				nr_val_types;
+};
+
+/* Iterate over results of histogram */
+struct traceeval_iterator {};
+
+/*
+ * print_err() - print an error message
+ * @fmt: String format
+ * @...: (optional) Additional arguments to print in conjunction with @format
+ */
+static void print_err(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	fprintf(stderr, "\n");
+}
+
+/*
+ * type_release() - free a struct traceeval_type array
+ * @defs: The array to release
+ * @len: The length of @defs
+ *
+ * It is assumed that all elements of @defs, within the length of @len, have
+ * name fields initialized to valid pointers.
+ *
+ * This function was designed to be used on an array allocated by type_alloc().
+ * Note that type_alloc() initializes all name fields of elements within the
+ * returned size.
+ */
+static void type_release(struct traceeval_type *defs, size_t len)
+{
+	if (!defs)
+		return;
+
+	for (size_t i = 0; i < len; i++) {
+		free(defs[i].name);
+	}
+
+	free(defs);
+}
+
+/*
+ * type_alloc() - clone a struct traceeval_type array
+ * @defs: The original array
+ * @copy: A pointer to where to place the @defs copy
+ *
+ * Clone traceeval_type array @defs to the heap, and place in @copy.
+ * @defs must be terminated with an instance of type TRACEEVAL_TYPE_NONE.
+ *
+ * The size of the copy pointed to by @copy is returned. It counts all elements
+ * in @defs excluding the terminating TRACEEVAL_TYPE_NONE traceeval_type.
+ * The copy contains everything from @defs excluding the TRACEEVAL_TYPE_NONE
+ * traceeval_type.
+ *
+ * The name field of each element of @defs (except for the terminating
+ * TRACEEVAL_TYPE_NONE) must be a NULL-terminated string. The validity of the
+ * name field is not checked, but errors are returned upon finding unset name
+ * fields and string duplication failures. It is guaranteed that all elements
+ * of the copy within the returned size have valid pointers in their name
+ * fields.
+ *
+ * Returns the size of the array pointed to by @copy, or, if an error occurs,
+ * the negative of the size of what's been allocated so far.
+ * If the return value is negative, the user is responsible for freeing
+ * -1 * return value number of elements from @copy.
+ */
+static size_t type_alloc(const struct traceeval_type *defs,
+		struct traceeval_type **copy)
+{
+	struct traceeval_type *new_defs = NULL;
+	size_t size;
+	size_t i;
+
+	if (!defs) {
+		*copy = NULL;
+		return 0;
+	}
+
+	for (size = 0; defs && defs[size].type != TRACEEVAL_TYPE_NONE; size++);
+
+	if (size) {
+		new_defs = calloc(size, sizeof(*new_defs));
+	} else {
+		*copy = NULL;
+		return 0;
+	}
+
+	for (i = 0; i < size; i++) {
+		/* copy current def data to new_def */
+		new_defs[i] = defs[i];
+
+		/* copy name to heap, ensures name copied */
+		if (!defs[i].name)
+			goto fail_type_name;
+		new_defs[i].name = strdup(defs[i].name);
+
+		if (!new_defs[i].name)
+			goto fail_type_name;
+	}
+
+	*copy = new_defs;
+	return size;
+
+fail_type_name:
+	if (defs[size].name)
+		print_err("failed to allocate name for traceeval_type %s", defs[size].name);
+	print_err("failed to allocate name for traceeval_type index %zu", size);
+	*copy = new_defs;
+	return i * -1;
+}
+
+/*
+ * traceeval_init - create a traceeval descriptor
+ * @keys: Defines the keys to differentiate traceeval entries
+ * @vals: Defines values attached to entries differentiated by @keys.
+ *
+ * The @keys and @vals define how the traceeval instance will be populated.
+ * The @keys will be used by traceeval_query() to find an instance within
+ * the "histogram". Note, both the @keys and @vals array must end with:
+ * { .type = TRACEEVAL_TYPE_NONE }.
+ *
+ * The @keys and @vals passed in are copied for internal use.
+ *
+ * For any member of @keys or @vals that isn't of type TRACEEVAL_TYPE_NONE,
+ * the name field must be a null-terminated string. For members of type
+ * TRACEEVAL_TYPE_NONE, the name is ignored.
+ *
+ * @vals can be NULL or start with its type field as TRACEEVAL_TYPE_NONE to
+ * define the values of the histogram to be empty.
+ * @keys must be populated with at least one element that is not
+ * TRACEEVAL_TYPE_NONE.
+ *
+ * Returns the descriptor on success, or NULL on error.
+ */
+struct traceeval *traceeval_init(const struct traceeval_type *keys,
+				 const struct traceeval_type *vals)
+{
+	struct traceeval *teval;
+	char *err_msg;
+
+	if (!keys)
+		return NULL;
+
+	if (keys->type == TRACEEVAL_TYPE_NONE) {
+		err_msg = "keys cannot start with type TRACEEVAL_TYPE_NONE";
+		goto fail_init_unalloced;
+	}
+
+	/* alloc teval */
+	teval = calloc(1, sizeof(*teval));
+	if (!teval) {
+		err_msg = "failed to allocate memory for traceeval instance";
+		goto fail_init_unalloced;
+	}
+
+	/* alloc key types */
+	teval->nr_key_types = type_alloc(keys, &teval->key_types);
+	if (teval->nr_key_types <= 0) {
+		teval->nr_key_types *= -1;
+		err_msg = "failed to allocate user defined keys";
+		goto fail_init;
+	}
+
+	/* alloc val types */
+	teval->nr_val_types = type_alloc(vals, &teval->val_types);
+	if (teval->nr_val_types < 0) {
+		teval->nr_val_types *= -1;
+		err_msg = "failed to allocate user defined values";
+		goto fail_init;
+	}
+
+	/* alloc hist */
+	teval->hist = calloc(1, sizeof(*teval->hist));
+	if (!teval->hist) {
+		err_msg = "failed to allocate memory for histogram";
+		goto fail_init;
+	}
+
+	return teval;
+
+fail_init:
+	traceeval_release(teval);
+
+fail_init_unalloced:
+	print_err(err_msg);
+	return NULL;
+}