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(const char *time_str, 462 struct perf_session *session, 463 struct perf_time_interval **ranges, 464 int *range_size, int *range_num) 465 { 466 bool has_percent = strchr(time_str, '%'); 467 struct perf_time_interval *ptime_range; 468 int size, num, ret = -EINVAL; 469 470 ptime_range = perf_time__range_alloc(time_str, &size); 471 if (!ptime_range) 472 return -ENOMEM; 473 474 if (has_percent) { 475 if (session->evlist->first_sample_time == 0 && 476 session->evlist->last_sample_time == 0) { 477 pr_err("HINT: no first/last sample time found in perf data.\n" 478 "Please use latest perf binary to execute 'perf record'\n" 479 "(if '--buildid-all' is enabled, please set '--timestamp-boundary').\n"); 480 goto error; 481 } 482 483 num = perf_time__percent_parse_str( 484 ptime_range, size, 485 time_str, 486 session->evlist->first_sample_time, 487 session->evlist->last_sample_time); 488 } else { 489 num = perf_time__parse_strs(ptime_range, time_str, size); 490 } 491 492 if (num < 0) 493 goto error_invalid; 494 495 *range_size = size; 496 *range_num = num; 497 *ranges = ptime_range; 498 return 0; 499 500 error_invalid: 501 pr_err("Invalid time string\n"); 502 error: 503 free(ptime_range); 504 return ret; 505 } 506 507 int timestamp__scnprintf_usec(u64 timestamp, char *buf, size_t sz) 508 { 509 u64 sec = timestamp / NSEC_PER_SEC; 510 u64 usec = (timestamp % NSEC_PER_SEC) / NSEC_PER_USEC; 511 512 return scnprintf(buf, sz, "%"PRIu64".%06"PRIu64, sec, usec); 513 } 514 515 int timestamp__scnprintf_nsec(u64 timestamp, char *buf, size_t sz) 516 { 517 u64 sec = timestamp / NSEC_PER_SEC, 518 nsec = timestamp % NSEC_PER_SEC; 519 520 return scnprintf(buf, sz, "%" PRIu64 ".%09" PRIu64, sec, nsec); 521 } 522 523 int fetch_current_timestamp(char *buf, size_t sz) 524 { 525 struct timeval tv; 526 struct tm tm; 527 char dt[32]; 528 529 if (gettimeofday(&tv, NULL) || !localtime_r(&tv.tv_sec, &tm)) 530 return -1; 531 532 if (!strftime(dt, sizeof(dt), "%Y%m%d%H%M%S", &tm)) 533 return -1; 534 535 scnprintf(buf, sz, "%s%02u", dt, (unsigned)tv.tv_usec / 10000); 536 537 return 0; 538 } 539