Coverage Report

Created: 2025-10-28 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/netcdf-c/libnczarr/zxcache.c
Line
Count
Source
1
/* Copyright 2018, University Corporation for Atmospheric
2
 * Research. See COPYRIGHT file for copying and redistribution
3
 * conditions. */
4
5
/**
6
 * @file @internal The functions which control NCZ
7
 * caching. These caching controls allow the user to change the cache
8
 * sizes of ZARR before opening files.
9
 *
10
 * @author Dennis Heimbigner, Ed Hartnett
11
 */
12
13
#include "zincludes.h"
14
#include "zcache.h"
15
#include "ncxcache.h"
16
#include "zfilter.h"
17
#include <stddef.h>
18
19
#undef DEBUG
20
21
#undef FLUSH
22
23
0
#define LEAFLEN 32
24
25
0
#define USEPARAMSIZE 0xffffffffffffffff
26
27
/* Forward */
28
static int get_chunk(NCZChunkCache* cache, NCZCacheEntry* entry);
29
static int put_chunk(NCZChunkCache* cache, NCZCacheEntry*);
30
static int verifycache(NCZChunkCache* cache);
31
static int flushcache(NCZChunkCache* cache);
32
static int constraincache(NCZChunkCache* cache, size64_t needed);
33
34
static void
35
setmodified(NCZCacheEntry* e, int tf)
36
0
{
37
0
    e->modified = tf;
38
0
}
39
40
/**************************************************/
41
/* Dispatch table per-var cache functions */
42
43
/**
44
 * @internal Set chunk cache size for a variable. This is the internal
45
 * function called by nc_set_var_chunk_cache().
46
 *
47
 * @param ncid File ID.
48
 * @param varid Variable ID.
49
 * @param size Size in bytes to set cache.
50
 * @param nelems # of entries in cache
51
 * @param preemption Controls cache swapping.
52
 *
53
 * @returns ::NC_NOERR No error.
54
 * @returns ::NC_EBADID Bad ncid.
55
 * @returns ::NC_ENOTVAR Invalid variable ID.
56
 * @returns ::NC_ESTRICTNC3 Attempting netcdf-4 operation on strict
57
 * nc3 netcdf-4 file.
58
 * @returns ::NC_EINVAL Invalid input.
59
 * @returns ::NC_EHDFERR HDF5 error.
60
 * @author Ed Hartnett
61
 */
62
int
63
NCZ_set_var_chunk_cache(int ncid, int varid, size_t cachesize, size_t nelems, float preemption)
64
0
{
65
0
    NC_GRP_INFO_T *grp;
66
0
    NC_FILE_INFO_T *h5;
67
0
    NC_VAR_INFO_T *var;
68
0
    NCZ_VAR_INFO_T *zvar;
69
0
    int retval = NC_NOERR;
70
71
    /* Check input for validity. */
72
0
    if (preemption < 0 || preemption > 1)
73
0
        {retval = NC_EINVAL; goto done;}
74
75
    /* Find info for this file and group, and set pointer to each. */
76
0
    if ((retval = nc4_find_nc_grp_h5(ncid, NULL, &grp, &h5)))
77
0
        goto done;
78
0
    assert(grp && h5);
79
80
    /* Find the var. */
81
0
    if (!(var = (NC_VAR_INFO_T *)ncindexith(grp->vars, (size_t)varid)))
82
0
        {retval = NC_ENOTVAR; goto done;}
83
0
    assert(var && var->hdr.id == varid);
84
85
0
    zvar = (NCZ_VAR_INFO_T*)var->format_var_info;
86
0
    assert(zvar != NULL && zvar->cache != NULL);
87
88
    /* Set the values. */
89
0
    var->chunkcache.size = cachesize;
90
0
    var->chunkcache.nelems = nelems;
91
0
    var->chunkcache.preemption = preemption;
92
93
    /* Fix up cache */
94
0
    if((retval = NCZ_adjust_var_cache(var))) goto done;
95
0
done:
96
0
    return retval;
97
0
}
98
99
/**
100
 * @internal Adjust the chunk cache of a var for better
101
 * performance.
102
 *
103
 * @note For contiguous and compact storage vars, or when parallel I/O
104
 * is in use, this function will do nothing and return ::NC_NOERR;
105
 *
106
 * @param grp Pointer to group info struct.
107
 * @param var Pointer to var info struct.
108
 *
109
 * @return ::NC_NOERR No error.
110
 * @author Ed Hartnett
111
 */
