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