Skip to content

Commit bdcf074

Browse files
authored
Merge pull request #8366 from fstagni/90_Pool_RAM
[9.0] feat: PoolCE takes care of RAM requirements
2 parents c4894f1 + c7c710d commit bdcf074

23 files changed

Lines changed: 485 additions & 196 deletions

File tree

docs/source/AdministratorGuide/Resources/computingelements.rst

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,22 @@ of the *ComputingElement* is located inside the corresponding site section in th
5757
# Site administrative domain
5858
LCG
5959
{
60-
# Site section
60+
# Site section. This is the DIRAC's site name.
6161
LCG.CNAF.it
6262
{
63-
# Site name
63+
# Alternative site name (e.g. site name in GOC DB)
6464
Name = CNAF
6565

66-
# List of valid CEs on the site
67-
CE = ce01.infn.it, ce02.infn.it
68-
6966
# Section describing each CE
7067
CEs
7168
{
72-
# Specific CE description section
69+
# Specific CE description section. This site name is unique.
7370
ce01.infn.it
7471
{
75-
# Type of the CE
72+
# Type of the CE. "HTCondorCE" and "AREX" and "SSH" are the most common types.
7673
CEType = HTCondorCE
7774

78-
# Section to describe various queue in the CE
75+
# Section to describe various (logical) queues in the CE.
7976
Queues
8077
{
8178
long
@@ -93,7 +90,6 @@ of the *ComputingElement* is located inside the corresponding site section in th
9390

9491
This is the general structure in which specific CE descriptions are inserted.
9592
The CE configuration is part of the general DIRAC configuration
96-
It can be placed in the general Configuration Service or in the local configuration of the DIRAC installation.
9793
Examples of the configuration can be found in the :ref:`full_configuration_example`, in the *Resources/Computing* section.
9894
You can find the options of a specific CE in the code documentation: :mod:`DIRAC.Resources.Computing`.
9995

docs/source/UserGuide/Tutorials/JobManagementAdvanced/index.rst

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -335,16 +335,15 @@ Jobs that can (or should) run using more than 1 processor should be described as
335335
using the "setNumberOfProcessors" method of the API::
336336

337337
j = Job()
338-
j.setCPUTime(500)
339-
j.setExecutable('echo',arguments='hello')
340-
j.setExecutable('ls',arguments='-l')
341-
j.setExecutable('echo', arguments='hello again')
342-
j.setName('MP test')
338+
...
343339
j.setNumberOfProcessors(16)
344340

345341
Calling ``Job().setNumberOfProcessors()``, with a value bigger than 1,
346342
will translate into adding also the "MultiProcessor" tag to the job description.
347343

344+
``Job().setNumberOfProcessors()`` takes at most 3 arguments, in this order:
345+
``numberOfProcessors`` is the exact number of requested processors, ``minNumberOfProcessors`` is the minimum allowed, ``maxNumberOfProcessors`` the maximum.
346+
348347
Users can specify in the job descriptions NumberOfProcessors and WholeNode parameters, e.g.::
349348

350349
NumberOfProcessors = 16;
@@ -356,6 +355,20 @@ This will be translated internally into 16Processors and WholeNode tags.
356355
This would allow resources (WN's) to put flexibly requirements on jobs to be taken, for example, avoiding single-core jobs on a multi-core nodes.
357356

358357

358+
Setting memory limits
359+
@@@@@@@@@@@@@@@@@@@@@
360+
361+
Jobs can (and probably should) set RAM limits.
362+
using the "setRAMRequirements" method of the API::
363+
364+
j = Job()
365+
...
366+
j.setRAMRequirements(2500, 4000)
367+
368+
Calling ``Job().setRAMRequirements()`` takes 2 values, where the first is the minimum required amount of RAM (in MB) that the job requests.
369+
The second value instead specifies the limit that should not be surpassed.
370+
371+
359372
Submitting jobs with specifc requirements (e.g. GPU)
360373
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
361374

src/DIRAC/Core/Utilities/CGroups2.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ def systemCall(self, *args, **kwargs):
300300
if "ceParameters" in kwargs:
301301
if cpuLimit := kwargs["ceParameters"].get("CPULimit", None):
302302
cores = float(cpuLimit)
303+
# MemoryLimitMB should be the job upper limit
303304
if memoryMB := int(kwargs["ceParameters"].get("MemoryLimitMB", 0)):
304305
memory = memoryMB * 1024 * 1024
305306
if kwargs["ceParameters"].get("MemoryNoSwap", "no").lower() in ("yes", "true"):

src/DIRAC/Interfaces/API/Job.py

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
"""
2-
Job Base Class
2+
Job Base Class
33
4-
This class provides generic job definition functionality suitable for any VO.
4+
This class provides generic job definition functionality suitable for any VO.
55
6-
Helper functions are documented with example usage for the DIRAC API. An example
7-
script (for a simple executable) would be::
6+
Helper functions are documented with example usage for the DIRAC API. An example
7+
script (for a simple executable) would be::
88
9-
from DIRAC.Interfaces.API.Dirac import Dirac
10-
from DIRAC.Interfaces.API.Job import Job
9+
from DIRAC.Interfaces.API.Dirac import Dirac
10+
from DIRAC.Interfaces.API.Job import Job
1111
12-
j = Job()
13-
j.setCPUTime(500)
14-
j.setExecutable('/bin/echo hello')
15-
j.setExecutable('yourPythonScript.py')
16-
j.setExecutable('/bin/echo hello again')
17-
j.setName('MyJobName')
12+
j = Job()
13+
j.setCPUTime(500)
14+
j.setExecutable('/bin/echo hello')
15+
j.setExecutable('yourPythonScript.py')
16+
j.setExecutable('/bin/echo hello again')
17+
j.setName('MyJobName')
1818
19-
dirac = Dirac()
20-
jobID = dirac.submitJob(j)
21-
print 'Submission Result: ',jobID
19+
dirac = Dirac()
20+
jobID = dirac.submitJob(j)
21+
print 'Submission Result: ',jobID
2222
23-
Note that several executables can be provided and wil be executed sequentially.
23+
Note that several executables can be provided and wil be executed sequentially.
2424
"""
25+
2526
import os
2627
import re
2728
import shlex
@@ -517,6 +518,50 @@ def setDestination(self, destination):
517518
return S_OK()
518519

519520
#############################################################################
521+
def setRAMRequirements(self, ramRequired: int = 0, maxRAM: int = 0):
522+
"""Helper function.
523+
Specify the RAM requirements for the job, in MB. 0 (default) means no specific requirements.
524+
525+
Example usage:
526+
527+
>>> job = Job()
528+
>>> job.setRAMRequirements(ramRequired=2000)
529+
means that the job needs at least 2 GBs of RAM to work. This is taken into consideration at job's matching time.
530+
The job definition does not specify an upper limit.
531+
From a user's point of view this is fine (normally, not for admins).
532+
533+
>>> job.setRAMRequirements(ramRequired=500, maxRAM=3800)
534+
means that the job needs 500 MBs of RAM to work. 3.8 GBs will then be the upper limit for CG2 limits.
535+
536+
>>> job.setRAMRequirements(ramRequired=3200, maxRAM=3200)
537+
means that we should match this job if there is at least 3.2 available GBs of run. At the same time, CG2 will not allow to use more than that.
538+
539+
>>> job.setRAMRequirements(maxRAM=4000)
540+
means that the job does not set a min amount of RAM (so can match--run "everywhere"), but the 4 GBs will then be the upper limit for CG2 limits.
541+
542+
>>> job.setRAMRequirements(ramRequired=8000, maxRAM=4000)
543+
Makes no sense, an error will be raised
544+
"""
545+
if ramRequired and maxRAM and ramRequired > maxRAM:
546+
return self._reportError("Invalid settings, ramRequired is higher than maxRAM")
547+
548+
if ramRequired:
549+
self._addParameter(
550+
self.workflow,
551+
"MinRAM",
552+
"JDL",
553+
ramRequired,
554+
"MBs of RAM requested",
555+
)
556+
if maxRAM:
557+
self._addParameter(
558+
self.workflow,
559+
"MaxRAM",
560+
"JDL",
561+
maxRAM,
562+
"Max MBs of RAM to be used",
563+
)
564+
520565
def setNumberOfProcessors(self, numberOfProcessors=None, minNumberOfProcessors=None, maxNumberOfProcessors=None):
521566
"""Helper function to set the number of processors required by the job.
522567
@@ -709,7 +754,7 @@ def setTag(self, tags):
709754
Example usage:
710755
711756
>>> job = Job()
712-
>>> job.setTag( ['WholeNode','8GBMemory'] )
757+
>>> job.setTag( ['WholeNode'] )
713758
714759
:param tags: single tag string or a list of tags
715760
:type tags: str or python:list

src/DIRAC/Resources/Computing/ComputingElement.py

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,40 @@
1-
""" The Computing Element class is a base class for all the various
2-
types CEs. It serves several purposes:
1+
"""The Computing Element class is a base class for all the various
2+
types CEs. It serves several purposes:
33
4-
- collects general CE related parameters to generate CE description
5-
for the job matching
6-
- provides logic for evaluation of the number of available CPU slots
7-
- provides logic for the proxy renewal while executing jobs
4+
- collects general CE related parameters to generate CE description
5+
for the job matching
6+
- provides logic for evaluation of the number of available CPU slots
7+
- provides logic for the proxy renewal while executing jobs
88
9-
The CE parameters are collected from the following sources, in hierarchy
10-
descending order:
9+
The CE parameters are collected from the following sources, in hierarchy
10+
descending order:
1111
12-
- parameters provided through setParameters() method of the class
13-
- parameters in /LocalSite configuration section
14-
- parameters in /LocalSite/<ceName>/ResourceDict configuration section
15-
- parameters in /LocalSite/ResourceDict configuration section
16-
- parameters in /LocalSite/<ceName> configuration section
17-
- parameters in /Resources/Computing/<ceName> configuration section
18-
- parameters in /Resources/Computing/CEDefaults configuration section
12+
- parameters provided through setParameters() method of the class
13+
- parameters in /LocalSite configuration section
14+
- parameters in /LocalSite/<ceName>/ResourceDict configuration section
15+
- parameters in /LocalSite/ResourceDict configuration section
16+
- parameters in /LocalSite/<ceName> configuration section
17+
- parameters in /Resources/Computing/<ceName> configuration section
18+
- parameters in /Resources/Computing/CEDefaults configuration section
1919
20-
The ComputingElement objects are usually instantiated with the help of
21-
ComputingElementFactory.
20+
The ComputingElement objects are usually instantiated with the help of
21+
ComputingElementFactory.
2222
23-
The ComputingElement class can be considered abstract. 3 kinds of abstract ComputingElements
24-
can be distinguished from it:
23+
The ComputingElement class can be considered abstract. 3 kinds of abstract ComputingElements
24+
can be distinguished from it:
2525
26-
- Remote ComputingElement: includes methods to interact with a remote ComputingElement
27-
(e.g. HtCondorCEComputingElement, AREXComputingElement).
28-
- Inner ComputingElement: includes methods to locally interact with an underlying worker node.
29-
It is worth noting that an Inner ComputingElement provides synchronous submission
30-
(the submission of a job is blocking the execution until its completion). It deals with one job at a time.
31-
- Inner Pool ComputingElement: includes methods to locally interact with Inner ComputingElements asynchronously.
32-
It can manage a pool of jobs running simultaneously.
26+
- Remote ComputingElement: includes methods to interact with a remote ComputingElement
27+
(e.g. HtCondorCEComputingElement, AREXComputingElement).
28+
- Inner ComputingElement: includes methods to locally interact with an underlying worker node.
29+
It is worth noting that an Inner ComputingElement provides synchronous submission
30+
(the submission of a job is blocking the execution until its completion). It deals with one job at a time.
31+
- Inner Pool ComputingElement: includes methods to locally interact with Inner ComputingElements asynchronously.
32+
It can manage a pool of jobs running simultaneously.
3333
34-
To configure the use of Tokens for CEs:
34+
To configure the use of Tokens for CEs:
3535
36-
* the CE is able to receive any token. Validation: 'Tag = Token' should be included in the CE parameters.
37-
* the CE is able to receive VO-specifc tokens. Validation: 'Tag = Token:<VO>' should be included in the CE parameters.
36+
* the CE is able to receive any token. Validation: 'Tag = Token' should be included in the CE parameters.
37+
* the CE is able to receive VO-specifc tokens. Validation: 'Tag = Token:<VO>' should be included in the CE parameters.
3838
3939
"""
4040

@@ -50,6 +50,7 @@
5050
from DIRAC.WorkloadManagementSystem.Utilities.JobParameters import (
5151
getNumberOfGPUs,
5252
getNumberOfProcessors,
53+
getAvailableRAM,
5354
)
5455

5556
INTEGER_PARAMETERS = ["CPUTime", "NumberOfProcessors", "NumberOfPayloadProcessors", "MaxRAM"]
@@ -211,12 +212,14 @@ def setParameters(self, ceOptions):
211212
generalCEDict.update(self.ceParameters)
212213
self.ceParameters = generalCEDict
213214

214-
# If NumberOfProcessors/GPUs is present in the description but is equal to zero
215+
# If NumberOfProcessors/GPUs/MaxRAM is present in the description but is equal to zero
215216
# interpret it as needing local evaluation
216-
if self.ceParameters.get("NumberOfProcessors", -1) == 0:
217+
if int(self.ceParameters.get("NumberOfProcessors", -1)) == 0:
217218
self.ceParameters["NumberOfProcessors"] = getNumberOfProcessors()
218-
if self.ceParameters.get("NumberOfGPUs", -1) == 0:
219+
if int(self.ceParameters.get("NumberOfGPUs", -1)) == 0:
219220
self.ceParameters["NumberOfGPUs"] = getNumberOfGPUs()
221+
if int(self.ceParameters.get("MaxRAM", 0)) == 0:
222+
self.ceParameters["MaxRAM"] = getAvailableRAM()
220223

221224
for key in ceOptions:
222225
if key in INTEGER_PARAMETERS:
@@ -252,6 +255,7 @@ def available(self):
252255
runningJobs = result["RunningJobs"]
253256
waitingJobs = result["WaitingJobs"]
254257
availableProcessors = result.get("AvailableProcessors")
258+
255259
ceInfoDict = dict(result)
256260

257261
maxTotalJobs = int(self.ceParameters.get("MaxTotalJobs", 0))
@@ -404,6 +408,7 @@ def getDescription(self):
404408
result = self.getCEStatus()
405409
if result["OK"]:
406410
ceDict["NumberOfProcessors"] = result.get("AvailableProcessors", result.get("NumberOfProcessors", 1))
411+
ceDict["MaxRAM"] = result.get("AvailableRAM", result.get("MaxRAM", 1024))
407412
else:
408413
self.log.error(
409414
"Failure getting CE status", "(we keep going without the number of waiting and running pilots/jobs)"

src/DIRAC/Resources/Computing/InProcessComputingElement.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def __init__(self, ceUniqueID):
2222
self.runningJobs = 0
2323

2424
self.processors = int(self.ceParameters.get("NumberOfProcessors", 1))
25+
self.maxRAM = int(self.ceParameters.get("MaxRAM", 0))
2526
self.ceParameters["MaxTotalJobs"] = 1
2627

2728
def submitJob(self, executableFile, proxy=None, inputs=None, **kwargs):
@@ -118,4 +119,6 @@ def getCEStatus(self):
118119
result["WaitingJobs"] = 0
119120
# processors
120121
result["AvailableProcessors"] = self.processors
122+
# RAM
123+
result["AvailableRAM"] = self.maxRAM
121124
return result

0 commit comments

Comments
 (0)