112
int
113
NCZ_adjust_var_cache(NC_VAR_INFO_T *var)
114
0
{
115
0
    int stat = NC_NOERR;
116
0
    NCZ_VAR_INFO_T* zvar = (NCZ_VAR_INFO_T*)var->format_var_info;
117
0
    NCZChunkCache* zcache = NULL;
118
119
0
    zcache = zvar->cache;
120
0
    if(zcache->valid) goto done;
121
122
#ifdef DEBUG
123
fprintf(stderr,"xxx: adjusting cache for: %s\n",var->hdr.name);
124
#endif
125
126
    /* completely empty the cache */
127
0
    flushcache(zcache);
128
129
    /* Reclaim any existing fill_chunk */
130
0
    if((stat = NCZ_reclaim_fill_chunk(zcache))) goto done;
131
    /* Reset the parameters */
132
0
    zvar->cache->params.size = var->chunkcache.size;
133
0
    zvar->cache->params.nelems = var->chunkcache.nelems;
134
0
    zvar->cache->params.preemption = var->chunkcache.preemption;
135
#ifdef DEBUG
136
    fprintf(stderr,"%s.cache.adjust: size=%ld nelems=%ld\n",
137
        var->hdr.name,(unsigned long)zvar->cache->maxsize,(unsigned long)zvar->cache->maxentries);
138
#endif
139
    /* One more thing, adjust the chunksize and count*/
140
0
    zcache->chunksize = zvar->chunksize;
141
0
    zcache->chunkcount = 1;
142
0
    if(var->ndims > 0) {
143
0
  size_t i;
144
0
  for(i=0;i<var->ndims;i++) {
145
0
      zcache->chunkcount *= var->chunksizes[i];
146
0
        }
147
0
    }
148
0
    zcache->valid = 1;
149
0
done:
150
0
    return stat;
151
0
}
152
153
/**************************************************/
154
/**
155
 * Create a chunk cache object
156
 *
157
 * @param var containing var
158
 * @param entrysize Size in bytes of an entry
159
 * @param cachep return cache pointer
160
 *
161
 * @return ::NC_NOERR No error.
162
 * @return ::NC_EINVAL Bad preemption.
163
 * @author Dennis Heimbigner, Ed Hartnett
164
 */
