Skip to content

Commit 93d9757

Browse files
committed
Retrieve process ID from standard connection string
1 parent 63a94e1 commit 93d9757

File tree

5 files changed

+223
-158
lines changed

5 files changed

+223
-158
lines changed

src/common/atlas/cluster.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,45 @@ import type { ApiClient } from "./apiClient.js";
33
import { LogId } from "../logger.js";
44

55
const DEFAULT_PORT = "27017";
6+
7+
function extractProcessIds(connectionString: string): Array<string> {
8+
if (!connectionString || connectionString === "") return [];
9+
10+
// Extract host:port pairs from connection string
11+
const matches = connectionString.match(/^mongodb:\/\/([^\/]+)/);
12+
if (!matches) {
13+
return [];
14+
}
15+
16+
// matches[1] gives us the host:port pairs
17+
const hostsString = matches[1];
18+
const hosts = hostsString?.split(",") ?? [];
19+
20+
return hosts?.map((host) => {
21+
const [hostname, port] = host.split(":");
22+
return `${hostname}:${port || DEFAULT_PORT}`;
23+
});
24+
}
625
export interface Cluster {
726
name?: string;
827
instanceType: "FREE" | "DEDICATED" | "FLEX";
928
instanceSize?: string;
1029
state?: "IDLE" | "CREATING" | "UPDATING" | "DELETING" | "REPAIRING";
1130
mongoDBVersion?: string;
1231
connectionString?: string;
32+
processIds?: string[];
1333
}
1434

1535
export function formatFlexCluster(cluster: FlexClusterDescription20241113): Cluster {
36+
const connectionString = cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard;
1637
return {
1738
name: cluster.name,
1839
instanceType: "FLEX",
1940
instanceSize: undefined,
2041
state: cluster.stateName,
2142
mongoDBVersion: cluster.mongoDBVersion,
22-
connectionString: cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard,
43+
connectionString,
44+
processIds: extractProcessIds(cluster.connectionStrings?.standard ?? ""),
2345
};
2446
}
2547

@@ -53,14 +75,16 @@ export function formatCluster(cluster: ClusterDescription20240805): Cluster {
5375

5476
const instanceSize = regionConfigs[0]?.instanceSize ?? "UNKNOWN";
5577
const clusterInstanceType = instanceSize === "M0" ? "FREE" : "DEDICATED";
78+
const connectionString = cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard;
5679

5780
return {
5881
name: cluster.name,
5982
instanceType: clusterInstanceType,
6083
instanceSize: clusterInstanceType === "DEDICATED" ? instanceSize : undefined,
6184
state: cluster.stateName,
6285
mongoDBVersion: cluster.mongoDBVersion,
63-
connectionString: cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard,
86+
connectionString,
87+
processIds: extractProcessIds(cluster.connectionStrings?.standard ?? ""),
6488
};
6589
}
6690

@@ -98,21 +122,17 @@ export async function inspectCluster(apiClient: ApiClient, projectId: string, cl
98122
}
99123
}
100124

