@codebaz/nextdoctor-agent 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +3 -0
- package/README.md +568 -0
- package/dist/detectors/__tests__/cold-start-threshold.test.d.ts +2 -0
- package/dist/detectors/__tests__/cold-start-threshold.test.d.ts.map +1 -0
- package/dist/detectors/__tests__/cold-start-threshold.test.js +156 -0
- package/dist/detectors/__tests__/cold-start-threshold.test.js.map +1 -0
- package/dist/detectors/__tests__/dynamic-route-candidate.test.d.ts +2 -0
- package/dist/detectors/__tests__/dynamic-route-candidate.test.d.ts.map +1 -0
- package/dist/detectors/__tests__/dynamic-route-candidate.test.js +318 -0
- package/dist/detectors/__tests__/dynamic-route-candidate.test.js.map +1 -0
- package/dist/detectors/__tests__/fetch-no-cache.test.d.ts +2 -0
- package/dist/detectors/__tests__/fetch-no-cache.test.d.ts.map +1 -0
- package/dist/detectors/__tests__/fetch-no-cache.test.js +199 -0
- package/dist/detectors/__tests__/fetch-no-cache.test.js.map +1 -0
- package/dist/detectors/base-detector.d.ts +17 -0
- package/dist/detectors/base-detector.d.ts.map +1 -0
- package/dist/detectors/base-detector.js +50 -0
- package/dist/detectors/base-detector.js.map +1 -0
- package/dist/detectors/cold-start-threshold.detector.d.ts +11 -0
- package/dist/detectors/cold-start-threshold.detector.d.ts.map +1 -0
- package/dist/detectors/cold-start-threshold.detector.js +87 -0
- package/dist/detectors/cold-start-threshold.detector.js.map +1 -0
- package/dist/detectors/dynamic-route-candidate.detector.d.ts +23 -0
- package/dist/detectors/dynamic-route-candidate.detector.d.ts.map +1 -0
- package/dist/detectors/dynamic-route-candidate.detector.js +96 -0
- package/dist/detectors/dynamic-route-candidate.detector.js.map +1 -0
- package/dist/detectors/fetch-no-cache.detector.d.ts +12 -0
- package/dist/detectors/fetch-no-cache.detector.d.ts.map +1 -0
- package/dist/detectors/fetch-no-cache.detector.js +178 -0
- package/dist/detectors/fetch-no-cache.detector.js.map +1 -0
- package/dist/detectors/index.d.ts +28 -0
- package/dist/detectors/index.d.ts.map +1 -0
- package/dist/detectors/index.js +97 -0
- package/dist/detectors/index.js.map +1 -0
- package/dist/detectors/types.d.ts +32 -0
- package/dist/detectors/types.d.ts.map +1 -0
- package/dist/detectors/types.js +2 -0
- package/dist/detectors/types.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +133 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +363 -0
- package/dist/init.js.map +1 -0
- package/dist/middleware.d.ts +10 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +61 -0
- package/dist/middleware.js.map +1 -0
- package/dist/optimization.d.ts +43 -0
- package/dist/optimization.d.ts.map +1 -0
- package/dist/optimization.js +139 -0
- package/dist/optimization.js.map +1 -0
- package/dist/system-monitor.d.ts +124 -0
- package/dist/system-monitor.d.ts.map +1 -0
- package/dist/system-monitor.js +221 -0
- package/dist/system-monitor.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
- package/src/detectors/__tests__/cold-start-threshold.test.ts +183 -0
- package/src/detectors/__tests__/dynamic-route-candidate.test.ts +365 -0
- package/src/detectors/__tests__/fetch-no-cache.test.ts +239 -0
- package/src/detectors/base-detector.ts +69 -0
- package/src/detectors/cold-start-threshold.detector.ts +95 -0
- package/src/detectors/dynamic-route-candidate.detector.ts +107 -0
- package/src/detectors/fetch-no-cache.detector.ts +204 -0
- package/src/detectors/index.ts +127 -0
- package/src/detectors/types.ts +38 -0
- package/src/index.ts +60 -0
- package/src/init.ts +424 -0
- package/src/middleware.ts +75 -0
- package/src/optimization.ts +164 -0
- package/src/system-monitor.ts +295 -0
- package/src/types.ts +66 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { cpus, loadavg, totalmem, freemem } from 'os';
|
|
2
|
+
import { performance } from 'perf_hooks';
|
|
3
|
+
import type { LogLevel } from './types.js';
|
|
4
|
+
|
|
5
|
+
export interface CPUMetrics {
|
|
6
|
+
timestamp: number;
|
|
7
|
+
usage: number; // percentage 0-100
|
|
8
|
+
loadAverage: {
|
|
9
|
+
oneMinute: number;
|
|
10
|
+
fiveMinutes: number;
|
|
11
|
+
fifteenMinutes: number;
|
|
12
|
+
};
|
|
13
|
+
coreCount: number;
|
|
14
|
+
systemLoadPerCore: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface MemoryMetrics {
|
|
18
|
+
timestamp: number;
|
|
19
|
+
heapUsed: number; // bytes
|
|
20
|
+
heapTotal: number; // bytes
|
|
21
|
+
external: number; // bytes
|
|
22
|
+
arrayBuffers: number; // bytes
|
|
23
|
+
heapUsagePercent: number;
|
|
24
|
+
systemMemoryUsed: number;
|
|
25
|
+
systemMemoryTotal: number;
|
|
26
|
+
systemMemoryUsagePercent: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SystemMetrics {
|
|
30
|
+
cpu: CPUMetrics;
|
|
31
|
+
memory: MemoryMetrics;
|
|
32
|
+
uptime: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* CPU Monitor - Real-time CPU usage tracking
|
|
37
|
+
*/
|
|
38
|
+
export class CPUMonitor {
|
|
39
|
+
private static readonly SAMPLE_INTERVAL = 100; // ms
|
|
40
|
+
private lastCPUTimes: number[] = [];
|
|
41
|
+
private lastSampleTime = performance.now();
|
|
42
|
+
private logFn?: (level: LogLevel, message: string, meta?: any) => void;
|
|
43
|
+
|
|
44
|
+
constructor(logFn?: (level: LogLevel, message: string, meta?: any) => void) {
|
|
45
|
+
this.logFn = logFn;
|
|
46
|
+
this.initializeBaseline();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private initializeBaseline(): void {
|
|
50
|
+
const cpus_info = cpus();
|
|
51
|
+
this.lastCPUTimes = cpus_info.map((cpu) => {
|
|
52
|
+
return (
|
|
53
|
+
cpu.times.user +
|
|
54
|
+
cpu.times.sys +
|
|
55
|
+
cpu.times.idle +
|
|
56
|
+
cpu.times.irq
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Calcula o uso de CPU em percentual
|
|
63
|
+
*/
|
|
64
|
+
getCPUUsage(): CPUMetrics {
|
|
65
|
+
const cpus_info = cpus();
|
|
66
|
+
const coreCount = cpus_info.length;
|
|
67
|
+
const [one, five, fifteen] = loadavg();
|
|
68
|
+
|
|
69
|
+
// Calculate average CPU usage across all cores
|
|
70
|
+
let totalUsage = 0;
|
|
71
|
+
|
|
72
|
+
cpus_info.forEach((cpu, index) => {
|
|
73
|
+
const currentTime =
|
|
74
|
+
cpu.times.user +
|
|
75
|
+
cpu.times.sys +
|
|
76
|
+
cpu.times.idle +
|
|
77
|
+
cpu.times.irq;
|
|
78
|
+
|
|
79
|
+
const lastTime = this.lastCPUTimes[index] || currentTime;
|
|
80
|
+
const timeDiff = currentTime - lastTime;
|
|
81
|
+
|
|
82
|
+
if (timeDiff > 0) {
|
|
83
|
+
const userTime = cpu.times.user - (this.lastCPUTimes[index] || 0);
|
|
84
|
+
const systemTime = cpu.times.sys - (this.lastCPUTimes[index] || 0);
|
|
85
|
+
const usage = ((userTime + systemTime) / timeDiff) * 100;
|
|
86
|
+
totalUsage += Math.min(100, Math.max(0, usage));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.lastCPUTimes[index] = currentTime;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const averageUsage = totalUsage / coreCount;
|
|
93
|
+
const systemLoadPerCore = one! / coreCount;
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
timestamp: Date.now(),
|
|
97
|
+
usage: Math.round(averageUsage * 100) / 100,
|
|
98
|
+
loadAverage: {
|
|
99
|
+
oneMinute: Math.round(one! * 100) / 100,
|
|
100
|
+
fiveMinutes: Math.round(five! * 100) / 100,
|
|
101
|
+
fifteenMinutes: Math.round(fifteen! * 100) / 100,
|
|
102
|
+
},
|
|
103
|
+
coreCount,
|
|
104
|
+
systemLoadPerCore: Math.round(systemLoadPerCore * 100) / 100,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Monitora CPU e alerta se exceder threshold
|
|
110
|
+
*/
|
|
111
|
+
checkCPUThreshold(threshold: number = 80): { exceeded: boolean; usage: number } {
|
|
112
|
+
const metrics = this.getCPUUsage();
|
|
113
|
+
const exceeded = metrics.usage > threshold;
|
|
114
|
+
|
|
115
|
+
if (exceeded && this.logFn) {
|
|
116
|
+
// @ts-ignore
|
|
117
|
+
this.logFn(2, `CPU usage exceeded threshold: ${metrics.usage}% > ${threshold}%`, {
|
|
118
|
+
usage: metrics.usage,
|
|
119
|
+
threshold,
|
|
120
|
+
cores: metrics.coreCount,
|
|
121
|
+
systemLoad: metrics.loadAverage,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
exceeded,
|
|
127
|
+
usage: metrics.usage,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Memory Monitor - Real-time memory usage tracking
|
|
134
|
+
*/
|
|
135
|
+
export class MemoryMonitor {
|
|
136
|
+
private logFn?: (level: LogLevel, message: string, meta?: any) => void;
|
|
137
|
+
|
|
138
|
+
constructor(logFn?: (level: LogLevel, message: string, meta?: any) => void) {
|
|
139
|
+
this.logFn = logFn;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Obtém métricas de memória
|
|
144
|
+
*/
|
|
145
|
+
getMemoryMetrics(): MemoryMetrics {
|
|
146
|
+
const memUsage = process.memoryUsage();
|
|
147
|
+
const systemTotal = totalmem();
|
|
148
|
+
const systemFree = freemem();
|
|
149
|
+
const systemUsed = systemTotal - systemFree;
|
|
150
|
+
|
|
151
|
+
const heapUsagePercent = (memUsage.heapUsed / memUsage.heapTotal) * 100;
|
|
152
|
+
const systemMemoryUsagePercent = (systemUsed / systemTotal) * 100;
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
timestamp: Date.now(),
|
|
156
|
+
heapUsed: memUsage.heapUsed,
|
|
157
|
+
heapTotal: memUsage.heapTotal,
|
|
158
|
+
external: memUsage.external,
|
|
159
|
+
arrayBuffers: memUsage.arrayBuffers || 0,
|
|
160
|
+
heapUsagePercent: Math.round(heapUsagePercent * 100) / 100,
|
|
161
|
+
systemMemoryUsed: systemUsed,
|
|
162
|
+
systemMemoryTotal: systemTotal,
|
|
163
|
+
systemMemoryUsagePercent: Math.round(systemMemoryUsagePercent * 100) / 100,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Verifica se memória excedeu threshold
|
|
169
|
+
*/
|
|
170
|
+
checkMemoryThreshold(threshold: number = 85): {
|
|
171
|
+
heapExceeded: boolean;
|
|
172
|
+
systemExceeded: boolean;
|
|
173
|
+
metrics: MemoryMetrics;
|
|
174
|
+
} {
|
|
175
|
+
const metrics = this.getMemoryMetrics();
|
|
176
|
+
const heapExceeded = metrics.heapUsagePercent > threshold;
|
|
177
|
+
const systemExceeded = metrics.systemMemoryUsagePercent > threshold;
|
|
178
|
+
|
|
179
|
+
if ((heapExceeded || systemExceeded) && this.logFn) {
|
|
180
|
+
// @ts-ignore
|
|
181
|
+
this.logFn(2, 'Memory threshold exceeded', {
|
|
182
|
+
heap: `${metrics.heapUsagePercent}% (${Math.round(metrics.heapUsed / 1024 / 1024)}MB)`,
|
|
183
|
+
system: `${metrics.systemMemoryUsagePercent}% (${Math.round(metrics.systemMemoryUsed / 1024 / 1024 / 1024)}GB)`,
|
|
184
|
+
threshold,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
heapExceeded,
|
|
190
|
+
systemExceeded,
|
|
191
|
+
metrics,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Formata bytes para string legível
|
|
197
|
+
*/
|
|
198
|
+
formatBytes(bytes: number): string {
|
|
199
|
+
if (bytes === 0) return '0 B';
|
|
200
|
+
const k = 1024;
|
|
201
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
202
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
203
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* System Monitor - Unified system metrics collector
|
|
209
|
+
*/
|
|
210
|
+
export class SystemMonitor {
|
|
211
|
+
private cpuMonitor: CPUMonitor;
|
|
212
|
+
private memoryMonitor: MemoryMonitor;
|
|
213
|
+
private startTime = Date.now();
|
|
214
|
+
private logFn?: (level: LogLevel, message: string, meta?: any) => void;
|
|
215
|
+
|
|
216
|
+
constructor(logFn?: (level: LogLevel, message: string, meta?: any) => void) {
|
|
217
|
+
this.logFn = logFn;
|
|
218
|
+
this.cpuMonitor = new CPUMonitor(logFn);
|
|
219
|
+
this.memoryMonitor = new MemoryMonitor(logFn);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Coleta todas as métricas do sistema
|
|
224
|
+
*/
|
|
225
|
+
getSystemMetrics(): SystemMetrics {
|
|
226
|
+
return {
|
|
227
|
+
cpu: this.cpuMonitor.getCPUUsage(),
|
|
228
|
+
memory: this.memoryMonitor.getMemoryMetrics(),
|
|
229
|
+
uptime: Date.now() - this.startTime,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Monitora saúde geral do sistema
|
|
235
|
+
*/
|
|
236
|
+
getSystemHealth(cpuThreshold: number = 80, memThreshold: number = 85): {
|
|
237
|
+
healthy: boolean;
|
|
238
|
+
warnings: string[];
|
|
239
|
+
metrics: SystemMetrics;
|
|
240
|
+
} {
|
|
241
|
+
const metrics = this.getSystemMetrics();
|
|
242
|
+
const warnings: string[] = [];
|
|
243
|
+
|
|
244
|
+
const { exceeded: cpuExceeded, usage: cpuUsage } = this.cpuMonitor.checkCPUThreshold(cpuThreshold);
|
|
245
|
+
const { heapExceeded, systemExceeded } = this.memoryMonitor.checkMemoryThreshold(memThreshold);
|
|
246
|
+
|
|
247
|
+
if (cpuExceeded) {
|
|
248
|
+
warnings.push(`High CPU usage: ${cpuUsage}% (threshold: ${cpuThreshold}%)`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (heapExceeded) {
|
|
252
|
+
warnings.push(`High heap memory: ${metrics.memory.heapUsagePercent}% (threshold: ${memThreshold}%)`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (systemExceeded) {
|
|
256
|
+
warnings.push(`High system memory: ${metrics.memory.systemMemoryUsagePercent}% (threshold: ${memThreshold}%)`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
healthy: warnings.length === 0,
|
|
261
|
+
warnings,
|
|
262
|
+
metrics,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Dashboard-friendly summary
|
|
268
|
+
*/
|
|
269
|
+
getSummary() {
|
|
270
|
+
const metrics = this.getSystemMetrics();
|
|
271
|
+
const health = this.getSystemHealth();
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
status: health.healthy ? 'healthy' : 'warning',
|
|
275
|
+
cpu: {
|
|
276
|
+
usage: `${metrics.cpu.usage}%`,
|
|
277
|
+
cores: metrics.cpu.coreCount,
|
|
278
|
+
load: metrics.cpu.loadAverage,
|
|
279
|
+
},
|
|
280
|
+
memory: {
|
|
281
|
+
heap: {
|
|
282
|
+
used: this.memoryMonitor.formatBytes(metrics.memory.heapUsed),
|
|
283
|
+
total: this.memoryMonitor.formatBytes(metrics.memory.heapTotal),
|
|
284
|
+
usage: `${metrics.memory.heapUsagePercent}%`,
|
|
285
|
+
},
|
|
286
|
+
system: {
|
|
287
|
+
used: this.memoryMonitor.formatBytes(metrics.memory.systemMemoryUsed),
|
|
288
|
+
total: this.memoryMonitor.formatBytes(metrics.memory.systemMemoryTotal),
|
|
289
|
+
usage: `${metrics.memory.systemMemoryUsagePercent}%`,
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
warnings: health.warnings,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export enum LogLevel {
|
|
2
|
+
DEBUG = 0,
|
|
3
|
+
INFO = 1,
|
|
4
|
+
WARN = 2,
|
|
5
|
+
ERROR = 3,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export enum ExporterType {
|
|
9
|
+
OTLP_HTTP = 'otlp-http',
|
|
10
|
+
VERCEL = 'vercel',
|
|
11
|
+
NONE = 'none',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RetryPolicy {
|
|
15
|
+
maxRetries: number;
|
|
16
|
+
initialDelayMs: number;
|
|
17
|
+
maxDelayMs: number;
|
|
18
|
+
backoffMultiplier: number;
|
|
19
|
+
randomizationFactor: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ExporterConfig {
|
|
23
|
+
type: ExporterType;
|
|
24
|
+
url?: string;
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
batchSize?: number;
|
|
27
|
+
batchTimeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface NextDoctorConfig {
|
|
31
|
+
projectToken: string;
|
|
32
|
+
endpoint: string;
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
serviceName?: string;
|
|
35
|
+
version?: string;
|
|
36
|
+
environment?: 'development' | 'staging' | 'production';
|
|
37
|
+
logLevel?: LogLevel;
|
|
38
|
+
exporter?: ExporterConfig;
|
|
39
|
+
retryPolicy?: Partial<RetryPolicy>;
|
|
40
|
+
captureLogs?: boolean;
|
|
41
|
+
captureMetrics?: boolean;
|
|
42
|
+
captureExceptions?: boolean;
|
|
43
|
+
samplingRate?: number; // 0.0 to 1.0
|
|
44
|
+
enableDebugLogging?: boolean;
|
|
45
|
+
timeout?: number; // ms
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface DetectedIssue {
|
|
49
|
+
id: string;
|
|
50
|
+
severity: 'info' | 'warning' | 'high' | 'critical';
|
|
51
|
+
message: string;
|
|
52
|
+
suggestion: string;
|
|
53
|
+
route?: string;
|
|
54
|
+
spanId?: string;
|
|
55
|
+
attributes?: Record<string, unknown>;
|
|
56
|
+
detectedAt: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface AgentHealth {
|
|
60
|
+
initialized: boolean;
|
|
61
|
+
isHealthy: boolean;
|
|
62
|
+
lastHealthCheckAt?: number;
|
|
63
|
+
exporterStatus: 'healthy' | 'degraded' | 'unreachable';
|
|
64
|
+
bufferedSpans: number;
|
|
65
|
+
errorCount: number;
|
|
66
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/index.ts","./src/init.ts","./src/middleware.ts","./src/optimization.ts","./src/system-monitor.ts","./src/types.ts","./src/detectors/base-detector.ts","./src/detectors/cold-start-threshold.detector.ts","./src/detectors/dynamic-route-candidate.detector.ts","./src/detectors/fetch-no-cache.detector.ts","./src/detectors/index.ts","./src/detectors/types.ts","./src/detectors/__tests__/cold-start-threshold.test.ts","./src/detectors/__tests__/dynamic-route-candidate.test.ts","./src/detectors/__tests__/fetch-no-cache.test.ts"],"version":"5.9.3"}
|