forecast = analysisService.forecast(validHistory, step, dynamicCount);
+
+ if (!forecast.isEmpty()) {
+ resultMap.put(rowInstance, forecast);
+ }
+ }
+ });
+
+ return Message.success(resultMap);
+ }
+
+ /**
+ * Parses a simple duration string (e.g., "1h", "6h", "1d", "1w", "4w", "12w") into milliseconds.
+ *
+ * Supported time units are:
+ *
+ * - s - seconds (e.g., "30s")
+ * - m - minutes (e.g., "15m")
+ * - h - hours (e.g., "1h", "6h")
+ * - d - days (e.g., "1d")
+ * - w - weeks (e.g., "4w", "12w")
+ *
+ *
+ * Examples of valid input:
+ *
+ * - "1h" -> 3,600,000
+ * - "6h" -> 21,600,000
+ * - "1d" -> 86,400,000
+ * - "4w" -> 2,419,200,000
+ *
+ *
+ * If the input is invalid or cannot be parsed, returns 0.
+ *
+ * @param timeToken the duration string to parse
+ * @return the duration in milliseconds, or 0 if input is invalid
+ */
+ private long parseSimpleDuration(String timeToken) {
+ if (timeToken == null) return 0;
+ // Define maximum allowed duration: 10 years in milliseconds
+ try {
+ String lower = timeToken.toLowerCase().trim();
+ if (lower.length() < 2) return 0;
+ char unit = lower.charAt(lower.length() - 1);
+ String numberPart = lower.substring(0, lower.length() - 1);
+ // Only allow digits, and limit length to 12 digits (trillion)
+ if (!numberPart.matches("\\d{1,12}")) return 0;
+ long value = Long.parseLong(numberPart);
+ long durationMs = 0;
+ switch (unit) {
+ case 's':
+ durationMs = value * 1000L;
+ break;
+ case 'm':
+ durationMs = value * 60L * 1000L;
+ break;
+ case 'h':
+ durationMs = value * 3600L * 1000L;
+ break;
+ case 'd':
+ durationMs = value * 24L * 3600L * 1000L;
+ break;
+ case 'w':
+ durationMs = value * 7L * 24L * 3600L * 1000L;
+ break;
+ default:
+ return 0;
+ }
+ if (durationMs < 0 || durationMs > MAX_DURATION_MS) {
+ return 0;
+ }
+ return durationMs;
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ /**
+ * Estimates the time step (interval) between consecutive history data points by calculating
+ * the median of the time differences between their timestamps.
+ *
+ * Only the first 100 data points are considered for efficiency and because this is typically
+ * sufficient for a reliable estimation.
+ *
+ * If there are fewer than 2 data points, or if all time differences are non-positive,
+ * a default value of 60000 milliseconds (1 minute) is returned.
+ *
+ * @param data the list of history data points, assumed to be sorted by time ascending
+ * @return the estimated time step in milliseconds (median of positive time differences)
+ */
+ private long estimateStep(List data) {
+ if (data.size() < 2) {
+ return 60000L; // default 1 min
+ }
+ List diffs = new ArrayList<>();
+ // Check first 100 points is enough for estimation
+ int limit = Math.min(data.size(), 100);
+ for (int i = 1; i < limit; i++) {
+ long diff = data.get(i).getTime() - data.get(i - 1).getTime();
+ if (diff > 0) {
+ diffs.add(diff);
+ }
+ }
+ if (diffs.isEmpty()) {
+ return 60000L;
+ }
+ Collections.sort(diffs);
+ return diffs.get(diffs.size() / 2);
+ }
+}
diff --git a/hertzbeat-manager/src/test/resources/sureness.yml b/hertzbeat-manager/src/test/resources/sureness.yml
index f04c5a2f7d3..dc51787c8f5 100644
--- a/hertzbeat-manager/src/test/resources/sureness.yml
+++ b/hertzbeat-manager/src/test/resources/sureness.yml
@@ -16,7 +16,7 @@
## -- sureness.yml account source -- ##
# config the resource restful api that need auth protection, base rbac
-# rule: api===method===role
+# rule: api===method===role
# eg: /api/v1/source1===get===[admin] means /api/v2/host===post support role[admin] access.
# eg: /api/v1/source2===get===[] means /api/v1/source2===get can not access by any role.
resourceRole:
@@ -71,9 +71,10 @@ resourceRole:
- /api/chat/**===get===[admin,user]
- /api/chat/**===post===[admin,user]
- /api/logs/ingest/**===post===[admin,user]
+ - /api/analysis/**===get===[admin,user]
# config the resource restful api that need bypass auth protection
-# rule: api===method
+# rule: api===method
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
excludedResource:
- /api/alert/sse/**===*
diff --git a/hertzbeat-startup/src/main/resources/sureness.yml b/hertzbeat-startup/src/main/resources/sureness.yml
index 282e93928a1..8d19327b936 100644
--- a/hertzbeat-startup/src/main/resources/sureness.yml
+++ b/hertzbeat-startup/src/main/resources/sureness.yml
@@ -16,7 +16,7 @@
## -- sureness.yml account source -- ##
# config the resource restful api that need auth protection, base rbac
-# rule: api===method===role
+# rule: api===method===role
# eg: /api/v1/source1===get===[admin] means /api/v2/host===post support role[admin] access.
# eg: /api/v1/source2===get===[] means /api/v1/source2===get can not access by any role.
resourceRole:
@@ -71,9 +71,10 @@ resourceRole:
- /api/chat/**===get===[admin,user]
- /api/chat/**===post===[admin]
- /api/logs/ingest/**===post===[admin,user]
+ - /api/analysis/**===get===[admin,user]
# config the resource restful api that need bypass auth protection
-# rule: api===method
+# rule: api===method
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
excludedResource:
- /api/alert/sse/**===*
diff --git a/pom.xml b/pom.xml
index 08ad1b0a1b0..ed49ea9541a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -93,6 +93,7 @@
hertzbeat-base
hertzbeat-log
hertzbeat-ai
+ hertzbeat-analysis
@@ -149,6 +150,7 @@
3.2.1
3.10.0
1.27.1
+ 3.6.1
0.8.11
2.40.0
@@ -271,6 +273,12 @@
hertzbeat-collector-collector
${hertzbeat.version}