diff options
Diffstat (limited to 'tools/perf/util/python.c')
| -rw-r--r-- | tools/perf/util/python.c | 522 | 
1 files changed, 509 insertions, 13 deletions
| diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index ea77bea0306f..779fe1280a56 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -14,10 +14,12 @@  #include "evlist.h"  #include "evsel.h"  #include "event.h" +#include "expr.h"  #include "print_binary.h"  #include "record.h"  #include "strbuf.h"  #include "thread_map.h" +#include "tp_pmu.h"  #include "trace-event.h"  #include "metricgroup.h"  #include "mmap.h" @@ -485,13 +487,19 @@ static PyObject *pyrf_event__new(const union perf_event *event)  	if ((event->header.type < PERF_RECORD_MMAP ||  	     event->header.type > PERF_RECORD_SAMPLE) &&  	    !(event->header.type == PERF_RECORD_SWITCH || -	      event->header.type == PERF_RECORD_SWITCH_CPU_WIDE)) +	      event->header.type == PERF_RECORD_SWITCH_CPU_WIDE)) { +		PyErr_Format(PyExc_TypeError, "Unexpected header type %u", +			     event->header.type);  		return NULL; +	}  	// FIXME this better be dynamic or we need to parse everything  	// before calling perf_mmap__consume(), including tracepoint fields. -	if (sizeof(pevent->event) < event->header.size) +	if (sizeof(pevent->event) < event->header.size) { +		PyErr_Format(PyExc_TypeError, "Unexpected event size: %zd < %u", +			     sizeof(pevent->event), event->header.size);  		return NULL; +	}  	ptype = pyrf_event__type[event->header.type];  	pevent = PyObject_New(struct pyrf_event, ptype); @@ -642,6 +650,209 @@ static int pyrf_thread_map__setup_types(void)  	return PyType_Ready(&pyrf_thread_map__type);  } +/** + * A python wrapper for perf_pmus that are globally owned by the pmus.c code. + */ +struct pyrf_pmu { +	PyObject_HEAD + +	struct perf_pmu *pmu; +}; + +static void pyrf_pmu__delete(struct pyrf_pmu *ppmu) +{ +	Py_TYPE(ppmu)->tp_free((PyObject *)ppmu); +} + +static PyObject *pyrf_pmu__name(PyObject *self) +{ +	struct pyrf_pmu *ppmu = (void *)self; + +	return PyUnicode_FromString(ppmu->pmu->name); +} + +static bool add_to_dict(PyObject *dict, const char *key, const char *value) +{ +	PyObject *pkey, *pvalue; +	bool ret; + +	if (value == NULL) +		return true; + +	pkey = PyUnicode_FromString(key); +	pvalue = PyUnicode_FromString(value); + +	ret = pkey && pvalue && PyDict_SetItem(dict, pkey, pvalue) == 0; +	Py_XDECREF(pkey); +	Py_XDECREF(pvalue); +	return ret; +} + +static int pyrf_pmu__events_cb(void *state, struct pmu_event_info *info) +{ +	PyObject *py_list = state; +	PyObject *dict = PyDict_New(); + +	if (!dict) +		return -ENOMEM; + +	if (!add_to_dict(dict, "name", info->name) || +	    !add_to_dict(dict, "alias", info->alias) || +	    !add_to_dict(dict, "scale_unit", info->scale_unit) || +	    !add_to_dict(dict, "desc", info->desc) || +	    !add_to_dict(dict, "long_desc", info->long_desc) || +	    !add_to_dict(dict, "encoding_desc", info->encoding_desc) || +	    !add_to_dict(dict, "topic", info->topic) || +	    !add_to_dict(dict, "event_type_desc", info->event_type_desc) || +	    !add_to_dict(dict, "str", info->str) || +	    !add_to_dict(dict, "deprecated", info->deprecated ? "deprecated" : NULL) || +	    PyList_Append(py_list, dict) != 0) { +		Py_DECREF(dict); +		return -ENOMEM; +	} +	Py_DECREF(dict); +	return 0; +} + +static PyObject *pyrf_pmu__events(PyObject *self) +{ +	struct pyrf_pmu *ppmu = (void *)self; +	PyObject *py_list = PyList_New(0); +	int ret; + +	if (!py_list) +		return NULL; + +	ret = perf_pmu__for_each_event(ppmu->pmu, +				       /*skip_duplicate_pmus=*/false, +				       py_list, +				       pyrf_pmu__events_cb); +	if (ret) { +		Py_DECREF(py_list); +		errno = -ret; +		PyErr_SetFromErrno(PyExc_OSError); +		return NULL; +	} +	return py_list; +} + +static PyObject *pyrf_pmu__repr(PyObject *self) +{ +	struct pyrf_pmu *ppmu = (void *)self; + +	return PyUnicode_FromFormat("pmu(%s)", ppmu->pmu->name); +} + +static const char pyrf_pmu__doc[] = PyDoc_STR("perf Performance Monitoring Unit (PMU) object."); + +static PyMethodDef pyrf_pmu__methods[] = { +	{ +		.ml_name  = "events", +		.ml_meth  = (PyCFunction)pyrf_pmu__events, +		.ml_flags = METH_NOARGS, +		.ml_doc	  = PyDoc_STR("Returns a sequence of events encoded as a dictionaries.") +	}, +	{ +		.ml_name  = "name", +		.ml_meth  = (PyCFunction)pyrf_pmu__name, +		.ml_flags = METH_NOARGS, +		.ml_doc	  = PyDoc_STR("Name of the PMU including suffixes.") +	}, +	{ .ml_name = NULL, } +}; + +/** The python type for a perf.pmu. */ +static PyTypeObject pyrf_pmu__type = { +	PyVarObject_HEAD_INIT(NULL, 0) +	.tp_name	= "perf.pmu", +	.tp_basicsize	= sizeof(struct pyrf_pmu), +	.tp_dealloc	= (destructor)pyrf_pmu__delete, +	.tp_flags	= Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, +	.tp_doc		= pyrf_pmu__doc, +	.tp_methods	= pyrf_pmu__methods, +	.tp_str         = pyrf_pmu__name, +	.tp_repr        = pyrf_pmu__repr, +}; + +static int pyrf_pmu__setup_types(void) +{ +	pyrf_pmu__type.tp_new = PyType_GenericNew; +	return PyType_Ready(&pyrf_pmu__type); +} + + +/** A python iterator for pmus that has no equivalent in the C code. */ +struct pyrf_pmu_iterator { +	PyObject_HEAD +	struct perf_pmu *pmu; +}; + +static void pyrf_pmu_iterator__dealloc(struct pyrf_pmu_iterator *self) +{ +	Py_TYPE(self)->tp_free((PyObject *) self); +} + +static PyObject *pyrf_pmu_iterator__new(PyTypeObject *type, PyObject *args __maybe_unused, +					PyObject *kwds __maybe_unused) +{ +	struct pyrf_pmu_iterator *itr = (void *)type->tp_alloc(type, 0); + +	if (itr != NULL) +		itr->pmu = perf_pmus__scan(/*pmu=*/NULL); + +	return (PyObject *) itr; +} + +static PyObject *pyrf_pmu_iterator__iter(PyObject *self) +{ +	Py_INCREF(self); +	return self; +} + +static PyObject *pyrf_pmu_iterator__iternext(PyObject *self) +{ +	struct pyrf_pmu_iterator *itr = (void *)self; +	struct pyrf_pmu *ppmu; + +	if (itr->pmu == NULL) { +		PyErr_SetNone(PyExc_StopIteration); +		return NULL; +	} +	// Create object to return. +	ppmu = PyObject_New(struct pyrf_pmu, &pyrf_pmu__type); +	if (ppmu) { +		ppmu->pmu = itr->pmu; +		// Advance iterator. +		itr->pmu = perf_pmus__scan(itr->pmu); +	} +	return (PyObject *)ppmu; +} + +/** The python type for the PMU iterator. */ +static PyTypeObject pyrf_pmu_iterator__type = { +	PyVarObject_HEAD_INIT(NULL, 0) +	.tp_name = "pmus.iterator", +	.tp_doc = "Iterator for the pmus string sequence.", +	.tp_basicsize = sizeof(struct pyrf_pmu_iterator), +	.tp_itemsize = 0, +	.tp_flags = Py_TPFLAGS_DEFAULT, +	.tp_new = pyrf_pmu_iterator__new, +	.tp_dealloc = (destructor) pyrf_pmu_iterator__dealloc, +	.tp_iter = pyrf_pmu_iterator__iter, +	.tp_iternext = pyrf_pmu_iterator__iternext, +}; + +static int pyrf_pmu_iterator__setup_types(void) +{ +	return PyType_Ready(&pyrf_pmu_iterator__type); +} + +static PyObject *pyrf__pmus(PyObject *self, PyObject *args) +{ +	// Calling the class creates an instance of the iterator. +	return PyObject_CallObject((PyObject *) &pyrf_pmu_iterator__type, /*args=*/NULL); +} +  struct pyrf_counts_values {  	PyObject_HEAD @@ -1093,6 +1304,151 @@ static PyObject *pyrf_evlist__all_cpus(struct pyrf_evlist *pevlist)  	return (PyObject *)pcpu_map;  } +static PyObject *pyrf_evlist__metrics(struct pyrf_evlist *pevlist) +{ +	PyObject *list = PyList_New(/*len=*/0); +	struct rb_node *node; + +	if (!list) +		return NULL; + +	for (node = rb_first_cached(&pevlist->evlist.metric_events.entries); node; +	     node = rb_next(node)) { +		struct metric_event *me = container_of(node, struct metric_event, nd); +		struct list_head *pos; + +		list_for_each(pos, &me->head) { +			struct metric_expr *expr = container_of(pos, struct metric_expr, nd); +			PyObject *str = PyUnicode_FromString(expr->metric_name); + +			if (!str || PyList_Append(list, str) != 0) { +				Py_DECREF(list); +				return NULL; +			} +			Py_DECREF(str); +		} +	} +	return list; +} + +static int prepare_metric(const struct metric_expr *mexp, +			  const struct evsel *evsel, +			  struct expr_parse_ctx *pctx, +			  int cpu_idx, int thread_idx) +{ +	struct evsel * const *metric_events = mexp->metric_events; +	struct metric_ref *metric_refs = mexp->metric_refs; + +	for (int i = 0; metric_events[i]; i++) { +		char *n = strdup(evsel__metric_id(metric_events[i])); +		double val, ena, run; +		int source_count = evsel__source_count(metric_events[i]); +		int ret; +		struct perf_counts_values *old_count, *new_count; + +		if (!n) +			return -ENOMEM; + +		if (source_count == 0) +			source_count = 1; + +		ret = evsel__ensure_counts(metric_events[i]); +		if (ret) +			return ret; + +		/* Set up pointers to the old and newly read counter values. */ +		old_count = perf_counts(metric_events[i]->prev_raw_counts, cpu_idx, thread_idx); +		new_count = perf_counts(metric_events[i]->counts, cpu_idx, thread_idx); +		/* Update the value in metric_events[i]->counts. */ +		evsel__read_counter(metric_events[i], cpu_idx, thread_idx); + +		val = new_count->val - old_count->val; +		ena = new_count->ena - old_count->ena; +		run = new_count->run - old_count->run; + +		if (ena != run && run != 0) +			val = val * ena / run; +		ret = expr__add_id_val_source_count(pctx, n, val, source_count); +		if (ret) +			return ret; +	} + +	for (int i = 0; metric_refs && metric_refs[i].metric_name; i++) { +		int ret = expr__add_ref(pctx, &metric_refs[i]); + +		if (ret) +			return ret; +	} + +	return 0; +} + +static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist, +					     PyObject *args, PyObject *kwargs) +{ +	int ret, cpu = 0, cpu_idx = 0, thread = 0, thread_idx = 0; +	const char *metric; +	struct rb_node *node; +	struct metric_expr *mexp = NULL; +	struct expr_parse_ctx *pctx; +	double result = 0; + +	if (!PyArg_ParseTuple(args, "sii", &metric, &cpu, &thread)) +		return NULL; + +	for (node = rb_first_cached(&pevlist->evlist.metric_events.entries); +	     mexp == NULL && node; +	     node = rb_next(node)) { +		struct metric_event *me = container_of(node, struct metric_event, nd); +		struct list_head *pos; + +		list_for_each(pos, &me->head) { +			struct metric_expr *e = container_of(pos, struct metric_expr, nd); + +			if (strcmp(e->metric_name, metric)) +				continue; + +			if (e->metric_events[0] == NULL) +				continue; + +			cpu_idx = perf_cpu_map__idx(e->metric_events[0]->core.cpus, +						    (struct perf_cpu){.cpu = cpu}); +			if (cpu_idx < 0) +				continue; + +			thread_idx = perf_thread_map__idx(e->metric_events[0]->core.threads, +							  thread); +			if (thread_idx < 0) +				continue; + +			mexp = e; +			break; +		} +	} +	if (!mexp) { +		PyErr_Format(PyExc_TypeError, "Unknown metric '%s' for CPU '%d' and thread '%d'", +			     metric, cpu, thread); +		return NULL; +	} + +	pctx = expr__ctx_new(); +	if (!pctx) +		return PyErr_NoMemory(); + +	ret = prepare_metric(mexp, mexp->metric_events[0], pctx, cpu_idx, thread_idx); +	if (ret) { +		expr__ctx_free(pctx); +		errno = -ret; +		PyErr_SetFromErrno(PyExc_OSError); +		return NULL; +	} +	if (expr__parse(&result, pctx, mexp->metric_expr)) +		result = 0.0; + +	expr__ctx_free(pctx); +	return PyFloat_FromDouble(result); +} +  static PyObject *pyrf_evlist__mmap(struct pyrf_evlist *pevlist,  				   PyObject *args, PyObject *kwargs)  { @@ -1209,8 +1565,10 @@ static PyObject *pyrf_evlist__read_on_cpu(struct pyrf_evlist *pevlist,  		return NULL;  	md = get_md(evlist, cpu); -	if (!md) +	if (!md) { +		PyErr_Format(PyExc_TypeError, "Unknown CPU '%d'", cpu);  		return NULL; +	}  	if (perf_mmap__read_init(&md->core) < 0)  		goto end; @@ -1320,6 +1678,18 @@ static PyMethodDef pyrf_evlist__methods[] = {  		.ml_doc	  = PyDoc_STR("CPU map union of all evsel CPU maps.")  	},  	{ +		.ml_name  = "metrics", +		.ml_meth  = (PyCFunction)pyrf_evlist__metrics, +		.ml_flags = METH_NOARGS, +		.ml_doc	  = PyDoc_STR("List of metric names within the evlist.") +	}, +	{ +		.ml_name  = "compute_metric", +		.ml_meth  = (PyCFunction)pyrf_evlist__compute_metric, +		.ml_flags = METH_VARARGS | METH_KEYWORDS, +		.ml_doc	  = PyDoc_STR("compute metric for given name, cpu and thread") +	}, +	{  		.ml_name  = "mmap",  		.ml_meth  = (PyCFunction)pyrf_evlist__mmap,  		.ml_flags = METH_VARARGS | METH_KEYWORDS, @@ -1546,10 +1916,6 @@ static const struct perf_constant perf__constants[] = {  static PyObject *pyrf__tracepoint(struct pyrf_evsel *pevsel,  				  PyObject *args, PyObject *kwargs)  { -#ifndef HAVE_LIBTRACEEVENT -	return NULL; -#else -	struct tep_event *tp_format;  	static char *kwlist[] = { "sys", "name", NULL };  	char *sys  = NULL;  	char *name = NULL; @@ -1558,12 +1924,7 @@ static PyObject *pyrf__tracepoint(struct pyrf_evsel *pevsel,  					 &sys, &name))  		return NULL; -	tp_format = trace_event__tp_format(sys, name); -	if (IS_ERR(tp_format)) -		return PyLong_FromLong(-1); - -	return PyLong_FromLong(tp_format->id); -#endif // HAVE_LIBTRACEEVENT +	return PyLong_FromLong(tp_pmu__id(sys, name));  }  static PyObject *pyrf_evsel__from_evsel(struct evsel *evsel) @@ -1688,8 +2049,128 @@ static PyObject *pyrf__parse_events(PyObject *self, PyObject *args)  	return result;  } +static PyObject *pyrf__parse_metrics(PyObject *self, PyObject *args) +{ +	const char *input; +	struct evlist evlist = {}; +	PyObject *result; +	PyObject *pcpus = NULL, *pthreads = NULL; +	struct perf_cpu_map *cpus; +	struct perf_thread_map *threads; +	int ret; + +	if (!PyArg_ParseTuple(args, "s|OO", &input, &pcpus, &pthreads)) +		return NULL; + +	threads = pthreads ? ((struct pyrf_thread_map *)pthreads)->threads : NULL; +	cpus = pcpus ? ((struct pyrf_cpu_map *)pcpus)->cpus : NULL; + +	evlist__init(&evlist, cpus, threads); +	ret = metricgroup__parse_groups(&evlist, /*pmu=*/"all", input, +					/*metric_no_group=*/ false, +					/*metric_no_merge=*/ false, +					/*metric_no_threshold=*/ true, +					/*user_requested_cpu_list=*/ NULL, +					/*system_wide=*/true, +					/*hardware_aware_grouping=*/ false); +	if (ret) { +		errno = -ret; +		PyErr_SetFromErrno(PyExc_OSError); +		return NULL; +	} +	result = pyrf_evlist__from_evlist(&evlist); +	evlist__exit(&evlist); +	return result; +} + +static PyObject *pyrf__metrics_groups(const struct pmu_metric *pm) +{ +	PyObject *groups = PyList_New(/*len=*/0); +	const char *mg = pm->metric_group; + +	if (!groups) +		return NULL; + +	while (mg) { +		PyObject *val = NULL; +		const char *sep = strchr(mg, ';'); +		size_t len = sep ? (size_t)(sep - mg) : strlen(mg); + +		if (len > 0) { +			val = PyUnicode_FromStringAndSize(mg, len); +			if (val) +				PyList_Append(groups, val); + +			Py_XDECREF(val); +		} +		mg = sep ? sep + 1 : NULL; +	} +	return groups; +} + +static int pyrf__metrics_cb(const struct pmu_metric *pm, +			    const struct pmu_metrics_table *table __maybe_unused, +			    void *vdata) +{ +	PyObject *py_list = vdata; +	PyObject *dict = PyDict_New(); +	PyObject *key = dict ? PyUnicode_FromString("MetricGroup") : NULL; +	PyObject *value = key ? pyrf__metrics_groups(pm) : NULL; + +	if (!value || PyDict_SetItem(dict, key, value) != 0) { +		Py_XDECREF(key); +		Py_XDECREF(value); +		Py_XDECREF(dict); +		return -ENOMEM; +	} + +	if (!add_to_dict(dict, "MetricName", pm->metric_name) || +	    !add_to_dict(dict, "PMU", pm->pmu) || +	    !add_to_dict(dict, "MetricExpr", pm->metric_expr) || +	    !add_to_dict(dict, "MetricThreshold", pm->metric_threshold) || +	    !add_to_dict(dict, "ScaleUnit", pm->unit) || +	    !add_to_dict(dict, "Compat", pm->compat) || +	    !add_to_dict(dict, "BriefDescription", pm->desc) || +	    !add_to_dict(dict, "PublicDescription", pm->long_desc) || +	    PyList_Append(py_list, dict) != 0) { +		Py_DECREF(dict); +		return -ENOMEM; +	} +	Py_DECREF(dict); +	return 0; +} + +static PyObject *pyrf__metrics(PyObject *self, PyObject *args) +{ +	const struct pmu_metrics_table *table = pmu_metrics_table__find(); +	PyObject *list = PyList_New(/*len=*/0); +	int ret; + +	if (!list) +		return NULL; + +	ret = pmu_metrics_table__for_each_metric(table, pyrf__metrics_cb, list); +	if (!ret) +		ret = pmu_for_each_sys_metric(pyrf__metrics_cb, list); + +	if (ret) { +		Py_DECREF(list); +		errno = -ret; +		PyErr_SetFromErrno(PyExc_OSError); +		return NULL; +	} +	return list; +} +  static PyMethodDef perf__methods[] = {  	{ +		.ml_name  = "metrics", +		.ml_meth  = (PyCFunction) pyrf__metrics, +		.ml_flags = METH_NOARGS, +		.ml_doc	  = PyDoc_STR( +			"Returns a list of metrics represented as string values in dictionaries.") +	}, +	{  		.ml_name  = "tracepoint",  		.ml_meth  = (PyCFunction) pyrf__tracepoint,  		.ml_flags = METH_VARARGS | METH_KEYWORDS, @@ -1701,6 +2182,19 @@ static PyMethodDef perf__methods[] = {  		.ml_flags = METH_VARARGS,  		.ml_doc	  = PyDoc_STR("Parse a string of events and return an evlist.")  	}, +	{ +		.ml_name  = "parse_metrics", +		.ml_meth  = (PyCFunction) pyrf__parse_metrics, +		.ml_flags = METH_VARARGS, +		.ml_doc	  = PyDoc_STR( +			"Parse a string of metrics or metric groups and return an evlist.") +	}, +	{ +		.ml_name  = "pmus", +		.ml_meth  = (PyCFunction) pyrf__pmus, +		.ml_flags = METH_NOARGS, +		.ml_doc	  = PyDoc_STR("Returns a sequence of pmus.") +	},  	{ .ml_name = NULL, }  }; @@ -1728,6 +2222,8 @@ PyMODINIT_FUNC PyInit_perf(void)  	    pyrf_evsel__setup_types() < 0 ||  	    pyrf_thread_map__setup_types() < 0 ||  	    pyrf_cpu_map__setup_types() < 0 || +	    pyrf_pmu_iterator__setup_types() < 0 || +	    pyrf_pmu__setup_types() < 0 ||  	    pyrf_counts_values__setup_types() < 0)  		return module; | 