165
int
166
NCZ_create_chunk_cache(NC_VAR_INFO_T* var, size64_t chunksize, char dimsep, NCZChunkCache** cachep)
167
0
{
168
0
    int stat = NC_NOERR;
169
0
    NCZChunkCache* cache = NULL;
170
0
    void* fill = NULL;
171
0
    NCZ_VAR_INFO_T* zvar = NULL;
172
  
173
0
    if(chunksize == 0) return NC_EINVAL;
174
175
0
    zvar = (NCZ_VAR_INFO_T*)var->format_var_info;
176
0
    if((cache = calloc(1,sizeof(NCZChunkCache))) == NULL)
177
0
  {stat = NC_ENOMEM; goto done;}
178
0
    cache->var = var;
179
0
    cache->ndims = var->ndims + zvar->scalar;
180
0
    cache->fillchunk = NULL;
181
0
    cache->chunksize = chunksize;
182
0
    cache->dimension_separator = dimsep;
183
0
    zvar->cache = cache;
184
185
0
    cache->chunkcount = 1;
186
0
    if(var->ndims > 0) {
187
0
  size_t i;
188
0
  for(i=0;i<var->ndims;i++) {
189
0
      cache->chunkcount *= var->chunksizes[i];
190
0
        }
191
0
    }
192
    
193
    /* Set default cache parameters */
194
0
    cache->params = NC_getglobalstate()->chunkcache;
195
196
#ifdef FLUSH
197
    cache->maxentries = 1;
198
#endif
199
200
#ifdef DEBUG
201
    fprintf(stderr,"%s.cache: nelems=%ld size=%ld\n",
202
        var->hdr.name,(unsigned long)cache->maxentries,(unsigned long)cache->maxsize);
203
#endif
204
0
    if((stat = ncxcachenew(LEAFLEN,&cache->xcache))) goto done;
205
0
    if((cache->mru = nclistnew()) == NULL)
206
0
  {stat = NC_ENOMEM; goto done;}
207
0
    nclistsetalloc(cache->mru,cache->params.nelems);
208
209
0
    if(cachep) {*cachep = cache; cache = NULL;}
210
0
done:
211
0
    nullfree(fill);
212
0
    NCZ_free_chunk_cache(cache);
213
0
    return THROW(stat);
214
0
}
215
216
static void
217
free_cache_entry(NCZChunkCache* cache, NCZCacheEntry* entry)
218
0
{
219
0
    if(entry) {
220
0
        int tid = cache->var->type_info->hdr.id;
221
0
  if(tid == NC_STRING && !entry->isfixedstring) {
222
0
            NC_reclaim_data(cache->var->container->nc4_info->controller,tid,entry->data,cache->chunkcount);
223
0
  }
224
0
  nullfree(entry->data);
225
0
  nullfree(entry->key.varkey);
226
0
  nullfree(entry->key.chunkkey);
227
0
  nullfree(entry);
228
0
    }
229
0
}
230
231
void
232
NCZ_free_chunk_cache(NCZChunkCache* cache)
233
0
{
234
0
    if(cache == NULL) return;
235
236
0
    ZTRACE(4,"cache.var=%s",cache->var->hdr.name);
237
238
    /* Iterate over the entries */
239
0
    while(nclistlength(cache->mru) > 0) {
240
0
  void* ptr;
241
0
        NCZCacheEntry* entry = nclistremove(cache->mru,0);
242
0
  (void)ncxcacheremove(cache->xcache,entry->hashkey,&ptr);
243
0
  assert(ptr == entry);
244
0
        free_cache_entry(cache,entry);
245
0
    }
246
#ifdef DEBUG
247
fprintf(stderr,"|cache.free|=%ld\n",nclistlength(cache->mru));
248
#endif
249
0
    ncxcachefree(cache->xcache);
250
0
    nclistfree(cache->mru);
251
0
    cache->mru = NULL;
252
0
    (void)NCZ_reclaim_fill_chunk(cache);
253
0
    nullfree(cache);
254
0
    (void)ZUNTRACE(NC_NOERR);
255
0
}
256
257
size64_t
258
NCZ_cache_entrysize(NCZChunkCache* cache)
259
0
{
260
0
    assert(cache);
261
0
    return cache->chunksize;
262
0
}
263
264
/* Return number of active entries in cache */
265
size64_t
266
NCZ_cache_size(NCZChunkCache* cache)
267
0
{
268
0
    assert(cache);
269
0
    return nclistlength(cache->mru);
270
0
}
271
272
int
273
NCZ_read_cache_chunk(NCZChunkCache* cache, const size64_t* indices, void** datap)
274
0
{
275
0
    int stat = NC_NOERR;
276
0
    int rank = cache->ndims;
277
0
    NCZCacheEntry* entry = NULL;
278
0
    ncexhashkey_t hkey = 0;
279
0
    int created = 0;
280
281
    /* the hash key */
282
0
    hkey = ncxcachekey(indices,sizeof(size64_t)*cache->ndims);
283
    /* See if already in cache */
284
0
    stat = ncxcachelookup(cache->xcache,hkey,(void**)&entry);
285
0
    switch(stat) {
286
0
    case NC_NOERR:
287
        /* Move to front of the lru */
288
0
        (void)ncxcachetouch(cache->xcache,hkey);
289
0
        break;
290
0
    case NC_ENOOBJECT: case NC_EEMPTY:
291
0
        entry = NULL; /* not found; */
292
0
  break;
293
0
    default: goto done;
294
0
    }
295
296
0
    if(entry == NULL) { /*!found*/
297
  /* Create a new entry */
298
0
  if((entry = calloc(1,sizeof(NCZCacheEntry)))==NULL)
299
0
      {stat = NC_ENOMEM; goto done;}
300
0
  memcpy(entry->indices,indices,(size_t)rank*sizeof(size64_t));
301
        /* Create the key for this cache */
302
0
        if((stat = NCZ_buildchunkpath(cache,indices,&entry->key))) goto done;
303
0
        entry->hashkey = hkey;
304
0
  assert(entry->data == NULL && entry->size == 0);
305
  /* Try to read the object from "disk"; might change size; will create if non-existent */
306
0
  if((stat=get_chunk(cache,entry))) goto done;
307
0
  assert(entry->data != NULL);
308
  /* Ensure cache constraints not violated; but do it before entry is added */
309
0
  if((stat=verifycache(cache))) goto done;
310
0
        nclistpush(cache->mru,entry);
311
0
  if((stat = ncxcacheinsert(cache->xcache,entry->hashkey,entry))) goto done;
312
0
    }
313
314
#ifdef DEBUG
315
fprintf(stderr,"|cache.read.lru|=%ld\n",nclistlength(cache->mru));
316
#endif
317
0
    if(datap) *datap = entry->data;
318
0
    entry = NULL;
319
    
320
0
done:
321
0
    if(created && stat == NC_NOERR)  stat = NC_EEMPTY; /* tell upper layers */
322
0
    if(entry) free_cache_entry(cache,entry);
323
0
    return THROW(stat);
324
0
}
325
326
#if 0
327
int
328
NCZ_write_cache_chunk(NCZChunkCache* cache, const size64_t* indices, void* content)
329
{
330
    int stat = NC_NOERR;
331
    int rank = cache->ndims;
332
    NCZCacheEntry* entry = NULL;
333
    ncexhashkey_t hkey;
334
    
335
    /* create the hash key */
336
    hkey = ncxcachekey(indices,sizeof(size64_t)*cache->ndims);
337
338
    if(entry == NULL) { /*!found*/
339
  /* Create a new entry */
340
  if((entry = calloc(1,sizeof(NCZCacheEntry)))==NULL)
341
      {stat = NC_ENOMEM; goto done;}
342
  memcpy(entry->indices,indices,rank*sizeof(size64_t));
343
        if((stat = NCZ_buildchunkpath(cache,indices,&entry->key))) goto done;
344
        entry->hashkey = hkey;
345
  /* Create the local copy space */
346
  entry->size = cache->chunksize;
347
  if((entry->data = calloc(1,cache->chunksize)) == NULL)
348
      {stat = NC_ENOMEM; goto done;}
349
  memcpy(entry->data,content,cache->chunksize);
350
    }
351
    setmodified(entry,1);
352
    nclistpush(cache->mru,entry); /* MRU order */
353
#ifdef DEBUG
354
fprintf(stderr,"|cache.write|=%ld\n",nclistlength(cache->mru));
355
#endif
356
    entry = NULL;
357
358
    /* Ensure cache constraints not violated */
359
    if((stat=verifycache(cache))) goto done;
360
361
done:
362
    if(entry) free_cache_entry(cache,entry);
363
    return THROW(stat);
364
}
365
#endif
366
367
/* Constrain cache */
368
static int
369
verifycache(NCZChunkCache* cache)
370
0
{
371
0
    int stat = NC_NOERR;
372
373
#if 0
374
    /* Sanity check; make sure at least one entry is always allowed */
375
    if(nclistlength(cache->mru) == 1)
376
  goto done;
377
#endif
378
0
    if((stat = constraincache(cache,USEPARAMSIZE))) goto done;
379
0
done:
380
0
    return stat;
381
0
}
382
383
/* Completely flush cache */
384
385
static int
386
flushcache(NCZChunkCache* cache)
387
0
{
388
0
    int stat = NC_NOERR;
389
#if 0
390
    size_t oldsize = cache->params.size;
391
    cache->params.size = 0;
392
    stat = constraincache(cache,USEPARAMSIZE);
393
    cache->params.size = oldsize;
394
#else
395
0
    stat = constraincache(cache,USEPARAMSIZE);
396
0
#endif
397
0
    return stat;
398
0
}
399
400
401
/* Remove entries to ensure cache is not
402
   violating any of its constraints.
403
   On entry, constraints might be violated.
404
   Make sure that the entryinuse (NULL => no constraint) is not reclaimed.
405
@param cache
406
@param needed make sure there is room for this much space; USEPARAMSIZE => ensure no more than  cache params is used.
407
*/
408
409
static int
410
constraincache(NCZChunkCache* cache, size64_t needed)
411
0
{
412
0
    int stat = NC_NOERR;
413
0
    size64_t final_size;
414
415
    /* If the cache is empty then do nothing */
416
0
    if(cache->used == 0) goto done;
417
418
0
    if(needed == USEPARAMSIZE)
419
0
        final_size = cache->params.size;
420
0
    else if(cache->used > needed)
421
0
        final_size = cache->used - needed;
422
0
    else
423
0
        final_size = 0;
424
425
    /* Flush from LRU end if we are at capacity */
426
0
    while(nclistlength(cache->mru) > cache->params.nelems || cache->used > final_size) {
427
0
  size_t i;
428
0
  void* ptr;
429
0
  NCZCacheEntry* e = ncxcachelast(cache->xcache); /* last entry is the least recently used */
430
0
  if(e == NULL) break;
431
0
        if((stat = ncxcacheremove(cache->xcache,e->hashkey,&ptr))) goto done;
432
0
    assert(e == ptr);
433
0
        for(i=0;i<nclistlength(cache->mru);i++) {
434
0
      e = nclistget(cache->mru,i);
435
0
      if(ptr == e) break;
436
0
  }
437
0
    assert(e != NULL);
438
0
  assert(i >= 0 && i < nclistlength(cache->mru));
439
0
  nclistremove(cache->mru,i);
440
0
  assert(cache->used >= e->size);
441
  /* Note that |old chunk data| may not be same as |new chunk data| because of filters */
442
0
  cache->used -= e->size; /* old size */
443
0
  if(e->modified) /* flush to file */
444
0
      stat=put_chunk(cache,e);
445
  /* reclaim */
446
0
        nullfree(e->data); nullfree(e->key.varkey); nullfree(e->key.chunkkey); nullfree(e);
447
0
    }
448
#ifdef DEBUG
449
fprintf(stderr,"|cache.makeroom|=%ld\n",nclistlength(cache->mru));
450
#endif
451
0
done:
452
0
    return stat;
453
0
}
454
455
/**
456
Push modified cache entries to disk.
457
Also make sure the cache size is correct.
458
@param cache
459
@return NC_EXXX error
460
*/
461
int
462
NCZ_flush_chunk_cache(NCZChunkCache* cache)
463
0
{
464
0
    int stat = NC_NOERR;
465
0
    size_t i;
466
467
0
    ZTRACE(4,"cache.var=%s |cache|=%d",cache->var->hdr.name,(int)nclistlength(cache->mru));
468
469
0
    if(NCZ_cache_size(cache) == 0) goto done;
470
    
471
    /* Iterate over the entries in hashmap */
472
0
    for(i=0;i<nclistlength(cache->mru);i++) {
473
0
        NCZCacheEntry* entry = nclistget(cache->mru,i);
474
0
        if(entry->modified) {
475
      /* Write out this chunk in toto*/
476
0
        if((stat=put_chunk(cache,entry)))
477
0
          goto done;
478
0
  }
479
0
        setmodified(entry,0);
480
0
    }
481
    /* Re-compute space used */
482
0
    cache->used = 0;
483
0
    for(i=0;i<nclistlength(cache->mru);i++) {
484
0
        NCZCacheEntry* entry = nclistget(cache->mru,i);
485
0
        cache->used += entry->size;
486
0
    }
487
    /* Make sure cache size and nelems are correct */
488
0
    if((stat=verifycache(cache))) goto done;
489
490
491
0
done:
492
0
    return ZUNTRACE(stat);
493
0
}
494
495
/* Ensure existence of some kind of fill chunk */
496
int
497
NCZ_ensure_fill_chunk(NCZChunkCache* cache)
498
0
{
499
0
    int stat = NC_NOERR;
500
0
    size_t i;
501
0
    NC_VAR_INFO_T* var = cache->var;
502
0
    nc_type typeid = var->type_info->hdr.id;
503
0
    size_t typesize = var->type_info->size;
504
505
0
    if(cache->fillchunk) goto done;
506
507
0
    if((cache->fillchunk = malloc(cache->chunksize))==NULL)
508
0
        {stat = NC_ENOMEM; goto done;}
509
0
    if(var->no_fill) {
510
        /* use zeros */
511
0
  memset(cache->fillchunk,0,cache->chunksize);
512
0
  goto done;
513
0
    }
514
0
    if((stat = NCZ_ensure_fill_value(var))) goto done;
515
0
    if(typeid == NC_STRING) {
516
0
        char* src = *((char**)(var->fill_value));
517
0
  char** dst = (char**)(cache->fillchunk);
518
0
        for(i=0;i<cache->chunkcount;i++) dst[i] = strdup(src);
519
0
    } else
520
0
    switch (typesize) {
521
0
    case 1: {
522
0
        unsigned char c = *((unsigned char*)var->fill_value);
523
0
        memset(cache->fillchunk,c,cache->chunksize);
524
0
        } break;
525
0
    case 2: {
526
0
        unsigned short fv = *((unsigned short*)var->fill_value);
527
0
        unsigned short* p2 = (unsigned short*)cache->fillchunk;
528
0
        for(i=0;i<cache->chunksize;i+=typesize) *p2++ = fv;
529
0
        } break;
530
0
    case 4: {
531
0
        unsigned int fv = *((unsigned int*)var->fill_value);
532
0
        unsigned int* p4 = (unsigned int*)cache->fillchunk;
533
0
        for(i=0;i<cache->chunksize;i+=typesize) *p4++ = fv;
534
0
        } break;
535
0
    case 8: {
536
0
        unsigned long long fv = *((unsigned long long*)var->fill_value);
537
0
        unsigned long long* p8 = (unsigned long long*)cache->fillchunk;
538
0
        for(i=0;i<cache->chunksize;i+=typesize) *p8++ = fv;
539
0
        } break;
540
0
    default: {
541
0
        unsigned char* p;
542
0
        for(p=cache->fillchunk,i=0;i<cache->chunksize;i+=typesize,p+=typesize)
543
0
            memcpy(p,var->fill_value,typesize);
544
0
        } break;
545
0
    }
546
0
done:
547
0
    return NC_NOERR;
548
0
}
549
    
