#include <sys/time.h>
#include <sys/gmon.h>
#include <sys/mman.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <unistd.h>
struct gmonparam _gmonparam = { GMON_PROF_OFF };
#include <pthread.h>
#include <thread_private.h>
static SLIST_HEAD(, gmonparam) _gmonfree = SLIST_HEAD_INITIALIZER(_gmonfree);
static SLIST_HEAD(, gmonparam) _gmoninuse = SLIST_HEAD_INITIALIZER(_gmoninuse);
_THREAD_PRIVATE_MUTEX(_gmonlock);
static pthread_key_t _gmonkey;
static int s_scale;
#define SCALE_1_TO_1 0x10000L
#define ERR(s) write(STDERR_FILENO, s, sizeof(s))
PROTO_NORMAL(_gmon_alloc);
PROTO_NORMAL(moncontrol);
#define PAGESIZE (1UL << _MAX_PAGE_SHIFT)
#define PAGEMASK (PAGESIZE - 1)
#define PAGEROUND(x) (((x) + (PAGEMASK)) & ~PAGEMASK)
static void
_gmon_destructor(void *arg)
{
struct gmonparam *p = arg;
_THREAD_PRIVATE_MUTEX_LOCK(_gmonlock);
SLIST_REMOVE(&_gmoninuse, p, gmonparam, list);
SLIST_INSERT_HEAD(&_gmonfree, p, list);
_THREAD_PRIVATE_MUTEX_UNLOCK(_gmonlock);
pthread_setspecific(_gmonkey, NULL);
}
void
_monstartup(u_long lowpc, u_long highpc)
{
int o;
struct gmonparam *p = &_gmonparam;
char *profdir = NULL;
char *a;
p->lowpc = ROUNDDOWN(lowpc, HISTFRACTION * sizeof(HISTCOUNTER));
p->highpc = ROUNDUP(highpc, HISTFRACTION * sizeof(HISTCOUNTER));
p->textsize = p->highpc - p->lowpc;
p->kcountsize = p->textsize / HISTFRACTION;
p->hashfraction = HASHFRACTION;
p->fromssize = p->textsize / p->hashfraction;
p->tolimit = p->textsize * ARCDENSITY / 100;
if (p->tolimit < MINARCS)
p->tolimit = MINARCS;
else if (p->tolimit > MAXARCS)
p->tolimit = MAXARCS;
p->tossize = p->tolimit * sizeof(struct tostruct);
p->outbuflen = sizeof(struct gmonhdr) + p->kcountsize +
MAXARCS * sizeof(struct rawarc);
a = mmap(NULL,
PAGEROUND(p->outbuflen) + _ALIGN(p->fromssize) + _ALIGN(p->tossize),
PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
if (a == MAP_FAILED) {
ERR("_monstartup: out of memory\n");
return;
}
p->outbuf = a;
p->kcount = (void *)(a + sizeof(struct gmonhdr));
p->rawarcs = (void *)(a + sizeof(struct gmonhdr) + p->kcountsize);
p->froms = (void *)(a + PAGEROUND(p->outbuflen));
p->tos = (void *)(a + PAGEROUND(p->outbuflen) + _ALIGN(p->fromssize));
o = p->highpc - p->lowpc;
if (p->kcountsize < o) {
#ifndef notdef
s_scale = ((float)p->kcountsize / o ) * SCALE_1_TO_1;
#else
int quot = o / p->kcountsize;
if (quot >= 0x10000)
s_scale = 1;
else if (quot >= 0x100)
s_scale = 0x10000 / quot;
else if (o >= 0x800000)
s_scale = 0x1000000 / (o / (p->kcountsize >> 8));
else
s_scale = 0x1000000 / ((o << 8) / p->kcountsize);
#endif
} else
s_scale = SCALE_1_TO_1;
if (issetugid() == 0)
profdir = getenv("PROFDIR");
if (profdir)
p->dirfd = open(profdir, O_DIRECTORY|O_CLOEXEC, 0);
else
p->dirfd = -1;
pthread_key_create(&_gmonkey, _gmon_destructor);
moncontrol(1);
if (p->dirfd != -1)
close(p->dirfd);
}
struct gmonparam *
_gmon_alloc(void)
{
struct gmonparam *p;
char *a;
if (_gmonparam.state == GMON_PROF_OFF)
return NULL;
_THREAD_PRIVATE_MUTEX_LOCK(_gmonlock);
p = SLIST_FIRST(&_gmonfree);
if (p != NULL) {
SLIST_REMOVE_HEAD(&_gmonfree, list);
SLIST_INSERT_HEAD(&_gmoninuse, p, list);
} else {
_THREAD_PRIVATE_MUTEX_UNLOCK(_gmonlock);
a = mmap(NULL,
_ALIGN(sizeof(*p)) + _ALIGN(_gmonparam.fromssize) +
_ALIGN(_gmonparam.tossize),
PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
if (a == MAP_FAILED) {
pthread_setspecific(_gmonkey, NULL);
ERR("_gmon_alloc: out of memory\n");
return NULL;
}
p = (struct gmonparam *)a;
*p = _gmonparam;
p->kcount = NULL;
p->kcountsize = 0;
p->froms = (void *)(a + _ALIGN(sizeof(*p)));
p->tos = (void *)(a + _ALIGN(sizeof(*p)) +
_ALIGN(_gmonparam.fromssize));
_THREAD_PRIVATE_MUTEX_LOCK(_gmonlock);
SLIST_INSERT_HEAD(&_gmoninuse, p, list);
}
_THREAD_PRIVATE_MUTEX_UNLOCK(_gmonlock);
pthread_setspecific(_gmonkey, p);
return p;
}
DEF_WEAK(_gmon_alloc);
static void
_gmon_merge_two(struct gmonparam *p, struct gmonparam *q)
{
u_long fromindex, selfpc, endfrom;
u_short *frompcindex, qtoindex, toindex;
long count;
struct tostruct *top;
endfrom = (q->fromssize / sizeof(*q->froms));
for (fromindex = 0; fromindex < endfrom; fromindex++) {
if (q->froms[fromindex] == 0)
continue;
for (qtoindex = q->froms[fromindex]; qtoindex != 0;
qtoindex = q->tos[qtoindex].link) {
selfpc = q->tos[qtoindex].selfpc;
count = q->tos[qtoindex].count;
frompcindex = &p->froms[fromindex];
toindex = *frompcindex;
if (toindex == 0) {
toindex = ++p->tos[0].link;
if (toindex >= p->tolimit)
goto overflow;
*frompcindex = (u_short)toindex;
top = &p->tos[(size_t)toindex];
top->selfpc = selfpc;
top->count = count;
top->link = 0;
goto done;
}
top = &p->tos[(size_t)toindex];
if (top->selfpc == selfpc) {
top->count+= count;
goto done;
}
for (; ; ) {
if (top->link == 0) {
toindex = ++p->tos[0].link;
if (toindex >= p->tolimit)
goto overflow;
top = &p->tos[(size_t)toindex];
top->selfpc = selfpc;
top->count = count;
top->link = *frompcindex;
*frompcindex = (u_short)toindex;
goto done;
}
top = &p->tos[top->link];
if (top->selfpc == selfpc) {
top->count += count;
goto done;
}
}
done: ;
}
}
overflow: ;
}
static void
_gmon_merge(void)
{
struct gmonparam *q;
_THREAD_PRIVATE_MUTEX_LOCK(_gmonlock);
SLIST_FOREACH(q, &_gmonfree, list)
_gmon_merge_two(&_gmonparam, q);
SLIST_FOREACH(q, &_gmoninuse, list) {
q->state = GMON_PROF_OFF;
_gmon_merge_two(&_gmonparam, q);
}
_THREAD_PRIVATE_MUTEX_UNLOCK(_gmonlock);
}
void
_mcleanup(void)
{
int fromindex, endfrom, totarc = 0, toindex;
u_long frompc;
struct rawarc rawarc;
struct gmonparam *p = &_gmonparam;
struct gmonhdr *hdr;
struct clockinfo clockinfo;
const int mib[2] = { CTL_KERN, KERN_CLOCKRATE };
size_t size;
#ifdef DEBUG
int log, len;
char dbuf[200];
#endif
if (p->state == GMON_PROF_ERROR)
ERR("_mcleanup: tos overflow\n");
size = sizeof(clockinfo);
if (sysctl(mib, 2, &clockinfo, &size, NULL, 0) == -1) {
clockinfo.profhz = 0;
} else if (clockinfo.profhz == 0) {
clockinfo.profhz = clockinfo.hz;
}
moncontrol(0);
#ifdef DEBUG
log = open("gmon.log", O_CREAT|O_TRUNC|O_WRONLY|O_CLOEXEC, 0664);
if (log == -1) {
perror("mcount: gmon.log");
close(fd);
return;
}
snprintf(dbuf, sizeof dbuf, "[mcleanup1] kcount 0x%x ssiz %d\n",
p->kcount, p->kcountsize);
write(log, dbuf, strlen(dbuf));
#endif
_gmon_merge();
hdr = (struct gmonhdr *)p->outbuf;
hdr->lpc = p->lowpc;
hdr->hpc = p->highpc;
hdr->ncnt = p->kcountsize + sizeof(*hdr);
hdr->version = GMONVERSION;
hdr->profrate = clockinfo.profhz;
endfrom = p->fromssize / sizeof(*p->froms);
for (fromindex = 0; fromindex < endfrom; fromindex++) {
if (p->froms[fromindex] == 0)
continue;
frompc = p->lowpc;
frompc += fromindex * p->hashfraction * sizeof(*p->froms);
for (toindex = p->froms[fromindex]; toindex != 0;
toindex = p->tos[toindex].link) {
#ifdef DEBUG
(void) snprintf(dbuf, sizeof dbuf,
"[mcleanup2] frompc 0x%x selfpc 0x%x count %d\n" ,
frompc, p->tos[toindex].selfpc,
p->tos[toindex].count);
write(log, dbuf, strlen(dbuf));
#endif
rawarc.raw_frompc = frompc;
rawarc.raw_selfpc = p->tos[toindex].selfpc;
rawarc.raw_count = p->tos[toindex].count;
memcpy(&p->rawarcs[totarc * sizeof(struct rawarc)],
&rawarc, sizeof rawarc);
totarc++;
if (totarc >= MAXARCS)
goto donearcs;
}
}
donearcs:
hdr->totarc = totarc * sizeof(struct rawarc);
}
void
moncontrol(int mode)
{
struct gmonparam *p = &_gmonparam;
if (mode) {
profil(p->outbuf, p->outbuflen, p->kcountsize, p->lowpc,
s_scale, p->dirfd);
p->state = GMON_PROF_ON;
} else {
profil(NULL, 0, 0, 0, 0, -1);
p->state = GMON_PROF_OFF;
}
}
DEF_WEAK(moncontrol);