xref: /linux/tools/perf/util/time-utils.c (revision 9f3926e08c26607a0dd5b1bc8a8aa1d03f72fcdc)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/time.h>
5 #include <linux/time64.h>
6 #include <time.h>
7 #include <errno.h>
8 #include <inttypes.h>
9 #include <math.h>
10 #include <ctype.h>
11 
12 #include "perf.h"
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 = strtoul(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 = strtoul(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 = strchr(p, ',') + 1;
145 		/* Skip white space */
146 		while (isspace(*p))
147 			p++;
148 		/* Skip the value, must not contain space or comma */
149 		while (*p && !isspace(*p)) {
150 			if (*p++ == ',') {
151 				rc = -EINVAL;
152 				goto out;
153 			}
154 		}
155 		/* Split and parse */
156 		if (*p)
157 			*p++ = 0;
158 		rc = perf_time__parse_str(ptime + i, arg);
159 		if (rc < 0)
160 			goto out;
161 	}
162 
163 	/* Parse the last piece */
164 	rc = perf_time__parse_str(ptime + i, p);
165 	if (rc < 0)
166 		goto out;
167 
168 	/* Check there is no overlap */
169 	for (i = 0; i < num - 1; i++) {
170 		if (ptime[i].end >= ptime[i + 1].start) {
171 			rc = -EINVAL;
172 			goto out;
173 		}
174 	}
175 
176 	rc = num;
177 out:
178 	free(str);
179 
180 	return rc;
181 }
182 
183 static int parse_percent(double *pcnt, char *str)
184 {
185 	char *c, *endptr;
186 	double d;
187 
188 	c = strchr(str, '%');
189 	if (c)
190 		*c = '\0';
191 	else
192 		return -1;
193 
194 	d = strtod(str, &endptr);
195 	if (endptr != str + strlen(str))
196 		return -1;
197 
198 	*pcnt = d / 100.0;
199 	return 0;
200 }
201 
202 static int set_percent_time(struct perf_time_interval *ptime, double start_pcnt,
203 			    double end_pcnt, u64 start, u64 end)
204 {
205 	u64 total = end - start;
206 
207 	if (start_pcnt < 0.0 || start_pcnt > 1.0 ||
208 	    end_pcnt < 0.0 || end_pcnt > 1.0) {
209 		return -1;
210 	}
211 
212 	ptime->start = start + round(start_pcnt * total);
213 	ptime->end = start + round(end_pcnt * total);
214 
215 	if (ptime->end > ptime->start && ptime->end != end)
216 		ptime->end -= 1;
217 
218 	return 0;
219 }
220 
221 static int percent_slash_split(char *str, struct perf_time_interval *ptime,
222 			       u64 start, u64 end)
223 {
224 	char *p, *end_str;
225 	double pcnt, start_pcnt, end_pcnt;
226 	int i;
227 
228 	/*
229 	 * Example:
230 	 * 10%/2: select the second 10% slice and the third 10% slice
231 	 */
232 
233 	/* We can modify this string since the original one is copied */
234 	p = strchr(str, '/');
235 	if (!p)
236 		return -1;
237 
238 	*p = '\0';
239 	if (parse_percent(&pcnt, str) < 0)
240 		return -1;
241 
242 	p++;
243 	i = (int)strtol(p, &end_str, 10);
244 	if (*end_str)
245 		return -1;
246 
247 	if (pcnt <= 0.0)
248 		return -1;
249 
250 	start_pcnt = pcnt * (i - 1);
251 	end_pcnt = pcnt * i;
252 
253 	return set_percent_time(ptime, start_pcnt, end_pcnt, start, end);
254 }
255 
256 static int percent_dash_split(char *str, struct perf_time_interval *ptime,
257 			      u64 start, u64 end)
258 {
259 	char *start_str = NULL, *end_str;
260 	double start_pcnt, end_pcnt;
261 	int ret;
262 
263 	/*
264 	 * Example: 0%-10%
265 	 */
266 
267 	ret = split_start_end(&start_str, &end_str, str, '-');
268 	if (ret || !start_str)
269 		return ret;
270 
271 	if ((parse_percent(&start_pcnt, start_str) != 0) ||
272 	    (parse_percent(&end_pcnt, end_str) != 0)) {
273 		free(start_str);
274 		return -1;
275 	}
276 
277 	free(start_str);
278 
279 	return set_percent_time(ptime, start_pcnt, end_pcnt, start, end);
280 }
281 
282 typedef int (*time_pecent_split)(char *, struct perf_time_interval *,
283 				 u64 start, u64 end);
284 
285 static int percent_comma_split(struct perf_time_interval *ptime_buf, int num,
286 			       const char *ostr, u64 start, u64 end,
287 			       time_pecent_split func)
288 {
289 	char *str, *p1, *p2;
290 	int len, ret, i = 0;
291 
292 	str = strdup(ostr);
293 	if (str == NULL)
294 		return -ENOMEM;
295 
296 	len = strlen(str);
297 	p1 = str;
298 
299 	while (p1 < str + len) {
300 		if (i >= num) {
301 			free(str);
302 			return -1;
303 		}
304 
305 		p2 = strchr(p1, ',');
306 		if (p2)
307 			*p2 = '\0';
308 
309 		ret = (func)(p1, &ptime_buf[i], start, end);
310 		if (ret < 0) {
311 			free(str);
312 			return -1;
313 		}
314 
315 		pr_debug("start time %d: %" PRIu64 ", ", i, ptime_buf[i].start);
316 		pr_debug("end time %d: %" PRIu64 "\n", i, ptime_buf[i].end);
317 
318 		i++;
319 
320 		if (p2)
321 			p1 = p2 + 1;
322 		else
323 			break;
324 	}
325 
326 	free(str);
327 	return i;
328 }
329 
330 static int one_percent_convert(struct perf_time_interval *ptime_buf,
331 			       const char *ostr, u64 start, u64 end, char *c)
332 {
333 	char *str;
334 	int len = strlen(ostr), ret;
335 
336 	/*
337 	 * c points to '%'.
338 	 * '%' should be the last character
339 	 */
340 	if (ostr + len - 1 != c)
341 		return -1;
342 
343 	/*
344 	 * Construct a string like "xx%/1"
345 	 */
346 	str = malloc(len + 3);
347 	if (str == NULL)
348 		return -ENOMEM;
349 
350 	memcpy(str, ostr, len);
351 	strcpy(str + len, "/1");
352 
353 	ret = percent_slash_split(str, ptime_buf, start, end);
354 	if (ret == 0)
355 		ret = 1;
356 
357 	free(str);
358 	return ret;
359 }
360 
361 int perf_time__percent_parse_str(struct perf_time_interval *ptime_buf, int num,
362 				 const char *ostr, u64 start, u64 end)
363 {
364 	char *c;
365 
366 	/*
367 	 * ostr example:
368 	 * 10%/2,10%/3: select the second 10% slice and the third 10% slice
369 	 * 0%-10%,30%-40%: multiple time range
370 	 * 50%: just one percent
371 	 */
372 
373 	memset(ptime_buf, 0, sizeof(*ptime_buf) * num);
374 
375 	c = strchr(ostr, '/');
376 	if (c) {
377 		return percent_comma_split(ptime_buf, num, ostr, start,
378 					   end, percent_slash_split);
379 	}
380 
381 	c = strchr(ostr, '-');
382 	if (c) {
383 		return percent_comma_split(ptime_buf, num, ostr, start,
384 					   end, percent_dash_split);
385 	}
386 
387 	c = strchr(ostr, '%');
388 	if (c)
389 		return one_percent_convert(ptime_buf, ostr, start, end, c);
390 
391 	return -1;
392 }
393 
394 struct perf_time_interval *perf_time__range_alloc(const char *ostr, int *size)
395 {
396 	const char *p1, *p2;
397 	int i = 1;
398 	struct perf_time_interval *ptime;
399 
400 	/*
401 	 * At least allocate one time range.
402 	 */
403 	if (!ostr)
404 		goto alloc;
405 
406 	p1 = ostr;
407 	while (p1 < ostr + strlen(ostr)) {
408 		p2 = strchr(p1, ',');
409 		if (!p2)
410 			break;
411 
412 		p1 = p2 + 1;
413 		i++;
414 	}
415 
416 alloc:
417 	*size = i;
418 	ptime = calloc(i, sizeof(*ptime));
419 	return ptime;
420 }
421 
422 bool perf_time__skip_sample(struct perf_time_interval *ptime, u64 timestamp)
423 {
424 	/* if time is not set don't drop sample */
425 	if (timestamp == 0)
426 		return false;
427 
428 	/* otherwise compare sample time to time window */
429 	if ((ptime->start && timestamp < ptime->start) ||
430 	    (ptime->end && timestamp > ptime->end)) {
431 		return true;
432 	}
433 
434 	return false;
435 }
436 
437 bool perf_time__ranges_skip_sample(struct perf_time_interval *ptime_buf,
438 				   int num, u64 timestamp)
439 {
440 	struct perf_time_interval *ptime;
441 	int i;
442 
443 	if ((!ptime_buf) || (timestamp == 0) || (num == 0))
444 		return false;
445 
446 	if (num == 1)
447 		return perf_time__skip_sample(&ptime_buf[0], timestamp);
448 
449 	/*
450 	 * start/end of multiple time ranges must be valid.
451 	 */
452 	for (i = 0; i < num; i++) {
453 		ptime = &ptime_buf[i];
454 
455 		if (timestamp >= ptime->start &&
456 		    (timestamp <= ptime->end || !ptime->end)) {
457 			return false;
458 		}
459 	}
460 
461 	return true;
462 }
463 
464 int perf_time__parse_for_ranges(const char *time_str,
465 				struct perf_session *session,
466 				struct perf_time_interval **ranges,
467 				int *range_size, int *range_num)
468 {
469 	bool has_percent = strchr(time_str, '%');
470 	struct perf_time_interval *ptime_range;
471 	int size, num, ret = -EINVAL;
472 
473 	ptime_range = perf_time__range_alloc(time_str, &size);
474 	if (!ptime_range)
475 		return -ENOMEM;
476 
477 	if (has_percent) {
478 		if (session->evlist->first_sample_time == 0 &&
479 		    session->evlist->last_sample_time == 0) {
480 			pr_err("HINT: no first/last sample time found in perf data.\n"
481 			       "Please use latest perf binary to execute 'perf record'\n"
482 			       "(if '--buildid-all' is enabled, please set '--timestamp-boundary').\n");
483 			goto error;
484 		}
485 
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 	*range_size = size;
499 	*range_num = num;
500 	*ranges = ptime_range;
501 	return 0;
502 
503 error_invalid:
504 	pr_err("Invalid time string\n");
505 error:
506 	free(ptime_range);
507 	return ret;
508 }
509 
510 int timestamp__scnprintf_usec(u64 timestamp, char *buf, size_t sz)
511 {
512 	u64  sec = timestamp / NSEC_PER_SEC;
513 	u64 usec = (timestamp % NSEC_PER_SEC) / NSEC_PER_USEC;
514 
515 	return scnprintf(buf, sz, "%"PRIu64".%06"PRIu64, sec, usec);
516 }
517 
518 int timestamp__scnprintf_nsec(u64 timestamp, char *buf, size_t sz)
519 {
520 	u64 sec  = timestamp / NSEC_PER_SEC,
521 	    nsec = timestamp % NSEC_PER_SEC;
522 
523 	return scnprintf(buf, sz, "%" PRIu64 ".%09" PRIu64, sec, nsec);
524 }
525 
526 int fetch_current_timestamp(char *buf, size_t sz)
527 {
528 	struct timeval tv;
529 	struct tm tm;
530 	char dt[32];
531 
532 	if (gettimeofday(&tv, NULL) || !localtime_r(&tv.tv_sec, &tm))
533 		return -1;
534 
535 	if (!strftime(dt, sizeof(dt), "%Y%m%d%H%M%S", &tm))
536 		return -1;
537 
538 	scnprintf(buf, sz, "%s%02u", dt, (unsigned)tv.tv_usec / 10000);
539 
540 	return 0;
541 }
542