SQLite4
Check-in [d9e0ad7566]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Improve speed test commands in lsmtest. Add script lsmperf.tcl to use these to generate gnuplot files.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: d9e0ad7566622321ecc2699258e0df2f5ed41113
User & Date: dan 2012-09-14 19:59:52
Context
2012-09-15
17:03
Improve performance testing script lsmperf.tcl. check-in: 108a6143bf user: dan tags: trunk
2012-09-14
19:59
Improve speed test commands in lsmtest. Add script lsmperf.tcl to use these to generate gnuplot files. check-in: d9e0ad7566 user: dan tags: trunk
2012-09-13
18:13
Add tests for dealing with inconsistent tree-headers in shared-memory. check-in: 4ea78ff1f3 user: dan tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to lsm-test/lsmtest_main.c.

468
469
470
471
472
473
474





























































































































475
476
477
478
479
480
481
....
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
....
1210
1211
1212
1213
1214
1215
1216

1217
1218
1219
1220
1221
1222
1223
  pLsm = tdb_lsm(pDb);
  if( pLsm ){
    tdb_lsm_config_str(pDb, "mmap=1 autowork=1 nmerge=4 worker_nmerge=4");
  }
  return pLsm;
}































































































































static void do_speed_write_hook2(
  void *pCtx,
  int bLog,
  i64 iOff,
  int nData,
  int nUs
................................................................................
  return rc;
}

static int st_do_show(int a, char **b)      { return do_show(a, b); }
static int st_do_work(int a, char **b)      { return do_work(a, b); }
static int st_do_io(int a, char **b)        { return do_io(a, b); }


#ifdef __linux__
#include <sys/time.h>
#include <sys/resource.h>

static void lsmtest_rusage_report(void){
  int res;
  struct rusage r;
  memset(&r, sizeof(r), 0);

  res = getrusage(RUSAGE_SELF, &r);
  assert( res==0 );

  printf("getrusage: { ru_maxrss %d ru_oublock %d ru_inblock %d }\n", 
      (int)r.ru_maxrss, (int)r.ru_oublock, (int)r.ru_inblock
  );
}
#else
static void lsmtest_rusage_report(void){
  /* no-op */
}
................................................................................
    {"writespeed",  do_writer_test},
    {"io",          st_do_io},

    {"insert",      do_insert},
    {"replay",      do_replay},

    {"speed",       do_speed_tests},

    {"show",        st_do_show},
    {"work",        st_do_work},
    {"test",        do_test},
    {0, 0}
  };
  int rc;                         /* Return Code */
  int iFunc;                      /* Index into aTest[] */







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<












|







 







>







468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
....
1299
1300
1301
1302
1303
1304
1305

1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
....
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
  pLsm = tdb_lsm(pDb);
  if( pLsm ){
    tdb_lsm_config_str(pDb, "mmap=1 autowork=1 nmerge=4 worker_nmerge=4");
  }
  return pLsm;
}

#define ST_REPEAT  0
#define ST_WRITE   1
#define ST_PAUSE   2
#define ST_FETCH   3
#define ST_SCAN    4
#define ST_NSCAN   5
#define ST_KEYSIZE 6
#define ST_VALSIZE 7

