adonisjs-server-stats 1.10.0 → 1.10.3
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/README.md +23 -14
- package/dist/core/config-utils.d.ts +8 -0
- package/dist/core/constants.d.ts +4 -0
- package/dist/core/dashboard-data-controller.d.ts +16 -0
- package/dist/core/dashboard-data-helpers.d.ts +12 -0
- package/dist/core/debug-data-controller.d.ts +4 -0
- package/dist/core/define-config-helpers.d.ts +25 -0
- package/dist/core/feature-detect-helpers.d.ts +36 -0
- package/dist/core/formatters-helpers.d.ts +23 -0
- package/dist/core/index.js +594 -509
- package/dist/core/log-utils-helpers.d.ts +13 -0
- package/dist/core/metrics.d.ts +3 -28
- package/dist/core/pagination.d.ts +0 -9
- package/dist/core/server-stats-controller.d.ts +6 -0
- package/dist/core/transmit-helpers.d.ts +7 -0
- package/dist/core/types-dashboard.d.ts +178 -0
- package/dist/core/types-diagnostics.d.ts +85 -0
- package/dist/core/types.d.ts +10 -442
- package/dist/react/{CacheSection-UCMptWyn.js → CacheSection-baMZotSn.js} +2 -2
- package/dist/react/CacheTab-2cw_rMzj.js +117 -0
- package/dist/react/{ConfigSection-DfFd-WRq.js → ConfigSection-DGgqjAal.js} +1 -1
- package/dist/react/{ConfigTab-Bdg8YMer.js → ConfigTab-H3OnYqmK.js} +1 -1
- package/dist/react/CustomPaneTab-B6r7ha0u.js +98 -0
- package/dist/react/{EmailsSection-CM7stSyh.js → EmailsSection-C-UZISG-.js} +2 -2
- package/dist/react/EmailsTab-DbK4Eobn.js +139 -0
- package/dist/react/{EventsSection-ByQ-9blq.js → EventsSection-C7RQW_LY.js} +2 -2
- package/dist/react/EventsTab-CfVr7AiM.js +57 -0
- package/dist/react/{FilterBar-DQRXpWrb.js → FilterBar-CQ7bD669.js} +15 -15
- package/dist/react/{JobsSection-DF3qEv9O.js → JobsSection-CQHNK_Ls.js} +2 -2
- package/dist/react/{JobsTab-BbrBWIOb.js → JobsTab-znzf6jzk.js} +54 -42
- package/dist/react/{LogsSection-DcFTZY7b.js → LogsSection-Dmm3rE2B.js} +9 -3
- package/dist/react/LogsTab-D8unMV5P.js +108 -0
- package/dist/react/{OverviewSection-C4T1ur51.js → OverviewSection-ABP9ueBo.js} +1 -1
- package/dist/react/{QueriesSection-PswteoF9.js → QueriesSection-CnmSkznA.js} +2 -2
- package/dist/react/{QueriesTab-osLUWd4L.js → QueriesTab-BQzcxEiW.js} +37 -40
- package/dist/react/{RelatedLogs-DFDOyUMr.js → RelatedLogs-3A8RuGKH.js} +15 -3
- package/dist/react/{RequestsSection-Nag30rEA.js → RequestsSection-kW79_M7k.js} +3 -3
- package/dist/react/{RoutesSection-BUSkM6PY.js → RoutesSection-BRhxrtjZ.js} +2 -2
- package/dist/react/RoutesTab-CpYH5lUw.js +68 -0
- package/dist/react/{TimelineTab-Covg5weo.js → TimelineTab-DjLR35Ce.js} +47 -53
- package/dist/react/index-CsImORX6.js +1121 -0
- package/dist/react/index.js +1 -1
- package/dist/react/react/components/{Dashboard/shared → shared}/FilterBar.d.ts +4 -3
- package/dist/react/react/hooks/useDashboardData.d.ts +4 -8
- package/dist/react/style.css +1 -1
- package/dist/src/collectors/app_collector.d.ts +0 -8
- package/dist/src/collectors/app_collector.js +45 -52
- package/dist/src/collectors/auto_detect.d.ts +0 -23
- package/dist/src/collectors/auto_detect.js +33 -55
- package/dist/src/collectors/db_pool_collector.d.ts +14 -16
- package/dist/src/collectors/db_pool_collector.js +72 -57
- package/dist/src/collectors/log_collector.d.ts +0 -47
- package/dist/src/collectors/log_collector.js +36 -65
- package/dist/src/collectors/queue_collector.d.ts +0 -20
- package/dist/src/collectors/queue_collector.js +60 -76
- package/dist/src/collectors/redis_collector.d.ts +10 -10
- package/dist/src/collectors/redis_collector.js +69 -66
- package/dist/src/config/deprecation_migration.d.ts +7 -0
- package/dist/src/config/deprecation_migration.js +201 -0
- package/dist/src/controller/debug_controller.d.ts +1 -1
- package/dist/src/controller/debug_controller.js +87 -81
- package/dist/src/dashboard/cache_handlers.d.ts +14 -0
- package/dist/src/dashboard/cache_handlers.js +52 -0
- package/dist/src/dashboard/chart_aggregator.d.ts +0 -7
- package/dist/src/dashboard/chart_aggregator.js +68 -50
- package/dist/src/dashboard/coalesce_cache.d.ts +25 -0
- package/dist/src/dashboard/coalesce_cache.js +47 -0
- package/dist/src/dashboard/dashboard_controller.d.ts +11 -37
- package/dist/src/dashboard/dashboard_controller.js +51 -544
- package/dist/src/dashboard/dashboard_page_assets.d.ts +17 -0
- package/dist/src/dashboard/dashboard_page_assets.js +51 -0
- package/dist/src/dashboard/dashboard_store.d.ts +19 -218
- package/dist/src/dashboard/dashboard_store.js +115 -1116
- package/dist/src/dashboard/dashboard_types.d.ts +83 -0
- package/dist/src/dashboard/dashboard_types.js +4 -0
- package/dist/src/dashboard/detail_queries.d.ts +19 -0
- package/dist/src/dashboard/detail_queries.js +98 -0
- package/dist/src/dashboard/email_event_builder.d.ts +8 -0
- package/dist/src/dashboard/email_event_builder.js +65 -0
- package/dist/src/dashboard/explain_query.d.ts +8 -0
- package/dist/src/dashboard/explain_query.js +22 -0
- package/dist/src/dashboard/filter_handlers.d.ts +23 -0
- package/dist/src/dashboard/filter_handlers.js +56 -0
- package/dist/src/dashboard/filtered_queries.d.ts +15 -0
- package/dist/src/dashboard/filtered_queries.js +155 -0
- package/dist/src/dashboard/flush_manager.d.ts +25 -0
- package/dist/src/dashboard/flush_manager.js +107 -0
- package/dist/src/dashboard/format_helpers.d.ts +126 -0
- package/dist/src/dashboard/format_helpers.js +140 -0
- package/dist/src/dashboard/inspector_manager.d.ts +36 -0
- package/dist/src/dashboard/inspector_manager.js +102 -0
- package/dist/src/dashboard/integrations/config_inspector.js +11 -13
- package/dist/src/dashboard/integrations/queue_inspector.d.ts +3 -3
- package/dist/src/dashboard/integrations/queue_inspector.js +13 -10
- package/dist/src/dashboard/jobs_handlers.d.ts +14 -0
- package/dist/src/dashboard/jobs_handlers.js +61 -0
- package/dist/src/dashboard/knex_factory.d.ts +18 -0
- package/dist/src/dashboard/knex_factory.js +91 -0
- package/dist/src/dashboard/migrator.js +30 -159
- package/dist/src/dashboard/migrator_tables.d.ts +19 -0
- package/dist/src/dashboard/migrator_tables.js +153 -0
- package/dist/src/dashboard/overview_queries.d.ts +66 -0
- package/dist/src/dashboard/overview_queries.js +155 -0
- package/dist/src/dashboard/overview_query_runners.d.ts +25 -0
- package/dist/src/dashboard/overview_query_runners.js +84 -0
- package/dist/src/dashboard/overview_store_queries.d.ts +40 -0
- package/dist/src/dashboard/overview_store_queries.js +69 -0
- package/dist/src/dashboard/paginate_helper.d.ts +12 -0
- package/dist/src/dashboard/paginate_helper.js +33 -0
- package/dist/src/dashboard/query_explain_handler.d.ts +10 -0
- package/dist/src/dashboard/query_explain_handler.js +80 -0
- package/dist/src/dashboard/read_queries.d.ts +32 -0
- package/dist/src/dashboard/read_queries.js +107 -0
- package/dist/src/dashboard/saved_filter_queries.d.ts +10 -0
- package/dist/src/dashboard/saved_filter_queries.js +24 -0
- package/dist/src/dashboard/storage_stats.d.ts +41 -0
- package/dist/src/dashboard/storage_stats.js +81 -0
- package/dist/src/dashboard/write_queue.d.ts +106 -0
- package/dist/src/dashboard/write_queue.js +225 -0
- package/dist/src/data/data_access.d.ts +2 -39
- package/dist/src/data/data_access.js +17 -193
- package/dist/src/data/data_access_helpers.d.ts +130 -0
- package/dist/src/data/data_access_helpers.js +212 -0
- package/dist/src/debug/debug_store.js +37 -32
- package/dist/src/debug/email_collector.d.ts +1 -10
- package/dist/src/debug/email_collector.js +78 -81
- package/dist/src/debug/event_collector.d.ts +0 -9
- package/dist/src/debug/event_collector.js +79 -62
- package/dist/src/debug/query_collector.js +23 -19
- package/dist/src/debug/route_inspector.d.ts +1 -5
- package/dist/src/debug/route_inspector.js +50 -51
- package/dist/src/debug/trace_collector.d.ts +9 -1
- package/dist/src/debug/trace_collector.js +21 -15
- package/dist/src/debug/types.d.ts +1 -1
- package/dist/src/define_config.d.ts +0 -65
- package/dist/src/define_config.js +93 -333
- package/dist/src/edge/client/dashboard.js +2 -2
- package/dist/src/edge/client/debug-panel-deferred.js +1 -1
- package/dist/src/edge/client/stats-bar.js +1 -1
- package/dist/src/edge/client-vue/dashboard.js +5 -5
- package/dist/src/edge/client-vue/debug-panel-deferred.js +3 -3
- package/dist/src/edge/client-vue/stats-bar.js +3 -3
- package/dist/src/edge/plugin.d.ts +0 -16
- package/dist/src/edge/plugin.js +57 -64
- package/dist/src/engine/request_metrics.d.ts +1 -0
- package/dist/src/engine/request_metrics.js +32 -42
- package/dist/src/middleware/request_tracking_middleware.d.ts +2 -8
- package/dist/src/middleware/request_tracking_middleware.js +65 -93
- package/dist/src/provider/auth_middleware_detector.d.ts +16 -0
- package/dist/src/provider/auth_middleware_detector.js +97 -0
- package/dist/src/provider/boot_helpers.d.ts +20 -0
- package/dist/src/provider/boot_helpers.js +91 -0
- package/dist/src/provider/boot_initializer.d.ts +28 -0
- package/dist/src/provider/boot_initializer.js +35 -0
- package/dist/src/provider/dashboard_init.d.ts +30 -0
- package/dist/src/provider/dashboard_init.js +138 -0
- package/dist/src/provider/dashboard_setup.d.ts +25 -0
- package/dist/src/provider/dashboard_setup.js +78 -0
- package/dist/src/provider/diagnostics.d.ts +134 -0
- package/dist/src/provider/diagnostics.js +127 -0
- package/dist/src/provider/email_bridge.d.ts +43 -0
- package/dist/src/provider/email_bridge.js +80 -0
- package/dist/src/provider/email_helpers.d.ts +13 -0
- package/dist/src/provider/email_helpers.js +68 -0
- package/dist/src/provider/pino_hook.d.ts +17 -0
- package/dist/src/provider/pino_hook.js +35 -0
- package/dist/src/provider/provider_helpers_extra.d.ts +47 -0
- package/dist/src/provider/provider_helpers_extra.js +177 -0
- package/dist/src/provider/server_stats_provider.d.ts +39 -85
- package/dist/src/provider/server_stats_provider.js +132 -951
- package/dist/src/provider/shutdown_helpers.d.ts +43 -0
- package/dist/src/provider/shutdown_helpers.js +70 -0
- package/dist/src/provider/toolbar_setup.d.ts +57 -0
- package/dist/src/provider/toolbar_setup.js +141 -0
- package/dist/src/routes/dashboard_routes.d.ts +14 -0
- package/dist/src/routes/dashboard_routes.js +197 -0
- package/dist/src/routes/debug_routes.d.ts +14 -0
- package/dist/src/routes/debug_routes.js +101 -0
- package/dist/src/routes/register_routes.d.ts +0 -78
- package/dist/src/routes/register_routes.js +22 -352
- package/dist/src/routes/stats_routes.d.ts +5 -0
- package/dist/src/routes/stats_routes.js +14 -0
- package/dist/src/styles/components.css +96 -0
- package/dist/src/styles/dashboard.css +8 -90
- package/dist/src/styles/debug-panel.css +1 -31
- package/dist/src/types.d.ts +305 -14
- package/dist/vue/{CacheSection-oFAJL3mo.js → CacheSection-ITqvpfH5.js} +1 -1
- package/dist/vue/{ConfigSection-BhfJ4KqL.js → ConfigSection-DTn3GslE.js} +1 -1
- package/dist/vue/{EmailsSection-BcNyhyHs.js → EmailsSection-DtLJ4XoS.js} +1 -1
- package/dist/vue/{EventsSection-r60Q5Lmu.js → EventsSection-BOYYz0Ty.js} +1 -1
- package/dist/vue/{JobsSection-BHL-hkQw.js → JobsSection-BazTxcJL.js} +1 -1
- package/dist/vue/{LogsSection-DRMGzJmg.js → LogsSection-D55PjTKX.js} +9 -3
- package/dist/vue/{LogsTab-Bg3o0Mm6.js → LogsTab-47zEK7jL.js} +4 -1
- package/dist/vue/{OverviewSection-CXh6Ja1B.js → OverviewSection-1uBKo-Tu.js} +1 -1
- package/dist/vue/{QueriesSection-IodIsCJ-.js → QueriesSection-rpoZ4ogd.js} +1 -1
- package/dist/vue/{RequestsSection-BPuMdmMc.js → RequestsSection-x7LvT0MC.js} +1 -1
- package/dist/vue/{RoutesSection-NKo3Rbq3.js → RoutesSection-CCD0zZqQ.js} +1 -1
- package/dist/vue/composables/useDashboardData.d.ts +12 -23
- package/dist/vue/index-C8MxnS7Q.js +1232 -0
- package/dist/vue/index.js +1 -1
- package/dist/vue/style.css +1 -1
- package/package.json +1 -1
- package/dist/react/CacheTab-CA8LB1J5.js +0 -123
- package/dist/react/CustomPaneTab-Bxtv_8Rw.js +0 -104
- package/dist/react/EmailsTab-BDhEiomM.js +0 -153
- package/dist/react/EventsTab-CMfY98Rl.js +0 -63
- package/dist/react/LogsTab-CicucmVk.js +0 -103
- package/dist/react/RoutesTab-DgVzd2PZ.js +0 -74
- package/dist/react/index-Cflz9Ebj.js +0 -1069
- package/dist/vue/index-Dtgysd26.js +0 -1229
|
@@ -1,6 +1,73 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import { ConfigInspector } from '../dashboard/integrations/config_inspector.js';
|
|
4
|
+
/** Maps collector name to feature key for data-driven feature detection. */
|
|
5
|
+
const COLLECTOR_FEATURE_MAP = [
|
|
6
|
+
['process', 'process'],
|
|
7
|
+
['system', 'system'],
|
|
8
|
+
['http', 'http'],
|
|
9
|
+
['db_pool', 'db'],
|
|
10
|
+
['redis', 'redis'],
|
|
11
|
+
['queue', 'queues'],
|
|
12
|
+
['redis', 'cache'],
|
|
13
|
+
['app', 'app'],
|
|
14
|
+
['log', 'log'],
|
|
15
|
+
];
|
|
16
|
+
/** Build collector-based feature flags from config. */
|
|
17
|
+
function buildCollectorFeatures(isAuto, collectorNames) {
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const [collector, feature] of COLLECTOR_FEATURE_MAP) {
|
|
20
|
+
result[feature] = isAuto || collectorNames.has(collector);
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
/** Build toolbar-related feature flags. */
|
|
25
|
+
function buildToolbarFeatures(config) {
|
|
26
|
+
const enabled = !!config?.devToolbar?.enabled;
|
|
27
|
+
return {
|
|
28
|
+
statsBar: true,
|
|
29
|
+
debugPanel: enabled,
|
|
30
|
+
dashboard: !!config?.devToolbar?.dashboard,
|
|
31
|
+
tracing: !!(config && (config.devToolbar?.tracing ?? true)),
|
|
32
|
+
emails: enabled,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Build combined features object from config. */
|
|
36
|
+
function buildFeaturesFromConfig(config) {
|
|
37
|
+
const rawCollectors = config?.collectors;
|
|
38
|
+
const isAuto = rawCollectors === 'auto';
|
|
39
|
+
const collectorNames = new Set(Array.isArray(rawCollectors) ? rawCollectors.map((c) => c.name) : []);
|
|
40
|
+
return {
|
|
41
|
+
...buildToolbarFeatures(config),
|
|
42
|
+
...buildCollectorFeatures(isAuto, collectorNames),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Build endpoint paths from config. */
|
|
46
|
+
function buildEndpoints(config) {
|
|
47
|
+
return {
|
|
48
|
+
stats: typeof config?.endpoint === 'string' ? config.endpoint : '/admin/api/server-stats',
|
|
49
|
+
debug: config?.devToolbar?.debugEndpoint ?? '/admin/api/debug',
|
|
50
|
+
dashboard: config?.devToolbar?.dashboardPath ?? '/__stats',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Build collector health + config info from engine. */
|
|
54
|
+
function buildCollectorInfo(engine) {
|
|
55
|
+
const healthList = engine?.getCollectorHealth() ?? [];
|
|
56
|
+
const configList = engine?.getCollectorConfigs() ?? [];
|
|
57
|
+
const configMap = new Map(configList.map((c) => [c.name, c.config]));
|
|
58
|
+
return healthList.map((h) => ({ ...h, config: configMap.get(h.name) ?? {} }));
|
|
59
|
+
}
|
|
60
|
+
/** Fetch storage stats, returning null on error. */
|
|
61
|
+
async function fetchStorageStats(dashboardStore) {
|
|
62
|
+
if (!dashboardStore)
|
|
63
|
+
return null;
|
|
64
|
+
try {
|
|
65
|
+
return await dashboardStore.getStorageStats();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
4
71
|
export default class DebugController {
|
|
5
72
|
store;
|
|
6
73
|
serverConfig;
|
|
@@ -15,56 +82,16 @@ export default class DebugController {
|
|
|
15
82
|
}
|
|
16
83
|
async config({ response }) {
|
|
17
84
|
const cfg = this.serverConfig;
|
|
18
|
-
const toolbarConfig = cfg?.devToolbar;
|
|
19
|
-
// Derive feature flags from the actual config
|
|
20
|
-
const rawCollectors = cfg?.collectors;
|
|
21
|
-
const isAuto = rawCollectors === 'auto';
|
|
22
|
-
const collectorNames = new Set(Array.isArray(rawCollectors) ? rawCollectors.map((c) => c.name) : []);
|
|
23
|
-
const features = {
|
|
24
|
-
statsBar: true,
|
|
25
|
-
debugPanel: !!toolbarConfig?.enabled,
|
|
26
|
-
dashboard: !!toolbarConfig?.dashboard,
|
|
27
|
-
tracing: toolbarConfig?.tracing ?? true,
|
|
28
|
-
process: isAuto || collectorNames.has('process'),
|
|
29
|
-
system: isAuto || collectorNames.has('system'),
|
|
30
|
-
http: isAuto || collectorNames.has('http'),
|
|
31
|
-
db: isAuto || collectorNames.has('db_pool'),
|
|
32
|
-
redis: isAuto || collectorNames.has('redis'),
|
|
33
|
-
queues: isAuto || collectorNames.has('queue'),
|
|
34
|
-
cache: isAuto || collectorNames.has('redis'),
|
|
35
|
-
app: isAuto || collectorNames.has('app'),
|
|
36
|
-
log: isAuto || collectorNames.has('log'),
|
|
37
|
-
emails: !!toolbarConfig?.enabled,
|
|
38
|
-
};
|
|
39
|
-
// Custom panes from config
|
|
40
|
-
const customPanes = toolbarConfig?.panes ?? [];
|
|
41
|
-
// Endpoint paths
|
|
42
|
-
const debugEndpoint = toolbarConfig?.debugEndpoint ?? '/admin/api/debug';
|
|
43
|
-
const dashboardPath = toolbarConfig?.dashboardPath ?? '/__stats';
|
|
44
|
-
const statsEndpoint = typeof cfg?.endpoint === 'string' ? cfg.endpoint : '/admin/api/server-stats';
|
|
45
|
-
const endpoints = {
|
|
46
|
-
stats: statsEndpoint,
|
|
47
|
-
debug: debugEndpoint,
|
|
48
|
-
dashboard: dashboardPath,
|
|
49
|
-
};
|
|
50
|
-
// Transmit config
|
|
51
|
-
const transmit = {
|
|
52
|
-
channelName: cfg?.channelName ?? 'admin/server-stats',
|
|
53
|
-
};
|
|
54
|
-
// App config + env vars (for the Config tab's APP CONFIG / ENV view)
|
|
55
85
|
const inspector = this.getConfigInspector();
|
|
56
|
-
const appConfig = inspector ? inspector.getConfig().config : {};
|
|
57
|
-
const envVars = inspector ? inspector.getEnvVars().env : {};
|
|
58
86
|
return response.json({
|
|
59
|
-
features,
|
|
60
|
-
customPanes,
|
|
61
|
-
endpoints,
|
|
62
|
-
transmit,
|
|
63
|
-
app:
|
|
64
|
-
env:
|
|
87
|
+
features: buildFeaturesFromConfig(cfg),
|
|
88
|
+
customPanes: cfg?.devToolbar?.panes ?? [],
|
|
89
|
+
endpoints: buildEndpoints(cfg),
|
|
90
|
+
transmit: { channelName: cfg?.channelName ?? 'admin/server-stats' },
|
|
91
|
+
app: inspector ? inspector.getConfig().config : {},
|
|
92
|
+
env: inspector ? inspector.getEnvVars().env : {},
|
|
65
93
|
});
|
|
66
94
|
}
|
|
67
|
-
/** Lazily create a ConfigInspector from the app reference. */
|
|
68
95
|
getConfigInspector() {
|
|
69
96
|
if (this.configInspector)
|
|
70
97
|
return this.configInspector;
|
|
@@ -74,12 +101,7 @@ export default class DebugController {
|
|
|
74
101
|
this.configInspector = new ConfigInspector(app);
|
|
75
102
|
return this.configInspector;
|
|
76
103
|
}
|
|
77
|
-
async
|
|
78
|
-
const engine = this.diagnosticsDeps.getEngine?.();
|
|
79
|
-
const dashboardStore = this.diagnosticsDeps.getDashboardStore?.();
|
|
80
|
-
const providerDiag = this.diagnosticsDeps.getProviderDiagnostics?.() ?? {};
|
|
81
|
-
// Cache package versions on first call — avoids readFile + JSON.parse
|
|
82
|
-
// and createRequire + require on every 3s poll from the Internals tab.
|
|
104
|
+
async cacheVersions() {
|
|
83
105
|
if (!this.cachedPackageVersion) {
|
|
84
106
|
try {
|
|
85
107
|
const pkgPath = fileURLToPath(new URL('../../../package.json', import.meta.url));
|
|
@@ -93,47 +115,31 @@ export default class DebugController {
|
|
|
93
115
|
if (!this.cachedAdonisVersion) {
|
|
94
116
|
try {
|
|
95
117
|
const { createRequire } = await import('node:module');
|
|
96
|
-
const
|
|
97
|
-
const adonisPkg =
|
|
118
|
+
const req = createRequire(import.meta.url);
|
|
119
|
+
const adonisPkg = req('@adonisjs/core/package.json');
|
|
98
120
|
this.cachedAdonisVersion = adonisPkg.version;
|
|
99
121
|
}
|
|
100
122
|
catch {
|
|
101
123
|
this.cachedAdonisVersion = 'unknown';
|
|
102
124
|
}
|
|
103
125
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const collectors = healthList.map((h) => ({
|
|
111
|
-
...h,
|
|
112
|
-
config: configMap.get(h.name) ?? {},
|
|
113
|
-
}));
|
|
114
|
-
// Buffer stats
|
|
115
|
-
const buffers = this.store.getBufferStats();
|
|
116
|
-
// Storage stats (if dashboard is active)
|
|
117
|
-
let storage = null;
|
|
118
|
-
if (dashboardStore) {
|
|
119
|
-
try {
|
|
120
|
-
storage = await dashboardStore.getStorageStats();
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
// Dashboard store not ready
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
+
}
|
|
127
|
+
async diagnostics({ response }) {
|
|
128
|
+
await this.cacheVersions();
|
|
129
|
+
const engine = this.diagnosticsDeps.getEngine?.();
|
|
130
|
+
const dashboardStore = this.diagnosticsDeps.getDashboardStore?.();
|
|
131
|
+
const providerDiag = this.diagnosticsDeps.getProviderDiagnostics?.() ?? {};
|
|
126
132
|
return response.json({
|
|
127
133
|
package: {
|
|
128
|
-
version:
|
|
134
|
+
version: this.cachedPackageVersion,
|
|
129
135
|
nodeVersion: process.version,
|
|
130
|
-
adonisVersion,
|
|
136
|
+
adonisVersion: this.cachedAdonisVersion,
|
|
131
137
|
uptime: process.uptime(),
|
|
132
138
|
},
|
|
133
139
|
...providerDiag,
|
|
134
|
-
collectors,
|
|
135
|
-
buffers,
|
|
136
|
-
storage,
|
|
140
|
+
collectors: buildCollectorInfo(engine),
|
|
141
|
+
buffers: this.store.getBufferStats(),
|
|
142
|
+
storage: await fetchStorageStats(dashboardStore),
|
|
137
143
|
});
|
|
138
144
|
}
|
|
139
145
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { InspectorManager } from './inspector_manager.js';
|
|
2
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
|
+
/**
|
|
4
|
+
* Handle GET /cache-stats — list cache keys and overall stats.
|
|
5
|
+
*/
|
|
6
|
+
export declare function handleCacheStats(inspectors: InspectorManager, { request, response }: HttpContext): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Handle GET /cache-stats/:key — get a single cache key's value and metadata.
|
|
9
|
+
*/
|
|
10
|
+
export declare function handleCacheKey(inspectors: InspectorManager, { params, response }: HttpContext): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Handle DELETE /cache-stats/:key — delete a single cache key.
|
|
13
|
+
*/
|
|
14
|
+
export declare function handleCacheKeyDelete(inspectors: InspectorManager, { params, response }: HttpContext): Promise<void>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { clamp } from '../utils/math_helpers.js';
|
|
2
|
+
/**
|
|
3
|
+
* Handle GET /cache-stats — list cache keys and overall stats.
|
|
4
|
+
*/
|
|
5
|
+
export async function handleCacheStats(inspectors, { request, response }) {
|
|
6
|
+
const inspector = await inspectors.getCacheInspector();
|
|
7
|
+
if (!inspector)
|
|
8
|
+
return response.json({ available: false, stats: null, keys: [] });
|
|
9
|
+
const qs = request.qs();
|
|
10
|
+
const pattern = qs.search || qs.pattern ? `*${qs.search || qs.pattern}*` : '*';
|
|
11
|
+
try {
|
|
12
|
+
const [stats, keyList] = await Promise.all([
|
|
13
|
+
inspector.getStats(),
|
|
14
|
+
inspector.listKeys(pattern, qs.cursor || '0', clamp(Number(qs.count) || 100, 1, 500)),
|
|
15
|
+
]);
|
|
16
|
+
return response.json({ available: true, stats, keys: keyList.keys, cursor: keyList.cursor });
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return response.json({ available: false, stats: null, keys: [] });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Handle GET /cache-stats/:key — get a single cache key's value and metadata.
|
|
24
|
+
*/
|
|
25
|
+
export async function handleCacheKey(inspectors, { params, response }) {
|
|
26
|
+
const inspector = await inspectors.getCacheInspector();
|
|
27
|
+
if (!inspector)
|
|
28
|
+
return response.notFound({ error: 'Cache not available' });
|
|
29
|
+
try {
|
|
30
|
+
const detail = await inspector.getKey(decodeURIComponent(params.key));
|
|
31
|
+
return detail ? response.json(detail) : response.notFound({ error: 'Key not found' });
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return response.notFound({ error: 'Key not found' });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Handle DELETE /cache-stats/:key — delete a single cache key.
|
|
39
|
+
*/
|
|
40
|
+
export async function handleCacheKeyDelete(inspectors, { params, response }) {
|
|
41
|
+
const inspector = await inspectors.getCacheInspector();
|
|
42
|
+
if (!inspector)
|
|
43
|
+
return response.notFound({ error: 'Cache not available' });
|
|
44
|
+
try {
|
|
45
|
+
return (await inspector.deleteKey(decodeURIComponent(params.key)))
|
|
46
|
+
? response.json({ deleted: true })
|
|
47
|
+
: response.notFound({ error: 'Key not found' });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return response.internalServerError({ error: 'Failed to delete cache key' });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -2,13 +2,6 @@ import type { Knex } from 'knex';
|
|
|
2
2
|
/**
|
|
3
3
|
* Periodically aggregates recent request data into time-bucketed
|
|
4
4
|
* metrics stored in `server_stats_metrics`.
|
|
5
|
-
*
|
|
6
|
-
* Runs every 60 seconds. Each tick:
|
|
7
|
-
* 1. Counts requests in the last minute
|
|
8
|
-
* 2. Calculates avg/p95 response time
|
|
9
|
-
* 3. Counts errors (4xx + 5xx)
|
|
10
|
-
* 4. Counts queries and average query duration
|
|
11
|
-
* 5. Stores a row with a bucket timestamp rounded to the minute
|
|
12
5
|
*/
|
|
13
6
|
export declare class ChartAggregator {
|
|
14
7
|
private db;
|
|
@@ -1,15 +1,70 @@
|
|
|
1
1
|
import { round } from '../utils/math_helpers.js';
|
|
2
2
|
import { toSqliteTimestamp } from '../utils/time_helpers.js';
|
|
3
|
+
/** Fetch request stats (count, avg, errors) for the given cutoff. */
|
|
4
|
+
async function fetchRequestStats(ctx) {
|
|
5
|
+
const stats = await ctx
|
|
6
|
+
.trx('server_stats_requests')
|
|
7
|
+
.where('created_at', '>=', ctx.cutoff)
|
|
8
|
+
.select(ctx.trx.raw('COUNT(*) as request_count'), ctx.trx.raw('ROUND(AVG(duration), 2) as avg_duration'), ctx.trx.raw('SUM(CASE WHEN status_code >= 400 THEN 1 ELSE 0 END) as error_count'))
|
|
9
|
+
.first();
|
|
10
|
+
return {
|
|
11
|
+
requestCount: Number(stats?.request_count ?? 0),
|
|
12
|
+
avgDuration: stats?.avg_duration,
|
|
13
|
+
errorCount: Number(stats?.error_count ?? 0),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** The empty-bucket row shape for minutes with no requests. */
|
|
17
|
+
const EMPTY_METRICS = {
|
|
18
|
+
request_count: 0,
|
|
19
|
+
avg_duration: 0,
|
|
20
|
+
p95_duration: 0,
|
|
21
|
+
error_count: 0,
|
|
22
|
+
query_count: 0,
|
|
23
|
+
avg_query_duration: 0,
|
|
24
|
+
};
|
|
25
|
+
/** Insert an empty metrics bucket (no requests in this minute). */
|
|
26
|
+
async function insertEmptyBucket(ctx) {
|
|
27
|
+
await ctx.trx('server_stats_metrics').insert({ bucket: ctx.bucket, ...EMPTY_METRICS });
|
|
28
|
+
}
|
|
29
|
+
/** Fetch p95 duration from the request table. */
|
|
30
|
+
async function fetchP95Duration(ctx, requestCount) {
|
|
31
|
+
const offset = Math.floor(requestCount * 0.95);
|
|
32
|
+
const row = await ctx
|
|
33
|
+
.trx('server_stats_requests')
|
|
34
|
+
.where('created_at', '>=', ctx.cutoff)
|
|
35
|
+
.orderBy('duration', 'asc')
|
|
36
|
+
.offset(Math.min(offset, requestCount - 1))
|
|
37
|
+
.limit(1)
|
|
38
|
+
.select('duration')
|
|
39
|
+
.first();
|
|
40
|
+
return row?.duration ?? 0;
|
|
41
|
+
}
|
|
42
|
+
/** Fetch query stats for the given cutoff. */
|
|
43
|
+
async function fetchQueryStats(ctx) {
|
|
44
|
+
const row = await ctx
|
|
45
|
+
.trx('server_stats_queries')
|
|
46
|
+
.where('created_at', '>=', ctx.cutoff)
|
|
47
|
+
.select(ctx.trx.raw('COUNT(*) as query_count'), ctx.trx.raw('AVG(duration) as avg_query_duration'))
|
|
48
|
+
.first();
|
|
49
|
+
return { queryCount: row?.query_count ?? 0, avgQueryDuration: row?.avg_query_duration ?? 0 };
|
|
50
|
+
}
|
|
51
|
+
/** Insert a full metrics row with all computed values. */
|
|
52
|
+
async function insertFullBucket(ctx, reqStats) {
|
|
53
|
+
const p95 = await fetchP95Duration(ctx, reqStats.requestCount);
|
|
54
|
+
const qs = await fetchQueryStats(ctx);
|
|
55
|
+
await ctx.trx('server_stats_metrics').insert({
|
|
56
|
+
bucket: ctx.bucket,
|
|
57
|
+
request_count: reqStats.requestCount,
|
|
58
|
+
avg_duration: round(reqStats.avgDuration),
|
|
59
|
+
p95_duration: round(p95),
|
|
60
|
+
error_count: reqStats.errorCount,
|
|
61
|
+
query_count: qs.queryCount,
|
|
62
|
+
avg_query_duration: round(qs.avgQueryDuration),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
3
65
|
/**
|
|
4
66
|
* Periodically aggregates recent request data into time-bucketed
|
|
5
67
|
* metrics stored in `server_stats_metrics`.
|
|
6
|
-
*
|
|
7
|
-
* Runs every 60 seconds. Each tick:
|
|
8
|
-
* 1. Counts requests in the last minute
|
|
9
|
-
* 2. Calculates avg/p95 response time
|
|
10
|
-
* 3. Counts errors (4xx + 5xx)
|
|
11
|
-
* 4. Counts queries and average query duration
|
|
12
|
-
* 5. Stores a row with a bucket timestamp rounded to the minute
|
|
13
68
|
*/
|
|
14
69
|
export class ChartAggregator {
|
|
15
70
|
db;
|
|
@@ -18,8 +73,6 @@ export class ChartAggregator {
|
|
|
18
73
|
this.db = db;
|
|
19
74
|
}
|
|
20
75
|
start() {
|
|
21
|
-
// Defer the first aggregation so the event loop stays responsive
|
|
22
|
-
// during dashboard initialization. Then run every 60s.
|
|
23
76
|
setTimeout(() => this.aggregate().catch(() => { }), 2_000);
|
|
24
77
|
this.timer = setInterval(() => {
|
|
25
78
|
this.aggregate().catch(() => { });
|
|
@@ -33,57 +86,22 @@ export class ChartAggregator {
|
|
|
33
86
|
}
|
|
34
87
|
async aggregate() {
|
|
35
88
|
const bucket = getBucketTimestamp();
|
|
36
|
-
// Single transaction — 1 pool acquire instead of 5.
|
|
37
89
|
await this.db.transaction(async (trx) => {
|
|
38
90
|
const existing = await trx('server_stats_metrics').where('bucket', bucket).first();
|
|
39
91
|
if (existing)
|
|
40
92
|
return;
|
|
41
93
|
const cutoff = toSqliteTimestamp(new Date(Date.now() - 60_000));
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const requestCount = Number(stats?.request_count ?? 0);
|
|
47
|
-
if (requestCount === 0) {
|
|
48
|
-
await trx('server_stats_metrics').insert({
|
|
49
|
-
bucket,
|
|
50
|
-
request_count: 0,
|
|
51
|
-
avg_duration: 0,
|
|
52
|
-
p95_duration: 0,
|
|
53
|
-
error_count: 0,
|
|
54
|
-
query_count: 0,
|
|
55
|
-
avg_query_duration: 0,
|
|
56
|
-
});
|
|
94
|
+
const ctx = { trx, bucket, cutoff };
|
|
95
|
+
const reqStats = await fetchRequestStats(ctx);
|
|
96
|
+
if (reqStats.requestCount === 0) {
|
|
97
|
+
await insertEmptyBucket(ctx);
|
|
57
98
|
return;
|
|
58
99
|
}
|
|
59
|
-
|
|
60
|
-
const p95Row = await trx('server_stats_requests')
|
|
61
|
-
.where('created_at', '>=', cutoff)
|
|
62
|
-
.orderBy('duration', 'asc')
|
|
63
|
-
.offset(Math.min(p95Offset, requestCount - 1))
|
|
64
|
-
.limit(1)
|
|
65
|
-
.select('duration')
|
|
66
|
-
.first();
|
|
67
|
-
const queryStats = await trx('server_stats_queries')
|
|
68
|
-
.where('created_at', '>=', cutoff)
|
|
69
|
-
.select(trx.raw('COUNT(*) as query_count'), trx.raw('AVG(duration) as avg_query_duration'))
|
|
70
|
-
.first();
|
|
71
|
-
await trx('server_stats_metrics').insert({
|
|
72
|
-
bucket,
|
|
73
|
-
request_count: requestCount,
|
|
74
|
-
avg_duration: round(stats?.avg_duration),
|
|
75
|
-
p95_duration: round(p95Row?.duration ?? 0),
|
|
76
|
-
error_count: Number(stats?.error_count ?? 0),
|
|
77
|
-
query_count: queryStats?.query_count ?? 0,
|
|
78
|
-
avg_query_duration: round(queryStats?.avg_query_duration ?? 0),
|
|
79
|
-
});
|
|
100
|
+
await insertFullBucket(ctx, reqStats);
|
|
80
101
|
});
|
|
81
102
|
}
|
|
82
103
|
}
|
|
83
|
-
/**
|
|
84
|
-
* Returns an ISO timestamp string rounded down to the current minute.
|
|
85
|
-
* Used as the bucket key for metrics aggregation.
|
|
86
|
-
*/
|
|
104
|
+
/** Returns an ISO timestamp string rounded down to the current minute. */
|
|
87
105
|
function getBucketTimestamp() {
|
|
88
106
|
const now = new Date();
|
|
89
107
|
now.setSeconds(0, 0);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-flight request coalescing with short-lived result caching.
|
|
3
|
+
*
|
|
4
|
+
* - coalesce(): prevents concurrent identical calls from executing
|
|
5
|
+
* in parallel. Only ONE executes; others get the same promise.
|
|
6
|
+
* - cached(): adds a TTL layer on top of coalesce so repeat
|
|
7
|
+
* requests within the window serve stale data instantly.
|
|
8
|
+
*/
|
|
9
|
+
export declare class CoalesceCache {
|
|
10
|
+
private inflight;
|
|
11
|
+
private resultCache;
|
|
12
|
+
/**
|
|
13
|
+
* Coalesce concurrent calls with the same key.
|
|
14
|
+
* Only the first call actually executes `fn`; subsequent
|
|
15
|
+
* concurrent calls receive the same promise.
|
|
16
|
+
*/
|
|
17
|
+
coalesce<T>(key: string, fn: () => Promise<T>): Promise<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Return a cached result if within TTL, otherwise
|
|
20
|
+
* fall through to coalesce().
|
|
21
|
+
*/
|
|
22
|
+
cached<T>(key: string, ttlMs: number, fn: () => Promise<T>): Promise<T>;
|
|
23
|
+
/** Clear all cached results (does not affect in-flight requests). */
|
|
24
|
+
clearCache(): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-flight request coalescing with short-lived result caching.
|
|
3
|
+
*
|
|
4
|
+
* - coalesce(): prevents concurrent identical calls from executing
|
|
5
|
+
* in parallel. Only ONE executes; others get the same promise.
|
|
6
|
+
* - cached(): adds a TTL layer on top of coalesce so repeat
|
|
7
|
+
* requests within the window serve stale data instantly.
|
|
8
|
+
*/
|
|
9
|
+
export class CoalesceCache {
|
|
10
|
+
inflight = new Map();
|
|
11
|
+
resultCache = new Map();
|
|
12
|
+
/**
|
|
13
|
+
* Coalesce concurrent calls with the same key.
|
|
14
|
+
* Only the first call actually executes `fn`; subsequent
|
|
15
|
+
* concurrent calls receive the same promise.
|
|
16
|
+
*/
|
|
17
|
+
coalesce(key, fn) {
|
|
18
|
+
const existing = this.inflight.get(key);
|
|
19
|
+
if (existing)
|
|
20
|
+
return existing;
|
|
21
|
+
const promise = fn().finally(() => this.inflight.delete(key));
|
|
22
|
+
this.inflight.set(key, promise);
|
|
23
|
+
return promise;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Return a cached result if within TTL, otherwise
|
|
27
|
+
* fall through to coalesce().
|
|
28
|
+
*/
|
|
29
|
+
cached(key, ttlMs, fn) {
|
|
30
|
+
const entry = this.resultCache.get(key);
|
|
31
|
+
if (entry && Date.now() < entry.expiresAt) {
|
|
32
|
+
return Promise.resolve(entry.data);
|
|
33
|
+
}
|
|
34
|
+
return this.coalesce(key, async () => {
|
|
35
|
+
const result = await fn();
|
|
36
|
+
this.resultCache.set(key, {
|
|
37
|
+
data: result,
|
|
38
|
+
expiresAt: Date.now() + ttlMs,
|
|
39
|
+
});
|
|
40
|
+
return result;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Clear all cached results (does not affect in-flight requests). */
|
|
44
|
+
clearCache() {
|
|
45
|
+
this.resultCache.clear();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,28 +1,12 @@
|
|
|
1
1
|
import type { DashboardStore } from './dashboard_store.js';
|
|
2
2
|
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
3
|
import type { ApplicationService } from '@adonisjs/core/types';
|
|
4
|
-
/**
|
|
5
|
-
* Controller for the full-page dashboard.
|
|
6
|
-
*
|
|
7
|
-
* Serves the dashboard HTML page and dashboard-specific JSON API
|
|
8
|
-
* endpoints (overview, requests, grouped queries, query explain,
|
|
9
|
-
* cache, jobs, config, saved filters).
|
|
10
|
-
*
|
|
11
|
-
* Data resource endpoints (queries, events, emails, traces, routes,
|
|
12
|
-
* logs) are handled by the unified ApiController — see
|
|
13
|
-
* `src/controller/api_controller.ts`.
|
|
14
|
-
*/
|
|
15
4
|
export default class DashboardController {
|
|
16
5
|
private dashboardStore;
|
|
17
6
|
private app;
|
|
18
|
-
private cacheInspector;
|
|
19
|
-
private queueInspector;
|
|
20
7
|
private configInspector;
|
|
21
|
-
private
|
|
22
|
-
private
|
|
23
|
-
private cachedCss;
|
|
24
|
-
private cachedJs;
|
|
25
|
-
private cachedTransmitClient;
|
|
8
|
+
private inspectors;
|
|
9
|
+
private pageAssets;
|
|
26
10
|
constructor(dashboardStore: DashboardStore, app: ApplicationService);
|
|
27
11
|
page(ctx: HttpContext): Promise<string | void>;
|
|
28
12
|
overview({ request, response }: HttpContext): Promise<void>;
|
|
@@ -30,28 +14,18 @@ export default class DashboardController {
|
|
|
30
14
|
requests({ request, response }: HttpContext): Promise<void>;
|
|
31
15
|
requestDetail({ params, response }: HttpContext): Promise<void>;
|
|
32
16
|
queriesGrouped({ request, response }: HttpContext): Promise<void>;
|
|
33
|
-
queryExplain(
|
|
34
|
-
cacheStats(
|
|
35
|
-
cacheKey(
|
|
36
|
-
cacheKeyDelete(
|
|
37
|
-
jobs(
|
|
38
|
-
jobDetail(
|
|
39
|
-
jobRetry(
|
|
17
|
+
queryExplain(ctx: HttpContext): Promise<unknown>;
|
|
18
|
+
cacheStats(ctx: HttpContext): Promise<void>;
|
|
19
|
+
cacheKey(ctx: HttpContext): Promise<void>;
|
|
20
|
+
cacheKeyDelete(ctx: HttpContext): Promise<void>;
|
|
21
|
+
jobs(ctx: HttpContext): Promise<void>;
|
|
22
|
+
jobDetail(ctx: HttpContext): Promise<void>;
|
|
23
|
+
jobRetry(ctx: HttpContext): Promise<void>;
|
|
40
24
|
config({ response }: HttpContext): Promise<void>;
|
|
41
25
|
savedFilters({ response }: HttpContext): Promise<void>;
|
|
42
|
-
createSavedFilter(
|
|
43
|
-
deleteSavedFilter(
|
|
44
|
-
/**
|
|
45
|
-
* Wraps a store call with null-guard + try/catch boilerplate.
|
|
46
|
-
* Returns emptyValue if the store is not ready or the fn throws.
|
|
47
|
-
*/
|
|
26
|
+
createSavedFilter(ctx: HttpContext): Promise<void>;
|
|
27
|
+
deleteSavedFilter(ctx: HttpContext): Promise<void>;
|
|
48
28
|
private withDb;
|
|
49
29
|
private checkAccess;
|
|
50
30
|
private getDashboardPath;
|
|
51
|
-
/** Lazy-init inspector pattern for cache and queue. */
|
|
52
|
-
private getInspector;
|
|
53
|
-
/** Fetch cache overview stats for the overview page. */
|
|
54
|
-
private fetchCacheOverview;
|
|
55
|
-
/** Fetch queue overview stats for the overview page. */
|
|
56
|
-
private fetchQueueOverview;
|
|
57
31
|
}
|