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