~xnox/ubuntu-archive-tools/sru-report-autopkgtest-vomit

1138 by Colin Watson
copy-build-scheduler: switch to Python 3
1
#! /usr/bin/python3
355 by Colin Watson
Apply GPLv3 to anything not already licensed; ok slangasek, broder, laney, kitterman, geser
2
3
# Copyright (C) 2011, 2012  Canonical Ltd.
4
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; version 3 of the License.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
276.1.1 by Francis J. Lacoste
Initial version.
17
# This script can be used to reschedule some of the copy archives
18
# builds so that they are processed like regular PPA builds.
19
#
20
# Copy archives builds have a huge penalty applied to them which means
21
# that they are only processed when there is nothing else being processed
22
# by the build farm. That's usually fine, but for some rebuilds, we want
339 by Colin Watson
remove trailing whitespace
23
# more timely processing, while at the same time, we do want to continue to
276.1.1 by Francis J. Lacoste
Initial version.
24
# service regular PPA builds.
25
#
26
# This script will try to have a portion of the build farm processing copy
27
# builds. It does that by rescoring builds to the normal build priority
28
# range. But will only rescore a few builds at a time, so as not to take ove
29
# the build pool. By default, it won't rescore more than 1/4 the number of
339 by Colin Watson
remove trailing whitespace
30
# available builders. So for example, if there are 12 i386 builders, only
276.1.1 by Francis J. Lacoste
Initial version.
31
# 3 builds at a time will have a "normal priority".
355 by Colin Watson
Apply GPLv3 to anything not already licensed; ok slangasek, broder, laney, kitterman, geser
32
1137 by Colin Watson
copy-build-scheduler: convert to argparse
33
import argparse
555 by Colin Watson
Simplify using collections.defaultdict.
34
from collections import defaultdict
276.1.1 by Francis J. Lacoste
Initial version.
35
import logging
36
import time
37
38
from launchpadlib.launchpad import Launchpad
39
40
41
API_NAME = 'copy-build-scheduler'
42
43
NEEDS_BUILDING = 'Needs building'
44
BUILDING = 'Currently building'
45
COPY_ARCHIVE_SCORE_PENALTY = 2600
276.1.4 by Francis J. Lacoste
Increase time between schedule run, and recompute capacity every 5 schedule iteration.
46
# Number of minutes to wait between schedule run.
47
SCHEDULE_PERIOD = 5
276.1.1 by Francis J. Lacoste
Initial version.
48
545 by Colin Watson
Make all scripts pass pep8(1).
49
1137 by Colin Watson
copy-build-scheduler: convert to argparse
50
def determine_builder_capacity(lp, args):
276.1.1 by Francis J. Lacoste
Initial version.
51
    """Find how many builders to use for copy builds by processor."""
52
    capacity = {}
1137 by Colin Watson
copy-build-scheduler: convert to argparse
53
    for processor in args.processors:
1139 by Colin Watson
copy-build-scheduler: skip inactive builders
54
        queue = [
55
            builder for builder in lp.builders.getBuildersForQueue(
56
                processor='/+processors/%s' % processor, virtualized=True)
57
            if builder.active]
276.1.1 by Francis J. Lacoste
Initial version.
58
        max_capacity = len(queue)
1137 by Colin Watson
copy-build-scheduler: convert to argparse
59
        capacity[processor] = round(max_capacity * args.builder_ratio)
276.1.1 by Francis J. Lacoste
Initial version.
60
        # Make sure at least 1 builders is associated
61
        if capacity[processor] == 0:
62
            capacity[processor] = 1
63
        logging.info(
64
            'Will use %d out of %d %s builders', capacity[processor],
65
            max_capacity, processor)
66
    return capacity
67
276.1.4 by Francis J. Lacoste
Increase time between schedule run, and recompute capacity every 5 schedule iteration.
68
276.1.1 by Francis J. Lacoste
Initial version.
69
def get_archive_used_builders_capacity(archive):
70
    """Return the number of builds currently being done for the archive."""
