// SPDX-License-Identifier: GPL-2.0 #include <stdlib.h> #include <string.h> #include <linux/string.h> #include <sys/time.h> #include <linux/time64.h> #include <time.h> #include <errno.h> #include <inttypes.h> #include <math.h> #include <linux/ctype.h> #include "debug.h" #include "time-utils.h" #include "session.h" #include "evlist.h" int parse_nsec_time(const char *str, u64 *ptime) { u64 time_sec, time_nsec; char *end; time_sec = strtoul(str, &end, 10); if (*end != '.' && *end != '\0') return -1; if (*end == '.') { int i; char nsec_buf[10]; if (strlen(++end) > 9) return -1; strncpy(nsec_buf, end, 9); nsec_buf[9] = '\0'; /* make it nsec precision */ for (i = strlen(nsec_buf); i < 9; i++) nsec_buf[i] = '0'; time_nsec = strtoul(nsec_buf, &end, 10); if (*end != '\0') return -1; } else time_nsec = 0; *ptime = time_sec * NSEC_PER_SEC + time_nsec; return 0; } static int parse_timestr_sec_nsec(struct perf_time_interval *ptime, char *start_str, char *end_str) { if (start_str && (*start_str != '\0') && (parse_nsec_time(start_str, &ptime->start) != 0)) { return -1; } if (end_str && (*end_str != '\0') && (parse_nsec_time(end_str, &ptime->end) != 0)) { return -1; } return 0; } static int split_start_end(char **start, char **end, const char *ostr, char ch) { char *start_str, *end_str; char *d, *str; if (ostr == NULL || *ostr == '\0') return 0; /* copy original string because we need to modify it */ str = strdup(ostr); if (str == NULL) return -ENOMEM; start_str = str; d = strchr(start_str, ch); if (d) { *d = '\0'; ++d; } end_str = d; *start = start_str; *end = end_str; return 0; } int perf_time__parse_str(struct perf_time_interval *ptime, const char *ostr) { char *start_str = NULL, *end_str; int rc; rc = split_start_end(&start_str, &end_str, ostr, ','); if (rc || !start_str) return rc; ptime->start = 0; ptime->end = 0; rc = parse_timestr_sec_nsec(ptime, start_str, end_str); free(start_str); /* make sure end time is after start time if it was given */ if (rc == 0 && ptime->end && ptime->end < ptime->start) return -EINVAL; pr_debug("start time %" PRIu64 ", ", ptime->start); pr_debug("end time %" PRIu64 "\n", ptime->end); return rc; } static int perf_time__parse_strs(struct perf_time_interval *ptime, const char *ostr, int size) { const char *cp; char *str, *arg, *p; int i, num = 0, rc = 0; /* Count the commas */ for (cp = ostr; *cp; cp++) num += !!(*cp == ','); if (!num) return -EINVAL; BUG_ON(num > size); str = strdup(ostr); if (!str) return -ENOMEM; /* Split the string and parse each piece, except the last */ for (i = 0, p = str; i < num - 1; i++) { arg = p; /* Find next comma, there must be one */ p = skip_spaces(strchr(p, ',') + 1); /* Skip the value, must not contain space or comma */ while (*p && !isspace(*p)) { if (*p++ == ',') { rc = -EINVAL; goto out; } } /* Split and parse */ if (*p) *p++ = 0; rc = perf_time__parse_str(ptime + i, arg); if (rc < 0) goto out; } /* Parse the last piece */ rc = perf_time__parse_str(ptime + i, p); if (rc < 0) goto out; /* Check there is no overlap */ for (i = 0; i < num - 1; i++) { if (ptime[i].end >= ptime[i + 1].start) { rc = -EINVAL; goto out; } } rc = num; out: free(str); return rc; } static int parse_percent(double *pcnt, char *str) { char *c, *endptr; double d; c = strchr(str, '%'); if (c) *c = '\0'; else return -1; d = strtod(str, &endptr); if (endptr != str + strlen(str)) return -1; *pcnt = d / 100.0; return 0; } static int set_percent_time(struct perf_time_interval *ptime, double start_pcnt, double end_pcnt, u64 start, u64 end) { u64 total = end - start; if (start_pcnt < 0.0 || start_pcnt > 1.0 || end_pcnt < 0.0 || end_pcnt > 1.0) { return -1; } ptime->start = start + round(start_pcnt * total); ptime->end = start + round(end_pcnt * total); if (ptime->end > ptime->start && ptime->end != end) ptime->end -= 1; return 0; } static int percent_slash_split(char *str, struct perf_time_interval *ptime, u64 start, u64 end) { char *p, *end_str; double pcnt, start_pcnt, end_pcnt; int i; /* * Example: * 10%/2: select the second 10% slice and the third 10% slice */ /* We can modify this string since the original one is copied */ p = strchr(str, '/'); if (!p) return -1; *p = '\0'; if (parse_percent(&pcnt, str) < 0) return -1; p++; i = (int)strtol(p, &end_str, 10); if (*end_str) return -1; if (pcnt <= 0.0) return -1; start_pcnt = pcnt * (i - 1); end_pcnt = pcnt * i; return set_percent_time(ptime, start_pcnt, end_pcnt, start, end); } static int percent_dash_split(char *str, struct perf_time_interval *ptime, u64 start, u64 end) { char *start_str = NULL, *end_str; double start_pcnt, end_pcnt; int ret; /* * Example: 0%-10% */ ret = split_start_end(&start_str, &end_str, str, '-'); if (ret || !start_str) return ret; if ((parse_percent(&start_pcnt, start_str) != 0) || (parse_percent(&end_pcnt, end_str) != 0)) { free(start_str); return -1; } free(start_str); return set_percent_time(ptime, start_pcnt, end_pcnt, start, end); } typedef int (*time_pecent_split)(char *, struct perf_time_interval *, u64 start, u64 end); static int percent_comma_split(struct perf_time_interval *ptime_buf, int num, const char *ostr, u64 start, u64 end, time_pecent_split func) { char *str, *p1, *p2; int len, ret, i = 0; str = strdup(ostr); if (str == NULL) return -ENOMEM; len = strlen(str); p1 = str; while (p1 < str + len) { if (i >= num) { free(str); return -1; } p2 = strchr(p1, ','); if (p2) *p2 = '\0'; ret = (func)(p1, &ptime_buf[i], start, end); if (ret < 0) { free(str); return -1; } pr_debug("start time %d: %" PRIu64 ", ", i, ptime_buf[i].start); pr_debug("end time %d: %" PRIu64 "\n", i, ptime_buf[i].end); i++; if (p2) p1 = p2 + 1; else break; } free(str); return i; } static int one_percent_convert(struct perf_time_interval *ptime_buf, const char *ostr, u64 start, u64 end, char *c) { char *str; int len = strlen(ostr), ret; /* * c points to '%'. * '%' should be the last character */ if (ostr + len - 1 != c) return -1; /* * Construct a string like "xx%/1" */ str = malloc(len + 3); if (str == NULL) return -ENOMEM; memcpy(str, ostr, len); strcpy(str + len, "/1"); ret = percent_slash_split(str, ptime_buf, start, end); if (ret == 0) ret = 1; free(str); return ret; } int perf_time__percent_parse_str(struct perf_time_interval *ptime_buf, int num, const char *ostr, u64 start, u64 end) { char *c; /* * ostr example: * 10%/2,10%/3: select the second 10% slice and the third 10% slice * 0%-10%,30%-40%: multiple time range * 50%: just one percent */ memset(ptime_buf, 0, sizeof(*ptime_buf) * num); c = strchr(ostr, '/'); if (c) { return percent_comma_split(ptime_buf, num, ostr, start, end, percent_slash_split); } c = strchr(ostr, '-'); if (c) { return percent_comma_split(ptime_buf, num, ostr, start, end, percent_dash_split); } c = strchr(ostr, '%'); if (c) return one_percent_convert(ptime_buf, ostr, start, end, c); return -1; } struct perf_time_interval *perf_time__range_alloc(const char *ostr, int *size) { const char *p1, *p2; int i = 1; struct perf_time_interval *ptime; /* * At least allocate one time range. */ if (!ostr) goto alloc; p1 = ostr; while (p1 < ostr + strlen(ostr)) { p2 = strchr(p1, ','); if (!p2) break; p1 = p2 + 1; i++; } alloc: *size = i; ptime = calloc(i, sizeof(*ptime)); return ptime; } bool perf_time__skip_sample(struct perf_time_interval *ptime, u64 timestamp) { /* if time is not set don't drop sample */ if (timestamp == 0) return false; /* otherwise compare sample time to time window */ if ((ptime->start && timestamp < ptime->start) || (ptime->end && timestamp > ptime->end)) { return true; } return false; } bool perf_time__ranges_skip_sample(struct perf_time_interval *ptime_buf, int num, u64 timestamp) { struct perf_time_interval *ptime; int i; if ((!ptime_buf) || (timestamp == 0) || (num == 0)) return false; if (num == 1) return perf_time__skip_sample(&ptime_buf[0], timestamp); /* * start/end of multiple time ranges must be valid. */ for (i = 0; i < num; i++) { ptime = &ptime_buf[i]; if (timestamp >= ptime->start && (timestamp <= ptime->end || !ptime->end)) { return false; } } return true; } int perf_time__parse_for_ranges_reltime(const char *time_str, struct perf_session *session, struct perf_time_interval **ranges, int *range_size, int *range_num, bool reltime) { bool has_percent = strchr(time_str, '%'); struct perf_time_interval *ptime_range; int size, num, ret = -EINVAL; ptime_range = perf_time__range_alloc(time_str, &size); if (!ptime_range) return -ENOMEM; if (has_percent || reltime) { if (session->evlist->first_sample_time == 0 && session->evlist->last_sample_time == 0) { pr_err("HINT: no first/last sample time found in perf data.\n" "Please use latest perf binary to execute 'perf record'\n" "(if '--buildid-all' is enabled, please set '--timestamp-boundary').\n"); goto error; } } if (has_percent) { num = perf_time__percent_parse_str( ptime_range, size, time_str, session->evlist->first_sample_time, session->evlist->last_sample_time); } else { num = perf_time__parse_strs(ptime_range, time_str, size); } if (num < 0) goto error_invalid; if (reltime) { int i; for (i = 0; i < num; i++) { ptime_range[i].start += session->evlist->first_sample_time; ptime_range[i].end += session->evlist->first_sample_time; } } *range_size = size; *range_num = num; *ranges = ptime_range; return 0; error_invalid: pr_err("Invalid time string\n"); error: free(ptime_range); return ret; } int perf_time__parse_for_ranges(const char *time_str, struct perf_session *session, struct perf_time_interval **ranges, int *range_size, int *range_num) { return perf_time__parse_for_ranges_reltime(time_str, session, ranges, range_size, range_num, false); } int timestamp__scnprintf_usec(u64 timestamp, char *buf, size_t sz) { u64 sec = timestamp / NSEC_PER_SEC; u64 usec = (timestamp % NSEC_PER_SEC) / NSEC_PER_USEC; return scnprintf(buf, sz, "%"PRIu64".%06"PRIu64, sec, usec); } int timestamp__scnprintf_nsec(u64 timestamp, char *buf, size_t sz) { u64 sec = timestamp / NSEC_PER_SEC, nsec = timestamp % NSEC_PER_SEC; return scnprintf(buf, sz, "%" PRIu64 ".%09" PRIu64, sec, nsec); } int fetch_current_timestamp(char *buf, size_t sz) { struct timeval tv; struct tm tm; char dt[32]; if (gettimeofday(&tv, NULL) || !localtime_r(&tv.tv_sec, &tm)) return -1; if (!strftime(dt, sizeof(dt), "%Y%m%d%H%M%S", &tm)) return -1; scnprintf(buf, sz, "%s%02u", dt, (unsigned)tv.tv_usec / 10000); return 0; }