101-
export async function getProcessIdFromCluster(
125+
export async function getProcessIdsFromCluster(
102126
apiClient: ApiClient,
103127
projectId: string,
104128
clusterName: string
105-
): Promise<string> {
129+
): Promise<string[]> {
106130
try {
107131
const cluster = await inspectCluster(apiClient, projectId, clusterName);
108-
if (!cluster.connectionString) {
109-
throw new Error("No connection string available for cluster");
110-
}
111-
const url = new URL(cluster.connectionString);
112-
return `${url.hostname}:${url.port || DEFAULT_PORT}`;
132+
return cluster.processIds || [];
113133
} catch (error) {
114134
throw new Error(
115-
`Failed to get processId from cluster: ${error instanceof Error ? error.message : String(error)}`
135+
`Failed to get processIds from cluster: ${error instanceof Error ? error.message : String(error)}`
116136
);
117137
}
118138
}

src/common/atlas/performanceAdvisorUtils.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { LogId } from "../logger.js";
22
import type { ApiClient } from "./apiClient.js";
3-
import { getProcessIdFromCluster } from "./cluster.js";
3+
import { getProcessIdsFromCluster } from "./cluster.js";
44
import type { components } from "./openapi.js";
55

66
export type SuggestedIndex = components["schemas"]["PerformanceAdvisorIndex"];
@@ -112,22 +112,36 @@ export async function getSlowQueries(
112112
namespaces?: Array<string>
113113
): Promise<{ slowQueryLogs: Array<SlowQueryLog> }> {
114114
try {
115-
const processId = await getProcessIdFromCluster(apiClient, projectId, clusterName);
115+
const processIds = await getProcessIdsFromCluster(apiClient, projectId, clusterName);
116116

117-
const response = await apiClient.listSlowQueries({
118-
params: {
119-
path: {
120-
groupId: projectId,
121-
processId,
122-
},
123-
query: {
124-
...(since && { since: since.getTime() }),
125-
...(namespaces && { namespaces: namespaces }),
117+
if (processIds.length === 0) {
118+
return { slowQueryLogs: [] };
119+
}
120+
121+
// Make parallel API calls for each process ID
122+
const slowQueryPromises = processIds.map((processId) =>
123+
apiClient.listSlowQueries({
124+
params: {
125+
path: {
126+
groupId: projectId,
127+
processId,
128+
},
129+
query: {
130+
...(since && { since: since.getTime() }),
131+
...(namespaces && { namespaces: namespaces }),
132+
},
126133
},
127-
},
128-
});
134+
})
135+
);
136+
137+
const responses = await Promise.all(slowQueryPromises);
138+
139+
// Combine all slow query logs from all process IDs
140+
const allSlowQueryLogs = responses.reduce((acc, response) => {
141+
return acc.concat(response.slowQueries ?? []);
142+
}, [] as Array<SlowQueryLog>);
129143

130-
return { slowQueryLogs: response.slowQueries ?? [] };
144+
return { slowQueryLogs: allSlowQueryLogs };
131145
} catch (err) {
132146
apiClient.logger.debug({
133147
id: LogId.atlasPaSlowQueryLogsFailure,

tests/integration/tools/atlas/atlasHelpers.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { ObjectId } from "mongodb";
2-
import type { Group } from "../../../../src/common/atlas/openapi.js";
2+
import type { ClusterDescription20240805, Group } from "../../../../src/common/atlas/openapi.js";
33
import type { ApiClient } from "../../../../src/common/atlas/apiClient.js";
44
import type { IntegrationTest } from "../../helpers.js";
55
import { setupIntegrationTest, defaultTestConfig, defaultDriverOptions } from "../../helpers.js";
66
import type { SuiteCollector } from "vitest";
77
import { afterAll, beforeAll, describe } from "vitest";
8+
import { Session } from "../../../../src/common/session.js";
89

910
export type IntegrationTestFunction = (integration: IntegrationTest) => void;
1011

@@ -143,3 +144,61 @@ async function createProject(apiClient: ApiClient): Promise<Group & Required<Pic
143144

144145
return group as Group & Required<Pick<Group, "id">>;
145146
}
147+
148+
export function sleep(ms: number): Promise<void> {
149+
return new Promise((resolve) => setTimeout(resolve, ms));
150+
}
151+
152+
export async function deleteAndWaitCluster(
153+
session: Session,
154+
projectId: string,
155+
clusterName: string,
156+
pollingInterval: number = 1000
157+
): Promise<void> {
158+
await session.apiClient.deleteCluster({
159+
params: {
160+
path: {
161+
groupId: projectId,
162+
clusterName,
163+
},
164+
},
165+
});
166+
while (true) {
167+
try {
168+
await session.apiClient.getCluster({
169+
params: {
170+
path: {
171+
groupId: projectId,
172+
clusterName,
173+
},
174+
},
175+
});
176+
await sleep(pollingInterval);
177+
} catch {
178+
break;
179+
}
180+
}
181+
}
182+
183+
export async function waitCluster(
184+
session: Session,
185+
projectId: string,
186+
clusterName: string,
187+
check: (cluster: ClusterDescription20240805) => boolean | Promise<boolean>,
188+
pollingInterval: number = 1000
189+
): Promise<void> {
190+
while (true) {
191+
const cluster = await session.apiClient.getCluster({
192+
params: {
193+
path: {
194+
groupId: projectId,
195+
clusterName,
196+
},
197+
},
198+
});
199+
if (await check(cluster)) {
200+
return;
201+
}
202+
await sleep(pollingInterval);
203+
}
204+
}

tests/integration/tools/atlas/clusters.test.ts

Lines changed: 9 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,16 @@
11
import type { Session } from "../../../../src/common/session.js";
22
import { expectDefined, getDataFromUntrustedContent, getResponseElements } from "../../helpers.js";
3-
import { describeWithAtlas, withProject, randomId, parseTable } from "./atlasHelpers.js";
4-
import type { ClusterDescription20240805 } from "../../../../src/common/atlas/openapi.js";
3+
import {
4+
describeWithAtlas,
5+
withProject,
6+
randomId,
7+
parseTable,
8+
deleteAndWaitCluster,
9+
waitCluster,
10+
sleep,
11+
} from "./atlasHelpers.js";
512
import { afterAll, beforeAll, describe, expect, it } from "vitest";
613

7-
function sleep(ms: number): Promise<void> {
8-
return new Promise((resolve) => setTimeout(resolve, ms));
9-
}
10-
11-
async function deleteAndWaitCluster(session: Session, projectId: string, clusterName: string): Promise<void> {
12-
await session.apiClient.deleteCluster({
13-
params: {
14-
path: {
15-
groupId: projectId,
16-
clusterName,
17-
},
18-
},
19-
});
20-
while (true) {
21-
try {
22-
await session.apiClient.getCluster({
23-
params: {
24-
path: {
25-
groupId: projectId,
26-
clusterName,
27-
},
28-
},
29-
});
30-
await sleep(1000);
31-
} catch {
32-
break;
33-
}
34-
}
35-
}
36-
37-
async function waitCluster(
38-
session: Session,
39-
projectId: string,
40-
clusterName: string,
41-
check: (cluster: ClusterDescription20240805) => boolean | Promise<boolean>
42-
): Promise<void> {
43-
while (true) {
44-
const cluster = await session.apiClient.getCluster({
45-
params: {
46-
path: {
47-
groupId: projectId,
48-
clusterName,
49-
},
50-
},
51-
});
52-
if (await check(cluster)) {
53-
return;
54-
}
55-
await sleep(1000);
56-
}
57-
}
58-
5914
describeWithAtlas("clusters", (integration) => {
6015
withProject(integration, ({ getProjectId, getIpAddress }) => {
6116
const clusterName = "ClusterTest-" + randomId;

0 commit comments

Comments
 (0)