550
int
551
NCZ_reclaim_fill_chunk(NCZChunkCache* zcache)
552
0
{
553
0
    int stat = NC_NOERR;
554
0
    if(zcache && zcache->fillchunk) {
555
0
  NC_VAR_INFO_T* var = zcache->var;
556
0
  int tid = var->type_info->hdr.id;
557
0
  size_t chunkcount = zcache->chunkcount;
558
0
        stat = NC_reclaim_data_all(var->container->nc4_info->controller,tid,zcache->fillchunk,chunkcount);
559
0
  zcache->fillchunk = NULL;
560
0
    }
561
0
    return stat;
562
0
}
563
564
int
565
NCZ_chunk_cache_modify(NCZChunkCache* cache, const size64_t* indices)
566
0
{
567
0
    int stat = NC_NOERR;
568
0
    ncexhashkey_t hkey = 0;
569
0
    NCZCacheEntry* entry = NULL;
570
571
    /* the hash key */
572
0
    hkey = ncxcachekey(indices,sizeof(size64_t)*cache->ndims);
573
574
    /* See if already in cache */
575
0
    if((stat=ncxcachelookup(cache->xcache, hkey, (void**)&entry))) {stat = NC_EINTERNAL; goto done;}
576
0
    setmodified(entry,1);
577
578
0
done:
579
0
    return THROW(stat);
580
0
}
581
582
/**************************************************/
583
/*
584
From Zarr V2 Specification:
585
"The compressed sequence of bytes for each chunk is stored under
586
a key formed from the index of the chunk within the grid of
587
chunks representing the array.  To form a string key for a
588
chunk, the indices are converted to strings and concatenated
589
with the dimension_separator character ('.' or '/') separating
590
each index. For example, given an array with shape (10000,
591
10000) and chunk shape (1000, 1000) there will be 100 chunks
592
laid out in a 10 by 10 grid. The chunk with indices (0, 0)
593
provides data for rows 0-1000 and columns 0-1000 and is stored
594
under the key "0.0"; the chunk with indices (2, 4) provides data
595
for rows 2000-3000 and columns 4000-5000 and is stored under the
596
key "2.4"; etc."
597
*/
598
599
/**
600
 * @param R Rank
601
 * @param chunkindices The chunk indices
602
 * @param dimsep the dimension separator
603
 * @param keyp Return the chunk key string
604
 */
