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(p: nsec_buf, q: end, size: 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(str: start_str, ptime: &ptime->start) != 0)) { |
56 | return -1; |
57 | } |
58 | |
59 | if (end_str && (*end_str != '\0') && |
60 | (parse_nsec_time(str: end_str, ptime: &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: &start_str, end: &end_str, ostr, ch: ','); |
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: ptime + i, ostr: arg); |
156 | if (rc < 0) |
157 | goto out; |
158 | } |
159 | |
160 | /* Parse the last piece */ |
161 | rc = perf_time__parse_str(ptime: ptime + i, ostr: 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: &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: &start_str, end: &end_str, ostr: str, ch: '-'); |
265 | if (ret || !start_str) |
266 | return ret; |
267 | |
268 | if ((parse_percent(pcnt: &start_pcnt, str: start_str) != 0) || |
269 | (parse_percent(pcnt: &end_pcnt, str: 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(p: str + len, q: "/1" ); |
349 | |
350 | ret = percent_slash_split(str, ptime: 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, func: percent_slash_split); |
376 | } |
377 | |
378 | c = strchr(ostr, '-'); |
379 | if (c) { |
380 | return percent_comma_split(ptime_buf, num, ostr, start, |
381 | end, func: 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: &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(ostr: time_str, size: &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_buf: ptime_range, num: size, |
488 | ostr: time_str, |
489 | start: session->evlist->first_sample_time, |
490 | end: session->evlist->last_sample_time); |
491 | } else { |
492 | num = perf_time__parse_strs(ptime: ptime_range, ostr: 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, reltime: 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, size: sz, fmt: "%s%02u" , dt, (unsigned)tv.tv_usec / 10000); |
557 | |
558 | return 0; |
559 | } |
560 | |