int do_speed_test2(int nArg, char **azArg){
  struct Option {
    const char *zOpt;
    int eVal;
    int iDefault;
  } aOpt[] = {
    { "-repeat",  ST_REPEAT,    10},
    { "-write",   ST_WRITE,  10000},
    { "-pause",   ST_PAUSE,      0},
    { "-fetch",   ST_FETCH,      0},
    { "-scan",    ST_SCAN,       0},
    { "-nscan",   ST_NSCAN,      0},
    { "-keysize", ST_KEYSIZE,   12},
    { "-valsize", ST_VALSIZE,  100},
    { "-system",  -1,            0},
    {0, 0, 0}
  };
  int i;
  int aParam[8];
  int rc = 0;

  TestDb *pDb;
  Datasource *pData;
  DatasourceDefn defn = { TEST_DATASOURCE_RANDOM, 0, 0, 0, 0 };
  char *zSystem = "";
  int bLsm = 1;

  /* Initialize aParam[] with default values. */
  for(i=0; i<ArraySize(aOpt); i++){
    if( aOpt[i].zOpt ) aParam[aOpt[i].eVal] = aOpt[i].iDefault;
  }

  /* Process the command line switches. */
  for(i=0; i<nArg; i+=2){
    int iSel;
    rc = testArgSelect(aOpt, "switch", azArg[i], &iSel);
    if( rc ) return rc;
    if( i+1==nArg ){
      testPrintError("option %s requires an argument\n", aOpt[iSel].zOpt);
      return 1;
    }
    if( aOpt[iSel].eVal>=0 ){
      aParam[aOpt[iSel].eVal] = atoi(azArg[i+1]);
    }else{
      int j;
      zSystem = azArg[i+1];
      bLsm = 0;
      for(j=0; zSystem[j]; j++){
        if( zSystem[j]=='=' ) bLsm = 1;
      }
    }
  }
  
  printf("#");
  for(i=0; i<ArraySize(aOpt); i++){
    if( aOpt[i].zOpt ){
      if( aOpt[i].eVal>=0 ){
        printf(" %s=%d", &aOpt[i].zOpt[1], aParam[aOpt[i].eVal]);
      }else{
        printf(" %s=\"%s\"", &aOpt[i].zOpt[1], zSystem);
      }
    }
  }
  printf("\n");

  defn.nMinKey = defn.nMaxKey = aParam[ST_KEYSIZE];
  defn.nMinVal = defn.nMaxVal = aParam[ST_VALSIZE];
  pData = testDatasourceNew(&defn);

  if( bLsm ){
    rc = tdb_lsm_open(zSystem, "testdb.lsm", 1, &pDb);
  }else{
    pDb = testOpen(zSystem, 1, &rc);
  }
  if( rc!=0 ) return rc;

  for(i=0; i<aParam[ST_REPEAT] && rc==0; i++){
    int msWrite, msFetch, msScan;
    int iFetch;
    int nWrite = aParam[ST_WRITE];

    testTimeInit();
    testWriteDatasourceRange(pDb, pData, i*nWrite, nWrite, &rc);
    msWrite = testTimeGet();

    if( aParam[ST_PAUSE] ){
      if( aParam[ST_PAUSE]/1000 ) sleep(aParam[ST_PAUSE]/1000);
      if( aParam[ST_PAUSE]%1000 ) usleep(1000 * (aParam[ST_PAUSE]%1000));
    }

    if( aParam[ST_FETCH] ){
      testTimeInit();
      for(iFetch=0; iFetch<aParam[ST_FETCH]; iFetch++){
        int iKey = testPrngValue(i*nWrite+iFetch) % ((i+1)*nWrite);
        testDatasourceFetch(pDb, pData, iKey, &rc);
      }
      msFetch = testTimeGet();
    }else{
      msFetch = 0;
    }

    if( i==(aParam[ST_REPEAT]-1) ){
      testTimeInit();
      testClose(&pDb);
      msWrite += testTimeGet();
    }

    printf("%d %d %d\n", i, msWrite, msFetch);
    fflush(stdout);
  }

  testClose(&pDb);
  testDatasourceFree(pData);

  return rc;
}

static void do_speed_write_hook2(
  void *pCtx,
  int bLog,
  i64 iOff,
  int nData,
  int nUs
................................................................................
  return rc;
}

static int st_do_show(int a, char **b)      { return do_show(a, b); }
static int st_do_work(int a, char **b)      { return do_work(a, b); }
static int st_do_io(int a, char **b)        { return do_io(a, b); }


#ifdef __linux__
#include <sys/time.h>
#include <sys/resource.h>

static void lsmtest_rusage_report(void){
  int res;
  struct rusage r;
  memset(&r, sizeof(r), 0);

  res = getrusage(RUSAGE_SELF, &r);
  assert( res==0 );

  printf("# getrusage: { ru_maxrss %d ru_oublock %d ru_inblock %d }\n", 
      (int)r.ru_maxrss, (int)r.ru_oublock, (int)r.ru_inblock
  );
}
#else
static void lsmtest_rusage_report(void){
  /* no-op */
}
................................................................................
    {"writespeed",  do_writer_test},
    {"io",          st_do_io},

    {"insert",      do_insert},
    {"replay",      do_replay},

    {"speed",       do_speed_tests},
    {"speed2",       do_speed_test2},
    {"show",        st_do_show},
    {"work",        st_do_work},
    {"test",        do_test},
    {0, 0}
  };
  int rc;                         /* Return Code */
  int iFunc;                      /* Index into aTest[] */

