diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..859e6f7
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..34e256f
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..eb5f63d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..e96534f
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..288b36b
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a5e3363
--- /dev/null
+++ b/README.md
@@ -0,0 +1,130 @@
+
+# About
+
+This project is supporting to fetch Cumulocity Measurements in batches/chunks.
+
+It is accepting as input:
+* Cumulocity tenant and -authentication information (baseUrl, user, pass)
+* A date time span (dateFrom, dateTo)
+* A Cumulocity device (optional)
+* The maximum limit for measurements per chunk
+
+...and has as output a list of chunks:
+* covering the entire input time span
+* being sorted
+* with non-colliding time spans (thus no duplicates)
+* with each chunk having less than the allowed measurements per chunk
+
+# Concept
+
+The implementation is based on two concepts:
+
+1) Use the ˙/measurement/measurements?pageSize=1&withTotalPages=true˚ endpoint parameters to count the number of measurements for a certain time span **before** actually fetching them
+2) Divide the time span recursively as long as the "measurement per time span" are fitting in the allowed chunk-size (input parameter). A binary tree is used as data structure:
+
+![Binary Tree Sample](/resources/imgs/binaryTree_sample.png)
+
+# Sample Output
+
+Output using a chunk size o 100,00 on a measurement collection of 611,210 Measurements:
+```
+*** Measurement Chunk result set ***
+Configuration:
+------------------------------------------------------------
+dateFrom: 2022-03-21T20:00:00.000Z
+dateTo: 2022-03-25T20:00:00.000Z
+deviceId: "407401764"
+chunkSize: 100,000
+
+Runtime:
+------------------------------------------------------------
+start: 2022-03-27T13:02:57.729Z
+end: 2022-03-27T13:03:00.487Z
+duration: 2,758 ms
+
+Results Overview:
+------------------------------------------------------------
+Count Measurements (total): 611,210
+Count Chunks (total): 8
+Count Chunks (total, no null): 8
+Count exec. data splits (total): 7
+Chunk size (max): 76,719
+Chunk size (min, no null): 76,070
+
+Chunks:
+dateFrom dateTo countElements
+----------------------------------- ----------------------------------- --------------------
+2022-03-21T20:00:00.000Z 2022-03-22T08:00:00.000Z 76,113
+2022-03-22T08:00:00.001Z 2022-03-22T20:00:00.000Z 76,070
+2022-03-22T20:00:00.001Z 2022-03-23T08:00:00.000Z 76,505
+2022-03-23T08:00:00.001Z 2022-03-23T20:00:00.000Z 76,398
+2022-03-23T20:00:00.001Z 2022-03-24T08:00:00.000Z 76,398
+2022-03-24T08:00:00.001Z 2022-03-24T20:00:00.000Z 76,398
+2022-03-24T20:00:00.001Z 2022-03-25T08:00:00.000Z 76,719
+2022-03-25T08:00:00.001Z 2022-03-25T20:00:00.000Z 76,609
+```
+Note that the chunks seems ~ equally distributed. This is due to the device sending data in a fairly constant frequency.
+
+
+Output using a chunk size o 1000 on a measurement collection of 11.223 Measurements:
+
+```
+*** Measurement Chunk result set ***
+Configuration:
+------------------------------------------------------------
+dateFrom: 2021-01-01T00:00:00.000Z
+dateTo: 2022-04-01T00:00:00.000Z
+deviceId: "100200"
+chunkSize: 1,000
+
+Runtime:
+------------------------------------------------------------
+start: 2022-03-27T13:01:56.754Z
+end: 2022-03-27T13:02:01.435Z
+duration: 4,681 ms
+
+Results Overview:
+------------------------------------------------------------
+Count Measurements (total): 11,223
+Count Chunks (total): 31
+Count Chunks (total, no null): 15
+Count exec. data splits (total): 30
+Chunk size (max): 947
+Chunk size (min, no null): 3
+
+Chunks:
+dateFrom dateTo countElements
+----------------------------------- ----------------------------------- --------------------
+2021-01-01T00:00:00.000Z 2021-08-16T12:00:00.000Z 0
+2021-08-16T12:00:00.001Z 2021-12-08T06:00:00.000Z 0
+2021-12-08T06:00:00.001Z 2022-02-03T03:00:00.000Z 0
+2022-02-03T03:00:00.001Z 2022-03-03T13:30:00.000Z 0
+2022-03-03T13:30:00.001Z 2022-03-17T18:45:00.000Z 3
+2022-03-17T18:45:00.001Z 2022-03-24T21:22:30.000Z 0
+2022-03-24T21:22:30.001Z 2022-03-25T18:42:11.250Z 0
+2022-03-25T18:42:11.251Z 2022-03-26T05:22:01.875Z 0
+2022-03-26T05:22:01.876Z 2022-03-26T10:41:57.188Z 0
+2022-03-26T10:41:57.189Z 2022-03-26T13:21:54.844Z 502
+2022-03-26T13:21:54.845Z 2022-03-26T14:41:53.672Z 0
+2022-03-26T14:41:53.673Z 2022-03-26T14:51:53.526Z 0
+2022-03-26T14:51:53.527Z 2022-03-26T14:56:53.453Z 0
+2022-03-26T14:56:53.454Z 2022-03-26T14:57:30.944Z 0
+2022-03-26T14:57:30.945Z 2022-03-26T14:57:49.690Z 192
+2022-03-26T14:57:49.691Z 2022-03-26T14:58:08.435Z 945
+2022-03-26T14:58:08.436Z 2022-03-26T14:58:27.181Z 943
+2022-03-26T14:58:27.182Z 2022-03-26T14:58:45.926Z 946
+2022-03-26T14:58:45.927Z 2022-03-26T14:59:04.671Z 938
+2022-03-26T14:59:04.672Z 2022-03-26T14:59:23.416Z 938
+2022-03-26T14:59:23.417Z 2022-03-26T14:59:42.162Z 947
+2022-03-26T14:59:42.163Z 2022-03-26T15:00:00.907Z 931
+2022-03-26T15:00:00.908Z 2022-03-26T15:00:19.653Z 933
+2022-03-26T15:00:19.654Z 2022-03-26T15:00:38.398Z 944
+2022-03-26T15:00:38.399Z 2022-03-26T15:00:57.144Z 947
+2022-03-26T15:00:57.145Z 2022-03-26T15:01:15.889Z 396
+2022-03-26T15:01:15.890Z 2022-03-26T15:01:53.379Z 0
+2022-03-26T15:01:53.380Z 2022-03-26T15:21:53.086Z 0
+2022-03-26T15:21:53.087Z 2022-03-26T16:01:52.500Z 0
+2022-03-26T16:01:52.501Z 2022-03-28T10:41:15.000Z 718
+2022-03-28T10:41:15.001Z 2022-04-01T00:00:00.000Z 0
+```
+Above sample device is sending less constant data -> that's why there are chunks with 0 elements existing. It's configurable to skip chunks of size 0 automatically.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..58e7fc1
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+
+ org.example
+ measurement-collector
+ 1.0-SNAPSHOT
+
+
+ 14
+ 14
+
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.12.0
+
+
+ org.json
+ json
+ 20190722
+
+
+ org.javatuples
+ javatuples
+ 1.2
+
+
+ org.projectlombok
+ lombok
+ 1.18.20
+
+
+
+
\ No newline at end of file
diff --git a/resources/imgs/binaryTree_sample.png b/resources/imgs/binaryTree_sample.png
new file mode 100644
index 0000000..b758bb1
Binary files /dev/null and b/resources/imgs/binaryTree_sample.png differ
diff --git a/src/main/java/collector/C8yHttpClient.java b/src/main/java/collector/C8yHttpClient.java
new file mode 100644
index 0000000..7089622
--- /dev/null
+++ b/src/main/java/collector/C8yHttpClient.java
@@ -0,0 +1,66 @@
+package collector;
+
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.Base64;
+import java.util.Optional;
+import org.json.*;
+
+public class C8yHttpClient {
+
+ private final String baseUrl;
+ private final String base64Auth;
+
+ public C8yHttpClient(String baseUrl, String user, String pass) {
+ this.baseUrl = baseUrl;
+ this.base64Auth = basicAuth(user, pass);
+ }
+
+
+ public Optional fetchNumberOfMeasurements(String dateFrom, String dateTo, String oid) {
+ URI uri = URI.create(String.format("%s/measurement/measurements?pageSize=1&withTotalPages=true%s%s%s",
+ baseUrl,
+ StringUtils.isNoneEmpty(dateFrom) ? "&dateFrom=" + dateFrom : "",
+ StringUtils.isNoneEmpty(dateTo) ? "&dateTo=" + dateTo : "",
+ StringUtils.isNoneEmpty(oid) ? "&source=" + oid : ""));
+
+ try {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(uri)
+ .header("Authorization", base64Auth)
+ .method("GET", HttpRequest.BodyPublishers.noBody())
+ .build();
+ HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
+
+ if(response == null || response.statusCode() != 200 || StringUtils.isEmpty(response.body())){
+ return Optional.empty();
+ }
+
+ Integer totalPages = getTotalNumberOfPages(response.body());
+ return Optional.of(totalPages);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return Optional.empty();
+ }
+
+ private Integer getTotalNumberOfPages(String responseBody) {
+ JSONObject obj = new JSONObject(responseBody);
+ Number number = obj.getJSONObject("statistics").getNumber("totalPages");
+ int res = number.intValue();
+ return res;
+ }
+
+ private String basicAuth(String username, String password) {
+ return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
+ }
+
+}
diff --git a/src/main/java/collector/ChunkCollector.java b/src/main/java/collector/ChunkCollector.java
new file mode 100644
index 0000000..f7b7e50
--- /dev/null
+++ b/src/main/java/collector/ChunkCollector.java
@@ -0,0 +1,58 @@
+package collector;
+
+import collector.recordset.ChunkResultSet;
+import collector.recordset.MeasurementChunkDescription;
+import collector.util.DateUtil;
+import collector.util.TimeSpan;
+import org.javatuples.Pair;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public class ChunkCollector {
+
+ private final C8yHttpClient client;
+
+ public ChunkCollector(C8yHttpClient client) {
+ this.client = client;
+ }
+
+ public ChunkResultSet collect(ChunkCollectorConfig collectorCfg) {
+ ChunkResultSet resultSet = new ChunkResultSet(collectorCfg);
+ resultSet.registerRunStart(Instant.now());
+
+ resultSet.addMeasurementChunkDescriptions(
+ collectChunks(collectorCfg.getDateFrom(), collectorCfg.getDateTo(), collectorCfg.getOid(), collectorCfg.getChunkSize(), resultSet)
+ );
+
+ resultSet.registerRunFinish(Instant.now());
+ return resultSet;
+ }
+
+ private List collectChunks(Instant dateFrom, Instant dateTo, String oid, int chunkSize, ChunkResultSet resultSet) {
+ List lst = new ArrayList<>();
+ Optional countElements = client.fetchNumberOfMeasurements(DateUtil.getFormattedUtcString(dateFrom), DateUtil.getFormattedUtcString(dateTo), oid);
+
+ if (countElements.isEmpty()) {
+ // if no measurements are found its '0', if its empty => exception (you might want to catch it already earlier...)
+ }
+
+ int ctElements = countElements.get();
+ MeasurementChunkDescription d = new MeasurementChunkDescription(new TimeSpan(dateFrom, dateTo), ctElements);
+
+ if (ctElements > chunkSize) {
+ Pair timeTuple = DateUtil.divideTimesByTwo(dateFrom, dateTo);
+ lst.addAll(collectChunks(timeTuple.getValue0().getDateFrom(), timeTuple.getValue0().getDateTo(), oid, chunkSize, resultSet));
+ lst.addAll(collectChunks(timeTuple.getValue1().getDateFrom(), timeTuple.getValue1().getDateTo(), oid, chunkSize, resultSet));
+ resultSet.registerDataSplitAction();
+ } else {
+ lst.add(d);
+ }
+
+ return lst;
+ }
+
+
+}
diff --git a/src/main/java/collector/ChunkCollectorConfig.java b/src/main/java/collector/ChunkCollectorConfig.java
new file mode 100644
index 0000000..758a666
--- /dev/null
+++ b/src/main/java/collector/ChunkCollectorConfig.java
@@ -0,0 +1,19 @@
+package collector;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.ToString;
+
+import java.time.Instant;
+
+@AllArgsConstructor
+@Getter
+@ToString
+@Builder
+public class ChunkCollectorConfig {
+ private Instant dateFrom;
+ private Instant dateTo;
+ private String oid;
+ private int chunkSize;
+}
diff --git a/src/main/java/collector/Main.java b/src/main/java/collector/Main.java
new file mode 100644
index 0000000..cd3cca8
--- /dev/null
+++ b/src/main/java/collector/Main.java
@@ -0,0 +1,56 @@
+package collector;
+
+import collector.recordset.ChunkResultSet;
+import collector.recordset.ConsoleChunkResultDescriptor;
+import collector.util.DateUtil;
+
+public class Main {
+
+ private final String baseUrl = "https://example.cumulocity.com";
+ private final String user = "t12345/my@user.de";
+ private final String pass = "mysecretpass";
+
+ private final String dateFrom = "2021-01-01T00:00:00.000Z";
+ private final String dateTo = "2022-04-01T00:00:00.0000Z";
+ private final String oid = "100200";
+
+ private final int chunkSize = 1000;
+
+ public static void main(String[] args) {
+ new Main().run();
+ }
+
+ public void run() {
+ C8yHttpClient client = new C8yHttpClient(baseUrl, user, pass);
+
+ /*
+ * Spin up a binary tree for all records:
+ * 1) Request the total amount of measurement records between time A and B
+ * 2) Divide the time span (always by two) until the amount of measurementRecords is <= max allowed chunk size
+ * => Result will be a list of time spans with no entry having more than the allowed number of measurements
+ *
+ * Note that only the number of measurements between certain timestamps requested, not the measurements themselves.
+ *
+ * */
+ ChunkCollector collector = new ChunkCollector(client);
+ ChunkResultSet resultSet = collector.collect(
+ ChunkCollectorConfig.builder()
+ .dateFrom(DateUtil.getDateTime(dateFrom))
+ .dateTo(DateUtil.getDateTime(dateTo))
+ .oid(oid)
+ .chunkSize(chunkSize)
+ .build()
+ );
+
+ ConsoleChunkResultDescriptor.instance().print(resultSet, true);
+
+ /*
+ * Run over all chunks, collect their (CSV-) Measurements and dump to File
+ * The chunks should be:
+ * a) already sorted ascending by time and
+ * b) have no time overlaps (thus should not have duplicates)
+ * */
+ new MeasurementProcessor().run(resultSet);
+ }
+
+}
diff --git a/src/main/java/collector/MeasurementProcessor.java b/src/main/java/collector/MeasurementProcessor.java
new file mode 100644
index 0000000..0cc003b
--- /dev/null
+++ b/src/main/java/collector/MeasurementProcessor.java
@@ -0,0 +1,29 @@
+package collector;
+
+import collector.recordset.ChunkResultSet;
+import collector.recordset.MeasurementChunkDescription;
+import collector.util.DateUtil;
+
+
+import java.util.List;
+
+public class MeasurementProcessor {
+
+ public void run(ChunkResultSet chunkResultSet) {
+
+ List chunks = chunkResultSet.getRecords();
+ for(MeasurementChunkDescription c : chunks){
+ if (c.getCountMeasurements() == null || c.getCountMeasurements() <= 0){
+ // skip empty chunk records
+ continue;
+ }
+
+ String dateFrom = DateUtil.getFormattedUtcString(c.getTimespan().getDateFrom());
+ String dateTo = DateUtil.getFormattedUtcString(c.getTimespan().getDateTo());
+ String deviceId = chunkResultSet.getCollectorCfg().getOid();
+
+ // TODO: request Measurements as CSV, append them in a file
+ }
+
+ }
+}
diff --git a/src/main/java/collector/recordset/ChunkResultSet.java b/src/main/java/collector/recordset/ChunkResultSet.java
new file mode 100644
index 0000000..560ec25
--- /dev/null
+++ b/src/main/java/collector/recordset/ChunkResultSet.java
@@ -0,0 +1,42 @@
+package collector.recordset;
+
+import collector.ChunkCollectorConfig;
+import collector.util.DateUtil;
+import collector.util.TimeSpan;
+import lombok.Getter;
+
+import java.text.DecimalFormat;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+
+@Getter
+public class ChunkResultSet {
+
+ private final ChunkCollectorConfig collectorCfg;
+ private final List records = new ArrayList<>();
+ private final TimeSpan runningTime = new TimeSpan();
+ private int ctDataSplits = 0;
+
+ public ChunkResultSet(ChunkCollectorConfig collectorCfg) {
+ this.collectorCfg = collectorCfg;
+ }
+
+ public void addMeasurementChunkDescriptions(List l) {
+ this.records.addAll(l);
+ }
+
+ public void registerRunStart(Instant startTime) {
+ this.runningTime.setDateFrom(startTime);
+ }
+
+ public void registerRunFinish(Instant finishTime) {
+ this.runningTime.setDateTo(finishTime);
+ }
+
+ public void registerDataSplitAction() {
+ this.ctDataSplits++;
+ }
+
+}
diff --git a/src/main/java/collector/recordset/ConsoleChunkResultDescriptor.java b/src/main/java/collector/recordset/ConsoleChunkResultDescriptor.java
new file mode 100644
index 0000000..b5232c5
--- /dev/null
+++ b/src/main/java/collector/recordset/ConsoleChunkResultDescriptor.java
@@ -0,0 +1,103 @@
+package collector.recordset;
+
+import collector.util.DateUtil;
+
+import java.text.DecimalFormat;
+import java.time.temporal.ChronoUnit;
+
+
+public class ConsoleChunkResultDescriptor {
+
+ private static final ConsoleChunkResultDescriptor instance = new ConsoleChunkResultDescriptor();
+
+ public static ConsoleChunkResultDescriptor instance(){
+ return instance;
+ }
+
+ public void print(ChunkResultSet resultSet, boolean includeChunks) {
+ System.out.println(describe(resultSet, includeChunks));
+ }
+
+ public String describe (ChunkResultSet resultSet, boolean includeChunks) {
+ StringBuilder sb = new StringBuilder();
+ DecimalFormat formatter = new DecimalFormat("#,###");
+
+ sb.append(System.lineSeparator());
+ sb.append("*** Measurement Chunk result set ***").append(System.lineSeparator());
+
+ // ### Input Config ###
+ sb.append("Configuration:").append(System.lineSeparator());
+ sb.append("-".repeat(60)).append(System.lineSeparator());
+ sb.append(String.format("%-15s %s" + System.lineSeparator(), "dateFrom:", DateUtil.getFormattedUtcString(resultSet.getCollectorCfg().getDateFrom())));
+ sb.append(String.format("%-15s %s" + System.lineSeparator(), "dateTo:", DateUtil.getFormattedUtcString(resultSet.getCollectorCfg().getDateTo())));
+ sb.append(String.format("%-15s \"%s\"" + System.lineSeparator(), "deviceId:", resultSet.getCollectorCfg().getOid()));
+ sb.append(String.format("%-15s %s" + System.lineSeparator(), "chunkSize:", formatter.format(resultSet.getCollectorCfg().getChunkSize())));
+
+ // ### Runtime infos ###
+ sb.append(System.lineSeparator());
+ sb.append("Runtime:").append(System.lineSeparator());
+ sb.append("-".repeat(60)).append(System.lineSeparator());
+ sb.append(String.format("%-15s %s" + System.lineSeparator(), "start:", DateUtil.getFormattedUtcString(resultSet.getRunningTime().getDateFrom())));
+ sb.append(String.format("%-15s %s" + System.lineSeparator(), "end:", DateUtil.getFormattedUtcString(resultSet.getRunningTime().getDateTo())));
+ sb.append(String.format("%-15s %s ms" + System.lineSeparator(), "duration:", formatter.format(resultSet.getRunningTime().between(ChronoUnit.MILLIS))));
+
+ // ### Results Overview ###
+ sb.append(System.lineSeparator());
+ sb.append("Results Overview:").append(System.lineSeparator());
+ sb.append("-".repeat(60)).append(System.lineSeparator());
+ sb.append(String.format("%-35s %s" + System.lineSeparator(), "Count Measurements (total):",
+ formatter.format(
+ resultSet.getRecords().stream()
+ .map(MeasurementChunkDescription::getCountMeasurements)
+ .reduce(Integer::sum)
+ .orElse(null))
+ )
+ );
+ sb.append(String.format("%-35s %s" + System.lineSeparator(), "Count Chunks (total):", formatter.format(resultSet.getRecords().size())));
+ sb.append(String.format("%-35s %s" + System.lineSeparator(), "Count Chunks (total, no null):",
+ formatter.format(
+ resultSet.getRecords().stream()
+ .map(MeasurementChunkDescription::getCountMeasurements)
+ .filter(l -> l > 0)
+ .count())
+ )
+ );
+ sb.append(String.format("%-35s %s" + System.lineSeparator(), "Count exec. data splits (total):", resultSet.getCtDataSplits()));
+ sb.append(String.format("%-35s %s" + System.lineSeparator(), "Chunk size (max):",
+ formatter.format(
+ resultSet.getRecords().stream()
+ .map(MeasurementChunkDescription::getCountMeasurements)
+ .reduce(Integer::max)
+ .orElse(null))
+ )
+ );
+ sb.append(String.format("%-35s %s" + System.lineSeparator(), "Chunk size (min, no null):",
+ formatter.format(
+ resultSet.getRecords().stream()
+ .map(MeasurementChunkDescription::getCountMeasurements)
+ .filter(l -> l > 0)
+ .reduce(Integer::min)
+ .orElse(null))
+ )
+ );
+
+ // ### Chunk Details ###
+ if (includeChunks) {
+ sb.append(System.lineSeparator());
+ sb.append("Chunks:").append(System.lineSeparator());
+ sb.append(String.format("%-35s %-35s %-20s" + System.lineSeparator(), "dateFrom", "dateTo", "countElements"));
+ sb.append(String.format("%-35s %-35s %-20s" + System.lineSeparator(), "-".repeat(35), "-".repeat(35), "-".repeat(20)));
+ resultSet.getRecords().forEach(
+ d -> sb.append(
+ String.format("%-35s %-35s %20s" + System.lineSeparator(),
+ DateUtil.getFormattedUtcString(d.getTimespan().getDateFrom()),
+ DateUtil.getFormattedUtcString(d.getTimespan().getDateTo()),
+ formatter.format(d.getCountMeasurements())
+ )
+ )
+ );
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/collector/recordset/MeasurementChunkDescription.java b/src/main/java/collector/recordset/MeasurementChunkDescription.java
new file mode 100644
index 0000000..6de06e7
--- /dev/null
+++ b/src/main/java/collector/recordset/MeasurementChunkDescription.java
@@ -0,0 +1,14 @@
+package collector.recordset;
+
+import collector.util.TimeSpan;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.ToString;
+
+@ToString
+@AllArgsConstructor
+@Getter
+public class MeasurementChunkDescription {
+ private final TimeSpan timespan;
+ private final Integer countMeasurements;
+}
diff --git a/src/main/java/collector/util/DateUtil.java b/src/main/java/collector/util/DateUtil.java
new file mode 100644
index 0000000..2590167
--- /dev/null
+++ b/src/main/java/collector/util/DateUtil.java
@@ -0,0 +1,54 @@
+package collector.util;
+
+import org.javatuples.Pair;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+
+public class DateUtil {
+
+ public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter
+ .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+
+
+ public static Instant getDateTime(String utcString){
+ return Instant.parse(utcString);
+ }
+
+// public static long getTimeMillis(String utcString){
+// return Instant.parse(utcString).toEpochMilli();
+// }
+//
+// public static Instant getDateTime(long millis){
+// return Instant.ofEpochMilli(millis);
+// }
+//
+// public static String getFormattedUtcString(long millis){
+// return getFormattedUtcString(Instant.ofEpochMilli(millis));
+// }
+
+ public static String getFormattedUtcString(Instant date){
+ ZonedDateTime utc = date.atZone(ZoneOffset.UTC);
+ String res = utc.format(DATE_TIME_FORMATTER);
+ return res;
+ }
+
+ public static Pair divideTimesByTwo(String utcDateFrom, String utcDateTo){
+ Instant instDateFrom = DateUtil.getDateTime(utcDateFrom);
+ Instant instDateTo = DateUtil.getDateTime(utcDateTo);
+
+ return divideTimesByTwo(instDateFrom, instDateTo);
+ }
+
+ public static Pair divideTimesByTwo(Instant dateFrom, Instant dateTo){
+ Instant dateALower = dateFrom;
+ Instant dateAUpper = dateFrom.plusMillis(ChronoUnit.MILLIS.between(dateFrom, dateTo) / 2);
+ Instant dateBLower = dateAUpper.plusMillis(1);
+ Instant dateBUpper = dateTo;
+ Pair res = Pair.with(new TimeSpan(dateALower, dateAUpper), new TimeSpan(dateBLower, dateBUpper));
+ return res;
+ }
+}
diff --git a/src/main/java/collector/util/TimeSpan.java b/src/main/java/collector/util/TimeSpan.java
new file mode 100644
index 0000000..a7d3fae
--- /dev/null
+++ b/src/main/java/collector/util/TimeSpan.java
@@ -0,0 +1,47 @@
+package collector.util;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+public class TimeSpan {
+
+ private Instant dateFrom;
+ private Instant dateTo;
+
+ public TimeSpan(){
+ }
+
+ public TimeSpan(Instant dateFrom, Instant dateTo){
+ this.dateFrom = dateFrom;
+ this.dateTo = dateTo;
+ }
+
+ public Instant getDateFrom() {
+ return dateFrom;
+ }
+
+ public Instant getDateTo() {
+ return dateTo;
+ }
+
+ public void setDateFrom(Instant dateFrom) {
+ this.dateFrom = dateFrom;
+ }
+
+ public void setDateTo(Instant dateTo) {
+ this.dateTo = dateTo;
+ }
+
+ public long between(ChronoUnit cu){
+ return cu.between(dateFrom, dateTo);
+ }
+
+ @Override
+ public String toString() {
+ return "Timespan{" +
+ "dateFrom=" + dateFrom +
+ ", dateTo=" + dateTo +
+ '}';
+ }
+
+}
diff --git a/target/classes/collector/C8yHttpClient.class b/target/classes/collector/C8yHttpClient.class
new file mode 100644
index 0000000..c1afd92
Binary files /dev/null and b/target/classes/collector/C8yHttpClient.class differ
diff --git a/target/classes/collector/ChunkCollector.class b/target/classes/collector/ChunkCollector.class
new file mode 100644
index 0000000..487b438
Binary files /dev/null and b/target/classes/collector/ChunkCollector.class differ
diff --git a/target/classes/collector/ChunkCollectorConfig$ChunkCollectorConfigBuilder.class b/target/classes/collector/ChunkCollectorConfig$ChunkCollectorConfigBuilder.class
new file mode 100644
index 0000000..49c5457
Binary files /dev/null and b/target/classes/collector/ChunkCollectorConfig$ChunkCollectorConfigBuilder.class differ
diff --git a/target/classes/collector/ChunkCollectorConfig.class b/target/classes/collector/ChunkCollectorConfig.class
new file mode 100644
index 0000000..f74a669
Binary files /dev/null and b/target/classes/collector/ChunkCollectorConfig.class differ
diff --git a/target/classes/collector/Main.class b/target/classes/collector/Main.class
new file mode 100644
index 0000000..6e9857d
Binary files /dev/null and b/target/classes/collector/Main.class differ
diff --git a/target/classes/collector/MeasurementProcessor.class b/target/classes/collector/MeasurementProcessor.class
new file mode 100644
index 0000000..f4bff24
Binary files /dev/null and b/target/classes/collector/MeasurementProcessor.class differ
diff --git a/target/classes/collector/recordset/ChunkResultSet.class b/target/classes/collector/recordset/ChunkResultSet.class
new file mode 100644
index 0000000..44f2185
Binary files /dev/null and b/target/classes/collector/recordset/ChunkResultSet.class differ
diff --git a/target/classes/collector/recordset/ConsoleChunkResultDescriptor.class b/target/classes/collector/recordset/ConsoleChunkResultDescriptor.class
new file mode 100644
index 0000000..dbb918d
Binary files /dev/null and b/target/classes/collector/recordset/ConsoleChunkResultDescriptor.class differ
diff --git a/target/classes/collector/recordset/MeasurementChunkDescription.class b/target/classes/collector/recordset/MeasurementChunkDescription.class
new file mode 100644
index 0000000..2ef87c6
Binary files /dev/null and b/target/classes/collector/recordset/MeasurementChunkDescription.class differ
diff --git a/target/classes/collector/util/DateUtil.class b/target/classes/collector/util/DateUtil.class
new file mode 100644
index 0000000..d772a6b
Binary files /dev/null and b/target/classes/collector/util/DateUtil.class differ
diff --git a/target/classes/collector/util/TimeSpan.class b/target/classes/collector/util/TimeSpan.class
new file mode 100644
index 0000000..b5058ac
Binary files /dev/null and b/target/classes/collector/util/TimeSpan.class differ
diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties
new file mode 100644
index 0000000..fe8242d
--- /dev/null
+++ b/target/maven-archiver/pom.properties
@@ -0,0 +1,5 @@
+#Generated by Maven
+#Sat Mar 26 17:50:15 CET 2022
+groupId=org.example
+artifactId=measurement-collector
+version=1.0-SNAPSHOT
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
new file mode 100644
index 0000000..d87efcd
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -0,0 +1,11 @@
+collector/recordset/MeasurementChunkDescription.class
+collector/C8yHttpClient.class
+collector/util/DateUtil.class
+collector/MeasurementProcessor.class
+collector/ChunkCollector.class
+collector/util/Tuple.class
+collector/Main.class
+collector/recordset/ChunkResultSet.class
+collector/util/StringUtil.class
+collector/CollectorConfig.class
+collector/util/MyTimespan.class
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
new file mode 100644
index 0000000..c57423d
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -0,0 +1,11 @@
+/Users/kobu/dev/measurement-collector/src/main/java/collector/MeasurementProcessor.java
+/Users/kobu/dev/measurement-collector/src/main/java/collector/ChunkCollector.java
+/Users/kobu/dev/measurement-collector/src/main/java/collector/util/StringUtil.java
+/Users/kobu/dev/measurement-collector/src/main/java/collector/CollectorConfig.java
+/Users/kobu/dev/measurement-collector/src/main/java/collector/C8yHttpClient.java
+/Users/kobu/dev/measurement-collector/src/main/java/collector/util/MyTimespan.java
+/Users/kobu/dev/measurement-collector/src/main/java/collector/util/DateUtil.java
+/Users/kobu/dev/measurement-collector/src/main/java/collector/Main.java
+/Users/kobu/dev/measurement-collector/src/main/java/collector/recordset/ChunkResultSet.java
+/Users/kobu/dev/measurement-collector/src/main/java/collector/util/Tuple.java
+/Users/kobu/dev/measurement-collector/src/main/java/collector/recordset/MeasurementChunkDescription.java
diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
new file mode 100644
index 0000000..e69de29
diff --git a/target/measurement-collector-1.0-SNAPSHOT.jar b/target/measurement-collector-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000..e2fc521
Binary files /dev/null and b/target/measurement-collector-1.0-SNAPSHOT.jar differ