// Copyright (c) 2005-2015 Ross Smith II. See Mit LICENSE in /LICENSE
#ifdef _MSC_VER
#pragma warning(disable:4996)
#endif
#include
#include
#include // time()
#include "getopt.h"
#include "version.h"
#define DEFAULT_BUCKETS (5)
#define MAX_BUCKETS (10)
#define DEFAULT_KILOS (1024)
#define DEFAULT_FILE_SIZE (1024 * 1024 * 100)
#define DEFAULT_READS (1000000)
#define DEFAULT_SECONDS (64)
#define DEFAULT_LAST (DEFAULT_SECONDS)
#define DEFAULT_REFRESH (4) // 64 / 32 / 16 / 8 / 4
#define APPNAME VER_INTERNAL_NAME
#define APPVERSION VER_STRING2
#define APPCOPYRIGHT VER_LEGAL_COPYRIGHT
static char *progname = APPNAME;
static char *short_options = "b:f:kl:m:r:s:vz:?";
static struct option long_options[] = {
{"buckets", required_argument, 0, 'b'},
{"kilo", no_argument, 0, 'k'},
{"kilobyte", no_argument, 0, 'k'},
{"last", required_argument, 0, 'l'},
{"mode", required_argument, 0, 'm'},
{"read", required_argument, 0, 'r'},
{"refresh", required_argument, 0, 'f'},
{"seconds", required_argument, 0, 's'},
{"size", required_argument, 0, 'z'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, '?'},
{NULL, 0, 0, 0}
};
void version() {
printf(APPNAME " " APPVERSION " - " __DATE__ "\n");
printf(APPCOPYRIGHT "\n");
}
void _usage() {
fprintf(stderr, "Usage: %s [options] file\n"
"\nOptions:\n"
" -b | --buckets n Use n buckets (default is %d, max is %d)\n"
" -s | --seconds n Run for n seconds (default is %d)\n"
" -l | --last n Seconds for last bucket (default is %d)\n"
" -m | --mode mode Mode can be 'read' or 'write' (default is read)\n"
" -r | --read n Read (or write) n KBs per call (default is %d)\n"
" -z | --size n Create a file of at most n kilobytes (default is %d)\n"
" -f | --refresh n Refresh display every n seconds (default is %d)\n"
" -k | --kilobyte Use 1024 for kilobyte (default is 1000)\n"
" -v | --version Show version and copyright information and quit\n"
" -? | --help Show this help message and quit (-?? = more help, etc.)\n",
progname,
DEFAULT_BUCKETS,
MAX_BUCKETS,
DEFAULT_SECONDS,
DEFAULT_SECONDS,
DEFAULT_KILOS,
DEFAULT_FILE_SIZE,
DEFAULT_REFRESH);
}
void examples() {
fprintf(stderr,
"\nExamples:\n"
" %s file & read from file (must already exist)\n"
" %s --mode write file & write to file 'file'\n"
" %s --size 1 --mode write file & write 1KB to file 'file'\n",
progname,
progname,
progname);
}
void usage(int exit_code) {
_usage();
exit(exit_code);
}
typedef enum {MODE_READ, MODE_WRITE} t_mode;
struct _opt {
int buckets;
bool kilobyte;
t_mode mode;
int kilos;
int last;
double refresh;
ULONGLONG refresh_ticks;
int seconds;
ULONGLONG size;
int help;
};
typedef struct _opt t_opt;
static t_opt opt = {
DEFAULT_BUCKETS, /* buckets */
false, /* kilobyte */
MODE_READ, /* mode */
DEFAULT_KILOS, /* kilos */
DEFAULT_LAST, /* last */
0, /* refresh */
0, /* refresh_ticks */
DEFAULT_SECONDS, /* seconds */
DEFAULT_FILE_SIZE, /* size */
0 /* help */
};
struct _bucket {
ULONGLONG ticks;
ULONGLONG ticks_div_2;
ULONGLONG bytes;
double ticksd;
double mb_per_sec;
char *title;
double title_divisor;
#ifdef _DEBUG
int debug;
#endif
};
typedef struct _bucket t_bucket;
t_bucket **bucket;
struct _read {
ULONGLONG start_tick;
ULONGLONG end_tick;
};
typedef struct _read t_read;
t_read **read;
int reads = DEFAULT_READS;
struct _stats {
ULONGLONG tick_frequency; /* ticks to seconds divisor */
double tick_frequencyd;
ULONGLONG start_ticks;
SYSTEMTIME lpStartTime;
char start_time[20];
ULONGLONG io_ticks;
};
typedef struct _stats t_stats;
t_stats stats;
/* per http://www.scit.wlv.ac.uk/cgi-bin/mansec?3C+basename */
static char* basename(char* s) {
char* rv;
if (!s || !*s)
return ".";
rv = s + strlen(s) - 1;
do {
if (*rv == '/' || *rv == '\\')
return rv + 1;
--rv;
} while (rv >= s);
return s;
}
static void FatalError(char *str) {
LPVOID lpMsgBuf;
DWORD err = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL);
fprintf(stderr, "\n%s: %s\n", str, lpMsgBuf);
exit(err ? err : 1);
}
static BOOL add_seconds(SYSTEMTIME *st, DWORD seconds, SYSTEMTIME *rv) {
FILETIME ft;
SystemTimeToFileTime(st, &ft);
ULARGE_INTEGER uli;
uli.HighPart = ft.dwHighDateTime;
uli.LowPart = ft.dwLowDateTime;
uli.QuadPart += (seconds * 10000000ui64);
ft.dwHighDateTime = uli.HighPart;
ft.dwLowDateTime = uli.LowPart;
return (BOOL) FileTimeToSystemTime(&ft, rv);
};
static char *systemtime_to_hhmmss(SYSTEMTIME *st, char *rv, int bufsiz) {
_snprintf(rv, bufsiz, "%02d:%02d:%02d", st->wHour, st->wMinute, st->wSecond);
return rv;
}
static char *seconds_to_hhmmss(DWORD seconds, char *rv, int bufsiz) {
DWORD hours = seconds / 3600;
seconds -= hours * 3600;
DWORD minutes = seconds / 60;
seconds -= minutes * 60;
_snprintf(rv, bufsiz, "%02d:%02d:%02d", hours, minutes, seconds);
return rv;
}
static ULONGLONG get_tick() {
typedef enum {STATE_UNINITIALIZED, STATE_USE_FREQUENCY, STATE_USE_TICKCOUNT} t_state;
static t_state state = STATE_UNINITIALIZED;
static DWORD last_ticks;
static ULONGLONG overflow_ticks = 0;
#define USE_TICK_COUNT
if (state == STATE_UNINITIALIZED) {
LARGE_INTEGER frequency = {0,0};
#ifndef USE_TICK_COUNT
QueryPerformanceFrequency(&frequency);
#endif // USE_TICK_COUNT
if (frequency.QuadPart >= 1000) {
state = STATE_USE_FREQUENCY;
stats.tick_frequency = frequency.QuadPart;
} else {
state = STATE_USE_TICKCOUNT;
stats.tick_frequency = 1000;
last_ticks = GetTickCount();
}
stats.tick_frequencyd = (double) (LONGLONG) stats.tick_frequency;
}
if (state == STATE_USE_FREQUENCY) {
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
return now.QuadPart;
}
DWORD ticks = GetTickCount();
if (ticks < last_ticks) {
overflow_ticks += 0x100000000;
}
return overflow_ticks + (ULONGLONG) ticks;
}
static void print_stats(int reads_made, ULONGLONG first_tick) {
static int bytes_per_read = opt.kilos * 1024;
static double kilosd = opt.kilobyte ? 1024 : 1000;
int i;
int j;
for (i = 0; i < opt.buckets; ++i) {
bucket[i]->bytes = 0;
}
ULONGLONG now_tick = get_tick();
#ifdef _DEBUG
printf("now_tick=%d\n", now_tick);
printf("\n");
printf(" end start elapsed ");
for (i = 0; i < opt.buckets; ++i) {
printf("%5d ", bucket[i]->ticks);
}
printf("\n");
printf(" i ticks ticks ticks ");
for (i = 0; i < opt.buckets; ++i) {
printf("%5d ", i);
}
printf("\n");
printf("----- ------- ------- ------- ");
for (i = 0; i < opt.buckets; ++i) {
printf("----- ");
}
printf("\n");
#endif
for (i = 0; i < reads_made; ++i) {
ULONGLONG end_ticks = now_tick - read[i]->end_tick;
ULONGLONG start_ticks = now_tick - read[i]->start_tick;
ULONGLONG elapsed_ticks = read[i]->end_tick - read[i]->start_tick;
#ifdef _DEBUG
printf("%5d %7I64d %7I64d %7I64d ", i, end_ticks, start_ticks, elapsed_ticks);
// printf("end_tick=%10I64d start_tick=%10I64d ", read[i]->end_tick, read[i]->start_tick);
for (j = 0; j < opt.buckets; ++j) {
bucket[j]->debug = 0;
}
#endif
for (j = 0; j < opt.buckets; ++j) {
if (start_ticks < bucket[j]->ticks) {
#ifdef _DEBUG
bucket[j]->debug = 1;
#endif
bucket[j]->bytes += bytes_per_read;
continue;
}
if (end_ticks >= bucket[j]->ticks) {
#ifdef _DEBUG
bucket[j]->debug = 2;
#endif
continue;
}
#ifdef _DEBUG
bucket[j]->debug = 3;
if (elapsed_ticks) {
ULONGLONG ull = bytes_per_read * (bucket[j]->ticks - end_ticks) / elapsed_ticks;
if (ull > bytes_per_read) {
__asm {int 3}
}
}
#endif
if (elapsed_ticks) {
bucket[j]->bytes += bytes_per_read * (bucket[j]->ticks - end_ticks) / elapsed_ticks;
} else {
bucket[j]->bytes += bytes_per_read;
}
/*
buckets[0].ticks = 32 (0->32)
buckets[1].ticks = 16 (0->16)
buckets[2].ticks = 8 (0->8)
buckets[3].ticks = 4 (0->4)
buckets[4].ticks = 2 (0->2)
+--+----+--------+----------------+--------------------------------+
0 2 4 8 16 32
+--+ (bucket 4)
+-------+ (bucket 3)
+----------------+ (bucket 2)
+---------------------------------+ (bucket 1)
+------------------------------------------------------------------+ (bucket 0)
32 30 28 24 16 0
+--+----+--------+----------------+--------------------------------+
| |
29 25
now = 32
end_tick = 29
start_tick = 25
end_ticks = 32 - 29 = 3
start_ticks = 32 - 25 = 7
elapsed_ticks = 4
*/
}
#ifdef _DEBUG
for (j = 0; j < opt.buckets; ++j) {
printf("%5d ", bucket[j]->debug);
}
printf("\n");
#endif
}
#ifdef _DEBUG
printf("*****************************************************************************\n");
#endif
for (i = 0; i < opt.buckets; ++i) {
bucket[i]->mb_per_sec = ((double) (LONGLONG) bucket[i]->bytes) / bucket[i]->ticksd / kilosd;
}
ULONGLONG first_ticks = now_tick - first_tick;
for (i = 0 ; i < opt.buckets; ++i) {
if (bucket[i]->ticks > first_ticks) {
break;
}
char *sp = i < opt.buckets - 1 ? " " : "";
printf("%7.3f%s", bucket[i]->mb_per_sec, sp);
}
printf("\r");
printf("\n");
#ifdef _DEBUG
printf("\n");
printf("\nbytes: ");
for (i = 0 ; i < opt.buckets; ++i) {
if (bucket[i]->ticks > first_ticks) {
break;
}
char *sp = i < opt.buckets - 1 ? " " : "";
printf("%10I64d%s", bucket[i]->bytes, sp);
}
printf("\n");
printf("\nseconds: ");
for (i = 0 ; i < opt.buckets; ++i) {
if (bucket[i]->ticks > first_ticks) {
break;
}
char *sp = i < opt.buckets - 1 ? " " : "";
printf("%05.5f%s", bucket[i]->ticksd / stats.tick_frequencyd, sp);
}
printf("\nbytes/sec: ");
for (i = 0 ; i < opt.buckets; ++i) {
if (bucket[i]->ticks > first_ticks) {
break;
}
char *sp = i < opt.buckets - 1 ? " " : "";
printf("%05.5f%s", ((double) (LONGLONG) bucket[i]->bytes) / bucket[i]->ticksd, sp);
}
printf("\nKB/sec: ");
for (i = 0 ; i < opt.buckets; ++i) {
if (bucket[i]->ticks > first_ticks) {
break;
}
char *sp = i < opt.buckets - 1 ? " " : "";
printf("%05.5f%s", ((double) (LONGLONG) bucket[i]->bytes) / bucket[i]->ticksd / kilosd, sp);
}
printf("\nMB/sec: ");
for (i = 0 ; i < opt.buckets; ++i) {
if (bucket[i]->ticks > first_ticks) {
break;
}
char *sp = i < opt.buckets - 1 ? " " : "";
printf("%05.5f%s", ((double) (LONGLONG) bucket[i]->bytes) / bucket[i]->ticksd / kilosd / kilosd, sp);
}
printf("\n");
printf("*****************************************************************************\n");
#endif
fflush(stdout);
}
//
static void GetSizeString (LONGLONG size, wchar_t *str) {
static wchar_t *b, *kb, *mb, *gb, *tb, *pb;
if (b == NULL) {
if (opt.kilobyte) {
kb = L"KB";
mb = L"MB";
gb = L"GB";
tb = L"TB";
pb = L"PB";
} else {
kb = L"KiB";
mb = L"MiB";
gb = L"GiB";
tb = L"TiB";
pb = L"PiB";
}
b = L"bytes";
}
DWORD kilo = opt.kilobyte ? 1024 : 1000;
LONGLONG kiloI64 = kilo;
double kilod = kilo;
if (size > kiloI64 * kilo * kilo * kilo * kilo * 99)
swprintf (str, L"%I64d %s", size/ kilo / kilo /kilo/kilo/kilo, pb);
else if (size > kiloI64*kilo*kilo*kilo*kilo)
swprintf (str, L"%.1f %s",(double)(size/kilod/kilo/kilo/kilo/kilo), pb);
else if (size > kiloI64*kilo*kilo*kilo*99)
swprintf (str, L"%I64d %s",size/kilo/kilo/kilo/kilo, tb);
else if (size > kiloI64*kilo*kilo*kilo)
swprintf (str, L"%.1f %s",(double)(size/kilod/kilo/kilo/kilo), tb);
else if (size > kiloI64*kilo*kilo*99)
swprintf (str, L"%I64d %s",size/kilo/kilo/kilo, gb);
else if (size > kiloI64*kilo*kilo)
swprintf (str, L"%.1f %s",(double)(size/kilod/kilo/kilo), gb);
else if (size > kiloI64*kilo*99)
swprintf (str, L"%I64d %s", size/kilo/kilo, mb);
else if (size > kiloI64*kilo)
swprintf (str, L"%.1f %s",(double)(size/kilod/kilo), mb);
else if (size > kiloI64)
swprintf (str, L"%I64d %s", size/kilo, kb);
else
swprintf (str, L"%I64d %s", size, b);
}
//
void print_ticks(char *fmt, ULONGLONG ticks, ULONGLONG tick_frequency) {
char io_time[255];
DWORD seconds = (DWORD) (ticks / tick_frequency);
seconds_to_hhmmss(seconds, io_time, sizeof(io_time));
printf(fmt, io_time);
}
int time_it(char *device_name) {
stats.start_ticks = get_tick();
stats.io_ticks = 0;
GetLocalTime(&stats.lpStartTime);
systemtime_to_hhmmss(&stats.lpStartTime, stats.start_time, sizeof(stats.start_time));
stats.start_ticks = get_tick();
// stats.device_name = device_name;
char err[256];
printf("File: %s\n", device_name);
HANDLE hnd = CreateFile(
device_name,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
opt.mode == MODE_READ ? OPEN_EXISTING : CREATE_ALWAYS, // todo fixme
0,
NULL);
if (hnd == INVALID_HANDLE_VALUE) {
_snprintf(err, sizeof(err), "Cannot open '%s'", device_name);
FatalError(err);
}
ULONGLONG last_sector = 0;
int bytes_per_read = opt.kilos * 1024;
char *buffer = (char *) malloc(bytes_per_read);
static double kilo = opt.kilobyte ? 1024 : 1000;
static char * mb = opt.kilobyte ? " MB" : "MiB";
int i;
for (i = 0; i < opt.buckets; ++i) {
char *sp = i < opt.buckets - 1 ? " " : "";
printf("%7.3f%s", bucket[i]->ticksd / stats.tick_frequencyd / bucket[i]->title_divisor, sp);
}
printf("\n");
for (i = 0; i < opt.buckets; ++i) {
char *sp = i < opt.buckets - 1 ? " " : "";
printf(" %s%s", bucket[i]->title, sp);
}
printf("\n");
for (i = 0; i < opt.buckets; ++i) {
char *sp = i < opt.buckets - 1 ? " " : "";
printf("%s/sec%s", mb, sp);
}
printf("\n");
for (i = 0; i < opt.buckets; ++i) {
char *sp = i < opt.buckets - 1 ? " " : "";
printf("-------%s", sp);
}
printf("\n");
LARGE_INTEGER li;
DWORD lpFileSizeHigh;
SetLastError(0);
li.LowPart = GetFileSize(hnd, &lpFileSizeHigh);
li.HighPart = lpFileSizeHigh;
if (GetLastError()) {
_snprintf(err, sizeof(err), "Failed to get file size for %s", device_name);
FatalError(err);
}
ULONGLONG file_size = li.QuadPart;
if (file_size < bytes_per_read) {
_snprintf(err, sizeof(err), "File %s must be at least %d bytes in size", device_name, bytes_per_read);
FatalError(err);
}
if (opt.size == 0) {
opt.size = li.QuadPart;
}
char *action = opt.mode == MODE_READ ? "read" : "write";
ULONGLONG end_tick = stats.start_ticks + opt.seconds * stats.tick_frequency;
ULONGLONG first_tick = get_tick();
ULONGLONG current_tick = first_tick;
ULONGLONG last_tick = first_tick;
int read_index = 0;
bool all_reads_made = false;
while (current_tick < end_tick) {
li.QuadPart = 0;
li.LowPart = SetFilePointer(hnd, li.LowPart, &li.HighPart, FILE_BEGIN);
//todo check error
for (ULONGLONG loop = 0; loop < file_size / bytes_per_read; ++loop) {
if (current_tick >= end_tick) {
break;
}
DWORD dwBytes = 0;
ULONGLONG before_tick = get_tick();
SetLastError(0);
BOOL rv;
switch (opt.mode) {
case MODE_READ:
rv = ReadFile(hnd, buffer, bytes_per_read, &dwBytes, NULL);
break;
case MODE_WRITE:
#ifdef DUMMY_WRITE
rv = true;
dwBytes = bytes_per_read;
#else
// rv = WriteFile(hnd, buffer, bytes_per_read, &dwBytes, NULL);
#endif
break;
default:
FatalError("Unsupported mode");
}
if (!rv || GetLastError()) {
_snprintf(err, sizeof(err), "Failed to %s %d bytes at byte %I64d", action, bytes_per_read - dwBytes, loop * bytes_per_read);
FatalError(err);
}
ULONGLONG after_tick = get_tick();
read[read_index]->start_tick = before_tick;
read[read_index]->end_tick = after_tick;
#ifdef _DEBUG
//printf("before_tick=%10I64d after_tick=%10I64d end_tick=%10I64d start_tick=%10I64d\n", before_tick, after_tick, read[read_index]->end_tick, read[read_index]->start_tick);
#endif
++read_index;
if (read_index >= reads) {
all_reads_made = true;
read_index = 0;
}
stats.io_ticks += after_tick - before_tick;
if (after_tick - last_tick >= opt.refresh_ticks) {
last_tick = after_tick;
print_stats(all_reads_made ? reads : read_index, first_tick);
}
current_tick = after_tick;
}
}
print_stats(all_reads_made ? reads : read_index, first_tick);
printf("\n");
fflush(stdout);
// printf("\n");
// print_ticks("io time: %s\n", stats.io_ticks, stats.tick_frequency);
// ULONGLONG elapsed_ticks = get_tick() - stats.start_ticks;
// print_ticks("Elapsed time: %s\n", elapsed_ticks, stats.tick_frequency);
// printf("\n");
free(buffer);
CloseHandle(hnd);
return 0;
}
int main(int argc, char * argv[]) {
progname = basename(argv[0]);
if (progname) {
int len = strlen(progname);
if (len > 4 && _stricmp(progname + len - 4, ".exe") == 0)
progname[len - 4] = '\0';
}
opterr = 0;
int option_index = 0;
optind = 1;
int i;
while (true) {
long l;
if (optind < argc && argv[optind] && argv[optind][0] == '/')
argv[optind][0] = '-';
int c = getopt_long(argc, argv, short_options, long_options, &option_index);
if (opterr) {
usage(1);
}
if (c == -1)
break;
switch (c) {
case 'b':
opt.buckets = atoi(optarg);
if (opt.buckets <= 0 || opt.buckets > MAX_BUCKETS)
usage(1);
break;
case 'f':
opt.refresh = atof(optarg);
if (opt.refresh <= 0)
usage(1);
break;
case 'k':
opt.kilobyte = true;
break;
case 'l':
l = atol(optarg);
if (l <= 0)
usage(1);
opt.last = l;
break;
case 'm':
if (stricmp(optarg, "read") == 0)
opt.mode = MODE_READ;
else
if (stricmp(optarg, "write") == 0)
opt.mode = MODE_WRITE;
else
usage(1);
break;
case 'r':
opt.kilos = atoi(optarg);
if (opt.kilos == 0)
usage(1);
break;
case 's':
l = atol(optarg);
if (l <= 0)
usage(1);
opt.seconds = l;
break;
case 'z':
l = atol(optarg);
if (l <= 0)
usage(1);
opt.size = l;
break;
case 'v': /* -v | --version Show version and copyright information and quit */
version();
exit(0);
break;
case '?': /* -? | --help Show this help message and quit */
++opt.help;
break;
case ':':
fprintf(stderr, "Option -%c requires an operand\n", optopt);
// fallthrough
default:
usage(1);
}
}
if (opt.help) {
_usage();
if (opt.help > 1) {
examples();
}
exit(0);
}
int devices = argc - optind;
if (devices == 0) {
fprintf(stderr, "%s: No files specified\n", progname);
usage(1);
}
stats.start_ticks = get_tick();
bucket = (t_bucket **) malloc(opt.buckets * sizeof(t_bucket *));
for (i = 0; i < opt.buckets; ++i) {
bucket[i] = (t_bucket*) malloc(sizeof(t_bucket));
}
int ticks = (int) (opt.last * stats.tick_frequency);
for (i = opt.buckets - 1; i >= 0; --i) {
ULONGLONG seconds = ticks / stats.tick_frequency;
if (seconds >= 3600) {
bucket[i]->title = " hour";
bucket[i]->title_divisor = 3600;
} else
if (seconds >= 60) {
bucket[i]->title = "minute";
bucket[i]->title_divisor = 60;
} else {
bucket[i]->title = "second";
bucket[i]->title_divisor = 1;
}
bucket[i]->ticks = ticks;
bucket[i]->ticksd = (double) (LONGLONG) ticks;
bucket[i]->ticks_div_2 = ticks / 2;
bucket[i]->mb_per_sec = 0;
ticks /= 2;
if (ticks == 0)
ticks = 1;
}
if (opt.refresh > 0) {
opt.refresh_ticks = (ULONGLONG) (LONGLONG) (opt.refresh * stats.tick_frequencyd);
} else {
opt.refresh = bucket[0]->ticksd / stats.tick_frequencyd;
opt.refresh_ticks = bucket[0]->ticks;
}
read = (t_read **) malloc(reads * sizeof(t_read *));
for (i = 0; i < reads; ++i) {
read[i] = (t_read*) malloc(sizeof(t_read));
read[i]->start_tick = 0;
read[i]->end_tick = 0;
}
for (i = optind; i < argc; ++i) {
time_it(argv[i]);
}
for (i = 0; i < opt.buckets; ++i) {
free(bucket[i]);
}
free(bucket);
for (i = 0; i < reads; ++i) {
free(read[i]);
}
free(read);
return 0;
}