|
299 | 299 | var weekAgo = now.AddDays(-7); |
300 | 300 | var monthAgo = now.AddDays(-30); |
301 | 301 |
|
302 | | - // Load statistics data with optimized queries |
303 | | - var allDbData = await db.Queryable<SensorData>() |
304 | | - .Where(s => s.Timestamp >= monthAgo) |
305 | | - .OrderBy(s => s.Timestamp) |
| 302 | + // Use database-level aggregation instead of loading all data into memory |
| 303 | + // Get distinct sensor names first |
| 304 | + var tempSensorNames = await db.Queryable<SensorData>() |
| 305 | + .Where(s => s.SensorType == "Temperature" && s.Timestamp >= monthAgo) |
| 306 | + .Select(s => s.SensorName) |
| 307 | + .Distinct() |
306 | 308 | .ToListAsync(); |
307 | | - |
308 | | - // Calculate averages |
309 | | - var tempSensors = allDbData.Where(d => d.SensorType == "Temperature").GroupBy(d => d.SensorName); |
310 | | - foreach (var group in tempSensors) |
311 | | - { |
312 | | - if (group.Key != null) |
313 | | - _tempAverages[group.Key] = ( |
314 | | - group.Where(d => d.Timestamp >= hourAgo).DefaultIfEmpty().Average(d => d?.Reading ?? 0), |
315 | | - group.Where(d => d.Timestamp >= dayAgo).DefaultIfEmpty().Average(d => d?.Reading ?? 0), |
316 | | - group.Where(d => d.Timestamp >= weekAgo).DefaultIfEmpty().Average(d => d?.Reading ?? 0), |
317 | | - group.DefaultIfEmpty().Average(d => d?.Reading ?? 0), |
318 | | - group.DefaultIfEmpty().Min(d => d?.Reading ?? 0), |
319 | | - group.DefaultIfEmpty().Max(d => d?.Reading ?? 0) |
320 | | - ); |
321 | | - } |
322 | 309 |
|
323 | | - var powerData = allDbData.Where(d => d.SensorName == "Pwr Consumption").ToList(); |
324 | | - if (powerData.Any()) |
| 310 | + foreach (var sensorName in tempSensorNames.Where(n => n != null)) |
325 | 311 | { |
326 | | - _powerAverages = ( |
327 | | - powerData.Where(d => d.Timestamp >= hourAgo).DefaultIfEmpty().Average(d => d?.Reading ?? 0), |
328 | | - powerData.Where(d => d.Timestamp >= dayAgo).DefaultIfEmpty().Average(d => d?.Reading ?? 0), |
329 | | - powerData.Where(d => d.Timestamp >= weekAgo).DefaultIfEmpty().Average(d => d?.Reading ?? 0), |
330 | | - powerData.DefaultIfEmpty().Average(d => d?.Reading ?? 0), |
331 | | - powerData.DefaultIfEmpty().Min(d => d?.Reading ?? 0), |
332 | | - powerData.DefaultIfEmpty().Max(d => d?.Reading ?? 0) |
333 | | - ); |
| 312 | + var stats = await CalculateSensorStats(db, sensorName!, hourAgo, dayAgo, weekAgo, monthAgo); |
| 313 | + _tempAverages[sensorName!] = stats; |
334 | 314 | } |
335 | | - |
336 | | - var fanSensors = allDbData.Where(d => d.SensorType == "Fan").GroupBy(d => d.SensorName); |
337 | | - foreach (var group in fanSensors) |
| 315 | + |
| 316 | + // Power statistics |
| 317 | + var powerStats = await CalculateSensorStats(db, "Pwr Consumption", hourAgo, dayAgo, weekAgo, monthAgo); |
| 318 | + _powerAverages = powerStats; |
| 319 | + |
| 320 | + // Fan statistics |
| 321 | + var fanSensorNames = await db.Queryable<SensorData>() |
| 322 | + .Where(s => s.SensorType == "Fan" && s.Timestamp >= monthAgo) |
| 323 | + .Select(s => s.SensorName) |
| 324 | + .Distinct() |
| 325 | + .ToListAsync(); |
| 326 | + |
| 327 | + foreach (var sensorName in fanSensorNames.Where(n => n != null)) |
338 | 328 | { |
339 | | - if (group.Key != null) |
340 | | - _fanAverages[group.Key] = ( |
341 | | - group.Where(d => d.Timestamp >= hourAgo).DefaultIfEmpty().Average(d => d?.Reading ?? 0), |
342 | | - group.Where(d => d.Timestamp >= dayAgo).DefaultIfEmpty().Average(d => d?.Reading ?? 0), |
343 | | - group.Where(d => d.Timestamp >= weekAgo).DefaultIfEmpty().Average(d => d?.Reading ?? 0), |
344 | | - group.DefaultIfEmpty().Average(d => d?.Reading ?? 0), |
345 | | - group.DefaultIfEmpty().Min(d => d?.Reading ?? 0), |
346 | | - group.DefaultIfEmpty().Max(d => d?.Reading ?? 0) |
347 | | - ); |
| 329 | + var stats = await CalculateSensorStats(db, sensorName!, hourAgo, dayAgo, weekAgo, monthAgo); |
| 330 | + _fanAverages[sensorName!] = stats; |
348 | 331 | } |
349 | 332 | } |
350 | 333 |
|
| 334 | + private async Task<(double Hour, double Day, double Week, double Month, double Min, double Max)> CalculateSensorStats( |
| 335 | + SqlSugar.SqlSugarScope db, string sensorName, DateTime hourAgo, DateTime dayAgo, DateTime weekAgo, DateTime monthAgo) |
| 336 | + { |
| 337 | + // Use database aggregation - much faster than loading all data |
| 338 | + var monthStats = await db.Queryable<SensorData>() |
| 339 | + .Where(s => s.SensorName == sensorName && s.Timestamp >= monthAgo) |
| 340 | + .Select(s => new { |
| 341 | + Avg = SqlSugar.SqlFunc.AggregateAvg(s.Reading), |
| 342 | + Min = SqlSugar.SqlFunc.AggregateMin(s.Reading), |
| 343 | + Max = SqlSugar.SqlFunc.AggregateMax(s.Reading) |
| 344 | + }) |
| 345 | + .FirstAsync(); |
| 346 | + |
| 347 | + var weekAvg = await db.Queryable<SensorData>() |
| 348 | + .Where(s => s.SensorName == sensorName && s.Timestamp >= weekAgo) |
| 349 | + .AvgAsync(s => s.Reading); |
| 350 | + |
| 351 | + var dayAvg = await db.Queryable<SensorData>() |
| 352 | + .Where(s => s.SensorName == sensorName && s.Timestamp >= dayAgo) |
| 353 | + .AvgAsync(s => s.Reading); |
| 354 | + |
| 355 | + var hourAvg = await db.Queryable<SensorData>() |
| 356 | + .Where(s => s.SensorName == sensorName && s.Timestamp >= hourAgo) |
| 357 | + .AvgAsync(s => s.Reading); |
| 358 | + |
| 359 | + return ( |
| 360 | + hourAvg, |
| 361 | + dayAvg, |
| 362 | + weekAvg, |
| 363 | + monthStats?.Avg ?? 0, |
| 364 | + monthStats?.Min ?? 0, |
| 365 | + monthStats?.Max ?? 0 |
| 366 | + ); |
| 367 | + } |
| 368 | + |
351 | 369 | private async Task LoadChartData() |
352 | 370 | { |
353 | 371 | // Check cache first |
354 | | - if (_chartDataCache.ContainsKey(_selectedTimeRange)) |
| 372 | + if (_chartDataCache.TryGetValue(_selectedTimeRange, out var cachedData)) |
355 | 373 | { |
356 | | - _chartData = _chartDataCache[_selectedTimeRange]; |
| 374 | + _chartData = cachedData; |
357 | 375 | await UpdateCharts(); |
358 | 376 | return; |
359 | 377 | } |
|
364 | 382 | var now = DateTime.Now; |
365 | 383 | var startTime = now.AddHours(-_selectedTimeRange); |
366 | 384 |
|
367 | | - // Load chart data with sampling for performance |
368 | | - var chartDataQuery = db.Queryable<SensorData>() |
| 385 | + // Get total count first to determine sampling strategy |
| 386 | + var totalCount = await db.Queryable<SensorData>() |
369 | 387 | .Where(s => s.Timestamp >= startTime) |
370 | | - .OrderBy(s => s.Timestamp); |
| 388 | + .CountAsync(); |
371 | 389 |
|
372 | | - var rawData = await chartDataQuery.ToListAsync(); |
| 390 | + List<SensorData> rawData; |
| 391 | + |
| 392 | + if (totalCount <= MaxDataPoints * 10) // If data is manageable, load directly |
| 393 | + { |
| 394 | + rawData = await db.Queryable<SensorData>() |
| 395 | + .Where(s => s.Timestamp >= startTime) |
| 396 | + .OrderBy(s => s.Timestamp) |
| 397 | + .ToListAsync(); |
| 398 | + } |
| 399 | + else |
| 400 | + { |
| 401 | + // Use database-level sampling for large datasets |
| 402 | + // Calculate sampling interval based on time range |
| 403 | + var intervalMinutes = _selectedTimeRange switch |
| 404 | + { |
| 405 | + 1 => 1, // 1 hour: every minute |
| 406 | + 6 => 2, // 6 hours: every 2 minutes |
| 407 | + 24 => 5, // 24 hours: every 5 minutes |
| 408 | + 168 => 30, // 7 days: every 30 minutes |
| 409 | + 720 => 120, // 30 days: every 2 hours |
| 410 | + _ => 5 |
| 411 | + }; |
| 412 | + |
| 413 | + // Get distinct timestamps at intervals, then fetch data for those timestamps |
| 414 | + var sampledTimestamps = await db.Queryable<SensorData>() |
| 415 | + .Where(s => s.Timestamp >= startTime) |
| 416 | + .GroupBy(s => new { |
| 417 | + Year = s.Timestamp.Year, |
| 418 | + Month = s.Timestamp.Month, |
| 419 | + Day = s.Timestamp.Day, |
| 420 | + Hour = s.Timestamp.Hour, |
| 421 | + MinuteGroup = s.Timestamp.Minute / intervalMinutes |
| 422 | + }) |
| 423 | + .Select(g => SqlSugar.SqlFunc.AggregateMin(g.Timestamp)) |
| 424 | + .ToListAsync(); |
| 425 | + |
| 426 | + if (sampledTimestamps.Any()) |
| 427 | + { |
| 428 | + rawData = await db.Queryable<SensorData>() |
| 429 | + .Where(s => sampledTimestamps.Contains(s.Timestamp)) |
| 430 | + .OrderBy(s => s.Timestamp) |
| 431 | + .ToListAsync(); |
| 432 | + } |
| 433 | + else |
| 434 | + { |
| 435 | + rawData = new List<SensorData>(); |
| 436 | + } |
| 437 | + } |
373 | 438 |
|
374 | | - // Normalize timestamps and sample data properly |
| 439 | + // Normalize timestamps and sample data if still too large |
375 | 440 | _chartData = NormalizeAndSampleData(rawData, MaxDataPoints); |
376 | 441 |
|
377 | 442 | // Cache the result |
|
0 commit comments