Skip to content

Commit 62625d6

Browse files
committed
Added defensive code for scenario where thread id <= 0
Signed-off-by: Doug Hoard <doug.hoard@gmail.com>
1 parent c61d2eb commit 62625d6

File tree

3 files changed

+115
-38
lines changed

3 files changed

+115
-38
lines changed

simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/ThreadExports.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import io.prometheus.client.Collector;
44
import io.prometheus.client.CounterMetricFamily;
55
import io.prometheus.client.GaugeMetricFamily;
6-
import io.prometheus.client.SampleNameFilter;
76
import io.prometheus.client.Predicate;
87

98
import java.lang.management.ManagementFactory;
@@ -12,8 +11,10 @@
1211
import java.util.ArrayList;
1312
import java.util.Collections;
1413
import java.util.HashMap;
14+
import java.util.HashSet;
1515
import java.util.List;
1616
import java.util.Map;
17+
import java.util.Set;
1718

1819
import static io.prometheus.client.SampleNameFilter.ALLOW_ALL;
1920

@@ -121,8 +122,26 @@ void addThreadMetrics(List<MetricFamilySamples> sampleFamilies, Predicate<String
121122
}
122123

123124
private Map<Thread.State, Integer> getThreadStateCountMap() {
125+
long[] threadIds = threadBean.getAllThreadIds();
126+
127+
// Code to remove any thread id values <= 0 and rebuild the array.
128+
// Because this code has to support Java 6, no ideal way to filter thread ids <= 0
129+
Set<Long> threadIdSet = new HashSet<Long>();
130+
for (Long threadId : threadIds) {
131+
if (threadId > 0) {
132+
threadIdSet.add(threadId);
133+
}
134+
}
135+
136+
int index = 0;
137+
threadIds = new long[threadIdSet.size()];
138+
for (Long threadId : threadIdSet) {
139+
threadIds[index] = threadId;
140+
index++;
141+
}
142+
124143
// Get thread information without computing any stack traces
125-
ThreadInfo[] allThreads = threadBean.getThreadInfo(threadBean.getAllThreadIds(), 0);
144+
ThreadInfo[] allThreads = threadBean.getThreadInfo(threadIds, 0);
126145

127146
// Initialize the map with all thread states
128147
HashMap<Thread.State, Integer> threadCounts = new HashMap<Thread.State, Integer>();

simpleclient_hotspot/src/test/java/io/prometheus/client/hotspot/ThreadExportsTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.lang.management.ThreadInfo;
99
import java.lang.management.ThreadMXBean;
1010
import java.util.Arrays;
11+
import java.util.concurrent.CountDownLatch;
1112

1213
import static org.junit.Assert.assertEquals;
1314
import static org.mockito.Mockito.when;
@@ -97,4 +98,42 @@ public void testThreadPools() {
9798
"jvm_threads_state", STATE_LABEL, STATE_TERMINATED_LABEL),
9899
.0000001);
99100
}
101+
102+
@Test
103+
public void testThreadId0() {
104+
final CountDownLatch countDownLatch = new CountDownLatch(1);
105+
106+
try {
107+
Runnable runnable = new Runnable() {
108+
@Override
109+
public void run() {
110+
try {
111+
countDownLatch.await();
112+
} catch (InterruptedException e) {
113+
// DO NOTHING
114+
}
115+
}
116+
};
117+
118+
Thread thread = new MyThread(runnable);
119+
thread.setDaemon(true);
120+
thread.start();
121+
122+
ThreadExports threadExports = new ThreadExports();
123+
threadExports.collect();
124+
} finally {
125+
countDownLatch.countDown();
126+
}
127+
}
128+
129+
class MyThread extends Thread {
130+
131+
public MyThread(Runnable runnable) {
132+
super(runnable);
133+
}
134+
135+
public long getId() {
136+
return 0;
137+
}
138+
}
100139
}

simpleclient_httpserver/src/main/java/io/prometheus/client/exporter/HTTPServer.java

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.concurrent.FutureTask;
2727
import java.util.concurrent.ThreadFactory;
2828
import java.util.concurrent.atomic.AtomicInteger;
29+
import java.util.logging.Logger;
2930
import java.util.zip.GZIPOutputStream;
3031

