diff --git a/src/common.h b/src/common.h
index 9eee563..eacea7d 100644
--- a/src/common.h
+++ b/src/common.h
@@ -26,10 +26,9 @@ static const char *NO_ARG = "/NONE/";
 
 #define TC_NIL_MSG	"(nil)"
 
-static inline bool is_all(const char *arg)
+static inline bool lax_cmp(const char *arg, const char *model)
 {
-	const char all[] = "all";
-	const char *p = &all[0];
+	const char *p = &model[0];
 
 	for (; *arg; arg++, p++) {
 		if (tolower(*arg) != *p)
@@ -38,6 +37,11 @@ static inline bool is_all(const char *arg)
 	return !(*p);
 }
 
+static inline bool is_all(const char *arg)
+{
+	return lax_cmp(arg, "all");
+}
+
 static inline bool is_no_arg(const char *arg)
 {
 	return arg[0] == '\0' || arg == NO_ARG;
diff --git a/src/ftracepy-utils.c b/src/ftracepy-utils.c
index 7f3daca..65aca81 100644
--- a/src/ftracepy-utils.c
+++ b/src/ftracepy-utils.c
@@ -1954,6 +1954,201 @@ PyObject *PyDynevent_is_enabled(PyDynevent *self, PyObject *args,
 	return ret;
 }
 
+static enum tracefs_hist_key_type hist_key_type(PyObject * py_type)
+{
+	int type = -1;
+
+	if (PyUnicode_Check(py_type)) {
+		const char *type_str = PyUnicode_DATA(py_type);
+
+		if (lax_cmp(type_str, "normal") ||
+		    lax_cmp(type_str, "n") )
+			type = TRACEFS_HIST_KEY_NORMAL;
+		else if (lax_cmp(type_str, "hex") ||
+			 lax_cmp(type_str, "h") )
+			type = TRACEFS_HIST_KEY_HEX;
+		else if (lax_cmp(type_str, "sym"))
+			 type = TRACEFS_HIST_KEY_SYM;
+		else if (lax_cmp(type_str, "sym_offset") ||
+			 lax_cmp(type_str, "so"))
+			type = TRACEFS_HIST_KEY_SYM_OFFSET;
+		else if (lax_cmp(type_str, "syscall") ||
+			 lax_cmp(type_str, "sc"))
+			type = TRACEFS_HIST_KEY_SYSCALL;
+		else if (lax_cmp(type_str, "execname") ||
+			 lax_cmp(type_str, "e"))
+			 type = TRACEFS_HIST_KEY_EXECNAME;
+		else if (lax_cmp(type_str, "log") ||
+			 lax_cmp(type_str, "l"))
+			type = TRACEFS_HIST_KEY_LOG;
+		else if (lax_cmp(type_str, "users") ||
+			 lax_cmp(type_str, "u"))
+			type = TRACEFS_HIST_KEY_USECS;
+		else if (lax_cmp(type_str, "max") ||
+			 lax_cmp(type_str, "m"))
+			type = TRACEFS_HIST_KEY_MAX;
+		else {
+			TfsError_fmt(NULL, "Unknown axis type %s\n",
+				     type_str);
+		}
+	} else if (PyLong_CheckExact(py_type)) {
+		type = PyLong_AsLong(py_type);
+	} else {
+		TfsError_fmt(NULL, "Unknown axis type %s\n");
+	}
+
+	return type;
+}
+
+static struct tracefs_hist *hist_from_key(struct tep_handle *tep,
+					  const char *system, const char *event,
+					  PyObject *py_key, PyObject * py_type)
+{
+	struct tracefs_hist *hist = NULL;
+	int type = 0;
+
+	if (PyUnicode_Check(py_key)) {
+		if (py_type) {
+			type = hist_key_type(py_type);
+			if (type < 0)
+				return NULL;
+		}
+
+		hist = tracefs_hist_alloc(tep, system, event,
+					  PyUnicode_DATA(py_key), type);
+	} else if (PyList_CheckExact(py_key)) {
+		int i, n_keys = PyList_Size(py_key);
+		struct tracefs_hist_axis *axes;
+		PyObject *item_type;
+
+		if (py_type) {
+			/*
+			 * The format of the types have to match the format
+			 * of the keys.
+			 */
+			if (!PyList_CheckExact(py_key) ||
+			    PyList_Size(py_type) != n_keys)
+				return NULL;
+		}
+
+		axes = calloc(n_keys + 1, sizeof(*axes));
+		if (!axes) {
+			MEM_ERROR
+			return NULL;
+		}
+
+		for (i = 0; i < n_keys; ++i) {
+			axes[i].key = str_from_list(py_key, i);
+			if (!axes[i].key)
+				return NULL;
+
+			if (py_type) {
+				item_type = PyList_GetItem(py_type, i);
+				if (!PyLong_CheckExact(item_type))
+					return NULL;
+
+				type = hist_key_type(item_type);
+				if (type < 0)
+					return NULL;
+
+				axes[i].type = type;
+			}
+		}
+
+		hist = tracefs_hist_alloc_nd(tep, system, event, axes);
+		free(axes);
+	}
+
+	return hist;
+}
+
+static struct tracefs_hist *hist_from_axis(struct tep_handle *tep,
+					   const char *system, const char *event,
+					   PyObject *py_axes)
+{
+	struct tracefs_hist *hist = NULL;
+	struct tracefs_hist_axis *axes;
+	PyObject *key, *value;
+	Py_ssize_t i = 0;
+	int n_axes;
+
+	n_axes = PyDict_Size(py_axes);
+	if (PyErr_Occurred())
+		return NULL;
+
+	axes = calloc(n_axes + 1, sizeof(*axes));
+	if (!axes) {
+		MEM_ERROR
+		return NULL;
+	}
+
+	while (PyDict_Next(py_axes, &i, &key, &value)) {
+		axes[i - 1].key = PyUnicode_DATA(key);
+		axes[i - 1].type = hist_key_type(value);
+		if (PyErr_Occurred()) {
+			PyErr_Print();
+			free(axes);
+			return NULL;
+		}
+	}
+
+	hist = tracefs_hist_alloc_nd(tep, system, event, axes);
+	free(axes);
+
+	return hist;
+}
+
+PyObject *PyFtrace_hist(PyObject *self, PyObject *args,
+					PyObject *kwargs)
+{
+	static char *kwlist[] =
+		{"system", "event", "key", "type", "axes", "name", NULL};
+	PyObject *py_axes = NULL, *py_key = NULL, *py_type = NULL;
+	const char *system, *event, *name = NULL;
+	struct tracefs_hist *hist = NULL;
+	struct tep_handle *tep;
+
+	if (!PyArg_ParseTupleAndKeywords(args,
+					 kwargs,
+					 "ss|OOOs",
+					 kwlist,
+					 &system,
+					 &event,
+					 &py_key,
+					 &py_type,
+					 &py_axes,
+					 &name)) {
+		return NULL;
+	}
+
+	tep = tracefs_local_events(tracefs_tracing_dir());
+	if (!tep)
+		goto fail;
+
+	if (py_key && ! py_axes) {
+		hist = hist_from_key(tep, system, event, py_key, py_type);
+	} else if (!py_key && py_axes) {
+		hist = hist_from_axis(tep, system, event, py_axes);
+	} else {
+		TfsError_setstr(NULL, "'key' or 'axis' must be provided.");
+		return NULL;
+	}
+
+	if (!hist)
+		goto fail;
+
+	if (name && tracefs_hist_add_name(hist, name) < 0)
+		goto fail;
+
+	return PyTraceHist_New(hist);
+
+ fail:
+	TfsError_fmt(NULL, "Failed to create histogram for %s/%s",
+		     system, event);
+	tracefs_hist_free(hist);
+	return NULL;
+}
+
 PyObject *PyFtrace_set_ftrace_loglevel(PyObject *self, PyObject *args,
 						       PyObject *kwargs)
 {
diff --git a/src/ftracepy-utils.h b/src/ftracepy-utils.h
index da31998..f69a6d4 100644
--- a/src/ftracepy-utils.h
+++ b/src/ftracepy-utils.h
@@ -178,6 +178,9 @@ PyObject *PyFtrace_register_kprobe(PyObject *self, PyObject *args,
 PyObject *PyFtrace_register_kretprobe(PyObject *self, PyObject *args,
 						      PyObject *kwargs);
 
+PyObject *PyFtrace_hist(PyObject *self, PyObject *args,
+					PyObject *kwargs);
+
 PyObject *PyFtrace_set_ftrace_loglevel(PyObject *self, PyObject *args,
 						       PyObject *kwargs);
 
diff --git a/src/ftracepy.c b/src/ftracepy.c
index 0f06796..7891b5e 100644
--- a/src/ftracepy.c
+++ b/src/ftracepy.c
@@ -327,6 +327,11 @@ static PyMethodDef ftracepy_methods[] = {
 	 METH_VARARGS | METH_KEYWORDS,
 	 "Define a kretprobe."
 	},
+	{"hist",
+	 (PyCFunction) PyFtrace_hist,
+	 METH_VARARGS | METH_KEYWORDS,
+	 "Define a histogram."
+	},
 	{"set_ftrace_loglevel",
 	 (PyCFunction) PyFtrace_set_ftrace_loglevel,
 	 METH_VARARGS | METH_KEYWORDS,
