[Notes] [Git][BuildStream/buildstream][Qinusty/563-cache-quota-restriction] 7 commits: Merge branch 'Qinusty/backport-576' into 'bst-1.2'



Title: GitLab

Tristan Van Berkom pushed to branch Qinusty/563-cache-quota-restriction at BuildStream / buildstream

Commits:

7 changed files:

Changes:

  • .gitlab-ci.yml
    ... ... @@ -143,7 +143,6 @@ docs:
    143 143
       - pip3 install sphinx-click
    
    144 144
       - pip3 install sphinx_rtd_theme
    
    145 145
       - cd dist && ./unpack.sh && cd buildstream
    
    146
    -  - pip3 install .
    
    147 146
       - make BST_FORCE_SESSION_REBUILD=1 -C doc
    
    148 147
       - cd ../..
    
    149 148
       - mv dist/buildstream/doc/build/html public
    

  • HACKING.rst
    ... ... @@ -256,9 +256,6 @@ using pip or some other mechanism::
    256 256
       # Additional optional dependencies required
    
    257 257
       pip3 install --user arpy
    
    258 258
     
    
    259
    -Furthermore, the documentation build requires that BuildStream itself
    
    260
    -be installed, as it will be used in the process of generating its docs.
    
    261
    -
    
    262 259
     To build the documentation, just run the following::
    
    263 260
     
    
    264 261
       make -C doc
    

  • buildstream/__main__.py
    1
    +##################################################################
    
    2
    +#                      Private Entry Point                       #
    
    3
    +##################################################################
    
    4
    +#
    
    5
    +# This allows running the cli when BuildStream is uninstalled,
    
    6
    +# as long as BuildStream repo is in PYTHONPATH, one can run it
    
    7
    +# with:
    
    8
    +#
    
    9
    +#    python3 -m buildstream [program args]
    
    10
    +#
    
    11
    +# This is used when we need to run BuildStream before installing,
    
    12
    +# like when we build documentation.
    
    13
    +#
    
    14
    +if __name__ == '__main__':
    
    15
    +    # pylint: disable=no-value-for-parameter
    
    16
    +    from ._frontend.cli import cli
    
    17
    +    cli()

  • buildstream/_context.py
    ... ... @@ -197,29 +197,55 @@ class Context():
    197 197
                                 "\nValid values are, for example: 800M 10G 1T 50%\n"
    
    198 198
                                 .format(str(e))) from e
    
    199 199
     
    
    200
    -        # If we are asked not to set a quota, we set it to the maximum
    
    201
    -        # disk space available minus a headroom of 2GB, such that we
    
    202
    -        # at least try to avoid raising Exceptions.
    
    200
    +        # Headroom intended to give BuildStream a bit of leeway.
    
    201
    +        # This acts as the minimum size of cache_quota and also
    
    202
    +        # is taken from the user requested cache_quota.
    
    203 203
             #
    
    204
    -        # Of course, we might still end up running out during a build
    
    205
    -        # if we end up writing more than 2G, but hey, this stuff is
    
    206
    -        # already really fuzzy.
    
    207
    -        #
    
    208
    -        if cache_quota is None:
    
    209
    -            stat = os.statvfs(artifactdir_volume)
    
    210
    -            # Again, the artifact directory may not yet have been
    
    211
    -            # created
    
    212
    -            if not os.path.exists(self.artifactdir):
    
    213
    -                cache_size = 0
    
    214
    -            else:
    
    215
    -                cache_size = utils._get_dir_size(self.artifactdir)
    
    216
    -            cache_quota = cache_size + stat.f_bsize * stat.f_bavail
    
    217
    -
    
    218 204
             if 'BST_TEST_SUITE' in os.environ:
    
    219 205
                 headroom = 0
    
    220 206
             else:
    
    221 207
                 headroom = 2e9
    
    222 208
     
    
    209
    +        stat = os.statvfs(artifactdir_volume)
    
    210
    +        available_space = (stat.f_bsize * stat.f_bavail)
    
    211
    +
    
    212
    +        # Again, the artifact directory may not yet have been created yet
    
    213
    +        #
    
    214
    +        if not os.path.exists(self.artifactdir):
    
    215
    +            cache_size = 0
    
    216
    +        else:
    
    217
    +            cache_size = utils._get_dir_size(self.artifactdir)
    
    218
    +
    
    219
    +        # Ensure system has enough storage for the cache_quota
    
    220
    +        #
    
    221
    +        # If cache_quota is none, set it to the maximum it could possibly be.
    
    222
    +        #
    
    223
    +        # Also check that cache_quota is atleast as large as our headroom.
    
    224
    +        #
    
    225
    +        if cache_quota is None:  # Infinity, set to max system storage
    
    226
    +            cache_quota = cache_size + available_space
    
    227
    +        if cache_quota < headroom:  # Check minimum
    
    228
    +            raise LoadError(LoadErrorReason.INVALID_DATA,
    
    229
    +                            "Invalid cache quota ({}): ".format(utils._pretty_size(cache_quota)) +
    
    230
    +                            "BuildStream requires a minimum cache quota of 2G.")
    
    231
    +        elif cache_quota > cache_size + available_space:  # Check maximum
    
    232
    +            raise LoadError(LoadErrorReason.INVALID_DATA,
    
    233
    +                            ("Your system does not have enough available " +
    
    234
    +                             "space to support the cache quota specified.\n" +
    
    235
    +                             "You currently have:\n" +
    
    236
    +                             "- {used} of cache in use at {local_cache_path}\n" +
    
    237
    +                             "- {available} of available system storage").format(
    
    238
    +                                 used=utils._pretty_size(cache_size),
    
    239
    +                                 local_cache_path=self.artifactdir,
    
    240
    +                                 available=utils._pretty_size(available_space)))
    
    241
    +
    
    242
    +        # Place a slight headroom (2e9 (2GB) on the cache_quota) into
    
    243
    +        # cache_quota to try and avoid exceptions.
    
    244
    +        #
    
    245
    +        # Of course, we might still end up running out during a build
    
    246
    +        # if we end up writing more than 2G, but hey, this stuff is
    
    247
    +        # already really fuzzy.
    
    248
    +        #
    
    223 249
             self.cache_quota = cache_quota - headroom
    
    224 250
             self.cache_lower_threshold = self.cache_quota / 2
    
    225 251
     
    

  • buildstream/utils.py
    ... ... @@ -612,6 +612,27 @@ def _parse_size(size, volume):
    612 612
         return int(num) * 1024**units.index(unit)
    
    613 613
     
    
    614 614
     
    
    615
    +# _pretty_size()
    
    616
    +#
    
    617
    +# Converts a number of bytes into a string representation in KB, MB, GB, TB
    
    618
    +# represented as K, M, G, T etc.
    
    619
    +#
    
    620
    +# Args:
    
    621
    +#   size (int): The size to convert in bytes.
    
    622
    +#   dec_places (int): The number of decimal places to output to.
    
    623
    +#
    
    624
    +# Returns:
    
    625
    +#   (str): The string representation of the number of bytes in the largest
    
    626
    +def _pretty_size(size, dec_places=0):
    
    627
    +    psize = size
    
    628
    +    unit = 'B'
    
    629
    +    for unit in ('B', 'K', 'M', 'G', 'T'):
    
    630
    +        if psize < 1024:
    
    631
    +            break
    
    632
    +        else:
    
    633
    +            psize /= 1024
    
    634
    +    return "{size:g}{unit}".format(size=round(psize, dec_places), unit=unit)
    
    635
    +
    
    615 636
     # A sentinel to be used as a default argument for functions that need
    
    616 637
     # to distinguish between a kwarg set to None and an unset kwarg.
    
    617 638
     _sentinel = object()
    

  • doc/Makefile
    ... ... @@ -31,6 +31,9 @@ ifneq ($(strip $(BST_FORCE_SESSION_REBUILD)),)
    31 31
     BST2HTMLOPTS = --force
    
    32 32
     endif
    
    33 33
     
    
    34
    +# Help Python find buildstream and its plugins
    
    35
    +PYTHONPATH=$(CURDIR)/..:$(CURDIR)/../buildstream/plugins
    
    36
    +
    
    34 37
     
    
    35 38
     .PHONY: all clean templates templates-clean sessions sessions-prep sessions-clean html devhelp
    
    36 39
     
    
    ... ... @@ -65,7 +68,6 @@ define plugin-doc-skeleton
    65 68
     endef
    
    66 69
     
    
    67 70
     
    
    68
    -# We set PYTHONPATH here because source/conf.py sys.modules hacks dont seem to help sphinx-build import the plugins
    
    69 71
     all: html devhelp
    
    70 72
     
    
    71 73
     clean: templates-clean sessions-clean
    
    ... ... @@ -103,7 +105,7 @@ sessions-prep:
    103 105
     #
    
    104 106
     sessions: sessions-prep
    
    105 107
     	for file in $(wildcard sessions/*.run); do		\
    
    106
    -	    $(BST2HTML) $(BST2HTMLOPTS) --description $$file;	\
    
    108
    +	    PYTHONPATH=$(PYTHONPATH) $(BST2HTML) $(BST2HTMLOPTS) $$file;	\
    
    107 109
     	done
    
    108 110
     
    
    109 111
     sessions-clean:
    
    ... ... @@ -114,7 +116,7 @@ sessions-clean:
    114 116
     #
    
    115 117
     html devhelp: templates sessions
    
    116 118
     	@echo "Building $@..."
    
    117
    -	PYTHONPATH=$(CURDIR)/../buildstream/plugins \
    
    119
    +	PYTHONPATH=$(PYTHONPATH) \
    
    118 120
     	    $(SPHINXBUILD) -b $@ $(ALLSPHINXOPTS) "$(BUILDDIR)/$@" \
    
    119 121
     	    $(wildcard source/*.rst) \
    
    120 122
     	    $(wildcard source/tutorial/*.rst) \
    

  • doc/bst2html.py
    ... ... @@ -204,7 +204,7 @@ def workdir(source_cache=None):
    204 204
             yield (tempdir, bst_config_file, source_cache)
    
    205 205
     
    
    206 206
     
    
    207
    -# run_command()
    
    207
    +# run_bst_command()
    
    208 208
     #
    
    209 209
     # Runs a command
    
    210 210
     #
    
    ... ... @@ -216,10 +216,30 @@ def workdir(source_cache=None):
    216 216
     # Returns:
    
    217 217
     #    (str): The colorized combined stdout/stderr of BuildStream
    
    218 218
     #
    
    219
    -def run_command(config_file, directory, command):
    
    220
    -    click.echo("Running command in directory '{}': bst {}".format(directory, command), err=True)
    
    219
    +def run_bst_command(config_file, directory, command):
    
    220
    +    click.echo("Running bst command in directory '{}': bst {}".format(directory, command), err=True)
    
    221 221
     
    
    222
    -    argv = ['bst', '--colors', '--config', config_file] + shlex.split(command)
    
    222
    +    argv = ['python3', '-m', 'buildstream', '--colors', '--config', config_file] + shlex.split(command)
    
    223
    +    p = subprocess.Popen(argv, cwd=directory, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    
    224
    +    out, _ = p.communicate()
    
    225
    +    return out.decode('utf-8').strip()
    
    226
    +
    
    227
    +
    
    228
    +# run_shell_command()
    
    229
    +#
    
    230
    +# Runs a command
    
    231
    +#
    
    232
    +# Args:
    
    233
    +#    directory (str): The project directory
    
    234
    +#    command (str): A shell command
    
    235
    +#
    
    236
    +# Returns:
    
    237
    +#    (str): The combined stdout/stderr of the shell command
    
    238
    +#
    
    239
    +def run_shell_command(directory, command):
    
    240
    +    click.echo("Running shell command in directory '{}': {}".format(directory, command), err=True)
    
    241
    +
    
    242
    +    argv = shlex.split(command)
    
    223 243
         p = subprocess.Popen(argv, cwd=directory, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    
    224 244
         out, _ = p.communicate()
    
    225 245
         return out.decode('utf-8').strip()
    
    ... ... @@ -373,12 +393,18 @@ def run_session(description, tempdir, source_cache, palette, config_file, force)
    373 393
             # Get the command string
    
    374 394
             command_str = _yaml.node_get(command, str, 'command')
    
    375 395
     
    
    396
    +        # Check whether this is a shell command and not a bst command
    
    397
    +        is_shell = _yaml.node_get(command, bool, 'shell', default_value=False)
    
    398
    +
    
    376 399
             # Check if there is fake output
    
    377 400
             command_fake_output = _yaml.node_get(command, str, 'fake-output', default_value=None)
    
    378 401
     
    
    379 402
             # Run the command, or just use the fake output
    
    380 403
             if command_fake_output is None:
    
    381
    -            command_out = run_command(config_file, directory, command_str)
    
    404
    +            if is_shell:
    
    405
    +                command_out = run_shell_command(directory, command_str)
    
    406
    +            else:
    
    407
    +                command_out = run_bst_command(config_file, directory, command_str)
    
    382 408
             else:
    
    383 409
                 command_out = command_fake_output
    
    384 410
     
    
    ... ... @@ -414,14 +440,8 @@ def run_session(description, tempdir, source_cache, palette, config_file, force)
    414 440
     @click.option('--palette', '-p', default='tango',
    
    415 441
                   type=click.Choice(['solarized', 'solarized-xterm', 'tango', 'xterm', 'console']),
    
    416 442
                   help="Selects a palette for the output style")
    
    417
    -@click.option('--output', '-o',
    
    418
    -              type=click.Path(file_okay=True, dir_okay=False, writable=True),
    
    419
    -              help="A file to store the output")
    
    420
    -@click.option('--description', '-d',
    
    421
    -              type=click.Path(file_okay=True, dir_okay=False, readable=True),
    
    422
    -              help="A file describing what to do")
    
    423
    -@click.argument('command', type=click.STRING, nargs=-1)
    
    424
    -def run_bst(directory, force, source_cache, description, palette, output, command):
    
    443
    +@click.argument('description', click.Path(file_okay=True, dir_okay=False, readable=True))
    
    444
    +def run_bst(directory, force, source_cache, description, palette):
    
    425 445
         """Run a bst command and capture stdout/stderr in html
    
    426 446
     
    
    427 447
         This command normally takes a description yaml file, see the HACKING
    
    ... ... @@ -430,45 +450,8 @@ def run_bst(directory, force, source_cache, description, palette, output, comman
    430 450
         if not source_cache and os.environ.get('BST_SOURCE_CACHE'):
    
    431 451
             source_cache = os.environ['BST_SOURCE_CACHE']
    
    432 452
     
    
    433
    -    if output is not None and not check_needs_build(None, output, force=force):
    
    434
    -        click.echo("No need to rebuild {}".format(output))
    
    435
    -        return 0
    
    436
    -
    
    437 453
         with workdir(source_cache=source_cache) as (tempdir, config_file, source_cache):
    
    438
    -
    
    439
    -        if description:
    
    440
    -            run_session(description, tempdir, source_cache, palette, config_file, force)
    
    441
    -            return 0
    
    442
    -
    
    443
    -        # Run a command specified on the CLI
    
    444
    -        #
    
    445
    -        if not directory:
    
    446
    -            directory = os.getcwd()
    
    447
    -        else:
    
    448
    -            directory = os.path.abspath(directory)
    
    449
    -            directory = os.path.realpath(directory)
    
    450
    -
    
    451
    -        if not command:
    
    452
    -            command = []
    
    453
    -        command_str = " ".join(command)
    
    454
    -
    
    455
    -        # Run the command
    
    456
    -        #
    
    457
    -        command_out = run_command(config_file, directory, command_str)
    
    458
    -
    
    459
    -        # Generate a nice html div for this output
    
    460
    -        #
    
    461
    -        converted = generate_html(command_out, directory, config_file,
    
    462
    -                                  source_cache, tempdir, palette,
    
    463
    -                                  command_str)
    
    464
    -
    
    465
    -    if output is None:
    
    466
    -        click.echo(converted)
    
    467
    -    else:
    
    468
    -        outdir = os.path.dirname(output)
    
    469
    -        os.makedirs(outdir, exist_ok=True)
    
    470
    -        with open(output, 'wb') as f:
    
    471
    -            f.write(converted.encode('utf-8'))
    
    454
    +        run_session(description, tempdir, source_cache, palette, config_file, force)
    
    472 455
     
    
    473 456
         return 0
    
    474 457
     
    



  • [Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]