• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eXist-db / exist / 41106813683

30 Oct 2026 06:25PM UTC coverage: 58.246% (+0.07%) from 58.179%
41106813683

push

github

web-flow
Merge pull request #6941 from joewiz/optimize/6895-direct-attribute-select

32560 of 61746 branches covered (52.73%)

Branch coverage included in aggregate %.

14 of 15 new or added lines in 2 files covered. (93.33%)

19 existing lines in 4 files now uncovered.

85668 of 141233 relevant lines covered (60.66%)

2.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

51.43
/exist-core/src/main/java/org/exist/http/ws/QueryMonitorBroadcaster.java
1
/*
2
 * eXist-db Open Source Native XML Database
3
 * Copyright (C) 2001 The eXist-db Authors
4
 *
5
 * info@exist-db.org
6
 * http://www.exist-db.org
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
 */
22
package org.exist.http.ws;
23

24
import com.fasterxml.jackson.core.JsonFactory;
25
import com.fasterxml.jackson.core.JsonGenerator;
26
import org.apache.logging.log4j.LogManager;
27
import org.apache.logging.log4j.Logger;
28
import org.exist.storage.BrokerPool;
29
import org.exist.storage.ProcessMonitor;
30
import org.exist.xquery.XQueryWatchDog;
31
import org.exist.xquery.functions.websocket.WebSocketEndpoint;
32

33
import javax.annotation.Nullable;
34
import java.io.IOException;
35
import java.io.StringWriter;
36

37
/**
38
 * Bridges the /ws/eval query lifecycle to the /ws pub/sub channel system,
39
 * broadcasting query events to admin monitoring clients subscribed to the
40
 * {@code _monitor} channel.
41
 *
42
 * <p>Also provides periodic snapshots of ALL running queries (including
43
 * REST/XQueryServlet queries that don't go through /ws/eval).</p>
44
 */
45
public final class QueryMonitorBroadcaster {
46

47
    private static final Logger LOG = LogManager.getLogger(QueryMonitorBroadcaster.class);
3 ✔
48
    private static final JsonFactory JSON_FACTORY = new JsonFactory();
4 ✔
49
    private static final String CHANNEL = "_monitor";
50
    private static final int MAX_SOURCE_LENGTH = 100;
1 ✔
51

52
    private QueryMonitorBroadcaster() {
53
        // utility class
54
    }
55

56
    /**
57
     * Broadcast a query lifecycle event to the _monitor channel.
58
     * No-op if nobody is subscribed.
59
     */
60
    public static void broadcastEvent(final String event, final String queryId,
61
                                       final String user, @Nullable final String query,
62
                                       @Nullable final String phase, final long items,
63
                                       final long elapsed) {
64
        if (WebSocketEndpoint.getChannelCount(CHANNEL) == 0) {
3 ✔
65
            return;
1 ✔
66
        }
67
        try {
68
            final String json = buildEventMessage(event, queryId, user, query, phase, items, elapsed);
9 ✔
69
            WebSocketEndpoint.sendAll(CHANNEL, json);
3 ✔
70
        } catch (final IOException e) {
1 ✔
71
            LOG.debug("Failed to broadcast monitor event: {}", e.getMessage());
×
72
        }
73
    }
1 ✔
74

75
    /**
76
     * Broadcast a snapshot of all running queries from the ProcessMonitor.
77
     * Called periodically (e.g., every 1 second) to catch queries from all
78
     * execution paths (REST, XQueryServlet, URL rewrite, etc.).
79
     */
80
    public static void broadcastSnapshot(final BrokerPool pool) {
81
        if (WebSocketEndpoint.getChannelCount(CHANNEL) == 0) {
3 !
82
            return;
1 ✔
83
        }
84
        try {
UNCOV
85
            final ProcessMonitor monitor = pool.getProcessMonitor();
×
UNCOV
86
            final XQueryWatchDog[] watchDogs = monitor.getRunningXQueries();
×
87

UNCOV
88
            final StringWriter sw = new StringWriter();
×
UNCOV
89
            try (final JsonGenerator gen = JSON_FACTORY.createGenerator(sw)) {
×
UNCOV
90
                gen.writeStartObject();
×
UNCOV
91
                gen.writeStringField("type", "monitor");
×
UNCOV
92
                gen.writeStringField("event", "snapshot");
×
UNCOV
93
                gen.writeArrayFieldStart("queries");
×
94

UNCOV
95
                for (final XQueryWatchDog wd : watchDogs) {
×
96
                    final var ctx = wd.getContext();
×
97
                    gen.writeStartObject();
×
98
                    gen.writeStringField("queryId", String.valueOf(System.identityHashCode(ctx)));
×
99
                    gen.writeStringField("user", ctx.getEffectiveUser().getName());
×
100

101
                    final String sourceKey = ctx.getSource() != null ? ctx.getSource().pathOrShortIdentifier() : "";
×
102
                    gen.writeStringField("source", sourceKey);
×
103

104
                    gen.writeStringField("phase", "evaluating");
×
105
                    gen.writeNumberField("elapsed",
×
106
                            System.currentTimeMillis() - wd.getStartTime());
×
107
                    gen.writeBooleanField("terminating", wd.isTerminating());
×
108
                    gen.writeEndObject();
×
109
                }
110

UNCOV
111
                gen.writeEndArray();
×
UNCOV
112
                gen.writeEndObject();
×
113
            }
114

UNCOV
115
            WebSocketEndpoint.sendAll(CHANNEL, sw.toString());
×
UNCOV
116
        } catch (final Exception e) {
×
117
            LOG.debug("Failed to broadcast monitor snapshot: {}", e.getMessage());
×
118
        }
UNCOV
119
    }
×
120

121
    private static String buildEventMessage(final String event, final String queryId,
122
                                             final String user, @Nullable final String query,
123
                                             @Nullable final String phase, final long items,
124
                                             final long elapsed) throws IOException {
125
        final StringWriter sw = new StringWriter();
4 ✔
126
        try (final JsonGenerator gen = JSON_FACTORY.createGenerator(sw)) {
8 ✔
127
            gen.writeStartObject();
2 ✔
128
            gen.writeStringField("type", "monitor");
4 ✔
129
            gen.writeStringField("event", event);
4 ✔
130
            gen.writeStringField("queryId", queryId);
4 ✔
131
            gen.writeStringField("user", user);
4 ✔
132
            if (query != null) {
2 ✔
133
                gen.writeStringField("source",
3 ✔
134
                        query.length() > MAX_SOURCE_LENGTH
4 !
135
                                ? query.substring(0, MAX_SOURCE_LENGTH) + "..."
×
136
                                : query);
1 ✔
137
            }
138
            if (phase != null) {
2 ✔
139
                gen.writeStringField("phase", phase);
4 ✔
140
            }
141
            gen.writeNumberField("items", items);
4 ✔
142
            gen.writeNumberField("elapsed", elapsed);
4 ✔
143
            gen.writeEndObject();
2 ✔
144
        }
145
        return sw.toString();
3 ✔
146
    }
147
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc