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 }