605
int
606
NCZ_buildchunkkey(size_t R, const size64_t* chunkindices, char dimsep, char** keyp)
607
0
{
608
0
    int stat = NC_NOERR;
609
0
    size_t r;
610
0
    NCbytes* key = ncbytesnew();
611
612
0
    if(keyp) *keyp = NULL;
613
614
0
    assert(islegaldimsep(dimsep));
615
    
616
0
    for(r=0;r<R;r++) {
617
0
  char sindex[64];
618
0
        if(r > 0) ncbytesappend(key,dimsep);
619
  /* Print as decimal with no leading zeros */
620
0
  snprintf(sindex,sizeof(sindex),"%lu",(unsigned long)chunkindices[r]); 
621
0
  ncbytescat(key,sindex);
622
0
    }
623
0
    ncbytesnull(key);
624
0
    if(keyp) *keyp = ncbytesextract(key);
625
626
0
    ncbytesfree(key);
627
0
    return THROW(stat);
628
0
}
629
630
/**
631
 * @internal Push data to chunk of a file.
632
 * If chunk does not exist, create it
633
 *
634
 * @param file Pointer to file info struct.
635
 * @param proj Chunk projection
636
 * @param datalen size of data
637
 * @param data Buffer containing the chunk data to write
638
 *
639
 * @return ::NC_NOERR No error.
640
 * @author Dennis Heimbigner
641
 */
