@pioneer-platform/markets 8.12.0 → 8.13.0
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/METRICS_MONITORING.md +285 -0
- package/PRICING_IMPROVEMENTS.md +327 -0
- package/lib/api-metrics-reporter.d.ts +20 -0
- package/lib/api-metrics-reporter.js +356 -0
- package/lib/ccxt-pricing.d.ts +34 -0
- package/lib/ccxt-pricing.js +421 -0
- package/lib/index.js +784 -145
- package/lib/metrics-logger.d.ts +72 -0
- package/lib/metrics-logger.js +435 -0
- package/lib/refill-scheduler.d.ts +22 -0
- package/lib/refill-scheduler.js +226 -0
- package/lib/token-bucket-manager.d.ts +47 -0
- package/lib/token-bucket-manager.js +342 -0
- package/lib/token-bucket.d.ts +87 -0
- package/lib/token-bucket.js +341 -0
- package/package.json +6 -3
- package/.turbo/turbo-build.log +0 -1
- package/test-results.log +0 -5073
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Db } from 'mongodb';
|
|
2
|
+
export interface APICallLog {
|
|
3
|
+
apiName: string;
|
|
4
|
+
endpoint?: string;
|
|
5
|
+
method?: string;
|
|
6
|
+
caip?: string;
|
|
7
|
+
symbol?: string;
|
|
8
|
+
tokenTier?: number;
|
|
9
|
+
success: boolean;
|
|
10
|
+
responseTime?: number;
|
|
11
|
+
statusCode?: number;
|
|
12
|
+
price?: number;
|
|
13
|
+
error?: string;
|
|
14
|
+
tokensUsed?: number;
|
|
15
|
+
tokensRemaining?: number;
|
|
16
|
+
costUSD?: number;
|
|
17
|
+
wasRateLimited?: boolean;
|
|
18
|
+
wasThrottled?: boolean;
|
|
19
|
+
cacheHit?: boolean;
|
|
20
|
+
timestamp: Date;
|
|
21
|
+
requestId?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class MetricsLogger {
|
|
24
|
+
private db;
|
|
25
|
+
private logsCollection;
|
|
26
|
+
private enabled;
|
|
27
|
+
constructor(db: Db);
|
|
28
|
+
/**
|
|
29
|
+
* Enable/disable logging (for performance testing)
|
|
30
|
+
*/
|
|
31
|
+
setEnabled(enabled: boolean): void;
|
|
32
|
+
/**
|
|
33
|
+
* Log API call
|
|
34
|
+
*/
|
|
35
|
+
logAPICall(logData: APICallLog): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Get hourly stats
|
|
38
|
+
*/
|
|
39
|
+
getHourlyStats(hour: Date): Promise<any[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Get daily stats
|
|
42
|
+
*/
|
|
43
|
+
getDailyStats(date: Date): Promise<any[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Get stats for date range
|
|
46
|
+
*/
|
|
47
|
+
getStatsForRange(startDate: Date, endDate: Date): Promise<any[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Get top requested tokens
|
|
50
|
+
*/
|
|
51
|
+
getTopTokens(limit?: number, hours?: number): Promise<any[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Get error rate for API
|
|
54
|
+
*/
|
|
55
|
+
getErrorRate(apiName: string, hours?: number): Promise<number>;
|
|
56
|
+
/**
|
|
57
|
+
* Get rate limit events
|
|
58
|
+
*/
|
|
59
|
+
getRateLimitEvents(apiName?: string, hours?: number): Promise<any[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Get cost summary for month
|
|
62
|
+
*/
|
|
63
|
+
getMonthlyCostSummary(year: number, month: number): Promise<any>;
|
|
64
|
+
/**
|
|
65
|
+
* Clean up old logs (run periodically)
|
|
66
|
+
*/
|
|
67
|
+
cleanupOldLogs(daysToKeep?: number): Promise<number>;
|
|
68
|
+
/**
|
|
69
|
+
* Get cache hit rate
|
|
70
|
+
*/
|
|
71
|
+
getCacheHitRate(hours?: number): Promise<number>;
|
|
72
|
+
}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Metrics Logger
|
|
4
|
+
|
|
5
|
+
Logs all API calls to MongoDB for analytics and cost tracking
|
|
6
|
+
*/
|
|
7
|
+
var __assign = (this && this.__assign) || function () {
|
|
8
|
+
__assign = Object.assign || function(t) {
|
|
9
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
10
|
+
s = arguments[i];
|
|
11
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
12
|
+
t[p] = s[p];
|
|
13
|
+
}
|
|
14
|
+
return t;
|
|
15
|
+
};
|
|
16
|
+
return __assign.apply(this, arguments);
|
|
17
|
+
};
|
|
18
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
19
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
20
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
21
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
22
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
23
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
24
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
28
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
29
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
30
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
31
|
+
function step(op) {
|
|
32
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
33
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
34
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
35
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
36
|
+
switch (op[0]) {
|
|
37
|
+
case 0: case 1: t = op; break;
|
|
38
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
39
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
40
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
41
|
+
default:
|
|
42
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
43
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
44
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
45
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
46
|
+
if (t[2]) _.ops.pop();
|
|
47
|
+
_.trys.pop(); continue;
|
|
48
|
+
}
|
|
49
|
+
op = body.call(thisArg, _);
|
|
50
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
51
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
+
exports.MetricsLogger = void 0;
|
|
56
|
+
var log = require('@pioneer-platform/loggerdog')();
|
|
57
|
+
var TAG = ' | MetricsLogger | ';
|
|
58
|
+
var MetricsLogger = /** @class */ (function () {
|
|
59
|
+
function MetricsLogger(db) {
|
|
60
|
+
this.enabled = true;
|
|
61
|
+
this.db = db;
|
|
62
|
+
this.logsCollection = db.collection('api_usage_logs');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Enable/disable logging (for performance testing)
|
|
66
|
+
*/
|
|
67
|
+
MetricsLogger.prototype.setEnabled = function (enabled) {
|
|
68
|
+
this.enabled = enabled;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Log API call
|
|
72
|
+
*/
|
|
73
|
+
MetricsLogger.prototype.logAPICall = function (logData) {
|
|
74
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
75
|
+
var error_1;
|
|
76
|
+
return __generator(this, function (_a) {
|
|
77
|
+
switch (_a.label) {
|
|
78
|
+
case 0:
|
|
79
|
+
if (!this.enabled)
|
|
80
|
+
return [2 /*return*/];
|
|
81
|
+
_a.label = 1;
|
|
82
|
+
case 1:
|
|
83
|
+
_a.trys.push([1, 3, , 4]);
|
|
84
|
+
return [4 /*yield*/, this.logsCollection.insertOne(__assign(__assign({}, logData), { timestamp: logData.timestamp || new Date() }))];
|
|
85
|
+
case 2:
|
|
86
|
+
_a.sent();
|
|
87
|
+
return [3 /*break*/, 4];
|
|
88
|
+
case 3:
|
|
89
|
+
error_1 = _a.sent();
|
|
90
|
+
// Don't block on metrics errors, but log them
|
|
91
|
+
log.debug(TAG, "Failed to log API metrics: ".concat(error_1.message));
|
|
92
|
+
return [3 /*break*/, 4];
|
|
93
|
+
case 4: return [2 /*return*/];
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Get hourly stats
|
|
100
|
+
*/
|
|
101
|
+
MetricsLogger.prototype.getHourlyStats = function (hour) {
|
|
102
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
103
|
+
var hourStart, hourEnd, pipeline;
|
|
104
|
+
return __generator(this, function (_a) {
|
|
105
|
+
switch (_a.label) {
|
|
106
|
+
case 0:
|
|
107
|
+
hourStart = new Date(hour);
|
|
108
|
+
hourStart.setMinutes(0, 0, 0);
|
|
109
|
+
hourEnd = new Date(hourStart);
|
|
110
|
+
hourEnd.setHours(hourStart.getHours() + 1);
|
|
111
|
+
pipeline = [
|
|
112
|
+
{
|
|
113
|
+
$match: {
|
|
114
|
+
timestamp: { $gte: hourStart, $lt: hourEnd }
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
$group: {
|
|
119
|
+
_id: '$apiName',
|
|
120
|
+
requestCount: { $sum: 1 },
|
|
121
|
+
successCount: { $sum: { $cond: ['$success', 1, 0] } },
|
|
122
|
+
errorCount: { $sum: { $cond: ['$success', 0, 1] } },
|
|
123
|
+
rateLimitCount: { $sum: { $cond: ['$wasRateLimited', 1, 0] } },
|
|
124
|
+
tokensUsed: { $sum: { $ifNull: ['$tokensUsed', 0] } },
|
|
125
|
+
totalCost: { $sum: { $ifNull: ['$costUSD', 0] } },
|
|
126
|
+
avgResponseTime: { $avg: '$responseTime' }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
];
|
|
130
|
+
return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
|
|
131
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Get daily stats
|
|
138
|
+
*/
|
|
139
|
+
MetricsLogger.prototype.getDailyStats = function (date) {
|
|
140
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
141
|
+
var dayStart, dayEnd, pipeline;
|
|
142
|
+
return __generator(this, function (_a) {
|
|
143
|
+
switch (_a.label) {
|
|
144
|
+
case 0:
|
|
145
|
+
dayStart = new Date(date);
|
|
146
|
+
dayStart.setHours(0, 0, 0, 0);
|
|
147
|
+
dayEnd = new Date(dayStart);
|
|
148
|
+
dayEnd.setDate(dayStart.getDate() + 1);
|
|
149
|
+
pipeline = [
|
|
150
|
+
{
|
|
151
|
+
$match: {
|
|
152
|
+
timestamp: { $gte: dayStart, $lt: dayEnd }
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
$group: {
|
|
157
|
+
_id: '$apiName',
|
|
158
|
+
requestCount: { $sum: 1 },
|
|
159
|
+
successCount: { $sum: { $cond: ['$success', 1, 0] } },
|
|
160
|
+
errorCount: { $sum: { $cond: ['$success', 0, 1] } },
|
|
161
|
+
rateLimitCount: { $sum: { $cond: ['$wasRateLimited', 1, 0] } },
|
|
162
|
+
tokensUsed: { $sum: { $ifNull: ['$tokensUsed', 0] } },
|
|
163
|
+
totalCost: { $sum: { $ifNull: ['$costUSD', 0] } },
|
|
164
|
+
avgResponseTime: { $avg: '$responseTime' }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
];
|
|
168
|
+
return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
|
|
169
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Get stats for date range
|
|
176
|
+
*/
|
|
177
|
+
MetricsLogger.prototype.getStatsForRange = function (startDate, endDate) {
|
|
178
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
179
|
+
var pipeline;
|
|
180
|
+
return __generator(this, function (_a) {
|
|
181
|
+
switch (_a.label) {
|
|
182
|
+
case 0:
|
|
183
|
+
pipeline = [
|
|
184
|
+
{
|
|
185
|
+
$match: {
|
|
186
|
+
timestamp: { $gte: startDate, $lt: endDate }
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
$group: {
|
|
191
|
+
_id: {
|
|
192
|
+
apiName: '$apiName',
|
|
193
|
+
date: { $dateToString: { format: '%Y-%m-%d', date: '$timestamp' } }
|
|
194
|
+
},
|
|
195
|
+
requestCount: { $sum: 1 },
|
|
196
|
+
successCount: { $sum: { $cond: ['$success', 1, 0] } },
|
|
197
|
+
tokensUsed: { $sum: { $ifNull: ['$tokensUsed', 0] } },
|
|
198
|
+
totalCost: { $sum: { $ifNull: ['$costUSD', 0] } }
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
$sort: { '_id.date': -1, '_id.apiName': 1 }
|
|
203
|
+
}
|
|
204
|
+
];
|
|
205
|
+
return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
|
|
206
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Get top requested tokens
|
|
213
|
+
*/
|
|
214
|
+
MetricsLogger.prototype.getTopTokens = function () {
|
|
215
|
+
return __awaiter(this, arguments, void 0, function (limit, hours) {
|
|
216
|
+
var since, pipeline;
|
|
217
|
+
if (limit === void 0) { limit = 10; }
|
|
218
|
+
if (hours === void 0) { hours = 24; }
|
|
219
|
+
return __generator(this, function (_a) {
|
|
220
|
+
switch (_a.label) {
|
|
221
|
+
case 0:
|
|
222
|
+
since = new Date(Date.now() - hours * 60 * 60 * 1000);
|
|
223
|
+
pipeline = [
|
|
224
|
+
{
|
|
225
|
+
$match: {
|
|
226
|
+
caip: { $exists: true },
|
|
227
|
+
timestamp: { $gte: since }
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
$group: {
|
|
232
|
+
_id: '$caip',
|
|
233
|
+
symbol: { $first: '$symbol' },
|
|
234
|
+
requestCount: { $sum: 1 },
|
|
235
|
+
tokensUsed: { $sum: { $ifNull: ['$tokensUsed', 0] } }
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
$sort: { requestCount: -1 }
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
$limit: limit
|
|
243
|
+
}
|
|
244
|
+
];
|
|
245
|
+
return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
|
|
246
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Get error rate for API
|
|
253
|
+
*/
|
|
254
|
+
MetricsLogger.prototype.getErrorRate = function (apiName_1) {
|
|
255
|
+
return __awaiter(this, arguments, void 0, function (apiName, hours) {
|
|
256
|
+
var since, pipeline, result, _a, total, errors;
|
|
257
|
+
if (hours === void 0) { hours = 24; }
|
|
258
|
+
return __generator(this, function (_b) {
|
|
259
|
+
switch (_b.label) {
|
|
260
|
+
case 0:
|
|
261
|
+
since = new Date(Date.now() - hours * 60 * 60 * 1000);
|
|
262
|
+
pipeline = [
|
|
263
|
+
{
|
|
264
|
+
$match: {
|
|
265
|
+
apiName: apiName,
|
|
266
|
+
timestamp: { $gte: since }
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
$group: {
|
|
271
|
+
_id: null,
|
|
272
|
+
total: { $sum: 1 },
|
|
273
|
+
errors: { $sum: { $cond: ['$success', 0, 1] } }
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
];
|
|
277
|
+
return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
|
|
278
|
+
case 1:
|
|
279
|
+
result = _b.sent();
|
|
280
|
+
if (result.length === 0)
|
|
281
|
+
return [2 /*return*/, 0];
|
|
282
|
+
_a = result[0], total = _a.total, errors = _a.errors;
|
|
283
|
+
return [2 /*return*/, errors / total];
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* Get rate limit events
|
|
290
|
+
*/
|
|
291
|
+
MetricsLogger.prototype.getRateLimitEvents = function (apiName_1) {
|
|
292
|
+
return __awaiter(this, arguments, void 0, function (apiName, hours) {
|
|
293
|
+
var since, match;
|
|
294
|
+
if (hours === void 0) { hours = 24; }
|
|
295
|
+
return __generator(this, function (_a) {
|
|
296
|
+
switch (_a.label) {
|
|
297
|
+
case 0:
|
|
298
|
+
since = new Date(Date.now() - hours * 60 * 60 * 1000);
|
|
299
|
+
match = {
|
|
300
|
+
wasRateLimited: true,
|
|
301
|
+
timestamp: { $gte: since }
|
|
302
|
+
};
|
|
303
|
+
if (apiName) {
|
|
304
|
+
match.apiName = apiName;
|
|
305
|
+
}
|
|
306
|
+
return [4 /*yield*/, this.logsCollection
|
|
307
|
+
.find(match)
|
|
308
|
+
.sort({ timestamp: -1 })
|
|
309
|
+
.limit(100)
|
|
310
|
+
.toArray()];
|
|
311
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* Get cost summary for month
|
|
318
|
+
*/
|
|
319
|
+
MetricsLogger.prototype.getMonthlyCostSummary = function (year, month) {
|
|
320
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
321
|
+
var startDate, endDate, pipeline, results, totalCost, totalRequests, totalTokens, byAPI, _i, results_1, result, apiName;
|
|
322
|
+
return __generator(this, function (_a) {
|
|
323
|
+
switch (_a.label) {
|
|
324
|
+
case 0:
|
|
325
|
+
startDate = new Date(year, month - 1, 1);
|
|
326
|
+
endDate = new Date(year, month, 1);
|
|
327
|
+
pipeline = [
|
|
328
|
+
{
|
|
329
|
+
$match: {
|
|
330
|
+
timestamp: { $gte: startDate, $lt: endDate }
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
$group: {
|
|
335
|
+
_id: '$apiName',
|
|
336
|
+
totalCost: { $sum: { $ifNull: ['$costUSD', 0] } },
|
|
337
|
+
requestCount: { $sum: 1 },
|
|
338
|
+
tokensUsed: { $sum: { $ifNull: ['$tokensUsed', 0] } }
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
];
|
|
342
|
+
return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
|
|
343
|
+
case 1:
|
|
344
|
+
results = _a.sent();
|
|
345
|
+
totalCost = 0;
|
|
346
|
+
totalRequests = 0;
|
|
347
|
+
totalTokens = 0;
|
|
348
|
+
byAPI = {};
|
|
349
|
+
for (_i = 0, results_1 = results; _i < results_1.length; _i++) {
|
|
350
|
+
result = results_1[_i];
|
|
351
|
+
apiName = result._id;
|
|
352
|
+
byAPI[apiName] = {
|
|
353
|
+
cost: result.totalCost,
|
|
354
|
+
requests: result.requestCount,
|
|
355
|
+
tokens: result.tokensUsed
|
|
356
|
+
};
|
|
357
|
+
totalCost += result.totalCost;
|
|
358
|
+
totalRequests += result.requestCount;
|
|
359
|
+
totalTokens += result.tokensUsed;
|
|
360
|
+
}
|
|
361
|
+
return [2 /*return*/, {
|
|
362
|
+
month: "".concat(year, "-").concat(String(month).padStart(2, '0')),
|
|
363
|
+
byAPI: byAPI,
|
|
364
|
+
totals: {
|
|
365
|
+
cost: totalCost,
|
|
366
|
+
requests: totalRequests,
|
|
367
|
+
tokens: totalTokens
|
|
368
|
+
}
|
|
369
|
+
}];
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
};
|
|
374
|
+
/**
|
|
375
|
+
* Clean up old logs (run periodically)
|
|
376
|
+
*/
|
|
377
|
+
MetricsLogger.prototype.cleanupOldLogs = function () {
|
|
378
|
+
return __awaiter(this, arguments, void 0, function (daysToKeep) {
|
|
379
|
+
var tag, cutoffDate, result;
|
|
380
|
+
if (daysToKeep === void 0) { daysToKeep = 90; }
|
|
381
|
+
return __generator(this, function (_a) {
|
|
382
|
+
switch (_a.label) {
|
|
383
|
+
case 0:
|
|
384
|
+
tag = TAG + ' | cleanupOldLogs | ';
|
|
385
|
+
cutoffDate = new Date(Date.now() - daysToKeep * 24 * 60 * 60 * 1000);
|
|
386
|
+
return [4 /*yield*/, this.logsCollection.deleteMany({
|
|
387
|
+
timestamp: { $lt: cutoffDate }
|
|
388
|
+
})];
|
|
389
|
+
case 1:
|
|
390
|
+
result = _a.sent();
|
|
391
|
+
log.info(tag, "Deleted ".concat(result.deletedCount, " logs older than ").concat(daysToKeep, " days"));
|
|
392
|
+
return [2 /*return*/, result.deletedCount];
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
};
|
|
397
|
+
/**
|
|
398
|
+
* Get cache hit rate
|
|
399
|
+
*/
|
|
400
|
+
MetricsLogger.prototype.getCacheHitRate = function () {
|
|
401
|
+
return __awaiter(this, arguments, void 0, function (hours) {
|
|
402
|
+
var since, pipeline, result, _a, total, cacheHits;
|
|
403
|
+
if (hours === void 0) { hours = 24; }
|
|
404
|
+
return __generator(this, function (_b) {
|
|
405
|
+
switch (_b.label) {
|
|
406
|
+
case 0:
|
|
407
|
+
since = new Date(Date.now() - hours * 60 * 60 * 1000);
|
|
408
|
+
pipeline = [
|
|
409
|
+
{
|
|
410
|
+
$match: {
|
|
411
|
+
timestamp: { $gte: since }
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
$group: {
|
|
416
|
+
_id: null,
|
|
417
|
+
total: { $sum: 1 },
|
|
418
|
+
cacheHits: { $sum: { $cond: ['$cacheHit', 1, 0] } }
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
];
|
|
422
|
+
return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
|
|
423
|
+
case 1:
|
|
424
|
+
result = _b.sent();
|
|
425
|
+
if (result.length === 0)
|
|
426
|
+
return [2 /*return*/, 0];
|
|
427
|
+
_a = result[0], total = _a.total, cacheHits = _a.cacheHits;
|
|
428
|
+
return [2 /*return*/, cacheHits / total];
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
};
|
|
433
|
+
return MetricsLogger;
|
|
434
|
+
}());
|
|
435
|
+
exports.MetricsLogger = MetricsLogger;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { TokenBucketManager } from './token-bucket-manager';
|
|
2
|
+
import { MetricsLogger } from './metrics-logger';
|
|
3
|
+
/**
|
|
4
|
+
* Schedule daily token bucket refill at midnight UTC
|
|
5
|
+
*/
|
|
6
|
+
export declare function scheduleDailyRefill(manager: TokenBucketManager, metricsLogger?: MetricsLogger): void;
|
|
7
|
+
/**
|
|
8
|
+
* Manual refill trigger (for admin use)
|
|
9
|
+
*/
|
|
10
|
+
export declare function manualRefill(manager: TokenBucketManager, metricsLogger?: MetricsLogger): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Cancel scheduled refill job
|
|
13
|
+
*/
|
|
14
|
+
export declare function cancelRefillSchedule(): void;
|
|
15
|
+
/**
|
|
16
|
+
* Get next refill time
|
|
17
|
+
*/
|
|
18
|
+
export declare function getNextRefillTime(): Date | null;
|
|
19
|
+
/**
|
|
20
|
+
* Check if refill is scheduled
|
|
21
|
+
*/
|
|
22
|
+
export declare function isRefillScheduled(): boolean;
|