1
2
3
4
5 package trace
6
7 import (
8 "container/heap"
9 "math"
10 "sort"
11 "strings"
12 "time"
13 )
14
15
16
17
18 type MutatorUtil struct {
19 Time int64
20
21
22 Util float64
23 }
24
25
26 type UtilFlags int
27
28 const (
29
30 UtilSTW UtilFlags = 1 << iota
31
32
33 UtilBackground
34
35
36 UtilAssist
37
38 UtilSweep
39
40
41
42
43 UtilPerProc
44 )
45
46
47
48
49
50
51
52
53 func MutatorUtilization(events []*Event, flags UtilFlags) [][]MutatorUtil {
54 if len(events) == 0 {
55 return nil
56 }
57
58 type perP struct {
59
60 gc int
61
62
63
64 series int
65 }
66 ps := []perP{}
67 stw := 0
68
69 out := [][]MutatorUtil{}
70 assists := map[uint64]bool{}
71 block := map[uint64]*Event{}
72 bgMark := map[uint64]bool{}
73
74 for _, ev := range events {
75 switch ev.Type {
76 case EvGomaxprocs:
77 gomaxprocs := int(ev.Args[0])
78 if len(ps) > gomaxprocs {
79 if flags&UtilPerProc != 0 {
80
81 for _, p := range ps[gomaxprocs:] {
82 out[p.series] = addUtil(out[p.series], MutatorUtil{ev.Ts, 0})
83 }
84 }
85 ps = ps[:gomaxprocs]
86 }
87 for len(ps) < gomaxprocs {
88
89 series := 0
90 if flags&UtilPerProc != 0 || len(out) == 0 {
91 series = len(out)
92 out = append(out, []MutatorUtil{{ev.Ts, 1}})
93 }
94 ps = append(ps, perP{series: series})
95 }
96 case EvGCSTWStart:
97 if flags&UtilSTW != 0 {
98 stw++
99 }
100 case EvGCSTWDone:
101 if flags&UtilSTW != 0 {
102 stw--
103 }
104 case EvGCMarkAssistStart:
105 if flags&UtilAssist != 0 {
106 ps[ev.P].gc++
107 assists[ev.G] = true
108 }
109 case EvGCMarkAssistDone:
110 if flags&UtilAssist != 0 {
111 ps[ev.P].gc--
112 delete(assists, ev.G)
113 }
114 case EvGCSweepStart:
115 if flags&UtilSweep != 0 {
116 ps[ev.P].gc++
117 }
118 case EvGCSweepDone:
119 if flags&UtilSweep != 0 {
120 ps[ev.P].gc--
121 }
122 case EvGoStartLabel:
123 if flags&UtilBackground != 0 && strings.HasPrefix(ev.SArgs[0], "GC ") && ev.SArgs[0] != "GC (idle)" {
124
125
126
127
128
129
130
131 if !(flags&UtilPerProc != 0 && ev.SArgs[0] == "GC (dedicated)") {
132 bgMark[ev.G] = true
133 ps[ev.P].gc++
134 }
135 }
136 fallthrough
137 case EvGoStart:
138 if assists[ev.G] {
139
140 ps[ev.P].gc++
141 }
142 block[ev.G] = ev.Link
143 default:
144 if ev != block[ev.G] {
145 continue
146 }
147
148 if assists[ev.G] {
149
150 ps[ev.P].gc--
151 }
152 if bgMark[ev.G] {
153
154 ps[ev.P].gc--
155 delete(bgMark, ev.G)
156 }
157 delete(block, ev.G)
158 }
159
160 if flags&UtilPerProc == 0 {
161
162 if len(ps) == 0 {
163 continue
164 }
165 gcPs := 0
166 if stw > 0 {
167 gcPs = len(ps)
168 } else {
169 for i := range ps {
170 if ps[i].gc > 0 {
171 gcPs++
172 }
173 }
174 }
175 mu := MutatorUtil{ev.Ts, 1 - float64(gcPs)/float64(len(ps))}
176
177
178
179 out[0] = addUtil(out[0], mu)
180 } else {
181
182 for i := range ps {
183 p := &ps[i]
184 util := 1.0
185 if stw > 0 || p.gc > 0 {
186 util = 0.0
187 }
188 out[p.series] = addUtil(out[p.series], MutatorUtil{ev.Ts, util})
189 }
190 }
191 }
192
193
194
195
196
197 mu := MutatorUtil{events[len(events)-1].Ts, 0}
198 for i := range ps {
199 out[ps[i].series] = addUtil(out[ps[i].series], mu)
200 }
201 return out
202 }
203
204 func addUtil(util []MutatorUtil, mu MutatorUtil) []MutatorUtil {
205 if len(util) > 0 {
206 if mu.Util == util[len(util)-1].Util {
207
208 return util
209 }
210 if mu.Time == util[len(util)-1].Time {
211
212 if mu.Util < util[len(util)-1].Util {
213 util[len(util)-1] = mu
214 }
215 return util
216 }
217 }
218 return append(util, mu)
219 }
220
221
222
223
224 type totalUtil float64
225
226 func totalUtilOf(meanUtil float64, dur int64) totalUtil {
227 return totalUtil(meanUtil * float64(dur))
228 }
229
230
231 func (u totalUtil) mean(dur time.Duration) float64 {
232 return float64(u) / float64(dur)
233 }
234
235
236
237 type MMUCurve struct {
238 series []mmuSeries
239 }
240
241 type mmuSeries struct {
242 util []MutatorUtil
243
244 sums []totalUtil
245
246
247 bands []mmuBand
248
249 bandDur int64
250 }
251
252 type mmuBand struct {
253
254
255 minUtil float64
256
257
258 cumUtil totalUtil
259
260
261
262 integrator integrator
263 }
264
265
266
267 func NewMMUCurve(utils [][]MutatorUtil) *MMUCurve {
268 series := make([]mmuSeries, len(utils))
269 for i, util := range utils {
270 series[i] = newMMUSeries(util)
271 }
272 return &MMUCurve{series}
273 }
274
275
276
277 var bandsPerSeries = 1000
278
279 func newMMUSeries(util []MutatorUtil) mmuSeries {
280
281 sums := make([]totalUtil, len(util))
282 var prev MutatorUtil
283 var sum totalUtil
284 for j, u := range util {
285 sum += totalUtilOf(prev.Util, u.Time-prev.Time)
286 sums[j] = sum
287 prev = u
288 }
289
290
291
292
293
294
295 numBands := bandsPerSeries
296 if numBands > len(util) {
297
298
299 numBands = len(util)
300 }
301 dur := util[len(util)-1].Time - util[0].Time
302 bandDur := (dur + int64(numBands) - 1) / int64(numBands)
303 if bandDur < 1 {
304 bandDur = 1
305 }
306
307
308 bands := make([]mmuBand, numBands+1)
309 s := mmuSeries{util, sums, bands, bandDur}
310 leftSum := integrator{&s, 0}
311 for i := range bands {
312 startTime, endTime := s.bandTime(i)
313 cumUtil := leftSum.advance(startTime)
314 predIdx := leftSum.pos
315 minUtil := 1.0
316 for i := predIdx; i < len(util) && util[i].Time < endTime; i++ {
317 minUtil = math.Min(minUtil, util[i].Util)
318 }
319 bands[i] = mmuBand{minUtil, cumUtil, leftSum}
320 }
321
322 return s
323 }
324
325 func (s *mmuSeries) bandTime(i int) (start, end int64) {
326 start = int64(i)*s.bandDur + s.util[0].Time
327 end = start + s.bandDur
328 return
329 }
330
331 type bandUtil struct {
332
333 series int
334
335 i int
336
337
338 utilBound float64
339 }
340
341 type bandUtilHeap []bandUtil
342
343 func (h bandUtilHeap) Len() int {
344 return len(h)
345 }
346
347 func (h bandUtilHeap) Less(i, j int) bool {
348 return h[i].utilBound < h[j].utilBound
349 }
350
351 func (h bandUtilHeap) Swap(i, j int) {
352 h[i], h[j] = h[j], h[i]
353 }
354
355 func (h *bandUtilHeap) Push(x any) {
356 *h = append(*h, x.(bandUtil))
357 }
358
359 func (h *bandUtilHeap) Pop() any {
360 x := (*h)[len(*h)-1]
361 *h = (*h)[:len(*h)-1]
362 return x
363 }
364
365
366 type UtilWindow struct {
367 Time int64
368
369 MutatorUtil float64
370 }
371
372 type utilHeap []UtilWindow
373
374 func (h utilHeap) Len() int {
375 return len(h)
376 }
377
378 func (h utilHeap) Less(i, j int) bool {
379 if h[i].MutatorUtil != h[j].MutatorUtil {
380 return h[i].MutatorUtil > h[j].MutatorUtil
381 }
382 return h[i].Time > h[j].Time
383 }
384
385 func (h utilHeap) Swap(i, j int) {
386 h[i], h[j] = h[j], h[i]
387 }
388
389 func (h *utilHeap) Push(x any) {
390 *h = append(*h, x.(UtilWindow))
391 }
392
393 func (h *utilHeap) Pop() any {
394 x := (*h)[len(*h)-1]
395 *h = (*h)[:len(*h)-1]
396 return x
397 }
398
399
400
401 type accumulator struct {
402 mmu float64
403
404
405
406
407 bound float64
408
409
410 nWorst int
411 wHeap utilHeap
412
413
414 mud *mud
415
416
417 preciseMass float64
418
419
420 lastTime int64
421 lastMU float64
422 }
423
424
425
426 func (acc *accumulator) resetTime() {
427
428
429
430 acc.lastTime = math.MaxInt64
431 }
432
433
434
435
436
437
438 func (acc *accumulator) addMU(time int64, mu float64, window time.Duration) bool {
439 if mu < acc.mmu {
440 acc.mmu = mu
441 }
442 acc.bound = acc.mmu
443
444 if acc.nWorst == 0 {
445
446
447 return mu == 0
448 }
449
450
451 if len(acc.wHeap) < acc.nWorst || mu < acc.wHeap[0].MutatorUtil {
452
453
454
455
456
457 for i, ui := range acc.wHeap {
458 if time+int64(window) > ui.Time && ui.Time+int64(window) > time {
459 if ui.MutatorUtil <= mu {
460
461 goto keep
462 } else {
463
464 heap.Remove(&acc.wHeap, i)
465 break
466 }
467 }
468 }
469
470 heap.Push(&acc.wHeap, UtilWindow{time, mu})
471 if len(acc.wHeap) > acc.nWorst {
472 heap.Pop(&acc.wHeap)
473 }
474 keep:
475 }
476
477 if len(acc.wHeap) < acc.nWorst {
478
479 acc.bound = 1.0
480 } else {
481
482 acc.bound = math.Max(acc.bound, acc.wHeap[0].MutatorUtil)
483 }
484
485 if acc.mud != nil {
486 if acc.lastTime != math.MaxInt64 {
487
488 acc.mud.add(acc.lastMU, mu, float64(time-acc.lastTime))
489 }
490 acc.lastTime, acc.lastMU = time, mu
491 if _, mudBound, ok := acc.mud.approxInvCumulativeSum(); ok {
492 acc.bound = math.Max(acc.bound, mudBound)
493 } else {
494
495
496
497 acc.bound = 1
498 }
499
500
501 return false
502 }
503
504
505 return len(acc.wHeap) == acc.nWorst && acc.wHeap[0].MutatorUtil == 0
506 }
507
508
509
510
511
512 func (c *MMUCurve) MMU(window time.Duration) (mmu float64) {
513 acc := accumulator{mmu: 1.0, bound: 1.0}
514 c.mmu(window, &acc)
515 return acc.mmu
516 }
517
518
519
520
521
522
523 func (c *MMUCurve) Examples(window time.Duration, n int) (worst []UtilWindow) {
524 acc := accumulator{mmu: 1.0, bound: 1.0, nWorst: n}
525 c.mmu(window, &acc)
526 sort.Sort(sort.Reverse(acc.wHeap))
527 return ([]UtilWindow)(acc.wHeap)
528 }
529
530
531
532
533
534
535
536
537
538
539
540 func (c *MMUCurve) MUD(window time.Duration, quantiles []float64) []float64 {
541 if len(quantiles) == 0 {
542 return []float64{}
543 }
544
545
546
547
548
549
550
551
552
553
554
555
556
557 maxQ := quantiles[0]
558 for _, q := range quantiles {
559 if q > maxQ {
560 maxQ = q
561 }
562 }
563
564
565
566
567
568
569 var duration int64
570 for _, s := range c.series {
571 duration1 := s.util[len(s.util)-1].Time - s.util[0].Time
572 if duration1 >= int64(window) {
573 duration += duration1 - int64(window)
574 }
575 }
576 qMass := float64(duration) * maxQ
577
578
579
580 acc := accumulator{mmu: 1.0, bound: 1.0, preciseMass: qMass, mud: new(mud)}
581 acc.mud.setTrackMass(qMass)
582 c.mmu(window, &acc)
583
584
585 out := make([]float64, len(quantiles))
586 for i := range out {
587 mu, _ := acc.mud.invCumulativeSum(float64(duration) * quantiles[i])
588 if math.IsNaN(mu) {
589
590
591
592
593
594
595
596
597
598
599
600
601
602 mu = acc.mmu
603 }
604 out[i] = mu
605 }
606 return out
607 }
608
609 func (c *MMUCurve) mmu(window time.Duration, acc *accumulator) {
610 if window <= 0 {
611 acc.mmu = 0
612 return
613 }
614
615 var bandU bandUtilHeap
616 windows := make([]time.Duration, len(c.series))
617 for i, s := range c.series {
618 windows[i] = window
619 if max := time.Duration(s.util[len(s.util)-1].Time - s.util[0].Time); window > max {
620 windows[i] = max
621 }
622
623 bandU1 := bandUtilHeap(s.mkBandUtil(i, windows[i]))
624 if bandU == nil {
625 bandU = bandU1
626 } else {
627 bandU = append(bandU, bandU1...)
628 }
629 }
630
631
632 heap.Init(&bandU)
633
634
635
636
637 for len(bandU) > 0 && bandU[0].utilBound < acc.bound {
638 i := bandU[0].series
639 c.series[i].bandMMU(bandU[0].i, windows[i], acc)
640 heap.Pop(&bandU)
641 }
642 }
643
644 func (c *mmuSeries) mkBandUtil(series int, window time.Duration) []bandUtil {
645
646
647
648
649
650
651 minBands := int((int64(window) + c.bandDur - 1) / c.bandDur)
652 maxBands := int((int64(window) + 2*(c.bandDur-1)) / c.bandDur)
653 if window > 1 && maxBands < 2 {
654 panic("maxBands < 2")
655 }
656 tailDur := int64(window) % c.bandDur
657 nUtil := len(c.bands) - maxBands + 1
658 if nUtil < 0 {
659 nUtil = 0
660 }
661 bandU := make([]bandUtil, nUtil)
662 for i := range bandU {
663
664
665
666
667 var util totalUtil
668
669
670
671 l := c.bands[i].minUtil
672 r1 := c.bands[i+minBands-1].minUtil
673 r2 := c.bands[i+maxBands-1].minUtil
674 minBand := math.Min(l, math.Min(r1, r2))
675
676
677
678 if minBands == 1 {
679 util += totalUtilOf(minBand, int64(window))
680 } else {
681 util += totalUtilOf(minBand, c.bandDur)
682 midBand := 0.0
683 switch {
684 case minBand == l:
685 midBand = math.Min(r1, r2)
686 case minBand == r1:
687 midBand = math.Min(l, r2)
688 case minBand == r2:
689 midBand = math.Min(l, r1)
690 }
691 util += totalUtilOf(midBand, tailDur)
692 }
693
694
695
696 if minBands > 2 {
697 util += c.bands[i+minBands-1].cumUtil - c.bands[i+1].cumUtil
698 }
699
700 bandU[i] = bandUtil{series, i, util.mean(window)}
701 }
702
703 return bandU
704 }
705
706
707
708 func (c *mmuSeries) bandMMU(bandIdx int, window time.Duration, acc *accumulator) {
709 util := c.util
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726 left := c.bands[bandIdx].integrator
727 right := left
728 time, endTime := c.bandTime(bandIdx)
729 if utilEnd := util[len(util)-1].Time - int64(window); utilEnd < endTime {
730 endTime = utilEnd
731 }
732 acc.resetTime()
733 for {
734
735 mu := (right.advance(time+int64(window)) - left.advance(time)).mean(window)
736 if acc.addMU(time, mu, window) {
737 break
738 }
739 if time == endTime {
740 break
741 }
742
743
744
745
746
747 minTime := time + int64((mu-acc.bound)*float64(window))
748
749
750
751
752 if t1, t2 := left.next(time), right.next(time+int64(window))-int64(window); t1 < t2 {
753 time = t1
754 } else {
755 time = t2
756 }
757 if time < minTime {
758 time = minTime
759 }
760 if time >= endTime {
761
762
763
764 time = endTime
765 }
766 }
767 }
768
769
770
771 type integrator struct {
772 u *mmuSeries
773
774
775 pos int
776 }
777
778
779
780
781 func (in *integrator) advance(time int64) totalUtil {
782 util, pos := in.u.util, in.pos
783
784
785
786
787
788
789 const maxSeq = 8
790 if pos+maxSeq < len(util) && util[pos+maxSeq].Time > time {
791
792 for pos+1 < len(util) && util[pos+1].Time <= time {
793 pos++
794 }
795 } else {
796
797 l, r := pos, len(util)
798 for l < r {
799 h := int(uint(l+r) >> 1)
800 if util[h].Time <= time {
801 l = h + 1
802 } else {
803 r = h
804 }
805 }
806 pos = l - 1
807 }
808 in.pos = pos
809 var partial totalUtil
810 if time != util[pos].Time {
811 partial = totalUtilOf(util[pos].Util, time-util[pos].Time)
812 }
813 return in.u.sums[pos] + partial
814 }
815
816
817
818 func (in *integrator) next(time int64) int64 {
819 for _, u := range in.u.util[in.pos:] {
820 if u.Time > time {
821 return u.Time
822 }
823 }
824 return 1<<63 - 1
825 }
826
View as plain text