555 by Colin Watson
Simplify using collections.defaultdict.
71
    capacity = defaultdict(int)
276.1.1 by Francis J. Lacoste
Initial version.
72
    building = archive.getBuildRecords(build_state=BUILDING)
73
    for build in building:
74
        capacity[build.arch_tag] += 1
75
    return capacity
76
77
1137 by Colin Watson
copy-build-scheduler: convert to argparse
78
def main():
79
    parser = argparse.ArgumentParser()
80
    parser.add_argument(
276.1.1 by Francis J. Lacoste
Initial version.
81
        '--lp-instance', default='production', dest='lp_instance',
530 by Colin Watson
copy-build-scheduler: typo
82
        help="Select the Launchpad instance to run against. Defaults to "
276.1.1 by Francis J. Lacoste
Initial version.
83
        "'production'")
1137 by Colin Watson
copy-build-scheduler: convert to argparse
84
    parser.add_argument(
276.1.1 by Francis J. Lacoste
Initial version.
85
        '-v', '--verbose', default=0, action='count', dest='verbose',
86
        help="Increase verbosity of the script. -v prints info messages"
87
        "-vv will print debug messages.")
1137 by Colin Watson
copy-build-scheduler: convert to argparse
88
    parser.add_argument(
276.1.1 by Francis J. Lacoste
Initial version.
89
        '-c', '--credentials', default=None, action='store',
90
        dest='credentials',
91
        help="Use the OAuth credentials in FILE instead of the desktop "
92
        "one.", metavar='FILE')
1137 by Colin Watson
copy-build-scheduler: convert to argparse
93
    parser.add_argument(
276.1.1 by Francis J. Lacoste
Initial version.
94
        '-d', '--distribution', default='ubuntu', action='store',
95
        dest='distribution',
96
        help="The archive distribution. Defaults to 'ubuntu'.")
1137 by Colin Watson
copy-build-scheduler: convert to argparse
97
    parser.add_argument(
98
        '-p', '--processor', action='append', dest='processors',
276.1.1 by Francis J. Lacoste
Initial version.
99
        help="The processor for which to schedule builds. "
100
        "Default to i386 and amd64.")
1137 by Colin Watson
copy-build-scheduler: convert to argparse
101
    parser.add_argument(
102
        '-r', '--ratio', default=0.25, action='store', type=float,
276.1.1 by Francis J. Lacoste
Initial version.
103
        dest='builder_ratio',
104
        help="The ratio of builders that you want to use for the copy "
1137 by Colin Watson
copy-build-scheduler: convert to argparse
105
        "builds.  Default to 25%% of the available builders.")
106
    parser.add_argument('copy_archive_name', help='Name of copy archive')
107
    args = parser.parse_args()
276.1.1 by Francis J. Lacoste
Initial version.
108
1137 by Colin Watson
copy-build-scheduler: convert to argparse
109
    if args.verbose >= 2:
276.1.1 by Francis J. Lacoste
Initial version.
110
        log_level = logging.DEBUG
1137 by Colin Watson
copy-build-scheduler: convert to argparse
111
    elif args.verbose == 1:
276.1.1 by Francis J. Lacoste
Initial version.
112
        log_level = logging.INFO
113
    else:
114
        log_level = logging.WARNING
115
    logging.basicConfig(level=log_level)
116
1137 by Colin Watson
copy-build-scheduler: convert to argparse
117
    if args.builder_ratio >= 1 or args.builder_ratio < 0:
276.1.1 by Francis J. Lacoste
Initial version.
118
        parser.error(
119
            'ratio should be a float between 0 and 1: %s' %
1137 by Colin Watson
copy-build-scheduler: convert to argparse
120
            args.builder_ratio)
121
122
    if not args.processors:
123
        args.processors = ['amd64', 'i386']
276.1.1 by Francis J. Lacoste
Initial version.
124
125
    lp = Launchpad.login_with(
1137 by Colin Watson
copy-build-scheduler: convert to argparse
126
        API_NAME, args.lp_instance,
127
        credentials_file=args.credentials,
276.1.1 by Francis J. Lacoste
Initial version.
128
        version='devel')
