Skip to content

Commit f0b24bb

Browse files
committed
Address comments
1 parent ce0466b commit f0b24bb

File tree

3 files changed

+26
-181
lines changed

3 files changed

+26
-181
lines changed

src/common/atlas/cluster.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,7 @@ export async function getProcessIdFromCluster(
109109
throw new Error("No connection string available for cluster");
110110
}
111111
const url = new URL(cluster.connectionString);
112-
const processId = `${url.hostname}:${url.port || DEFAULT_PORT}`;
113-
return processId;
112+
return `${url.hostname}:${url.port || DEFAULT_PORT}`;
114113
} catch (error) {
115114
throw new Error(
116115
`Failed to get processId from cluster: ${error instanceof Error ? error.message : String(error)}`

src/common/atlas/performanceAdvisorUtils.ts

Lines changed: 7 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
import { LogId } from "../logger.js";
22
import type { ApiClient } from "./apiClient.js";
33
import { getProcessIdFromCluster } from "./cluster.js";
4+
import type { components } from "./openapi.js";
45

5-
export enum PerformanceAdvisorOperation {
6-
SUGGESTED_INDEXES = "suggestedIndexes",
7-
DROP_INDEX_SUGGESTIONS = "dropIndexSuggestions",
8-
SLOW_QUERY_LOGS = "slowQueryLogs",
9-
SCHEMA_SUGGESTIONS = "schemaSuggestions",
10-
}
6+
export type SuggestedIndex = components["schemas"]["PerformanceAdvisorIndex"];
117

12-
export interface SuggestedIndex {
13-
avgObjSize?: number;
14-
id?: string;
15-
impact?: Array<string>;
16-
index?: Array<{ [key: string]: 1 | -1 }>;
17-
namespace?: string;
18-
weight?: number;
19-
}
8+
export type DropIndexSuggestion = components["schemas"]["DropIndexSuggestionsIndex"];
9+
10+
export type SlowQueryLogMetrics = components["schemas"]["PerformanceAdvisorSlowQueryMetrics"];
11+
12+
export type SlowQueryLog = components["schemas"]["PerformanceAdvisorSlowQuery"];
2013

2114
interface SuggestedIndexesResponse {
2215
content: {
@@ -42,16 +35,6 @@ interface SlowQueriesResponse {
4235
slowQueries?: Array<SlowQueryLog>;
4336
}
4437

45-
export interface DropIndexSuggestion {
46-
accessCount?: number;
47-
index?: Array<{ [key: string]: 1 | -1 }>;
48-
name?: string;
49-
namespace?: string;
50-
shards?: Array<string>;
51-
since?: string;
52-
sizeBytes?: number;
53-
}
54-
5538
export type SchemaTriggerType =
5639
| "PERCENT_QUERIES_USE_LOOKUP"
5740
| "NUMBER_OF_QUERIES_USE_LOOKUP"
@@ -102,28 +85,6 @@ export interface SchemaRecommendation {
10285
recommendation?: SchemaRecommedationType;
10386
}
10487

105-
interface SlowQueryLogMetrics {
106-
docsExamined?: number;
107-
docsExaminedReturnedRatio?: number;
108-
docsReturned?: number;
109-
fromUserConnection?: boolean;
110-
hasIndexCoverage?: boolean;
111-
hasSort?: boolean;
112-
keysExamined?: number;
113-
keysExaminedReturnedRatio?: number;
114-
numYields?: number;
115-
operationExecutionTime?: number;
116-
responseLength?: number;
117-
}
118-
119-
export interface SlowQueryLog {
120-
line?: string;
121-
metrics?: SlowQueryLogMetrics;
122-
namespace?: string;
123-
opType?: string;
124-
replicaState?: string;
125-
}
126-
12788
export interface PerformanceAdvisorData {
12889
suggestedIndexes: Array<SuggestedIndex>;
12990
dropIndexSuggestions: {
@@ -253,106 +214,3 @@ export async function getSlowQueries(
253214
throw new Error(`Failed to list slow query logs: ${err instanceof Error ? err.message : String(err)}`);
254215
}
255216
}
256-
257-
export function formatSuggestedIndexesTable(suggestedIndexes: Array<SuggestedIndex>): string {
258-
if (suggestedIndexes.length === 0) return "No suggested indexes found.";
259-
260-
const rows = suggestedIndexes
261-
.map((index, i) => {
262-
const namespace = index.namespace ?? "N/A";
263-
const weight = index.weight ?? "N/A";
264-
const avgObjSize = index.avgObjSize ?? "N/A";
265-
const indexKeys = index.index ? index.index.map((key) => Object.keys(key)[0]).join(", ") : "N/A";
266-
return `${i + 1} | ${namespace} | ${weight} | ${avgObjSize} | ${indexKeys}`;
267-
})
268-
.join("\n");
269-
270-
return `# | Namespace | Weight | Avg Obj Size | Index Keys
271-
---|-----------|--------|--------------|------------
272-
${rows}`;
273-
}
274-
275-
export function formatDropIndexesTable(dropIndexSuggestions: {
276-
hiddenIndexes: Array<DropIndexSuggestion>;
277-
redundantIndexes: Array<DropIndexSuggestion>;
278-
unusedIndexes: Array<DropIndexSuggestion>;
279-
}): string {
280-
const allIndexes = [
281-
...dropIndexSuggestions.hiddenIndexes.map((index) => ({ ...index, type: "Hidden" })),
282-
...dropIndexSuggestions.redundantIndexes.map((index) => ({ ...index, type: "Redundant" })),
283-
...dropIndexSuggestions.unusedIndexes.map((index) => ({ ...index, type: "Unused" })),
284-
];
285-
286-
if (allIndexes.length === 0) return "No drop index suggestions found.";
287-
288-
const rows = allIndexes
289-
.map((index, i) => {
290-
const name = index.name ?? "N/A";
291-
const namespace = index.namespace ?? "N/A";
292-
const type = index.type ?? "N/A";
293-
const sizeBytes = index.sizeBytes ?? "N/A";
294-
const accessCount = index.accessCount ?? "N/A";
295-
return `${i + 1} | ${name} | ${namespace} | ${type} | ${sizeBytes} | ${accessCount}`;
296-
})
297-
.join("\n");
298-
299-
return `# | Index Name | Namespace | Type | Size (bytes) | Access Count
300-
---|------------|-----------|------|--------------|-------------
301-
${rows}`;
302-
}
303-
304-
export function formatSlowQueriesTable(slowQueryLogs: Array<SlowQueryLog>): string {
305-
if (slowQueryLogs.length === 0) return "No slow query logs found.";
306-
307-
const rows = slowQueryLogs
308-
.map((log, i) => {
309-
const namespace = log.namespace ?? "N/A";
310-
const opType = log.opType ?? "N/A";
311-
const executionTime = log.metrics?.operationExecutionTime ?? "N/A";
312-
const docsExamined = log.metrics?.docsExamined ?? "N/A";
313-
const docsReturned = log.metrics?.docsReturned ?? "N/A";
314-
return `${i + 1} | ${namespace} | ${opType} | ${executionTime}ms | ${docsExamined} | ${docsReturned}`;
315-
})
316-
.join("\n");
317-
318-
return `# | Namespace | Operation | Execution Time | Docs Examined | Docs Returned
319-
---|-----------|-----------|---------------|---------------|---------------
320-
${rows}`;
321-
}
322-
323-
function getTriggerDescription(triggerType: SchemaTriggerType | undefined): string {
324-
if (!triggerType) return "N/A";
325-
return SCHEMA_TRIGGER_DESCRIPTIONS[triggerType] ?? triggerType;
326-
}
327-
328-
function getNamespaceTriggerDescriptions(namespace: { triggers?: Array<{ triggerType?: SchemaTriggerType }> }): string {
329-
if (!namespace.triggers) return "N/A";
330-
331-
return namespace.triggers.map((trigger) => getTriggerDescription(trigger.triggerType)).join(", ");
332-
}
333-
334-
function getTriggerDescriptions(suggestion: SchemaRecommendation): string {
335-
if (!suggestion.affectedNamespaces) return "N/A";
336-
337-
return suggestion.affectedNamespaces.map((namespace) => getNamespaceTriggerDescriptions(namespace)).join(", ");
338-
}
339-
340-
export function formatSchemaSuggestionsTable(schemaSuggestions: Array<SchemaRecommendation>): string {
341-
if (schemaSuggestions.length === 0) return "No schema suggestions found.";
342-
343-
const rows = schemaSuggestions
344-
.map((suggestion: SchemaRecommendation, i) => {
345-
const recommendation = suggestion.recommendation
346-
? (SCHEMA_RECOMMENDATION_DESCRIPTIONS[suggestion.recommendation] ?? suggestion.recommendation)
347-
: "N/A";
348-
const description = suggestion.description ?? "N/A";
349-
const triggeredBy = getTriggerDescriptions(suggestion);
350-
const affectedNamespaces = suggestion.affectedNamespaces?.length ?? 0;
351-
return `${i + 1} | ${recommendation} | ${description} | ${triggeredBy} | ${affectedNamespaces} namespaces`;
352-
})
353-
.join("\n");
354-
355-
return `# | Recommendation | Description | Triggered By | Affected Namespaces
356-
---|---------------|-------------|----------|-------------------
357-
${rows}`;
358-
}

src/tools/atlas/read/listPerformanceAdvisor.ts

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import {
88
getDropIndexSuggestions,
99
getSchemaAdvice,
1010
getSlowQueries,
11-
PerformanceAdvisorOperation,
1211
type PerformanceAdvisorData,
13-
formatSuggestedIndexesTable,
14-
formatDropIndexesTable,
15-
formatSlowQueriesTable,
16-
formatSchemaSuggestionsTable,
1712
} from "../../../common/atlas/performanceAdvisorUtils.js";
1813

14+
const PerformanceAdvisorOperationType = z.enum([
15+
"suggestedIndexes",
16+
"dropIndexSuggestions",
17+
"slowQueryLogs",
18+
"schemaSuggestions",
19+
]);
1920
export class ListPerformanceAdvisorTool extends AtlasToolBase {
2021
public name = "atlas-list-performance-advisor";
2122
protected description = "List MongoDB Atlas performance advisor recommendations";
@@ -24,8 +25,8 @@ export class ListPerformanceAdvisorTool extends AtlasToolBase {
2425
projectId: z.string().describe("Atlas project ID to list performance advisor recommendations"),
2526
clusterName: z.string().describe("Atlas cluster name to list performance advisor recommendations"),
2627
operations: z
27-
.array(z.nativeEnum(PerformanceAdvisorOperation))
28-
.default(Object.values(PerformanceAdvisorOperation))
28+
.array(PerformanceAdvisorOperationType)
29+
.default(PerformanceAdvisorOperationType.options)
2930
.describe("Operations to list performance advisor recommendations"),
3031
since: z.date().describe("Date to list slow query logs since").optional(),
3132
namespaces: z.array(z.string()).describe("Namespaces to list slow query logs").optional(),
@@ -48,15 +49,15 @@ export class ListPerformanceAdvisorTool extends AtlasToolBase {
4849
try {
4950
const performanceAdvisorPromises = [];
5051

51-
if (operations.includes(PerformanceAdvisorOperation.SUGGESTED_INDEXES)) {
52+
if (operations.includes("suggestedIndexes")) {
5253
performanceAdvisorPromises.push(
5354
getSuggestedIndexes(this.session.apiClient, projectId, clusterName).then(({ suggestedIndexes }) => {
5455
data.suggestedIndexes = suggestedIndexes;
5556
})
5657
);
5758
}
5859

59-
if (operations.includes(PerformanceAdvisorOperation.DROP_INDEX_SUGGESTIONS)) {
60+
if (operations.includes("dropIndexSuggestions")) {
6061
performanceAdvisorPromises.push(
6162
getDropIndexSuggestions(this.session.apiClient, projectId, clusterName).then(
6263
({ hiddenIndexes, redundantIndexes, unusedIndexes }) => {
@@ -66,7 +67,7 @@ export class ListPerformanceAdvisorTool extends AtlasToolBase {
6667
);
6768
}
6869

69-
if (operations.includes(PerformanceAdvisorOperation.SLOW_QUERY_LOGS)) {
70+
if (operations.includes("slowQueryLogs")) {
7071
performanceAdvisorPromises.push(
7172
getSlowQueries(this.session.apiClient, projectId, clusterName, since, namespaces).then(
7273
({ slowQueryLogs }) => {
@@ -76,7 +77,7 @@ export class ListPerformanceAdvisorTool extends AtlasToolBase {
7677
);
7778
}
7879

79-
if (operations.includes(PerformanceAdvisorOperation.SCHEMA_SUGGESTIONS)) {
80+
if (operations.includes("schemaSuggestions")) {
8081
performanceAdvisorPromises.push(
8182
getSchemaAdvice(this.session.apiClient, projectId, clusterName).then(({ recommendations }) => {
8283
data.schemaSuggestions = recommendations;
@@ -96,49 +97,36 @@ export class ListPerformanceAdvisorTool extends AtlasToolBase {
9697
};
9798
}
9899

99-
// Format the data as tables
100100
let formattedOutput = "";
101-
let totalItems = 0;
102101

103102
if (data.suggestedIndexes.length > 0) {
104-
const suggestedIndexesTable = formatSuggestedIndexesTable(data.suggestedIndexes);
105-
formattedOutput += `\n## Suggested Indexes\n${suggestedIndexesTable}\n`;
106-
totalItems += data.suggestedIndexes.length;
103+
formattedOutput += `\n## Suggested Indexes\n${JSON.stringify(data.suggestedIndexes)}\n`;
107104
}
108105

109106
if (
110107
data.dropIndexSuggestions.hiddenIndexes.length > 0 ||
111108
data.dropIndexSuggestions.redundantIndexes.length > 0 ||
112109
data.dropIndexSuggestions.unusedIndexes.length > 0
113110
) {
114-
const dropIndexesTable = formatDropIndexesTable(data.dropIndexSuggestions);
115-
formattedOutput += `\n## Drop Index Suggestions\n${dropIndexesTable}\n`;
116-
totalItems +=
117-
data.dropIndexSuggestions.hiddenIndexes.length +
118-
data.dropIndexSuggestions.redundantIndexes.length +
119-
data.dropIndexSuggestions.unusedIndexes.length;
111+
formattedOutput += `\n## Drop Index Suggestions\n${JSON.stringify(data.dropIndexSuggestions)}\n`;
120112
}
121113

122114
if (data.slowQueryLogs.length > 0) {
123-
const slowQueriesTable = formatSlowQueriesTable(data.slowQueryLogs);
124-
formattedOutput += `\n## Slow Query Logs\n${slowQueriesTable}\n`;
125-
totalItems += data.slowQueryLogs.length;
115+
formattedOutput += `\n## Slow Query Logs\n${JSON.stringify(data.slowQueryLogs)}\n`;
126116
}
127117

128118
if (data.schemaSuggestions.length > 0) {
129-
const schemaTable = formatSchemaSuggestionsTable(data.schemaSuggestions);
130-
formattedOutput += `\n## Schema Suggestions\n${schemaTable}\n`;
131-
totalItems += data.schemaSuggestions.length;
119+
formattedOutput += `\n## Schema Suggestions\n${JSON.stringify(data.schemaSuggestions)}\n`;
132120
}
133121

134-
if (totalItems === 0) {
122+
if (formattedOutput === "") {
135123
return {
136124
content: [{ type: "text", text: "No performance advisor recommendations found." }],
137125
};
138126
}
139127

140128
return {
141-
content: formatUntrustedData(`Found ${totalItems} performance advisor recommendations`, formattedOutput),
129+
content: formatUntrustedData("Performance advisor data", formattedOutput),
142130
};
143131
}
144132
}

0 commit comments

Comments
 (0)