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 "perf.h" 14 #include "debug.h" 15 #include "time-utils.h" 16 #include "session.h" 17 #include "evlist.h" 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 } 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 68 static int split_start_end(char **start, char **end, const char *ostr, char ch) 69 { 70 char *start_str, *end_str; 71 char *d, *str; 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 81 start_str = str; 82 d = strchr(start_str, ch); 83 if (d) { 84 *d = '\0'; 85 ++d; 86 } 87 end_str = d; 88 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 107 rc = parse_timestr_sec_nsec(ptime, start_str, end_str); 108 109 free(start_str); 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 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 */ 145 p = skip_spaces(strchr(p, ',') + 1); 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 181 static int parse_percent(double *pcnt, char *str) 182 { 183 char *c, *endptr; 184 double d; 185 186 c = strchr(str, '%'); 187 if (c) 188 *c = '\0'; 189 else 190 return -1; 191 192 d = strtod(str, &endptr); 193 if (endptr != str + strlen(str)) 194 return -1; 195 196 *pcnt = d / 100.0; 197 return 0; 198 } 199 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 213 if (ptime->end > ptime->start && ptime->end != end) 214 ptime->end -= 1; 215 216 return 0; 217 } 218 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; 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 251 return set_percent_time(ptime, start_pcnt, end_pcnt, start, end); 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; 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 277 return set_percent_time(ptime, start_pcnt, end_pcnt, start, end); 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 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 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 368 * 50%: just one percent 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 385 c = strchr(ostr, '%'); 386 if (c) 387 return one_percent_convert(ptime_buf, ostr, start, end, c); 388 389 return -1; 390 } 391 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 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 } 434 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 441 if ((!ptime_buf) || (timestamp == 0) || (num == 0)) 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 && 454 (timestamp <= ptime->end || !ptime->end)) { 455 return false; 456 } 457 } 458 459 return true; 460 } 461 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 { 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) { 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 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); 489 } else { 490 num = perf_time__parse_strs(ptime_range, time_str, size); 491 } 492 493 if (num < 0) 494 goto error_invalid; 495 496 *range_size = size; 497 *range_num = num; 498 *ranges = ptime_range; 499 return 0; 500 501 error_invalid: 502 pr_err("Invalid time string\n"); 503 error: 504 free(ptime_range); 505 return ret; 506 } 507 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 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 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 } 540