/* * Copyright (c) 2022 Roger Seguin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ static char rcsid[] = "$Id: diskcpbench.C,v 1.1 2022/10/21 08:39:12 RogerSeguin Exp $" "\nCopyright (c) 2022 Roger Seguin "; /* Description: Indicate how much time it takes for copying a file of given size. Copy can be done across separate disks or network. (disk I/O and network benchmark) Usage: copybench options input_file output_file */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #define TMPFILE "diskcpbench.dat" static int Usage (const char *const This, FILE * p_pFOut) { FILE *pFOut = p_pFOut; fprintf (stdout, "%s\n", rcsid + 5); fprintf (stdout, "Disk/network benchmarking tool\n"); fprintf (pFOut, "Usage\n"); fprintf (pFOut, "\t%s [-b block_size] [-s file_size] source destination\n", This); fprintf (pFOut, "\t%s -h\n", This); fprintf (pFOut, "Options\n"); fprintf (pFOut, "\t-b blockSize:\tread/write by blocks of indicated size in kiB\n" "\t\t\t(default is 64 kiB)\n"); fprintf (pFOut, "\t-s fileSize:\t" "size in MiB of the bencmarking file\n" "\t\t\t(default is 1024 MiB)\n"); fprintf (pFOut, "\t-h:\t\toutput this help and exit\n"); fprintf (pFOut, "Description\n"); fprintf (pFOut, "\tCreate the benchmarking file \"source\" and copy it to \"destination\"\n"); fprintf (pFOut, "\t(If the indicated source is a directory, \"" TMPFILE "\" will be used)\n"); fprintf (pFOut, "\tBoth source and destination files are removed after measurement\n"); return (0); } static int64_t CurrentTime_ms () { struct timespec oTime; int status; status = clock_gettime (CLOCK_REALTIME, &oTime); if (status == -1) { perror ("clock_gettime()"); return (-1); } return (oTime.tv_sec * 1000 + oTime.tv_nsec / 1000000); } static int Write (const size_t p_DataSize_MiB, const char *p_FileName, const size_t p_BlockSize_B) { const int64_t DataSize_B = (int64_t) p_DataSize_MiB * 1024 * 1024; const int nloops = DataSize_B / p_BlockSize_B; char acBuffer[p_BlockSize_B]; int fd; int64_t nbytes = 0; memset (acBuffer, 0, sizeof (acBuffer)); fd = open("/dev/urandom", O_RDONLY); if (fd == -1) perror ("/dev/urandom"); else { read(fd, acBuffer, sizeof (acBuffer)); close(fd); } fd = open (p_FileName, O_CREAT | O_RDWR, S_IRUSR + S_IWUSR); if (fd == -1) { perror (p_FileName); return (-1); } for (int i = 0; i < nloops; i++) { ssize_t n = write (fd, acBuffer, sizeof (acBuffer)); if (n == -1) { perror ("write()"); close (fd); return (-1); } else if (!n) break; nbytes += n; } fsync (fd); close (fd); if (nbytes != DataSize_B) { fprintf (stderr, "Unknown writing error\n"); return (-1); } return (0); } int Copy (const size_t p_DataSize_MiB, const char *p_FileName1, const char *p_FileName2, const size_t p_BlockSize_B) { const int64_t DataSize_B = (int64_t) p_DataSize_MiB * 1024 * 1024; const int nloops = DataSize_B / p_BlockSize_B; char acBuffer[p_BlockSize_B]; int fd1, fd2; int64_t nbytes = 0; int64_t t1, t2; // ms t1 = CurrentTime_ms (); fd1 = open (p_FileName1, O_RDONLY); if (fd1 == -1) { perror (p_FileName1); return (-1); } fd2 = open (p_FileName2, O_CREAT | O_RDWR, S_IRUSR + S_IWUSR); if (fd2 == -1) { perror (p_FileName2); close (fd1); return (-1); } for (int i = 0; i < nloops; i++) { ssize_t n = read (fd1, acBuffer, sizeof (acBuffer)); if (n == -1) { perror ("read()"); close (fd1); close (fd2); return (-1); } else if (!n) break; write (fd2, acBuffer, n); nbytes += n; } close (fd1); fsync(fd2); close (fd2); t2 = CurrentTime_ms (); if (nbytes != DataSize_B) { fprintf (stderr, "Unknown reading error\n"); return (-1); } return (static_cast < int >(t2 - t1)); } static void printResult(const char *p_pcAction, size_t p_DataSize_MiB, int p_duration_ms, size_t p_BlockSize_kiB) { size_t speedMiB_perSec = (p_duration_ms ? (1000 * p_DataSize_MiB / p_duration_ms) : 0); size_t speedMB_perSec = speedMiB_perSec * 1024 * 1024 / 1000000; double speedGbit_perSec = 8.0 * speedMB_perSec / 1000.0; printf ( "%s: %lu MiB by %lu-kiB blocks in %u ms (%lu MiB/s = %lu MB/s = %.1f Gbit/s)\n", p_pcAction, p_DataSize_MiB, p_BlockSize_kiB, p_duration_ms, speedMiB_perSec, speedMB_perSec, speedGbit_perSec); } static bool isDirectory(const char* path) { struct stat attr; int status = stat(path, &attr); return ((status == 0) && S_ISDIR(attr.st_mode)); } static void setFilename (char *filename, const char *path, const char *defaultBasename) { strcpy (filename, path); char *pc = filename + strlen(filename) - 1; if (*pc == '/') *pc = 0; /* Remove trailing "/" */ if (isDirectory (filename)) { strcat (filename, "/"); strcat (filename, defaultBasename); } } int main (int argc, char **argv) { size_t DataSize_MiB = 1024; /* How much to read/write? */ char FileName1[PATH_MAX]; char FileName2[PATH_MAX]; size_t BlockSize_kiB = 16; /* by blocks of what size? */ char c; int fError = 0, fUsage = 0; int d; // ms /* Parse command line options */ putenv ((char *) "POSIXLY_CORRECT=1"); for (; !fUsage && !fError && (c = getopt (argc, argv, "b:s:h")) != EOF;) switch (c) { case 'b': /* Block size */ BlockSize_kiB = atoi (optarg); fError |= (BlockSize_kiB <= 0); break; case 's': /* Block size */ DataSize_MiB = atoi (optarg); fError |= (DataSize_MiB <= 0); break; case 'h': /* Help */ fUsage = 1; break; case '?': default: fError = 1; } fError |= (argc != (optind + 2)); fUsage |= fError; /* Display usage message whether requested or error detected */ if (fUsage) { Usage (basename (argv[0]), fError ? stderr : stdout); return (fError ? ~0 : 0); } setFilename (FileName1, argv[1], TMPFILE); setFilename (FileName2, argv[2], basename (FileName1)); size_t BlockSize_B = BlockSize_kiB * 1024; printf ("Preparing %ld-MiB data file %s to copy\n", DataSize_MiB, FileName1); unlink (FileName1); d = Write (DataSize_MiB, FileName1, BlockSize_B); if (d == -1) return (~0); printf ("Copying %s to %s\n", FileName1, FileName2); unlink (FileName2); d = Copy (DataSize_MiB, FileName1, FileName2, BlockSize_B); if (d != -1) printResult("Copy", DataSize_MiB, d, BlockSize_kiB); unlink (FileName1); unlink (FileName2); return (0); } /* * $Log: diskcpbench.C,v $ * Revision 1.1 2022/10/21 08:39:12 RogerSeguin * Initial revision * */