xref: /openbmc/linux/tools/perf/util/time-utils.c (revision 3cf3cdea)
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 = 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 = 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