xref: /OK3568_Linux_fs/yocto/poky/scripts/lib/resulttool/regression.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# resulttool - regression analysis
2*4882a593Smuzhiyun#
3*4882a593Smuzhiyun# Copyright (c) 2019, Intel Corporation.
4*4882a593Smuzhiyun# Copyright (c) 2019, Linux Foundation
5*4882a593Smuzhiyun#
6*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
7*4882a593Smuzhiyun#
8*4882a593Smuzhiyun
9*4882a593Smuzhiyunimport resulttool.resultutils as resultutils
10*4882a593Smuzhiyunimport json
11*4882a593Smuzhiyun
12*4882a593Smuzhiyunfrom oeqa.utils.git import GitRepo
13*4882a593Smuzhiyunimport oeqa.utils.gitarchive as gitarchive
14*4882a593Smuzhiyun
15*4882a593Smuzhiyundef compare_result(logger, base_name, target_name, base_result, target_result):
16*4882a593Smuzhiyun    base_result = base_result.get('result')
17*4882a593Smuzhiyun    target_result = target_result.get('result')
18*4882a593Smuzhiyun    result = {}
19*4882a593Smuzhiyun    if base_result and target_result:
20*4882a593Smuzhiyun        for k in base_result:
21*4882a593Smuzhiyun            base_testcase = base_result[k]
22*4882a593Smuzhiyun            base_status = base_testcase.get('status')
23*4882a593Smuzhiyun            if base_status:
24*4882a593Smuzhiyun                target_testcase = target_result.get(k, {})
25*4882a593Smuzhiyun                target_status = target_testcase.get('status')
26*4882a593Smuzhiyun                if base_status != target_status:
27*4882a593Smuzhiyun                    result[k] = {'base': base_status, 'target': target_status}
28*4882a593Smuzhiyun            else:
29*4882a593Smuzhiyun                logger.error('Failed to retrieved base test case status: %s' % k)
30*4882a593Smuzhiyun    if result:
31*4882a593Smuzhiyun        resultstring = "Regression: %s\n            %s\n" % (base_name, target_name)
32*4882a593Smuzhiyun        for k in sorted(result):
33*4882a593Smuzhiyun            resultstring += '    %s: %s -> %s\n' % (k, result[k]['base'], result[k]['target'])
34*4882a593Smuzhiyun    else:
35*4882a593Smuzhiyun        resultstring = "Match: %s\n       %s" % (base_name, target_name)
36*4882a593Smuzhiyun    return result, resultstring
37*4882a593Smuzhiyun
38*4882a593Smuzhiyundef get_results(logger, source):
39*4882a593Smuzhiyun    return resultutils.load_resultsdata(source, configmap=resultutils.regression_map)
40*4882a593Smuzhiyun
41*4882a593Smuzhiyundef regression(args, logger):
42*4882a593Smuzhiyun    base_results = get_results(logger, args.base_result)
43*4882a593Smuzhiyun    target_results = get_results(logger, args.target_result)
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun    regression_common(args, logger, base_results, target_results)
46*4882a593Smuzhiyun
47*4882a593Smuzhiyundef regression_common(args, logger, base_results, target_results):
48*4882a593Smuzhiyun    if args.base_result_id:
49*4882a593Smuzhiyun        base_results = resultutils.filter_resultsdata(base_results, args.base_result_id)
50*4882a593Smuzhiyun    if args.target_result_id:
51*4882a593Smuzhiyun        target_results = resultutils.filter_resultsdata(target_results, args.target_result_id)
52*4882a593Smuzhiyun
53*4882a593Smuzhiyun    matches = []
54*4882a593Smuzhiyun    regressions = []
55*4882a593Smuzhiyun    notfound = []
56*4882a593Smuzhiyun
57*4882a593Smuzhiyun    for a in base_results:
58*4882a593Smuzhiyun        if a in target_results:
59*4882a593Smuzhiyun            base = list(base_results[a].keys())
60*4882a593Smuzhiyun            target = list(target_results[a].keys())
61*4882a593Smuzhiyun            # We may have multiple base/targets which are for different configurations. Start by
62*4882a593Smuzhiyun            # removing any pairs which match
63*4882a593Smuzhiyun            for c in base.copy():
64*4882a593Smuzhiyun                for b in target.copy():
65*4882a593Smuzhiyun                    res, resstr = compare_result(logger, c, b, base_results[a][c], target_results[a][b])
66*4882a593Smuzhiyun                    if not res:
67*4882a593Smuzhiyun                        matches.append(resstr)
68*4882a593Smuzhiyun                        base.remove(c)
69*4882a593Smuzhiyun                        target.remove(b)
70*4882a593Smuzhiyun                        break
71*4882a593Smuzhiyun            # Should only now see regressions, we may not be able to match multiple pairs directly
72*4882a593Smuzhiyun            for c in base:
73*4882a593Smuzhiyun                for b in target:
74*4882a593Smuzhiyun                    res, resstr = compare_result(logger, c, b, base_results[a][c], target_results[a][b])
75*4882a593Smuzhiyun                    if res:
76*4882a593Smuzhiyun                        regressions.append(resstr)
77*4882a593Smuzhiyun        else:
78*4882a593Smuzhiyun            notfound.append("%s not found in target" % a)
79*4882a593Smuzhiyun    print("\n".join(sorted(matches)))
80*4882a593Smuzhiyun    print("\n".join(sorted(regressions)))
81*4882a593Smuzhiyun    print("\n".join(sorted(notfound)))
82*4882a593Smuzhiyun
83*4882a593Smuzhiyun    return 0
84*4882a593Smuzhiyun
85*4882a593Smuzhiyundef regression_git(args, logger):
86*4882a593Smuzhiyun    base_results = {}
87*4882a593Smuzhiyun    target_results = {}
88*4882a593Smuzhiyun
89*4882a593Smuzhiyun    tag_name = "{branch}/{commit_number}-g{commit}/{tag_number}"
90*4882a593Smuzhiyun    repo = GitRepo(args.repo)
91*4882a593Smuzhiyun
92*4882a593Smuzhiyun    revs = gitarchive.get_test_revs(logger, repo, tag_name, branch=args.branch)
93*4882a593Smuzhiyun
94*4882a593Smuzhiyun    if args.branch2:
95*4882a593Smuzhiyun        revs2 = gitarchive.get_test_revs(logger, repo, tag_name, branch=args.branch2)
96*4882a593Smuzhiyun        if not len(revs2):
97*4882a593Smuzhiyun            logger.error("No revisions found to compare against")
98*4882a593Smuzhiyun            return 1
99*4882a593Smuzhiyun        if not len(revs):
100*4882a593Smuzhiyun            logger.error("No revision to report on found")
101*4882a593Smuzhiyun            return 1
102*4882a593Smuzhiyun    else:
103*4882a593Smuzhiyun        if len(revs) < 2:
104*4882a593Smuzhiyun            logger.error("Only %d tester revisions found, unable to generate report" % len(revs))
105*4882a593Smuzhiyun            return 1
106*4882a593Smuzhiyun
107*4882a593Smuzhiyun    # Pick revisions
108*4882a593Smuzhiyun    if args.commit:
109*4882a593Smuzhiyun        if args.commit_number:
110*4882a593Smuzhiyun            logger.warning("Ignoring --commit-number as --commit was specified")
111*4882a593Smuzhiyun        index1 = gitarchive.rev_find(revs, 'commit', args.commit)
112*4882a593Smuzhiyun    elif args.commit_number:
113*4882a593Smuzhiyun        index1 = gitarchive.rev_find(revs, 'commit_number', args.commit_number)
114*4882a593Smuzhiyun    else:
115*4882a593Smuzhiyun        index1 = len(revs) - 1
116*4882a593Smuzhiyun
117*4882a593Smuzhiyun    if args.branch2:
118*4882a593Smuzhiyun        revs2.append(revs[index1])
119*4882a593Smuzhiyun        index1 = len(revs2) - 1
120*4882a593Smuzhiyun        revs = revs2
121*4882a593Smuzhiyun
122*4882a593Smuzhiyun    if args.commit2:
123*4882a593Smuzhiyun        if args.commit_number2:
124*4882a593Smuzhiyun            logger.warning("Ignoring --commit-number2 as --commit2 was specified")
125*4882a593Smuzhiyun        index2 = gitarchive.rev_find(revs, 'commit', args.commit2)
126*4882a593Smuzhiyun    elif args.commit_number2:
127*4882a593Smuzhiyun        index2 = gitarchive.rev_find(revs, 'commit_number', args.commit_number2)
128*4882a593Smuzhiyun    else:
129*4882a593Smuzhiyun        if index1 > 0:
130*4882a593Smuzhiyun            index2 = index1 - 1
131*4882a593Smuzhiyun            # Find the closest matching commit number for comparision
132*4882a593Smuzhiyun            # In future we could check the commit is a common ancestor and
133*4882a593Smuzhiyun            # continue back if not but this good enough for now
134*4882a593Smuzhiyun            while index2 > 0 and revs[index2].commit_number > revs[index1].commit_number:
135*4882a593Smuzhiyun                index2 = index2 - 1
136*4882a593Smuzhiyun        else:
137*4882a593Smuzhiyun            logger.error("Unable to determine the other commit, use "
138*4882a593Smuzhiyun                      "--commit2 or --commit-number2 to specify it")
139*4882a593Smuzhiyun            return 1
140*4882a593Smuzhiyun
141*4882a593Smuzhiyun    logger.info("Comparing:\n%s\nto\n%s\n" % (revs[index1], revs[index2]))
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun    base_results = resultutils.git_get_result(repo, revs[index1][2])
144*4882a593Smuzhiyun    target_results = resultutils.git_get_result(repo, revs[index2][2])
145*4882a593Smuzhiyun
146*4882a593Smuzhiyun    regression_common(args, logger, base_results, target_results)
147*4882a593Smuzhiyun
148*4882a593Smuzhiyun    return 0
149*4882a593Smuzhiyun
150*4882a593Smuzhiyundef register_commands(subparsers):
151*4882a593Smuzhiyun    """Register subcommands from this plugin"""
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun    parser_build = subparsers.add_parser('regression', help='regression file/directory analysis',
154*4882a593Smuzhiyun                                         description='regression analysis comparing the base set of results to the target results',
155*4882a593Smuzhiyun                                         group='analysis')
156*4882a593Smuzhiyun    parser_build.set_defaults(func=regression)
157*4882a593Smuzhiyun    parser_build.add_argument('base_result',
158*4882a593Smuzhiyun                              help='base result file/directory/URL for the comparison')
159*4882a593Smuzhiyun    parser_build.add_argument('target_result',
160*4882a593Smuzhiyun                              help='target result file/directory/URL to compare with')
161*4882a593Smuzhiyun    parser_build.add_argument('-b', '--base-result-id', default='',
162*4882a593Smuzhiyun                              help='(optional) filter the base results to this result ID')
163*4882a593Smuzhiyun    parser_build.add_argument('-t', '--target-result-id', default='',
164*4882a593Smuzhiyun                              help='(optional) filter the target results to this result ID')
165*4882a593Smuzhiyun
166*4882a593Smuzhiyun    parser_build = subparsers.add_parser('regression-git', help='regression git analysis',
167*4882a593Smuzhiyun                                         description='regression analysis comparing base result set to target '
168*4882a593Smuzhiyun                                                     'result set',
169*4882a593Smuzhiyun                                         group='analysis')
170*4882a593Smuzhiyun    parser_build.set_defaults(func=regression_git)
171*4882a593Smuzhiyun    parser_build.add_argument('repo',
172*4882a593Smuzhiyun                              help='the git repository containing the data')
173*4882a593Smuzhiyun    parser_build.add_argument('-b', '--base-result-id', default='',
174*4882a593Smuzhiyun                              help='(optional) default select regression based on configurations unless base result '
175*4882a593Smuzhiyun                                   'id was provided')
176*4882a593Smuzhiyun    parser_build.add_argument('-t', '--target-result-id', default='',
177*4882a593Smuzhiyun                              help='(optional) default select regression based on configurations unless target result '
178*4882a593Smuzhiyun                                   'id was provided')
179*4882a593Smuzhiyun
180*4882a593Smuzhiyun    parser_build.add_argument('--branch', '-B', default='master', help="Branch to find commit in")
181*4882a593Smuzhiyun    parser_build.add_argument('--branch2', help="Branch to find comparision revisions in")
182*4882a593Smuzhiyun    parser_build.add_argument('--commit', help="Revision to search for")
183*4882a593Smuzhiyun    parser_build.add_argument('--commit-number', help="Revision number to search for, redundant if --commit is specified")
184*4882a593Smuzhiyun    parser_build.add_argument('--commit2', help="Revision to compare with")
185*4882a593Smuzhiyun    parser_build.add_argument('--commit-number2', help="Revision number to compare with, redundant if --commit2 is specified")
186*4882a593Smuzhiyun
187