-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathWorkloadAnalysisController.java
More file actions
113 lines (99 loc) · 5.1 KB
/
Copy pathWorkloadAnalysisController.java
File metadata and controls
113 lines (99 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package com.dbaagent.controller;
import com.dbaagent.model.WorkloadAnalysisReport;
import com.dbaagent.repository.WorkloadAnalysisReportRepository;
import com.dbaagent.service.WorkloadAnalysisService;
import com.dbaagent.service.security.AccessControlService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* On-demand holistic Workload Analysis.
*
* <p>Trigger is manual (UI button); the run is a long-running background job
* (virtual thread) that composes index recs, rewrites, pre-aggregation recs,
* and index-health cleanup into one {@link WorkloadAnalysisReport}. Clients
* start a run, then poll {@code /status} (or {@code /latest}) for progress.
*/
@RestController
@RequestMapping("/workload-analysis")
@RequiredArgsConstructor
@Slf4j
public class WorkloadAnalysisController {
// Reads use canReadContent; only `run` requires canManageContent. The reads were all
// gated on manage, which is the write tier — EffectiveConnectionAccess's own comment
// lists slow-query analytics under read. Latent today because every grant resolves to
// FULL_CONTENT (so both predicates are true), but it would deny the whole Workload tab
// to a read-only grant the moment one is reintroduced.
private final WorkloadAnalysisService workloadAnalysisService;
private final WorkloadAnalysisReportRepository reportRepository;
private final AccessControlService accessControlService;
/**
* Kick off a workload analysis. Idempotent under concurrency: if one is
* already PENDING/RUNNING for this connection, returns that one instead of
* starting a second.
*/
@PostMapping("/{connectionId}/run")
public ResponseEntity<Map<String, Object>> run(@PathVariable String connectionId) {
accessControlService.assertCanManageConnectionContent(connectionId);
var active = workloadAnalysisService.findActive(connectionId);
if (active.isPresent()) {
return ResponseEntity.ok(Map.of(
"reportId", active.get().getId(),
"status", active.get().getStatus().name(),
"alreadyRunning", true));
}
WorkloadAnalysisReport report = workloadAnalysisService.createPendingReport(
connectionId, accessControlService.getCurrentUsername());
String reportId = report.getId();
Thread.ofVirtual().name("workload-analysis-" + reportId.substring(0, 8).toLowerCase(Locale.ROOT))
.start(() -> workloadAnalysisService.runAnalysis(reportId));
return ResponseEntity.ok(Map.of(
"reportId", reportId,
"status", report.getStatus().name(),
"alreadyRunning", false));
}
/** Lightweight poll payload — status + progress without the full report blob. */
@GetMapping("/{connectionId}/status")
public ResponseEntity<Map<String, Object>> status(@PathVariable String connectionId) {
accessControlService.assertCanReadConnectionContent(connectionId);
return reportRepository.findFirstByConnectionIdOrderByStartedAtDesc(connectionId)
.map(r -> ResponseEntity.ok(Map.<String, Object>of(
"reportId", r.getId(),
"status", r.getStatus().name(),
"progress", r.getProgress(),
"currentStep", r.getCurrentStep() == null ? "" : r.getCurrentStep(),
"candidateCount", r.getCandidateCount(),
"indexRecCount", r.getIndexRecCount(),
"rewriteRecCount", r.getRewriteRecCount(),
"preAggRecCount", r.getPreAggRecCount())))
.orElseGet(() -> ResponseEntity.noContent().build());
}
/** The full latest report (with the composed sections). 204 if none yet. */
@GetMapping("/{connectionId}/latest")
public ResponseEntity<WorkloadAnalysisReport> latest(@PathVariable String connectionId) {
accessControlService.assertCanReadConnectionContent(connectionId);
return reportRepository.findFirstByConnectionIdOrderByStartedAtDesc(connectionId)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.noContent().build());
}
/** A specific report by id. */
@GetMapping("/report/{reportId}")
public ResponseEntity<WorkloadAnalysisReport> getReport(@PathVariable String reportId) {
return reportRepository.findById(reportId)
.map(r -> {
accessControlService.assertCanReadConnectionContent(r.getConnectionId());
return ResponseEntity.ok(r);
})
.orElseGet(() -> ResponseEntity.notFound().build());
}
/** Recent report history (newest first), metadata only via the entity. */
@GetMapping("/{connectionId}/history")
public ResponseEntity<List<WorkloadAnalysisReport>> history(@PathVariable String connectionId) {
accessControlService.assertCanReadConnectionContent(connectionId);
return ResponseEntity.ok(reportRepository.findTop20ByConnectionIdOrderByStartedAtDesc(connectionId));
}
}