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 | | MQLDT Parm | qm.ini parm(s) |
41 | | --dir | Any directory hosted by the same file-system as Log:LogPath |
42 | | --fileSize | Log:LogFilePages x 4096 |
43 | | --numFiles | Log:LogPrimaryFiles |
44 |
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 | | Total writes to files | The total number of writev operations completed over the duration of the test |
77 | | IOPS | The total number of writev operations completed per second over the duration of the test |
78 | | Total bytes written to files | Total bytes written over the duration of the test (i.e Total writes to files x blocksize) |
79 | | 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' |
80 | | 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' |
81 | | Avg bytes/sec written to files | Average write bandwidth over duration of test. |
82 | | 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. |
83 | | Min bytes/sec (slowest write) | The theoretical minimum bandwidth, if every write had the maximum latency indicated above |
84 | | 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. |
85 | | Max bytes/sec (fastest write) | The theoretical maximum bandwidth, if every write had the minimum latency indicated above |
86 | | Avg latency of write (ns) | The average time (in nanoseconds) to complete a write during the test. |
87 |
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 | | Storage Class | Approximate observed latency |
110 | | Local NVMe | <100 microsec |
111 | | Local SAN | <7 millisec |
112 | | Cloud Block storage | <10 millisec |
113 | | Cloud File storage | <50 millisec |
114 |
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 |
--------------------------------------------------------------------------------