xref: /linux/tools/perf/util/time-utils.c (revision 8ab1b51fa45e29edcbd887208f046a2af0e92a08)
1  // SPDX-License-Identifier: GPL-2.0
2  #include <stdlib.h>
3  #include <string.h>
4  #include <linux/string.h>
5  #include <sys/time.h>
6  #include <linux/time64.h>
7  #include <time.h>
8  #include <errno.h>
9  #include <inttypes.h>
10  #include <math.h>
11  #include <linux/ctype.h>
12  
13  #include "debug.h"
14  #include "time-utils.h"
15  #include "session.h"
16  #include "evlist.h"
17  
18  int parse_nsec_time(const char *str, u64 *ptime)
19  {
20  	u64 time_sec, time_nsec;
21  	char *end;
22  
23  	time_sec = strtoull(str, &end, 10);
24  	if (*end != '.' && *end != '\0')
25  		return -1;
26  
27  	if (*end == '.') {
28  		int i;
29  		char nsec_buf[10];
30  
31  		if (strlen(++end) > 9)
32  			return -1;
33  
34  		strncpy(nsec_buf, end, 9);
35  		nsec_buf[9] = '\0';
36  
37  		/* make it nsec precision */
38  		for (i = strlen(nsec_buf); i < 9; i++)
39  			nsec_buf[i] = '0';
40  
41  		time_nsec = strtoull(nsec_buf, &end, 10);
42  		if (*end != '\0')
43  			return -1;
44  	} else
45  		time_nsec = 0;
46  
47  	*ptime = time_sec * NSEC_PER_SEC + time_nsec;
48  	return 0;
49  }
50  
51  static int parse_timestr_sec_nsec(struct perf_time_interval *ptime,
52  				  char *start_str, char *end_str)
53  {
54  	if (start_str && (*start_str != '\0') &&
55  	    (parse_nsec_time(start_str, &ptime->start) != 0)) {
56  		return -1;
57  	}
58  
59  	if (end_str && (*end_str != '\0') &&
60  	    (parse_nsec_time(end_str, &ptime->end) != 0)) {
61  		return -1;
62  	}
63  
64  	return 0;
65  }
66  
67  static int split_start_end(char **start, char **end, const char *ostr, char ch)
68  {
69  	char *start_str, *end_str;
70  	char *d, *str;
71  
72  	if (ostr == NULL || *ostr == '\0')
73  		return 0;
74  
75  	/* copy original string because we need to modify it */
76  	str = strdup(ostr);
77  	if (str == NULL)
78  		return -ENOMEM;
79  
80  	start_str = str;
81  	d = strchr(start_str, ch);
82  	if (d) {
83  		*d = '\0';
84  		++d;
85  	}
86  	end_str = d;
87  
88  	*start = start_str;
89  	*end = end_str;
90  
91  	return 0;
92  }
93  
94  int perf_time__parse_str(struct perf_time_interval *ptime, const char *ostr)
95  {
96  	char *start_str = NULL, *end_str;
97  	int rc;
98  
99  	rc = split_start_end(&start_str, &end_str, ostr, ',');
100  	if (rc || !start_str)
101  		return rc;
102  
103  	ptime->start = 0;
104  	ptime->end = 0;
105  
106  	rc = parse_timestr_sec_nsec(ptime, start_str, end_str);
107  
108  	free(start_str);
109  
110  	/* make sure end time is after start time if it was given */
111  	if (rc == 0 && ptime->end && ptime->end < ptime->start)
112  		return -EINVAL;
113  
114  	pr_debug("start time %" PRIu64 ", ", ptime->start);
115  	pr_debug("end time %" PRIu64 "\n", ptime->end);
116  
117  	return rc;
118  }
119  
120  static int perf_time__parse_strs(struct perf_time_interval *ptime,
121  				 const char *ostr, int size)
122  {
123  	const char *cp;
124  	char *str, *arg, *p;
125  	int i, num = 0, rc = 0;
126  
127  	/* Count the commas */
128  	for (cp = ostr; *cp; cp++)
129  		num += !!(*cp == ',');
130  
131  	if (!num)
132  		return -EINVAL;
133  
134  	BUG_ON(num > size);
135  
136  	str = strdup(ostr);
137  	if (!str)
138  		return -ENOMEM;
139  
140  	/* Split the string and parse each piece, except the last */
141  	for (i = 0, p = str; i < num - 1; i++) {
142  		arg = p;
143  		/* Find next comma, there must be one */
144  		p = skip_spaces(strchr(p, ',') + 1);
145  		/* Skip the value, must not contain space or comma */
146  		while (*p && !isspace(*p)) {
147  			if (*p++ == ',') {
148  				rc = -EINVAL;
149  				goto out;
150  			}
151  		}
152  		/* Split and parse */
153  		if (*p)
154  			*p++ = 0;
155  		rc = perf_time__parse_str(ptime + i, arg);
156  		if (rc < 0)
157  			goto out;
158  	}
159  
160  	/* Parse the last piece */
161  	rc = perf_time__parse_str(ptime + i, p);
162  	if (rc < 0)
163  		goto out;
164  
165  	/* Check there is no overlap */
166  	for (i = 0; i < num - 1; i++) {
167  		if (ptime[i].end >= ptime[i + 1].start) {
168  			rc = -EINVAL;
169  			goto out;
170  		}
171  	}
172  
173  	rc = num;
174  out:
175  	free(str);
176  
177  	return rc;
178  }
179  
180  static int parse_percent(double *pcnt, char *str)
181  {
182  	char *c, *endptr;
183  	double d;
184  
185  	c = strchr(str, '%');
186  	if (c)
187  		*c = '\0';
188  	else
189  		return -1;
190  
191  	d = strtod(str, &endptr);
192  	if (endptr != str + strlen(str))
193  		return -1;
194  
195  	*pcnt = d / 100.0;
196  	return 0;
197  }
198  
199  static int set_percent_time(struct perf_time_interval *ptime, double start_pcnt,
200  			    double end_pcnt, u64 start, u64 end)
201  {
202  	u64 total = end - start;
203  
204  	if (start_pcnt < 0.0 || start_pcnt > 1.0 ||
205  	    end_pcnt < 0.0 || end_pcnt > 1.0) {
206  		return -1;
207  	}
208  
209  	ptime->start = start + round(start_pcnt * total);
210  	ptime->end = start + round(end_pcnt * total);
211  
212  	if (ptime->end > ptime->start && ptime->end != end)
213  		ptime->end -= 1;
214  
215  	return 0;
216  }
217  
218  static int percent_slash_split(char *str, struct perf_time_interval *ptime,
219  			       u64 start, u64 end)
220  {
221  	char *p, *end_str;
222  	double pcnt, start_pcnt, end_pcnt;
223  	int i;
224  
225  	/*
226  	 * Example:
227  	 * 10%/2: select the second 10% slice and the third 10% slice
228  	 */
229  
230  	/* We can modify this string since the original one is copied */
231  	p = strchr(str, '/');
232  	if (!p)
233  		return -1;
234  
235  	*p = '\0';
236  	if (parse_percent(&pcnt, str) < 0)
237  		return -1;
238  
239  	p++;
240  	i = (int)strtol(p, &end_str, 10);
241  	if (*end_str)
242  		return -1;
243  
244  	if (pcnt <= 0.0)
245  		return -1;
246  
247  	start_pcnt = pcnt * (i - 1);
248  	end_pcnt = pcnt * i;
249  
250  	return set_percent_time(ptime, start_pcnt, end_pcnt, start, end);
251  }
252  
253  static int percent_dash_split(char *str, struct perf_time_interval *ptime,
254  			      u64 start, u64 end)
255  {
256  	char *start_str = NULL, *end_str;
257  	double start_pcnt, end_pcnt;
258  	int ret;
259  
260  	/*
261  	 * Example: 0%-10%
262  	 */
263  
264  	ret = split_start_end(&start_str, &end_str, str, '-');
265  	if (ret || !start_str)
266  		return ret;
267  
268  	if ((parse_percent(&start_pcnt, start_str) != 0) ||
269  	    (parse_percent(&end_pcnt, end_str) != 0)) {
270  		free(start_str);
271  		return -1;
272  	}
273  
274  	free(start_str);
275  
276  	return set_percent_time(ptime, start_pcnt, end_pcnt, start, end);
277  }
278  
279  typedef int (*time_pecent_split)(char *, struct perf_time_interval *,
280  				 u64 start, u64 end);
281  
282  static int percent_comma_split(struct perf_time_interval *ptime_buf, int num,
283  			       const char *ostr, u64 start, u64 end,
284  			       time_pecent_split func)
285  {
286  	char *str, *p1, *p2;
287  	int len, ret, i = 0;
288  
289  	str = strdup(ostr);
290  	if (str == NULL)
291  		return -ENOMEM;
292  
293  	len = strlen(str);
294  	p1 = str;
295  
296  	while (p1 < str + len) {
297  		if (i >= num) {
298  			free(str);
299  			return -1;
300  		}
301  
302  		p2 = strchr(p1, ',');
303  		if (p2)
304  			*p2 = '\0';
305  
306  		ret = (func)(p1, &ptime_buf[i], start, end);
307  		if (ret < 0) {
308  			free(str);
309  			return -1;
310  		}
311  
312  		pr_debug("start time %d: %" PRIu64 ", ", i, ptime_buf[i].start);
313  		pr_debug("end time %d: %" PRIu64 "\n", i, ptime_buf[i].end);
314  
315  		i++;
316  
317  		if (p2)
318  			p1 = p2 + 1;
319  		else
320  			break;
321  	}
322  
323  	free(str);
324  	return i;
325  }
326  
327  static int one_percent_convert(struct perf_time_interval *ptime_buf,
328  			       const char *ostr, u64 start, u64 end, char *c)
329  {
330  	char *str;
331  	int len = strlen(ostr), ret;
332  
333  	/*
334  	 * c points to '%'.
335  	 * '%' should be the last character
336  	 */
337  	if (ostr + len - 1 != c)
338  		return -1;
339  
340  	/*
341  	 * Construct a string like "xx%/1"
342  	 */
343  	str = malloc(len + 3);
344  	if (str == NULL)
345  		return -ENOMEM;
346  
347  	memcpy(str, ostr, len);
348  	strcpy(str + len, "/1");
349  
350  	ret = percent_slash_split(str, ptime_buf, start, end);
351  	if (ret == 0)
352  		ret = 1;
353  
354  	free(str);
355  	return ret;
356  }
357  
358  int perf_time__percent_parse_str(struct perf_time_interval *ptime_buf, int num,
359  				 const char *ostr, u64 start, u64 end)
360  {
361  	char *c;
362  
363  	/*
364  	 * ostr example:
365  	 * 10%/2,10%/3: select the second 10% slice and the third 10% slice
366  	 * 0%-10%,30%-40%: multiple time range
367  	 * 50%: just one percent
368  	 */
369  
370  	memset(ptime_buf, 0, sizeof(*ptime_buf) * num);
371  
372  	c = strchr(ostr, '/');
373  	if (c) {
374  		return percent_comma_split(ptime_buf, num, ostr, start,
375  					   end, percent_slash_split);
376  	}
377  
378  	c = strchr(ostr, '-');
379  	if (c) {
380  		return percent_comma_split(ptime_buf, num, ostr, start,
381  					   end, percent_dash_split);
382  	}
383  
384  	c = strchr(ostr, '%');
385  	if (c)
386  		return one_percent_convert(ptime_buf, ostr, start, end, c);
387  
388  	return -1;
389  }
390  
391  struct perf_time_interval *perf_time__range_alloc(const char *ostr, int *size)
392  {
393  	const char *p1, *p2;
394  	int i = 1;
395  	struct perf_time_interval *ptime;
396  
397  	/*
398  	 * At least allocate one time range.
399  	 */
400  	if (!ostr)
401  		goto alloc;
402  
403  	p1 = ostr;
404  	while (p1 < ostr + strlen(ostr)) {
405  		p2 = strchr(p1, ',');
406  		if (!p2)
407  			break;
408  
409  		p1 = p2 + 1;
410  		i++;
411  	}
412  
413  alloc:
414  	*size = i;
415  	ptime = calloc(i, sizeof(*ptime));
416  	return ptime;
417  }
418  
419  bool perf_time__skip_sample(struct perf_time_interval *ptime, u64 timestamp)
420  {
421  	/* if time is not set don't drop sample */
422  	if (timestamp == 0)
423  		return false;
424  
425  	/* otherwise compare sample time to time window */
426  	if ((ptime->start && timestamp < ptime->start) ||
427  	    (ptime->end && timestamp > ptime->end)) {
428  		return true;
429  	}
430  
431  	return false;
432  }
433  
434  bool perf_time__ranges_skip_sample(struct perf_time_interval *ptime_buf,
435  				   int num, u64 timestamp)
436  {
437  	struct perf_time_interval *ptime;
438  	int i;
439  
440  	if ((!ptime_buf) || (timestamp == 0) || (num == 0))
441  		return false;
442  
443  	if (num == 1)
444  		return perf_time__skip_sample(&ptime_buf[0], timestamp);
445  
446  	/*
447  	 * start/end of multiple time ranges must be valid.
448  	 */
449  	for (i = 0; i < num; i++) {
450  		ptime = &ptime_buf[i];
451  
452  		if (timestamp >= ptime->start &&
453  		    (timestamp <= ptime->end || !ptime->end)) {
454  			return false;
455  		}
456  	}
457  
458  	return true;
459  }
460  
461  int perf_time__parse_for_ranges_reltime(const char *time_str,
462  				struct perf_session *session,
463  				struct perf_time_interval **ranges,
464  				int *range_size, int *range_num,
465  				bool reltime)
466  {
467  	bool has_percent = strchr(time_str, '%');
468  	struct perf_time_interval *ptime_range;
469  	int size, num, ret = -EINVAL;
470  
471  	ptime_range = perf_time__range_alloc(time_str, &size);
472  	if (!ptime_range)
473  		return -ENOMEM;
474  
475  	if (has_percent || reltime) {
476  		if (session->evlist->first_sample_time == 0 &&
477  		    session->evlist->last_sample_time == 0) {
478  			pr_err("HINT: no first/last sample time found in perf data.\n"
479  			       "Please use latest perf binary to execute 'perf record'\n"
480  			       "(if '--buildid-all' is enabled, please set '--timestamp-boundary').\n");
481  			goto error;
482  		}
483  	}
484  
485  	if (has_percent) {
486  		num = perf_time__percent_parse_str(
487  				ptime_range, size,
488  				time_str,
489  				session->evlist->first_sample_time,
490  				session->evlist->last_sample_time);
491  	} else {
492  		num = perf_time__parse_strs(ptime_range, time_str, size);
493  	}
494  
495  	if (num < 0)
496  		goto error_invalid;
497  
498  	if (reltime) {
499  		int i;
500  
501  		for (i = 0; i < num; i++) {
502  			ptime_range[i].start += session->evlist->first_sample_time;
503  			ptime_range[i].end += session->evlist->first_sample_time;
504  		}
505  	}
506  
507  	*range_size = size;
508  	*range_num = num;
509  	*ranges = ptime_range;
510  	return 0;
511  
512  error_invalid:
513  	pr_err("Invalid time string\n");
514  error:
515  	free(ptime_range);
516  	return ret;
517  }
518  
519  int perf_time__parse_for_ranges(const char *time_str,
520  				struct perf_session *session,
521  				struct perf_time_interval **ranges,
522  				int *range_size, int *range_num)
523  {
524  	return perf_time__parse_for_ranges_reltime(time_str, session, ranges,
525  					range_size, range_num, false);
526  }
527  
528  int timestamp__scnprintf_usec(u64 timestamp, char *buf, size_t sz)
529  {
530  	u64  sec = timestamp / NSEC_PER_SEC;
531  	u64 usec = (timestamp % NSEC_PER_SEC) / NSEC_PER_USEC;
532  
533  	return scnprintf(buf, sz, "%"PRIu64".%06"PRIu64, sec, usec);
534  }
535  
536  int timestamp__scnprintf_nsec(u64 timestamp, char *buf, size_t sz)
537  {
538  	u64 sec  = timestamp / NSEC_PER_SEC,
539  	    nsec = timestamp % NSEC_PER_SEC;
540  
541  	return scnprintf(buf, sz, "%" PRIu64 ".%09" PRIu64, sec, nsec);
542  }
543  
544  int fetch_current_timestamp(char *buf, size_t sz)
545  {
546  	struct timeval tv;
547  	struct tm tm;
548  	char dt[32];
549  
550  	if (gettimeofday(&tv, NULL) || !localtime_r(&tv.tv_sec, &tm))
551  		return -1;
552  
553  	if (!strftime(dt, sizeof(dt), "%Y%m%d%H%M%S", &tm))
554  		return -1;
555  
556  	scnprintf(buf, sz, "%s%02u", dt, (unsigned)tv.tv_usec / 10000);
557  
558  	return 0;
559  }
560