root/tools/perf/util/time-utils.c
// 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 = strtoull(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 = strtoull(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, const 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)
{
        const 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;
}