129
130
    try:
1137 by Colin Watson
copy-build-scheduler: convert to argparse
131
        distribution = lp.distributions[args.distribution]
276.1.1 by Francis J. Lacoste
Initial version.
132
    except KeyError:
1137 by Colin Watson
copy-build-scheduler: convert to argparse
133
        parser.error('unknown distribution: %s' % args.distribution)
276.1.1 by Francis J. Lacoste
Initial version.
134
1137 by Colin Watson
copy-build-scheduler: convert to argparse
135
    archive = distribution.getArchive(name=args.copy_archive_name)
276.1.1 by Francis J. Lacoste
Initial version.
136
    if archive is None:
1137 by Colin Watson
copy-build-scheduler: convert to argparse
137
        parser.error('unknown archive: %s' % args.copy_archive_name)
276.1.1 by Francis J. Lacoste
Initial version.
138
276.1.4 by Francis J. Lacoste
Increase time between schedule run, and recompute capacity every 5 schedule iteration.
139
    iteration = 0
276.1.1 by Francis J. Lacoste
Initial version.
140
    while True:
276.1.5 by Francis J. Lacoste
Typo.
141
        # Every 5 schedules run - and on the first - compute available
142
        # capacity.
276.1.4 by Francis J. Lacoste
Increase time between schedule run, and recompute capacity every 5 schedule iteration.
143
        if (iteration % 5) == 0:
1137 by Colin Watson
copy-build-scheduler: convert to argparse
144
            capacity = determine_builder_capacity(lp, args)
276.1.4 by Francis J. Lacoste
Increase time between schedule run, and recompute capacity every 5 schedule iteration.
145
        iteration += 1
146
276.1.1 by Francis J. Lacoste
Initial version.
147
        pending_builds = archive.getBuildRecords(build_state=NEEDS_BUILDING)
148
        logging.debug('Found %d pending builds.' % len(pending_builds))
149
        if len(pending_builds) == 0:
150
            logging.info('No more builds pending. We are done.')
151
            break
152
153
        used_capacity = get_archive_used_builders_capacity(archive)
154
155
        # For each processor, rescore up as many builds as we have
156
        # capacity for.
1137 by Colin Watson
copy-build-scheduler: convert to argparse
157
        for processor in args.processors:
276.1.1 by Francis J. Lacoste
Initial version.
158
            builds_to_rescore = (
159
                capacity[processor] - used_capacity.get(processor, 0))
160
            logging.debug(
161
                'Will try to rescore %d %s builds', builds_to_rescore,
162
                processor)
163
            for build in pending_builds:
164
                if builds_to_rescore <= 0:
165
                    break
166
167
                if build.arch_tag != processor:
168
                    continue
169
170
                if build.score < 0:
171
                    # Only rescore builds that look like the negative
172
                    # copy archive modified have been applied.
173
                    logging.info('Rescoring %s' % build.title)
174
                    # This should make them considered like a regular build.
175
                    build.rescore(
176
                        score=build.score + COPY_ARCHIVE_SCORE_PENALTY)
276.1.3 by Francis J. Lacoste
Better logging.
177
                else:
178
                    logging.debug('%s already rescored', build.title)
276.1.1 by Francis J. Lacoste
Initial version.
179
180
                # If the score was already above 0, it was probably
181
                # rescored already, count it against our limit anyway.
182
                builds_to_rescore -= 1
183
276.1.4 by Francis J. Lacoste
Increase time between schedule run, and recompute capacity every 5 schedule iteration.
184
        # Reschedule in a while.
185
        logging.debug('Sleeping for %d minutes.', SCHEDULE_PERIOD)
186
        time.sleep(SCHEDULE_PERIOD * 60)
276.1.1 by Francis J. Lacoste
Initial version.
187
188
189
if __name__ == '__main__':
1137 by Colin Watson
copy-build-scheduler: convert to argparse
190
    main()