github.com/matrixorigin/matrixone@v0.7.0/pkg/common/mpool/mpool.go (about) 1 // Copyright 2021 - 2022 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package mpool 16 17 import ( 18 "fmt" 19 "strings" 20 "sync" 21 "sync/atomic" 22 "unsafe" 23 24 "github.com/matrixorigin/matrixone/pkg/common/moerr" 25 "github.com/matrixorigin/matrixone/pkg/logutil" 26 "github.com/matrixorigin/matrixone/pkg/util/stack" 27 ) 28 29 // Mo's extremely simple memory pool. 30 31 // Stats 32 type MPoolStats struct { 33 NumAlloc atomic.Int64 // number of allocations 34 NumFree atomic.Int64 // number of frees 35 NumGoAlloc atomic.Int64 // number of go runtime alloc 36 NumAllocBytes atomic.Int64 // number of bytes allocated 37 NumFreeBytes atomic.Int64 // number of bytes freed 38 NumCurrBytes atomic.Int64 // current number of bytes 39 HighWaterMark atomic.Int64 // high water mark 40 } 41 42 func (s *MPoolStats) Report(tab string) string { 43 if s.HighWaterMark.Load() == 0 { 44 // empty, reduce noise. 45 return "" 46 } 47 48 ret := "" 49 ret += fmt.Sprintf("%s allocations : %d\n", tab, s.NumAlloc.Load()) 50 ret += fmt.Sprintf("%s frees : %d\n", tab, s.NumFree.Load()) 51 ret += fmt.Sprintf("%s go runtime allocations : %d\n", tab, s.NumGoAlloc.Load()) 52 ret += fmt.Sprintf("%s alloc bytes : %d\n", tab, s.NumAllocBytes.Load()) 53 ret += fmt.Sprintf("%s free bytes : %d\n", tab, s.NumFreeBytes.Load()) 54 ret += fmt.Sprintf("%s current bytes : %d\n", tab, s.NumCurrBytes.Load()) 55 ret += fmt.Sprintf("%s high water mark : %d\n", tab, s.HighWaterMark.Load()) 56 return ret 57 } 58 59 func (s *MPoolStats) ReportJson() string { 60 if s.HighWaterMark.Load() == 0 { 61 return "" 62 } 63 ret := "{" 64 ret += fmt.Sprintf("\"alloc\": %d,", s.NumAlloc.Load()) 65 ret += fmt.Sprintf("\"free\": %d,", s.NumFree.Load()) 66 ret += fmt.Sprintf("\"goalloc\": %d,", s.NumGoAlloc.Load()) 67 ret += fmt.Sprintf("\"allocBytes\": %d,", s.NumAllocBytes.Load()) 68 ret += fmt.Sprintf("\"freeBytes\": %d,", s.NumFreeBytes.Load()) 69 ret += fmt.Sprintf("\"currBytes\": %d,", s.NumCurrBytes.Load()) 70 ret += fmt.Sprintf("\"highWaterMark\": %d", s.HighWaterMark.Load()) 71 ret += "}" 72 return ret 73 } 74 75 // Update alloc stats, return curr bytes 76 func (s *MPoolStats) RecordAlloc(tag string, sz int64) int64 { 77 s.NumAlloc.Add(1) 78 s.NumAllocBytes.Add(sz) 79 curr := s.NumCurrBytes.Add(sz) 80 hwm := s.HighWaterMark.Load() 81 if curr > hwm { 82 swapped := s.HighWaterMark.CompareAndSwap(hwm, curr) 83 if swapped && curr/GB != hwm/GB { 84 logutil.Infof("MPool %s new high watermark\n%s", tag, s.Report(" ")) 85 } 86 } 87 return curr 88 } 89 90 // Update free stats, return curr bytes. 91 func (s *MPoolStats) RecordFree(tag string, sz int64) int64 { 92 s.NumFree.Add(1) 93 s.NumFreeBytes.Add(sz) 94 curr := s.NumCurrBytes.Add(-sz) 95 if curr < 0 { 96 logutil.Errorf("Mpool %s free bug, stats: %s", tag, s.Report(" ")) 97 panic(moerr.NewInternalErrorNoCtx("mpool freed more bytes than alloc")) 98 } 99 return curr 100 } 101 102 func (s *MPoolStats) RecordManyFrees(tag string, nfree, sz int64) int64 { 103 s.NumFree.Add(nfree) 104 s.NumFreeBytes.Add(sz) 105 curr := s.NumCurrBytes.Add(-sz) 106 if curr < 0 { 107 logutil.Errorf("Mpool %s free many bug, stats: %s", tag, s.Report(" ")) 108 panic(moerr.NewInternalErrorNoCtx("mpool freemany freed more bytes than alloc")) 109 } 110 return curr 111 } 112 113 const ( 114 NumFixedPool = 7 115 kMemHdrSz = 16 116 B = 1 117 KB = 1024 118 MB = 1024 * KB 119 GB = 1024 * MB 120 TB = 1024 * GB 121 PB = 1024 * TB 122 ) 123 124 // Fixed pool configurations. Number of entries in fixed pool. 125 // 126 // Small: esp, 0 is a valid value. 127 var NoFixed = []int{0, 0, 0, 0, 0, 0, 0} 128 var Small = []int{1024, 1024, 1024, 1024, 512, 0, 0} 129 var Mid = []int{4096, 4096, 1024, 1024, 1024, 512, 512} 130 var Large = []int{32 * 1024, 16 * 1024, 8 * 1024, 4 * 1024, 1024, 1024, 1024} 131 132 // Pool emement size 133 var PoolElemSize = []int{64, 128, 256, 512, 1024, 2048, 4096} 134 135 // Zeros, enough for largest pool element 136 var ZeroSlice = make([]byte, 4096) 137 138 // Memory header, kMemHdrSz bytes. 139 type memHdr struct { 140 poolId int64 141 allocSz int32 142 fixedPoolIdx int8 143 guard [3]uint8 144 } 145 146 func (pHdr *memHdr) SetGuard() { 147 pHdr.guard[0] = 0xDE 148 pHdr.guard[1] = 0xAD 149 pHdr.guard[2] = 0xBF 150 } 151 152 func (pHdr *memHdr) CheckGuard() bool { 153 return pHdr.guard[0] == 0xDE && pHdr.guard[1] == 0xAD && pHdr.guard[2] == 0xBF 154 } 155 156 // pool for fixed elements. Note that we preconfigure the pool size. 157 // We should consider implement some kind of growing logic. 158 type fixedPool struct { 159 eleSz int 160 eleCnt int 161 stats MPoolStats 162 buf []uint64 163 flist *freelist 164 } 165 166 // Initaialze a fixed pool 167 func (fp *fixedPool) initPool(tag string, poolid int64, idx int, eleCnt int, cap int64) (int64, error) { 168 eleSz := PoolElemSize[idx] 169 fp.eleSz = eleSz 170 fp.eleCnt = eleCnt 171 172 nb := (kMemHdrSz + eleSz) * eleCnt 173 if nb == 0 { 174 return 0, nil 175 } 176 177 if int64(nb) >= cap { 178 return 0, moerr.NewInternalErrorNoCtx("initPool failed, not enough space %d < %d", nb, cap) 179 } 180 181 // The poll, is considered allocated, so do accouting with global stats. 182 curr := globalStats.RecordAlloc(tag, int64(nb)) 183 if curr > GlobalCap() { 184 // OOM, return nb back to globalStats 185 globalStats.RecordFree(tag, int64(nb)) 186 return 0, moerr.NewOOMNoCtx() 187 } 188 189 fp.flist = make_freelist(int32(eleCnt)) 190 191 // Really allocate buffer, and put hdr of each slot into pool. 192 // nb is always 8x, 193 fp.buf = make([]uint64, nb/8) 194 ptr := unsafe.Pointer(&fp.buf[0]) 195 196 for i := 0; i < eleCnt; i++ { 197 offset := (kMemHdrSz + eleSz) * i 198 hdr := unsafe.Add(ptr, offset) 199 pHdr := (*memHdr)(hdr) 200 pHdr.poolId = poolid 201 pHdr.fixedPoolIdx = int8(idx) 202 pHdr.allocSz = -1 203 pHdr.SetGuard() 204 205 fp.flist.put(hdr) 206 } 207 return int64(nb), nil 208 } 209 210 /* 211 func (fp *fixedPool) destroy() { 212 // not necessary, but doing it anyway 213 // original here to maintain stats. All relavent stats are maintained 214 // in MPool itself. 215 fp.flist.destroy() 216 fp.buf = nil 217 } 218 */ 219 220 type detailInfo struct { 221 cnt, bytes int64 222 } 223 224 type mpoolDetails struct { 225 mu sync.Mutex 226 alloc map[string]detailInfo 227 free map[string]detailInfo 228 } 229 230 func newMpoolDetails() *mpoolDetails { 231 mpd := mpoolDetails{} 232 mpd.alloc = make(map[string]detailInfo) 233 mpd.free = make(map[string]detailInfo) 234 return &mpd 235 } 236 237 func (d *mpoolDetails) recordAlloc(nb int64) { 238 f := stack.Caller(2) 239 k := fmt.Sprintf("%v", f) 240 d.mu.Lock() 241 defer d.mu.Unlock() 242 243 info := d.alloc[k] 244 info.cnt += 1 245 info.bytes += nb 246 d.alloc[k] = info 247 } 248 249 func (d *mpoolDetails) recordFree(nb int64) { 250 f := stack.Caller(2) 251 k := fmt.Sprintf("%v", f) 252 d.mu.Lock() 253 defer d.mu.Unlock() 254 255 info := d.free[k] 256 info.cnt += 1 257 info.bytes += nb 258 d.free[k] = info 259 } 260 261 func (d *mpoolDetails) reportJson() string { 262 d.mu.Lock() 263 defer d.mu.Unlock() 264 ret := `{"alloc": {` 265 allocs := make([]string, 0) 266 for k, v := range d.alloc { 267 kvs := fmt.Sprintf("\"%s\": [%d, %d]", k, v.cnt, v.bytes) 268 allocs = append(allocs, kvs) 269 } 270 ret += strings.Join(allocs, ",") 271 ret += `}, "free": {` 272 frees := make([]string, 0) 273 for k, v := range d.free { 274 kvs := fmt.Sprintf("\"%s\": [%d, %d]", k, v.cnt, v.bytes) 275 frees = append(frees, kvs) 276 } 277 ret += strings.Join(frees, ",") 278 ret += "}}" 279 return ret 280 } 281 282 // The memory pool. 283 type MPool struct { 284 id int64 // mpool generated, used to look up the MPool 285 tag string // user supplied, for debug/inspect 286 cap int64 // pool capacity 287 stats MPoolStats // stats 288 pools [7]fixedPool 289 sels *sync.Pool // weirdness, keep old API but this should go away. 290 details *mpoolDetails 291 } 292 293 func (mp *MPool) EnableDetailRecording() { 294 if mp.details == nil { 295 mp.details = newMpoolDetails() 296 } 297 } 298 299 func (mp *MPool) DisableDetailRecording() { 300 mp.details = nil 301 } 302 303 func (mp *MPool) Stats() *MPoolStats { 304 return &mp.stats 305 } 306 307 func (mp *MPool) Cap() int64 { 308 if mp.cap == 0 { 309 return PB 310 } 311 return mp.cap 312 } 313 314 func (mp *MPool) FixedPoolStats(i int) *MPoolStats { 315 if i < 0 || i > NumFixedPool { 316 panic(moerr.NewInternalErrorNoCtx("accessing stats of %d-th fixed pool", i)) 317 } 318 return &mp.pools[i].stats 319 } 320 321 func (mp *MPool) initPool(sz []int) error { 322 var tot int64 323 cap := mp.Cap() 324 for i, cnt := range sz { 325 nb, err := mp.pools[i].initPool(mp.tag, mp.id, i, cnt, cap-tot) 326 if err != nil { 327 return err 328 } else if nb > 0 { 329 mp.stats.RecordAlloc(mp.tag, nb) 330 } 331 tot += nb 332 } 333 return nil 334 } 335 336 func (mp *MPool) destroy() { 337 if mp.stats.NumAlloc.Load() < mp.stats.NumFree.Load() { 338 logutil.Errorf("mp error: %s", mp.stats.Report("")) 339 } 340 341 // We do not call each individual fixedPool's destroy 342 // because they recorded pooled elements alloc/frees. 343 // Those are not reflected in globalStats. 344 // Here we just compensate whatever left over in mp.stats 345 // into globalStats. 346 globalStats.RecordManyFrees(mp.tag, 347 mp.stats.NumAlloc.Load()-mp.stats.NumFree.Load(), 348 mp.stats.NumCurrBytes.Load()) 349 } 350 351 // For test. 352 func MustNewZero() *MPool { 353 return MustNewZeroWithTag("zero_fixed_mp_for_test") 354 } 355 356 func MustNewZeroWithTag(tag string) *MPool { 357 mp, err := NewMPool(tag, 0, NoFixed) 358 if err != nil { 359 panic(err) 360 } 361 return mp 362 } 363 364 // New a MPool. Tag is user supplied, used for debugging/diagnostics. 365 func NewMPool(tag string, cap int64, sz []int) (*MPool, error) { 366 if len(sz) != NumFixedPool { 367 return nil, moerr.NewInternalErrorNoCtx("invalid mpool size config") 368 } 369 370 if cap > 0 { 371 // simple sanity check 372 if cap < 1024*1024 { 373 return nil, moerr.NewInternalErrorNoCtx("mpool cap %d too small", cap) 374 } 375 if cap > GlobalCap() { 376 return nil, moerr.NewInternalErrorNoCtx("mpool cap %d too big, global cap %d", cap, globalCap) 377 } 378 } 379 380 id := atomic.AddInt64(&nextPool, 1) 381 var mp MPool 382 mp.id = id 383 mp.tag = tag 384 mp.cap = cap 385 err := mp.initPool(sz) 386 if err != nil { 387 mp.destroy() 388 return nil, err 389 } 390 mp.sels = &sync.Pool{ 391 New: func() any { 392 ss := make([]int64, 0, 16) 393 return &ss 394 }, 395 } 396 397 globalPools.Store(id, &mp) 398 // logutil.Infof("creating mpool %s, cap %d, fixed size %v", tag, cap, sz) 399 return &mp, nil 400 } 401 402 func (mp *MPool) Report() string { 403 ret := fmt.Sprintf(" mpool stats: %s", mp.Stats().Report(" ")) 404 for i := range mp.pools { 405 ret += fmt.Sprintf(" fixed pool %d stats: %s", i, mp.FixedPoolStats(i).Report(" ")) 406 } 407 return ret 408 } 409 410 func (mp *MPool) ReportJson() string { 411 ss := mp.stats.ReportJson() 412 if ss == "" { 413 return fmt.Sprintf("{\"%s\": \"\"}", mp.tag) 414 } 415 ret := fmt.Sprintf("{\"%s\": %s", mp.tag, ss) 416 for i := range mp.pools { 417 ps := mp.FixedPoolStats(i).ReportJson() 418 if ps != "" { 419 ret += fmt.Sprintf(",\n \"Fixed-%d\": %s", i, ps) 420 } 421 } 422 423 if mp.details != nil { 424 ret += `,\n "detailed_alloc": ` 425 ret += mp.details.reportJson() 426 } 427 428 return ret + "}" 429 } 430 431 func (mp *MPool) CurrNB() int64 { 432 return mp.stats.NumCurrBytes.Load() 433 } 434 435 func DeleteMPool(mp *MPool) { 436 if mp == nil { 437 return 438 } 439 440 // logutil.Infof("destroy mpool %s, cap %d, stats\n%s", mp.tag, mp.cap, mp.Report()) 441 globalPools.Delete(mp.id) 442 mp.destroy() 443 } 444 445 var nextPool int64 446 var globalCap int64 447 var globalStats MPoolStats 448 var globalPools sync.Map 449 450 func InitCap(cap int64) { 451 if cap < GB { 452 globalCap = GB 453 } else { 454 globalCap = cap 455 } 456 } 457 458 func GlobalStats() *MPoolStats { 459 return &globalStats 460 } 461 func GlobalCap() int64 { 462 if globalCap == 0 { 463 return PB 464 } 465 return globalCap 466 } 467 468 func sizeToIdx(size int) int { 469 for i, sz := range PoolElemSize { 470 if int(size) <= sz { 471 return i 472 } 473 } 474 return NumFixedPool 475 } 476 477 func (fp *fixedPool) alloc(sz int) []byte { 478 if fp.eleCnt == 0 { 479 return nil 480 } 481 // We have already done the accounting when we init the fixed pool 482 // so we don't do any global, or mpool level accounting. 483 hdr := fp.flist.get() 484 if hdr != nil { 485 // alloc from fixed pool, record alloc bytes 486 fp.stats.RecordAlloc("", int64(sz)) 487 } else { 488 // failure to alloc, record this stats 489 fp.stats.NumGoAlloc.Add(1) 490 return nil 491 } 492 493 pHdr := (*memHdr)(hdr) 494 pHdr.allocSz = int32(sz) 495 bPtr := (*byte)(unsafe.Add(hdr, kMemHdrSz)) 496 497 // Keep cap as best as we can 498 bs := unsafe.Slice(bPtr, fp.eleSz) 499 // zero the content 500 copy(bs, ZeroSlice) 501 return bs[:sz] 502 } 503 504 func (mp *MPool) Alloc(sz int) ([]byte, error) { 505 if sz < 0 || sz > GB { 506 return nil, moerr.NewInternalErrorNoCtx("Invalid alloc size %d", sz) 507 } 508 509 if sz == 0 { 510 // Alloc size of 0, return nil instead of a []byte{}. Otherwise, 511 // later when we try to free, we will not be able to get a[0] 512 return nil, nil 513 } 514 515 idx := sizeToIdx(sz) 516 if idx < NumFixedPool { 517 bs := mp.pools[idx].alloc(sz) 518 if bs != nil { 519 return bs, nil 520 } 521 } 522 523 // fallback to go alloc, first, check we are under cap 524 gcurr := globalStats.RecordAlloc("global", int64(sz)) 525 if gcurr > GlobalCap() { 526 globalStats.RecordFree("global", int64(sz)) 527 return nil, moerr.NewOOMNoCtx() 528 } 529 530 // check if it is under my cap 531 mycurr := mp.stats.RecordAlloc(mp.tag, int64(sz)) 532 if mycurr > mp.Cap() { 533 mp.stats.RecordFree(mp.tag, int64(sz)) 534 return nil, moerr.NewInternalErrorNoCtx("mpool out of space, alloc %d bytes, cap %d", sz, mp.cap) 535 } 536 537 if mp.details != nil { 538 mp.details.recordAlloc(int64(sz)) 539 } 540 541 // allocate! 542 bs := make([]uint64, (sz+kMemHdrSz+7)/8) 543 hdr := unsafe.Pointer(&bs[0]) 544 pHdr := (*memHdr)(hdr) 545 pHdr.poolId = mp.id 546 pHdr.fixedPoolIdx = NumFixedPool 547 pHdr.allocSz = int32(sz) 548 pHdr.SetGuard() 549 550 return unsafe.Slice((*byte)(unsafe.Add(hdr, kMemHdrSz)), sz), nil 551 } 552 553 func (fp *fixedPool) free(hdr unsafe.Pointer) { 554 pHdr := (*memHdr)(hdr) 555 fp.stats.RecordFree("", int64(pHdr.allocSz)) 556 557 if pHdr.allocSz == -1 { 558 // double free. 559 panic(moerr.NewInternalErrorNoCtx("free size -1, possible double free")) 560 } 561 pHdr.allocSz = -1 562 fp.flist.put(hdr) 563 } 564 565 func (mp *MPool) Free(bs []byte) { 566 if bs == nil || cap(bs) == 0 { 567 // free nil is OK. 568 return 569 } 570 571 bs = bs[:1] 572 pb := (unsafe.Pointer)(&bs[0]) 573 offset := -kMemHdrSz 574 hdr := unsafe.Add(pb, offset) 575 pHdr := (*memHdr)(hdr) 576 577 if !pHdr.CheckGuard() { 578 panic(moerr.NewInternalErrorNoCtx("mp header corruption")) 579 } 580 581 if pHdr.poolId == mp.id { 582 if pHdr.fixedPoolIdx < NumFixedPool { 583 mp.pools[pHdr.fixedPoolIdx].free(hdr) 584 } else { 585 if pHdr.allocSz == -1 { 586 // double free. 587 panic(moerr.NewInternalErrorNoCtx("free size -1, possible double free")) 588 } 589 if mp.details != nil { 590 mp.details.recordFree(int64(pHdr.allocSz)) 591 } 592 mp.stats.RecordFree(mp.tag, int64(pHdr.allocSz)) 593 globalStats.RecordFree(mp.tag, int64(pHdr.allocSz)) 594 pHdr.allocSz = -1 595 } 596 } else { 597 // cross pool free. 598 otherPool, ok := globalPools.Load(pHdr.poolId) 599 if !ok { 600 panic(moerr.NewInternalErrorNoCtx("invalid mpool id %d", pHdr.poolId)) 601 } 602 (otherPool.(*MPool)).Free(bs) 603 } 604 } 605 606 func (mp *MPool) Realloc(old []byte, sz int) ([]byte, error) { 607 if sz <= cap(old) { 608 return old[:sz], nil 609 } 610 ret, err := mp.Alloc(sz) 611 if err != nil { 612 return ret, err 613 } 614 copy(ret, old) 615 mp.Free(old) 616 return ret, nil 617 } 618 619 // alignUp rounds n up to a multiple of a. a must be a power of 2. 620 func alignUp(n, a int) int { 621 return (n + a - 1) &^ (a - 1) 622 } 623 624 // divRoundUp returns ceil(n / a). 625 func divRoundUp(n, a int) int { 626 // a is generally a power of two. This will get inlined and 627 // the compiler will optimize the division. 628 return (n + a - 1) / a 629 } 630 631 // Returns size of the memory block that mallocgc will allocate if you ask for the size. 632 func roundupsize(size int) int { 633 if size < _MaxSmallSize { 634 if size <= smallSizeMax-8 { 635 return int(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]]) 636 } else { 637 return int(class_to_size[size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]]) 638 } 639 } 640 if size+_PageSize < size { 641 return size 642 } 643 return alignUp(size, _PageSize) 644 } 645 646 // Grow is like Realloc but we try to be a little bit more aggressive on growing 647 // the slice. 648 func (mp *MPool) Grow(old []byte, sz int) ([]byte, error) { 649 if sz < len(old) { 650 return nil, moerr.NewInternalErrorNoCtx("mpool grow actually shrinks, %d, %d", len(old), sz) 651 } 652 if sz <= cap(old) { 653 return old[:sz], nil 654 } 655 656 // copy-paste go slice's grow strategy 657 newcap := cap(old) 658 doublecap := newcap + newcap 659 if sz > doublecap { 660 newcap = sz 661 } else { 662 const threshold = 256 663 if newcap < threshold { 664 newcap = doublecap 665 } else { 666 for 0 < newcap && newcap < sz { 667 newcap += (newcap + 3*threshold) / 4 668 } 669 if newcap <= 0 { 670 newcap = sz 671 } 672 } 673 } 674 newcap = roundupsize(newcap) 675 676 ret, err := mp.Realloc(old, newcap) 677 if err != nil { 678 return ret, err 679 } 680 return ret[:sz], nil 681 } 682 683 func (mp *MPool) Grow2(old []byte, old2 []byte, sz int) ([]byte, error) { 684 len1 := len(old) 685 len2 := len(old2) 686 if sz < len1+len2 { 687 return nil, moerr.NewInternalErrorNoCtx("mpool grow2 actually shrinks, %d+%d, %d", len1, len2, sz) 688 } 689 ret, err := mp.Grow(old, sz) 690 if err != nil { 691 return nil, err 692 } 693 copy(ret[len1:len1+len2], old2) 694 return ret, nil 695 } 696 697 func (mp *MPool) PutSels(sels []int64) { 698 mp.sels.Put(&sels) 699 } 700 701 func (mp *MPool) GetSels() []int64 { 702 ss := mp.sels.Get().(*[]int64) 703 return (*ss)[:0] 704 } 705 706 func (mp *MPool) Increase(nb int64) error { 707 gcurr := globalStats.RecordAlloc("global", nb) 708 if gcurr > GlobalCap() { 709 globalStats.RecordFree(mp.tag, nb) 710 return moerr.NewOOMNoCtx() 711 } 712 713 // check if it is under my cap 714 mycurr := mp.stats.RecordAlloc(mp.tag, nb) 715 if mycurr > mp.Cap() { 716 mp.stats.RecordFree(mp.tag, nb) 717 return moerr.NewInternalErrorNoCtx("mpool out of space, alloc %d bytes, cap %d", nb, mp.cap) 718 } 719 return nil 720 } 721 722 func (mp *MPool) Decrease(nb int64) { 723 mp.stats.RecordFree(mp.tag, nb) 724 globalStats.RecordFree("global", nb) 725 } 726 727 func MakeSliceWithCap[T any](n, cap int, mp *MPool) ([]T, error) { 728 var t T 729 tsz := unsafe.Sizeof(t) 730 bs, err := mp.Alloc(int(tsz) * cap) 731 if err != nil { 732 return nil, err 733 } 734 ptr := unsafe.Pointer(&bs[0]) 735 tptr := (*T)(ptr) 736 ret := unsafe.Slice(tptr, cap) 737 return ret[:n:cap], nil 738 } 739 740 func MakeSlice[T any](n int, mp *MPool) ([]T, error) { 741 return MakeSliceWithCap[T](n, n, mp) 742 } 743 744 func MakeSliceArgs[T any](mp *MPool, args ...T) ([]T, error) { 745 ret, err := MakeSlice[T](len(args), mp) 746 if err != nil { 747 return ret, err 748 } 749 copy(ret, args) 750 return ret, nil 751 } 752 753 // Report memory usage in json. 754 func ReportMemUsage(tag string) string { 755 gstat := fmt.Sprintf("{\"global\":%s}", globalStats.ReportJson()) 756 if tag == "global" { 757 return "[" + gstat + "]" 758 } 759 760 var poolStats []string 761 if tag == "" { 762 poolStats = append(poolStats, gstat) 763 } 764 765 gather := func(key, value any) bool { 766 mp := value.(*MPool) 767 if tag == "" || tag == mp.tag { 768 poolStats = append(poolStats, mp.ReportJson()) 769 } 770 return true 771 } 772 globalPools.Range(gather) 773 774 return "[" + strings.Join(poolStats, ",") + "]" 775 } 776 777 func MPoolControl(tag string, cmd string) string { 778 if tag == "" || tag == "global" { 779 return "Cannot enable detail on mpool global stats" 780 } 781 782 cmdFunc := func(key, value any) bool { 783 mp := value.(*MPool) 784 if tag == mp.tag { 785 switch cmd { 786 case "enable_detail": 787 mp.EnableDetailRecording() 788 case "disable_detail": 789 mp.DisableDetailRecording() 790 } 791 } 792 return true 793 } 794 795 globalPools.Range(cmdFunc) 796 return "ok" 797 }