Websocket fix 1

This commit is contained in:
Urtzi Alfaro
2025-08-01 17:55:14 +02:00
parent 2f6f13bfef
commit 81e7ab7432
2 changed files with 573 additions and 481 deletions

View File

@@ -13,7 +13,7 @@ TEST_PASSWORD="TestPassword123!"
TEST_NAME="Test Bakery Owner"
REAL_CSV_FILE="bakery_sales_2023_2024.csv"
WS_BASE="ws://localhost:8002/api/v1/ws"
WS_TEST_DURATION=200 # seconds to listen for WebSocket messages
WS_TEST_DURATION=2000 # seconds to listen for WebSocket messages
WS_PID=""
@@ -158,20 +158,429 @@ check_timezone_error() {
return 1 # No timezone error
}
# Function to test WebSocket connection using websocat (if available) or Node.js
test_websocket_with_nodejs_builtin() {
local tenant_id="$1"
local job_id="$2"
local max_duration="$3" # Maximum time to wait (fallback)
echo "Using Node.js with built-in modules for WebSocket testing..."
echo "Will monitor until job completion or ${max_duration}s timeout"
# Create ENHANCED Node.js WebSocket test script
local ws_test_script="/tmp/websocket_test_$job_id.js"
cat > "$ws_test_script" << 'EOF'
// ENHANCED WebSocket test - waits for job completion
const https = require('https');
const http = require('http');
const crypto = require('crypto');
const tenantId = process.argv[2];
const jobId = process.argv[3];
const maxDuration = parseInt(process.argv[4]) * 1000; // Convert to milliseconds
const accessToken = process.argv[5];
const wsUrl = process.argv[6];
console.log(`🚀 Starting enhanced WebSocket monitoring`);
console.log(`Connecting to: ${wsUrl}`);
console.log(`Will wait for job completion (max ${maxDuration/1000}s)`);
// Parse WebSocket URL
const url = new URL(wsUrl);
const isSecure = url.protocol === 'wss:';
const port = url.port || (isSecure ? 443 : 80);
// Create WebSocket key
const key = crypto.randomBytes(16).toString('base64');
// WebSocket handshake headers
const headers = {
'Upgrade': 'websocket',
'Connection': 'Upgrade',
'Sec-WebSocket-Key': key,
'Sec-WebSocket-Version': '13',
'Authorization': `Bearer ${accessToken}`
};
const options = {
hostname: url.hostname,
port: port,
path: url.pathname,
method: 'GET',
headers: headers
};
console.log(`Attempting WebSocket handshake to ${url.hostname}:${port}${url.pathname}`);
const client = isSecure ? https : http;
let messageCount = 0;
let jobCompleted = false;
let lastProgressUpdate = Date.now();
let highestProgress = 0;
// Enhanced job tracking
const jobStats = {
startTime: Date.now(),
progressUpdates: 0,
stepsCompleted: [],
productsProcessed: [],
errors: []
};
const req = client.request(options);
req.on('upgrade', (res, socket, head) => {
console.log('✅ WebSocket handshake successful');
console.log('📡 Monitoring training progress...\n');
let buffer = Buffer.alloc(0);
socket.on('data', (data) => {
buffer = Buffer.concat([buffer, data]);
// WebSocket frame parsing
while (buffer.length >= 2) {
const firstByte = buffer[0];
const secondByte = buffer[1];
const fin = (firstByte & 0x80) === 0x80;
const opcode = firstByte & 0x0F;
const masked = (secondByte & 0x80) === 0x80;
let payloadLength = secondByte & 0x7F;
let offset = 2;
// Handle extended payload length
if (payloadLength === 126) {
if (buffer.length < offset + 2) break;
payloadLength = buffer.readUInt16BE(offset);
offset += 2;
} else if (payloadLength === 127) {
if (buffer.length < offset + 8) break;
const high = buffer.readUInt32BE(offset);
const low = buffer.readUInt32BE(offset + 4);
if (high !== 0) {
console.log('⚠️ Large payload detected, skipping...');
buffer = buffer.slice(offset + 8);
continue;
}
payloadLength = low;
offset += 8;
}
// Check if we have the complete frame
if (buffer.length < offset + payloadLength) {
break; // Wait for more data
}
// Extract payload
const payload = buffer.slice(offset, offset + payloadLength);
buffer = buffer.slice(offset + payloadLength);
// Handle different frame types
if (opcode === 1 && fin) { // Text frame
messageCount++;
lastProgressUpdate = Date.now();
const timestamp = new Date().toLocaleTimeString();
try {
const messageText = payload.toString('utf8');
const message = JSON.parse(messageText);
// Enhanced message processing
processTrainingMessage(message, timestamp);
} catch (e) {
const rawText = payload.toString('utf8');
console.log(`[${timestamp}] ⚠️ Raw message: ${rawText.substring(0, 200)}${rawText.length > 200 ? '...' : ''}`);
}
} else if (opcode === 8) { // Close frame
console.log('🔌 WebSocket closed by server');
socket.end();
return;
} else if (opcode === 9) { // Ping frame
// Send pong response
const pongFrame = Buffer.concat([
Buffer.from([0x8A, payload.length]),
payload
]);
socket.write(pongFrame);
} else if (opcode === 10) { // Pong frame
// Ignore pong responses
continue;
}
}
});
// Enhanced message processing function
function processTrainingMessage(message, timestamp) {
const messageType = message.type || 'unknown';
const data = message.data || {};
console.log(`[${timestamp}] 📨 Message ${messageCount}: ${messageType.toUpperCase()}`);
// Track job statistics
if (messageType === 'progress') {
jobStats.progressUpdates++;
const progress = data.progress || 0;
const step = data.current_step || 'Unknown step';
const product = data.current_product;
// Update highest progress
if (progress > highestProgress) {
highestProgress = progress;
}
// Track steps
if (step && !jobStats.stepsCompleted.includes(step)) {
jobStats.stepsCompleted.push(step);
}
// Track products
if (product && !jobStats.productsProcessed.includes(product)) {
jobStats.productsProcessed.push(product);
}
// Display progress with enhanced formatting
console.log(` 📊 Progress: ${progress}% (${step})`);
if (product) {
console.log(` 🍞 Product: ${product}`);
}
if (data.products_completed && data.products_total) {
console.log(` 📦 Products: ${data.products_completed}/${data.products_total} completed`);
}
if (data.estimated_time_remaining_minutes) {
console.log(` ⏱️ ETA: ${data.estimated_time_remaining_minutes} minutes`);
}
} else if (messageType === 'completed') {
jobCompleted = true;
const duration = Math.round((Date.now() - jobStats.startTime) / 1000);
console.log(`\n🎉 TRAINING COMPLETED SUCCESSFULLY!`);
console.log(` ⏱️ Total Duration: ${duration}s`);
if (data.results) {
const results = data.results;
if (results.successful_trainings !== undefined) {
console.log(` ✅ Models Trained: ${results.successful_trainings}`);
}
if (results.total_products !== undefined) {
console.log(` 📦 Total Products: ${results.total_products}`);
}
if (results.success_rate !== undefined) {
console.log(` 📈 Success Rate: ${results.success_rate}%`);
}
}
// Close connection after completion
setTimeout(() => {
console.log('\n📊 Training job completed - closing WebSocket connection');
socket.end();
}, 2000); // Wait 2 seconds to ensure all final messages are received
} else if (messageType === 'failed') {
jobCompleted = true;
jobStats.errors.push(data);
console.log(`\n❌ TRAINING FAILED!`);
if (data.error) {
console.log(` 💥 Error: ${data.error}`);
}
if (data.error_details) {
console.log(` 📝 Details: ${JSON.stringify(data.error_details, null, 2)}`);
}
// Close connection after failure
setTimeout(() => {
console.log('\n📊 Training job failed - closing WebSocket connection');
socket.end();
}, 2000);
} else if (messageType === 'step_completed') {
console.log(` ✅ Step completed: ${data.step_name || 'Unknown'}`);
} else if (messageType === 'product_started') {
console.log(` 🚀 Started training: ${data.product_name || 'Unknown product'}`);
} else if (messageType === 'product_completed') {
console.log(` ✅ Product completed: ${data.product_name || 'Unknown product'}`);
if (data.metrics) {
console.log(` 📊 Metrics: ${JSON.stringify(data.metrics, null, 2)}`);
}
}
console.log(''); // Add spacing between messages
}
socket.on('end', () => {
const duration = Math.round((Date.now() - jobStats.startTime) / 1000);
console.log(`\n📊 WebSocket connection ended`);
console.log(`📨 Total messages received: ${messageCount}`);
console.log(`⏱️ Connection duration: ${duration}s`);
console.log(`📈 Highest progress reached: ${highestProgress}%`);
if (jobCompleted) {
console.log('✅ Job completed successfully - connection closed normally');
process.exit(0);
} else {
console.log('⚠️ Connection ended before job completion');
console.log(`📊 Progress reached: ${highestProgress}%`);
console.log(`📋 Steps completed: ${jobStats.stepsCompleted.length}`);
process.exit(1);
}
});
socket.on('error', (error) => {
console.log(`❌ WebSocket error: ${error.message}`);
process.exit(1);
});
// Enhanced ping mechanism - send pings more frequently
const pingInterval = setInterval(() => {
if (socket.writable && !jobCompleted) {
try {
const pingFrame = Buffer.from([0x89, 0x00]);
socket.write(pingFrame);
} catch (e) {
// Ignore ping errors
}
}
}, 5000); // Ping every 5 seconds
// Heartbeat check - ensure we're still receiving messages
const heartbeatInterval = setInterval(() => {
if (!jobCompleted) {
const timeSinceLastMessage = Date.now() - lastProgressUpdate;
if (timeSinceLastMessage > 60000) { // 60 seconds without messages
console.log('\n⚠ No messages received for 60 seconds');
console.log(' This could indicate the training is stuck or connection issues');
console.log(` Last progress: ${highestProgress}%`);
} else if (timeSinceLastMessage > 30000) { // 30 seconds warning
console.log(`\n💤 Quiet period: ${Math.round(timeSinceLastMessage/1000)}s since last update`);
console.log(' (This is normal during intensive training phases)');
}
}
}, 15000); // Check every 15 seconds
// Safety timeout - close connection if max duration exceeded
const safetyTimeout = setTimeout(() => {
if (!jobCompleted) {
clearInterval(pingInterval);
clearInterval(heartbeatInterval);
console.log(`\n⏰ Maximum duration (${maxDuration/1000}s) reached`);
console.log(`📊 Final status:`);
console.log(` 📨 Messages received: ${messageCount}`);
console.log(` 📈 Progress reached: ${highestProgress}%`);
console.log(` 📋 Steps completed: ${jobStats.stepsCompleted.length}`);
console.log(` 🍞 Products processed: ${jobStats.productsProcessed.length}`);
if (messageCount > 0) {
console.log('\n✅ WebSocket communication was successful!');
console.log(' Training may still be running - check server logs for completion');
} else {
console.log('\n⚠ No messages received during monitoring period');
}
socket.end();
}
}, maxDuration);
// Clean up intervals when job completes
socket.on('end', () => {
clearInterval(pingInterval);
clearInterval(heartbeatInterval);
clearTimeout(safetyTimeout);
});
});
req.on('response', (res) => {
console.log(`❌ HTTP response instead of WebSocket upgrade: ${res.statusCode}`);
console.log('Response headers:', res.headers);
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
if (body) console.log('Response body:', body);
process.exit(1);
});
});
req.on('error', (error) => {
console.log(`❌ Connection error: ${error.message}`);
process.exit(1);
});
req.end();
EOF
# Run the ENHANCED Node.js WebSocket test
local ws_url="$WS_BASE/tenants/$tenant_id/training/jobs/$job_id/live"
echo "Starting enhanced WebSocket monitoring..."
node "$ws_test_script" "$tenant_id" "$job_id" "$max_duration" "$ACCESS_TOKEN" "$ws_url"
local exit_code=$?
# Clean up
rm -f "$ws_test_script"
if [ $exit_code -eq 0 ]; then
log_success "Training job completed successfully!"
echo " 📡 WebSocket monitoring detected job completion"
echo " 🎉 Real-time progress tracking worked perfectly"
else
log_warning "WebSocket monitoring ended before job completion"
echo " 📊 Check the progress logs above for details"
fi
return $exit_code
}
install_websocat_if_needed() {
if ! command -v websocat >/dev/null 2>&1; then
echo "📦 Installing websocat for better WebSocket testing..."
# Try to install websocat (works on most Linux systems)
if command -v cargo >/dev/null 2>&1; then
cargo install websocat 2>/dev/null || true
elif [ -x "$(command -v wget)" ]; then
wget -q -O /tmp/websocat "https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl" 2>/dev/null || true
if [ -f /tmp/websocat ]; then
chmod +x /tmp/websocat
sudo mv /tmp/websocat /usr/local/bin/ 2>/dev/null || mv /tmp/websocat ~/bin/ 2>/dev/null || true
fi
fi
if command -v websocat >/dev/null 2>&1; then
log_success "websocat installed successfully"
return 0
else
log_warning "websocat installation failed, using Node.js fallback"
return 1
fi
fi
return 0
}
# IMPROVED: WebSocket connection function with better tool selection
test_websocket_connection() {
local tenant_id="$1"
local job_id="$2"
local duration="$3"
log_step "4.2. Testing WebSocket connection for real-time training progress"
log_step "4.2. Connecting to WebSocket for real-time progress monitoring"
echo "WebSocket URL: $WS_BASE/tenants/$tenant_id/training/jobs/$job_id/live"
echo "Test duration: ${duration}s"
echo ""
# Check if websocat is available
if command -v websocat >/dev/null 2>&1; then
# Try to install websocat if not available
if install_websocat_if_needed; then
test_websocket_with_websocat "$tenant_id" "$job_id" "$duration"
elif command -v node >/dev/null 2>&1; then
test_websocket_with_nodejs_builtin "$tenant_id" "$job_id" "$duration"
@@ -221,382 +630,6 @@ test_websocket_with_websocat() {
fi
}
# Test WebSocket using Node.js
test_websocket_with_nodejs() {
local tenant_id="$1"
local job_id="$2"
local duration="$3"
echo "Using Node.js for WebSocket testing..."
# Create Node.js WebSocket test script
local ws_test_script="/tmp/websocket_test_$job_id.js"
cat > "$ws_test_script" << 'EOF'
const WebSocket = require('ws');
const tenantId = process.argv[2];
const jobId = process.argv[3];
const duration = parseInt(process.argv[4]) * 1000;
const accessToken = process.argv[5];
const wsUrl = process.argv[6];
console.log(`Connecting to: ${wsUrl}`);
const ws = new WebSocket(wsUrl, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
let messageCount = 0;
let startTime = Date.now();
ws.on('open', function() {
console.log('✅ WebSocket connected successfully');
// Send periodic pings
const pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send('ping');
}
}, 5000);
// Close after duration
setTimeout(() => {
clearInterval(pingInterval);
console.log(`\n📊 WebSocket test completed after ${duration/1000}s`);
console.log(`📨 Total messages received: ${messageCount}`);
if (messageCount > 0) {
console.log('✅ WebSocket communication successful');
} else {
console.log('⚠️ No training progress messages received');
console.log(' This may be normal if training completed quickly');
}
ws.close();
process.exit(0);
}, duration);
});
ws.on('message', function(data) {
messageCount++;
const timestamp = new Date().toLocaleTimeString();
try {
const message = JSON.parse(data);
console.log(`\n[${timestamp}] 📨 Message ${messageCount}:`);
console.log(` Type: ${message.type || 'unknown'}`);
console.log(` Job ID: ${message.job_id || 'unknown'}`);
if (message.data) {
if (message.data.progress !== undefined) {
console.log(` Progress: ${message.data.progress}%`);
}
if (message.data.current_step) {
console.log(` Step: ${message.data.current_step}`);
}
if (message.data.current_product) {
console.log(` Product: ${message.data.current_product}`);
}
if (message.data.estimated_time_remaining_minutes) {
console.log(` ETA: ${message.data.estimated_time_remaining_minutes} minutes`);
}
}
// Special handling for completion messages
if (message.type === 'completed') {
console.log('🎉 Training completed!');
} else if (message.type === 'failed') {
console.log('❌ Training failed!');
}
} catch (e) {
console.log(`[${timestamp}] Raw message: ${data}`);
}
});
ws.on('error', function(error) {
console.log('❌ WebSocket error:', error.message);
});
ws.on('close', function(code, reason) {
console.log(`\n🔌 WebSocket closed (code: ${code}, reason: ${reason || 'normal'})`);
process.exit(code === 1000 ? 0 : 1);
});
EOF
# Run Node.js WebSocket test
local ws_url="$WS_BASE/tenants/$tenant_id/training/jobs/$job_id/live"
node "$ws_test_script" "$tenant_id" "$job_id" "$duration" "$ACCESS_TOKEN" "$ws_url" &
WS_PID=$!
# Wait for completion
wait $WS_PID
local exit_code=$?
# Clean up
rm -f "$ws_test_script"
if [ $exit_code -eq 0 ]; then
log_success "WebSocket test completed successfully"
else
log_warning "WebSocket test completed with issues"
fi
}
test_websocket_with_nodejs_builtin() {
local tenant_id="$1"
local job_id="$2"
local duration="$3"
echo "Using Node.js with built-in modules for WebSocket testing..."
# Create Node.js WebSocket test script using built-in modules only
local ws_test_script="/tmp/websocket_test_$job_id.js"
cat > "$ws_test_script" << 'EOF'
// WebSocket test using only built-in Node.js modules
const { WebSocket } = require('node:http');
const https = require('https');
const http = require('http');
const crypto = require('crypto');
const tenantId = process.argv[2];
const jobId = process.argv[3];
const duration = parseInt(process.argv[4]) * 1000;
const accessToken = process.argv[5];
const wsUrl = process.argv[6];
console.log(`Connecting to: ${wsUrl}`);
console.log(`Duration: ${duration/1000}s`);
// Parse WebSocket URL
const url = new URL(wsUrl);
const isSecure = url.protocol === 'wss:';
const port = url.port || (isSecure ? 443 : 80);
// Create WebSocket key (required for WebSocket handshake)
const key = crypto.randomBytes(16).toString('base64');
// WebSocket handshake headers
const headers = {
'Upgrade': 'websocket',
'Connection': 'Upgrade',
'Sec-WebSocket-Key': key,
'Sec-WebSocket-Version': '13',
'Authorization': `Bearer ${accessToken}`
};
const options = {
hostname: url.hostname,
port: port,
path: url.pathname,
method: 'GET',
headers: headers
};
console.log(`Attempting WebSocket handshake to ${url.hostname}:${port}${url.pathname}`);
const client = isSecure ? https : http;
let messageCount = 0;
let startTime = Date.now();
const req = client.request(options);
req.on('upgrade', (res, socket, head) => {
console.log('✅ WebSocket handshake successful');
let buffer = '';
socket.on('data', (data) => {
buffer += data.toString();
// Process complete WebSocket frames
while (buffer.length > 0) {
// Simple WebSocket frame parsing (for text frames)
if (buffer.length < 2) break;
const firstByte = buffer.charCodeAt(0);
const secondByte = buffer.charCodeAt(1);
const opcode = firstByte & 0x0F;
const masked = (secondByte & 0x80) === 0x80;
let payloadLength = secondByte & 0x7F;
let offset = 2;
if (payloadLength === 126) {
if (buffer.length < offset + 2) break;
payloadLength = (buffer.charCodeAt(offset) << 8) | buffer.charCodeAt(offset + 1);
offset += 2;
} else if (payloadLength === 127) {
if (buffer.length < offset + 8) break;
// For simplicity, assume payload length fits in 32 bits
payloadLength = (buffer.charCodeAt(offset + 4) << 24) |
(buffer.charCodeAt(offset + 5) << 16) |
(buffer.charCodeAt(offset + 6) << 8) |
buffer.charCodeAt(offset + 7);
offset += 8;
}
if (buffer.length < offset + payloadLength) break;
// Extract payload
let payload = buffer.slice(offset, offset + payloadLength);
buffer = buffer.slice(offset + payloadLength);
if (opcode === 1) { // Text frame
messageCount++;
const timestamp = new Date().toLocaleTimeString();
try {
const message = JSON.parse(payload);
console.log(`\n[${timestamp}] 📨 Message ${messageCount}:`);
console.log(` Type: ${message.type || 'unknown'}`);
console.log(` Job ID: ${message.job_id || 'unknown'}`);
if (message.data) {
if (message.data.progress !== undefined) {
console.log(` Progress: ${message.data.progress}%`);
}
if (message.data.current_step) {
console.log(` Step: ${message.data.current_step}`);
}
}
if (message.type === 'completed') {
console.log('🎉 Training completed!');
} else if (message.type === 'failed') {
console.log('❌ Training failed!');
}
} catch (e) {
console.log(`[${timestamp}] Raw message: ${payload}`);
}
} else if (opcode === 8) { // Close frame
console.log('🔌 WebSocket closed by server');
socket.end();
return;
}
}
});
socket.on('end', () => {
console.log(`\n📊 WebSocket test completed`);
console.log(`📨 Total messages received: ${messageCount}`);
if (messageCount > 0) {
console.log('✅ WebSocket communication successful');
} else {
console.log('⚠️ No messages received during test period');
}
process.exit(0);
});
socket.on('error', (error) => {
console.log('❌ WebSocket error:', error.message);
process.exit(1);
});
// Send periodic pings to keep connection alive
const pingInterval = setInterval(() => {
if (socket.writable) {
// Send ping frame (opcode 9)
const pingFrame = Buffer.from([0x89, 0x00]);
socket.write(pingFrame);
}
}, 10000);
// Close after duration
setTimeout(() => {
clearInterval(pingInterval);
console.log(`\n⏰ Test duration (${duration/1000}s) completed`);
console.log(`📨 Total messages received: ${messageCount}`);
if (messageCount > 0) {
console.log('✅ WebSocket communication successful');
} else {
console.log('⚠️ No training progress messages received');
console.log(' This is normal if training completed before WebSocket connection');
}
socket.end();
process.exit(0);
}, duration);
});
req.on('response', (res) => {
console.log(`❌ HTTP response instead of WebSocket upgrade: ${res.statusCode}`);
console.log('Response headers:', res.headers);
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
if (body) console.log('Response body:', body);
});
process.exit(1);
});
req.on('error', (error) => {
console.log('❌ Connection error:', error.message);
process.exit(1);
});
req.end();
EOF
# Run the Node.js WebSocket test
local ws_url="$WS_BASE/tenants/$tenant_id/training/jobs/$job_id/live"
echo "Starting WebSocket test..."
node "$ws_test_script" "$tenant_id" "$job_id" "$duration" "$ACCESS_TOKEN" "$ws_url"
local exit_code=$?
# Clean up
rm -f "$ws_test_script"
return $exit_code
}
# Fallback: Test WebSocket using curl (limited functionality)
test_websocket_with_curl() {
local tenant_id="$1"
local job_id="$2"
local duration="$3"
log_warning "WebSocket testing tools not available (websocat/node.js)"
echo "Falling back to HTTP polling simulation..."
# Create a simple HTTP-based progress polling simulation
local poll_endpoint="$API_BASE/api/v1/tenants/$tenant_id/training/jobs/$job_id/status"
local end_time=$(($(date +%s) + duration))
local poll_count=0
echo "Simulating real-time updates by polling: $poll_endpoint"
echo "Duration: ${duration}s"
while [ $(date +%s) -lt $end_time ]; do
poll_count=$((poll_count + 1))
echo ""
echo "[$(date '+%H:%M:%S')] Poll #$poll_count - Checking training status..."
STATUS_RESPONSE=$(curl -s -X GET "$poll_endpoint" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "X-Tenant-ID: $tenant_id")
echo "Response:"
echo "$STATUS_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$STATUS_RESPONSE"
# Check if training is complete
if echo "$STATUS_RESPONSE" | grep -q '"status".*"completed"\|"status".*"failed"'; then
log_success "Training status detected as complete/failed - stopping polling"
break
fi
sleep 5
done
log_success "HTTP polling simulation completed ($poll_count polls)"
echo "💡 For real WebSocket testing, install: npm install -g websocat"
}
# Wait for WebSocket messages and analyze them
wait_for_websocket_messages() {
local ws_log="$1"
@@ -660,8 +693,8 @@ wait_for_websocket_messages() {
# Enhanced training step with WebSocket testing
enhanced_training_step_with_completion_check() {
echo -e "${STEP_ICONS[3]} ${PURPLE}STEP 4: MODEL TRAINING WITH WEBSOCKET MONITORING${NC}"
echo "Enhanced training step with real-time progress monitoring"
echo -e "${STEP_ICONS[3]} ${PURPLE}STEP 4: MODEL TRAINING WITH SMART WEBSOCKET MONITORING${NC}"
echo "Enhanced training step with completion-aware progress monitoring"
echo ""
log_step "4.1. Initiating model training with FULL dataset"
@@ -694,42 +727,50 @@ enhanced_training_step_with_completion_check() {
echo " Job ID: $WEBSOCKET_JOB_ID"
echo " Status: $JOB_STATUS"
# Check if training completed instantly
# Determine monitoring strategy based on initial status
if [ "$JOB_STATUS" = "completed" ]; then
log_warning "Training completed instantly - no real-time progress to monitor"
echo " This can happen when:"
echo " • Models are already trained and cached"
echo " • No valid products found in sales data"
echo " • Training data is insufficient"
echo " • Models are already trained and cached"
echo ""
# Show training results
TOTAL_PRODUCTS=$(extract_json_field "$TRAINING_RESPONSE" "training_results.total_products")
SUCCESSFUL_TRAININGS=$(extract_json_field "$TRAINING_RESPONSE" "training_results.successful_trainings")
SALES_RECORDS=$(extract_json_field "$TRAINING_RESPONSE" "data_summary.sales_records")
echo ""
echo "📊 Training Summary:"
echo " Sales records: $SALES_RECORDS"
echo " Products found: $TOTAL_PRODUCTS"
echo " Successful trainings: $SUCCESSFUL_TRAININGS"
if [ "$TOTAL_PRODUCTS" = "0" ]; then
log_warning "No products found for training"
echo " Possible causes:"
echo " • CSV doesn't contain valid product names"
echo " • Product column is missing or malformed"
echo " • Insufficient sales data per product"
fi
# Still test WebSocket for demonstration
# Brief WebSocket connection test
log_step "4.2. Testing WebSocket endpoint (demonstration mode)"
echo "Even though training is complete, testing WebSocket connection..."
echo "Testing WebSocket connection for 10 seconds..."
test_websocket_with_nodejs_builtin "$TENANT_ID" "$WEBSOCKET_JOB_ID" "10"
else
# Training is in progress - monitor with WebSocket
log_step "4.2. Connecting to WebSocket for real-time progress monitoring"
test_websocket_with_nodejs_builtin "$TENANT_ID" "$WEBSOCKET_JOB_ID" "$WS_TEST_DURATION"
# Training is in progress - use smart monitoring
log_step "4.2. Starting smart WebSocket monitoring"
echo " Strategy: Monitor until job completion"
echo " Maximum wait time: ${WS_TEST_DURATION}s (safety timeout)"
echo " Will automatically close when training completes"
echo ""
# Use enhanced monitoring with longer timeout for real training
local SMART_DURATION=$WS_TEST_DURATION
# Estimate duration based on data size (optional enhancement)
if [ -n "$SALES_RECORDS" ] && [ "$SALES_RECORDS" -gt 1000 ]; then
# For large datasets, extend timeout
SMART_DURATION=$((WS_TEST_DURATION * 2))
echo " 📊 Large dataset detected ($SALES_RECORDS records)"
echo " 🕐 Extended timeout to ${SMART_DURATION}s for thorough training"
fi
test_websocket_with_nodejs_builtin "$TENANT_ID" "$WEBSOCKET_JOB_ID" "$SMART_DURATION"
fi
else
@@ -786,41 +827,6 @@ services_check() {
done
}
check_websocket_prerequisites() {
echo -e "${PURPLE}🔍 Checking WebSocket testing prerequisites...${NC}"
# Check for websocat
if command -v websocat >/dev/null 2>&1; then
log_success "websocat found - will use for WebSocket testing"
return 0
fi
# Check for Node.js
if command -v node >/dev/null 2>&1; then
local node_version=$(node --version 2>/dev/null || echo "unknown")
log_success "Node.js found ($node_version) - will use for WebSocket testing"
# Check if ws module is available (try to require it)
if node -e "require('ws')" 2>/dev/null; then
log_success "Node.js 'ws' module available"
else
log_warning "Node.js 'ws' module not found"
echo " Install with: npm install -g ws"
echo " Will attempt to use built-in functionality..."
fi
return 0
fi
log_warning "Neither websocat nor Node.js found"
echo " WebSocket testing will use HTTP polling fallback"
echo " For better testing, install one of:"
echo " • websocat: cargo install websocat"
echo " • Node.js: https://nodejs.org/"
return 1
}
services_check
echo ""
@@ -1167,7 +1173,7 @@ echo ""
# STEP 4: MODEL TRAINING (ONBOARDING PAGE STEP 4)
# =================================================================
check_websocket_prerequisites
test_websocket_connection
enhanced_training_step_with_completion_check