Skip to content

Commit c93a024

Browse files
authored
[service_discovery] enabling service discovery via named pipe. (#113)
* [service_discovery] enabling service discovery via named pipe. [service_discovery] fixing configuration. [service_discovery] store SD configs in its own cache. [service_discovery] use named pipes to receive service discovery configs. [service_discovery] parsing pipe input into config. [service_discovery] move relevant options to AppConfig. [service_discovery] fixing up service discovery config traversal. [service_discovery] only remove config from list if not a service discovery received config. [logging] prettify logging statement. * [service_discovery] making Java 6 compatible. * [service_discovery] cleanup. * [jmxfetch] camelcase those vars... * [jmxfetch][service_discovery] adding service discovery configuration test. * [jmxfetch][service_discovery] regex lookbehind was seriously broken in older java 1.6. [jmxfetch][service_discovery] fixing regex to be more Java6 friendly. * [travis-ci] we should probably start testing against jdk8. * [jmxfetch][skip ci] change scope of mockito dependency to test only.
1 parent e69217c commit c93a024

File tree

7 files changed

+337
-14
lines changed

7 files changed

+337
-14
lines changed

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
language: java
22
jdk:
3+
- oraclejdk8
34
- oraclejdk7
45
- openjdk7
56
- openjdk6

pom.xml

+15-1
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
<properties>
1515
<commons-io.version>2.4</commons-io.version>
1616
<commons-lang.version>2.6</commons-lang.version>
17+
<apache-commons-lang3.version>3.5</apache-commons-lang3.version>
1718
<guava.version>17.0</guava.version>
1819
<java-dogstatsd-client.version>2.1.0</java-dogstatsd-client.version>
1920
<jcommander.version>1.35</jcommander.version>
2021
<junit.version>4.11</junit.version>
2122
<log4j.version>1.2.17</log4j.version>
23+
<mockito.version>2.2.27</mockito.version>
2224
<maven-surefire-plugin.version>2.9</maven-surefire-plugin.version>
2325
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
2426
<snakeyaml.version>1.13</snakeyaml.version>
@@ -59,7 +61,13 @@
5961
<groupId>commons-lang</groupId>
6062
<artifactId>commons-lang</artifactId>
6163
<version>${commons-lang.version}</version>
62-
</dependency>
64+
</dependency>
65+
<dependency>
66+
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
67+
<groupId>org.apache.commons</groupId>
68+
<artifactId>commons-lang3</artifactId>
69+
<version>${apache-commons-lang3.version}</version>
70+
</dependency>
6371
<dependency>
6472
<groupId>com.datadoghq</groupId>
6573
<artifactId>java-dogstatsd-client</artifactId>
@@ -95,6 +103,12 @@
95103
<artifactId>snakeyaml</artifactId>
96104
<version>${snakeyaml.version}</version>
97105
</dependency>
106+
<dependency>
107+
<groupId>org.mockito</groupId>
108+
<artifactId>mockito-core</artifactId>
109+
<version>${mockito.version}</version>
110+
<scope>test</scope>
111+
</dependency>
98112
</dependencies>
99113
<scm>
100114
<connection>scm:git:git@github.com:Datadog/jmxfetch.git</connection>

src/main/java/org/datadog/jmxfetch/App.java

+136-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
package org.datadog.jmxfetch;
22

3+
import java.io.ByteArrayInputStream;
34
import java.io.File;
45
import java.io.FileInputStream;
6+
import java.io.FileFilter;
57
import java.io.FileNotFoundException;
8+
import java.io.InputStream;
69
import java.io.IOException;
10+
import java.io.UnsupportedEncodingException;
11+
import java.util.Arrays;
712
import java.util.ArrayList;
813
import java.util.Enumeration;
914
import java.util.HashMap;
15+
import java.util.concurrent.ConcurrentHashMap;
16+
import java.util.concurrent.atomic.AtomicBoolean;
17+
import java.util.regex.Pattern;
18+
import java.util.regex.Matcher;
1019
import java.util.Iterator;
1120
import java.util.LinkedHashMap;
1221
import java.util.LinkedList;
@@ -19,6 +28,7 @@
1928
import org.apache.log4j.Appender;
2029
import org.apache.log4j.Level;
2130
import org.apache.log4j.Logger;
31+
import org.apache.commons.lang3.CharEncoding;
2232
import org.datadog.jmxfetch.reporter.Reporter;
2333
import org.datadog.jmxfetch.util.CustomLogger;
2434

@@ -29,9 +39,13 @@
2939
@SuppressWarnings("unchecked")
3040
public class App {
3141
private final static Logger LOGGER = Logger.getLogger(App.class.getName());
42+
private final static String SERVICE_DISCOVERY_PREFIX = "SD-";
3243
public static final String CANNOT_CONNECT_TO_INSTANCE = "Cannot connect to instance ";
44+
private static final String SD_CONFIG_SEP = "#### SERVICE-DISCOVERY ####";
3345
private static int loopCounter;
34-
private HashMap<String, YamlParser> configs;
46+
private AtomicBoolean reinit = new AtomicBoolean(false);
47+
private ConcurrentHashMap<String, YamlParser> configs;
48+
private ConcurrentHashMap<String, YamlParser> sdConfigs = new ConcurrentHashMap<String, YamlParser>();
3549
private ArrayList<Instance> instances = new ArrayList<Instance>();
3650
private LinkedList<Instance> brokenInstances = new LinkedList<Instance>();
3751
private AppConfig appConfig;
@@ -132,6 +146,10 @@ public void run() {
132146
new ShutdownHook().attachShutDownHook();
133147
}
134148

149+
public void setReinit(boolean reinit) {
150+
this.reinit.set(reinit);
151+
}
152+
135153
public static int getLoopCounter() {
136154
return loopCounter;
137155
}
@@ -145,16 +163,84 @@ private static void clearInstances(List<Instance> instances) {
145163
}
146164
}
147165

166+
private String getSDName(String config){
167+
String[] splitted = config.split(System.getProperty("line.separator"), 2);
168+
169+
return SERVICE_DISCOVERY_PREFIX + splitted[0].substring(2, splitted[0].length());
170+
}
171+
172+
public boolean processServiceDiscovery(byte[] buffer) {
173+
boolean reinit = false;
174+
String[] discovered;
175+
176+
try {
177+
String configs = new String(buffer, CharEncoding.UTF_8);
178+
discovered = configs.split(App.SD_CONFIG_SEP + System.getProperty("line.separator"));
179+
} catch(UnsupportedEncodingException e) {
180+
LOGGER.debug("Unable to parse byte buffer to UTF-8 String.");
181+
return false;
182+
}
183+
184+
for (String config : discovered) {
185+
if (config == null || config.isEmpty()) {
186+
continue;
187+
}
188+
189+
try{
190+
String name = getSDName(config);
191+
LOGGER.debug("Attempting to apply config. Name: " + name + "\nconfig: \n" + config);
192+
InputStream stream = new ByteArrayInputStream(config.getBytes(CharEncoding.UTF_8));
193+
YamlParser yaml = new YamlParser(stream);
194+
195+
if (this.addConfig(name, yaml)){
196+
reinit = true;
197+
LOGGER.debug("Configuration added succesfully reinit in order");
198+
}
199+
} catch(UnsupportedEncodingException e) {
200+
LOGGER.debug("Unable to parse byte buffer to UTF-8 String.");
201+
}
202+
}
203+
204+
return reinit;
205+
}
206+
148207
void start() {
149208
// Main Loop that will periodically collect metrics from the JMX Server
209+
long start_ms = System.currentTimeMillis();
210+
long delta_s = 0;
211+
FileInputStream sdPipe = null;
212+
213+
try {
214+
sdPipe = new FileInputStream(appConfig.getServiceDiscoveryPipe()); //Should we use RandomAccessFile?
215+
} catch (FileNotFoundException e) {
216+
LOGGER.warn("Unable to open named pipe - Service Discovery disabled.");
217+
sdPipe = null;
218+
}
219+
150220
while (true) {
151-
// Exit on exit file trigger
221+
// Exit on exit file trigger...
152222
if (appConfig.getExitWatcher().shouldExit()){
153223
LOGGER.info("Exit file detected: stopping JMXFetch.");
154224
System.exit(0);
155225
}
156226

227+
// any SD configs waiting in pipe?
228+
try {
229+
if(sdPipe != null && sdPipe.available() > 0) {
230+
int len = sdPipe.available();
231+
byte[] buffer = new byte[len];
232+
sdPipe.read(buffer);
233+
setReinit(processServiceDiscovery(buffer));
234+
}
235+
} catch(IOException e) {
236+
LOGGER.warn("Unable to read from pipe - Service Discovery configuration may have been skipped.");
237+
}
238+
157239
long start = System.currentTimeMillis();
240+
if (this.reinit.get()) {
241+
init(true);
242+
}
243+
158244
if (instances.size() > 0) {
159245
doIteration();
160246
} else {
@@ -279,10 +365,39 @@ public void doIteration() {
279365
}
280366
}
281367

282-
private HashMap<String, YamlParser> getConfigs(AppConfig config) {
283-
HashMap<String, YamlParser> configs = new HashMap<String, YamlParser>();
368+
public boolean addConfig(String name, YamlParser config) {
369+
// named groups not supported with Java6: "(?<check>.{1,30})_(?<version>\\d{0,30})"
370+
Pattern pattern = Pattern.compile(SERVICE_DISCOVERY_PREFIX+"(.{1,30})_(\\d{0,30})");
371+
372+
Matcher matcher = pattern.matcher(name);
373+
if (!matcher.find()) {
374+
// bad name.
375+
return false;
376+
}
377+
378+
// Java 6 doesn't allow name matching - group 1 is "check"
379+
String check = matcher.group(1);
380+
if (this.configs.containsKey(check)) {
381+
// there was already a file config for the check.
382+
return false;
383+
}
384+
385+
this.sdConfigs.put(name, config);
386+
this.setReinit(true);
387+
388+
return true;
389+
}
390+
391+
private ConcurrentHashMap<String, YamlParser> getConfigs(AppConfig config) {
392+
ConcurrentHashMap<String, YamlParser> configs = new ConcurrentHashMap<String, YamlParser>();
284393
YamlParser fileConfig;
285-
for (String fileName : config.getYamlFileList()) {
394+
395+
List<String> fileList = config.getYamlFileList();
396+
if (fileList == null) {
397+
return configs;
398+
}
399+
400+
for (String fileName : fileList) {
286401
File f = new File(config.getConfdDirectory(), fileName);
287402
String name = f.getName().replace(".yaml", "");
288403
FileInputStream yamlInputStream = null;
@@ -306,6 +421,7 @@ private HashMap<String, YamlParser> getConfigs(AppConfig config) {
306421
}
307422
}
308423
}
424+
309425
LOGGER.info("Found " + configs.size() + " config files");
310426
return configs;
311427
}
@@ -336,11 +452,23 @@ public void init(boolean forceNewConnection) {
336452

337453

338454
Iterator<Entry<String, YamlParser>> it = configs.entrySet().iterator();
339-
while (it.hasNext()) {
340-
Map.Entry<String, YamlParser> entry = it.next();
455+
// SD config cache doesn't remove configs - it just overwrites.
456+
Iterator<Entry<String, YamlParser>> itSD = sdConfigs.entrySet().iterator();
457+
while (it.hasNext() || itSD.hasNext()) {
458+
Map.Entry<String, YamlParser> entry;
459+
boolean sdIterator = false;
460+
if (it.hasNext()) {
461+
entry = it.next();
462+
} else {
463+
entry = itSD.next();
464+
sdIterator = true;
465+
}
466+
341467
String name = entry.getKey();
342468
YamlParser yamlConfig = entry.getValue();
343-
it.remove();
469+
if(!sdIterator) {
470+
it.remove();
471+
}
344472

345473
ArrayList<LinkedHashMap<String, Object>> configInstances = ((ArrayList<LinkedHashMap<String, Object>>) yamlConfig.getYamlInstances());
346474
if (configInstances == null || configInstances.size() == 0) {

src/main/java/org/datadog/jmxfetch/AppConfig.java

+38-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ class AppConfig {
2828
public static final HashSet<String> ACTIONS = new HashSet<String>(Arrays.asList(ACTION_COLLECT, ACTION_LIST_EVERYTHING,
2929
ACTION_LIST_COLLECTED, ACTION_LIST_MATCHING, ACTION_LIST_NOT_MATCHING, ACTION_LIST_LIMITED, ACTION_HELP, ACTION_LIST_JVMS));
3030

31+
private static final String SD_WIN_PIPE_PATH = "\\\\.\\pipe\\";
32+
private static final String SD_PIPE_NAME = "dd-service_discovery";
33+
3134
@Parameter(names = {"--help", "-h"},
3235
description = "Display this help page",
3336
help = true)
@@ -49,6 +52,11 @@ class AppConfig {
4952
required = true)
5053
private String confdDirectory;
5154

55+
@Parameter(names = {"--tmp_directory", "-T"},
56+
description = "Absolute path to a temporary directory",
57+
required = false)
58+
private String tmpDirectory = "/tmp";
59+
5260
@Parameter(names = {"--reporter", "-r"},
5361
description = "Reporter to use: should be either \"statsd:[STATSD_PORT]\" or \"console\"",
5462
validateWith = ReporterValidator.class,
@@ -58,7 +66,7 @@ class AppConfig {
5866

5967
@Parameter(names = {"--check", "-c"},
6068
description = "Yaml file name to read (must be in the confd directory)",
61-
required = true,
69+
required = false,
6270
variableArity = true)
6371
private List<String> yamlFileList;
6472

@@ -68,6 +76,16 @@ class AppConfig {
6876
required = false)
6977
private int checkPeriod = 15000;
7078

79+
@Parameter(names = {"--sd_standby", "-w"},
80+
description = "Service Discovery standby.",
81+
required = false)
82+
private boolean sdStandby = false;
83+
84+
@Parameter(names = {"--sd_pipe", "-S"},
85+
description = "Service Discovery pipe name.",
86+
required = false)
87+
private String sdPipe = SD_PIPE_NAME;
88+
7189
@Parameter(names = {"--status_location", "-s"},
7290
description = "Absolute path of the status file. (default to null = no status file written)",
7391
converter = StatusConverter.class,
@@ -110,6 +128,10 @@ public int getCheckPeriod() {
110128
return checkPeriod;
111129
}
112130

131+
public boolean getSDStandby() {
132+
return sdStandby;
133+
}
134+
113135
public Reporter getReporter() {
114136
return reporter;
115137
}
@@ -122,11 +144,26 @@ public String getConfdDirectory() {
122144
return confdDirectory;
123145
}
124146

147+
public String getTmpDirectory() {
148+
return tmpDirectory;
149+
}
150+
125151
public String getLogLevel() {
126152
return logLevel;
127153
}
128154

129155
public String getLogLocation() {
130156
return logLocation;
131157
}
158+
159+
public String getServiceDiscoveryPipe() {
160+
String pipePath;
161+
162+
if (System.getProperty("os.name").startsWith("Windows")) {
163+
pipePath = SD_WIN_PIPE_PATH + "/" + sdPipe;
164+
} else {
165+
pipePath = getTmpDirectory() + "/" + sdPipe;
166+
}
167+
return pipePath;
168+
}
132169
}

0 commit comments

Comments
 (0)