Skip to content

Commit 817b961

Browse files
committed
Merge base tool branch
2 parents fe2e79a + 3217e95 commit 817b961

File tree

3 files changed

+55
-273
lines changed

3 files changed

+55
-273
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)}`
Lines changed: 10 additions & 229 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,23 @@
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-
}
11-
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-
}
6+
export type SuggestedIndex = components["schemas"]["PerformanceAdvisorIndex"];
7+
export type DropIndexSuggestion = components["schemas"]["DropIndexSuggestionsIndex"];
8+
export type SlowQueryLogMetrics = components["schemas"]["PerformanceAdvisorSlowQueryMetrics"];
9+
export type SlowQueryLog = components["schemas"]["PerformanceAdvisorSlowQuery"];
2010

2111
interface SuggestedIndexesResponse {
22-
content: {
23-
suggestedIndexes?: Array<SuggestedIndex>;
24-
};
12+
content: components["schemas"]["PerformanceAdvisorResponse"];
2513
}
26-
2714
interface DropIndexesResponse {
28-
content: {
29-
hiddenIndexes?: Array<DropIndexSuggestion>;
30-
redundantIndexes?: Array<DropIndexSuggestion>;
31-
unusedIndexes?: Array<DropIndexSuggestion>;
32-
};
15+
content: components["schemas"]["DropIndexSuggestionsResponse"];
3316
}
34-
3517
interface SchemaAdviceResponse {
36-
content: {
37-
recommendations?: Array<SchemaRecommendation>;
38-
};
39-
}
40-
41-
interface SlowQueriesResponse {
42-
slowQueries?: Array<SlowQueryLog>;
43-
}
44-
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-
55-
export type SchemaTriggerType =
56-
| "PERCENT_QUERIES_USE_LOOKUP"
57-
| "NUMBER_OF_QUERIES_USE_LOOKUP"
58-
| "DOCS_CONTAIN_UNBOUNDED_ARRAY"
59-
| "NUMBER_OF_NAMESPACES"
60-
| "DOC_SIZE_TOO_LARGE"
61-
| "NUM_INDEXES"
62-
| "QUERIES_CONTAIN_CASE_INSENSITIVE_REGEX";
63-
64-
export const SCHEMA_TRIGGER_DESCRIPTIONS: Record<SchemaTriggerType, string> = {
65-
PERCENT_QUERIES_USE_LOOKUP: "High percentage of queries (>50%) use $lookup operations",
66-
NUMBER_OF_QUERIES_USE_LOOKUP: "High number of queries (>100) use $lookup operations",
67-
DOCS_CONTAIN_UNBOUNDED_ARRAY: "Arrays with over 10000 entries detected in the collection(s)",
68-
NUMBER_OF_NAMESPACES: "Too many namespaces (collections) in the database (>100)",
69-
DOC_SIZE_TOO_LARGE: "Documents larger than 2 MB found in the collection(s)",
70-
NUM_INDEXES: "More than 30 indexes detected in the collection(s) scanned",
71-
QUERIES_CONTAIN_CASE_INSENSITIVE_REGEX: "Queries use case-insensitive regular expressions",
72-
};
73-
74-
type SchemaRecommedationType =
75-
| "REDUCE_LOOKUP_OPS"
76-
| "AVOID_UNBOUNDED_ARRAY"
77-
| "REDUCE_DOCUMENT_SIZE"
78-
| "REMOVE_UNNECESSARY_INDEXES"
79-
| "REDUCE_NUMBER_OF_NAMESPACES"
80-
| "OPTIMIZE_CASE_INSENSITIVE_REGEX_QUERIES"
81-
| "OPTIMIZE_TEXT_QUERIES";
82-
83-
export const SCHEMA_RECOMMENDATION_DESCRIPTIONS: Record<SchemaRecommedationType, string> = {
84-
REDUCE_LOOKUP_OPS: "Reduce the use of $lookup operations",
85-
AVOID_UNBOUNDED_ARRAY: "Avoid using unbounded arrays in documents",
86-
REDUCE_DOCUMENT_SIZE: "Reduce the size of documents",
87-
REMOVE_UNNECESSARY_INDEXES: "Remove unnecessary indexes",
88-
REDUCE_NUMBER_OF_NAMESPACES: "Reduce the number of collections in the database",
89-
OPTIMIZE_CASE_INSENSITIVE_REGEX_QUERIES: "Optimize case-insensitive regex queries",
90-
OPTIMIZE_TEXT_QUERIES: "Optimize text search queries",
91-
};
92-
93-
export interface SchemaRecommendation {
94-
affectedNamespaces?: Array<{
95-
namespace?: string | null;
96-
triggers?: Array<{
97-
description?: string;
98-
triggerType?: SchemaTriggerType;
99-
}>;
100-
}>;
101-
description?: string;
102-
recommendation?: SchemaRecommedationType;
103-
}
104-
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-
127-
export interface PerformanceAdvisorData {
128-
suggestedIndexes: Array<SuggestedIndex>;
129-
dropIndexSuggestions: {
130-
hiddenIndexes: Array<DropIndexSuggestion>;
131-
redundantIndexes: Array<DropIndexSuggestion>;
132-
unusedIndexes: Array<DropIndexSuggestion>;
133-
};
134-
slowQueryLogs: Array<SlowQueryLog>;
135-
schemaSuggestions: Array<SchemaRecommendation>;
18+
content: components["schemas"]["SchemaAdvisorResponse"];
13619
}
20+
export type SchemaRecommendation = components["schemas"]["SchemaAdvisorItemRecommendation"];
13721

13822
export async function getSuggestedIndexes(
13923
apiClient: ApiClient,
@@ -243,7 +127,7 @@ export async function getSlowQueries(
243127
},
244128
});
245129

246-
return { slowQueryLogs: (response as SlowQueriesResponse).slowQueries ?? [] };
130+
return { slowQueryLogs: response.slowQueries ?? [] };
247131
} catch (err) {
248132
apiClient.logger.debug({
249133
id: LogId.atlasPaSlowQueryLogsFailure,
@@ -253,106 +137,3 @@ export async function getSlowQueries(
253137
throw new Error(`Failed to list slow query logs: ${err instanceof Error ? err.message : String(err)}`);
254138
}
255139
}
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-
}

0 commit comments

Comments
 (0)