HOWTO Best Practices
OS 12
Initial Testing without UMS
Security / Password
-
Password
Provides details on the user types and their roles in IGEL OS. You can configure passwords for the user types to protect your endpoint devices against unwanted changes.
-
Logon Settings
Provides options for logon settings are available in IGEL OS.
-
Active Directory/Kerberos
Shows how to configure the options for Active Directory with Kerberos in IGEL OS.
-
Single Sign On (SSO)
IGEL SSO will work with identity provider (IdP) that supports OpenID Connect.
UMS
Initial setup for UMS can be done with embedded database with plans to migrate the embedded database to external database once devices reach a certain number.
NOTE: For small installations, a single UMS Server instance (standard UMS) with an embedded database is usually sufficient. If required, a single-instance installation can be easily extended anytime to a Distributed UMS installation by installing additional servers (and in the case of an embedded database, by switching preliminarily to an external data source).
Summary of Health Check for Universal Management Suite (UMS):
- UMS server CPU, RAM, and disk resources validated – 4CPUs, 16GB RAM, 100GB Space
- Java Heap sized to ~50% of installed RAM
- UMS upgrade method documented (App Store vs UMS-managed)
- Global update configuration reviewed
- UMS ID imported into Customer / Support Portal
- Embedded DB, optimize DB with command:
sudo umsadmin-cli db optimize
- SQL DB, have SQL administrator performance-optimize the SQL database
- Validate IGEL KB: Antivirus Configuration on IGEL UMS Server
Use Single UMS for Production, QA / Pre-Production, Test, and Sandbox
-
Create Folder Trees in Devices and Configuration for each environment
- Production
- QA
- Test
- Sandbox
- Etc.
-
Create permission groups for each environment:
- Administrators (prod-admin, qa-admin, etc.)
- Help Desk (prod-hd, qa-hd, etc.)
- Etc.
-
Create user accounts for each environment and assign to the permission groups
- prod-adm-userid (assigned to prod-admin)
- prod-hd-userid (assigned to prod-hd)
- qa-adm-userid (assigned to qa-admin)
- qa-hd-userid (assigned to qa-hd)
- test-adm-userid (assigned to test-admin)
- test-hd-userid (assigned to test-hd)
- Etc.
-
Set permissions in Devices and Configuration such that users can only see items for the environment that they are logged into
-
Once a profile passes the QA, then move into the Production environment.
-
Use a revision control system, such as GitHub, to hold the unzipped exported profiles for versioning.
Sample UMS Backup Scripts (Linux)
Backup UMS Embedded Database
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 | cat << "EOF" > get-backups-from-ums.sh
#!/bin/bash
#set -x
#trap read debug
SSH_TO_SERVER="igel-ums-server"
# Add umsadmin-cli and chown to sudo list so that password is not needed
# /etc/sudoers.d/igelums-umsadmin-cli
# igelums ALL=(root) NOPASSWD: /usr/local/bin/umsadmin-cli
# /etc/sudoers.d/chown
# igelums ALL=(root) NOPASSWD: /usr/bin/chown
confirm() {
local action="$1"
echo
read -rp "Press <Enter> to ${action} or type 'q' to quit: " ans
[[ "$ans" =~ ^[Qq]$ ]] && {
echo "Aborted."
exit 1
}
}
echo "======== Creating UMS backup ========"
confirm "create the UMS backup on ${SSH_TO_SERVER}"
ssh -t "${SSH_TO_SERVER}" \
'sudo umsadmin-cli --quiet db backup -o /tmp/ums-backup-$(date +%y%m%d%H%M).pbak --full; \
sudo chown igelums:igelums /tmp/ums-backup-*.pbak'
echo "======== Finished UMS backup ========"
echo "======== Starting - Copying UMS backup ========"
confirm "copy the backup from ${SSH_TO_SERVER}"
scp "${SSH_TO_SERVER}:/tmp/ums-backup-*.pbak" .
echo "======== Finished - Copying UMS backup ========"
echo "======== Starting - Removing UMS backup ========"
confirm "remove the backup from ${SSH_TO_SERVER}"
ssh -t "${SSH_TO_SERVER}" 'rm -f /tmp/ums-backup-*.pbak'
echo "======== Finished - Removing UMS backup ========"
EOF
chmod a+x get-backups-from-ums.sh
|
Backup UMS Files
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 | cat << "EOF" > get-rsync-from-ums.sh
#!/bin/bash
#set -x
#trap read debug
#
# Pull IGEL UMS App Proxy and File Transfer data from a remote UMS server
#
# Add rsync to sudo list so that password is not needed
# /etc/sudoers.d/rsync
# igelums ALL=(root) NOPASSWD: /usr/bin/rsync
#
set -euo pipefail
###########################################################################
# Configuration
###########################################################################
REMOTE_USER="igel-user-admin"
REMOTE_HOST="igel-server-name"
REMOTE_PORT="22"
SSH_KEY="${HOME}/.ssh/id_ed25519"
LOCAL_BACKUP_ROOT="/media/37221ab3-cd80-4536-9157-23d8dd1c9b73/Code/UMS-Backups"
LOGFILE="${REMOTE_HOST}-backup.log"
# Remote directories to back up
REMOTE_PATHS=(
"/opt/IGEL/RemoteManager/rmguiserver/persistent/ums-appproxy/files/"
"/opt/IGEL/RemoteManager/rmguiserver/webapps/ums_filetransfer/"
)
###########################################################################
DATE=$(date +"%Y-%m-%d_%H-%M-%S")
DESTINATION="${LOCAL_BACKUP_ROOT}/${REMOTE_HOST}"
mkdir -p "${DESTINATION}"
echo "==================================================" | tee -a "$LOGFILE"
echo "Backup started: $(date)" | tee -a "$LOGFILE"
for REMOTE_PATH in "${REMOTE_PATHS[@]}"; do
# Remove leading slash so the directory structure is recreated locally
RELATIVE_PATH="${REMOTE_PATH#/}"
LOCAL_PATH="${DESTINATION}/${RELATIVE_PATH}"
mkdir -p "$LOCAL_PATH"
echo
echo "Backing up:"
echo " $REMOTE_PATH"
echo " -> $LOCAL_PATH"
echo
rsync \
-aHAX \
--numeric-ids \
--delete \
--partial \
--append-verify \
--info=progress2 \
--human-readable \
--rsync-path="sudo rsync" \
-e "ssh -i ${SSH_KEY} -p ${REMOTE_PORT} -o Compression=no" \
"${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}" \
"$LOCAL_PATH" \
| tee -a "$LOGFILE"
done
echo
echo "Backup completed: $(date)" | tee -a "$LOGFILE"
EOF
chmod a+x get-rsync-from-ums.sh
|
ICG vs. Reverse Proxy
-
IGEL Cloud Gateway vs. Reverse Proxy for the Communication between UMS 12 and IGEL OS Devices
With the launch of IGEL Universal Management Suite (UMS) 12, the Unified Protocol used for all communication between the UMS and IGEL OS 12 devices was introduced. The Unified Protocol is a secure protocol that uses TCP 8443. However, depending on the structure of your UMS environment, company's security policies, etc., it may be insufficient, and the use of the IGEL Cloud Gateway (ICG) or reverse proxy may be required.
Summary of Health Check for IGEL Cloud Gateway (ICG)
- Number of ICG servers documented
- ICG CPU and memory validated
- ICG OS version documented
- External access validated via server-status page
- ICG certificates reviewed and valid
- CN / SAN values verified
- In-memory cache tuned (heavy usage)
- Java Heap tuned (heavy usage)
- Performance Tuning (Heavy Usage Environments):
- In-Memory Cache: Update the size of the cache in MB, under /opt/IGEL/icg/usg/conf/application.properties:
cache.heap.size=2048
- Java Heap: Update the Java Heap size under /opt/IGEL/icg/usg/webapps/usg.conf:
JAVA_OPTS="-Xms512M -Xmx3584M"
- ULimit Size: Review /etc/systemd/system/icg-server.service
Remote Security Logging in IGEL
The remote security logging feature for the IGEL Universal Management Suite (UMS), the IGEL Cloud Gateway (ICG) and the IGEL Management Interface (IMI). The remote security logging feature logs security relevant events in a separate log files that can be picked up by a configured log collector/SIEM.
Instead of installing a 3rd party agent onto IGEL OS, use data collected by UMS to feed into your CMDB.
If the data needed is not currently being collected, then use IGEL UMS Device Attributes to collect the information.
Now that you have the information in the IGEL UMS, create view and administrative task that will generate data file used in ETL Job.
Now use the data file as input for your ETL job into your CMDB. Follow guidance from your CMDB vendor for setting up ETL job.
Use IGEL Profile Templates
These profiles are available as templates for assisting with configuration of the different supported workflows.
Azure Application Gateway for UMS Reverse Proxy
Checklist of items to collect
- Public FQDN (fully qualified domain name) and Port of the Reverse Proxy:
- FQDN / IP and Port of the configured listener for Device onboarding:
- Set Public Address and Port of the UMS Process Configuration:
Note: In case the public address of the UMS differs from the UMS address, the public address and port must be set. This option can be set under UMS Administration > UMS Network > Server.
- Private FQDN address used by the Azure Application Gateway for UMS connection:
- Export UMS Web Certificate Chain as
Export certificate chain to keystore:
- Export EST CA Client Certificate Chain from
UMS Administration > Server Network Settings > Export Client Certificate Chain:
- Export UMS Web Root Certificate for Azure from
UMS Administration > Global Configuration > Certificate Management > Web . Export Certificate:
- Define UMS endpoint paths for Reverse Proxy integration. The used/required paths for OS 12 and UMS Web App:
The paths required for OS 12 device connections to the UMS (via a Reverse Proxy) are:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | The root path is:
/device-connector/device/*
in more details it would be:
/device-connector/device/ws-connect
/device-connector/device/portforwarding
/device-connector/device/.well-known/est/*
and also the appproxy path is required:
/ums-appproxy/*
The device communication is always TLSv1.3.
In case the UMS Web App should be used via a Reverse Proxy the following paths are required:
/wums-app/*
/webapp/*
Here TLSv1.2 or TLSv1.3 is used.
|
IGEL UD3 M350C (IGEL OS 12.9.0 PR3) Running Microsoft Edge
- IGEL OS 12.9.0+3
- Start collection script
- Start Microsoft Edge and open tabs to multiple sites
- Input tar file into ChatGPT with prompt:
| Provide a detailed review and summary table for this linux system and create a pdf file.
|
| Provide a detailed review and summary table for this linux system and create a pdf file. Include details on the NAME and VERSION from /etc/os-release in the summary table. Include details from the dmidecode in the summary table.
|
PDF Summary Report from ChatGPT
IGEL UD3 M350C (IGEL OS 12.9.0 PR3 vs. 12.10.0) Running Microsoft Edge
- IGEL OS 12.10.0
- Start collection script
- Start Microsoft Edge and open tabs to multiple sites
- Input tar file into ChatGPT with prompt:
| Provide a detailed review and summary table for this linux system and create a pdf file. Include details on the NAME and VERSION from /etc/os-release in the summary table. Include details from the dmidecode in the summary table. Compare the two data sets and show if IGEL OS 12.10.0 is better for browser workloads.
|
IGEL OS 12.10.0 appears better suited for browser workloads on this IGEL M350C system.
The most meaningful improvements were:
- I/O wait decreased from 6.28% to 1.57%
- Average eMMC utilization decreased from 22.1% to 11.5%
- Read latency decreased from 3.54 ms to 1.41 ms
- Write latency decreased from 10.13 ms to 4.08 ms
- Initial swap usage decreased from 993 MiB to 48 MiB
- Aggregate browser CPU usage decreased by approximately 35%
CPU idle capacity remained almost unchanged, at approximately 15%, so the primary benefit is reduced storage, swap, and system overhead rather than additional CPU headroom.
PDF Summary Report from ChatGPT
Browser Testing Automation with Selenium
| Write a Selenium Test (EdgeTest.java) script to open Edge browser and then open tabs to www.igel.com, www.hp.com, www.lg.com, www.lenovo.com, getnerdio.com and www.island.io. Loop for defined number of seconds, LOOP_SECONDS, and in the loop refresh the above web sites.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93 | cat << "EOF" > EdgeTest.java
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WindowType;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.edge.EdgeOptions;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
public class EdgeTest {
// Total runtime
private static final int LOOP_SECONDS = 300;
// Seconds between refreshes
private static final int REFRESH_INTERVAL_SECONDS = 10;
private static final String[] URLS = {
"https://www.igel.com",
"https://www.hp.com",
"https://www.lg.com",
"https://www.lenovo.com",
"https://getnerdio.com",
"https://www.island.io"
};
public static void main(String[] args) {
EdgeOptions options = new EdgeOptions();
// Uncomment if desired
// options.addArguments("--start-maximized");
// options.addArguments("--disable-notifications");
// options.addArguments("--inprivate");
WebDriver driver = new EdgeDriver(options);
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(60));
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
driver.manage().window().maximize();
List<String> tabs = new ArrayList<>();
try {
// First tab
driver.get(URLS[0]);
tabs.add(driver.getWindowHandle());
// Remaining tabs
for (int i = 1; i < URLS.length; i++) {
driver.switchTo().newWindow(WindowType.TAB);
driver.get(URLS[i]);
tabs.add(driver.getWindowHandle());
}
long endTime = System.currentTimeMillis() + (LOOP_SECONDS * 1000L);
int cycle = 1;
while (System.currentTimeMillis() < endTime) {
System.out.println("Refresh cycle " + cycle);
for (String tab : tabs) {
driver.switchTo().window(tab);
System.out.println("Refreshing: " + driver.getCurrentUrl());
try {
driver.navigate().refresh();
} catch (Exception ex) {
System.out.println("Refresh failed: " + ex.getMessage());
}
}
cycle++;
Thread.sleep(REFRESH_INTERVAL_SECONDS * 1000L);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
driver.quit();
System.out.println("Finished.");
}
}
}
EOF
|
- Compile and run automated test software
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | cat << "EOF" > automated-test-software.sh
#!/bin/bash
#set -x
#trap read debug
#
# set JAVA_HOME
#
pushd .
cd /services/azul_openjdk/zulu*
eval JAVA_HOME=$(pwd)
PATH=$JAVA_HOME/bin:$PATH
popd
#
# compile EdgeTest.java
#
javac -cp ".:/services/selenium/*" EdgeTest.java
#
# run EdgeTest
#
java -cp ".:/services/selenium/*" EdgeTest
EOF
chmod a+x automated-test-software.sh
|
Script to collect data
- Accepts a configurable RUNTIME (seconds) and DISK (drive to watch)
- Runs various commands simultaneously
- Saves each command's output into separate timestamped files
- Stops commands automatically after the specified runtime
- Creates a tar file containing all collected data for easy upload to AI tool
- Copy the script and paste into terminal window to create
collect-perf-data.sh
- Run as
root or comment out the dmidecode section
- Need to know and set disk drive (lsblk to find disk at bottom of listing)
- Run for 200 seconds with disk drive nvme01n1
| collect-perf-data.sh 200 nvme01n1
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362 | cat << "EOF" > collect-perf-data.sh
#!/bin/bash
#set -x
#trap read debug
set -uo pipefail
###############################################################################
# Configuration
#
# NOTES:
#
# Run as root or comment out the dmidecode section
# Need to know and set disk drive (lsblk to find disk at bottom of listing)
###############################################################################
RUNTIME="${1:-60}" # Runtime in seconds
DISK="${2:-nvme0n1}" # Disk device without /dev/
#(lsblk to find disk at bottom of listing)
PS_INTERVAL=10 # Seconds between process snapshots
PS_MAX_ENTRIES=200 # Maximum process/thread entries per snapshot
SS_INTERVAL=10 # Seconds between TCP socket snapshots
OUTDIR="performance-$(hostname)-$(date +%Y%m%d-%H%M%S)"
###############################################################################
# Validate arguments
###############################################################################
if ! [[ "$RUNTIME" =~ ^[1-9][0-9]*$ ]]; then
echo "Error: RUNTIME must be a positive integer."
echo "Usage: $0 [runtime-seconds] [disk-device]"
exit 1
fi
if [[ "$DISK" == /dev/* ]]; then
DISK_PATH="$DISK"
else
DISK_PATH="/dev/$DISK"
fi
if [[ ! -b "$DISK_PATH" ]]; then
echo "Error: Disk device does not exist: $DISK_PATH"
exit 1
fi
###############################################################################
# Verify required commands
###############################################################################
for command in \
dmidecode \
vmstat \
iostat \
pidstat \
mpstat \
sar.sysstat \
ps \
ss \
ip \
free \
head \
timeout \
lsblk \
tar \
date \
sleep \
hostname \
uname \
bash
do
if ! command -v "$command" >/dev/null 2>&1; then
echo "Error: Required command not found: $command"
exit 1
fi
done
###############################################################################
# Create output directory
###############################################################################
mkdir -p "$OUTDIR"
echo "Collecting performance data for ${RUNTIME} seconds..."
echo "Disk: $DISK_PATH"
echo "Output directory: $OUTDIR"
echo "Process snapshot interval: ${PS_INTERVAL} seconds"
echo "TCP socket snapshot interval: ${SS_INTERVAL} seconds"
echo
###############################################################################
# Collect initial system and network information
###############################################################################
{
echo "Collection start: $(date --iso-8601=seconds)"
echo "Hostname: $(hostname)"
echo "Kernel: $(uname -r)"
echo "Runtime: $RUNTIME seconds"
echo "Disk: $DISK_PATH"
echo "Process snapshot interval: $PS_INTERVAL seconds"
echo "Process snapshot maximum entries: $PS_MAX_ENTRIES"
echo "TCP socket snapshot interval: $SS_INTERVAL seconds"
echo
echo "===== /etc/os-release ====="
cat /etc/os-release
echo
echo "===== lsblk ====="
lsblk -o NAME,PATH,TYPE,SIZE,FSTYPE,MOUNTPOINTS,MODEL
echo
echo "===== free -h ====="
free -h
echo
echo "===== ip -s link ====="
ip -s link
echo
echo "===== ip route ====="
ip route
echo
echo "===== dmidecode ====="
dmidecode
} >"$OUTDIR/system-info.txt" 2>"$OUTDIR/system-info-errors.txt"
###############################################################################
# Start collectors
###############################################################################
echo "Starting vmstat..."
timeout --signal=INT --kill-after=5s "$RUNTIME" \
vmstat 1 \
>"$OUTDIR/vmstat.txt" \
2>"$OUTDIR/vmstat-errors.txt" &
VMSTAT_PID=$!
echo "Starting iostat..."
timeout --signal=INT --kill-after=5s "$RUNTIME" \
iostat -xz 1 "$DISK_PATH" \
>"$OUTDIR/iostat.txt" \
2>"$OUTDIR/iostat-errors.txt" &
IOSTAT_PID=$!
echo "Starting pidstat..."
timeout --signal=INT --kill-after=5s "$RUNTIME" \
pidstat -rudh -t 1 \
>"$OUTDIR/pidstat.txt" \
2>"$OUTDIR/pidstat-errors.txt" &
PIDSTAT_PID=$!
echo "Starting mpstat..."
timeout --signal=INT --kill-after=5s "$RUNTIME" \
mpstat -P ALL 1 \
>"$OUTDIR/mpstat.txt" \
2>"$OUTDIR/mpstat-errors.txt" &
MPSTAT_PID=$!
echo "Starting sar network collection..."
timeout --signal=INT --kill-after=5s "$RUNTIME" \
sar.sysstat -n DEV 1 \
>"$OUTDIR/sar-network.txt" \
2>"$OUTDIR/sar-network-errors.txt" &
SAR_PID=$!
echo "Starting process snapshot collection..."
timeout --signal=INT --kill-after=5s "$RUNTIME" \
bash -c '
interval="$1"
max_entries="$2"
while true; do
echo "======================================================================"
echo "Snapshot time: $(date --iso-8601=seconds)"
echo "======================================================================"
ps -eLo pid,tid,psr,pcpu,pmem,comm,args --sort=-pcpu |
head -n "$((max_entries + 1))"
echo
sleep "$interval"
done
' bash "$PS_INTERVAL" "$PS_MAX_ENTRIES" \
>"$OUTDIR/processes.txt" \
2>"$OUTDIR/processes-errors.txt" &
PS_PID=$!
echo "Starting TCP socket collection..."
timeout --signal=INT --kill-after=5s "$RUNTIME" \
bash -c '
interval="$1"
while true; do
echo "======================================================================"
echo "Snapshot time: $(date --iso-8601=seconds)"
echo "======================================================================"
echo
echo "===== ss -ti ====="
ss -ti
echo
echo "===== ss -tpn ====="
ss -tpn
echo
sleep "$interval"
done
' bash "$SS_INTERVAL" \
>"$OUTDIR/tcp-sockets.txt" \
2>"$OUTDIR/tcp-sockets-errors.txt" &
SS_PID=$!
echo
echo "Collector PIDs:"
echo " vmstat: $VMSTAT_PID"
echo " iostat: $IOSTAT_PID"
echo " pidstat: $PIDSTAT_PID"
echo " mpstat: $MPSTAT_PID"
echo " sar: $SAR_PID"
echo " ps: $PS_PID"
echo " ss: $SS_PID"
echo
###############################################################################
# Wait for collectors
###############################################################################
wait "$VMSTAT_PID"
VMSTAT_STATUS=$?
wait "$IOSTAT_PID"
IOSTAT_STATUS=$?
wait "$PIDSTAT_PID"
PIDSTAT_STATUS=$?
wait "$MPSTAT_PID"
MPSTAT_STATUS=$?
wait "$SAR_PID"
SAR_STATUS=$?
wait "$PS_PID"
PS_STATUS=$?
wait "$SS_PID"
SS_STATUS=$?
###############################################################################
# Check collector exit statuses
###############################################################################
# GNU timeout normally returns status 124 when the requested runtime expires.
if [[ "$VMSTAT_STATUS" -ne 0 && "$VMSTAT_STATUS" -ne 124 ]]; then
echo "Warning: vmstat exited with status $VMSTAT_STATUS"
fi
if [[ "$IOSTAT_STATUS" -ne 0 && "$IOSTAT_STATUS" -ne 124 ]]; then
echo "Warning: iostat exited with status $IOSTAT_STATUS"
fi
if [[ "$PIDSTAT_STATUS" -ne 0 && "$PIDSTAT_STATUS" -ne 124 ]]; then
echo "Warning: pidstat exited with status $PIDSTAT_STATUS"
fi
if [[ "$MPSTAT_STATUS" -ne 0 && "$MPSTAT_STATUS" -ne 124 ]]; then
echo "Warning: mpstat exited with status $MPSTAT_STATUS"
fi
if [[ "$SAR_STATUS" -ne 0 && "$SAR_STATUS" -ne 124 ]]; then
echo "Warning: sar.sysstat exited with status $SAR_STATUS"
fi
if [[ "$PS_STATUS" -ne 0 && "$PS_STATUS" -ne 124 ]]; then
echo "Warning: process snapshot collection exited with status $PS_STATUS"
fi
if [[ "$SS_STATUS" -ne 0 && "$SS_STATUS" -ne 124 ]]; then
echo "Warning: TCP socket collection exited with status $SS_STATUS"
fi
###############################################################################
# Collect final network interface and route information
###############################################################################
{
echo
echo "===== Final free -h ====="
free -h
echo
echo "===== Final ip -s link ====="
ip -s link
echo
echo "===== Final ip route ====="
ip route
} >>"$OUTDIR/system-info.txt" 2>>"$OUTDIR/system-info-errors.txt"
###############################################################################
# Record collection completion
###############################################################################
{
echo
echo "Collection end: $(date --iso-8601=seconds)"
echo "vmstat exit status: $VMSTAT_STATUS"
echo "iostat exit status: $IOSTAT_STATUS"
echo "pidstat exit status: $PIDSTAT_STATUS"
echo "mpstat exit status: $MPSTAT_STATUS"
echo "sar.sysstat exit status: $SAR_STATUS"
echo "process snapshot exit status: $PS_STATUS"
echo "TCP socket collection exit status: $SS_STATUS"
} >>"$OUTDIR/system-info.txt"
###############################################################################
# Remove empty error files
###############################################################################
for error_file in \
system-info-errors.txt \
vmstat-errors.txt \
iostat-errors.txt \
pidstat-errors.txt \
mpstat-errors.txt \
sar-network-errors.txt \
processes-errors.txt \
tcp-sockets-errors.txt
do
if [[ ! -s "$OUTDIR/$error_file" ]]; then
rm -f "$OUTDIR/$error_file"
fi
done
###############################################################################
# Create archive
###############################################################################
ARCHIVE="${OUTDIR}.tar.gz"
tar -czf "$ARCHIVE" "$OUTDIR"
TAR_STATUS=$?
if [[ "$TAR_STATUS" -ne 0 ]]; then
echo "Error: Failed to create archive: $ARCHIVE"
exit "$TAR_STATUS"
fi
echo
echo "Collection complete."
echo "Archive created: $ARCHIVE"
echo
echo "Upload this archive for analysis:"
echo " $ARCHIVE"
EOF
chmod a+x collect-perf-data.sh
|