Skip to content

Commit 67a41fa

Browse files
sathyendranvvkb1pooja-intel
authored
TimeSeries, Multimodal: merge release branch (#1164)
This PR merges changes from the release branch for TimeSeries and Multimodal components, focusing on improving container security, adding source tracking for MQTT publishers, and enhancing documentation with troubleshooting guides. - Enhanced security by adding read_only: true and no-new-privileges options to multiple Docker services - Implemented source identification for multiple MQTT publisher instances using INSTANCE_ID environment variable - Added comprehensive troubleshooting documentation for common deployment issues Signed-off-by: B, Vinod K <[email protected]> Signed-off-by: Vellaisamy, Sathyendran <[email protected]> Signed-off-by: Pooja Kumbharkar <[email protected]> Co-authored-by: Vinod Kumar B <[email protected]> Co-authored-by: Pooja Kumbharkar <[email protected]>
1 parent 355b2b7 commit 67a41fa

File tree

15 files changed

+294
-55
lines changed

15 files changed

+294
-55
lines changed

manufacturing-ai-suite/industrial-edge-insights-multimodal/configs/time-series-analytics-microservice/udfs/weld_anomaly_detector.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
total_no_pts = int(os.getenv('BENCHMARK_TOTAL_PTS', "0"))
3030
logging_level = getattr(logging, log_level, logging.INFO)
3131

32+
# Primary weld current threshold
33+
WELD_CURRENT_THRESHOLD = 50
34+
3235
# Configure logging
3336
logging.basicConfig(
3437
level=logging_level, # Set the log level to DEBUG
@@ -104,39 +107,38 @@ def begin_batch(self, begin_req):
104107
def point(self, point):
105108
""" A point has arrived.
106109
"""
107-
server = None
110+
stream_src = None
108111
start_time = time.time_ns()
109112
if "source" in point.tags:
110-
server = point.tags["source"]
113+
stream_src = point.tags["source"]
114+
elif "source" in point.fieldsString:
115+
stream_src = point.fieldsString["source"]
111116

112117
global enable_benchmarking
113118
if enable_benchmarking:
114-
if server not in self.points_received:
115-
self.points_received[server] = 0
116-
if self.points_received[server] >= self.max_points:
119+
if stream_src not in self.points_received:
120+
self.points_received[stream_src] = 0
121+
if self.points_received[stream_src] >= self.max_points:
122+
logger.info(f"Benchmarking: Reached max points {self.max_points} for source {stream_src}. Skipping further processing.")
117123
return
118-
self.points_received[server] += 1
119-
124+
self.points_received[stream_src] += 1
120125
fields = {}
121126
for key, value in point.fieldsDouble.items():
122127
fields[key] = value
123128

124129
for key, value in point.fieldsInt.items():
125130
fields[key] = value
126-
127-
for key, value in point.fieldsString.items():
128-
fields[key] = value
129131

130132
point_series = pd.Series(fields)
131-
if "Primary Weld Current" in point_series and point_series["Primary Weld Current"] > 50:
133+
if "Primary Weld Current" in point_series and point_series["Primary Weld Current"] > WELD_CURRENT_THRESHOLD:
132134
defect_likelihood_main = self.model.predict_proba(point_series)
133135
bad_defect = defect_likelihood_main[0]*100
134136
good_defect = defect_likelihood_main[1]*100
135137
if bad_defect > 50:
136138
point.fieldsDouble["anomaly_status"] = 1.0
137139
logger.info(f"Good Weld: {good_defect:.2f}%, Defective Weld: {bad_defect:.2f}%")
138140
else:
139-
logger.info("Good Weld: N/A, Defective Weld: N/A")
141+
logger.info("Primary Weld Current below threshold (%d). Skipping anomaly detection.", WELD_CURRENT_THRESHOLD)
140142

141143
point.fieldsDouble["Good Weld"] = round(good_defect, 2) if "good_defect" in locals() else 0.0
142144
point.fieldsDouble["Defective Weld"] = round(bad_defect, 2) if "bad_defect" in locals() else 0.0
@@ -147,7 +149,7 @@ def point(self, point):
147149
point.fieldsDouble["processing_time"] = processing_time
148150
point.fieldsDouble["end_end_time"] = end_end_time
149151

150-
logger.info("Processing point %s %s for source %s", point.time, time.time(), server)
152+
logger.info("Processing point %s %s for source %s", point.time, time.time(), stream_src)
151153

152154
response = udf_pb2.Response()
153155
if "anomaly_status" not in point.fieldsDouble:

manufacturing-ai-suite/industrial-edge-insights-multimodal/docker-compose.yml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# Copyright (C) 2025 Intel Corporation
44
# SPDX-License-Identifier: Apache-2.0
55
#
6-
version: '3.6'
76
services:
87
ia-telegraf:
98
user: "${TIMESERIES_UID}:${TIMESERIES_UID}"
@@ -233,7 +232,8 @@ services:
233232
container_name: nginx_proxy
234233
read_only: true
235234
user: "${TIMESERIES_UID}:${TIMESERIES_UID}"
236-
# entrypoint: ["sleep", "infinity"]
235+
security_opt:
236+
- no-new-privileges
237237
command: >
238238
/bin/sh -c "/usr/local/bin/nginx-cert-gen.sh && exec nginx -g 'daemon off;'"
239239
environment:
@@ -268,7 +268,10 @@ services:
268268
TIMESERIES_UID: ${TIMESERIES_UID}
269269
container_name: ia-fusion-analytics
270270
image: ${DOCKER_REGISTRY}${FUSION_MODULE_IMAGE}${IMAGE_SUFFIX:+-${IMAGE_SUFFIX}}
271+
read_only: true
271272
restart: unless-stopped
273+
security_opt:
274+
- no-new-privileges
272275
environment:
273276
# MQTT Configuration
274277
MQTT_BROKER: ia-mqtt-broker
@@ -298,6 +301,7 @@ services:
298301
image: ${DLSTREAMER_PIPELINE_SERVER_IMAGE}
299302
container_name: dlstreamer-pipeline-server
300303
hostname: dlstreamer-pipeline-server
304+
read_only: true
301305
networks:
302306
- timeseries_network
303307
restart: unless-stopped
@@ -379,6 +383,10 @@ services:
379383
image: bluenviron/mediamtx:1.11.3
380384
container_name: mediamtx
381385
restart: unless-stopped
386+
read_only: true
387+
security_opt:
388+
- no-new-privileges
389+
user: "${TIMESERIES_UID}:${TIMESERIES_UID}"
382390
ports:
383391
- ${WHIP_SERVER_PORT}:8889 # WebRTC
384392
- 9554:8554 # RTSP
@@ -407,6 +415,9 @@ services:
407415
coturn:
408416
image: coturn/coturn:4.7.0
409417
container_name: coturn
418+
read_only: true
419+
security_opt:
420+
- no-new-privileges
410421
ports:
411422
- "${COTURN_UDP_PORT}:3478"
412423
- "${COTURN_UDP_PORT}:3478/udp"
@@ -450,4 +461,4 @@ volumes:
450461
driver: local
451462
driver_opts:
452463
type: tmpfs
453-
device: tmpfs
464+
device: tmpfs

manufacturing-ai-suite/industrial-edge-insights-multimodal/docs/user-guide/get-started.md

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ cd edge-ai-suites/manufacturing-ai-suite/industrial-edge-insights-multimodal
7676
> - The sample app is deployed by pulling the pre-built container images of the sample app
7777
> from the docker hub OR from the internal container registry (login to the docker registry from cli and configure `DOCKER_REGISTRY`
7878
> env variable in `.env` file at `edge-ai-suites/manufacturing-ai-suite/industrial-edge-insights-multimodal`)
79-
> - The `CONTINUOUS_SIMULATOR_INGESTION` variable in the `.env` file (for Docker Compose) and in `helm/values.yaml` (for Helm deployments)
80-
> is set to `true` by default, enabling continuous looping of simulator data. To ingest the simulator data only once (without looping),
79+
> - The `CONTINUOUS_SIMULATOR_INGESTION` variable in the `.env` file (for Docker Compose) is set to `true` by default,
80+
> enabling continuous looping of simulator data. To ingest the simulator data only once (without looping),
8181
> set this variable to `false`.
8282
> - The update rate of the graph and table may lag by a few seconds and might not perfectly align with the video stream, since
8383
> Grafana’s minimum refresh interval is 5 seconds.
@@ -104,10 +104,6 @@ cd edge-ai-suites/manufacturing-ai-suite/industrial-edge-insights-multimodal
104104

105105
1. Get into the InfluxDB* container.
106106

107-
> **Note**: Use `kubectl exec -it <influxdb-pod-name> -n <namespace> -- /bin/bash` for the helm deployment
108-
> where for <namespace> replace with namespace name where the application was deployed and
109-
> for <influxdb-pod-name> replace with InfluxDB pod name.
110-
111107
``` bash
112108
docker exec -it ia-influxdb bash
113109
```
@@ -120,7 +116,6 @@ cd edge-ai-suites/manufacturing-ai-suite/industrial-edge-insights-multimodal
120116
121117
``` bash
122118
# For below command, the INFLUXDB_USERNAME and INFLUXDB_PASSWORD needs to be fetched from `.env` file
123-
# for docker compose deployment and `values.yml` for helm deployment
124119
influx -username <username> -password <passwd>
125120
use datain # database access
126121
show measurements
@@ -133,8 +128,6 @@ cd edge-ai-suites/manufacturing-ai-suite/industrial-edge-insights-multimodal
133128
134129
- Use link `http://<host_ip>:3000` to launch Grafana from browser (preferably, chrome browser)
135130
136-
> **Note**: Use link `http://<host_ip>:30001` to launch Grafana from browser (preferably, chrome browser) for the helm deployment
137-
138131
- Login to the Grafana with values set for `VISUALIZER_GRAFANA_USER` and `VISUALIZER_GRAFANA_PASSWORD`
139132
in `.env` file and select **Multimodal Weld Defect Detection Dashboard**.
140133

manufacturing-ai-suite/industrial-edge-insights-multimodal/docs/user-guide/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,6 @@ system-requirements
4242
weld-defect-detection/index
4343
how-to-guides/index
4444
release_notes/Overview.md
45+
troubleshoot-guide
4546
:::
4647
hide_directive-->

manufacturing-ai-suite/industrial-edge-insights-multimodal/docs/user-guide/release_notes/Overview.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
- [December 2025](./dec-2025.md)
44

5+
<!--hide_directive
56
```{toctree}
67
:maxdepth: 5
78
:hidden:
89
dec-2025.md
910
```
11+
hide_directive-->
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Troubleshoot Guide
2+
3+
## 1. Seeing "No Data" in Grafana
4+
5+
### Issue
6+
7+
Grafana panels show **"No Data"** even though the container/stack is
8+
running.
9+
10+
### Reason
11+
12+
The **system date/time is incorrect** on the device. If the system time
13+
is wrong, data timestamps fall outside Grafana's query window.
14+
15+
### Solution
16+
17+
Check the date/time using the command below:
18+
19+
``` sh
20+
$ date
21+
```
22+
23+
Set the correct date/time manually:
24+
25+
``` sh
26+
$ sudo date -s 'YYYY-MM-DD HH:MM:SS' # Replace with your actual date and time
27+
```
28+
29+
Set date/time from the internet:
30+
31+
``` sh
32+
$ sudo date -s "$(wget --method=HEAD -qSO- --max-redirect=0 google.com 2>&1 | sed -n 's/^ *Date: *//p')"
33+
```
34+
35+
---
36+
37+
## 2. Influx -- Data Being Deleted Beyond Retention Policy (RP)
38+
39+
### Issue
40+
41+
- Data appears to be deleted beyond the configured retention policy
42+
(RP).
43+
- InfluxDB 1.x deletes old data based on the retention policy duration
44+
and shard group duration.
45+
46+
### Reason
47+
48+
- Data is grouped into **shards**.
49+
- Shards are deleted only when **all data inside them** is older than
50+
the RP.
51+
- For RPs **≤ 2 days**, shard group duration = **1 hour**.
52+
- InfluxDB always expires data at **RP + shard duration**.
53+
54+
Example:
55+
56+
For a **1-hour RP**: - Data written at **00:00** goes into the shard
57+
covering **00:00--01:00**. - The shard closes at **01:00**. - InfluxDB
58+
deletes the shard only when everything inside it is past the RP → at
59+
**02:00**.
60+
61+
So the effective expiration time is **1 hour RP + 1 hour shard duration
62+
= 2 hours**.
63+
64+
Retention Policy Shard Duration Actual Expiry
65+
------------------ ---------------- -----------------
66+
1 hour 1 hour 2 hours
67+
2 days 1 hour 2 days + 1 hr
68+
30 days 24 hours 30 days + 24 hr
69+
70+
### Solution
71+
72+
- Understand that this is **normal and expected behavior** in InfluxDB
73+
1.x.
74+
- A 1-hour RP will **always** result in \~2 hours before deletion.
75+
- No configuration can force deletion exactly at the RP limit.
76+
77+
---
78+
79+
## 3. Time Series Analytics Microservice (Docker) -- Takes Time to Start or Shows Python Packages Installing
80+
81+
### Issue
82+
83+
The Time Series Analytics Microservice takes time to start or displays
84+
messages about Python packages being installed.
85+
86+
### Reason
87+
88+
UDF packages require several dependent packages to be installed during
89+
runtime, as specified under `udfs/requirements.txt`. Once these
90+
dependencies are installed, the **Time Series Analytics** microservice
91+
initializes and starts inferencing.
92+
93+
### Solution
94+
95+
No action required --- wait for the **time-series-analytics**
96+
microservice to complete downloading the dependent packages and
97+
initialize Kapacitor to start inference.
98+
99+
---

manufacturing-ai-suite/industrial-edge-insights-time-series/Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,11 @@ up_mqtt_ingestion: check_env_variables down
133133
if [ $(INCLUDE) = 'validation' ]; then \
134134
$(DOCKER_COMPOSE) -f $(DOCKER_COMPOSE_FILE) -f $(DOCKER_COMPOSE_VALIDATION_FILE) up --scale ia-opcua-server=0 -d; \
135135
else \
136-
$(DOCKER_COMPOSE) up --scale ia-mqtt-publisher=$(num_of_streams) --scale ia-opcua-server=0 -d; \
136+
for i in $$(seq 1 $(num_of_streams)); do \
137+
echo "Starting ia-mqtt-publisher instance $$i for SAMPLE_APP: $$SAMPLE_APP";\
138+
$(DOCKER_COMPOSE) run -d --name ia-mqtt-publisher-$$i -e INSTANCE_ID=$$i ia-mqtt-publisher; \
139+
done; \
140+
$(DOCKER_COMPOSE) up --scale ia-opcua-server=0 -d $(shell $(DOCKER_COMPOSE) config --services | grep -v ia-mqtt-publisher); \
137141
fi;
138142

139143
# Run Docker containers

manufacturing-ai-suite/industrial-edge-insights-time-series/apps/weld-anomaly-detection/telegraf-config/Telegraf.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3946,6 +3946,7 @@
39463946
]
39473947
name_override = "weld-sensor-data"
39483948
data_format = "json"
3949+
json_string_fields = ["source"]
39493950
#
39503951
# # if true, messages that can't be delivered while the subscriber is offline
39513952
# # will be delivered when it comes back (such as on service restart).

manufacturing-ai-suite/industrial-edge-insights-time-series/apps/weld-anomaly-detection/time-series-analytics-config/udfs/weld_anomaly_detector.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
total_no_pts = int(os.getenv('BENCHMARK_TOTAL_PTS', "0"))
3030
logging_level = getattr(logging, log_level, logging.INFO)
3131

32+
# Primary weld current threshold
33+
WELD_CURRENT_THRESHOLD = 50
34+
3235
# Configure logging
3336
logging.basicConfig(
3437
level=logging_level, # Set the log level to DEBUG
@@ -104,39 +107,38 @@ def begin_batch(self, begin_req):
104107
def point(self, point):
105108
""" A point has arrived.
106109
"""
107-
server = None
110+
stream_src = None
108111
start_time = time.time_ns()
109112
if "source" in point.tags:
110-
server = point.tags["source"]
113+
stream_src = point.tags["source"]
114+
elif "source" in point.fieldsString:
115+
stream_src = point.fieldsString["source"]
111116

112117
global enable_benchmarking
113118
if enable_benchmarking:
114-
if server not in self.points_received:
115-
self.points_received[server] = 0
116-
if self.points_received[server] >= self.max_points:
119+
if stream_src not in self.points_received:
120+
self.points_received[stream_src] = 0
121+
if self.points_received[stream_src] >= self.max_points:
122+
logger.info(f"Benchmarking: Reached max points {self.max_points} for source {stream_src}. Skipping further processing.")
117123
return
118-
self.points_received[server] += 1
119-
124+
self.points_received[stream_src] += 1
120125
fields = {}
121126
for key, value in point.fieldsDouble.items():
122127
fields[key] = value
123128

124129
for key, value in point.fieldsInt.items():
125130
fields[key] = value
126-
127-
for key, value in point.fieldsString.items():
128-
fields[key] = value
129131

130132
point_series = pd.Series(fields)
131-
if "Primary Weld Current" in point_series and point_series["Primary Weld Current"] > 50:
133+
if "Primary Weld Current" in point_series and point_series["Primary Weld Current"] > WELD_CURRENT_THRESHOLD:
132134
defect_likelihood_main = self.model.predict_proba(point_series)
133135
bad_defect = defect_likelihood_main[0]*100
134136
good_defect = defect_likelihood_main[1]*100
135137
if bad_defect > 50:
136138
point.fieldsDouble["anomaly_status"] = 1.0
137139
logger.info(f"Good Weld: {good_defect:.2f}%, Defective Weld: {bad_defect:.2f}%")
138140
else:
139-
logger.info("Good Weld: N/A, Defective Weld: N/A")
141+
logger.info("Primary Weld Current below threshold (%d). Skipping anomaly detection.", WELD_CURRENT_THRESHOLD)
140142

141143
point.fieldsDouble["Good Weld"] = round(good_defect, 2) if "good_defect" in locals() else 0.0
142144
point.fieldsDouble["Defective Weld"] = round(bad_defect, 2) if "bad_defect" in locals() else 0.0
@@ -147,7 +149,7 @@ def point(self, point):
147149
point.fieldsDouble["processing_time"] = processing_time
148150
point.fieldsDouble["end_end_time"] = end_end_time
149151

150-
logger.info("Processing point %s %s for source %s", point.time, time.time(), server)
152+
logger.info("Processing point %s %s for source %s", point.time, time.time(), stream_src)
151153

152154
response = udf_pb2.Response()
153155
if "anomaly_status" not in point.fieldsDouble:

manufacturing-ai-suite/industrial-edge-insights-time-series/apps/wind-turbine-anomaly-detection/telegraf-config/Telegraf.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3946,6 +3946,8 @@
39463946
]
39473947
name_override = "wind-turbine-data"
39483948
data_format = "json"
3949+
json_string_fields = ["source"]
3950+
39493951
#
39503952
# # if true, messages that can't be delivered while the subscriber is offline
39513953
# # will be delivered when it comes back (such as on service restart).

0 commit comments

Comments
 (0)