/* CACHE.C    (c)Copyright Greg Smith, 2002-2009                     */
/*            Dynamic cache manager for multi-threaded applications  */

//FIXME ?? Dynamic resizing is disabled

// $Id: cache.c 5125 2009-01-23 12:01:44Z bernard $
//
// $Log$
// Revision 1.16  2007/06/23 00:04:03  ivan
// Update copyright notices to include current year (2007)
//
// Revision 1.15  2006/12/08 09:43:16  jj
// Add CVS message log
//

#include "hstdinc.h"

#define _CACHE_C_
#define _HDASD_DLL_

#include "hercules.h"

static CACHEBLK cacheblk[CACHE_MAX_INDEX];

/*-------------------------------------------------------------------*/
/* Public functions                                                  */
/*-------------------------------------------------------------------*/
int cache_nbr (int ix)
{
    if (cache_check_ix(ix)) return -1;
    return cacheblk[ix].nbr;
}

int cache_busy (int ix)
{
    if (cache_check_ix(ix)) return -1;
    return cacheblk[ix].busy;
}

int cache_empty (int ix)
{
    if (cache_check_ix(ix)) return -1;
    return cacheblk[ix].empty;
}

int cache_waiters (int ix)
{
    if (cache_check_ix(ix)) return -1;
    return cacheblk[ix].waiters;
}

long long cache_size (int ix)
{
    if (cache_check_ix(ix)) return -1;
    return cacheblk[ix].size;
}

long long cache_hits (int ix)
{
    if (cache_check_ix(ix)) return -1;
    return cacheblk[ix].hits;
}

long long cache_misses (int ix)
{
    if (cache_check_ix(ix)) return -1;
    return cacheblk[ix].misses;
}

int cache_busy_percent (int ix)
{
    if (cache_check_ix(ix)) return -1;
    return (cacheblk[ix].busy * 100) / cacheblk[ix].nbr;
}

int cache_empty_percent (int ix)
{
    if (cache_check_ix(ix)) return -1;
    return (cacheblk[ix].empty * 100) / cacheblk[ix].nbr;
}

int cache_hit_percent (int ix)
{
    long long total;
    if (cache_check_ix(ix)) return -1;
    total = cacheblk[ix].hits + cacheblk[ix].misses;
    if (total == 0) return -1;
    return (int)((cacheblk[ix].hits * 100) / total);
}

int cache_lookup (int ix, U64 key, int *o)
{
    int i,p;
    if (o) *o = -1;
    if (cache_check_ix(ix)) return -1;
    /* `p' is the preferred index */
    p = (int)(key % cacheblk[ix].nbr);
    if (cacheblk[ix].cache[p].key == key) {
        i = p;
        cacheblk[ix].fasthits++;
    }
    else {
        if (cache_isbusy(ix, p) || cacheblk[ix].age - cacheblk[ix].cache[p].age < 20)
            p = -2;
        for (i = 0; i < cacheblk[ix].nbr; i++) {
            if (cacheblk[ix].cache[i].key == key) break;
            if (o && !cache_isbusy(ix, i)
             && (*o < 0 || i == p || cacheblk[ix].cache[i].age < cacheblk[ix].cache[*o].age))
                if (*o != p) *o = i;
        }
    }
    if (i >= cacheblk[ix].nbr) {
        i = -1;
        cacheblk[ix].misses++;
    }
    else
        cacheblk[ix].hits++;

    if (i < 0 && o && *o < 0) cache_adjust(ix,1);
    else cache_adjust(ix, 0);
    return i;
}

int cache_scan (int ix, CACHE_SCAN_RTN rtn, void *data)
{
int      i;                             /* Cache index               */
int      rc;                            /* Return code               */
int      answer = -1;                   /* Answer from routine       */

    if (cache_check_ix(ix)) return -1;
    for (i = 0; i < cacheblk[ix].nbr; i++) {
        rc = (rtn)(&answer, ix, i, data);
        if (rc != 0) break;
    }
    return answer;
}

