linux-stable/tools/perf/util/hwmon_pmu.c
Ian Rogers 3f61a12b08 perf hwmon_pmu: Use openat rather than dup to refresh directory
The hwmon PMU test will make a temp directory, open the directory with
O_DIRECTORY then fill it with contents. As the open is before the
filling the contents the later fdopendir may reflect the initial empty
state, meaning no events are seen. Change to re-open the directory,
rather than dup the fd, so the latest contents are seen.

Minor tweaks/additions to debug messages.

Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@kernel.org>
Link: https://lore.kernel.org/r/20241206042306.1055913-1-irogers@google.com
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
2024-12-09 15:00:03 -08:00

840 lines
20 KiB
C

// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
#include "counts.h"
#include "debug.h"
#include "evsel.h"
#include "hashmap.h"
#include "hwmon_pmu.h"
#include "pmu.h"
#include <internal/xyarray.h>
#include <internal/threadmap.h>
#include <perf/threadmap.h>
#include <sys/types.h>
#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <api/fs/fs.h>
#include <api/io.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/zalloc.h>
/** Strings that correspond to enum hwmon_type. */
static const char * const hwmon_type_strs[HWMON_TYPE_MAX] = {
NULL,
"cpu",
"curr",
"energy",
"fan",
"humidity",
"in",
"intrusion",
"power",
"pwm",
"temp",
};
#define LONGEST_HWMON_TYPE_STR "intrusion"
/** Strings that correspond to enum hwmon_item. */
static const char * const hwmon_item_strs[HWMON_ITEM__MAX] = {
NULL,
"accuracy",
"alarm",
"auto_channels_temp",
"average",
"average_highest",
"average_interval",
"average_interval_max",
"average_interval_min",
"average_lowest",
"average_max",
"average_min",
"beep",
"cap",
"cap_hyst",
"cap_max",
"cap_min",
"crit",
"crit_hyst",
"div",
"emergency",
"emergency_hist",
"enable",
"fault",
"freq",
"highest",
"input",
"label",
"lcrit",
"lcrit_hyst",
"lowest",
"max",
"max_hyst",
"min",
"min_hyst",
"mod",
"offset",
"pulses",
"rated_max",
"rated_min",
"reset_history",
"target",
"type",
"vid",
};
#define LONGEST_HWMON_ITEM_STR "average_interval_max"
static const char *const hwmon_units[HWMON_TYPE_MAX] = {
NULL,
"V", /* cpu */
"A", /* curr */
"J", /* energy */
"rpm", /* fan */
"%", /* humidity */
"V", /* in */
"", /* intrusion */
"W", /* power */
"Hz", /* pwm */
"'C", /* temp */
};
struct hwmon_pmu {
struct perf_pmu pmu;
struct hashmap events;
int hwmon_dir_fd;
};
/**
* union hwmon_pmu_event_key: Key for hwmon_pmu->events as such each key
* represents an event.
*
* Related hwmon files start <type><number> that this key represents.
*/
union hwmon_pmu_event_key {
long type_and_num;
struct {
int num :16;
enum hwmon_type type :8;
};
};
/**
* struct hwmon_pmu_event_value: Value in hwmon_pmu->events.
*
* Hwmon files are of the form <type><number>_<item> and may have a suffix
* _alarm.
*/
struct hwmon_pmu_event_value {
/** @items: which item files are present. */
DECLARE_BITMAP(items, HWMON_ITEM__MAX);
/** @alarm_items: which item files are present. */
DECLARE_BITMAP(alarm_items, HWMON_ITEM__MAX);
/** @label: contents of <type><number>_label if present. */
char *label;
/** @name: name computed from label of the form <type>_<label>. */
char *name;
};
bool perf_pmu__is_hwmon(const struct perf_pmu *pmu)
{
return pmu && pmu->type >= PERF_PMU_TYPE_HWMON_START &&
pmu->type <= PERF_PMU_TYPE_HWMON_END;
}
bool evsel__is_hwmon(const struct evsel *evsel)
{
return perf_pmu__is_hwmon(evsel->pmu);
}
static size_t hwmon_pmu__event_hashmap_hash(long key, void *ctx __maybe_unused)
{
return ((union hwmon_pmu_event_key)key).type_and_num;
}
static bool hwmon_pmu__event_hashmap_equal(long key1, long key2, void *ctx __maybe_unused)
{
return ((union hwmon_pmu_event_key)key1).type_and_num ==
((union hwmon_pmu_event_key)key2).type_and_num;
}
static int hwmon_strcmp(const void *a, const void *b)
{
const char *sa = a;
const char * const *sb = b;
return strcmp(sa, *sb);
}
bool parse_hwmon_filename(const char *filename,
enum hwmon_type *type,
int *number,
enum hwmon_item *item,
bool *alarm)
{
char fn_type[24];
const char **elem;
const char *fn_item = NULL;
size_t fn_item_len;
assert(strlen(LONGEST_HWMON_TYPE_STR) < sizeof(fn_type));
strlcpy(fn_type, filename, sizeof(fn_type));
for (size_t i = 0; fn_type[i] != '\0'; i++) {
if (fn_type[i] >= '0' && fn_type[i] <= '9') {
fn_type[i] = '\0';
*number = strtoul(&filename[i], (char **)&fn_item, 10);
if (*fn_item == '_')
fn_item++;
break;
}
if (fn_type[i] == '_') {
fn_type[i] = '\0';
*number = -1;
fn_item = &filename[i + 1];
break;
}
}
if (fn_item == NULL || fn_type[0] == '\0' || (item != NULL && fn_item[0] == '\0')) {
pr_debug3("hwmon_pmu: not a hwmon file '%s'\n", filename);
return false;
}
elem = bsearch(&fn_type, hwmon_type_strs + 1, ARRAY_SIZE(hwmon_type_strs) - 1,
sizeof(hwmon_type_strs[0]), hwmon_strcmp);
if (!elem) {
pr_debug3("hwmon_pmu: not a hwmon type '%s' in file name '%s'\n",
fn_type, filename);
return false;
}
*type = elem - &hwmon_type_strs[0];
if (!item)
return true;
*alarm = false;
fn_item_len = strlen(fn_item);
if (fn_item_len > 6 && !strcmp(&fn_item[fn_item_len - 6], "_alarm")) {
assert(strlen(LONGEST_HWMON_ITEM_STR) < sizeof(fn_type));
strlcpy(fn_type, fn_item, fn_item_len - 5);
fn_item = fn_type;
*alarm = true;
}
elem = bsearch(fn_item, hwmon_item_strs + 1, ARRAY_SIZE(hwmon_item_strs) - 1,
sizeof(hwmon_item_strs[0]), hwmon_strcmp);
if (!elem) {
pr_debug3("hwmon_pmu: not a hwmon item '%s' in file name '%s'\n",
fn_item, filename);
return false;
}
*item = elem - &hwmon_item_strs[0];
return true;
}
static void fix_name(char *p)
{
char *s = strchr(p, '\n');
if (s)
*s = '\0';
while (*p != '\0') {
if (strchr(" :,/\n\t", *p))
*p = '_';
else
*p = tolower(*p);
p++;
}
}
static int hwmon_pmu__read_events(struct hwmon_pmu *pmu)
{
DIR *dir;
struct dirent *ent;
int dup_fd, err = 0;
struct hashmap_entry *cur, *tmp;
size_t bkt;
if (pmu->pmu.sysfs_aliases_loaded)
return 0;
/*
* Use a dup-ed fd as closedir will close it. Use openat so that the
* directory contents are refreshed.
*/
dup_fd = openat(pmu->hwmon_dir_fd, ".", O_DIRECTORY);
if (dup_fd == -1)
return -ENOMEM;
dir = fdopendir(dup_fd);
if (!dir) {
close(dup_fd);
return -ENOMEM;
}
while ((ent = readdir(dir)) != NULL) {
enum hwmon_type type;
int number;
enum hwmon_item item;
bool alarm;
union hwmon_pmu_event_key key = { .type_and_num = 0 };
struct hwmon_pmu_event_value *value;
if (ent->d_type != DT_REG)
continue;
if (!parse_hwmon_filename(ent->d_name, &type, &number, &item, &alarm)) {
pr_debug3("Not a hwmon file '%s'\n", ent->d_name);
continue;
}
key.num = number;
key.type = type;
if (!hashmap__find(&pmu->events, key.type_and_num, &value)) {
value = zalloc(sizeof(*value));
if (!value) {
err = -ENOMEM;
goto err_out;
}
err = hashmap__add(&pmu->events, key.type_and_num, value);
if (err) {
free(value);
err = -ENOMEM;
goto err_out;
}
}
__set_bit(item, alarm ? value->alarm_items : value->items);
if (item == HWMON_ITEM_LABEL) {
char buf[128];
int fd = openat(pmu->hwmon_dir_fd, ent->d_name, O_RDONLY);
ssize_t read_len;
if (fd < 0)
continue;
read_len = read(fd, buf, sizeof(buf));
while (read_len > 0 && buf[read_len - 1] == '\n')
read_len--;
if (read_len > 0)
buf[read_len] = '\0';
if (buf[0] == '\0') {
pr_debug("hwmon_pmu: empty label file %s %s\n",
pmu->pmu.name, ent->d_name);
close(fd);
continue;
}
value->label = strdup(buf);
if (!value->label) {
pr_debug("hwmon_pmu: memory allocation failure\n");
close(fd);
continue;
}
snprintf(buf, sizeof(buf), "%s_%s", hwmon_type_strs[type], value->label);
fix_name(buf);
value->name = strdup(buf);
if (!value->name)
pr_debug("hwmon_pmu: memory allocation failure\n");
close(fd);
}
}
if (hashmap__size(&pmu->events) == 0)
pr_debug2("hwmon_pmu: %s has no events\n", pmu->pmu.name);
hashmap__for_each_entry_safe((&pmu->events), cur, tmp, bkt) {
union hwmon_pmu_event_key key = {
.type_and_num = cur->key,
};
struct hwmon_pmu_event_value *value = cur->pvalue;
if (!test_bit(HWMON_ITEM_INPUT, value->items)) {
pr_debug("hwmon_pmu: %s removing event '%s%d' that has no input file\n",
pmu->pmu.name, hwmon_type_strs[key.type], key.num);
hashmap__delete(&pmu->events, key.type_and_num, &key, &value);
zfree(&value->label);
zfree(&value->name);
free(value);
}
}
pmu->pmu.sysfs_aliases_loaded = true;
err_out:
closedir(dir);
return err;
}
struct perf_pmu *hwmon_pmu__new(struct list_head *pmus, int hwmon_dir, const char *sysfs_name, const char *name)
{
char buf[32];
struct hwmon_pmu *hwm;
hwm = zalloc(sizeof(*hwm));
if (!hwm)
return NULL;
hwm->hwmon_dir_fd = hwmon_dir;
hwm->pmu.type = PERF_PMU_TYPE_HWMON_START + strtoul(sysfs_name + 5, NULL, 10);
if (hwm->pmu.type > PERF_PMU_TYPE_HWMON_END) {
pr_err("Unable to encode hwmon type from %s in valid PMU type\n", sysfs_name);
goto err_out;
}
snprintf(buf, sizeof(buf), "hwmon_%s", name);
fix_name(buf + 6);
hwm->pmu.name = strdup(buf);
if (!hwm->pmu.name)
goto err_out;
hwm->pmu.alias_name = strdup(sysfs_name);
if (!hwm->pmu.alias_name)
goto err_out;
hwm->pmu.cpus = perf_cpu_map__new("0");
if (!hwm->pmu.cpus)
goto err_out;
INIT_LIST_HEAD(&hwm->pmu.format);
INIT_LIST_HEAD(&hwm->pmu.aliases);
INIT_LIST_HEAD(&hwm->pmu.caps);
hashmap__init(&hwm->events, hwmon_pmu__event_hashmap_hash,
hwmon_pmu__event_hashmap_equal, /*ctx=*/NULL);
list_add_tail(&hwm->pmu.list, pmus);
return &hwm->pmu;
err_out:
free((char *)hwm->pmu.name);
free(hwm->pmu.alias_name);
free(hwm);
close(hwmon_dir);
return NULL;
}
void hwmon_pmu__exit(struct perf_pmu *pmu)
{
struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu);
struct hashmap_entry *cur, *tmp;
size_t bkt;
hashmap__for_each_entry_safe((&hwm->events), cur, tmp, bkt) {
struct hwmon_pmu_event_value *value = cur->pvalue;
zfree(&value->label);
zfree(&value->name);
free(value);
}
hashmap__clear(&hwm->events);
close(hwm->hwmon_dir_fd);
}
static size_t hwmon_pmu__describe_items(struct hwmon_pmu *hwm, char *out_buf, size_t out_buf_len,
union hwmon_pmu_event_key key,
const unsigned long *items, bool is_alarm)
{
size_t bit;
char buf[64];
size_t len = 0;
for_each_set_bit(bit, items, HWMON_ITEM__MAX) {
int fd;
if (bit == HWMON_ITEM_LABEL || bit == HWMON_ITEM_INPUT)
continue;
snprintf(buf, sizeof(buf), "%s%d_%s%s",
hwmon_type_strs[key.type],
key.num,
hwmon_item_strs[bit],
is_alarm ? "_alarm" : "");
fd = openat(hwm->hwmon_dir_fd, buf, O_RDONLY);
if (fd > 0) {
ssize_t read_len = read(fd, buf, sizeof(buf));
while (read_len > 0 && buf[read_len - 1] == '\n')
read_len--;
if (read_len > 0) {
long long val;
buf[read_len] = '\0';
val = strtoll(buf, /*endptr=*/NULL, 10);
len += snprintf(out_buf + len, out_buf_len - len, "%s%s%s=%g%s",
len == 0 ? " " : ", ",
hwmon_item_strs[bit],
is_alarm ? "_alarm" : "",
(double)val / 1000.0,
hwmon_units[key.type]);
}
close(fd);
}
}
return len;
}
int hwmon_pmu__for_each_event(struct perf_pmu *pmu, void *state, pmu_event_callback cb)
{
struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu);
struct hashmap_entry *cur;
size_t bkt;
if (hwmon_pmu__read_events(hwm))
return false;
hashmap__for_each_entry((&hwm->events), cur, bkt) {
static const char *const hwmon_scale_units[HWMON_TYPE_MAX] = {
NULL,
"0.001V", /* cpu */
"0.001A", /* curr */
"0.001J", /* energy */
"1rpm", /* fan */
"0.001%", /* humidity */
"0.001V", /* in */
NULL, /* intrusion */
"0.001W", /* power */
"1Hz", /* pwm */
"0.001'C", /* temp */
};
static const char *const hwmon_desc[HWMON_TYPE_MAX] = {
NULL,
"CPU core reference voltage", /* cpu */
"Current", /* curr */
"Cumulative energy use", /* energy */
"Fan", /* fan */
"Humidity", /* humidity */
"Voltage", /* in */
"Chassis intrusion detection", /* intrusion */
"Power use", /* power */
"Pulse width modulation fan control", /* pwm */
"Temperature", /* temp */
};
char alias_buf[64];
char desc_buf[256];
char encoding_buf[128];
union hwmon_pmu_event_key key = {
.type_and_num = cur->key,
};
struct hwmon_pmu_event_value *value = cur->pvalue;
struct pmu_event_info info = {
.pmu = pmu,
.name = value->name,
.alias = alias_buf,
.scale_unit = hwmon_scale_units[key.type],
.desc = desc_buf,
.long_desc = NULL,
.encoding_desc = encoding_buf,
.topic = "hwmon",
.pmu_name = pmu->name,
.event_type_desc = "Hwmon event",
};
int ret;
size_t len;
len = snprintf(alias_buf, sizeof(alias_buf), "%s%d",
hwmon_type_strs[key.type], key.num);
if (!info.name) {
info.name = info.alias;
info.alias = NULL;
}
len = snprintf(desc_buf, sizeof(desc_buf), "%s in unit %s named %s.",
hwmon_desc[key.type],
pmu->name + 6,
value->label ?: info.name);
len += hwmon_pmu__describe_items(hwm, desc_buf + len, sizeof(desc_buf) - len,
key, value->items, /*is_alarm=*/false);
len += hwmon_pmu__describe_items(hwm, desc_buf + len, sizeof(desc_buf) - len,
key, value->alarm_items, /*is_alarm=*/true);
snprintf(encoding_buf, sizeof(encoding_buf), "%s/config=0x%lx/",
pmu->name, cur->key);
ret = cb(state, &info);
if (ret)
return ret;
}
return 0;
}
size_t hwmon_pmu__num_events(struct perf_pmu *pmu)
{
struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu);
hwmon_pmu__read_events(hwm);
return hashmap__size(&hwm->events);
}
bool hwmon_pmu__have_event(struct perf_pmu *pmu, const char *name)
{
struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu);
enum hwmon_type type;
int number;
union hwmon_pmu_event_key key = { .type_and_num = 0 };
struct hashmap_entry *cur;
size_t bkt;
if (!parse_hwmon_filename(name, &type, &number, /*item=*/NULL, /*is_alarm=*/NULL))
return false;
if (hwmon_pmu__read_events(hwm))
return false;
key.type = type;
key.num = number;
if (hashmap_find(&hwm->events, key.type_and_num, /*value=*/NULL))
return true;
if (key.num != -1)
return false;
/* Item is of form <type>_ which means we should match <type>_<label>. */
hashmap__for_each_entry((&hwm->events), cur, bkt) {
struct hwmon_pmu_event_value *value = cur->pvalue;
key.type_and_num = cur->key;
if (key.type == type && value->name && !strcasecmp(name, value->name))
return true;
}
return false;
}
static int hwmon_pmu__config_term(const struct hwmon_pmu *hwm,
struct perf_event_attr *attr,
struct parse_events_term *term,
struct parse_events_error *err)
{
if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) {
enum hwmon_type type;
int number;
if (parse_hwmon_filename(term->config, &type, &number,
/*item=*/NULL, /*is_alarm=*/NULL)) {
if (number == -1) {
/*
* Item is of form <type>_ which means we should
* match <type>_<label>.
*/
struct hashmap_entry *cur;
size_t bkt;
attr->config = 0;
hashmap__for_each_entry((&hwm->events), cur, bkt) {
union hwmon_pmu_event_key key = {
.type_and_num = cur->key,
};
struct hwmon_pmu_event_value *value = cur->pvalue;
if (key.type == type && value->name &&
!strcasecmp(term->config, value->name)) {
attr->config = key.type_and_num;
break;
}
}
if (attr->config == 0)
return -EINVAL;
} else {
union hwmon_pmu_event_key key = {
.type_and_num = 0,
};
key.type = type;
key.num = number;
attr->config = key.type_and_num;
}
return 0;
}
}
if (err) {
char *err_str;
parse_events_error__handle(err, term->err_val,
asprintf(&err_str,
"unexpected hwmon event term (%s) %s",
parse_events__term_type_str(term->type_term),
term->config) < 0
? strdup("unexpected hwmon event term")
: err_str,
NULL);
}
return -EINVAL;
}
int hwmon_pmu__config_terms(const struct perf_pmu *pmu,
struct perf_event_attr *attr,
struct parse_events_terms *terms,
struct parse_events_error *err)
{
struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu);
struct parse_events_term *term;
int ret;
ret = hwmon_pmu__read_events(hwm);
if (ret)
return ret;
list_for_each_entry(term, &terms->terms, list) {
if (hwmon_pmu__config_term(hwm, attr, term, err))
return -EINVAL;
}
return 0;
}
int hwmon_pmu__check_alias(struct parse_events_terms *terms, struct perf_pmu_info *info,
struct parse_events_error *err)
{
struct parse_events_term *term =
list_first_entry(&terms->terms, struct parse_events_term, list);
if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) {
enum hwmon_type type;
int number;
if (parse_hwmon_filename(term->config, &type, &number,
/*item=*/NULL, /*is_alarm=*/NULL)) {
info->unit = hwmon_units[type];
if (type == HWMON_TYPE_FAN || type == HWMON_TYPE_PWM ||
type == HWMON_TYPE_INTRUSION)
info->scale = 1;
else
info->scale = 0.001;
}
return 0;
}
if (err) {
char *err_str;
parse_events_error__handle(err, term->err_val,
asprintf(&err_str,
"unexpected hwmon event term (%s) %s",
parse_events__term_type_str(term->type_term),
term->config) < 0
? strdup("unexpected hwmon event term")
: err_str,
NULL);
}
return -EINVAL;
}
int perf_pmus__read_hwmon_pmus(struct list_head *pmus)
{
char *line = NULL;
DIR *class_hwmon_dir;
struct dirent *class_hwmon_ent;
char buf[PATH_MAX];
const char *sysfs = sysfs__mountpoint();
if (!sysfs)
return 0;
scnprintf(buf, sizeof(buf), "%s/class/hwmon/", sysfs);
class_hwmon_dir = opendir(buf);
if (!class_hwmon_dir)
return 0;
while ((class_hwmon_ent = readdir(class_hwmon_dir)) != NULL) {
size_t line_len;
int hwmon_dir, name_fd;
struct io io;
if (class_hwmon_ent->d_type != DT_LNK)
continue;
scnprintf(buf, sizeof(buf), "%s/class/hwmon/%s", sysfs, class_hwmon_ent->d_name);
hwmon_dir = open(buf, O_DIRECTORY);
if (hwmon_dir == -1) {
pr_debug("hwmon_pmu: not a directory: '%s/class/hwmon/%s'\n",
sysfs, class_hwmon_ent->d_name);
continue;
}
name_fd = openat(hwmon_dir, "name", O_RDONLY);
if (name_fd == -1) {
pr_debug("hwmon_pmu: failure to open '%s/class/hwmon/%s/name'\n",
sysfs, class_hwmon_ent->d_name);
close(hwmon_dir);
continue;
}
io__init(&io, name_fd, buf, sizeof(buf));
io__getline(&io, &line, &line_len);
if (line_len > 0 && line[line_len - 1] == '\n')
line[line_len - 1] = '\0';
hwmon_pmu__new(pmus, hwmon_dir, class_hwmon_ent->d_name, line);
close(name_fd);
}
free(line);
closedir(class_hwmon_dir);
return 0;
}
#define FD(e, x, y) (*(int *)xyarray__entry(e->core.fd, x, y))
int evsel__hwmon_pmu_open(struct evsel *evsel,
struct perf_thread_map *threads,
int start_cpu_map_idx, int end_cpu_map_idx)
{
struct hwmon_pmu *hwm = container_of(evsel->pmu, struct hwmon_pmu, pmu);
union hwmon_pmu_event_key key = {
.type_and_num = evsel->core.attr.config,
};
int idx = 0, thread = 0, nthreads, err = 0;
nthreads = perf_thread_map__nr(threads);
for (idx = start_cpu_map_idx; idx < end_cpu_map_idx; idx++) {
for (thread = 0; thread < nthreads; thread++) {
char buf[64];
int fd;
snprintf(buf, sizeof(buf), "%s%d_input",
hwmon_type_strs[key.type], key.num);
fd = openat(hwm->hwmon_dir_fd, buf, O_RDONLY);
FD(evsel, idx, thread) = fd;
if (fd < 0) {
err = -errno;
goto out_close;
}
}
}
return 0;
out_close:
if (err)
threads->err_thread = thread;
do {
while (--thread >= 0) {
if (FD(evsel, idx, thread) >= 0)
close(FD(evsel, idx, thread));
FD(evsel, idx, thread) = -1;
}
thread = nthreads;
} while (--idx >= 0);
return err;
}
int evsel__hwmon_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread)
{
char buf[32];
int fd;
ssize_t len;
struct perf_counts_values *count, *old_count = NULL;
if (evsel->prev_raw_counts)
old_count = perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread);
count = perf_counts(evsel->counts, cpu_map_idx, thread);
fd = FD(evsel, cpu_map_idx, thread);
len = pread(fd, buf, sizeof(buf), 0);
if (len <= 0) {
count->lost++;
return -EINVAL;
}
buf[len] = '\0';
if (old_count) {
count->val = old_count->val + strtoll(buf, NULL, 10);
count->run = old_count->run + 1;
count->ena = old_count->ena + 1;
} else {
count->val = strtoll(buf, NULL, 10);
count->run++;
count->ena++;
}
return 0;
}