|
5 | 5 | "context" |
6 | 6 | "encoding/json" |
7 | 7 | "fmt" |
| 8 | + "math" |
8 | 9 | "os" |
9 | 10 | "sort" |
10 | 11 | ) |
@@ -60,12 +61,17 @@ func LoadServers(path string) ([]PingableServer, error) { |
60 | 61 |
|
61 | 62 | func QueryDataPoints(ip string, duration string) ([]ServerDataPoint, string, error) { |
62 | 63 | queryApi := database.InfluxClient.QueryAPI(os.Getenv("INFLUXDB_ORG")) |
| 64 | + renderMaxDataPoints, minDataPoints := QueryPointBudget(duration) |
| 65 | + queryMaxDataPoints := renderMaxDataPoints |
| 66 | + if durationLongerThanADay(duration) { |
| 67 | + queryMaxDataPoints = renderMaxDataPoints * 4 |
| 68 | + } |
63 | 69 |
|
64 | 70 | query, _, step, err := BuildInfluxQueryFromParams(QueryParams{ |
65 | 71 | Start: duration, |
66 | 72 | ServerFilter: ip, |
67 | | - MaxDataPoints: 500, |
68 | | - MinDataPoints: 10, |
| 73 | + MaxDataPoints: queryMaxDataPoints, |
| 74 | + MinDataPoints: minDataPoints, |
69 | 75 | UseAdaptive: false, |
70 | 76 | }) |
71 | 77 |
|
@@ -115,7 +121,19 @@ func QueryDataPoints(ip string, duration string) ([]ServerDataPoint, string, err |
115 | 121 | return nil, "0m", err |
116 | 122 | } |
117 | 123 |
|
118 | | - return mergeServerDataPoints(dataPoints, extremes), step, nil |
| 124 | + points := mergeServerDataPoints(dataPoints, extremes) |
| 125 | + points = downsampleServerDataPoints(points, renderMaxDataPoints) |
| 126 | + |
| 127 | + return points, step, nil |
| 128 | +} |
| 129 | + |
| 130 | +func durationLongerThanADay(duration string) bool { |
| 131 | + rangeInMinutes, err := timeToMinutes(duration) |
| 132 | + if err != nil { |
| 133 | + return false |
| 134 | + } |
| 135 | + |
| 136 | + return math.Abs(rangeInMinutes) >= 1440 |
119 | 137 | } |
120 | 138 |
|
121 | 139 | func queryExtremeDataPoints(ip string, duration string) ([]ServerDataPoint, error) { |
@@ -240,6 +258,99 @@ func mergeServerDataPoints(base []ServerDataPoint, extras []ServerDataPoint) []S |
240 | 258 | return merged |
241 | 259 | } |
242 | 260 |
|
| 261 | +func downsampleServerDataPoints(points []ServerDataPoint, maxPoints int) []ServerDataPoint { |
| 262 | + if maxPoints <= 0 || len(points) <= maxPoints { |
| 263 | + return points |
| 264 | + } |
| 265 | + |
| 266 | + sort.Slice(points, func(i, j int) bool { |
| 267 | + if points[i].Timestamp != points[j].Timestamp { |
| 268 | + return points[i].Timestamp < points[j].Timestamp |
| 269 | + } |
| 270 | + if points[i].PlayerCount != points[j].PlayerCount { |
| 271 | + return points[i].PlayerCount < points[j].PlayerCount |
| 272 | + } |
| 273 | + if points[i].Ip != points[j].Ip { |
| 274 | + return points[i].Ip < points[j].Ip |
| 275 | + } |
| 276 | + return points[i].Name < points[j].Name |
| 277 | + }) |
| 278 | + |
| 279 | + bucketCount := maxPoints / 4 |
| 280 | + if bucketCount < 1 { |
| 281 | + bucketCount = 1 |
| 282 | + } |
| 283 | + |
| 284 | + target := make([]ServerDataPoint, 0, maxPoints) |
| 285 | + seen := make(map[string]struct{}, maxPoints) |
| 286 | + bucketSize := float64(len(points)) / float64(bucketCount) |
| 287 | + |
| 288 | + appendPoint := func(point ServerDataPoint) { |
| 289 | + key := fmt.Sprintf("%d|%d|%s|%s", point.Timestamp, point.PlayerCount, point.Ip, point.Name) |
| 290 | + if _, ok := seen[key]; ok { |
| 291 | + return |
| 292 | + } |
| 293 | + seen[key] = struct{}{} |
| 294 | + target = append(target, point) |
| 295 | + } |
| 296 | + |
| 297 | + for bucket := 0; bucket < bucketCount; bucket++ { |
| 298 | + start := int(math.Floor(float64(bucket) * bucketSize)) |
| 299 | + end := int(math.Floor(float64(bucket+1) * bucketSize)) |
| 300 | + if bucket == bucketCount-1 { |
| 301 | + end = len(points) |
| 302 | + } |
| 303 | + if start < 0 { |
| 304 | + start = 0 |
| 305 | + } |
| 306 | + if end > len(points) { |
| 307 | + end = len(points) |
| 308 | + } |
| 309 | + if start >= end { |
| 310 | + continue |
| 311 | + } |
| 312 | + |
| 313 | + bucketPoints := points[start:end] |
| 314 | + first := bucketPoints[0] |
| 315 | + last := bucketPoints[len(bucketPoints)-1] |
| 316 | + minPoint := first |
| 317 | + maxPoint := first |
| 318 | + |
| 319 | + for _, point := range bucketPoints[1:] { |
| 320 | + if point.PlayerCount < minPoint.PlayerCount || (point.PlayerCount == minPoint.PlayerCount && point.Timestamp < minPoint.Timestamp) { |
| 321 | + minPoint = point |
| 322 | + } |
| 323 | + if point.PlayerCount > maxPoint.PlayerCount || (point.PlayerCount == maxPoint.PlayerCount && point.Timestamp < maxPoint.Timestamp) { |
| 324 | + maxPoint = point |
| 325 | + } |
| 326 | + } |
| 327 | + |
| 328 | + bucketSelection := []ServerDataPoint{first, minPoint, maxPoint, last} |
| 329 | + for _, point := range bucketSelection { |
| 330 | + appendPoint(point) |
| 331 | + } |
| 332 | + } |
| 333 | + |
| 334 | + sort.Slice(target, func(i, j int) bool { |
| 335 | + if target[i].Timestamp != target[j].Timestamp { |
| 336 | + return target[i].Timestamp < target[j].Timestamp |
| 337 | + } |
| 338 | + if target[i].PlayerCount != target[j].PlayerCount { |
| 339 | + return target[i].PlayerCount < target[j].PlayerCount |
| 340 | + } |
| 341 | + if target[i].Ip != target[j].Ip { |
| 342 | + return target[i].Ip < target[j].Ip |
| 343 | + } |
| 344 | + return target[i].Name < target[j].Name |
| 345 | + }) |
| 346 | + |
| 347 | + if len(target) > maxPoints { |
| 348 | + return target[:maxPoints] |
| 349 | + } |
| 350 | + |
| 351 | + return target |
| 352 | +} |
| 353 | + |
243 | 354 | func recordValueToInt(value interface{}) (int, bool) { |
244 | 355 | switch v := value.(type) { |
245 | 356 | case int: |
|
0 commit comments