| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package trace |
| |
| import ( |
| "math" |
| "sort" |
| ) |
| |
| // mud is an updatable mutator utilization distribution. |
| // |
| // This is a continuous distribution of duration over mutator |
| // utilization. For example, the integral from mutator utilization a |
| // to b is the total duration during which the mutator utilization was |
| // in the range [a, b]. |
| // |
| // This distribution is *not* normalized (it is not a probability |
| // distribution). This makes it easier to work with as it's being |
| // updated. |
| // |
| // It is represented as the sum of scaled uniform distribution |
| // functions and Dirac delta functions (which are treated as |
| // degenerate uniform distributions). |
| type mud struct { |
| sorted, unsorted []edge |
| |
| // trackMass is the inverse cumulative sum to track as the |
| // distribution is updated. |
| trackMass float64 |
| // trackBucket is the bucket in which trackMass falls. If the |
| // total mass of the distribution is < trackMass, this is |
| // len(hist). |
| trackBucket int |
| // trackSum is the cumulative sum of hist[:trackBucket]. Once |
| // trackSum >= trackMass, trackBucket must be recomputed. |
| trackSum float64 |
| |
| // hist is a hierarchical histogram of distribution mass. |
| hist [mudDegree]float64 |
| } |
| |
| const ( |
| // mudDegree is the number of buckets in the MUD summary |
| // histogram. |
| mudDegree = 1024 |
| ) |
| |
| type edge struct { |
| // At x, the function increases by y. |
| x, delta float64 |
| // Additionally at x is a Dirac delta function with area dirac. |
| dirac float64 |
| } |
| |
| // add adds a uniform function over [l, r] scaled so the total weight |
| // of the uniform is area. If l==r, this adds a Dirac delta function. |
| func (d *mud) add(l, r, area float64) { |
| if area == 0 { |
| return |
| } |
| |
| if r < l { |
| l, r = r, l |
| } |
| |
| // Add the edges. |
| if l == r { |
| d.unsorted = append(d.unsorted, edge{l, 0, area}) |
| } else { |
| delta := area / (r - l) |
| d.unsorted = append(d.unsorted, edge{l, delta, 0}, edge{r, -delta, 0}) |
| } |
| |
| // Update the histogram. |
| h := &d.hist |
| lbFloat, lf := math.Modf(l * mudDegree) |
| lb := int(lbFloat) |
| if lb >= mudDegree { |
| lb, lf = mudDegree-1, 1 |
| } |
| if l == r { |
| h[lb] += area |
| } else { |
| rbFloat, rf := math.Modf(r * mudDegree) |
| rb := int(rbFloat) |
| if rb >= mudDegree { |
| rb, rf = mudDegree-1, 1 |
| } |
| if lb == rb { |
| h[lb] += area |
| } else { |
| perBucket := area / (r - l) / mudDegree |
| h[lb] += perBucket * (1 - lf) |
| h[rb] += perBucket * rf |
| for i := lb + 1; i < rb; i++ { |
| h[i] += perBucket |
| } |
| } |
| } |
| |
| // Update mass tracking. |
| if thresh := float64(d.trackBucket) / mudDegree; l < thresh { |
| if r < thresh { |
| d.trackSum += area |
| } else { |
| d.trackSum += area * (thresh - l) / (r - l) |
| } |
| if d.trackSum >= d.trackMass { |
| // The tracked mass now falls in a different |
| // bucket. Recompute the inverse cumulative sum. |
| d.setTrackMass(d.trackMass) |
| } |
| } |
| } |
| |
| // setTrackMass sets the mass to track the inverse cumulative sum for. |
| // |
| // Specifically, mass is a cumulative duration, and the mutator |
| // utilization bounds for this duration can be queried using |
| // approxInvCumulativeSum. |
| func (d *mud) setTrackMass(mass float64) { |
| d.trackMass = mass |
| |
| // Find the bucket currently containing trackMass by computing |
| // the cumulative sum. |
| sum := 0.0 |
| for i, val := range d.hist[:] { |
| newSum := sum + val |
| if newSum > mass { |
| // mass falls in bucket i. |
| d.trackBucket = i |
| d.trackSum = sum |
| return |
| } |
| sum = newSum |
| } |
| d.trackBucket = len(d.hist) |
| d.trackSum = sum |
| } |
| |
| // approxInvCumulativeSum is like invCumulativeSum, but specifically |
| // operates on the tracked mass and returns an upper and lower bound |
| // approximation of the inverse cumulative sum. |
| // |
| // The true inverse cumulative sum will be in the range [lower, upper). |
| func (d *mud) approxInvCumulativeSum() (float64, float64, bool) { |
| if d.trackBucket == len(d.hist) { |
| return math.NaN(), math.NaN(), false |
| } |
| return float64(d.trackBucket) / mudDegree, float64(d.trackBucket+1) / mudDegree, true |
| } |
| |
| // invCumulativeSum returns x such that the integral of d from -∞ to x |
| // is y. If the total weight of d is less than y, it returns the |
| // maximum of the distribution and false. |
| // |
| // Specifically, y is a cumulative duration, and invCumulativeSum |
| // returns the mutator utilization x such that at least y time has |
| // been spent with mutator utilization <= x. |
| func (d *mud) invCumulativeSum(y float64) (float64, bool) { |
| if len(d.sorted) == 0 && len(d.unsorted) == 0 { |
| return math.NaN(), false |
| } |
| |
| // Sort edges. |
| edges := d.unsorted |
| sort.Slice(edges, func(i, j int) bool { |
| return edges[i].x < edges[j].x |
| }) |
| // Merge with sorted edges. |
| d.unsorted = nil |
| if d.sorted == nil { |
| d.sorted = edges |
| } else { |
| oldSorted := d.sorted |
| newSorted := make([]edge, len(oldSorted)+len(edges)) |
| i, j := 0, 0 |
| for o := range newSorted { |
| if i >= len(oldSorted) { |
| copy(newSorted[o:], edges[j:]) |
| break |
| } else if j >= len(edges) { |
| copy(newSorted[o:], oldSorted[i:]) |
| break |
| } else if oldSorted[i].x < edges[j].x { |
| newSorted[o] = oldSorted[i] |
| i++ |
| } else { |
| newSorted[o] = edges[j] |
| j++ |
| } |
| } |
| d.sorted = newSorted |
| } |
| |
| // Traverse edges in order computing a cumulative sum. |
| csum, rate, prevX := 0.0, 0.0, 0.0 |
| for _, e := range d.sorted { |
| newCsum := csum + (e.x-prevX)*rate |
| if newCsum >= y { |
| // y was exceeded between the previous edge |
| // and this one. |
| if rate == 0 { |
| // Anywhere between prevX and |
| // e.x will do. We return e.x |
| // because that takes care of |
| // the y==0 case naturally. |
| return e.x, true |
| } |
| return (y-csum)/rate + prevX, true |
| } |
| newCsum += e.dirac |
| if newCsum >= y { |
| // y was exceeded by the Dirac delta at e.x. |
| return e.x, true |
| } |
| csum, prevX = newCsum, e.x |
| rate += e.delta |
| } |
| return prevX, false |
| } |