int cache_lock(int ix)
{
    if (cache_check_cache(ix)) return -1;
    obtain_lock(&cacheblk[ix].lock);
    return 0;
}

int cache_unlock(int ix)
{
    if (cache_check_ix(ix)) return -1;
    release_lock(&cacheblk[ix].lock);
    if (cacheblk[ix].empty == cacheblk[ix].nbr)
        cache_destroy(ix);
    return 0;
}

int cache_wait(int ix)
{
    struct timeval  now;
    struct timespec tm;

    if (cache_check_ix(ix)) return -1;
    if (cacheblk[ix].busy < cacheblk[ix].nbr)
        return 0;
    if (cache_adjust(ix, 1))
        return 0;
    gettimeofday (&now, NULL);
    tm.tv_sec = now.tv_sec;
    tm.tv_nsec = (now.tv_usec + CACHE_WAITTIME) * 1000;
    tm.tv_sec += tm.tv_nsec / 1000000000;
    tm.tv_nsec = tm.tv_nsec % 1000000000;
    cacheblk[ix].waiters++; cacheblk[ix].waits++;
#if 0
    timed_wait_condition(&cacheblk[ix].waitcond, &cacheblk[ix].lock, &tm);
#else
    wait_condition(&cacheblk[ix].waitcond, &cacheblk[ix].lock);
#endif
    cacheblk[ix].waiters--;
    return 0;
}

U64 cache_getkey(int ix, int i)
{
    if (cache_check(ix,i)) return (U64)-1;
    return cacheblk[ix].cache[i].key;
}

U64 cache_setkey(int ix, int i, U64 key)
{
    U64 oldkey;
    int empty;

    if (cache_check(ix,i)) return (U64)-1;
    empty = cache_isempty(ix, i);
    oldkey = cacheblk[ix].cache[i].key;
    cacheblk[ix].cache[i].key = key;
    if (empty && !cache_isempty(ix, i))
        cacheblk[ix].empty--;
    else if (!empty && cache_isempty(ix, i))
        cacheblk[ix].empty++;
    return oldkey;
}

U32 cache_getflag(int ix, int i)
{
    if (cache_check(ix,i)) return (U32)-1;
    return cacheblk[ix].cache[i].flag;
}

U32 cache_setflag(int ix, int i, U32 andbits, U32 orbits)
{
    U32 oldflags;
    int empty;
    int busy;

    if (cache_check(ix,i)) return (U32)-1;

    empty = cache_isempty(ix, i);
    busy = cache_isbusy(ix, i);
    oldflags = cacheblk[ix].cache[i].flag;

    cacheblk[ix].cache[i].flag &= andbits;
    cacheblk[ix].cache[i].flag |= orbits;

    if (!cache_isbusy(ix, i) && cacheblk[ix].waiters > 0)
        signal_condition(&cacheblk[ix].waitcond);
    if (busy && !cache_isbusy(ix, i))
        cacheblk[ix].busy--;
    else if (!busy && cache_isbusy(ix, i))
        cacheblk[ix].busy++;
    if (empty && !cache_isempty(ix, i))
        cacheblk[ix].empty--;
    else if (!empty && cache_isempty(ix, i))
        cacheblk[ix].empty++;
    return oldflags;
}

U64 cache_getage(int ix, int i)
{
    if (cache_check(ix,i)) return (U64)-1;
    return cacheblk[ix].cache[i].age;
}

U64 cache_setage(int ix, int i)
{
    U64 oldage;
    int empty;

    if (cache_check(ix,i)) return (U64)-1;
    empty = cache_isempty(ix, i);
    oldage = cacheblk[ix].cache[i].age;
    cacheblk[ix].cache[i].age = ++cacheblk[ix].age;
    if (empty) cacheblk[ix].empty--;
    return oldage;
}

void *cache_getbuf(int ix, int i, int len)
{
    if (cache_check(ix,i)) return NULL;
    if (len > 0
     && cacheblk[ix].cache[i].buf != NULL
     && cacheblk[ix].cache[i].len < len) {
        cacheblk[ix].size -= cacheblk[ix].cache[i].len;
        free (cacheblk[ix].cache[i].buf);
        cacheblk[ix].cache[i].buf = NULL;
        cacheblk[ix].cache[i].len = 0;
    }
    if (cacheblk[ix].cache[i].buf == NULL && len > 0)
        cache_allocbuf (ix, i, len);
    return cacheblk[ix].cache[i].buf;
}