Changes to lsm-test/lsmtest_tdb3.c.

749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
  pDb->env.xShmUnmap = testEnvShmUnmap;
  pDb->env.xSleep = testEnvSleep;

  rc = lsm_new(&pDb->env, &pDb->db);
  if( rc==LSM_OK ){
    lsm_config_log(pDb->db, xLog, 0);
    lsm_config_work_hook(pDb->db, xWorkHook, (void *)pDb);
    tdb_lsm_config_str((TestDb *)pDb, zCfg);
    rc = lsm_open(pDb->db, zFilename);
    if( rc!=LSM_OK ){
      test_lsm_close((TestDb *)pDb);
      pDb = 0;
    }
  }

  *ppDb = (TestDb *)pDb;







|
|







749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
  pDb->env.xShmUnmap = testEnvShmUnmap;
  pDb->env.xSleep = testEnvSleep;

  rc = lsm_new(&pDb->env, &pDb->db);
  if( rc==LSM_OK ){
    lsm_config_log(pDb->db, xLog, 0);
    lsm_config_work_hook(pDb->db, xWorkHook, (void *)pDb);
    rc = tdb_lsm_config_str((TestDb *)pDb, zCfg);
    if( rc==LSM_OK ) rc = lsm_open(pDb->db, zFilename);
    if( rc!=LSM_OK ){
      test_lsm_close((TestDb *)pDb);
      pDb = 0;
    }
  }

  *ppDb = (TestDb *)pDb;

Changes to src/lsmInt.h.

281
282
283
284
285
286
287

288
289
290
291
292
293
294
  int nTreeLimit;                 /* Configured by LSM_CONFIG_WRITE_BUFFER */
  int nMerge;                     /* Configured by LSM_CONFIG_NMERGE */
  int nLogSz;                     /* Configured by LSM_CONFIG_LOG_SIZE */
  int bUseLog;                    /* Configured by LSM_CONFIG_USE_LOG */
  int nDfltPgsz;                  /* Configured by LSM_CONFIG_PAGE_SIZE */
  int nDfltBlksz;                 /* Configured by LSM_CONFIG_BLOCK_SIZE */
  int nMaxFreelist;               /* Configured by LSM_CONFIG_MAX_FREELIST */

  int bMultiProc;                 /* Configured by L_C_MULTIPLE_PROCESSES */

  /* Sub-system handles */
  FileSystem *pFS;                /* On-disk portion of database */
  Database *pDatabase;            /* Database shared data */

  /* Client transaction context */







>







281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
  int nTreeLimit;                 /* Configured by LSM_CONFIG_WRITE_BUFFER */
  int nMerge;                     /* Configured by LSM_CONFIG_NMERGE */
  int nLogSz;                     /* Configured by LSM_CONFIG_LOG_SIZE */
  int bUseLog;                    /* Configured by LSM_CONFIG_USE_LOG */
  int nDfltPgsz;                  /* Configured by LSM_CONFIG_PAGE_SIZE */
  int nDfltBlksz;                 /* Configured by LSM_CONFIG_BLOCK_SIZE */
  int nMaxFreelist;               /* Configured by LSM_CONFIG_MAX_FREELIST */
  int bMmap;                      /* Configured by LSM_CONFIG_MMAP */
  int bMultiProc;                 /* Configured by L_C_MULTIPLE_PROCESSES */

  /* Sub-system handles */
  FileSystem *pFS;                /* On-disk portion of database */
  Database *pDatabase;            /* Database shared data */

  /* Client transaction context */

Changes to src/lsm_file.c.

389
390
391
392
393
394
395

396
397
398
399
400
401
402
....
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
    pFS->zDb = (char *)&pFS[1];
    pFS->zLog = &pFS->zDb[nDb+1];
    pFS->nPagesize = LSM_PAGE_SIZE;
    pFS->nBlocksize = LSM_BLOCK_SIZE;
    pFS->nMetasize = 4 * 1024;
    pFS->pDb = pDb;
    pFS->pEnv = pDb->pEnv;


    /* Make a copy of the database and log file names. */
    memcpy(pFS->zDb, zDb, nDb+1);
    memcpy(pFS->zLog, zDb, nDb);
    memcpy(&pFS->zLog[nDb], "-log", 5);

    /* Allocate the hash-table here. At some point, it should be changed
................................................................................
/*
** Return the sector-size as reported by the log file handle.
*/
int lsmFsSectorSize(FileSystem *pFS){
  return lsmEnvSectorSize(pFS->pEnv, pFS->fdLog);
}

