Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(298)

Side by Side Diff: go/src/infra/appengine/test-results/model/aggregate_result.go

Issue 2231393002: test-results: Add merge and trim methods (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: tests: remove unecessary Trim calls Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | go/src/infra/appengine/test-results/model/aggregate_result_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 package model 1 package model
2 2
3 import ( 3 import (
4 "bytes" 4 "bytes"
5 "encoding/json" 5 "encoding/json"
6 "errors" 6 "errors"
7 "fmt" 7 "fmt"
8 "io" 8 "io"
9 "io/ioutil" 9 "io/ioutil"
10 "strconv" 10 "strconv"
(...skipping 348 matching lines...) Expand 10 before | Expand all | Expand 10 after
359 } 359 }
360 360
361 // AggregateTestLeaf is the summary of test results at the l of a tests trie. 361 // AggregateTestLeaf is the summary of test results at the l of a tests trie.
362 type AggregateTestLeaf struct { 362 type AggregateTestLeaf struct {
363 Results []ResultSummary 363 Results []ResultSummary
364 Runtimes []RuntimeSummary 364 Runtimes []RuntimeSummary
365 Expected []string 365 Expected []string
366 Bugs []string 366 Bugs []string
367 } 367 }
368 368
369 func (l *AggregateTestLeaf) node() {} 369 func (leaf *AggregateTestLeaf) node() {}
370 370
371 // aggregateTestLeafAux is used to marshal and unmarshal AggregateTestLeaf. 371 // aggregateTestLeafAux is used to marshal and unmarshal AggregateTestLeaf.
372 type aggregateTestLeafAux struct { 372 type aggregateTestLeafAux struct {
373 Results []ResultSummary `json:"results,omitempty"` 373 Results []ResultSummary `json:"results,omitempty"`
374 Runtimes []RuntimeSummary `json:"times,omitempty"` 374 Runtimes []RuntimeSummary `json:"times,omitempty"`
375 Expected *string `json:"expected,omitempty"` 375 Expected *string `json:"expected,omitempty"`
376 Bugs []string `json:"bugs,omitempty"` 376 Bugs []string `json:"bugs,omitempty"`
377 } 377 }
378 378
379 // MarshalJSON marshal l into JSON. 379 // MarshalJSON marshal l into JSON.
380 func (l *AggregateTestLeaf) MarshalJSON() ([]byte, error) { 380 func (leaf *AggregateTestLeaf) MarshalJSON() ([]byte, error) {
381 aux := aggregateTestLeafAux{ 381 aux := aggregateTestLeafAux{
382 » » Results: l.Results, 382 » » Results: leaf.Results,
383 » » Runtimes: l.Runtimes, 383 » » Runtimes: leaf.Runtimes,
384 » » Bugs: l.Bugs, 384 » » Bugs: leaf.Bugs,
385 } 385 }
386 » if s := strings.Join(l.Expected, " "); len(s) > 0 { 386 » if s := strings.Join(leaf.Expected, " "); len(s) > 0 {
387 aux.Expected = &s 387 aux.Expected = &s
388 } 388 }
389 return json.Marshal(&aux) 389 return json.Marshal(&aux)
390 } 390 }
391 391
392 // UnmarshalJSON unmarshal the supplied data into l. 392 // UnmarshalJSON unmarshal the supplied data into l.
393 func (l *AggregateTestLeaf) UnmarshalJSON(data []byte) error { 393 func (leaf *AggregateTestLeaf) UnmarshalJSON(data []byte) error {
394 var aux aggregateTestLeafAux 394 var aux aggregateTestLeafAux
395 if err := json.Unmarshal(data, &aux); err != nil { 395 if err := json.Unmarshal(data, &aux); err != nil {
396 return err 396 return err
397 } 397 }
398 398
399 » l.Results = aux.Results 399 » leaf.Results = aux.Results
400 » l.Runtimes = aux.Runtimes 400 » leaf.Runtimes = aux.Runtimes
401 if aux.Expected != nil { 401 if aux.Expected != nil {
402 » » l.Expected = strings.Split(*aux.Expected, " ") 402 » » leaf.Expected = strings.Split(*aux.Expected, " ")
403 } 403 }
404 » l.Bugs = aux.Bugs 404 » leaf.Bugs = aux.Bugs
405 405
406 return nil 406 return nil
407 } 407 }
408 408
409 // defaultFields sets default values for missing/invalid fields. 409 // defaultFields sets default values for missing/invalid fields.
410 func (l *AggregateTestLeaf) defaultFields() { 410 func (leaf *AggregateTestLeaf) defaultFields() {
411 » if len(l.Results) == 0 { 411 » if len(leaf.Results) == 0 {
412 » » l.Results = []ResultSummary{{1, "N"}} 412 » » leaf.Results = []ResultSummary{{1, "N"}}
413 } 413 }
414 » if len(l.Runtimes) == 0 { 414 » if len(leaf.Runtimes) == 0 {
415 » » l.Runtimes = []RuntimeSummary{{1, 0}} 415 » » leaf.Runtimes = []RuntimeSummary{{1, 0}}
416 } 416 }
417 } 417 }
418 418
419 // ResultSummary is the type of test failure and count of how many 419 // ResultSummary is the type of test failure and count of how many
420 // times the running time occured. 420 // times the running time occured.
421 type ResultSummary struct { 421 type ResultSummary struct {
422 Count int 422 Count int
423 Type string 423 Type string
424 } 424 }
425 425
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
477 return err 477 return err
478 } 478 }
479 if len(tmp) != 2 { 479 if len(tmp) != 2 {
480 return fmt.Errorf("model: UnmarshalJSON: RuntimeSummary wrong le ngth: %d, expect: %d", len(tmp), 2) 480 return fmt.Errorf("model: UnmarshalJSON: RuntimeSummary wrong le ngth: %d, expect: %d", len(tmp), 2)
481 } 481 }
482 482
483 rs.Count = int(tmp[0]) 483 rs.Count = int(tmp[0])
484 rs.Runtime = tmp[1] 484 rs.Runtime = tmp[1]
485 return nil 485 return nil
486 } 486 }
487
488 var (
489 // ErrBuildNumberConflict is returned when the build numbers
490 // are the same when merging.
491 ErrBuildNumberConflict = errors.New("build number conflict")
492
493 // ErrBuilderNameConflict is returned when the builder names
494 // do not match when merging.
495 ErrBuilderNameConflict = errors.New("builder name conflict")
496 )
497
498 // Merge merges other into ag.
499 func (ag *AggregateResult) Merge(other *AggregateResult) error {
500 if ag.Builder != other.Builder {
501 return ErrBuilderNameConflict
502 }
503 if ag.BuilderInfo == nil {
504 ag.BuilderInfo = &BuilderInfo{}
505 }
506 ag.Version = ResultsVersion
507 return ag.BuilderInfo.Merge(other.BuilderInfo)
508 }
509
510 // Merge merges other into info.
511 //
512 // The returned error is ErrBuildNumberConflict when
513 // other.BuildNumbers[0] already has the latest build number.
514 func (info *BuilderInfo) Merge(other *BuilderInfo) error {
515 if len(info.BuildNumbers) > 0 && len(other.BuildNumbers) > 0 {
516 if info.BuildNumbers[0] == other.BuildNumbers[0] {
517 return ErrBuildNumberConflict
518 }
519 }
520
521 info.SecondsEpoch = append(other.SecondsEpoch, info.SecondsEpoch...)
522 info.BlinkRevs = append(other.BlinkRevs, info.BlinkRevs...)
523 info.BuildNumbers = append(other.BuildNumbers, info.BuildNumbers...)
524 info.ChromeRevs = append(other.ChromeRevs, info.ChromeRevs...)
525
526 if info.FailuresByType == nil && other.FailuresByType != nil {
527 info.FailuresByType = make(map[string][]int)
528 }
529 for k, v := range other.FailuresByType {
530 info.FailuresByType[k] = append(v, info.FailuresByType[k]...)
531 }
532
533 info.FailureMap = FailureLongNames
534
535 if info.Tests == nil {
536 info.Tests = AggregateTest{}
537 }
538
539 info.Tests.WalkLeaves(func(_ string, leaf *AggregateTestLeaf) {
540 leaf.Expected = nil
541 leaf.Bugs = nil
542 })
543
544 return info.Tests.Merge(other.Tests)
545 }
546
547 // Merge merges other into at.
548 func (at *AggregateTest) Merge(other AggregateTest) error {
549 // Shallow copy but OK. We take care to not modify otherCopy
550 // values; instead always create new objects
551 // and assign to otherCopy[key].
552 otherCopy := make(AggregateTest, len(other))
553 for k, v := range other {
554 otherCopy[k] = v
555 }
556
557 for k, v := range *at {
558 if _, ok := otherCopy[k]; !ok {
559 switch v.(type) {
560 case *AggregateTestLeaf:
561 l := &AggregateTestLeaf{}
562 l.defaultFields()
563 otherCopy[k] = l
564 case AggregateTest:
565 otherCopy[k] = AggregateTest{}
566 }
567 }
568 }
569
570 for k, v := range otherCopy {
571 // Key does not exist: assign entire subtree.
572 if _, ok := (*at)[k]; !ok {
573 if *at == nil {
574 *at = AggregateTest{}
575 }
576 (*at)[k] = v
577 continue
578 }
579
580 // Leaf node.
581 if leaf1, ok := (*at)[k].(*AggregateTestLeaf); ok {
582 leaf2, ok := v.(*AggregateTestLeaf)
583 if !ok {
584 return errors.New("model: Merge: expected *Aggre gateTestLeaf")
585 }
586 if err := leaf1.Merge(leaf2); err != nil {
587 return err
588 }
589 continue
590 }
591
592 // Not leaf node: merge subtree recursively.
593 at1, ok := (*at)[k].(AggregateTest)
594 if !ok {
595 return errors.New("model: Merge: expected AggregateTest" )
596 }
597 at2, ok := v.(AggregateTest)
598 if !ok {
599 return errors.New("model: Merge: expected AggregateTest" )
600 }
601 if err := at1.Merge(at2); err != nil {
602 return err
603 }
604 }
605
606 return nil
607 }
608
609 // Merge merges other into leaf.
610 func (leaf *AggregateTestLeaf) Merge(other *AggregateTestLeaf) error {
611 // Bugs and Expected should come from from other only.
612 leaf.Bugs = other.Bugs
613 if len(other.Expected) == 1 && other.Expected[0] != "PASS" {
614 leaf.Expected = other.Expected
615 }
616
617 for _, r := range other.Results {
618 if len(leaf.Results) > 0 && r.Type == leaf.Results[0].Type {
619 leaf.Results[0].Count += r.Count
620 } else {
621 leaf.Results = append([]ResultSummary{r}, leaf.Results.. .)
622 }
623 }
624
625 for _, r := range other.Runtimes {
626 if len(leaf.Runtimes) > 0 && r.Runtime == leaf.Runtimes[0].Runti me {
627 leaf.Runtimes[0].Count += r.Count
628 } else {
629 leaf.Runtimes = append([]RuntimeSummary{r}, leaf.Runtime s...)
630 }
631 }
632
633 return nil
634 }
635
636 const (
637 // ResultsSize is the size that "results.json" should be trimmed to.
638 ResultsSize = 500
639
640 // ResultsSmallSize is the size that "results_small.json" should
641 // be trimmed to.
642 ResultsSmallSize = 100
643
644 runtimeThresholdNormal float64 = 3 // In seconds.
645 runtimeThresholdDebug float64 = 9 // In seconds.
646 )
647
648 func isDebugBuilder(builder string) bool {
649 for _, s := range []string{"debug", "dbg"} {
650 if strings.Contains(strings.ToLower(builder), s) {
651 return true
652 }
653 }
654 return false
655 }
656
657 // Trim trims the leaves of Tests in ar to the specified size.
658 func (ag *AggregateResult) Trim(size int) error {
659 t := runtimeThresholdNormal
660
661 if isDebugBuilder(ag.Builder) {
662 t = runtimeThresholdDebug
663 }
664
665 return ag.Tests.trim(size, t)
666 }
667
668 func (at AggregateTest) trim(size int, threshold float64) error {
669 for k, v := range at {
670 if leaf, ok := v.(*AggregateTestLeaf); ok {
671 leaf.trim(size)
672 if leaf.shouldDelete(threshold) {
673 delete(at, k)
674 }
675 continue
676 }
677
678 child, ok := v.(AggregateTest)
679 if !ok {
680 return errors.New("model: trim: expected AggregateTest")
681 }
682 if err := child.trim(size, threshold); err != nil {
683 return err
684 }
685 if len(child) == 0 {
686 delete(at, k)
687 }
688 }
689 return nil
690 }
691
692 func (leaf *AggregateTestLeaf) trim(size int) {
693 n := 0
694
695 for i, r := range leaf.Results {
696 leaf.Results[i].Count = min(r.Count, size)
697 n += r.Count
698 if n >= size {
699 leaf.Results = leaf.Results[:i+1]
700 break
701 }
702 }
703
704 n = 0
705
706 for i, r := range leaf.Runtimes {
707 leaf.Runtimes[i].Count = min(r.Count, size)
708 n += r.Count
709 if n >= size {
710 leaf.Runtimes = leaf.Runtimes[:i+1]
711 break
712 }
713 }
714 }
715
716 func min(a, b int) int {
717 if a < b {
718 return a
719 }
720 return b
721 }
722
723 var deletableTypes = map[string]bool{"P": true, "N": true, "Y": true}
724
725 func (leaf *AggregateTestLeaf) shouldDelete(threshold float64) bool {
726 if len(leaf.Expected) == 1 && leaf.Expected[0] != "PASS" {
727 return false
728 }
729 if leaf.Bugs != nil {
730 return false
731 }
732
733 for _, r := range leaf.Results {
734 if !deletableTypes[r.Type] {
735 return false
736 }
737 }
738 for _, r := range leaf.Runtimes {
739 if r.Runtime >= threshold {
740 return false
741 }
742 }
743
744 return true
745 }
OLDNEW
« no previous file with comments | « no previous file | go/src/infra/appengine/test-results/model/aggregate_result_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698