void *cache_setbuf(int ix, int i, void *buf, int len)
{
    void *oldbuf;
    if (cache_check(ix,i)) return NULL;
    oldbuf = cacheblk[ix].cache[i].buf;
    cacheblk[ix].size -= cacheblk[ix].cache[i].len;
    cacheblk[ix].cache[i].buf = buf;
    cacheblk[ix].cache[i].len = len;
    cacheblk[ix].size += len;
    return oldbuf;
}

int cache_getlen(int ix, int i)
{
    if (cache_check(ix,i)) return -1;
    return cacheblk[ix].cache[i].len;
}

int cache_getval(int ix, int i)
{
    if (cache_check(ix,i)) return -1;
    return cacheblk[ix].cache[i].value;
}

int cache_setval(int ix, int i, int val)
{
    int rc;
    if (cache_check(ix,i)) return -1;
    rc = cacheblk[ix].cache[i].value;
    cacheblk[ix].cache[i].value = val;
    return rc;
}

int cache_release(int ix, int i, int flag)
{
    void *buf;
    int   len;
    int   empty;
    int   busy;

    if (cache_check(ix,i)) return -1;

    empty = cache_isempty(ix, i);
    busy = cache_isbusy(ix, i);

    buf = cacheblk[ix].cache[i].buf;
    len = cacheblk[ix].cache[i].len;

    memset (&cacheblk[ix].cache[i], 0, sizeof(CACHE));

    if ((flag & CACHE_FREEBUF) && buf != NULL) {
        free (buf);
        cacheblk[ix].size -= len;
        buf = NULL;
        len = 0;
    }

    cacheblk[ix].cache[i].buf = buf;
    cacheblk[ix].cache[i].len = len;

    if (cacheblk[ix].waiters > 0)
        signal_condition(&cacheblk[ix].waitcond);

    if (!empty) cacheblk[ix].empty++;
    if (busy) cacheblk[ix].busy--;

    return 0;
}

DLL_EXPORT int cache_cmd(int argc, char *argv[], char *cmdline)
{
    int ix, i;

    UNREFERENCED(cmdline);
    UNREFERENCED(argc);
    UNREFERENCED(argv);

    for (ix = 0; ix < CACHE_MAX_INDEX; ix++) {
        if (cacheblk[ix].magic != CACHE_MAGIC) {
            logmsg ("cache[%d] ....... not created\n", ix);
            continue;
        }
        logmsg ("\n"
                "cache............ %10d\n"
                "nbr ............. %10d\n"
                "busy ............ %10d\n"
                "busy%% ........... %10d\n"
                "empty ........... %10d\n"
                "waiters ......... %10d\n"
                "waits ........... %10d\n"
                "buf size ........ %10" I64_FMT "d\n"
                "hits ............ %10" I64_FMT "d\n"
                "fast hits ....... %10" I64_FMT "d\n"
                "misses .......... %10" I64_FMT "d\n"
                "hit%% ............ %10d\n"
                "age ............. %10" I64_FMT "d\n"
                "last adjusted ... %s"
                "last wait ....... %s"
                "adjustments ..... %10d\n",
          ix, cacheblk[ix].nbr, cacheblk[ix].busy, cache_busy_percent(ix),
          cacheblk[ix].empty, cacheblk[ix].waiters, cacheblk[ix].waits,
          cacheblk[ix].size, cacheblk[ix].hits, cacheblk[ix].fasthits,
          cacheblk[ix].misses, cache_hit_percent(ix), cacheblk[ix].age,
          ctime(&cacheblk[ix].atime), ctime(&cacheblk[ix].wtime),
          cacheblk[ix].adjusts);
        if (argc > 1)
          for (i = 0; i < cacheblk[ix].nbr; i++)
            logmsg ("[%4d] %16.16" I64_FMT "x %8.8x %10p %6d %10" I64_FMT "d\n",
              i, cacheblk[ix].cache[i].key, cacheblk[ix].cache[i].flag,
              cacheblk[ix].cache[i].buf, cacheblk[ix].cache[i].len,
              cacheblk[ix].cache[i].age);
    }
    return 0;
}

