package pro.panopticon.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import pro.panopticon.client.model.ComponentInfo;
import pro.panopticon.client.model.Measurement;
import pro.panopticon.client.model.Status;
import pro.panopticon.client.sensor.Sensor;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static java.util.stream.Collectors.toList;

public class PanopticonClient {

    private final Logger LOG = LoggerFactory.getLogger(this.getClass());

    private static final int TIMEOUT = 10_000;

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1);

    private final String baseUri;
    private CloseableHttpClient client;

    public PanopticonClient(String baseUri) {
        this.baseUri = baseUri;
        client = createHttpClient();
    }

    private CloseableHttpClient createHttpClient() {
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(TIMEOUT)
                .setConnectTimeout(TIMEOUT)
                .setConnectionRequestTimeout(TIMEOUT)
                .build();

        return HttpClientBuilder.create()
                .setDefaultRequestConfig(requestConfig)
                .build();
    }

    public boolean update(Status status) {
        try {
            String json = OBJECT_MAPPER.writeValueAsString(status);
            String uri = baseUri + "/external/status";

            LOG.debug("Updating status: " + uri);
            LOG.debug("...with JSON: " + json);

            BasicHttpEntity entity = new BasicHttpEntity();
            entity.setContent(new ByteArrayInputStream(json.getBytes()));

            HttpPost httpPost = new HttpPost(uri);
            httpPost.setEntity(entity);
            httpPost.setHeader("Content-Type", "application/json");

            try (CloseableHttpResponse response = client.execute(httpPost)) {
                LOG.debug("Response: " + response.getStatusLine().getStatusCode());
                return response.getStatusLine().getStatusCode() < 300;
            }
        } catch (IOException e) {
            LOG.warn("Error when updating status", e);
            return false;
        }
    }

    public void startScheduledStatusUpdate(ComponentInfo componentInfo, List<Sensor> sensors) {
        Runnable runnable = () -> {
            long before = System.currentTimeMillis();
            List<Measurement> measurements = sensors.parallelStream()
                    .map(Sensor::measure)
                    .flatMap(List::stream)
                    .collect(toList());
            long afterMeasurements = System.currentTimeMillis();
            boolean success = update(new Status(componentInfo, measurements));
            long afterStatusPost = System.currentTimeMillis();

            long measurementTime = afterMeasurements - before;
            long statuspostTime = afterStatusPost - afterMeasurements;

            if (success) {
                LOG.info("Sent status update with " + measurements.size() + " measurements. Fetch measurements took " + measurementTime + "ms. Posting status took " + statuspostTime + "ms.");
            } else {
                LOG.warn("Could not update status");
            }
        };
        SCHEDULER.scheduleWithFixedDelay(runnable, 0, 1, TimeUnit.MINUTES);
    }
}

@@ -81,193 +81,8 @@
 er;%0A
-import pro.panopticon.client.model.ComponentInfo;%0Aimport pro.panopticon.client.model.Measurement;%0Aimport pro.panopticon.client.model.Status;%0Aimport pro.panopticon.client.sensor.Sensor;%0A
 impo
@@ -451,16 +451,201 @@
 Factory;
+%0Aimport pro.panopticon.client.model.ComponentInfo;%0Aimport pro.panopticon.client.model.Measurement;%0Aimport pro.panopticon.client.model.Status;%0Aimport pro.panopticon.client.sensor.Sensor;
 %0A%0Aimport
@@ -703,16 +703,44 @@
 eption;%0A
+import java.util.ArrayList;%0A
 import j
@@ -2962,16 +2962,38 @@
 () -%3E %7B%0A
+            try %7B%0A    
         
@@ -3030,32 +3030,36 @@
 ntTimeMillis();%0A
+    
             List
@@ -3136,30 +3136,409 @@
     
+    
 .map(
-Sensor::measure)%0A
+(sensor) -%3E %7B%0A                            try %7B%0A                                return sensor.measure();%0A                            %7D catch (Exception e) %7B%0A                                LOG.warn(%22Got error running sensor: %22 + sensor.getClass().getName(), e);%0A                                return new ArrayList%3CMeasurement%3E();%0A                            %7D%0A                        %7D)%0A    
     
@@ -3596,16 +3596,20 @@
         
+    
 .collect
@@ -3624,32 +3624,36 @@
 ));%0A            
+   
+ 
 long afterMeasur
@@ -3681,32 +3681,36 @@
 ntTimeMillis();%0A
+    
             bool
@@ -3768,24 +3768,28 @@
 urements));%0A
+    
             
@@ -3848,24 +3848,28 @@
             
+    
 long measure
@@ -3907,16 +3907,20 @@
 before;%0A
+    
         
@@ -3995,16 +3995,20 @@
         
+    
 if (succ
@@ -4010,24 +4010,28 @@
 (success) %7B%0A
+    
             
@@ -4222,16 +4222,20 @@
         
+  
+  
 %7D else %7B
@@ -4235,16 +4235,20 @@
  else %7B%0A
+    
         
@@ -4288,24 +4288,165 @@
 e status%22);%0A
+                %7D%0A            %7D catch (Exception e) %7B%0A                LOG.warn(%22Got error when measuring sensors to send to panopticon%22, e);%0A
             

Make panopticon client more resilient to exceptions in the sensors, so that the scheduled job doesn't crash