|
1 | | -from typing import TypedDict |
| 1 | +from typing import Optional, TypedDict |
2 | 2 | from datetime import datetime |
3 | 3 | from django.db import connection |
| 4 | +from itertools import repeat |
4 | 5 |
|
5 | 6 | from kernelCI_app.helpers.database import dict_fetchall |
6 | 7 | from kernelCI_app.cache import get_query_cache, set_query_cache |
@@ -339,15 +340,250 @@ def get_hardware_details_data( |
339 | 340 | return records |
340 | 341 |
|
341 | 342 |
|
| 343 | +def _get_build_duration_clause(builds_duration) -> str: |
| 344 | + clause = "" |
| 345 | + |
| 346 | + # builds |
| 347 | + duration_min, duration_max = builds_duration |
| 348 | + if duration_min: |
| 349 | + clause += f"AND builds.duration >= {duration_min}\n" |
| 350 | + if duration_max: |
| 351 | + clause += f"AND builds.duration <= {duration_max}\n" |
| 352 | + |
| 353 | + return clause |
| 354 | + |
| 355 | + |
| 356 | +def _get_boot_test_duration_clause(boots_duration, tests_duration) -> str: |
| 357 | + clause = "" |
| 358 | + |
| 359 | + # tests |
| 360 | + duration_min, duration_max = tests_duration |
| 361 | + if duration_min: |
| 362 | + clause += f"AND tests.duration >= {duration_min}\n" |
| 363 | + if duration_max: |
| 364 | + clause += f"AND tests.duration <= {duration_max}\n" |
| 365 | + |
| 366 | + # boots |
| 367 | + duration_min, duration_max = boots_duration |
| 368 | + if duration_min: |
| 369 | + clause += ( |
| 370 | + "AND (NOT (tests.path like 'boot.%%' or tests.path = 'boot') " |
| 371 | + f"OR tests.duration >= {duration_min})\n" |
| 372 | + ) |
| 373 | + if duration_max: |
| 374 | + clause += ( |
| 375 | + "AND (NOT (tests.path like 'boot.%%' or tests.path = 'boot') " |
| 376 | + f"OR tests.duration <= {duration_max})\n" |
| 377 | + ) |
| 378 | + |
| 379 | + return clause |
| 380 | + |
| 381 | + |
| 382 | +def get_hardware_details_filters( |
| 383 | + *, |
| 384 | + hardware_id: str, |
| 385 | + origin: str, |
| 386 | + start_datetime: datetime, |
| 387 | + end_datetime: datetime, |
| 388 | +) -> dict[str, list]: |
| 389 | + |
| 390 | + cache_key = "hardwareDetailsFilters" |
| 391 | + |
| 392 | + tests_cache_params = { |
| 393 | + "hardware_id": hardware_id, |
| 394 | + "origin": origin, |
| 395 | + "start_date": start_datetime, |
| 396 | + "end_date": end_datetime, |
| 397 | + } |
| 398 | + |
| 399 | + query_rows = get_query_cache(cache_key, tests_cache_params) |
| 400 | + |
| 401 | + if query_rows: |
| 402 | + return query_rows[0] |
| 403 | + |
| 404 | + query = """ |
| 405 | + SELECT array_agg(distinct builds.config_name) AS config_name, |
| 406 | + array_agg(distinct builds.architecture) AS architecture, |
| 407 | + array_agg(distinct builds.compiler) AS compiler, |
| 408 | + array_agg(distinct tests.misc->>'runtime') AS lab |
| 409 | + FROM |
| 410 | + tests |
| 411 | + INNER JOIN builds ON |
| 412 | + tests.build_id = builds.id |
| 413 | + WHERE |
| 414 | + ( |
| 415 | + tests.environment_compatible @> ARRAY[%s]::TEXT[] |
| 416 | + OR tests.environment_misc ->> 'platform' = %s |
| 417 | + ) |
| 418 | + AND tests.origin = %s |
| 419 | + AND tests.start_time >= %s |
| 420 | + AND tests.start_time <= %s; |
| 421 | + """ |
| 422 | + params = [ |
| 423 | + hardware_id, |
| 424 | + hardware_id, |
| 425 | + origin, |
| 426 | + start_datetime, |
| 427 | + end_datetime, |
| 428 | + ] |
| 429 | + |
| 430 | + with connection.cursor() as cursor: |
| 431 | + cursor.execute(query, params) |
| 432 | + query_rows = dict_fetchall(cursor) |
| 433 | + set_query_cache(key=cache_key, params=tests_cache_params, rows=query_rows) |
| 434 | + return query_rows[0] |
| 435 | + |
| 436 | + |
| 437 | +def get_hardware_details_summary( |
| 438 | + *, |
| 439 | + hardware_id: str, |
| 440 | + origin: str, |
| 441 | + trees_with_selected_commits: list[Tree], |
| 442 | + builds_duration: (Optional[int], Optional[int]), |
| 443 | + boots_duration: (Optional[int], Optional[int]), |
| 444 | + tests_duration: (Optional[int], Optional[int]), |
| 445 | + start_datetime: datetime, |
| 446 | + end_datetime: datetime, |
| 447 | +): |
| 448 | + |
| 449 | + cache_key = "hardwareDetailsSummary" |
| 450 | + |
| 451 | + tests_cache_params = { |
| 452 | + "hardware_id": hardware_id, |
| 453 | + "origin": origin, |
| 454 | + "trees": trees_with_selected_commits, |
| 455 | + "start_date": start_datetime, |
| 456 | + "end_date": end_datetime, |
| 457 | + "builds_duration": builds_duration, |
| 458 | + "boots_duration": boots_duration, |
| 459 | + "tests_duration": tests_duration, |
| 460 | + } |
| 461 | + |
| 462 | + query_rows = get_query_cache(cache_key, tests_cache_params) |
| 463 | + |
| 464 | + if query_rows: |
| 465 | + return query_rows |
| 466 | + |
| 467 | + commit_hashes = [tree.head_git_commit_hash for tree in trees_with_selected_commits] |
| 468 | + |
| 469 | + builds_duration_clause = _get_build_duration_clause(builds_duration) |
| 470 | + boots_tests_duration_clause = _get_boot_test_duration_clause( |
| 471 | + boots_duration, tests_duration |
| 472 | + ) |
| 473 | + |
| 474 | + query = """ |
| 475 | + (SELECT |
| 476 | + COUNT(distinct builds.id) AS count, |
| 477 | + checkouts.origin, |
| 478 | + builds.status AS status, |
| 479 | + count(incidents.id) AS known_issues, |
| 480 | + array[builds.compiler, builds.architecture] AS compiler_arch, |
| 481 | + builds.config_name, |
| 482 | + builds.misc->>'runtime' AS lab, |
| 483 | + tests.environment_misc->>'platform' AS platform, |
| 484 | + tests.environment_compatible, |
| 485 | + checkouts.origin, |
| 486 | + checkouts.tree_name, |
| 487 | + checkouts.git_repository_url, |
| 488 | + checkouts.git_commit_tags, |
| 489 | + checkouts.git_commit_name, |
| 490 | + checkouts.git_repository_branch, |
| 491 | + checkouts.git_commit_hash, |
| 492 | + true AS is_build, |
| 493 | + false AS is_test, |
| 494 | + false AS is_boot |
| 495 | + FROM |
| 496 | + builds |
| 497 | + INNER JOIN tests ON |
| 498 | + tests.build_id = builds.id |
| 499 | + INNER JOIN checkouts ON |
| 500 | + builds.checkout_id = checkouts.id |
| 501 | + LEFT OUTER JOIN incidents ON |
| 502 | + builds.id = incidents.build_id |
| 503 | + WHERE |
| 504 | + ( |
| 505 | + builds.config_name IS NOT NULL |
| 506 | + AND builds.id not like 'maestro:dummy_%%' |
| 507 | + AND (tests.environment_compatible @> ARRAY[%s]::TEXT[] |
| 508 | + OR tests.environment_misc ->> 'platform' = %s) |
| 509 | + ) |
| 510 | + AND builds.origin = %s |
| 511 | + AND builds.start_time >= %s |
| 512 | + AND builds.start_time <= %s |
| 513 | + AND checkouts.git_commit_hash IN ({0}) {1} |
| 514 | + GROUP BY checkouts.id, builds.status, tests.environment_compatible, compiler_arch, |
| 515 | + builds.config_name, lab, platform, is_boot) |
| 516 | + UNION ALL |
| 517 | + (SELECT |
| 518 | + COUNT(*) AS tests_count, |
| 519 | + checkouts.origin, |
| 520 | + tests.status AS status, |
| 521 | + count(incidents.id) AS known_issues, |
| 522 | + array[builds.compiler, builds.architecture] AS compiler_arch, |
| 523 | + builds.config_name, |
| 524 | + tests.misc->>'runtime' AS lab, |
| 525 | + tests.environment_misc->>'platform' AS platform, |
| 526 | + tests.environment_compatible, |
| 527 | + checkouts.origin, |
| 528 | + checkouts.tree_name, |
| 529 | + checkouts.git_repository_url, |
| 530 | + checkouts.git_commit_tags, |
| 531 | + checkouts.git_commit_name, |
| 532 | + checkouts.git_repository_branch, |
| 533 | + checkouts.git_commit_hash, |
| 534 | + false AS is_build, |
| 535 | + true AS is_test, |
| 536 | + (tests.path like 'boot.%%' or tests.path = 'boot') AS is_boot |
| 537 | + FROM |
| 538 | + builds |
| 539 | + inner JOIN tests ON |
| 540 | + tests.build_id = builds.id |
| 541 | + INNER JOIN checkouts ON |
| 542 | + builds.checkout_id = checkouts.id |
| 543 | + LEFT OUTER JOIN incidents ON |
| 544 | + tests.id = incidents.test_id |
| 545 | + WHERE |
| 546 | + ( |
| 547 | + (tests.environment_compatible @> ARRAY[%s]::TEXT[] |
| 548 | + OR tests.environment_misc ->> 'platform' = %s) |
| 549 | + ) |
| 550 | + AND tests.origin = %s |
| 551 | + AND tests.start_time >= %s |
| 552 | + AND tests.start_time <= %s |
| 553 | + AND checkouts.git_commit_hash IN ({0}) {2} |
| 554 | + GROUP BY checkouts.id, tests.status, tests.environment_compatible, compiler_arch, |
| 555 | + builds.config_name, lab, platform, is_boot); |
| 556 | + """.format( |
| 557 | + ",".join(repeat("%s", len(commit_hashes))), |
| 558 | + builds_duration_clause, |
| 559 | + boots_tests_duration_clause, |
| 560 | + ) |
| 561 | + |
| 562 | + params = [ |
| 563 | + hardware_id, |
| 564 | + hardware_id, |
| 565 | + origin, |
| 566 | + start_datetime, |
| 567 | + end_datetime, |
| 568 | + *commit_hashes, |
| 569 | + ] |
| 570 | + |
| 571 | + # TODO: check if we can reuse parameters to avoid double passing |
| 572 | + params = [*params, *params] |
| 573 | + |
| 574 | + with connection.cursor() as cursor: |
| 575 | + cursor.execute(query, params) |
| 576 | + query_rows = dict_fetchall(cursor) |
| 577 | + set_query_cache(key=cache_key, params=tests_cache_params, rows=query_rows) |
| 578 | + return query_rows |
| 579 | + |
| 580 | + |
342 | 581 | def query_records( |
343 | 582 | *, hardware_id: str, origin: str, trees: list[Tree], start_date: int, end_date: int |
344 | 583 | ) -> list[dict] | None: |
345 | 584 | commit_hashes = [tree.head_git_commit_hash for tree in trees] |
346 | 585 |
|
347 | | - # TODO Treat commit_hash collision (it can happen between repos) |
348 | | - with connection.cursor() as cursor: |
349 | | - cursor.execute( |
350 | | - """ |
| 586 | + query = """ |
351 | 587 | SELECT |
352 | 588 | tests.id, |
353 | 589 | tests.origin AS test_origin, |
@@ -411,19 +647,22 @@ def query_records( |
411 | 647 | ORDER BY |
412 | 648 | issues."_timestamp" DESC |
413 | 649 | """.format( |
414 | | - ",".join(["%s"] * len(commit_hashes)) |
415 | | - ), |
416 | | - [ |
417 | | - hardware_id, |
418 | | - hardware_id, |
419 | | - origin, |
420 | | - start_date, |
421 | | - end_date, |
422 | | - ] |
423 | | - + commit_hashes, |
424 | | - ) |
| 650 | + ",".join(["%s"] * len(commit_hashes)) |
| 651 | + ) |
425 | 652 |
|
426 | | - return dict_fetchall(cursor) |
| 653 | + params = [ |
| 654 | + hardware_id, |
| 655 | + hardware_id, |
| 656 | + origin, |
| 657 | + start_date, |
| 658 | + end_date, |
| 659 | + ] + commit_hashes |
| 660 | + |
| 661 | + # TODO Treat commit_hash collision (it can happen between repos) |
| 662 | + with connection.cursor() as cursor: |
| 663 | + cursor.execute(query, params) |
| 664 | + query_rows = dict_fetchall(cursor) |
| 665 | + return query_rows |
427 | 666 |
|
428 | 667 |
|
429 | 668 | def get_hardware_summary_data( |
|
0 commit comments