3132
import com.sun.net.httpserver.Authenticator;
@@ -68,6 +69,10 @@ protected ByteArrayOutputStream initialValue()
6869
* Handles Metrics collections from the given registry.
6970
*/
7071
public static class HTTPMetricHandler implements HttpHandler {
72+
73+
private static final Logger LOGGER = Logger.getLogger(HTTPMetricHandler.class.getName());
74+
private static final String INTERNAL_SERVER_ERROR = "Internal Server Error";
75+
7176
private final CollectorRegistry registry;
7277
private final LocalByteArray response = new LocalByteArray();
7378
private final Supplier<Predicate<String>> sampleNameFilterSupplier;
@@ -83,49 +88,63 @@ public HTTPMetricHandler(CollectorRegistry registry, Supplier<Predicate<String>>
8388
}
8489

8590
@Override
86-
public void handle(HttpExchange t) throws IOException {
87-
String query = t.getRequestURI().getRawQuery();
88-
String contextPath = t.getHttpContext().getPath();
89-
ByteArrayOutputStream response = this.response.get();
90-
response.reset();
91-
OutputStreamWriter osw = new OutputStreamWriter(response, Charset.forName("UTF-8"));
92-
if ("/-/healthy".equals(contextPath)) {
93-
osw.write(HEALTHY_RESPONSE);
94-
} else {
95-
String contentType = TextFormat.chooseContentType(t.getRequestHeaders().getFirst("Accept"));
96-
t.getResponseHeaders().set("Content-Type", contentType);
97-
Predicate<String> filter = sampleNameFilterSupplier == null ? null : sampleNameFilterSupplier.get();
98-
filter = SampleNameFilter.restrictToNamesEqualTo(filter, parseQuery(query));
99-
if (filter == null) {
100-
TextFormat.writeFormat(contentType, osw, registry.metricFamilySamples());
91+
public void handle(HttpExchange httpExchange) {
92+
try {
93+
String query = httpExchange.getRequestURI().getRawQuery();
94+
String contextPath = httpExchange.getHttpContext().getPath();
95+
ByteArrayOutputStream response = this.response.get();
96+
response.reset();
97+
OutputStreamWriter osw = new OutputStreamWriter(response, Charset.forName("UTF-8"));
98+
if ("/-/healthy".equals(contextPath)) {
99+
osw.write(HEALTHY_RESPONSE);
101100
} else {
102-
TextFormat.writeFormat(contentType, osw, registry.filteredMetricFamilySamples(filter));
101+
String contentType = TextFormat.chooseContentType(httpExchange.getRequestHeaders().getFirst("Accept"));
102+
httpExchange.getResponseHeaders().set("Content-Type", contentType);
103+
Predicate<String> filter = sampleNameFilterSupplier == null ? null : sampleNameFilterSupplier.get();
104+
filter = SampleNameFilter.restrictToNamesEqualTo(filter, parseQuery(query));
105+
if (filter == null) {
106+
TextFormat.writeFormat(contentType, osw, registry.metricFamilySamples());
107+
} else {
108+
TextFormat.writeFormat(contentType, osw, registry.filteredMetricFamilySamples(filter));
109+
}
103110
}
104-
}
105111

106-
osw.close();
112+
osw.close();
113+
114+
if (shouldUseCompression(httpExchange)) {
115+
httpExchange.getResponseHeaders().set("Content-Encoding", "gzip");
116+
httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
117+
final GZIPOutputStream os = new GZIPOutputStream(httpExchange.getResponseBody());
118+
try {
119+
response.writeTo(os);
120+
} finally {
121+
os.close();
122+
}
123+
} else {
124+
long contentLength = response.size();
125+
if (contentLength > 0) {
126+
httpExchange.getResponseHeaders().set("Content-Length", String.valueOf(contentLength));
127+
}
128+
if (httpExchange.getRequestMethod().equals("HEAD")) {
129+
contentLength = -1;
130+
}
131+
httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, contentLength);
132+
response.writeTo(httpExchange.getResponseBody());
133+
}
134+
httpExchange.close();
135+
} catch (Throwable throwable) {
136+
LOGGER.warning("Exception handling request, " + throwable.getMessage());
137+
LOGGER.throwing(HTTPMetricHandler.class.getName(), "handle", throwable);
107138

108-
if (shouldUseCompression(t)) {
109-
t.getResponseHeaders().set("Content-Encoding", "gzip");
110-
t.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
111-
final GZIPOutputStream os = new GZIPOutputStream(t.getResponseBody());
112139
try {
113-
response.writeTo(os);
114-
} finally {
115-
os.close();
116-
}
117-
} else {
118-
long contentLength = response.size();
119-
if (contentLength > 0) {
120-
t.getResponseHeaders().set("Content-Length", String.valueOf(contentLength));
121-
}
122-
if (t.getRequestMethod().equals("HEAD")) {
123-
contentLength = -1;
140+
byte[] internalServerErrorBytes = INTERNAL_SERVER_ERROR.getBytes("UTF-8");
141+
httpExchange.getRequestHeaders().set("Content-Type", "text/plain");
142+
httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_INTERNAL_ERROR, internalServerErrorBytes.length);
143+
httpExchange.getResponseBody().write(internalServerErrorBytes);
144+
} catch (Throwable throwable2) {
145+
// Ignore since the response may already be partially committed
124146
}
125-
t.sendResponseHeaders(HttpURLConnection.HTTP_OK, contentLength);
126-
response.writeTo(t.getResponseBody());
127147
}
128-
t.close();
129148
}
130149
}
131150

0 commit comments

Comments
 (0)