642
static int
643
put_chunk(NCZChunkCache* cache, NCZCacheEntry* entry)
644
0
{
645
0
    int stat = NC_NOERR;
646
0
    NC_FILE_INFO_T* file = NULL;
647
0
    NCZ_FILE_INFO_T* zfile = NULL;
648
0
    NCZMAP* map = NULL;
649
0
    char* path = NULL;
650
0
    nc_type tid = NC_NAT;
651
0
    void* strchunk = NULL;
652
653
0
    ZTRACE(5,"cache.var=%s entry.key=%s",cache->var->hdr.name,entry->key);
654
0
    LOG((3, "%s: var: %p", __func__, cache->var));
655
656
0
    file = (cache->var->container)->nc4_info;
657
0
    zfile = file->format_file_info;
658
0
    map = zfile->map;
659
660
    /* Collect some info */
661
0
    tid = cache->var->type_info->hdr.id;
662
663
0
    if(tid == NC_STRING && !entry->isfixedstring) {
664
        /* Convert from char* to char[strlen] format */
665
0
        int maxstrlen = NCZ_get_maxstrlen((NC_OBJ*)cache->var);
666
0
        assert(maxstrlen > 0);
667
0
        if((strchunk = malloc((size_t)cache->chunkcount * (size_t)maxstrlen))==NULL) {stat = NC_ENOMEM; goto done;}
668
        /* copy char* to char[] format */
669
0
        if((stat = NCZ_char2fixed((const char**)entry->data,strchunk,cache->chunkcount,maxstrlen))) goto done;
670
        /* Reclaim the old chunk */
671
0
        if((stat = NC_reclaim_data_all(file->controller,tid,entry->data,cache->chunkcount))) goto done;
672
0
        entry->data = NULL;
673
0
        entry->data = strchunk; strchunk = NULL;
674
0
        entry->size = (cache->chunkcount * (size64_t)maxstrlen);
675
0
        entry->isfixedstring = 1;
676
0
    }
677
678
679
#ifdef NETCDF_ENABLE_NCZARR_FILTERS
680
    /* Make sure the entry is in filtered state */
681
    if(!entry->isfiltered) {
682
        NC_VAR_INFO_T* var = cache->var;
683
        void* filtered = NULL; /* pointer to the filtered data */
684
  size_t flen; /* length of filtered data */
685
  /* Get the filter chain to apply */
686
  NClist* filterchain = (NClist*)var->filters;
687
  if(nclistlength(filterchain) > 0) {
688
      /* Apply the filter chain to get the filtered data; will reclaim entry->data */
689
      if((stat = NCZ_applyfilterchain(file,var,filterchain,entry->size,entry->data,&flen,&filtered,ENCODING))) goto done;
690
      /* Fix up the cache entry */
691
      /* Note that if filtered is different from entry->data, then entry->data will have been freed */
692
      entry->data = filtered;
693
      entry->size = flen;
694
            entry->isfiltered = 1;
695
  }
696
    }
697
#endif
698
699
0
    path = NCZ_chunkpath(entry->key);
700
0
    stat = nczmap_write(map,path,entry->size,entry->data);
701
0
    nullfree(path); path = NULL;
702
703
0
    switch(stat) {
704
0
    case NC_NOERR:
705
0
  break;
706
0
    case NC_ENOOBJECT: case NC_EEMPTY:
707
0
    default: goto done;
708
0
    }
709
0
done:
710
0
    nullfree(strchunk);
711
0
    nullfree(path);
712
0
    return ZUNTRACE(stat);
713
0
}
714
715
/**
716
 * @internal Push data from memory to file.
717
 *
718
 * @param cache Pointer to parent cache
719
 * @param key chunk key
720
 * @param entry cache entry to read into
721
 *
722
 * @return ::NC_NOERR No error.
723
 * @author Dennis Heimbigner
724
 */
