Skip to content

Serve bettr

We are currently serving bettr via shiny-server within singularity. So this recipe aims to install singularity on a linux machine, get the bettr image, and set up a cron to retrieve metrics.

The host needs to face the internet. And a firewall.

We are aware rstudioconnect could simplify this, or just running bettr locally.

Installs

singularity

We assume an apt-apt distribution, i.e. debian or ubuntu.

sudo apt-get update

sudo apt-get update && sudo apt-get install -y \
    build-essential \
    uuid-dev \
    libgpgme-dev \
    squashfs-tools \
    libseccomp-dev \
    wget \
    pkg-config \
    git \
    cryptsetup-bin \
    libgpgme11-dev

mkdir -p ~/soft/go
cd $_

export VERSION=1.13.5 OS=linux ARCH=amd64 && \
    wget https://dl.google.com/go/go$VERSION.$OS-$ARCH.tar.gz

sudo tar -C /usr/local -xzvf go$VERSION.$OS-$ARCH.tar.gz

echo 'export GOPATH=${HOME}/go' >> ~/.bashrc && \
    echo 'export PATH=/usr/local/go/bin:${PATH}:${GOPATH}/bin' >> ~/.bashrc && \
    source ~/.bashrc

mkdir -p ~/soft/singularity
cd $_

export VERSION=3.8.3 && # adjust this as necessary \
    wget https://github.com/sylabs/singularity/releases/download/v3.8.3/singularity-ce-3.8.3.tar.gz

tar -xzf singularity-*${VERSION}.tar.gz

cd sing*

./mconfig && \
    make -C ./builddir

sudo make -C ./builddir install

# test

singularity exec library://alpine cat /etc/alpine-release

apache

sudo apt install apache2

Also, configure iptables. Or ufw, open port 80/443, 22, and whatever the port the bettr image is going to use below.

bettr image

The bettr image is generated by https://renkulab.io/gitlab/omnibenchmark/bettr-deployer .

serving the bettr app

To read the bettr image from the registry, an user token with read_registry power is needed; below encoded as INGULARITY_DOCKER_PASSWORD.

mkdir -p ~/bettr_deployer/apps ~/bettr_deployer/logs ~/bettr_deployer/lib ~/bettr_deployer/tmp

cd ~/bettr_deployer
export SINGULARITY_DOCKER_USERNAME='izaskun.xx@xxxuzh.ch'
export SINGULARITY_DOCKER_PASSWORD='KxasgasgasgxK' # read_registry granted
export NAMESPACE="omnibenchmark"
export ID="obm_bettr"
export VERSION="1d0b31b"

singularity instance start --env SHINY_LOG_STDERR=1 \
            --bind ./apps:/srv/shiny-server/bettr \
            --bind ./log:/var/log/shiny-server_bettr \
            --bind ./lib:/var/lib/shiny-server \
            --bind ./tmp:/tmp \
            bettr-deployer_"$ID"-"$VERSION".sif "$ID"_"$VERSION"

singularity exec instance://"$ID"_"$VERSION" "shiny-server" &

So apps will be served if placed at ~/bettr_deployer/apps, i.e.:

/home/shiny/bettr_deployer/apps/omni_clustering/:
total 12
-rw-rw-r-- 1 shiny shiny 1614 Jul 27 13:28 app.R
drwxrwxr-x 2 shiny shiny 4096 Sep  1  2022 data
-rw-rw-r-- 1 shiny shiny    0 Sep  1  2022 restart.txt

/home/shiny/bettr_deployer/apps/omni_clustering/data:
total 340
-rw-rw-r-- 1 shiny shiny 347951 Jan 19  2023 summary.json

where app.R contains

#!/usr/bin/env R

## Retrieve data

## make recognize the argument as the data that has been just downloaded, for FAIR
suppressPackageStartupMessages({
  library("bettr")
})

resDir   <- 'data'
bstheme  <- 'darkly'
appTitle <- 'bettr'
metrics  <- 'all'

res_files <- list.files(path = paste0(resDir), full.names = TRUE)

## Read result files
out <- jsonlite::read_json(res_files, simplifyVector = TRUE)

## reconverting true false to logical
colnames(out$metricInfo) <- c("Metric", "Group")
out$initialTransforms <- lapply(out$initialTransforms, function(x){
  lapply(x, function(y){
    as.logical(y)
  })
})

# replacing na value
out$idInfo[is.na(out$idInfo)] <- "NaN"

# call bettr
bettr(
  df = out$df,
  idCol = if (is.null(out$idCol)) {
    "Method"
  } else {
    out$idCol
  },
  metrics = if (is.null(out$metrics)){
    setdiff(colnames(out$df), out$idCol)
  } else {
    out$metrics
  },
  initialTransforms = if (is.null(out$initialTransforms)) {
    list()
  } else {
    out$initialTransforms
  },
  metricInfo = out$metricInfo,
  metricColors = out$metricColors,
  idInfo = out$idInfo,
  idColors = out$idColors,
  weightResolution = if (is.null(out$weightResolution)) {
    0.05
  } else {
    out$weightResolution
  },
  bstheme = if (is.null(out$bstheme)) {
    "darkly"
  } else {
    out$bstheme
  },
  appTitle = if (is.null(out$appTitle)) {
    "bettr"
  } else {
    out$appTitle

And the data is either a fixed snapshot (or retrieved periodically via cron) of a suitably formated metrics file, like this one.

firewall / shiny ports

Mind to open port 3840 to fit the docker container shiny-server's port, or update the Dockerfile or the singularity port mapping as needed.

cron to retrieve results

Using the clustering benchmark as an example:

cd /home/shiny/bettr_deployer/apps/omni_clustering/data

wget -q https://renkulab.io/gitlab/omnibenchmark/omni_clustering/omni_clustering_summary/-/raw/main/data/omni_clustering_summary/omni_clustering_summary.json -O summary.json

## to make sure the shiny app reloads
touch ../app.R

Tips and troubleshooting