mirror of
https://github.com/MarginaliaSearch/MarginaliaSearch.git
synced 2025-02-23 13:09:00 +00:00
(control) WIP control service
This commit is contained in:
parent
fba466d6e2
commit
2283ceb77d
@ -11,6 +11,8 @@ import nu.marginalia.renderer.RendererFactory;
|
||||
import nu.marginalia.service.server.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Spark;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -25,7 +27,10 @@ public class ControlService extends Service {
|
||||
private final ServiceMonitors monitors;
|
||||
private final MustacheRenderer<Object> indexRenderer;
|
||||
private final MustacheRenderer<Map<?,?>> servicesRenderer;
|
||||
private final MustacheRenderer<Map<?,?>> eventsRenderer;
|
||||
private final MustacheRenderer<Map<?,?>> messageQueueRenderer;
|
||||
private final MqPersistence messageQueuePersistence;
|
||||
private final StaticResources staticResources;
|
||||
|
||||
|
||||
@Inject
|
||||
@ -35,14 +40,20 @@ public class ControlService extends Service {
|
||||
EventLogService eventLogService,
|
||||
RendererFactory rendererFactory,
|
||||
MqPersistence messageQueuePersistence,
|
||||
ControlProcesses controlProcesses
|
||||
ControlProcesses controlProcesses,
|
||||
StaticResources staticResources,
|
||||
MessageQueueViewService messageQueueViewService
|
||||
) throws IOException {
|
||||
|
||||
super(params);
|
||||
this.monitors = monitors;
|
||||
indexRenderer = rendererFactory.renderer("control/index");
|
||||
servicesRenderer = rendererFactory.renderer("control/services");
|
||||
eventsRenderer = rendererFactory.renderer("control/events");
|
||||
messageQueueRenderer = rendererFactory.renderer("control/message-queue");
|
||||
|
||||
this.messageQueuePersistence = messageQueuePersistence;
|
||||
this.staticResources = staticResources;
|
||||
|
||||
Spark.get("/public/heartbeats", (req, res) -> {
|
||||
res.type("application/json");
|
||||
@ -50,15 +61,18 @@ public class ControlService extends Service {
|
||||
}, gson::toJson);
|
||||
|
||||
Spark.get("/public/", (req, rsp) -> indexRenderer.render(Map.of()));
|
||||
Spark.get("/public/services", (req, rsp) -> servicesRenderer.render(
|
||||
Map.of("heartbeats", heartbeatService.getHeartbeats(),
|
||||
"events", eventLogService.getLastEntries(100)
|
||||
)));
|
||||
|
||||
Spark.get("/public/services", (req, rsp) -> servicesRenderer.render(Map.of("heartbeats", heartbeatService.getHeartbeats())));
|
||||
Spark.get("/public/events", (req, rsp) -> eventsRenderer.render(Map.of("events", eventLogService.getLastEntries(20))));
|
||||
Spark.get("/public/message-queue", (req, rsp) -> messageQueueRenderer.render(Map.of("messages", messageQueueViewService.getLastEntries(20))));
|
||||
|
||||
Spark.get("/public/repartition", (req, rsp) -> {
|
||||
controlProcesses.start("REPARTITION-REINDEX");
|
||||
return "OK";
|
||||
});
|
||||
|
||||
Spark.get("/public/:resource", this::serveStatic);
|
||||
|
||||
monitors.subscribe(this::logMonitorStateChange);
|
||||
|
||||
Thread reaperThread = new Thread(this::reapMessageQueue, "message-queue-reaper");
|
||||
@ -66,6 +80,16 @@ public class ControlService extends Service {
|
||||
reaperThread.start();
|
||||
}
|
||||
|
||||
|
||||
private Object serveStatic(Request request, Response response) {
|
||||
String resource = request.params("resource");
|
||||
|
||||
staticResources.serveStatic("control", resource, request, response);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
private void reapMessageQueue() {
|
||||
|
||||
for (;;) {
|
||||
|
@ -32,7 +32,7 @@ public class EventLogService {
|
||||
while (rs.next()) {
|
||||
entries.add(new EventLogEntry(
|
||||
rs.getString("SERVICE_NAME"),
|
||||
rs.getString("INSTANCE"),
|
||||
trimUUID(rs.getString("INSTANCE")),
|
||||
rs.getTimestamp("EVENT_TIME").toLocalDateTime().toLocalTime().toString(),
|
||||
rs.getString("EVENT_TYPE"),
|
||||
rs.getString("EVENT_MESSAGE")
|
||||
@ -44,6 +44,11 @@ public class EventLogService {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String trimUUID(String uuid) {
|
||||
if (uuid.length() > 8) {
|
||||
return uuid.substring(0, 8);
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public class HeartbeatService {
|
||||
heartbeats.add(new ServiceHeartbeat(
|
||||
rs.getString("SERVICE_NAME"),
|
||||
rs.getString("SERVICE_BASE"),
|
||||
rs.getString("INSTANCE"),
|
||||
trimUUID(rs.getString("INSTANCE")),
|
||||
rs.getInt("TSDIFF") / 1000.,
|
||||
rs.getBoolean("ALIVE")
|
||||
));
|
||||
@ -45,4 +45,11 @@ public class HeartbeatService {
|
||||
|
||||
return heartbeats;
|
||||
}
|
||||
|
||||
private String trimUUID(String uuid) {
|
||||
if (uuid.length() > 8) {
|
||||
return uuid.substring(0, 8);
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
package nu.marginalia.control;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import nu.marginalia.control.model.EventLogEntry;
|
||||
import nu.marginalia.control.model.MessageQueueEntry;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Singleton
|
||||
public class MessageQueueViewService {
|
||||
|
||||
private final HikariDataSource dataSource;
|
||||
|
||||
@Inject
|
||||
public MessageQueueViewService(HikariDataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
public List<MessageQueueEntry> getLastEntries(int n) {
|
||||
try (var conn = dataSource.getConnection();
|
||||
var query = conn.prepareStatement("""
|
||||
SELECT ID, RELATED_ID, SENDER_INBOX, RECIPIENT_INBOX, FUNCTION, OWNER_INSTANCE, OWNER_TICK, STATE, CREATED_TIME, UPDATED_TIME, TTL
|
||||
FROM PROC_MESSAGE
|
||||
ORDER BY ID DESC
|
||||
LIMIT ?
|
||||
""")) {
|
||||
|
||||
query.setInt(1, n);
|
||||
List<MessageQueueEntry> entries = new ArrayList<>(n);
|
||||
var rs = query.executeQuery();
|
||||
while (rs.next()) {
|
||||
entries.add(new MessageQueueEntry(
|
||||
rs.getLong("ID"),
|
||||
rs.getLong("RELATED_ID"),
|
||||
rs.getString("SENDER_INBOX"),
|
||||
rs.getString("RECIPIENT_INBOX"),
|
||||
rs.getString("FUNCTION"),
|
||||
trimUUID(rs.getString("OWNER_INSTANCE")),
|
||||
rs.getLong("OWNER_TICK"),
|
||||
rs.getString("STATE"),
|
||||
rs.getTimestamp("CREATED_TIME").toLocalDateTime().toLocalTime().toString(),
|
||||
rs.getTimestamp("UPDATED_TIME").toLocalDateTime().toLocalTime().toString(),
|
||||
rs.getInt("TTL")
|
||||
));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
private String trimUUID(String uuid) {
|
||||
if (null == uuid) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (uuid.length() > 8) {
|
||||
return uuid.substring(0, 8);
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package nu.marginalia.control.model;
|
||||
|
||||
public record MessageQueueEntry (
|
||||
long id,
|
||||
long relatedId,
|
||||
String senderInbox,
|
||||
String recipientInbox,
|
||||
String function,
|
||||
String ownerInstance,
|
||||
long ownerTick,
|
||||
String state,
|
||||
String createdTime,
|
||||
String updatedTime,
|
||||
int ttl
|
||||
)
|
||||
{
|
||||
}
|
@ -1,4 +1,38 @@
|
||||
body {
|
||||
font-family: serif;
|
||||
line-height: 1.6;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 20ch auto;
|
||||
grid-gap: 1em;
|
||||
grid-template-areas:
|
||||
"left right";
|
||||
}
|
||||
body > nav {
|
||||
grid-area: left;
|
||||
}
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
nav ul li {
|
||||
line-height: 2;
|
||||
}
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
padding: 0.5ch;
|
||||
display: block;
|
||||
color: #000;
|
||||
background-color: #ccc;
|
||||
}
|
||||
nav ul li a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
nav ul li a.current {
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
body > section {
|
||||
grid-area: right;
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Control Service</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
{{> control/partials/nav}}
|
||||
|
||||
<section>
|
||||
<h1>Events</h1>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Service Name</th>
|
||||
<th>Instance</th>
|
||||
<th>Event Time</th>
|
||||
<th>Type</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
{{#each events}}
|
||||
<tr>
|
||||
<td>{{serviceName}}</td>
|
||||
<td>{{instance}}</td>
|
||||
<td>{{eventTime}}</td>
|
||||
<td>{{eventType}}</td>
|
||||
<td>{{eventMessage}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Control Service</title>
|
||||
<viewport content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Control Service</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
{{> control/partials/nav}}
|
||||
|
||||
<section>
|
||||
<h1>Events</h1>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Message ID</th>
|
||||
<th>Related ID</th>
|
||||
<th>Recipient</th>
|
||||
<th>Sender</th>
|
||||
<th>Function</th>
|
||||
<th>Owner Instance</th>
|
||||
<th>Owner Tick</th>
|
||||
<th>State</th>
|
||||
<th>Created Time</th>
|
||||
<th>Updated Time</th>
|
||||
<th>TTL</th>
|
||||
</tr>
|
||||
{{#each messages}}
|
||||
<tr>
|
||||
<td>{{id}}</td>
|
||||
<td>{{relatedId}}</td>
|
||||
<td>{{recipientInbox}}</td>
|
||||
<td>{{senderInbox}}</td>
|
||||
<td>{{function}}</td>
|
||||
<td>{{ownerInstance}}</td>
|
||||
<td>{{ownerTick}}</td>
|
||||
<td>{{state}}</td>
|
||||
<td>{{createdTime}}</td>
|
||||
<td>{{updatedTime}}</td>
|
||||
<td>{{ttl}}</td>
|
||||
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
@ -2,6 +2,8 @@
|
||||
<ul>
|
||||
<li><a href="/">Overview</a></li>
|
||||
<li><a href="services">Services</a></li>
|
||||
<li><a href="events">Events</a></li>
|
||||
<li><a href="message-queue">Message Queue</a></li>
|
||||
<li><a href="processes">Processes</a></li>
|
||||
</ul>
|
||||
</nav>
|
@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Control Service</title>
|
||||
<viewport content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
@ -24,27 +24,6 @@
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
|
||||
<h2>Events</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Service Name</th>
|
||||
<th>Instance</th>
|
||||
<th>Event Time</th>
|
||||
<th>Type</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
{{#each events}}
|
||||
<tr>
|
||||
<td>{{serviceName}}</td>
|
||||
<td>{{instance}}</td>
|
||||
<td>{{eventTime}}</td>
|
||||
<td>{{eventType}}</td>
|
||||
<td>{{eventMessage}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
@ -98,6 +98,7 @@ services:
|
||||
container_name: "nginx-gw"
|
||||
ports:
|
||||
- "127.0.0.1:8080:80"
|
||||
- "127.0.0.1:8081:81"
|
||||
volumes:
|
||||
- "./run/nginx-site.conf:/etc/nginx/conf.d/default.conf"
|
||||
networks:
|
||||
|
@ -33,11 +33,27 @@ server {
|
||||
proxy_pass http://assistant-service:5025/public$request_uri;
|
||||
access_log off;
|
||||
}
|
||||
location /control/ {
|
||||
proxy_pass http://control-service:5090/public/;
|
||||
}
|
||||
location / {
|
||||
proxy_pass http://search-service:5023/public/;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
server {
|
||||
listen 81;
|
||||
listen [::]:81;
|
||||
server_name control;
|
||||
|
||||
proxy_set_header X-Context $remote_addr-$connection;
|
||||
proxy_set_header X-Extern-Url $scheme://$host$request_uri;
|
||||
proxy_set_header X-Extern-Domain $scheme://$host;
|
||||
proxy_set_header X-User-Agent $http_user_agent;
|
||||
|
||||
proxy_set_header X-Public "1";
|
||||
|
||||
location / {
|
||||
proxy_pass http://control-service:5090/public/;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user