/*
** This function implements the lsm_config(LSM_CONFIG_MMAP) request. This
** setting may only be modified if there are currently no outstanding page
** references.
*/
int lsmConfigMmap(lsm_db *pDb, int *piParam){
  int iNew = *piParam;
  FileSystem *pFS = pDb->pFS;
  if( LSM_IS_64_BIT && (pFS->nOut==0 && (iNew==0 || iNew==1)) ){
    pFS->bUseMmap = iNew;
  }
  *piParam = pFS->bUseMmap;
  return LSM_OK;
}

/*
** Helper function for lsmInfoArrayStructure().
*/
static Segment *startsWith(Segment *pRun, Pgno iFirst){
  return (iFirst==pRun->iFirst) ? pRun : 0;
}








>







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
....
1345
1346
1347
1348
1349
1350
1351















1352
1353
1354
1355
1356
1357
1358
    pFS->zDb = (char *)&pFS[1];
    pFS->zLog = &pFS->zDb[nDb+1];
    pFS->nPagesize = LSM_PAGE_SIZE;
    pFS->nBlocksize = LSM_BLOCK_SIZE;
    pFS->nMetasize = 4 * 1024;
    pFS->pDb = pDb;
    pFS->pEnv = pDb->pEnv;
    pFS->bUseMmap = pDb->bMmap;

    /* Make a copy of the database and log file names. */
    memcpy(pFS->zDb, zDb, nDb+1);
    memcpy(pFS->zLog, zDb, nDb);
    memcpy(&pFS->zLog[nDb], "-log", 5);

    /* Allocate the hash-table here. At some point, it should be changed
................................................................................
/*
** Return the sector-size as reported by the log file handle.
*/
int lsmFsSectorSize(FileSystem *pFS){
  return lsmEnvSectorSize(pFS->pEnv, pFS->fdLog);
}
















/*
** Helper function for lsmInfoArrayStructure().
*/
static Segment *startsWith(Segment *pRun, Pgno iFirst){
  return (iFirst==pRun->iFirst) ? pRun : 0;
}

Changes to src/lsm_main.c.

336
337
338
339
340
341
342
343



344
345
346
347
348
349
350
      }
      *piVal = pDb->eSafety;
      break;
    }

    case LSM_CONFIG_MMAP: {
      int *piVal = va_arg(ap, int *);
      rc = lsmConfigMmap(pDb, piVal);



      break;
    }

    case LSM_CONFIG_USE_LOG: {
      int *piVal = va_arg(ap, int *);
      if( pDb->nTransOpen==0 && (*piVal==0 || *piVal==1) ){
        pDb->bUseLog = *piVal;







|
>
>
>







336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
      }
      *piVal = pDb->eSafety;
      break;
    }

    case LSM_CONFIG_MMAP: {
      int *piVal = va_arg(ap, int *);
      if( pDb->pDatabase==0 ){
        pDb->bMmap = (LSM_IS_64_BIT && *piVal);
      }
      *piVal = pDb->bMmap;
      break;
    }

    case LSM_CONFIG_USE_LOG: {
      int *piVal = va_arg(ap, int *);
      if( pDb->nTransOpen==0 && (*piVal==0 || *piVal==1) ){
        pDb->bUseLog = *piVal;

Changes to src/lsm_shared.c.

553
554
555
556
557
558
559

560

561

562

563
564
565
566
567
568
569
        lsmFsMetaPageRelease(pPg);
      }
      bDone = (iDisk>=iCkpt);
    }

    if( rc==LSM_OK && bDone==0 ){
      int iMeta = (pShm->iMetaPage % 2) + 1;

      rc = lsmFsSyncDb(pDb->pFS);

      if( rc==LSM_OK ) rc = lsmCheckpointStore(pDb, iMeta);

      if( rc==LSM_OK ) rc = lsmFsSyncDb(pDb->pFS);

      if( rc==LSM_OK ) pShm->iMetaPage = iMeta;
    }
  }

  lsmShmLock(pDb, LSM_LOCK_CHECKPOINTER, LSM_LOCK_UNLOCK, 0);
  return rc;
}







>
|
>

>
|
>







