| // Copyright 2009 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. |
| |
| // Malloc profiling. |
| // Patterned after tcmalloc's algorithms; shorter code. |
| |
| package runtime |
| #include "runtime.h" |
| #include "arch_GOARCH.h" |
| #include "malloc.h" |
| #include "mprof.h" |
| #include "defs_GOOS_GOARCH.h" |
| #include "type.h" |
| |
| // NOTE(rsc): Everything here could use cas if contention became an issue. |
| extern Lock runtime·proflock; |
| |
| // All memory allocations are local and do not escape outside of the profiler. |
| // The profiler is forbidden from referring to garbage-collected memory. |
| |
| enum { MProf, BProf }; // profile types |
| |
| enum { |
| BuckHashSize = 179999, |
| }; |
| static Bucket **buckhash; |
| extern Bucket *runtime·mbuckets; // memory profile buckets |
| extern Bucket *runtime·bbuckets; // blocking profile buckets |
| static uintptr bucketmem; |
| |
| // Return the bucket for stk[0:nstk], allocating new bucket if needed. |
| static Bucket* |
| stkbucket(int32 typ, uintptr size, uintptr *stk, int32 nstk, bool alloc) |
| { |
| int32 i; |
| uintptr h; |
| Bucket *b; |
| |
| if(buckhash == nil) { |
| buckhash = runtime·SysAlloc(BuckHashSize*sizeof buckhash[0], &mstats.buckhash_sys); |
| if(buckhash == nil) |
| runtime·throw("runtime: cannot allocate memory"); |
| } |
| |
| // Hash stack. |
| h = 0; |
| for(i=0; i<nstk; i++) { |
| h += stk[i]; |
| h += h<<10; |
| h ^= h>>6; |
| } |
| // hash in size |
| h += size; |
| h += h<<10; |
| h ^= h>>6; |
| // finalize |
| h += h<<3; |
| h ^= h>>11; |
| |
| i = h%BuckHashSize; |
| for(b = buckhash[i]; b; b=b->next) |
| if(b->typ == typ && b->hash == h && b->size == size && b->nstk == nstk && |
| runtime·mcmp((byte*)b->stk, (byte*)stk, nstk*sizeof stk[0]) == 0) |
| return b; |
| |
| if(!alloc) |
| return nil; |
| |
| b = runtime·persistentalloc(sizeof *b + nstk*sizeof stk[0], 0, &mstats.buckhash_sys); |
| bucketmem += sizeof *b + nstk*sizeof stk[0]; |
| runtime·memmove(b->stk, stk, nstk*sizeof stk[0]); |
| b->typ = typ; |
| b->hash = h; |
| b->size = size; |
| b->nstk = nstk; |
| b->next = buckhash[i]; |
| buckhash[i] = b; |
| if(typ == MProf) { |
| b->allnext = runtime·mbuckets; |
| runtime·mbuckets = b; |
| } else { |
| b->allnext = runtime·bbuckets; |
| runtime·bbuckets = b; |
| } |
| return b; |
| } |
| |
| static void |
| MProf_GC(void) |
| { |
| Bucket *b; |
| |
| for(b=runtime·mbuckets; b; b=b->allnext) { |
| b->data.mp.allocs += b->data.mp.prev_allocs; |
| b->data.mp.frees += b->data.mp.prev_frees; |
| b->data.mp.alloc_bytes += b->data.mp.prev_alloc_bytes; |
| b->data.mp.free_bytes += b->data.mp.prev_free_bytes; |
| |
| b->data.mp.prev_allocs = b->data.mp.recent_allocs; |
| b->data.mp.prev_frees = b->data.mp.recent_frees; |
| b->data.mp.prev_alloc_bytes = b->data.mp.recent_alloc_bytes; |
| b->data.mp.prev_free_bytes = b->data.mp.recent_free_bytes; |
| |
| b->data.mp.recent_allocs = 0; |
| b->data.mp.recent_frees = 0; |
| b->data.mp.recent_alloc_bytes = 0; |
| b->data.mp.recent_free_bytes = 0; |
| } |
| } |
| |
| // Record that a gc just happened: all the 'recent' statistics are now real. |
| void |
| runtime·MProf_GC(void) |
| { |
| runtime·lock(&runtime·proflock); |
| MProf_GC(); |
| runtime·unlock(&runtime·proflock); |
| } |
| |
| // Called by malloc to record a profiled block. |
| void |
| runtime·MProf_Malloc(void *p, uintptr size) |
| { |
| uintptr stk[32]; |
| Bucket *b; |
| int32 nstk; |
| |
| nstk = runtime·callers(1, stk, nelem(stk)); |
| runtime·lock(&runtime·proflock); |
| b = stkbucket(MProf, size, stk, nstk, true); |
| b->data.mp.recent_allocs++; |
| b->data.mp.recent_alloc_bytes += size; |
| runtime·unlock(&runtime·proflock); |
| |
| // Setprofilebucket locks a bunch of other mutexes, so we call it outside of proflock. |
| // This reduces potential contention and chances of deadlocks. |
| // Since the object must be alive during call to MProf_Malloc, |
| // it's fine to do this non-atomically. |
| runtime·setprofilebucket(p, b); |
| } |
| |
| // Called by malloc to record a profiled block. |
| void |
| runtime·mprofMalloc_m(void) |
| { |
| uintptr stk[32]; |
| Bucket *b; |
| int32 nstk; |
| uintptr size; |
| void *p; |
| |
| size = g->m->scalararg[0]; |
| p = g->m->ptrarg[0]; |
| g->m->ptrarg[0] = nil; |
| |
| if(g->m->curg == nil) |
| nstk = runtime·callers(1, stk, nelem(stk)); |
| else |
| nstk = runtime·gcallers(g->m->curg, 1, stk, nelem(stk)); |
| runtime·lock(&runtime·proflock); |
| b = stkbucket(MProf, size, stk, nstk, true); |
| b->data.mp.recent_allocs++; |
| b->data.mp.recent_alloc_bytes += size; |
| runtime·unlock(&runtime·proflock); |
| |
| // Setprofilebucket locks a bunch of other mutexes, so we call it outside of proflock. |
| // This reduces potential contention and chances of deadlocks. |
| // Since the object must be alive during call to MProf_Malloc, |
| // it's fine to do this non-atomically. |
| runtime·setprofilebucket(p, b); |
| } |
| |
| // Called when freeing a profiled block. |
| void |
| runtime·MProf_Free(Bucket *b, uintptr size, bool freed) |
| { |
| runtime·lock(&runtime·proflock); |
| if(freed) { |
| b->data.mp.recent_frees++; |
| b->data.mp.recent_free_bytes += size; |
| } else { |
| b->data.mp.prev_frees++; |
| b->data.mp.prev_free_bytes += size; |
| } |
| runtime·unlock(&runtime·proflock); |
| } |
| |
| int64 runtime·blockprofilerate; // in CPU ticks |
| |
| void |
| runtime·SetBlockProfileRate(intgo rate) |
| { |
| int64 r; |
| |
| if(rate <= 0) |
| r = 0; // disable profiling |
| else { |
| // convert ns to cycles, use float64 to prevent overflow during multiplication |
| r = (float64)rate*runtime·tickspersecond()/(1000*1000*1000); |
| if(r == 0) |
| r = 1; |
| } |
| runtime·atomicstore64((uint64*)&runtime·blockprofilerate, r); |
| } |
| |
| void |
| runtime·blockevent(int64 cycles, int32 skip) |
| { |
| int32 nstk; |
| int64 rate; |
| uintptr stk[32]; |
| Bucket *b; |
| |
| if(cycles <= 0) |
| return; |
| rate = runtime·atomicload64((uint64*)&runtime·blockprofilerate); |
| if(rate <= 0 || (rate > cycles && runtime·fastrand1()%rate > cycles)) |
| return; |
| |
| if(g->m->curg == nil || g->m->curg == g) |
| nstk = runtime·callers(skip, stk, nelem(stk)); |
| else |
| nstk = runtime·gcallers(g->m->curg, skip, stk, nelem(stk)); |
| runtime·lock(&runtime·proflock); |
| b = stkbucket(BProf, 0, stk, nstk, true); |
| b->data.bp.count++; |
| b->data.bp.cycles += cycles; |
| runtime·unlock(&runtime·proflock); |
| } |
| |
| void |
| runtime·blockevent_m(void) |
| { |
| runtime·blockevent(g->m->scalararg[0] + ((int64)g->m->scalararg[1]<<32), g->m->scalararg[2]); |
| } |
| |
| void |
| runtime·iterate_memprof(void (*callback)(Bucket*, uintptr, uintptr*, uintptr, uintptr, uintptr)) |
| { |
| Bucket *b; |
| |
| runtime·lock(&runtime·proflock); |
| for(b=runtime·mbuckets; b; b=b->allnext) { |
| callback(b, b->nstk, b->stk, b->size, b->data.mp.allocs, b->data.mp.frees); |
| } |
| runtime·unlock(&runtime·proflock); |
| } |
| |
| // Go interface to profile data. (Declared in debug.go) |
| |
| |
| // Must match StackRecord in debug.go. |
| typedef struct TRecord TRecord; |
| struct TRecord { |
| uintptr stk[32]; |
| }; |
| |
| static void |
| saveg(uintptr pc, uintptr sp, G *gp, TRecord *r) |
| { |
| int32 n; |
| |
| n = runtime·gentraceback(pc, sp, 0, gp, 0, r->stk, nelem(r->stk), nil, nil, false); |
| if(n < nelem(r->stk)) |
| r->stk[n] = 0; |
| } |
| |
| func GoroutineProfile(b Slice) (n int, ok bool) { |
| uintptr pc, sp, i; |
| TRecord *r; |
| G *gp; |
| |
| sp = runtime·getcallersp(&b); |
| pc = (uintptr)runtime·getcallerpc(&b); |
| |
| ok = false; |
| n = runtime·gcount(); |
| if(n <= b.len) { |
| runtime·semacquire(&runtime·worldsema, false); |
| g->m->gcing = 1; |
| runtime·stoptheworld(); |
| |
| n = runtime·gcount(); |
| if(n <= b.len) { |
| ok = true; |
| r = (TRecord*)b.array; |
| saveg(pc, sp, g, r++); |
| for(i = 0; i < runtime·allglen; i++) { |
| gp = runtime·allg[i]; |
| if(gp == g || runtime·readgstatus(gp) == Gdead) |
| continue; |
| saveg(~(uintptr)0, ~(uintptr)0, gp, r++); |
| } |
| } |
| |
| g->m->gcing = 0; |
| runtime·semrelease(&runtime·worldsema); |
| runtime·starttheworld(); |
| } |
| } |
| |
| // Tracing of alloc/free/gc. |
| |
| static Lock tracelock; |
| |
| void |
| runtime·tracealloc(void *p, uintptr size, Type *type) |
| { |
| runtime·lock(&tracelock); |
| g->m->traceback = 2; |
| if(type == nil) |
| runtime·printf("tracealloc(%p, %p)\n", p, size); |
| else |
| runtime·printf("tracealloc(%p, %p, %S)\n", p, size, *type->string); |
| if(g->m->curg == nil || g == g->m->curg) { |
| runtime·goroutineheader(g); |
| runtime·traceback((uintptr)runtime·getcallerpc(&p), (uintptr)runtime·getcallersp(&p), 0, g); |
| } else { |
| runtime·goroutineheader(g->m->curg); |
| runtime·traceback(~(uintptr)0, ~(uintptr)0, 0, g->m->curg); |
| } |
| runtime·printf("\n"); |
| g->m->traceback = 0; |
| runtime·unlock(&tracelock); |
| } |
| |
| void |
| runtime·tracefree(void *p, uintptr size) |
| { |
| runtime·lock(&tracelock); |
| g->m->traceback = 2; |
| runtime·printf("tracefree(%p, %p)\n", p, size); |
| runtime·goroutineheader(g); |
| runtime·traceback((uintptr)runtime·getcallerpc(&p), (uintptr)runtime·getcallersp(&p), 0, g); |
| runtime·printf("\n"); |
| g->m->traceback = 0; |
| runtime·unlock(&tracelock); |
| } |
| |
| void |
| runtime·tracegc(void) |
| { |
| runtime·lock(&tracelock); |
| g->m->traceback = 2; |
| runtime·printf("tracegc()\n"); |
| // running on m->g0 stack; show all non-g0 goroutines |
| runtime·tracebackothers(g); |
| runtime·printf("end tracegc\n"); |
| runtime·printf("\n"); |
| g->m->traceback = 0; |
| runtime·unlock(&tracelock); |
| } |