// SPDX-License-Identifier: GPL-2.0
#include <linux/list.h>
#include <linux/compiler.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
#include <dirent.h>
#include <api/fs/fs.h>
#include <locale.h>
#include <regex.h>
#include "util.h"
#include "pmu.h"
#include "parse-events.h"
#include "cpumap.h"
#include "header.h"
#include "pmu-events/pmu-events.h"
#include "cache.h"
#include "string2.h"
struct perf_pmu_format {
char *name;
int value;
DECLARE_BITMAP(bits, PERF_PMU_FORMAT_BITS);
struct list_head list;
};
int perf_pmu_parse(struct list_head *list, char *name);
extern FILE *perf_pmu_in;
static LIST_HEAD(pmus);
/*
* Parse & process all the sysfs attributes located under
* the directory specified in 'dir' parameter.
*/
int perf_pmu__format_parse(char *dir, struct list_head *head)
{
struct dirent *evt_ent;
DIR *format_dir;
int ret = 0;
format_dir = opendir(dir);
if (!format_dir)
return -EINVAL;
while (!ret && (evt_ent = readdir(format_dir))) {
char path[PATH_MAX];
char *name = evt_ent->d_name;
FILE *file;
if (!strcmp(name, ".") || !strcmp(name, ".."))
continue;
snprintf(path, PATH_MAX, "%s/%s", dir, name);
ret = -EINVAL;
file = fopen(path, "r");
if (!file)
break;
perf_pmu_in = file;
ret = perf_pmu_parse(head, name);
fclose(file);
}
closedir(format_dir);
return ret;
}
/*
* Reading/parsing the default pmu format definition, which should be
* located at:
* /sys/bus/event_source/devices/<dev>/format as sysfs group attributes.
*/
static int pmu_format(const char *name, struct list_head *format)
{
struct stat st;
char path[PATH_MAX];
const char *sysfs = sysfs__mountpoint();
if (!sysfs)
return -1;
snprintf(path, PATH_MAX,
"%s" EVENT_SOURCE_DEVICE_PATH "%s/format", sysfs, name);
if (stat(path, &st) < 0)
return 0; /* no error if format does not exist */
if (perf_pmu__format_parse(path, format))
return -1;
return 0;
}
static int convert_scale(const char *scale, char **end, double *sval)
{
char *lc;
int ret = 0;
/*
* save current locale
*/
lc = setlocale(LC_NUMERIC, NULL);
/*
* The lc string may be allocated in static storage,
* so get a dynamic copy to make it survive setlocale
* call below.
*/
lc = strdup(lc);
if (!lc) {
ret = -ENOMEM;
goto out;
}
/*
* force to C locale to ensure kernel
* scale string is converted correctly.
* kernel uses default C locale.
*/
setlocale(LC_NUMERIC, "C");
*sval = strtod(scale, end);
out:
/* restore locale */
setlocale(LC_NUMERIC, lc);
free(lc);
return ret;
}
static int perf_pmu__parse_scale(struct perf_pmu_alias *alias, char *dir, char *name)
{
struct stat st;
ssize_t sret;
char scale[128];
int fd, ret = -1;
char path[PATH_MAX];
scnprintf(path, PATH_MAX, "%s/%s.scale", dir, name);
fd = open(path, O_RDONLY);
if (fd == -1)
return -1;
if (fstat(fd, &st) < 0)
goto error;
sret = read(fd, scale, sizeof(scale)-1);
if (sret < 0)
goto error;
if (scale[sret - 1] == '\n')
scale[sret - 1] = '\0';
else
scale[sret] = '\0';
ret = convert_scale(scale, NULL, &alias->scale);
error:
close(fd);
return ret;
}
static int perf_pmu__parse_unit(struct perf_pmu_alias *alias, char *dir, char *name)
{
char path[PATH_MAX];
ssize_t sret;
int fd;
scnprintf(path, PATH_MAX, "%s/%s.unit", dir, name);
fd = open(path, O_RDONLY);
if (fd == -1)
return -1;
sret = read(fd, alias->unit, UNIT_MAX_LEN);
if (sret < 0)
goto error;
close(fd);
if (alias->unit[sret - 1] == '\n')
alias->unit[sret - 1] = '\0';
else
alias->unit[sret] = '\0';
return 0;
error:
close(fd);
alias->unit[0] = '\0';
return -1;
}
static int
perf_pmu__parse_per_pkg(struct perf_pmu_alias *alias, char *dir, char *name)
{
char path[PATH_MAX];
int fd;
scnprintf(path, PATH_MAX, "%s/%s.per-pkg", dir, name);
fd = open(path, O_RDONLY);
if (fd == -1)
return -1;
close(fd);
alias->per_pkg = true;
return 0;
}
static int perf_pmu__parse_snapshot(struct perf_pmu_alias *alias,
char *dir, char *name)
{
char path[PATH_MAX];
int fd;
scnprintf(path, PATH_MAX, "%s/%s.snapshot", dir, name);
fd = open(path, O_RDONLY);
if (fd == -1)
return -1;
alias->snapshot = true;
close(fd);
return 0;
}
static void perf_pmu_assign_str(char *name, const char *field, char **old_str,
char **new_str)
{
if (!*old_str)
goto set_new;
if (*new_str) { /* Have new string, check with old */
if (strcasecmp(*old_str, *new_str))
pr_debug("alias %s differs in field '%s'\n",
name, field);
zfree(old_str);
} else /* Nothing new --> keep old string */
return;
set_new:
*old_str = *new_str;
*new_str = NULL;
}
static void perf_pmu_update_alias(struct perf_pmu_alias *old,
struct perf_pmu_alias *newalias)
{
perf_pmu_assign_str(old->name, "desc", &old->desc, &newalias->desc);
perf_pmu_assign_str(old->name, "long_desc", &old->long_desc,
&newalias->long_desc);
perf_pmu_assign_str(old->name, "topic", &old->topic, &newalias->topic);
perf_pmu_assign_str(old->name, "metric_expr", &old->metric_expr,
&newalias->metric_expr);
perf_pmu_assign_str(old->name, "metric_name", &old->metric_name,
&newalias->metric_name);
perf_pmu_assign_str(old->name, "value", &old->str, &newalias->str);
old->scale = newalias->scale;
old->per_pkg = newalias->per_pkg;
old->snapshot = newalias->snapshot;
memcpy(old->unit, newalias->unit, sizeof(old->unit));
}
/* Delete an alias entry. */
static void perf_pmu_free_alias(struct perf_pmu_alias *newalias)
{
zfree(&newalias->name);
zfree(&newalias->desc);
zfree(&newalias->long_desc);
zfree(&newalias->topic);
zfree(&newalias->str);
zfree(&newalias->metric_expr);
zfree(&newalias->metric_name);
parse_events_terms__purge(&newalias->terms);
free(newalias);
}
/* Merge an alias, search in alias list. If this name is already
* present merge both of them to combine all information.
*/
static bool perf_pmu_merge_alias(struct perf_pmu_alias *newalias,
struct list_head *alist)
{
struct perf_pmu_alias *a;
list_for_each_entry(a, alist, list) {
if (!strcasecmp(newalias->name, a->name)) {
perf_pmu_update_alias(a, newalias);
perf_pmu_free_alias(newalias);
return true;
}
}
return false;
}
static int __perf_pmu__new_alias(struct list_head *list, char *dir, char *name,
char *desc, char *val,
char *long_desc, char *topic,
char *unit, char *perpkg,
char *metric_expr,
char *metric_name)
{
struct parse_events_term *term;
struct perf_pmu_alias *alias;
int ret;
int num;
char newval[256];
alias = malloc(sizeof(*alias));
if (!alias)
return -ENOMEM;
INIT_LIST_HEAD(&alias->terms);
alias->scale = 1.0;
alias->unit[0] = '\0';
alias->per_pkg = false;
alias->snapshot = false;
ret = parse_events_terms(&alias->terms, val);
if (ret) {
pr_err("Cannot parse alias %s: %d\n", val, ret);
free(alias);
return ret;
}
/* Scan event and remove leading zeroes, spaces, newlines, some
* platforms have terms specified as
* event=0x0091 (read from files ../<PMU>/events/<FILE>
* and terms specified as event=0x91 (read from JSON files).
*
* Rebuild string to make alias->str member comparable.
*/
memset(newval, 0, sizeof(newval));
ret = 0;
list_for_each_entry(term, &alias->terms, list) {
if (ret)
ret += scnprintf(newval + ret, sizeof(newval) - ret,
",");
if (term->type_val == PARSE_EVENTS__TERM_TYPE_NUM)
ret += scnprintf(newval + ret, sizeof(newval) - ret,
"%s=
|