/*-------------------------------------------------------------------*/
/* Private functions                                                 */
/*-------------------------------------------------------------------*/
static int cache_create (int ix)
{
    cache_destroy (ix);
    cacheblk[ix].magic = CACHE_MAGIC;
//FIXME See the note in cache.h about CACHE_DEFAULT_L2_NBR
    cacheblk[ix].nbr = ix != CACHE_L2 ? CACHE_DEFAULT_NBR : CACHE_DEFAULT_L2_NBR;
    cacheblk[ix].empty = cacheblk[ix].nbr;
    initialize_lock (&cacheblk[ix].lock);
    initialize_condition (&cacheblk[ix].waitcond);
    cacheblk[ix].cache = calloc (cacheblk[ix].nbr, sizeof(CACHE));
    if (cacheblk[ix].cache == NULL) {
        logmsg (_("HHCCH001E calloc failed cache[%d] size %d: %s\n"),
                ix, cacheblk[ix].nbr * sizeof(CACHE), strerror(errno));
        return -1;
    }
    return 0;
}

static int cache_destroy (int ix)
{
    int i;
    if (cacheblk[ix].magic == CACHE_MAGIC) {
        destroy_lock (&cacheblk[ix].lock);
        destroy_condition (&cacheblk[ix].waitcond);
        if (cacheblk[ix].cache) {
            for (i = 0; i < cacheblk[ix].nbr; i++)
                cache_release(ix, i, CACHE_FREEBUF);
            free (cacheblk[ix].cache);
        }
    }
    memset(&cacheblk[ix], 0, sizeof(CACHEBLK));
    return 0;
}

static int cache_check_ix(int ix)
{
    if (ix < 0 || ix >= CACHE_MAX_INDEX) return -1;
    return 0;
}

static int cache_check_cache(int ix)
{
    if (cache_check_ix(ix)
     || (cacheblk[ix].magic != CACHE_MAGIC && cache_create(ix)))
        return -1;
    return 0;
}

static int cache_check(int ix, int i)
{
    if (cache_check_ix(ix) || i < 0 || i >= cacheblk[ix].nbr)
        return -1;
    return 0;
}

static int cache_isbusy(int ix, int i)
{
    return ((cacheblk[ix].cache[i].flag & CACHE_BUSY) != 0);
}

static int cache_isempty(int ix, int i)
{
    return (cacheblk[ix].cache[i].key  == 0
         && cacheblk[ix].cache[i].flag == 0
         && cacheblk[ix].cache[i].age  == 0);
}