553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
        lsmFsMetaPageRelease(pPg);
      }
      bDone = (iDisk>=iCkpt);
    }

    if( rc==LSM_OK && bDone==0 ){
      int iMeta = (pShm->iMetaPage % 2) + 1;
      if( pDb->eSafety!=LSM_SAFETY_OFF ){
        rc = lsmFsSyncDb(pDb->pFS);
      }
      if( rc==LSM_OK ) rc = lsmCheckpointStore(pDb, iMeta);
      if( rc==LSM_OK && pDb->eSafety!=LSM_SAFETY_OFF){
        rc = lsmFsSyncDb(pDb->pFS);
      }
      if( rc==LSM_OK ) pShm->iMetaPage = iMeta;
    }
  }

  lsmShmLock(pDb, LSM_LOCK_CHECKPOINTER, LSM_LOCK_UNLOCK, 0);
  return rc;
}

Added tool/lsmperf.tcl.

































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/bin/sh
# \
exec tclsh "$0" "$@"


proc exec_lsmtest_speed {nSec spec} {
  set fd [open [list |lsmtest speed2 {*}$spec]]
  set res [list]

  set initial [clock seconds]

  while {![eof $fd] && ($nSec==0 || ([clock second]-$initial)<$nSec)} { 
    set line [gets $fd]
    puts $line
    if {[string range $line 0 0]=="#"} continue
    lappend res $line
  }
  catch { close $fd }
  set res
}

proc write_to_file {zFile zScript} {
  set fd [open $zFile w]
  puts $fd $zScript
  close $fd
}

proc exec_gnuplot_script {script png} {
  write_to_file out "
    $script
    pause -1
  "
  
  set script "
    set terminal png
    $script
  "
  exec gnuplot << $script > $png 2>/dev/null
}

proc do_write_test {nSec nWrite nFetch nRepeat zSystem zPng} {
  set wt [list -w $nWrite -r $nRepeat -f $nFetch -system $zSystem]
  set res [exec_lsmtest_speed $nSec $wt]
  set script "set boxwidth [expr $nWrite/2]"
  append script {
    set xlabel "Rows Inserted"
    set y2label "Selects per second"
    set ylabel "Inserts per second"
    set yrange [0:*]
    set xrange [0:*]
    set xrange [0:*]
    set key box lw 0.01
  }

  if {$nFetch>0} {
    append script {
      set ytics nomirror
      set y2tics nomirror
      set y2range [0:*]
    }
  }

  append script {plot "-" ti "INSERT" with boxes fs solid lc rgb "#B0C4DE"}
  if {$nFetch>0} {
    append script {, "-" ti "SELECT" axis x1y2 with points lw 3 lc }
    append script {rgb "#000000"}
  }
  append script "\n"

  foreach row $res {
    foreach {i msInsert msFetch} $row {}
    set x [expr $i*$nWrite + $nWrite/2]
    append script "$x [expr int($nWrite * 1000.0 / $msInsert)]\n"
  }
  append script "end\n"

  if {$nFetch>0} {
    foreach row $res {
      foreach {i msInsert msFetch} $row {}
      set x [expr $i*$nWrite + $nWrite]
      append script "$x [expr int($nFetch * 1000.0 / $msFetch)]\n"
    }
    append script "end\n"
  }

  append script "pause -1\n"
  exec_gnuplot_script $script $zPng
}

do_write_test 60 100000 100000 100 "mmap=1 multi_proc=0 safety=1" x.png
after 10000
do_write_test 60 100000 100000 100 leveldb y.png




Changes to tool/lsmview.tcl.

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#############################################################################

proc exec_lsmtest_show {args} {
  set fd [open [list |lsmtest show {*}$args]]
  set res ""
  while {![eof $fd]} { 
    set line [gets $fd]
    if {[regexp {^getrusage*} $line]} continue
    if {[regexp {^Leaked*} $line]} continue
    append res $line
    append res "\n"
  }
  close $fd
  string trim $res
}







|







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#############################################################################

proc exec_lsmtest_show {args} {
  set fd [open [list |lsmtest show {*}$args]]
  set res ""
  while {![eof $fd]} { 
    set line [gets $fd]
    if {[regexp {^\#.*} $line]} continue
    if {[regexp {^Leaked*} $line]} continue
    append res $line
    append res "\n"
  }
  close $fd
  string trim $res
}