Mercurial > gemma
comparison pkg/controllers/stretches.go @ 3452:ae6c09fbc590
fairway depth/availability: bring unified CVS to streches and sections, too.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Fri, 24 May 2019 13:09:08 +0200 |
parents | d7ddb21f7017 |
children | 7265adcc5baa |
comparison
equal
deleted
inserted
replaced
3451:8634d2bd0aac | 3452:ae6c09fbc590 |
---|---|
58 measurements availMeasurements | 58 measurements availMeasurements |
59 ldc []float64 | 59 ldc []float64 |
60 breaks []float64 | 60 breaks []float64 |
61 access func(*availMeasurement) float64 | 61 access func(*availMeasurement) float64 |
62 } | 62 } |
63 | |
64 availResult struct { | |
65 label string | |
66 from, to time.Time | |
67 ldc, breaks []time.Duration | |
68 } | |
69 | |
70 availCalculation struct { | |
71 result *availResult | |
72 ldc, breaks []time.Duration | |
73 } | |
74 | |
75 availJob struct { | |
76 result *availResult | |
77 bn *fullStretchBottleneck | |
78 } | |
63 ) | 79 ) |
80 | |
81 func (r *availResult) add(c availCalculation) { | |
82 for i, v := range c.ldc { | |
83 r.ldc[i] += v | |
84 } | |
85 for i, v := range c.breaks { | |
86 r.breaks[i] += v | |
87 } | |
88 } | |
64 | 89 |
65 func (bns stretchBottlenecks) contains(limiting string) bool { | 90 func (bns stretchBottlenecks) contains(limiting string) bool { |
66 for i := range bns { | 91 for i := range bns { |
67 if bns[i].limiting == limiting { | 92 if bns[i].limiting == limiting { |
68 return true | 93 return true |
157 func stretchAvailableFairwayDepth(rw http.ResponseWriter, req *http.Request) { | 182 func stretchAvailableFairwayDepth(rw http.ResponseWriter, req *http.Request) { |
158 | 183 |
159 vars := mux.Vars(req) | 184 vars := mux.Vars(req) |
160 stretch := vars["kind"] == "stretch" | 185 stretch := vars["kind"] == "stretch" |
161 name := vars["name"] | 186 name := vars["name"] |
162 | |
163 mode := intervalMode(req.FormValue("mode")) | 187 mode := intervalMode(req.FormValue("mode")) |
164 | 188 |
165 depthbreaks, widthbreaks := afdRefs, afdRefs | 189 depthbreaks, widthbreaks := afdRefs, afdRefs |
166 | 190 |
167 from, ok := parseFormTime(rw, req, "from", time.Now().AddDate(-1, 0, 0)) | 191 from, ok := parseFormTime(rw, req, "from", time.Now().AddDate(-1, 0, 0)) |
248 http.StatusInternalServerError, | 272 http.StatusInternalServerError, |
249 ) | 273 ) |
250 return | 274 return |
251 } | 275 } |
252 | 276 |
253 type ( | |
254 result struct { | |
255 label string | |
256 from, to time.Time | |
257 ldc, breaks []time.Duration | |
258 } | |
259 | |
260 calculation struct { | |
261 result *result | |
262 ldc, breaks []time.Duration | |
263 } | |
264 | |
265 job struct { | |
266 result *result | |
267 bn *fullStretchBottleneck | |
268 } | |
269 ) | |
270 | |
271 n := runtime.NumCPU() / 2 | 277 n := runtime.NumCPU() / 2 |
272 if n == 0 { | 278 if n == 0 { |
273 n = 1 | 279 n = 1 |
274 } | 280 } |
275 | 281 |
276 jobCh := make(chan job) | 282 jobCh := make(chan availJob) |
277 calcCh := make(chan calculation, n) | 283 calcCh := make(chan availCalculation, n) |
278 done := make(chan struct{}) | 284 done := make(chan struct{}) |
279 | 285 |
280 var wg sync.WaitGroup | 286 var wg sync.WaitGroup |
281 | 287 |
282 go func() { | 288 go func() { |
283 defer close(done) | 289 defer close(done) |
284 for calc := range calcCh { | 290 for calc := range calcCh { |
285 ldc := calc.result.ldc | 291 calc.result.add(calc) |
286 for i, v := range calc.ldc { | |
287 ldc[i] += v | |
288 } | |
289 breaks := calc.result.breaks | |
290 for i, v := range calc.breaks { | |
291 breaks[i] += v | |
292 } | |
293 } | 292 } |
294 }() | 293 }() |
295 | 294 |
296 for i := 0; i < n; i++ { | 295 for i := 0; i < n; i++ { |
297 wg.Add(1) | 296 wg.Add(1) |
308 breaks := bn.measurements.classify( | 307 breaks := bn.measurements.classify( |
309 res.from, res.to, | 308 res.from, res.to, |
310 bn.breaks, | 309 bn.breaks, |
311 bn.access, | 310 bn.access, |
312 ) | 311 ) |
313 calcCh <- calculation{ | 312 calcCh <- availCalculation{ |
314 result: res, | 313 result: res, |
315 breaks: breaks, | 314 breaks: breaks, |
316 ldc: ldc, | 315 ldc: ldc, |
317 } | 316 } |
318 } | 317 } |
319 }() | 318 }() |
320 } | 319 } |
321 | 320 |
322 var results []*result | 321 var results []*availResult |
323 | 322 |
324 interval := intervals[mode](from, to) | 323 interval := intervals[mode](from, to) |
325 | 324 |
326 var breaks []float64 | 325 var breaks []float64 |
327 | 326 |
331 breaks = widthbreaks | 330 breaks = widthbreaks |
332 } | 331 } |
333 | 332 |
334 for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() { | 333 for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() { |
335 | 334 |
336 res := &result{ | 335 res := &availResult{ |
337 label: label, | 336 label: label, |
338 from: pfrom, | 337 from: pfrom, |
339 to: pto, | 338 to: pto, |
340 ldc: make([]time.Duration, 2), | 339 ldc: make([]time.Duration, 2), |
341 breaks: make([]time.Duration, len(breaks)+1), | 340 breaks: make([]time.Duration, len(breaks)+1), |
342 } | 341 } |
343 results = append(results, res) | 342 results = append(results, res) |
344 | 343 |
345 for _, bn := range loaded { | 344 for _, bn := range loaded { |
346 jobCh <- job{bn: bn, result: res} | 345 jobCh <- availJob{bn: bn, result: res} |
347 } | 346 } |
348 } | 347 } |
348 | |
349 close(jobCh) | 349 close(jobCh) |
350 wg.Wait() | 350 wg.Wait() |
351 close(calcCh) | 351 close(calcCh) |
352 <-done | 352 <-done |
353 | 353 |
354 rw.Header().Add("Content-Type", "text/csv") | 354 rw.Header().Add("Content-Type", "text/csv") |
355 | 355 |
356 out := csv.NewWriter(rw) | 356 out := csv.NewWriter(rw) |
357 | 357 |
358 // label, classes, lnwl | 358 // label, lnwl, classes |
359 record := make([]string, 1+1+len(breaks)+1) | 359 record := make([]string, 1+2+len(breaks)+1) |
360 record[0] = "#label" | 360 record[0] = "# time" |
361 record[1] = "# >= LDC [h]" | 361 record[1] = "# < LDC [h]" |
362 record[2] = "# >= LDC [h]" | |
362 for i, v := range breaks { | 363 for i, v := range breaks { |
363 if useDepth && useWidth { | 364 if useDepth && useWidth { |
364 if i == 0 { | 365 if i == 0 { |
365 record[2] = "# < break_1 [h]" | 366 record[3] = "# < break_1 [h]" |
366 } | 367 } |
367 record[i+3] = fmt.Sprintf("# >= break_%d", i+1) | 368 record[i+4] = fmt.Sprintf("# >= break_%d", i+1) |
368 } else { | 369 } else { |
369 if i == 0 { | 370 if i == 0 { |
370 record[2] = fmt.Sprintf("# < %.2f [h]", v) | 371 record[3] = fmt.Sprintf("# < %.2f [h]", v) |
371 } | 372 } |
372 record[i+3] = fmt.Sprintf("# >= %.2f [h]", v) | 373 record[i+4] = fmt.Sprintf("# >= %.2f [h]", v) |
373 } | 374 } |
374 } | 375 } |
375 | 376 |
376 if err := out.Write(record); err != nil { | 377 if err := out.Write(record); err != nil { |
377 // Too late for HTTP status message. | 378 // Too late for HTTP status message. |
382 // Normalize to look like as we have only one bottleneck. | 383 // Normalize to look like as we have only one bottleneck. |
383 scale := 1 / float64(len(loaded)) | 384 scale := 1 / float64(len(loaded)) |
384 | 385 |
385 for _, r := range results { | 386 for _, r := range results { |
386 record[0] = r.label | 387 record[0] = r.label |
387 record[1] = fmt.Sprintf("%.3f", r.ldc[1].Hours()*scale) | 388 for i, v := range r.ldc { |
389 record[1+i] = fmt.Sprintf("%.3f", v.Hours()*scale) | |
390 } | |
388 | 391 |
389 for i, d := range r.breaks { | 392 for i, d := range r.breaks { |
390 record[2+i] = fmt.Sprintf("%.3f", d.Hours()*scale) | 393 record[3+i] = fmt.Sprintf("%.3f", d.Hours()*scale) |
391 } | 394 } |
392 | 395 |
393 if err := out.Write(record); err != nil { | 396 if err := out.Write(record); err != nil { |
394 // Too late for HTTP status message. | 397 // Too late for HTTP status message. |
395 log.Printf("error: %v\n", err) | 398 log.Printf("error: %v\n", err) |
418 func stretchAvailabilty(rw http.ResponseWriter, req *http.Request) { | 421 func stretchAvailabilty(rw http.ResponseWriter, req *http.Request) { |
419 | 422 |
420 vars := mux.Vars(req) | 423 vars := mux.Vars(req) |
421 stretch := vars["kind"] == "stretch" | 424 stretch := vars["kind"] == "stretch" |
422 name := vars["name"] | 425 name := vars["name"] |
426 mode := intervalMode(req.FormValue("mode")) | |
423 | 427 |
424 if name == "" { | 428 if name == "" { |
425 http.Error( | 429 http.Error( |
426 rw, | 430 rw, |
427 fmt.Sprintf("Missing %s name", vars["kind"]), | 431 fmt.Sprintf("Missing %s name", vars["kind"]), |
520 http.StatusInternalServerError, | 524 http.StatusInternalServerError, |
521 ) | 525 ) |
522 return | 526 return |
523 } | 527 } |
524 | 528 |
525 type calculation struct { | |
526 ldc, afd []time.Duration | |
527 } | |
528 | |
529 n := runtime.NumCPU() / 2 | 529 n := runtime.NumCPU() / 2 |
530 if n == 0 { | 530 if n == 0 { |
531 n = 1 | 531 n = 1 |
532 } | 532 } |
533 | 533 |
534 var breaks []float64 | 534 jobCh := make(chan availJob) |
535 if useDepth { | 535 calcCh := make(chan availCalculation, n) |
536 breaks = depthbreaks | |
537 } else { | |
538 breaks = widthbreaks | |
539 } | |
540 | |
541 jobCh := make(chan *fullStretchBottleneck) | |
542 calcCh := make(chan calculation, n) | |
543 done := make(chan struct{}) | 536 done := make(chan struct{}) |
544 | |
545 ldc := make([]time.Duration, 2) | |
546 afd := make([]time.Duration, len(breaks)+1) | |
547 | 537 |
548 var wg sync.WaitGroup | 538 var wg sync.WaitGroup |
549 | 539 |
550 go func() { | 540 go func() { |
551 defer close(done) | 541 defer close(done) |
552 for calc := range calcCh { | 542 for calc := range calcCh { |
553 for i, v := range calc.ldc { | 543 calc.result.add(calc) |
554 ldc[i] += v | |
555 } | |
556 for i, v := range calc.afd { | |
557 afd[i] += v | |
558 } | |
559 } | 544 } |
560 }() | 545 }() |
561 | 546 |
562 for i := 0; i < n; i++ { | 547 for i := 0; i < n; i++ { |
563 wg.Add(1) | 548 wg.Add(1) |
564 go func() { | 549 go func() { |
565 defer wg.Done() | 550 defer wg.Done() |
566 for bn := range jobCh { | 551 for job := range jobCh { |
552 bn := job.bn | |
553 res := job.result | |
567 ldc := bn.measurements.classify( | 554 ldc := bn.measurements.classify( |
568 from, to, | 555 from, to, |
569 bn.ldc, | 556 bn.ldc, |
570 (*availMeasurement).getValue, | 557 (*availMeasurement).getValue, |
571 ) | 558 ) |
572 | 559 |
573 afd := bn.measurements.classify( | 560 breaks := bn.measurements.classify( |
574 from, to, | 561 from, to, |
575 bn.breaks, | 562 bn.breaks, |
576 bn.access, | 563 bn.access, |
577 ) | 564 ) |
578 calcCh <- calculation{ldc: ldc, afd: afd} | 565 calcCh <- availCalculation{ |
566 result: res, | |
567 breaks: breaks, | |
568 ldc: ldc, | |
569 } | |
579 } | 570 } |
580 }() | 571 }() |
581 } | 572 } |
582 | 573 |
583 for _, bn := range loaded { | 574 var results []*availResult |
584 jobCh <- bn | 575 |
576 interval := intervals[mode](from, to) | |
577 | |
578 var breaks []float64 | |
579 | |
580 if useDepth { | |
581 breaks = depthbreaks | |
582 } else { | |
583 breaks = widthbreaks | |
584 } | |
585 | |
586 for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() { | |
587 | |
588 res := &availResult{ | |
589 label: label, | |
590 from: pfrom, | |
591 to: pto, | |
592 ldc: make([]time.Duration, 2), | |
593 breaks: make([]time.Duration, len(breaks)+1), | |
594 } | |
595 results = append(results, res) | |
596 | |
597 for _, bn := range loaded { | |
598 jobCh <- availJob{bn: bn, result: res} | |
599 } | |
585 } | 600 } |
586 | 601 |
587 close(jobCh) | 602 close(jobCh) |
588 wg.Wait() | 603 wg.Wait() |
589 close(calcCh) | 604 close(calcCh) |
590 <-done | 605 <-done |
591 | 606 |
592 duration := to.Sub(from) * time.Duration(len(loaded)) | 607 rw.Header().Add("Content-Type", "text/csv") |
593 | 608 |
594 lnwlPercents := durationsToPercentage(duration, ldc) | 609 out := csv.NewWriter(rw) |
595 afdPercents := durationsToPercentage(duration, afd) | 610 |
596 | 611 // label, lnwl, classes |
597 _ = lnwlPercents | 612 record := make([]string, 1+2+len(breaks)+1) |
598 _ = afdPercents | 613 record[0] = "# time" |
599 | 614 record[1] = "# < LDC [%%]" |
600 /* // TODO: Rewrite this | 615 record[2] = "# >= LDC [%%]" |
601 | 616 for i, v := range breaks { |
602 type ldcOutput struct { | 617 if useDepth && useWidth { |
603 Value float64 `json:"value"` | 618 if i == 0 { |
604 Below float64 `json:"below"` | 619 record[3] = "# < break_1 [%%]" |
605 Above float64 `json:"above"` | 620 } |
606 } | 621 record[i+4] = fmt.Sprintf("# >= break_%d [%%]", i+1) |
607 | 622 } else { |
608 type intervalOutput struct { | 623 if i == 0 { |
609 From *float64 `json:"from,omitempty"` | 624 record[3] = fmt.Sprintf("# < %.3f [%%]", v) |
610 To *float64 `json:"to,omitempty"` | 625 } |
611 Percent float64 `json:"percent"` | 626 record[i+4] = fmt.Sprintf("# >= %.3f [%%]", v) |
612 } | 627 } |
613 | 628 } |
614 type output struct { | 629 |
615 LDC []intervalOutput `json:"ldc"` | 630 if err := out.Write(record); err != nil { |
616 AFD []intervalOutput `json:"afd"` | 631 // Too late for HTTP status message. |
617 } | 632 log.Printf("error: %v\n", err) |
618 | 633 return |
619 out := output{ | 634 } |
620 LDC: []intervalOutput{ | 635 |
621 {To: new(float64), Percent: lnwlPercents[0]}, | 636 for _, r := range results { |
622 {From: new(float64), Percent: lnwlPercents[1]}, | 637 duration := r.to.Sub(r.from) * time.Duration(len(loaded)) |
623 }, | 638 |
624 } | 639 ldcPercents := durationsToPercentage(duration, r.ldc) |
625 | 640 breaksPercents := durationsToPercentage(duration, r.breaks) |
626 // XXX: In mixed mode the breaks are not correct! | 641 |
627 | 642 record[0] = r.label |
628 for i, percent := range afdPercents { | 643 |
629 var from, to *float64 | 644 for i, v := range ldcPercents { |
630 switch { | 645 record[2+i] = fmt.Sprintf("%.3f", v) |
631 case i == 0: | 646 } |
632 to = &breaks[i] | 647 |
633 case i == len(afdPercents)-1: | 648 for i, v := range breaksPercents { |
634 from = &breaks[len(breaks)-1] | 649 record[3+i] = fmt.Sprintf("%.3f", v) |
635 default: | 650 } |
636 from = &breaks[i-1] | 651 |
637 to = &breaks[i] | 652 if err := out.Write(record); err != nil { |
638 } | 653 // Too late for HTTP status message. |
639 out.AFD = append(out.AFD, intervalOutput{ | 654 log.Printf("error: %v\n", err) |
640 From: from, | 655 return |
641 To: to, | 656 } |
642 Percent: percent, | 657 } |
643 }) | 658 |
644 } | 659 out.Flush() |
645 | 660 if err := out.Error(); err != nil { |
646 jr = JSONResult{Result: &out} | 661 // Too late for HTTP status message. |
647 return | 662 log.Printf("error: %v\n", err) |
648 */ | 663 } |
649 } | 664 } |