#!/bin/sh #------------------------------------------------------------------------------ # ========= | # \\ / F ield | OpenFOAM: The Open Source CFD Toolbox # \\ / O peration | # \\ / A nd | www.openfoam.com # \\/ M anipulation | #------------------------------------------------------------------------------- # Copyright (C) 2015 OpenFOAM Foundation # Copyright (C) 2019-2023 OpenCFD Ltd. #------------------------------------------------------------------------------ # License # This file is part of OpenFOAM, distributed under GPL-3.0-or-later. # # Script # foamMonitor # # Description # Monitor data with Gnuplot from time-value(s) graphs written by OpenFOAM # e.g. by functionObjects # - requires gnuplot, gnuplot_x11, sed, awk # #------------------------------------------------------------------------------ printHelp() { cat<<USAGE Usage: ${0##*/} [OPTIONS] <file> Options: -g | -grid Draw grid lines -i | -idle <time> Stop if <file> unchanging for <time> sec (default = 60) -l | -logscale Plot y-axis data on log scale -r | -refresh <time> Refresh display every <time> sec (default = 10) -x | -xrange <range> Set <range> of x-axis data, format "[0:1]" -y | -yrange <range> Set <range> of y-axis data, format "[0:1]" -h | -help Display short help and exit Monitor data with Gnuplot from time-value(s) graphs written by OpenFOAM e.g. by functionObjects. For example, foamMonitor -l postProcessing/residuals/0/residuals.dat USAGE exit 0 # A clean exit } # Report error and exit die() { exec 1>&2 echo echo "Error encountered:" while [ "$#" -ge 1 ]; do echo " $1"; shift; done echo echo "See '${0##*/} -help' for usage" echo exit 1 } # Count the number of scalars between ( and ) to deduce the field dimensions count_components() { _text="$1" # Extract text between the first ( and ) _matches="$(echo "$_text" | sed -n 's/^[^(]*(\([^)]*\)).*$/\1/p')" # Use grep to find numbers (doubles and 0) between ( and ), then count them _count="$(echo "$_matches" | grep -Eo '\b[0-9]+\.?[0-9]*[eE]?[-+]?[0-9]+\b|\b0\b' | wc -l)" echo "$_count" } # Set Gnuplot header plotFileHeader() { TITLE="$1" NCOLOURS="$2" SIZE="1200,627" cat<<EOF set term x11 1 font "helvetica,17" linewidth 1.5 persist noraise size $SIZE $LOGSCALE $XRANGE $YRANGE $GRID set title "$TITLE" set xlabel "$XLABEL" set key outside set linetype cycle "$NCOLOURS" plot \\ EOF } # Set Gnuplot footer plotFileFooter() { cat<<EOF pause $REFRESH reread EOF } # Count number of tokens in a variable howMany() { ( set -f; set -- $1; echo $# ) } #------------------------------------------------------------------------------- IDLE=60 REFRESH=10 LOGSCALE="" XRANGE="" YRANGE="" GRID="" GNUPLOT=$(which gnuplot) [ ! "$GNUPLOT" = "" ] || die "foamMonitor requires Gnuplot installed" command -v sed >/dev/null || die "foamMonitor requires sed installed" command -v awk >/dev/null || die "foamMonitor requires awk installed" #------------------------------------------------------------------------------- # Parse options while [ "$#" -gt 0 ] do case "$1" in -h | -help*) printHelp ;; -i | -idle) [ "$#" -ge 2 ] || die "'$1' option requires an argument" if [ -n "${2##*[!0-9]*}" ] then IDLE=$2 else die "Argument of '$1' is not an integer: '$2'" fi shift 2 ;; -l | -logscale) LOGSCALE="set logscale y" shift 1 ;; -r | -refresh) [ "$#" -ge 2 ] || die "'$1' option requires an argument" if [ -n "${2##*[!0-9]*}" ] then REFRESH=$2 else die "Argument of '$1' is not an integer: '$2'" fi shift 2 ;; -x | -xrange) [ "$#" -ge 2 ] || die "'$1' option requires an argument" XRANGE="set xrange $2" shift 2 ;; -y | -yrange) [ "$#" -ge 2 ] || die "'$1' option requires an argument" YRANGE="set yrange $2" shift 2 ;; -g | -grid) GRID="set grid" shift 1 ;; -*) die "unknown option: '$*'" ;; *) break ;; esac done [ "$#" -eq 1 ] || die "Incorrect arguments specified" [ -f "$1" ] || die "File $1 does not exit" FILE="$1" # Get KEYS from header KEYS=$(grep -E '^#' "$FILE" | tail -1) [ "$KEYS" = "" ] && KEYS="# Step" NKEYS=$(howMany "$KEYS") NCOLS=$(grep -m 1 '^[^#]' "$FILE" | awk '{ print NF }') NCOMPS=$(count_components "$(grep -m 1 '^[^#]' "$FILE")") # With full column labels, NKEYS = NCOLS + 1, since it includes "#" # If NKEYS > NCOLS + 1, REMOVE EXCESS KEYS NCOLSPONE=$((NCOLS+1)) [ "$NKEYS" -gt "$NCOLSPONE" ] && KEYS=$(echo "$KEYS" | cut -d" " -f1-$NCOLSPONE) # Remove # and Time keys XLABEL=$(echo "$KEYS" | cut -d " " -f2) KEYS=$(echo "$KEYS" | cut -d " " -f3-) NKEYS=$(howMany "$KEYS") # Create the legend items TEMPKEYS="" for i in $KEYS do case "$NCOMPS" in 0) # scalar TEMPKEYS="$TEMPKEYS $i" ;; 3) # vector for j in x y z do TEMPKEYS="$TEMPKEYS ${i}_${j}" done ;; 6) # symmetric tensor for j in xx xy xz yy yz zz do TEMPKEYS="$TEMPKEYS ${i}_${j}" done ;; 9) # tensor for j in xx xy xz yx yy yz zx zy zz do TEMPKEYS="$TEMPKEYS ${i}_${j}" done ;; *) # handle other cases if needed echo "Unsupported number of components: $NCOMPS" exit 1 ;; esac done KEYS=$TEMPKEYS # Create plots GPFILE=$(mktemp) PLOT_TITLE="$(echo "$FILE" | awk -F'/' '{print $(NF-2) "/" $NF}')" NUM_ELEMENTS="$((NKEYS * NCOMPS))" plotFileHeader "$PLOT_TITLE" "$NUM_ELEMENTS" > "$GPFILE" i=1 j=1 for field in $KEYS do i=$((i+1)) # Reject any keys consisting of 'iter|converged|solver' words case "$field" in *"iter"*|*"converged"*|*"solver"*) continue ;; esac # Reject any parentheses, and configure gnuplot for the underscore character PLOTLINE="\"< sed 's/[()]//g' $FILE\" u 1:${i} w l dt ${j} t \"$(echo "$field" | sed 's/_/\\\\_/g')\"" if [ "$i" -lt "$NCOLS" ] then PLOTLINE="$PLOTLINE, \\" fi echo "$PLOTLINE" >> "$GPFILE" # Change the dash type after every 5 non-rejected lines [ $(((i-1) % 5)) -eq 0 ] && j=$((j+1)) done # Scrap ', \' characters from the tail of plot command, if necessary if tail -n 1 "$GPFILE" | grep -q ', \\$'; then { head -n -1 "$GPFILE"; tail -n 1 "$GPFILE" | sed 's/, \\$//'; } > tmp.gp mv -f -- tmp.gp "$GPFILE" fi plotFileFooter >> "$GPFILE" touch "$FILE" $GNUPLOT "$GPFILE" & PID=$! while true do MODTIME="$(stat --format=%Y "$FILE")" IDLEAGO=$(($(date +%s)-IDLE)) test "$MODTIME" -gt "$IDLEAGO" || break sleep "$REFRESH" done kill -9 $PID rm -f "$GPFILE" #------------------------------------------------------------------------------