@nuvlore/extension-bds 0.1.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.
Files changed (109) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +3 -0
  3. package/agents/bds-airflow-job-status-readonly.yaml +123 -0
  4. package/agents/bds-disk-investigator.yaml +112 -0
  5. package/agents/bds-inventory-readonly.yaml +60 -0
  6. package/agents/bds-oncall-reporter.yaml +113 -0
  7. package/agents/bds-prism-service-proxy-timeout-readonly.yaml +105 -0
  8. package/agents/bds-scylla-tenant-bucket-readonly.yaml +104 -0
  9. package/agents/bds-tenant-data-operation-log-readonly.yaml +98 -0
  10. package/agents/bds-yarn-app-health-readonly.yaml +114 -0
  11. package/agents/pharos-alert-investigator.yaml +81 -0
  12. package/package.json +60 -0
  13. package/skills/bds-airflow-job-status-investigation/SKILL.md +104 -0
  14. package/skills/bds-airflow-job-status-investigation/references/airflow-access-runbook.md +35 -0
  15. package/skills/bds-airflow-job-status-investigation/references/airflow-bas-dr-runbook.md +42 -0
  16. package/skills/bds-airflow-job-status-investigation/references/airflow-copy-and-migration-runbook.md +63 -0
  17. package/skills/bds-airflow-job-status-investigation/references/airflow-job-status-runbook.md +15 -0
  18. package/skills/bds-airflow-job-status-investigation/references/airflow-output-runbook.md +28 -0
  19. package/skills/bds-airflow-job-status-investigation/references/airflow-snapshot-runbook.md +38 -0
  20. package/skills/bds-disk-io-alert-investigation/SKILL.md +116 -0
  21. package/skills/bds-hadoop-review/SKILL.md +66 -0
  22. package/skills/bds-machine-inventory/SKILL.md +48 -0
  23. package/skills/bds-oncall-followup/SKILL.md +71 -0
  24. package/skills/bds-oncall-followup/references/oncall-followup-runbook.md +58 -0
  25. package/skills/bds-prism-service-proxy-timeout-investigation/SKILL.md +73 -0
  26. package/skills/bds-prism-service-proxy-timeout-investigation/references/service-proxy-timeout-runbook.md +48 -0
  27. package/skills/bds-scylla-tenant-bucket-investigation/SKILL.md +73 -0
  28. package/skills/bds-scylla-tenant-bucket-investigation/references/tenant-bucket-runbook.md +91 -0
  29. package/skills/bds-tenant-data-operation-log-investigation/SKILL.md +89 -0
  30. package/skills/bds-yarn-app-health-investigation/SKILL.md +77 -0
  31. package/skills/bds-yarn-app-health-investigation/references/yarn-app-health-runbook.md +53 -0
  32. package/skills/pharos-alert-investigation/SKILL.md +80 -0
  33. package/src/bds-atomic-log-tools.mjs +300 -0
  34. package/src/bds-team-membership-claude-output.mjs +36 -0
  35. package/src/bds-team-membership-quality.mjs +90 -0
  36. package/tools/bds_airflow_dag_log_read.mjs +24 -0
  37. package/tools/bds_airflow_job_status_command_pack.mjs +587 -0
  38. package/tools/bds_bas_log_read.mjs +23 -0
  39. package/tools/bds_bdm_httpd_log_read.mjs +24 -0
  40. package/tools/bds_chef_client_log_read.mjs +23 -0
  41. package/tools/bds_dashboard_links.mjs +178 -0
  42. package/tools/bds_dashboard_query_knowledge.mjs +258 -0
  43. package/tools/bds_dashboard_query_read.mjs +120 -0
  44. package/tools/bds_disk_io_alert_command_pack.mjs +287 -0
  45. package/tools/bds_environment_host_lookup.mjs +741 -0
  46. package/tools/bds_hdfs_datanode_log_read.mjs +23 -0
  47. package/tools/bds_hdfs_journalnode_log_read.mjs +23 -0
  48. package/tools/bds_hdfs_namenode_log_read.mjs +23 -0
  49. package/tools/bds_hdfs_path_knowledge.mjs +324 -0
  50. package/tools/bds_information_source_knowledge.mjs +356 -0
  51. package/tools/bds_kms_log_read.mjs +23 -0
  52. package/tools/bds_log_path_knowledge.mjs +267 -0
  53. package/tools/bds_machine_inventory.mjs +434 -0
  54. package/tools/bds_mapreduce_historyserver_log_read.mjs +23 -0
  55. package/tools/bds_migration_service_operation_command_pack.mjs +233 -0
  56. package/tools/bds_oncall_followup_payload_draft.mjs +285 -0
  57. package/tools/bds_pharos_query_pack.mjs +218 -0
  58. package/tools/bds_portal_operation_knowledge.mjs +212 -0
  59. package/tools/bds_prism_clone_status_links.mjs +129 -0
  60. package/tools/bds_prism_service_proxy_timeout_query_pack.mjs +224 -0
  61. package/tools/bds_public_cloud_pod_log_read.mjs +18 -0
  62. package/tools/bds_scylla_tenant_bucket_command_pack.mjs +323 -0
  63. package/tools/bds_service_proxy_log_read.mjs +23 -0
  64. package/tools/bds_spark_historyserver_log_read.mjs +32 -0
  65. package/tools/bds_spas_mt_log_read.mjs +23 -0
  66. package/tools/bds_tenant_data_operation_log_command_pack.mjs +321 -0
  67. package/tools/bds_yarn_app_health_command_pack.mjs +529 -0
  68. package/tools/bds_yarn_nodemanager_log_read.mjs +23 -0
  69. package/tools/bds_yarn_resourcemanager_log_read.mjs +23 -0
  70. package/tools/bds_zookeeper_log_read.mjs +23 -0
  71. package/tools/confluence_handoff_page_draft.mjs +173 -0
  72. package/vendor/dashboard/src/grafana-dashboard-query.mjs +61 -0
  73. package/vendor/extension-read-only-toolkit/LICENSE +202 -0
  74. package/vendor/extension-read-only-toolkit/README.md +29 -0
  75. package/vendor/extension-read-only-toolkit/package.json +46 -0
  76. package/vendor/extension-read-only-toolkit/src/devops-data-paths.mjs +222 -0
  77. package/vendor/extension-read-only-toolkit/src/devops-diagnostics.mjs +440 -0
  78. package/vendor/extension-read-only-toolkit/src/enterprise-api-read.mjs +180 -0
  79. package/vendor/extension-read-only-toolkit/src/read-only-command-pack.mjs +1 -0
  80. package/vendor/extension-read-only-toolkit/src/read-only-plan.mjs +45 -0
  81. package/vendor/extension-read-only-toolkit/src/read-only-shell-policy.d.mts +26 -0
  82. package/vendor/extension-read-only-toolkit/src/read-only-shell-policy.mjs +170 -0
  83. package/vendor/extension-workday-infra-access/LICENSE +202 -0
  84. package/vendor/extension-workday-infra-access/README.md +3 -0
  85. package/vendor/extension-workday-infra-access/agents/workday-infra-access-readonly.yaml +183 -0
  86. package/vendor/extension-workday-infra-access/package.json +49 -0
  87. package/vendor/extension-workday-infra-access/skills/workday-infra-access/SKILL.md +102 -0
  88. package/vendor/extension-workday-infra-access/skills/workday-infra-access/references/access-boundary-runbook.md +43 -0
  89. package/vendor/extension-workday-infra-access/skills/workday-infra-access/references/access-runbook.md +15 -0
  90. package/vendor/extension-workday-infra-access/skills/workday-infra-access/references/access-scylla-cloud-runbook.md +39 -0
  91. package/vendor/extension-workday-infra-access/skills/workday-infra-access/references/access-spc-argo-runbook.md +99 -0
  92. package/vendor/extension-workday-infra-access/src/workday-access-profiles.mjs +194 -0
  93. package/vendor/extension-workday-infra-access/tools/aws_cust_federated_readonly_command_pack.mjs +234 -0
  94. package/vendor/extension-workday-infra-access/tools/gcp_readonly_command_pack.mjs +167 -0
  95. package/vendor/extension-workday-infra-access/tools/public_cloud_access_status.mjs +48 -0
  96. package/vendor/extension-workday-infra-access/tools/workday_boundary_login.mjs +460 -0
  97. package/vendor/extension-workday-infra-access/tools/workday_private_cloud_sudo_readonly_command.mjs +386 -0
  98. package/vendor/extension-workday-infra-access/tools/workday_public_cloud_environment_map.mjs +189 -0
  99. package/vendor/extension-workday-infra-access/tools/workday_public_cloud_kubernetes_read.mjs +297 -0
  100. package/vendor/extension-workday-infra-access/tools/workday_public_cloud_spc_login.mjs +505 -0
  101. package/vendor/extension-workday-infra-access/tools/workday_spc_argo_readonly_command_pack.mjs +215 -0
  102. package/vendor/extension-workday-infra-access/workflows/public-cloud-readonly-investigation.js +99 -0
  103. package/workflows/bds-airflow-job-status-investigation.js +168 -0
  104. package/workflows/bds-disk-io-alert-investigation.js +173 -0
  105. package/workflows/bds-oncall-followup.js +112 -0
  106. package/workflows/bds-prism-service-proxy-timeout-investigation.js +69 -0
  107. package/workflows/bds-scylla-tenant-bucket-investigation.js +139 -0
  108. package/workflows/bds-tenant-data-operation-log-investigation.js +189 -0
  109. package/workflows/bds-yarn-app-health-investigation.js +217 -0
@@ -0,0 +1,23 @@
1
+ export const description = "Read Chef client logs from a private-cloud host through the approved Boundary sudo read tool.";
2
+
3
+ import { createPrivateCloudLogReadTool, fixedPrivateLogSpec } from "../src/bds-atomic-log-tools.mjs";
4
+
5
+ const spec = fixedPrivateLogSpec({
6
+ toolName: "bds_chef_client_log_read",
7
+ description: "Read Chef client logs from a private-cloud host through the approved Boundary sudo read tool.",
8
+ path: "/var/log/chef/client.log"
9
+ });
10
+
11
+ export const __test = spec;
12
+
13
+ const sharedTool = createPrivateCloudLogReadTool(spec);
14
+
15
+ export default {
16
+ name: sharedTool.name,
17
+ description,
18
+ inputSchema: sharedTool.inputSchema,
19
+ meta: sharedTool.meta,
20
+ annotations: sharedTool.annotations,
21
+ invoke: sharedTool.invoke,
22
+ handler: sharedTool.handler
23
+ };
@@ -0,0 +1,178 @@
1
+ export const description = "Build BDS Grafana, Pharos alerting, silence, and Solas links from alert labels such as alertname, source, and bds_cluster.";
2
+
3
+ import { z } from "zod";
4
+ import { defineReadOnlyTool, fail, ok } from "@nuvlore/extension-read-only-toolkit/devops-diagnostics";
5
+
6
+ const dashboards = {
7
+ cluster: { uid: "iQhYywmVz", slug: "bds-clusters", title: "BDS Clusters" },
8
+ datanode: { uid: "Bp5q5ixVz", slug: "bds-datanode", title: "BDS Datanode" },
9
+ hdfs: { uid: "ysSId274z", slug: "bds-hdfs", title: "BDS HDFS" },
10
+ yarn: { uid: "HnpfOgV4z", slug: "bds-yarn", title: "BDS YARN" },
11
+ namenode: { uid: "XPkK8QmVa", slug: "bds-namenode", title: "BDS Namenode" },
12
+ resourcemanager: { uid: "1U97nRG4z", slug: "bds-resourcemanager", title: "BDS Resourcemanager" },
13
+ nodemanager: { uid: "lD9rPZ-Vz", slug: "bds-nodemanager", title: "BDS Nodemanager" },
14
+ k8s: { uid: "AvFKfSfVk", slug: "bds-k8-dc", title: "BDS K8 DC" },
15
+ kms: { uid: "9kz5yk74z", slug: "bds-kms", title: "BDS KMS" },
16
+ zk: { uid: "TjGI3_a4k", slug: "bds-zookeeper", title: "BDS ZooKeeper" },
17
+ airflow: { uid: "lYJhbDY4z", slug: "bds-airflow", title: "BDS Airflow" },
18
+ spark: { uid: "-TvJPdnGz2", slug: "bds-spark-applications", title: "BDS Spark Applications" },
19
+ single_server: { uid: "u6xjOTK4k", slug: "inf-bds-single-server-dashboard", title: "INF BDS Single Server Dashboard" }
20
+ };
21
+
22
+ export class DashboardRecommendationRule {
23
+ constructor({ id, dashboards: recommended }) {
24
+ this.id = id;
25
+ this.dashboards = recommended;
26
+ }
27
+ }
28
+
29
+ export class DashboardRecommendationPolicy {
30
+ constructor(rules = [
31
+ new DashboardRecommendationRule({ id: "disk", dashboards: ["single_server", "datanode", "hdfs", "cluster"] }),
32
+ new DashboardRecommendationRule({ id: "yarn", dashboards: ["yarn", "resourcemanager", "nodemanager", "cluster"] }),
33
+ new DashboardRecommendationRule({ id: "kubernetes", dashboards: ["k8s", "cluster"] }),
34
+ new DashboardRecommendationRule({ id: "airflow", dashboards: ["airflow", "cluster"] }),
35
+ new DashboardRecommendationRule({ id: "spark", dashboards: ["spark", "yarn", "cluster"] }),
36
+ ], fallback = ["cluster", "single_server", "yarn", "hdfs", "k8s"]) {
37
+ this.rulesById = new Map(rules.map((rule) => [rule.id, rule]));
38
+ this.fallback = fallback;
39
+ }
40
+
41
+ recommend(domain = "") {
42
+ const rule = this.rulesById.get(domain);
43
+ return rule?.dashboards ?? this.fallback;
44
+ }
45
+ }
46
+
47
+ const dashboardRecommendationPolicy = new DashboardRecommendationPolicy();
48
+
49
+ function grafanaHost(environment) {
50
+ if (environment === "dev") return "grafana.dev.wdpharos.io";
51
+ if (environment === "il4") return "grafana.pharos-metrics.workdaygovcloud.com";
52
+ return "grafana.produs.us.wdpharos.io";
53
+ }
54
+
55
+ function addCommonParams(url, input) {
56
+ url.searchParams.set("orgId", "1");
57
+ url.searchParams.set("from", input.from ?? "now-6h");
58
+ url.searchParams.set("to", input.to ?? "now");
59
+ if (input.refresh) url.searchParams.set("refresh", input.refresh);
60
+ if (input.cluster) url.searchParams.set("var-bds_cluster", input.cluster);
61
+ if (input.namespace) url.searchParams.set("var-namespace", input.namespace);
62
+ if (input.source) url.searchParams.set("var-source", input.source);
63
+ return url;
64
+ }
65
+
66
+ function dashboardUrl(environment, dashboard, input) {
67
+ const def = dashboards[dashboard];
68
+ const url = new URL(`https://${grafanaHost(environment)}/d/${def.uid}/${def.slug}`);
69
+ return addCommonParams(url, input).toString();
70
+ }
71
+
72
+ function alertRuleUrl(environment, alertName) {
73
+ const search = alertName
74
+ ? `datasource:Cortex label:pharos_namespace=bds ${alertName}`
75
+ : "datasource:Cortex label:pharos_namespace=bds";
76
+ const url = new URL(`https://${grafanaHost(environment)}/alerting/list`);
77
+ url.searchParams.set("search", search);
78
+ return url.toString();
79
+ }
80
+
81
+ function silenceUrl(environment, filters = []) {
82
+ const url = new URL(`https://${grafanaHost(environment)}/alerting/silences`);
83
+ if (filters.length) {
84
+ url.searchParams.set("queryString", filters.map((filter) => `${filter.key}=${filter.value}`).join(","));
85
+ }
86
+ return url.toString();
87
+ }
88
+
89
+ function solasUrl(input) {
90
+ const query = [
91
+ "server_type:bds",
92
+ input.cluster ? `server_role:${input.cluster}` : "",
93
+ input.source ? `hostname:${input.source}` : ""
94
+ ].filter(Boolean).join(" AND ");
95
+ const url = new URL("https://solas.workday.com/s/readonly/app/discover");
96
+ url.searchParams.set("query", query);
97
+ url.searchParams.set("from", input.from ?? "now-6h");
98
+ url.searchParams.set("to", input.to ?? "now");
99
+ return url.toString();
100
+ }
101
+
102
+ function recommendedDashboards(domain = "") {
103
+ return dashboardRecommendationPolicy.recommend(domain);
104
+ }
105
+
106
+ function bdsDashboardLinks(input) {
107
+ const filters = [
108
+ input.alertName ? { key: "alertname", value: input.alertName } : null,
109
+ input.source ? { key: "source", value: input.source } : null,
110
+ input.cluster ? { key: "bds_cluster", value: input.cluster } : null
111
+ ].filter(Boolean);
112
+ const selected = input.dashboards?.length ? input.dashboards : recommendedDashboards(input.domain);
113
+ const result = {
114
+ mode: "link-pack",
115
+ provider: "bds-observability",
116
+ environment: input.environment,
117
+ domain: input.domain ?? "",
118
+ alertName: input.alertName ?? "",
119
+ source: input.source ?? "",
120
+ cluster: input.cluster ?? "",
121
+ timeRange: { from: input.from ?? "now-6h", to: input.to ?? "now" },
122
+ dashboards: selected.map((dashboard) => ({
123
+ key: dashboard,
124
+ title: dashboards[dashboard].title,
125
+ url: dashboardUrl(input.environment, dashboard, input)
126
+ })),
127
+ pharos: {
128
+ alertRule: alertRuleUrl(input.environment, input.alertName ?? ""),
129
+ silences: silenceUrl(input.environment, filters)
130
+ },
131
+ solas: solasUrl(input),
132
+ filters
133
+ };
134
+ return JSON.stringify(result, null, 2);
135
+ }
136
+
137
+ export class BdsDashboardLinksService {
138
+ invoke(input) {
139
+ return bdsDashboardLinks(input);
140
+ }
141
+ }
142
+
143
+ const defaultService = new BdsDashboardLinksService();
144
+
145
+ export default defineReadOnlyTool({
146
+ name: "bds_dashboard_links",
147
+ description: "Build BDS Grafana, Pharos alerting, silence, and Solas links from alert labels such as alertname, source, and bds_cluster.",
148
+ inputSchema: {
149
+ environment: z.enum(["dev", "prod", "il4"]).default("prod"),
150
+ alertName: z.string().trim().min(1).max(200).regex(/^[A-Za-z0-9_.:-]+$/).optional(),
151
+ source: z.string().trim().min(1).max(255).regex(/^[A-Za-z0-9_.:@/-]+$/).optional(),
152
+ cluster: z.string().trim().min(1).max(128).regex(/^[A-Za-z0-9_.:-]+$/).optional(),
153
+ namespace: z.string().trim().min(1).max(80).regex(/^[A-Za-z0-9_.:-]+$/).default("bds"),
154
+ domain: z.enum(["disk", "yarn", "kubernetes", "airflow", "spark"]).optional(),
155
+ from: z.string().trim().min(1).max(80).default("now-6h"),
156
+ to: z.string().trim().min(1).max(80).default("now"),
157
+ refresh: z.string().trim().min(1).max(40).optional(),
158
+ dashboards: z.array(z.enum(Object.keys(dashboards))).max(12).optional()
159
+ },
160
+ async handler(input) {
161
+ try {
162
+ return ok(defaultService.invoke(input));
163
+ } catch (error) {
164
+ return fail(error);
165
+ }
166
+ }
167
+ });
168
+
169
+ export const __test = {
170
+ BdsDashboardLinksService,
171
+ DashboardRecommendationPolicy,
172
+ DashboardRecommendationRule,
173
+ alertRuleUrl,
174
+ bdsDashboardLinks,
175
+ dashboardUrl,
176
+ recommendedDashboards,
177
+ silenceUrl
178
+ };
@@ -0,0 +1,258 @@
1
+ export const description = "Scan BDS Grafana dashboard JSON files and summarize learned PromQL patterns: metrics, labels, functions, variables, domains, and representative panel queries.";
2
+
3
+ import { readFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { z } from "zod";
6
+ import { defineReadOnlyTool, fail, ok } from "@nuvlore/extension-read-only-toolkit/devops-diagnostics";
7
+ import { dashboardCatalog, extractPanelQueries, extractVariables } from "./bds_dashboard_query_read.mjs";
8
+
9
+ const DEFAULT_DASHBOARD_DIR = "/Users/boqiang.liang/500-work/bds-terraform/alert-manager/dashboards";
10
+
11
+ const PROMQL_FUNCTIONS = new Set([
12
+ "abs", "avg", "avg_over_time", "ceil", "changes", "clamp", "count", "count_over_time",
13
+ "delta", "deriv", "floor", "histogram_quantile", "increase", "irate", "label_replace",
14
+ "last_over_time", "max", "max_over_time", "min", "min_over_time", "predict_linear",
15
+ "quantile", "rate", "round", "sort", "sort_desc", "sum", "time", "topk"
16
+ ]);
17
+
18
+ function increment(map, key, amount = 1) {
19
+ if (!key) return;
20
+ map.set(key, (map.get(key) ?? 0) + amount);
21
+ }
22
+
23
+ function topEntries(map, limit) {
24
+ return [...map.entries()]
25
+ .sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
26
+ .slice(0, limit)
27
+ .map(([name, count]) => ({ name, count }));
28
+ }
29
+
30
+ function metricNames(expr) {
31
+ const names = new Set();
32
+ const regex = /\b([a-zA-Z_:][a-zA-Z0-9_:]*)\s*(?=\{|\[|$)/g;
33
+ for (const match of expr.matchAll(regex)) {
34
+ const name = match[1];
35
+ if (!PROMQL_FUNCTIONS.has(name) && !["by", "without", "bool", "offset"].includes(name)) {
36
+ names.add(name);
37
+ }
38
+ }
39
+ return [...names];
40
+ }
41
+
42
+ function labelNames(expr) {
43
+ const names = new Set();
44
+ const regex = /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:=~|!~|=|!=)/g;
45
+ for (const match of expr.matchAll(regex)) names.add(match[1]);
46
+ return [...names];
47
+ }
48
+
49
+ function functionNames(expr) {
50
+ const names = new Set();
51
+ const regex = /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
52
+ for (const match of expr.matchAll(regex)) {
53
+ if (PROMQL_FUNCTIONS.has(match[1])) names.add(match[1]);
54
+ }
55
+ return [...names];
56
+ }
57
+
58
+ function variableNames(expr) {
59
+ const names = new Set();
60
+ const regex = /\$([a-zA-Z_][a-zA-Z0-9_]*)|\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}|\[\[([a-zA-Z_][a-zA-Z0-9_]*)\]\]/g;
61
+ for (const match of expr.matchAll(regex)) names.add(match[1] ?? match[2] ?? match[3]);
62
+ return [...names];
63
+ }
64
+
65
+ export const BDS_DASHBOARD_DOMAIN_RULES = [
66
+ { domain: "airflow", dashboards: ["airflow"] },
67
+ { domain: "hdfs-control-plane", dashboards: ["journalnode", "namenode", "zookeeper"] },
68
+ { domain: "hdfs", dashboards: ["datanode", "hdfs"] },
69
+ { domain: "yarn", dashboards: ["nodemanager", "resource_manager", "resourcemanager", "rm", "yarn"] },
70
+ { domain: "kubernetes", dashboards: ["k8s"] },
71
+ { domain: "kms", dashboards: ["kms"] },
72
+ { domain: "scylla", dashboards: ["migration_service", "scylla_bdm", "service_proxy"] },
73
+ { domain: "spark", dashboards: ["spark"] }
74
+ ];
75
+
76
+ export class BdsDashboardDomainClassifier {
77
+ constructor({ rules = BDS_DASHBOARD_DOMAIN_RULES, fallback = "general" } = {}) {
78
+ this.domainByDashboard = new Map();
79
+ for (const rule of rules) {
80
+ for (const dashboard of rule.dashboards ?? []) {
81
+ this.domainByDashboard.set(dashboard, rule.domain);
82
+ }
83
+ }
84
+ this.fallback = fallback;
85
+ }
86
+
87
+ classify({ key = "" } = {}) {
88
+ return this.domainByDashboard.get(key) || this.fallback;
89
+ }
90
+ }
91
+
92
+ const defaultDomainClassifier = new BdsDashboardDomainClassifier();
93
+
94
+ async function loadDashboard(fileName, dashboardDir) {
95
+ const dashboardPath = join(dashboardDir, fileName);
96
+ return {
97
+ dashboardPath,
98
+ dashboard: JSON.parse(await readFile(dashboardPath, "utf8"))
99
+ };
100
+ }
101
+
102
+ async function scanDashboard(key, fileName, input, domainClassifier = defaultDomainClassifier) {
103
+ const { dashboardPath, dashboard } = await loadDashboard(fileName, input.dashboardDir ?? DEFAULT_DASHBOARD_DIR);
104
+ const panels = extractPanelQueries(dashboard, {
105
+ cluster: input.cluster,
106
+ namespace: input.namespace,
107
+ source: input.source,
108
+ scyllaCluster: input.scyllaCluster,
109
+ limit: 1_000
110
+ });
111
+ const metrics = new Map();
112
+ const labels = new Map();
113
+ const functions = new Map();
114
+ const variables = new Map();
115
+ const panelExamples = [];
116
+ let queryCount = 0;
117
+
118
+ for (const panel of panels) {
119
+ for (const target of panel.targets) {
120
+ queryCount += 1;
121
+ for (const metric of metricNames(target.expr)) increment(metrics, metric);
122
+ for (const label of labelNames(target.expr)) increment(labels, label);
123
+ for (const fn of functionNames(target.expr)) increment(functions, fn);
124
+ for (const variable of variableNames(target.expr)) increment(variables, variable);
125
+ if (panelExamples.length < input.examplesPerDashboard) {
126
+ panelExamples.push({
127
+ panel: panel.title,
128
+ refId: target.refId,
129
+ expr: target.expr,
130
+ renderedExpr: target.renderedExpr
131
+ });
132
+ }
133
+ }
134
+ }
135
+
136
+ return {
137
+ key,
138
+ fileName,
139
+ dashboardPath,
140
+ uid: dashboard.uid ?? "",
141
+ title: dashboard.title ?? "",
142
+ domain: domainClassifier.classify({ key }),
143
+ variablesDefined: extractVariables(dashboard).map((variable) => ({
144
+ name: variable.name,
145
+ definition: variable.definition
146
+ })),
147
+ panelCount: panels.length,
148
+ queryCount,
149
+ topMetrics: topEntries(metrics, input.topLimit),
150
+ topLabels: topEntries(labels, input.topLimit),
151
+ promqlFunctions: topEntries(functions, input.topLimit),
152
+ queryVariables: topEntries(variables, input.topLimit),
153
+ examples: panelExamples
154
+ };
155
+ }
156
+
157
+ function mergeDashboardKnowledge(dashboards, input) {
158
+ const metrics = new Map();
159
+ const labels = new Map();
160
+ const functions = new Map();
161
+ const variables = new Map();
162
+ const domains = new Map();
163
+ let queryCount = 0;
164
+ let panelCount = 0;
165
+
166
+ for (const dashboard of dashboards) {
167
+ queryCount += dashboard.queryCount;
168
+ panelCount += dashboard.panelCount;
169
+ increment(domains, dashboard.domain);
170
+ for (const item of dashboard.topMetrics) increment(metrics, item.name, item.count);
171
+ for (const item of dashboard.topLabels) increment(labels, item.name, item.count);
172
+ for (const item of dashboard.promqlFunctions) increment(functions, item.name, item.count);
173
+ for (const item of dashboard.queryVariables) increment(variables, item.name, item.count);
174
+ }
175
+
176
+ return {
177
+ dashboardCount: dashboards.length,
178
+ panelCount,
179
+ queryCount,
180
+ domains: topEntries(domains, input.topLimit),
181
+ topMetrics: topEntries(metrics, input.topLimit),
182
+ topLabels: topEntries(labels, input.topLimit),
183
+ promqlFunctions: topEntries(functions, input.topLimit),
184
+ queryVariables: topEntries(variables, input.topLimit)
185
+ };
186
+ }
187
+
188
+ export class BdsDashboardQueryKnowledgeService {
189
+ constructor({ domainClassifier = defaultDomainClassifier } = {}) {
190
+ this.domainClassifier = domainClassifier;
191
+ }
192
+
193
+ async invoke(input) {
194
+ const keys = input.dashboard && input.dashboard !== "all"
195
+ ? [input.dashboard]
196
+ : [...new Set(Object.keys(dashboardCatalog))];
197
+ const dashboards = [];
198
+ const seenFiles = new Set();
199
+ for (const key of keys) {
200
+ const fileName = dashboardCatalog[key];
201
+ if (!fileName || seenFiles.has(fileName)) continue;
202
+ seenFiles.add(fileName);
203
+ dashboards.push(await scanDashboard(key, fileName, input, this.domainClassifier));
204
+ }
205
+
206
+ return JSON.stringify({
207
+ mode: "query-knowledge",
208
+ provider: "bds-dashboard-json",
209
+ dashboardDir: input.dashboardDir ?? DEFAULT_DASHBOARD_DIR,
210
+ renderedFor: {
211
+ cluster: input.cluster ?? "",
212
+ namespace: input.namespace ?? "",
213
+ source: input.source ?? "",
214
+ scyllaCluster: input.scyllaCluster ?? ""
215
+ },
216
+ summary: mergeDashboardKnowledge(dashboards, input),
217
+ dashboards
218
+ }, null, 2);
219
+ }
220
+ }
221
+
222
+ async function bdsDashboardQueryKnowledge(input) {
223
+ return new BdsDashboardQueryKnowledgeService().invoke(input);
224
+ }
225
+
226
+ const defaultService = new BdsDashboardQueryKnowledgeService();
227
+
228
+ export default defineReadOnlyTool({
229
+ name: "bds_dashboard_query_knowledge",
230
+ description: "Scan BDS Grafana dashboard JSON files and summarize learned PromQL patterns: metrics, labels, functions, variables, domains, and representative panel queries.",
231
+ inputSchema: {
232
+ dashboard: z.enum(["all", ...Object.keys(dashboardCatalog)]).default("all"),
233
+ dashboardDir: z.string().trim().min(1).max(500).default(DEFAULT_DASHBOARD_DIR),
234
+ cluster: z.string().trim().min(1).max(128).regex(/^[A-Za-z0-9_.:-]+$/).optional(),
235
+ namespace: z.string().trim().min(1).max(80).regex(/^[A-Za-z0-9_.:-]+$/).optional(),
236
+ source: z.string().trim().min(1).max(255).regex(/^[A-Za-z0-9_.:@/-]+$/).optional(),
237
+ scyllaCluster: z.string().trim().min(1).max(128).regex(/^[A-Za-z0-9_.:-]+$/).optional(),
238
+ topLimit: z.number().int().positive().max(100).default(20),
239
+ examplesPerDashboard: z.number().int().nonnegative().max(20).default(3)
240
+ },
241
+ async handler(input) {
242
+ try {
243
+ return ok(await defaultService.invoke(input));
244
+ } catch (error) {
245
+ return fail(error);
246
+ }
247
+ }
248
+ });
249
+
250
+ export const __test = {
251
+ bdsDashboardQueryKnowledge,
252
+ metricNames,
253
+ labelNames,
254
+ functionNames,
255
+ variableNames,
256
+ BdsDashboardDomainClassifier,
257
+ BdsDashboardQueryKnowledgeService
258
+ };
@@ -0,0 +1,120 @@
1
+ export const description = "Read every PromQL query from a local BDS Grafana dashboard JSON, including variables and rendered expressions for supplied cluster/source values.";
2
+
3
+ import { readFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { z } from "zod";
6
+ import { defineReadOnlyTool, fail, ok } from "@nuvlore/extension-read-only-toolkit/devops-diagnostics";
7
+ import {
8
+ extractPanelQueries as extractGenericPanelQueries,
9
+ extractVariables
10
+ } from "../vendor/dashboard/src/grafana-dashboard-query.mjs";
11
+
12
+ export { extractVariables } from "../vendor/dashboard/src/grafana-dashboard-query.mjs";
13
+
14
+ const DEFAULT_DASHBOARD_DIR = "/Users/boqiang.liang/500-work/bds-terraform/alert-manager/dashboards";
15
+
16
+ export const dashboardCatalog = {
17
+ airflow: "bds_airflow.json",
18
+ cluster: "bds_clusters.json",
19
+ clusters: "bds_clusters.json",
20
+ datanode: "bds_datanodes.json",
21
+ hdfs: "bds_hdfs.json",
22
+ journalnode: "bds_jn.json",
23
+ k8s: "bds_k8s.json",
24
+ kms: "bds_kms.json",
25
+ migration_service: "bds_migration_service.json",
26
+ namenode: "bds_nn.json",
27
+ nodemanager: "bds_nm.json",
28
+ resource_manager: "bds_rm.json",
29
+ resourcemanager: "bds_rm.json",
30
+ rm: "bds_rm.json",
31
+ scylla_bdm: "bds_scylla_bdm.json",
32
+ service_proxy: "bds_scylla_service_proxy.json",
33
+ spark: "bds_spark.json",
34
+ stats: "bds_stats.json",
35
+ top_level: "bds_top_level.json",
36
+ yarn: "bds_yarn.json",
37
+ zookeeper: "bds_zk.json"
38
+ };
39
+
40
+ export function extractPanelQueries(dashboard, input) {
41
+ const variableValues = {
42
+ bds_cluster: input.cluster ?? "",
43
+ namespace: input.namespace ?? "",
44
+ source: input.source ?? "",
45
+ scylla_cluster: input.scyllaCluster ?? ""
46
+ };
47
+ return extractGenericPanelQueries(dashboard, {
48
+ variableValues,
49
+ panelTitleContains: input.panelTitleContains,
50
+ queryContains: input.queryContains,
51
+ limit: input.limit ?? 200
52
+ }).map((panel) => ({
53
+ id: panel.id,
54
+ title: panel.title,
55
+ type: panel.type,
56
+ description: panel.description,
57
+ targets: panel.targets
58
+ .filter((target) => target.language === "promql")
59
+ .map((target) => ({
60
+ refId: target.refId,
61
+ expr: target.query,
62
+ renderedExpr: target.renderedQuery,
63
+ legendFormat: target.legendFormat,
64
+ datasource: target.datasource
65
+ }))
66
+ })).filter((panel) => panel.targets.length);
67
+ }
68
+
69
+ export class BdsDashboardQueryReadService {
70
+ async invoke(input) {
71
+ const fileName = dashboardCatalog[input.dashboard];
72
+ if (!fileName) throw new Error(`Unknown BDS dashboard: ${input.dashboard}`);
73
+ const dashboardPath = join(input.dashboardDir ?? DEFAULT_DASHBOARD_DIR, fileName);
74
+ const dashboard = JSON.parse(await readFile(dashboardPath, "utf8"));
75
+ const panels = extractPanelQueries(dashboard, input);
76
+ return JSON.stringify({
77
+ mode: "read-only",
78
+ provider: "bds-dashboard-json",
79
+ dashboard: input.dashboard,
80
+ fileName,
81
+ dashboardPath,
82
+ uid: dashboard.uid ?? "",
83
+ title: dashboard.title ?? "",
84
+ variables: extractVariables(dashboard),
85
+ panelCount: panels.length,
86
+ panels
87
+ }, null, 2);
88
+ }
89
+ }
90
+
91
+ async function bdsDashboardQueryRead(input) {
92
+ return new BdsDashboardQueryReadService().invoke(input);
93
+ }
94
+
95
+ const defaultService = new BdsDashboardQueryReadService();
96
+
97
+ export default defineReadOnlyTool({
98
+ name: "bds_dashboard_query_read",
99
+ description: "Read every PromQL query from a local BDS Grafana dashboard JSON, including variables and rendered expressions for supplied cluster/source values.",
100
+ inputSchema: {
101
+ dashboard: z.enum(Object.keys(dashboardCatalog)),
102
+ dashboardDir: z.string().trim().min(1).max(500).default(DEFAULT_DASHBOARD_DIR),
103
+ cluster: z.string().trim().min(1).max(128).regex(/^[A-Za-z0-9_.:-]+$/).optional(),
104
+ namespace: z.string().trim().min(1).max(80).regex(/^[A-Za-z0-9_.:-]+$/).optional(),
105
+ source: z.string().trim().min(1).max(255).regex(/^[A-Za-z0-9_.:@/-]+$/).optional(),
106
+ scyllaCluster: z.string().trim().min(1).max(128).regex(/^[A-Za-z0-9_.:-]+$/).optional(),
107
+ panelTitleContains: z.string().trim().min(1).max(120).optional(),
108
+ queryContains: z.string().trim().min(1).max(160).optional(),
109
+ limit: z.number().int().positive().max(500).default(200)
110
+ },
111
+ async handler(input) {
112
+ try {
113
+ return ok(await defaultService.invoke(input));
114
+ } catch (error) {
115
+ return fail(error);
116
+ }
117
+ }
118
+ });
119
+
120
+ export const __test = { bdsDashboardQueryRead, extractPanelQueries, extractVariables, BdsDashboardQueryReadService };