static int cache_adjust(int ix, int n)
{
#if 0
    time_t now;
    int    busypct, hitpct, nbr, empty, sz;

    now = time(NULL);
    busypct = cache_busy_percent(ix);
    hitpct = cache_hit_percent(ix);
    nbr = cacheblk[ix].nbr;
    empty = cache_empty(ix);
    sz = cacheblk[ix].size;

    if (n == 0) {
        /* Normal adjustments */
        if (now - cacheblk[ix].atime < CACHE_ADJUST_INTERVAL) return 0;
        cacheblk[ix].atime = now;

        /* Increase cache if a lot of busy entries */
        if (((nbr <= CACHE_ADJUST_NUMBER || sz < CACHE_ADJUST_SIZE) && busypct >= CACHE_ADJUST_BUSY1)
         || busypct > CACHE_ADJUST_BUSY2)
            return cache_resize(ix, CACHE_ADJUST_RESIZE);

        /* Decrease cache if too many empty entries */
        if (nbr > CACHE_ADJUST_NUMBER && empty >= CACHE_ADJUST_EMPTY)
            return cache_resize(ix, -CACHE_ADJUST_RESIZE);

        /* Increase cache if hit percentage is too low */
        if (hitpct > 0) {
            if ((nbr <= CACHE_ADJUST_NUMBER && hitpct < CACHE_ADJUST_HIT1)
             || hitpct < CACHE_ADJUST_HIT2)
                return cache_resize(ix, CACHE_ADJUST_RESIZE);
        }

        /* Decrease cache if hit percentage is ok and not many busy */
        if (hitpct >= CACHE_ADJUST_HIT3 && busypct <= CACHE_ADJUST_BUSY3
         && cacheblk[ix].size >= CACHE_ADJUST_SIZE)
            return cache_resize(ix, -CACHE_ADJUST_RESIZE);
    } else {
        /* All cache entries are busy */
        if (nbr <= CACHE_ADJUST_NUMBER)
            return cache_resize(ix, CACHE_ADJUST_RESIZE);

        /* Increase cache if previous wait within this interval */
        if (now - cacheblk[ix].wtime <= CACHE_ADJUST_WAITTIME) {
            return cache_resize(ix, CACHE_ADJUST_RESIZE);
        }
        cacheblk[ix].wtime = now;
    }
#else
    UNREFERENCED(ix);
    UNREFERENCED(n);
#endif
    return 0;
}

#if 0
static int cache_resize (int ix, int n)
{
    CACHE *cache;
    int    i;

    if (n == 0) return 0;
    else if (n > 0) {
        /* Increase cache size */
        cache = realloc (cacheblk[ix].cache, (cacheblk[ix].nbr + n) * sizeof(CACHE));
        if (cache == NULL) {
            logmsg (_("HHCCH002W realloc increase failed cache[%d] size %d: %s\n"),
               ix, (cacheblk[ix].nbr + n) * sizeof(CACHE), strerror(errno));
            return 0;
        }
        cacheblk[ix].cache = cache;
        for (i = cacheblk[ix].nbr; i < cacheblk[ix].nbr +n; i++)
            memset(&cacheblk[ix].cache[i], 0, sizeof(CACHE));
        cacheblk[ix].nbr += n;
        cacheblk[ix].empty += n;
        cacheblk[ix].adjusts++;
    } else if (n < 0) {
        /* Decrease cache size */
        for (i = cacheblk[ix].nbr - 1; i >= cacheblk[ix].nbr + n; i--)
            if (cache_isbusy(ix, i)) break;
            else cache_release(ix, i, CACHE_FREEBUF);
        n = cacheblk[ix].nbr - i + 1;
        if (n == 0) return 0;
        cache = realloc (cacheblk[ix].cache, (cacheblk[ix].nbr - n) * sizeof(CACHE));
        if (cache == NULL) {
            logmsg (_("HHCCH003W realloc decrease failed cache[%d] size %d: %s\n"),
               ix, (cacheblk[ix].nbr - n) * sizeof(CACHE), strerror(errno));
            return 0;
        }
        cacheblk[ix].cache = cache;
        cacheblk[ix].nbr -= n;
        cacheblk[ix].empty -= n;
        cacheblk[ix].adjusts++;
    }
    return 1;
}
#endif

static void cache_allocbuf(int ix, int i, int len)
{
    cacheblk[ix].cache[i].buf = calloc (len, 1);
    if (cacheblk[ix].cache[i].buf == NULL) {
        logmsg (_("HHCCH004W buf calloc failed cache[%d] size %d: %s\n"),
                ix, len, strerror(errno));
        logmsg (_("HHCCH005W releasing inactive buffer space\n"));
        for (i = 0; i < cacheblk[ix].nbr; i++)
            if (!cache_isbusy(ix, i)) cache_release(ix, i, CACHE_FREEBUF);
        cacheblk[ix].cache[i].buf = calloc (len, 1);
        if (cacheblk[ix].cache[i].buf == NULL) {
            logmsg (_("HHCCH006E Unable to calloc buf cache[%d] size %d: %s\n"),
                    ix, len, strerror(errno));
            return;
        }
    }
    cacheblk[ix].cache[i].len = len;
    cacheblk[ix].size += len;
}
