// 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.

package runtime

import (
	"unsafe"
)

// Malloc profiling.
// Patterned after tcmalloc's algorithms; shorter code.

// NOTE(rsc): Everything here could use cas if contention became an issue.
var proflock lock

// All memory allocations are local and do not escape outside of the profiler.
// The profiler is forbidden from referring to garbage-collected memory.

var (
	mbuckets *bucket // memory profile buckets
	bbuckets *bucket // blocking profile buckets
)

// MemProfile returns n, the number of records in the current memory profile.
// If len(p) >= n, MemProfile copies the profile into p and returns n, true.
// If len(p) < n, MemProfile does not change p and returns n, false.
//
// If inuseZero is true, the profile includes allocation records
// where r.AllocBytes > 0 but r.AllocBytes == r.FreeBytes.
// These are sites where memory was allocated, but it has all
// been released back to the runtime.
//
// Most clients should use the runtime/pprof package or
// the testing package's -test.memprofile flag instead
// of calling MemProfile directly.
func MemProfile(p []MemProfileRecord, inuseZero bool) (n int, ok bool) {
	golock(&proflock)
	clear := true
	for b := mbuckets; b != nil; b = b.allnext {
		if inuseZero || b.data.mp.alloc_bytes != b.data.mp.free_bytes {
			n++
		}
		if b.data.mp.allocs != 0 || b.data.mp.frees != 0 {
			clear = false
		}
	}
	if clear {
		// Absolutely no data, suggesting that a garbage collection
		// has not yet happened. In order to allow profiling when
		// garbage collection is disabled from the beginning of execution,
		// accumulate stats as if a GC just happened, and recount buckets.
		mprof_GC()
		mprof_GC()
		n = 0
		for b := mbuckets; b != nil; b = b.allnext {
			if inuseZero || b.data.mp.alloc_bytes != b.data.mp.free_bytes {
				n++
			}
		}
	}
	if n <= len(p) {
		ok = true
		idx := 0
		for b := mbuckets; b != nil; b = b.allnext {
			if inuseZero || b.data.mp.alloc_bytes != b.data.mp.free_bytes {
				record(&p[idx], b)
				idx++
			}
		}
	}
	gounlock(&proflock)
	return
}

func mprof_GC() {
	for b := mbuckets; b != nil; 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
	}
}

// Write b's data to r.
func record(r *MemProfileRecord, b *bucket) {
	r.AllocBytes = int64(b.data.mp.alloc_bytes)
	r.FreeBytes = int64(b.data.mp.free_bytes)
	r.AllocObjects = int64(b.data.mp.allocs)
	r.FreeObjects = int64(b.data.mp.frees)
	for i := 0; uintptr(i) < b.nstk && i < len(r.Stack0); i++ {
		r.Stack0[i] = *(*uintptr)(add(unsafe.Pointer(&b.stk), uintptr(i)*ptrSize))
	}
	for i := b.nstk; i < uintptr(len(r.Stack0)); i++ {
		r.Stack0[i] = 0
	}
}

// BlockProfile returns n, the number of records in the current blocking profile.
// If len(p) >= n, BlockProfile copies the profile into p and returns n, true.
// If len(p) < n, BlockProfile does not change p and returns n, false.
//
// Most clients should use the runtime/pprof package or
// the testing package's -test.blockprofile flag instead
// of calling BlockProfile directly.
func BlockProfile(p []BlockProfileRecord) (n int, ok bool) {
	golock(&proflock)
	for b := bbuckets; b != nil; b = b.allnext {
		n++
	}
	if n <= len(p) {
		ok = true
		idx := 0
		for b := bbuckets; b != nil; b = b.allnext {
			bp := (*bprofrecord)(unsafe.Pointer(&b.data))
			p[idx].Count = int64(bp.count)
			p[idx].Cycles = int64(bp.cycles)
			i := 0
			for uintptr(i) < b.nstk && i < len(p[idx].Stack0) {
				p[idx].Stack0[i] = *(*uintptr)(add(unsafe.Pointer(&b.stk), uintptr(i)*ptrSize))
				i++
			}
			for i < len(p[idx].Stack0) {
				p[idx].Stack0[i] = 0
				i++
			}
			idx++
		}
	}
	gounlock(&proflock)
	return
}

// Stack formats a stack trace of the calling goroutine into buf
// and returns the number of bytes written to buf.
// If all is true, Stack formats stack traces of all other goroutines
// into buf after the trace for the current goroutine.
func Stack(buf []byte, all bool) int {
	sp := getcallersp(unsafe.Pointer(&buf))
	pc := getcallerpc(unsafe.Pointer(&buf))
	mp := acquirem()
	gp := mp.curg
	if all {
		semacquire(&worldsema, false)
		mp.gcing = 1
		releasem(mp)
		stoptheworld()
		if mp != acquirem() {
			gothrow("Stack: rescheduled")
		}
	}

	n := 0
	if len(buf) > 0 {
		gp.writebuf = &buf[0]
		gp.writenbuf = int32(len(buf))
		goroutineheader(gp)
		traceback(pc, sp, 0, gp)
		if all {
			tracebackothers(gp)
		}
		n = len(buf) - int(gp.writenbuf)
		gp.writebuf = nil
		gp.writenbuf = 0
	}

	if all {
		mp.gcing = 0
		semrelease(&worldsema)
		starttheworld()
	}
	releasem(mp)
	return n
}

// ThreadCreateProfile returns n, the number of records in the thread creation profile.
// If len(p) >= n, ThreadCreateProfile copies the profile into p and returns n, true.
// If len(p) < n, ThreadCreateProfile does not change p and returns n, false.
//
// Most clients should use the runtime/pprof package instead
// of calling ThreadCreateProfile directly.
func ThreadCreateProfile(p []StackRecord) (n int, ok bool) {
	first := (*m)(atomicloadp(unsafe.Pointer(&allm)))
	for mp := first; mp != nil; mp = mp.alllink {
		n++
	}
	if n <= len(p) {
		ok = true
		i := 0
		for mp := first; mp != nil; mp = mp.alllink {
			for s := range mp.createstack {
				p[i].Stack0[s] = uintptr(mp.createstack[s])
			}
			i++
		}
	}
	return
}