package com.librato.metrics.client; import java.net.URI; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; /** * The main class that should be used to access the Librato API */ public class LibratoClient { private static final String LIB_VERSION = Versions.getVersion("META-INF/maven/com.librato.metrics/librato-java/pom.properties", LibratoClient.class); private final URI uri; private final int batchSize; private final Duration connectTimeout; private final Duration readTimeout; private final IPoster poster; private final ExecutorService executor; private final SDResponseConverter sdResponseConverter = new SDResponseConverter(); private final MDResponseConverter mdResponseConverter = new MDResponseConverter(); private final Map<String, String> measurementPostHeaders = new HashMap<String, String>(); public static LibratoClientBuilder builder(String email, String token) { return new LibratoClientBuilder(email, token); } LibratoClient(LibratoClientAttributes attrs) { this.uri = URIs.removePath(attrs.uri); this.batchSize = attrs.batchSize; this.connectTimeout = attrs.connectTimeout; this.readTimeout = attrs.readTimeout; this.poster = attrs.poster; BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>(); ThreadFactory threadFactory = new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("librato-client"); thread.setDaemon(true); return thread; } }; this.executor = new ThreadPoolExecutor(0, attrs.maxInflightRequests, 10, TimeUnit.SECONDS, queue, threadFactory, new CallerRunsPolicy()); measurementPostHeaders.put("Content-Type", "application/json"); measurementPostHeaders.put("Authorization", Authorization.buildAuthHeader(attrs.email, attrs.token)); measurementPostHeaders.put("User-Agent", String.format("%s librato-java/%s", attrs.agentIdentifier, LIB_VERSION)); } public PostMeasuresResult postMeasures(Measures measures) { PostMeasuresResult result = new PostMeasuresResult(); if (measures.isEmpty()) { return result; } Future<List<PostResult>> sdFuture = null; final Measures sdMeasures = measures.toSD(); Measures mdMeasures = measures.toMD(); if (!sdMeasures.isEmpty()) { sdFuture = postMeasures("/v1/metrics", sdMeasures, sdResponseConverter, new IBuildsPayload() { @Override public byte[] build(Measures measures) { return buildSDPayload(measures); } }); } Future<List<PostResult>> mdFuture = null; if (!mdMeasures.isEmpty()) { mdFuture = postMeasures("/v1/measurements", mdMeasures, mdResponseConverter, new IBuildsPayload() { @Override public byte[] build(Measures measures) { return buildMDPayload(measures); } }); } if (sdFuture != null) { result.results.addAll(Futures.get(sdFuture)); } if (mdFuture != null) { result.results.addAll(Futures.get(mdFuture)); } return result; } private Future<List<PostResult>> postMeasures(final String uri, final Measures measures, final IResponseConverter responseConverter, final IBuildsPayload payloadBuilder) { return executor.submit(new Callable<List<PostResult>>() { @Override public List<PostResult> call() throws Exception { List<PostResult> results = new LinkedList<PostResult>(); for (Measures batch : measures.partition(LibratoClient.this.batchSize)) { byte[] payload = payloadBuilder.build(batch); try { HttpResponse response = poster.post(fullUrl(uri), connectTimeout, readTimeout, measurementPostHeaders, payload); results.add(responseConverter.convert(payload, response)); } catch (Exception e) { results.add(responseConverter.convert(payload, e)); } } return results; } }); } private byte[] buildSDPayload(Measures measures) { final Map<String, Object> payload = new HashMap<String, Object>(); Maps.putIfNotNull(payload, "measure_time", measures.getEpoch()); Maps.putIfNotNull(payload, "source", Sanitizer.LAST_PASS.apply(measures.getSource())); Maps.putIfNotNull(payload, "period", measures.getPeriod()); List<Map<String, Object>> gauges = new LinkedList<Map<String, Object>>(); List<Map<String, Object>> counters = new LinkedList<Map<String, Object>>(); for (IMeasure measure : measures.getMeasures()) { Map<String, Object> measureMap = measure.toMap(); if (measure.isGauge()) { gauges.add(measureMap); } else { counters.add(measureMap); } } Maps.putIfNotEmpty(payload, "counters", counters); Maps.putIfNotEmpty(payload, "gauges", gauges); return Json.serialize(payload); } private byte[] buildMDPayload(Measures measures) { final Map<String, Object> payload = new HashMap<String, Object>(); Maps.putIfNotNull(payload, "time", measures.getEpoch()); Maps.putIfNotNull(payload, "period", measures.getPeriod()); if (!measures.getTags().isEmpty()) { payload.put("tags", Tags.toMap(measures.getTags())); } List<Map<String, Object>> gauges = new LinkedList<Map<String, Object>>(); for (IMeasure measure : measures.getMeasures()) { Map<String, Object> measureMap = measure.toMap(); gauges.add(measureMap); } Maps.putIfNotEmpty(payload, "measurements", gauges); return Json.serialize(payload); } private String fullUrl(String path) { return this.uri.toString() + path; } }