cds-caching 1.2.0 → 1.3.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.
Files changed (79) hide show
  1. package/README.md +195 -1114
  2. package/app/dashboard/Component-dbg.js +59 -0
  3. package/app/dashboard/Component-dbg.js.map +1 -0
  4. package/app/dashboard/Component.js +2 -0
  5. package/app/dashboard/Component.js.map +1 -0
  6. package/app/dashboard/Component.ts +59 -0
  7. package/app/dashboard/controller/App-dbg.controller.js +132 -0
  8. package/app/dashboard/controller/App-dbg.controller.js.map +1 -0
  9. package/app/dashboard/controller/App.controller.js +2 -0
  10. package/app/dashboard/controller/App.controller.js.map +1 -0
  11. package/app/dashboard/controller/App.controller.ts +147 -0
  12. package/app/dashboard/controller/BaseController-dbg.js +75 -0
  13. package/app/dashboard/controller/BaseController-dbg.js.map +1 -0
  14. package/app/dashboard/controller/BaseController.js +2 -0
  15. package/app/dashboard/controller/BaseController.js.map +1 -0
  16. package/app/dashboard/controller/BaseController.ts +88 -0
  17. package/app/dashboard/controller/Cache-dbg.controller.js +421 -0
  18. package/app/dashboard/controller/Cache-dbg.controller.js.map +1 -0
  19. package/app/dashboard/controller/Cache.controller.js +2 -0
  20. package/app/dashboard/controller/Cache.controller.js.map +1 -0
  21. package/app/dashboard/controller/Cache.controller.ts +490 -0
  22. package/app/dashboard/controller/Main-dbg.controller.js +32 -0
  23. package/app/dashboard/controller/Main-dbg.controller.js.map +1 -0
  24. package/app/dashboard/controller/Main.controller.js +2 -0
  25. package/app/dashboard/controller/Main.controller.js.map +1 -0
  26. package/app/dashboard/controller/Main.controller.ts +31 -0
  27. package/app/dashboard/controller/SingleMetric-dbg.controller.js +306 -0
  28. package/app/dashboard/controller/SingleMetric-dbg.controller.js.map +1 -0
  29. package/app/dashboard/controller/SingleMetric.controller.js +2 -0
  30. package/app/dashboard/controller/SingleMetric.controller.js.map +1 -0
  31. package/app/dashboard/controller/SingleMetric.controller.ts +335 -0
  32. package/app/dashboard/i18n/i18n.properties +61 -0
  33. package/app/dashboard/i18n/i18n_de.properties +3 -0
  34. package/app/dashboard/i18n/i18n_en.properties +3 -0
  35. package/app/dashboard/index-cdn.html +28 -0
  36. package/app/dashboard/index.html +19 -0
  37. package/app/dashboard/manifest.json +145 -0
  38. package/app/dashboard/model/formatter-dbg.js +253 -0
  39. package/app/dashboard/model/formatter-dbg.js.map +1 -0
  40. package/app/dashboard/model/formatter.js +2 -0
  41. package/app/dashboard/model/formatter.js.map +1 -0
  42. package/app/dashboard/model/formatter.ts +275 -0
  43. package/app/dashboard/model/models-dbg.js +13 -0
  44. package/app/dashboard/model/models-dbg.js.map +1 -0
  45. package/app/dashboard/model/models.js +2 -0
  46. package/app/dashboard/model/models.js.map +1 -0
  47. package/app/dashboard/model/models.ts +13 -0
  48. package/app/dashboard/resources/sap-ui-custom-dbg.js +4695 -0
  49. package/app/dashboard/resources/sap-ui-custom-dbg.js.map +1 -0
  50. package/app/dashboard/resources/sap-ui-custom.js +5794 -0
  51. package/app/dashboard/resources/sap-ui-custom.js.map +1 -0
  52. package/app/dashboard/service/CacheStatisticsService-dbg.js +267 -0
  53. package/app/dashboard/service/CacheStatisticsService-dbg.js.map +1 -0
  54. package/app/dashboard/service/CacheStatisticsService.js +2 -0
  55. package/app/dashboard/service/CacheStatisticsService.js.map +1 -0
  56. package/app/dashboard/service/CacheStatisticsService.ts +397 -0
  57. package/app/dashboard/view/App.view.xml +18 -0
  58. package/app/dashboard/view/Cache.view.xml +777 -0
  59. package/app/dashboard/view/Main.view.xml +57 -0
  60. package/app/dashboard/view/SingleMetric.view.xml +337 -0
  61. package/cds-plugin.js +52 -0
  62. package/db/cache-store.cds +12 -0
  63. package/db/statistics.cds +128 -0
  64. package/index.cds +2 -128
  65. package/lib/CachingService.js +13 -0
  66. package/lib/add.js +17 -0
  67. package/lib/operations/AsyncOperations.js +162 -111
  68. package/lib/operations/BasicOperations.js +35 -0
  69. package/lib/operations/CapOperations.js +92 -40
  70. package/lib/support/CacheStatisticsHandler.js +35 -10
  71. package/lib/support/CacheStoreManager.js +29 -0
  72. package/lib/support/KeyvCDS.js +149 -0
  73. package/lib/support/MultitenancyDetector.js +25 -0
  74. package/lib/support/RuntimeConfigurationManager.js +69 -37
  75. package/lib/support/StatisticsPersistenceManager.js +18 -2
  76. package/lib/support/Telemetry.js +137 -0
  77. package/lib/util.js +29 -11
  78. package/package.json +44 -12
  79. package/srv/caching-api-service.js +47 -2
