-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsimulateUser.js
More file actions
182 lines (148 loc) · 8.33 KB
/
simulateUser.js
File metadata and controls
182 lines (148 loc) · 8.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
const fs = require("fs"); // Used to read and write to other local files (JSON file for genereated data and csv file with results)
const axios = require("axios"); // Library to make HTTP requests (used for sending requests to the API)
const { generateFakePatient } = require("./generateTestData"); // Import our function to generate fake patient data
// CLI arguments for terminal
const operation = process.argv[2]; // what operation: post / get / put
const db = process.argv[3]; // which databbase: mongo or couch
const indexType = process.argv[4]; // index type: none / simple / compound / partial
const volume = parseInt(process.argv[5]); // Volume of dataposts
const queryString = process.argv[6] || ""; // Optional query string (only needed GET requests)
const updateType = process.argv[7] || ""; // test type: simple / compound / partial (only needed for PUT requests)
const url = `http://localhost:3000/${db}/patient`; // dynamical url depending on DB argument
const dataFile = `./data/post_${volume}.json`; // JSON file with generated patient data to ensure same dataset in both databases
const csvFile = `./results_${operation}_${db}_${indexType}_${volume}_${queryString}_${updateType}.csv`; // dynamical csv logfile for test results
// Validate CLI arguments
if (!operation || !db || !indexType || !volume) {
console.error("Missing required arguments. Use: node simulateUser.js post/get/put mongo/couch none/simple/compound/partial 1000");
process.exit(1); // Stop the script if any argument is missing or has the wrong format
}
if (operation === "get" && !queryString) { // GET operations uses premade filters (see routes) based o the query, or everything would be retrieved
console.error("Missing queryString for GET operation.");
process.exit(1);
}
if (operation === "put" && !updateType) { // PUT operations require selector and update (see runPUT) to know what kind of update to run
console.error("Missing updateType for PUT operation.");
process.exit(1);
}
// Create the CSV file if it dosen't exists
if (!fs.existsSync(csvFile)) {
fs.writeFileSync(csvFile, "operation,db,indexType,volume,query string/test type,responseTimeMs\n");
}
let patients = []; // Array to hold generated patients
// Prepare patient data (load from file or generate new)
if (fs.existsSync(dataFile)) { // Uses file with patient data for the volume if it already exists
console.log(`Using existing patient data from ${dataFile}`);
patients = JSON.parse(fs.readFileSync(dataFile));
} else {
console.log(`No saved data found, generating ${volume} patients...`);
patients = []; // Empty array to hold new generated patients
for (let i = 0; i < volume; i++) {
patients.push(generateFakePatient()); // Loops "volume" times and calls generateFakePatient() each time to create a new patient and add it to the array
}
if (!fs.existsSync("./data")) { // Make sure the data folder exists
fs.mkdirSync("./data");
}
fs.writeFileSync(dataFile, JSON.stringify(patients, null, 2)); // Save generated data to file
console.log(`Saved generated patients to ${dataFile}`);
}
// Is run if operation = post (bulk insert)
async function runPost() {
const batchSize = 500; // Split data into smaller chunks to not overload the API
let totalInserted = 0;
let totalDuration = 0;
for (let i = 0; i < patients.length; i += batchSize) {
const batch = patients.slice(i, i + batchSize); // Slice out the current batch from the big patient array
let batchData;
if (db === "couch") {
batchData = batch.map(patient => ({
...patient,
_id: patient.personalNumber
}));// CouchDB: Use personalNumber as _id to ensure each document is unique (CouchDB doesn't support "required" fields since it doesn't use schemas)
} else {
// Use batch as it is for MongoDB
batchData = batch;
}
try {
const response = await axios.post(url, batchData); // Send request to API
const duration = response.data.responseTimeMs; // Get the responsetime for each batch
totalDuration += duration; // Adds the time it took for each batch to the total duration
let insertedCount;
if (db === "couch") {
insertedCount = response.data.insertedCount; // For CouchDB: the response always includes insertedCount
} else {
// MongoDB kanske inte alltid returnerar insertedCount pga ordered: false, så vi faller tillbaka på batch.length
insertedCount = response.data.insertedCount || batch.length; // MongoDB might not always include insertedCount in response due to "ordered: false" in mongoPatientRoutes.js router.POST. Use if it's included in the response, otherwise fall back to the number of documents in the batch (batch.length)
}
totalInserted += insertedCount; // Add the number of successfully inserted documents to the total count
} catch (error) {
console.error(`Failed to POST batch starting at patient ${i}:`, error.message); // Log failed batch in console
}
}
// Log a single summary row i CSV file after all batches are done
const row = `POST,${db},${indexType},${totalInserted},"",${totalDuration}\n`; // Create one line for CSV log
fs.appendFileSync(csvFile, row); // Append the line to CSV file
console.log(`Total inserted: ${totalInserted} patients in ${totalDuration} ms`); // Log the result of the operation in console
}
// Is run if operation = get (filtered fetch)
async function runGet() {
try {
const fullUrl = queryString ? `${url}?${queryString}` : url; // Add query string if provided
const response = await axios.get(fullUrl); // Make the request
const time = response.data.responseTimeMs; // Get how long it took from API response
const count = response.data.data.length; // Get how many patients matched
const row = `GET,${db},${indexType},${volume},${queryString},${time}\n`;
fs.appendFileSync(csvFile, row);
console.log(`GET complete. Time: ${time} ms. Hits: ${count}`); // Summary of operation in console
} catch (error) {
console.error("GET failed:", error.message); // Log in console if operation fails
}
}
// Is run if operation = put (bulk update)
async function runPut() {
let selector = {}; // Filter for what docs to update
let update = {}; // What field/fields to update
// Define which filter + update
if (updateType === "simple") {
selector = { name: "Anna" };
update = { city: "Littleton" };
} else if (updateType === "compound") {
const today = new Date();
const sixtyYearsAgo = new Date(today.getFullYear() - 60, today.getMonth(), today.getDate());
selector = {
riskGroup: "high",
birthDate: { "$lte": sixtyYearsAgo.toISOString().split("T")[0] } // Match patients equal to or older than 60 with riskGroup 'high'
};
update = { needsFollowUp: true };// Add follow-up field
} else if (updateType === "partial") {
selector = {
active: true,
birthDate: { "$lt": "1970-01-01" }
};
update = { active: false };
} else {
console.error("Unknown updateType for PUT operation.");
process.exit(1); // Exit if unknown
}
try {
const response = await axios.put(url, { selector, update }); // Send update to API
const totalTime = response.data.responseTimeMs; // Get responsetime
const updatedDocsCount = response.data.modifiedDocsCount; // How many documents were updated
const row = `PUT,${db},${indexType},${volume},${updateType},${totalTime}\n`;
fs.appendFileSync(csvFile, row);
console.log(`PUT complete. Total time: ${totalTime} ms. Hits: ${updatedDocsCount}`); // Summary of operation in console
} catch (error) {
console.error("PUT failed:", error.message); // Log in console if operation fails
}
}
// Run correct function based on operation (main)
(async () => {
if (operation === "post") {
await runPost();
} else if (operation === "get") {
await runGet();
} else if (operation === "put") {
await runPut();
} else {
console.error("Invalid operation. Use 'post', 'get' or 'put'."); // Log error in console if unknown operation
}
})();