(control) Helpful tooltips for the Actor table.

This commit is contained in:
Viktor Lofgren 2023-08-13 12:55:49 +02:00
parent e51bf8619d
commit 8210e49b4e
21 changed files with 119 additions and 30 deletions

View File

@ -59,9 +59,9 @@ public class ActorStateMachine {
registerStates(stateGraph);
isDirectlyInitializable = stateGraph.isDirectlyInitializable();
for (var declaredState : stateGraph.declaredStates()) {
if (!allStates.containsKey(declaredState.name())) {
throw new IllegalArgumentException("State " + declaredState.name() + " is not defined in the state graph");
stateGraph.declaredStates().forEach((name, declaredState) -> {
if (!allStates.containsKey(name)) {
throw new IllegalArgumentException("State " + name + " is not defined in the state graph");
}
if (!allStates.containsKey(declaredState.next())) {
throw new IllegalArgumentException("State " + declaredState.next() + " is not defined in the state graph");
@ -71,7 +71,7 @@ public class ActorStateMachine {
throw new IllegalArgumentException("State " + state + " is not defined in the state graph");
}
}
}
});
resume();

View File

@ -18,6 +18,9 @@ public abstract class AbstractStateGraph {
this.stateFactory = stateFactory;
}
/** User-facing description of the actor. */
public abstract String describe();
public void transition(String state) {
throw new ControlFlowException(state, null);
}
@ -30,7 +33,6 @@ public abstract class AbstractStateGraph {
throw new ControlFlowException("ERROR", "");
}
public <T> void error(T payload) {
throw new ControlFlowException("ERROR", payload);
}
@ -54,13 +56,13 @@ public abstract class AbstractStateGraph {
return false;
}
public Set<GraphState> declaredStates() {
Set<GraphState> ret = new HashSet<>();
public Map<String, GraphState> declaredStates() {
Map<String, GraphState> ret = new HashMap<>();
for (var method : getClass().getMethods()) {
var gs = method.getAnnotation(GraphState.class);
if (gs != null) {
ret.add(gs);
ret.put(gs.name(), gs);
}
}

View File

@ -3,7 +3,7 @@ package nu.marginalia.control.actor;
public enum Actor {
CRAWL,
RECRAWL,
RECONVERT_LOAD,
CONVERT_AND_LOAD,
CONVERTER_MONITOR,
LOADER_MONITOR,
CRAWLER_MONITOR,

View File

@ -35,7 +35,7 @@ public class ControlActors {
GsonFactory gsonFactory,
BaseServiceParams baseServiceParams,
ConvertActor convertActor,
ReconvertAndLoadActor reconvertAndLoadActor,
ConvertAndLoadActor convertAndLoadActor,
CrawlActor crawlActor,
RecrawlActor recrawlActor,
ConverterMonitorActor converterMonitorFSM,
@ -56,7 +56,7 @@ public class ControlActors {
register(Actor.CRAWL, crawlActor);
register(Actor.RECRAWL, recrawlActor);
register(Actor.CONVERT, convertActor);
register(Actor.RECONVERT_LOAD, reconvertAndLoadActor);
register(Actor.CONVERT_AND_LOAD, convertAndLoadActor);
register(Actor.CONVERTER_MONITOR, converterMonitorFSM);
register(Actor.LOADER_MONITOR, loaderMonitor);

View File

@ -40,6 +40,10 @@ public class AbstractProcessSpawnerActor extends AbstractStateGraph {
private final ProcessService.ProcessId processId;
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
public String describe() {
return "Spawns a(n) " + processId + " process and monitors its inbox for messages";
}
@Inject
public AbstractProcessSpawnerActor(StateFactory stateFactory,
MqPersistence persistence,

View File

@ -34,6 +34,11 @@ public class FileStorageMonitorActor extends AbstractStateGraph {
private static final String END = "END";
private final FileStorageService fileStorageService;
@Override
public String describe() {
return "Monitor the file storage directories and purge any file storage area that has been marked for deletion," +
" and remove any file storage area that is missing from disk.";
}
@Inject
public FileStorageMonitorActor(StateFactory stateFactory,

View File

@ -20,6 +20,10 @@ public class MessageQueueMonitorActor extends AbstractStateGraph {
private static final String END = "END";
private final MqPersistence persistence;
@Override
public String describe() {
return "Periodically run maintenance tasks on the message queue";
}
@Inject
public MessageQueueMonitorActor(StateFactory stateFactory,

View File

@ -34,6 +34,11 @@ public class ProcessLivenessMonitorActor extends AbstractStateGraph {
this.heartbeatService = heartbeatService;
}
@Override
public String describe() {
return "Periodically check to ensure that the control service's view of running processes is agreement with the process heartbeats table.";
}
@GraphState(name = INITIAL, next = MONITOR)
public void init() {
}

View File

@ -52,6 +52,11 @@ public class ConvertActor extends AbstractStateGraph {
public long loaderMsgId = 0L;
};
@Override
public String describe() {
return "Convert a set of crawl data into a format suitable for loading into the database.";
}
@Inject
public ConvertActor(StateFactory stateFactory,
ActorProcessWatcher processWatcher,

View File

@ -30,7 +30,7 @@ import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
@Singleton
public class ReconvertAndLoadActor extends AbstractStateGraph {
public class ConvertAndLoadActor extends AbstractStateGraph {
// STATES
@ -63,13 +63,18 @@ public class ReconvertAndLoadActor extends AbstractStateGraph {
public long loaderMsgId = 0L;
};
@Override
public String describe() {
return "Process a set of crawl data and then load it into the database.";
}
@Inject
public ReconvertAndLoadActor(StateFactory stateFactory,
ActorProcessWatcher processWatcher,
ProcessOutboxes processOutboxes,
FileStorageService storageService,
IndexClient indexClient,
Gson gson
public ConvertAndLoadActor(StateFactory stateFactory,
ActorProcessWatcher processWatcher,
ProcessOutboxes processOutboxes,
FileStorageService storageService,
IndexClient indexClient,
Gson gson
)
{
super(stateFactory);

View File

@ -46,6 +46,11 @@ public class CrawlActor extends AbstractStateGraph {
public long crawlerMsgId = 0L;
};
@Override
public String describe() {
return "Run the crawler with the given crawl spec using no previous crawl data for a reference";
}
@Inject
public CrawlActor(StateFactory stateFactory,
ProcessOutboxes processOutboxes,

View File

@ -52,6 +52,11 @@ public class CrawlJobExtractorActor extends AbstractStateGraph {
public record CrawlJobExtractorArguments(String description) { }
public record CrawlJobExtractorArgumentsWithURL(String description, String url) { }
@Override
public String describe() {
return "Run the crawler job extractor process";
}
@GraphState(name = CREATE_FROM_LINK, next = END,
resume = ResumeBehavior.ERROR,
description = """

View File

@ -48,6 +48,11 @@ public class ExportDataActor extends AbstractStateGraph {
public FileStorageId storageId = null;
};
@Override
public String describe() {
return "Export data from the database to a storage area of type EXPORT.";
}
@Inject
public ExportDataActor(StateFactory stateFactory,
FileStorageService storageService,

View File

@ -46,6 +46,10 @@ public class RecrawlActor extends AbstractStateGraph {
public long crawlerMsgId = 0L;
};
@Override
public String describe() {
return "Run the crawler with the given crawl spec using previous crawl data for a reference";
}
public static RecrawlMessage recrawlFromCrawlData(FileStorageId crawlData) {
return new RecrawlMessage(null, crawlData, 0L);
}

View File

@ -32,6 +32,11 @@ public class TriggerAdjacencyCalculationActor extends AbstractStateGraph {
this.processService = processService;
}
@Override
public String describe() {
return "Calculate website similarities";
}
@GraphState(name = INITIAL, next = END,
resume = ResumeBehavior.ERROR,
description = """

View File

@ -33,6 +33,11 @@ public class TruncateLinkDatabase extends AbstractStateGraph {
public FileStorageId storageId = null;
};
@Override
public String describe() {
return "Remove all data from the link database.";
}
@Inject
public TruncateLinkDatabase(StateFactory stateFactory,
HikariDataSource dataSource)

View File

@ -1,6 +1,11 @@
package nu.marginalia.control.model;
public record ActorRunState(String name, String state, boolean terminal, boolean canStart) {
public record ActorRunState(String name,
String state,
String actorDescription,
String stateDescription,
boolean terminal,
boolean canStart) {
public String stateIcon() {
if (terminal) {
return "\uD83D\uDE34";

View File

@ -7,17 +7,17 @@ import nu.marginalia.mqsm.state.MachineState;
import java.util.*;
import java.util.stream.Collectors;
public record ActorStateGraph(List<ActorState> states) {
public record ActorStateGraph(String description, List<ActorState> states) {
public ActorStateGraph(AbstractStateGraph graph, MachineState currentState) {
this(getStateList(graph, currentState));
this(graph.describe(), getStateList(graph, currentState));
}
private static List<ActorState> getStateList(
AbstractStateGraph graph,
MachineState currentState)
{
Map<String, GraphState> declaredStates = graph.declaredStates().stream().collect(Collectors.toMap(GraphState::name, gs -> gs));
Map<String, GraphState> declaredStates = graph.declaredStates();
Set<GraphState> seenStates = new HashSet<>(declaredStates.size());
LinkedList<GraphState> edge = new LinkedList<>();

View File

@ -4,17 +4,20 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
import nu.marginalia.control.actor.ControlActors;
import nu.marginalia.control.actor.task.CrawlJobExtractorActor;
import nu.marginalia.control.actor.task.ReconvertAndLoadActor;
import nu.marginalia.control.actor.task.ConvertAndLoadActor;
import nu.marginalia.control.actor.task.RecrawlActor;
import nu.marginalia.control.actor.Actor;
import nu.marginalia.control.model.ActorRunState;
import nu.marginalia.control.model.ActorStateGraph;
import nu.marginalia.db.storage.model.FileStorageId;
import nu.marginalia.mqsm.graph.GraphState;
import nu.marginalia.mqsm.state.MachineState;
import spark.Request;
import spark.Response;
import java.util.Comparator;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@Singleton
public class ControlActorService {
@ -64,7 +67,7 @@ public class ControlActorService {
}
public Object triggerProcessing(Request request, Response response) throws Exception {
controlActors.start(
Actor.RECONVERT_LOAD,
Actor.CONVERT_AND_LOAD,
FileStorageId.parse(request.params("fid"))
);
return "";
@ -75,24 +78,45 @@ public class ControlActorService {
// Start the FSM from the intermediate state that triggers the load
controlActors.startFrom(
Actor.RECONVERT_LOAD,
ReconvertAndLoadActor.LOAD,
new ReconvertAndLoadActor.Message(null, fid, 0L, 0L)
Actor.CONVERT_AND_LOAD,
ConvertAndLoadActor.LOAD,
new ConvertAndLoadActor.Message(null, fid, 0L, 0L)
);
return "";
}
private final ConcurrentHashMap<String, String> actorStateDescriptions = new ConcurrentHashMap<>();
public Object getActorStates() {
return controlActors.getActorStates().entrySet().stream().map(e -> {
final var stateGraph = controlActors.getActorDefinition(e.getKey());
final MachineState state = e.getValue();
final String actorDescription = stateGraph.describe();
final String machineName = e.getKey().name();
final String stateName = state.name();
final String stateDescription = actorStateDescriptions.computeIfAbsent(
(machineName + "." + stateName),
k -> Optional.ofNullable(stateGraph.declaredStates().get(stateName))
.map(GraphState::description)
.orElse("Description missing for " + stateName)
);
final boolean terminal = state.isFinal();
final boolean canStart = controlActors.isDirectlyInitializable(e.getKey()) && terminal;
return new ActorRunState(machineName, stateName, terminal, canStart);
return new ActorRunState(machineName,
stateName,
actorDescription,
stateDescription,
terminal,
canStart);
})
.filter(s -> !s.terminal() || s.canStart())
.sorted(Comparator.comparing(ActorRunState::name))

View File

@ -9,6 +9,7 @@
{{> control/partials/nav}}
<section>
<h1>{{actor}}</h1>
<p>{{state-graph.description}}</p>
{{> control/partials/actor-state-graph}}
{{> control/partials/message-queue-table}}
</section>

View File

@ -7,8 +7,8 @@
</tr>
{{#each actors}}
<tr>
<td><a href="/actors/{{name}}">{{name}}</a></td>
<td>{{stateIcon}}&nbsp;{{state}}</td>
<td title="{{actorDescription}}"><a href="/actors/{{name}}">{{name}}</a></td>
<td title="{{stateDescription}}">{{stateIcon}}&nbsp;{{state}}</td>
<td>
{{#unless terminal}}
<form id="toggle-{{name}}"