725
static int
726
get_chunk(NCZChunkCache* cache, NCZCacheEntry* entry)
727
0
{
728
0
    int stat = NC_NOERR;
729
0
    NCZMAP* map = NULL;
730
0
    NC_FILE_INFO_T* file = NULL;
731
0
    NCZ_FILE_INFO_T* zfile = NULL;
732
0
    NC_TYPE_INFO_T* xtype = NULL;
733
0
    char** strchunk = NULL;
734
0
    size64_t size;
735
0
    int empty = 0;
736
0
    char* path = NULL;
737
0
    int tid;
738
739
0
    ZTRACE(5,"cache.var=%s entry.key=%s sep=%d",cache->var->hdr.name,entry->key,cache->dimension_separator);
740
    
741
0
    LOG((3, "%s: file: %p", __func__, file));
742
743
0
    file = (cache->var->container)->nc4_info;
744
0
    zfile = file->format_file_info;
745
0
    map = zfile->map;
746
0
    assert(map);
747
748
    /* Collect some info */
749
0
    xtype = cache->var->type_info;
750
0
    tid = xtype->hdr.id;
751
752
    /* get size of the "raw" data on "disk" */
753
0
    path = NCZ_chunkpath(entry->key);
754
0
    stat = nczmap_len(map,path,&size);
755
0
    nullfree(path); path = NULL;
756
0
    switch(stat) {
757
0
    case NC_NOERR: entry->size = size; break;
758
0
    case NC_ENOOBJECT: case NC_EEMPTY: empty = 1; stat = NC_NOERR; break;
759
0
    default: goto done;
760
0
    }
761
762
    /* make room in the cache */
763
0
    if((stat = constraincache(cache,size))) goto done;    
764
765
0
    if(!empty) {
766
        /* Make sure we have a place to read it */
767
0
        if((entry->data = (void*)calloc(1,entry->size)) == NULL)
768
0
      {stat = NC_ENOMEM; goto done;}
769
  /* Read the raw data */
770
0
        path = NCZ_chunkpath(entry->key);
771
0
        stat = nczmap_read(map,path,0,entry->size,(char*)entry->data);
772
0
        nullfree(path); path = NULL;
773
0
        switch (stat) {
774
0
        case NC_NOERR: break;
775
0
        case NC_ENOOBJECT: case NC_EEMPTY: empty = 1; stat = NC_NOERR;break;
776
0
  default: goto done;
777
0
  }
778
0
        entry->isfiltered = (int)FILTERED(cache); /* Is the data being read filtered? */
779
0
  if(tid == NC_STRING)
780
0
      entry->isfixedstring = 1; /* fill cache is in char[maxstrlen] format */
781
0
    }
782
0
    if(empty) {
783
  /* fake the chunk */
784
0
        setmodified(entry,(file->no_write?0:1));
785
0
  entry->size = cache->chunksize;
786
0
  entry->data = NULL;
787
0
        entry->isfixedstring = 0;
788
0
        entry->isfiltered = 0;
789
        /* apply fill value */
790
0
  if(cache->fillchunk == NULL)
791
0
      {if((stat = NCZ_ensure_fill_chunk(cache))) goto done;}
792
0
  if((entry->data = calloc(1,entry->size))==NULL) {stat = NC_ENOMEM; goto done;}
793
0
  if((stat = NCZ_copy_data(file,cache->var,cache->fillchunk,cache->chunkcount,ZREADING,entry->data))) goto done;
794
0
  stat = NC_NOERR;
795
0
    }
796
#ifdef NETCDF_ENABLE_NCZARR_FILTERS
797
    /* Make sure the entry is in unfiltered state */
798
    if(!empty && entry->isfiltered) {
799
        NC_VAR_INFO_T* var = cache->var;
800
        void* unfiltered = NULL; /* pointer to the unfiltered data */
801
        void* filtered = NULL; /* pointer to the filtered data */
802
  size_t unflen; /* length of unfiltered data */
803
  assert(tid != NC_STRING || entry->isfixedstring);
804
  /* Get the filter chain to apply */
805
  NClist* filterchain = (NClist*)var->filters;
806
  if(nclistlength(filterchain) == 0) {stat = NC_EFILTER; goto done;}
807
  /* Apply the filter chain to get the unfiltered data */
808
  filtered = entry->data;
809
  entry->data = NULL;
810
  if((stat = NCZ_applyfilterchain(file,var,filterchain,entry->size,filtered,&unflen,&unfiltered,!ENCODING))) goto done;
811
  /* Fix up the cache entry */
812
  entry->data = unfiltered;
813
  entry->size = unflen;
814
  entry->isfiltered = 0;
815
    }
816
#endif
817
818
0
    if(tid == NC_STRING && entry->isfixedstring) {
819
        /* Convert from char[strlen] to char* format */
820
0
  int maxstrlen = NCZ_get_maxstrlen((NC_OBJ*)cache->var);
821
0
  assert(maxstrlen > 0);
822
  /* copy char[] to char* format */
823
0
  if((strchunk = (char**)malloc(sizeof(char*)*cache->chunkcount))==NULL)
824
0
        {stat = NC_ENOMEM; goto done;}
825
0
  if((stat = NCZ_fixed2char(entry->data,strchunk,cache->chunkcount,maxstrlen))) goto done;
826
  /* Reclaim the old chunk */
827
0
  nullfree(entry->data);
828
0
  entry->data = NULL;
829
0
  entry->data = strchunk; strchunk = NULL;
830
0
  entry->size = cache->chunkcount * sizeof(char*);
831
0
  entry->isfixedstring = 0;
832
0
    }
833
834
    /* track new chunk */
835
0
    cache->used += entry->size;
836
837
0
done:
838
0
    nullfree(strchunk);
839
0
    nullfree(path);
840
0
    return ZUNTRACE(stat);
841
0
}
842
843
int
844
NCZ_buildchunkpath(NCZChunkCache* cache, const size64_t* chunkindices, struct ChunkKey* key)
845
0
{
846
0
    int stat = NC_NOERR;
847
0
    char* chunkname = NULL;
848
0
    char* varkey = NULL;
849
850
0
    assert(key != NULL);
851
    /* Get the chunk object name */
852
0
    if((stat = NCZ_buildchunkkey(cache->ndims, chunkindices, cache->dimension_separator, &chunkname))) goto done;
853
    /* Get the var object key */
854
0
    if((stat = NCZ_varkey(cache->var,&varkey))) goto done;
855
0
    key->varkey = varkey; varkey = NULL;
856
0
    key->chunkkey = chunkname; chunkname = NULL;    
857
858
0
done:
859
0
    nullfree(chunkname);
860
0
    nullfree(varkey);
861
0
    return THROW(stat);
862
0
}
863
864
void
865
NCZ_dumpxcacheentry(NCZChunkCache* cache, NCZCacheEntry* e, NCbytes* buf)
866
0
{
867
0
    char s[8192];
868
0
    char idx[64];
869
0
    size_t i;
870
871
0
    ncbytescat(buf,"{");
872
0
    snprintf(s,sizeof(s),"modified=%u isfiltered=%u indices=",
873
0
  (unsigned)e->modified,
874
0
  (unsigned)e->isfiltered
875
0
  );
876
0
    ncbytescat(buf,s);
877
0
    for(i=0;i<cache->ndims;i++) {
878
0
  snprintf(idx,sizeof(idx),"%s%llu",(i==0?"":"."),e->indices[i]);
879
0
  ncbytescat(buf,idx);
880
0
    }
881
0
    snprintf(s,sizeof(s),"size=%llu data=%p",
882
0
  e->size,
883
0
  e->data
884
0
  );
885
0
    ncbytescat(buf,s);
886
0
    ncbytescat(buf,"}");
887
0
}
888
889
void
890
NCZ_printxcache(NCZChunkCache* cache)
891
0
{
892
0
    static char xs[20000];
893
0
    NCbytes* buf = ncbytesnew();
894
0
    char s[8192];
895
0
    size_t i;
896
897
0
    ncbytescat(buf,"NCZChunkCache:\n");
898
0
    snprintf(s,sizeof(s),"\tvar=%s\n\tndims=%u\n\tchunksize=%u\n\tchunkcount=%u\n\tfillchunk=%p\n",
899
0
      cache->var->hdr.name,
900
0
      (unsigned)cache->ndims,
901
0
      (unsigned)cache->chunksize,
902
0
      (unsigned)cache->chunkcount,
903
0
  cache->fillchunk
904
0
  );
905
0
    ncbytescat(buf,s);
906
907
0
    snprintf(s,sizeof(s),"\tmaxentries=%u\n\tmaxsize=%u\n\tused=%u\n\tdimsep='%c'\n",
908
0
      (unsigned)cache->params.nelems,
909
0
  (unsigned)cache->params.size,
910
0
      (unsigned)cache->used,
911
0
      cache->dimension_separator
912
0
  );
913
0
    ncbytescat(buf,s);
914
    
915
0
    snprintf(s,sizeof(s),"\tmru: (%u)\n",(unsigned)nclistlength(cache->mru));
916
0
    ncbytescat(buf,s);
917
0
    if(nclistlength(cache->mru)==0)    
918
0
        ncbytescat(buf,"\t\t<empty>\n");
919
0
    for(i=0;i<nclistlength(cache->mru);i++) {
920
0
  NCZCacheEntry* e = (NCZCacheEntry*)nclistget(cache->mru,i);
921
0
  snprintf(s,sizeof(s),"\t\t[%zu] ", i);
922
0
  ncbytescat(buf,s);
923
0
  if(e == NULL)
924
0
      ncbytescat(buf,"<null>");
925
0
  else
926
0
      NCZ_dumpxcacheentry(cache, e, buf);
927
0
  ncbytescat(buf,"\n");
928
0
    }
929
930
0
    xs[0] = '\0';
931
0
    strlcat(xs,ncbytescontents(buf),sizeof(xs));
932
0
    ncbytesfree(buf);
933
    fprintf(stderr,"%s\n",xs);
934
0
}