changeset 3430:6994602d2935

fairway availibilty: Implemented for streches and sections.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Thu, 23 May 2019 17:28:14 +0200
parents 7bbab09cdf71
children adf06af7a79c
files pkg/controllers/bottlenecks.go pkg/controllers/stretches.go
diffstat 2 files changed, 232 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/pkg/controllers/bottlenecks.go	Thu May 23 16:49:28 2019 +0200
+++ b/pkg/controllers/bottlenecks.go	Thu May 23 17:28:14 2019 +0200
@@ -247,9 +247,9 @@
 	return b
 }
 
-func durationsToPercentage(from, to time.Time, classes []time.Duration) []float64 {
+func durationsToPercentage(duration time.Duration, classes []time.Duration) []float64 {
 	percents := make([]float64, len(classes))
-	total := 100 / to.Sub(from).Seconds()
+	total := 100 / duration.Seconds()
 	for i, v := range classes {
 		percents[i] = v.Seconds() * total
 	}
@@ -324,7 +324,6 @@
 	conn *sql.Conn,
 	bottleneck string,
 ) ([]float64, error) {
-
 	var value float64
 	err := conn.QueryRowContext(ctx, selectGaugeLDCSQL, bottleneck).Scan(&value)
 	switch {
@@ -333,7 +332,6 @@
 	case err != nil:
 		return nil, err
 	}
-	log.Printf("info: LDC = %.2f\n", value)
 	return []float64{value}, nil
 }
 
@@ -367,7 +365,12 @@
 	req *http.Request,
 	conn *sql.Conn,
 ) (jr JSONResult, err error) {
+
 	bn := mux.Vars(req)["objnam"]
+	var from, to time.Time
+	var los int
+	var breaks []float64
+	var ldcRefs []float64
 
 	if bn == "" {
 		err = JSONError{
@@ -377,8 +380,6 @@
 		return
 	}
 
-	var from, to time.Time
-
 	if f := req.FormValue("from"); f != "" {
 		if from, err = parseTime(f, "from"); err != nil {
 			return
@@ -399,9 +400,6 @@
 		to, from = from, to
 	}
 
-	log.Printf("info: time interval: (%v - %v)\n", from, to)
-
-	var los int
 	if l := req.FormValue("los"); l != "" {
 		if los, err = parseInt(l, "los"); err != nil {
 			return
@@ -410,7 +408,6 @@
 		los = 1
 	}
 
-	var breaks []float64
 	if b := req.FormValue("breaks"); b != "" {
 		breaks = breaksToReferenceValue(b)
 	} else {
@@ -419,7 +416,6 @@
 
 	ctx := req.Context()
 
-	var ldcRefs []float64
 	if ldcRefs, err = loadLDCReferenceValue(ctx, conn, bn); err != nil {
 		return
 	}
@@ -432,6 +428,8 @@
 		return
 	}
 
+	log.Printf("info: time interval: (%v - %v)\n", from, to)
+
 	var ms availMeasurements
 	if ms, err = loadDepthValues(ctx, conn, bn, los, from, to); err != nil {
 		return
@@ -457,8 +455,9 @@
 		(*availMeasurement).getDepth,
 	)
 
-	lnwlPercents := durationsToPercentage(from, to, lnwl)
-	afdPercents := durationsToPercentage(from, to, afd)
+	duration := to.Sub(from)
+	lnwlPercents := durationsToPercentage(duration, lnwl)
+	afdPercents := durationsToPercentage(duration, afd)
 
 	type ldcOutput struct {
 		Value float64 `json:"value"`
--- a/pkg/controllers/stretches.go	Thu May 23 16:49:28 2019 +0200
+++ b/pkg/controllers/stretches.go	Thu May 23 17:28:14 2019 +0200
@@ -260,6 +260,7 @@
 			depthbreaks, widthbreaks,
 		)
 		if err != nil {
+			log.Printf("error: %v\n", err)
 			errors = append(errors, err)
 			continue
 		}
@@ -445,6 +446,224 @@
 	req *http.Request,
 	conn *sql.Conn,
 ) (jr JSONResult, err error) {
-	// TODO: Implement me!
+
+	vars := mux.Vars(req)
+	stretch := vars["kind"] == "stretch"
+	name := vars["name"]
+
+	var from, to time.Time
+	var los int
+
+	depthbreaks, widthbreaks := afdRefs, afdRefs
+
+	if f := req.FormValue("from"); f != "" {
+		if from, err = parseTime(f, "from"); err != nil {
+			return
+		}
+	} else {
+		from = time.Now().AddDate(-1, 0, 0).UTC()
+	}
+
+	if t := req.FormValue("to"); t != "" {
+		if to, err = parseTime(t, "to"); err != nil {
+			return
+		}
+	} else {
+		to = from.AddDate(1, 0, 0).UTC()
+	}
+
+	if to.Before(from) {
+		to, from = from, to
+	}
+
+	if l := req.FormValue("los"); l != "" {
+		if los, err = parseInt(l, "los"); err != nil {
+			return
+		}
+	} else {
+		los = 1
+	}
+
+	if b := req.FormValue("depthbreaks"); b != "" {
+		depthbreaks = breaksToReferenceValue(b)
+	}
+
+	if b := req.FormValue("widthbreaks"); b != "" {
+		widthbreaks = breaksToReferenceValue(b)
+	}
+
+	ctx := req.Context()
+
+	var bns stretchBottlenecks
+	if bns, err = loadStretchBottlenecks(ctx, conn, stretch, name); err != nil {
+		return
+	}
+
+	if len(bns) == 0 {
+		err = JSONError{
+			Code:    http.StatusNotFound,
+			Message: "No bottlenecks found.",
+		}
+		return
+	}
+
+	useDepth, useWidth := bns.contains("depth"), bns.contains("width")
+
+	if useDepth && useWidth && len(widthbreaks) != len(depthbreaks) {
+		err = JSONError{
+			Code: http.StatusBadRequest,
+			Message: fmt.Sprintf("class breaks lengths differ: %d != %d",
+				len(widthbreaks), len(depthbreaks)),
+		}
+		return
+	}
+
+	log.Printf("info: time interval: (%v - %v)\n", from, to)
+
+	var loaded []*fullStretchBottleneck
+	var errors []error
+
+	for i := range bns {
+		l, err := loadFullStretchBottleneck(
+			ctx,
+			conn,
+			&bns[i],
+			los,
+			from, to,
+			depthbreaks, widthbreaks,
+		)
+		if err != nil {
+			log.Printf("error: %v\n", err)
+			errors = append(errors, err)
+			continue
+		}
+		loaded = append(loaded, l)
+	}
+
+	if len(loaded) == 0 {
+		err = JSONError{
+			Code:    http.StatusInternalServerError,
+			Message: fmt.Sprintf("No bottleneck loaded: %v", joinErrors(errors)),
+		}
+		return
+	}
+
+	type calculation struct {
+		ldc, afd []time.Duration
+	}
+
+	n := runtime.NumCPU() / 2
+	if n == 0 {
+		n = 1
+	}
+
+	var breaks []float64
+	if useDepth {
+		breaks = depthbreaks
+	} else {
+		breaks = widthbreaks
+	}
+
+	jobCh := make(chan *fullStretchBottleneck)
+	calcCh := make(chan calculation, n)
+	done := make(chan struct{})
+
+	ldc := make([]time.Duration, 2)
+	afd := make([]time.Duration, len(breaks)+1)
+
+	var wg sync.WaitGroup
+
+	go func() {
+		defer close(done)
+		for calc := range calcCh {
+			for i, v := range calc.ldc {
+				ldc[i] += v
+			}
+			for i, v := range calc.afd {
+				afd[i] += v
+			}
+		}
+	}()
+
+	for i := 0; i < n; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			for bn := range jobCh {
+				ldc := bn.measurements.classify(
+					from, to,
+					bn.ldc,
+					(*availMeasurement).getValue,
+				)
+
+				afd := bn.measurements.classify(
+					from, to,
+					bn.breaks,
+					bn.access,
+				)
+				calcCh <- calculation{ldc: ldc, afd: afd}
+			}
+		}()
+	}
+
+	for _, bn := range loaded {
+		jobCh <- bn
+	}
+
+	close(jobCh)
+	wg.Wait()
+	close(calcCh)
+	<-done
+
+	duration := to.Sub(from) * time.Duration(len(loaded))
+
+	lnwlPercents := durationsToPercentage(duration, ldc)
+	afdPercents := durationsToPercentage(duration, afd)
+
+	type ldcOutput struct {
+		Value float64 `json:"value"`
+		Below float64 `json:"below"`
+		Above float64 `json:"above"`
+	}
+
+	type intervalOutput struct {
+		From    *float64 `json:"from,omitempty"`
+		To      *float64 `json:"to,omitempty"`
+		Percent float64  `json:"percent"`
+	}
+
+	type output struct {
+		LDC []intervalOutput `json:"ldc"`
+		AFD []intervalOutput `json:"afd"`
+	}
+
+	out := output{
+		LDC: []intervalOutput{
+			{To: new(float64), Percent: lnwlPercents[0]},
+			{From: new(float64), Percent: lnwlPercents[1]},
+		},
+	}
+
+	// XXX: In mixed mode the breaks are not correct!
+
+	for i, percent := range afdPercents {
+		var from, to *float64
+		switch {
+		case i == 0:
+			to = &breaks[i]
+		case i == len(afdPercents)-1:
+			from = &breaks[len(breaks)-1]
+		default:
+			from = &breaks[i-1]
+			to = &breaks[i]
+		}
+		out.AFD = append(out.AFD, intervalOutput{
+			From:    from,
+			To:      to,
+			Percent: percent,
+		})
+	}
+
+	jr = JSONResult{Result: &out}
 	return
 }