Skip to content

Commit f7f795b

Browse files
authored
Merge pull request #174 from mhoban/151-config-profiles
Add support for external configuration profiles Modify/improve resource allocation Update documentation
2 parents 3b260ee + fe79485 commit f7f795b

9 files changed

Lines changed: 208 additions & 52 deletions

File tree

README.md

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ For more information on the original eDNAFlow pipeline and other software used a
4747
+ [Non-demultiplexed paired-end runs](#non-demultiplexed-paired-end-runs)
4848
+ [For previously-demultiplexed paired-end runs](#for-previously-demultiplexed-paired-end-runs)
4949
* [Contents of output directories](#contents-of-output-directories)
50+
* [Configuration profiles](#configuration-profiles)
5051
* [When things go wrong (interpreting errors)](#when-things-go-wrong-interpreting-errors)
5152
* [A note on globs/wildcards](#a-note-on-globswildcards)
5253
- [Description of run options](#description-of-run-options)
@@ -139,12 +140,7 @@ For manual installation of Nextflow, follow the instructions at [on the nextflow
139140
To install Singularity manually, follow the instructions at [singularity installation](https://sylabs.io/guides/3.5/admin-guide/installation.html). If working on HPC, you may need to contact your HPC helpdesk. The Singularity installation how-to is long and complicated, but if you're on a RedHat or Debian-adjacent distro, there are .deb and .rpm packages that can be found at [https://github.com/sylabs/singularity/releases/latest](https://github.com/sylabs/singularity/releases/latest).
140141

141142
#### Podman
142-
Manual podman installation instructions can be found [here](https://podman.io/docs/installation). rainbow_bridge should work with podman out of the box, but you will have to specify a podman profile for it to function properly. There are two profiles built in: `podman_arm`, and `podman_intel`. These both tell nextflow to use podman for its container system, and the second half just specifies your CPU architecture. Most systems will probably use the `_intel` variant, but if you are on a newer Mac with Apple silicon, you'll want to use the `_arm` variant. To run the pipeline using podman as its container system, specify a profile with the following option (note the single dash in the '-profile' option):
143-
144-
```bash
145-
# in this example, we're running on a Mac with the M3 processor
146-
$ rainbow_bridge.nf -profile podman_arm <further options>
147-
```
143+
Manual podman installation instructions can be found [here](https://podman.io/docs/installation). rainbow_bridge should work with podman out of the box, but you will have to specify a podman profile for it to function properly. There are two profiles built in: `podman_arm`, and `podman_intel`. These both tell nextflow to use podman for its container system, and the second half just specifies your CPU architecture. Most systems will probably use the `_intel` variant, but if you are on a newer Mac with Apple silicon, you'll want to use the `_arm` variant. See the section on [configuration profiles](#configuration-profiles) for information on using these named profiles.
148144

149145
### Testing installation
150146

@@ -391,6 +387,57 @@ When the pipeline finishes, output from each step can be found in directories co
391387
| work/ | A bunch of nonsense | All internal and intermediate files processed by nextflow | |
392388
| .nextflow/ | various | Hidden nextflow-generated internal folder | |
393389

390+
## Configuration profiles
391+
392+
Resource availability, container subsystems, and various other aspects vary from computer to computer. To that end, nextflow allows the creation of custom named configuration profiles that can be loaded when running rainbow_bridge to customize various settings. Details about the creation of these profiles is beyond the scope of this documentations, but can be found in the [nextflow documentation](https://www.nextflow.io/docs/latest/config.html#config-profiles). By default, rainbow_bridge loads the `standard` profile, which uses the 'local' nextflow executor and limits its maximum CPUs and memory to the system limits or the values passed to the `--max-cpus` and `--max-memory` options (whichever is smaller). It also uses singularity as its default container engine. rainbow_bridge comes with the following built-in configuration profiles:
393+
394+
| Profile name | Description |
395+
| ------------ | ----------- |
396+
| standard (loaded automatically) | Default profile: local executor, cpus/memory set to system limits or `--max-cpus`/`--max-memory`, singularity container engine |
397+
| singularity | Enables the singularity container engine |
398+
| podman_intel | Enables the podman container engine with intel architecture |
399+
| podman_arm | Enables the podman container engine with ARM architecture |
400+
401+
To use any named profile when running rainbow_bridge, simply pass it using the `-profile <profile name>` option (note again the single dash, since it's a nextflow option and not a rainbow_bridge option). Note that specifying any named profile will override the `standard` profile, so that container/executor settings may need to be redefined.
402+
403+
rainbow_bridge will automatically load profiles found in files matching the pattern `conf/profiles/*.config` within the pipeline's installation directory. To create a custom profile, first define your profile in a file with the `.config` extension and copy it to `conf/profiles` subdirectory under the location of the rainbow_bridge script file. For example, if you've got a server called `bigiron` with 100 cpus and 700 GB of memory and you've installed rainbow_bridge to `/opt/pipelines/rainbow_bridge`, you could create a file called `bigiron.config`, and save it to `/opt/pipelines/rainbow_bridge/conf/profiles`. The `bigiron.config` file might look something like this:
404+
405+
```
406+
bigiron {
407+
executor {
408+
name = 'local'
409+
cpus = 100
410+
memory = 700.GB
411+
}
412+
}
413+
```
414+
415+
As mentioned above, this profile will override the `standard` profile, and since a container system is not specified, nextflow will look for executables on the local filesystem. Fortunately, nextflow supports multiple profiles: just separate the names with a comma. For this example, if we wanted to use the `bigiron` profile with the singularity container system, we could launch rainbow_bridge using `-profile bigiron,singularity`, like this:
416+
417+
```console
418+
$ rainbow_bridge.nf -profile bigiron,singularity <...further options...>
419+
```
420+
421+
If you want to define a profile but don't have write access to the `<rainbow_bridge>/conf/profiles` directory, you can create a custom config file containing your profile, save it anywhere, and pass its filename to rainbow_bridge with the `-c` option (single dash again!). rainbow_bridge will still load any built-in profiles from `conf/profiles`. In this case, you will have to enclose your profile definition in the `profiles {}` scope, like this:
422+
423+
```
424+
profiles {
425+
bigiron {
426+
executor {
427+
name = 'local'
428+
cpus = 100
429+
memory = 700.GB
430+
}
431+
}
432+
}
433+
```
434+
435+
And (assuming you've named the file `bigiron.config` and saved it in the directory where you're running your analysis), execute the pipeline like this:
436+
437+
```console
438+
$ rainbow_bridge.nf -c bigiron.config -profile bigiron,singularity <...further options...>
439+
```
440+
394441
## When things go wrong (interpreting errors)
395442

396443
Occasionally your pipeline run will encounter something it doesn't know how to handle and it will fail. There are two general failure modes: silent and loud.
@@ -796,6 +843,9 @@ These options allow you to allocate resources (CPUs and memory) to rainbow_bridg
796843
<small>**`--max-memory [mem]`**</small>: Maximum memory available to nextflow processes, e.g., '8.GB' (default: maximum available system memory)
797844
<small>**`--max-cpus [num]`**</small>: Maximum cores available to nextflow processes (default: maximum available system CPUs)
798845
<small>**`--max-time [time]`**</small>: Maximum time allocated to each pipeline process, e.g., '2.h' (default: 10d)
846+
<small>**`--max-retries [num]`**</small>: The maxmimum number of times (default: 1) rainbow_bridge will attempt to re-execute a process that fails due to resource limitations. Resource allocation requests will be multiplied by the number of retry attempts.
847+
848+
Within rainbow_bridge, different processes are allocated different amount of base resources, depending on how memory- or CPU-intensive they are. However, the pipeline will not exceed the values passed to `--max-cpus` or `--max-memory`. Thus, if a given process is allocated 6 CPUs by default but the user passes `--max-cpus 2`, it will only use 2 CPUs.
799849

800850
### Singularity options
801851

conf/base.config

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
* but also the base resource usage settings
44
*/
55
process {
6+
7+
// set caching option
8+
cache = 'lenient'
9+
10+
// set default cpus and memory
11+
cpus = { check_max( 1 * task.attempt, 'cpus' ) }
12+
memory = { check_max( 6.GB * task.attempt, 'memory' ) }
13+
14+
// set error strategy
15+
errorStrategy = { task.exitStatus in ((130..145) + 104 + 175) ? 'retry' : 'finish' }
16+
maxRetries = { params.maxRetries }
17+
maxErrors = '-1'
18+
619
withLabel: 'shell' { container = 'quay.io/nextflow/bash:latest' }
720
withLabel: 'obitools' { container = 'quay.io/biocontainers/obitools:1.2.13--py27heb79e2c_3' }
821
withLabel: 'blast' { container = 'quay.io/biocontainers/blast:2.17.0--h66d330f_0' }
@@ -37,34 +50,34 @@ process {
3750
// these labels control various aspects of resource allocation
3851
withLabel:process_single {
3952
cpus = { 1 }
40-
memory = { 6.GB * task.attempt }
53+
memory = { check_max(6.GB * task.attempt,'memory') }
4154
// time = { 4.h * task.attempt }
4255
}
4356
withLabel:process_low {
44-
cpus = { 2 * task.attempt }
45-
memory = { 12.GB * task.attempt }
57+
cpus = { check_max(2 * task.attempt ,'cpus')}
58+
memory = { check_max(12.GB * task.attempt,'memory') }
4659
// time = { 4.h * task.attempt }
4760
}
4861
withLabel:process_lowish {
49-
cpus = { 4 * task.attempt }
50-
memory = { 12.GB * task.attempt }
62+
cpus = { check_max(4 * task.attempt ,'cpus')}
63+
memory = { check_max(12.GB * task.attempt,'memory') }
5164
// time = { 4.h * task.attempt }
5265
}
5366
withLabel:process_medium {
54-
cpus = { 6 * task.attempt }
55-
memory = { 36.GB * task.attempt }
67+
cpus = { check_max(6 * task.attempt ,'cpus')}
68+
memory = { check_max(36.GB * task.attempt,'memory') }
5669
// time = { 8.h * task.attempt }
5770
}
5871
withLabel:process_high {
59-
cpus = { 12 * task.attempt }
60-
memory = { 72.GB * task.attempt }
72+
cpus = { check_max(12 * task.attempt ,'cpus')}
73+
memory = { check_max(72.GB * task.attempt,'memory') }
6174
// time = { 16.h * task.attempt }
6275
}
6376
withLabel:process_more_memory {
64-
memory = { 10.GB * task.attempt }
77+
memory = { check_max(10.GB * task.attempt,'memory') }
6578
}
6679
withLabel:process_high_memory {
67-
memory = { 200.GB * task.attempt }
80+
memory = { check_max(200.GB * task.attempt,'memory') }
6881
}
6982
withLabel:error_ignore {
7083
errorStrategy = 'ignore'
@@ -73,12 +86,10 @@ process {
7386
errorStrategy = 'retry'
7487
maxRetries = 2
7588
}
76-
// allow all cpus
77-
withLabel: 'all_cpus' { cpus = { (int)params.maxCpus } }
78-
79-
cache = 'lenient'
8089

81-
// set default cpus and memory
82-
cpus = { check_max( 1 * task.attempt, 'cpus' ) }
83-
memory = { check_max( 6.GB * task.attempt, 'memory' ) }
90+
// allocate full spread of cpus & memory
91+
withLabel:process_full {
92+
cpus = { params.maxCpus }
93+
memory = { params.maxMemory }
94+
}
8495
}

conf/profiles/docker.config

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
docker {
2+
docker.enabled = true
3+
conda.enabled = false
4+
singularity.enabled = false
5+
podman.enabled = false
6+
shifter.enabled = false
7+
charliecloud.enabled = false
8+
apptainer.enabled = false
9+
docker.runOptions = '-u $(id -u):$(id -g)'
10+
}

conf/profiles/podman_arm.config

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* use podman as container engine assuming ARM architecture */
2+
/* (e.g., for mac with apple silicon) */
3+
podman_arm {
4+
podman {
5+
enabled = true
6+
runOptions = "--platform linux/arm64"
7+
}
8+
}

conf/profiles/podman_intel.config

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* use podman as container assuming intel architecture */
2+
/* (e.g., for mac with intel silicon) */
3+
podman_intel {
4+
podman {
5+
enabled = true
6+
runOptions = "--platform linux/amd64"
7+
}
8+
}

conf/profiles/singularity.config

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
singularity {
2+
singularity.enabled = true
3+
singularity.autoMounts = true
4+
conda.enabled = false
5+
docker.enabled = false
6+
podman.enabled = false
7+
shifter.enabled = false
8+
charliecloud.enabled = false
9+
apptainer.enabled = false
10+
11+
// construct options for singularity bind directories
12+
if (params.bindDir && params.bindDir != '') {
13+
runOptions = "-B " + params.bindDir.split().join(" -B ")
14+
}
15+
16+
// set singularity cache directory if specified
17+
if (params.singularityCache && params.singularityCache != "") {
18+
cacheDir = params.singularityCache
19+
}
20+
}

lib/helper.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ class helper {
272272
--max-memory [mem] Maximum memory available to nextflow processes, e.g., '8.GB' (default: ${params.maxMemory})
273273
--max-cpus [num] Maximum cores available to nextflow processes default: ${params.maxCpus})
274274
--max-time [time] Maximum time allocated to each pipeline process, e.g., '2.h' (default: ${params.maxTime})
275+
--max-retries [num] The maxmimum number of times rainbow_bridge will attempt to re-execute a process
276+
that fails due to resource limitations (with increased resources for each iteration)
277+
(default: 1)
275278
276279
Singularity options:
277280
--bind-dir [dir] Space-separated list of directories to bind within singularity images

nextflow.config

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ trace {
2222
/*
2323
* Define the pipeline parameters and their default values
2424
* Each of these parameters can be specified at command line (e.g. --barcode 'x.txt'); if none specified the below will be set as default
25-
* We set them like this instead of inside a params {} block because this way their case gets properly translated from cameCase to kebab-case
2625
*/
27-
params {
26+
params {
2827
/*
2928
* these options concern where to find sequence reads and barcode file,
3029
* which can be done several ways
@@ -154,9 +153,10 @@ params {
154153
help = false
155154

156155
/* resource options */
157-
maxMemory = Runtime.runtime.maxMemory()
158-
maxCpus = Runtime.runtime.availableProcessors()
156+
maxMemory = 6.GB
157+
maxCpus = 1
159158
maxTime = 240.h
159+
maxRetries = 1
160160
}
161161
// the parameter case translation hasn't happened yet at this
162162
// point in the config reading/parsing, so we'll have to handle it
@@ -195,21 +195,79 @@ def check_max(obj, type) {
195195
}
196196
}
197197

198+
// presumably platform-independent function to get total memory (in bytes)
199+
// and optionally return it as a MemoryUnit
200+
def mem(mu=true) {
201+
def total = 0
202+
try {
203+
// Try JVM-specific bean (works on most HotSpot / OpenJDK)
204+
def osBean = java.lang.management.ManagementFactory.getOperatingSystemMXBean()
205+
if (osBean.metaClass.hasProperty(osBean, "totalPhysicalMemorySize")) {
206+
total = osBean.totalPhysicalMemorySize
207+
}
208+
} catch (e) {
209+
// Fall back to OS commands
210+
def os = System.getProperty("os.name").toLowerCase()
211+
if (os.contains("linux")) {
212+
// Parse /proc/meminfo
213+
def meminfo = new File("/proc/meminfo").text
214+
total = (meminfo =~ /MemTotal:\s+(\d+)\s+kB/)[0][1].toLong() * 1024L
215+
}
216+
else if (os.contains("mac")) {
217+
// macOS sysctl
218+
def m = "sysctl -n hw.memsize".execute().text.trim().toLong()
219+
total = m
220+
}
221+
else if (os.contains("win")) {
222+
// Windows WMIC (older versions) or PowerShell (newer)
223+
try {
224+
def m = "wmic computersystem get TotalPhysicalMemory".execute().text.find(/\d+/).toLong()
225+
total = m
226+
} catch (ignored) {
227+
// PowerShell fallback
228+
def psCmd = "powershell Get-CimInstance Win32_OperatingSystem | Select-Object TotalVisibleMemorySize,FreePhysicalMemory"
229+
def out = psCmd.execute().text
230+
def numbers = (out =~ /\d+/)*.toLong()
231+
if (numbers.size() >= 2) {
232+
def totalKb = numbers[0]
233+
total = totalKb * 1024L
234+
}
235+
}
236+
}
237+
}
238+
239+
try {
240+
if (mu) {
241+
return "${(int)Math.floor(total/1024L/1024L)}.MB" as nextflow.util.MemoryUnit
242+
} else {
243+
return total
244+
}
245+
} catch (ignored) {
246+
return -1
247+
}
248+
}
198249

199250
/* Load base.config by default for all profiles */
200251
includeConfig 'conf/base.config'
201252

202253
/* define execution profiles */
203254
profiles {
204-
255+
205256
/* standard profile is loaded by default */
206257
standard {
207258

208259
// make default executor local
209-
// and limit max cpus to param value
210260
executor.name = 'local'
211-
executor.cpus = (int)params.maxCpus
212-
executor.memory = params.maxMemory
261+
262+
// limit max cpus to min of param value and system processors
263+
executor.cpus = Math.min(Runtime.runtime.availableProcessors(),(int)params.maxCpus)
264+
265+
// get system memory in bytes
266+
def sys_mem = mem(false)
267+
// get params.maxMemory as memory unit
268+
def p_mem = params.maxMemory as nextflow.util.MemoryUnit
269+
// limit memory to min of system memory and maxMemory param
270+
executor.memory = Math.min(sys_mem,p_mem.bytes)
213271

214272
singularity {
215273
/* enable singularity and have it do automounts */
@@ -228,21 +286,9 @@ profiles {
228286
}
229287
}
230288

231-
/* use podman as container engine assuming ARM architecture */
232-
/* (e.g., for mac with apple silicon) */
233-
podman_arm {
234-
podman {
235-
enabled = true
236-
runOptions = "--platform linux/arm64"
237-
}
238-
}
239-
240-
/* use podman as container assuming intel architecture */
241-
/* (e.g., for mac with intel silicon) */
242-
podman_intel {
243-
podman {
244-
enabled = true
245-
runOptions = "--platform linux/amd64"
246-
}
247-
}
289+
// load external profiles from conf/profiles/*.config
290+
try {
291+
new java.io.File("${projectDir}/conf/profiles")
292+
.eachFileMatch(~/(?i)^.+\.config$/) { file -> includeConfig file.absolutePath }
293+
} catch (java.io.FileNotFoundException ignore) { }
248294
}

rainbow_bridge.nf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ process merge_relabeled {
417417
// dereplication, chimera removal, zOTU table generation
418418
process dereplicate {
419419
label 'denoiser'
420-
label 'process_high'
420+
label 'process_full'
421421

422422
publishDir "${params.outDir}/zotus", mode: params.publishMode
423423

@@ -534,7 +534,7 @@ process dereplicate {
534534
// run blast query
535535
process blast {
536536
label 'blast'
537-
label 'all_cpus'
537+
label 'process_full'
538538

539539
publishDir {
540540
def pid = String.format("%d",(Integer)num(params.percentIdentity ))
@@ -693,7 +693,7 @@ process collapse_taxonomy {
693693
// run insect classifier model
694694
process insect {
695695
label 'r'
696-
label 'all_cpus'
696+
label 'process_full'
697697

698698
publishDir {
699699
def offs = String.format("%d",(Integer)num(params.insectOffset))

0 commit comments

Comments
 (0)