How to create and configure .cmd and .substitutions files

4. How to create and configure .cmd and .substitutions files#

Prerequisites:

See also

See .cmd, .template and .substitutions explanations for more explanations about how to use .cmd, .template and .substitutions files.


4.1. .cmd creation and configuration#

See also

For a definition of the .cmd file, see its glossary definition : .cmd

See also

For more details about the .cmd file, see the .cmd explanations

You have to create and configure your .cmd file (e.g. myTargetMonitoringTop/iocBoot/iocMyTargetMonitoring/st_target1.cmd), in order to do so you can use the st_tests.cmd file as a reference/example (it is used for the SSH Monitor tests). The st_tests.cmd file is similar to the one below:

$ vi myTargetMonitoringTop/iocBoot/iocMyTargetMonitoring/st_target1.cmd

    > #!../../bin/linux-x86_64/myTargetMonitoring
    >
    > ###################################################################################################
    > # README
    > ########
    > #
    > # ⚠️  This `.cmd` file can be used as a template for your own `.cmd`. If you do so, make sur
    > # to follow the `ℹ️ signs!
    >
    > ###################################################################################################
    > # ENVIRONMENT SETTINGS
    > ######################
    > #
    >
    > < envPaths
    >
    > # ℹ️ Change the prompt for the IOC Shell (useful when running multiple IOC programs in parallel,
    > # because it helps identifying them clearly):
    > epicsEnvSet("IOCSH_PS1", "Target1 Monitoring> ")
    >
    > # Set an environment variable locating your `.substitutions` files:
    > epicsEnvSet("SUBSTITUTIONS_FILES", "${TOP}/db/*.sub*")
    >
    > # ℹ️ If you want to run multiple IOC programs on the same machine, then change this TCP port (use a
    > # different port per IOC program, default port is 5064 for Channel Access):
    > epicsEnvSet("EPICS_CAS_SERVER_PORT", "5064")  
    >                                               
    > ###################################################################################################
    > # SSH_OPTS_AND_ARGS & PREFIX
    > ##############################
    > #
    > # ℹ️ You have to change the below `SSH_OPTS_AND_ARGS` macro according to your needs! This is where you
    > #   specify the SSH options and arguments (as described by `man ssh` on the host machine) in order to connect to
    > #   the target machine.
    > #
    > # E.g. Here are the details of what is being specified here: 
    > #
    > # - `target-sshmonitor-user@192.168.1.3`: the target user name (`target-sshmonitor-user`) and the
    > #   target machine IP address (`192.168.1.3`).
    > #
    > # - `-i /home/host-sshmonitor-user/.ssh/host_to_target_ssh_monitor_ed25519_key`: the SSH key
    > #   location associated to the target-sshmonitor-user of the target machine.
    > #
    > # - `-o BatchMode=yes`: disable user interaction such as password prompts and host key confirmation
    > #   requests.
    > #
    > # - `-o PasswordAuthentication=no`: avoids password authentication prompt to appear.
    > # 
    > # - `-o ConnectionAttempts=3`: 3 connection(s) attempt(s) to try before exiting (one per second).
    > # 
    > # - `-x`: disables x11 forwarding.
    > #
    > # - Avoid closing and reopening a new SSH connection for each instruction/PV, by adding the three
    > #   following options (`ControlMaster`, `ControlPath` and `ControlPersist`):
    > # 
    > #     - `-o ControlMaster=auto`: Enables the sharing of multiple sessions over a single network
    > #       connection. If set to auto, it creates a master session automatically, but if there is a
    > #       master session already available, subsequent sessions are automatically multiplexed.
    > #
    > #     - `-o ControlPath=~/.ssh/%C`: Specifies the path to the control socket used for connection
    > #       sharing. In this path, %C generates a SHA1 hash (depending on the target information like
    > #       username, hostname, port number) in order to get a short and unique socket name.
    > #
    > #     - `-o ControlPersist=60s`: When used in conjunction with ControlMaster, specifies that the
    > #       master connection should remain open (for 60 seconds here) in the background (waiting for
    > #       future client connections) after the initial client connection has been closed.
    > #
    > epicsEnvSet("SSH_OPTS_AND_ARGS", "target-sshmonitor-user@192.168.1.3 -i /home/host-sshmonitor-user/.ssh/host_to_target_ssh_monitor_ed25519_key -o BatchMode=yes -o PasswordAuthentication=no -o ConnectionAttempts=3 -x -o ControlMaster=auto -o ControlPath=~/.ssh/%C -o ControlPersist=60s")
    >
    > # ℹ️ Specify the below `PREFIX` macro with a unique target name:
    > # 
    > epicsEnvSet("PREFIX", "target1:")
    >
    > ###################################################################################################
    > # LOAD .dbd AND REGISTER "REGISTRARS"
    > #####################################
    > #
    >
    > # ℹ️ Allow to access more EPICS SCAN intervals, which is needed. Replace the below
    > # `${SSHMONITOR}` macro with the one specified in your ../../configure/RELEASE file, if the
    > # macro name isn't `SSHMONITOR` already:
    > dbLoadDatabase("${SSHMONITOR}/dbd/menuScan.dbd")
    >
    > # ℹ️ Replace the below `myTargetMonitoring` string  by your IOC name (specified by your
    > # previous `makeBaseApp.pl` command), e.g. replace the below line by
    > # `dbLoadDatabase("${TOP}/dbd/<your-ioc-name>.dbd")`:
    > dbLoadDatabase("${TOP}/dbd/myTargetMonitoring.dbd")
    >
    > # ℹ️ Replace the below `myTargetMonitoring` string  by your IOC name (specified by your
    > # previous `makeBaseApp.pl` command), e.g. replace the below line by
    > # `<your-ioc-name>_registerRecordDeviceDriver(pdbbase)`:
    > myTargetMonitoring_registerRecordDeviceDriver(pdbbase)
    >
    > ###################################################################################################
    > # CHECK RECORDS NAMES
    > #####################
    > #
    > 
    > # Throw an error if multiple records share the same name 
    > # (but won't prevent the IOC program from starting):
    > var(dbRecordsOnceOnly, 1)
    >
    > ###################################################################################################
    > # CHECK RECORDS NAMES
    > #####################
    > #
    >
    > # ℹ️ You can comment or uncomment any of the below `dbLoadTemplate` lines which are
    > #   loading/importing `.substitutions` files (depending on what you want to monitor):
    > #
    > # ℹ️ Review carefully the content of the `.substitutions` files you want to use before
    > #   loading/importing them!
    > 
    > dbLoadTemplate("${TOP}/db/example_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > dbLoadTemplate("${TOP}/db/processors_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}, SCAN_MACRO=10 second")
    > dbLoadTemplate("${TOP}/db/memory_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}, SCAN_MACRO=10 second")
    > dbLoadTemplate("${TOP}/db/partitions_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > dbLoadTemplate("${TOP}/db/connection_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > #dbLoadTemplate("${TOP}/db/archiver_appliance_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > 
    > # ℹ️ See <https://gitlab.com/sshmonitor/sshmonitor/-/tree/master/sshmonitorApp/db> for the 
    > #   full list of available "default" `.substitutions` files, depending on what you intend to monitor.
    > #   Note that those "default" `.substitutions` files should have been imported by adding the
    > #   below instruction in the `./<your-app-name>App/Db/Makefile` file:
    > #   `DB_INSTALLS += $(wildcard $(SSHMONITOR)/db/*.substitutions)`
    > #
    > # ℹ️ You can also create your own `.substitutions` files in order to monitor the metrics you want
    > #   (if so, do not hesitate to take inspiration from
    > #   https://gitlab.com/sshmonitor/sshmonitor/-/blob/tree/glob/sshmonitorApp/db/example_monitoring.substitutions):
    > 
    > #dbLoadTemplate("${TOP}/db/your_own_specific_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > #dbLoadTemplate("${TOP}/iocBoot/${IOC}/your_other_own_specific_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > 
    > 
    > ###################################################################################################
    > # CHECK CATEGORY NAMES
    > ######################
    > #
    > 
    > system('cat "${SUBSTITUTIONS_FILES}" | grep "CATEGORY = " | tr -d " " | tr "\"" " " | awk "{print $1 $2}" | grep -v "\#" 2>/dev/null | uniq -D | grep . && (echo "ERROR: illegal categories: multiple CATEGORY macros share the same name!"; kill -9 $PPID) || echo "INFO: substitutions files ${SUBSTITUTIONS_FILES} are OK"')
    > 
    > 
    > ###################################################################################################
    > # START IOC PROGRAM
    > ###################
    > #
    > 
    > iocInit

$ chmod +x myTargetMonitoringTop/iocBoot/iocMyTargetMonitoring/st_target1.cmd

This is a quite standard .cmd file, except for the optional system(...) command used to avoid a common SSH Monitor error leading to duplicated PVs.

See also

See the .cmd explanations for more details about this command.


4.2. .substitutions creation and configuration#

See also

For a definition of the .substitutions file, see its glossary definition: .substitutions.

See also

See the .substitutions explanations for more details about the .substitutions file.

In the above .cmd file, some of the “default” .substitutions files of SSH Monitor have been used,
by declaring the below commands:

    > ...
    > dbLoadTemplate("${TOP}/db/example_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > dbLoadTemplate("${TOP}/db/processors_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}, SCAN_MACRO=10 second")
    > dbLoadTemplate("${TOP}/db/memory_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}, SCAN_MACRO=10 second")
    > dbLoadTemplate("${TOP}/db/partitions_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > dbLoadTemplate("${TOP}/db/connection_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > #dbLoadTemplate("${TOP}/db/archiver_appliance_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > ...

Those .substitutions files are pre-configured for common/recommended monitoring purposes. Like memory monitoring, processors monitoring, partitions monitoring, etc.

See also

See the recommended monitoring how-to guides for more details about those .substitutions files.

But you also might want to create and configure your own .substitutions file(s) (e.g. myTargetMonitoringTop/iocBoot/iocMyTargetMonitoring/target1.substitutions), in order to monitor the exact data you want, the way you want. To that end, you can look at the “default” SSH Monitor .substitutions files as references/examples. In particular, you can check the example_monitoring.substitutions file, which is similar to the one below:

$ vi myTargetMonitoringTop/iocBoot/iocMyTargetMonitoring/target1.substitutions

    > ################
    > # ⚠️  ESCAPES  ⚠️ 
    > ################
    > #
    > # Inside a macro value, some characters need to be escaped, if you don't want them to be
    > # interpreted by the EPICS parser:
    > # - inside double-quotes, escape `$` by `\\\\`
    > # - inside double-quotes, escape `(` by `\\`
    > # - inside double-quotes, escape `)` by `\\`
    > #
    > # E.g.:
    > # STRING_1_INSTRUCTION = "echo 'here are some special chars: \\\\$, \\(, \\), [, ], {, },...'"
    >
    > global
    > {
    >     PREFIX = "${PREFIX_MACRO}"
    >     SSH_OPTS_AND_ARGS = "${SSH_OPTS_AND_ARGS_MACRO}"
    > }
    >
    > file "${TOP}/db/ssh_monitor_core.template"
    > {
    >     {
    >         CATEGORY = "example:" # category name: must be unique across all the 
    >                                       # .substitutions files that are loaded by the .cmd file.
    >                                       # this name will be part of the record's names, 
    >                                       # defined in this scope
    > 
    >         SCAN = "5 minute" # specify at which time interval you want the data to be retrieved
    >                           # in this scope (60 seconds by default if not specified)
    > 
    >         #####################
    >         # INSTRUCTIONS basics
    >         #####################
    >         #
    >         # An "instruction" is a combination of, at least, the 3 following macros:
    >         #
    >         # PV_NAME_<TYPE>_<ID> = "write:your:pv:name"
    >         # <TYPE>_<ID>_INSTRUCTION = "echo 'write your shell instruction'"
    >         # LOAD_<TYPE>_<ID> = ""
    >         #
    >         # Where `<TYPE>` is either:
    >         #     * `SCALAR` (if SCALAR_<ID>_INSTRUCTION returns double-precision floating-point number)
    >         #     * `STRING` (if STRING_<ID>_INSTRUCTION returns sequence of characters)
    >         #
    >         # Where `<ID>` is either
    >         #     * ranging from 1 to 15 (if `SCALAR`)
    >         #     * ranging from 1 to 5 (if `STRING`)
    >         #
    >         # Optional macros can also be defined, e.g. to specify alarms, engineering units,
    >         # instructions length, etc.
    >         # Detailed examples about those macros can be found below.
    >         #
    >         # ⚠️  If the macro `PV_NAME_<TYPE>_<ID>` is empty (or loaded but not defined), then the
    >         #    associated record/PV will be miss-named.
    >         #
    >         # ⚠️  If two macros `PV_NAME_<TYPE>_<ID>` have the same value in the same scope, then one
    >         #    could overwrite the other.
    >         #
    >         # ⚠️  By default, the value of `<TYPE>_<ID>_INSTRUCTION` can be 1024 characters long, but
    >         #    if it's not enough, then you can overwrite the default length with
    >         #    `<TYPE>_<ID>_INSTRUCTION_LENGTH = "1234"`.
    >         #
    >         # ⚠️  Do not forget to load your instruction with `LOAD_<TYPE>_<ID> = ""`.
    > 
    > 
    >         # scalar instruction 1:
    >         #----------------------
    >         # the result of this instruction will be stored in
    >         # the record called "${PREFIX}${CATEGORY}${PV_NAME_SCALAR_1}"
    >         #
    >         PV_NAME_SCALAR_1 = "scalar1"
    >         SCALAR_1_INSTRUCTION = "echo 1 #LOCAL" #ℹ️ Note that `#LOCAL` specifies that the instruction is 
    >                                                # run on the host rather than the target
    >         LOAD_SCALAR_1 = ""
    >         SCALAR_1_INSTRUCTION_LENGTH = "16" # specify the number of characters used 
    >                                            # in ${SCALAR_1_INSTRUCTION} (default is 1024)
    >                                            # this is optional if length is less than 1024
    >
    >         # scalar instruction 2:
    >         #----------------------
    >         # the result of this instruction will be stored in
    >         # the record called '${PREFIX}${CATEGORY}${PV_NAME_SCALAR_2}'
    >         #
    >         PV_NAME_SCALAR_2 = "ping_test"
    >         SCALAR_2_INSTRUCTION = "ping -c 3 192.168.1.2 | grep -q '3 packets transmitted, 3 received, 0% packet loss' && echo 0 || echo 1 #LOCAL"
    >         PREC_SCALAR_2 = "0"
    >         LOAD_SCALAR_2 = ""
    >         # if ${SCALAR_2_INSTRUCTION} > 0 then MAJOR alarm:
    >         HIHI_SCALAR_2 = "1"
    >         LOAD_HIHI_SCALAR_2 = ""
    >         HHSV_SCALAR_2 = "MAJOR"
    >         LOAD_HHSV_SCALAR_2 = ""
    >         # if ${SCALAR_2_INSTRUCTION} < 0 then MAJOR alarm:
    >         LOLO_SCALAR_2 = "-1"
    >         LOAD_LOLO_SCALAR_2 = ""
    >         LLSV_SCALAR_2 = "MAJOR"
    >         LOAD_LLSV_SCALAR_2 = ""
    >         # (if ${SCALAR_2_INSTRUCTION} == 0 then no alarm)
    >
    >         # scalar instruction 5:
    >         #----------------------
    >         # the result of this instruction will be stored in
    >         # the record called "${PREFIX}${CATEGORY}${PV_NAME_SCALAR_5}"
    >         #
    >         PV_NAME_SCALAR_5 = "scalar5"
    >         SCALAR_5_INSTRUCTION = "shuf -i 0-100 -n 1"
    >         LOAD_SCALAR_5 = ""
    >         # specify that engineering unit is %
    >         EGU_SCALAR_5 = "%"
    >         LOAD_EGU_SCALAR_5 = ""
    >         # if ${SCALAR_5_INSTRUCTION} > 80 then MAJOR alarm:
    >         HIHI_SCALAR_5 = "80"
    >         LOAD_HIHI_SCALAR_5 = ""
    >         HHSV_SCALAR_5 = "MAJOR"
    >         LOAD_HHSV_SCALAR_5 = ""
    >         # if ${SCALAR_5_INSTRUCTION} > 60 then MINOR alarm:
    >         HIGH_SCALAR_5 = "60"
    >         LOAD_HIGH_SCALAR_5 = ""
    >         HSV_SCALAR_5 = "MINOR"
    >         LOAD_HSV_SCALAR_5 = ""
    >         # if ${SCALAR_5_INSTRUCTION} < 40 then MINOR alarm:
    >         LOW_SCALAR_5 = "40"
    >         LOAD_LOW_SCALAR_5 = ""
    >         LSV_SCALAR_5 = "MINOR"
    >         LOAD_LSV_SCALAR_5 = ""
    >         # ${SCALAR_5_INSTRUCTION} < 20 then MAJOR alarm:
    >         LOLO_SCALAR_5 = "20"
    >         LOAD_LOLO_SCALAR_5 = ""
    >         LLSV_SCALAR_5 = "MAJOR"
    >         LOAD_LLSV_SCALAR_5 = ""
    >         # (if 60 >= ${SCALAR_5_INSTRUCTION} >= 40 then no alarm)
    > 
    >         # scalar instruction 10 used as a binary value:
    >         #----------------------------------------------
    >         # the result of this instruction will be stored in
    >         # the record called "${PREFIX}${CATEGORY}${PV_NAME_SCALAR_10}"
    >         #
    >         PV_NAME_SCALAR_10 = "binary"
    >         SCALAR_10_INSTRUCTION = "echo 1"
    >         LOAD_SCALAR_10 = ""
    >         # if ${SCALAR_10_INSTRUCTION} > 0 then MAJOR alarm:
    >         HIHI_SCALAR_10 = "1"
    >         LOAD_HIHI_SCALAR_10 = ""
    >         HHSV_SCALAR_10 = "MAJOR"
    >         LOAD_HHSV_SCALAR_10 = ""
    >         # if ${SCALAR_10_INSTRUCTION} < 0 then MAJOR alarm:
    >         LOLO_SCALAR_10 = "-1"
    >         LOAD_LOLO_SCALAR_10 = ""
    >         LLSV_SCALAR_10 = "MAJOR"
    >         LOAD_LLSV_SCALAR_10 = ""
    >         # (if ${SCALAR_10_INSTRUCTION} == 0 then no alarm)
    > 
    >         # string instruction 1:
    >         #----------------------
    >         # the result of this instruction will be stored in
    >         # the record called "${PREFIX}${CATEGORY}${PV_NAME_STRING_1}"
    >         #
    >         PV_NAME_STRING_1 = "string1"
    >         STRING_1_INSTRUCTION = "echo abcdefghij_abcdefghij_abcdefghij_abcdefghij_abcdefghij #DEBUG"
    >         # ℹ️ Note that `#DEBUG` specifies that the instruction should be logged in the IOC Shell
    >         LOAD_STRING_1 = ""
    >         # specify the number of characters used in ${STRING_1_INSTRUCTION} (default is 1024)
    >         STRING_1_INSTRUCTION_LENGTH = "72"
    > 
    >         # string instruction 2:
    >         #----------------------
    >         # the result of this instruction will be stored in
    >         # the record called "${PREFIX}${CATEGORY}${PV_NAME_STRING_2}"
    >         #
    >         PV_NAME_STRING_2 = "local_hostname"
    >         STRING_2_INSTRUCTION = "cat /etc/hostname #LOCAL+DEBUG"
    >         # ℹ️ Note that `#LOCAL+DEBUG` specifies that the instruction is run on the host rather than the target
    >         #   and that the instruction should be logged in the IOC Shell
    >         #   (works also with `#DEBUG+LOCAL`)
    >         LOAD_STRING_2 = ""
    >     }
    > }

After creating your .substitutions file, don’t forget to load it in the .cmd (e.g. st_target1.cmd):

$ vi myTargetMonitoringTop/iocBoot/iocMyTargetMonitoring/st_target1.cmd

    > ...
  + > dbLoadTemplate("${TOP}/iocBoot/${IOC}/target1.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > ...

Tip

This is a nice little example, but it’s not complete. For more detailed references, you can look at SSH Monitor .substitutions files. Lots of “common instructions” are already available in those files, so do not hesitate to use them as examples.

See also

See the custom shell instructions how-to for more details about how to design its own shell instructions and retrieve the data you want to monitor.

See also

See the alarm records’ fields how-to for more details about how to set alarm thresholds for any “INSTRUCTION”.