Skip to content

Commit d2fa04c

Browse files
authored
feat: Atlas list performance advisor tool (#609)
1 parent c12de89 commit d2fa04c

17 files changed

+1413
-68
lines changed

scripts/accuracy/runAccuracyTests.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ export MDB_ACCURACY_RUN_ID=$(npx uuid v4)
88
# export MDB_AZURE_OPEN_AI_API_KEY=""
99
# export MDB_AZURE_OPEN_AI_API_URL=""
1010

11+
# For providing Atlas API credentials (required for Atlas tools)
12+
# Set dummy values for testing (allows Atlas tools to be registered for mocking)
13+
export MDB_MCP_API_CLIENT_ID=${MDB_MCP_API_CLIENT_ID:-"test-atlas-client-id"}
14+
export MDB_MCP_API_CLIENT_SECRET=${MDB_MCP_API_CLIENT_SECRET:-"test-atlas-client-secret"}
15+
1116
# For providing a mongodb based storage to store accuracy result
1217
# export MDB_ACCURACY_MDB_URL=""
1318
# export MDB_ACCURACY_MDB_DB=""

scripts/filter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ function filterOpenapi(openapi: OpenAPIV3_1.Document): OpenAPIV3_1.Document {
4141
"deleteProjectIpAccessList",
4242
"listOrganizationProjects",
4343
"listAlerts",
44+
"listDropIndexes",
45+
"listClusterSuggestedIndexes",
46+
"listSchemaAdvice",
47+
"listSlowQueries",
4448
];
4549

4650
const filteredPaths = {};

src/common/atlas/apiClient.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,42 @@ export class ApiClient {
429429
return data;
430430
}
431431

432+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
433+
async listDropIndexes(options: FetchOptions<operations["listDropIndexes"]>) {
434+
const { data, error, response } = await this.client.GET(
435+
"/api/atlas/v2/groups/{groupId}/clusters/{clusterName}/performanceAdvisor/dropIndexSuggestions",
436+
options
437+
);
438+
if (error) {
439+
throw ApiClientError.fromError(response, error);
440+
}
441+
return data;
442+
}
443+
444+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
445+
async listSchemaAdvice(options: FetchOptions<operations["listSchemaAdvice"]>) {
446+
const { data, error, response } = await this.client.GET(
447+
"/api/atlas/v2/groups/{groupId}/clusters/{clusterName}/performanceAdvisor/schemaAdvice",
448+
options
449+
);
450+
if (error) {
451+
throw ApiClientError.fromError(response, error);
452+
}
453+
return data;
454+
}
455+
456+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
457+
async listClusterSuggestedIndexes(options: FetchOptions<operations["listClusterSuggestedIndexes"]>) {
458+
const { data, error, response } = await this.client.GET(
459+
"/api/atlas/v2/groups/{groupId}/clusters/{clusterName}/performanceAdvisor/suggestedIndexes",
460+
options
461+
);
462+
if (error) {
463+
throw ApiClientError.fromError(response, error);
464+
}
465+
return data;
466+
}
467+
432468
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
433469
async listDatabaseUsers(options: FetchOptions<operations["listDatabaseUsers"]>) {
434470
const { data, error, response } = await this.client.GET(
@@ -508,6 +544,18 @@ export class ApiClient {
508544
return data;
509545
}
510546

547+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
548+
async listSlowQueries(options: FetchOptions<operations["listSlowQueries"]>) {
549+
const { data, error, response } = await this.client.GET(
550+
"/api/atlas/v2/groups/{groupId}/processes/{processId}/performanceAdvisor/slowQueryLogs",
551+
options
552+
);
553+
if (error) {
554+
throw ApiClientError.fromError(response, error);
555+
}
556+
return data;
557+
}
558+
511559
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
512560
async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
513561
const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs", options);

src/common/atlas/cluster.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,37 @@
11
import type { ClusterDescription20240805, FlexClusterDescription20241113 } from "./openapi.js";
22
import type { ApiClient } from "./apiClient.js";
33
import { LogId } from "../logger.js";
4+
import { ConnectionString } from "mongodb-connection-string-url";
45

6+
type AtlasProcessId = `${string}:${number}`;
7+
8+
function extractProcessIds(connectionString: string): Array<AtlasProcessId> {
9+
if (!connectionString) {
10+
return [];
11+
}
12+
const connectionStringUrl = new ConnectionString(connectionString);
13+
return connectionStringUrl.hosts as Array<AtlasProcessId>;
14+
}
515
export interface Cluster {
616
name?: string;
717
instanceType: "FREE" | "DEDICATED" | "FLEX";
818
instanceSize?: string;
919
state?: "IDLE" | "CREATING" | "UPDATING" | "DELETING" | "REPAIRING";
1020
mongoDBVersion?: string;
1121
connectionString?: string;
22+
processIds?: Array<string>;
1223
}
1324

1425
export function formatFlexCluster(cluster: FlexClusterDescription20241113): Cluster {
26+
const connectionString = cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard;
1527
return {
1628
name: cluster.name,
1729
instanceType: "FLEX",
1830
instanceSize: undefined,
1931
state: cluster.stateName,
2032
mongoDBVersion: cluster.mongoDBVersion,
21-
connectionString: cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard,
33+
connectionString,
34+
processIds: extractProcessIds(cluster.connectionStrings?.standard ?? ""),
2235
};
2336
}
2437

@@ -52,14 +65,16 @@ export function formatCluster(cluster: ClusterDescription20240805): Cluster {
5265

5366
const instanceSize = regionConfigs[0]?.instanceSize ?? "UNKNOWN";
5467
const clusterInstanceType = instanceSize === "M0" ? "FREE" : "DEDICATED";
68+
const connectionString = cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard;
5569

5670
return {
5771
name: cluster.name,
5872
instanceType: clusterInstanceType,
5973
instanceSize: clusterInstanceType === "DEDICATED" ? instanceSize : undefined,
6074
state: cluster.stateName,
6175
mongoDBVersion: cluster.mongoDBVersion,
62-
connectionString: cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard,
76+
connectionString,
77+
processIds: extractProcessIds(cluster.connectionStrings?.standard ?? ""),
6378
};
6479
}
6580

@@ -96,3 +111,18 @@ export async function inspectCluster(apiClient: ApiClient, projectId: string, cl
96111
}
97112
}
98113
}
114+
115+
export async function getProcessIdsFromCluster(
116+
apiClient: ApiClient,
117+
projectId: string,
118+
clusterName: string
119+
): Promise<Array<string>> {
120+
try {
121+
const cluster = await inspectCluster(apiClient, projectId, clusterName);
122+
return cluster.processIds || [];
123+
} catch (error) {
124+
throw new Error(
125+
`Failed to get processIds from cluster: ${error instanceof Error ? error.message : String(error)}`
126+
);
127+
}
128+
}

0 commit comments

Comments
 (0)