@@ -0,0 +1,397 @@
1
+ import MessageBox from "sap/m/MessageBox";
2
+ import ODataModel from "sap/ui/model/odata/v4/ODataModel";
3
+
4
+ export interface CacheStatistics {
5
+ hits: number;
6
+ misses: number;
7
+ sets: number;
8
+ deletes: number;
9
+ errors: number;
10
+
11
+ // Overall latency metrics
12
+ avgLatency: number;
13
+ p95Latency: number;
14
+ p99Latency: number;
15
+ minLatency: number;
16
+ maxLatency: number;
17
+
18
+ // Hit-specific latency metrics
19
+ avgHitLatency: number;
20
+ p95HitLatency: number;
21
+ p99HitLatency: number;
22
+ minHitLatency: number;
23
+ maxHitLatency: number;
24
+
25
+ // Miss-specific latency metrics
26
+ avgMissLatency: number;
27
+ p95MissLatency: number;
28
+ p99MissLatency: number;
29
+ minMissLatency: number;
30
+ maxMissLatency: number;
31
+
32
+ // Set/Delete latency metrics
33
+ avgSetLatency: number;
34
+ avgDeleteLatency: number;
35
+
36
+ // Performance metrics
37
+ memoryUsage: number;
38
+ itemCount: number;
39
+ hitRatio: number;
40
+ throughput: number;
41
+ errorRate: number;
42
+ cacheEfficiency: number;
43
+ uptimeMs: number;
44
+ uniqueKeys: number;
45
+ topKeys: KeyAccess[];
46
+ coldKeys: KeyAccess[];
47
+ lastPersisted: string;
48
+ }
49
+
50
+ export interface KeyAccess {
51
+ keyName: string;
52
+ hits: number;
53
+ misses: number;
54
+ sets: number;
55
+ deletes: number;
56
+ total: number;
57
+ lastAccess: string;
58
+ dataType: string;
59
+ serviceName: string;
60
+ entityName: string;
61
+ operation: string;
62
+ metadata: string;
63
+ }
64
+
65
+ export interface HistoricalStatistics {
66
+ ID: string;
67
+ cache: string;
68
+ timestamp: string;
69
+ period: string;
70
+
71
+ // Read-through metrics (high value with latencies)
72
+ hits: number;
73
+ misses: number;
74
+ sets: number;
75
+ deletes: number;
76
+ errors: number;
77
+ totalRequests: number;
78
+
79
+ // Read-through latency metrics
80
+ avgHitLatency: number;
81
+ p95HitLatency: number;
82
+ p99HitLatency: number;
83
+ minHitLatency: number;
84
+ maxHitLatency: number;
85
+ avgMissLatency: number;
86
+ p95MissLatency: number;
87
+ p99MissLatency: number;
88
+ minMissLatency: number;
89
+ maxMissLatency: number;
90
+ avgSetLatency: number;
91
+ avgDeleteLatency: number;
92
+
93
+ // Read-through performance metrics
94
+ hitRatio: number;
95
+ throughput: number;
96
+ errorRate: number;
97
+ cacheEfficiency: number;
98
+
99
+ // Native function metrics (basic counts only)
100
+ nativeSets: number;
101
+ nativeGets: number;
102
+ nativeDeletes: number;
103
+ nativeClears: number;
104
+ nativeDeleteByTags: number;
105
+ nativeErrors: number;
106
+ totalNativeOperations: number;
107
+
108
+ // Native function performance metrics
109
+ nativeThroughput: number;
110
+ nativeErrorRate: number;
111
+
112
+ // Common metrics
113
+ memoryUsage: number;
114
+ itemCount: number;
115
+ uptimeMs: number;
116
+ }
117
+
118
+ /**
119
+ * Service class for interacting with the Cache Statistics API using OData V4
120
+ */
121
+ export default class CacheStatisticsService {
122
+ private model: ODataModel;
123
+
124
+ constructor(model?: ODataModel) {
125
+ this.model = model || new ODataModel({
126
+ serviceUrl: "/odata/v4/caching/",
127
+ autoExpandSelect: true,
128
+ groupId: "$auto"
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Get the OData model instance
134
+ */
135
+ getModel(): ODataModel {
136
+ return this.model;
137
+ }
138
+
139
+ /**
140
+ * Get current cache metrics using entity set
141
+ */
142
+ async getCurrentMetrics(cacheName: string): Promise<CacheStatistics | null> {
143
+ try {
144
+ const binding = this.model.bindList("/Metrics", undefined, undefined, undefined, {
145
+ $filter: `period eq 'hourly' and cache eq '${cacheName}'`,
146
+ $orderby: "timestamp desc"
147
+ });
148
+
149
+ const data = await binding.requestContexts();
150
+ const results = data.map((item: any) => item.getObject());
151
+
152
+ if (results && results.length > 0) {
153
+ return this.transformStatistics(results[0]);
154
+ } else {
155
+ return null;
156
+ }
157
+ } catch (error) {
158
+ console.error("Error fetching current metrics:", error);
159
+ MessageBox.error("Failed to load current metrics");
160
+ return null;
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Get historical metrics using entity set
166
+ */
167
+ async getHistoricalMetrics(cacheName: string, period: string = "hourly", from?: string, to?: string): Promise<HistoricalStatistics[]> {
168
+ try {
169
+ let filter = `period eq '${period}' and cache eq '${cacheName}'`;
170
+ if (from) {
171
+ filter += ` and timestamp ge ${from}`;
172
+ }
173
+ if (to) {
174
+ filter += ` and timestamp le ${to}`;
175
+ }
176
+
177
+ const binding = this.model.bindList("/Metrics", undefined, undefined, undefined, {
178
+ $filter: filter,
179
+ $orderby: "timestamp desc"
180
+ });
181
+
182
+ const data = await binding.requestContexts();
183
+ return data.map((item: any) => item.getObject());
184
+ } catch (error) {
185
+ console.error("Error fetching historical metrics:", error);
186
+ MessageBox.error("Failed to load historical metrics");
187
+ return [];
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Get top accessed keys using entity set
193
+ */
194
+ async getTopKeys(cacheName: string, limit: number = 10): Promise<KeyAccess[]> {
195
+ try {
196
+ const binding = this.model.bindList("/KeyMetrics", undefined, undefined, undefined, {
197
+ $filter: `period eq 'current' and cache eq '${cacheName}'`,
198
+ $orderby: "totalRequests desc"
199
+ });
200
+
201
+ const data = await binding.requestContexts();
202
+ return data.map((item: any) => item.getObject());
203
+ } catch (error) {
204
+ console.error("Error fetching top keys:", error);
205
+ MessageBox.error("Failed to load top keys");
206
+ return [];
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Get cold keys using entity set
212
+ */
213
+ async getColdKeys(cacheName: string, limit: number = 10): Promise<KeyAccess[]> {
214
+ try {
215
+ const binding = this.model.bindList("/KeyMetrics", undefined, undefined, undefined, {
216
+ $filter: `period eq 'current' and cache eq '${cacheName}'`,
217
+ $orderby: "lastAccess asc"
218
+ });
219
+
220
+ const data = await binding.requestContexts();
221
+ return data.map((item: any) => item.getObject());
222
+ } catch (error) {
223
+ console.error("Error fetching cold keys:", error);
224
+ MessageBox.error("Failed to load cold keys");
225
+ return [];
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Persist statistics using action
231
+ */
232
+ async persistStatistics(cacheName: string): Promise<boolean> {
233
+ try {
234
+ // For actions, we'll use a simple approach with the model's submitBatch
235
+ const context = this.model.bindContext("/persistStatistics(...)");
236
+
237
+ return new Promise((resolve, reject) => {
238
+ // Execute the action
239
+ context.execute().then(() => {
240
+ resolve(true);
241
+ }).catch((error: any) => {
242
+ console.error("Error persisting statistics:", error);
243
+ MessageBox.error("Failed to persist statistics");
244
+ reject(error);
245
+ });
246
+ });
247
+ } catch (error) {
248
+ console.error("Error persisting statistics:", error);
249
+ MessageBox.error("Failed to persist statistics");
250
+ return false;
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Transform statistics data from API format to internal format
256
+ */
257
+ private transformStatistics(data: any): CacheStatistics {
258
+ return {
259
+ hits: data.hits || 0,
260
+ misses: data.misses || 0,
261
+ sets: data.sets || 0,
262
+ deletes: data.deletes || 0,
263
+ errors: data.errors || 0,
264
+
265
+ // Overall latency metrics
266
+ avgLatency: data.avgLatency || 0,
267
+ p95Latency: data.p95Latency || 0,
268
+ p99Latency: data.p99Latency || 0,
269
+ minLatency: data.minLatency || 0,
270
+ maxLatency: data.maxLatency || 0,
271
+
272
+ // Hit-specific latency metrics
273
+ avgHitLatency: data.avgHitLatency || 0,
274
+ p95HitLatency: data.p95HitLatency || 0,
275
+ p99HitLatency: data.p99HitLatency || 0,
276
+ minHitLatency: data.minHitLatency || 0,
277
+ maxHitLatency: data.maxHitLatency || 0,
278
+
279
+ // Miss-specific latency metrics
280
+ avgMissLatency: data.avgMissLatency || 0,
281
+ p95MissLatency: data.p95MissLatency || 0,
282
+ p99MissLatency: data.p99MissLatency || 0,
283
+ minMissLatency: data.minMissLatency || 0,
284
+ maxMissLatency: data.maxMissLatency || 0,
285
+
286
+ // Set/Delete latency metrics
287
+ avgSetLatency: data.avgSetLatency || 0,
288
+ avgDeleteLatency: data.avgDeleteLatency || 0,
289
+
290
+ // Performance metrics
291
+ memoryUsage: data.memoryUsage || 0,
292
+ itemCount: data.itemCount || 0,
293
+ hitRatio: data.hitRatio || 0,
294
+ throughput: data.throughput || 0,
295
+ errorRate: data.errorRate || 0,
296
+ cacheEfficiency: data.cacheEfficiency || 0,
297
+ uptimeMs: data.uptimeMs || 0,
298
+ uniqueKeys: 0,
299
+ topKeys: [],
300
+ coldKeys: [],
301
+ lastPersisted: data.lastPersisted || new Date().toISOString()
302
+ };
303
+ }
304
+
305
+ /**
306
+ * Get all available metrics for a cache
307
+ */
308
+ async getAvailableMetrics(cacheName: string): Promise<any[]> {
309
+ try {
310
+ const binding = this.model.bindList("/Metrics", undefined, undefined, undefined, {
311
+ $filter: `cache eq '${cacheName}'`,
312
+ $orderby: "timestamp desc"
313
+ });
314
+
315
+ const data = await binding.requestContexts();
316
+ return data.map((item: any) => item.getObject());
317
+ } catch (error) {
318
+ console.error("Error fetching available metrics:", error);
319
+ MessageBox.error("Failed to load available metrics");
320
+ return [];
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Get a specific metric by ID
326
+ */
327
+ async getMetricById(cacheName: string, metricId: string): Promise<CacheStatistics | null> {
328
+ try {
329
+ const binding = this.model.bindList("/Metrics", undefined, undefined, undefined, {
330
+ $filter: `ID eq '${metricId}' and cache eq '${cacheName}'`
331
+ });
332
+
333
+ const data = await binding.requestContexts();
334
+ const results = data.map((item: any) => item.getObject());
335
+
336
+ if (results && results.length > 0) {
337
+ return this.transformStatistics(results[0]);
338
+ } else {
339
+ return null;
340
+ }
341
+ } catch (error) {
342
+ console.error("Error fetching metric by ID:", error);
343
+ MessageBox.error("Failed to load metric");
344
+ return null;
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Set metrics enabled/disabled
350
+ */
351
+ async setMetricsEnabled(cacheName: string, enabled: boolean): Promise<boolean> {
352
+ try {
353
+ const context = this.model.bindContext(`/setMetricsEnabled(...)`);
354
+ context.setParameter("cache", cacheName);
355
+ context.setParameter("enabled", enabled);
356
+
357
+ return new Promise((resolve, reject) => {
358
+ context.execute().then(() => {
359
+ resolve(true);
360
+ }).catch((error: any) => {
361
+ console.error("Error setting metrics enabled:", error);
362
+ MessageBox.error("Failed to update metrics configuration");
363
+ reject(error);
364
+ });
365
+ });
366
+ } catch (error) {
367
+ console.error("Error setting metrics enabled:", error);
368
+ MessageBox.error("Failed to update metrics configuration");
369
+ return false;
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Set key metrics enabled/disabled
375
+ */
376
+ async setKeyMetricsEnabled(cacheName: string, enabled: boolean): Promise<boolean> {
377
+ try {
378
+ const context = this.model.bindContext(`/setKeyMetricsEnabled(...)`);
379
+ context.setParameter("cache", cacheName);
380
+ context.setParameter("enabled", enabled);
381
+
382
+ return new Promise((resolve, reject) => {
383
+ context.execute().then(() => {
384
+ resolve(true);
385
+ }).catch((error: any) => {
386
+ console.error("Error setting key metrics enabled:", error);
387
+ MessageBox.error("Failed to update key metrics configuration");
388
+ reject(error);
389
+ });
390
+ });
391
+ } catch (error) {
392
+ console.error("Error setting key metrics enabled:", error);
393
+ MessageBox.error("Failed to update key metrics configuration");
394
+ return false;
395
+ }
396
+ }
397
+ }
@@ -0,0 +1,18 @@
1
+ <mvc:View controllerName="cds.plugin.caching.dashboard.controller.App" displayBlock="true"
2
+ xmlns="sap.m"
3
+ xmlns:core="sap.ui.core"
4
+ xmlns:l="sap.ui.layout"
5
+ xmlns:f="sap.f"
6
+ xmlns:form="sap.ui.layout.form"
7
+ xmlns:fb="sap.ui.comp.filterbar"
8
+ xmlns:card="sap.f.cards"
9
+ xmlns:viz="sap.viz.ui5.controls"
10
+ xmlns:viz.feeds="sap.viz.ui5.controls.common.feeds"
11
+ xmlns:mvc="sap.ui.core.mvc">
12
+
13
+ <App id="app">
14
+ <f:FlexibleColumnLayout id="fcl" layout="{app>/layout}">
15
+ </f:FlexibleColumnLayout>
16
+ </App>
17
+
18
+ </mvc:View>