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