├── .gitignore ├── makefile ├── makefile.debug ├── src ├── opts.h ├── fileio.h ├── util.h ├── util.c ├── opts.c ├── mqldt.c └── fileio.c ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | #Ignore Object files 2 | *.o 3 | 4 | #Ignore executable 5 | mqldt 6 | 7 | #Ignore CSV files 8 | *.csv 9 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # 2 | #******************************************************************************** 3 | # Copyright (c) 2017 IBM Corp. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # 18 | # Contributors: 19 | # Paul Harris - Initial implementation 20 | #******************************************************************************** 21 | # 22 | #******************************************************************************** 23 | #* * 24 | #* IBM MQ Log Disk Tester * 25 | #* * 26 | #******************************************************************************** 27 | CC=gcc 28 | 29 | # Source directory 30 | SRC = src 31 | 32 | CFLAGS= -pthread -lm -lrt 33 | 34 | # Full list of object files to be created (one for each source file) 35 | OBJS = $(patsubst $(SRC)/%.c, $(SRC)/%.o, $(wildcard $(SRC)/*.c)) 36 | 37 | mqldt: $(OBJS) 38 | $(CC) -o mqldt $(OBJS) $(CFLAGS) 39 | 40 | clean: 41 | rm $(SRC)/*.o mqldt 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /makefile.debug: -------------------------------------------------------------------------------- 1 | # 2 | #******************************************************************************** 3 | # Copyright (c) 2017 IBM Corp. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # 18 | # Contributors: 19 | # Paul Harris - Initial implementation 20 | #******************************************************************************** 21 | # 22 | #******************************************************************************** 23 | #* * 24 | #* IBM MQ Log Disk Tester * 25 | #* * 26 | #******************************************************************************** 27 | CC=gcc 28 | 29 | # Source directory 30 | SRC = src 31 | 32 | CFLAGS= -pthread -lm -lrt -g 33 | 34 | # Full list of object files to be created (one for each source file) 35 | OBJS = $(patsubst $(SRC)/%.c, $(SRC)/%.o, $(wildcard $(SRC)/*.c)) 36 | 37 | mqldt: $(OBJS) 38 | $(CC) -o mqldt $(OBJS) $(CFLAGS) 39 | 40 | clean: 41 | rm $(SRC)/*.o mqldt 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/opts.h: -------------------------------------------------------------------------------- 1 | /**/ 2 | /******************************************************************************* 3 | * Copyright (c) 2017 IBM Corp. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | /* 18 | * Contributors: 19 | * Paul Harris - Initial implementation 20 | *******************************************************************************/ 21 | /**/ 22 | /*******************************************************************************/ 23 | /* */ 24 | /* IBM MQ Log Disk Tester */ 25 | /* */ 26 | /*******************************************************************************/ 27 | #ifndef _OPTS_H 28 | #define _OPTS_H 29 | 30 | void parseOptions(int, char **const); 31 | void printOptions(); 32 | 33 | struct Options { 34 | char *blockSizeStr; /* -b, --bsize option */ 35 | int *blockSize; /* Parsed blocksizes */ 36 | int blockSizeCount; /* Number of elements in blockSize */ 37 | long alignment; /* Memory alignment of buffer */ 38 | char *directory; /* -d, --dir option */ 39 | char *filePrefix; /* -f, --filePrefix */ 40 | int numFiles; /* -n, --numFiles */ 41 | int fileSize; /* -s, --fileSize */ 42 | int duration; /* -t, --duration */ 43 | int linearLogging; /* -l, --linear */ 44 | char *csvFile; /* -c, --csvFile */ 45 | int backgroundThreads; /* -p, --pthreads */ 46 | int qm; /* -q, --qm */ 47 | int delay; /* -z, --delay */ 48 | }; 49 | 50 | extern struct Options options; 51 | 52 | #endif -------------------------------------------------------------------------------- /src/fileio.h: -------------------------------------------------------------------------------- 1 | /**/ 2 | /******************************************************************************* 3 | * Copyright (c) 2017 IBM Corp. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | /* 18 | * Contributors: 19 | * Paul Harris - Initial implementation 20 | *******************************************************************************/ 21 | /**/ 22 | /*******************************************************************************/ 23 | /* */ 24 | /* IBM MQ Log Disk Tester */ 25 | /* */ 26 | /*******************************************************************************/ 27 | #ifndef _FILEIO_H 28 | #define _FILEIO_H 29 | 30 | #include 31 | #include "util.h" 32 | 33 | struct fstats { 34 | int update_count; 35 | long persec_bytes; 36 | long total_writes; 37 | long total_bytes; 38 | long interval_max_bytes_sec; 39 | long interval_min_bytes_sec; 40 | long interval_max_latency; 41 | long interval_min_latency; 42 | long max_bytes_sec; 43 | long min_bytes_sec; 44 | long avg_bytes_sec; 45 | long intervalTimer; 46 | }; 47 | 48 | struct fileStore { 49 | int **files; 50 | int currentFile; 51 | int fs_blockSize; 52 | struct iovec writeVec[1]; 53 | struct fstats stats; 54 | struct timer resultTimings; 55 | int thread; 56 | }; 57 | 58 | struct fileStore *prepareFiles(int tnum); 59 | struct fileStore *prepareStats(int tnum); 60 | ssize_t writeToFile(struct fileStore *fs, int writeBlockSize); 61 | void setBlockSize(struct fileStore *fs, int writeBlockSize); 62 | void resetFiles(struct fileStore *fs); 63 | void closeFiles(struct fileStore *fs); 64 | void updateFileStats(struct fileStore *fs); 65 | void printFileStats(struct fileStore *fs); 66 | void printQMFileStats(struct fileStore *fs); 67 | void csvFileStatsTitles(FILE *csvFile); 68 | void csvQMFileStatsTitles(FILE *csvFile); 69 | void csvFileStats(struct fileStore *fs, FILE *csvFile); 70 | void csvQMFileStats(struct fileStore *fs, FILE *csvFile); 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /**/ 2 | /******************************************************************************* 3 | * Copyright (c) 2017 IBM Corp. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | /* 18 | * Contributors: 19 | * Paul Harris - Initial implementation 20 | *******************************************************************************/ 21 | /**/ 22 | /*******************************************************************************/ 23 | /* */ 24 | /* IBM MQ Log Disk Tester */ 25 | /* */ 26 | /*******************************************************************************/ 27 | #ifndef _UTIL_H 28 | #define _UTIL_H 29 | 30 | #include 31 | #include 32 | 33 | #define tStart(X) clock_gettime(CLOCK_MONOTONIC, X.check_time1); 34 | #define ONE_SEC_IN_NS 1000000000 35 | #define HALF_SEC_IN_NS 500000000 36 | 37 | extern FILE *csvFile; 38 | extern pthread_mutex_t mutex; 39 | extern pthread_cond_t condition; 40 | extern volatile int started; 41 | 42 | struct timer { 43 | struct timespec *start_time; /*Make these pointers so we can flip them easily in tCheck*/ 44 | struct timespec *check_time1; 45 | struct timespec *check_time2; 46 | int check_count; 47 | long min_time; 48 | int min_time_instance; 49 | long avg_time; 50 | long max_time; 51 | int max_time_instance; 52 | }; 53 | 54 | //Calculates time difference between the current time and the last time this method was called, updates min/max/avg time values 55 | static inline long tCheck(struct timer *timerIn) { 56 | long duration; 57 | struct timespec *temp; 58 | 59 | clock_gettime(CLOCK_MONOTONIC, timerIn->check_time2); 60 | timerIn->check_count++; 61 | 62 | if (timerIn->check_time2->tv_nsec > timerIn->check_time1->tv_nsec) { 63 | duration = ((timerIn->check_time2->tv_sec - timerIn->check_time1->tv_sec) * ONE_SEC_IN_NS - (timerIn->check_time1->tv_nsec - timerIn->check_time2->tv_nsec)); 64 | } else { 65 | duration = ((timerIn->check_time2->tv_sec - timerIn->check_time1->tv_sec) * ONE_SEC_IN_NS + (timerIn->check_time2->tv_nsec - timerIn->check_time1->tv_nsec)); 66 | } 67 | 68 | if (duration > timerIn->max_time) { 69 | timerIn->max_time = duration; 70 | timerIn->max_time_instance = timerIn->check_count; 71 | } 72 | if (duration < timerIn->min_time) { 73 | timerIn->min_time = duration; 74 | timerIn->min_time_instance = timerIn->check_count; 75 | } 76 | 77 | timerIn->avg_time = ((timerIn->avg_time * (timerIn->check_count - 1)) + duration) / (timerIn->check_count); 78 | 79 | /*flip the timespecs*/ 80 | temp = timerIn->check_time1; 81 | timerIn->check_time1 = timerIn->check_time2; 82 | timerIn->check_time2 = temp; 83 | 84 | return duration; 85 | } 86 | 87 | void tInit(struct timer *timerIn); 88 | void tReset(struct timer *timerIn); 89 | 90 | void printTimerStats(struct timer *timerIn, int blockSize); 91 | void csvTimerStatsTitles(FILE *csvFile); 92 | void csvTimerStats(struct timer *timerIn, FILE *csvFile, int blockSize); 93 | 94 | long file_GetPhysicalBlockSize(char *path); 95 | char *UtilMakeBigString(int size, long alignment); 96 | 97 | void *runTest(void *arg); 98 | void openCSVFile(); 99 | #endif 100 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /**/ 2 | /******************************************************************************* 3 | * Copyright (c) 2017 IBM Corp. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | /* 18 | * Contributors: 19 | * Paul Harris - Initial implementation 20 | *******************************************************************************/ 21 | /**/ 22 | /*******************************************************************************/ 23 | /* */ 24 | /* IBM MQ Log Disk Tester */ 25 | /* */ 26 | /*******************************************************************************/ 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "util.h" 33 | 34 | /* 35 | ** Method: UtilMakeBigString 36 | ** 37 | ** Function to build a string of a given size. 38 | ** The string is allocated with malloc and needs to be disposed of by the caller. 39 | ** 40 | ** Input Parameters: size - the length of the string to build 41 | ** 42 | ** Returns: a pointer to the built character string 43 | ** 44 | ** The string start with ascii 65 ("A") and increment up to ascii 122 ("z") 45 | ** before wrapping round again. 46 | */ 47 | char *UtilMakeBigString(int size, long alignment) { 48 | char *str = NULL; 49 | 50 | posix_memalign((void **)&str, alignment, size); 51 | if (NULL != str) { 52 | int i; 53 | char c = 65; 54 | for (i = 0; i < size; i++) { 55 | str[i] = c++; 56 | if (c > 122) 57 | c = 65; 58 | } 59 | 60 | /* Null terminate the string for trace etc */ 61 | str[size - 1] = '\0'; 62 | } 63 | /*printf("Buffer address %p\n", (void *) str);*/ 64 | return (char *)(str); 65 | } 66 | 67 | long file_GetPhysicalBlockSize(char *path) { 68 | struct stat sb; 69 | if (stat(path, &sb) == -1) { 70 | printf("Can't stat %s\n", path); 71 | exit(EXIT_FAILURE); 72 | } 73 | return (long)sb.st_blksize; 74 | } 75 | 76 | void tInit(struct timer *timerIn) { 77 | timerIn->start_time = (struct timespec *)malloc(sizeof(struct timespec)); 78 | timerIn->check_time1 = (struct timespec *)malloc(sizeof(struct timespec)); 79 | timerIn->check_time2 = (struct timespec *)malloc(sizeof(struct timespec)); 80 | timerIn->max_time = 0; 81 | timerIn->min_time = LONG_MAX; 82 | timerIn->avg_time = 0; 83 | timerIn->check_count = 0; 84 | clock_gettime(CLOCK_MONOTONIC, timerIn->start_time); 85 | } 86 | 87 | void tReset(struct timer *timerIn) { 88 | timerIn->max_time = 0; 89 | timerIn->min_time = LONG_MAX; 90 | timerIn->avg_time = 0; 91 | timerIn->check_count = 0; 92 | clock_gettime(CLOCK_MONOTONIC, timerIn->start_time); 93 | } 94 | 95 | const char maxLatencyDesc[] = "Max latency of write (ns)"; 96 | const char minLatencyDesc[] = "Min latency of write (ns)"; 97 | const char avgLatencyDesc[] = "Avg latency of write (ns)"; 98 | const char maxBytesSecDesc[] = "Max bytes/sec (fastest write)"; 99 | const char minBytesSecDesc[] = "Min bytes/sec (slowest write)"; 100 | 101 | void printTimerStats(struct timer *timerIn, int blockSize) { 102 | printf("%s : %'15li (#%'i)\n", maxLatencyDesc, timerIn->max_time, timerIn->max_time_instance); 103 | printf("%s : %'15li\n", minBytesSecDesc, (long)(blockSize / ((float)timerIn->max_time / 1000000000))); 104 | printf("%s : %'15li (#%'i)\n", minLatencyDesc, timerIn->min_time, timerIn->min_time_instance); 105 | printf("%s : %'15li\n", maxBytesSecDesc, (long)(blockSize / ((float)timerIn->min_time / 1000000000))); 106 | printf("%s : %'15li\n", avgLatencyDesc, timerIn->avg_time); 107 | } 108 | 109 | void csvTimerStatsTitles(FILE *csvFile) { 110 | fprintf(csvFile, "%s,%s,%s,%s,%s", maxLatencyDesc, minLatencyDesc, avgLatencyDesc, minBytesSecDesc, maxBytesSecDesc); 111 | } 112 | 113 | void csvTimerStats(struct timer *timerIn, FILE *csvFile, int blockSize) { 114 | fprintf(csvFile, "%li,%li,%li,%li,%li", timerIn->max_time, timerIn->min_time, timerIn->avg_time, (long)(blockSize / ((float)timerIn->max_time / 1000000000)), (long)(blockSize / ((float)timerIn->min_time / 1000000000))); 115 | } 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mqldt - IBM MQ Log Disk Tester 2 | 3 | The purpose of this tool is to test the capability of a Linux mounted filesystem being used (or being proposed) to host an MQ recovery log. 4 | 5 | Warning!! 6 |
  • If any queue manager is currently using the filesystem being tested:
  • 7 |
    • Do NOT specify existing log files to write to.
    • 8 |
    • Create a new directory on the filesystem to be tested (this must be in the same file system as the current MQ logs)
    • 9 |
    • Running this tool WILL adversely affect the performance of the existing queue manager(s), for the duration of the test, (which may, in turn, cause applications problems whose symptoms last beyond the filesystem test.
    • 10 |
11 | 12 | The tool is similar in purpose to fio, but has been written to test writing to a filesystem with the same options, and in the same way, that MQ writes to its recovery logs, without having to specify lots of options on the command line to get it right. 13 | 14 | There are additionally, some problems with the use of fio, which this tool addresses (like being unable to get the open and write options exactly the same as MQ, and the pre-loading and usage pattern of the log files). 15 | 16 | What this tool cannot do, is tell you exactly what performance you will get from your persistent MQ workload, even when log writes are the limiting factor. The workload on a machine can impact the performance of the IOPs, especially on modern multi-core machines. The best solution is always to test MQ itself. 17 | 18 |
 19 | Usage:
 20 | 
 21 | mqldt <options>
 22 | 
 23 | Options:
 24 | --dir         : Directory in which create and test files being written to. 
 25 | --filePrefix  : prefix of test files
 26 | --bsize       : Blocksize(s), of writes to be used. Can be a single value, or a comma separated list. Supports k suffix.
 27 | --fileSize    : File size of each test file
 28 | --numFiles    : Number of files to create and use
 29 | --duration    : Number of seconds to test
 30 | --csvFile     : Optional csv output file
 31 | --qm          : Number of Queue Managers
 32 | --delay       : Delay to add to each IO write (microsec)
 33 | 
34 | e.g. 35 | mqldt --dir=/var/san1/testdir --filePrefix=mqtestfile --bsize=5K,9K,17K,24K,49K,48K,54K,62K,77K,95K,105K --fileSize=67108864 --numFiles=16 --duration=40 --csvFile=./san_log.csv 36 | 37 | Use the values in qm.ini (or those planned) to calculate some of the parameters to MQLDT. 38 | 39 | 40 | 41 | 42 | 43 | 44 |
MQLDT Parmqm.ini parm(s)
--dirAny directory hosted by the same file-system as Log:LogPath
--fileSizeLog:LogFilePages x 4096
--numFilesLog:LogPrimaryFiles
45 | 46 | ## Sample output: 47 | 48 |
 49 | Options (specified or defaulted to)
 50 | =====================================================================================
 51 | Write blocksize             (--bsize)              : 128K
 52 | Directory to write to       (--dir)                : /var/san1/testdir
 53 | Test file prefix            (--filePrefix)         : mqtestfile
 54 | Number of files to write to (--numFiles)           : 24
 55 | Size of test files          (--fileSize)           : 67108864
 56 | Test duration               (--duration)           : 20
 57 | 
 58 | Creating files...
 59 | Executing test for write blocksize 131072 (128k). Seconds elapsed -> 20/20
 60 | 
 61 | Total writes to files                                :          46,178
 62 | Total bytes written to files                         :   6,052,642,816
 63 | Max bytes/sec written to files (over 1 sec interval) :     311,689,216
 64 | Min bytes/sec written to files (over 1 sec interval) :     297,795,584
 65 | Avg bytes/sec written to files                       :     302,732,356
 66 | 
 67 | Max latency of write (ns)      :       4,977,438 (#24,649)
 68 | Min bytes/sec (slowest write)  :      26,333,226
 69 | Min latency of write (ns)      :         375,889 (#42,421)
 70 | Max bytes/sec (fastest write)  :     348,698,688
 71 | Avg latency of write (ns)      :       4,935,648
 72 | 
73 | 74 | ## Description of Output 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
Total writes to filesThe total number of writev operations completed over the duration of the test
IOPSThe total number of writev operations completed per second over the duration of the test
Total bytes written to filesTotal bytes written over the duration of the test (i.e Total writes to files x blocksize)
Max bytes/sec written to files (over 1 sec interval)MQLDT calculates how much data has been written every second, this is the maximum write bandwidth measured for any one second duration. If the test is only run for one second this number will be the same as 'Avg bytes/sec written to files'
Min bytes/sec written to files (over 1 sec interval)This is the minimum write bandwidth measured for any one second duration '(see Max bytes/sec written to files (over 1 sec interval)', above). If the test is only run for one second this number will be the same as 'Avg bytes/sec written to files'
Avg bytes/sec written to filesAverage write bandwidth over duration of test.
Max latency of write (ns)The longest time (in nanoseconds) to complete a write during the test. The number of the write is indicated in brackets.
Min bytes/sec (slowest write)The theoretical minimum bandwidth, if every write had the maximum latency indicated above
Min latency of write (ns)The shortest time (in nanoseconds) to complete a write during the test. The number of the write is indicated in brackets.
Max bytes/sec (fastest write)The theoretical maximum bandwidth, if every write had the minimum latency indicated above
Avg latency of write (ns)The average time (in nanoseconds) to complete a write during the test.
88 | 89 | ## Multiple Queue Managers 90 | This tool has now been extended to simulate multiple Queue Managers writing to the same IO device. 91 | Use the `--qm=2` option to run with 2 queue managers, each owning their own set of log files as specified by the configuration provided, noting that for qm 2 to 10, a numeric value is appended to the directory location. 92 | 93 | i.e If `--qm=3 --dir=/tmp/mqldt` is provided, directories labeled `/tmp/mqldt1, /tmp/mqldt2 and /tmp/mqldt3` need to be created before the test is run 94 | 95 | Support for up to 10 QM is provided, although only a single block size can be executed per test. 96 | 97 | ## Container image (docker/podman) 98 | There is a repo that builds a dockerized version of mqldt called [mqldt-c](https://github.com/ibm-messaging/mqldt-c). 99 | 100 | The built image from that repo is available [here](https://quay.io/stmassey/mqldt) and can be pulled with the following command: 101 | ``` 102 | docker pull quay.io/stmassey/mqldt 103 | ``` 104 | 105 | ## Expected latency 106 | The latency reported will indicate the time spent waiting for the IO write to complete and will vary depending on the underlying storage and the size of the block size, but for some ballpark reference figures, here are some indications 107 | of latency measured in contrasting environments: 108 | 109 | 110 | 111 | 112 | 113 | 114 |
Storage ClassApproximate observed latency
Local NVMe<100 microsec
Local SAN<7 millisec
Cloud Block storage<10 millisec
Cloud File storage<50 millisec
115 | -------------------------------------------------------------------------------- /src/opts.c: -------------------------------------------------------------------------------- 1 | /**/ 2 | /******************************************************************************* 3 | * Copyright (c) 2017 IBM Corp. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | /* 18 | * Contributors: 19 | * Paul Harris - Initial implementation 20 | *******************************************************************************/ 21 | /**/ 22 | /*******************************************************************************/ 23 | /* */ 24 | /* IBM MQ Log Disk Tester */ 25 | /* */ 26 | /*******************************************************************************/ 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "opts.h" 34 | #include "util.h" 35 | 36 | void display_usage(void) { 37 | puts("mqldt - MQ log file tester"); 38 | puts("Usage: mqldt --dir= --bsize= [ --filePrefix= --fileSize= --numFiles= --duration= --csvFile= --qm=<#qm> --delay=]"); 39 | exit(8); 40 | } 41 | 42 | int alignmentSpecified = 0; 43 | static const char *optString = "h?b:d:f:s:t:n:c:a:p:q:z:"; 44 | 45 | static const struct option longOpts[] = { 46 | {"bsize", required_argument, NULL, 'b'}, 47 | {"dir", required_argument, NULL, 'd'}, 48 | {"filePrefix", optional_argument, NULL, 'f'}, 49 | {"fileSize", optional_argument, NULL, 's'}, 50 | {"duration", optional_argument, NULL, 't'}, 51 | {"numFiles", required_argument, NULL, 'n'}, 52 | {"csvFile", optional_argument, NULL, 'c'}, 53 | {"alignment", optional_argument, NULL, 'a'}, 54 | {"backgroundThreads", optional_argument, NULL, 'p'}, 55 | {"help", no_argument, NULL, 'h'}, 56 | {"qm", optional_argument, NULL, 'q'}, 57 | {"delay", optional_argument, NULL, 'z'}, 58 | {NULL, no_argument, NULL, 0}}; 59 | 60 | struct Options options; 61 | 62 | void parseOptions(int argc, char *argv[]) { 63 | int longIndex; 64 | int opt = getopt_long(argc, argv, optString, longOpts, &longIndex); 65 | int i = 0; 66 | char *bSizeValue; 67 | 68 | /*Set defaults*/ 69 | options.directory = "."; 70 | options.filePrefix = "mqldt_testfile"; 71 | options.fileSize = 1638400; 72 | options.alignment = 4096; /*MQ Sets log page alignment to 4K*/ 73 | options.duration = 10; 74 | options.blockSizeCount = 0; 75 | options.csvFile = NULL; 76 | options.backgroundThreads = 0; 77 | options.numFiles = 8; 78 | options.qm = 1; 79 | options.delay = 0; 80 | 81 | if (opt == -1) { 82 | display_usage(); 83 | return; 84 | } 85 | 86 | while (opt != -1) { 87 | //It's an undocumented 'feature' of getopt_long that optional parameters need to be specified with the equals sign 88 | //This isn't trapped by the normal error processing (indicated by an opt value of '?') 89 | if(!optarg && opt !='?') { 90 | printf("No value specified for parameter --%s. Hint: long form parms must be specified as =.\n",longOpts[longIndex].name); 91 | display_usage(); 92 | } 93 | 94 | switch (opt) { 95 | case 'b': 96 | options.blockSizeStr = malloc(strlen(optarg) + 1); 97 | strcpy(options.blockSizeStr, optarg); 98 | 99 | for (i = 0; i < strlen(optarg); i++) { 100 | if (optarg[i] == ',') 101 | options.blockSizeCount++; 102 | } 103 | options.blockSizeCount++; 104 | options.blockSize = malloc(options.blockSizeCount * sizeof(int)); 105 | 106 | bSizeValue = strtok(optarg, ","); 107 | for (i = 0; i < options.blockSizeCount; i++) { 108 | if (bSizeValue[strlen(bSizeValue) - 1] != 'k' && bSizeValue[strlen(bSizeValue) - 1] != 'K') { 109 | fprintf(stderr, "Error: --bsize must be specified as k \n"); 110 | display_usage(); 111 | } else { 112 | options.blockSize[i] = atoi(bSizeValue) * 1024; 113 | } 114 | bSizeValue = strtok(NULL, ","); 115 | } 116 | break; 117 | 118 | case 'c': 119 | options.csvFile = optarg; 120 | break; 121 | 122 | case 'd': 123 | options.directory = optarg; 124 | break; 125 | 126 | case 'f': 127 | options.filePrefix = optarg; 128 | break; 129 | 130 | case 's': 131 | options.fileSize = atoi(optarg); 132 | break; 133 | 134 | case 't': 135 | options.duration = atoi(optarg); 136 | if (options.duration < 1) { 137 | fprintf(stderr, "Error: value of duration is too small\n"); 138 | display_usage(); 139 | } 140 | break; 141 | 142 | case 'n': 143 | options.numFiles = atoi(optarg); 144 | if (options.numFiles > 10000) { 145 | fprintf(stderr, "Error: value of numFiles is too big\n"); 146 | display_usage(); 147 | } 148 | if (options.numFiles < 1) { 149 | fprintf(stderr, "Error: value of numFiles is too small\n"); 150 | display_usage(); 151 | } 152 | break; 153 | 154 | case 'a': 155 | options.alignment = atoi(optarg); 156 | alignmentSpecified = 1; 157 | break; 158 | 159 | case 'p': 160 | options.backgroundThreads = atoi(optarg); 161 | break; 162 | 163 | case 'q': 164 | options.qm = atoi(optarg); 165 | if (options.qm > 10) { 166 | puts("A maximum of 10 simulated queue managers is supported\n"); 167 | display_usage(); 168 | } 169 | break; 170 | 171 | case 'z': 172 | options.delay = atoi(optarg); 173 | break; 174 | 175 | case 'h': /* fall-through is intentional */ 176 | case '?': 177 | display_usage(); 178 | break; 179 | 180 | default: 181 | /* Shouldn't get here. */ 182 | break; 183 | } 184 | opt = getopt_long(argc, argv, optString, longOpts, &longIndex); 185 | } 186 | if (options.alignment == 0) { 187 | options.alignment = file_GetPhysicalBlockSize(options.directory); 188 | } 189 | if ((options.qm > 1) && (options.blockSizeCount > 1)) { 190 | puts("MQLDT currently only supports a single block size with more than 1 QM"); 191 | display_usage(); 192 | } 193 | } 194 | 195 | void printOptions() { 196 | puts("Options (specified or defaulted to)"); 197 | puts("====================================================================================="); 198 | printf("Write blocksize (--bsize) : %s\n", options.blockSizeStr); 199 | printf("Directory to write to (--dir) : %s\n", options.directory); 200 | printf("Test file prefix (--filePrefix) : %s\n", options.filePrefix); 201 | printf("Number of files to write to (--numFiles) : %i\n", options.numFiles); 202 | printf("Size of test files (--fileSize) : %i\n", options.fileSize); 203 | /*printf("Simulate linear logging? (--linear) : %i\n",options.linearLogging);*/ 204 | printf("Test duration (--duration) : %i\n", options.duration); 205 | if (options.delay) { 206 | printf("Delay (microsec) (--delay) : %i\n", options.delay); 207 | } 208 | if (alignmentSpecified) { 209 | printf("Record alignment (--alignment) : %ld\n", options.alignment); 210 | } 211 | if (options.backgroundThreads > 0) { 212 | printf("Background Threads (--backgroundThreads) : %i\n", options.backgroundThreads); 213 | } 214 | if (options.qm > 1) { 215 | printf("Queue Managers (--qm) : %i\n", options.qm); 216 | } 217 | if (options.csvFile) { 218 | printf("CSV File (--csvFile) : %s\n", options.csvFile); 219 | } 220 | puts(""); 221 | } 222 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/mqldt.c: -------------------------------------------------------------------------------- 1 | /**/ 2 | /******************************************************************************* 3 | * Copyright (c) 2017 IBM Corp. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | /* 18 | * Contributors: 19 | * Paul Harris - Initial implementation 20 | *******************************************************************************/ 21 | /**/ 22 | /*******************************************************************************/ 23 | /* */ 24 | /* IBM MQ Log Disk Tester */ 25 | /* */ 26 | /*******************************************************************************/ 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "fileio.h" 35 | #include "opts.h" 36 | #include "util.h" 37 | 38 | FILE *csvFile; 39 | pthread_mutex_t mutex; 40 | pthread_cond_t condition; 41 | volatile int started; 42 | 43 | void *consume_cpu(void *arg) { 44 | clock_t start, end; 45 | double runTime; 46 | start = clock(); 47 | int i, num = 1, primes = 0; 48 | //puts("Thread started"); 49 | while (num <= 1000000000) { 50 | i = 2; 51 | while (i <= num) { 52 | if (num % i == 0) 53 | break; 54 | i++; 55 | } 56 | if (i == num) 57 | primes++; 58 | num++; 59 | } 60 | 61 | end = clock(); 62 | runTime = (end - start) / (double)CLOCKS_PER_SEC; 63 | //printf("This machine calculated all %d prime numbers under 1000 in %g seconds\n", primes, runTime); 64 | puts("Thread ended"); 65 | } 66 | 67 | void openCSVFile() { 68 | if (options.csvFile != NULL) { 69 | if (options.qm == 1) { 70 | csvFile = fopen(options.csvFile, "w"); 71 | } else { 72 | csvFile = fopen(options.csvFile, "a"); 73 | fprintf(csvFile, "Running MQLDT over %d Queue Managers\n", options.qm); 74 | } 75 | if (csvFile == NULL) { 76 | perror("Error opening csv file for writing"); 77 | exit(8); 78 | } 79 | fprintf(csvFile, "Blocksize,"); 80 | if (options.qm == 1) { 81 | csvFileStatsTitles(csvFile); 82 | } else { 83 | csvQMFileStatsTitles(csvFile); 84 | } 85 | fprintf(csvFile, ","); 86 | csvTimerStatsTitles(csvFile); 87 | fprintf(csvFile, "\n"); 88 | } 89 | return; 90 | } 91 | 92 | void CSVStats(struct fileStore *files, struct timer *t1, int blockSize) { 93 | if (options.csvFile != NULL) { 94 | fprintf(csvFile, "%i,", blockSize); 95 | if (options.qm == 1) { 96 | csvFileStats(files, csvFile); 97 | } else { 98 | csvQMFileStats(files, csvFile); 99 | } 100 | fprintf(csvFile, ","); 101 | csvTimerStats(t1, csvFile, blockSize); 102 | fprintf(csvFile, "\n"); 103 | } 104 | } 105 | 106 | void CSVQMStats(struct fileStore *files, struct timer *t1, int blockSize) { 107 | if (options.csvFile != NULL) { 108 | fprintf(csvFile, "%i,", blockSize); 109 | csvQMFileStats(files, csvFile); 110 | fprintf(csvFile, ","); 111 | csvTimerStats(t1, csvFile, blockSize); 112 | fprintf(csvFile, "\n"); 113 | } 114 | } 115 | 116 | 117 | void *runTest(void *arg) { 118 | struct timer t1; 119 | int elapsed = 0; 120 | int thread_err, testCount; 121 | int elapsedStringDigits = 0; 122 | int elapsedStringTailChars = 0; 123 | 124 | struct fileStore *files; 125 | files = (struct fileStore *)arg; 126 | 127 | // We report secs elapsed as 'elapsed/duration', e.g 34/100, this value is how many chars the back half of that string takes (e.g. '/100') 128 | // Also include additional space for nicer formatting when backspace char doesnt work (log stream) 129 | elapsedStringTailChars = floor(log10(options.duration)) + 3; 130 | 131 | //Wait for sync to ensure all threads start at the same time (after file creation has finished) 132 | if (options.qm > 1) { 133 | pthread_mutex_lock(&mutex); 134 | while (started == 0) { 135 | pthread_cond_wait(&condition, &mutex); 136 | } 137 | pthread_mutex_unlock(&mutex); 138 | } 139 | 140 | //Construct the timer and set the start time of the test 141 | tInit(&t1); 142 | for (testCount = 0; testCount < options.blockSizeCount; testCount++) { 143 | setBlockSize(files, options.blockSize[testCount]); 144 | 145 | //Only display on first thread (0 for single thread, 1 for multi-thread) 146 | if (files->thread <= 1) 147 | fprintf(stderr, "Executing test for write blocksize %i bytes (%iKB). Seconds elapsed -> ", options.blockSize[testCount], options.blockSize[testCount] / 1024); 148 | 149 | //Stores current time in start time 150 | tReset(&t1); 151 | 152 | //Stores current time in check_time1 153 | tStart(t1); 154 | do { 155 | writeToFile(files, options.blockSize[testCount]); 156 | 157 | //tCheck stores current time in check_time2 and calculates difference between each check_time, flips time2->time1 158 | files->stats.intervalTimer += tCheck(&t1); 159 | 160 | if (files->stats.intervalTimer >= ONE_SEC_IN_NS) { 161 | if (files->thread <= 1) { 162 | elapsed++; 163 | elapsedStringDigits = floor(log10(elapsed)) + 1; 164 | fprintf(stderr, "%i/%i ", elapsed, options.duration); 165 | fflush(stderr); 166 | /*Backspace the cursor the right number of digits*/ 167 | fprintf(stderr, "%.*s", (elapsedStringDigits + elapsedStringTailChars), "\b\b\b\b\b\b\b\b\b\b\b"); 168 | } 169 | updateFileStats(files); 170 | } 171 | /*Note the not operator. Read the line below as 'until')*/ 172 | //Second clause required if start time nsec was at or close to 999999999 173 | } while (!(((t1.check_time1->tv_sec - t1.start_time->tv_sec == options.duration) && (t1.check_time1->tv_nsec > t1.start_time->tv_nsec)) || 174 | (t1.check_time1->tv_sec - t1.start_time->tv_sec > options.duration))); 175 | 176 | updateFileStats(files); 177 | 178 | if (files->thread <= 1) { 179 | fprintf(stdout, "%i/%i", options.duration, options.duration); 180 | puts(""); 181 | puts(""); 182 | } 183 | 184 | //For single QM test, we can dump out stats as we iterate through the block sizes 185 | if (options.qm == 1) { 186 | printFileStats(files); 187 | puts(""); 188 | printTimerStats(&t1, options.blockSize[testCount]); 189 | puts(""); 190 | elapsed = 0; 191 | CSVStats(files, &t1, options.blockSize[testCount]); 192 | resetFiles(files); /*Reset SEEK pointers to start of files if running further tests */ 193 | } 194 | //Flush stdout between each blocksize to ensure no corruption between STDERR and STDOUT 195 | fflush(stdout); 196 | } 197 | // If we have multi QM, we have only a single block size, store timings and return 198 | if (options.qm > 1) { 199 | memcpy(&files->resultTimings, &t1, sizeof(struct timer)); 200 | } 201 | 202 | closeFiles(files); 203 | return 0; 204 | } 205 | 206 | void processStats(struct fileStore *array[]) { 207 | int k; 208 | for (k = 1; k <= options.qm; k++) { 209 | array[0]->stats.total_writes += array[k]->stats.total_writes; 210 | array[0]->stats.total_bytes += array[k]->stats.total_writes * options.blockSize[0]; 211 | 212 | if (array[k]->stats.interval_max_bytes_sec > array[0]->stats.interval_max_bytes_sec) { 213 | array[0]->stats.interval_max_bytes_sec = array[k]->stats.interval_max_bytes_sec; 214 | } 215 | if (array[k]->stats.interval_min_bytes_sec < array[0]->stats.interval_min_bytes_sec) { 216 | array[0]->stats.interval_min_bytes_sec = array[k]->stats.interval_min_bytes_sec; 217 | } 218 | array[0]->stats.avg_bytes_sec += array[k]->stats.avg_bytes_sec; 219 | 220 | if (array[k]->resultTimings.max_time > array[0]->resultTimings.max_time) { 221 | array[0]->resultTimings.max_time = array[k]->resultTimings.max_time; 222 | array[0]->resultTimings.max_time_instance = array[k]->resultTimings.max_time_instance; 223 | } 224 | if (array[k]->resultTimings.min_time < array[0]->resultTimings.min_time) { 225 | array[0]->resultTimings.min_time = array[k]->resultTimings.min_time; 226 | array[0]->resultTimings.min_time_instance = array[k]->resultTimings.min_time_instance; 227 | } 228 | array[0]->resultTimings.avg_time += array[k]->resultTimings.avg_time; 229 | } 230 | array[0]->resultTimings.avg_time /= options.qm; 231 | } 232 | 233 | int main(int argc, char *argv[]) { 234 | struct fileStore *files; 235 | struct fileStore *filesArray[11]; 236 | int thread = 1; 237 | int thread_err; 238 | pthread_t tid[11]; 239 | int k; 240 | pthread_t bgtid[100]; 241 | int bgCount; 242 | 243 | parseOptions(argc, argv); 244 | puts(""); 245 | printOptions(); 246 | openCSVFile(); 247 | 248 | //Start background threads if required 249 | for (bgCount = 0; bgCount < options.backgroundThreads; bgCount++) { 250 | thread_err = pthread_create(&(bgtid[bgCount]), NULL, &consume_cpu, NULL); 251 | if (thread_err) { 252 | puts("Error creating background thread\n"); 253 | exit(8); 254 | } 255 | } 256 | if (options.backgroundThreads > 0) 257 | printf("%i backgound threads started\n", options.backgroundThreads); 258 | 259 | if (options.qm > 1) { 260 | printf("Running simulation of %d Queue Managers\n", options.qm); 261 | files = prepareStats(0); 262 | filesArray[0] = files; 263 | 264 | //Create conditional flag which can be used on each thread, so that they are notified when file creation is finished 265 | pthread_mutex_init(&mutex, NULL); 266 | pthread_cond_init(&condition, NULL); 267 | pthread_mutex_lock(&mutex); 268 | started = 0; 269 | pthread_mutex_unlock(&mutex); 270 | 271 | while (thread <= options.qm) { 272 | 273 | files = prepareFiles(thread); 274 | filesArray[thread] = files; 275 | 276 | //Spawn onto thread 277 | thread_err = pthread_create(&tid[thread], NULL, &runTest, (void *)filesArray[thread]); 278 | if (thread_err) { 279 | puts("Error creating writing thread\n"); 280 | exit(8); 281 | } 282 | thread++; 283 | } 284 | pthread_mutex_lock(&mutex); 285 | started = 1; 286 | pthread_cond_broadcast(&condition); 287 | pthread_mutex_unlock(&mutex); 288 | 289 | //Wait for all threads to finish. 290 | for (k = 1; k <= options.qm; k++) { 291 | pthread_join(tid[k], NULL); 292 | } 293 | processStats(filesArray); 294 | //Process results 295 | printQMFileStats(filesArray[0]); 296 | puts(""); 297 | printTimerStats(&filesArray[0]->resultTimings, options.blockSize[0]); 298 | puts(""); 299 | CSVStats(filesArray[0], &filesArray[0]->resultTimings, options.blockSize[0]); 300 | } else { 301 | //Single qm 302 | files = prepareFiles(0); 303 | runTest(files); 304 | } 305 | 306 | if (options.csvFile != NULL) 307 | fclose(csvFile); 308 | } 309 | -------------------------------------------------------------------------------- /src/fileio.c: -------------------------------------------------------------------------------- 1 | /**/ 2 | /******************************************************************************* 3 | * Copyright (c) 2017 IBM Corp. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | /* 18 | * Contributors: 19 | * Paul Harris - Initial implementation 20 | *******************************************************************************/ 21 | /**/ 22 | /*******************************************************************************/ 23 | /* */ 24 | /* IBM MQ Log Disk Tester */ 25 | /* */ 26 | /*******************************************************************************/ 27 | #define _GNU_SOURCE /*Needed to support O_DIRECT*/ 28 | 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include "fileio.h" 42 | #include "opts.h" 43 | #include "util.h" 44 | 45 | /* 46 | * prepareFiles 47 | * 48 | * Create the files to be written to (fully populated with seek positions set back to 0). 49 | * Create a fileStore structure to keep the handles, seek positions, file statistics and a pointer to 50 | * the properly aligned string (of --blockSize length) to be use for subsequent writes. 51 | * 52 | * Also creates the memory aligned string to be used for write operations 53 | */ 54 | char *bigString; 55 | 56 | //This method is used to initialise an instance of the fileStore to store data totalled across all QM 57 | struct fileStore *prepareStats(int tnum) { 58 | struct fileStore *fs; 59 | 60 | fs = (struct fileStore *)malloc(sizeof(struct fileStore)); 61 | fs->thread = tnum; 62 | fs->stats.total_writes = 0; 63 | fs->stats.interval_max_bytes_sec = 0; 64 | fs->stats.interval_min_bytes_sec = LONG_MAX; 65 | fs->stats.interval_max_latency = 0; 66 | fs->stats.interval_min_latency = LONG_MAX; 67 | fs->stats.update_count = 0; 68 | fs->stats.persec_bytes = 0; 69 | fs->stats.max_bytes_sec = 0; 70 | fs->stats.avg_bytes_sec = 0; 71 | fs->stats.min_bytes_sec = LONG_MAX; 72 | fs->stats.intervalTimer = 0; 73 | 74 | fs->resultTimings.avg_time = 0; 75 | fs->resultTimings.max_time = 0; 76 | fs->resultTimings.min_time = LONG_MAX; 77 | 78 | return fs; 79 | } 80 | 81 | struct fileStore *prepareFiles(int tnum) { 82 | int currFileIndex = 0; 83 | int stemOffset = 0; 84 | char *currFileName = NULL; 85 | int i; 86 | int writeLen; 87 | 88 | struct fileStore *fs; 89 | struct iovec format_vec[1]; /*Use this to 'format' the files*/ 90 | struct stat stat_buf; 91 | 92 | /*int openOptions = O_CREAT|O_RDWR|O_SYNC|O_DSYNC|O_DIRECT;*/ 93 | int openOptions = O_CREAT | O_RDWR | O_DSYNC | O_DIRECT; 94 | 95 | //Second file handle not currently used 96 | int openOptions2 = O_RDWR | O_DSYNC; 97 | /*int openOptions = O_CREAT|O_RDWR|O_SYNC|O_DSYNC|O_DIRECT;*/ 98 | 99 | int rc = -1; 100 | 101 | fprintf(stdout, "Creating files...for thread: %d\n", tnum); 102 | fflush(stdout); 103 | umask(0); /*Change umask to allow us to set all file permissions*/ 104 | 105 | fs = (struct fileStore *)malloc(sizeof(struct fileStore)); 106 | fs->thread = tnum; 107 | fs->stats.total_writes = 0; 108 | fs->stats.interval_max_bytes_sec = 0; 109 | fs->stats.interval_min_bytes_sec = LONG_MAX; 110 | fs->stats.interval_max_latency = 0; 111 | fs->stats.interval_min_latency = LONG_MAX; 112 | fs->stats.update_count = 0; 113 | fs->stats.persec_bytes = 0; 114 | fs->stats.max_bytes_sec = 0; 115 | fs->stats.avg_bytes_sec = 0; 116 | fs->stats.min_bytes_sec = LONG_MAX; 117 | fs->stats.intervalTimer = 0; 118 | 119 | bigString = UtilMakeBigString(options.fileSize, options.alignment); 120 | 121 | fs->writeVec[0].iov_base = bigString; 122 | fs->writeVec[0].iov_len = 0; 123 | 124 | /*Construct first filespec*/ 125 | // 7 characters are 1 for seperator, 5 for extension ".0000" and 1 for Null 126 | // Additional character is if indexed files are used (for multiple queue managers) 127 | if (tnum >= 10) { 128 | currFileName = (char *)malloc(strlen(options.directory) + strlen(options.filePrefix) + 7 + 2); 129 | } else if (tnum > 0) { 130 | currFileName = (char *)malloc(strlen(options.directory) + strlen(options.filePrefix) + 7 + 1); 131 | } else { 132 | currFileName = (char *)malloc(strlen(options.directory) + strlen(options.filePrefix) + 7); 133 | } 134 | 135 | if (currFileName == NULL) { 136 | fprintf(stderr, "Error allocating filename\n"); 137 | exit; 138 | } else { 139 | strcpy(currFileName, options.directory); 140 | if (tnum > 0) 141 | sprintf(&currFileName[strlen(currFileName)], "%d", tnum); 142 | strcat(currFileName, "/"); 143 | strcat(currFileName, options.filePrefix); 144 | stemOffset = strlen(currFileName) + 1; 145 | strcat(currFileName, ".0000"); 146 | } 147 | 148 | /*Create 2d array of files with fd and seek position for each file*/ 149 | fs->files = malloc(options.numFiles * sizeof(int *)); 150 | 151 | if (fs->files == NULL) { 152 | fprintf(stderr, "Error allocating file array\n"); 153 | exit; 154 | } 155 | //Allocate space for file handle, seek position and file handle 2 156 | for (i = 0; i < options.numFiles; i++) { 157 | fs->files[i] = malloc(3 * sizeof(int)); 158 | if (fs->files[i] == NULL) { 159 | fprintf(stderr, "Error allocating file array\n"); 160 | exit; 161 | } 162 | } 163 | 164 | format_vec[0].iov_base = bigString; 165 | format_vec[0].iov_len = options.fileSize; 166 | 167 | for (i = 0; i < options.numFiles; i++) { 168 | rc = stat(currFileName, &stat_buf); 169 | //If file already exists, check current size and force population if different 170 | if (rc == 0) { 171 | if (((long long)stat_buf.st_size) != options.fileSize) { 172 | rc = -1; 173 | } 174 | } 175 | fs->files[i][0] = open(currFileName, openOptions, 0660); 176 | if (fs->files[i][0] == -1) { 177 | printf("Error creating (or opening existing) test file %s: %s\n", currFileName, strerror(errno)); 178 | exit(8); 179 | } 180 | 181 | fs->files[i][2] = open(currFileName, openOptions2); 182 | if (fs->files[i][0] == -1) { 183 | printf("Error creating 2nd handle on test file %s: %s\n", currFileName, strerror(errno)); 184 | exit(8); 185 | } 186 | 187 | if (rc == -1) { 188 | writeLen = writev(fs->files[i][0], format_vec, 1); 189 | if (writeLen == -1) { 190 | printf("Error populating test file %s: %s\n", currFileName, strerror(errno)); 191 | exit(8); 192 | } 193 | } 194 | fs->files[i][1] = 0; /*set seek position to start of file*/ 195 | if (lseek(fs->files[i][0], 0, SEEK_SET) == -1) { 196 | printf("Error setting seek position on test file %s to 0: %s\n", currFileName, strerror(errno)); 197 | exit(8); 198 | } 199 | 200 | currFileIndex++; 201 | sprintf(currFileName + stemOffset, "%04i", currFileIndex); 202 | } 203 | umask(022); /*Change umask to allow us to set all file permissions*/ 204 | return fs; 205 | } 206 | 207 | ssize_t writeToFile(struct fileStore *fs, int writeBlockSize) { 208 | ssize_t writeLen; 209 | 210 | if (fs->files[fs->currentFile][1] + writeBlockSize > options.fileSize) { 211 | /*Next write will take us beyond end of file size, so reset seek posion to 0, and move on to next file*/ 212 | fs->files[fs->currentFile][1] = 0; 213 | if (lseek(fs->files[fs->currentFile][0], 0, SEEK_SET) == -1) { 214 | perror("Error resetting seek position on test file to 0"); 215 | exit(8); 216 | } 217 | 218 | fs->currentFile++; 219 | if (fs->currentFile >= options.numFiles) 220 | fs->currentFile = 0; 221 | } 222 | 223 | if ((writeLen = writev(fs->files[fs->currentFile][0], fs->writeVec, 1)) == -1) { 224 | perror("Error writing to test files"); 225 | exit(8); 226 | } else { 227 | fs->stats.total_writes++; 228 | fs->stats.persec_bytes += writeLen; 229 | if (options.delay) { 230 | usleep(options.delay); 231 | } 232 | } 233 | 234 | fs->files[fs->currentFile][1] += writeBlockSize; 235 | if (lseek(fs->files[fs->currentFile][0], fs->files[fs->currentFile][1], SEEK_SET) == -1) { 236 | perror("Error setting seek position on test file"); 237 | exit(8); 238 | } 239 | return writeLen; 240 | } 241 | 242 | void closeFiles(struct fileStore *fs) { 243 | int i; 244 | for (i = 0; i < options.numFiles; i++) { 245 | close(fs->files[i][0]); 246 | close(fs->files[i][2]); 247 | } 248 | } 249 | 250 | void resetFiles(struct fileStore *fs) { 251 | int i; 252 | for (i = 0; i < options.numFiles; i++) { 253 | lseek(fs->files[i][0], 0, SEEK_SET); 254 | //Shouldnt we reset our seek, else we risk moving onto next file too quickly when changing blocksize 255 | fs->files[i][1] = 0; 256 | } 257 | fs->stats.total_writes = 0; 258 | fs->stats.interval_max_bytes_sec = 0; 259 | fs->stats.interval_min_bytes_sec = LONG_MAX; 260 | fs->stats.interval_max_latency = 0; 261 | fs->stats.interval_min_latency = LONG_MAX; 262 | fs->stats.update_count = 0; 263 | fs->stats.persec_bytes = 0; 264 | fs->stats.max_bytes_sec = 0; 265 | fs->stats.avg_bytes_sec = 0; 266 | fs->stats.min_bytes_sec = LONG_MAX; 267 | fs->stats.intervalTimer = 0; 268 | } 269 | 270 | void setBlockSize(struct fileStore *fs, int writeBlockSize) { 271 | fs->writeVec[0].iov_len = writeBlockSize; 272 | } 273 | 274 | void updateFileStats(struct fileStore *fs) { 275 | //This method is usually invoked when the intervalTimer exceeds 1s, its also called at the end. 276 | //If the final call is greater than half a second, then interpolate, else ignore 277 | if (fs->stats.intervalTimer < HALF_SEC_IN_NS) { 278 | //To small an interval 279 | fs->stats.persec_bytes = 0; 280 | fs->stats.intervalTimer = 0; 281 | return; 282 | } else if (fs->stats.intervalTimer < ONE_SEC_IN_NS) { 283 | fs->stats.persec_bytes = fs->stats.persec_bytes * 1 / ((float)fs->stats.intervalTimer / ONE_SEC_IN_NS); 284 | } //else interval timer will be just over 1s 285 | 286 | if (fs->stats.persec_bytes > fs->stats.interval_max_bytes_sec) 287 | fs->stats.interval_max_bytes_sec = fs->stats.persec_bytes; 288 | if (fs->stats.persec_bytes < fs->stats.interval_min_bytes_sec) 289 | fs->stats.interval_min_bytes_sec = fs->stats.persec_bytes; 290 | 291 | fs->stats.avg_bytes_sec = ((fs->stats.avg_bytes_sec * fs->stats.update_count) + fs->stats.persec_bytes) / (fs->stats.update_count + 1); 292 | fs->stats.update_count++; 293 | 294 | fs->stats.persec_bytes = 0; 295 | fs->stats.intervalTimer = 0; 296 | } 297 | 298 | const char totalBytesDesc[] = "Total bytes written to files"; 299 | const char intervalMaxBytesSecDesc[] = "Max bytes/sec written to files (over 1 sec interval)"; 300 | const char intervalMinBytesSecDesc[] = "Min bytes/sec written to files (over 1 sec interval)"; 301 | const char avgBytesSecDesc[] = "Avg bytes/sec written to files"; 302 | const char totalWritesDesc[] = "Total writes to files"; 303 | const char IOPSDesc[] = "IOPS"; 304 | 305 | const char intervalMaxBytesSecDesc2[] = "Max bytes/sec written to files by a single qm (over 1 sec interval)"; 306 | const char intervalMinBytesSecDesc2[] = "Min bytes/sec written to files by a single qm (over 1 sec interval)"; 307 | const char avgBytesSecDesc2[] = "Avg bytes/sec written to files by all queue managers"; 308 | 309 | void printFileStats(struct fileStore *fs) { 310 | setlocale(LC_ALL, ""); 311 | printf("%s : %'15li\n", totalWritesDesc, fs->stats.total_writes); 312 | printf("%s : %'15li\n", IOPSDesc, fs->stats.total_writes / options.duration); 313 | printf("%s : %'15li\n", totalBytesDesc, (fs->stats.total_writes * fs->writeVec[0].iov_len)); 314 | printf("%s : %'15li\n", intervalMaxBytesSecDesc, fs->stats.interval_max_bytes_sec); 315 | printf("%s : %'15li\n", intervalMinBytesSecDesc, fs->stats.interval_min_bytes_sec); 316 | printf("%s : %'15li\n", avgBytesSecDesc, fs->stats.avg_bytes_sec); 317 | } 318 | 319 | void printQMFileStats(struct fileStore *fs) { 320 | setlocale(LC_ALL, ""); 321 | printf("%s : %'15li\n", totalWritesDesc, fs->stats.total_writes); 322 | printf("%s : %'15li\n", IOPSDesc, fs->stats.total_writes / options.duration); 323 | printf("%s : %'15li\n", totalBytesDesc, fs->stats.total_bytes); 324 | printf("%s : %'15li\n", intervalMaxBytesSecDesc2, fs->stats.interval_max_bytes_sec); 325 | printf("%s : %'15li\n", intervalMinBytesSecDesc2, fs->stats.interval_min_bytes_sec); 326 | printf("%s : %'15li\n", avgBytesSecDesc2, fs->stats.avg_bytes_sec); 327 | } 328 | 329 | void csvFileStatsTitles(FILE *csvFile) { 330 | if (fprintf(csvFile, "%s,%s,%s,%s,%s", totalWritesDesc, totalBytesDesc, intervalMaxBytesSecDesc, intervalMinBytesSecDesc, avgBytesSecDesc) < 0) { 331 | perror("Error writing to csvFile"); 332 | exit(8); 333 | } 334 | } 335 | 336 | void csvQMFileStatsTitles(FILE *csvFile) { 337 | if (fprintf(csvFile, "%s,%s,%s,%s,%s", totalWritesDesc, totalBytesDesc, intervalMaxBytesSecDesc2, intervalMinBytesSecDesc2, avgBytesSecDesc2) < 0) { 338 | perror("Error writing to csvFile"); 339 | exit(8); 340 | } 341 | } 342 | 343 | void csvFileStats(struct fileStore *fs, FILE *csvFile) { 344 | if (fprintf(csvFile, "%li,%li,%li,%li,%li", fs->stats.total_writes, (fs->stats.total_writes * fs->writeVec[0].iov_len), fs->stats.interval_max_bytes_sec, fs->stats.interval_min_bytes_sec, fs->stats.avg_bytes_sec) < 0) { 345 | perror("Error writing to csvFile"); 346 | exit(8); 347 | } 348 | } 349 | 350 | void csvQMFileStats(struct fileStore *fs, FILE *csvFile) { 351 | if (fprintf(csvFile, "%li,%li,%li,%li,%li", fs->stats.total_writes, fs->stats.total_bytes, fs->stats.interval_max_bytes_sec, fs->stats.interval_min_bytes_sec, fs->stats.avg_bytes_sec) < 0) { 352 | perror("Error writing to csvFile"); 353 | exit(8); 354 | } 355 | } 356 | --------------------------------------------------------------------------------